<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
 <title>Bèr ‘berkes’ Kessels</title>
 <link href="https://berk.es/atom.xml" rel="self"/>
 <link href="https://berk.es/"/>
 <updated>2026-11-09T15:16:11Z</updated>
 <id>https://berk.es/</id>
 <author>
   <name>Bèr ‘berkes’ Kessels</name>
   <email>ber@berk.es</email>
 </author>

 
 <entry>
   <title>Ignore Rust&amp;#39;s target build directories in Deja-Dup</title>
   <link href="https://berk.es/2024/08/10/ignore-rust-target-build-directories-in-deja-dup/"/>
   <updated>2024-08-10T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2024/08/10/ignore-rust-target-build-directories-in-deja-dup/</id>
   <content type="html">&amp;lt;p&amp;gt;I use Deja-Dup, to backup my Ubuntu machines. And just like &amp;lt;a href=&amp;#34;https://medium.com/@shimo164/ignore-node-modules-directories-in-deja-dup-433997fd2461&amp;#34;&amp;gt;shimo describes in this blog post&amp;lt;/a&amp;gt;, I too want to avoid backing up gigabytes of &amp;lt;code&amp;gt;node_module&amp;lt;/code&amp;gt; directories&amp;lt;sup id=&amp;#34;fnref:1&amp;#34;&amp;gt;&amp;lt;a href=&amp;#34;#fn:1&amp;#34; class=&amp;#34;footnote-ref&amp;#34; role=&amp;#34;doc-noteref&amp;#34;&amp;gt;1&amp;lt;/a&amp;gt;&amp;lt;/sup&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But I also want to avoid backup up gigabytes of &amp;lt;code&amp;gt;target&amp;lt;/code&amp;gt; directories from&amp;#xA;building Rust projects with Cargo. I have accumulated some 50&#43; Rust codebases&amp;#xA;scattered throughout my project directories. Their combined &amp;lt;code&amp;gt;target&amp;lt;/code&amp;gt;&amp;#xA;directories take up about 9GB at time of writing. And all of that is&amp;#xA;reproducible, cache-like data. It doesn&amp;#39;t need to be backed up.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So, I adapted &amp;lt;em&amp;gt;Shimo&amp;#39;s&amp;lt;/em&amp;gt; crontab to add &amp;lt;code&amp;gt;.deja-dup-ignore&amp;lt;/code&amp;gt; to all:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;All directories named target&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Where the parent directory has a &amp;lt;code&amp;gt;Cargo.toml&amp;lt;/code&amp;gt; file&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In a crontab:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre tabindex=&amp;#34;0&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-cron&amp;#34; data-lang=&amp;#34;cron&amp;#34;&amp;gt;05 10    *   *   *   find ~/ -type d -name target -exec bash -c &amp;amp;#39;if [ -f &amp;amp;#34;$(dirname {})/Cargo.toml&amp;amp;#34; ]; then touch &amp;amp;#34;{}/.deja-dup-ignore&amp;amp;#34;; fi&amp;amp;#39; \; 2&amp;amp;gt;&amp;amp;amp;1&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;p&amp;gt;The command runs find, which will execute a bash command on every directory&amp;#xA;called &amp;lt;code&amp;gt;target&amp;lt;/code&amp;gt;. This bash command then gets for the parent dir - &amp;lt;code&amp;gt;$(dirname {})&amp;lt;/code&amp;gt; - of this &amp;lt;code&amp;gt;target&amp;lt;/code&amp;gt; directory. It will then check if this parent dir has a&amp;#xA;Cargo.toml file - &amp;lt;code&amp;gt;if [ -f &amp;amp;quot;$(dirname {})/Cargo.toml&amp;amp;quot; ];&amp;lt;/code&amp;gt; and if so, add a&amp;#xA;.deja-dup-ignore ignore file in this target dir.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I don&amp;#39;t just want to ignore any &amp;lt;code&amp;gt;target&amp;lt;/code&amp;gt; directory. I have at&amp;#xA;least one legit directory named &amp;lt;code&amp;gt;target&amp;lt;/code&amp;gt; (containing a business target). There&amp;#xA;will be more, there probably are already. Hence the added check for the Cargo.toml file.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This speeds up my backup from over an hour a week, to under 20 minutes a week.&amp;#xA;And it saves me some 20GB in backup space. Almost a quarter of the space of my&amp;#xA;incremental backups were &amp;lt;code&amp;gt;node_modules&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;target&amp;lt;/code&amp;gt; directories.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;footnotes&amp;#34; role=&amp;#34;doc-endnotes&amp;#34;&amp;gt;&amp;#xA;&amp;lt;hr&amp;gt;&amp;#xA;&amp;lt;ol&amp;gt;&amp;#xA;&amp;lt;li id=&amp;#34;fn:1&amp;#34;&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Unfortunately a feature request to add some pattern ignore system to Deja-Dup &amp;lt;a href=&amp;#34;https://gitlab.gnome.org/World/deja-dup/-/issues/112&amp;#34;&amp;gt;has been ignored for years&amp;lt;/a&amp;gt;. Personally, I&amp;#39;d love Deja-Dup to ignore any patterns found in any .gitignore file it encounters. Same as ripgrep and fd and some more modern CLI tools do. For me that&amp;#39;d be the &amp;lt;em&amp;gt;opinionated simplicity&amp;lt;/em&amp;gt; that I seek in software. But alas.&amp;amp;#160;&amp;lt;a href=&amp;#34;#fnref:1&amp;#34; class=&amp;#34;footnote-backref&amp;#34; role=&amp;#34;doc-backlink&amp;#34;&amp;gt;&amp;amp;#x21a9;&amp;amp;#xfe0e;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ol&amp;gt;&amp;#xA;&amp;lt;/div&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>The Fediverse never Forgets</title>
   <link href="https://berk.es/2022/12/23/fediverse-never-forgets/"/>
   <updated>2022-12-23T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2022/12/23/fediverse-never-forgets/</id>
   <content type="html">&amp;lt;p&amp;gt;Deleting content, reliably, from a decentralised network is just not possible.&amp;#xA;The fediverse (Mastodon, Pleroma, PixelFed, etc.) in theory, never forgets. The&amp;#xA;internet never forgets either.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When I helped design a decentralized application[1], and later again an&amp;#xA;Event-Sourced setup, we applied cryptography to solve this: encrypt all data that&amp;#xA;should be deletable, then throw away the key(s) to &amp;amp;quot;delete&amp;amp;quot; the data.&amp;#xA;ActivityPub relies very little on cryptography and certainly not as a way to&amp;#xA;&amp;amp;quot;delete&amp;amp;quot; stuff. The common solution to removing stuff from a distributed network,&amp;#xA;isn&amp;#39;t possible, on the Fediverse.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So, any content that has been published, should, in theory, be considered out&amp;#xA;of your hands: with no way to remove it. In that sense, the fediverse adds&amp;#xA;nothing new. People can (and will) have screenshots, proxies can (and will) keep copies.&amp;#xA;Archivers have copies, search engines have it indexed, data-collectors have it&amp;#xA;collected, AI embedded in their models, and so on.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Deleting content from a centralized service (i.e. a tweet from Twitter) doesn&amp;#39;t&amp;#xA;guarantee that it is deleted from all those places; at most, it will prevent&amp;#xA;distribution (forwarding, copying) of stuff that hasn&amp;#39;t been distributed&amp;#xA;(forwarded, copied) yet. Same with deleting a blog-post on medium, a repository&amp;#xA;on Github, an image on Instagram, or a comment on Reddit. If your data hit the&amp;#xA;internet, anyone might have stored a copy. But since there&amp;#39;s only one central&amp;#xA;authority that has the content, and everyone else must ask &amp;lt;em&amp;gt;them&amp;lt;/em&amp;gt; for that&amp;#xA;content in all other cases.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;lt;em&amp;gt;fediverse&amp;lt;/em&amp;gt;, however, amplifies the distribution. Where a centralized&amp;#xA;service operates on the idea of &amp;amp;quot;at any time, just ask us, and we&amp;#39;ll send you a&amp;#xA;copy if you have access&amp;amp;quot; (on-demand), the fediverse operates on the idea &amp;amp;quot;we&amp;#39;ll&amp;#xA;send you a copy because you have access&amp;amp;quot; (push). &amp;amp;quot;Storing copies&amp;amp;quot; is the modus&amp;#xA;operandi of the fediverse.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In order for your Mastodon-post to reach hundreds of people, it is copied many&amp;#xA;times[3]. Aside from this being &amp;lt;a href=&amp;#34;/2022/11/08/fediverse-inefficiencies/&amp;#34;&amp;gt;rather&amp;#xA;inefficient&amp;lt;/a&amp;gt;,&amp;#xA;it means your &amp;lt;a href=&amp;#34;https://gdpr.eu/right-to-be-forgotten/&amp;#34;&amp;gt;Right to be forgotten&amp;#xA;(GDPR)&amp;lt;/a&amp;gt; is technically impossible to&amp;#xA;uphold on the fediverse. For a privacy-touting network, this is a problem. Or&amp;#xA;at least something to be very aware of. In practice, it is not a very big&amp;#xA;problem, though.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Mastodon implements ActivityPub rather well. This is what &amp;lt;a href=&amp;#34;https://www.w3.org/TR/activitypub/#delete-activity-outbox&amp;#34;&amp;gt;ActivityPub says&amp;#xA;about deleting&amp;lt;/a&amp;gt;&amp;#xA;(Emphasis mine):&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;6.4 Delete Activity&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The Delete activity is used to delete an already existing object. The side&amp;#xA;effect of this is that the server &amp;lt;strong&amp;gt;MAY&amp;lt;/strong&amp;gt; replace the object with a Tombstone of&amp;#xA;the object that will be displayed in activities which reference the deleted&amp;#xA;object.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For those unfamiliar with the words MAY, MUST etc &amp;lt;a href=&amp;#34;https://www.rfc-editor.org/rfc/rfc2119&amp;#34;&amp;gt;in RFCs&amp;lt;/a&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;MAY This word, or the adjective &amp;amp;quot;OPTIONAL&amp;amp;quot;, mean that an item is truly&amp;#xA;optional.  One vendor may choose to include the item because a particular&amp;#xA;marketplace requires it or because the vendor feels that it enhances the&amp;#xA;product while another vendor may omit the same item.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So even those that implement ActivityPub good-willing and properly, could omit&amp;#xA;deleting entirely. Essentially, the ActivityPub standard does not adhere to the&amp;#xA;GDPRs&amp;#39; right to be forgotten (IANAL).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And that leaves out all those that aren&amp;#39;t good-willing, or deliberately&amp;#xA;careless. Those that index the fediverse to mine advertising data, to build&amp;#xA;indexes for bullying, or those that collect security-sensitive data for&amp;#xA;phishing or whatnot. Malicious participants will most likely not adhere to the&amp;#xA;standard anyway. Instances that oppose the GDPR for ideological reasons, or&amp;#xA;live by the idea that nothing should ever be &amp;amp;quot;(self-)sensored&amp;amp;quot;. So even if the&amp;#xA;standard would enforce deleting, anyone can just choose to not follow the&amp;#xA;standard.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;While this may sound paranoid, it&amp;#39;s not very far-fetched. Especially because on&amp;#xA;this fediverse, many minorities and privacy-sensitive people congregate; those&amp;#xA;with a higher risk of being bullied, phished, stalked, tracked and so on. It&amp;#39;s&amp;#xA;for that reason that Mastodon has &amp;lt;a href=&amp;#34;https://www.bentasker.co.uk/posts/blog/opinion/arguments-for-and-against-auto-deleting-mastodon-toots.html&amp;#34;&amp;gt;automatic deletion built right&amp;#xA;in!&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This &amp;amp;quot;delete your content to increase privacy&amp;amp;quot; is an often heard mantra, it&amp;#xA;even gets it&amp;#39;s own feature in Mastodon, the most used software. But this feature&amp;#xA;promises much more than it can ever deliver.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;What happens under the hood, when you delete a post, is that it sends out a&amp;#xA;&amp;lt;em&amp;gt;Delete&amp;lt;/em&amp;gt; request for that post to all servers that this post &amp;lt;em&amp;gt;has reached&amp;lt;/em&amp;gt;&amp;#xA;&amp;lt;a href=&amp;#34;https://github.com/mastodon/mastodon/blob/main/app/lib/status_reach_finder.rb&amp;#34;&amp;gt;code&amp;#xA;here&amp;lt;/a&amp;gt;.&amp;#xA;This is a best effort action. Your instance cannot know for certain who really&amp;#xA;received the content: a boost will have distributed it further, as will a reply&amp;#xA;with a mention, or even a relay passing it along. It tries, though. It tries to&amp;#xA;follow all the boosts, mentions, relays. But obviously only knows about those&amp;#xA;that reported this boost, mention or forwarding successfully. And this ignores&amp;#xA;an actual issue: if your post was distributed to instance-foo, and that&amp;#xA;instance then block yours, or your instance now blocks them[2], the delete&amp;#xA;request won&amp;#39;t be delivered. You could see this as someone sending a second&amp;#xA;email in which the sender asks everyone to please delete the previous&amp;#xA;embarrasing email: not a great privacy model.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;All the servers that then receive that &amp;lt;em&amp;gt;Delete&amp;lt;/em&amp;gt; &amp;lt;strong&amp;gt;may&amp;lt;/strong&amp;gt; chose to remove the&amp;#xA;content (or replace it with a placeholder: a so called Tombstone). But even the&amp;#xA;good-willing may fail: a glitch might prevent this: maybe the server is&amp;#xA;overloaded, maybe it is down, maybe it hits an error, anything, really. In a&amp;#xA;centralized service, such an even would probably be noticed and re-handled&amp;#xA;later. In a distributed setup, we cannot ever know if the Delete request was&amp;#xA;honored. And even if this is handled, it only gets removed from that instance&amp;#39;s&amp;#xA;database (and potentially from its ElasticSearch cluster): but not from its&amp;#xA;backups or logs. Your instance does have backups, right? Right?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The same goes for deleting your profile. Or for editing a post. Or editing your&amp;#xA;profile. Any data, any images, thoughts, biographies, links, names or avatars&amp;#xA;that you put out there publicly should be considered distributed, and therefore&amp;#xA;out of your hands: impossible to delete.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;On the fediverse, it is best to consider anything that you published, to be&amp;#xA;out there. Forever.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But that is also where the good news is: you can protect yourself, your data,&amp;#xA;and your content from &amp;amp;quot;getting out there&amp;amp;quot; in the first place.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;https://allthings.how/how-to-lock-your-mastodon-account/&amp;#34;&amp;gt;You can set a profile to protected&amp;lt;/a&amp;gt;. This means only people that you&amp;#xA;grant access, will be able to read your profile&amp;#39;s details and, more&amp;#xA;importantly, the content you write. Only your followers will get a copy of&amp;#xA;your posts. Though any follower (or their server) might still choose to keep&amp;#xA;the data; so keep that in mind when granting follow-requests.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;https://docs.joinmastodon.org/user/posting/#privacy&amp;#34;&amp;gt;You can limit the reach of a post&amp;lt;/a&amp;gt;. This means only a selected group&amp;#xA;will get a copy. And they -at least in theory- aren&amp;#39;t able to copy it to&amp;#xA;other people: boosting should be disabled (by their server). The only threat&amp;#xA;is now very similar to centralised social media: a receiver taking a screenshot,&amp;#xA;or copying it from screen.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Content that hasn&amp;#39;t been made public, hasn&amp;#39;t distributed beyond your reach. So&amp;#xA;even &amp;lt;em&amp;gt;if&amp;lt;/em&amp;gt; any of those you sent it to, cannot or will not delete it, at least&amp;#xA;you know who might still have it (and whether or not you trust them to keep it&amp;#xA;safe). It&amp;#39;s like sending an email to a limited amount of people. I know not to&amp;#xA;include that gossipy aunt. And I know that if any of the recipients uses Gmail,&amp;#xA;Google might mine my email for advertisers, eventhough I, the sender, am not&amp;#xA;using Gmail, Google still gets a copy. But I can control this: If I truly&amp;#xA;distrust Gmail I could choose to never send emails to anyone on Gmail. I&amp;#39;ts in&amp;#xA;&amp;lt;em&amp;gt;my&amp;lt;/em&amp;gt; hands. With Mastodon, you can, if you want, choose to never deliver your&amp;#xA;content to certain servers and it&amp;#39;s far &amp;lt;em&amp;gt;easier&amp;lt;/em&amp;gt; than it would be for that&amp;#xA;hypothetic gmail-avoider.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;amp;quot;threat&amp;amp;quot; (if it can be called that at all - maybe limitation is a better&amp;#xA;word?), is at the moment mostly theoretical. And while that might change, it&amp;#xA;hasn&amp;#39;t yet.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Mastodon, and, as far as I know, also Pleroma, PixelFed, PeerTube and&amp;#xA;probably others, do try their best to actually delete your content when you&amp;#xA;request it to be deleted, you cannot be certain that a recipient is running an&amp;#xA;(unmodified) Mastodon. Or that they secure their servers, or that their&amp;#xA;instance won&amp;#39;t be sold to some advertising company. Or that they run into technical&amp;#xA;problems and fail to follow up on such a delete request.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Right now, a delete request, in practice, will get through and will be&amp;#xA;followed up. Your deleted content is unlikely to end up in public places. And&amp;#xA;you have the tools to protect yourself from actors that don&amp;#39;t follow up on&amp;#xA;deletion requests. Deleting content will, quite certainly, reduce the places&amp;#xA;where a copy is kept. Maybe even reduce it to zero!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But, as I posted earlier:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In the #fediverse, deleting content is really just a suggestion. Servers can,&amp;#xA;and will ignore it entirely. Keeping the content that you deleted.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;https://bitcoinhackers.org/@berkes/109556281027022788&amp;#34;&amp;gt;@berkes@bitcoinhackers.org&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;hr&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[1] Blockchain stuff. Defi and all. Obviously never went anywhere.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[2] I&amp;#39;m not entirely sure of this. Reading through the code makes me think&amp;#xA;that a mastodon instance will actually still deliver delete-requests to&amp;#xA;instances it blocks. But that seems weird, and it does leak information (though&amp;#xA;only the fact that a post with id X has ever existed). I might be wrong.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[3] The ActivityPub protocol allows two modi here: the email-concept, where a&amp;#xA;server will copy it to the final recipient, and so might hold on to hundred&amp;#xA;identical copies if a message was delivered to hundred users on their server.&amp;#xA;The other is where the server just keeps one version and serves that to each&amp;#xA;recipient.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>New Project: Fedetivity   Automate your Mastodon Account</title>
   <link href="https://berk.es/2022/12/13/new-project-fedetivity-automate-your-mastodon-account/"/>
   <updated>2022-12-13T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2022/12/13/new-project-fedetivity-automate-your-mastodon-account/</id>
   <content type="html">&amp;lt;p&amp;gt;A few months ago, we shelved Plannel, on which I worked as co-founder for about&amp;#xA;a year. There was a need, there was problem, but the actual solution turned out&amp;#xA;to not be tech (to improve project communication between clients and&amp;#xA;web/creative agencies), but human (really just a &amp;amp;quot;project/account manager&amp;amp;quot;, not&amp;#xA;more tools). It&amp;#39;s a hard lesson, but a good one.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;After trying to join a few existing startups as CTO or co-founder, I decided to&amp;#xA;try fix a problem that I&amp;#39;ve been having for a while. Those are the best&amp;#xA;problems to solve. And to hopefully turn into a product and business.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The project is &amp;lt;a href=&amp;#34;https://fedetivity.com&amp;#34;&amp;gt;Fedetivity&amp;lt;/a&amp;gt;. A solution to easily&amp;#xA;automate your Mastodon account. But first, how I got to this idea.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;m still working a few hours a week on&amp;#xA;&amp;lt;a href=&amp;#34;https://flockingbird.social&amp;#34;&amp;gt;Flockingbird&amp;lt;/a&amp;gt; which wants to become the Fediverse&amp;#xA;version of &amp;amp;quot;LinkedIn&amp;amp;quot;: a professional social network. Flockingbird, however&amp;#xA;much I love it, has no viable business-model; at least not one that I see or&amp;#xA;believe in. So I&amp;#39;ve made it my side-project.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Within Flockingbird, I&amp;#39;ve developed a bot that hunts the fediverse for&amp;#xA;Job-postings, then indexes those in a job-board. This is&amp;#xA;&amp;lt;a href=&amp;#34;https://search.flockingbird.social&amp;#34;&amp;gt;search.flockingbird.social&amp;lt;/a&amp;gt;, the bot is&amp;#xA;&amp;lt;a href=&amp;#34;https://botsin.space/@hunter2&amp;#34;&amp;gt;@hunter2&amp;lt;/a&amp;gt;. The bot is&amp;#xA;flaky and unreliable: it often crashes, loses connection, misses replies. To&amp;#xA;solve this, I need in-depth knowledge of websockets (in Mastodon),&amp;#xA;multithreading, federation, ActivityPub, HTML scraping, mastodon APIs, mastodon&amp;#xA;permission scopes, and so forth. Writing an interactive bot (that doesn&amp;#39;t&amp;#xA;overload it&amp;#39;s mastodon instance) turns out to be hard. But I&amp;#39;m solving that for hunter2!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So, why not share it?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And that&amp;#39;s what I&amp;#39;m going to work on: taking the most difficult part of writing&amp;#xA;bots and other automation for Mastodon or the Fediverse, and turn that into a&amp;#xA;product. SAAS and Open Source. Hence, &amp;lt;strong&amp;gt;Fedetivity&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For those wondering: the &amp;lt;a href=&amp;#34;https://www.fediverse.to/&amp;#34;&amp;gt;fediverse&amp;lt;/a&amp;gt; is a name for a&amp;#xA;set of decentralized, interconnected social media platforms. It&amp;#39;s seen a&amp;#xA;enormous influx and is on the rise, ever since Elon Musk took over Twitter. But&amp;#xA;it was already steadily growing after every other scandal by one of the giant&amp;#xA;social media companies.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Coming months, I want to ask as many questions to anyone who has a need to&amp;#xA;automate stuff on their mastodon or fediverse account. Or already does so. I&amp;#39;ve&amp;#xA;blocked my agenda to offer free consultancy on mastodon automation. In the hope&amp;#xA;that helping people to solve their problem, teaches me about those problems.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If you have a bot, want to move to the fediverse, have moved to the fediverse,&amp;#xA;just want to learn more about the fediverse, need to automate or integrate&amp;#xA;anything there, just let me know! I can help you with resources, ideas, tools,&amp;#xA;anything, really. I can even program these automations for you, if there&amp;#39;s a&amp;#xA;need.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;m sure I can help, while helping myself. At the very least help you to&amp;#xA;understand how the fediverse looks towards automation (and not have you&amp;#xA;immediately banned, or blocked). So, please feel free to &amp;lt;a href=&amp;#34;https://calendly.com/fedetivity/fedetivity-mastodon-automation-consult&amp;#34;&amp;gt;book a free&amp;#xA;consult&amp;lt;/a&amp;gt;!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;My vision for &amp;lt;em&amp;gt;Fedetivity&amp;lt;/em&amp;gt; is that it becomes the If-This-Then-That, CircleCI&amp;#xA;or Travis of fediverse automation. Where tech folks can write their own&amp;#xA;automation pipelines and non-techies can build those though a friendly UI. And&amp;#xA;where anyone has the freedom to run the software on their own, but where my&amp;#xA;hosted version is free for small users and paid for the bigger ones.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But, first, I&amp;#39;ll be writing software to solve my own immediate problem: a tool&amp;#xA;that listens to mentions, DMs, and as much of the fediverses&amp;#39; status-updates as&amp;#xA;possible. Then filter it, and forward that to a bot, &amp;lt;em&amp;gt;hunter2&amp;lt;/em&amp;gt;, so that this bot&amp;#xA;can focus on it&amp;#39;s domain (job postings) rather than waste time on juggling&amp;#xA;flaky websockets, (de)serializing, authorization, API calls, and other annoying,&amp;#xA;time-consuming details.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It&amp;#39;s one of those problems where I really wished it was solved already, and I&amp;#xA;could just pay someone for a hosted product. Even when Flockingbird isn&amp;#39;t&amp;#xA;making any money, I&amp;#39;d gladly pay to be able to focus more on finding and&amp;#xA;filtering great job-postings. I think those kind of problems are the best to&amp;#xA;solve. If &amp;lt;em&amp;gt;I&amp;lt;/em&amp;gt; would pay someone for this product, than surely someone else&amp;#xA;will? But even if no-one else feels this problem, or no-one else wants to pay&amp;#xA;for it, I do need to solve it anyway, since I&amp;#39;m stuck on this problem today.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>The Fediverse is Inefficient (but that&amp;#39;s a good trade-off)</title>
   <link href="https://berk.es/2022/11/08/fediverse-inefficiencies/"/>
   <updated>2022-11-08T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2022/11/08/fediverse-inefficiencies/</id>
   <content type="html">&amp;lt;p&amp;gt;Let&amp;#39;s address the mammoth in the room: the &amp;lt;a href=&amp;#34;https://fediverse.party/en/fediverse/&amp;#34;&amp;gt;fediverse&amp;lt;/a&amp;gt;, the network of &amp;lt;a href=&amp;#34;https://joinmastodon.org/&amp;#34;&amp;gt;mastodon&amp;lt;/a&amp;gt;&amp;#xA;servers, is very inefficient.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In this post I&amp;#39;ll show why it is inefficient and why that isn&amp;#39;t a problem.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A great analogy to explain this with is growing food.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;gardening-making-my-own-food-decentralized&amp;#34;&amp;gt;Gardening: making my own food, decentralized&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I spend a lot of my time in my vegetable garden, our communal orchard, chicken&amp;#xA;coop, and keeping bees. A lot of my food is produced by myself, &amp;lt;em&amp;gt;decentralized&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Compare this to one farmer who produces thousands of tonnes of potatoes,&amp;#xA;another one who produces tonnes of tomatoes another single farmer keeping&amp;#xA;500.000 chickens, or a beekeeper with a huge truck with several millions of&amp;#xA;bees travelling from gestation- to gestation. It really holds no comparison.&amp;#xA;I&amp;#39;ve grown two third of my food myself[1], but my tomatoes would&amp;#39;ve roughly&amp;#xA;cost me €35 per kilo, if I calculated everything including a minimum hourly&amp;#xA;wage. My honey would cost over €10 a jar if I&amp;#39;d pay my time. And my neighbors&amp;#xA;do the same in &amp;lt;em&amp;gt;their&amp;lt;/em&amp;gt; garden. It&amp;#39;s terribly inefficient.[2]&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But that doesn&amp;#39;t matter, because it&amp;#39;s a trade-off.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I get a lot of good things back. The labor is good. I&amp;#39;ve solved many software&amp;#xA;bugs while weeding a patch of garden[3]. I&amp;#39;ve come up with some of the best system&amp;#xA;architectures while going through one of my hives. I&amp;#39;ve had the best fun making&amp;#xA;jam, drying fruits or conserving harvest. Much better than those guilt-ridden&amp;#xA;nights wasting hours of another addicting Netflix series.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;lt;strong&amp;gt;incentives&amp;lt;/strong&amp;gt; are different. I don&amp;#39;t have shareholders demanding more&amp;#xA;tomatoes, have no loans to pay for the most efficient chicken-coop. I don&amp;#39;t&amp;#xA;need to out-compete my neighbors. No pressure to sell my produce for the right&amp;#xA;price. &amp;lt;strong&amp;gt;The inefficiency doesn&amp;#39;t matter&amp;lt;/strong&amp;gt;!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;There is &amp;lt;strong&amp;gt;connection&amp;lt;/strong&amp;gt;. By growing my own, I feel connected to the thing that&amp;#xA;literally keeps me alive: my food.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;There is &amp;lt;strong&amp;gt;reward&amp;lt;/strong&amp;gt;. I&amp;#39;m convinced it&amp;#39;s purely psychological, but a self-made&amp;#xA;sauerkraut, a pasta from own (carefully selected) tomatoes or fries from these&amp;#xA;special potatoes just tastes so much better than anything wrapped in plastic&amp;#xA;from a supermarket.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;There are &amp;lt;strong&amp;gt;different efficiencies&amp;lt;/strong&amp;gt;. My food isn&amp;#39;t shipped across oceans (in&amp;#xA;airplanes). There isn&amp;#39;t a single gram of waste in my &amp;amp;quot;supply-chain&amp;amp;quot;: everything&amp;#xA;ends as compost or chicken food (manure!)[4].&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;There is &amp;lt;strong&amp;gt;resilience&amp;lt;/strong&amp;gt;. If my potatoes fail, my neighbors&amp;#39; probably won&amp;#39;t. And&amp;#xA;even if theirs fail, I still have pumpkins, corn, spinach and much more to cover the&amp;#xA;loss. When our chickens get the chicken-flu, they won&amp;#39;t all die (we&amp;#39;ve selected&amp;#xA;them for strength, not optimal-egg-laying-efficiency). And even if they do,&amp;#xA;it&amp;#39;s &amp;amp;quot;just&amp;amp;quot; 8 chickens dead, not 100.000s. Monoculture is a terrible risk.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With decentralized systems like the Fediverse, like Mastodon, we see very&amp;#xA;similar trade-offs. We see the same kinds of efficiencies, but the same kinds&amp;#xA;of inefficiencies.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;fediverse-technical-inefficiencies&amp;#34;&amp;gt;Fediverse Technical Inefficiencies&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The obvious inefficiency are all the &amp;lt;a href=&amp;#34;https://fediverse.observer/stats&amp;#34;&amp;gt;tens of&amp;#xA;thousands&amp;lt;/a&amp;gt; of servers running mastodon. All&amp;#xA;of them run databases, storage, workers, webservers and so on. A single post&amp;#xA;with an image may reach &amp;lt;a href=&amp;#34;https://mastodon.nu/@gretathunberg&amp;#34;&amp;gt;32.1k people&amp;lt;/a&amp;gt; and&amp;#xA;all &amp;lt;em&amp;gt;their&amp;lt;/em&amp;gt; contacts, if these people are spread across thousands of servers,&amp;#xA;that image is now stored on all those thousands servers. The (meta) data is&amp;#xA;duplicated across thousands databases and thousands of servers spend time&amp;#xA;validating and processing the post[5].&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Any distributed system is &amp;lt;a href=&amp;#34;http://scholar.googleusercontent.com/scholar?q=cache:ylLbdPSfTbkJ:scholar.google.com/&#43;Decentralization&#43;Briefing&#43;Notes&#43;-&#43;World&#43;Bank&amp;amp;amp;hl=vi&amp;amp;amp;as_sdt=0,5&amp;#34;&amp;gt;inefficient&amp;lt;/a&amp;gt;, for one, because it lacks the&amp;#xA;&amp;lt;a href=&amp;#34;https://en.wikipedia.org/wiki/Economies_of_scale&amp;#34;&amp;gt;economy of scale&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And so is the case with the fediverse. The six million users would &amp;amp;quot;easily&amp;amp;quot; fit&amp;#xA;on a mastodon on under thirty (virtual) servers, a very few large PostgreSQL&amp;#xA;database servers and a single file-server/storage. I know, because I&amp;#39;ve built&amp;#xA;and grown such Rails systems, with millions of users (on AWS). Certainly not&amp;#xA;thousands of servers. Definitely not thousands of database-servers.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Even if Mastodon were to be &amp;lt;a href=&amp;#34;https://github.com/ansuz/RIIR&amp;#34;&amp;gt;rewritten in Rust&amp;lt;/a&amp;gt;,&amp;#xA;tuned, and changed into a backend that can host thousands of users on a single&amp;#xA;Raspberry-Pi running on solar power, it still is inefficient. For one, because&amp;#xA;that backend would be even more efficient when employed in a centralized setup.&amp;#xA;And secondly because there is a lot of network overhead.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;All the servers also need to communicate and distribute the posts, media,&amp;#xA;profiles and metadata. Over the internet. &amp;lt;a href=&amp;#34;https://www.w3.org/TR/activitypub/&amp;#34;&amp;gt;Activity-pub the underlying&amp;#xA;protocol&amp;lt;/a&amp;gt; is very chatty, necessarily. And it&amp;#xA;does this over HTTP, hence TCP/IP. All of which is chatty and relatively&amp;#xA;inefficient. And because every server is communicating with every other server,&amp;#xA;all require extra CPU time, storage, memory and other precious resources just&amp;#xA;to distribute a post.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;fediverse-human-inefficiencies&amp;#34;&amp;gt;Fediverse Human Inefficiencies&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;All the mastodon (and Pleroma, Pixelfed, Misskey and such) servers are managed&amp;#xA;by humans. Some humans will manage several servers, and some servers are&amp;#xA;managed by several humans. In any case, this causes a lot of duplicated work.&amp;#xA;Done mostly by volunteers. Unpaid.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;All posts, accounts, media are moderated by humans. Moderators must wade&amp;#xA;through spam, racism, hate, (child)porn and abuse. Daily. And then blocked. A&amp;#xA;single spam-post might -worst case- have to be dealt with by tens-of-thousands&amp;#xA;of moderators. Each one reviewing it and potentially taking action. There&amp;#39;s,&amp;#xA;again, a &amp;lt;em&amp;gt;lot&amp;lt;/em&amp;gt; of duplicate work[6].&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;gardening-in-the-fediverse&amp;#34;&amp;gt;Gardening in the Fediverse&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Each server on the fediverse is like a personal vegetable garden, or a&amp;#xA;neighborhood orchard. Lots of overhead, lots of duplicate work, lots of&amp;#xA;inefficiencies that could be solved by handing it over to a few huge-scale&amp;#xA;&amp;amp;quot;farmers&amp;amp;quot; instead. Instead of tuning thousands of databases over and over, just&amp;#xA;&amp;amp;quot;buy&amp;amp;quot; a single big database from something like AWS RDS: their economy of scale&amp;#xA;makes it far more efficient than all the small ones combined.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But like with gardening, it doesn&amp;#39;t really matter that it&amp;#39;s inefficient. No&amp;#xA;shareholders to demand duplicate work to be eliminated. No competition to&amp;#xA;out-compete. No growth required to keep relevant. As long as two people can&amp;#xA;communicate over &amp;amp;quot;the fediverse&amp;amp;quot; it&amp;#39;s a full-on success. Nothing else matters.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;All the extra work, done by humans, creates an immense feeling of connection.&amp;#xA;When a participant reports troublesome content, they know the &amp;lt;em&amp;gt;humans&amp;lt;/em&amp;gt; who&amp;#xA;handle the report. Trust them. This human connection is, and has always been,&amp;#xA;a crucial factor in creating healthy communities.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;While moderating a fediverse server is hard work, it&amp;#39;s also rewarding. The same&amp;#xA;way getting your email-inbox empty is. But also because there&amp;#39;s a direct,&amp;#xA;positive impact on the humans that signed up on your server. Often many thank&amp;#xA;an admin in person. People trusted this human with their social network and&amp;#xA;communication.&amp;#xA;Whenever a server-admin asks for donations, support start streaming in: never&amp;#xA;enough to cover all hours of work and often not even enough to cover the full&amp;#xA;costs. But people gladly pay a few dollars a month when they feel connected to the&amp;#xA;ones they&amp;#39;re paying it to. Gladly pay humans for their hard work, yet&amp;#xA;reluctantly pay a fee for an automated &amp;amp;quot;validation icon&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A fediverse-alike network sees different kinds of efficiencies pop up. People&amp;#xA;develop many &amp;lt;a href=&amp;#34;https://github.com/BasixKOR/awesome-activitypub&amp;#34;&amp;gt;alternative types of&amp;#xA;software&amp;lt;/a&amp;gt; to handle different&amp;#xA;needs and niches. A&amp;#xA;furry-fetish group can tune their moderation and setup very different to a&amp;#xA;group who wants to build a safe-space-community for victims of cyber-bullying.&amp;#xA;No central setup could attune itself to all these different and dynamic needs&amp;#xA;and wants efficiently. Validation of participants becomes &amp;lt;a href=&amp;#34;https://martinfowler.com/articles/your-org-run-mastodon.html&amp;#34;&amp;gt;so much&amp;#xA;simpler&amp;lt;/a&amp;gt; and&amp;#xA;banning bullies and hate is a single click away.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The original internet was set up decentralized, with the goal to be resilient&amp;#xA;to failing parts and to attacks. A lot of this property has been undone by&amp;#xA;re-centralisation: if AWS has an outage, half the internet goes down.&amp;#xA;When Twitter is run into the ground by some Billionaire Chaos Monkey, the whole&amp;#xA;world sees it&amp;#39;s journalist and government communication break down. A (lifted)&amp;#xA;ban on Twitter has actual effect on democracy and society.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;https://redecentralize.org/&amp;#34;&amp;gt;Re-decentralising&amp;lt;/a&amp;gt; is, therefore, very much&amp;#xA;needed to make our society resilient. We cannot afford to have a single&amp;#xA;software bug break all airplanes, cars or thermostats. Or to have the&amp;#xA;communication of a country be dependent on the unpredictable whims of a&amp;#xA;billionaire. The education of our kids be governed by advertising algorithms.&amp;#xA;We cannot afford to be held hostage by monopolies when it comes to our&amp;#xA;infrastructure, communication and information.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;As a European, I clearly see how monoculture is spreading over the world.&amp;#xA;American monoculture. Renewed prudism (imagine that a child sees a nipple!&amp;#xA;gasp!), replaced traditions (we now all have this ridiculous festival of&amp;#xA;consumerism, called &amp;amp;quot;Black Friday&amp;amp;quot;, without ever having thanksgiving. We&amp;#39;re&amp;#xA;almost done replacing our weird Dutch Santa Claus -5th of December- with this&amp;#xA;American Coca-Cola version).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Plurality is key to evolution. It is what makes us as humans progress.&amp;#xA;Monoculture enlarges the risks, and halts progress. Both in biology and in tech.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The only alternatives would be either to embrace a communist-like centralized,&amp;#xA;monopolized, governance by entities that are governed only by a few&amp;#xA;shareholders or billionaires. Or to forego all the internet, automation and&amp;#xA;digitization of the last centuries and move back to the early 1900s.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Both aren&amp;#39;t acceptable alternatives for me. So I&amp;#39;ll keep working on my own&amp;#xA;&amp;lt;a href=&amp;#34;https://www.technologyreview.com/2020/09/03/1007716/digital-gardens-let-you-cultivate-your-own-little-bit-of-the-internet/&amp;#34;&amp;gt;digital&amp;#xA;garden&amp;lt;/a&amp;gt;.&amp;#xA;And on my physical garden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;hr&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[1] I don&amp;#39;t eat meat, which makes this easier. Though I did learn to butcher a hare this year - roadkill near my home. I ate that: first meat after some 15 years.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[2] There&amp;#39;s a long standing argument if we&amp;#39;d be able to &amp;amp;quot;feed the world&amp;amp;quot; like this. I am convinced, by studies and papers, not just feelings, that we can. But &amp;lt;em&amp;gt;only&amp;lt;/em&amp;gt; if we change our consuming patterns. Seasonal crops, local crops, no meat (or it becoming extremely exceptional, like caviar or so), no fish etc.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[3] Which begs another question: should my clients pay me for the hours tending to my bees, for weeding my garden, or just for the hours that I&amp;#39;m actually staring at a screen?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[4] Admitted: if I count the crops eaten by animals, the honey I leave in for the bees, and the fruit I didn&amp;#39;t manage to preserve, I did lose a lot. Almost a third, I guess. It was all eaten, just not by me. And it did contribute to a healthy ecosystem.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[5] Ironically all using electricity and therefore (indirectly) emitting CO2.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[6] There&amp;#39;s good tools, shared lists to block, and mechanisms to block large amounts at once. Still, each server needs humans to do this on their particular server.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Blog comments on a static site via social networks</title>
   <link href="https://berk.es/2022/10/12/blog-comments-on-a-static-site-via-social-networks/"/>
   <updated>2022-10-12T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2022/10/12/blog-comments-on-a-static-site-via-social-networks/</id>
   <content type="html">&amp;lt;p&amp;gt;Most blogs have a comment section below each article. A relic from the days&amp;#xA;that blogs itself were the bulk of &amp;amp;quot;social media&amp;amp;quot;. Before Slashdot and Digg,&amp;#xA;there simply was no place to discuss the article other than in the comments&amp;#xA;below that article. It&amp;#39;s still a core feature of Drupal, WordPress, and other&amp;#xA;blog-software.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A static site, however, cannot have comments in the same way. The blog-software&amp;#xA;generates the HTML, but cannot handle incoming comments or anything dynamic&amp;#xA;really. One can add a form and/or embed a service like Disqus or&amp;#xA;&amp;lt;a href=&amp;#34;https://isso-comments.de/&amp;#34;&amp;gt;isso&amp;lt;/a&amp;gt; for that, but aside from privacy concerns, it&amp;#xA;somewhat defeats the purpose of a static site.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But also, today, discussions are hardly held on the blog itself anymore, but&amp;#xA;rather on platforms such as Reddit, Hackernews or Lobsters. And obviously&amp;#xA;Facebook, Twitter, Mastodon and even LinkedIn.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I ditched the common solution to this, Disqus, long ago for privacy concerns.&amp;#xA;And never even considered an alternative. The signal to spam ratio was far too&amp;#xA;high: for every on-topic comment, I had to moderate tens, sometimes hundreds of&amp;#xA;nonsense comments. Even with some solid spam-protection in place[1]. At some&amp;#xA;point, I spent more time moderating comments than writing articles.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I found that the discussions on Hackernews and even Reddit, are far better than&amp;#xA;what happened in the early days on this blog (back when it was Drupal 3, still)&amp;#xA;in the comments. So why not leverage that?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Turns out there is &amp;lt;a href=&amp;#34;https://discu.eu&amp;#34;&amp;gt;discu.eu&amp;lt;/a&amp;gt;, a fantastic service that you&amp;#xA;provide with an URL and then gives back results from various social networks.&amp;#xA;Places where that URL is discussed. Hackernews, Reddit, and/or Lobsters.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It has an API, that I can call with some JavaScript and then insert in the&amp;#xA;page. I&amp;#39;ve added that to this blog. But also extracted it into it&amp;#39;s own&amp;#xA;repository:&amp;#xA;&amp;lt;a href=&amp;#34;https://github.com/berkes/discueu.js/&amp;#34;&amp;gt;github.com/berkes/discueu.js&amp;lt;/a&amp;gt;. Tried to&amp;#xA;make it easy to copy[2] into your own static site or blog.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Check out &amp;lt;a href=&amp;#34;https://berkes.github.io/discueu.js/&amp;#34;&amp;gt;a demo here&amp;lt;/a&amp;gt;.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We can then implement this on our site as follows:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;script src=&amp;#34;https://gist.github.com/berkes/b6bc1a4c463bd64f653dbdcf245c7912.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It consists of four parts: the &amp;lt;code&amp;gt;Discussion&amp;lt;/code&amp;gt;,  a &amp;lt;code&amp;gt;DiscussionsRepo&amp;lt;/code&amp;gt;, a &amp;lt;code&amp;gt;Renderer&amp;lt;/code&amp;gt;&amp;#xA;and a set of templates in HTML.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;lt;code&amp;gt;Discussion&amp;lt;/code&amp;gt; is the main class: a controller so to say. It gets passed&amp;#xA;in (dependency injection) a &amp;lt;code&amp;gt;Renderer&amp;lt;/code&amp;gt; and a &amp;lt;code&amp;gt;DiscussionsRepo&amp;lt;/code&amp;gt;. Idea behind&amp;#xA;this setup, is that it&amp;#39;s easy to test and easy to swap out for other implementation.&amp;#xA;Maybe you want a Repo that sorts differently, or has additional filters (only&amp;#xA;return stuff with a ranking over 10, e.g.). Or maybe a renderer that builds its&amp;#xA;HTML in JavaScript rather than from &amp;lt;code&amp;gt;&amp;amp;lt;template&amp;amp;gt;&amp;lt;/code&amp;gt; tags[3].&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;lt;code&amp;gt;discussionsRepo&amp;lt;/code&amp;gt; gets the URL for discussions passed in, a list of&amp;#xA;networks to filter by (maybe you only want reddit?) and needs to return either&amp;#xA;an empty list (no errors, but nothing found), a list with discussion objects,&amp;#xA;or a &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; (an error).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;lt;code&amp;gt;Renderer&amp;lt;/code&amp;gt; then is handled this list and builds a DOM from&amp;#xA;&amp;lt;a href=&amp;#34;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template&amp;#34;&amp;gt;&amp;lt;code&amp;gt;&amp;amp;lt;template&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/a&amp;gt;&amp;#xA;pieces found in the HTML. It renders either a list of discussions found around&amp;#xA;the web, or the message that nothing was found, or an error.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You will need to register at discu.eu to get a proper API key, but the &amp;lt;code&amp;gt;test&amp;lt;/code&amp;gt; key&amp;#xA;works for testing.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Please let me know at &amp;lt;a href=&amp;#34;https://github.com/berkes/discueu.js/issues&amp;#34;&amp;gt;the repo&amp;#xA;issues&amp;lt;/a&amp;gt; or in a PR, if you see any&amp;#xA;improvements (I do!).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And big thanks to the work done by &amp;lt;a href=&amp;#34;https://xojoc.pw/&amp;#34;&amp;gt;Alexandru&amp;#xA;Cojocaru&amp;lt;/a&amp;gt; on discu.eu. I&amp;#39;m merely adding a few lines of code&amp;#xA;to his hard work.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;hr&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[1] One reason for that is that static site blogs, by nature, have fairly&amp;#xA;good SEO, so such blogs are a rewarding target to spam.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[2] It&amp;#39;s not a proper library yet, and I doubt I&amp;#39;ll make it one. The&amp;#xA;use-cases are diverse and the actual code so simple, that abstracting this as&amp;#xA;a library that can handle most common use-cases, makes it way more complex&amp;#xA;than needed. Sometimes it&amp;#39;s just best to copy the code from a library into&amp;#xA;your project, tweak it, and own it.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[3] I&amp;#39;m aware this is somewhat a-typical Javascript, but I like Dependency&amp;#xA;Injection, and I like composition and encapsulation, which I find cumbersome&amp;#xA;and hard to achieve in functional JavaScript style.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Value Objects in Rust</title>
   <link href="https://berk.es/2022/09/28/value-objects-in-rust/"/>
   <updated>2022-09-28T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2022/09/28/value-objects-in-rust/</id>
   <content type="html">&amp;lt;p&amp;gt;When writing software in an OOP language, Value Objects are an invaluable (pun&amp;#xA;intended) tool in my toolbelt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The idea of Value Objects arose from &amp;lt;a href=&amp;#34;https://www.dddcommunity.org/book/evans_2003/&amp;#34;&amp;gt;Domain Driven&amp;#xA;Design&amp;lt;/a&amp;gt;, but are useful in and&amp;#xA;on itself, so outside of DDD.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Examples of Value Objects are things like numbers, dates, monies and strings.&amp;#xA;Usually, they are small objects which are used quite widely. Their identity&amp;#xA;is based on their state rather than on their object identity. This way, you&amp;#xA;can have multiple copies of the same conceptual Value Object. Every $5 note&amp;#xA;has its own identity (thanks to its serial number), but the cash economy&amp;#xA;relies on every $5 note having the same value as every other $5 note.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;http://wiki.c2.com/?ValueObject&amp;#34;&amp;gt;C2 Wiki&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Value Objects must adhere to three rules. I like to add two.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It has no identity.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It is immutable.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It is comparable.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It has meaning to the business.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It is guaranteed to be valid according to the business.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Usually, only &amp;lt;a href=&amp;#34;https://medium.com/swlh/value-objects-to-the-rescue-28c563ad97c6&amp;#34;&amp;gt;the first&amp;#xA;three&amp;lt;/a&amp;gt; are&amp;#xA;mentioned. I always add the last two, to include &amp;lt;em&amp;gt;why&amp;lt;/em&amp;gt; we want this. In other&amp;#xA;words: if there is no need for a business meaning, or there is no validation,&amp;#xA;a Value Object may be overkill. Sometimes something really is&amp;#xA;just an integer or string.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In most software, we use Primitives all over the place. Need a URL? Just&amp;#xA;pass a string. A price? Decimal, or Int (or, heaven forbid: a float). A from-&amp;#xA;and to date for some report? Send in two Dates. And so on. This is also known&amp;#xA;as the &amp;lt;em&amp;gt;&amp;lt;a href=&amp;#34;https://refactoring.guru/smells/primitive-obsession&amp;#34;&amp;gt;Primitive&amp;#xA;Obsession&amp;lt;/a&amp;gt; Code-smell&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This causes a lot of issues. Most important issue being: it &amp;lt;a href=&amp;#34;https://blog.ploeh.dk/2015/01/19/from-primitive-obsession-to-domain-modelling/&amp;#34;&amp;gt;spreads your&amp;#xA;business&amp;#xA;rules&amp;lt;/a&amp;gt;&amp;#xA;all over the place. Or simply leaves you with no business rules at all. And&amp;#xA;secondly: Meaning is lost. A price as decimal may be unambiguous enough, but a&amp;#xA;&amp;lt;em&amp;gt;let weight = 3.76;&amp;lt;/em&amp;gt; certainly is ambiguous.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Also, primitives aren&amp;#39;t immutable by definition (but in Rust at least they are&amp;#xA;by default) and while often they can be compared, and lack identity, they most&amp;#xA;certainly lack any meaning to the business. And we cannot guarantee their&amp;#xA;validity. They have no place in the &amp;lt;em&amp;gt;Domain model&amp;lt;/em&amp;gt;. So they tick some boxes,&amp;#xA;but not all.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;An contrived example of &amp;amp;quot;software&amp;amp;quot; that emails your boss some report of recent&amp;#xA;requests to a website. This uses primitives: strings, integers, vectors etc.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;script src=&amp;#34;https://gist.github.com/berkes/2dce2ce11198caa6ddb502ade17fc7de.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Our &amp;lt;em&amp;gt;API&amp;lt;/em&amp;gt; is both &amp;lt;code&amp;gt;PageRequest&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;make_and_send_report&amp;lt;/code&amp;gt;. Aside from the&amp;#xA;obvious issue that the function has multiple responsibilities, this API leaves&amp;#xA;a lot to desire. The users (colleagues, future-me, today-me, contractors etc) need&amp;#xA;additional information that the code does not convey.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Our business logic of what is a valid email is encapsulated: good! But we&amp;#xA;must now remember to use that encapsulated rule everywhere we want to do&amp;#xA;stuff with an email-address[1].&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;What is the &amp;lt;code&amp;gt;size&amp;lt;/code&amp;gt;? Apparently when diving deep into the code, it represents kilobytes.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Can a size be negative? 18 quintillion kilobytes? Zero?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;What is the &amp;lt;code&amp;gt;response_code&amp;lt;/code&amp;gt;? Maybe a HTTP code? If so, what is zero? Or 65000?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Please note that &amp;lt;code&amp;gt;PageRequest&amp;lt;/code&amp;gt; is not a value object here. It has identity&amp;#xA;(though no explicit ID field), it makes no sense to compare on its content and&amp;#xA;it would probably be something that comes from a database or other source in&amp;#xA;reality. It would be either an Aggregate or Entity. But that&amp;#39;s for another&amp;#xA;discussion.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;value-objects-to-the-rescue&amp;#34;&amp;gt;Value Objects to the rescue&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;First, a Value Object wraps one, or multiple &amp;lt;em&amp;gt;things&amp;lt;/em&amp;gt;. An example of&amp;#xA;multiple-things &amp;lt;a href=&amp;#34;https://www.martinfowler.com/bliki/ValueObject.html&amp;#34;&amp;gt;would be a 2D&amp;#xA;coordinate&amp;lt;/a&amp;gt; or a &amp;amp;quot;list of things&amp;amp;quot;.&amp;#xA;My contrived example lacks such Value Objects that wrap multiple primitives,&amp;#xA;but I&amp;#39;ll touch on this later.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For now, let&amp;#39;s turn the email address into a Value Object:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;script src=&amp;#34;https://gist.github.com/berkes/8b450fedc2fd85ae6312512d6225f812.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A HttpResponseCode and PageSize would follow the same pattern.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We then use them as:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;script src=&amp;#34;https://gist.github.com/berkes/347adbb0fda1bd20eb447bc9b0feca0e.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;An intermediate version where all this is implemented can be found &amp;lt;a href=&amp;#34;https://play.rust-lang.org/?version=stable&amp;amp;amp;mode=debug&amp;amp;amp;edition=2021&amp;amp;amp;gist=072423a39857793d25aadfe87deac190&amp;#34;&amp;gt;in this playground&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This pattern is:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;We define a simple struct. It wraps the value.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;We implement a constructor. In that constructor, we place any business rules. So that we can be &amp;lt;em&amp;gt;sure&amp;lt;/em&amp;gt; that any such Value Object created through this constructor is valid, by our business-rules.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;When the constructor gets an invalid value, it will return an error &amp;lt;em&amp;gt;in runtime&amp;lt;/em&amp;gt;.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;We implement the Display trait, so that our users can display it.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is far from ideal, still. But we already have a few boxes ticked:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It has no identity.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It is immutable.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It has meaning to the business.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Still missing:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It is guaranteed to be valid according to the business.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It is comparable.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For the first, we need to enforce that users&amp;#39; go through our constructors.&amp;#xA;For the second, we can implement the &amp;lt;a href=&amp;#34;https://doc.rust-lang.org/std/cmp/trait.PartialEq.html&amp;#34;&amp;gt;&amp;lt;code&amp;gt;PartialEq&amp;lt;/code&amp;gt; trait&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And for the impatient: yes, we will clean up the API and make it ergonomic later on. The code, with its Results, Displays and whatnots has arguably become worse now. But first some other issues to solve.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In order to avoid someone from simply calling &amp;lt;code&amp;gt;EmailAddress { value: String::from(&amp;amp;quot;invalid-email&amp;amp;quot;) }&amp;lt;/code&amp;gt; we can place our &amp;lt;a href=&amp;#34;https://stackoverflow.com/questions/73882769/force-use-of-a-constructor-in-rust/73882994&amp;#34;&amp;gt;Value Objects in a module and only make the struct and constructor public.&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;script src=&amp;#34;https://gist.github.com/berkes/f0964debed38d8809297fc3dcf5be231.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Implementing the &amp;lt;code&amp;gt;PartialEq&amp;lt;/code&amp;gt; trait is trivial too. Or, even simpler, with derive:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;script src=&amp;#34;https://gist.github.com/berkes/480c233fc70f98e40f1576bbd906d061.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Most value-objects will be simple enough for &amp;lt;code&amp;gt;derive&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Which we can then use throughout our code when checking equality. E.g. a&amp;#xA;business rule that ensures we never ever email john:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;script src=&amp;#34;https://gist.github.com/berkes/793359fc7890fd58bb6c532a5919c4cf.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Whether or not to use the derive or the impl version depends mostly on the&amp;#xA;business needs. Typically, Value Objects are simple, so the derive version will&amp;#xA;almost always be good: our value &amp;lt;em&amp;gt;struct&amp;lt;/em&amp;gt;s hardly ever will have fields that&amp;#xA;aren&amp;#39;t relevant to the comparison.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But maybe our business says: &amp;amp;quot;bob&#43;one@example.com and &amp;lt;a href=&amp;#34;mailto:bob&#43;two@example.com&amp;#34;&amp;gt;bob&#43;two@example.com&amp;lt;/a&amp;gt; are&amp;#xA;the same&amp;amp;quot;. Or maybe our HttpResponseCode 488 should be the same as 400.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Now everything is ticked:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It has no identity. - Simply don&amp;#39;t add some id: field.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It is immutable. - Default in Rust&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It is comparable. - Derive or implement PartialEq&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It has meaning to the business. - Give them proper names&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It is guaranteed to be valid according to the business. - Enforce constructors.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But we can improve a lot still. I won&amp;#39;t go through all the improvements that&amp;#xA;make it &amp;lt;em&amp;gt;more proper rust-ish&amp;lt;/em&amp;gt; but will discuss some improvements are specific&amp;#xA;for the Value Objects. And one makes them a lot nicer to work with.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Most important, is an issue with our &amp;lt;code&amp;gt;PageSize&amp;lt;/code&amp;gt;. It is still unclear in what&amp;#xA;unit this is. We could rename it to &amp;lt;code&amp;gt;PageSizeBytes&amp;lt;/code&amp;gt;, implement some &amp;lt;code&amp;gt;From&amp;lt;/code&amp;gt; traits&amp;#xA;to convert between &amp;lt;code&amp;gt;PageSizeBytes&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;PageSizeKiloBytes&amp;lt;/code&amp;gt; and so on. Or we could&amp;#xA;improve the API of this object. E.g. a &amp;lt;code&amp;gt;::new_from_kilobytes()&amp;lt;/code&amp;gt; constructor,&amp;#xA;and then some fancy &amp;lt;code&amp;gt;.as_megabytes()&amp;lt;/code&amp;gt;, etc. getters.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When to choose what, depends on the domain and requirements.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We can lean on the type checker for cases where a specific unit is a&amp;#xA;requirement. E.g. maybe a function &amp;lt;em&amp;gt;must&amp;lt;/em&amp;gt; operate on bytes, so binding the&amp;#xA;function parameter to &amp;lt;em&amp;gt;PageSizeBytes&amp;lt;/em&amp;gt; then helps: &amp;lt;code&amp;gt;fn is_outlier(size: PageSizeBytes)&amp;lt;/code&amp;gt;.&amp;#xA;We can use contracts (traits) for cases where we want to be sure that we can&amp;#xA;read a specific unit from the Value Object, with, say a &amp;lt;code&amp;gt;as_kilobytes()&amp;lt;/code&amp;gt;. In that&amp;#xA;case, a generic over a trait &amp;lt;code&amp;gt;fn bw_used&amp;amp;lt;T: DataSize&amp;amp;gt;(size: T)&amp;lt;/code&amp;gt; might work&amp;#xA;best, where &amp;lt;code&amp;gt;trait DataSize&amp;lt;/code&amp;gt; enforces a &amp;lt;code&amp;gt;as_kilobytes()&amp;lt;/code&amp;gt; interface and the type&amp;#xA;system ensures that whatever we pass in, has this method.&amp;#xA;And in cases where all this doesn&amp;#39;t matter, where only the business meaning&amp;#xA;and validation is of importance, we can bind to a more generic Value Object type.&amp;#xA;In our example, the &amp;lt;code&amp;gt;HttpResponseCode&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;EmailAddress&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In the example, I would go for a PageSize, but clarify the unit in the&amp;#xA;constructor and a reader:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;script src=&amp;#34;https://gist.github.com/berkes/69d1b785d2e98ea05c912fd0e51ce8cb.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This shows another benefit of Value Objects: they can be unit-tested, and&amp;#xA;unit-testing them is dead-easy.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In the first version, had I wanted to test anything related to converting kilo-&amp;#xA;mega etc bytes, I would probably have had to test that the email being sent,&amp;#xA;contains the correct strings. But now, I can unit-test the getters and setters&amp;#xA;on &amp;lt;code&amp;gt;PageSize&amp;lt;/code&amp;gt;, which is the most easy (and fastest) kind of test.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A last improvement is to make the Value Objects easier to use. In the&amp;#xA;intermediate version, we see that all our Value Objects must get the &amp;lt;code&amp;gt;impl Display&amp;lt;/code&amp;gt; so that we can display them. The usize, or String we had before, can&amp;#xA;easily be displayed. Whats worse: we cannot do all the common operators on our&amp;#xA;Value Objects: they cannot be added, summed, divided, etc. What if we want to&amp;#xA;add &amp;amp;quot;average page size&amp;amp;quot; to our report? We then need to extract the value. In&amp;#xA;this we&amp;#39;re lucky, because we already have the &amp;lt;code&amp;gt;as_kilobytes()&amp;lt;/code&amp;gt; getters. But&amp;#xA;another example could be a &amp;lt;code&amp;gt;ViewCount&amp;lt;/code&amp;gt; which we may want to sum up. This then&amp;#xA;needs another getter. More boilerplate code, more calls:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;script src=&amp;#34;https://gist.github.com/berkes/5bebce03c9d6aa2da01e831af9f5fd95.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Sometimes this isn&amp;#39;t an issue. Like when our getters are already there and&amp;#xA;required to disambiguate. Or when operations values don&amp;#39;t make sense: what&amp;#xA;would it even mean to have the average HTTP status code? Or to sum them up?&amp;#xA;Yet in the new example above, we&amp;#39;d be helped if we could forward any method calls,&amp;#xA;or operators, to the wrapped value. &amp;lt;a href=&amp;#34;https://doc.rust-lang.org/std/ops/trait.Deref.html&amp;#34;&amp;gt;&amp;lt;code&amp;gt;Deref&amp;lt;/code&amp;gt;&amp;lt;/a&amp;gt; can be of help:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;script src=&amp;#34;https://gist.github.com/berkes/67d8d6dc1dc7a8d143682fa3cc61f9ec.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We still have to deref any value before using it, but that is rather simple. In&amp;#xA;the example these are the&amp;lt;code&amp;gt;*vc&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;*report_title&amp;lt;/code&amp;gt;. This &amp;lt;a href=&amp;#34;https://rust-unofficial.github.io/patterns/anti_patterns/deref.html&amp;#34;&amp;gt;is considered an&amp;#xA;antipattern&amp;lt;/a&amp;gt;&amp;#xA;though. But, like always, &amp;amp;quot;it depends&amp;amp;quot;. With a very simple value object, deref&amp;#xA;makes sense: it may not be 100% semantic correct: deref is meant for custom&amp;#xA;pointer types and simple value objects can be considered such a pointer, but&amp;#xA;not entirely. For value objects that wrap multiple primitives, deref won&amp;#39;t work. And when&amp;#xA;we add semantics, like the &amp;lt;code&amp;gt;as_kilobytes()&amp;lt;/code&amp;gt; it isn&amp;#39;t needed, and would add only&amp;#xA;confusion. So use with care and be aware of the downsides. Such as:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Using this pattern gives subtly different semantics from most OO languages&amp;#xA;with regards to self. Usually it remains a reference to the sub-class, with&amp;#xA;this pattern it will be the &amp;#39;class&amp;#39; where the method is defined.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I personally don&amp;#39;t use &amp;lt;code&amp;gt;deref&amp;lt;/code&amp;gt; that often. Only early on, but I find that when&amp;#xA;a value object improves and solidifies over time, I almost always remove the&amp;#xA;deref at some point in favor of semantic getters[2].&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So, to sum up:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;If the wrapped value makes sense as primitive, and we want to allow any&amp;#xA;operations or methods to be called directly on it, &amp;lt;code&amp;gt;Deref&amp;lt;/code&amp;gt; can be of help.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;If the wrapped value is ambiguous, named getters rather than a generic one, allow&amp;#xA;us to return a Primitive on which we can operate as we want.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;If the wrapped value make no business sense as Primitive (e.g. our status code),&amp;#xA;we should prohibit getting to this primitive.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;If the wrapped value is made up of multiple primitives, operations don&amp;#39;t make&amp;#xA;sense: getting to the underlying primitives should be prohibited.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The last one needs some extra attention:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;multiple-values&amp;#34;&amp;gt;Multiple values&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Often a Value Object is made up from multiple values. An example would be a&amp;#xA;&amp;lt;code&amp;gt;Point&amp;lt;/code&amp;gt;, coordinates in a 2d plane:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;script src=&amp;#34;https://gist.github.com/berkes/2807159461d50c9d893c30b71539807c.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is another great example where Value Objects make sense. We certainly don&amp;#39;t want to&amp;#xA;pass the two &amp;lt;code&amp;gt;lat: usize&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;lon: usize&amp;lt;/code&amp;gt; around all over the place. Not only is that &amp;lt;a href=&amp;#34;https://refactoring.guru/smells/data-clumps&amp;#34;&amp;gt;a codesmell&amp;lt;/a&amp;gt;, it is prone to errors (you &amp;lt;em&amp;gt;will&amp;lt;/em&amp;gt; swap lat and lon somewhere, you will set one but not the other if you make them &amp;lt;code&amp;gt;Option&amp;lt;/code&amp;gt;al. etc.)&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A use-case that I come across more often, though, is a &amp;lt;code&amp;gt;from: DateTime, to: DateTime&amp;lt;/code&amp;gt;. There&amp;#xA;is some obvious &amp;lt;code&amp;gt;DateRange&amp;lt;/code&amp;gt; waiting to be implemented. This &amp;lt;code&amp;gt;DateRange&amp;lt;/code&amp;gt; can ensure the from is never later than the to. It can get extra fancy helpers so that a user can ask &amp;lt;code&amp;gt;date_range.intersects(other_range)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;date_range.includes(date)&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;date_range.length_in_seconds()&amp;lt;/code&amp;gt; or such.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A third value object would be a collection. A list, queue, vector, array, etc.&amp;#xA;Most collections are fine as primitive. But quite often they lack the&amp;#xA;business-validation. E.g. a list of &amp;amp;quot;todo tasks&amp;amp;quot; cannot ever contain a Task&amp;#xA;that is &amp;amp;quot;done&amp;amp;quot;. Or a top-5 &amp;lt;code&amp;gt;Songs&amp;lt;/code&amp;gt; cannot ever contain 6 entries. But how to&amp;#xA;wrap collections ergonomically, is an entire post on itself.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;usage-of-libraries&amp;#34;&amp;gt;Usage of Libraries&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In the first example, way above, we have a &amp;lt;code&amp;gt;url: String&amp;lt;/code&amp;gt;. A URL is not a&amp;#xA;String, just as a date isn&amp;#39;t a string, or a credit-card number isn&amp;#39;t a string.&amp;#xA;It&amp;#39;s a value with meaning, validation, helpers and so on. &amp;lt;code&amp;gt;My name is Bèr&amp;lt;/code&amp;gt; is&amp;#xA;not a valid URL, yet our program accepted this as URL just fine. When dealing&amp;#xA;with URLS, you often need to extract a hostname, path, protocol and so on, also&amp;#xA;lacking in our example.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And rather than writing our own &amp;lt;code&amp;gt;Url&amp;lt;/code&amp;gt; Value Object, we can leverage one of the&amp;#xA;many crates. For example &amp;lt;a href=&amp;#34;https://docs.rs/url/latest/url/&amp;#34;&amp;gt;&amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt;&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We could use such struct provided by a library directly. Which is often Good&amp;#xA;Enough. But to limit our coupling, we could wrap it in our own struct; Value&amp;#xA;Objects are a great place to do this. They double as &amp;lt;em&amp;gt;Anti Corruption Layer&amp;lt;/em&amp;gt; in&amp;#xA;a convenient place.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In addition, wrapping it with our own version, allows different business rules.&amp;#xA;Maybe what &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt;, the crate deems valid, isn&amp;#39;t for our domain. Maybe we can&amp;#xA;only accept URLs that are https, or only ever for our own &amp;lt;code&amp;gt;example.com&amp;lt;/code&amp;gt;&amp;#xA;hostname. In that case wrapping ,and then &amp;lt;code&amp;gt;Deref&amp;lt;/code&amp;gt;ing an &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt; in our own value&amp;#xA;object, is simple and makes the API (and errors and such) consistent.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;That leaves us with the final implementation of the Value Objects:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;script src=&amp;#34;https://gist.github.com/berkes/551a9b453a6d21f59671a7d17c5d9677.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;https://play.rust-lang.org/?version=stable&amp;amp;amp;mode=debug&amp;amp;amp;edition=2021&amp;amp;amp;gist=b4215d207f16be4c3c79941eedf37772&amp;#34;&amp;gt;You can play around with this here&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This leaves a lot to be desired and improved, still. But a lot of implicit&amp;#xA;errors that were in the first version were fixed. And it clearly shows some of&amp;#xA;the neat tricks that Rust allows us to employ. Even though &amp;lt;a href=&amp;#34;https://doc.rust-lang.org/book/ch17-00-oop.html&amp;#34;&amp;gt;Rust isn&amp;#39;t Object&amp;#xA;Oriented&amp;lt;/a&amp;gt; in the traditional&amp;#xA;sense, doesn&amp;#39;t have &amp;lt;em&amp;gt;objects&amp;lt;/em&amp;gt;, we can still use the Value Object Pattern (if&amp;#xA;you may call it that) to put business rules, -logic and meaning in our Rust&amp;#xA;programs.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;conclusion&amp;#34;&amp;gt;Conclusion&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Value Objects are a great tool to bring business-meaning and -rules into our&amp;#xA;code. They allow us to fix a lot of common code-smells. And they remove ambiguity&amp;#xA;and often make our code much better readable and therefore much easier to maintain.&amp;#xA;Value Objects are a great place to add some nifty helpers and converters.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In Rust, even though there are no &amp;lt;em&amp;gt;Objects&amp;lt;/em&amp;gt; we can leverage structs, methods,&amp;#xA;traits, modules and the type checker to get Value Objects that make business sense,&amp;#xA;are valid within our domain, are ergonomic and require rather little boilerplate.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;hr&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[1] And, yes, this isn&amp;#39;t any sort of email-validation. It&amp;#39;s an example!&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[2] In Ruby, where I use Value Objects a lot too, and which is fully OOP, I find&amp;#xA;the same goes. I never just blanket forward all calls to the wrapped&amp;#xA;primitive. Because that leads to coupling with the primitive, which is one of&amp;#xA;the reasons for using value objects in the first place. Primitives also come&amp;#xA;with a very large interface (at least in Rust and Ruby they do), many of which&amp;#xA;don&amp;#39;t make sense in the domain meaning. What is &amp;lt;code&amp;gt;status_code.is_odd()&amp;lt;/code&amp;gt; or&amp;#xA;&amp;lt;code&amp;gt;status_code.len()&amp;lt;/code&amp;gt;?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>&amp;#34;How do I test X&amp;#34; is almost always answered with &amp;#34;by controlling X&amp;#34;</title>
   <link href="https://berk.es/2022/09/19/test-x-by-controlling-x/"/>
   <updated>2022-09-19T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2022/09/19/test-x-by-controlling-x/</id>
   <content type="html">&amp;lt;p&amp;gt;Last week I stumbled upon a &amp;lt;a href=&amp;#34;https://stackoverflow.com/questions/35858323/how-can-i-test-rust-methods-that-depend-on-environment-variables&amp;#34;&amp;gt;StackOverflow&amp;#xA;answer&amp;lt;/a&amp;gt;, where&amp;#xA;&amp;lt;em&amp;gt;Shepmaster&amp;lt;/em&amp;gt; wrote a great quote about software testing:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;amp;quot;How do I test X&amp;amp;quot; is almost always answered with &amp;amp;quot;by controlling X&amp;amp;quot;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is simple, and may sound trivial. But it has some interesting consequences.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;ability-to-control-x&amp;#34;&amp;gt;Ability to control X&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;First, there is the question: &amp;amp;quot;can I control X?&amp;amp;quot;. Because if you cannot, testing it&amp;#xA;becomes impossible. When &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; is some external service, or tool, for example a payment&amp;#xA;provider or email-server, and there is no way to control it, you cannot (, and&amp;#xA;therefore should not) test it.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;how-to-control-x&amp;#34;&amp;gt;How to control X&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Second is the question &amp;amp;quot;how can I control X&amp;amp;quot;. For once, this can be answered&amp;#xA;different than with &amp;lt;em&amp;gt;it depends&amp;lt;/em&amp;gt;, because we can control X, by ensuring that X&amp;#xA;is ours. By keeping it simple, and by ensuring it relies only on things that we&amp;#xA;control. That is not easy. But entire books have been written on architectural&amp;#xA;patterns that allow us to &amp;lt;strong&amp;gt;easily control X&amp;lt;/strong&amp;gt;: so that we can easily test X.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;ease-of-controlling&amp;#34;&amp;gt;Ease of controlling&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So third is the question &amp;amp;quot;how easy can I control X&amp;amp;quot;. The StackOverflow question&amp;#xA;that Shepmaster was answering was about environment variables (env vars). They are&amp;#xA;reasonably easy to control in most tests (in most languages and frameworks).&amp;#xA;But harder when you run tests in parallel because all running tests will&amp;#xA;re-use the same shared env-vars. If test 1 sets env var &amp;amp;quot;URL&amp;amp;quot; to&amp;#xA;&amp;amp;quot;http://localhost:3000&amp;amp;quot; and test 2 sets it to &amp;amp;quot;http://example.com&amp;amp;quot;, there will&amp;#xA;be conflicts. Other difficulties are that a service you are testing, may need&amp;#xA;to be restarted to pick up a change to an environment variable, or that&amp;#xA;environment variables are enforced or overridden by your OS, CI, or hosting.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Environment variables are harder to control than stuff that we designed to be&amp;#xA;controlled by us.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;An example of such an architectual pattern would be a&amp;#xA;&amp;amp;quot;config-repository-adapter&amp;amp;quot;. Some&amp;#xA;&amp;lt;a href=&amp;#34;https://alistair.cockburn.us/hexagonal-architecture/&amp;#34;&amp;gt;adapter&amp;lt;/a&amp;gt; that you can&amp;#xA;swap out. In production, dev and CI it may be use the &amp;lt;code&amp;gt;EnvVarConfig&amp;lt;/code&amp;gt;, and in&amp;#xA;test a &amp;lt;code&amp;gt;MemoryConfig&amp;lt;/code&amp;gt;. Don&amp;#39;t let the words &amp;amp;quot;adapter&amp;amp;quot; put you off. This works&amp;#xA;just as well for software that isn&amp;#39;t following some Java EnterpriseAdapterEnvVarConfigFactoryDecorator-&amp;amp;quot;pattern&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We can easily build &amp;lt;code&amp;gt;MemoryConfig&amp;lt;/code&amp;gt;  &amp;lt;strong&amp;gt;so that we own it, and can easily control it&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In the same thread, Simon Whitehead &amp;lt;a href=&amp;#34;https://stackoverflow.com/a/35858566/73673&amp;#34;&amp;gt;shows a great example in&amp;#xA;rust&amp;lt;/a&amp;gt;. If you are more familiar&amp;#xA;with Ruby, an example would be:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;Config&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;get&amp;lt;/span&amp;gt;(name)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;raise&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;NotImplementedError&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;EnvVarConfig&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&amp;amp;lt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Config&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;get&amp;lt;/span&amp;gt;(name)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;ENV&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;name&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;MemoryConfig&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&amp;amp;lt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Config&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;get&amp;lt;/span&amp;gt;(name)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    @config &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;||=&amp;lt;/span&amp;gt; {}&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    @config&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;name&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;set&amp;lt;/span&amp;gt;(name)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    @config &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;||&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; {}&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    @config&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;name&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; name&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Trivial, right? Yet testability goes way up, because we &amp;lt;em&amp;gt;control X&amp;lt;/em&amp;gt;. Accidental&amp;#xA;benefit, is that we can easily swap out EnvVarConfig for a FileConfig,&amp;#xA;&amp;lt;code&amp;gt;EncryptedVaultConfig&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;CommandlineArgsConfig&amp;lt;/code&amp;gt; if we need. Another example&amp;#xA;of the common statement that &amp;lt;em&amp;gt;easier to test software&amp;lt;/em&amp;gt; is &amp;lt;em&amp;gt;better software&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;in-unit-tests&amp;#34;&amp;gt;In unit-tests&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Because everybody has a different view of what &amp;amp;quot;units&amp;amp;quot; are, I &amp;lt;a href=&amp;#34;https://web.archive.org/web/20220823194605/http://www.codingthearchitecture.com/2015/03/08/package_by_component_and_architecturally_aligned_testing.html&amp;#34;&amp;gt;too&amp;lt;/a&amp;gt; prefer the term &amp;amp;quot;class test&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When we test a class X, how do we control the class X?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;By sending messages to it (calling methods or functions). Or by passing in&amp;#xA;stuff that we control.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We cannot control private methods or private state. So we cannot test that.&amp;#xA;This is nothing new, but &amp;lt;a href=&amp;#34;https://stackoverflow.com/search?q=how%20can%20I%20test%20private%20methods&amp;#34;&amp;gt;somehow too often forgotten&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We often don&amp;#39;t control the dependencies of that class, stuff the class X depends on,&amp;#xA;and when we cannot control them, we cannot test X. But we can ensure that the&amp;#xA;if class X depends on Y, that we control Y. Dependency injection is the most&amp;#xA;common solution.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;To illustrate:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;user_repo &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;MemoryUserRepo&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;new()&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;insert(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;username&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;berkes&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;password&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;hunter2&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;sut &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;AuthenticationService&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;new(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;user_repo&amp;lt;/span&amp;gt;: user_repo)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;assert_equal(sut&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;authenticate(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;berkes&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;hunter3&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;error, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;invalid password&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;AuthenticationService somewhere calls &amp;lt;code&amp;gt;find(username)&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;user_repo&amp;lt;/code&amp;gt; it&amp;#xA;got passed in, then checks the password using ComplexCryptography that is of no&amp;#xA;concern to the outer world. So all &amp;lt;code&amp;gt;MemoryUserRepo&amp;lt;/code&amp;gt; needs is to provide a&amp;#xA;&amp;lt;code&amp;gt;find&amp;lt;/code&amp;gt; that returns a &amp;lt;code&amp;gt;user&amp;lt;/code&amp;gt;[1] similar to how a &amp;lt;code&amp;gt;ActiveDirectoryClusterUserRepo&amp;lt;/code&amp;gt;&amp;#xA;would return a user from it&amp;#39;s 120-server big active-directory-cluster. Yet&amp;#xA;where it&amp;#39;s really tough (if not impossible) to control that giant cluster,&amp;#xA;controlling a list in memory is trivial. Hell, it could even be hardcoded in&amp;#xA;a &amp;lt;code&amp;gt;HardcodedUser.find()&amp;lt;/code&amp;gt; method if we only ever need it in this test.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Through dependency injection, &amp;lt;strong&amp;gt;when we want to test X, we can control X, because&amp;#xA;we control all the dependencies of X.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;in-integration-tests&amp;#34;&amp;gt;In integration tests&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When we test a group of classes within some boundary X, how do we control that boudary X?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;First, by ensuring that everything in the boundary stays in that boundary. By&amp;#xA;ensuring that classes in the group only interact with eachother, we can easily&amp;#xA;control the entire group through its public interface. But when elements&amp;#xA;(classes) in that boundary depend on external systems like databases, env-vars,&amp;#xA;servers or worse: stuff in boundary Y, we need to control all those.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is really another way of saying that tight coupling is bad (for testability).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The solution like above, is to ensure that everything inside the boundary&amp;#xA;depends on &amp;amp;quot;stuff outside&amp;amp;quot; through simple, easy and controllable interfaces. E.g. a ports-and-adapters style. Or just simple decorators, services or whatever pattern fits best: as long as we can swap it out when testing, its fine.&amp;#xA;Obviously: the least we have of those, the better. So &amp;amp;quot;everything within our boundary&amp;amp;quot; should stay within that boundary as much as possible.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;To illustrate:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;mail_handler &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;MemoryMailQueue&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;new()&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;payment_server &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;StripeMock&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;new()&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;sut &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Reimbursement&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;new(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;order_id&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#ae81ff&amp;#34;&amp;gt;1337&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;payment_server&amp;lt;/span&amp;gt;: payment_server, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;mail_handler&amp;lt;/span&amp;gt;: mail_handler)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;assert(sut&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;call(), &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;Reimbursement failed&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;assert(payment_server&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;requests&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;body&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;parse()&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;type, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;reimburse&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;assert_equal(mail_handler&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;sent&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;first&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;subject, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;You are reimbursed for order 1337&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Again simple dependency injection. We control the mail_handler, so we can test&amp;#xA;the mail_handler. We control the payment_server, so we can test the&amp;#xA;payment_server.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;in-end-to-end-tests&amp;#34;&amp;gt;In end-to-end tests&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In order to test the entire application, we must control our entire application.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is where things get muddy. Because what our users consider &amp;amp;quot;the entire application&amp;amp;quot; almost certainly includes &amp;lt;em&amp;gt;things&amp;lt;/em&amp;gt; that we cannot control (in our tests).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For example, we want to ensure that a notification mail is delivered, stripe is called, and what more, when reimbursing.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;As a logged in a admin, when a client paid for order 1337, and on the&amp;#xA;admin-orders page, I hit the reimburse button on order 337. Then the money&amp;#xA;should be reimbursed, and a notification mail sent out to the client.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We cannot control Stripe. Nor can we control the mailserver. But we can replace them with services that act nearly similar and that we &amp;lt;em&amp;gt;can&amp;lt;/em&amp;gt; control.).&amp;#xA;For email-servers, there is e.g. &amp;lt;a href=&amp;#34;https://testmail.app/&amp;#34;&amp;gt;testmail.app&amp;lt;/a&amp;gt;. Many larger mail-delivery services have such features built in, e.g. Sendgrid allows you to check if a mail was delivered by &amp;lt;a href=&amp;#34;https://docs.sendgrid.com/api-reference/e-mail-activity/filter-all-messages&amp;#34;&amp;gt;checking that an email was sent through their API&amp;lt;/a&amp;gt;. Or stripe allows you to &amp;lt;a href=&amp;#34;https://stripe.com/docs/testing&amp;#34;&amp;gt;interact with their API in testing mode&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is complex, slow and fragile. But that is expected for end-to-end tests. Which is why the &amp;lt;a href=&amp;#34;https://martinfowler.com/bliki/TestPyramid.html&amp;#34;&amp;gt;testing pyramid&amp;lt;/a&amp;gt; puts them at the top: you want least of these. You want to depend least on these. Exactly because &amp;lt;em&amp;gt;Controlling X, when X is the outside world, is tough. And so that makes testing X tough&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But in this case, not clear from the use-case, another important thing to&amp;#xA;control is the application state. &amp;lt;em&amp;gt;As an admin&amp;lt;/em&amp;gt; implies that there somehow are&amp;#xA;admins, and that we are logged as one. &amp;lt;em&amp;gt;Hitting the reimburse button on order&amp;#xA;1337&amp;lt;/em&amp;gt; implies that order 1337 exists, and is in a state that it can be&amp;#xA;reimbursed.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When all this is a single database, controlling that isn&amp;#39;t &amp;lt;em&amp;gt;too&amp;lt;/em&amp;gt; hard. We could&amp;#xA;just poke around in that database from our tests and generate the correct&amp;#xA;records. It becomes harder when this database often changes. It becomes even&amp;#xA;harder if some of this state lives externally. The admin in this case mightn&amp;#39;t&amp;#xA;notice that the authorization is done on an external service or that the order&amp;#xA;was filled from some event-stream, rather than a relational database. So I think that&amp;#xA;our tests here shouldn&amp;#39;t deal with those details either.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I, therefore, prefer to drive all these &amp;amp;quot;state&amp;amp;quot; through the public UI. There&amp;#xA;must be some place where we can add admins, or where admins can log in, or where&amp;#xA;clients can place orders. &amp;amp;quot;Just&amp;amp;quot; walk through all these screens from the test and&amp;#xA;you should end up with a state where you can start testing the actual feature.&amp;#xA;It&amp;#39;s a quite extreme form of &amp;amp;quot;only use the public interface when testing&amp;amp;quot;,&amp;#xA;whatever works for you. But, for end-to-end tests, which test the public&amp;#xA;interface, the only real public interface is, well, the public interface[4].&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If you &amp;lt;strong&amp;gt;must test the entire application, we must control not just the&amp;#xA;application, but all its external dependencies&amp;lt;/strong&amp;gt;. Which is impossible in&amp;#xA;practice. But we can get closer. The fewer of those we have, the easier it&amp;#xA;becomes. So if we declare &amp;amp;quot;the database&amp;amp;quot;, or &amp;amp;quot;the single-sign-on authentication&amp;#xA;service&amp;amp;quot; as not some external application, but as part of the application, we&amp;#xA;need not control them directly: we can control them through their public&amp;#xA;interface!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;mocking-and-stubbing&amp;#34;&amp;gt;Mocking and stubbing.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Stubbing means that we &amp;lt;em&amp;gt;control X by replacing parts of it, with something that&amp;#xA;we control&amp;lt;/em&amp;gt;.  But subtly different to &amp;lt;em&amp;gt;dependency injection&amp;lt;/em&amp;gt;: If we stub e.g.&amp;#xA;the &amp;lt;code&amp;gt;random()&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;Math.random()&amp;lt;/code&amp;gt;, we replace only that function with one that we&amp;#xA;control (and that always returns 42, for example), we aren&amp;#39;t really&amp;#xA;controlling &amp;lt;code&amp;gt;Math&amp;lt;/code&amp;gt;, we are really poking around and replacing behaviour at&amp;#xA;runtime (this won&amp;#39;t work for many statically typed languages, for good&amp;#xA;reasons too). Yet when we inject the subject-under-test with our own implementation of&amp;#xA;&amp;lt;code&amp;gt;Math&amp;lt;/code&amp;gt;, in which we control what the function &amp;lt;code&amp;gt;random()&amp;lt;/code&amp;gt; does, we use&amp;#xA;dependency injecting.[2]&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;In order to test X, when that depends on Y, we want to completely control Y,&amp;#xA;not just one detail in Y&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Coincidentally, this leads to looser coupling.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Mocking means that we inject our own implementations of Y, which is what the&amp;#xA;whole &amp;lt;em&amp;gt;dependency injection&amp;lt;/em&amp;gt; is about. But mocking comes in many flavours. For one,&amp;#xA;it is often misused as alias for stubbing. More correctly, though, it means &amp;lt;a href=&amp;#34;https://stackoverflow.com/a/2666006/73673&amp;#34;&amp;gt;creating&amp;#xA;objects that simulate the behaviour of a real&amp;#xA;object&amp;lt;/a&amp;gt;. Yet &amp;lt;em&amp;gt;to simulate&amp;lt;/em&amp;gt; is vague.&amp;#xA;Again, &amp;lt;em&amp;gt;Shepmaster&amp;lt;/em&amp;gt;s&amp;#39; quote can help: A mock is something that &amp;lt;strong&amp;gt;the tests&amp;#xA;control, to replace something that the test would otherwise not control&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;amp;quot;adapters&amp;amp;quot; mentioned above are therefore very simple mocks. The more complex&amp;#xA;mocks need to be, the harder it becomes to control them. This implies another&amp;#xA;issue, though: the more complex mocks need to be, the harder it is to test. Which&amp;#xA;is a sign that the code we are testing, probably isn&amp;#39;t good.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;To turn it around: When a &amp;lt;em&amp;gt;subject under test&amp;lt;/em&amp;gt;, X, depends on Y, and you&amp;#xA;&amp;lt;em&amp;gt;need&amp;lt;/em&amp;gt; complex behaviour to simulate Y, then X is depending too much on Y.&amp;#xA;Either Y should be part of the &amp;amp;quot;unit&amp;amp;quot; (bounded context, module, whatever you&amp;#xA;name your compartments), &amp;lt;em&amp;gt;or&amp;lt;/em&amp;gt; Y and X should be decoupled more.[3]&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Coincidentally, this leads to the software design principle of &amp;lt;a href=&amp;#34;https://wiki.c2.com/?CouplingAndCohesion&amp;#34;&amp;gt;increased&amp;#xA;cohesion and looser coupling&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;conclusion&amp;#34;&amp;gt;Conclusion&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The phrase&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;amp;quot;How do I test X&amp;amp;quot; is almost always answered with &amp;amp;quot;by controlling X&amp;amp;quot;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;is provoking, simple and has some interesting consequences. But I think it&amp;#39;s not&amp;#xA;entirely complete. I miss the dependencies. My version would therefore be;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;amp;quot;How do I test X&amp;amp;quot; is almost always answered with &amp;amp;quot;by controlling X and&amp;#xA;everything that X depends on&amp;amp;quot;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;hr&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[1] Turtles-all-the-way-down, though: this &amp;lt;code&amp;gt;user&amp;lt;/code&amp;gt;, obviously, should not depend&amp;#xA;on a specific database implementation, ideally it would be an immutable, flat, simple&amp;#xA;struct (e.g. a value-object). But certainly not some &amp;lt;em&amp;gt;model&amp;lt;/em&amp;gt; that itself&amp;#xA;relies on availability of database-servers, event-streams, or other&amp;#xA;subsystems to fill itself. Or more precise: the adapter that our tests control&amp;#xA;should return such a simple version. If the &amp;amp;quot;actual&amp;amp;quot; adapter returns some&amp;#xA;complex, wired-up object: fine. As long as our AuthenticationService does not&amp;#xA;rely on all that complexity and wiring, we are fine: we reward ourselves with&amp;#xA;loose-coupling by being lazy!&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[2] A stub, however, can be a mock. I know... I use &amp;lt;code&amp;gt;stub&amp;lt;/code&amp;gt; here in the sense it is&amp;#xA;commonly used in testing: to replace a single method. Not to replace a&amp;#xA;module, class or subsystem with one that we control: that would be &amp;lt;em&amp;gt;mocking&amp;lt;/em&amp;gt;.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[3] Which I why I, and many people with me, often say we dislike &amp;lt;em&amp;gt;mocking&amp;lt;/em&amp;gt;. I&amp;#xA;&amp;lt;em&amp;gt;like&amp;lt;/em&amp;gt; dependency injection. And the things I inject &amp;lt;em&amp;gt;can&amp;lt;/em&amp;gt; be mocks, but I&amp;#xA;prefer them to be the real thing. Yet when &amp;lt;em&amp;gt;the real dependency&amp;lt;/em&amp;gt; cannot be&amp;#xA;controlled easily, then that is a sign of trouble. In other words: if you&amp;#xA;need to mock, you probably have a deeper problem. Maybe that cannot be&amp;#xA;solved, in which case a mock is a band-aid. And when you consider &amp;amp;quot;test&amp;#xA;adapters&amp;amp;quot; as &amp;lt;em&amp;gt;mocks&amp;lt;/em&amp;gt;, then sure, mocking is proper. But only in certain&amp;#xA;layers and use-cases.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[4] Or I sincerely do hope that clients in your e-commerce system cannot&amp;#xA;place orders by writing records to your database directly...&amp;#xA;There are many downsides to this setup. But also many upsides. A topic for another post.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Using a Framework will harm the maintenance of your software</title>
   <link href="https://berk.es/2022/09/06/frameworks-harm-maintenance/"/>
   <updated>2022-09-06T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2022/09/06/frameworks-harm-maintenance/</id>
   <content type="html">&amp;lt;p&amp;gt;In this article I&amp;#39;m putting together my quotes, thoughts and notes on the idea&amp;#xA;that Frameworks harm the maintainability of the software you build in that&amp;#xA;framework. I&amp;#39;m proposing that Frameworks:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;are harming maintainability, but not deliberate.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;have different goals than you or your team.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;make trade-offs that harm maintainability of the projects built in them.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;are designed to take your project hostage.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;offer some their benefits, and &amp;lt;em&amp;gt;don&amp;#39;t&amp;lt;/em&amp;gt; harm maintainability, when used&amp;#xA;in a decoupled fashion.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;what-is-a-framework-and-what-isnt&amp;#34;&amp;gt;What is a Framework and what isn&amp;#39;t.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In this article when talking about a framework, I mean a narrow&amp;#xA;definition. Not just any third party code used, and not&amp;#xA;just a methodology or architecture:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;[...] a software framework is an abstraction in which software, providing&amp;#xA;generic functionality, can be selectively changed by &amp;lt;strong&amp;gt;additional&amp;#xA;user-written code&amp;lt;/strong&amp;gt;, thus providing application-specific software. [..]&amp;#xA;Frameworks have key distinguishing features that separate them from normal libraries:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;inversion of control: In a framework, unlike in libraries or in standard&amp;#xA;user applications, the &amp;lt;strong&amp;gt;overall program&amp;#39;s flow of control is not dictated by&amp;#xA;the caller, but by the framework&amp;lt;/strong&amp;gt;. This is usually achieved with the&amp;#xA;Template Method Pattern.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[...]&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;extensibility: A user can extend the framework – usually by selective&amp;#xA;overriding – or programmers can &amp;lt;strong&amp;gt;add specialized user code to provide&amp;#xA;specific functionality&amp;lt;/strong&amp;gt;. [...]&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;non-modifiable framework code: The framework code, in general, is not&amp;#xA;supposed to be modified, while accepting user-implemented extensions. In&amp;#xA;other words, &amp;lt;strong&amp;gt;users can extend the framework, but cannot modify its code.&amp;lt;/strong&amp;gt;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Emphasis mine, from &amp;lt;a href=&amp;#34;https://en.wikipedia.org/wiki/Software_framework&amp;#34;&amp;gt;wikipedia&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Frameworks, by definition then, offer functionality, behaviour, flow and&amp;#xA;defaults all of which are built in, some of which is unchangeable or dictated.&amp;#xA;They allow a user to add code, rather then change the external code.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The maintenance-problems that frameworks can introduce, apply to all&amp;#xA;software-frameworks, but my experience is limited to frameworks for &amp;lt;em&amp;gt;web&amp;#xA;services&amp;lt;/em&amp;gt; (API, backends, full-stack), &amp;lt;em&amp;gt;commandline&amp;lt;/em&amp;gt; and &amp;lt;em&amp;gt;GUI&amp;lt;/em&amp;gt;. The examples&amp;#xA;limit to web-frameworks only. Because in 2022, more and more software is on the&amp;#xA;web, or moving there.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;People use frameworks, because it is supposed to make software-development more&amp;#xA;standardised, faster, easier, more secure, better scaleable, more consistent or&amp;#xA;&amp;lt;em&amp;gt;more fun&amp;lt;/em&amp;gt;. Ironically, the Wikipedia lemma doesn&amp;#39;t provide any benefits for&amp;#xA;using Frameworks, only downsides.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The idea behind the Standardisation, is that developers are forced to work in a&amp;#xA;predefined way. That organisation of code is enforced and that APIs and logic&amp;#xA;becomes recognisable across projects that use the same framework. Yet the only&amp;#xA;scientific-ish prove I found hints at the opposite. The &amp;lt;a href=&amp;#34;https://puppet.com/blog/2021-state-of-devops-report/&amp;#34;&amp;gt;State Of the DevOps&amp;#xA;report&amp;lt;/a&amp;gt; indicates that&amp;#xA;technology such as use of frameworks, matters little for success. And enforcing&amp;#xA;those, even less so.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Companies that have..&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A team that defines the standards, processes, practices,&amp;#xA;frameworks or architectures that other teams must follow.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;...are amongst the lowest performers. Reversed: companies that lack this,&amp;#xA;amongst the high performers.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In other words: enforced standardising the tech, doesn&amp;#39;t pay off.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This makes sense: if everyone in a company is forced to use, say, Django, for&amp;#xA;any project, regardless, there will be a lot of projects where Django is a very&amp;#xA;poor choice.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;That still allows a framework to offer other benefits within a project or&amp;#xA;within a team though. But the &amp;lt;em&amp;gt;standardisation&amp;lt;/em&amp;gt; (and consistency) benefit&amp;#xA;seems to not exist, and even be opposite.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The attributes of speed (of development), &amp;lt;em&amp;gt;more fun&amp;lt;/em&amp;gt; and ease very much&amp;#xA;depend on what phase a project is in. A framework tool that generates the&amp;#xA;code for the models saves you writing initial code[1]. I might save . But&amp;#xA;on the scale of seven people developing that software for over a decade,&amp;#xA;that half an hour is insignificant. Especially because over such a long&amp;#xA;period of time maybe hundreds such models can be generated, yet all the&amp;#xA;other tens of thousands of hours are spent modifying and maintaining&amp;#xA;existing code. Below I&amp;#39;ll show how this &amp;amp;quot;speed of development&amp;amp;quot; actually&amp;#xA;counters the speed of development over longer periods of time:&amp;#xA;harming maintenance.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And the attributes of security and performance are very&amp;#xA;context-dependant. Frameworks, by definition, add a lot of code to a&amp;#xA;project. Which at best isn&amp;#39;t in the way, but at worse offers a very big attack&amp;#xA;surface and a giant amount of overhead. Below I will show how these&amp;#xA;attributes can be achieved, easier even, by not using a framework.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;what-is-harming-maintenance&amp;#34;&amp;gt;What is &amp;amp;quot;harming maintenance.&amp;amp;quot;&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When we launch our software, and it becomes a success, in the sense that&amp;#xA;it is used, we will maintain it (or we should). Maintenance is typically categorized:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Corrective Software Maintenance - AKA bugfixes&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Preventative Software Maintenance - AKA preventing the bugs, stability&amp;#xA;improvements&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Perfective Software Maintenance - AKA finishing touch.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Adaptive Software Maintenance - AKA continuous development.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In this article, though, I consider any changes to software after it is&amp;#xA;put to used, to be &amp;lt;em&amp;gt;maintenance&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Anything that slows down such continued development over any period of&amp;#xA;time, I consider &amp;lt;em&amp;gt;harming it&amp;lt;/em&amp;gt;. So if the use of framework slows down&amp;#xA;shipping of new features today, it is causing harm.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And when the use of a framework allows shipping features fast early on, at the&amp;#xA;cost of slowing down the shipping of new features or changes later on, that is&amp;#xA;harming maintenance.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A third type of harm is when the framework diverts resources into work&amp;#xA;that has nothing to do with delivering value to your customers. Work&amp;#xA;like upgrading, deprecation, education and information ingestion&amp;#xA;(learning about new features, e.g.). Those take away valuable (and often&amp;#xA;scarce) resources. All the hours you spend upgrading your stack&amp;#xA;are not spent delivering new features that users or market want.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And a last type of harm, is when a framework that was once a good fit for a&amp;#xA;project, is no longer a good fit. Because either the framework moved in a&amp;#xA;different direction, or because the software built in that framework moved in a&amp;#xA;direction that no longer fits the ideas or ideal use-case of the framework.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;frameworks-have-different-goals-than-you-or-your-team&amp;#34;&amp;gt;Frameworks have different goals than you or your team.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And yet despite the huge commitment you&amp;#39;ve made to the framework, the&amp;#xA;framework has made no reciprocal commitment to you at all. That&amp;#xA;framework is free to evolve in any direction that pleases its author.&amp;#xA;When it does, you realize that that you are simply going to have to&amp;#xA;follow along like a faithful puppy.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Here&amp;#39;s what &amp;lt;a href=&amp;#34;https://www.flickr.com/photos/planetargon/127984254/&amp;#34;&amp;gt;DHH apparently has to&amp;#xA;say&amp;lt;/a&amp;gt;, about your&amp;#xA;concerns of the direction he is taking his framework. Most frameworks authors&amp;#xA;aren&amp;#39;t that hostile, though. They honestly care about their users, I&amp;#39;m certain.&amp;#xA;DHH probably does care about how happy you are when using Rails too, even when&amp;#xA;he doesn&amp;#39;t express that as clear. But then too all these authors care more&amp;#xA;about &amp;lt;em&amp;gt;onboarding people&amp;lt;/em&amp;gt; and &amp;lt;em&amp;gt;keeping people on board&amp;lt;/em&amp;gt; than that you can still&amp;#xA;continue to deliver value in fifteen, twenty years.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When looking at the &amp;lt;em&amp;gt;marketing&amp;lt;/em&amp;gt; of a series of popular web-frameworks,&amp;#xA;we see that of &amp;lt;a href=&amp;#34;https://www.djangoproject.com/start/overview/&amp;#34;&amp;gt;Django&amp;lt;/a&amp;gt;,&amp;#xA;&amp;lt;a href=&amp;#34;https://rubyonrails.org/&amp;#34;&amp;gt;Rails&amp;lt;/a&amp;gt;,&amp;#xA;&amp;lt;a href=&amp;#34;https://spring.io/why-spring&amp;#34;&amp;gt;Spring&amp;lt;/a&amp;gt;,&amp;#xA;&amp;lt;a href=&amp;#34;https://www.gatsbyjs.com/&amp;#34;&amp;gt;Gatsby&amp;lt;/a&amp;gt;, and &amp;lt;a href=&amp;#34;&amp;#34;&amp;gt;Symfony&amp;lt;/a&amp;gt; only the last&amp;#xA;mentions maintenance or Maintainability: (emphasis mine)&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Speed up the creation &amp;lt;strong&amp;gt;and maintenance&amp;lt;/strong&amp;gt; of your PHP web applications.&amp;#xA;End repetitive coding tasks and enjoy the power of controlling your&amp;#xA;code.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Though how they provide this, is handwavy:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;and the use of Best Practices guarantees the stability, maintainability and upgrade-ability of the applications you develop.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Slightly less provoking as DHH above, is the more official stance of&amp;#xA;Rails on how it supports you over a longer period of time:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When a release series is no longer supported, it’s your own&amp;#xA;responsibility to deal with bugs and security issues. We may provide&amp;#xA;backports of the fixes and publish them to git, however there will be&amp;#xA;no new versions released. If you are not comfortable maintaining your&amp;#xA;own versions, you should upgrade to a supported version.&amp;#xA;-- &amp;lt;a href=&amp;#34;https://rubyonrails.org/maintenance&amp;#34;&amp;gt;https://rubyonrails.org/maintenance&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;At least it&amp;#39;s honest and clear: the framework won&amp;#39;t support you over&amp;#xA;time. You&amp;#39;ll have to divert resources in updating and migrating your&amp;#xA;project to keep your project on recent versions of Rails.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But even when the frameworks&amp;#39; goals and yours perfectly align, you cannot&amp;#xA;predict the future. Especially at the start of a project, this future is&amp;#xA;unsure. Will the product always be a web-app? Are we certain we will only&amp;#xA;ever release this application for Windows desktops? Do we know for sure that a&amp;#xA;relational-database is the best storage in future? Do we need that&amp;#xA;scalability? Are we certain we don&amp;#39;t need that scalability? Will there be&amp;#xA;javascript PWAs in ten years? Twenty?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Yet, when building your product &amp;lt;em&amp;gt;in&amp;lt;/em&amp;gt; a framework, you choose, very early on,&amp;#xA;to marry it. Forever together. At a moment that you have the least information&amp;#xA;to make that commitment, you are making it.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;frameworks-make-trade-offs-that-harm-maintainability-of-the-projects-built-in-them&amp;#34;&amp;gt;Frameworks make trade-offs that harm maintainability of the projects built in them.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Like with any software, the authors of Frameworks, must make trade-offs.&amp;#xA;For example, reading the payoffs on their websites, we can clearly see that&amp;#xA;all popular frameworks value speed of development and scalability most of all.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Yet, both those traits are perpendicular to maintainability. At worst they&amp;#xA;hamper maintainability, at best they don&amp;#39;t improve it.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Speed of development is sometimes achieved through code-generation&amp;#xA;(boilerplate), but more often through inheritance. When the framework generates&amp;#xA;code for you, it creates the code, but doesn&amp;#39;t maintain it. E.g. a &amp;lt;em&amp;gt;framework&amp;lt;/em&amp;gt;&amp;#xA;like react-boilerplate or create-react-app work this way. They are really&amp;#xA;&amp;amp;quot;just&amp;amp;quot; fancy code-generators. Code that must be maintained or else it will&amp;#xA;degrade. Will accumulate duplication, inconsistencies, incompatibilities, etc.&amp;#xA;So called &amp;amp;quot;code rot&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The other method is where the framework solves this code-rot problem. By&amp;#xA;sticking all that code in superclasses (or reusable functions), it offers such&amp;#xA;&amp;amp;quot;code&amp;amp;quot; in a single, often logical place instead of spread out.&amp;#xA;As a user (a developer using the framework), you inherit a class, or mix in a&amp;#xA;class, module, or function. The framework is injected and through this injection&amp;#xA;offers an API for you to use.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;E.g. with Rails, the default single inheritance for what is considered &amp;amp;quot;A&amp;#xA;model&amp;amp;quot;, adds a mind-boggling public surface to your objects. E.g. a Post,&amp;#xA;having three fields in the database:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-Ruby&amp;#34; data-lang=&amp;#34;Ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;Post&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&amp;amp;lt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;ActiveRecord&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;::&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Base&amp;lt;/span&amp;gt;; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;This gives you no less than 767 public class methods and 487 public&amp;#xA;instance methods: you inherit over 1200 methods by subclassing[2]!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Because your projects&amp;#39; Post class now provides these, &amp;lt;strong&amp;gt;you are&amp;#xA;responsible for maintaining them&amp;lt;/strong&amp;gt;. After all: &amp;lt;em&amp;gt;your&amp;lt;/em&amp;gt; class offers them to&amp;#xA;its users. These methods live on your class, your instances.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But they live deep down in the code of the framework. You are responsible, but&amp;#xA;lack the ability or power to maintain them. It&amp;#39;s in the definition of what&amp;#xA;makes it a framework that you cannot change this, cannot own it.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The framework now might decide that at some point a method like&amp;#xA;&amp;lt;code&amp;gt;title_came_from_user?&amp;lt;/code&amp;gt; method may be deprecated or changed. We now offer a&amp;#xA;large public interface by mixing framework into our project, we will use that&amp;#xA;throughout our code but hold no power over it. We are at the mercy of updates,&amp;#xA;backwards-compatibility-guarantees or goodwill and availability of the&amp;#xA;framework authors. Some of which may dismiss your worries with an R-rated&amp;#xA;slide. Most of which, though, are more friendly but lack the resources to keep&amp;#xA;all this API stable forever. Yet a framework such as Drupal (if one may call it&amp;#xA;a framework), comes with an upgrade that is so immense, in practice it means a&amp;#xA;complete rewrite of the project. Every few years! Others are friendlier and try&amp;#xA;to remain backwards compatible, or offer much smaller upgrade steps. But all,&amp;#xA;each and every one, has updates. That we must follow. On which we must act.&amp;#xA;Which occasionally require us to change existing code.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Many frameworks aren&amp;#39;t as extreme as Rails with its public interface of over&amp;#xA;1200 methods. But all offer an API, functions, classes, to be used by the&amp;#xA;user of the framework: it&amp;#39;s the whole point of the framework to offer this!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We will use this code. We will then, over time, couple our code ever more tightly&amp;#xA;to the framework. Up to a point where it is completely dependent on that&amp;#xA;framework.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is also why people generally say that one develops &amp;lt;em&amp;gt;in a framework&amp;lt;/em&amp;gt;, not&amp;#xA;&amp;lt;em&amp;gt;with a framework&amp;lt;/em&amp;gt;. You build your project &amp;lt;em&amp;gt;in&amp;lt;/em&amp;gt; the framework.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And the performance or scalability they guarantee is performance &amp;lt;em&amp;gt;compared to&amp;#xA;other similar frameworks&amp;lt;/em&amp;gt;. Software is made more performant and better scalable&amp;#xA;through architectural choices, low level optimizations and, above all, less&amp;#xA;code. Not more. And it is somewhat of a false-flag operation: frameworks have&amp;#xA;often been in the news because they were the source of visible performance&amp;#xA;issues for projects built in such frameworks. Rails&amp;#39; became known for its bad&amp;#xA;performance through twitters&amp;#39; Fail-Whale and then Twitter announcing it rewrote&amp;#xA;its Rails codebase in Java. It&amp;#39;s a statement to divert from the fact that most&amp;#xA;frameworks add significant performance overhead on a project.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;All of the common solutions to scaling and performance-issues: architectural&amp;#xA;choices, low-level optimizations, and &amp;amp;quot;less code&amp;amp;quot; require the freedom to change&amp;#xA;our code when we find we have performance issues. Choices and optimizations we&amp;#xA;can only make later on, when we have information. If anything, frameworks harm&amp;#xA;our scalability, because they make it harder to move to other frameworks,&amp;#xA;architectures or set-ups that fit our usage-profiles better. When you get&amp;#xA;Fail-Whales, you want to optimize problematic code, not rewrite everything in&amp;#xA;Java (or Rust, its 2022 after all).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;frameworks-are-designed-to-take-your-project-hostage&amp;#34;&amp;gt;Frameworks are designed to take your project hostage.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;By mixing in framework code, and by using it, a project becomes tightly bound&amp;#xA;to that framework. Every time we write a &amp;lt;code&amp;gt;belongs_to(:author)&amp;lt;/code&amp;gt; in Rails or a&amp;#xA;&amp;lt;code&amp;gt;models.ForeignKey(&amp;amp;quot;Band&amp;amp;quot;)&amp;lt;/code&amp;gt; in Django we are binding your project more tightly&amp;#xA;to the framework.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Good, maintainable binding, is when we have a tiny surface where we&amp;#xA;bind our domain to the framework. Bad, hard to maintain binding is when this&amp;#xA;surface is big, fuzzy or completely absent. Even worse, even harder to maintain, is&amp;#xA;when our domain and businesslogic gets intermixed with framework code. When&amp;#xA;high-level business-concepts mix up with low-level delivery mechanisms. When&amp;#xA;business-logic gets spread throughout those delivery mechanisms and we must read&amp;#xA;through controllers, views, models, factories, services, configuration-files,&amp;#xA;libraries, framework code, just to understand why a User may be created in case&amp;#xA;A, but not in case B.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Frameworks abstract away many of the technical details. They most often&amp;#xA;provide an ORM that abstracts away how we deal with a database,&amp;#xA;sometimes to a point that developers don&amp;#39;t have to know they are using a&amp;#xA;database at all. Just call a &amp;lt;code&amp;gt;model.save&amp;lt;/code&amp;gt; or a &amp;lt;code&amp;gt;User.find_by(email: &amp;amp;quot;example.com&amp;amp;quot;)&amp;lt;/code&amp;gt; and it will save or fetch data regardless of whether it&amp;#xA;lives in PostgreSQL, sqlite or even MongoDB.&amp;#xA;The trade-off here is that you now don&amp;#39;t bind to a specific database, but bind&amp;#xA;to the ORM and Framework. You get freedom to use any database, at the cost of&amp;#xA;the freedom to use another ORM and framework.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Delivery mechanisms like HTTP, storage (like databases), event-buses,&amp;#xA;logging, messaging, all of these are details. They are irrelevant to&amp;#xA;your business-logic, you domain.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The architecture of an accounting app should scream &amp;amp;quot;accounting&amp;amp;quot; not Spring &amp;amp;amp; Hibernate.&amp;#xA;&amp;lt;a href=&amp;#34;https://twitter.com/unclebobmartin/status/118365858581069824&amp;#34;&amp;gt;@unclebobmartin&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But frameworks don&amp;#39;t just put themselves up front, they encourage mixing up&amp;#xA;this logic. They offer their API, classes, functions for us to use throughout&amp;#xA;our business-logic. So not only is our code then tightly coupled to framework&amp;#xA;code, it gets mixed up. Even worse, they often encourage us to spread this&amp;#xA;business-logic all through these &amp;amp;quot;details&amp;amp;quot;. In a web-MVC the M is the storage,&amp;#xA;the V the template and the C the http layer. There is no single, logical place&amp;#xA;to keep your domain code: the framework actively encourages you to just drop&amp;#xA;it wherever it happens to be easiest. Rather than where it will keep your code&amp;#xA;most maintainable.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It is not uncommon for a project in a framework to look something like:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-Ruby&amp;#34; data-lang=&amp;#34;Ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;create&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;if&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;User&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;exists?(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;email&amp;lt;/span&amp;gt;: params&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:email&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt;) &amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    render &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:new&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;status&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:already_exists&amp;lt;/span&amp;gt; &amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;elsif&amp;lt;/span&amp;gt; user&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;save&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    flash&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:success&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; flash_message_for(@user, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:successfully_created&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    redirect_to edit_admin_user_path(@user)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;else&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    render &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:new&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;status&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:unprocessable_entity&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;user_params&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  params&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;require(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:user&amp;lt;/span&amp;gt;)&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;permit(permitted_user_attributes &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;|&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;                               &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:use_billing&amp;lt;/span&amp;gt;,&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;                                &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;role_ids&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[]&amp;lt;/span&amp;gt;,&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;                                &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;ship_address_attributes&amp;lt;/span&amp;gt;: permitted_address_attributes,&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;                                &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;bill_address_attributes&amp;lt;/span&amp;gt;: permitted_address_attributes&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;When we read closely through this, we experience a rollercoaster ride. There is&amp;#xA;hardly any cohesion, and we jump from Domain logic, via Framework APIs to&amp;#xA;delivery-mechanism-details to security-details, to businesslogic and back. Up&amp;#xA;and down the stack. There is quite some business-logic in what should be purely&amp;#xA;a HTTP-layer.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In a clean, or screaming, or hexagonal, or any layered architecture, we&amp;#xA;separate those concerns and avoid mixing them up, while keeping the&amp;#xA;business-logic contained to a single place.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Frameworks don&amp;#39;t play an important role there, that&amp;#39;s the whole point of a&amp;#xA;self-contained, isolated domain area (or layer). Such domain-code&amp;#xA;doesn&amp;#39;t rely on &amp;lt;em&amp;gt;details&amp;lt;/em&amp;gt; such as how to deserialize JSON, HTTP-headers,&amp;#xA;database-transactions, connection-pools, etc. Such a domain merely cares&amp;#xA;about it&amp;#39;s domain language: it calls an abstract &amp;lt;code&amp;gt;posts_repository.create(post)&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Such systems are far better maintainable, because the role of all code&amp;#xA;is clear. It is isolated and has great cohesion. If ever you change&amp;#xA;anything about how Posts are stored in the repository (you move away&amp;#xA;from that MongoDB, and choose to just write out markdown files on disk)&amp;#xA;this change is isolated to the &amp;lt;code&amp;gt;PostsRepository&amp;lt;/code&amp;gt; and that alone. Nothing in&amp;#xA;your business-logic needs to be touched.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When you move such details to the side, into isolated, bound layers only, the&amp;#xA;software becomes more maintainable, because changes are isolated. And with such&amp;#xA;an architecture, a framework, if used at all, is moved aside as well. It lives&amp;#xA;in the fringes of your application. It can be easily replaced one piece at a time.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;frameworks-offer-some-their-benefits-and-dont-harm-maintainability-when-used-in-a-decoupled-fashion&amp;#34;&amp;gt;Frameworks offer some their benefits, and &amp;lt;em&amp;gt;don&amp;#39;t&amp;lt;/em&amp;gt; harm maintainability, when used in a decoupled fashion.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Many people will argue that &amp;lt;em&amp;gt;not using a framework&amp;lt;/em&amp;gt; means &amp;lt;em&amp;gt;write&amp;#xA;everything yourself&amp;lt;/em&amp;gt;. This is a false dichotomy. We can use libraries&amp;#xA;and frameworks just fine. We should use code. Should avoid writing the same&amp;#xA;logic over and over. We should depend on (security)experts for&amp;#xA;security-critical code. We should not write our own cryptography, or&amp;#xA;password-handling if it can be avoided. We should use off-the-shelve libraries&amp;#xA;for this.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But we should give them a clear, and well-isolated place in our project.&amp;#xA;The code that routes HTTP-paths to method-calls (or commands or whatever&amp;#xA;your domain offers at its boundary) lives in aside, in a HTTP layer, it&amp;#39;s a&amp;#xA;detail. And it certainly not ever deals with business-logic. The more&amp;#xA;isolation, the better maintainable.&amp;#xA;The code that handles e.g. token authentication should not be written by us, but&amp;#xA;be included in a single, well contained, bounded area. One that encapsulates this&amp;#xA;and translates it into domain language, preferably. E.g. behind a&amp;#xA;&amp;lt;code&amp;gt;authentication.is_known_as_admin(request.token)&amp;lt;/code&amp;gt; rather than sprinkled throughout&amp;#xA;our controllers, commandline-interfaces, scripts, or async jobs.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The code that sends out messages, is called by your domain as a simple&amp;#xA;&amp;lt;code&amp;gt;messenger.deliver(recipent, body)&amp;lt;/code&amp;gt;. Behind that method call might be an entire&amp;#xA;message-delivery framework, one that does exponential retries, buffering, smart&amp;#xA;routing, can handle both push-notifications and emails, etc.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The code that persists the Expenses, is called by your domain as a simple&amp;#xA;&amp;lt;code&amp;gt;expenses_repository.add(expense)&amp;lt;/code&amp;gt;. And might use the most complex&amp;#xA;distributed-database framework on earth. Or it might use a fancy framework to&amp;#xA;push expenses into an online accounting tool.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The point is not to never use frameworks, but to isolate them. To call them&amp;#xA;from a single place. One that we own. That we are responsible for and that we&amp;#xA;limit very much in what it can touch.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Yet most frameworks come with all those details up-front and mixed up. They&amp;#xA;often make it very hard, if not impossible, to be isolated. To be contained&amp;#xA;to a single interface. And when they do, they stop being a framework and start&amp;#xA;becoming a library very fast.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;but-why-are-there-no-frameworks-that-offer-this&amp;#34;&amp;gt;But why are there no frameworks that offer this?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;First, that would defeat the purpose. Because the idea is to have&amp;#xA;independence of a framework. And building a framework with the sole&amp;#xA;purpose of not using that framework is rather counterproductive.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Secondly, well-maintainable software can evolve over time, to match&amp;#xA;changing needs.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When you migrate from HTTP to an event-bus as delivery mechanism, you no&amp;#xA;longer have need for an HTTP framework, obviously. When you move from a&amp;#xA;web-based service to one using only native mobile apps, you no longer&amp;#xA;need all the HTML/CSS/asset stuff, but do need a way to serialize and&amp;#xA;handle JSON requests. Maintainability requires your software to evolve&amp;#xA;with you. A HTTP-framework will offer HTTP. Some MVC framework will offer&amp;#xA;an ORM that uses a relational database. But when your needs change and&amp;#xA;you no longer need  all the HTTP, or templating, it is still there. When&amp;#xA;you decide it fits the business best to store data in a database, but&amp;#xA;rather distribute it as JSON-files an ORM framework will become&amp;#xA;obsolete. Yet it is still there.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Third, one often hardly needs a &amp;lt;em&amp;gt;framework&amp;lt;/em&amp;gt; to do stuff. For&amp;#xA;example, an architecture like &amp;lt;a href=&amp;#34;https://www.martinfowler.com/bliki/CQRS.html&amp;#34;&amp;gt;CQRS&amp;lt;/a&amp;gt;&amp;#xA;is really mostly a simple &amp;lt;code&amp;gt;if(is_command) { command(params) } else { query(params) }&amp;lt;/code&amp;gt;. You don&amp;#39;t need a framework to do an if/else.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And last, maintenance isn&amp;#39;t about using specific tools or frameworks.&amp;#xA;As Symfony aptly pointed out:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;and the use of Best Practices guarantees the stability, maintainability and upgrade-ability of the applications you develop.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;One of those &amp;amp;quot;Best Practices&amp;amp;quot; is to not let a framework rule your&amp;#xA;project!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;hr&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[1] Something that is probably much better left to either standalone tools,&amp;#xA;IDEs, or IDEs leveraging those standalone tools. E.g. any editor or IDE worth&amp;#xA;its disk-space has some form of &amp;amp;quot;snippets&amp;amp;quot; or &amp;amp;quot;templates&amp;amp;quot; in 2022.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[2] Ruby, the language Rails is written in, comes with quite a lot of methods&amp;#xA;already. But even if you remove those from this list, you have over a&amp;#xA;thousand methods offered by Rails.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Exponential compound interest on Technical Debt. And how I avoided it.</title>
   <link href="https://berk.es/2022/08/30/exponential-compound-interest-on-technical-debt-and-how-i-avoided-it/"/>
   <updated>2022-08-30T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2022/08/30/exponential-compound-interest-on-technical-debt-and-how-i-avoided-it/</id>
   <content type="html">&amp;lt;p&amp;gt;In this post I&amp;#39;ll try to explain why not all technical debt is crippling. And&amp;#xA;what I learned, dealing with technical debt. I&amp;#39;ll try to show that:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Technical Debt isn&amp;#39;t always bad.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It is bad, dangerous even, when it starts to compound.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;The interest must be calculated into operations.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;We must deliberately and actively choose when and where to take on technical&amp;#xA;debt.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;One trick to take on technical debt but keep the interest low, is to decouple&amp;#xA;our code.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;During my career, I&amp;#39;ve worked on many codebases. One thing that differed a lot&amp;#xA;between projects, is the effect of &amp;amp;quot;technical debt&amp;amp;quot;. A few projects where&amp;#xA;completely crippled by it. Where &amp;amp;quot;paying the debt&amp;amp;quot; took the entire companies&amp;#39;&amp;#xA;engineering &amp;amp;quot;budget&amp;amp;quot;: hardly able to react to the market, to deliver feature&amp;#xA;requests, to keep users happy. All effort &amp;amp;quot;wasted&amp;amp;quot; on just keeping the software&amp;#xA;barely running.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Yet other projects, often much larger, than those ones above, or with far&amp;#xA;smaller teams, managed to keep delivering for years, decades even. Managed to&amp;#xA;maintain a high velocity. Continuously.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;ve been pondering why that is, for years. Until I read &amp;lt;a href=&amp;#34;https://www.goodreads.com/book/show/6617037-debt&amp;#34;&amp;gt;Debt the first 5000&amp;#xA;years&amp;lt;/a&amp;gt; by David Graeber. And&amp;#xA;until I started to seriously research companies (stocks) to invest in. I then&amp;#xA;realized that in finances, not all debts are equal. That having big debts, in&amp;#xA;itself is not bad. But that the types of debt are what make them dangerous.&amp;#xA;And I learned to appreciate the effect of compounding interest.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/interest_simple_vs_compound.png&amp;#34; alt=&amp;#34;3% interest on debt: Simple vs Compound&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When we talk about &amp;lt;em&amp;gt;Technical Debt&amp;lt;/em&amp;gt; we usually don&amp;#39;t categorize it. After all,&amp;#xA;the term was coined as just an analogy, not something scientific:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Shipping first time code is like going into debt. A little debt speeds&amp;#xA;development so long as it is paid back promptly with a rewrite... The danger&amp;#xA;occurs when the debt is not repaid. Every minute spent on not-quite-right&amp;#xA;code counts as interest on that debt. Entire engineering organizations can be&amp;#xA;brought to a stand-still under the debt load of an unconsolidated&amp;#xA;implementation, object-oriented or otherwise.&amp;#xA;— Ward Cunningham, 1992&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But the idea isn&amp;#39;t new. In 1980 already, Computer Scientist Meir Manny Lehman&amp;#xA;saw a clear cause and effect. And stressed that it is important we continually&amp;#xA;keep paying off those debts:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;As an evolving program is continually changed, its complexity, reflecting&amp;#xA;deteriorating structure, increases unless work is done to maintain or reduce&amp;#xA;it.&amp;#xA;— Meir Manny Lehman, 1980&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Technical debt will cause our project to deteriorate over time. The accumulation&amp;#xA;is what makes it such a powerful and dangerous concept. As DHH and Jason fried&amp;#xA;point out: it gets worse over time, and you&amp;#39;ll often only notice that when it&amp;#xA;is too late:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Promises pile up like debt, and they accrue interest, too. The longer you&amp;#xA;wait &amp;amp;gt; to fulfill them, the more they cost to pay off and the worse the&amp;#xA;regret. When it’s time to do the work, you realize just how expensive that&amp;#xA;really was.&amp;#xA;— Jason Fried, David Heinemeier Hansson in It Doesn’t Have to Be Crazy at Work&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When we don&amp;#39;t pay back the Interest, over time will not just accumulate, it will&amp;#xA;compound. Compounding interest means that if don&amp;#39;t pay off interest, that&amp;#xA;interest itself becomes debt too. Which increases the debt, which increases the&amp;#xA;interest. Ad infinitum, exponential.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Consider above graph, in which I show a debt of $1000, with 3% interest.&amp;#xA;One (blue) line is where interest is paid off. The other, is where interest&amp;#xA;isn&amp;#39;t paid off and is added to the debt. After 100 periods, one has paid off&amp;#xA;a total of $3000. The other now has a debt of over $50.000.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Compound interest is the eighth wonder of the world. He who understands it,&amp;#xA;earns it … he who doesn’t … pays it.&amp;#xA;— Albert Einstein&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Yet, many companies take on debt. Many of the highest valued companies in the&amp;#xA;world, have large debt/equity ratios: not just large debts, but large debts compared&amp;#xA;to their equity. E.g. Apple has a debt ratio of over 100%: it has more&amp;#xA;debt than that it has equity. And Apple has &amp;lt;em&amp;gt;a mind boggling-lot&amp;lt;/em&amp;gt; of equity:&amp;#xA;and therefore a mind-boggling lot of debt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/apple-debt.png&amp;#34; alt=&amp;#34;Screenshot showing amount of debt that Apple has&amp;#34;&amp;gt;&amp;#xA;Screenshot from &amp;lt;a href=&amp;#34;https://simplywall.st/stocks/us/tech/nasdaq-aapl/apple#health&amp;#34;&amp;gt;SeekingAlpha&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Like with such financial debts, I have experienced projects that took on&amp;#xA;technical debt in software, safely. Without grinding to a halt or going&amp;#xA;bankrupt, without turning into a &amp;lt;a href=&amp;#34;https://www.goodreads.com/book/show/17255186-the-phoenix-project&amp;#34;&amp;gt;Phoenix&amp;#xA;project&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When is technical debt safe? Where is it safe? For that, we must first take a&amp;#xA;detour. To get a common misconception is out of the way: &amp;lt;strong&amp;gt;That we,&amp;#xA;engineers, programmers, aren&amp;#39;t the ones responsible for the debt&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;David Graeber points out in his provoking book, that financial debt&amp;#xA;isn&amp;#39;t equally distributed. Or, more precise: who has to pay it back, isn&amp;#39;t&amp;#xA;equally distributed:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;As it turns out, we don&amp;#39;t &amp;amp;quot;all&amp;amp;quot; have to pay our debts. Only some of us do.&amp;#xA;― David Graeber, Debt: The First 5,000 Years&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The typical story, we, engineers like to tell ourselves, is that it is&amp;#xA;&amp;amp;quot;management forces us&amp;amp;quot; to make the shortcuts[1]. That &amp;amp;quot;management&amp;amp;quot; is taking on&amp;#xA;the debts and that &amp;amp;quot;we&amp;amp;quot; then have to pay it back. I&amp;#39;m convinced this is a&amp;#xA;misconception. A story &amp;amp;quot;we&amp;amp;quot; tell ourselves to shirk our engineering responsibility.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A medium post on this topic &amp;lt;a href=&amp;#34;https://medium.com/swlh/technical-interest-or-how-technical-debt-can-bankrupt-your-code-60b1a5f61ac7&amp;#34;&amp;gt;confirms that&amp;#xA;misconception&amp;lt;/a&amp;gt;&amp;#xA;when explaining the concept of technical debt with an example:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Of course, you thought that after shipping your app, you would have time to&amp;#xA;go back, get rid of the hack you made, redesign the architecture, implement&amp;#xA;it, and make your code look good again. In other words, get rid of your&amp;#xA;technical debt. [...] Those same stakeholders come to you and say that the&amp;#xA;app needs to get that new feature as soon as possible. You tell them that you&amp;#xA;are just trying to make the code be in good shape again. And you hear back&amp;#xA;that there is simply no time for that now, you can take care of that later.&amp;#xA;[...] In fact, it has to go through some loops and hoops in order to make it&amp;#xA;work.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A junior, that I once mentored, explained why his code was becoming more and more messy:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;amp;quot;Management needs me to churn out new features, rather than work on stuff&amp;#xA;that improves the software&amp;amp;quot;. (paraphrased)&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I have thought this same fallacy many times. But I, and this junior, am&amp;#xA;making a few mistakes here:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;I did not quantify or clarify the debt and the interest clearly. The whole&amp;#xA;business should be worried by this debt, not just a (few) programmer(s).&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;I made it look like &amp;amp;quot;taking on debt&amp;amp;quot; and &amp;amp;quot;paying back debt&amp;amp;quot; are entirely&amp;#xA;different projects, which we plan in succession, rather than something that&amp;#xA;we (also) do as part of our daily work.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;I am not taking &amp;amp;quot;the business&amp;amp;quot; serious enough. The part that actually pays my&amp;#xA;beer. I wrongly consider &amp;amp;quot;engineering&amp;amp;quot; vs &amp;amp;quot;business&amp;amp;quot; as opposites, rather&amp;#xA;than as the enablers of each-other they are.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;That taking on technical debt can be avoided, and is not a &amp;lt;em&amp;gt;fact of life&amp;lt;/em&amp;gt; of&amp;#xA;a software project.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And, above all, I did not design the code in such a way that it can safely take&amp;#xA;on the inevitable debt. Or even embrace it.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With a monetary debt, there are two things to do to avoid bankruptcy through&amp;#xA;compound interest: keep paying off the interest and debts, or keep the interest&amp;#xA;rates low.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is an and/and, not or/or: with low interest rates, continuously paying off&amp;#xA;the debt is easy, which is why people will lend money for low interests. It&amp;#39;s a&amp;#xA;positive feedback loop!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With high interests, it is hard to pay off debts. And when there&amp;#39;s a high risk&amp;#xA;of not paying off debts continuously, people will charge much higher interest,&amp;#xA;making it harder to keep paying them off. A negative feedback loop!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is the same for technical debt. High interest means we cannot pay it off:&amp;#xA;we have so much hacks and ugly code, that forever we remain behind on actually&amp;#xA;fixing all that. All available time is wasted on &amp;amp;quot;emergencies&amp;amp;quot; and on &amp;amp;quot;fixing&amp;#xA;bugs&amp;amp;quot; caused by the debt. But that then also means that the interest remains high.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With technical debt, the common &amp;amp;quot;answer to technical debt&amp;amp;quot; is to just keep&amp;#xA;reserving time to improve the codebase. Always, continuously, refactor. Keep&amp;#xA;paying off your debts. Something-something Lannister.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But the second part of that negative feedback loop is often ignored: we should&amp;#xA;keep the interest low. With low interest, having a large debt is fine.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;keep-the-interest-rates-low&amp;#34;&amp;gt;Keep the interest rates low&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For example, if we borrow $1000, over 200 weeks, with a weekly interest rate of&amp;#xA;2, 6, or 12%, and don&amp;#39;t pay off anything, this is the difference:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;table&amp;gt;&amp;#xA;  &amp;lt;thead&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;loan&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;2%&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;6%&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;12%&amp;lt;/th&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;  &amp;lt;/thead&amp;gt;&amp;#xA;  &amp;lt;tbody&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;$1,000.00&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;$2,997.74&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;$5,601.05&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;$10,607.13&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;  &amp;lt;/tbody&amp;gt;&amp;#xA;&amp;lt;/table&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With a low interest, 2%, the loan has &amp;lt;strong&amp;gt;grown almost three times&amp;lt;/strong&amp;gt;. With 12%, the&amp;#xA;loan &amp;lt;strong&amp;gt;grew over ten times&amp;lt;/strong&amp;gt;. In technical debt, we cannot calculate it this precise.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But we could take the ratios: If we take on technical debt, with a low interest&amp;#xA;(2%), what we built today with two FTE, will cost us the work of 6 FTE in&amp;#xA;future to &amp;amp;quot;pay back&amp;amp;quot;.&amp;#xA;But with a high interest (12%)% would cost us 20FTE in that same future.&amp;#xA;The increased compounding effect caused by the height of interest is real and visible.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;So, how do we avoid paying a big interest? How can we safely take on debt, like&amp;#xA;e.g. the company Apple does with money, without going bankrupt?&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;One thing I saw between the projects described in the first paragraph, is that&amp;#xA;the problematic projects had a high interest. Not so much a high rate of debt.&amp;#xA;And that this was always caused by tight coupling. Orthogonality:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In computing, the term has come to signify a kind of independence or&amp;#xA;decoupling. Two or more things are orthogonal if changes in one do not affect&amp;#xA;any of the others.&amp;#xA;— David Thomas and Andrew Hunt in The Pragmatic Programmer&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In a tight coupled our system, a shortcut, quick hack, will easily spread into&amp;#xA;everywhere. It will touch everything that you have today, but also everything&amp;#xA;that is introduced in the future will be influenced. &amp;lt;strong&amp;gt;This is a compounding&amp;#xA;effect&amp;lt;/strong&amp;gt;. But worse is that tearing it out in future is hard: after all, the&amp;#xA;chance that &amp;amp;quot;paying back the debt&amp;amp;quot; has unindented side-effects grows over time.&amp;#xA;&amp;lt;strong&amp;gt;This is another compounding effect&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;tight-coupling&amp;#34;&amp;gt;Tight Coupling&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;By decoupling our system, taking on debt, can be kept locally. Isolated. The&amp;#xA;effect, the interest, is not affected by future work on other parts. It is&amp;#xA;still there, but only affects our work when we touch the isolated part. And&amp;#xA;ripping it out is much easier. If, for some reason, we leave the debt to grow&amp;#xA;unmanageable, we don&amp;#39;t need The Big Rewrite™ of the entire system, we, at most,&amp;#xA;might need to rewrite the isolated subsystem.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A typical SAAS web app may look something like below[2].&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/diagram-coupled.png&amp;#34; alt=&amp;#34;Network diagram of a tightly coupled software system&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We see at least two important problems. And both caused real issues down the&amp;#xA;line. Both turned out to be crippling technical debt. In different projects,&amp;#xA;though. The first one I saw repeated in three different projects, all three&amp;#xA;struggling with the debt it brought. The second is when the refactoring (paying&amp;#xA;off the debt) is done wrong, or at the wrong moment. Where the refactoring&amp;#xA;itself becomes the technical debt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We can see that everything is coupled to &amp;amp;quot;role&amp;amp;quot; and &amp;amp;quot;user&amp;amp;quot;. The infamous&amp;#xA;User-becomes-a-God-model issue. E.g. to answer &amp;lt;em&amp;gt;can a user create a task in&amp;#xA;this project&amp;lt;/em&amp;gt;, the developers coupled almost every class to every other class.&amp;#xA;The &amp;lt;em&amp;gt;subscription-Plan&amp;lt;/em&amp;gt;, together with the &amp;lt;em&amp;gt;Role&amp;lt;/em&amp;gt; determine what actions a &amp;lt;em&amp;gt;User&amp;lt;/em&amp;gt;&amp;#xA;can take on &amp;lt;em&amp;gt;Task&amp;lt;/em&amp;gt; in a certain &amp;lt;em&amp;gt;Project&amp;lt;/em&amp;gt;. To answer that question, we are&amp;#xA;going through six of the ten classes. We have tightly coupled over half of our&amp;#xA;codebase. Obvious problems arose: E.g. these codebases had (unit-ish) tests&amp;#xA;that would take over a minute, hundreds of lines of code, to set-up a database,&amp;#xA;just so the test could then create a task on which it could test stuff. Even if&amp;#xA;the test was entirely unrelated to &amp;amp;quot;creating a task&amp;amp;quot;, it has to create&amp;#xA;subscriptions, plans, roles, users, projects, task, (in a database!) just to test&amp;#xA;that &amp;amp;quot;Given a task with status completed, when I edit it, I can only change it&amp;#xA;to status new&amp;amp;quot;. Such tests ran slow, but also took hours to write.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In both codebases, this technical debt was once, a long time ago, &amp;amp;quot;quickly&amp;#xA;added&amp;amp;quot; through a library. Not very well thought through. Technical debt not&amp;#xA;taken on deliberate and within constraints. At the time it probably wasn&amp;#39;t even&amp;#xA;seen as technical debt, not even as a potential problem, but the accumulating&amp;#xA;interest wasn&amp;#39;t tackled and compounded into a situation where literally a&amp;#xA;majority of classes had references to nearly all other classes.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When, for example, I introduced, a sorting feature on a table on dashboard, this accidentally broke the unrelated PDF reporting feature.&amp;#xA;&amp;lt;strong&amp;gt;Textbook tight-coupling&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;lt;em&amp;gt;solution&amp;lt;/em&amp;gt; implemented, however, in both these projects, was to increase&amp;#xA;the amount of manual testing (man-hours, interest-related costs!)  to spend way&amp;#xA;more on code-reviews that warranted (fear, also interest-related cost), to over&amp;#xA;design new features and to simply have downtime, crashes or performance-issues&amp;#xA;and timeouts every few months. I.e. to not pay off the debt, but instead&amp;#xA;continue paying just the interest. And to allow this interest to remain high.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The second example, from another codebase, is what happens when someone&amp;#xA;takes &amp;amp;quot;DRY, don&amp;#39;t repeat yourself&amp;amp;quot; too literal. The code had several&amp;#xA;upload-an-image-and-attach-that-to-X classes. An engineer saw that duplication,&amp;#xA;refactored that into a single class that &amp;amp;quot;handled all uploads&amp;amp;quot;. This appears as&amp;#xA;good refactoring: paying off some technical debt! But alas, if we look closely&amp;#xA;(and with hindsight) it was &amp;lt;em&amp;gt;accidental duplication&amp;lt;/em&amp;gt;. We, unknowingly, took on&amp;#xA;debt instead of paying it off. If we had a modularized architecture, and&amp;#xA;enforced it, we probably wouldn&amp;#39;t even consider this refactoring.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;loose-coupling&amp;#34;&amp;gt;Loose coupling&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In the projects that did not struggle with technical debt&amp;#xA;(but, again, some had large amounts of debt, they just didn&amp;#39;t suffer from it),&amp;#xA;the architecture looked different. There were clear subsystems: modules. Not&amp;#xA;microservices[3], but just well defined boundaries. In some systems classes&amp;#xA;could, technically, reach into a boundary and poke around there: it was just&amp;#xA;discipline that kept the team from actually doing that.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;ve taken the diagram above, and without moving the &amp;amp;quot;classes&amp;amp;quot;, I introduced&amp;#xA;boundaries: placed them in modules. And I reversed the &amp;amp;quot;attachment&amp;amp;quot;-DRY&amp;#xA;refactoring by giving each class its own dedicated attachment back. I did move&amp;#xA;any components just to make it more readable though: just added containers.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/diagram-decoupled.png&amp;#34; alt=&amp;#34;Network diagram of a decoupled software system&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;What we see now, is modularized, compartmentalized architecture. There is an&amp;#xA;&amp;amp;quot;Authorization subsystem&amp;amp;quot;, which will answer the question &amp;amp;quot;can user with this&amp;#xA;ID perform action X on a thing with this ID&amp;amp;quot; in isolation, on its own, using&amp;#xA;data only it owns etc.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If we now take on technical debt in this authorization subsystem (business needs a&amp;#xA;log-in-with-github-button yesterday), it doesn&amp;#39;t touch anything but this subsystem.&amp;#xA;&amp;lt;strong&amp;gt;The interest doesn&amp;#39;t go up exponential, it goes up linear&amp;lt;/strong&amp;gt;. And if we take on&amp;#xA;technical debt in the billing subsystem (Sales needs a free trial which can&amp;#xA;access all features), it only touches the finance subsystem.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The projects which didn&amp;#39;t suffer from the technical debt, were compartmentalized&amp;#xA;like this. The subsystems isolated. Communication and coupling minimised to&amp;#xA;well-defined ports (interfaces, methods etc).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In these projects the teams were confident to take on technical debt. But&amp;#xA;&amp;lt;em&amp;gt;only&amp;lt;/em&amp;gt; if it remained compartmentalized. Never if the &amp;amp;quot;technical debt&amp;amp;quot; meant&amp;#xA;introducing coupling. Never if it meant to compromise the boundaries, break the&amp;#xA;isolation. Because debt that breaks the boundaries, will compound quickly into a&amp;#xA;spiral of compounding interest that can never be paid off.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;rewrite-from-scratch&amp;#34;&amp;gt;Rewrite from Scratch&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;As long as it is isolated, we also have a clear worst-case-scenario: being forced to&amp;#xA;&amp;lt;em&amp;gt;rewrite the entire subsystem&amp;lt;/em&amp;gt;. The worst possible interest of our technical&amp;#xA;debt is known: rewrite subsystem X.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;ve actually paid off technical debt this way, a few times[4]. One&amp;#xA;of those was an (auto-generated)avatar-subsystem, which took just two days to rewrite&amp;#xA;from scratch and turn into a microservice, because, unlike in the first diagram&amp;#xA;above, we always kept the classes and algorithms for the avatars&amp;#xA;isolated from all other stuff that had to do with &amp;amp;quot;Users&amp;amp;quot;. Yet, internally,&amp;#xA;within the boundaries of this &amp;amp;quot;avatar subsystem&amp;amp;quot; the code was a terrible mess,&amp;#xA;performed horribly, was tightly coupled to a specific storage implementation,&amp;#xA;and withheld the entire codebase from scaling up horizontally - it was the last part that could not run parallel.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Two days, one FTE: That was all it took to pay off our biggest chunk of&amp;#xA;technical debt within the company. And we always knew the size of the debt, and&amp;#xA;therefore did not shirk back from taking on even more debt: because it&amp;#39;s effect&amp;#xA;was always isolated. Bounded.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;conclusion&amp;#34;&amp;gt;Conclusion&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With proper compartments, a decoupled architecture, technical debt can be&amp;#xA;contained. Which causes it&amp;#39;s interest, the effort required to pay it off, to&amp;#xA;grow linear, rather then exponential. Which can still lead to a crippling debt.&amp;#xA;Still lead to compounding interest. But which keeps if far more manageable.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Technical debt, ugly code, allowing us to deliver a feature today, which will&amp;#xA;cost us, with interest, much more effort to solve in a year, than we save&amp;#xA;today. But sometimes that is what is needed to keep the business afloat. And&amp;#xA;often is fine. Provided we keep the interest rates low.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It is up to the engineers to make this visible. And it is up to the engineers&amp;#xA;to keep the interest low through software architecture and discipline. And up&amp;#xA;to the entire business to keep paying off the debt, or at the very least the&amp;#xA;paying off the interest, because the compounding effect will otherwise grind&amp;#xA;the project to halt. Or even cause bankruptcy.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Discussion and comments are welcome over at &amp;lt;a href=&amp;#34;https://news.ycombinator.com/item?id=32660834&amp;#34;&amp;gt;this hackernews post&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;hr&amp;gt;&amp;#xA;&amp;lt;ol&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;But in my experience those places hardly exist. Probably, because they won&amp;#39;t survive competition where engineers can take responsibility. And because in reality, management and C-level will listen to engineers, but might choose to ignore their warnings. Either because engineers have shown a history of &amp;amp;quot;cry wolf&amp;amp;quot;, or because other issues press harder on the business as a whole. Most often the last. And to engineers who lack this perspective for whatever reason, it may look like management is &amp;amp;quot;forcing technical debt&amp;amp;quot; upon them. Without seeing that it&amp;#39;s really an &amp;amp;quot;Us&amp;amp;quot;, not &amp;amp;quot;them vs me&amp;amp;quot; thing.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;The actual domain of both the cases where I encountered this was not &amp;amp;quot;project management&amp;amp;quot;. There weren&amp;#39;t literal &amp;amp;quot;tasks&amp;amp;quot; nor literal &amp;amp;quot;projects&amp;amp;quot;. The real code was far more complex, the reality was far worse than what I describe here.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Though microservices are the culmination of decoupling, when done right, there are many steps between a &amp;amp;quot;highly coupled system&amp;amp;quot; and &amp;amp;quot;a highly decoupled microservice system&amp;amp;quot;. The most obvious being a monolith with properly decoupled, internal modules. And with rigidly enforced (domain) boundaries.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;In general, I severely dislike &amp;amp;quot;rewrite from scratch&amp;amp;quot;. Books have been filled by why this is a bad idea, and I agree. But if the rewrite is isolated, small, and in a well-tested (integration test) system, it might prove the most efficient way to get rid of technical debt. Sometimes.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ol&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>My Reasons For Using Rust (as a Ruby developer)</title>
   <link href="https://berk.es/2022/08/23/my-reasons-for-using-rust/"/>
   <updated>2022-08-23T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2022/08/23/my-reasons-for-using-rust/</id>
   <content type="html">&amp;lt;p&amp;gt;By now, there are thousands of articles explaining why Rust is a good&amp;#xA;option/the fastest/cleanest/nicest/whateverest. While reading about objective&amp;#xA;attributes that speak for- or against a programming language, a talk by&amp;#xA;&amp;lt;a href=&amp;#34;https://www.youtube.com/watch?v=IYLf8lUqR40&amp;#34;&amp;gt;Owen Synge&amp;lt;/a&amp;gt; stuck with me.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;He explains why &amp;lt;em&amp;gt;he&amp;lt;/em&amp;gt; likes Rust. Why &amp;lt;em&amp;gt;he&amp;lt;/em&amp;gt; chose it. And what &amp;lt;em&amp;gt;he&amp;lt;/em&amp;gt; values in a&amp;#xA;language. I took this idea for &amp;lt;a href=&amp;#34;/rre-rust/pres.html&amp;#34;&amp;gt;my rust presentation&amp;lt;/a&amp;gt;. And&amp;#xA;now wanted to explain it in some more detail.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I like this perspective, because it shows whether the person &amp;lt;em&amp;gt;promoting&amp;lt;/em&amp;gt; Rust,&amp;#xA;values the same attributes; it shows &amp;lt;em&amp;gt;why&amp;lt;/em&amp;gt; someone chose a language, framework&amp;#xA;or architecture with relevant context.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For me, the three most important attributes of a framework or language are that:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Software should work for decades.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;A system has Good Defaults™: Being Lazy leads to good software; To make &amp;amp;quot;bad&amp;amp;quot;&amp;#xA;software you must employ extra effort.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It is Simpler (not necessarily easier) to maintain, host, test, and deliver.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I am, primarily, web-developer. Lot&amp;#39;s of backend work, with DevOps and&amp;#xA;infrastructure automation and occasionally frontend development. But all the&amp;#xA;software that I write is to support mobile- and web-apps. This means that&amp;#xA;attributes like &amp;lt;em&amp;gt;being able to run on this 20 years old ACME controller&amp;lt;/em&amp;gt; or&amp;#xA;even the performance, are less important or even irrelevant. For me.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;ve been developing &amp;lt;a href=&amp;#34;/tags.html#ruby-ref&amp;#34;&amp;gt;in Ruby and Rails since 2005&amp;lt;/a&amp;gt; and&amp;#xA;full-time &amp;lt;a href=&amp;#34;/2013/10/06/i-am-a-webdeveloper-using-ruby-rails-and-other-open-source/&amp;#34;&amp;gt;since&amp;#xA;2013&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I try not to advertise myself as an &amp;amp;quot;X developer&amp;amp;quot;, but use the language that&amp;#xA;fits the use-case best. On a typical day, I&amp;#39;ll write some Ruby, some Python,&amp;#xA;kludge around with JavaScript, do some Typescript and write some Rust.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;While I think it&amp;#39;s important to train myself to be a&amp;#xA;[Polyglot](&amp;lt;a href=&amp;#34;https://en.wikipedia.org/wiki/Polyglot_(computing)&amp;#34;&amp;gt;https://en.wikipedia.org/wiki/Polyglot_(computing)&amp;lt;/a&amp;gt; programmer, I&amp;#39;ll&amp;#xA;always have &amp;lt;em&amp;gt;one&amp;lt;/em&amp;gt; &amp;lt;a href=&amp;#34;https://twitter.com/tenderlove/status/1561849944083902464&amp;#34;&amp;gt;Go-to&amp;#xA;language&amp;lt;/a&amp;gt;. One&amp;#xA;language that I know much deeper, for which my environment is tuned, which I&amp;#xA;have much more experience in, and so on. Currently that is Ruby, but I&amp;#39;m&amp;#xA;quickly shifting that to Rust.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Because Rust ticks those boxes.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;software-should-work-for-decades&amp;#34;&amp;gt;Software should work for decades&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Most of my software, over 90%, is archived, obsolete, unused, dead. But in the&amp;#xA;rare 10%, I want to be sure that it can be maintained over longer time.&amp;#xA;Yet it is impossible to guess up-front what this 10% is. There is software&amp;#xA;running, which I quickly hacked up late-night in an emergency. Which has been&amp;#xA;provisioning thousands of WordPress servers for almost 10 years now, it got&amp;#xA;improved later, though. Yet a very cleanly architectured ORM for a REST&amp;#xA;backend, with all sorts of Design Patterns, has ran for a few months in a&amp;#xA;staging environment, and then threw out because we decided PHP/Drupal really wasn&amp;#39;t&amp;#xA;going to cut that project.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Which is why I at least &amp;lt;em&amp;gt;try&amp;lt;/em&amp;gt; to architecture all code in a way that it can be&amp;#xA;improved upon. It doesn&amp;#39;t have to be good at the start, but must be possible to&amp;#xA;be &amp;lt;em&amp;gt;made good&amp;lt;/em&amp;gt; later.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The programming language, a framework and its ecosystem solidify a lot of&amp;#xA;options early on. More then &amp;lt;em&amp;gt;The Architecture&amp;lt;/em&amp;gt;, in my experience. It&amp;#39;s relative&amp;#xA;easy to re-architecture something: incremental refactoring. But it&amp;#39;s not easy&amp;#xA;to swap out a framework or language for another. That almost always requires&amp;#xA;&amp;lt;em&amp;gt;big rewrite&amp;lt;/em&amp;gt;, which, as we all probably know, is a&amp;#xA;&amp;lt;a href=&amp;#34;https://www.goodreads.com/book/show/17255186-the-phoenix-project&amp;#34;&amp;gt;company-killer&amp;lt;/a&amp;gt;.&amp;#xA;A wrong choice of language or framework leads almost inevitably to a &amp;lt;em&amp;gt;painted&amp;#xA;yourself into a corner&amp;lt;/em&amp;gt; problem.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And those solidified options should support maintenance over long time. This&amp;#xA;is where &amp;lt;em&amp;gt;software architecture&amp;lt;/em&amp;gt; plays the biggest role, but language has an&amp;#xA;important say in this. As does the culture of the ecosystem.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In Rails, for example, it &amp;lt;a href=&amp;#34;https://rubyonrails.org/doctrine#provide-sharp-knives&amp;#34;&amp;gt;is made explicitly&amp;#xA;clear&amp;lt;/a&amp;gt; that doing&amp;#xA;&amp;amp;quot;stupid things&amp;amp;quot; is OK. While I applaud the idea that it &amp;lt;em&amp;gt;is possible&amp;lt;/em&amp;gt;, I have&amp;#xA;seen this lead to problems in every Rails project that I worked on. Where maintaining&amp;#xA;some &amp;amp;quot;smart&amp;amp;quot; hack, fancy DSL or full-featured library over time required&amp;#xA;recurring, and significant cost. Even grinded entire teams to a halt. Not even&amp;#xA;able to react to market. But even when, with great discipline, you manage to keep&amp;#xA;your application clean and maintainable, there are technical fundamentals, of&amp;#xA;the language that make maintenance harder or easier.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ruby, like Python, for example, requires complex runtime environments and&amp;#xA;versioning. Rbenv, Gems, Bundler, whatnot. All of these move all the time.&amp;#xA;Every week at least one of my &amp;amp;quot;shelved&amp;amp;quot; Ruby projects, or JavaScript&amp;#xA;dependencies needs some critical security update.&amp;#xA;Any Ruby project that I haven&amp;#39;t touched for months, requires significant&amp;#xA;effort to &amp;lt;em&amp;gt;just get running locally&amp;lt;/em&amp;gt;. If at all. I have a &amp;lt;a href=&amp;#34;https://spicy-scarlet-slug-demo.onrender.com/&amp;#34;&amp;gt;tiny rust&amp;#xA;plaything&amp;lt;/a&amp;gt; which I wrote, is&amp;#xA;finished, and keeps running without me ever looking at it [for months](. As a Ruby&amp;#xA;developer, this is a new experience!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is less visible when you have a team working on one project day in day out.&amp;#xA;But I can assure you, the total time spent on upgrading gems, pips, npm, linter,&amp;#xA;CI, tests &amp;lt;em&amp;gt;just to keep it running month-to-month&amp;lt;/em&amp;gt; is significant.&amp;#xA;All this is effort I cannot spend on writing the software (that makes me money).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It is more visible in agencies that have many PHP, WordPress, Drupal, Rails or&amp;#xA;Django projects. Where a client comes back after 20 months to ask for that X&amp;#xA;integration, which then, most often turns into a full-blown rewrite of the&amp;#xA;entire project[1].&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Rust was built with stability in mind. It&amp;#39;s one of the core principles is to be&amp;#xA;fully backwards compatible and &amp;lt;a href=&amp;#34;https://blog.rust-lang.org/2014/10/30/Stability.html&amp;#34;&amp;gt;support versions&amp;#xA;forever&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The release of Rust 1.0 established &amp;amp;quot;stability without stagnation&amp;amp;quot; as a core&amp;#xA;Rust deliverable. Ever since the 1.0 release, the rule for Rust has been that&amp;#xA;once a feature has been released on stable, we are committed to supporting&amp;#xA;that feature for all future releases.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Rust invented a &amp;lt;a href=&amp;#34;https://doc.rust-lang.org/edition-guide/editions/index.html&amp;#34;&amp;gt;smart&amp;#xA;combination&amp;lt;/a&amp;gt; of&amp;#xA;versions, editions and releases, in which it can continue rapid improvement to&amp;#xA;avoid stagnation, but to ensure the project you write today, will compile and&amp;#xA;run in ten years. And to help you upgrade, if you want, but never force you to.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;lt;em&amp;gt;crates&amp;lt;/em&amp;gt; system: libraries, with dependency management, support this idea&amp;#xA;too. You&amp;#39;ll continue to have access to old libraries, can easily keep them&amp;#xA;locally, or even on a self-hosted library-server. But where all ruby-gems,&amp;#xA;pips, or npm-packages &amp;lt;em&amp;gt;must&amp;lt;/em&amp;gt; run on the same environment, with Rust you can mix&amp;#xA;and mash old and new libraries just fine.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Any future compiler will compile your old rust code, so you can pick up an old&amp;#xA;project work on that, alongside any &amp;lt;em&amp;gt;now modern&amp;lt;/em&amp;gt; rust projects. You&amp;#39;ll miss&amp;#xA;features, you might be confused by &amp;lt;em&amp;gt;how stuff was done back then&amp;lt;/em&amp;gt;, but it will&amp;#xA;compile. It will compile any old dependencies alongside any newer. And it will&amp;#xA;run.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;a-system-has-good-defaults&amp;#34;&amp;gt;A system has Good Defaults™&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;m a lazy programmer, or at least try to be. And working test&amp;#xA;driven (TDD), this means cutting corners, writing the quickest hack that works,&amp;#xA;before refactoring and cleaning up. But often a rough, hard-coded or duplicated&amp;#xA;piece of code will make it to production: I also don&amp;#39;t like to waste time on&amp;#xA;cleaning up stuff that the customers don&amp;#39;t need. And even if I, and my team,&amp;#xA;were always diligent, and industrious, there will be a deadline, or emergency,&amp;#xA;where corners must be cut. Where code is rushed out.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I want my frameworks and programming languages to acknowledge this.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;To make &amp;lt;em&amp;gt;Doing the Proper Thing&amp;lt;/em&amp;gt; easy, and &amp;lt;em&amp;gt;doing the bad thing&amp;lt;/em&amp;gt; possible, but&amp;#xA;harder. That way, I, the lazy programmer, build secure, clean, maintainable software&amp;#xA;when I&amp;#39;m being lazy. Will forget a &amp;lt;code&amp;gt;private&amp;lt;/code&amp;gt; marker. I will forget to make a&amp;#xA;variable immutable. I will make a typo in a filename of a source file. I will&amp;#xA;forget to mark a dependency as requirement for this one module.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Rust was built with this in mind. And it trickles down into many of the&amp;#xA;frameworks, libraries and working-groups.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The most visible is how all variables are immutable, unless you explicitly&amp;#xA;make them mutable. How definitions, attributes and variables are private,&amp;#xA;unless you mark them public. How no dependencies are available until explicitly&amp;#xA;included. How all code is memory safe unless you explicitly mark a chunk as&amp;#xA;unsafe. And so on.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You&amp;#39;ll have to write extra keywords, insert deliberate markers and keywords to&amp;#xA;&amp;amp;quot;do the worse thing&amp;amp;quot;. Not that mutable variables are always &amp;lt;em&amp;gt;bad&amp;lt;/em&amp;gt;, but&amp;#xA;immutable, if possible, is better. So when the software makes me think &amp;lt;em&amp;gt;hmm, do&amp;#xA;I really need this to be mutable? What if instead I just...&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Slightly less visible is how the Rust compiler forces you to handle all&amp;#xA;exceptions and errors explicitly. You&amp;#39;ll never see your Rust program crash&amp;#xA;because some file could not be read, without first explicitly allowing that&amp;#xA;crash in the code. And therefore think about that case. This goes for &amp;lt;em&amp;gt;anything&amp;lt;/em&amp;gt;&amp;#xA;that could go wrong. From parsing a CSV, to missing commandline arguments, to&amp;#xA;form-fields being empty: if it crashes, you explicitly allowed that crash&amp;#xA;(which is as simple as a &amp;lt;code&amp;gt;.expect(&amp;amp;quot;Reticulating Splines&amp;amp;quot;)&amp;lt;/code&amp;gt;-call though).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Cargo, the default Rust toolkit, comes with a formatter that will format my&amp;#xA;code according to Community Best Practices. It comes with a test framework and&amp;#xA;runner. No need to &amp;lt;em&amp;gt;set that up one day&amp;lt;/em&amp;gt;. It&amp;#39;s there, at the first commit. It&amp;#xA;comes with a linter. The rust compiler, famously won&amp;#39;t compile if it sees&amp;#xA;errors. All that is available on my laptop, CI, build system, without any&amp;#xA;configuration, or set-up.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Cargo assumes a well-documented layout of the code, that makes any rust project&amp;#xA;recognisable, but mostly avoids me having to spend time on decisions on my&amp;#xA;directory and file-layout: it follows the code-layout. It&amp;#39;s clear where tests go,&amp;#xA;it&amp;#39;s clear how modules are split, how to name directories.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I can configure or override all this. I can change the linting and formatting&amp;#xA;rules (I know at least two former co-workers who would immediately spend days&amp;#xA;tuning all this....). But being lazy, I&amp;#39;d rather leave them at the defaults. And&amp;#xA;those happen to be &amp;lt;em&amp;gt;extremely well thought out&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;it-is-simpler-to-maintain-host-test-and-deliver&amp;#34;&amp;gt;It is Simpler to maintain, host, test, and deliver.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Rust -by default- statically compiles its runtime and dependencies in the&amp;#xA;resulting binary. This comes at the cost of rather big binaries, but it means that&amp;#xA;I can just plonk the compiled binary for Linux, onto a Linux machine and run it.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;All dependency management is done compile time. This makes deployment as easy as&amp;#xA;an rsync or scp and a restart.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It makes a CI workflow ridiculous simply: a mere &amp;lt;code&amp;gt;cargo check&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cargo build --release&amp;lt;/code&amp;gt;. No need to setup rbenv, or compile the right version of node or&amp;#xA;python. No need to add test frameworks or set up linters. It&amp;#39;s all there.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The built-in test-runner will parallelize your builds, will find the tests&amp;#xA;based on naming conventions. Will build the project in test-mode, will report&amp;#xA;in one of many popular test-report formats. And so on. All without&amp;#xA;configuration or set-up.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In a typical Ruby project, almost half my tests are to catch cases that a&amp;#xA;proper type system would catch. Things like &amp;lt;em&amp;gt;what if the email is null&amp;lt;/em&amp;gt; or&amp;#xA;&amp;lt;em&amp;gt;what to do when the file cannot be read&amp;lt;/em&amp;gt;. With Rust, I trust the compiler for&amp;#xA;all this. My tests can then focus &amp;lt;em&amp;gt;only&amp;lt;/em&amp;gt; on business-logic. I write and run a&amp;#xA;lot less tests.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Delivering the software is about as easy as deployment. Just email someone the&amp;#xA;binary, or .exe and have them run it. No need to unzip jars, install runtimes,&amp;#xA;check for versions, DLLs or .so files. The binary for your architecture will&amp;#xA;most likely just run.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And when maintaining software, complexity is the enemy. Simple the antidote,&amp;#xA;but getting there, &amp;lt;a href=&amp;#34;https://bradapp.blogspot.com/2006/05/simple-aint-easy-myths-and.html&amp;#34;&amp;gt;is not&amp;#xA;easy&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Rust doesn&amp;#39;t offer direct tools to help make software simpler. I&amp;#39;m afraid&amp;#xA;that&amp;#39;s more of an art. But it does offer a well-designed, large standard&amp;#xA;library. Every week I find a chunk of code that I can replace with a single&amp;#xA;call to something Rust offers out of the box. Often removing tens or even&amp;#xA;hundreds of lines of code. And more often than not, you don&amp;#39;t need third party&amp;#xA;libraries or even frameworks to build a feature.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Rust promotes &amp;lt;em&amp;gt;commandline tools&amp;lt;/em&amp;gt; in it&amp;#39;s tutorials and books. Rather than&amp;#xA;desktop, HTML or GUI applications. It makes it easy to rapidly crank out a new&amp;#xA;project, rather than shoehorning a feature into the existing project (make the&amp;#xA;&amp;amp;quot;good option&amp;amp;quot; the &amp;amp;quot;easy option&amp;amp;quot;!).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;any-downsides&amp;#34;&amp;gt;Any downsides?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Rust is certainly not perfect for me, though.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;lt;em&amp;gt;lazy&amp;lt;/em&amp;gt; trait, has another perspective, for example. I don&amp;#39;t always &amp;lt;em&amp;gt;want&amp;lt;/em&amp;gt;&amp;#xA;to think about a potential error like, say &amp;amp;quot;the CSV file not being UTF8&amp;amp;quot;, but&amp;#xA;Rust forces me to deal with that edge-case. Even if this is a tool to run over a&amp;#xA;single CSV file and then get archived. Developing in Rust is certainly slower&amp;#xA;for me, than in Ruby. Part of that to experience. But a large part to how rust&amp;#xA;forces me to deal with all sorts of use-cases always, all the time.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;While I appreciate that Rust has no class inheritance, and relies only on&amp;#xA;composition, Its lack of classes and objects are unfamiliar to me. And require&amp;#xA;me to re-learn a lot of design-patterns. To design setups that I commonly write&amp;#xA;without even thinking about. I expect this to fade over time, as I gain&amp;#xA;experience with traits and structs, but I do miss it often.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I typically try out ideas outside of my codebase. A quick, isolated proof of&amp;#xA;concept or mock. Isolation being the key. With python, JavaScript&amp;#xA;or Ruby that is a mere &amp;lt;code&amp;gt;python3&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;node&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;irb&amp;lt;/code&amp;gt; away. Rust has an &amp;lt;a href=&amp;#34;https://play.rust-lang.org/&amp;#34;&amp;gt;online&amp;#xA;playground&amp;lt;/a&amp;gt; but I miss being able to do this&amp;#xA;locally. A quick &amp;lt;code&amp;gt;cargo init trial&amp;lt;/code&amp;gt; works, but I find it to still be a too big&amp;#xA;hurdle often, and continue hacking in my actual project. And inevitably get&amp;#xA;distracted by some incompatibility or unrelated error, and then fail the PoC.&amp;#xA;It doesn&amp;#39;t have to be a REPL, just a really fast scratchpad or temporary&amp;#xA;workspace.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But even when I work in Python, or Ruby, Bash, JavaScript, I apply the lessons&amp;#xA;that my newest senior peer-programmer has taught me. The lessons that the&amp;#xA;Rust-compiler taught me. So even when I don&amp;#39;t write Rust, Rust has made me a&amp;#xA;better programmer. Or less-bad, maybe?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;hr&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;[1]  I consider this a terrible business model; I know from own experience that&amp;#xA;rewrite or upgrades to newer versions are a significant source of Revenue for&amp;#xA;Drupal agencies. I dislike this very much.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;input disabled=&amp;#34;&amp;#34; type=&amp;#34;checkbox&amp;#34;&amp;gt; It is perfectly possible, though to compile a binary that dynamically links, or&amp;#xA;requires external runtimes or other dependencies. But that requires extra&amp;#xA;flags, config and work. Another example of the &amp;amp;quot;good defaults&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Event Source your Spreadsheets for Flexibility and Maintainability</title>
   <link href="https://berk.es/2022/08/16/event-source-your-spreadsheets-for-flexibility-and-maintainability/"/>
   <updated>2022-08-16T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2022/08/16/event-source-your-spreadsheets-for-flexibility-and-maintainability/</id>
   <content type="html">&amp;lt;p&amp;gt;Event Sourcing (ES) is one of those software paradigms that I wish I had known about much earlier. In hindsight &amp;lt;a href=&amp;#34;https://twitter.com/berkes/status/1559268085655371776&amp;#34;&amp;gt;several of my projects and companies would have benefited greatly&amp;lt;/a&amp;gt; from an Event Sourced Architecture.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Event Sourcing is, by no means, a magic bullet. It&amp;#39;s an architecture that has a place and use. But for many &amp;lt;strong&amp;gt;spreadsheets&amp;lt;/strong&amp;gt; it turns out to be an excellent fit!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Spreadsheets are notorious in how they evolve and accumulate logic over time, so applying some best-practices and even architectural patterns up front, helps a lot in keeping the spreadsheet maintainable and extensible.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;(Whether or not you should use a spreadsheet in the first place, is a whole different discussion. The answer is quite often &amp;amp;quot;nope&amp;amp;quot;, in my experience. But let&amp;#39;s assume we checked the alternatives and found: yes, this a good one for a spreadsheet, because [insert reasons])&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For example, I&amp;#39;m tracking my investment portfolio in Google Sheets. Or maybe you need a quick dashboard summarizing the sales you make in various e-commerce platforms.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In such cases, it really helps to set up the sheet &amp;amp;quot;Event Sourced&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;event-source-what&amp;#34;&amp;gt;Event Source What?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Event Sourcing ensures that all changes to application state are stored as a&amp;#xA;sequence of events. Not just can we query these events, we can also use the&amp;#xA;event log to reconstruct past states, and as a foundation to automatically&amp;#xA;adjust the state to cope with retroactive changes.&amp;#xA;&amp;lt;a href=&amp;#34;https://martinfowler.com/eaaDev/EventSourcing.html&amp;#34;&amp;gt;Martin Fowler&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;That&amp;#39;s it! That is everything there is to know about Event Sourcing: all the&amp;#xA;rest is implementation detail. Concepts like Domain Aggregates, CQRS, Commands,&amp;#xA;Command Handlers, Snapshots, Projections, and so forth, which you&amp;#39;ll often see&amp;#xA;when reading about Event Sourcing, are implementation details. Necessary&amp;#xA;details when implementing Event Sourced architectures and to keep code&amp;#xA;maintainable, solve common problems or avoid some downsides.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But for the sake of understanding &amp;lt;em&amp;gt;why&amp;lt;/em&amp;gt; Event Sourcing is useful, the only important part&amp;#xA;is &amp;amp;quot;application state is stored in a series of events&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;To explain with an example: the balance on your bank-account isn&amp;#39;t a column in&amp;#xA;a database table called &amp;lt;code&amp;gt;balances&amp;lt;/code&amp;gt;, it&amp;#39;s the sum of the amounts on each payment&amp;#xA;and deposit ever done on that bank-account: the events are the source, your&amp;#xA;balance is just a derivative thereof.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Event Sourcing is nothing new, it&amp;#39;s actually &amp;lt;em&amp;gt;really&amp;lt;/em&amp;gt; old, probably thousands&amp;#xA;of years. Bookkeeping has been done by writing a log of events, for centuries,&amp;#xA;since as long as it exists, really. But also domains like shipping works like&amp;#xA;this; since ages the captain keeps a &amp;amp;quot;captain log&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/captains_log.webp&amp;#34; alt=&amp;#34;Captain&amp;#39;s Log on Monitor from Star Trek&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;One important detail often used in Event Sourcing, is the so-called projection.&amp;#xA;As said, this is an implementation detail, but one that we can use for the&amp;#xA;spreadsheet version very well too.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A common scenario in this context is taking events created in the write model&amp;#xA;[...] and calculating a read model view (e.g. an order summary containing the&amp;#xA;paid invoice number, outstanding invoices items, due date status, etc.). This&amp;#xA;type of object can be stored in a different database and used for queries.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;https://www.eventstore.com/&amp;#34;&amp;gt;Event Source introduction by EventStoreDB&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In other words: projections are &amp;lt;em&amp;gt;read-only&amp;lt;/em&amp;gt; tables containing summaries,&amp;#xA;conversions from (subsets) of the events. The &amp;lt;code&amp;gt;balance&amp;lt;/code&amp;gt; mentioned above, might&amp;#xA;very well be stored in such a projection, called &amp;lt;code&amp;gt;customer_balances&amp;lt;/code&amp;gt;, in reality.&amp;#xA;So to avoid having to loop through all the 8000&#43; transactions you made in your&amp;#xA;bank account on each tap in your banking-app. Sometimes these are called&amp;#xA;&amp;amp;quot;caches&amp;amp;quot;, since they very much act as a cache: they can be re-built from all&amp;#xA;events at any time, and might be out of data (eventual consistent) at moments.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;spreadsheets&amp;#34;&amp;gt;Spreadsheets&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When designing a spreadsheet, it really pays off to follow some best practices.&amp;#xA;One is to &amp;lt;em&amp;gt;have only one single, obvious, place where to insert data&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In practice, such data most often comes from CSV files, exports, or, indeed,&amp;#xA;logs. Banks, Brokers, E-commerce, Bookkeeping, Project-management: most such&amp;#xA;systems have an export. And with almost all, those exports are some form of&amp;#xA;&amp;amp;quot;list of things that happened&amp;amp;quot;: a list of events!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So. When we insert data, it makes a lot of sense to make the entry a &amp;lt;em&amp;gt;literal&amp;lt;/em&amp;gt;&amp;#xA;copy-paste of that export[1].&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If we then agree this sheet is &amp;amp;quot;append only&amp;amp;quot;[2], we are done: we have an&amp;#xA;Event Source! We now have a sheet that holds the history of the things that we&amp;#xA;wish to track. We can add new entries to the bottom[3] and we can calculate all&amp;#xA;the numbers and summaries that we want from here.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dashboards, cross-references, enrichments, all can be done by referencing and&amp;#xA;querying this one sheet.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;downsides&amp;#34;&amp;gt;Downsides&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Apart from the fact that spreadsheets in itself are quite often the wrong tool&amp;#xA;for the job, this setup has some clear &amp;lt;em&amp;gt;disadvantages&amp;lt;/em&amp;gt; though.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For one, we need quite complex lookups. The formula&amp;#39;s to calculate a sum&amp;#xA;quickly become quite complex when it needs to filter certain events only, or&amp;#xA;group by certain event attributes. But we&amp;#39;ll look at some solutions below.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Spreadsheet&amp;#39;s are very good at 2-dimensional data, but at handling more complex data.&amp;#xA;If, say, an order contains multiple products, it is often represented in a&amp;#xA;clumsy way: for example one row per product but each row repeats the&amp;#xA;order-number, customer-data and so on.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;table&amp;gt;&amp;#xA;  &amp;lt;thead&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;Date&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;order#&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;Price&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;Product&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;Customer&amp;lt;/th&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;  &amp;lt;/thead&amp;gt;&amp;#xA;  &amp;lt;tbody&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;02-12&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;R1337&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;1.99&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;Banana&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;b_@e_com&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;02-12&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;R1337&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.99&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;Apple&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;b_@e_com&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;04-12&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;R8008&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.99&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;Apple&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;d_@e_org&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;  &amp;lt;/tbody&amp;gt;&amp;#xA;&amp;lt;/table&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Or worse, the repeated rows leave those fields empty, and only the first holds it.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;table&amp;gt;&amp;#xA;  &amp;lt;thead&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;Date&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;order#&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;Price&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;Product&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;Customer&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;Total&amp;lt;/th&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;  &amp;lt;/thead&amp;gt;&amp;#xA;  &amp;lt;tbody&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;02-12&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;R1337&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;b_@e_com&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;2.98&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;02-12&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;1.99&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;Banana&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;02-12&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.99&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;Apple&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;04-12&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;R8008&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;d_@e_org&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.99&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;04-12&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.99&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;Apple&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;  &amp;lt;/tbody&amp;gt;&amp;#xA;&amp;lt;/table&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In both cases, we really have data in more dimensions: one order &amp;lt;em&amp;gt;has many&amp;lt;/em&amp;gt;&amp;#xA;line-items (products). In both cases, the &amp;lt;em&amp;gt;event&amp;lt;/em&amp;gt; really is only the placement&amp;#xA;of the order, (order placed event) and not the rows describing the contents of&amp;#xA;that order.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This mismatch is quite impossible to solve easily; it&amp;#39;s a limitation of&amp;#xA;spreadsheets. But becomes more apparent when we wish that the&amp;#xA;copy-pasted-data-entries really were the events, and not &amp;amp;quot;events and then some&amp;#xA;contents of the events&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And then that &amp;amp;quot;Transactions2010020114:32.CSV&amp;amp;quot; that I download from my&amp;#xA;creditcard supplier, or the &amp;amp;quot;Account%20History.csv&amp;amp;quot; that my stock-broker&amp;#xA;provides often includes several types of events in one table. In those cases, I&amp;#xA;need even more complex queries over the events, as every query needs to filter&amp;#xA;out specific events only.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When there are &amp;lt;em&amp;gt;many&amp;lt;/em&amp;gt; events, thousands or more, things get slow. Or even&amp;#xA;impossible[4].&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;As with the dimensions of data, Spreadsheet software often shows that it isn&amp;#39;t&amp;#xA;really designed for such a setup. It often lacks details or requires hacks and manual work.&amp;#xA;For example, when enriching data, you&amp;#39;ll probably need complex &amp;lt;code&amp;gt;IF(ISEMPTY(), 0, THEACTUALFORMULA)&amp;lt;/code&amp;gt; just to avoid pesky &amp;amp;quot;#N/A&amp;amp;quot; errors, which will&amp;#xA;trickle through everywhere. Or a hard-coded range (you will insert an A2:A1001,&amp;#xA;for certain) that now gives the wrong data because you just&amp;#xA;inserted event 1002.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Those limitations are there even if you don&amp;#39;t set you sheets up Event Sourced,&amp;#xA;just that they are more annoying when you try to set it up flexible,&amp;#xA;future-proof and maintainable.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;demo&amp;#34;&amp;gt;Demo!&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The details, some best practices and more background can best be explained with&amp;#xA;an example. As demo, I prepared a &amp;lt;a href=&amp;#34;https://docs.google.com/spreadsheets/d/115cM5Jo5KqsKmCftS4bxkV9B0p-Kh6jPqA4Iljvm6XU/edit?usp=sharing&amp;#34;&amp;gt;Google Sheet, and shared&amp;#xA;it&amp;lt;/a&amp;gt;.&amp;#xA;(feel free to leave your comments in this sheet if you see improvements)&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/dashboard.png&amp;#34; alt=&amp;#34;Dashboard Screenshot&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This demo is a &amp;amp;quot;dashboard&amp;amp;quot; for small e-commerce business. It contains both a&amp;#xA;sample data-set. I deliberately kept the list short, so that we can see and&amp;#xA;cross-reference what happens. The data-set is modelled (very loosely) after how&amp;#xA;an e-commerce platform offers their exports. They are sales and refunds. This&amp;#xA;would be a table into which we can append, by copy-pasting, our monthly&amp;#xA;downloaded CSV entries from this platform.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;the-events&amp;#34;&amp;gt;The Events&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;One tab is called &amp;amp;quot;events&amp;amp;quot;. This is the place where we append new entries as&amp;#xA;they come a in a download. Our demo has one source of events, but often you&amp;#39;ll find&amp;#xA;that you want to include multiple sources. For examples sales made on Etsy and&amp;#xA;sales made on Shopify. The exports from these places will not be compatible.&amp;#xA;It is then a bad idea to mix these up in one &amp;amp;quot;events&amp;amp;quot; tab. Instead, it is far&amp;#xA;cleaner to have multiple &amp;amp;quot;event entry tabs&amp;amp;quot;, e.g. &amp;amp;quot;etsy_events&amp;amp;quot; and &amp;amp;quot;shopify_events&amp;amp;quot;.&amp;#xA;This trick also works for when exports&amp;#39; formats change in future. A provider&amp;#xA;may (and you can be certain at least one will!) change the export format without&amp;#xA;being backwards compatible. Adding or removing columns, changing the format&amp;#xA;etc. In that case I&amp;#39;ll just add a new tab &amp;amp;quot;v202203_events&amp;amp;quot; or so.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;There is &amp;lt;strong&amp;gt;no deduplication&amp;lt;/strong&amp;gt; nor an easy way to deduplicate. So, if on, say&amp;#xA;February 5th we download the entries for &amp;amp;quot;this year&amp;amp;quot;, copy paste them, and then&amp;#xA;in April we download the entries for Q1, we will have all events from Jan 1st&amp;#xA;to Feb 5th double.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The obvious solution is to just not enter double events. But if we need a&amp;#xA;solution for this, I&amp;#39;ll touch on one further below. Some exports don&amp;#39;t allow&amp;#xA;you to provide a date-range, there the same goes: just be sure to copy-paste&amp;#xA;only the new stuff.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A sample from the data to describe some of the &amp;lt;em&amp;gt;Domain Logic&amp;lt;/em&amp;gt; around these events&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;table&amp;gt;&amp;#xA;  &amp;lt;thead&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;DateTime&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;SKU&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;order_id&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;customer_name&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;customer_email&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;cost_basis&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;sale_price&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;costs&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;currency&amp;lt;/th&amp;gt;&amp;#xA;          &amp;lt;th&amp;gt;type&amp;lt;/th&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;  &amp;lt;/thead&amp;gt;&amp;#xA;  &amp;lt;tbody&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;2021-08-02T03:00:50Z&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;KJC_559&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;R71218&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;Jameson Mayhew&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;a href=&amp;#34;mailto:jmayhewkv@usgs.gov&amp;#34;&amp;gt;jmayhewkv@usgs.gov&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;1.46&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;2.03&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.73&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;EUR&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;sale&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;2021-08-02T09:34:36Z&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;AGY_380&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;R71218&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;Jameson Mayhew&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;a href=&amp;#34;mailto:jmayhewkv@usgs.gov&amp;#34;&amp;gt;jmayhewkv@usgs.gov&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;1.21&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;2.04&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.87&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;EUR&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;sale&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;2021-08-02T14:52:44Z&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;HCZ_519&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;R68804&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;Ignaz Vallerine&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;a href=&amp;#34;mailto:ivallerine50@cornell.edu&amp;#34;&amp;gt;ivallerine50@cornell.edu&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.35&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;3.51&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.22&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;USD&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;sale&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;2021-08-03T03:19:36Z&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;UNP_232&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;R68804&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;Ignaz Vallerine&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;a href=&amp;#34;mailto:ivallerine50@cornell.edu&amp;#34;&amp;gt;ivallerine50@cornell.edu&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;1.6&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;1.53&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.13&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;USD&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;sale&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;2021-08-03T11:29:12Z&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;DHQ_687&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;R68804&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;Ignaz Vallerine&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;a href=&amp;#34;mailto:ivallerine50@cornell.edu&amp;#34;&amp;gt;ivallerine50@cornell.edu&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;1.37&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;2.29&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.68&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;USD&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;sale&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;2021-08-03T13:59:03Z&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;SVQ_548&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;R73703&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;Lorens Gulleford&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;a href=&amp;#34;mailto:lgullefordl5@instagram.com&amp;#34;&amp;gt;lgullefordl5@instagram.com&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.86&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;3.41&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.08&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;EUR&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;sale&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;2021-08-06T12:15:00Z&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;UNP_232&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;R68804&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;Ignaz Vallerine&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;a href=&amp;#34;mailto:ivallerine50@cornell.edu&amp;#34;&amp;gt;ivallerine50@cornell.edu&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;-1.53&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.15&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;EUR&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;refund&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;      &amp;lt;tr&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;2021-08-06T07:24:36Z&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;RON_531&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;R19813&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;Roz Cossar&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;&amp;lt;a href=&amp;#34;mailto:rcossarc2@smh.com.au&amp;#34;&amp;gt;rcossarc2@smh.com.au&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.35&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;2.38&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;0.79&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;EUR&amp;lt;/td&amp;gt;&amp;#xA;          &amp;lt;td&amp;gt;sale&amp;lt;/td&amp;gt;&amp;#xA;      &amp;lt;/tr&amp;gt;&amp;#xA;  &amp;lt;/tbody&amp;gt;&amp;#xA;&amp;lt;/table&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The data is duplicated, a unique OrderId describes an order, but additional&amp;#xA;rows describe additional products in that order. We have a type column which&amp;#xA;describes the kind of event.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In domain-language, we see Jameson having placed an order for two products for&amp;#xA;a total of 4.07 (this amount is not included!). The product has some costs&amp;#xA;attached for us (dropshipping fee, the costs to make it, or to buy it). We see&amp;#xA;that some products were bought in EUR, some in USD. And we see that Ignaz got a&amp;#xA;refund for one of the 3 products they ordered three days earlier.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A common export will probably contain much more columns, but for the sake of&amp;#xA;clarity and demonstration, I&amp;#39;ve kept it small.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;dashboard&amp;#34;&amp;gt;Dashboard.&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Now that we have the data, we should start with the most important: what do we&amp;#xA;want to show or calculate.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Spreadsheet experts often call this &amp;lt;a href=&amp;#34;https://blog.sheetgo.com/spreadsheets-tips/5-best-practices-for-spreadsheet-data-structure/&amp;#34;&amp;gt;Start at the&amp;#xA;End&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;To fill the dashboard shown above, we need some chart showing daily sales, we&amp;#xA;need to sum up specific events, to get to sales and cost totals and we need to&amp;#xA;sum all refunds (in the dataset just one, though). We need to group some data&amp;#xA;to count how many unique entries there were and we need to find the best&amp;#xA;selling products.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We now have the source - the events - and a target - the dashboard. Next step&amp;#xA;is to add the intermediate calculations.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;lookup-tables&amp;#34;&amp;gt;Lookup tables.&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Lookup tables, or, in Event Source lingo &amp;lt;em&amp;gt;Projections&amp;lt;/em&amp;gt;, have two purposes in our spreadsheet.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The first is to move all calculations that enrich, parse, or reformat events&amp;#xA;away from the events table. So that this table contains &amp;lt;em&amp;gt;only the data exactly&amp;#xA;as it appears in our exports&amp;lt;/em&amp;gt;. The second is to make summaries, and -yes! There they are- &amp;lt;em&amp;gt;pivot tables&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Our source data has several problems. One is that the timestamps use an ISO&amp;#xA;standard, which Google sheets cannot parse (yes! Google sheet cannot parse the&amp;#xA;one and only official international standard way to write dates. Really).&amp;#xA;The other is that prices are in different currencies. And we want to calculate&amp;#xA;with the exchange rates as they happen on the day of the transaction. We&amp;#xA;certainly cannot just sum price in EUR with one in USD. Often you&amp;#39;ll need to&amp;#xA;parse, enrich or add much more data.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Again, it is unwise to do this in the &amp;amp;quot;events&amp;amp;quot; table. That makes copy-pasting&amp;#xA;very clumsy, it increases the chance of accidentally overriding and it spreads&amp;#xA;your code (formulas) all over. Let&amp;#39;s keep the events tab &amp;amp;quot;append only&amp;amp;quot;: no&amp;#xA;calculations there!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Using&amp;#xA;&amp;lt;a href=&amp;#34;https://infoinspired.com/google-docs/spreadsheet/importrange-function-with-conditions/&amp;#34;&amp;gt;QUERY&amp;lt;/a&amp;gt;&amp;#xA;we can pull the data into another tab. This one I called &amp;lt;code&amp;gt;lookup_sales_events&amp;lt;/code&amp;gt;.&amp;#xA;I prepend all my &amp;amp;quot;lookup tables&amp;amp;quot; to clarify their purpose. And to make clear&amp;#xA;that this is not a place to insert data. Lookup tables too are &amp;amp;quot;read only&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The query here is &amp;lt;code&amp;gt; =QUERY(events!A1:J1001, &amp;amp;quot;where J = &amp;#39;sale&amp;#39;&amp;amp;quot;)&amp;lt;/code&amp;gt;, which selects all&amp;#xA;events whose type is &amp;lt;code&amp;gt;sale&amp;lt;/code&amp;gt;, with all its columns from the events.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A similar tab is added for &amp;lt;code&amp;gt;refund&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Right of this tab I&amp;#39;ve added some enrichments. One is an ugly hack to parse the&amp;#xA;ISO dates, another to calculate the EUR version of any USD-priced event, using&amp;#xA;&amp;lt;a href=&amp;#34;https://www.howtogeek.com/449986/how-to-convert-currency-in-google-sheets/&amp;#34;&amp;gt;&amp;lt;code&amp;gt;GOOGLEFINANCE&amp;lt;/code&amp;gt; Currency&amp;#xA;conversions&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;One of the mentioned technical issue surfaces here: if we were to add events,&amp;#xA;the right columns won&amp;#39;t grow with it automatically. We would miss this data&amp;#xA;without seeing an error! The solution adds complexity to the Demo, but would&amp;#xA;include additional logic in the formulas like &amp;lt;code&amp;gt;IF(ISEMPTY(), &amp;amp;quot;&amp;amp;quot;, [the current formula])&amp;lt;/code&amp;gt;. And even then, it only goes so far as we&amp;#39;ve dragged the right hand&amp;#xA;range to. If we dragged it to row 500 and we insert the 501st (actually, the&amp;#xA;498th, we have two header rows) &amp;amp;quot;sale&amp;amp;quot; event, we will miss it and might not&amp;#xA;notice it! Stay vigilant!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Such projections are also the ideal place to deduplicate any events, in the&amp;#xA;exceptional case that it is impossible to avoid duplicating during entering.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For my overview I like to format this table a little, add some lines, headers,&amp;#xA;colors etc.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;m personally a big fan of adding aggregates[5] of a column, such as sums,&amp;#xA;averages etc &amp;lt;em&amp;gt;above&amp;lt;/em&amp;gt; the table. This makes it much easier to let the table grow&amp;#xA;down (which it will if we add more events) and always reference to the same&amp;#xA;cell for an aggregate. Other people prefer to put such aggregates in their own&amp;#xA;sheet. Whatever rocks your boat.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Some common formulas used to build such lookup tables are &amp;lt;code&amp;gt;QUERY()&amp;lt;/code&amp;gt;,&amp;#xA;&amp;lt;code&amp;gt;FILTER()&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;UNIQUE()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;IMPORTRANGE()&amp;lt;/code&amp;gt;. The last one is great when you want&amp;#xA;your Event Source to be a separate sheet or even CSV file stored somewhere else.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For the sums, averages, etc the common formulas are &amp;lt;code&amp;gt;COUNTIF&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;SUMIF&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;SUM(FILTER())&amp;lt;/code&amp;gt; etc.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Another common type of lookup table can be found in the demo under &amp;lt;em&amp;gt;lookup_products&amp;lt;/em&amp;gt;. Which counts the amount of products sold.&amp;#xA;For this we use &amp;lt;a href=&amp;#34;https://www.extendoffice.com/documents/excel/4707-google-sheets-count-number-of-occurrence.html&amp;#34;&amp;gt;&amp;lt;code&amp;gt;COUNTIF&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;UNIQUE&amp;lt;/code&amp;gt; to count occurrences&amp;lt;/a&amp;gt;&amp;#xA;I chose to build this lookup table from another lookup table. Generally I try to avoid such inference and try to have each lookup_table use only the Event Source as source. But that would complicate and duplicate the formulas to filter only on &amp;amp;quot;sale&amp;amp;quot; events. It&amp;#39;s a trade-off.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And a third type of lookup is a so-called &amp;lt;em&amp;gt;pivot table&amp;lt;/em&amp;gt;. Such a table groups&amp;#xA;data by one column, calculates aggregates over the groups and presents that in&amp;#xA;another table. Under &amp;lt;em&amp;gt;lookup_pivot_daily_sales&amp;lt;/em&amp;gt; we group by day, and calculate&amp;#xA;the total amount of sales for each day. Used to then generate the chart on the&amp;#xA;Dashboard. To make this pivot-table a tad simpler, I added a &amp;lt;em&amp;gt;date&amp;lt;/em&amp;gt; column to&amp;#xA;the &amp;lt;em&amp;gt;sales_events&amp;lt;/em&amp;gt;. This can be done in the pivot table too, but this makes the&amp;#xA;already difficult to grasp pivot table even harder to grasp.&amp;#xA;Like above, I chose to use another lookup_table as source. Same trade-off.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;dashboard-again&amp;#34;&amp;gt;Dashboard again&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;From here we can go back to the dashboard fill in all the data, add a chart,&amp;#xA;and &amp;lt;code&amp;gt;QUERY()&amp;lt;/code&amp;gt; the top grossing products. My rule of thumb is to avoid any logic&amp;#xA;and calculations on the dashboard: just presentation, nothing more. I violated&amp;#xA;this a little, with summing the income and costs and negating the costs.&amp;#xA;Normally I would make another sheet where I calculate this, and then only&amp;#xA;reference this from the dashboard. But that would complicate the demo. So I&amp;#xA;left it for a next round of refactoring.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;future&amp;#34;&amp;gt;Future&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With the events as data-source, we should have all data to calculate any possible&amp;#xA;&amp;lt;em&amp;gt;projection&amp;lt;/em&amp;gt; onto our dashboard. Just add one or more lookup-tables to prepare the data,&amp;#xA;then refer to the outcome in that projection in a dashboard.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Need a monthly summary? Follow the pattern that we already have for our daily sales.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Need a date-range? Add a form (just one or two cells with a different color, or a&amp;#xA;dropdown) to the dashboard and use that to select a subset of the events, and&amp;#xA;either fill another projection with that data, or simply use the value in that&amp;#xA;field as parameter in a formula.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Sometimes, however, we will need data that don&amp;#39;t yet have and that isn&amp;#39;t in our events.&amp;#xA;These basically fall in two categories:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Events from now on have an additional field holding this data.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Events that we don&amp;#39;t yet have.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;We need another lookup table or external data source to enrich this data.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For example we may want to add to our dashboard from which country a sale was&amp;#xA;made and our events lack a country, IP-address or something that can be used to&amp;#xA;provide this country. If we can get the provider to now add that field, we will&amp;#xA;have a list of events without that data, and a list of events with it. Even if&amp;#xA;the provider fills our previous events with this data in the export, it is&amp;#xA;probably not smart to start overwriting the existing events. In Event sourced&amp;#xA;software this generally is not possible and most often &amp;amp;quot;forbidden&amp;amp;quot; even: events&amp;#xA;are read-only, the list is append-only. We should never change past events. We&amp;#xA;should never delete past events.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In our spreadsheet it is possible, but still unwise, as a lot of projections or&amp;#xA;calculations may (and will) break the moment our source table suddenly has&amp;#xA;columns shifted or changed. It is far easier to add a new tab with e.g.&amp;#xA;&amp;lt;code&amp;gt;v3_events&amp;lt;/code&amp;gt; or whatever versioning you want. Then add a projection sheet which&amp;#xA;combines all source &amp;lt;code&amp;gt;event&amp;lt;/code&amp;gt;s tabs and normalizes them. Using complex QUERY()&amp;#xA;formulas. The benefit is that all normalization, filtering, parsing, again,&amp;#xA;happens in the projections and nowhere else.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A second example may be when we need to extend our dashboard with data on how fast&amp;#xA;orders are delivered to the doorstep. For that we will need additional events.&amp;#xA;Events that our delivery service or postal service may have. Many will provide&amp;#xA;a CSV export that we could use as &amp;amp;quot;events&amp;amp;quot; and from which we can then project&amp;#xA;and calculate the time of delivery.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The third example may be when we do have said IP-address in the export but up&amp;#xA;to now never used it. And we want to get the &amp;lt;em&amp;gt;country&amp;lt;/em&amp;gt; from that. Via some&amp;#xA;GeoIP lookup service (some of which even have addons for popular spreadsheet&amp;#xA;software), we could then fetch the &amp;lt;code&amp;gt;country&amp;lt;/code&amp;gt; for an IP address in a lookup&amp;#xA;table.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So, any future extension is done with either more fields on existing events,&amp;#xA;additional events, or with more projections.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Need the amount of days that we didn&amp;#39;t sell? A projection. Average order size?&amp;#xA;Projection. Recurring customers? Projection. And so on. Once the structure is&amp;#xA;established, extending it, is mostly following a pattern.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h1 id=&amp;#34;conclusion&amp;#34;&amp;gt;Conclusion&amp;lt;/h1&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Spreadsheets can be kept clean, clear, extendable, by organizing them with&amp;#xA;ideas from Event Sourcing.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is by no means fully or even properly Event Sourced software. It is,&amp;#xA;however, an architecture to clearly distinguish logic from input from&amp;#xA;presentation in a spreadsheet. And to make extending it in future predictable&amp;#xA;and relatively easy.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It keeps the logic in a simple place. It reduces the amount of indirections&amp;#xA;(references to other cells and other calculations) minimal and flat. And it gives&amp;#xA;you a clear place where to copy and paste new entries.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;hr&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[1] More sophisticated, but omitted from the demo here because of complexity, is to import, or link to, such exports. The &amp;amp;quot;Event Source&amp;amp;quot; then becomes a CSV file, or list of files in a directory somewhere. This greatly eases the &amp;amp;quot;data inserts&amp;amp;quot; as all you need to do, is download the export and plonk it in the right directory with the right name.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[2]  I&amp;#39;m not enough of an excel guru to know if you can actually make a sheet &amp;amp;quot;append only&amp;amp;quot;, but just documenting this, is enough, in my experience. If you don&amp;#39;t trust the users of the sheet to follow the guidelines, a spreadsheet &amp;lt;em&amp;gt;really&amp;lt;/em&amp;gt; is the wrong tool to begin with.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[3] Appending at the bottom is much easier than inserting rows at the top. The oldest entry then sits at the top, and newer ones get added below. But inserting at the top is certainly possible, just more work and more chances of accidentally overwriting data.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[4] There are solutions to this, though. One is a common bookkeeping practice to &amp;amp;quot;close the books&amp;amp;quot;. In Event Sourcing, this is solved with snapshots. Both, in essence, insert an additional event which says &amp;amp;quot;all events up to here can be summarized with the following data:&amp;amp;quot; and then includes that data. With a spreadsheet, I generally copy the sheet every year, insert one or more event that sets all the opening balances copied from on the previous sheet, and I&amp;#39;m good to go.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;[5] For clarity: in DDD, which is often mentioned alongside of ES, an Aggregate has a different meaning. Here an Aggregate is just a calculated value from a column. Such as the sum of all prices or an average or a count.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>It&amp;#39;s not Ruby that&amp;#39;s slow, it&amp;#39;s your database</title>
   <link href="https://berk.es/2022/08/09/ruby-slow-database-slow/"/>
   <updated>2022-08-09T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2022/08/09/ruby-slow-database-slow/</id>
   <content type="html">&amp;lt;p&amp;gt;Many people keep repeating that Ruby is slow. It is. But that doesn&amp;#39;t matter,&amp;#xA;because your database is so much slower that it is the bottleneck. So, an&amp;#xA;alternative title would be &amp;amp;quot;Ruby is slow, but that doesn&amp;#39;t matter for you.&amp;amp;quot;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;While writing a gem that &amp;lt;a href=&amp;#34;https://github.com/berkes/postgres_key_value&amp;#34;&amp;gt;offers key-value storage in your existing Postgresql&amp;#xA;database&amp;lt;/a&amp;gt;, and benchmarking it,&amp;#xA;my old mantra kept popping up: Ruby isn&amp;#39;t slow, the database is slow. So much&amp;#xA;that I decided to collect the benchmarks and backup that mantra for myself.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In the industry, this is called&amp;#xA;&amp;lt;a href=&amp;#34;https://en.m.wikipedia.org/wiki/I/O_bound&amp;#34;&amp;gt;I/O-bound&amp;lt;/a&amp;gt;, and is opposed by&amp;#xA;&amp;lt;a href=&amp;#34;https://en.wikipedia.org/wiki/CPU-bound&amp;#34;&amp;gt;CPU-Bound&amp;lt;/a&amp;gt; performance. Most Ruby&amp;#xA;performance issues that I helped solve, fell in the first. The slowness of Ruby&amp;#xA;wasn&amp;#39;t causing any problems.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;ruby-is-slow-but&amp;#34;&amp;gt;Ruby is slow, but...&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Let&amp;#39;s be clear: ruby is slow. The garbage collector, JIT compiler,&amp;#xA;its highly dynamic nature, the ability to change the code runtime and so on,&amp;#xA;all add up to a sluggish language.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;However, when People say &amp;amp;quot;Ruby is slow,&amp;amp;quot; when diving deeper, this critique&amp;#xA;often falls in one of three categories:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ol&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Yes, Ruby is slow, and that is a problem for our use-case.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Yes, Ruby is slow, but in practice this does not matter for us.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Yes, the Ruby application is slow, but really, its the stack, not just the language.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ol&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I want to dive deeper into the last one, but let&amp;#39;s get the first two out of the&amp;#xA;way, first.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ruby is becoming faster year over year, and while that is very welcome, it&amp;#xA;probably won&amp;#39;t matter in the bigger picture:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I also think that speed isn&amp;#39;t a major factor slowing down Ruby adoption. Most&amp;#xA;people who use Ruby don’t need it to be faster. They like the extra free&amp;#xA;speed, sure. But they weren&amp;#39;t avoiding Ruby for speed reasons.&amp;#xA;-- &amp;lt;a href=&amp;#34;https://www.fastruby.io/blog/ruby/performance/why-wasnt-ruby-3-faster.html&amp;#34;&amp;gt;https://www.fastruby.io/blog/ruby/performance/why-wasnt-ruby-3-faster.html&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Because performance really is very dependant on context:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;[...] how fast does your system need to be? And how fast is it&amp;#xA;now? If you can test its current performance and know what good performance&amp;#xA;looks like, then you should feel confident in making a change. Sometimes&amp;#xA;making one thing slower in exchange for other things is the right thing to&amp;#xA;do, especially if slower is still perfectly acceptable.&amp;#xA;-- &amp;lt;a href=&amp;#34;https://www.goodreads.com/book/show/22512931-building-microservices&amp;#34;&amp;gt;Sam Newman in Building Microservices&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So, often it hardly matters that it is slow, because your use-case does not&amp;#xA;need the scale, speed, or throughput that Ruby chokes on. Or because the&amp;#xA;trade-offs are worth it: Often the quicker development, cheaper development,&amp;#xA;faster time-to-market etc is worth the extra resources (servers, hardware,&amp;#xA;SAAS) you must throw at your app to keep it performing acceptable.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Not always, but often.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;a-quick-benchmark&amp;#34;&amp;gt;A quick benchmark&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;To re-confirm how bad Ruby performs, I made a quick benchmark comparing Ruby&amp;#xA;and Rust for a (simplified) real-world job that I recently ran into: parse a&amp;#xA;CSV, grab a number from a column, then bucket-count the&amp;#xA;results. The simplified version does this for a (My actual version did this for&amp;#xA;a CSV ten times as big as the example used here). The example counts how many&amp;#xA;votes a movie has and groups those counts: between 0 and 10 votes, between 10&amp;#xA;and 100 votes etc.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;To compare somewhat honestly, I tried to create a version in Rust and Ruby that&amp;#xA;are internally as similar as possible. The result is both ugly Ruby and ugly&amp;#xA;Rust. Buggy even. And none are optimized for performance. I&amp;#39;m certain the Ruby&amp;#xA;and Rust version can be improved (but even as Ruby-expert, and Rust-novice, I&amp;#xA;already know the Rust version is easier to optimize further then the Ruby&amp;#xA;version).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;All benchmark code is found in &amp;lt;a href=&amp;#34;https://github.com/berkes/ruby-slow-database-slow&amp;#34;&amp;gt;an accompanying GitHub repo&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is not Proper Science, but it shows the obvious: Ruby is slower[1].&amp;#xA;Rust:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre tabindex=&amp;#34;0&amp;#34;&amp;gt;&amp;lt;code&amp;gt;ber@berkes:db_benchmarks ⌁ time ./target/release/movie_ratings &amp;#xA;Some(0..=10): ###################### - 445&amp;#xA;Some(10..=100): ############################################################ - 1208&amp;#xA;Some(100..=1000): ############################################################################################################### - 2229&amp;#xA;Some(1000..=10000): ############################################# - 914&amp;#xA;Some(10000..=18446744073709551615):  - 7&amp;#xA;&amp;#xA;real&amp;#x9;0m0,162s&amp;#xA;user&amp;#x9;0m0,146s&amp;#xA;sys&amp;#x9;0m0,016s&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;p&amp;gt;Ruby&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre tabindex=&amp;#34;0&amp;#34;&amp;gt;&amp;lt;code&amp;gt;ber@berkes:db_benchmarks ⌁ time ruby movie_ratings.rb &amp;#xA;10000..:  - 7&amp;#xA;1000..10000: ############################################# - 914&amp;#xA;100..1000: ############################################################################################################### - 2229&amp;#xA;10..100: ############################################################ - 1208&amp;#xA;0..10: ###################### - 445&amp;#xA;&amp;#xA;real&amp;#x9;0m1,491s&amp;#xA;user&amp;#x9;0m1,389s&amp;#xA;sys&amp;#x9;0m0,103s&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;p&amp;gt;The Rust version is about 10x as fast as the Ruby version. Relative, this is&amp;#xA;huge! With larger files this speed difference does not increase linear, though,&amp;#xA;but janky. A large part of this time is startup time (hard to measure in this&amp;#xA;use-case) and the JIT compiler. Another part is the GC in Ruby &amp;amp;quot;arbitrarily&amp;#xA;kicking in&amp;amp;quot; and halting all progress until it is finished: dealing with large&amp;#xA;datasets, makes this a real and annoying problem.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But how about the absolute difference here? The Ruby version is just over 1.2&amp;#xA;second slower. Enough to be a little annoying while testing and developing.&amp;#xA;When you run this over-and-over (in automated tests, I hope!) this gobbles up&amp;#xA;mere minutes in a day, though: A total of 1.2 seconds on a script that is ran&amp;#xA;about 20 times while developing and then maybe weekly in a cron? Not at all:&amp;#xA;You wasted half a minute and just under 5 seconds per month.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;m only focusing on CPU here, but Memory is just as big an issue, yet far less&amp;#xA;visible in the typical use-case of modern software: customers, interacting with&amp;#xA;server software, will experience slowness, but won&amp;#39;t directly experience memory&amp;#xA;use. The main reason, however, not to dive to deep into this, is that&amp;#xA;benchmarking memory is rather complicated.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;So, yes. Ruby is slow and using a lot of resources. It makes trade-offs, so&amp;#xA;maybe the overall costs, including development, is less. It depends on your&amp;#xA;case, is never an absolute.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;its-the-stack-that-makes-it-slow-not-just-the-language&amp;#34;&amp;gt;It&amp;#39;s the stack, that makes it slow, not just the language.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Now, let&amp;#39;s address the elephant in the room: Rails. While there&amp;#xA;are some Ruby projects that don&amp;#39;t deal with Rails, the majority of Ruby-code&amp;#xA;running in production is running Ruby on Rails. I personally write most of my&amp;#xA;code in Ruby, but hardly ever write Rails (I don&amp;#39;t like Rails very much; another&amp;#xA;post, another time), but I&amp;#39;m also aware that I&amp;#39;m an exception that&amp;#xA;to the rule: Ruby development is almost always &amp;amp;quot;web-development in Rails.&amp;amp;quot;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;One issue with Rails (or, arguably, a benefit?) is that it is highly&amp;#xA;coupled to the database. Rails is all about The Sacred Dictating Database.&amp;#xA;Without a database, Rails is pretty useless, and gets in your way more than it&amp;#xA;helps[2]. Furthermore, Rails is about the web. You can do non-web-stuff in Rails,&amp;#xA;but that really makes no sense at all: Rails is for HTTP. And Rails is &amp;lt;em&amp;gt;big&amp;lt;/em&amp;gt;,&amp;#xA;massively so[3]. And often chooses ergonomics (developer friendliness) over&amp;#xA;performance, as does Ruby, the language. This is fine! But this means that in&amp;#xA;Rails, even more than in Ruby, performance &amp;lt;em&amp;gt;is&amp;lt;/em&amp;gt; a problem.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So, the &amp;amp;quot;stack&amp;amp;quot; means &amp;amp;quot;Ruby on Rails using a database.&amp;amp;quot; And because Rails&amp;#xA;is web, doing only[4] HTTP request-responses we&amp;#39;ll be looking at Ruby in&amp;#xA;context of web-services only.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;To dissect the issue, I&amp;#39;ll be comparing some non-rails, non-http, ruby scripts.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ruby isn&amp;#39;t particular good at juggling significant amounts of data, yet this is&amp;#xA;what, deep down, webservices are all about.&amp;#xA;To illustrate the relative performance, we compare writing, and reading a&amp;#xA;million records to various sources: Memory, a SQLite database in memory and a&amp;#xA;Postgresql database.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Obviously, and unsurprisingly, memory is magnitudes faster than anything else[7].&amp;#xA;Postgresql, here, is a docker container that gets only only CPU, and isn&amp;#39;t tuned&amp;#xA;at all. This is not about the absolute numbers, so it doesn&amp;#39;t matter that much&amp;#xA;what the exact Postgresql setup is. What matters is the magnitute of difference.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-text&amp;#34; data-lang=&amp;#34;text&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;ber@berkes:db_benchmarks ⌁ ruby ruby_slow.rb &amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;                           user     system      total        real&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;Mem write              0.005277   0.000000   0.005277 (  0.005271)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;Sqlite mem write       0.080462   0.000000   0.080462 (  0.080464)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;Postgres write         0.665662   0.151700   0.817362 (  3.068891)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;Mem read               0.002772   0.000000   0.002772 (  0.002767)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;Sqlite mem read       10.323161   0.021355  10.344516 ( 10.345039)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;Postgres read          8.296689   0.041118   8.337807 (  8.682667)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Writing into a database is &amp;lt;em&amp;gt;slow&amp;lt;/em&amp;gt;. And it gets slower even when you have tuned&amp;#xA;the database for read-speed, with indexes, and/or that database is under load.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The above timings do need more dissecting, though. They don&amp;#39;t tell what makes&amp;#xA;it slow. And, surprisingly, this is another part of the stack the ORM. I used&amp;#xA;sequel, because it is simpler, so we can dissect easier.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Looking at two flamegraphs, we see that when inserting, Postgresql really is the&amp;#xA;bottleneck there. This makes sense, because a database has quite some work to do&amp;#xA;on insert. Our table is simple and has only one, and then of the lightest type, index.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Writing to a database is slow. So slow that all other timings become insignificant&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/out_flamegraph_pg_read.svg&amp;#34; alt=&amp;#34;flamegraph of the Postgresql insertion variant&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/out_flamegraph_pg_write.svg&amp;#34; alt=&amp;#34;flamegraph of the Postgresql read variant&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;On reading, Postgresql is less of a bottleneck. This is partly due to the&amp;#xA;extremely simple lookup (no joins, using one index, very little data to fetch, etc).&amp;#xA;The parsing (juggling of data) takes the majority of time: &amp;lt;code&amp;gt;DateTime::parse&amp;lt;/code&amp;gt;.&amp;#xA;Or, reversed: the &amp;lt;code&amp;gt;DateTime::parse&amp;lt;/code&amp;gt; is such a performance-hog, that it makes the&amp;#xA;time spent in the database insignificant.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;We have now identified two performance-problems in our stack: Postgresql, and the ORM&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;To be clear: this isn&amp;#39;t to show that sequel is slow, or that DateTime::parse is&amp;#xA;problematic[8]. This is to show that the more tooling we add to our stack, the worse&amp;#xA;the performance becomes. Again: obvious and unsurprising. But worth reiterating.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Before pulling entire Rails into the benchmarks, lets isolate ORM in Rails:&amp;#xA;ActiveRecord. Again, very much simplified and again, the queries don&amp;#39;t fetch&amp;#xA;anything complex, so the time spent in the database is very little.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre tabindex=&amp;#34;0&amp;#34;&amp;gt;&amp;lt;code&amp;gt;                           user     system      total        real&amp;#xA;Postgres Sequel write  0.679423   0.112094   0.791517 (  2.963639)&amp;#xA;Postgres Sequel read   8.798584   0.011155   8.809739 (  9.194935)&amp;#xA;Postgres AR write      1.741980   0.189130   1.931110 (  4.404335)&amp;#xA;Postgres AR read       1.551020   0.040676   1.591696 (  1.922000)&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;p&amp;gt;Writing Through ActiveRecord:&amp;#xA;&amp;lt;img src=&amp;#34;/images/inline/flamegraph_ar_write.svg&amp;#34; alt=&amp;#34;Flamegraph AR Write&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Reading Through ActiveRecord:&amp;#xA;&amp;lt;img src=&amp;#34;/images/inline/flamegraph_ar_read.svg&amp;#34; alt=&amp;#34;Flamegraph AR Read&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Reading Through Sequel:&amp;#xA;&amp;lt;img src=&amp;#34;/images/inline/flamegraph_sequel_read.svg&amp;#34; alt=&amp;#34;Flamegraph Sequel Read&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Writing Through Sequel:&amp;#xA;&amp;lt;img src=&amp;#34;/images/inline/flamegraph_sequel_write.svg&amp;#34; alt=&amp;#34;Flamegraph Sequel Write&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We can clearly see that the &amp;lt;code&amp;gt;DateTime::parse&amp;lt;/code&amp;gt; from sequel remains a problem. I&amp;#xA;suspect, ActiveRecord uses a much better performing method to hoist the&amp;#xA;datetime from Postgresql to a native DateTime.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Yet relatively speaking, this poor performance of Ruby hardly matters. It&amp;#xA;hardly matters that Ruby halts all code for 15ms to do garbage collection, if&amp;#xA;the fastest database-query takes 150ms. The entire overhead of JIT, all the&amp;#xA;bazillion layers of Rack and Rails HTTP parsing and forwarding, is nothing&amp;#xA;besides a 190ms insert query writing to the database.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The example fetches a single record from a single table: no things that a&amp;#xA;relational database is so good for, but which also causes actual performance&amp;#xA;issues in that database: no joins, sorting, filtering, calculations etc.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;So, even with a very poor performing ORM, the Database remains the primary time consumer.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;scaling-up&amp;#34;&amp;gt;Scaling Up&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We&amp;#39;ve all been there: our Ruby/Rails code grows so convoluted becomes so poorly&amp;#xA;set-up that the stack (or your custom code) really is the bottleneck: this&amp;#xA;is easy to solve! Just add extra servers. A single request won&amp;#39;t become much&amp;#xA;faster, but at least the load on the server no longer brings down the&amp;#xA;performance for all other users. Your app won&amp;#39;t become faster, but will be able&amp;#xA;to grow to more users.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You can easily do this, until the database becomes the bottleneck again.&amp;#xA;Writing to a relational database is always a centralised problem: we can only&amp;#xA;scale that up vertically: larger database-server. For the query (read) side, we&amp;#xA;can add complexity to solve it: read-copies (formerly known as &amp;amp;quot;slaves&amp;amp;quot;).&amp;#xA;Almost all common relational database servers allow this. It&amp;#39;s not trivial,&amp;#xA;because it introduces &amp;amp;quot;eventual consistency&amp;amp;quot; to a setup/framework that was never&amp;#xA;designed to be eventual consistent but it&amp;#39;s doable. Writing (create, insert,&amp;#xA;update, delete etc) isn&amp;#39;t: the database will, at some point, be your&amp;#xA;bottleneck. Unless it isn&amp;#39;t ever: but then performance never was a problem to&amp;#xA;begin with.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Solving performance issues in Ruby code is easy: just throw more servers at&amp;#xA;it. Solving database performance issues isn&amp;#39;t that easy because scaling up a&amp;#xA;relational database is hard or even impossible at some point.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Another conclusion here would be that to keep your code scalable you should&amp;#xA;keep as much logic, transformations, etc in code[9]. By pushing business logic,&amp;#xA;constraints, validations and calculations into the database, you lose the&amp;#xA;simplest, and often cheapest, means of performance gains: &amp;amp;quot;Throw More Servers&amp;#xA;At It.&amp;amp;quot;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;rails&amp;#34;&amp;gt;Rails&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;As mentioned several times, Rails&amp;#39; complexity does cause real and hard to solve&amp;#xA;performance-issues. So we need to look at that too.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;To throw a quote by DHH back at Rails:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;“All of the fancy optimizations are optimizations to get you closer to the&amp;#xA;performance you would’ve gotten if you just hadn’t used so much technology” ☝️&amp;#xA;&amp;lt;a href=&amp;#34;https://macwright.com/2020/05/10/spa-fatigue.html&amp;#34;&amp;gt;https://macwright.com/2020/05/10/spa-fatigue.html&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;https://twitter.com/dhh/status/1259644085322670080&amp;#34;&amp;gt;https://twitter.com/dhh/status/1259644085322670080&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Rails&amp;#39; internal complexity has two profound effects on performance. One is that&amp;#xA;it has tons of abstractions, critically referred to as &amp;amp;quot;Black Magick.&amp;amp;quot;&amp;#xA;The other is that your data passes through all these layers and all this&amp;#xA;complexity before the request-response of a typical HTTP cycle is finished.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With Ruby being a relatively slow language when handling data (see below),&amp;#xA;the more code your data has to pass through, the slower the result becomes.&amp;#xA;This is true for all software, but amplified with Ruby. Rails&amp;#39; 163500 lines of&amp;#xA;Ruby (dd 01-04-2022) certainly don&amp;#39;t help speed this up.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;amp;quot;Lines of code&amp;amp;quot; are not a metric for performance, but they are an indication.&amp;#xA;Don&amp;#39;t forget that even the smallest rails project still boots hundreds of&amp;#xA;thousands of lines of code, even if your data passes through only a fraction&amp;#xA;thereof.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Benchmarking Rails&amp;#39; has been done over and over. Instead of continuing with the&amp;#xA;&amp;amp;quot;benchmarks&amp;amp;quot; and flamegraphs for the entire stack, including Rails, I&amp;#39;ll now&amp;#xA;get a bit more meta. Less on numbers, more conceptual. Because with Rails, I&amp;#39;m&amp;#xA;convinced the performance problems are conceptual. Technical performance problems,&amp;#xA;as showed above, are caused by Ruby, not Rails.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;ActiveRecord (the implementation in Rails, not the pattern per-sé) is an&amp;#xA;abstraction over a system (a relational database) that requires a lot of&amp;#xA;detailed knowledge to keep performant. ActiveRecord (the pattern) not only is a&amp;#xA;leaky abstraction, it mostly is an abstraction that hides details that cannot&amp;#xA;be hidden and should not be hidden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;More practically: That one &amp;lt;code&amp;gt;User.active.includes(:roles)&amp;lt;/code&amp;gt; I threw in to fix an&amp;#xA;N&#43;1 query years ago, &amp;lt;a href=&amp;#34;https://axiomq.com/blog/better-performance-for-rails-app-with-joins-and-eager-loading/&amp;#34;&amp;gt;dynamically chooses what it thinks you&amp;#xA;need&amp;lt;/a&amp;gt;.&amp;#xA;It may &amp;amp;quot;suddenly, magically, dynamically&amp;amp;quot; start to build other JOINS and&amp;#xA;queries and degrade your performance. (Okay, not runtime from one minute to the&amp;#xA;next, but over small changes).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;ve had a database server cluster go down on a multi-million-users app,&amp;#xA;because of this: a simple, unrelated change in an unrelated controller&amp;#xA;caused Rails to switch over to an outer join with an humongous materialized&amp;#xA;view that was never meant to be joined this way (it was for reporting). But&amp;#xA;Rails&amp;#39; magic decided that from now on, it was going to use that. Every pageload suddenly did a ~2s&amp;#xA;database query, gobbling all CPU and IO up on the database server. Ouch.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A stupid mistake, certainly. One that we did not see because in development and&amp;#xA;test, the performance never decreased. But one that we should&amp;#39;ve noticed.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I do, however, blame &amp;amp;quot;Rails&amp;amp;quot; for making it so easy to make that mistake. In&amp;#xA;this specific case, we found and solved it fast. But I&amp;#39;ve worked on&amp;#xA;codebases that were riddled with such mistakes. The only reason those projects&amp;#xA;kept running was because of the huge Heroku server (@$1200/month) keeping it&amp;#xA;afloat for a few hundred visitors. A day. Where such mistakes don&amp;#39;t bring down&amp;#xA;database clusters, but incrementally accumulate to an expensive, and terrible&amp;#xA;performing app. A 20ms slowdown is hardly measurable. Hundred 20ms slowdowns,&amp;#xA;one by one added over months, does make the response unacceptably (it&amp;#xA;depends...I know) slow. And, worst of all, where these &amp;amp;quot;mistakes&amp;amp;quot; were labelled&amp;#xA;as &amp;amp;quot;Done The Rails Way&amp;amp;quot; by the team.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Rails is full of such footguns (Which Rails calls &amp;lt;a href=&amp;#34;https://rubyonrails.org/doctrine#provide-sharp-knives&amp;#34;&amp;gt;sharp&amp;#xA;knives&amp;lt;/a&amp;gt;). Most of which are&amp;#xA;harmless on their own. But which compound. It&amp;#xA;is easy to join up tables in suboptimal ways, to sort or filter on un-indexed&amp;#xA;columns. Active-record is filled with tools that make it easy to abuse the&amp;#xA;database horrendously, without warnings. The amount of Rails apps that I worked&amp;#xA;on, whith some form of &amp;lt;code&amp;gt;.sort(params[:sort_by])&amp;lt;/code&amp;gt; is astounding: in 2021 alone,&amp;#xA;I worked on three separate rails apps, all of which could DOS the database by&amp;#xA;firing requests with &amp;lt;code&amp;gt;?sort=some_unindexed_field&amp;lt;/code&amp;gt;. While this example is&amp;#xA;extreme and may count as security issue, it illustrates how easy it is to make&amp;#xA;your apps performance horrible.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The sorting-by-un-indexed-field example illustrates how Rails&amp;#39;&amp;#xA;coupling to the database makes many of its performance issues database&amp;#xA;issues.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In my experience, performance issues in Rails are always:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ol&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;N&#43;1 queries. Easy to detect. Hard to fix (without introducing massive coupling issues).&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Unoptimized joins. It&amp;#39;s far too easy to add simple &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt; which allows&amp;#xA;developers to fire queries at the database that are way too heavy. This is&amp;#xA;near impossible to fix once introduced and spread through the app. There&amp;#39;s&amp;#xA;always code that, in the end runs something like&amp;#xA;&amp;lt;code&amp;gt;User.with_access_to(project).notifications.last.sent_to&amp;lt;/code&amp;gt;. And which&amp;#xA;happens to query over five join tables and joins on at least one index that&amp;#xA;wasn&amp;#39;t meant for this. Causing some 800ms query. On each pageload.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Unoptimized &amp;lt;code&amp;gt;where&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;group&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;order&amp;lt;/code&amp;gt; calls. Using columns that are hard&amp;#xA;or poorly optimized to filter, group, or order on. Using non-indexed columns.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ol&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;My rule of thumb is that each added or removed &amp;lt;code&amp;gt;where&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;has_many&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;group&amp;lt;/code&amp;gt; or any such&amp;#xA;active-record method &amp;lt;em&amp;gt;must be accompanied by a database migration&amp;lt;/em&amp;gt;.&amp;#xA;Because the only times you don&amp;#39;t need to optimize the database for this new way of querying it,&amp;#xA;is if you already had indexes that you weren&amp;#39;t using before (meaning it was poorly optimized before).&amp;#xA;The other time is when you re-use existing indices, in which case you most&amp;#xA;probably should refactor to move the querying to a single-responsibility (e.g.&amp;#xA;a named scope).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With Rails&amp;#39; human-friendly active-record API, it is easy to forget that you are&amp;#xA;still just querying a complex relational database. One that needs&amp;#xA;nudging and &amp;lt;a href=&amp;#34;https://dba.stackexchange.com/questions/tagged/query-performance&amp;#34;&amp;gt;tuning and&amp;#xA;tweaking&amp;lt;/a&amp;gt;&amp;#xA;just to keep serving you the data within reasonable time.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;With Rails it is easy to stack up many tiny mistakes that make your&amp;#xA;Database the bottleneck. But even if you have all that under control, a high&amp;#xA;performing database-call is still a magnitude slower than many other calls.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;It is still a factor thousand or more faster to fill some array from memory and&amp;#xA;code, then to fill that array from a database. As I showed in the first paragraph.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;so-what-should-i-do-about-it&amp;#34;&amp;gt;So? What should I do about it?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Some rules of thumb that I employ are:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Don&amp;#39;t use the database when avoidable. Which is always more often than I&amp;#xA;think. I don&amp;#39;t need to store the 195 countries of the world in a database and&amp;#xA;join when showing a country- dropdown. Just hardcode it or put in config read&amp;#xA;on boot. Hell, maybe your entire product catalogue of the e-commerce site can&amp;#xA;be a single YAML read on boot? This goes for &amp;lt;em&amp;gt;many&amp;lt;/em&amp;gt; more objects than I often think.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Keep all logic out of the database. It already is the slowest point. And&amp;#xA;hardest to scale up.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Be vigilant of any &amp;lt;code&amp;gt;sort()&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;where()&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;joins()&amp;lt;/code&amp;gt;, etc calls. They &amp;lt;em&amp;gt;must&amp;lt;/em&amp;gt; be&amp;#xA;accompanied by a migration to tune at least the indexes, if added (or removed).&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Keep all database-calls simple. As few joins as possible, as few filters and&amp;#xA;sorts as possible. Databases in general can optimize much easier for this.&amp;#xA;This also keeps the app decoupled from the actual database details.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;N&#43;1 Queries are not always bad. Sometimes even preferred. Because they enable&amp;#xA;business-logic to remain in code. And keep the logic of what to fetch in a&amp;#xA;single place, allowing performance optimization there.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Remain aware of where the actual performance issues fall. Proactively scale up&amp;#xA;according to whether the performance is IO-bound or computational. And pray&amp;#xA;for it to be computational.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;hr&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;[1] To push the counterpoint though: as a Rust noob, I spent over an hour&amp;#xA;writing the Rust version and as a Ruby senior (10&#43; years), less than 10&amp;#xA;minutes. I&amp;#39;d need to run both versions way over 2000 times before the extra&amp;#xA;time I spent developing the Rust version starts paying off in extra time&amp;#xA;waiting for it to run.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;[2] I am certain that you can show me a project where you run Rails without a&amp;#xA;Database and where that makes sense. They cases are there. Some that I came&amp;#xA;across, are: &amp;amp;quot;I already know Rails, but not Sinatra,&amp;amp;quot; or &amp;amp;quot;management requires&amp;#xA;us to run everything on a similar codebase.&amp;amp;quot; Actually, scratch that last one.&amp;#xA;Most are valid reasons, except the last one: that is a horrible reason to&amp;#xA;choose Rails.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;[3] A quick grep: Over 9000 classes, over 33000 methods; excluding all the&amp;#xA;magic dynamic methods like the ones that wrap around your database model. This&amp;#xA;excludes the 70-something dependencies that rails itself comes with.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;[4] A common Rails app will be sending email, probably generating PDFs,&amp;#xA;ingesting CSVs of exporting CSVs, but all interaction, typically, goes through&amp;#xA;HTTP. I&amp;#39;m aware of the exceptions (I worked on them) where Rails is used only&amp;#xA;for running cron-jobs, ETL-pipelines or even media-encoding, but those are&amp;#xA;really that: exceptions.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;[5] Ironically, the performance issue becomes less articulated in this&amp;#xA;non-http, non-rails context, yet in these cases people generally dismiss ruby&amp;#xA;as option, for its performance-issues. Which, catch-22, is one of the reasons&amp;#xA;Ruby is hardly used outside of Rails (and/or Web).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;[7] What might be surprising, is that lookups from the SQLite in memory are&amp;#xA;slower than lookups from the database. But this illustrates another important&amp;#xA;issue: the database runs in a separate thread, maybe even on separate hardware.&amp;#xA;So load gets distributed: with SQLite, and our memory example, one singe Ruby&amp;#xA;thread is doing all the filtering, fetching and hoisting. With an external&amp;#xA;database, this is offset. Depending on your setup, the Ruby thread might even&amp;#xA;continue work while the database does its lookups. In this case, Postgresql,&amp;#xA;which is optimized to filter and fetch data, can do this faster than&amp;#xA;SQLite-inside-ruby. In a typical production setup, the Postgresql is even better&amp;#xA;suited at this.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;[8] Please do note that while &amp;lt;code&amp;gt;DateTime::parse&amp;lt;/code&amp;gt; is slow, this function is&amp;#xA;written in C. The slowness is not because it&amp;#39;s written in Ruby, but probably&amp;#xA;because parsing such complex texts is slow. It would probably be just as slow&amp;#xA;for a feature-comparable version in Rust.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;[9] There are many more reasons why this is a better idea. The most obvious one&amp;#xA;being that you can never put all business-logic in the database, even if you&amp;#xA;wanted. So you will have business-logic in multiple places without any&amp;#xA;structure of what goes where. So the obvious solution to keep it in one place,&amp;#xA;is... to keep in one place. The only place where it all can be kept: your&amp;#xA;application.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>The Waning of Ruby and Rails</title>
   <link href="https://berk.es/2022/03/08/the-waning-of-ruby-and-rails/"/>
   <updated>2022-03-08T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2022/03/08/the-waning-of-ruby-and-rails/</id>
   <content type="html">&amp;lt;p&amp;gt;Almost 12 years ago I answered a &amp;lt;a href=&amp;#34;https://stackoverflow.com/questions/3794270/is-ruby-on-rails-or-at-least-the-community-dying&amp;#34;&amp;gt;StackOverflow&amp;#xA;question&amp;lt;/a&amp;gt;&amp;#xA;&amp;lt;em&amp;gt;is ruby on rails (or at least the community) dying? [closed]&amp;lt;/em&amp;gt; with &amp;lt;em&amp;gt;[there is] still [a] very active community around it&amp;lt;/em&amp;gt;. Today, 2022, I&amp;#xA;see this declining more rapidly than before.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The most obvious (and unscientific) place to look is Google Trends. For&amp;#xA;both Ruby, and Ruby on Rails, we see a clear downward trend for years.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/google_trend_ruby.png&amp;#34; alt=&amp;#34;Google Trends&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;First a sharp bump, then it settles on a sideways pattern, but after&amp;#xA;2016 decline sets in. I don&amp;#39;t have an explanation for the sharp decline&amp;#xA;in 2020 and presume it is a glitch in Googles data. Yet the trend&amp;#xA;remains clear: downwards.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Back in 2010, &amp;lt;a href=&amp;#34;https://stackoverflow.com/a/3794316/73673&amp;#34;&amp;gt;I answered&amp;lt;/a&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ruby on Rails was a Hype. That means a lot of people jumped on the bandwagon because that is what they do: jumping on bandwagons (for a living).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;After that hype, many communities popped up, in various languages that mimic Rails. Or try to. Or just took the good ideas and applied them to their community. Now you have gazillion halfbaked PHP-frameworks, and a few actually good ones. You have Django (python), Zend, Symfony (PHP) and even in Ruby, some alternative frameworks. That has spread the attention. There used to be only One Good Framework (sic.) now there are many.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;That said, Rails 3 has just been released. Rails 3 is cutting-edge again. It has all the ingredients for noSQL (the one-but-latest Hype) HTML5 (the latest Hype) and many javascript-frameworks and interactions (the next-to-be Hype).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;That said, Rails is not just Hypes. It is actually a fantastic framework. With a still very active community around it. Just look at github, and visit the trending repo&amp;#39;s there once in a while and you will see a Great Rails Thing there every week.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If you want to keep up to date, I would advice:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://www.rubyinside.com&amp;#34;&amp;gt;http://www.rubyinside.com&amp;lt;/a&amp;gt; a blog all about Ruby.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://5by5.tv/rubyshow&amp;#34;&amp;gt;http://5by5.tv/rubyshow&amp;lt;/a&amp;gt; a podcast with (most of) all the news in Rails and Ruby land.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;First the last: dedicated podcasts or ruby news-sites have all&amp;#xA;disappeared, many haven&amp;#39;t been replaced. There&amp;#39;s a &amp;lt;a href=&amp;#34;https://rubyweekly.com&amp;#34;&amp;gt;fantastic weekly&amp;#xA;newsletter&amp;lt;/a&amp;gt;, but that&amp;#39;s about it.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This highlights a problem that is a feedback-loop: without good&amp;#xA;information (and tutorials) the influx of new devs will dry up. And&amp;#xA;without a flow of new developers, there is less demand for this information (and&amp;#xA;tutorials). If we look at e.g. Udemy, as of today (march 2022) there are&amp;#xA;a meagre 109 course on Ruby (on Rails) on Udemy. Compared to over 10.000&amp;#xA;each, for Python, Java, or JavaScript. One of the best &amp;lt;a href=&amp;#34;https://news.learnenough.com/ruby-on-rails-800-pound-gorilla&amp;#34;&amp;gt;Rails courses&amp;lt;/a&amp;gt; had its last public update in 2020. Other services such as go-rails are offering courses, just that the landscape is changing, not becoming worse, per-sé.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With a still very active community around it. Just look at github, and visit the trending repo&amp;#39;s there once in a while and you will see a Great Rails Thing there every week.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is no longer the case. Support, gems, and developers working in the&amp;#xA;open, are waning. As example, let&amp;#39;s look at the gems for a service that wasn&amp;#39;t&amp;#xA;around during the initial hype. And one that&amp;#39;s not tied to Rails: Azure.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The support for&amp;#xA;&amp;lt;a href=&amp;#34;https://www.ruby-toolbox.com/categories/Microsoft_Azure&amp;#34;&amp;gt;Azure&amp;lt;/a&amp;gt; is&amp;#xA;in a bad state. Many unmaintained, hardly any activity in last few&amp;#xA;years. With a lot of unresolved issues. For example the official &amp;lt;a href=&amp;#34;https://github.com/Azure/azure-storage-ruby/issues&amp;#34;&amp;gt;Library by&amp;#xA;Azure itself&amp;lt;/a&amp;gt; has 22&amp;#xA;issues open, amongst which dependency issues caused by depending on&amp;#xA;very old versions of other libraries (Nokogiri). I know, this is N=1,&amp;#xA;but I picked this as example, not as proof.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;ve recently started working on a (Ruby, not Rails) project where we&amp;#xA;need a lot of integrations: payment service providers, cloud&amp;#xA;storage, project-management and so on. Modern SAAS - started in the last&amp;#xA;decade, almost all lack Official Ruby Clients or SDKs for their APIs.&amp;#xA;Yet they offer them for Java, JavaScript, Python or even Rust.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;https://api.slack.com/tools&amp;#34;&amp;gt;Slack&amp;lt;/a&amp;gt; has no official clients or SDKs for&amp;#xA;Ruby, (but do for other languages) nor does&amp;#xA;&amp;lt;a href=&amp;#34;https://github.com/Dropbox?q=&amp;amp;amp;type=all&amp;amp;amp;language=ruby&amp;#34;&amp;gt;Dropbox&amp;lt;/a&amp;gt;.&amp;#xA;Azure, as linked above, is hardly maintained, of all&amp;#xA;&amp;lt;a href=&amp;#34;https://github.com/search?o=desc&amp;amp;amp;q=org%3AHubSpot&#43;hubspot-api%2A&amp;amp;amp;s=stars&amp;amp;amp;type=Repositories&amp;#34;&amp;gt;HubSpot&amp;lt;/a&amp;gt;&amp;#xA;API clients, the Ruby version is least popular (based on stars and&amp;#xA;forks) and least frequent updated. Modern project management like&amp;#xA;Monday, Teamleader or Notion all lack any reference to Ruby at all. Do&amp;#xA;note that these are examples of popular SAAS that don&amp;#39;t have primary&amp;#xA;Ruby support. Others do offer it: from AWS to Square: there are&amp;#xA;top-notch, well-maintained gems for them.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I should run an actual data analysis over ruby gems, their repo&amp;#39;s, open&amp;#xA;issues and so on, but glancing over the numbers already shows worrisome&amp;#xA;trends. We can see that, if we grab a handful of SAAS services, Ruby&amp;#xA;support is lacking. Back in 2010, when I answered that SO question, this&amp;#xA;was entirely opposite: the most prominent SDK or API client were the&amp;#xA;Ruby ones. One obvious reason is that, back then, the teams developing&amp;#xA;the APIs and SAAS did this in Ruby themselves. Companies that were&amp;#xA;around then, often have good Ruby clients. Companies from the last few&amp;#xA;years often not. And the community versions of those are often missing&amp;#xA;entirely or poorly maintained.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If we look at large SAAS or software companies, we can see&amp;#xA;that the ones running on Ruby (on Rails) are all from the early days.&amp;#xA;I have a hard time finding any&amp;#xA;successful SAAS building their products on Rails after 2020. Github: 2008, Shopify:&amp;#xA;2006, Twitter: 2006, Groupon: 2008, Zendesk: 2007, AirBnB&amp;#xA;2008, Fiverr: 2010. The only larger companies, running Ruby or Rails and started&amp;#xA;after 2010 I could find are Stripe (2011) and Gitlab (2014). Discourse and&amp;#xA;Mastodon are the only recent popular Ruby-based Open Source projects that I am&amp;#xA;aware of.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Obviously there&amp;#39;s a strong survivor-bias and skewed correlation here:&amp;#xA;successful companies take decades to become that. So naturally&amp;#xA;successful SAAS will be older, regardless of using Rails.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;popped up, in various languages that mimic Rails. [...] Now you have gazillion halfbaked PHP-frameworks, and a few actually good ones. You have Django (python), Zend, Symfony (PHP) and even in Ruby, some alternative frameworks.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;My point, back in 2010, was that Rapid Application Development (RAD)&amp;#xA;development frameworks, using Model View Controller (MVC) architecture,&amp;#xA;were in demand, fueled by the success of Rails. I have a separate&amp;#xA;post planned on &amp;amp;quot;RAD web frameworks&amp;amp;quot;, MVC and ActiveRecord, but it is safe&amp;#xA;to say that such frameworks, amongst which Rails, have found&amp;#xA;their niche, yet are by no means a silver bullet. It us took some&amp;#xA;iterations to find that these architectures are not well-suited for a&amp;#xA;large group of problems and domains and to develop and find&amp;#xA;alternatives. So Rails&amp;#39; decline in popularity may be fueled by a generic&amp;#xA;decline in MVC and RAD, regardless of the language.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When we look at some other sources, &amp;lt;a href=&amp;#34;https://insights.stackoverflow.com/survey/2021#most-loved-dreaded-and-wanted-language-want&amp;#34;&amp;gt;the 2021 StackOverflow survey&amp;#xA;results&amp;lt;/a&amp;gt;&amp;#xA;is telling too: Ruby, and Rails dangle in the bottom quadrant of all&amp;#xA;lists. Ruby is &amp;amp;quot;dreaded&amp;amp;quot; and &amp;amp;quot;loved&amp;amp;quot; in equal measure. Unfortunately,&amp;#xA;they don&amp;#39;t have accessible &amp;lt;em&amp;gt;trends&amp;lt;/em&amp;gt; published, but StackOverflow has a&amp;#xA;separate tool based on activity on StackOverflow by tags:&amp;#xA;&amp;lt;a href=&amp;#34;https://insights.stackoverflow.com/trends?tags=java%2Cc%2Cc%2B%2B%2Cpython%2Cc%23%2Cvb.net%2Cjavascript%2Cassembly%2Cphp%2Cperl%2Cruby%2Cvb%2Cswift%2Cr%2Cobjective-c&amp;#34;&amp;gt;Ruby&amp;lt;/a&amp;gt;&amp;#xA;is both waning and in the lower quadrant there too for decades. This is&amp;#xA;confirmed by the &amp;lt;a href=&amp;#34;https://www.tiobe.com/tiobe-index/&amp;#34;&amp;gt;Tiobe index&amp;lt;/a&amp;gt;,&amp;#xA;where Ruby is declining slowly, year after year. Both relative to other&amp;#xA;languages and absolute.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So, purely anecdotal, and mostly by glancing over published numbers, and based on a&amp;#xA;gut-feeling, I would revise my statement from 2010: yes, Ruby, ruby on&amp;#xA;rails, the community is clearly waning. Not dying, though! Just like&amp;#xA;Pascal and COBOL (and Perl) never died, if only because of legacy, Ruby&amp;#xA;will remain around. I would certainly not say it is a ship going down:&amp;#xA;But a ship slowing down month after month.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Popularity, however, says nothing about quality. If popularity was a&amp;#xA;measure for quality, then Internet Explorer 6 was the best browser ever&amp;#xA;(note for millennials: it wasn&amp;#39;t). Ruby still gives the fantastic&amp;#xA;development experience it gave when it was released in 2005. It only&amp;#xA;became better. Rails still is a great way to get a prototype&amp;#xA;demo, or minimum viable product online in days with least surprises.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Does that mean learning Ruby or Rails is a poor career choice?&amp;#xA;Certainly not! The &amp;lt;a href=&amp;#34;https://www.linkedin.com/jobs/search/?keywords=Ruby&amp;#34;&amp;gt;demand for Rails and Ruby&amp;#xA;developers&amp;lt;/a&amp;gt; is high&amp;#xA;as ever, if only because general demand for developers is ever&amp;#xA;increasing. And all that SAAS, built since 2008, needs developers for&amp;#xA;the coming decades. But be aware of the risk of becoming the COBOL or Perl&amp;#xA;developer maintaining some dusty legacy if the downward trend for Ruby&amp;#xA;continues as it did last decade.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Dutch government killing Post-a-Coin, my crypto startup</title>
   <link href="https://berk.es/2020/01/10/dutch-government-killing-crypto-startups/"/>
   <updated>2020-01-10T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2020/01/10/dutch-government-killing-crypto-startups/</id>
   <content type="html">&amp;lt;p&amp;gt;Last summer I started a sideproject.&amp;#xA;&amp;lt;a href=&amp;#34;https://postacoin.com&amp;#34;&amp;gt;Post-a-Coin&amp;lt;/a&amp;gt;. A simple and straightforward idea,&amp;#xA;really: you can buy high quality postcards with Bitcoin preloaded to give&amp;#xA;as a gift. For the crypto-nerds: a paper wallet, really^1.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I envisioned it as a small side-project: a tiny company that&amp;#xA;makes some revenue to keep itself running, but which, above all, helps&amp;#xA;to put bitcoin into more hands.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/postacoin_example.jpg&amp;#34; alt=&amp;#34;An example of a card I&amp;#39;m selling&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This autumn, I decided to stop rolling it out. To kill it off.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The reason is the draconian Dutch implementation of the European AMLD5&amp;#xA;directive, which is certainly going to solve the &amp;amp;quot;&amp;lt;em&amp;gt;rampant problem with&amp;#xA;money laundering with cryptocurrencies /s&amp;lt;/em&amp;gt;&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This law is the Dutch interpretation of a European guideline, &amp;lt;a href=&amp;#34;https://www.coindesk.com/dutch-crypto-startups-brawl-with-regulators-over-scope-of-eu-money-laundering-rule&amp;#34;&amp;gt;but the&amp;#xA;Dutch version goes far further than what is required by the European&amp;#xA;directive&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Here, the Dutch Central bank will to perform the supervision over all companies&amp;#xA;dealing with cryptocurrencies: custodial wallets, exchanges, services,&amp;#xA;etc. This law was supposed to activate today, January 10th 2020, but has&amp;#xA;been postponed because of bureaucratic reasons, not because it is a bad&amp;#xA;law, but some technicalities with a debate in the House&amp;#xA;of Representatives being cancelled last year.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Don&amp;#39;t get me wrong, I consider it a &amp;lt;em&amp;gt;good&amp;lt;/em&amp;gt; thing when governments step up&amp;#xA;and try to protect the society they represent, with laws. I consider it&amp;#xA;&amp;lt;em&amp;gt;Good For Bitcoin&amp;lt;/em&amp;gt;, when our government decides to supervise the companies&amp;#xA;that deal with bitcoin (or any other cryptocurrency). If recognition and&amp;#xA;supervision by a government can fix the bad reputation of&amp;#xA;Cryptocurrencies, then by all means: Implement it!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Whenever I mention Post-a-Coin to family, friends or others unfamiliar&amp;#xA;with the crypto-world, I get a reaction that includes the words&amp;#xA;&amp;amp;quot;Criminals&amp;amp;quot;, &amp;amp;quot;Paedophiles&amp;amp;quot; or &amp;amp;quot;Ransomware&amp;amp;quot;. Sometimes all three. Not&amp;#xA;having to fight that perception would help Post-a-Coin a great deal.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If that supervision is implemented well, it will show the world, and&amp;#xA;Joe-average, that those supervised cryptocurrency-companies are serious,&amp;#xA;well-run, law-abiding companies. And not some dark-web-criminals putting&amp;#xA;ransomware on your PC or selling childporn.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The problem lies in the &amp;lt;em&amp;gt;implemented well&amp;lt;/em&amp;gt;, though. Because the Dutch&amp;#xA;supervision will not be implemented well. &amp;lt;a href=&amp;#34;https://twitter.com/finhstamsterdam/status/1206344611872083969?s=19&amp;#34;&amp;gt;At all! (Dutch twitter thread highlighting all the democratic and legal issues with this implementation)&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;m aware of at least one other company &amp;lt;a href=&amp;#34;https://bottlepay.helpscoutdocs.com/article/40-official-announcement-on-the-shutdown-of-bottle-pay&amp;#34;&amp;gt;stopping its business due to&amp;#xA;this law&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For me, for Post-a-Coin, the problems with the law are five-fold: It is expensive,&amp;#xA;complex, unfair, will result in monopolies and be wholly ineffective:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;It is expensive&amp;lt;/strong&amp;gt;. How much each supervised company has to pay, is&amp;#xA;unclear, but just the fee one has to pay to be supervised is going to&amp;#xA;cost a lot. Numbers like €150.000 or €77.000 per company, per year, have&amp;#xA;been going around. For certain is that &amp;amp;quot;the supervised group of&amp;#xA;companies&amp;amp;quot; have to pay the entire costs of that supervision themselves.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;My company, selling a fifty-odd Bitcoin-postcards a month, will&amp;#xA;certainly not be able to pay such fees. I would need to turn it into a&amp;#xA;company that will rival the likes of Hallmark, just to be able to pay&amp;#xA;those kinds of fees. And even if that were realistic, I don&amp;#39;t want to&amp;#xA;grow into the CEO of one of the largest postcard companies in the world:&amp;#xA;I want it to remain a nice sideproject.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Edit&amp;lt;/strong&amp;gt;: the Central Bank &amp;lt;a href=&amp;#34;https://www.toezicht.dnb.nl/2/50-237929.jspn&amp;#34;&amp;gt;gave a final&amp;#xA;cost-specification&amp;lt;/a&amp;gt;. I&amp;#xA;missed that, eventhough I&amp;#39;m on a mailing list AND asked them about this&amp;#xA;several times, they failed to contact me about this final spec.&amp;#xA;In any case: €5000 for a request for registration (and it looks like you&amp;#xA;have to pay, regardless of whether they will register you in the end).&amp;#xA;Following years are unknown and deemed to be more expensive, because that is&amp;#xA;when &amp;amp;quot;the niche&amp;amp;quot; needs to cover the entire costs of all the&amp;#xA;registrations themselves.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;It is complex&amp;lt;/strong&amp;gt;. I&amp;#39;m running this company from a shared office space. It&amp;#xA;is a just a Sole Trader entity (Eenmanszaak in Dutch). As a tiny&amp;#xA;startup, it makes no sense at all to set up an LLC entity (B.V. in&amp;#xA;Dutch) with a board of directors and such. Yet this is required. Not by&amp;#xA;letter in this law, but everything in the legal documents assumes a large&amp;#xA;company with HR-divisions, Director of Risk and Compliance, boards of&amp;#xA;directors and so on.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;At a meeting with the Dutch Central Bank, I asked them about the&amp;#xA;procedure in which they will review the people on the board of a&amp;#xA;directors, a requirement by that supervision.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The answer was telling: &amp;amp;quot;Your HR department will need to write a&amp;#xA;document about each director of your company&amp;amp;quot;. I could buy myself a box&amp;#xA;of different hats, one with &amp;amp;quot;HR&amp;amp;quot; written on it. And then write those document myself.&amp;#xA;It shows how disconnected an institute like the Central Bank is with the world of small companies.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;They simply cannot envision companies being small. Understandable: up to&amp;#xA;a few years ago, all they dealt with, was supervising a handful of&amp;#xA;large banks. Supervising 20-odd of the biggest multinationals in The&amp;#xA;Netherlands is entirely different from supervising several hundreds of&amp;#xA;startups who are inventing weird stuff with new and hard-to-grasp&amp;#xA;cryptography in monetary systems. Like postcards that hold Bitcoin.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In order to comply, I would, basically, need to enlist Risk and Analysis&amp;#xA;people just to comply with the regulations. Not because the (growing)&amp;#xA;business needs them. Complying is actually really easy here. I know the&amp;#xA;risks, and am 99% certain that I&amp;#39;ll catch a terrorist trying to send a&amp;#xA;bitcoin-loaded postcard somewhere. I&amp;#39;ll know immediately when you are&amp;#xA;using this service to launder money (and will think you are a really&amp;#xA;dumb money-launderer for using such an inefficient mechanism to launder your&amp;#xA;money).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;It is unfair&amp;lt;/strong&amp;gt;. A larger company can very easily just move outside of The&amp;#xA;Netherlands. Technically, any company operating in The Netherlands has&amp;#xA;to register, but if they don&amp;#39;t, there&amp;#39;s nothing in place to force them.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So any company with enough legal personnel on board, will just continue&amp;#xA;operating from Malta, Panama, Ukraine or whatever letterbox-setup they&amp;#xA;can afford. But moving my entire business (Me, a laptop and an offline&amp;#xA;printer) to Panama is a ridiculously heavy move, just to sell some&amp;#xA;postcards.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In other words: &amp;lt;a href=&amp;#34;https://finance.yahoo.com/news/deribit-move-platform-panama-response-202143405.html&amp;#34;&amp;gt;if you can pay lawyers to set up some foreign&amp;#xA;construction&amp;lt;/a&amp;gt;,&amp;#xA;you don&amp;#39;t need to comply. You can basically choose to pay a lot to&amp;#xA;comply, or to pay a lot to not have to comply.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;It won&amp;#39;t achieve what is tried to be achieved&amp;lt;/strong&amp;gt;: Those looking to set-up&amp;#xA;exchanges or other crypto products in a shady or gray area of operation&amp;#xA;will still be able to do so, as explained above. Only those with good&amp;#xA;intentions are hit by this supervision. I assume and hope that the&amp;#xA;benefits for those participating outweigh the downsides, because&amp;#xA;otherwise this law will certainly achieve the exact opposite. The&amp;#xA;well-intended players will be outperformed by those who circumvent these&amp;#xA;laws. Because those who evade the laws operate far cheaper.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;It makes monopolies&amp;lt;/strong&amp;gt;: All above, show that after the law activates, we&amp;#39;ll&amp;#xA;have a large barrier-to-entry for new companies. I don&amp;#39;t blame the current Dutch exchanges or hold anything against them.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But they are the ones benefiting from this in several ways, while&amp;#xA;making it far harder for their competition to grow and accelerate.  The&amp;#xA;existing exchanges benefit from a shakeout and a high barrier to entry,&amp;#xA;if only because it gives them less competition to worry about.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The Dutch Central bank benefits from fewer participants too: they get their money&amp;#xA;either way, but with fewer participants they have to do less work. So&amp;#xA;there are only incentives in place to reduce the amount of companies in&amp;#xA;cryptocurrency.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;hr&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I really liked the idea of Post-a-Coin.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But with such laws coming to place, the Dutch government forces me to&amp;#xA;close my company. Before I&amp;#39;m legally required to apply for registration&amp;#xA;at the Central Bank, &amp;lt;strong&amp;gt;I&amp;#39;ll close down the shop&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Another startup killed by bureaucrats trying to control a niche with rules and laws.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;hr&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;^1: And yes, dear crypto-nerds, I know this is &amp;amp;quot;insecure&amp;amp;quot;. I&amp;#39;ve seen&amp;#xA;your private key on that card, because I will print it on the card and&amp;#xA;cover it with a neat scratch-off-sticker. You only have &amp;lt;a href=&amp;#34;https://postacoin.com/pages/security&amp;#34;&amp;gt;my dearest and&amp;#xA;sincerest promise that I&amp;#39;ll delete them&amp;lt;/a&amp;gt;. Which, in my opinion is good&amp;#xA;enough security for a $25 gift. Which is also an important reason that&amp;#xA;this product cannot and will not be used to launder money: it is&amp;#xA;technically completely unfit&amp;#xA;for that.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Using Bitwarden to store Ansible-vault password</title>
   <link href="https://berk.es/2019/10/19/ansible-vault-password-with-bitwarden/"/>
   <updated>2019-10-19T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2019/10/19/ansible-vault-password-with-bitwarden/</id>
   <content type="html">&amp;lt;p&amp;gt;When provisioning servers with Ansible, managing the secrets is quite a&amp;#xA;hassle, even with ansible-vault helping there.  A typical ansible-vaults&amp;#xA;contains all sorts of critical secret keys, such as API-keys, root&amp;#xA;(sudo) passwords of sysadmins, databases passwords and so on. And it is&amp;#xA;protected with a single password. Anyone running ansible, needs that&amp;#xA;password; otherwise ansible won&amp;#39;t provision properly.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Storing that password in plain text on your hard drive is a definite&amp;#xA;no-go. That is insecure, and with more people involved requires syncing&amp;#xA;it across all your colleagues&amp;#39; computers.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The solution is to use a password manager that allows sharing a password&amp;#xA;with a team. And then hook that up to ansible, so ansible can read it&amp;#xA;from there.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ansible has a feature that allows users to&amp;#xA;&amp;lt;a href=&amp;#34;https://docs.ansible.com/ansible/latest/user_guide/vault.html#providing-vault-passwords&amp;#34;&amp;gt;define a script which returns the vault password&amp;lt;/a&amp;gt;:&amp;#xA;&amp;lt;code&amp;gt;--vault-password-file&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ansible itself has &amp;lt;a href=&amp;#34;https://github.com/ansible/ansible/blob/devel/contrib/vault/vault-keyring-client.py&amp;#34;&amp;gt;an example&amp;#xA;script&amp;lt;/a&amp;gt;,&amp;#xA;which uses your OS keyring as source for this vault password. But, alas,&amp;#xA;I&amp;#39;m not very comfortable with gnome-keyring and prefer&amp;#xA;&amp;lt;a href=&amp;#34;https://bitwarden.com&amp;#34;&amp;gt;Bitwarden&amp;lt;/a&amp;gt;. Most of these OS-keyrings also don&amp;#39;t&amp;#xA;allow sharing with other users.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bitwarden is my preferred password manager because it is the only truly&amp;#xA;Open-source password manager with sharing-abilities and syncing built&amp;#xA;in. Everything in and around Bitwarden is open-source, even&amp;#xA;the server handling the syncing. You can host it yourself (on premise)&amp;#xA;even; I trust their servers and service so far, but love the idea of&amp;#xA;being able to bail out and host my own, if they ever break that trust:&amp;#xA;When it comes to passwords you&amp;#39;ll really want to avoid any risk of&amp;#xA;vendor lock-in. Open-Source allows for the adagio &amp;amp;quot;Don&amp;#39;t trust,&amp;#xA;verify!&amp;amp;quot;. User-friendly, free, open-source and cross-platform; I really&amp;#xA;don&amp;#39;t understand why it is not more popular, actually.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;ve made a tiny bash script that interacts with bitwarden. It requires the&amp;#xA;&amp;lt;code&amp;gt;bitwarden-cli&amp;lt;/code&amp;gt; to &amp;lt;a href=&amp;#34;https://help.bitwarden.com/article/cli/#quick-start&amp;#34;&amp;gt;be&amp;#xA;installed&amp;lt;/a&amp;gt;, which&amp;#xA;is available for most OSes.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The script requires you to log in to bitwarden first. This can probably&amp;#xA;be added to the script as well, using &amp;lt;code&amp;gt;bw unlock --check&amp;lt;/code&amp;gt;, but that too,&amp;#xA;adds unnecessary complexity: the errors shown when not logged in, are&amp;#xA;clear enough, I think.  Bitwarden (all the apps, so the CLI app too)&amp;#xA;have a staged login: you log in with your email, password, and optional&amp;#xA;2fa. This is set as default on your device. Then you unlock the database&amp;#xA;in order to access the passwords using only the password.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Introducing a tiny bash script called  &amp;lt;code&amp;gt;ansible-vault-pass.sh&amp;lt;/code&amp;gt;, which&amp;#xA;handles the unlocking and then looks up and returns the vault password&amp;#xA;is simple:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-bash&amp;#34; data-lang=&amp;#34;bash&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#!/bin/bash&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;_BW_VAULT_ENTRY_ID&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;ansible-vault&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;_bw_session&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;$(&amp;lt;/span&amp;gt;bw unlock --raw&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;)&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;echo &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;$(&amp;lt;/span&amp;gt;bw get password &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;${&amp;lt;/span&amp;gt;_BW_VAULT_ENTRY_ID&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt; --session &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;${&amp;lt;/span&amp;gt;_bw_session&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt; --raw&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;)&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;It has the entry-id hardcoded; for me, that is &amp;amp;quot;good enough&amp;amp;quot;. Especially&amp;#xA;since it avoids a lot of complexity.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Now, running ansible should be very easy and only prompt for your&amp;#xA;bitwarden password:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-bash&amp;#34; data-lang=&amp;#34;bash&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;ansible-playbook webservers.yml --vault-password-file&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt;/path/to/ansible-vault-pass.sh&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;The &amp;lt;code&amp;gt;--vault-password-file&amp;lt;/code&amp;gt; option can be given a default in the&amp;#xA;main Ansible configuration (commonly &amp;lt;code&amp;gt;~/.ansible.cfg&amp;lt;/code&amp;gt;):&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ini&amp;#34; data-lang=&amp;#34;ini&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;[defaults]&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;vault_password_file&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;/path/to/ansible-vault-pass.sh&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;The latter, somehow, must be an absolute path. No idea why: ansible is weirdly&amp;#xA;picky in these things often.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;No, you can run any ansible command without the &amp;lt;code&amp;gt;--vault-password-file&amp;lt;/code&amp;gt;&amp;#xA;option. For example:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-bash&amp;#34; data-lang=&amp;#34;bash&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;ansible all -a &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;hostname&amp;amp;#34;&amp;lt;/span&amp;gt; -f &amp;lt;span style=&amp;#34;color:#ae81ff&amp;#34;&amp;gt;10&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;h2 id=&amp;#34;result&amp;#34;&amp;gt;Result&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;The vault password can be stored securely in a properly encrypted&amp;#xA;Bitwarden database.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;The vault password can be shared amongst colleagues using Bitwardens&amp;#39;&amp;#xA;built in syncing, sharing and access system.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;The ansible-vault itself can now be used to store all the secrets&amp;#xA;needed for provisioning.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;We only need to type in the bitwarden password every run. Allowing for&amp;#xA;long, difficult, and easily rotatable vault-passwords.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Every colleague has her own bitwarden login, so everyone uses their&amp;#xA;own password instead of having to remember a shared one.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Two security notes, though:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Ansible-vault, as storage for secrets, is secure, but can be&amp;#xA;brute-forced. So using a hard, long and often changing password is&amp;#xA;probably a requirement. Especially if employees rotate often.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Everyone with access to the ansible-vault password in their Bitwarden&amp;#xA;account, can read the entire ansible-vault. Treat all the secrets in&amp;#xA;there as such. E.g. if a colleague leaves, consider rotating all the&amp;#xA;secrets stored in that vault; just revoking the access in Bitwarden&amp;#xA;is not enough, nor is changing the ansible-vault password stored in&amp;#xA;Bitwarden. Anyone with access can have easily dumped all the secrets&amp;#xA;onto their hard drive at some point.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Algemene Voorwaarden Deponeren met Bitcoin Blockchain en IPFS</title>
   <link href="https://berk.es/2019/09/27/algemene-voorwaarden-deponeren-met-bitcoin-blockchain-en-ipfs/"/>
   <updated>2019-09-27T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2019/09/27/algemene-voorwaarden-deponeren-met-bitcoin-blockchain-en-ipfs/</id>
   <content type="html">&amp;lt;p&amp;gt;Anno 2019 gaat het deponeren van &amp;lt;strong&amp;gt;algemene voorwaarden&amp;lt;/strong&amp;gt;, bij de KVK&amp;#xA;gelukkig al wel digitaal. Maar daar is dan alles ook mee gezegd. In &amp;lt;a href=&amp;#34;https://www.kvk.nl/inschrijven-en-wijzigen/deponeren/algemene-voorwaarden-deponeren/&amp;#34;&amp;gt;de&amp;#xA;instructies&amp;lt;/a&amp;gt;&amp;#xA;van deze KVK is te lezen:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;amp;quot;Voorzie het document van je bedrijfsnaam (deze bedrijfsnaam moet gelijk zijn aan de naam waarmee je bent ingeschreven in het Handelsregister) en stuur dit naar .... De datum van ontvangst van je mail of post wordt gehanteerd als deponeringsdatum.&amp;#xA;Zodra de deponering is geadministreerd ontvang je een bevestigingsbrief met de datum van deponering en een factuur.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dit is omslachtig, foutgevoelig en daarmee duur. Een alternatief, maar&amp;#xA;meestal nóg duurder, en vaak net zo omslachtig, is om je algemene&amp;#xA;voorwaarden bij een rechtbank te deponeren.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;De KVK, en rechtbank verzorgen bij het deponeren twee belangrijke functies:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ol&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Een externe partij (iemand anders dan jij of je klant) heeft een kopie van je &amp;lt;em&amp;gt;algemene voorwaarden&amp;lt;/em&amp;gt;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Een vertrouwde externe partij zet een datumstempel op je document:&amp;#xA;bewijs dat jij op datum T het document D had.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ol&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Laat dit nu juist twee functies zijn waar een gedecentraliseerde&amp;#xA;database (blockchain) uitermate geschikt voor is!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dus bedachten wij met Nobleton een deponeringssysteem waarbij we&amp;#xA;moderne technologie, nieuwe en open standaarden aaneen koppelen om die&amp;#xA;twee functies te verzorgen. Vooralsnog enkel voor het &amp;lt;a href=&amp;#34;https://nobleton.nl/deponeren/&amp;#34;&amp;gt;Deponeren van&amp;#xA;&amp;lt;em&amp;gt;algemene voorwaarden&amp;lt;/em&amp;gt;&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Het mooie -volgens mij dan- is dat iedereen dit na kan maken en na kan&amp;#xA;doen. Bij ons is het geautomatiseerd en -vinden wij- super&amp;#xA;gebruiksvriendelijk. Maar alle technologie is vrij, gratis en openbaar&amp;#xA;te gebruiken en zelf te controleren. Met wat onderzoek, het installeren&amp;#xA;van wat software en het aaneen koppelen van checksums, en digitale&amp;#xA;gereedschappen, kun je het best zelf doen. Gratis, en onafhankelijk van&amp;#xA;ons, de KVK of andere bedrijven. Wij nemen je deze stappen en dat onderzoek&amp;#xA;uit handen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Hoe werkt dat dan ongeveer? (Terzijde: ik ben me bewust van de&amp;#xA;oversimplificatie en manke analogieën; het&amp;#xA;klopt inderdaad theoretisch niet perfect. Het gaat mij om het uitleggen&amp;#xA;van de principes, niet om de wiskundige of cryptografische juistheid)&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;opentimestamps&amp;#34;&amp;gt;OpenTimestamps&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dit is zowel een protocol als een vrij te gebruiken dienst.  De dienst&amp;#xA;is &amp;lt;a href=&amp;#34;https://opentimestamps.org&amp;#34;&amp;gt;OpenTimestamps.org&amp;lt;/a&amp;gt;. Waarop je eenvoudig&amp;#xA;documenten kunt uploaden en daarvan een cryptografisch bewijs ontvangt&amp;#xA;dat dit document bestond op het moment van uploaden. Dit bewijs&amp;#xA;gebruikt de Bitcoin Blockchain.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;OpenTimestamps, het protocol, is een lijst afspraken en standaarden&amp;#xA;waaraan software moet voldoen die gebruik wil maken van deze&amp;#xA;stempeldienst. Of waaraan software moet voldoen om zélf een&amp;#xA;stempeldienst te worden. Het is dus geen bedrijf, of stuk software; je&amp;#xA;zit dus niet vast aan één dienst, één software of één partij.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In grote lijnen zeggen de afspraken dat een tijdsstempel als volgt gezet&amp;#xA;wordt:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ol&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Maak een checksum van een document. Een checksum is een&amp;#xA;cryptografisch begrip, te complex om hier in detail uit te leggen, maar hier&amp;#xA;voldoet het om het te zien als een &amp;amp;quot;vingerafdruk&amp;amp;quot;: Elk uniek document&amp;#xA;heeft een unieke vingerafdruk. Verandert er één letter of byte, dan&amp;#xA;krijg je een hele nieuwe vingerafdruk.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;We slaan deze checksum in een &amp;lt;em&amp;gt;Merkle tree&amp;lt;/em&amp;gt;. Dit kun je zien als een&amp;#xA;cryptografische database waarbij je honderden of duizenden zulke&amp;#xA;checksums kunt &amp;amp;quot;samenvatten&amp;amp;quot; in één checksum. En waarbij die éne&amp;#xA;checksum bewijst dat al die honderden er ook inzaten. Een&amp;#xA;OpenTimestamp server verzamelt documenten gedurende minuten, of uren&amp;#xA;en maakt er dan één zo&amp;#39;n &amp;amp;quot;samenvatting&amp;amp;quot; van. Je kunt het ook zien als&amp;#xA;&amp;amp;quot;de vingerafdruk van honderden vingerafdrukken&amp;amp;quot;.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Deze éne checksum wordt opgeslagen in de Bitcoin Blockchain. Een transactie van&amp;#xA;€0,00, waarbij, als het ware, de samengevatte vingerafdruk van&amp;#xA;honderden documenten als &amp;amp;quot;betalingskenmerk&amp;amp;quot; wordt opgenomen. Pas als&amp;#xA;er genoeg documenten verzameld zijn wordt het gestempeld. Maar nooit&amp;#xA;wordt langer dan enkele uren gewacht. Meestal binnen minuten al.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Een verwijzing naar de transactie, doet nu dienst als &amp;amp;quot;tijdstempel&amp;amp;quot;.&amp;#xA;Je hebt nu bewijs dat jij ten tijde van deze transactie het document&amp;#xA;had. Die verwijzing is tevens een soort &amp;amp;quot;eigendomsbewijs&amp;amp;quot; dat jíj het&amp;#xA;liet stempelen en niet je concurrent.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ol&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;bitcoin&amp;#34;&amp;gt;Bitcoin&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We gebruiken de &amp;lt;a href=&amp;#34;https://bitcoinuitleg.nl/&amp;#34;&amp;gt;Bitcoin blockchain&amp;lt;/a&amp;gt; voor&amp;#xA;deze tijdstempels. Ten eerste, omdat hiermee het eerste, bekendste en&amp;#xA;meestgebruikte stempelprotocol, OpenTimestamps werkt. Maar ten&amp;#xA;tweede, omdat Bitcoin veruit de &amp;amp;quot;veiligste&amp;amp;quot; en meest bestendige&amp;#xA;blockchain is.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bitcoin is de meestgebruikte blockchain, met de grootste hoeveelheid&amp;#xA;miners en deelnemers. Daarmee is het een blockchain waarin het zó enorm&amp;#xA;duur is om achteraf nog iets te wijzigen, dat dit nu én in de toekomst&amp;#xA;als onmogelijk geacht wordt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Kortom: staat de &amp;amp;quot;vingerafdruk&amp;amp;quot; van je &amp;lt;em&amp;gt;algemene voorwaarden&amp;lt;/em&amp;gt; er eenmaal&amp;#xA;in, dan kan niemand het meer wijzigen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bij centrale partijen als de KVK is dat wel mogelijk. De KVK kan door&amp;#xA;een hack, storing, menselijke fout, onder dwang, of theoretisch zelfs&amp;#xA;kwaadwillendheid, achteraf deponeringen best nog veranderen.&amp;#xA;Op de Bitcoin Blockchain is dat onmogelijk.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;ipfs&amp;#34;&amp;gt;IPFS&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Vastleggen dat een document bestond op datum T, is één ding. Dat jíj het&amp;#xA;in handen had op datum T een tweede.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar nét zo belangrijk is het kunnen opvragen van gedeponeerde&amp;#xA;documenten. Al je klanten moeten je &amp;lt;em&amp;gt;algemene voorwaarden&amp;lt;/em&amp;gt; kunnen&amp;#xA;opvragen, ongeacht of jowu bedrijf nog bestaat, of dat jij ze (nog) wel&amp;#xA;wilt overhandigen aan die vervelende klant.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wij gebruiken hiervoor een &amp;lt;a href=&amp;#34;https://ipfs.io/&amp;#34;&amp;gt;decentraal bestandssysteem:&amp;#xA;IPFS&amp;lt;/a&amp;gt;. IPFS kun je vergelijken met een enorme &amp;amp;quot;online&amp;#xA;schijf&amp;amp;quot; waarbij iedereen die eraan deelneemt, kleine stukjes van zijn&amp;#xA;schijf beschikbaar stelt en die, aaneengeregen, een enorme,&amp;#xA;onafhankelijke schijf vormen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een nadeel bij deze techniek is dat een bestand wat weinig gebruikt&amp;#xA;wordt, na verloop van tijd niet meer beschikbaar is. De redenen hiervan&amp;#xA;zijn vrij technisch, maar wij lossen dat voorlopig op door de bestanden&amp;#xA;te blijven &amp;amp;quot;seeden&amp;amp;quot;: door ze zelf actief te blijven verspreiden over het&amp;#xA;netwerk. Wij zoeken nog naar andere partijen die dit met ons willen&amp;#xA;gaan doen. Helemaal 100% onafhankelijk is het dus nog niet. Voor de&amp;#xA;geïnteresseerden: bedrijven achter IPFS werken hier ook aan met onder&amp;#xA;andere FileCoin.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Onafhankelijk betekent daarin dat het in principe niet uitmaakt of Nobleton de&amp;#xA;documenten nog heeft, of dat een centrale hostingpartij de servers wel&amp;#xA;online houdt: zolang het IPFS-netwerk van &amp;amp;quot;aaneengekoppelde schijven&amp;amp;quot;&amp;#xA;online blijft, zijn de bestanden te delen. Het is ook niet mogelijk om&amp;#xA;bestanden te wijzigen of te verwijderen. Wanneer versie 13.37 van je&amp;#xA;&amp;lt;em&amp;gt;algemene voorwaarden&amp;lt;/em&amp;gt; gedeeld zijn, zijn ze beschikbaar onder de&amp;#xA;vingerafdruk van dat document. Verander je één letter erin, en wordt die&amp;#xA;geüpload, dan heeft dat een andere vingerafdruk: het is een nieuw&amp;#xA;bestand. Dit nieuwe bestand staat nu naast het oude. Iedere vingerafdruk&amp;#xA;vormt meteen ook een URL (een link) waarop het document te downloaden is.&amp;#xA;Hiervoor is geen speciale software nodig. Voor het plaatsen, het uploaden dus,&amp;#xA;wel.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dit zijn geweldige eigenschappen voor het deponeren en beschikbaar&amp;#xA;stellen van documenten.&amp;#xA;Immers: je bent niet afhankelijk van de medewerking van bedrijven,&amp;#xA;partijen of personen om bij de &amp;lt;em&amp;gt;algemene voorwaarden&amp;lt;/em&amp;gt; van die bedrijven te&amp;#xA;komen. Je kunt ook onafhankelijk nagaan of een versie wel exact klopt:&amp;#xA;niemand kan documenten per ongeluk of stiekem veranderen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bij Nobleton is deze IPFS-vingerafdruk (en dus de link om het te&amp;#xA;downloaden van het IPFS netwerk) tevens ook het deponeringsnummer.&amp;#xA;Simpel en doeltreffend.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;de-toekomst-van-deponeren&amp;#34;&amp;gt;De toekomst van &amp;amp;quot;Deponeren&amp;amp;quot;&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wij geloven dat deze moderne technieken als Bitcoin, Blockchain en IPFS&amp;#xA;het mogelijk maken om véél goedkoper én zonder derden of centrale&amp;#xA;partijen, documenten te kunnen deponeren. Dit is nu nog erg ingewikkeld,&amp;#xA;maar wij denken dit veel eenvoudiger te kunnen maken. Eenvoudiger nog&amp;#xA;dan deponeren bij &amp;amp;quot;klassieke&amp;amp;quot; partijen als de KVK of een rechtbank.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Onze volgende stap is om bestanden ook te kunnen versleutelen, waardoor&amp;#xA;je met dit systeem ook geheime documenten kunt deponeren. We zijn dit&amp;#xA;nog aan het onderzoeken en aan het testen: we moeten immers zorg dragen&amp;#xA;dat het nu én in de toekomst met veel krachtigere computers, alles goed&amp;#xA;en sterk versleuteld is en blijft.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Daarnaast werken we aan een zoekmachine voor de gedeponeerde algemene&amp;#xA;voorwaarden. Deze moet ook helemaal onafhankelijk draaien: ongeacht of&amp;#xA;wij over tien jaar nog bestaan: jij kunt de gedeponeerde documenten&amp;#xA;terugvinden!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Stay Tuned&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>The Private Blockchain Fallacy</title>
   <link href="https://berk.es/2018/09/19/the-private-blockchain-fallacy/"/>
   <updated>2018-09-19T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2018/09/19/the-private-blockchain-fallacy/</id>
   <content type="html">&amp;lt;p&amp;gt;In all the blockchain hype, a simple, yet self-refuting idea&amp;#xA;keeps popping up: a &amp;lt;em&amp;gt;Private Blockchain&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In order to understand why a &amp;lt;em&amp;gt;private blockchain&amp;lt;/em&amp;gt; is nonsense, we must&amp;#xA;first define what a block chain is and what it is not. Since Nakamoto&amp;#xA;&amp;lt;a href=&amp;#34;https://bitcoin.org/bitcoin.pdf&amp;#34;&amp;gt;coined (PDF)&amp;lt;/a&amp;gt; the term, lets see if&amp;#xA;his description helps:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;a peer-to-peer distributed timestamp server to generate computational&amp;#xA;proof of the chronological order of transactions&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It does highlight some important traits of &amp;amp;quot;a block chain&amp;amp;quot; (Nakamoto&amp;#xA;used a space between both words, I use the current popular term&amp;#xA;blockchain):&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;peer-to-peer: implying distribution; at least ruling out a central&amp;#xA;authority&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;computational proof: implying it to be verifiable&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;timestamp-server/chronological ordering: it&amp;#39;s goal, but also implying&amp;#xA;permanence&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Marco Iansiti and Karim R Lakhani have a &amp;lt;a href=&amp;#34;https://en.wikipedia.org/wiki/Blockchain&amp;#34;&amp;gt;more accessible explanation&amp;lt;/a&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;an open, distributed ledger that can record transactions between two&amp;#xA;parties efficiently and in a verifiable and permanent way.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;open (often called &amp;lt;em&amp;gt;permissionless&amp;lt;/em&amp;gt;)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;distributed (often called &amp;lt;em&amp;gt;decentralized&amp;lt;/em&amp;gt;)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;verifiable&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;permanent (often called &amp;lt;em&amp;gt;immutable&amp;lt;/em&amp;gt;)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In other words: anything that does not match those criteria, is by&amp;#xA;definition, not a blockchain. It may be something similar, or even&amp;#xA;something using similar techniques, but not &amp;lt;em&amp;gt;a blockchain&amp;lt;/em&amp;gt;. This is&amp;#xA;important.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We can also look at it from the other side: if a blockchain offers these&amp;#xA;traits, when do we need a blockchain?:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/do_you_need_a_blockchain.png&amp;#34; alt=&amp;#34;logical graph to determine if one needs a blockchain&amp;#34;&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;(I downloaded this image years ago and forgot to note the author.&amp;#xA;Sorry.)&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This has the same treats, but turns the argument around: if your&amp;#xA;requirements don&amp;#39;t fit the exact things that blockchain offer: you don&amp;#39;t&amp;#xA;need a blockchain. In the case of a &amp;lt;em&amp;gt;private blockchain&amp;lt;/em&amp;gt;: if it is not&amp;#xA;public, you don&amp;#39;t need a blockchain. We could stop here.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;reasons-for-wanting-a-private-blockchain&amp;#34;&amp;gt;Reasons for wanting a &amp;lt;em&amp;gt;private blockchain&amp;lt;/em&amp;gt;&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So, why do people come up with private blockchains in the first place?  We can group the arguments into three main arguments. I&amp;#39;ll add a fourth.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ol&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;So we can store private data or other kinds of data that should not&amp;#xA;be public. (We scrap the &amp;lt;em&amp;gt;open&amp;lt;/em&amp;gt; and &amp;lt;em&amp;gt;verifiable&amp;lt;/em&amp;gt; parts for privacy)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;So we can control who has read/write access (We scrap the &amp;lt;em&amp;gt;open&amp;lt;/em&amp;gt;&amp;#xA;part for control)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;So we can scale better (We scrap the &amp;lt;em&amp;gt;distributed&amp;lt;/em&amp;gt; part for speed).&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Because we are not yet confident enough about the security and exact&amp;#xA;traits to release on a public platform (We scrap the &amp;lt;em&amp;gt;permanent&amp;lt;/em&amp;gt; part&amp;#xA;for flexibility)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ol&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The fourth is the only valid argument to ever use a private blockchain:&amp;#xA;as a temporary phase because you are still developing and finding out&amp;#xA;the ins- and outs of your blockchain-application: very similar to how&amp;#xA;you first release an app to your five friends and not upload it&amp;#xA;immediately to an app store.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;All others reasons are pure nonsense, because they don&amp;#39;t &amp;lt;em&amp;gt;need a&amp;#xA;blockchain&amp;lt;/em&amp;gt; to get the exact same outcome. Or because by definition,&amp;#xA;deploying that way, makes it &amp;lt;em&amp;gt;not a blockchain&amp;lt;/em&amp;gt;. One could say, that by&amp;#xA;deploying a blockchain in a private environment, it stops being a&amp;#xA;blockchain.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But, there is more than just a play of words. Keep tuned.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;private-data-public&amp;#34;&amp;gt;Private data: public.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A blockchain has to be public in order to ensure &amp;lt;em&amp;gt;permanence&amp;lt;/em&amp;gt; or&amp;#xA;&amp;lt;em&amp;gt;immutability&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Let&amp;#39;s compare a blockchain to a household book. When I have such a book,&amp;#xA;in a drawer in my office, I&amp;#39;m the only one who can read it and change&amp;#xA;it.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So, when for some reason I bought a pair of expensive shoes, and a week&amp;#xA;later want to hide that fact, I can simply remove that line from the&amp;#xA;book. Or overwrite it with &amp;amp;quot;pair of scissors&amp;amp;quot;. And boom. I never bought&amp;#xA;shoes (according to the book).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But if that book is accessible amongst family members, for everyone to&amp;#xA;review, or copy, it becomes a little more &amp;amp;quot;immutable&amp;amp;quot;.  I can still&amp;#xA;remove the &amp;amp;quot;expensive shoes&amp;amp;quot; line, but people might know, and&amp;#xA;family-members can call me out on it. With even more people who can&amp;#xA;access the book (why not mail a weekly copy to all family and friends?)&amp;#xA;the harder it becomes to change stuff afterwards.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A blockchain, however, has an additional trait: through&amp;#xA;applied cryptography, it allows one to easily detect when a line is&amp;#xA;changed. So all people who have a copy can detect with certainty and&amp;#xA;easily that something has been changed that should not have been&amp;#xA;changed. But this trait is only useful if other stakeholders can get&amp;#xA;copies. In a private blockchain it is really easy to change something,&amp;#xA;even when that change has a big, clearly visible effect. Simply because&amp;#xA;there is no-one to witness that clearly visible effect!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;lt;em&amp;gt;public&amp;lt;/em&amp;gt; part, is what ensures the permanency. Not because it is&amp;#xA;impossible to change something (it is perfectly doable) but because that&amp;#xA;change, or its cryptographically effects, are seen by, other&amp;#xA;participants.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bitcoin, for example is not permanent either, in theoretic sense. If&amp;#xA;enough participants decide that the 0.01 BTC that you paid to John a&amp;#xA;year ago, is sent to Mallory instead, that is what will happen: &amp;lt;a href=&amp;#34;https://blog.ethereum.org/2016/07/20/hard-fork-completed/&amp;#34;&amp;gt;history&amp;#xA;will be rewritten&amp;lt;/a&amp;gt;.&amp;#xA;Bitcoins &amp;lt;em&amp;gt;Immutability&amp;lt;/em&amp;gt; stems from the fact that anyone can detect such&amp;#xA;a change, and that a vast majority then needs to acknowledge that&amp;#xA;change.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;lt;em&amp;gt;public&amp;lt;/em&amp;gt; part furthermore ensures that those participants will get&amp;#xA;hurt when making such a retrospective change: they have a stake in the&amp;#xA;(Bitcoin) blockchain, which will decrease in value because they just&amp;#xA;have proved that this blockchain can be retrospectively mutated.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In a blockchain, public, or private, &amp;lt;em&amp;gt;immutability&amp;lt;/em&amp;gt; is more of an&amp;#xA;agreement amongst all participants that &amp;lt;em&amp;gt;we won&amp;#39;t change history.&amp;#xA;Promised!&amp;lt;/em&amp;gt; it is not some magical &amp;amp;quot;thing that comes from cryptography&amp;amp;quot;.&amp;#xA;In a private blockchain that promise is easy to make. And even easier to&amp;#xA;enforce: if Mallory changes some old record, just throw him out!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With that in mind: immutability is an agreement, so you can just choose&amp;#xA;any database, even a shared excel sheet, and then promise each other&amp;#xA;never to change anything. All you need is detection that something was&amp;#xA;changed, which most database systems (or logging) will offer. You can&amp;#xA;even add some cryptography to it to make tampering more obvious or&amp;#xA;easier to revert or deal with.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;permissionless-because-we-need-control&amp;#34;&amp;gt;Permissionless because we need control.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A blockchain has to be permissionless in order to ensure &amp;lt;em&amp;gt;permanence&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is very simple actually. If you can control who has access, you&amp;#xA;control who decides what the truth is. Therefore, you hold the keys to&amp;#xA;change history in any way that you want.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In the analogy of the household book, the person who controls who may&amp;#xA;write in the book and who may read from it, is, by extension, the person&amp;#xA;who controls what will get in the book and what not.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;That person is, by extension, also the one in full control of rewriting&amp;#xA;the history. Because when the access-controller wants something changed,&amp;#xA;he can simply remove anyone opposing that change from write (and read)&amp;#xA;access and then promote himself (or a proxy) to be able to write. And&amp;#xA;then change it.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If other people can still read, they might notice that history is&amp;#xA;rewritten, but will lack all power to do something about it. The truth&amp;#xA;is what is in the blockchain, and they cannot change that, because of&amp;#xA;lack of permissions. It matters little how clumsy or detectable such a&amp;#xA;retrospective change is: the one with access control can, through that&amp;#xA;power, make these changes.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Immutability in a blockchain is not something magical that comes from a&amp;#xA;fancy technology . But it comes from the conditions a blockchain requires: the&amp;#xA;environment, or &amp;amp;quot;world&amp;amp;quot; in which it runs make it immutable.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The irony is that, actually, there is a magic technology that ensures&amp;#xA;immutability. And that technology is called blockchain. And hence &amp;lt;em&amp;gt;has&amp;lt;/em&amp;gt;&amp;#xA;to be a public blockchain. We&amp;#39;ve come full-circle.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;no-distribution-because-of-scaling-or-speed&amp;#34;&amp;gt;No Distribution because of scaling or speed.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A blockchain has to be distributed in order to ensure &amp;lt;em&amp;gt;a verifiable truth&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The opposite of distribution is centralization. To follow the &amp;amp;quot;household&amp;#xA;book&amp;amp;quot; analogy, distribution would mean that several times a day (maybe&amp;#xA;even after every change) copies are made and exchanged amongst people.&amp;#xA;More important, however, is that none of these copies is considered &amp;amp;quot;The&amp;#xA;Truth&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If there is only ever one copy of the household book, changing stuff in&amp;#xA;there (like ripping out a page) is trivial and -if done well- undetectable.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The obvious solution would be to photocopy every page (say, daily) and&amp;#xA;keep those copies in multiple places. This is distribution. This is&amp;#xA;inefficient, slow and expensive, as opposed to having one copy and simply trusting that.&amp;#xA;But in essence what a blockchain does. Because of the exact reason of&amp;#xA;trust: so that we don&amp;#39;t &amp;lt;em&amp;gt;have to trust one copyholder to tell us the&amp;#xA;truth&amp;lt;/em&amp;gt;. This is the &amp;lt;em&amp;gt;trustless&amp;lt;/em&amp;gt; nature of a blockchain.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When all the copies at all moments can be considered &amp;lt;em&amp;gt;the truth&amp;lt;/em&amp;gt; any&amp;#xA;difference between any copy will be detectable and should trigger an&amp;#xA;alarm. We then need a conflict resolution: which one is the truth.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In Bitcoin, they simply say: what the majority of copy-holders have is&amp;#xA;the truth. So if your book is copied amongst five people and two have in&amp;#xA;there &amp;amp;quot;berkes bought expensive shoes&amp;amp;quot; but three don&amp;#39;t, berkes did not&amp;#xA;buy expensive shoes.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If only one book is elected as The Truth, and all others are simply&amp;#xA;copies that could be used to proof that something was there at some&amp;#xA;point, that really proves nothing: who is to say the person holding the&amp;#xA;copy did not forge some records in there?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Distribution amongst all participants is crucial, because any central&amp;#xA;authority can otherwise change history at will and without detection:&amp;#xA;Verification is not possible and trust in an authority, or even a small&amp;#xA;group of authorities, is required.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;To turn it around: when you don&amp;#39;t need that trustlessness, when you can&amp;#xA;trust a central authority, you don&amp;#39;t need a blockchain. To turn it&amp;#xA;around again: if it requires trust, has an authority, by nature, it is&amp;#xA;not a blockchain.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;but-what-about-my-favorite-coin&amp;#34;&amp;gt;But what about my favorite coin?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If it has a &amp;amp;quot;private&amp;amp;quot; or &amp;amp;quot;permissioned&amp;amp;quot; blockchain, it is not using a&amp;#xA;blockchain. It might still be a cryptographically backed database, a&amp;#xA;solid cryptocurrency of very modern database, but it simply is not using&amp;#xA;a blockchain. And because it is not using it, it lacks one or more of&amp;#xA;the crucial traits.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Which means a single company could run away with your coins at any&amp;#xA;moment (yes, Ripple, looking at you). Or which means it is fundamentally&amp;#xA;insecure. Or it may offer very interesting solutions nonetheless.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I can probably build a very useful and interesting monetary exchange system&amp;#xA;based on trust and a Google spreadsheet. But it ain&amp;#39;t a blockchain. It&amp;#39;s&amp;#xA;&amp;amp;quot;just&amp;amp;quot; a spreadsheet on a Google server (with a really novel idea&amp;#xA;around it).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;you-say-tomato-i-say-tomato&amp;#34;&amp;gt;&amp;amp;quot;You say tomato, I say tomato&amp;amp;quot;&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Sure. If you want to call any &amp;lt;em&amp;gt;database&amp;lt;/em&amp;gt; a blockchain, go ahead. If you&amp;#xA;insist on calling a Google spreadsheet a blockchain: fine. Its not some&amp;#xA;protected or trademarked term. You are even allowed to call my bicycle a&amp;#xA;blockchain. It has a chain after all.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But this helps no-one. Because you are either reinventing a wheel, or&amp;#xA;you are using an extremely inefficient technology in a place where far&amp;#xA;better fitting, more efficient technologies would help you far better.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Some &amp;lt;em&amp;gt;private blockchains&amp;lt;/em&amp;gt; are not blockchains because they are simply&amp;#xA;other technologies.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But unfortunately most &amp;lt;em&amp;gt;private blockchains&amp;lt;/em&amp;gt; are really just&amp;#xA;inefficient, insecure or simply stupid implementations in which a public&amp;#xA;blockchain-setup is used behind closed doors. This offers none of the&amp;#xA;benefits of that public version, but all of the downsides.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Take git: git is not a blockchain; git is &amp;lt;a href=&amp;#34;https://medium.com/girl-writes-code/git-is-a-directed-acyclic-graph-and-what-the-heck-does-that-mean-b6c8dec65059&amp;#34;&amp;gt;a&amp;#xA;DAG&amp;lt;/a&amp;gt;;&amp;#xA;so don&amp;#39;t call it a blockchain. A DAG is very elegant, efficient&amp;#xA;data-structure. That could even be used for distributed data-storage&amp;#xA;within a small group of participants that need some&amp;#xA;cryptographically-secure tamper-detection of their data. I&amp;#39;m mentioning&amp;#xA;git, because there are some cryptocurrencies that do exactly this: using&amp;#xA;a DAG to share truth amongst a private set of participants.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Or take BitTorrent: BitTorrent is not a blockchain; so don&amp;#39;t call it&amp;#xA;that. BitTorrent is a very neat implementation of Merkle trees, and&amp;#xA;really brilliant for distributing large amounts of data over a large&amp;#xA;amount of participants. Not a blockchain. I&amp;#39;m mentioning it, because&amp;#xA;this technology is often enough if you need to ensure that all&amp;#xA;participants have a copy of the data.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;There are literally thousands of types of databases out there, some&amp;#xA;allow ridiculously &amp;lt;a href=&amp;#34;https://couchdb.apache.org/&amp;#34;&amp;gt;easy syncing&amp;lt;/a&amp;gt;, others&amp;#xA;allow &amp;lt;a href=&amp;#34;https://redis.io&amp;#34;&amp;gt;insane throughput&amp;lt;/a&amp;gt;, again others allow really&amp;#xA;thorough consistency checks. Or cryptographically signed logs. Or&amp;#xA;encryption of the data, or sharing of parts of data, or ... I could go&amp;#xA;on. We don&amp;#39;t call all these &amp;amp;quot;blockchains&amp;amp;quot; because of a hype. They are&amp;#xA;not: They are proper solutions to a specific use-case. A blockchain is&amp;#xA;one such solution, and like most technology, it applies to a very small&amp;#xA;subset of use-cases. A &amp;lt;em&amp;gt;private&amp;lt;/em&amp;gt; version, however, can never be such a&amp;#xA;subset.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Positive feedback, as well as images of cats, calling me names, for&amp;#xA;hating on your beloved altcoins, or other comments are very welcome at&amp;#xA;&amp;lt;a href=&amp;#34;https://twitter.com/berkes&amp;#34;&amp;gt;my twitter&amp;lt;/a&amp;gt; or &amp;lt;a href=&amp;#34;https://www.reddit.com/u/berkes&amp;#34;&amp;gt;on&amp;#xA;reddit&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This post was translated to &amp;lt;a href=&amp;#34;https://bitcoinspot.nl/de-misvatting-van-de-private-blockchain.html&amp;#34;&amp;gt;Dutch by&amp;#xA;bitcoinspot.nl&amp;lt;/a&amp;gt;. Thanks!&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Making a case for JavaScript, in-browser Mining</title>
   <link href="https://berk.es/2018/02/15/making-a-case-for-javascript-mining/"/>
   <updated>2018-02-15T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2018/02/15/making-a-case-for-javascript-mining/</id>
   <content type="html">&amp;lt;p&amp;gt;So, today, &amp;lt;a href=&amp;#34;https://en.wikipedia.org/wiki/Brendan_Eich&amp;#34;&amp;gt;Brendan Eich&amp;lt;/a&amp;gt;&amp;#xA;spoke up against mining on your browser.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Eich is the founder of Mozilla, Inventor or JavaScript, and current CEO of the ground-breaking browser &amp;lt;a href=&amp;#34;https://brave.com/&amp;#34;&amp;gt;Brave&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;On &amp;lt;a href=&amp;#34;https://twitter.com/BrendanEich/status/963845740077314048&amp;#34;&amp;gt;twitter, he says&amp;lt;/a&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Brave now blocks the abusive-even-when-opt-in (1.75 cores on my MBP&amp;#xA;which became too hot to call a laptop) 1st party Monero mining script&amp;#xA;at Salon, thanks to fast work by @lukemulks.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I have several problems with this tweet, and this this new&amp;#xA;block-them-all feature in the Brave browser and most adblockers.&amp;#xA;Which I&amp;#39;ll explain below.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;First, some background on Brave: Brave has its own cryptocurrency, BAT,&amp;#xA;which is a payment system that allows you, the user of Brave, to pay a&amp;#xA;website or publisher. A very nice alternative for ads, so to say.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I love this idea, on a high level: to let me, a user, decide&amp;#xA;to pay a site that I peruse. I&amp;#39;ve ran Brave ever since they came with&amp;#xA;this idea and a built-in Bitcoin Wallet, to leverage this same idea&amp;#xA;(which they now moved to a BAT system).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Now, some background on browser-mining. Quite some cryptocurrencies use&amp;#xA;&amp;lt;a href=&amp;#34;https://en.bitcoin.it/wiki/CryptoNight&amp;#34;&amp;gt;a mining algorithm&amp;lt;/a&amp;gt; for which making specialised hardware is impossible&amp;#xA;(currently infeasible, actually). &amp;lt;a href=&amp;#34;https://getmonero.org/&amp;#34;&amp;gt;Monero&amp;lt;/a&amp;gt; is one of them, the most&amp;#xA;famous. So, Monero, for example, is mined on &amp;amp;quot;normal&amp;amp;quot; CPU&amp;#39;s. In order to&amp;#xA;mine more Monero, you need more CPU&amp;#39;s. And because of how the mining&amp;#xA;works, it can be done in your browser, using a miner that is written in&amp;#xA;JavaScript: a browser miner.&amp;#xA;And when I want to &amp;amp;quot;mine more Monero&amp;amp;quot;, where can I &amp;amp;quot;obtain&amp;amp;quot; more CPU? Right: by running that miner on the&amp;#xA;visitors of a website.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;ve spent 3 hours writing this article, spent ฿130 on&amp;#xA;coffee (That is Thai Baht, not Bitcoin) and considerable research on&amp;#xA;monero and coinhive to get this to you. If you like my work, consider&amp;#xA;pressing the button below, to run a miner on your computer, and pay me&amp;#xA;with a few Watts of your electricity. Or to see how it works, without&amp;#xA;paying me, see the &amp;lt;a href=&amp;#34;https://coinhive.com&amp;#34;&amp;gt;demo on Coinhive&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;script src=&amp;#34;https://authedmine.com/lib/simple-ui.min.js&amp;#34; async&amp;gt;&amp;lt;/script&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;coinhive-miner&amp;#34; &amp;#xA;&amp;#x9;style=&amp;#34;width: 638; height: 110px&amp;#34;&amp;#xA;&amp;#x9;data-key=&amp;#34;VIdRYiFeVMnRF81dydg1ey4MTdHKE4xq&amp;#34;&amp;#xA;&amp;#x9;data-autostart=&amp;#34;false&amp;#34;&amp;#xA;&amp;#x9;data-whitelabel=&amp;#34;false&amp;#34;&amp;#xA;&amp;#x9;data-background=&amp;#34;#000000&amp;#34;&amp;#xA;&amp;#x9;data-text=&amp;#34;#eeeeee&amp;#34;&amp;#xA;&amp;#x9;data-action=&amp;#34;#00ff00&amp;#34;&amp;#xA;&amp;#x9;data-graph=&amp;#34;#555555&amp;#34;&amp;#xA;&amp;#x9;data-threads=&amp;#34;2&amp;#34;&amp;#xA;&amp;#x9;data-throttle=&amp;#34;0.5&amp;#34;&amp;gt;&amp;#xA;&amp;#x9;&amp;lt;em&amp;gt;Loading...&amp;lt;/em&amp;gt;&amp;#xA;&amp;lt;/div&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Note that there&amp;#39;s is a big chance that your adblocker is blocking it, so&amp;#xA;&amp;lt;a href=&amp;#34;https://authedmine.com/media/miner.html?key=VIdRYiFeVMnRF81dydg1ey4MTdHKE4xq&amp;#34;&amp;gt;head over to a standalone page&amp;#xA;instead&amp;lt;/a&amp;gt;,&amp;#xA;if you wish. Or disable the adblocker, I&amp;#39;m running nothing but my own&amp;#xA;piwik - pinky promise.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The first problem with Brave&amp;#39;s implementation, is that it is default-on&amp;#xA;and an all-or-nothing thing. An alternative would be a popup like&amp;#xA;&amp;amp;quot;Google Maps want&amp;#39;s to access your location. [Allow] [Never]&amp;amp;quot;. You&amp;#39;ve&amp;#xA;probably seen it. Below is an example from Chrome (in Dutch, sorry):&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/confirmation.png&amp;#34; alt=&amp;#34;Confirmation dialog example&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre tabindex=&amp;#34;0&amp;#34;&amp;gt;&amp;lt;code&amp;gt;berk.es wants to&amp;#xA;  mine Cryptocurrency on your laptop.&amp;#xA;&amp;#xA;                     [Block] [Allow]&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;p&amp;gt;Brave could have gone for this established UI-pattern, but instead they&amp;#xA;chose to &amp;amp;quot;block it all&amp;amp;quot;. A pity. A missed opportunity.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Imagine the possibilities:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Donate to Wikipedia by mining a few minutes, instead of dealing with their annoying beggar-popups&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Use Netflix for free, by running a miner on one CPU core when watching&amp;#xA;series.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Avoid the privacy-abusive advertising industry by running JavaScript&amp;#xA;on your computer, rather than tracking users all over the place.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;CAPTCHA&amp;#39;s, DDoS protection, all by running a few seconds of a miner on&amp;#xA;your laptop.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Have &amp;amp;quot;outgoing affiliate links&amp;amp;quot; that don&amp;#39;t work in secret behind the&amp;#xA;scenes with trackers, cookies and whatnot, but a clear &amp;lt;code&amp;gt;[Mine and Redirect] or [skip mining]&amp;lt;/code&amp;gt;-page URLs. A step-up from a &amp;lt;a href=&amp;#34;https://cnhv.co/1ma5t&amp;#34;&amp;gt;link like&amp;#xA;this&amp;lt;/a&amp;gt; (NOTE: following that link does pay me a few&amp;#xA;grains of coffee actually, thanks!)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The second problem I have with Brave is the fact that &amp;amp;quot;mining&amp;#xA;cryptocurrencies to donate to a website&amp;amp;quot; is a direct competitor of their&amp;#xA;own platform, where you donate to a website by obtaining their own&amp;#xA;currency and paying with that.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Competition is always good, don&amp;#39;t get me wrong.  It&amp;#39;s just that this&amp;#xA;direct competition gives Brave incentive to block their competitors in&amp;#xA;their browser. Or to present the competition as &amp;amp;quot;inherently evil&amp;amp;quot; and so&amp;#xA;on. All under the pretence of &amp;amp;quot;protecting the users&amp;amp;quot;. Which, in some&amp;#xA;way, they actually do.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;m not pretending to know the  that Brave or Brendan Eich are&amp;#xA;implementing their &amp;amp;quot;mining blocking&amp;amp;quot; because of this. But I do know that&amp;#xA;their own incentives are perpendicular to those of incentives like&amp;#xA;Coinhive. That should make you at least wary of what they say about&amp;#xA;each other: a competitor will never say about their competition that it is&amp;#xA;a good alternative.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This could be a reason that the Brave developers chose to not&amp;#xA;white-list &amp;amp;quot;JavaScript mining initiatives&amp;amp;quot; which try to implement an&amp;#xA;actual friendly, opt-in system. Or to work together with those that try&amp;#xA;to be friendly, honest and clear about the mining on a website&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The third problem I have is how this &amp;amp;quot;block all mining&amp;amp;quot; and the noise&amp;#xA;around it, drowns out any proper discussion about it. The&amp;#xA;current stance, fueled by brave and Brendan Eich is that &amp;amp;quot;Browser Mining&amp;#xA;Equals Evil&amp;amp;quot;. I fear the discussion is closed and an otherwise&amp;#xA;interesting technology is killed off.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This certainly is the fault of all the &amp;amp;quot;Evil&amp;amp;quot; miners our there, though.&amp;#xA;They are abusing ad-networks, hacking websites, routers and CDNs,&amp;#xA;for some free Monero. The overwhelming majority of browser-mining is evil and done&amp;#xA;without your consent. That &amp;lt;em&amp;gt;is&amp;lt;/em&amp;gt; bad. And should be blocked.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But the solution to work towards would be to block the evil ones and work&amp;#xA;together with companies like Coinhive, to improve the initiatives for&amp;#xA;honest and friendly-use-cases.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;As a sidenote: One of the biggest &amp;amp;quot;threats&amp;amp;quot; for CPU-mined cryptocurrency&amp;#xA;like Monero, currently, are all these evil players. The miners&amp;#xA;operating botnets, hacking CDNs to inject Browser miners, stealing AWS&amp;#xA;credentials, are rewarded much higher, while honest miners, with a&amp;#xA;laptop (and some cheap solar energy) stand no chance. I would not be&amp;#xA;surprised to learn that a vast majority of mining power in Monero comes from&amp;#xA;&amp;amp;quot;stolen&amp;amp;quot; CPU-cycles. But that is a different discussion.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Which brings me to the &amp;amp;quot;energy use of cryptocurrency&amp;amp;quot;-debate. A lot of&amp;#xA;people dislike all cryptocurrency because it is &amp;amp;quot;uselessly&amp;amp;quot; burning up&amp;#xA;electricity. A stance that I find just as strange as someone being&amp;#xA;against neon-ads, or public television screens. All these screens on&amp;#xA;city-streets, all these huge Coca-Cola signs on top of buildings&amp;#xA;combined -a wild guess, I could not find any proper figures to back this&amp;#xA;up- probably use far more energy than Bitcoin or Monero does. And it is&amp;#xA;just as &amp;amp;quot;useless&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But even if true: a proper, opt-in, consensual miner on a website allows&amp;#xA;someone who dislikes this whole crypto-crap to just ignore it. If you&amp;#xA;don&amp;#39;t like it: don&amp;#39;t participate. If you do see the value in it, think&amp;#xA;it is not &amp;amp;quot;useless&amp;amp;quot;, then it is a nice way to make a micro-payment to&amp;#xA;that site.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But, back to that evil network of JavaScript miners: they have&amp;#xA;messed up an otherwise fantastic opportunity.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You can see this opportunity in action above: a single click of a button and&amp;#xA;you can pay me for writing this (again, I need to stress this to make&amp;#xA;the point clear: you can simply also ignore it!)&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Clicked it? You just paid me!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You did not need to register with PayPal, or pay fees to a&amp;#xA;credit card-company. You did not have to buy some token or currency on an&amp;#xA;exchange. Did not have to install a special browser to donate, there&amp;#xA;were no wallets to top-up, no copies of ID-cards to send to some server&amp;#xA;(that will be hacked in some future) because of KYC-money-laundering-regulations. No&amp;#xA;secret keys to back-up, no wallets that can get hacked, or&amp;#xA;twelve-words-backup-phrases to securely store away in that safe you&amp;#xA;still need to buy.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Just a press of a button and you are rewarding my work. And&amp;#xA;paying for another coffee.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;It is privacy-friendly&amp;lt;/strong&amp;gt;: had I embedded some ads from Bing or Google,&amp;#xA;they, and hundreds of other data-miners out there would be tracking you,&amp;#xA;my precious visitor (the only tracking this site has, is my self-ran,&amp;#xA;self-hosted, privacy-enhanced Piwik analytics). Ads are the really very&amp;#xA;evil, deceptive and abusive alternative here: they mine your data, sell&amp;#xA;it, trade with it, and use it to present more ads. Most of the time all&amp;#xA;this &amp;amp;quot;stealing of your data&amp;amp;quot; happens without your consent and without&amp;#xA;you knowing: that is why I tend to call them thieves: they steal data&amp;#xA;from you that you never agreed on giving away.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;It is very clear and honest, when opt-in&amp;lt;/strong&amp;gt;: I&amp;#39;m &amp;lt;em&amp;gt;not&amp;lt;/em&amp;gt; running anything&amp;#xA;on your computer. I&amp;#39;m not selling your data to trackers or ad-networks.&amp;#xA;I&amp;#39;m offering you a choice to donate to me. Had I ran ads, I would be&amp;#xA;selling the privacy of you, my visitors, without your consent, to large&amp;#xA;companies that live of gathering (stealing) and mining that data. Which&amp;#xA;is far worse. Of all the alternatives, an opt-in JavaScript miner is the&amp;#xA;most honest one, since it&amp;#39;s abundantly clear what I&amp;#39;m offering. And you&amp;#xA;are in your full rights to simply ignore my request.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Now, if you don&amp;#39;t feel like clicking that mining-thing above, feel free&amp;#xA;to &amp;amp;quot;pay&amp;amp;quot; me, by sharing this story, or dropping me &amp;lt;a href=&amp;#34;https://twitter.com/berkes/&amp;#34;&amp;gt;a line on @berkes,&amp;#xA;my twitter feed&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Bitcoin turning into a multi layered system is the most interesting thing in crypto in 2018</title>
   <link href="https://berk.es/2018/02/09/bitcoin-turning-into-a-multi-layered-system-is-the-most-interesting-thing-in-crypto-in-2018/"/>
   <updated>2018-02-09T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2018/02/09/bitcoin-turning-into-a-multi-layered-system-is-the-most-interesting-thing-in-crypto-in-2018/</id>
   <content type="html">&amp;lt;p&amp;gt;When you use Tinder, and you swipe someone, you probably don&amp;#39;t sit there&amp;#xA;thinking &amp;amp;quot;Let&amp;#39;s create some TCP packages and send them over IP, hoping&amp;#xA;they reach the phone of that nice looking fellow there&amp;amp;quot;.  You probably&amp;#xA;just think in terms of &amp;amp;quot;lets swipe this nice fellow, Leo&amp;amp;quot;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;m bringing Tinder into this story to show the power of a layered&amp;#xA;architecture. You can swipe Leo because the Internet is made out of&amp;#xA;layers that You, Tinder, your phone, apps, your browser, can use for&amp;#xA;&amp;amp;quot;free&amp;amp;quot;. Disclaimer: I don&amp;#39;t actually have a Tinder, so I actually don&amp;#39;t&amp;#xA;know if &amp;amp;quot;swiping&amp;amp;quot; is the right term. But, well, this is a story about&amp;#xA;Bitcoin.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So, TCP/IP is made up out of four layers: Link layer, Internet Layer,&amp;#xA;Transport Layer and Application Layer. For this story, only the last&amp;#xA;two are interesting. On the internet, data is transported in the&amp;#xA;transport layer. Applications such as your browser, Tinder,&amp;#xA;your email-client or even the security camera at your front-door, use&amp;#xA;the transport layer to transport data. The power of this design becomes&amp;#xA;apparent if you turn it around: Applications don&amp;#39;t need to invent,&amp;#xA;maintain or run their own network, cables, or protocols. They can just&amp;#xA;tell the Transport layer &amp;amp;quot;Hey, I&amp;#39;ve got a swipe, for Leo, can you&amp;#xA;deliver it to the Tinder servers? (so they can send it along to Leo)&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Now, back to Bitcoin. The Bitcoin community is rolling out&amp;#xA;this thing called &amp;amp;quot;&amp;lt;a href=&amp;#34;https://lightning.network&amp;#34;&amp;gt;Lightning Network&amp;lt;/a&amp;gt;&amp;amp;quot;. It is a layer on top of&amp;#xA;Bitcoin, in which value can be transported between people (aka &amp;amp;quot;make&amp;#xA;payments&amp;amp;quot;). It is &amp;lt;em&amp;gt;one&amp;lt;/em&amp;gt; of the possible layers that can run on top of&amp;#xA;Bitcoin, but it is the first, and an important one: making payments is&amp;#xA;one of the most important features of Bitcoin today, so logically that&amp;#xA;this is the first thing to be moved into an application layer.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This Lightning Network can be &amp;lt;a href=&amp;#34;&amp;#34;&amp;gt;used today&amp;lt;/a&amp;gt;. Sure, Leo needs to have a&amp;#xA;Lightning Network enabled &amp;lt;a href=&amp;#34;https://play.google.com/store/apps/details?id=fr.acinq.eclair.wallet&amp;#34;&amp;gt;client&amp;lt;/a&amp;gt; as &amp;lt;a href=&amp;#34;https://github.com/lightninglabs/lightning-app&amp;#34;&amp;gt;do you&amp;lt;/a&amp;gt;, you might need to compile some&amp;#xA;stuff, might need to run your own server and so on, but it &amp;lt;em&amp;gt;is&amp;lt;/em&amp;gt;&amp;#xA;possible. Today.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Essentially Lightning Network is the birth of an application layer on&amp;#xA;top of Bitcoin.  This might seem uneventful, but the birth of this&amp;#xA;second layer gives Bitcoin a new purpose: it &amp;amp;quot;degrades&amp;amp;quot; Bitcoin to a mere&amp;#xA;transport layer for value. This is not some &amp;amp;quot;Pop! And we&amp;#39;re done&amp;amp;quot; event,&amp;#xA;but a long process. Right now, Bitcoin is that transport layer, but is&amp;#xA;also, still an application layer: you can buy pizza, or buy&amp;#xA;beekeeping-gear through this transport layer, just fine. So it isn&amp;#39;t&amp;#xA;very layered &amp;lt;em&amp;gt;yet&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This new layer is going to be so much better at this &amp;amp;quot;paying&amp;amp;quot; thing, that it&amp;#xA;will take an important feature &amp;amp;quot;away&amp;amp;quot; from Bitcoin: payments. But,&amp;#xA;before you get all angry: like with TCP/IP, one can use a layer&amp;#xA;directly, if you wish. You can just skip the transport layer, and&amp;#xA;deliver data directly over one of the lower layers, if you insist. You&amp;#xA;application can skip all the application layer stuff and interact with&amp;#xA;the transport layer directly, which happens a lot, actually. You&amp;#39;ve probably&amp;#xA;seen these &amp;amp;quot;Use TCP/IP | use UDP&amp;amp;quot;-toggles in some settings of some app.&amp;#xA;Here an app can bypass, say, HTTP, TCP and so on, and use a much more&amp;#xA;raw way of delivering. You can still interact with Bitcoin, in order to&amp;#xA;transfer or manage funds, just fine. It&amp;#39;s just that with this new&amp;#xA;Application layer, it will become much easier to just use that instead.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If you want to &amp;lt;a href=&amp;#34;https://bitcoinmagazine.com/articles/ordering-meals-with-bitcoins-takeaway-com-bitcoins-pizza-home-1385160906/&amp;#34;&amp;gt;buy takeway&amp;lt;/a&amp;gt;, or &amp;lt;a href=&amp;#34;https://imkershop.nl/&amp;#34;&amp;gt;beekeeping gear&amp;lt;/a&amp;gt;, today, both you&amp;#xA;and the recieving party interact with the Transport layer directly.&amp;#xA;Tomorrow, we both will interact with an application layer; probably the&amp;#xA;Lightning Network, to settle that payment instead.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;There will be more layers on top of Bitcoin, there will be layers on top&amp;#xA;of layers on top of layers, but deep down below, Bitcoin is the Layer&amp;#xA;that ensures value is transferred from you to Leo.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;To me, this proves, again, that Bitcoin, as a project, &amp;amp;quot;Gets It&amp;amp;quot;.&amp;#xA;Bitcoin does not need to be everything: it only needs to be a system to&amp;#xA;store and transfer value. Nothing more!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It does not need to invent, develop and maintain all the layers, just&amp;#xA;like Tinder does not need to maintain and invent everything from cables&amp;#xA;to how-to-get-a-swipe-to-Leo-protocols.&amp;#xA;Bitcoin needs to be a very secure, very solid, very stable layer to&amp;#xA;maintain these funds for all the layers on top of it. And Bitcoin is&amp;#xA;just that.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We should note, though, that a layered architecture was not envisioned&amp;#xA;by the inventor and early adopters of Bitcoin. They envisioned it more as a&amp;#xA;monolith: a single piece of software that handles all the possible&amp;#xA;use-cases and features in itself. At least, that is how I read the&amp;#xA;whitepaper: no-where was there a mention of &amp;amp;quot;Application layers&amp;amp;quot; or even&amp;#xA;&amp;amp;quot;layers&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Second layers can choose different models, use-cases, or different parameters.&amp;#xA;Lightning Network is complex but also (very) secure. It is decentralised,&amp;#xA;albeit maybe (time will tell) less so than Bitcoin itself. Other&amp;#xA;networks might opt for less security. Or even more centralisation. Or&amp;#xA;tweak other parameters.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If, for example, all you need to register is &amp;amp;quot;I still owe you a beer&amp;amp;quot;,&amp;#xA;there could very well be a layer that maintains &amp;amp;quot;all the beers owed by&amp;#xA;everyone&amp;amp;quot; in a central database (or it&amp;#39;s own blockchain) and which&amp;#xA;registers a daily &amp;amp;quot;state of the beer&amp;amp;quot; on the Bitcoin layer. The&amp;#xA;possibilities are endless.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A lot of altcoins (or their advocates) did not design a layered system&amp;#xA;either. So many of these altcoins offer some &amp;amp;quot;feature&amp;amp;quot;, like &amp;amp;quot;speed&amp;amp;quot;, or&amp;#xA;&amp;amp;quot;programmability&amp;amp;quot;, or &amp;amp;quot;the ability to track bananas&amp;amp;quot; in their core.&amp;#xA;They often present those built-in features as &amp;amp;quot;the Bitcoin killer&amp;amp;quot;, but&amp;#xA;frankly, most of them have implemented these feature in the wrong place:&amp;#xA;as core part of their entire system, rather than as additional layers on&amp;#xA;top of standard value-transport-layers.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When you start looking at Bitcoin as &amp;amp;quot;merely&amp;amp;quot; &amp;lt;em&amp;gt;a the transport-layer for&amp;#xA;value&amp;lt;/em&amp;gt;, you might start to see the opportunities&amp;#xA;for other layers on top. And you might see a missing feature as good&amp;#xA;design, rather than as a missed opportunity, or as a sign that Bitcoin&amp;#xA;is doomed.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You don&amp;#39;t need &amp;amp;quot;instant transactions, zero-fee&amp;amp;quot; in your transport-layer,&amp;#xA;you need that in your application layer. So saying that &amp;amp;quot;Ripple is&amp;#xA;better because it can scale up to Visa-Scale&amp;amp;quot; is nonsense, because you&amp;#xA;should also mention the trade-off: Ripple has chosen to give away a lot of&amp;#xA;security maybe even all of it, in order to gain speed.&amp;#xA;And yes, I&amp;#39;m picking out Ripple because I consider that the&amp;#xA;biggest scam of the 21st century (closely followed by the Roger Ver&amp;#xA;Coin, by the way). Also, I&amp;#39;m not saying that it is a zero-sum game: that&amp;#xA;you can choose either speed or security. But making trade-offs is part&amp;#xA;of the game. Bitcoin does not make trade-offs if that hurts the&amp;#xA;decentralisation-property, or if it hurts security.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;TCP/IP is not a very efficient system. A lot of resources are spent to&amp;#xA;ensure your &amp;amp;quot;swipe for Leo&amp;amp;quot; ends up at Leo&amp;#39;s phone and not at Marks&amp;#39;&amp;#xA;phone, or even your current boyfriends phone. In some cases this&amp;#xA;overhead can be &amp;amp;quot;ridiculous&amp;amp;quot;: sometimes far more data is sent around&amp;#xA;ensuring that your swipe arrives at the right place, than the actual&amp;#xA;content of, say, the swipe itself. I mean: TCP/IP is brilliant, but it&amp;#xA;needs a lot of trade-offs to be fault-tolerant, decentralised, secure&amp;#xA;and stable. Sometimes systems choose different protocols because TCP/IP&amp;#xA;is just not fast enough: you don&amp;#39;t connect your computer-screen over the&amp;#xA;network to your computer, you use HDMI, or VGA: some other protocol that&amp;#xA;is much better at delivering pixels to your screen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bitcoin&amp;#39;s function is similar: it needs to be solid and secure. It must&amp;#xA;be slow and clunky, if that is what is needed to be solid and secure.&amp;#xA;It&amp;#39;s sole function is to guarantee that your funds are secure, that&amp;#xA;transactions are valid and that there is no single party that can take&amp;#xA;over the network or your funds.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;As such, Bitcoin does not include a &amp;amp;quot;programming language&amp;amp;quot;, like Ethereum&amp;#xA;does (Note: I actually &amp;lt;em&amp;gt;do&amp;lt;/em&amp;gt; like Ethereum but for different reasons),&amp;#xA;because Bitcoin chooses security over &amp;amp;quot;fancy&amp;amp;quot; new features like&amp;#xA;programming languages. It leaves things like &amp;amp;quot;smart contracts&amp;amp;quot; or&amp;#xA;&amp;amp;quot;programmability&amp;amp;quot; to another layer. Instead of including it in the base&amp;#xA;layer. Note, though that such a smart-contract-layer does not (really)&amp;#xA;exist yet, but nothing fundamental stops it from being rolled out.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Nor does Bitcoin offer very good privacy (compared to e.g. Monero or&amp;#xA;Dash). But there could very well be an application layer, some&amp;#xA;alternative to Lightning Network that enhances privacy. So, rather than&amp;#xA;building it into the base layer, it leaves increased privacy to the&amp;#xA;application layer.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bitcoin does not offer an exchange in it&amp;#39;s base-layer either (Like e.g.&amp;#xA;Stellar does). Nor does it offer file-storage, computing power or&amp;#xA;&amp;lt;a href=&amp;#34;https://bananacoin.io/&amp;#34;&amp;gt;tracking of Banana&amp;#39;s&amp;lt;/a&amp;gt; in it&amp;#39;s base layer.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;By &amp;lt;em&amp;gt;not&amp;lt;/em&amp;gt; implementing features, by choosing to be conservative, Bitcoin&amp;#xA;remains the most secure, most solid, and most predictable Transport&amp;#xA;Layer for transporting value. Ever. Exactly the features you want from&amp;#xA;such a basic layer.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;As a closing note, I&amp;#39;d like to stress that there certainly are&amp;#xA;altcoin-projects that are completely layered by design. Quite some&amp;#xA;&amp;amp;quot;cryptocurrency projects&amp;amp;quot; are actually an application layer on top of&amp;#xA;another transport layer: a vast majority of altcoins are basically&amp;#xA;tokens on Ethereum: they &amp;lt;em&amp;gt;are the Application layer&amp;lt;/em&amp;gt; on top of Ethereum!&amp;#xA;So: I&amp;#39;m not saying that all altcoins are wrong and only Bitcoin get&amp;#39;s it&amp;#xA;right: I&amp;#39;m only offering an alternative way to view Bitcoin: not as a&amp;#xA;polished, finished, fancy project to be downloaded from the iTunes&amp;#xA;store, but as a single, technical layer. An important component&amp;#xA;in a vast and rapidly changing new field: managing value online.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Positive feedback, as well as images of cats, calling me &amp;lt;em&amp;gt;literally&amp;#xA;hitler&amp;lt;/em&amp;gt; for hating on your beloved altcoins, or other comments are very welcome&amp;#xA;at &amp;lt;a href=&amp;#34;https://twitter.com/berkes&amp;#34;&amp;gt;my twitter&amp;lt;/a&amp;gt; or &amp;lt;a href=&amp;#34;https://www.reddit.com/u/berkes&amp;#34;&amp;gt;on reddit&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>F/Rite Air ICO</title>
   <link href="https://berk.es/2017/11/13/f-rite-air-ico/"/>
   <updated>2017-11-13T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2017/11/13/f-rite-air-ico/</id>
   <content type="html">&amp;lt;p&amp;gt;F/Rite air, &amp;lt;a href=&amp;#34;https://web.archive.org/web/20050831124638/http://www.iex.nl/columns/columns_artikel.asp?colid=3622&amp;#34;&amp;gt;was een 1-aprilgrap van&amp;#xA;IEX&amp;lt;/a&amp;gt; in 2005.&amp;#xA;Zij toonden hiermee aan dat mensen, zonder enige kennis&amp;#xA;van zaken, zonder enig onderzoek, aandelen kochten in een tech-bedrijf&amp;#xA;wat &amp;amp;quot;gebakken lucht&amp;amp;quot; verkocht. Dat was het hoogte (of laagte-)punt van&amp;#xA;de dot-com bubble.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Nu is er de blockchain bubble. Vooral zichtbaar in de vorm van ICO&amp;#39;s.&amp;#xA;Deze ICO&amp;#39;s zijn &amp;lt;a href=&amp;#34;https://tweakers.net/nieuws/131769/afm-waarschuwt-voor-initial-coin-offerings.html?showReaction=10770549#r_10770549&amp;#34;&amp;gt;de laatste dagen overal in het nieuws&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;ICO&amp;#39;s, initial coin offerings, &amp;lt;a href=&amp;#34;https://www.economist.com/blogs/economist-explains/2017/08/economist-explains-17&amp;#34;&amp;gt;zijn een nieuwe vorm van financiering&amp;#xA;voor&amp;#xA;bedrijven&amp;lt;/a&amp;gt;.&amp;#xA;Vooral bedrijven die &amp;amp;quot;iets met blockchain doen&amp;amp;quot;[1].  Na Bitcoin kwamen&amp;#xA;vele nieuwe cryptomunten (AltCoins) waarvan één hier bijzondere aandacht&amp;#xA;verdient: &amp;lt;a href=&amp;#34;http://en.bitcoinwiki.org/Ethereum&amp;#34;&amp;gt;Ethereum&amp;lt;/a&amp;gt;. Omdat Ethereum&amp;#xA;naast een munt (de Ether) waarmee je elkaar (bijna nergens, haha) kunt&amp;#xA;betalen, een systeem bevat waarmee smart contracts geschreven kunnen&amp;#xA;worden.&amp;#xA;Klinkt vaag: daarom wat uitleg:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een smart contract is een klein programma&amp;#39;tje dat verspreid wordt&amp;#xA;naar alle deelnemers op het Ethereum netwerk en welke dan door&amp;#xA;computer(s) op dat netwerk kan worden gedraaid. Het is dus openbaar,&amp;#xA;onafhankelijk en &amp;amp;quot;van iedereen&amp;amp;quot;. Een voorbeeld:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een smart contract met daarin een klein adreslijstje van 10 &amp;amp;quot;personen&amp;amp;quot;. Aan het&amp;#xA;begin van de maand wordt in dit smart contract een bedrag gestort door&amp;#xA;Bas. En aan het einde van de maand, mogen deze 10 personen 1/10e van het&amp;#xA;bedrag innen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een heel simpele vorm van een &amp;amp;quot;loonadministratie&amp;amp;quot; als smart contract:&amp;#xA;loonadministratie op de Blockchain; en we noemen het BlockWage.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Het doel en voordeel is niet meteen duidelijk, maar is er wel:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Gedurende de maand zit het &amp;amp;quot;bedrag&amp;amp;quot; vast: Baas Bas heeft het al&amp;#xA;overgemaakt, en kan het niet terughalen. De tien werknemers weten&amp;#xA;zeker dat Bas er niet met de pot vandoor kan, ook al wonen deze 10&amp;#xA;mensen over de hele wereld en staat Bas bekend als iemand die nogal vaak&amp;#xA;en snel &amp;amp;quot;failliet&amp;amp;quot; gaat: ze weten &amp;lt;em&amp;gt;zeker&amp;lt;/em&amp;gt; dat het loon van deze maand&amp;#xA;binnen komt: het staat immers al klaar in het Smart Contract, iets wat&amp;#xA;door iedereen gecontroleerd kan worden: wiskundig te bewijzen is, zelfs.&amp;#xA;Zonder dat iemand Bas kent, vertrouwt of zelfs maar weet wie het&amp;#xA;eigenlijk precies is, weet iedereen zeker dat 10 vastgestelde personen&amp;#xA;een bedrag aan het eind van de maand kunnen ophalen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Je kunt dit zo ingewikkeld maken als je wilt. Met urenadministratie die&amp;#xA;voor de verdeling zorgt, of een democratisch stem-systeem in weer andere&amp;#xA;smart-contracts, die bepaalt welke personen in het adreslijstje staan en&amp;#xA;wat de verdeelsleutel is, enzovoort. Laat je fantasie de vrije loop, je&amp;#xA;kunt het zo uitgebreid maken als je wilt, met ingebouwde&amp;#xA;spaarregelingen, fooienpotten, bonussen, etcetera. En alles wordt&amp;#xA;bestuurd vanuit een &amp;amp;quot;democratisch&amp;amp;quot; stem-systeem.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Iedereen mag &amp;amp;quot;tokens&amp;amp;quot; inzetten om te stemmen. Eén token is één stem. En&amp;#xA;die tokens, die beheer je weer met smart contracts, kunnen mensen&amp;#xA;onderling uitwisselen enzovoort: een &amp;amp;quot;munteenheid voor binnen één&amp;#xA;bedrijf&amp;amp;quot;, zeg maar. Uiteraard kun je een deel van je loon uitgekeerd&amp;#xA;krijgen in deze tokens. We noemen het systeem &amp;amp;quot;BlockWage&amp;amp;quot; en die tokens zijn&amp;#xA;WageCoins.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Kortom, die smart-contracts, daar kan van alles mee. Eigendomsrechten,&amp;#xA;huurcontracten, CO2 certificaten, landingsrechten, een stadspark met een&amp;#xA;eigen budget, uit te keren aan iedereen die het park onderhoudt.&amp;#xA;Enzovoort.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En dit &amp;lt;em&amp;gt;gaat&amp;lt;/em&amp;gt; de wereld veranderen. Wat precies, weet niemand. Er zullen&amp;#xA;bestaande bedrijfstakken vervangen worden door smart-contracts. Er&amp;#xA;zullen enorm veel &amp;amp;quot;smart contract&amp;amp;quot; bedrijven floppen en er zullen&amp;#xA;bedrijven met heel onverwachte toepassingen gigantisch rijk worden. Er&amp;#xA;zal enorm veel geld verdiend en verloren worden rondom zulke bedrijven.&amp;#xA;En dat maakt hebberig.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Terug naar de ICO&amp;#39;s. Wanneer iemand een heel set aan smart-contracts&amp;#xA;verzint om een bestaand systeem om te gooien, heeft deze vaak geld&amp;#xA;nodig. En omdat een &amp;amp;quot;lening van de bank&amp;amp;quot; duur, moeilijk en traag is, kun&amp;#xA;je net zo goed al heel vroeg investeerders aantrekken: crowdfunding. En&amp;#xA;omdat een beursgang duur, moeilijk en traag is, en het opzetten van een&amp;#xA;aandelenstructuur dat ook is, kun je net zo goed diezelfde smart&amp;#xA;contracts inzetten. &amp;amp;quot;De eerste 20% van de WageCoins worden verkocht aan&amp;#xA;vroege deelnemers. De volgende 80% gebruikt om de lonen uit te betalen&amp;amp;quot;.&amp;#xA;Een PDFje erbij waarin je met wiskunde, statistiek en wat economie&amp;#xA;aantoont dat het de hele wereld gaat veranderen een miljoen dollar voor&amp;#xA;een tweet over jou BlockWage/WageCoin van &amp;lt;a href=&amp;#34;https://i.imgur.com/l6I6nlD.jpg&amp;#34;&amp;gt;een of andere&amp;#xA;beroemdheid&amp;lt;/a&amp;gt; en bam, het geld stroomt&amp;#xA;binnen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En dan blijkt BlockWage inderdaad een knallend succes in de landbouw, omdat&amp;#xA;werknemers de werkgevers daar niet vertrouwen, uitzendbureaus enorme marges&amp;#xA;berekenen en arbeiders de ene dag tomaten staan te plukken bij Boer&amp;#xA;Tomas en de volgende dat Bollen pellen bij Boer Pelle en daarom loon uitkeren&amp;#xA;nogal ingewikkeld is.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Waardoor die WageCoins waardevol zijn. Die landarbeiders houden de 2%&amp;#xA;WageCoins zelf vast of maken ze over naar hun vakbondsvertegenwoordiger,&amp;#xA;omdat ze hiermee samen meer dan de helft van de zeggenschap hebben over&amp;#xA;het bedrijf dat hun loon bepaalt en betaalt. Waardoor die 20% die tussen&amp;#xA;aandeelhouders verhandelt wordt steeds duurder wordt. Winst! Rijkdom!&amp;#xA;Miljoenen!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Of, BlockWage blijkt inderdaad gewoon een heel slecht idee en na het&amp;#xA;verkopen van de eerste &amp;amp;quot;20%&amp;amp;quot; voor anderhalf miljoen Euro, vertrekt heer&amp;#xA;Mallory, de bedenker, naar de Bahama&amp;#39;s met de pot.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Omdat mensen hebberig zijn en bijna iedere maand op tv of bij DWDD of&amp;#xA;een of ander &amp;amp;quot;leken&amp;amp;quot;-programma horen over mensen die schatjehemeltjerijk&amp;#xA;werden met Bitcoins of andere &amp;amp;quot;blockchain dingen&amp;amp;quot; willen zij dat ook.&amp;#xA;Enorm veel mensen balen dat &amp;amp;quot;ze er niet op tijd bij waren&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En daar maken ook oplichters graag gebruik van. Door te doen alsof dit &amp;amp;quot;het&amp;#xA;nieuwe Bitcoin&amp;amp;quot; is, een hype te creëren, zorgen ze ervoor dat (tien)&amp;#xA;duizenden mensen vooraan staan, in de hoop dat ze nu wel &amp;amp;quot;op tijd erbij&amp;#xA;zijn&amp;amp;quot;. Het wrange is, dat sommige van die &amp;amp;quot;Tokens&amp;amp;quot; ook heel even heel&amp;#xA;veel waard worden: deze &amp;amp;quot;aandelen&amp;amp;quot; schieten in de eerste dagen in prijs&amp;#xA;omhoog; waarmee de eerste instappers dus enorm veel geld kunnen&amp;#xA;verdienen. En die verhalen de volgende hype bij de volgende ICO alleen&amp;#xA;nog groter maken.&amp;#xA;Bijvoorbeeld &amp;lt;a href=&amp;#34;https://uetoken.com/&amp;#34;&amp;gt;The world&amp;#39;s first 100% honest Ethereum&amp;#xA;ICO.&amp;lt;/a&amp;gt;. Het F/Rite air van 2017, ironisch, maar met&amp;#xA;een heldere boodschap: stuur nooit geld naar een willekeurig persoon op&amp;#xA;het internet. Een boodschap die, helaas, niet vaak genoeg herhaald kan&amp;#xA;worden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dus zijn er tussen een paar heel mooie ideeën, een paar geweldige&amp;#xA;ondernemers en een paar bedrijven met gigantische potentie, een heleboel&amp;#xA;oplichters.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Het bericht dat we uit de huidige aandacht voor ICO&amp;#39;s moeten&amp;#xA;meenemen is vooral dat &amp;amp;quot;goed informeren&amp;amp;quot; van cruciaal belang is. &amp;lt;a href=&amp;#34;https://www.forbes.com/sites/forbestechcouncil/2017/08/24/what-to-consider-before-investing-in-initial-coin-offerings/#29ef16763604&amp;#34;&amp;gt;Ken je&amp;#xA;mensen erachter? Vertrouw je die? Vertrouw je het bedrijfsmodel? Het&amp;#xA;plan?&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Klinkt het te mooi om waar te zijn? Dan is het dat waarschijnlijk&amp;#xA;ook. Is het een mooi onderbouwd plan door iemand die je kent en waaraan&amp;#xA;je graag wat geld wilt geven om daarmee zijn plan uit te werken? Dan kan&amp;#xA;het best wel eens werken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;hr&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;[1] Wanneer ik het hier over &amp;amp;quot;Blockchain&amp;amp;quot; heb, bedoel ik vooral de&amp;#xA;technische betekenis: een decentraal kasboek:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een blockchain [...] is een gedistribueerde database die een gestaag&amp;#xA;groeiende lijst bijhoudt van data-items die gehard zijn tegen&amp;#xA;manipulatie en vervalsing. Zelfs de beheerder van nodes kan deze&amp;#xA;gegevens niet vervalsen.[...] Dit gebeurt door middel van een&amp;#xA;consensus. Met een blockchain kan ervoor worden gezorgd dat een derde&amp;#xA;partij niet nodig is om de betrouwbaarheid van een transactie te&amp;#xA;waarborgen. &amp;lt;a href=&amp;#34;https://nl.wikipedia.org/wiki/Blockchain&amp;#34;&amp;gt;Wikipedia&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;[2] Een loonadministratie op de blockchain, BlockWage, dus, zie&amp;#xA;ik niet veel kans maken, hoogstens in (internationale) arbeidsrelaties&amp;#xA;waarbij men elkaar helemaal niet kent en vertrouwt. Maar daar gaat het&amp;#xA;hier niet om&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Simple time logging on top of git flow</title>
   <link href="https://berk.es/2016/04/06/simple-time-logging-on-top-of-git-flow/"/>
   <updated>2016-04-06T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2016/04/06/simple-time-logging-on-top-of-git-flow/</id>
   <content type="html">&amp;lt;p&amp;gt;My current team found out that we should have tracked some time over the last year.&amp;#xA;Extracting timelogs in retrosepct is not fun. Git helps a lot, combined with chat-logs from Slack, Google Calendars will give a good basis. A day of grep, sed, and awk, and you have some time-logs.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I decided that from now on, I want to track what I start and finish working on in a basic log. And I am using git with&amp;#xA;&amp;lt;a href=&amp;#34;https://github.com/petervanderdoes/gitflow/wiki/Installation&amp;#34;&amp;gt;git-flow by Peter van der Does&amp;lt;/a&amp;gt;, which is what you get when you&amp;#xA;&amp;lt;code&amp;gt;apt-get install git-flow&amp;lt;/code&amp;gt;. This allows special git-flow hooks.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I want this to write logs to a simple textfile. But have a place where I could call external APIs to insert some tracking data into external trackers, when my team uses these.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The result is certainly not a replacement for actual timetracking. But a log that will aide with answering &amp;amp;quot;when did you work on what?&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-text&amp;#34; data-lang=&amp;#34;text&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;[2016-04-06T06:43:13Z] /home/ber/Documenten/BLG_blog STARTED article-git-flow-logging&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;[2016-04-06T06:43:16Z] /home/ber/tmp/flowtest STARTED a-feature&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;[2016-04-06T06:43:47Z] /home/ber/tmp/flowtest STARTED another-feature&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;[2016-04-06T07:12:10Z] /home/ber/Documenten/BLG_blog FINISHED article-git-flow-logging&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;[2016-04-06T07:43:52Z] /home/ber/tmp/flowtest FINISHED another-feature&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;These will be written out when using&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-bash&amp;#34; data-lang=&amp;#34;bash&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;git flow feature start some-feature&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;git flow feature finish branch&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;## or the short alternative&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;git flow feature finish&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;It requires you to work with git-flow and use feature branches for everything. But you should use topic branches anyway.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Git-flow triggers its own hooks. So just create a simple utility script that is exectuable and logs an activity, or calls an external API or whatever you are using. Then call that script from the git-flow hooks.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Note that, as far as I can tell, the upsteam git-flow by nvie himself, does not&amp;#xA;have own git-hooks. &amp;lt;a href=&amp;#34;https://github.com/petervanderdoes/gitflow&amp;#34;&amp;gt;Peter van der Does&amp;#39; fork&amp;lt;/a&amp;gt;&amp;#xA;has this. Which is also the source used for the Debian package (so also for&amp;#xA;Ubuntu).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-bash&amp;#34; data-lang=&amp;#34;bash&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#!/bin/bash&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;set -e&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;working_dir&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;$(&amp;lt;/span&amp;gt;pwd&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;)&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;feature&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt;$2&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;action&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt;$1&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;now&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;$(&amp;lt;/span&amp;gt;date -u &#43;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;%Y-%m-%dT%H:%M:%SZ&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;)&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;# ISO8601&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;echo &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;[&amp;lt;/span&amp;gt;$now&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;] &amp;lt;/span&amp;gt;$working_dir&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt; &amp;lt;/span&amp;gt;$action&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt; &amp;lt;/span&amp;gt;$feature&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;&amp;lt;/span&amp;gt; &amp;amp;gt;&amp;amp;gt; ~/.git-flow-feature.log&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Write that to e.g. &amp;lt;code&amp;gt;~/bin/log-git-flow-feature&amp;lt;/code&amp;gt; and make executable with&amp;#xA;&amp;lt;code&amp;gt;chmod &#43;x ~/bin/log-git-flow-feature&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Note: when you create scripts with &amp;lt;code&amp;gt;git-foo&amp;lt;/code&amp;gt;&amp;#xA;a subcommand &amp;lt;code&amp;gt;git foo&amp;lt;/code&amp;gt; is made available. You probably don&amp;#39;t want to name this&amp;#xA;script &amp;lt;code&amp;gt;git-flow-log-feature&amp;lt;/code&amp;gt; or so, to prevent &amp;lt;code&amp;gt;git flow log&amp;lt;/code&amp;gt; from becoming&amp;#xA;a command.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Now just add two hooks and make them exectuable. This will add hooks to a specific&amp;#xA;git repo:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-bash&amp;#34; data-lang=&amp;#34;bash&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;echo &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;log-git-flow-feature STARTED &amp;amp;#34;$@&amp;amp;#34;&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;amp;gt;&amp;amp;gt; /path/to/project/.git/hooks/pre-flow-feature-start&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;chmod &#43;x /path/to/project/.git/hooks/pre-flow-feature-start&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;echo &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;log-git-flow-feature FINISHED &amp;amp;#34;$@&amp;amp;#34;&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;amp;gt;&amp;amp;gt; /path/to/project/.git/hooks/pre-flow-feature-finish&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;chmod &#43;x /path/to/project/.git/hooks/pre-flow-feature-finish&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;When I need to call an external time-tracker, the &amp;lt;code&amp;gt;~/bin/log-git-flow-feature&amp;lt;/code&amp;gt;&amp;#xA;script is the place to do this. An example:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-bash&amp;#34; data-lang=&amp;#34;bash&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#...&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;curl -X POST -D &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;{ &amp;amp;#39;note&amp;amp;#39;: &amp;amp;#39;&amp;lt;/span&amp;gt;$feature&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt; in &amp;lt;/span&amp;gt;$working_dir&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39; }&amp;amp;#34;&amp;lt;/span&amp;gt; http://timetracker.io/api/entry&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;I&amp;#39;ve create &amp;lt;a href=&amp;#34;https://gist.github.com/berkes/714141a6c131b20496fc00d6e3a90d38&amp;#34;&amp;gt;a gist with the contents of the files&amp;lt;/a&amp;gt; so&amp;#xA;if you want to enhance it, feel free to fork it!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;There is a lot of room for improvement:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;make this work with &amp;amp;quot;generic&amp;amp;quot; git-hooks instead of relying on git-flow. Should&amp;#xA;probably match against patterns in branches that are created, merged, rebased&amp;#xA;etc.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;map directories with projects, log name of the project.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;don&amp;#39;t use &amp;amp;quot;pwd&amp;amp;quot; but determine the actual working copy of git instead, to allow&amp;#xA;this to work with fancy setups or when working from within subdirectories.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;log &amp;lt;code&amp;gt;git flow feature checkout&amp;lt;/code&amp;gt; as well, to log switching between (long)running branches.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;fall back on generic git-hooks&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Beste Nu.nl. Dit vinden wij jammer.</title>
   <link href="https://berk.es/2015/12/14/beste-nunl-dit-vinden-wij-jammer/"/>
   <updated>2015-12-14T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2015/12/14/beste-nunl-dit-vinden-wij-jammer/</id>
   <content type="html">&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Beste Nu.nl,&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wij zien dat u een hele zwik aan &amp;lt;em&amp;gt;trackers&amp;lt;/em&amp;gt;, &amp;lt;em&amp;gt;analytics&amp;lt;/em&amp;gt;, en andere aggressieve,&amp;#xA;privacyschendende bedrijven op uw site toelaat.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dit vinden wij jammer, want NU.nl verdient haar geld blijkbaar met het verkopen&amp;#xA;van haar bezoekers aan allerhande louche bedrijven.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wij willen daarom geen uitzondering maken voor NU.nl en zullen advertenties&amp;#xA;blijven blokkeren.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Nu.nl controleert of uw browser advertenties blokkeert. Steeds meer sites doen&amp;#xA;dat. Het verdienmodel van NU.nl en dergelijke sites, in begint namelijk in te storten.&amp;#xA;&amp;amp;quot;Zonder advertenties geen gratis artikelen&amp;amp;quot;, roepen ze.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/nu_nl_adblocked.png&amp;#34; alt=&amp;#34;NU.nl verzoekt om de adblocker uit te zetten&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar de argumenten zijn onzuiver. Advertenties zijn het probleem niet. Een&amp;#xA;simpele advertentie van een zorgverzekeraar naast een artikel over dure&amp;#xA;medicijnen zal niemand storen. Al helemaal niet als het een relevante tekst is&amp;#xA;waarin de zorgverzekeraar belooft dure medicijnen te blijven vergoeden. Vraag nu een offerte aan.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;NU.nl verkoopt echter niet eenvoudig ruimte om te adverteren. Ze verkopen plek om&amp;#xA;ons, de bezoekers te bespioneren. Een plek waar honderden bedrijven zeer nauwkeurige, soms zelfs enge gegevens verzamelen&amp;#xA;om dat te verkopen aan letterlijk iedereen die geld hiervoor biedt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een willekeurige pagina op NU.nl, 14 December 2015 heeft meer dan tien trackers:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/nu_nl_trackers.png&amp;#34; alt=&amp;#34;Trackers door Privacy Badger gemeld&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;ad.360yield.com&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;rtax.criteo.com&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;cdns.gigya.com&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;sat.sanoma.fi&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;b.scorecardresearch.com&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;js-agent.newrelic.com&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;https://www.google-analytics.com&amp;#34;&amp;gt;www.google-analytics.com&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;rc.bt.ilsemedia.nl&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;cts.snmmd.nl&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;zoeken.startpagina.nl&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een paar hiervan zijn voor technische redenen aanwezig, maar het merendeel is&amp;#xA;gewoon commercieel: verdienmodel &amp;amp;quot;data verzamelen en verkopen&amp;amp;quot;. Die geven jullie&amp;#xA;een platform. En die blokkeren adblockers en &amp;lt;a href=&amp;#34;https://www.eff.org/privacybadger&amp;#34;&amp;gt;privacy-tools&amp;lt;/a&amp;gt;, dan weer. Om onze privacy een beetje te beschermen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Zet die dingen uit, NU.nl. Weiger het verkopen van je bezoekers aan al die grote&amp;#xA;advertentieboeren. Plaats gewoon nette advertenties, zoals bijvoorbeeld &amp;lt;a href=&amp;#34;https://duck.co/help/company/advertising-and-affiliates&amp;#34;&amp;gt;DuckDuckGo&amp;lt;/a&amp;gt; dat doet:&amp;#xA;advertenties die niet álles traceren en opslaan van degene die ze bekijken.&amp;#xA;Maar die gewoon een regeltje tekst of een leuk plaatje bevatten en linken naar&amp;#xA;de site van de adverteerder. Advertenties die niet controleren hoe oud ik ben,&amp;#xA;welke lettertypes ik geïnstalleerd heb, sites ik bezocht heb, hoe mijn moeder&amp;#xA;heet en of ik wel snel genoeg lees. Gewoon: advertenties waarvoor jullie geld&amp;#xA;krijgen om ze te plaatsen, en die de adverteerders bezoekers oplevert. Advertenties die&amp;#xA;niet &amp;lt;a href=&amp;#34;http://thumbnails-visually.netdna-ssl.com/display-advertising-technology-landscape_502919a4078a7.jpeg&amp;#34;&amp;gt;honderden bedrijven&amp;lt;/a&amp;gt; mijn surfgedrag toespelen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Jullie gaan anders kopje onder. Het kost wat moeite, maar iedereen kan uw advertenties blokkeren. En&amp;#xA;steeds meer mensen zullen dat ook doen: wij zijn die rommel simpelweg spuugzat.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Denk eens na, NU.nl: waarom zouden zoveel mensen, zoveel moeite doen om advertenties&amp;#xA;te blokkeren? Onderzoek dat eens?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Overweeg advertenties die niet mijn hele ziel en zaligheid aan meer dan tien verschillende bedrijven verkopen.&amp;#xA;Dan zet ik graag mijn advertentieblokkade voor jullie uit.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Add sorting to your product page in Spree</title>
   <link href="https://berk.es/2015/05/09/add-sorting-to-your-product-page-in-spree/"/>
   <updated>2015-05-09T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2015/05/09/add-sorting-to-your-product-page-in-spree/</id>
   <content type="html">&amp;lt;p&amp;gt;For a &amp;lt;a href=&amp;#34;https://shop.annatreurniet.nl&amp;#34;&amp;gt;crafts e-commerce shop&amp;lt;/a&amp;gt;, built with &amp;lt;a href=&amp;#34;http://spreecommerce.com&amp;#34;&amp;gt;Spree&amp;lt;/a&amp;gt;, I wanted to add an option to sort the products on the products page.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In this blogpost, I&amp;#39;ll describe how one can build such a feature, more to create some understanding of how in Spree such customisations can be developed.&amp;#xA;Spree has everything in place to add this, but it can be a bit daunting to find all the bits and pieces that could and should be overridden. Which I hope to clarify a bit. In order to built this feature, we re-open some &amp;lt;code&amp;gt;Helpers&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Controllers&amp;lt;/code&amp;gt; from Spree and we inject some HTML using deface.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You could write this as a spree extension, but that requires even more moving parts to be in place. And I think it is a better, more efficient way to first write the customisation in your main app and then, later on, when things have settled, extract it into a spree extension.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You could write this TDD, and you should really write at least some tests to spec out your changes. But testing overrides of methods, controller actions and so on, is &amp;lt;em&amp;gt;really&amp;lt;/em&amp;gt; daunting in Spree: you&amp;#39;ll be stubbing and mocking a gazillion of unrelated before-filters, finders and scopes. Just to spec that your method adds one other scope, you might need over 100 lines of setup. This is a problem, but not one that I want to address in this post.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Let&amp;#39;s get rolling. First, override the &amp;lt;a href=&amp;#34;https://github.com/spree/spree/blob/3-0-stable/frontend/app/controllers/spree/products_controller.rb#L11&amp;#34;&amp;gt;ProductsController#index&amp;lt;/a&amp;gt; action. Add a file &amp;lt;code&amp;gt;app/controllers/spree/products_controller_decorator.rb&amp;lt;/code&amp;gt;.&amp;#xA;The idea is to add a sorting scope that is already &amp;lt;a href=&amp;#34;https://github.com/spree/spree/blob/3-0-stable/core/app/models/spree/product/scopes.rb&amp;#34;&amp;gt;available on&amp;#xA;Product&amp;lt;/a&amp;gt;. The ordering scopes I want add are &amp;lt;code&amp;gt;ascend_by_updated_at&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ascend&#43;by_master_price&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;descend_by_master_price&amp;lt;/code&amp;gt;. When implementing this, you can add &amp;lt;code&amp;gt;sorting=ascend_by_updated_at&amp;lt;/code&amp;gt; or one of the other sorting scopes to the URL of the app (e.g. http://localhost:3000/orders?sorting=ascend_by_updated_at). This way we can finish the controller and then move on to the view.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;controller&amp;#34;&amp;gt;Controller&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;module&amp;lt;/span&amp;gt; Spree&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;ProductsController&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;class_eval &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;index&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      @searcher &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; build_searcher(params&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;merge(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;include_images&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;true&amp;lt;/span&amp;gt;))&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      sorting_scope &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; params&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:sorting&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;].&amp;lt;/span&amp;gt;try(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:to_sym&amp;lt;/span&amp;gt;) &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;||&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:ascend_by_updated_at&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      @products &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; @searcher&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;retrieve_products&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;send(sorting_scope)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      @taxonomies &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Spree&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;::&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Taxonomy&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;includes(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;root&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:children&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Now that this works, it needs to be secured and cleaned up:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;module&amp;lt;/span&amp;gt; Spree&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;ProductsController&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;class_eval &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    helper_method &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:sorting_param&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    alias_method &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:old_index&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:index&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;index&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      old_index &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;# Like calling super: http://stackoverflow.com/a/13806783/73673&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      @products &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; @products&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;send(sorting_scope)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;sorting_param&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      params&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:sorting&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;].&amp;lt;/span&amp;gt;try(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:to_sym&amp;lt;/span&amp;gt;) &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;||&amp;lt;/span&amp;gt; default_sorting&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;private&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;sorting_scope&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      allowed_sortings&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;include?(sorting_param) ? sorting_param : default_sorting&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;default_sorting&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:ascend_by_updated_at&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;allowed_sortings&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:descend_by_master_price&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:ascend_by_master_price&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:ascend_by_updated_at&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;There is a lot going on here. But the general idea is to use &amp;lt;a href=&amp;#34;http://stackoverflow.com/a/13806783/73673&amp;#34;&amp;gt;method aliasing&amp;lt;/a&amp;gt; to be able to extend the original index.&amp;#xA;We have, furthermore, split out the scope-selecting into several methods. This allows setting a default and whitelisting allowed scopes. You don&amp;#39;t want people to call just any string and call that as a method on our model (e.g. &amp;lt;code&amp;gt;sorting=delete_all&amp;lt;/code&amp;gt;). I&amp;#39;ve declared&amp;lt;code&amp;gt;sorting_param&amp;lt;/code&amp;gt; as a helper method because we&amp;#39;ll need it later on (I know, not very YAGNI, but for the sake of brevity, let&amp;#39;s already implement it here).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If you want to find out where a view, controller or model is defining something, you can either run &amp;lt;code&amp;gt;bundle open spree_core&amp;lt;/code&amp;gt; (or &amp;lt;code&amp;gt;spree_frontend&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;spree_backend&amp;lt;/code&amp;gt;) or simply clone the stable branch into a directory and browse or search the code there. However, make very sure you have the correct version. For example, when using Github to browse the code, you have a good chance of copying the wrong (too old or too new) versions of a method into your project in order to override.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;view-and-helpers&amp;#34;&amp;gt;View and helpers&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;First iteration is to copy over Spree&amp;#39;s &amp;lt;a href=&amp;#34;https://github.com/spree/spree/blob/master/frontend/app/views/spree/shared/_products.html.erb&amp;#34;&amp;gt;products&amp;lt;/a&amp;gt; partial to &amp;lt;code&amp;gt;app/views/spree/shared/_products.html.erb&amp;lt;/code&amp;gt;. We add the sorting links there. In a later iteration we&amp;#39;ll rely on deface to inject our code, rather then duplicating the partial.&amp;#xA;But, for now, Rails will simply pick our file instead of Spree&amp;#39;s version. In it, we add our links:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre tabindex=&amp;#34;0&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-erb&amp;#34; data-lang=&amp;#34;erb&amp;#34;&amp;gt;&amp;#xA;...&amp;#xA;&amp;amp;lt;% if products.any? %&amp;amp;gt;&amp;#xA;&amp;amp;lt;div class=&amp;amp;#34;row sorting&amp;amp;#34;&amp;amp;gt;&amp;amp;lt;div class=&amp;amp;#34;col-sm-6 col-sm-offset-6&amp;amp;#34;&amp;amp;gt;&amp;#xA;  Sort by:&amp;#xA;  &amp;amp;lt;ul class=&amp;amp;#34;list-inline&amp;amp;#34;&amp;amp;gt;&amp;#xA;    &amp;amp;lt;li&amp;amp;gt;&amp;amp;lt;%= link_to &amp;amp;#34;Newest&amp;amp;#34;, params.merge(sorting: :ascend_by_updated_at) %&amp;amp;gt;&amp;amp;lt;/li&amp;amp;gt;&amp;#xA;    &amp;amp;lt;li&amp;amp;gt;&amp;amp;lt;%= link_to &amp;amp;#34;Lowest price&amp;amp;#34;, params.merge(sorting: :ascend_by_master_price) %&amp;amp;gt;&amp;amp;lt;/li&amp;amp;gt;&amp;#xA;    &amp;amp;lt;li&amp;amp;gt;&amp;amp;lt;%= link_to &amp;amp;#34;Highest price&amp;amp;#34;, params.merge(sorting: :descend_by_master_price) %&amp;amp;gt;&amp;amp;lt;/li&amp;amp;gt;&amp;#xA;  &amp;amp;lt;/ul&amp;amp;gt;&amp;#xA;&amp;amp;lt;/div&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;#xA;&amp;amp;lt;div id=&amp;amp;#34;products&amp;amp;#34; data-hook&amp;amp;gt;&amp;#xA;...&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;p&amp;gt;For the links, we use &amp;lt;code&amp;gt;params.merge(...)&amp;lt;/code&amp;gt; in order to persist any search, paging or filter params.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;On your development server, this will work, but when you look at &amp;lt;a href=&amp;#34;https://github.com/spree/spree/blob/3-0-stable/frontend/app/views/spree/home/index.html.erb#L8&amp;#34;&amp;gt;products/index.html.erb&amp;lt;/a&amp;gt;, you can see &amp;lt;code&amp;gt;cache(cache_key_for_products). It uses [&amp;lt;/code&amp;gt;cache_key_for_products`](&amp;lt;a href=&amp;#34;https://github.com/spree/spree/blob/3-0-stable/core/app/helpers/spree/products_helper.rb#L54)&amp;#34;&amp;gt;https://github.com/spree/spree/blob/3-0-stable/core/app/helpers/spree/products_helper.rb#L54)&amp;lt;/a&amp;gt;. The list of products will be cached, which is good, because the queries can be very heavy. But that cache disregards our ordering and the active-state of our links. We need to add the sorting to the cache-key.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In order to override it, we have to add it to a file called &amp;lt;code&amp;gt;app/helpers/spree/products_helper_decorator.rb&amp;lt;/code&amp;gt;. Because we are changing a lot inside the method, we can&amp;#39;t really use the aliasing as used earlier, it won&amp;#39;t help us a lot. Instead, we simply override the entire method. And document our changes.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;module&amp;lt;/span&amp;gt; Spree&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;ProductsHelper&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;module_eval &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;##&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;# Override cache_key_for_products to add caching per sort param.&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;cache_key_for_products&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      count &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; @products&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;count&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;# Instead of default max_updated_at, we look at the first product in the list&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;# And we add sorting, so that we get a product-cache per sorting param&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      first_id &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; @products&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;first&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;id&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      sorting &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; params&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:sorting&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;I18n&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;locale&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;/&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;current_currency&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;/spree/products/all-&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;params&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:page&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;-&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;first_id&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;-&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;sorting&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;-&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;count&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;We now have a working sorting feature, but it needs improvement.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;deface&amp;#34;&amp;gt;Deface&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;One thing we should clean up, is our override of the partial file. Unless you want to change how a file behaves, or want to alter its HTML structure, you should avoid copying them over. That makes upgrading a lot harder. And it can breeak a lot of addons that want to inject HTML into the views.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;https://guides.spreecommerce.com/developer/view.html#using-deface&amp;#34;&amp;gt;Deface is made for this&amp;lt;/a&amp;gt;, so let&amp;#39;s use it. Create a file &amp;lt;code&amp;gt;app/overrides/sorting_links_in_products.rb&amp;lt;/code&amp;gt; (And possibly restart the rails server, I&amp;#39;ve found that deface sometimes does not pick up new files otherwise):&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Deface&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;::&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Override&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;new(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:virtual_path&amp;lt;/span&amp;gt;  &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;spree/shared/_products&amp;amp;#34;&amp;lt;/span&amp;gt;,&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;                     &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:insert_before&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;#products[data-hook]&amp;amp;#34;&amp;lt;/span&amp;gt;,&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;                     &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:partial&amp;lt;/span&amp;gt;       &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;spree/shared/sorting_links&amp;amp;#34;&amp;lt;/span&amp;gt;,&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;                     &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:name&amp;lt;/span&amp;gt;          &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;sorting_links_in_products&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;And a partial &amp;lt;code&amp;gt;app/views/spree/shared/_sorting_links.html.erb&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre tabindex=&amp;#34;0&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-erb&amp;#34; data-lang=&amp;#34;erb&amp;#34;&amp;gt;&amp;#xA;&amp;amp;lt;% if products.any? &amp;amp;amp;&amp;amp;amp; params[:controller] == &amp;amp;#39;spree/products&amp;amp;#39; %&amp;amp;gt;&amp;#xA;&amp;amp;lt;div class=&amp;amp;#34;row sorting&amp;amp;#34;&amp;amp;gt;&amp;amp;lt;div class=&amp;amp;#34;col-sm-6 col-sm-offset-6&amp;amp;#34;&amp;amp;gt;&amp;#xA;  &amp;amp;lt;%= t(:sort_by) %&amp;amp;gt;&amp;#xA;  &amp;amp;lt;ul class=&amp;amp;#34;list-inline&amp;amp;#34;&amp;amp;gt;&amp;#xA;    &amp;amp;lt;li&amp;amp;gt;&amp;amp;lt;%= link_to_unless current_sorting?(:ascend_by_updated_at), t(:newest), params.merge(sorting: :ascend_by_updated_at) %&amp;amp;gt;&amp;amp;lt;/li&amp;amp;gt;&amp;#xA;    &amp;amp;lt;li&amp;amp;gt;&amp;amp;lt;%= link_to_unless current_sorting?(:ascend_by_master_price), t(:lowest_price), params.merge(sorting: :ascend_by_master_price) %&amp;amp;gt;&amp;amp;lt;/li&amp;amp;gt;&amp;#xA;    &amp;amp;lt;li&amp;amp;gt;&amp;amp;lt;%= link_to_unless current_sorting?(:descend_by_master_price), t(:highest_price), params.merge(sorting: :descend_by_master_price) %&amp;amp;gt;&amp;amp;lt;/li&amp;amp;gt;&amp;#xA;  &amp;amp;lt;/ul&amp;amp;gt;&amp;#xA;&amp;amp;lt;/div&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;#xA;&amp;amp;lt;% end %&amp;amp;gt;&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;p&amp;gt;Some changes to the first iteration of this erb-code, are that we now use the locales to render strings, and that we only render the sorting-link as a link when it is not active.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;ve also added an additional condition where I check for the controller. This is not the cleanest, but since the &amp;lt;code&amp;gt;products&amp;lt;/code&amp;gt; partial can be reused (it is a shared partial), and we inject into this partial regardless of who is requesting it, we should only add the sorting links when we are sure the partial is being renderd via the controller that can handle the sorting. For example, the partial is being used when rendering the products in a taxonomy. And there, the products already are sorted, so we don&amp;#39;t want to sort them. But the controller rendering them, there, will ignore the sorting param and won&amp;#39;t have the helper-methods we use.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Note tha we could clean this out even more by DRY-ing up the &amp;lt;code&amp;gt;link_to_unless current_sorting...&amp;lt;/code&amp;gt; repetition with e.g. another helper, or partials, or both, but IMHO that is overengineering. Some repetition in views is fine: they give us the freedom to place some icons, override a text and so on, much easier and cleaner.&amp;#xA;We can now remove our version of &amp;lt;code&amp;gt;products&amp;lt;/code&amp;gt; partial, since it is no longer overriding the Spree version.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;One cleanup was to use a &amp;lt;code&amp;gt;current_sorting?&amp;lt;/code&amp;gt; helper. Which does not&amp;#xA;exist, so let&amp;#39;s create that with the familiar decorator/monkey-patching. Add it to &amp;lt;code&amp;gt;app/helpers/spree/products_helper_decorator.rb&amp;lt;/code&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;module&amp;lt;/span&amp;gt; Spree&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;ProductsHelper&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;module_eval &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#... cache_key_for_products&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;current_sorting?&amp;lt;/span&amp;gt;(key)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      sorting_param &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;==&amp;lt;/span&amp;gt; key&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;to_sym&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;In this helper, &amp;lt;code&amp;gt;sorting_param&amp;lt;/code&amp;gt; is requested from the controller, which re-uses the default, that&amp;#39;s why we made it a helper earlier.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We now have a few sorting links that are implemented without hacking Spree, and without copying over entire classes or files. We can still lean on Spree and upgrade it quite safely.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Oh, and I&amp;#39;ll leave the CSS and declaration of localised strings as homework.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Do you often override these Spree core items? Do you know any tips and tricks on how to manage these during Spree upgrades?&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>When FactoryGirl leads to bad habits</title>
   <link href="https://berk.es/2015/02/19/when-factorygirl-leads-to-bad-habits/"/>
   <updated>2015-02-19T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2015/02/19/when-factorygirl-leads-to-bad-habits/</id>
   <content type="html">&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;https://github.com/thoughtbot/factory_girl&amp;#34;&amp;gt;FactoryGirl&amp;lt;/a&amp;gt; is a solution for cleaning up the repetitive task of&amp;#xA;setting up test data, used in many Rails projects.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I think FactoryGirl encourages bad design by hiding design problems.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The need for repetitive or complex test-data is an indication that your code and its API need improvement. Instead of improving the application, FactoryGirl enables you to only improve this in your test suite: that is not fixing at all, it is hiding the problem.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;building-up-a-state-before-we-can-test&amp;#34;&amp;gt;Building up a state before we can test&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We have an app that measures fuel-usage. The feature, or integration test for inserting a&amp;#xA;&amp;amp;quot;measurement&amp;amp;quot; could be something like this, when not using FactoryGirl, we can immediately see a problem: (the testing framework is irrelevant here, so I am using RSpec to minimize the noise because it allows me to be concise)&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;scenario&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;secretary on a project with cars adds new fuel-usage entries and sees them&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  project &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Project&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;create(name: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;new bridge in sahara&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;starts_on&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Date&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;today&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;beginning_of_year, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;ends_on&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Date&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;today&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;end_of_year)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  user &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;User&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;create(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;email&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;speedy-joe@example.com&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;password&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;secret&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;password_confirmation&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;secret&amp;amp;#39;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  user&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;roles&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;create(name: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;secretary&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;on&amp;lt;/span&amp;gt;: project)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Resource&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;new(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;type&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:car&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;slug&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;13-37-42&amp;amp;#39;&amp;lt;/span&amp;gt;, name: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Joes Truck&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;project&amp;lt;/span&amp;gt;: project)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  sign_in user&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  visit(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;projects/&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;project&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;id&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;/resources/&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;car&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;id&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;/entries/new&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  fill_in(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Measurement&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;with&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#ae81ff&amp;#34;&amp;gt;12&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  click_button(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Create Measurement&amp;amp;#39;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  expect(page)&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;to have_content(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Date&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;today&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;: 12 liter&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;If you consider that only the last four lines are relevant for this&amp;#xA;test,the first block is just noisy set-up. Now, we could refactor this&amp;#xA;into a before-block in RSpec. Or into a helper method&amp;#xA;&amp;lt;code&amp;gt;create_project_with_secretary_and_car&amp;lt;/code&amp;gt;. But that is what FactoryGirl&amp;#xA;does for us, with a much nicer interface then such ad-hoc helpers. With FactoryGirl&amp;#xA;the test could look like:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;scenario&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;secretary on a project with cars adds new fuel-usage entries and sees them&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  user &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; create(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:user_on_project&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;role_name&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;secretary&amp;amp;#39;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  create(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:car&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;project&amp;lt;/span&amp;gt;: user&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;project)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  sign_in user&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  visit(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;projects/&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;project&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;id&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;/resources/&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;car&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;id&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;/entries/new&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  fill_in(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Measurement&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;with&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#ae81ff&amp;#34;&amp;gt;12&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  click_button(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Create Measurement&amp;amp;#39;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  expect(page)&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;to have_content(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Date&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;today&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;: 12 liter&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;The first benefit becomes clear because we see far fewer lines of irrelevant objects being created.&amp;#xA;The second benefit of FactoryGirl immediately becomes clear too: we no longer need to provide irrelevant attributes such as the project name.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We have, however not fixed the our application, the implementation is still clumsy; our applications API is still unfriendly. A rake task, an &amp;amp;quot;import&amp;amp;quot;-job, a REST-client still needs to walk through (or consider the state of) all the other records.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If you consider your tests to be one (important) user of your API, a client, the problem is clear: we need to fix the application, not the client using that API.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;scenario&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;secretary on a project with cars adds new fuel-usage entries and sees them&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  resource &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Car&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;new(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Joes Truck&amp;amp;#39;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  project &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Project&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;create(name: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;New Bridge&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;resources&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;resource&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  user &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Secretary&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;create(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;email&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;speedy-joe@example.com&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;password&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;secret&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;project&amp;lt;/span&amp;gt;: project)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  sign_in user&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  visit(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;projects/&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;project&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;id&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;/resources/&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;car&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;id&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;/entries/new&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  fill_in(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Measurement&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;with&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#ae81ff&amp;#34;&amp;gt;12&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  click_button(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Create Measurement&amp;amp;#39;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  expect(page)&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;to have_content(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Date&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;today&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;: 12 liter&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;We have added code to our application that is used by one of the&amp;#xA;clients: the tests. We have abstracted some of the details away: a&amp;#xA;client does not need to know how we have implemented roles. For&amp;#xA;example: we can use &amp;lt;code&amp;gt;Car&amp;lt;/code&amp;gt; to build a resource, which takes care of&amp;#xA;creating a slug, and setting other defaults. If, instead we created some&amp;#xA;&amp;amp;quot;car&amp;amp;quot; trait in FactoryGirl, that effort was &amp;amp;quot;wasted&amp;amp;quot; on our app. Worse:&amp;#xA;we still maintain knowledge about how &amp;amp;quot;resource&amp;amp;quot; deals with cars in our&amp;#xA;test suite. That knowledge is merely centralised in a FactoryGirl file,&amp;#xA;but still lives in the client: the tests-suite. Again worse: if your&amp;#xA;trait happens to be &amp;amp;quot;helpful&amp;amp;quot; and create a &amp;lt;code&amp;gt;Project&amp;lt;/code&amp;gt; too, the factories&amp;#xA;become unpredictable. I&amp;#39;ve spent many hours debugging tests only to find&amp;#xA;out that some factory &amp;amp;quot;helpfully&amp;amp;quot; created its own relations: leaving me&amp;#xA;with &amp;amp;quot;why the hell are there three Projects, all in a different state, when I thought I created only one?&amp;amp;quot;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;As with any good API-design, you want your clients to remain as dumb as possible. You certainly don&amp;#39;t want a client having to know how projects, roles, users, resources and so on are related to one another.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In the last example, we have improved our application. Introduced useful defaults, additional, useful models all of which are immediately used by at least one&amp;#xA;user of the code: the tests.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Other users can start using these &amp;lt;code&amp;gt;Secretary&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;Car&amp;lt;/code&amp;gt; models and the automatic defaults on &amp;lt;code&amp;gt;Projects&amp;lt;/code&amp;gt; too, now. They allow us to clean up code that used the clumsy interface: controllers, rake-tasks, async-workers and so on.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Quite often, I&amp;#39;ve found that by removing the usage of a&amp;#xA;&amp;lt;code&amp;gt;FactoryGirl.create&amp;lt;/code&amp;gt; in a test, I could improve not only the code under&amp;#xA;test, but a lot of other places in the application too: Suddenly you&amp;#xA;realize that the &amp;amp;quot;case param[:resource_type]&amp;amp;quot; in your controller can be&amp;#xA;completely removed and replaced with a few smart calls to &amp;lt;code&amp;gt;Car.new&amp;lt;/code&amp;gt; and&amp;#xA;&amp;lt;code&amp;gt;Boat.new&amp;lt;/code&amp;gt; and so on. It even, quite often, resulted in improvements of&amp;#xA;the UX: by setting sane defaults, instead of presenting the user with a&amp;#xA;&amp;amp;quot;starts on is a required field&amp;amp;quot; we save it and set a default. All this&amp;#xA;because we learned from our tests that the code had some issues. All&amp;#xA;this we would not have learned if instead we cleaned up the tests with&amp;#xA;FactoryGirl.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;unit-testing&amp;#34;&amp;gt;Unit testing&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;d argue that with Unit tests there is even more reason not to use&amp;#xA;FactoryGirl. I often go as far as to forbid the usage of FactoryGirl&amp;#xA;in unit-tests.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;First of all, there is the &amp;amp;quot;unit&amp;amp;quot; part of the unit test: A test for&amp;#xA;Role, applies to Role alone. If you, somehow need to store a User with&amp;#xA;every Role, in order to test that Role, you have tightly coupled code.&amp;#xA;When your Role cannot live without a User (or Vice Versa), you don&amp;#39;t have&amp;#xA;a &amp;lt;code&amp;gt;Role&amp;lt;/code&amp;gt; and a &amp;lt;code&amp;gt;User&amp;lt;/code&amp;gt;, but a &amp;lt;code&amp;gt;UserWithRole&amp;lt;/code&amp;gt; unit. Since we want&amp;#xA;decoupled code (we want decoupled code, don&amp;#39;t we?) getting into this&amp;#xA;mess is something to avoid. FactoryGirl does not help you to avoid this,&amp;#xA;it even encourages this design somewhat:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;describe &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;User&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  describe &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;#function&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    it &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;includes the name of the role and its project&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      user &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; create(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:user&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:secretary_on_project&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      expect(user&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;name)&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;to be &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Secretary for New Bridge&amp;amp;#39;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;User&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;function&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    roles&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;map { &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;|&amp;lt;/span&amp;gt;r&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;|&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;r&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;name&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt; for &amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;r&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;project&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;name&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;&amp;lt;/span&amp;gt; }&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;join(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;,&amp;amp;#39;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;This looks like a nice, clean test. But only because we have hidden away&amp;#xA;all the complexity of coming to a state in which the user has a role and&amp;#xA;a project and both have a name. And because the test looks nice and clean, we might think we&amp;#xA;are done. We are not: One line of code with four issues (and&amp;#xA;probably actual bugs):&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It cannot deal with a user that has no roles.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It cannot deal with a user with roles which have no project.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It will render strange texts when a role or a project has no name.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;The User is dealing with attributes on roles, and even attributes on&amp;#xA;that roles&amp;#39; attributes (Law of Demeter).&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Sidenote: a method like &amp;lt;code&amp;gt;function&amp;lt;/code&amp;gt; should, IMO not be part of the Model,&amp;#xA;but is presentation and should either live in the views, or in some&amp;#xA;decorator or presentation double &amp;lt;code&amp;gt;UserPrester&amp;lt;/code&amp;gt;. For the sake of the&amp;#xA;example, let&amp;#39;s leave it here, just note that our tests are telling us&amp;#xA;this too: we are creating a presentable name in an object that deals&amp;#xA;wit details wit database-state: we are violating the Single&amp;#xA;Responsibility Principle.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When I recently encountered several of these exact issues in one of my projects&amp;#39; codebase, the developers argued that the&amp;#xA;solution was to add more &amp;amp;quot;validation&amp;amp;quot;. &amp;amp;quot;Yea, but our models&amp;#xA;require a role and each role requires a project. And we are only&amp;#xA;rendering stored users, so this should never happen&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It will happen, when you render the unstored role back on the screen when there validation-issues on storing the&amp;#xA;user, or its role. Or when deleting a project and not dealing correctly with deleting the associated roles and users.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When you test with a clean, predefined set of test-data, you won&amp;#39;t notice all such edge cases. But when you test with the simplest and most extreme edge case: an unstored, empty &amp;lt;code&amp;gt;User&amp;lt;/code&amp;gt;, most, if not all, these problems will be avoided; in the design, rather then in validations, delete-hooks, checking-for-nil in views and so on.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If we had tested without FactoryGirl, it would have looked something&amp;#xA;like this:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;describe &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;#function&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  before { @user &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;User&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;new } &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;# In RSpec, this is returned when using the method &amp;amp;#39;subject&amp;amp;#39;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  context &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;when user has roles&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    before { @user&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;roles &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Struct&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;new(name: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Secretary&amp;amp;#39;&amp;lt;/span&amp;gt;)&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt; }&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    it &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;includes the name of the role&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      expect(@user&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;function)&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;to be &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Secretary&amp;amp;#39;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    context &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;when role has project&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      before { @user&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;roles &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Struct&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;new(name: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Secretary&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;project&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Struct&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;new(name: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;New Bridge&amp;amp;#39;&amp;lt;/span&amp;gt;))&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt; }&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      it &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;includes the name of the project&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;        expect(@user&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;function)&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;to be &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;New Bridge&amp;amp;#39;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  context &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;when user has no roles&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    it &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;is &amp;amp;#34;no function&amp;amp;#34;&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      expect(@user&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;function)&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;to be &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;no function&amp;amp;#39;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Now our tests tell us about the problems:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;We explicitly have to deal with the situation when a user has no roles.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;We see clearly that the &amp;amp;quot;function&amp;amp;quot; is reaching too deep, through roles&amp;#xA;into the project.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Because it is made clear, we can now fix that:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;describe &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;#function&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  context &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;when user has roles&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    before { subject&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;roles &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Struct&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;new(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;as_function&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Secretary on New Bridge&amp;amp;#39;&amp;lt;/span&amp;gt;)&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt; }&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      it &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;includes role as a function&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      expect(subject&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;function)&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;to be &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Secretary on New Bridge&amp;amp;#39;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  context &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;when user has no roles&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    it { expect(subject&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;function)&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;to be &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;no function&amp;amp;#39;&amp;lt;/span&amp;gt; }&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;The tests are clean, and explain very well how we deal with edge cases.&amp;#xA;Because we have minimized the issues appearing in edge cases and because&amp;#xA;we have delegated the methods to the right objects.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;User&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;function&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;return&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;no function&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;unless&amp;lt;/span&amp;gt; roles&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    roles&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;map(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:as_function&amp;lt;/span&amp;gt;)&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;join(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;, &amp;amp;#39;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;Role&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;as_function&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;return&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;no project&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;unless&amp;lt;/span&amp;gt; project&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;name&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt; on &amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;#{&amp;lt;/span&amp;gt;project&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;name&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;We have now clearly improved our code, not just the tests. Bonus: since we no&amp;#xA;longer have to store stuff in the database, our unit-test is much faster now.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Sidenote: &amp;lt;code&amp;gt;subject.roles = [Struct.new]&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;subject.roles = [double(:role)]&amp;lt;/code&amp;gt; is a way of mocking the &amp;lt;code&amp;gt;Role&amp;lt;/code&amp;gt;: a User, nor its&amp;#xA;unit-test, needs to know how &amp;#39;Role&amp;#39; exactly works, making that&amp;#xA;explicitly clear in a test is often considered a good practice. Yet&amp;#xA;mocking and stubbing itself has a lot of downsides too. That is beyond&amp;#xA;this post. One important thing, though: this code, when using&amp;#xA;ActiveRecord Relations, will not work, since AR does not allow &amp;lt;code&amp;gt;roles&amp;lt;/code&amp;gt;&amp;#xA;to be assigned anything other then a list of &amp;lt;code&amp;gt;Roles&amp;lt;/code&amp;gt;: passing in a&amp;#xA;double or Struct will cause an exception. I use &amp;lt;code&amp;gt;mock_model()&amp;lt;/code&amp;gt; to fix&amp;#xA;this.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Whether you consider a controller a unit and test it as such, or whether&amp;#xA;you test it in a more &amp;amp;quot;integration&amp;amp;quot; makes little difference for the&amp;#xA;usage of FactoryGirl. A controller &amp;lt;a href=&amp;#34;http://mixandgo.com/blog/how-to-test-controllers-in-rails&amp;#34;&amp;gt;tested as&amp;#xA;unit&amp;lt;/a&amp;gt;, has no&amp;#xA;interaction with the database at all: so it really needs no FactoryGirl.&amp;#xA;When you test a controller and let it go through the database, that&amp;#xA;database needs state. But the above &amp;amp;quot;feature test&amp;amp;quot; examples apply here&amp;#xA;even more: if a certain controller action needs a lot of records&amp;#xA;in the database before it &amp;amp;quot;can work&amp;amp;quot;, the problem lies there, not in&amp;#xA;your tests which create all these records.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;dont-hide-problems-fix-them&amp;#34;&amp;gt;Don&amp;#39;t hide problems, fix them.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;FactoryGirl is a solution for hiding problems with your application and&amp;#xA;its API. When a test tells you that the API is clumsy, or dirty, instead&amp;#xA;of fixing that API, FactoryGirl encourages you to hide this problem.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;d argue that when some test needs 20&#43; records created before it can&amp;#xA;run, it is far better to leave these 20&#43; lines of code creating these&amp;#xA;records in your tests. At least you then admit and document the problem&amp;#xA;you have. Refactoring them with FactoryGirl makes it appear as if there&amp;#xA;is no problem with your application code. This is even more true for&amp;#xA;unit-tests that use FactoryGirl.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;when-to-use-factorygirl&amp;#34;&amp;gt;When to use FactoryGirl?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Is it completely useless and should you never use it then? No, it has a&amp;#xA;very good usecase: when your API does demand complex state, or lots of&amp;#xA;repetitive attributes. When your &amp;lt;code&amp;gt;Order&amp;lt;/code&amp;gt; requires a &amp;lt;code&amp;gt;ShippingAddress&amp;lt;/code&amp;gt;, it is poor&amp;#xA;practice to hide this away in factories. Even more so in the unit-tests&amp;#xA;of &amp;lt;code&amp;gt;Order&amp;lt;/code&amp;gt;. Instead consider introducing a &amp;lt;code&amp;gt;ShippedOrder&amp;lt;/code&amp;gt; model a&amp;#xA;&amp;#39;OrderBuilder&amp;lt;code&amp;gt;service-object or an&amp;lt;/code&amp;gt;Order.create_with_address()` helper&amp;#xA;even. Improve the API of your application.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I also want to give a shout-out to the developers of FactoryGirl: I&amp;#39;ve&amp;#xA;used it with great pleasure to replace Rails core -even more clumsy-&amp;#xA;fixtures. Please don&amp;#39;t read this as an attack on FactoryGirl and its&amp;#xA;developers, it are personal observations on using a &amp;amp;quot;test data framework&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But when Address requires you to type your address over and over in your&amp;#xA;tests, then FactoryGirl is a nice DSL to put the &amp;amp;quot;generating a street,&amp;#xA;city and postal code&amp;amp;quot; in a central place. After all: generating this, is&amp;#xA;part of the task of the client, not the API. FactoryGirl is a fancy&amp;#xA;solution for creating such data. But since factorygirl is very tightly&amp;#xA;coupled to the database and the models that represent that database, it&amp;#xA;is way more.&amp;#xA;When you need an &amp;amp;quot;address data&amp;amp;quot; in your tests, you could just as well&amp;#xA;add a small helper:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;LoremIzer&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;address&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    { &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;street&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Diagon Alley&amp;amp;#39;&amp;lt;/span&amp;gt;,&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;number&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;13a&amp;amp;#39;&amp;lt;/span&amp;gt;,&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;city&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;London&amp;amp;#39;&amp;lt;/span&amp;gt;,&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;postal_code&amp;lt;/span&amp;gt;: &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;1337&amp;amp;#39;&amp;lt;/span&amp;gt;}&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;It is only when such helpers grow large and wieldy: when they start to&amp;#xA;turn into a framework, that FactoryGirl (or another &amp;lt;a href=&amp;#34;https://github.com/EmmanuelOga/ffaker&amp;#34;&amp;gt;data generation&amp;#xA;gem&amp;lt;/a&amp;gt; has a good place in your app.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Selecting a Good WYSIWYG for Rails: it&amp;#39;s all about use-cases</title>
   <link href="https://berk.es/2015/02/03/selecting-a-good-wysiwyg-for-rails-its-all-about-usecases/"/>
   <updated>2015-02-03T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2015/02/03/selecting-a-good-wysiwyg-for-rails-its-all-about-usecases/</id>
   <content type="html">&amp;lt;p&amp;gt;There is no such thing as &amp;amp;quot;a good WYSIWYG&amp;amp;quot; without looking closely at the case you are trying to solve and the preconditions you want to set. WYSIWYG, is, unfortunately, a label that is applied to far too many solutions for varying problems.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When people ask for &amp;amp;quot;A WYSIWYG-editor&amp;amp;quot; they are often asking for a solution to one of the following three problems:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Needing to add markup up to body-texts.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;In need of more freedom when layouting pages without having to go&amp;#xA;through the development-team and/or releases.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Editing copy-writing without having to go through the development-team&amp;#xA;and/or releases.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Unfortunately, many WYSIWYG-editors start off in one such use-case and then try to incorporate the other two; fail and grow unwieldy. I consider &amp;amp;quot;a good WYSIWYG-editor&amp;amp;quot; one that stays close to its usecase and implements that well. But even then, WYSIWYG-editors far often add more problems than that they solve. Sometimes you don&amp;#39;t need a WYSIWYG-editor at all, so the first thing you should do as a developer, before the need or request for such an editor comes up, is to look closely at the editing workflow and look where you can improve this. Quite often a request for &amp;amp;quot;a WYSIWYG-editor&amp;amp;quot; is actually a sign that the user has different needs then what you built in the CMS. And when there is a requirement in the specifications, it often is a sign that the person drafting these requirements did not think through the editing workflow very well.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Off course there are really good reasons to need such editors. But in&amp;#xA;that case, it is very important to know what it is used for. And then&amp;#xA;pick one (or more) to solve these problems. &amp;amp;quot;A WYSIWYG&amp;amp;quot; is by no means a&amp;#xA;magic potion that you can drop on your CMS and solve all the editor&amp;#39;s&amp;#xA;problems.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;marking-up-body-texts&amp;#34;&amp;gt;Marking up body-texts.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is your typical-usecase. But note, that such an editor is not very good at layout. Nor should it be, layout is a different use-case.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;An editor for such a use-case, must allow setting some inline styles, editing inline text and assets and allow you to add styles (mostly classes) that will effect the layout. A good WYSIWYG-editor will visualise this process and apply the styles for these classes inside the editor. E.g. if you mark something as &amp;amp;quot;aside quote&amp;amp;quot; it will be styled as &amp;amp;quot;a quote that is pulled aside&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The most obvious place where you will see this going wrong, is with responsive-designs. HTML was always meant as a &amp;amp;quot;suggestion&amp;amp;quot; to the browser on how to render it, with CSS to instruct the browser on how to style, place and layout it (and yes, CSS has been rather poor in this too, but that is a different issue). Your texts (the body-texts) are best treated that way too: where the WYSIWYG-edit adds markup which the &amp;amp;quot;design&amp;amp;quot;, through CSS most likely, can then leverage to style, design and layout.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Storing the layout-information with(in) the content is a guarantee for disaster when you decide to change the designs. I&amp;#39;ve done several jobs where I helped people into the &amp;amp;quot;new mobile age&amp;amp;quot; by writing large, ugly parsers that clean body-texts of any layout that breaks mobile, but was stored in the database. Believe me, you don&amp;#39;t want that markup in your database.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Having such &amp;amp;quot;content&amp;amp;quot; in your database&amp;amp;quot;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-html&amp;#34; data-lang=&amp;#34;html&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;amp;lt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;p&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;style&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;float:right; border: 2px solid #eee;&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;amp;lt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;b&amp;lt;/span&amp;gt;&amp;amp;gt;Computers are useless. They can only give you answers.&amp;amp;lt;/&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;b&amp;lt;/span&amp;gt;&amp;amp;gt;&amp;amp;lt;/&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;br&amp;lt;/span&amp;gt;&amp;amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;amp;lt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;b&amp;lt;/span&amp;gt;&amp;amp;gt;&amp;amp;lt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;i&amp;lt;/span&amp;gt;&amp;amp;gt;— Pablo Picasso&amp;amp;lt;/&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;i&amp;lt;/span&amp;gt;&amp;amp;gt;&amp;amp;lt;/&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;b&amp;lt;/span&amp;gt;&amp;amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;amp;lt;/&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;p&amp;lt;/span&amp;gt;&amp;amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Is awful (and this is, by far, not the worse a WISYWYG-editor can and&amp;#xA;will output. Divs. Divs everywhere).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-html&amp;#34; data-lang=&amp;#34;html&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;amp;lt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;quote&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;aside&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  Computers are useless. They can only give you answers.&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;amp;lt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;footer&amp;lt;/span&amp;gt;&amp;amp;gt;— Pablo Picasso&amp;amp;lt;/&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;footer&amp;lt;/span&amp;gt;&amp;amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;amp;lt;/&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;quote&amp;lt;/span&amp;gt;&amp;amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;...is better.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;My personal favorite is &amp;lt;a href=&amp;#34;http://wymeditor.github.io&amp;#34;&amp;gt;WYMeditor&amp;lt;/a&amp;gt;, though I am looking for a more modern looking alternative.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When the audience allows for it (most important: if I can train/instruct the users) a markdown &#43; live preview is preferred. My goto is &amp;lt;a href=&amp;#34;http://oscargodson.github.io/EpicEditor/&amp;#34;&amp;gt;epiceditor&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Markdown has several huge benefits: you are storing a simplified, deduced markup in the database, no need for ugly HTML-parsing or regexp replacement of body-texts when changing the layout. And depending on where you want to send the text, you can compile it to the appropriate markup. HTML for the web, a different HTML through JSON for your app, PDF, epub, and so on. All of that is really only possible with very clean HTML. And allowing &amp;amp;quot;only clean&amp;amp;quot; HTML without using a different markup-language is very, very hard.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With something like:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-text&amp;#34; data-lang=&amp;#34;text&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;amp;gt; Computers are useless. They can only give you answers.&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;amp;gt; — Pablo Picasso&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;... the error tolerance is large; where before you had to allow &amp;lt;code&amp;gt;class=&amp;amp;quot;aside&amp;amp;quot;&amp;lt;/code&amp;gt; but not &amp;lt;code&amp;gt;class=&amp;amp;quot;button btn-round&amp;amp;quot;&amp;lt;code&amp;gt;, you now no longer need to deal with this. When you allow a &amp;amp;quot;classic&amp;amp;quot; WYSIWYG you  will allow all sorts of (broken) HTML too.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;There is one thing certain: prepare for support-hell: you&amp;#39;ll be debugging broken HTML from the moment you deploy a classic WYSIWYG-editor. &amp;amp;quot;The mobile site is broken since the last deploy&amp;amp;quot;. &amp;amp;quot;No, it&amp;#39;s not. It&amp;#39;s broken because you placed a left-floating image in a title in a table, probably not on purpose, but you did nonetheless.&amp;amp;quot;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;layouting-pages&amp;#34;&amp;gt;Layouting Pages.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If you really want your users to do this, build a CMS for this. This is certainly not something a WYSIWYG-editor can solve for you. You might need to build something that allows users to move widgets around. E.g. though &amp;lt;a href=&amp;#34;https://github.com/apotonick/apotomo&amp;#34;&amp;gt;Apotomo&amp;lt;/a&amp;gt;. If you consider that even a professional webdeveloper with good understanding of CSS, a nice grid-framework at her disposal and clean HTML5 boilerplate has a hard time to build nice layouts, you&amp;#39;ll understand that a tool to create such layouts without touching all that code is nearly impossible. It is certainly not something you&amp;#39;ll drop on your project and release for your interns to add content to. So this is really not about WYSIWYG-editors anymore, but all about building an intuitive CMS. Which is a completely different topic.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But, you could assign a small part of the page to be &amp;amp;quot;layouted&amp;amp;quot;. In that case, often the &amp;amp;quot;layout possibilities&amp;amp;quot; are very limited and the aforementioned &amp;lt;em&amp;gt;Marking up body-texts&amp;lt;/em&amp;gt; applies. This is not about choosing an editor, but about defining the exact possibilities and areas that need to be edited and layouted.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Alternatively, you can assign small areas that can be edited, in combination with a tool to choose from pre-defined layouts. This is what e.g. &amp;lt;a href=&amp;#34;https://github.com/modeset/cmsimple&amp;#34;&amp;gt;cmsimple&amp;lt;/a&amp;gt; does. This works very well when you have a site with a few pages only and little dynamic content. The moment you need to mix this concept with, say, an upcoming-events calendar it becomes muddy and hard. Your users now need to edit everything around this calender in one place (the WYSIWYG-editor for the layout) and the other pieces somewhere else (in an events-CRUD-list e.g.). This is a hard problem but solved by mixing in copy-editing tools.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;copy-editing&amp;#34;&amp;gt;Copy-editing.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Often, in-between all the dynamic parts, you want to change the copy-writing. Case at hand: a list of &amp;amp;quot;upcoming events&amp;amp;quot;. It shows a dynamic list of events that will start in the next three weeks. The text and title above that list should be editable. You could use &amp;lt;a href=&amp;#34;http://jejacks0n.github.io/mercury/&amp;#34;&amp;gt;Mercury&amp;lt;/a&amp;gt; (which is what cmsimple leverages), but in that case you&amp;#39;d need to build a backend to capture and save/update the content. Worse is that you&amp;#39;ll have two different CMS-es: one to edit the copy-writing and one to manage the events.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For this, there is &amp;lt;a href=&amp;#34;http://opensourcerails.com/phrasing/&amp;#34;&amp;gt;phrasing&amp;lt;/a&amp;gt; it has all that in place and works nice. The copy-writing can be kept down to the bare minimum: something that is done by a different person, or at the very least in a different workflow. The moment that someone has to toggle between managing content and editing the copy-writing within one task, is the moment you&amp;#39;ll need to refine the workflow. Most probably move some &amp;amp;quot;copy&amp;amp;quot; out of the copy-writing and into their own management. Say, someone edits the subtitle on the homepage on a daily base to contain the latest headline of the news-section: the user really needs a &amp;amp;quot;highlight-management&amp;amp;quot; within the content-management for the news-items.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Note that regardless of the solution, performance could become a bottleneck when there is a lot of copy-writing, because the texts now no longer come from a simple template-file but from a database. This is solvable with simple caching. And note that regardless of the solution, intermixing this with multilingual copy-writing will become a hell that you cannot ever hope to get out of.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But then again. Adding a WYSIWYG-editor in itself means entering a hell&amp;#xA;that you cannot ever hope to get out of.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;small&amp;gt;This post was made as a &amp;lt;a href=&amp;#34;http://www.reddit.com/r/rails/comments/2ukgm3/looking_for_a_good_wysiwyg/&amp;#34;&amp;gt;comment on Reddit in r/rails&amp;lt;/a&amp;gt; and then edited and redacted to be a blogpost.&amp;lt;/small&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Uitgevers, geef mij niet de schuld, wanneer ik mijn ebooks niet van jullie koop!</title>
   <link href="https://berk.es/2014/02/04/new-post/"/>
   <updated>2014-02-04T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2014/02/04/new-post/</id>
   <content type="html">&amp;lt;p&amp;gt;Schrijvers en uitgevers zijn bang. &amp;lt;a href=&amp;#34;http://www.volkskrant.nl/vk/nl/3352/boeken/article/detail/3589862/2014/02/04/90-procent-van-boeken-op-e-readers-gratis-of-illegaal.dhtml&amp;#34;&amp;gt;Mensen kopen hun boeken niet meer&amp;lt;/a&amp;gt;. Goh. Al &amp;lt;a href=&amp;#34;/2010/08/11/uitgevers-maak-van-mij-geen-boekenpiraat/&amp;#34;&amp;gt;in 2010&amp;lt;/a&amp;gt; schreef ik al dat het veel makkelijker is om illegale boeken te krijgen dan legale.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Daar is niets in veranderd. (Ik kocht, trouwens, in 2013 voor bijna €200 boeken voor mijn e-reader,&amp;#xA;maar dat terzijde).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Er zijn, voor mij, als fervent lezer, ongeveer de volgende&amp;#xA;mogelijkheden, in volgorde van moeilijkheid:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Het boek helemaal niet lezen.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Dan maar de papieren versie kopen.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Het boek illegaal downloaden.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Het boek kopen.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dat zit zo: Ik heb thuis geen Windows&amp;#xA;computers. Dus vallen alle boeken van bol.com en&amp;#xA;andere grote ebook verkopers automatisch af: je hebt daarvoor super&amp;#xA;gebruiksonvriendelijke, lelijke software nodig. Software die in de DRM&amp;#xA;voorziet: die voorkomt dat jij het boek uitleent aan je moeder, nadat je het uithebt. En deze software draait niet op Linux.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Als je groepen mensen categorisch uitsluit uit je winkel, dan moet je niet raar&amp;#xA;opkijken als die groepen niets meer bij je willen kopen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Sommigen van jullie zoeken hun heil inmiddels in, wat heet, &amp;lt;em&amp;gt;on device&amp;#xA;purchase&amp;lt;/em&amp;gt;, &amp;lt;a href=&amp;#34;http://store.kobobooks.com/en-nl/&amp;#34;&amp;gt;direct op mijn&amp;#xA;e-reader een boek kopen&amp;lt;/a&amp;gt;; helaas ook met DRM, dus het gekochtte boek uitlenen aan mijn&amp;#xA;vrouw, zit er ook hier niet in (Iets wat ik uiteraard met een illegaal boek wel gewoon kan; met een papieren boek trouwens ook). Het is echter een kleine verbetering ten opzichte van&amp;#xA;helemaal niet kunnen kopen, omdat ik nu&amp;#xA;tenminste mág betalen. Maar slechts een héél kleine: Als Kobo verdwijnt&amp;#xA;(en, zoals jullie heel goed weten, boekenverkopers verdwijnen nogal&amp;#xA;vaak), of als ik besluit dat mijn volgende e-reader een Frobo moet zijn,&amp;#xA;ben ik al mijn aldaar gekochtte boeken voorgoed kwijt. Foetsie. Dat&amp;#xA;vooruitzicht maakt dat ik wel vijf keer nadenk om daar mijn boeken te&amp;#xA;kopen. En jullie zullen vast wel begrijpen dat als ik over een paar jaar&amp;#xA;mijn Frobo heb, ik deze boeken natuurlijk niet weer opnieuw ga kopen;&amp;#xA;dat de piratebay dan toch echt het betere alternatief is.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En er zijn bij jullie collega-uitgevers van IT-boeken ook &amp;lt;a href=&amp;#34;http://pragprog.com/&amp;#34;&amp;gt;heel verlichtte&amp;#xA;geesten&amp;lt;/a&amp;gt; die DRM-vrije downloads verkopen. Ga daar&amp;#xA;eens mee praten. Hier kan ik de auteur ondersteunen door voor zijn boek te betalen. Het is heel makkelijk om te betalen, zelfs.Pragprog en O&amp;#39;Reilly verdienen veel aan me. Ik zou eerlijk gezegd niet eens weten of hun boeken ook illegaal te vinden zijn.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar het ergste, beste uitgevers, is nog wel dat het illegaal downloaden&amp;#xA;helemaal niet zo gemakkelijk is. De&amp;#xA;gebruikerservaring (&amp;lt;em&amp;gt;experience&amp;lt;/em&amp;gt;) is ronduit slecht. Als je al je weg&amp;#xA;vind in het oerwoud van torrent-sites, torrent-proxies,&amp;#xA;download-share-sites en versleutelde rar-bestandjes, dan zijn de&amp;#xA;gedownloadde boeken&amp;#xA;vaak van erg slechte kwaliteit. Zo zijn heel veel&amp;#xA;boeken heel slecht geconverteerd, met lelijke woordafbrekingen, vol&amp;#xA;spelfouten, ontbreken plaatjes en moet je vaak lang zoeken voor je een&amp;#xA;specifieke titel kunt vinden die ook nog eens downloadbaar is. Die&amp;#xA;&amp;lt;em&amp;gt;experience&amp;lt;/em&amp;gt; kan écht veel beter, zonder grote investeringen. Daar&amp;#xA;betalen mensen als ik graag een paar euro voor; geloof me: dat heeft&amp;#xA;Netflix en Spotify allang bewezen. Daar zijn lessen geleerd: dóe daar&amp;#xA;wat mee.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar nog veel erger is dat veel boeken helemaal niet &amp;amp;quot;digitaal&amp;amp;quot; worden&amp;#xA;uitgegeven. Zijn jullie soms bang voor digitaal? Het is niet alsof het&amp;#xA;hele productieproces van een boek nog op typmachines en met van die&amp;#xA;letterbakken gaat, toch: je hebt ze echt wel digitaal.&amp;#xA;Daarbij blijkt dat de markt zó ontzettend verlegen zit om ebooks van&amp;#xA;boeken die jullie niet als ebook uitgeven, dat mensen ze zelf maar gaan&amp;#xA;maken: scannen, verbeteren, overtikken. Wat een moeite, wat een energie.&amp;#xA;Maar deze community is zo ontzettend groot, dat vrijwel alle relatief&amp;#xA;popuplaire boeken inmiddels op deze manier te vinden zijn.&amp;#xA;Daar waar jullie uit angst voor piraten, boeken niet digitaal uitgaven,&amp;#xA;zijn jullie inmiddels zelfs overbodig gemaakt door diezelfde piraten.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dus hou onmiddelijk op met zeuren over piraterij. Er is maar één&amp;#xA;schuldige partij aan te wijzen en dat zijn jullie zelf. Jullie hebben&amp;#xA;gefaald om een goede, makkelijke manier aan te bieden waarmee mensen&amp;#xA;uberhaupt jullie boeken kunnen kopen. Jullie hebben vaak zelfs helemaal&amp;#xA;verzaakt om boeken digitaal te verkopen. Wanneer jullie dan nu naar mij,&amp;#xA;jullie klant, gaan wijzen dat ik niet genoeg boeken koop, dan zijn&amp;#xA;jullie naast dom en klantonvriendelijk, gewoon enorm slechte&amp;#xA;ondernemers, die het verdienen om failliet te gaan. Breng eerst je eigen&amp;#xA;shit op orde, pas je aan aan je klanten en hun wensen maar hou op met&amp;#xA;anderen de schuld te geven van je eigen fouten.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Olaf van den Heuvel is een maximum flapdrol</title>
   <link href="https://berk.es/2013/11/28/olaf-van-den-heuvel-is-een-maximum-flapdrol/"/>
   <updated>2013-11-28T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2013/11/28/olaf-van-den-heuvel-is-een-maximum-flapdrol/</id>
   <content type="html">&amp;lt;p&amp;gt;Een van de mooie dingen van een verstorend concept als Bitcoin, is dat&amp;#xA;&amp;amp;quot;de gevestigde orde&amp;amp;quot; zich er geen raad mee weet. Dat is niet erg, voor&amp;#xA;een deel heeft dat tijd nodig, voor een ander deel, zorgt het voor&amp;#xA;marktvernieuwing (als in: diegenen die blijven vasthouden dat paard en&amp;#xA;wagen echt een beter vervoermiddel is dan een auto of fiets, gaan&amp;#xA;failliet).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar mooier is, om te zien hoe de &amp;lt;a href=&amp;#34;https://www.youtube.com/watch?v=SG5jw3FWArE&amp;#34;&amp;gt;Dr. Clavans&amp;lt;/a&amp;gt; hun onkunde ten toon spreiden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Vandaag &amp;lt;strong&amp;gt;Olaf van den Heuvel&amp;lt;/strong&amp;gt;. Op zijn stelling &amp;lt;a href=&amp;#34;http://www.rtlnieuws.nl/economie/home/bitcoin-zal-niet-lang-onder-ons-blijven&amp;#34;&amp;gt;&amp;amp;quot;Bitcoin zal niet lang onder ons blijven&amp;amp;quot;&amp;lt;/a&amp;gt; is niet veel aan te merken: het is zijn mening en een goede waarschuwing. Pas op: risico, bubble-gedrag!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar dan &amp;amp;quot;onderbouwt&amp;amp;quot; hij zijn stelling. En toont aan dat hij eigenlijk&amp;#xA;geen enkel benul heeft waarover hij praat.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;zolang ik niet meer transparantie heb, over wat bepaalt de schaarste van Bitcoin, vind ik het heel lastig om dat als betrouwbaar betaalmiddel te zien».&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Hij begint zijn verhaal hiermee, bovenstaande transcriptie is wanneer&amp;#xA;hij dit herhaalt. Bitcoin. Geen transparantie.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Als er &amp;lt;em&amp;gt;iets&amp;lt;/em&amp;gt; is dat bij Bitcoin anders is dan alle betaalsystemen en&amp;#xA;economieën die we tot nog toe kennen, is het wel die transparantie!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ten eerste is er het volledig open protocol. De papers, alle&amp;#xA;(academische) discussie daarover, alle economische modellen en wat dies&amp;#xA;meer zij, is zo transparant als maar zijn kan. Iedereen die meer dan&amp;#xA;vijf minuten besteed aan het lezen over Bitcoin weet dat er nooit meer&amp;#xA;dan 21 miljoen kunnen zijn, dat dit mathematisch is vastgelegd. Iedereen&amp;#xA;die meer dan twintig minuten doorleest snapt dat alle economische&amp;#xA;modellen van niet alleen het protocol maar van alle randeffecten tot in&amp;#xA;den treuren in alle transparantie bediscussieerd worden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dan is het er het bijna&amp;#xA;religieuze open source mantra: het staat de werkgever van heer van der&amp;#xA;Heuvel vrij om een eigen Bitcoin-programma, app, of site te ontwikkelen.&amp;#xA;Sterker nog, dat zou de hele gemeenschap ten zeerste toejuichen. Ik heb&amp;#xA;geen flauw benul van wat er precies in mijn randomreader zit, waarmee ik al mijn betalingen bevestig.&amp;#xA;Maar ik weet vrij nauwkeurig hoe iedere Bitcoin gemaakt, verdeeld, bevestigd en beveiligd wordt, door het lezen van specs, whitepapers en broncode. Dat kan, omdat&amp;#xA;alle software &amp;lt;strong&amp;gt;transparant&amp;lt;/strong&amp;gt; is: open source.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En dan is er de blockchain: Als je weet wie of wat er &amp;lt;a href=&amp;#34;http://www.piratenpartij.nl/blog/dirk-poot/hartelijk-dank-voor-de-bitcoins&amp;#34;&amp;gt;achter een&amp;#xA;rekeningnummer&amp;#xA;zit&amp;lt;/a&amp;gt;&amp;#xA;kan iedereen nagaan waar het heengaat. Het kán eenvoudigweg niet&amp;#xA;transparanter.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Mijnheer van der Heuvel, u bent een econoom met een goede aanstelling. U&amp;#xA;wordt door economieredacties gebeld voor uw kennis en mening over&amp;#xA;technische zaken.&amp;#xA;Ik verwacht dan ook dat u weet waarover u praat. Wanneer u echter&amp;#xA;(het gebrek aan) &amp;lt;em&amp;gt;transparantie&amp;lt;/em&amp;gt; opvoert als argument waarom Bitcoin een&amp;#xA;slecht idee is, geeft u niet alleen aan dat u geen flauw benul heeft&amp;#xA;waarover u praat, u erkent daarmee vooral dat u een flapdrol bent.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Hoe kom ik aan Bitcoin, en een Bitcoin rekening?</title>
   <link href="https://berk.es/2013/11/27/hoe-kom-ik-aan-bitcoin-en-een-bitcoin-rekening/"/>
   <updated>2013-11-27T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2013/11/27/hoe-kom-ik-aan-bitcoin-en-een-bitcoin-rekening/</id>
   <content type="html">&amp;lt;p&amp;gt;Deze vraag kreeg ik de afgelopen weken verschillende keren. Laat ik&amp;#xA;beginnen met de bekende &amp;lt;a href=&amp;#34;/2013/04/01/bitcoins-kopen-weet-wat-je-doet/&amp;#34;&amp;gt;waarschuwing die ik eerder al&amp;#xA;gaf&amp;lt;/a&amp;gt;: Weet&amp;#xA;wat je doet. Besef dat de kans groot is dat je alles verliest. Misschien&amp;#xA;is de kans dat je alles verliest wel groter dan dat je iets aan je&amp;#xA;bitcoin-avontuur overhoud. Dit is dan ook stap 1: &amp;lt;a href=&amp;#34;http://bitcoin.org/nl/wat-u-moet-weten&amp;#34;&amp;gt;Informeer&amp;#xA;jezelf!&amp;lt;/a&amp;gt;. En dek jezelf in: ga&amp;#xA;niet je hele spaarrekening overzetten, maar alléén maar geld dat je kunt&amp;#xA;missen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h1 id=&amp;#34;een-bitcoin-rekening&amp;#34;&amp;gt;Een Bitcoin-rekening.&amp;lt;/h1&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik krijg vaak de vraag van mensen hoe ze een rekening kunnen openen. Of&amp;#xA;de opmerking dat dit moeilijk is. Het is allesbehalve moeilijk! Maar&amp;#xA;berust vooral op een veel gemaakt misverstand: een bitcoin-rekening hoef&amp;#xA;je niet bij een site, bedrijf of organisatie af te nemen!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Download een&amp;#xA;&amp;lt;a href=&amp;#34;https://play.google.com/store/apps/details?id=de.schildbach.wallet&amp;#34;&amp;gt;app&amp;lt;/a&amp;gt;.&amp;#xA;Dat is alles; je hebt nu een rekening. Op je telefoon, dus zet er geen&amp;#xA;grote bedragen op!&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Download een programma op je pc: &amp;lt;a href=&amp;#34;http://bitcoin.org/nl/download&amp;#34;&amp;gt;BitcoinQT&amp;lt;/a&amp;gt;, &amp;lt;a href=&amp;#34;https://multibit.org/&amp;#34;&amp;gt;MultiBit&amp;lt;/a&amp;gt; of &amp;lt;a href=&amp;#34;https://bitcoinarmory.com/&amp;#34;&amp;gt;Bitcoin Armory&amp;lt;/a&amp;gt;. Dat is alles; je hebt nu een rekening. Op een computer, die je deelt met alle virussen, familieleden, collega&amp;#39;s enzovoort. Dus ook hier: zet er geen grote bedragen op!&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Open een van de vele web-based wallets. Deze beheer je niet zelf en&amp;#xA;zijn daarom maar deels betrouwbaar. Ook hier weer de les om er niet al&amp;#xA;te veel op te zetten, je bent niet beschermd en de kans is erg groot dat&amp;#xA;op de dienst waar je jou geld op opslaat een keer ingebroken wordt en je&amp;#xA;alles kwijtraakt. &amp;lt;a href=&amp;#34;https://blockchain.info/&amp;#34;&amp;gt;Blockchain.info&amp;lt;/a&amp;gt; is de&amp;#xA;bekendste.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;h1 id=&amp;#34;bitcoins-wisselen&amp;#34;&amp;gt;Bitcoins wisselen.&amp;lt;/h1&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Het makkelijkst is om ze te kopen voor Euro&amp;#39;s. Bij een wisselkantoor.&amp;#xA;Hiervan zijn er vele, in vele soorten en maten. Veel beginners proberen&amp;#xA;meteen bij het grootste en bekendste handelskantoor,&amp;#xA;&amp;lt;a href=&amp;#34;http://mtgox.com&amp;#34;&amp;gt;MtGox&amp;lt;/a&amp;gt; een rekening te openen. Dat is alsof je naar&amp;#xA;de Beurs in Amsterdam gaat, om Thaise Baht te wisselen voor je&amp;#xA;zomervakantie. Dat doe je niet op een beursvloer, maar gewoon bij een&amp;#xA;GWK op de hoek. MtGox of de Europese &amp;lt;a href=&amp;#34;https://bitcoin-central.net&amp;#34;&amp;gt;Bitcoin-central&amp;lt;/a&amp;gt; zijn zulke beursvloeren. Maar er zijn een heleboel GWKs in Nederland. En een boel oplichters.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Enkele goed aangeschreven kantoren zijn:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;https://bitonic.nl/&amp;#34;&amp;gt;Bitonic&amp;lt;/a&amp;gt;; betalen met iDeal (of gebruik &amp;lt;a href=&amp;#34;https://bitonic.nl/?refcode=pz3mBx94765&amp;#34;&amp;gt;deze link naar Bitonic, waarmee ik een vergoeding ontvang&amp;lt;/a&amp;gt;.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;https://www.bitcoin.de/en?cr=1&amp;#34;&amp;gt;Bitcoin.de&amp;lt;/a&amp;gt;; Betalen met&amp;#xA;bankoverschrijvingen.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Meer kun je terugvinden op &amp;lt;a href=&amp;#34;http://howtobuybitcoins.info/nl.html&amp;#34;&amp;gt;How to Buy Bitcoins in The Netherlands&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wanneer je veel geld wilt wisselen, is de vuistregel om eerst wat af te tasten: wissel een tientje in, wissel het terug in. Zo bouw je vertrouwen op en heb je enig inzicht in de procedures, duur en betrouwbaarheid van het kantoor. Je loopt toch ook niet met al je vakantiegeld in een koffertje naar het Belhuis/wasserette/telefoonverkoper om de hoek om het om te wisselen naar Braziliaanse Real zonder de eigenaar een beetje te vertrouwen?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Of ga op zoek naar iemand die je in levende lijve wil helpen. Op &amp;lt;a href=&amp;#34;https://localbitcoins.com/&amp;#34;&amp;gt;localbitcoins&amp;lt;/a&amp;gt; zijn vaak mensen te vinden waar je mag langskomen om een briefje van vijftig om te wisselen. Uiteraard ook hier weer de waarschuwing dat je niet met twee ton in een koffertje moet langsrijden :).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Minen is weer een heel ander verhaal en vergt veel kennis, geduld en&amp;#xA;dure, speciale hardware. Voor degene die enkel een Pizza of twee wil &amp;lt;a href=&amp;#34;http://blog.thuisbezorgd.nl/bitcoins-voor-pizza/&amp;#34;&amp;gt;gaan&amp;#xA;kopen&amp;lt;/a&amp;gt; is dit absoluut&amp;#xA;de moeilijkste optie.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h1 id=&amp;#34;het-is-heel-makkelijk&amp;#34;&amp;gt;Het is heel makkelijk.&amp;lt;/h1&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ondanks alle ingewikkelde concepten, cryptografische en economische&amp;#xA;modellen erachter, is het gebruik erg simpel.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Je &amp;lt;em&amp;gt;hoeft&amp;lt;/em&amp;gt; je nergens aan te melden, te identificeren, of te&amp;#xA;registreren.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Je &amp;lt;em&amp;gt;hoeft&amp;lt;/em&amp;gt; geen ingewikkelde mining-computers te kopen om aan wat&amp;#xA;(fracties van) Bitcoins te komen.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Je &amp;lt;em&amp;gt;hoeft&amp;lt;/em&amp;gt; helemaal geen registratie- of andere complexe procedures&amp;#xA;door om wat Euro&amp;#39;s te wisselen voor Bitcoins.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een app op je telefoon, en een contact op localbitcoin die ter plekke je&amp;#xA;Euro&amp;#39;s inwisselt en ze overmaakt naar je telefoon is genoeg. Of een&amp;#xA;iDeal betaling op Bitonic en het vanaf daar overmaken naar je rekening&amp;#xA;op je pc is ook voldoende.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h1 id=&amp;#34;en-geef-ze-uit&amp;#34;&amp;gt;En geef ze uit.&amp;lt;/h1&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik ben geen econoom, maar het principe is zo eenvoudig dat zelfs ik het&amp;#xA;kan begrijpen: een Bitcoin is zoveel waard als wat je ermee kunt kopen:&amp;#xA;Goederen of ander geld. De waarde wordt dus voor een groot deel bepaald door wat je ermee kunt kopen. Als&amp;#xA;niemand ermee koopt, verkoopt ook niemand producten voor Bitcoin. En is&amp;#xA;die waarde laag. De beste &amp;amp;quot;investering&amp;amp;quot; is dan ook om het (ook) uit te&amp;#xA;geven!&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>I am a webdeveloper (using Ruby, Rails and other Open Source)</title>
   <link href="https://berk.es/2013/10/06/i-am-a-webdeveloper-using-ruby-rails-and-other-open-source/"/>
   <updated>2013-10-06T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2013/10/06/i-am-a-webdeveloper-using-ruby-rails-and-other-open-source/</id>
   <content type="html">&amp;lt;p&amp;gt;Last few weeks, I had to explain quite a few times what it is I am doing&amp;#xA;now. Time for a summary.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h1 id=&amp;#34;i-am-not-a-wordpress-developer&amp;#34;&amp;gt;I am not a WordPress Developer.&amp;lt;/h1&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Source of the confusion is &amp;lt;a href=&amp;#34;http://www.savvii.nl&amp;#34;&amp;gt;Savvii&amp;lt;/a&amp;gt;, a brand-new&amp;#xA;WordPress hoster where I work. Although Floor Drees &amp;lt;a href=&amp;#34;http://arrrrcamp.be/talks#floor-drees&amp;#34;&amp;gt;forbade me to use&amp;#xA;the word &amp;lt;em&amp;gt;Senior&amp;lt;/em&amp;gt;&amp;lt;/a&amp;gt;, I am the Senior Developer at Savvii.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And I develop in Ruby. And Rails. And then some more.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h1 id=&amp;#34;i-also-dont-develop-with-or-for-drupal&amp;#34;&amp;gt;I also don&amp;#39;t develop with or for Drupal.&amp;lt;/h1&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;After my decision to stop all &amp;lt;a href=&amp;#34;/2012/10/01/farewell-drupal/&amp;#34;&amp;gt;Drupal Work&amp;lt;/a&amp;gt; I&amp;#39;ve honed my Rails skills. Improved my understanding of OO patterns, software architecture. Got to love TDD and BDD, and hardly ever regretted my decision.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h1 id=&amp;#34;i-am-a-webdeveloper-developing-in-ruby-rails-and-using-other-tools&amp;#34;&amp;gt;I am a webdeveloper. Developing in Ruby, Rails. And using other tools.&amp;lt;/h1&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And then, an offer to help set-up a startup in my own town, a WordPress hoster, came along. Last Wednesday we launched our &amp;lt;a href=&amp;#34;http://www.startuplessonslearned.com/2009/03/minimum-viable-product.html&amp;#34;&amp;gt;MVP&amp;lt;/a&amp;gt; (yay, we launched!). And the custom parts of our infra are &amp;lt;a href=&amp;#34;http://translate.google.com/translate?sl=nl&amp;amp;amp;tl=en&amp;amp;amp;js=n&amp;amp;amp;prev=_t&amp;amp;amp;hl=nl&amp;amp;amp;ie=UTF-8&amp;amp;amp;u=http%3A%2F%2Fwww.savvii.nl%2Fblog%2Fsavvii-systeem-architectuur%2F&amp;amp;amp;act=url&amp;#34;&amp;gt;mostly Ruby&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We do dissect our WordPress sites. To find why the heck something is&amp;#xA;(still) slow. And I oversee some WordPress plugin development: our&amp;#xA;Hosting plugin that makes WordPress talk to our, yes, Ruby backends. But&amp;#xA;I am not a WordPress Developer. And neither did I leave Drupal for&amp;#xA;WordPress as some seem to think.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A webdeveloper at an online bookstore needs to know about the world of&amp;#xA;selling books. And when you&amp;#39;re a developer for an online&amp;#xA;3D-printing-service you will visit many 3D-printing conferences. That is&amp;#xA;what confused some folks: suddenly they see me tweeting about WordCamp&amp;#xA;and assume I&amp;#39;m now building WordPress sites. It&amp;#39;s merely one of the best&amp;#xA;sides of being a software-developer: that you&amp;#39;ve get to know so many&amp;#xA;diverse industries, because you need to capture that industry in&amp;#xA;software. I&amp;#39;ve now been given the chance to capture the industry of&amp;#xA;managing many, varying WordPress-sites in a fine piece of software.&amp;#xA;Which is about all the WordPress development I&amp;#39;ll be doing.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;(And next up, are &amp;lt;a href=&amp;#34;http://docs.opscode.com/chef/chef_overview.html&amp;#34;&amp;gt;Chef&amp;lt;/a&amp;gt;, fine-tuning our Sinatra, Slim and Rails apps, building a nifty logging-platform, A RESTFull (probably composer-based) update-system for plugins, core and themes, a RESTFull Backup-service and a giant load of other infrastructural projects).&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Doe één ding en doe dat goed</title>
   <link href="https://berk.es/2013/10/03/doe-n-ding-en-doe-dat-goed/"/>
   <updated>2013-10-03T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2013/10/03/doe-n-ding-en-doe-dat-goed/</id>
   <content type="html">&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Dit artikel verscheen eerder op de &amp;lt;a href=&amp;#34;http://www.savvii.nl/blog/savvii-systeem-architectuur/&amp;#34;&amp;gt;blog van Savvii&amp;lt;/a&amp;gt;, de Wordpreshoster waar ik momenteel werk.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Als hoster willen we natuurlijk zoveel mogelijk automatiseren, hiervoor moeten we allerhande onderdelen ontwikkelen: van het afhandelen van bestellingen tot het updaten van de wordpress-sites.&amp;#xA;Voor ons is dan van belang dat dit &amp;lt;em&amp;gt;backend&amp;lt;/em&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;#x9;&amp;lt;li&amp;gt;snel aan te passen is aan nieuwe eisen, wensen en inzichten&amp;lt;/li&amp;gt;&amp;#xA;&amp;#x9;&amp;lt;li&amp;gt;goed overweg kan met fouten en storingen&amp;lt;/li&amp;gt;&amp;#xA;&amp;#x9;&amp;lt;li&amp;gt;heel veilig is&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;We hebben er daarom al heel vroeg voor gekozen om alles als heel kleine, gefocuste, losse onderdelen te maken. Waarbij we veel inspiratie halen bij &amp;lt;a href=&amp;#34;http://www.faqs.org/docs/artu/ch01s06.html&amp;#34;&amp;gt;the Unix Philosophy&amp;lt;/a&amp;gt;:&amp;#xA;&amp;lt;blockquote cite=&amp;#34;http://www.faqs.org/docs/artu/ch01s06.html&amp;#34;&amp;gt;This is the Unix philosophy: Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;h3&amp;gt;Rule of Modularity: Write simple parts connected by clean interfaces.&amp;lt;/h3&amp;gt;&amp;#xA;In ons geval willen we daarbij ook volledig onafhankelijk zijn van de locatie waarop iets draait. We kiezen ervoor om alle communicatie tussen alle componenten met &amp;lt;a href=&amp;#34;https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm&amp;#34;&amp;gt;RESTfull&amp;lt;/a&amp;gt; JSON te laten communiceren. Dat betekent dat we alle communicatie over HTTP doen, waarbij we JSON rondsturen.&amp;#xA;&amp;lt;h3&amp;gt;Rule of Extensibility: Design for the future, because it will be here sooner than you think.&amp;lt;/h3&amp;gt;&amp;#xA;We hebben een paar concrete uitgangspunten:&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Alle componenten zullen doorontwikkeld of vervangen worden.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Alle componenten zullen op een moment falen, uitgaan of fouten vertonen.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;De markt en de community rondom WordPress is heel veranderlijk.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Het mooie aan een modulair ontwerp met een standaard-communicatie, is dat we iedere component ten alle tijden kunnen vervangen door een andere. Als bijvoorbeeld de backup-oplossing niet meer meeschaalt, vervangen we het door een andere zonder dat het hele systeem daarvoor plat hoeft te gaan!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Omdat alle componenten ook onafhankelijk zijn van elkaars beschikbaarheid kunnen ze ten alle tijden falen of kapot gaan, zonder dat de rest van het systeem daardoor mee omvalt. Als bijvoorbeeld de facturatieomgeving plat-gaat (vanwege een upgrade, bijvoorbeeld), heeft dat geen enkel effect op het waar bijvoorbeeld de backups gemaakt worden. Bij een zogenaamd &amp;lt;em&amp;gt;monolithische&amp;lt;/em&amp;gt; opzet zit alles in één grote applicatie en valt bij een storing altijd de hele applicatie uit.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En omdat we parallel aan de verschillende onderdelen kunnen werken, kunnen we veel sneller anticiperen op veranderingen of nieuwe inzichten: wanneer bijvoorbeeld een nieuwe WordPress-versie een heel nieuw upgrade-mechanisme heeft, schakelen we eenvoudigweg een nieuwe update-robot in!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Hoe we zorgen dat we continu en supersnel kunnen releasen, is voor een volgende blogpost.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3&amp;gt;Rule of Simplicity: Design for simplicity; add complexity only where you must.&amp;lt;/h3&amp;gt;&amp;#xA;Om alle componenten zo dom en simpel mogelijk te kunnen houden zit in het &amp;#34;midden&amp;#34; &amp;lt;em&amp;gt;Evvii,&amp;lt;/em&amp;gt; onze API. Dit is een applicatie die opdrachten en informatieverzoeken ontvangt (over RESTful HTTP, dus), en deze informatie weer teruggeeft in JSON.&amp;#xA;&amp;lt;p&amp;gt;De buitenwereld kan niet bij Evvii, maar bijvoorbeeld de &amp;lt;em&amp;gt;admin-omgeving&amp;lt;/em&amp;gt; waarin jij onder meer je sites beheert, kan er wel bij. Deze admin-omgeving (we noemen hem Wallii), kan dus communiceren met Evvii, maar hoeft zelf helemaal niets aan business-logic te hebben: Dat doet Evvii.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een bijkomend voordeel is dat de veiligheid hierdoor verhoogd wordt: de diensten die &amp;amp;quot;publiek&amp;amp;quot; op het internet draaien zijn héél simpel en alleen daardoor al makkelijker te beveiligen. Maar deze webdiensten kunnen zelf helemaal nergens bij, behalve dan bij Evvii. Waardoor we als het ware een tussenlaag hebben die ook weer extra te beveiligen is. Uiteraard is beveiliging veel meer dan dit, en zullen we ook hieraan nog uitgebreid aandacht besteden in toekomstige blogposts.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Evvii stuurt alle losse componenten aan en voorziet in alle informatieverzoeken. De losse componenten weten verder niets af van de andere componenten. Dit maakt het makkelijker voor ons om componenten te vervangen, verwijderen of aan te passen. Evvii is daarmee wel het kritieke deel: de bottleneck. Hoe we dat oplossen is voor een volgende blogpost.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Evvii is daarmee wel het meest complexe deel, maar op zich nog altijd vrij eenvoudig: Evvii weet niet &amp;lt;strong&amp;gt;hoe&amp;lt;/strong&amp;gt; een backup gemaakt wordt, enkel &amp;lt;strong&amp;gt;wie&amp;lt;/strong&amp;gt; ze de opdracht kan geven dat te doen! Daarmee kan Evvii nog altijd vrij simpel gehouden worden: ze hoeft enkel te weten wanneer ze wie welke vraag hoeft te stellen, niets meer. Hoe we al deze taken uitvoeren en alles schaalbaar én snel proberen te houden is ook voor een latere blogpost.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3&amp;gt;Rule of Composition: Design programs to be connected with other programs.&amp;lt;/h3&amp;gt;&amp;#xA;Een ander voordeel van het op deze manier opbouwen is dat we allerhande &amp;lt;em&amp;gt;tools&amp;lt;/em&amp;gt; en programmeertalen kunnen inzetten. Ze communiceren immers over een protocol in een formaat dat vrijwel iedere taal en heel veel diensten gewoon begrijpen. De gereedschappen die dat niet doen krijgen een eenvoudige wrapper waardoor ze via een webservice bediend kunnen worden. Jullie WordPress sites, bijvoorbeeld, zijn niet altijd over het web met JSON te besturen: dat zou ook onveilig zijn. Dus besturen we ze op de server met &amp;lt;a href=&amp;#34;http://wp-cli.org/&amp;#34;&amp;gt;wp-cli&amp;lt;/a&amp;gt;. Maar dat is ook een commandline tool en geen webservice: we moeten daar dus een webservice voor maken. Wanneer we die stabiel en veilig hebben, zullen we deze uiteraard releasen als Open Source Software.&amp;#xA;&amp;lt;p&amp;gt;We bouwen dus overal kleine webservices omheen. En we doen dat vooral in &amp;lt;a href=&amp;#34;http://rubyonrails.org/&amp;#34;&amp;gt;Rails&amp;lt;/a&amp;gt;, &amp;lt;a href=&amp;#34;http://www.sinatrarb.com/&amp;#34;&amp;gt;Sinatra&amp;lt;/a&amp;gt; en &amp;lt;a href=&amp;#34;http://slimframework.com/&amp;#34;&amp;gt;Slim&amp;lt;/a&amp;gt;. En de tools die we zelf bouwen zijn al allemaal webservices vanaf het begin.&amp;lt;/p&amp;gt;</content>
 </entry>
 
 <entry>
   <title>So, I am starting as Ruby Developer at Savvii</title>
   <link href="https://berk.es/2013/07/31/starting-as-ruby-developer-at-savvii/"/>
   <updated>2013-07-31T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2013/07/31/starting-as-ruby-developer-at-savvii/</id>
   <content type="html">&amp;lt;p&amp;gt;Monday (5th of &amp;lt;del&amp;gt;Juli&amp;lt;/del&amp;gt;August) I am starting at the startup &amp;lt;a href=&amp;#34;http://savvii.nl&amp;#34;&amp;gt;Savvii&amp;lt;/a&amp;gt;. To&amp;#xA;be part of the team that wants to bring you the very best WordPress&amp;#xA;hosting in the Netherlands.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I am going to develop the software for the back end: program the robots that&amp;#xA;do all the magic behind-the-scenes work.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Because I am a Ruby-developer, I&amp;#39;ll do this in Ruby. We&amp;#xA;needs some technical things like long-running threads and asynchronous&amp;#xA;workers and stuff, which makes Ruby (and Rails) one of the best&amp;#xA;candidates. I&amp;#39;ll be blogging more about the technical stuff on our yet-to-go-online blog. And probably here too.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;WordPress, for those who are not too much into this stuff, is the most used, of &amp;lt;a href=&amp;#34;http://trends.builtwith.com/cms&amp;#34;&amp;gt;the&amp;#xA;three most popular CMSes&amp;lt;/a&amp;gt;. It is the&amp;#xA;software &amp;lt;a href=&amp;#34;http://en.wordpress.com/notable-users/&amp;#34;&amp;gt;behind many famous and popular&amp;#xA;websites&amp;lt;/a&amp;gt; and is commonly known&amp;#xA;as a Blogging-system, rather then a generic CMS.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;wat-you-be-doing-wordpress-development&amp;#34;&amp;gt;WAT!? You be doing WordPress-development?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Probably a little, but my task is&amp;#xA;to get the back end-software running, which is building stuff like CRMs, billing gateways, provisioning APIs and whatnot.&amp;#xA;My &amp;lt;a href=&amp;#34;https://twitter.com/fuegas&amp;#34;&amp;gt;fellow-programmer&amp;lt;/a&amp;gt; is the main PHP-and WordPress-developer. But I will certainly dive into WordPress now&amp;#xA;and again to make it play nicely with all the Ruby back ends.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;but-didnt-you-say-you-hate-wordpress-drupal-and-all-that-other-php-stuff&amp;#34;&amp;gt;But didn&amp;#39;t you say you hate WordPress, Drupal and all that other PHP-stuff?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Nope. Apparently a lot of people seem to think that I am &amp;amp;quot;against&amp;amp;quot;&amp;#xA;Drupal, WordPress or even PHP in general. On contrary: I like them a&amp;#xA;lot. And very often advise people to use this software for their&amp;#xA;websites.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I merely think that such CMSes have their place and cases, but are often abused&amp;#xA;in cases (and projects) where they fit poorly: the right tool for the&amp;#xA;job. Apparently many people read that as &amp;amp;quot;haters gonna hate&amp;amp;quot;. I don&amp;#39;t.&amp;#xA;Hate.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;what-about-your-own-company&amp;#34;&amp;gt;What about your own company?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;ll be putting my all my focus on Savvii, on making it&amp;#xA;a great hoster. But I expect to go back to some small side-projects once we are&amp;#xA;moving. Not sure how it will combine, but time will learn.&amp;#xA;&amp;lt;a href=&amp;#34;/about.html&amp;#34;&amp;gt;Berk.es&amp;lt;/a&amp;gt; will continue to exist, albeit&amp;#xA;dormant for a while.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;savvii-wordpress-hoster&amp;#34;&amp;gt;Savvii? WordPress hoster?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We are just starting, so all the details are still unclear.&amp;#xA;But in short, translated from &amp;lt;a href=&amp;#34;http://savvii.nl&amp;#34;&amp;gt;the placeholder&amp;#xA;website&amp;lt;/a&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Savvii is the new managed WordPress platform for the Netherlands.&amp;#xA;That means speed (caching, CDNs, tweaks), security (scans, free fixes,&amp;#xA;automatic updates) and service!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Savvii is part of the family of companies around &amp;lt;a href=&amp;#34;http://www.blixem.nl&amp;#34;&amp;gt;bliXem&amp;#xA;internet&amp;lt;/a&amp;gt;, which is where I&amp;#39;ll be working. Yes,&amp;#xA;that is Nijmegen, so I can go to work on my bike.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I am really grateful to become part of such a good team and to be able&amp;#xA;to get the opportunity to help such a promising startup going!&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Over de anonimiteit van Bitcoin</title>
   <link href="https://berk.es/2013/05/23/over-de-anonimiteit-van-bitcoin/"/>
   <updated>2013-05-23T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2013/05/23/over-de-anonimiteit-van-bitcoin/</id>
   <content type="html">&amp;lt;p&amp;gt;Het bedrijf dat in Nederland pintransacties verwerkt, &amp;lt;em&amp;gt;Equence&amp;lt;/em&amp;gt;,&amp;#xA;&amp;lt;a href=&amp;#34;http://nos.nl/artikel/510009-banken-verkopen-pingedrag-klanten.html&amp;#34;&amp;gt;kondigde aan om ons pingedrag te verkopen&amp;lt;/a&amp;gt; aan geïnterresseerde&amp;#xA;marketeers en bedrijven. Ze beweren dat deze transacties anoniem zijn.&amp;#xA;Dat is onzin, pintransacties (Of iDeal, creditcard, Paypal of eender&amp;#xA;welke digitale transactie) kán eenvoudigweg niet anoniem zijn; je&amp;#xA;rekeningnummer is immers direct gekoppeld aan je, geïdentificeerde,&amp;#xA;persoon. Ooit een bankrekening proberen te openen op naam van Jelle Snikkelsma, Kerkstraat 14 te Grashuizen?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Bitcoin&amp;lt;/strong&amp;gt;, hoor je vaak, &amp;lt;strong&amp;gt;is wél anoniem&amp;lt;/strong&amp;gt;. Bitcoin-betalingen, zijn,&amp;#xA;inderdaad, niet tot een persoon terug te herleiden als je dat niet wilt. Vrijwel&amp;#xA;vergelijkbaar met betalen met contant geld. Als ik jou een briefje van vijf euro geef en&amp;#xA;daarna mijn hele levensverhaal vertel, is de betaling niet anoniem.&amp;#xA;Maar als ik je vijf euro per post opstuur, dan is (bijna) niet te&amp;#xA;achterhalen dat dit van mij afkomt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Deze anonimiteit bij bitcoin is vrij uitzonderlijk&amp;#xA;voor een digitaal systeem. Wanneer je dan bedenkt dat Bitcoin in feite&amp;#xA;een enorm, publiek kasboek is, waarin iedere transactie wordt&amp;#xA;weggeschreven, klinkt dit al helemaal vreemd.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bitcoin werkt namelijk met pseudoniemen. Cryptografische pseudoniemen;&amp;#xA;je rekening is niet meer dan een public en private keypair, je&amp;#xA;&amp;lt;em&amp;gt;portemonee&amp;lt;/em&amp;gt;. En bitcoin werkt daarom niet met registratie:&amp;#xA;iedereen die een public en private (geheime) keypair kan aanmaken, heeft onmiddellijk een werkende bitcoin-rekening.&amp;#xA;In analogie&amp;#xA;met een bankrekening zou dat zijn: iedereen die een uniek bankrekeningnummer en&amp;#xA;bijbehorende pincode kan verzinnen en die kan onthouden, heeft een&amp;#xA;bankrekening; zonder dat het banksysteem daar ooit vanaf hoeft te weten.&amp;#xA;De cryptografie garandeert dat enkel jijzelf toegang hebt tot die&amp;#xA;bankrekening (dat de pincode precies de goede is om geld mee van de rekening over te maken). En zorgt ervoor dat er nooit (in de praktische zin van &amp;amp;quot;nooit&amp;amp;quot;: niet in&amp;#xA;theoretische) twee mensen dezelfde bankrekening verzinnen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;De portemonnee, en dan met name je public-key, moet echter niet naar jou&amp;#xA;te herleiden zijn. Je moet, in de praktijk, dus niet je publieke sleutel versturen vanaf&amp;#xA;een IP-adres wat naar jou persoon terugleidt. En dat versturen ervan, doe&amp;#xA;je bij iedere transactie.  En daarbij kun je heel eenvoudig een (of meer) nieuwe portemonnee aanmaken en&amp;#xA;al je geld daarheen overmaken. Als jij je pseudoniem geheim wilt houden&amp;#xA;kan dat. En is die rekening verder op geen enkele manier te herleiden&amp;#xA;tot jou.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Daarbij heeft je portemonnee oneindig veel bankrekeningnummers, &amp;lt;em&amp;gt;Bitcoin&amp;#xA;addresses&amp;lt;/em&amp;gt; genoemd, waarmee je geld naar je portemonnee kunt overmaken. En&amp;#xA;van waaraf je geld kunt overmaken. In de praktijk zul je dus voor iedere&amp;#xA;transactie een nieuw, wegwerp-adres aanmaken. Dus zelfs als één zo&amp;#39;n&amp;#xA;adres wel terug naar jou te herleiden is (omdat je bijvoorbeeld een boek&amp;#xA;bestelde en dat thuis laat bezorgen), dan nóg staat een volgende betaling&amp;#xA;daar vrijwel los van. Wanneer dus de éne betaling die je binnenkrijgt voor&amp;#xA;een factuur is waar je met naam en toenaam op genoemd staat, is alléén&amp;#xA;dat adres naar jou terug te leiden en niet te koppelen aan alle andere&amp;#xA;betalingen die je ontvangt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ook hier hangt het dus vooral af van hoeveel privacy je wílt; maar je&amp;#xA;hebt, in tegenstelling tot dat bankpasje in je broekzak, tenminste de&amp;#xA;mógelijkheid om je identiteit verborgen te houden bij bestellingen,&amp;#xA;betalingen en zelfs bij het ontvangen van geld.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dát beste mijnheer Rietveld van Equens, is privacy. Het verkopen van&amp;#xA;cijfers aan Ohra, over hoe vaak een groepje rekeninghouders op Station&amp;#xA;Utrecht een blikje Grolsch koopt (en de volgende ochtend op Station&amp;#xA;Nijmegen bij de DA een pakje Aspirine), is dat niet!&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Bitcoins kopen? Weet wat je doet.</title>
   <link href="https://berk.es/2013/04/01/bitcoins-kopen-weet-wat-je-doet/"/>
   <updated>2013-04-01T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2013/04/01/bitcoins-kopen-weet-wat-je-doet/</id>
   <content type="html">&amp;lt;p&amp;gt;Afgelopen dagen heb ik aardig wat vragen over Bitcoin beantwoord. Daarom&amp;#xA;een algemene samenvatting. In de vorm van een waarschuwing.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bitcoin is overal in het nieuws. &amp;lt;a href=&amp;#34;http://bitcoincharts.com/charts/mtgoxUSD#rg90ztgSzm1g10zm2g25zv&amp;#34;&amp;gt;BBC&amp;lt;/a&amp;gt;, &amp;lt;a href=&amp;#34;http://www.foxnews.com/tech/2013/03/29/digital-currency-bitcoin-surpasses-20-national-currencies-in-value/&amp;#34;&amp;gt;Fox&amp;lt;/a&amp;gt; en vandaag ook het&amp;#xA;&amp;lt;a href=&amp;#34;http://nos.nl/artikel/490896-vijf-vragen-over-bitcoins.html&amp;#34;&amp;gt;NOS achtuurjournaal&amp;lt;/a&amp;gt;. De reden is de &amp;lt;a href=&amp;#34;http://bitcoincharts.com/charts/mtgoxUSD#rg90ztgSzm1g10zm2g25zv&amp;#34;&amp;gt;enorme wisselkoersstijging&amp;lt;/a&amp;gt;. Vanmiddag ging de prijs van 1 Bitcoin door de $100-grens: je betaalt dus $100 voor één Bitcoin. Begin dit jaar was dat nog minder dan $20.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Laat vooropstaan dat ik Bitcoin een geweldige toekomst toedicht. Ik&amp;#xA;geloof erin; doe dat al &amp;lt;a href=&amp;#34;/2011/05/29/bitcoins-de-revolutionaire-valuta-met-een-potentie-voor-de-teloorgang-van-ons-banksysteem/&amp;#34;&amp;gt;bijna twee jaar&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Opeens wil iedereen Bitcoin. Hieronder drie redenen waarom je dat nu&amp;#xA;misschien beter niet kunt doen. Of helemaal beter niet kunt doen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;garanties-veiligheid-en-kennis-vereist&amp;#34;&amp;gt;Garanties, veiligheid en kennis vereist.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bitcoin is decentraal, dus niemand is verantwoordelijk voor jou geld.&amp;#xA;Bitcoin heeft wel waarde en bedrijven en zelfs banken waar je jou&amp;#xA;Bitcoins kunt onderbrengen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar de meesten zullen hun spaarpot gewoon op hun computer zetten. Als&amp;#xA;je al moeite hebt met virusscanners, wachtwoorden onthouden of je&amp;#xA;hardeschijf te versleutelen is dit eenvoudigweg geen optie. Ja die éne&amp;#xA;Bitcoin kun je best op je Android-telefoon bewaren. Maar die paarduizend&amp;#xA;euro niet. De kans is vrij groot dat iemand ze gaat stelen. En dan is er&amp;#xA;geen grote bank die garant staat of zelfs maar moeite doet om jou te&amp;#xA;beschermen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Je kunt je Bitcoin bij een van de vele online banken hiervoor&amp;#xA;onderbrengen, maar de kans dat zo&amp;#39;n bank beroofd wordt is ook reëel en&amp;#xA;dan is het onzeker of je het geld nog terugkrijgt. Meestal niet.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Weet waar je mee bezig bent, of huur hiervoor iemand in. Blijf er anders&amp;#xA;beter van weg.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;gelovers-gebruikers-en-handelaren&amp;#34;&amp;gt;Gelovers, gebruikers en handelaren&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In de Bitcoin-economie worden aardig wat bedrijven opgezet, accepteren&amp;#xA;steeds meer handelaren het als betaalmiddel, en gaat er &amp;lt;a href=&amp;#34;https://blockchain.info/nl/charts/n-transactions&amp;#34;&amp;gt;daadwerkelijk&amp;#xA;heel veel geld rond&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar er zijn ook heel veel daghandelaren. Dat zijn veel mensen die&amp;#xA;helemaal niet (persé) in het systeem geloven, maar enkel afkomen op de&amp;#xA;enorme koersstijgingen. Deze mensen leveren maar een kleine bijdrage aan&amp;#xA;de échte waarde van deze munt. Die waarde wordt namelijk vooral bepaald&amp;#xA;door wat je ermee kunt kopen. Vandaar dat alle ondernemers die Bitcoin&amp;#xA;aannemen, of die diensten rondom Bitcoin ontwikkelen, veel meer waarde&amp;#xA;in de economie stoppen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Hoe minder mensen zulke waarde in het systeem stoppen, hoe instabieler&amp;#xA;de economie. Als je dus alleen van plan bent om snel rijk te worden en&amp;#xA;die rijkdom direct in Euro&amp;#39;s wilt omzetten, mag dat natuurlijk. Maar als&amp;#xA;teveel mensen op deze manier in Bitcoin zitten, zakt alles natuurlijk&amp;#xA;ook gewoon weer in.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Stap er dus ófwel voor heel korte tijd in, maar weet dat je dan veel&amp;#xA;verstand moet hebben van valutahandel en daghandel. Als leek heeft het&amp;#xA;enkel zin om met Bitcoin aan de slag te gaan voor lange termijn. En dan&amp;#xA;kun je beter even afwachten tot wat duidelijker is of deze huidige koers&amp;#xA;een bubbel is (veel te hoog, dus) of dat het weer aan het stabiliseren&amp;#xA;is. Anders beleg je spaarcentjes voor een veel te hoge koers.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Persoonlijk geloof ik dat Bitcoin (of een Bitcoin-achtige) over een&amp;#xA;paar, vier, vijf, jaar nog véél en véél groter is, lange-termijn dus.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;de-prijs-is-veel-te-hoog&amp;#34;&amp;gt;De prijs is (veel te) hoog.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Althans, dat zeggen de meesten die wél verstand van economie hebben in&amp;#xA;de Bitcoin-community. Ik praat die lui hier enkel na. De reële waarde&amp;#xA;ligt eerder onder de $20 per Bitcoin, zeker niet $100.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Momenteel is de prijs vooral zo hoog, door de enorme vraag. Die vraag&amp;#xA;komt weer door de plotselinge zichtbaarheid in alle media.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Er is dus een enorm grote kans op een harde waardedaling in de komende&amp;#xA;weken. Dat zeg ik, nogmaals, als leek in economische wetenschappen.&amp;#xA;Maar die kans is groot, doordat er relatief (te) veel daghandelaren in&amp;#xA;de economie zitten (ten opzichte van mensen die reële waarde in de vorm&amp;#xA;van goederen en diensten) aanbieden. En omdat deze grote groep&amp;#xA;anticipeert op een koersstijging. Noem ze de &amp;amp;quot;opportunisten&amp;amp;quot;.&amp;#xA;Bij de minste of geringste daling zullen deze mensen eruit stappen. Omdat&amp;#xA;ze enerzijds niet in de lange termijn geloven (of er geheel niet in&amp;#xA;geloven) en anderzijds enkel erin gestapt zijn voor een waardestijging.&amp;#xA;Stappen al deze mensen eruit, dan corrigeert de waarde terug naar de&amp;#xA;reële waarde. Wat die waarde precies is, weet niemand. Maar kijk je&amp;#xA;naar &amp;lt;a href=&amp;#34;http://bitcoincharts.com/charts/mtgoxUSD#rg360ztgSzm1g10zm2g25zvzl&amp;#34;&amp;gt;wat langetermijngrafieken&amp;lt;/a&amp;gt;,&amp;#xA;dan is te zien dat de waarde van midden september tot eind januari,&amp;#xA;rond de $14 lag en nauwelijks schommelde.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Kenners en experts waarschuwen voor een te hoge prijs. De kans is,&amp;#xA;volgens hen, op dit moment groot dat de waarde in de komende weken weer&amp;#xA;gaat dalen. Neem dit risico mee in je overweging.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Please, Ruby devs, join() your paths</title>
   <link href="https://berk.es/2013/03/21/please-ruby-devs-join-your-paths/"/>
   <updated>2013-03-21T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2013/03/21/please-ruby-devs-join-your-paths/</id>
   <content type="html">&amp;lt;p&amp;gt;Like in most programming languages, when you write paths in ruby, e.g. to open a file you pass in a string:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;filename &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;bar.txt&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;File&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;open(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;/home/foo/&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&#43;&amp;lt;/span&amp;gt; filename)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;This is a serious smell for several reasons. Not, as people often&amp;#xA;believe, just to cater the few Ruby developers on Windows (Windows knows how to follow&amp;#xA;&amp;amp;quot;/foo/bar/&amp;amp;quot; paths just as well as &amp;amp;quot;\foo\bar&amp;amp;quot; nowadays).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But mostly because this does not scale, gets convoluted real quick. Like&amp;#xA;so:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;config_dir &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;config/&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;File&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;dirname(__FILE__) &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&#43;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;/../&amp;amp;#34;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&#43;&amp;lt;/span&amp;gt; config_dir &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&#43;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;/environment.rb&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#=&amp;amp;gt; ./../config//environment.rb&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Ruby offers a great &amp;lt;a href=&amp;#34;http://apidock.com/ruby/File/join/class?q=file.join&amp;#34;&amp;gt;&amp;lt;code&amp;gt;File.join()&amp;lt;/code&amp;gt;&amp;lt;/a&amp;gt; class method, for this. This simply uses the &amp;lt;code&amp;gt;File::SEPARATOR&amp;lt;/code&amp;gt; to join a string.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;config_dir &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;config/&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;File&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;join(&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;File&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;dirname(__FILE__), &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;..&amp;amp;#34;&amp;lt;/span&amp;gt;, config_dir, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;environment.rb&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#=&amp;amp;gt; ./../config/environment.rb&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;As you may notice, double slashes are eliminated.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Also, you can inherit this behaviour from &amp;lt;a href=&amp;#34;http://www.ruby-doc.org/stdlib-1.9.3/libdoc/pathname/rdoc/Pathname.html&amp;#34;&amp;gt;&amp;lt;code&amp;gt;Pathname&amp;lt;/code&amp;gt;&amp;lt;/a&amp;gt;, like &amp;lt;code&amp;gt;Rails.root&amp;lt;/code&amp;gt; does.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;config_dir &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;config/&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Rails&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;root&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;join(config_dir, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;environment.rb&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#=&amp;amp;gt; /path/to/rails/project/config/environment.rb&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Rolling your own, is very beneficial, and simple too.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;MyConfig&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;dir&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Pathname&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;new(&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;File&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;join(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;/&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;etc&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;myapp&amp;amp;#34;&amp;lt;/span&amp;gt;))&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;mc &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;MyConfig&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;new&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;mc&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;dir&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;join(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;templates&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;example.html&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#=&amp;amp;gt; &amp;amp;#34;/etc/myapp/templates/example.html&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;There really is no reason to fiddle with strings, concatenate slashes&amp;#xA;and whatnot, to build paths. Join is so much easier, more powerfull and&amp;#xA;above all, cleaner and more portable.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Drupal Imagecache security vulnarability with DDOS attack explained</title>
   <link href="https://berk.es/2013/03/04/drupal-imagecache-security-vulnarability-with-ddos-attack-explained/"/>
   <updated>2013-03-04T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2013/03/04/drupal-imagecache-security-vulnarability-with-ddos-attack-explained/</id>
   <content type="html">&amp;lt;p&amp;gt;Nearly a year ago, long before I decided to move out of &amp;lt;a href=&amp;#34;/2012/10/01/farewell-drupal/&amp;#34;&amp;gt;Drupalwork&amp;#xA;entirely&amp;lt;/a&amp;gt;, I reported a security vulnarability in Drupal 7 core&amp;#xA;in imagecache. Since imagecache is used on most Drupal6 instances this&amp;#xA;problem occurs there too. I had the draft for this poste, tucked away on&amp;#xA;an offline disk (security-details should not live &amp;amp;quot;online&amp;amp;quot; or in &amp;amp;quot;the&amp;#xA;cloud&amp;amp;quot;, ever); and, obviously, the day I arrive in Thailand for a&amp;#xA;vacation, Drupal released the CVE.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I made a proof of concept, and a tool to test it. A screencast explaining the issue is found below:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;iframe width=&amp;#34;420&amp;#34; height=&amp;#34;315&amp;#34; src=&amp;#34;http://www.youtube.com/embed/JP7Q4co0shk&amp;#34; frameborder=&amp;#34;0&amp;#34; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The issue itself is really simple, the solution is hard; because&amp;#xA;Imagecache was designed &amp;amp;quot;wrong&amp;amp;quot; in the first place. Let me explain.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You have really basic Drupal7-site on &amp;lt;a href=&amp;#34;http://example.com&amp;#34;&amp;gt;http://example.com&amp;lt;/a&amp;gt;, with content-type story that has an image-field. Using three&amp;#xA;imagecache-styles: &amp;amp;quot;medium&amp;amp;quot;, &amp;amp;quot;large&amp;amp;quot; and &amp;amp;quot;thumbnail&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Imagecache works by creating new images from an original, on demand,&amp;#xA;when a particular url is requested:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&amp;amp;amp;lt;img src=&amp;amp;quot;http://example.com/sites/default/files/styles/medium/public/field/image/news.jpg&amp;amp;quot; /&amp;amp;amp;gt;&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dissecting that url, we see:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://example.com/sites/default/files/&amp;#34;&amp;gt;http://example.com/sites/default/files/&amp;lt;/a&amp;gt; is where uploaded files are&amp;#xA;stored. This can also be something like &amp;lt;a href=&amp;#34;http://acme.com/sites/acme-is-evil.org/files/&amp;#34;&amp;gt;http://acme.com/sites/acme-is-evil.org/files/&amp;lt;/a&amp;gt; in case of multisite.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;/styles/ is the directory where imageges are cahed under.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;/medium/ is the style applied to this image&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;/public/ the &amp;amp;quot;driver&amp;amp;quot;, usually either &amp;amp;quot;private&amp;amp;quot; or &amp;amp;quot;public&amp;amp;quot;.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;/field/image/news.jpg where the image is stored. The original can&amp;#xA;therefore be found at &amp;lt;a href=&amp;#34;http://example.com/sites/default/files/field/image/news.jpg&amp;#34;&amp;gt;http://example.com/sites/default/files/field/image/news.jpg&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In this case, a &amp;lt;em&amp;gt;derivative&amp;lt;/em&amp;gt; called &amp;lt;em&amp;gt;medium&amp;lt;/em&amp;gt; is created. Because creating images&amp;#xA;is heavy, they are stored on disk, so a next time, the webserver can&amp;#xA;serve this image right-away.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Let me repeat that: Because &amp;lt;em&amp;gt;creating images is heavy&amp;lt;/em&amp;gt;, they are &amp;lt;em&amp;gt;stored&amp;#xA;on disk&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The idea is as simple as it is wrong: The first time (when the image is created) a full Drupal is booted up,&amp;#xA;that Drupal-instance applies the various image-manipulations you have configured&amp;#xA;for that style, and then serves and saves the image.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;amp;quot;But why is that Wrong?&amp;amp;quot;, you ask?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Because you never know when the &amp;lt;em&amp;gt;heavy stuff&amp;lt;/em&amp;gt; will be invoked. It is&amp;#xA;unpredictable.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And because the &amp;lt;em&amp;gt;heavy stuff&amp;lt;/em&amp;gt; is initialized by your visitors. People from&amp;#xA;the evil, outside world. They can fire up your image-creating just by&amp;#xA;visiting urls.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;ddos&amp;#34;&amp;gt;DDOS&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is a typical DDOS vector: making a server do heavy stuff by&amp;#xA;throwing something at it from outside. Typically in an orchestrated&amp;#xA;attack that involves many people from many places throwing stuff at it.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;the-actual-issues-mixing-images-and-styles&amp;#34;&amp;gt;The actual issues: mixing images and styles&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Everything above is not a large problem, because 90%, or more, of the&amp;#xA;images used in img-tags on your site, are already created and cached on&amp;#xA;disk. An attacker will need to find the last 10% and request these urls.&amp;#xA;This is limited.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But, there are more, far more, possible images then those you use in the&amp;#xA;img-tags.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We have two images. A frontpage-banner and a user-avatar. They are&amp;#xA;&amp;lt;em&amp;gt;usually&amp;lt;/em&amp;gt; used with two imagecache styles:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;http://example.com/sites/default/files/styles/avatar-thumb/public/users/123.jpg&amp;#xA;http://example.com/sites/default/files/styles/front-banner/public/field/image/fancy_banner.png&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I could just swap the styles and create a front-banner from the user-avatar,&amp;#xA;and an avatar from the banner, like so:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;http://example.com/sites/default/files/styles/front-banner/public/users/user_123.jpg&amp;#xA;http://example.com/sites/default/files/styles/avatar-thumb/public/field/image/fancy_banner.png&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And what is worse, you can pull any image in your &amp;lt;em&amp;gt;files&amp;lt;/em&amp;gt; directory through imagecache. Including that huge 7MB hi-res upload you forgot there. And if you consider the fact the tool imagemagick (often used as engine to convert the images) can actually handle pdfs, html and many other files you probaly have lying around in your &amp;lt;em&amp;gt;files&amp;lt;/em&amp;gt; directory, you know how much your system can be hurt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This all gets worse with the size of the images that can be abused and&amp;#xA;the heavyness of the imagecache-styles you have defined. Adding&amp;#xA;watermarks, smartcropping, overlays, rounded corners and whatnot make&amp;#xA;the generation of a derivative much heavier then merely resizing an image.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;the-other-issue-recursiveness&amp;#34;&amp;gt;The other issue: recursiveness&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When we look above, we can see that imagecache will gladly pick up any&amp;#xA;file, pull it trough the image-profiles you have defined, using the toolkits at hand and then &amp;lt;em&amp;gt;write out a file to disk&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Guess where? Yup, in the &amp;lt;em&amp;gt;files&amp;lt;/em&amp;gt; directory. Adding another file that can&amp;#xA;be pulled trough imagecache. So, you can imagecache-already-imagecached&amp;#xA;files. [insert inception jokes here].&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is where the attackers have the opportunity to fill up your&amp;#xA;servers&amp;#39; harddrive. By simply generating image-styles by mixing up&amp;#xA;images and styles, you can create a huge amount of unexpected images.&amp;#xA;You them pull these trough imagecache again, to duplicate that huge&amp;#xA;amount. And again. And again. Untill urls grow so large that the&amp;#xA;webserver refuses them. Apache&amp;#39;s limit lies around 4000 characters.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A site with only one, 0.1MB image image and two styles can gain several thousand&amp;#xA;directories, nearly fivehundred copies of imagecache derivatives making&amp;#xA;a total of ~50MB of new images. All an attacker needs to do, is send 500&amp;#xA;HEAD requests to your server, doable in a fraction of a second.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A site with thousands of images and five imagecache styles will get&amp;#xA;terabytes of new images in mere minutes. Obviously depending on the&amp;#xA;speed of the server and how many (HEAD) requests the server allows simultaniously.&amp;#xA;Or in days. Doing only a few-hundred requests each day, yet filling up&amp;#xA;your disk slowly but surely, after which your average server will either&amp;#xA;start crashing, or your hoster will send you large bills for extra&amp;#xA;storage and so on.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Also note that one does not need to download the to-be-generated file. Just requesting it,&amp;#xA;with a HEAD is enough.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;the-proof-of-concept-tool&amp;#34;&amp;gt;The proof of concept tool&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Find it &amp;lt;a href=&amp;#34;https://github.com/berkes/canhaz&amp;#34;&amp;gt;on github&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Please note that the tool is made for investigative use only; but be&amp;#xA;aware that others might not heed this notice and either build such a&amp;#xA;tool themselves (it is really simple) or use it to bring down your site.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Because of this, I chose to cripple it a little. The tool cannot detect wether you have applied the&amp;#xA;security patch or not, or if you have different measures in place.&amp;#xA;Because of this, I have removed the crawling and parrallel part too, limiting it to&amp;#xA;images and imagecache-styles found on the page you insert manually.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The tool was made to investigate when and how a system would crash or&amp;#xA;choke using these attacks. Please investigate and learn about the CMS&amp;#xA;and the modules you are you are using.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Prepare your system for Ruby.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;$ sudo apt-get install ruby rubygems #OSX and most Linuxes already have these&amp;#xA;$ sudo gem install bundle&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Clone the tool and install the dependencies.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;$ git clone https://github.com/berkes/canhaz.git&amp;#xA;$ cd canhaz&amp;#xA;$ bundle install&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Run!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;$ ./canhaz # Shows all tasks&amp;#xA;$ canhaz hit http://example.com 20 # generates max 20 imagecache&amp;#xA;                                   # derivatives, by investigating&amp;#xA;                                   # example.com&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;lessons-learned&amp;#34;&amp;gt;Lessons learned&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Don&amp;#39;t do on-demand generation of things that require heavy work. In this&amp;#xA;case, derivatives needed for a user-avatar should be created when a user&amp;#xA;uploads that avatar. Even better is to let a &amp;lt;a href=&amp;#34;http://en.wikipedia.org/wiki/Thread_pool_pattern&amp;#34;&amp;gt;worker queue&amp;lt;/a&amp;gt; deal with the actual generation, that way dedicated machines can deal with the heavy lifting,&amp;#xA;and users don&amp;#39;t have to wait in front of a loading page while you are&amp;#xA;making images. For PHP the standard tool&amp;#xA;&amp;lt;a href=&amp;#34;http://www.php.net/manual/en/book.gearman.php&amp;#34;&amp;gt;Gearman&amp;lt;/a&amp;gt;, has worked&amp;#xA;well for me; just don&amp;#39;t expect it to be like resqueue, sidekiq or pythons RQ (yet).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Magic &amp;amp;quot;handyness&amp;amp;quot; like allowing any image to be &amp;amp;quot;imagecached&amp;amp;quot; is&amp;#xA;usefull in development, but not in production. So, on your development&amp;#xA;environment, you may want imagecached images to be generated on the fly (and probably not&amp;#xA;cached, damn you, drush cc-all), you certainly don&amp;#39;t want this&amp;#xA;flexibility on a production server. You probably want to call some build&amp;#xA;task while deploying to re-generated all your images there. Once. Before&amp;#xA;deploying.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And for Drupal8: get rid of imagecache and implement a much simpler&amp;#xA;on-submit image-builder. It should create the derivatives for when&amp;#xA;a File is created and passes validations. This not only solves any such &amp;amp;quot;unpredictable load&amp;amp;quot;&amp;#xA;issues, it allows for much easier CDNs, static-file-servers, caching and&amp;#xA;more. The on-demand architecture has too much downsides to warrant the&amp;#xA;only upside: flexibility.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Tagadelic: TDD, OOP and seeking maintainer</title>
   <link href="https://berk.es/2013/02/10/tagadelic-tdd-oop-and-seeking-maintainer/"/>
   <updated>2013-02-10T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2013/02/10/tagadelic-tdd-oop-and-seeking-maintainer/</id>
   <content type="html">&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;TL;DR&amp;lt;/strong&amp;gt;: &amp;lt;a href=&amp;#34;http://drupal.org/project/tagadelic&amp;#34;&amp;gt;Tagadelic&amp;lt;/a&amp;gt; is close to a Drupal 7 release, with an easy upgrade&amp;#xA;path to Drupal 8. It is completely rewritten, Object Oriented and Test&amp;#xA;Driven. Since I don&amp;#39;t do any Drupal anymore, I am looking for someone who can maintain&amp;#xA;a clean, OOP and TDD-module, to take it over from me.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;There is just so much you can do when porting an age-old module again&amp;#xA;and again. Tagadelic has been around since mid-2005, has been ported&amp;#xA;over and over again. Mind you: not upgraded but ported. Quick, dirty and&amp;#xA;&amp;amp;quot;works-for-me&amp;amp;quot; ports. Like most other modules, actually. There never was&amp;#xA;a stable release for Drupal7, because the module never was really stable&amp;#xA;in the first place. Yes, it might work (for you), but that is far from&amp;#xA;&amp;lt;em&amp;gt;stable&amp;lt;/em&amp;gt; and releasable.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Between 2005 and now, I learned programming properly. I mean, OOP,&amp;#xA;unit-testing, patterns and all that (This also lead to me, &amp;lt;a href=&amp;#34;/2012/10/01/farewell-drupal/&amp;#34;&amp;gt;abandoning all&amp;#xA;my Drupal work, mostly&amp;lt;/a&amp;gt;).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;None of my publicly released Drupal-modules ever&amp;#xA;resembled that progress I made; mainly because Drupal itself is not OOP. Has poor&amp;#xA;testing abilities (please read on, I will explain later) and applies&amp;#xA;quite a few anti-patterns. This makes writing really clean and pretty code,&amp;#xA;somewhat discouraging. Most of its examples, best practices and defaults&amp;#xA;go straight against what is in general considered best practice.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But since Tagadelic is used by a lot of people, I wanted to create a&amp;#xA;&amp;lt;em&amp;gt;proper&amp;lt;/em&amp;gt; replacement. A module with pretty code, easy to implement APIs&amp;#xA;and some additional, turnkey modules for those who cannot or will not write these&amp;#xA;few lines of PHP. A module that resembles what I now consider good code&amp;#xA;and properly developed. As a replacement for what I thought proper 8&amp;#xA;years ago.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I coded for several months and today released the first alpha.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In the long run, I can conclude three things:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Drupal is not really ready for OOP development. The interfacing between my module and Drupal&amp;#xA;required me to write wrappers (so that Tagadelic classes access&amp;#xA;Drupal-functions in an OOP-manner) and to write the modules themselves&amp;#xA;with global functions, since that is how Drupal expects the hooks and&amp;#xA;implementations.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;DrupalWebTest is way, way too slow and feature-poor for Test Driven&amp;#xA;Development (TDD). Tagadelic only has about 150 DrupalWebTests, but running&amp;#xA;them all takes over 5 minutes (on my machine: quad-core Intel 2.67GHz, SSD drives&amp;#xA;only). Note that in a typical Rails (being -rightfully- known for being very&amp;#xA;slow) with cucumber suite of over 600 tests takes under 30 seconds;&amp;#xA;that includes selenium opening Firefox and clicking around in a few&amp;#xA;tests. 30 seconds is considered unacceptably slow there.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When developing test-driven (or Behaviour Driven) you typically&amp;#xA;run the isolated tests five, six times. And the entire suite of tests at&amp;#xA;least once. So aside from the actual coding, the testing alone takes 30&amp;#xA;minutes. This is both discouraging (meh, I&amp;#39;ll just assume everything is&amp;#xA;still green, will test in next iteration) and very hard for your &amp;amp;quot;flow&amp;amp;quot;&amp;#xA;and concentration.&amp;#xA;It is feature-poor in a sense that I ended up writing most assertions&amp;#xA;and several set-up functions myself. assertXpath()? Nope. assertHasId()?&amp;#xA;Nope. assertIdenticalArrays()? Nope. And worse is that it breaks a very&amp;#xA;important rule for testing: isolation. If you want to test whether some&amp;#xA;admin-setting can be saved and creates the proper variable, you are also&amp;#xA;testing whether a Drupal is installed properly, user can log in, is admin,&amp;#xA;can access a page, has nodes, has access to creating these nodes and so&amp;#xA;on. I ended up poking into the database (not even &amp;amp;quot;my&amp;amp;quot; tables) because&amp;#xA;somewhere in the clutter of setup-tasks stuff was created but it failed.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It is really fun to write unit-tests with &amp;lt;a href=&amp;#34;https://github.com/sebastianbergmann/phpunit/&amp;#34;&amp;gt;phpUnit&amp;lt;/a&amp;gt;. I was very much&amp;#xA;positively impressed by that test-environment and by using it. The&amp;#xA;biggest adventure was how to stub out Drupal. Drupal, using global&amp;#xA;functions for stuff like &amp;lt;code&amp;gt;check_plain()&amp;lt;/code&amp;gt; is nearly impossible to mock&amp;#xA;and stub. I solved this by extending my &amp;lt;a href=&amp;#34;https://github.com/berkes/tagadelic/blob/7.x-2.x/tests/TagadelicCloudTest.php#L106&amp;#34;&amp;gt;DrupalWrapper&amp;lt;/a&amp;gt; and&amp;#xA;stubbing that. After all: I don&amp;#39;t care whether &amp;lt;code&amp;gt;check_plain()&amp;lt;/code&amp;gt; itself&amp;#xA;works and clears out XSS, I only care whether or not my classes call&amp;#xA;that function in proper places to ensure clean output. Testing whether&amp;#xA;&amp;lt;code&amp;gt;check_plain()&amp;lt;/code&amp;gt; works is not my concern, here. I chose phpUnit over&amp;#xA;DrupalUnit, because the latter is pretty much &amp;lt;a href=&amp;#34;http://stackoverflow.com/questions/6045241/unit-testing-drupal-with-drupalunittestcase-fails-on-database-setup/6046100#6046100&amp;#34;&amp;gt;unusable for&amp;#xA;unit-testing of arbitrary classes&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And now it is time for someone, or several someones to slowly take over the module. Together&amp;#xA;we will release a Drupal7 2.0 version and then I can carry over all&amp;#xA;project rights on my last Drupalproject.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Interested? You should be:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Familiar with PHP OOP development. You should probably feel that the&amp;#xA;usual way of Developing Drupal modules in a none-OOP manner is not&amp;#xA;a very good way.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Familiar with PHPunit and Drupal Tests. You should feel strongly for&amp;#xA;TDD and good test-coverage. You should probably feel that even though&amp;#xA;writing Drupal Web Tests is not (yet) perfect and requires time and&amp;#xA;effort, it always should happen.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Able to maintain such a module for a substantial time. It being TDD&amp;#xA;and all, means that it won&amp;#39;t take you a lot of effort or time. But it&amp;#xA;would be a shame if three months after a release you abandon it&amp;#xA;altogether because you like Node.js better. Or so.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Wanting to develop on Github. At least until the 2.0 release.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Testing colored output with Cucumber</title>
   <link href="https://berk.es/2013/02/04/testing-colored-output-with-cucumber/"/>
   <updated>2013-02-04T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2013/02/04/testing-colored-output-with-cucumber/</id>
   <content type="html">&amp;lt;p&amp;gt;I am improving a Command line app to &amp;lt;a href=&amp;#34;https://github.com/berkes/todotxt&amp;#34;&amp;gt;manage my todos&amp;lt;/a&amp;gt;. I am developing it entirely &amp;#39;Behaviour Driven&amp;#39;, using &amp;lt;a href=&amp;#34;https://github.com/cucumber/aruba&amp;#34;&amp;gt;Cucumber and Aruba&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;All is pretty straightforward, but I had a hard time testing the colors in&amp;#xA;the output. Colors are made with&amp;#xA;&amp;lt;a href=&amp;#34;http://rubydoc.info/gems/rainbow/1.1.4/frames&amp;#34;&amp;gt;Rainbow&amp;lt;/a&amp;gt;; which is&amp;#xA;really neat, but sometimes a little too smart. Rainbow detects when it&amp;#xA;outputs to something that cannot handle colors and turns them off. The&amp;#xA;solution turned out to be really simple though.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Lets start with a simple script that outputs some Rastafari&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#!/usr/bin/env ruby&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;require &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;thor&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;require &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;rainbow&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;Example&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&amp;amp;lt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Thor&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  desc &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;example&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;an example task&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;example&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    puts &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;Yah!&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;color(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:red&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    puts &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;...&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;color(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:black&amp;lt;/span&amp;gt;)&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;bright&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    puts &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;Rasta-&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;color(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:yellow&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    puts &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;fari&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;color(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:green&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Example&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;start()&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Running this, results in:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/rasta_cli.png&amp;#34; alt=&amp;#34;Example output&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But piping this into a file, or for example less, shows no colors;&amp;#xA;This is a useful feature built into Rainbow.&amp;#xA;When testing with cucumber, the colors are gone too:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-cucumber&amp;#34; data-lang=&amp;#34;cucumber&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Feature:&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt; Example&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;  &amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Scenario:&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt; Yah!&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;    When &amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;I run `example example`&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;    &amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Then &amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;it should pass with:&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;      &amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;&amp;amp;#34;&amp;amp;#34;&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;      Yah!&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;      ...&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;      &amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;&amp;amp;#34;&amp;amp;#34;&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;This passes, but does not test any colors. First thing is to tell&amp;#xA;Aruba/Cucumber to not strip the colors, ansi-codes, with an &amp;lt;code&amp;gt;@ansi&amp;lt;/code&amp;gt; tag.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Next thing is to tell Rainbow to output colors regardless of where it&amp;#xA;outputs to. We need to do do this in the application itself, by making&amp;#xA;the application a little more testable. However, Aruba strips the colors&amp;#xA;for a reason: it is really hard to test when all your output is littered&amp;#xA;with ANSI escape codes. You really only want to force Rainbow to output&amp;#xA;them when you are testing for colors.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#...&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;Example&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&amp;amp;lt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Thor&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;initialize&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;*&amp;lt;/span&amp;gt;args)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;super&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Sickill&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;::&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Rainbow&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;enabled &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;true&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;if&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;ENV&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;FORCE_COLORS&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;==&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;TRUE&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#....&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;This allows you to force coloring when testing or running by setting the&amp;#xA;variable, like so &amp;lt;code&amp;gt;export FORCE_COLORS=TRUE; ./bin/example example&amp;lt;/code&amp;gt;. A step could&amp;#xA;them look like &amp;amp;quot;When I run &amp;lt;code&amp;gt;export FORCE_COLORS=TRUE; ./bin/example example&amp;lt;/code&amp;gt;&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;More usefull however, is that we can set this variable in cucumber for&amp;#xA;all the &amp;lt;code&amp;gt;@ansi&amp;lt;/code&amp;gt;-tagged scenario&amp;#39;s. In a support-file&amp;#xA;&amp;lt;code&amp;gt;features/support/ansi.rb&amp;lt;/code&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Before&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;@ansi&amp;amp;#39;&amp;lt;/span&amp;gt;) &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;ENV&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;FORCE_COLORS&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;TRUE&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;With the scenario tagged @ansi, it fails: &amp;lt;code&amp;gt;expected &amp;amp;quot;\e[31mYah!\e[0m\n\e[30m\e[1m...\e[0m\n\e[33mRasta-\e[0m\n\e[32mfari\e[0m\n&amp;amp;quot; to include &amp;amp;quot;Yah! ...&amp;lt;/code&amp;gt;. Good.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Testing against strings like &amp;amp;quot;\e[31m&amp;amp;quot;, however, is both error-prone and&amp;#xA;unreadable. A simple new step definition, in which we add the&amp;#xA;ansi-escape codes, using Rainbow, to the to-be-tested string. Which allows us to test colors really&amp;#xA;easy.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;lt;code&amp;gt;features/support/ansi.rb&amp;lt;/code&amp;gt; should include &amp;amp;quot;rainbow&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Then&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;/^it should output &amp;amp;#34;([^&amp;amp;#34;]*)&amp;amp;#34; in &amp;amp;#34;([^&amp;amp;#34;]*)&amp;amp;#34;$/&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;|&amp;lt;/span&amp;gt;string, color&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;|&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  assert_partial_output(string&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;color(color&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;to_sym), all_output)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-cucumber&amp;#34; data-lang=&amp;#34;cucumber&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Feature:&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt; Example&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;  @ansi&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;  &amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Scenario:&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt; Yah!&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;    When &amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;I run `example example`&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;    &amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Then &amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;it should output &amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;Yah!&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;&amp;amp;#34; in &amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;red&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;&amp;amp;#34;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Readable, easy testing of your colored output!&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Make cucumber open the browser with the current page</title>
   <link href="https://berk.es/2013/01/08/make-cucumber-open-the-browser-with-the-current-page/"/>
   <updated>2013-01-08T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2013/01/08/make-cucumber-open-the-browser-with-the-current-page/</id>
   <content type="html">&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;http://pragprog.com/book/hwcuc/the-cucumber-book&amp;#34;&amp;gt;The Cucumber Book&amp;lt;/a&amp;gt;&amp;#xA;describes a really nifty trick when testing web-pages: open the browser&amp;#xA;when a step fails. This is a feature provided by cucumber itself.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Add a support file &amp;lt;code&amp;gt;features/support/debugging.rb&amp;lt;/code&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;After&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;|&amp;lt;/span&amp;gt;scenario&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;|&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  save_and_open_page &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;if&amp;lt;/span&amp;gt; scenario&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;failed?&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;And add &amp;lt;a href=&amp;#34;https://github.com/copiousfreetime/launchy&amp;#34;&amp;gt;launchy&amp;lt;/a&amp;gt; to your gemfile, and &amp;lt;code&amp;gt;bundle install&amp;lt;/code&amp;gt;. (or install it with whatever else&amp;#xA;you use).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;group &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:test&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;   &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#...&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;   gem &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;launchy&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;~&amp;amp;gt; 2.1.2&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;This will save the page that cucumber is looking at, then open it in&amp;#xA;your browser. Works fine, untill you have a large suite of features and&amp;#xA;some refactoring breaks many features. Having to close twenty tabs in&amp;#xA;your browser after each run is counterproductive and often really&amp;#xA;frustrating.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I solved this with a flag that allows me to fire this debugging-trick&amp;#xA;only when I need it.&amp;#xA;When I have a failing scenario, and I want to investigate it by&amp;#xA;inspecting the page, I run my cucumber with an additional&amp;#xA;environment-variable:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-bash&amp;#34; data-lang=&amp;#34;bash&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;$ cucumber debug&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt;open&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;After&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;|&amp;lt;/span&amp;gt;scenario&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;|&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  save_and_open_page &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;if&amp;lt;/span&amp;gt; scenario&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;failed? &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;and&amp;lt;/span&amp;gt; (&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;ENV&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;debug&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;==&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;open&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;The &amp;lt;code&amp;gt;debug=&amp;lt;/code&amp;gt; syntax allows for more simple tricks too. Like &amp;lt;code&amp;gt;debug=pp&amp;lt;/code&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;require &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;pp&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;After&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;|&amp;lt;/span&amp;gt;scenario&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;|&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  save_and_open_page &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;if&amp;lt;/span&amp;gt; scenario&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;failed? &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;and&amp;lt;/span&amp;gt; (&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;ENV&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;debug&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;==&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;open&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  pp(page) &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;if&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;ENV&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;debug&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;==&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;pp&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Simple trick, works like a charm.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Developing a tiny ecommerce site using microframework Slim</title>
   <link href="https://berk.es/2012/11/19/developing-a-tiny-ecommerce-site-using-microframework-slim/"/>
   <updated>2012-11-19T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2012/11/19/developing-a-tiny-ecommerce-site-using-microframework-slim/</id>
   <content type="html">&amp;lt;p&amp;gt;Last elections, I stepped forward as a volunteer for the &amp;lt;a href=&amp;#34;http://www.piratenpartij.nl/&amp;#34;&amp;gt;Dutch&amp;#xA;Pirateparty&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;They were struggling with Drupal, and the most pressing issue was&amp;#xA;getting a webshop going, using the Dutch Payment system iDeal, to take care of &amp;lt;a href=&amp;#34;https://lidworden.piratenpartij.nl/&amp;#34;&amp;gt;new member subscriptions&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I had quite some experience with Drupal e-commerce, and the default Dutch payment-method &amp;lt;a href=&amp;#34;http://groups.drupal.org/node/16772&amp;#34;&amp;gt;iDeal&amp;lt;/a&amp;gt;, in Drupal. Yet decided to build something from scratch instead, here is a short&amp;#xA;introduction, some reasoning and then how I build the shop.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;reasoning&amp;#34;&amp;gt;Reasoning&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;managing-the-drupalproject-was-hard-as-it-was&amp;#34;&amp;gt;Managing the Drupalproject was hard as it was.&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Three people were already working, around the clock, at that time, to get the&amp;#xA;Drupalsite running and bug-free: theming, organic-groups-integration and&amp;#xA;LDAP-integration (centralising the login), amongst the daily &amp;amp;quot;we need&amp;#xA;this and that page, if possible, ready before yesterday).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Adding a fourth track, integrating e-commerce, was just too much juggling and managing. And&amp;#xA;granting access to their codebase and server for just about everyone who wanted to&amp;#xA;help was not an option; so all commits would have to go via already&amp;#xA;overloaded other volunteers.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Making, and deploying this as standalone application, was a good option.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;drupals-complete-lack-of-decent-e-commerce-options&amp;#34;&amp;gt;Drupals&amp;#39; (complete) lack of decent E-commerce options.&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Yes, there is Commerce now. But in Juli, when all this was happening, it&amp;#xA;was simply not ready, for example, there was no stable iDeal payment and&amp;#xA;building one (against a still not stable payment-layer) was just too&amp;#xA;much work. But even now, I have great doubts about Commerces&amp;#39; set-up,&amp;#xA;architectures and concepts. Too much layers, too much in-browser&amp;#xA;configuration and overly complicated juggling of several tens of&amp;#xA;modules. I guess that is partly my aversion against this overly&amp;#xA;complicated drupal-configuring-by-clicking-together stuff, but it might&amp;#xA;also be because Drupal, as a CMS, will simply never have the focus and&amp;#xA;targeted development that any of the bazillion e-commerce alternatives&amp;#xA;have.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And Übercart has actually never been a serious option to me.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Integrating and developing the iDeal part for payment-module, was going to be much more work then building from scratch anyway.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;easy-for-new-volunteers&amp;#34;&amp;gt;Easy for new volunteers.&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Drupal is becoming harder and harder to jump into and start developing, with each release;&amp;#xA;this site was Drupal 7 and I found that a lot of volunteers had trouble&amp;#xA;solving trivial issues in this Drupalsite. I am not talking about&amp;#xA;installing the eighty-fifth module, or configuring another view and&amp;#xA;entity. I am talking theming, bugfixing and module-development.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;One of the things I did was helping with many such trivial issues like&amp;#xA;organising the menu&amp;#39;s, side blocks, organic-groups setup and so on. There&amp;#xA;already was a shortage of people who knew enough of Drupal to help.&amp;#xA;There was no shortage of people who knew some CSS and HTML, though, yet&amp;#xA;getting them up-to-speed to employ these skills in Drupal theming and&amp;#xA;configuration proved too much work. I learned (again) how hard and&amp;#xA;difficult Drupal is, even for experienced (web)developers.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Also, the concept of this &amp;amp;quot;become a member&amp;amp;quot; thing was very much&amp;#xA;unstable: no one knew exactly what was needed, required and such. If you&amp;#xA;asked four people about what was required, you&amp;#39;d get four different (and&amp;#xA;even contradicting) answers. So I decided we needed the most agile&amp;#xA;system possible. Something that could have a new release several times a&amp;#xA;day, something that anyone with basic webdevelopment-skills could help&amp;#xA;fix or improve.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;slim&amp;#34;&amp;gt;Slim&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Hence &amp;lt;strong&amp;gt;PHP&amp;lt;/strong&amp;gt;, hence &amp;lt;a href=&amp;#34;http://www.slimframework.com/&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Slim&amp;lt;/strong&amp;gt;&amp;lt;/a&amp;gt;, and managed and deployed with &amp;lt;a href=&amp;#34;/2012/08/03/git-deploy-or-how-i-learned-to-stop-worrying-and-love-deployment/&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;git&amp;#xA;deploy&amp;lt;/strong&amp;gt;&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;requirements-documentation-and-functional-designs-wireframes&amp;#34;&amp;gt;Requirements, documentation and functional designs (wireframes)&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Before I started coding I wrote down some guidelines made a few&amp;#xA;&amp;lt;a href=&amp;#34;https://github.com/piratenpartij/lidworden/blob/doc/lidworden.markdown&amp;#34;&amp;gt;mockups&amp;lt;/a&amp;gt; and created the most basic requirement-doc possible.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;the-set-up&amp;#34;&amp;gt;The set-up&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;lt;a href=&amp;#34;https://github.com/piratenpartij/lidworden&amp;#34;&amp;gt;code&amp;lt;/a&amp;gt; consists of a few&amp;#xA;directories and a very few files.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-php&amp;#34; data-lang=&amp;#34;php&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;├&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;Slim&amp;lt;/span&amp;gt;        &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;# Contains the Slim Framework Library (classes)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;├&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;templates&amp;lt;/span&amp;gt;   &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;# Contains snippets and PHP-files to render the actual HTML&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;├&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;config&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;inc&amp;lt;/span&amp;gt;  &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;# A few settings and globals&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;├&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;ideal&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;inc&amp;lt;/span&amp;gt;   &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;# Class/lib to handle the payment processor&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;├&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;index&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;php&amp;lt;/span&amp;gt;   &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;# The actual application&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;└&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;secrets&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;inc&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;# file which is excluded from the public git and from the docroot.&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;h3 id=&amp;#34;template&amp;#34;&amp;gt;Template&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The template was generated from the existing Drupalsite, by saving the page with Firefox (wget will not save CSS and such), parsing that trough &amp;lt;code&amp;gt;tidy&amp;lt;/code&amp;gt;, and then manually clean up the rest of the HTML.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This turned out to be, by far, the most work and resulted in two extra&amp;#xA;directories &amp;amp;quot;CSS&amp;amp;quot; and &amp;amp;quot;JS&amp;amp;quot;. The biggest problem was Drupal&amp;#39;s extremely&amp;#xA;convoluted HTML, with nested divs twenty(levels) deep at some points.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In the end we learned that making the HTML, CSS and JavaScript from&amp;#xA;scratch would not only have saved us many hours, it would have left us&amp;#xA;with a far easier to maintain application.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The initial idea to keep as close to Drupal&amp;#39;s output as possible. Since that would&amp;#xA;allow us to transfer changes in design to this subsite too, was not&amp;#xA;practical.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;the-application&amp;#34;&amp;gt;The application&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The entire application &amp;lt;a href=&amp;#34;https://github.com/piratenpartij/lidworden/blob/develop/index.php&amp;#34;&amp;gt;lives in index.php&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In a Slim application, you set up routes that react to a HTTP-request,&amp;#xA;do stuff, and then return other stuff. Like so&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-php&amp;#34; data-lang=&amp;#34;php&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;$app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;get&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;/&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;function&amp;lt;/span&amp;gt; () &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;use&amp;lt;/span&amp;gt; ($app) {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;render&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;_head.inc&amp;amp;#39;&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;render&amp;lt;/span&amp;gt;(&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;landing.php&amp;amp;#39;&amp;lt;/span&amp;gt;,&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;array&amp;lt;/span&amp;gt;(&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;default_amount&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;DEFAULT_AMOUNT&amp;lt;/span&amp;gt;,&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;actions&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;get_actions&amp;lt;/span&amp;gt;(),&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    )&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  );&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;render&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;_footer.inc&amp;amp;#39;&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  });&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;This is a PHP5 syntax, where you can create&amp;#xA;The above does the following:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ol&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;register a path, &amp;amp;quot;/&amp;amp;quot; with the HTTP-verb GET.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Add an anonymous function to that; which will be executed when &amp;amp;quot;/&amp;amp;quot; is&amp;#xA;requested. PHP5 supports &amp;lt;a href=&amp;#34;http://www.php.net/manual/en/functions.anonymous.php&amp;#34;&amp;gt;anonymous functions, closures&amp;lt;/a&amp;gt;.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;When the function is executed, we call &amp;lt;code&amp;gt;render&amp;lt;/code&amp;gt; to tell what files under &amp;amp;quot;/templates/&amp;amp;quot; to render. We do this for &amp;lt;code&amp;gt;head.inc&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;footer.inc&amp;lt;/code&amp;gt;.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;The template &amp;lt;code&amp;gt;landing.php&amp;lt;/code&amp;gt; is rendered, but gets two variables passed along, which can then be &amp;lt;a href=&amp;#34;https://github.com/piratenpartij/lidworden/blob/develop/templates/landing.php&amp;#34;&amp;gt;printed in landing.php&amp;lt;/a&amp;gt;. Note the &amp;amp;quot;Bug&amp;amp;quot; where we don&amp;#39;t actually print the actions? This part has been rewritten so often that things got messed up a little.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ol&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is all code needed to create a &amp;lt;a href=&amp;#34;https://lidworden.piratenpartij.nl&amp;#34;&amp;gt;page, with a form&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When you post this form, a slightly more complex function is called, the&amp;#xA;simplified version of that is:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-php&amp;#34; data-lang=&amp;#34;php&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;$app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;post&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;/pirate&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;function&amp;lt;/span&amp;gt; () &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;use&amp;lt;/span&amp;gt; ($app) {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $pirate &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; $app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;request&amp;lt;/span&amp;gt;()&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;params&amp;lt;/span&amp;gt;();&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;//... more preparing of the $pirate that will be stored in the database&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;if&amp;lt;/span&amp;gt; (&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;!&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;valid&amp;lt;/span&amp;gt;($app,&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;email&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;FALSE&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;/.&#43;@.&#43;\..&#43;/&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;E-mailadres is niet correct&amp;amp;#39;&amp;lt;/span&amp;gt;)) {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;//...Lots of other validations of all the fields.&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    $app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;redirect&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;/&amp;amp;#34;&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  }&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $pirate &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;write_pirate&amp;lt;/span&amp;gt;($pirate);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;write_mail&amp;lt;/span&amp;gt;($pirate);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $ideal &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;new&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;Ideal&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;MERCHANT_ID&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;SUB_ID&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;HASH_KEY&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;AQUIRER_URL&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;//... Preparing a hidden form for payment&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;render&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;_head.inc&amp;amp;#39;&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;render&amp;lt;/span&amp;gt;(&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;pirate.php&amp;amp;#39;&amp;lt;/span&amp;gt;,&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;array&amp;lt;/span&amp;gt;(&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;hidden_form&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; $ideal&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;hidden_form&amp;lt;/span&amp;gt;(),&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;url&amp;amp;#39;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; $ideal&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;aquirer_url&amp;lt;/span&amp;gt;,&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    )&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  );&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;render&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;_footer.inc&amp;amp;#39;&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  });&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;The values you posted are validated, then written to the database, and&amp;#xA;placed in a mail.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A new page is then rendered, with a hidden-form which will be posted to&amp;#xA;the payment-system.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;ideal&amp;#34;&amp;gt;iDeal&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We have the simplest form of iDeal payment, iDeal lite. This is an&amp;#xA;offsite-payment, where you simply create a form with hidden values and&amp;#xA;POST that to the offsite payment-system. They then parse the POSTed&amp;#xA;values and present the customer with a payment-workflow.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;On success, the customer might return to &amp;lt;code&amp;gt;/success&amp;lt;/code&amp;gt; on error, there is a&amp;#xA;chance they end on &amp;lt;code&amp;gt;/error&amp;lt;/code&amp;gt;. Because this is by no means a confirmation,&amp;#xA;we simply render a success or error page there; but take no actions.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-php&amp;#34; data-lang=&amp;#34;php&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;$app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;get&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;/error&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;function&amp;lt;/span&amp;gt; () &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;use&amp;lt;/span&amp;gt; ($app) {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt; $app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;render&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;_head.inc&amp;amp;#39;&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt; $app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;render&amp;lt;/span&amp;gt;(&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;error.php&amp;amp;#39;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  );&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;render&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;_footer.inc&amp;amp;#39;&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;});&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Other ideal-versions have a Post-back system, where their server&amp;#xA;confirms the payment; one of the earlier version of this application had&amp;#xA;that, since we did not know exactly what iDeal-version we&amp;#39;d go with. A&amp;#xA;confirm would be really simple to implement here:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-php&amp;#34; data-lang=&amp;#34;php&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;$app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;post&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;/confirm&amp;amp;#39;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;function&amp;lt;/span&amp;gt; () &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;use&amp;lt;/span&amp;gt; ($app) {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $payment &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; $app&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;request&amp;lt;/span&amp;gt;()&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;params&amp;lt;/span&amp;gt;();&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $db &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;get_connection&amp;lt;/span&amp;gt;();&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $stmt &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; $db&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;prepare&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;UPDATE pirates SET status = :status WHERE id =&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;  :id&amp;amp;#34;&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $stmt&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;bindParam&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;status&amp;amp;#34;&amp;lt;/span&amp;gt;, $payment[&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;paymentStatus&amp;amp;#34;&amp;lt;/span&amp;gt;]);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $stmt&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;bindParam&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;pirate_id&amp;amp;#34;&amp;lt;/span&amp;gt;, $payment[&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;purchaseID&amp;amp;#34;&amp;lt;/span&amp;gt;]);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $stmt&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;execute&amp;lt;/span&amp;gt;();&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;});&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Obviously, in reality you&amp;#39;d probably need to parse the $payment to&amp;#xA;extract the correct information (the status and the user whose payment&amp;#xA;it was) from there. But again: you can see how extremely simple it is to implement.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;deploying&amp;#34;&amp;gt;Deploying&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Since everything is stored in code, we can deploy really simple, using a&amp;#xA;git-push. Everyone with write access to the codebase can push changes&amp;#xA;there.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This allows for really fast rolling releases. Unfortunately, this breaks&amp;#xA;when someone hacks the code online. And with so many people, under such&amp;#xA;large pressure, that will happen. So all the time saved by having&amp;#xA;deployments under our fingertips with git, was undone by merging in&amp;#xA;changes that were not pushed correctly.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The solution to this is to make it easier and simpler to deploy The&amp;#xA;Right Way, then to hack something on a live server. (And not, as some&amp;#xA;might say, to make it harder to do the wrong thing; like removing Vim on&amp;#xA;the server or disallowing access to the files on the server).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I assumed git-deploy would be this easy, but apparently people under&amp;#xA;stress grabbed The Vim over SSH and hacked away on production code&amp;#xA;anyway. Apparently the git-route was not simple enough.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;conclusion&amp;#34;&amp;gt;Conclusion&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Several nights of coding, about 20 hours, of which 12 hours were CSS-&amp;#xA;and HTML-cleaning and fiddling, we made a really simple login-system.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The system was simple enough for others to start hacking on, in mere&amp;#xA;minutes.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And its result was simple, stable and friendly enough to handle the&amp;#xA;subscriptions of over 1100 in less then a month time.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Another example of what I call the &amp;amp;quot;seeping trough of the complicated underlying&amp;#xA;technology&amp;amp;quot;. In order to keep a project and its result clean, simple and&amp;#xA;friendly, make sure the technology you use is simple, friendly and&amp;#xA;clean; &amp;lt;a href=&amp;#34;http://2.bp.blogspot.com/-bRoNlTFFl4k/T8BDgLJQCJI/AAAAAAAAAwI/Mc9VGyhwYXA/s1600/einstein-if-you-cant-explain-it-simply-you-dont-understand-it-well-enough.jpg&amp;#34;&amp;gt;KISS&amp;lt;/a&amp;gt;. Despite all the unstructured and rushed development, this application still works, is reasonable clean and can be revived and&amp;#xA;improved in mere minutes.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Something I doubt would have been possible with a large stack of&amp;#xA;e-commerce modules in Drupal; or hacked into a Magento or other shop; let&amp;#xA;alone built with Zend, Rails or Whatever other framework.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Statische websites beheren anno 2012</title>
   <link href="https://berk.es/2012/11/12/statische-websites-beheren-anno-2012/"/>
   <updated>2012-11-12T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2012/11/12/statische-websites-beheren-anno-2012/</id>
   <content type="html">&amp;lt;p&amp;gt;Statische sites, bijvoorbeeld weblogs gemaakt met simpele HTML-bestandjes, maken een terugkomst. Gereedschappen als &amp;lt;a href=&amp;#34;http://jekyllrb.com/&amp;#34;&amp;gt;Jekyll&amp;lt;/a&amp;gt; samen met moderne revisiebeheersystemen als &amp;lt;code&amp;gt;git&amp;lt;/code&amp;gt;, maken het opzetten van een simpele, statische site, vaak vele malen makkelijker dan het inregelen van een CMS. In dit artikel leg ik kort uit hoe een CMS zich dat verhoudt tot een statische site. Daarna bekijk ik een rijtje voor- en nadelen, mogelijkheden en onmogelijkheden. En als laatste beschrijf ik kort, hoe je in drie simpele (voor programmeurs, hackers en meer ervaren computergebruikers, althans) stappen, een site met Jekyll kunt opzetten.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een CMS genereert de pagina&amp;#39;s wanneer ze opgevraagd worden. Voor élke pagina, voor iedere persoon, wordt telkens, eenmalig, een pagina opgebouwd. Dit blogartikeltje bijvoorbeeld, verandert niet meer tussen het moment dat jij en die ene andere lezer het lezen; dus waarom voor jou en die andere lezer een op maat gemaakte pagina opbouwen?  Waarom is het nodig dat ik voor jou en de personen die voor en na jou komen telkens opnieuw in een database ga graven om daaruit de laatste updates te zoeken en daarmee een pagina opbouw?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een Static Site Generator (SSG), genereert eenmalig, bijvoorbeeld bij een update, alle bestanden en pagina&amp;#39;s. Het idee is niet nieuw, vroeger waren gereedschappen die dit voor je deden, zoals Dreamweaver, de norm. Tegenwoordig zijn complexe, CMS-gebaseerde, of dynamische sites de norm. Maar eigenlijk meestal overbodig. Voorbeelden van moderne Static Site Genergators zijn:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://jekyllrb.com&amp;#34;&amp;gt;Jekyll&amp;lt;/a&amp;gt;, in Ruby, gebruikt door onder meer&amp;#xA;Github.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://ringce.com/hyde&amp;#34;&amp;gt;Hyde&amp;lt;/a&amp;gt;, in python, &amp;lt;a href=&amp;#34;http://philipm.at/2011/0507/&amp;#34;&amp;gt;vergelijkbaar&amp;lt;/a&amp;gt; met Jekyll.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://www.phrozn.info/en/&amp;#34;&amp;gt;Phrozn&amp;lt;/a&amp;gt;, in PHP, probeert Jekyll en Hyde&amp;#xA;te benaderen.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We zien dan ook een opleving van Static Site Generators, hoofdzakelijk aangedreven door git. Git is een revisiebeheersysteem waarmee je makkelijk kunt &amp;lt;a href=&amp;#34;/2012/08/03/git-deploy-or-how-i-learned-to-stop-worrying-and-love-deployment/&amp;#34;&amp;gt;releasen&amp;lt;/a&amp;gt;, en makkelijk heel veel bestanden kunt beheren.&amp;#xA;Deze generatie static site generators, gebruiken geen databases of complexe en dure pakketten, maar, heel eenvoudig, tekstbestandjes.  Gewoon een tekstbestand voor iedere pagina en artikeltje. Dit tekstbestandjes beheer je met git, een revisiebeheersysteem. Met wat scripts maak je van de tekstbestandjes daadwerkelijke webpagina&amp;#39;s en die zet je online.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een Static Site Generator heeft enkele nadelen, maar ook enorme voordelen ten opzichte van&amp;#xA;dynamische sites:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;dynamische-en-interactieve-functionaliteit&amp;#34;&amp;gt;Dynamische en interactieve functionaliteit.&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een statische site heeft simpelweg geen toegang tot een database. Het plaatsen en opslaan van bijvoorbeeld reacties is daarom simpelweg onmogelijk; dat maakt een en ander enorm veilig, maar maakt veel sites en concepten onmogelijk met statische sites. Sites waarbij mensen inloggen, berichten, artikelen, producten of wat dan ook, plaatsen, kun je gewoon niet uitvoeren met een statische site.&amp;#xA;Voor reacties op artikelen kune je nog wat trucjes met bijvoorbeeld facebook of &amp;lt;a href=&amp;#34;http://disqus.com/&amp;#34;&amp;gt;disqus&amp;lt;/a&amp;gt; uitvoeren. Maar verder dan dat: zo goed als onmogelijk. Als je interactie nodig hebt, registraties, insturen van zaken door gebruikers, dan is een statische site gewoon geen optie.&amp;#xA;Maar voor de meeste redactioneel gedreven sites (ook als die redactie slechts één persoon is) is het een uitermate geschikte tool.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;performance&amp;#34;&amp;gt;Performance&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Op een gemiddeld webservertje, kun je met een gemiddelde Wordpress of Drupal-site (twee veelgebruikte CMSen) vaak minder dan tien gebruikers per minuut aan. Pagina&amp;#39;s laden duurt tussen de halve en enkele seconden.&amp;#xA;En je moet voor bijna iedere site, groot of klein, ingewikkelde caching-, proxy of databases tunen. Gewoon om een paar pagina&amp;#39;s te tonen. Een statische pagina is om en nabij de honderduizend keer zo snel als de meest eenvoudige Drupal-pagina.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Veel CMSen vallen dan ook terug op allerhande caching-servers en lagen. Het is niet ongebruikelijk om een volledige server (reken vanaf €300/jaar) voor caching in te zetten. Caching is in feite niets meer dan eenmalig de gegenereerde pagina (of onderdelen ervan) ergens op te slaan zodat ze voor de volgende bezoeker niet opnieuw gegenereerd hoeven te worden. Veelal nogal onzinnig: een volledig, op de server draaiend CMS, om statische pagina&amp;#39;s mee te maken. Dat kan slimmer (hint: dat CMS elders neerzetten, bijvoorbeeld op je eigen computer en de gegenereerde pagina&amp;#39;s zelf op de server plaatsen). Caching is voor de meeste CMSen onontbeerlijk en maakt een groot deel van de dynamiek en interactie weer ongedaan. Veelal blijkt het voor sites eigenlijk goed mogelijk om die vier jaar oude archiefpagina niet telkens opnieuw uit een database op te bouwen, maar gewoon eenmalig weg te schrijven als een statisch bestand en nooit meer naar om te kijken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;piekperformance&amp;#34;&amp;gt;Piekperformance&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Zelfs wanneer je een goed getunede server hebt met een mooi ingeregeld CMS, dan &amp;lt;a href=&amp;#34;http://www.destentor.nl/nieuws/algemeen/binnenland/12002326/Website-Omroep-Gelderland-weer-in-de-lucht.ece&amp;#34;&amp;gt;gaat&amp;lt;/a&amp;gt; het &amp;lt;a href=&amp;#34;http://webwereld.nl/nieuws/105305/crisis-nl-blundert-zichzelf-offline.html&amp;#34;&amp;gt;bij&amp;lt;/a&amp;gt; piekbelasting &amp;lt;a href=&amp;#34;http://www.it-executive.nl/dossiers/dossier/programmeerfout_haalde_website_ns_onderuit&amp;#34;&amp;gt;vaak mis&amp;lt;/a&amp;gt;.&amp;#xA;Meestal is de onderlinge oorzaak de complexiteit van het geheel.&amp;#xA;Allerhande gekoppelde redactionele systemen, advertentieservers en vele onderling gekoppelde servers. Wanneer je enkel statische files serveert is de opzet zó eenvoudig dat bij piekbelasting de site gewoon online gehouden kan worden, in heel extreme gevallen bijvoorbeeld door wat extra servers bij te schakelen. Iets wat met de meeste dynamische systemen gewoon niet kan. Terwijl juist bij piekbelasting de bereikbaarheid belangrijk is. Is het niet voor de kritieke communicatie en informatie bij calamtiteiten, dan op zijn minst omdat je tijdens een piek lekker veel advertentieinkomsten binnenharkt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Je ziet dan ook bij extreme situaties dat (grote) sites overschakelen op statische files, of zelfs altijd statische files hebben klaarstaan om het over te nemen als het CMS het niet langer trekt. De CNN tijdens 9/11 was een prachtig voorbeeld daarvan: een simpele pagina waarop de redacteuren telkens nieuwe informatie toevoegden of herschreven; hun CMS was allang daarvoor gestorven aan overbelasting.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;security&amp;#34;&amp;gt;Security&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Omdat je het meest complexe en tevens meest blootgelegde deel uit je applicatiestapel (stack) haalt, is beveiligen heel makkelijk.&amp;#xA;Geen databases, administratieomgevingen en interactieve delen die opengebroken kunnen worden. De enige die zaken op de server kan plaatsen is de applicatie of persoon die de statische files erop zet; dus dat is enorm sterk te beveiligen. In een HTML-bestand kun je (welhaast) niet inbreken. In diezelfde pagina van een CMS eigenlijk altijd wel (alleen is nu nog niet bekend hoe).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Om deze reden kun je ook enorm besparen op onderhoud; je hoeft immers enkel nog de webserver(s) te onderhouden; iets wat sowieso bijna niemand meer zelf doet. Bij een update-frequentie van eens per paar weken voor Wordpress, of eens per week voor Drupal, met een paar modules, scheelt dit al snel tien of meer uur aan duurbetaalde ontwikkelaars, per maand.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;hosting-en-serverkosten&amp;#34;&amp;gt;Hosting en serverkosten&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;De hosting kan enorm simpel worden uitgevoerd. Vanwege de duizenden malen hogere performance, kun je in theorie duizenden malen zoveel sites hosten op dezelfde hardware. Meestal kun je een statische site met enkele duizenden bezoekers per week heel makkelijk gratis en toch redelijk professioneel hosten. Meestal hoef je voor veel grotere sites slechts één server op te tuigen, in plaats van een hele boom van servers. Grote hosters zoals de NPO hebben vaak vele tientallen malen zoveel applicatieservers (de servers die de CMSen draaien) dan statsiche content-servers; gewoon omdat die laatste zoveel makkelijker tot onvoorstelbare performance op te schalen zijn.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Het web, en daarmee de bekendste servers en alle protocollen waren ook gewoon ontworpen voor statische content. Pas nu, 2012, komen webservers en protocollen beschikbaar die eigenlijk technisch geschikt zijn voor interactieve sites. Alles wat we tot nu toe deden was knutselen en rommelen in de marge. Als je dus teruggaat naar die basis, statische paginas, worden zaken vele malen gemakkelijker.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;hackability&amp;#34;&amp;gt;Hackability&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Je hele stapel aan serversoftware kun je terugbrengen naar één lokaal draaiend stukje gereedschap; geen &amp;lt;code&amp;gt;mod_php&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cgi&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;databases&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;caching proxies&amp;lt;/code&amp;gt; enzovoort.  En zelfs dat lokale stukje software is meestal erg simpel. Jekyll zelf bestaat uit een paar klasses en enkele tientallen files, niet meer. Met wat knutselwerk kun je ook met vier PHP-scriptjes HTML-bestanden maken uit tekstfiletjes.&amp;#xA;Bovendien draait de applicatie niet op de (productie-)server, dus is uitproberen van kleine wijzingingen heel makkelijk. Pas als je tevreden bent, kopieer je alles naar online.&amp;#xA;Het is daarom zeer, zeer hackable (hackable als in: aanpasbaar, niet als in &amp;lt;em&amp;gt;inbraakgevoelig&amp;lt;/em&amp;gt;). Met een klein beetje programmeerervaring kun je zo heel grote sites optuigen. Veel makkelijker en overzichtelijker dan in de code van een of ander CMS te gaan rondsleutelen; iets wat bovendien vaak sterk wordt &amp;lt;a href=&amp;#34;http://wordpress.stackexchange.com/questions/1639/what-are-some-reasons-why-you-should-not-hack-wordpress-core-files#1671&amp;#34;&amp;gt;afgeraden&amp;lt;/a&amp;gt;. Maar bij statische-site-generators juist de norm en bedoeling is!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Deze aanpasbaarheid zorgt ook dat je een stuk vrijer bent dan wanneer je alles volgens de structuur of standaarden van het gekozen framework of CMS doet.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;seo&amp;#34;&amp;gt;SEO&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;De eenvoud en hackability van statische-sites laat het toe om tot op zeer gedetailleerd niveau aanpassingen te maken: superschone HTML, goede -tags, nette doorverwijzingen enzovoort. De snelheid zorgt er bovendien voor dat de robots van zoekmachines in enkele seconden je hele site kunnen inlezen en indexeren; bij veel CMSen duurt dit weken; vooral omdat robots je site niet willen platleggen en ze, wanneer ze zien dat je een CMS hebt, wat rustiger gaan indexeren.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;hogere-drempel&amp;#34;&amp;gt;Hogere drempel&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Het aanpassen van tekstbestandjes op je schijf geeft een hogere drempel; hoger voor onervaren gebruikers om te gaan schrijven aan tekst voor een site, en hoger voor jezelf, omdat het plaatsen van een artikel net even een grotere taak lijkt dan op een CMS.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dit maakt dat veel blogs die met statische sites gemaakt zijn, veelal langere artikelen bevatten, met minder hoge frequentie. Het is minder geschikt voor iemand die viermaal daags een update wil plaatsen, liefst vanaf haar mobiele telefoon. Maar meer voor iemand die dagen schaaft aan een stukje.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;integratie-van-allerhande-gadgets&amp;#34;&amp;gt;Integratie van allerhande gadgets&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;De meeste CMSen integreren twitter, facebook, en wat dies meer zij, ook gewoon via zogenaamde &amp;amp;quot;embedcodes&amp;amp;quot;. Dat kan op een statische site dus precies zo makkelijk.&amp;#xA;Maar als de integratie iets verder gaat, zoals het automatisch koppelen van comments aan facebook; of het vanzelf plaatsen van links van nieuwe artikelen op twitter, dan is wat meer kunst- en vliegwerk vereist.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;grotere-organisaties&amp;#34;&amp;gt;Grotere organisaties.&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;De huidige statische sites gaan ervanuit dat je op je PC een lijstje met bestanden hebt die bij publicatie omgezet worden naar HTML.&amp;#xA;Met meer mensen samenwerken is wat moeilijk. Tenzij die mensen dit met een revisiebeheersysteem doen. Daaronder valt ook bijvoorbeeld een gedeelde Dropbox waarin enkele mensen tegelijk tekstbestandjes plaatsen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar een standaard redactioneel team met (online) workflows die via vele teams, eindredacteurs enzovoort naar online gaat, is het waarschijnlijk wat minder geschikt. Althans, je kunt ook gewoon elkaar tekstbestandjes mailen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;jekyll-opzetten&amp;#34;&amp;gt;Jekyll opzetten&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Hieronder een korte howto van een site met Jekyll. Zo een site is heel simpel en plat. Wil je wat sneller vooruitkomen, dan kun je met bijvoorbeeld &amp;lt;a href=&amp;#34;http://octopress.org/&amp;#34;&amp;gt;octopress&amp;lt;/a&amp;gt; of &amp;lt;a href=&amp;#34;http://jekyllbootstrap.com/&amp;#34;&amp;gt;Jekyllbootstrap&amp;lt;/a&amp;gt;.  Maar die zijn meteen veel complexer en vereisen waarschijnlijk weer een heleboel opruimwerk. Ik begon zelf met Jekyll Bootstrap, maar heb erg veel tijd moeten steken in het weghalen van vanalles. Achteraf bezien was het veel sneller geweest om met een standaard Jekyll te beginnen en gewoon zelf alles op te zetten.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Daarom de drie stappen om je site te maken:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;1-installatie&amp;#34;&amp;gt;1. Installatie&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Installeer de Gem (of eerst ruby, rubygems en dan Jekyll, op Linux soms nodig, op Mac heb je deze al):&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-bash&amp;#34; data-lang=&amp;#34;bash&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;$ sudo apt-get ruby rubygems &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;# op Mac niet nodig dus, op Linux soms.&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;$ sudo gem install jekyll&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Of installeer Jekyll vanuit je packagemanger in Ubuntu. Beter is via gems, want de deb packages in Ubuntu willen nogal eens achterlopen op de gems, wat Ruby&amp;#39;s eigen pakettensysteem is.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-bash&amp;#34; data-lang=&amp;#34;bash&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;$ sudo apt-get install jekyll.&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;h3 id=&amp;#34;2-mappen-en-bestanden-aanmaken&amp;#34;&amp;gt;2. Mappen en bestanden aanmaken&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maak mappen aan waarin je je statische site en de layout enzo gaat bouwen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;#xA;    .&amp;#xA;    |-- _config.yml&amp;#xA;    |-- _includes&amp;#xA;    |-- _layouts&amp;#xA;    |   |-- default.html&amp;#xA;    |   `-- post.html&amp;#xA;    |-- _posts&amp;#xA;    |   |-- 2012-12-30-mijn-eerste-blogartikel.markdown&amp;#xA;    |   `-- 2012-12-31-het-was-een-goed-jaar.markdown&amp;#xA;    |-- _site&amp;#xA;    `-- index.html&amp;#xA;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;default.html&amp;lt;/code&amp;gt; bevat de HTML waarmee je jou site opmaakt, &amp;lt;code&amp;gt;post.html&amp;lt;/code&amp;gt; de opmaakt van een enkel artikel binnen deze standaard opmaakt. Hierin zet je waarschijnlijk &amp;lt;a href=&amp;#34;https://github.com/mojombo/jekyll/wiki/Template-Data&amp;#34;&amp;gt;enkele variabelen&amp;lt;/a&amp;gt; om bij het maken van de HTML, de titels, datums enzovoort te plaatsen. De posts schrijf je als simpel tekstbestandje, maar wat extra informatie (zoals tags, auteur enzovoort) kun je &amp;lt;a href=&amp;#34;https://github.com/mojombo/jekyll/wiki/YAML-Front-Matter&amp;#34;&amp;gt;bovenaan in het bestandje kwijt&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;index.html&amp;lt;/code&amp;gt; vul je met wat tekst voor je voorpagina &amp;lt;a href=&amp;#34;https://github.com/shopify/liquid/wiki/liquid-for-designers&amp;#34;&amp;gt;en code&amp;lt;/a&amp;gt; om een lijstje met artikelen te plaatsen, bijvoorbeeld:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;{{ &amp;amp;quot;{% for posts in site.posts limit:10 &amp;amp;quot; }}%}&amp;#xA;  {{ &amp;amp;quot;{{ post.date | date_to_string&amp;amp;quot; }}}}: &amp;amp;lt;a href=&amp;amp;quot;{{ &amp;amp;quot;{{ BASE_PATH &amp;amp;quot; }}}}{{ &amp;amp;quot;{{ post.url &amp;amp;quot; }}}}&amp;amp;quot;&amp;amp;gt;{{ &amp;amp;quot;{{ post.title &amp;amp;quot; }}}}&amp;amp;lt;/a&amp;amp;gt;&amp;amp;lt;br /&amp;amp;gt;&amp;#xA;{{ &amp;amp;quot;{% endfor &amp;amp;quot; }}%}&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Gerenderd als:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;30 Dec 2012: Mijn Eerste Blogartikel&amp;#xA;31 Dec 2012: Het was een Goed Jaar&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;3-omzetten-naar-html-de-site-genereren&amp;#34;&amp;gt;3. Omzetten naar HTML: de site genereren.&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Roep &amp;lt;code&amp;gt;jekyll --server&amp;lt;/code&amp;gt; aan. Open een browser op &amp;lt;a href=&amp;#34;http://localhost:4000&amp;#34;&amp;gt;localhost:4000&amp;lt;/a&amp;gt;. En viola, je site.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Met &amp;lt;code&amp;gt;jekyll generate&amp;lt;/code&amp;gt;, vul je de &amp;lt;code&amp;gt;_site&amp;lt;/code&amp;gt; map met de statische versie&amp;#xA;van je site; deze kun je op vrijwel iedere host plaatsen. Hosten (gratis) op&amp;#xA;Github kan ook, maar vereist wat &amp;lt;a href=&amp;#34;https://help.github.com/articles/using-jekyll-with-pages&amp;#34;&amp;gt;kennis van git en github&amp;#39;s hosting&amp;lt;/a&amp;gt;.&amp;#xA;Uiteraard kun je het plaatsen van de statische content helemaal&amp;#xA;automatiseren, bijvoorbeeld met git, of een van de vele &amp;lt;a href=&amp;#34;https://github.com/mojombo/jekyll/wiki/Deployment&amp;#34;&amp;gt;andere manieren&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Fix SEGV for Vims command t on Ubuntu 12.10</title>
   <link href="https://berk.es/2012/10/23/fix-segv-for-vims-command-t-on-ubuntu-1210/"/>
   <updated>2012-10-23T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2012/10/23/fix-segv-for-vims-command-t-on-ubuntu-1210/</id>
   <content type="html">&amp;lt;p&amp;gt;The upgrade to Ubuntu 12.10 upgraded my Ruby version to 1.9.3 (yay!).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This, however, broke my &amp;lt;a href=&amp;#34;https://wincent.com/products/command-t&amp;#34;&amp;gt;command-t&amp;lt;/a&amp;gt; a vim-plugin to open files&amp;#xA;quickly. Command-t is a compiled plugin (for speed) and needs to be&amp;#xA;compiled against the system-wide Ruby. Else vim crashes with a &amp;lt;code&amp;gt;SEGV&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A little &amp;lt;a href=&amp;#34;https://github.com/carlhuda/janus/issues/215#issuecomment-3003126&amp;#34;&amp;gt;searching&amp;lt;/a&amp;gt;, &amp;lt;a href=&amp;#34;http://deangerber.com/blog/2012/01/09/vim-caught-deadly-signal-segv/&amp;#34;&amp;gt;showed me&amp;lt;/a&amp;gt;&amp;#xA;that command-t was the problem and needed to be&amp;#xA;recompiled. Obviously only when you had installed command-t before the upgrade to 12.10&amp;#xA;(and thus compiled against the previous ruby version). As nearly always, once you know the problem, the fix is&amp;#xA;easy on Ubuntu; the Vim and gVim are already compiled against the correct library.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;First, checking what version command-t is compiled against:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;  $ ldd ~/.vim/bundle/command-t/ruby/command-t/ext.so&amp;#xA;  linux-gate.so.1 =&amp;amp;gt;  (0xb7714000)&amp;#xA;  libruby1.8.so.1.8 =&amp;amp;gt; /usr/lib/libruby1.8.so.1.8 (0xb7696000)&amp;#xA;  libc.so.6 =&amp;amp;gt; /lib/i386-linux-gnu/libc.so.6 (0xb7330000)&amp;#xA;  libpthread.so.0 =&amp;amp;gt; /lib/i386-linux-gnu/libpthread.so.0 (0xb7314000)&amp;#xA;  librt.so.1 =&amp;amp;gt; /lib/i386-linux-gnu/librt.so.1 (0xb730b000)&amp;#xA;  libdl.so.2 =&amp;amp;gt; /lib/i386-linux-gnu/libdl.so.2 (0xb7306000)&amp;#xA;  libcrypt.so.1 =&amp;amp;gt; /lib/i386-linux-gnu/libcrypt.so.1 (0xb72d5000)&amp;#xA;  libm.so.6 =&amp;amp;gt; /lib/i386-linux-gnu/libm.so.6 (0xb72a9000)&amp;#xA;  /lib/ld-linux.so.2 (0xb7715000)&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Hmm. &amp;lt;code&amp;gt;libruby1.8.so.1.8&amp;lt;/code&amp;gt;, not good.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Cd into the command-t location (mine is at &amp;lt;code&amp;gt;~/.vim/bundle/command-t/&amp;lt;/code&amp;gt;),&amp;#xA;feth the lastest version, clean the old make and re-make. In order to re-make I use the rakefile,&amp;#xA;you might have to install rake first. And, important, make sure you run&amp;#xA;the system ruby if using gemsets. Like so:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;  $ rvm use system&amp;#xA;  $ ruby -v &amp;amp;amp;&amp;amp;amp; which ruby # Just to know what we are using.&amp;#xA;  $ sudo gem install rake # We need rake to build.&amp;#xA;&amp;#xA;  $ cd ~/.vim/bundle/command-t/&amp;#xA;  $ git pull --rebase origin &amp;amp;amp;&amp;amp;amp; git checkout master&amp;#xA;&amp;#xA;  $ make clean # remove old compilations and installation&amp;#xA;  $ rake make  # rebuild the &amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And there you go:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;  $ ldd ~/.vim/bundle/command-t/ruby/command-t/ext.so &amp;#xA;  linux-gate.so.1 =&amp;amp;gt;  (0xb7714000)&amp;#xA;  libruby-1.9.1.so.1.9 =&amp;amp;gt; /usr/lib/libruby-1.9.1.so.1.9 (0xb74da000)&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Happy &amp;lt;a href=&amp;#34;https://wincent.com/products/command-t&amp;#34;&amp;gt;command-t-ing&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Spree e-commerce on budgethoster Site5</title>
   <link href="https://berk.es/2012/10/22/spree-on-budgethoster-site5/"/>
   <updated>2012-10-22T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2012/10/22/spree-on-budgethoster-site5/</id>
   <content type="html">&amp;lt;p&amp;gt;Recently I rolled out a&amp;#xA;Ruby-on-Rails/&amp;lt;a href=&amp;#34;http://spreecommerce.com&amp;#34;&amp;gt;Spree&amp;lt;/a&amp;gt;-based webshop on the&amp;#xA;budgethoster &amp;lt;a href=&amp;#34;htp://site5.com&amp;#34;&amp;gt;Site5&amp;lt;/a&amp;gt;. I thought to share some gotcha&amp;#39;s, reasoning&amp;#xA;around this project. To debunk the idea that hosting for Rails is either&amp;#xA;complex and hard, or done on Heroku. To explain a bit about Spree as a&amp;#xA;good option for your e-commerce (or not) and to go a little into how I modified&amp;#xA;Spree.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;My wife makes &amp;lt;a href=&amp;#34;http://annatreurniet.nl&amp;#34;&amp;gt;custom bags, smartphone- and tablet sleeves, and all sorts&amp;#xA;of leather, handcrafted jewelery&amp;lt;/a&amp;gt;. Obviously she wanted to sell some&amp;#xA;through the internet; So I made her a webshop.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;reasons-for-choosing-for-spree&amp;#34;&amp;gt;Reasons for choosing for Spree&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I decided to go for Spree, after some investigation. A few important&amp;#xA;reasons where:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It&amp;#39;s catchphrase &amp;lt;em&amp;gt;It was designed to make customization and upgrades&amp;#xA;as simple as possible&amp;lt;/em&amp;gt;.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It is built in Ruby on Rails, and that is what I do. (Although I&amp;#xA;probably know more about PHP, and Drupal, at that).&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;We wanted a simple shop. Her needs are mostly simplicity; less&amp;#xA;features in the shop equals better. Spree promises that; as opposed to&amp;#xA;most ready-to-go shopping tools, that promise every feature you may&amp;#xA;wish. And get in your way.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;We needed flexibility. Simple means that it offers less options and&amp;#xA;choices. But also that the business-logic needs to handle more.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;I wanted the site to be ready for mobile and thus to be &amp;lt;a href=&amp;#34;http://zomigi.com/blog/examples-of-flexible-layouts-with-css3-media-queries/&amp;#34;&amp;gt;responsive&amp;lt;/a&amp;gt;. Rails is ready for this. HTML5 and all that. So is Spree.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Some reasons why I had thoughts about either writing my own e-commerce or&amp;#xA;going with a PHP-solution were:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;I had (and have, read on) my questions about how true the catchphrase&amp;#xA;&amp;lt;em&amp;gt;It was designed to make customization... as simple as possible&amp;lt;/em&amp;gt; was,&amp;#xA;in reality.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Spree is Rails. Rails requires professional hosting; not a problem for&amp;#xA;a project developed for over 1k; where hosting at $20 per month is peanuts. In this&amp;#xA;case $20 per month is far over budget; again, the insides of her exact&amp;#xA;business-choices are out of scope for this project. Good-enough&amp;#xA;PHP-hosters come for under the $5 per month.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;I was going to build it for free, obviously. So we could not afford me&amp;#xA;spending hundreds of hours on tweaking some tool, CSS and whatnot. A&amp;#xA;turnkey-solution like &amp;lt;a href=&amp;#34;https://en.wikipedia.org/wiki/Magento&amp;#34;&amp;gt;Magento&amp;lt;/a&amp;gt; would be a faster solution.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;We chose the Dutch bank and payment provider &amp;lt;a href=&amp;#34;https://www.rabobank.nl/bedrijven/producten/betalen_en_ontvangen/geld_ontvangen/rabo_omnikassa/&amp;#34;&amp;gt;Omnikassa&amp;lt;/a&amp;gt; because&amp;#xA;they simply offered the best deal for her. There was no omnikassa&amp;#xA;payment plugin for Spree, but there were some for more famous&amp;#xA;frameworks.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I went for Spree after proof-of-concepting my two biggest challenges:&amp;#xA;offsite payment and (Zurb-foundation](&amp;lt;a href=&amp;#34;http://foundation.zurb.com&amp;#34;&amp;gt;http://foundation.zurb.com&amp;lt;/a&amp;gt;) as CSS/HTML framework, instead of&amp;#xA;the default &amp;lt;a href=&amp;#34;http://www.getskeleton.com/&amp;#34;&amp;gt;Skeleton&amp;lt;/a&amp;gt;, came out quite&amp;#xA;alright. And a 30 days trial on &amp;lt;a href=&amp;#34;https://www.site5.com/&amp;#34;&amp;gt;Site5&amp;lt;/a&amp;gt; proved me that hosting there was&amp;#xA;going to work out.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;zurb-foundation&amp;#34;&amp;gt;Zurb Foundation&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I replaced most views with my own HTML, so that I can use Zurb&amp;#xA;foundation. It offered just a little more features, such as a &amp;lt;a href=&amp;#34;http://foundation.zurb.com/docs/orbit.php&amp;#34;&amp;gt;slider&amp;lt;/a&amp;gt;&amp;#xA;and more advanced responsive features; like hiding entire subtrees of&amp;#xA;elements on certain devises.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In the end, we decided to go Desktop-first; I am re-doing some of the&amp;#xA;views now, so they are prettier on mobile too. The reason we put this&amp;#xA;lower in our todo, is that the most-used payment system in the&amp;#xA;Netherlands, iDeal, trough our PSP omnikassa, is not mobile-friendly in&amp;#xA;the first place. So why offer an entire mobile-webshop when people&amp;#xA;cannot pay (properly) on a mobile?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It turned out to be really easy, but quite a journey of discovery trough all the&amp;#xA;spree-gems and its views, before I found out what views to copy into my&amp;#xA;own projects. The CSS was just as cumbersome to override. In the end, I&amp;#xA;decided to simply do away with &amp;lt;em&amp;gt;all&amp;lt;/em&amp;gt; CSS and JavaScript for the front-end and roll my own.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;site5-hosting&amp;#34;&amp;gt;Site5 Hosting.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For under €5 per month you get a server with git, ssh-access and&amp;#xA;Passenger to host your Rails application. A few Euro more for a static&amp;#xA;IP address, which I need for the SSL-certificate. It is an e-commerce,&amp;#xA;you need HTTPs.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It requires a &amp;lt;a href=&amp;#34;http://kb.site5.com/ruby-on-rails/how-to-deploy-a-rails-3-application-with-phusion-passenger/&amp;#34;&amp;gt;few settings to be changed&amp;lt;/a&amp;gt; to my Rails application and it&amp;#xA;requires Ruby &amp;lt;a href=&amp;#34;http://kb.site5.com/ruby-on-rails/ruby-rails-gems/&amp;#34;&amp;gt;1.8.7&amp;lt;/a&amp;gt; since that is what their Passenger is configured&amp;#xA;to use, but in the end it works just fine.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The only problem I had was some asset-precompiling issue, where the&amp;#xA;compiler just died on me. After a support-call, the Site5-engineers upped&amp;#xA;some memory on the server and I could compile just fine.&amp;#xA;It turned out that some spree plugin came with several hundreds of&amp;#xA;(demo) asset-files like huge videos that needed to be &amp;amp;quot;compiled&amp;amp;quot; by the&amp;#xA;asset-pipeline. Cleaning up &amp;lt;a href=&amp;#34;https://github.com/berkes/spree_slider/commits/master&amp;#34;&amp;gt;Spree Slider&amp;lt;/a&amp;gt; and removing its assets&amp;#xA;fixed the issue for good. But hey, I don&amp;#39;t expect a budget-hoster to&amp;#xA;support me compiling hundreds of megabytes of video and other&amp;#xA;demo-stuff. Fair enough.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Also, their uptime is reasonable. Not enterprise-alike reliable (IMHO), so I&amp;#xA;have &amp;lt;a href=&amp;#34;http://nagiosplugins.org/man/check_http&amp;#34;&amp;gt;nagios&amp;lt;/a&amp;gt; to check the&amp;#xA;frontpage every several minutes for certain strings to occur (It looks&amp;#xA;if the words &amp;amp;quot;Anna Treurniet&amp;amp;quot; occur in the &amp;lt;code&amp;gt;&amp;amp;lt;title&amp;amp;gt;&amp;lt;/code&amp;gt;). Every odd&amp;#xA;month (or so) there is a restart or some short downtime. One time Site5&amp;#xA;changed the MySQL-setup (location of the socket moved to elsewhere) so I&amp;#xA;had over half a day of downtime until solved. And now and again the&amp;#xA;application gets shut-down for no clear reason, so it needs to boot,&amp;#xA;resulting in the webshop loading very slow for one or two visitors. The&amp;#xA;nagios checking actually kind-of solves this too, since it acts as a&amp;#xA;&amp;amp;quot;visitor&amp;amp;quot; opening the site every five minutes.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;All in all, I am very happy with this host. It offers far (far!) more then one&amp;#xA;would expect from a €5/month. In my &amp;amp;quot;enterprise&amp;amp;quot;-jobs, I have to deal with €500-and-up-per-month&amp;#xA;hosters who have far worse deals, support, deployment and uptime.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Their absolute biggest downside is the way their bulkhosting environment holds them back from upgrading to Ruby 1.9.3.&amp;#xA;So, if you depend on a reasonable recent Ruby-version, bad luck.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;git-deploy&amp;#34;&amp;gt;Git Deploy&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In order to keep the deployment smooth and somewhat close to the&amp;#xA;experience I have with &amp;lt;a href=&amp;#34;http://heroku.com&amp;#34;&amp;gt;Heroku&amp;lt;/a&amp;gt;, I use &amp;lt;a href=&amp;#34;https://github.com/mislav/git-deploy&amp;#34;&amp;gt;git-deploy&amp;lt;/a&amp;gt;. Git deploy consists of a few simple hooks that run on the server in post-receive hooks. So, after you push your changes to production with &amp;lt;code&amp;gt;git push production&amp;lt;/code&amp;gt;, the server runs a few commands, like (when needed) database-migrations, assets and cache-refreshing and then a restart of the Rails application. I have used this for &amp;lt;a href=&amp;#34;http://www.berk.es/2012/08/03/git-deploy-or-how-i-learned-to-stop-worrying-and-love-deployment/&amp;#34;&amp;gt;other, PHP-based systems too&amp;lt;/a&amp;gt;.&amp;#xA;Some problems, as mentioned above with asset-precompiling aside, it&amp;#xA;works like a charm.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Obviously, when using git to manage the deployment, you need a &amp;lt;a href=&amp;#34;http://www.jeffkreeftmeijer.com/2010/why-arent-you-using-git-flow/&amp;#34;&amp;gt;good&amp;#xA;branching and releasing management&amp;lt;/a&amp;gt;. With that in place, I can fix and deploy changes within minutes. Yes. Minutes. Probably faster than most of you can log in over SSH, find the sourcecode on production, and hack a fix in with Vim.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;testing-and-tdd&amp;#34;&amp;gt;Testing and TDD&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Unfortunately, I did not manage to get good integration tests for Spree&amp;#xA;going. Most of the extensions lack any form of tests. Spree itself is is&amp;#xA;covered pretty well, but integrating Spree means changing configuration,&amp;#xA;overriding behaviours with &amp;lt;a href=&amp;#34;http://guides.spreecommerce.com/logic_customization.html&amp;#34;&amp;gt;Decorators&amp;lt;/a&amp;gt;. And I still have no idea how&amp;#xA;to tests these properly. The rest is mostly view-overrides, which often&amp;#xA;breaks Spree&amp;#39;s own tests and requires me to rewrite all the spree-tests&amp;#xA;in my application. It mostly boils down to my inexperience with testing&amp;#xA;mostly, though.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;omnikassa&amp;#34;&amp;gt;Omnikassa&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A &amp;lt;a href=&amp;#34;http://www.spreecommerce.com/extensions/95-spree-omni&amp;#34;&amp;gt;Spree Extension&amp;lt;/a&amp;gt; was written to allow offsite-payment with the&amp;#xA;Dutch payment-system Omnikassa. This is and was a mess. Spree had no&amp;#xA;(proper) support for offsite payments, so I had to hack into the entire&amp;#xA;checkout-workflow in order to get this payment-system going. The&amp;#xA;Omnikassa-extension somehow breaks the feature in Spree to allow&amp;#xA;discounts; it breaks certain orders in the back-end and whatnot. Spree&amp;#xA;1.2.0 promises to have this checkout-workflow-inflexibility fixed, but&amp;#xA;an upgrade is rather hard, seeing all the customisations the application&amp;#xA;needed.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;spree-again&amp;#34;&amp;gt;Spree again?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I don&amp;#39;t think I will use Spree for a future e-commerce project. Despite&amp;#xA;its promise to be the most flexible solution, I found it making too much&amp;#xA;assumptions and being far too inflexible in areas. I&amp;#39;d rather roll my&amp;#xA;own, next time.&amp;#xA;The most important parts that Spree offers me, are either very easy to&amp;#xA;develop myself (products, categories, their views, content-management),&amp;#xA;not needed (credit card-handling, user-login, 3rd party&amp;#xA;statistics) or covered in solid Rails plugins (administration, editors).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Against small-things-made-hard, like changing the checkout-workflow&amp;#xA;(one-click-checkout?, offsite-payments), manually ordering the products&amp;#xA;on the frontpage, integrating a simple CMS for the few &amp;amp;quot;static pages&amp;amp;quot;&amp;#xA;and so on. Usually, in an Average Rails-projects these things take a&amp;#xA;few hours programming and deploying. Here they took me days of stepping&amp;#xA;through spree-core code in order to understand how my Decorator did&amp;#xA;(not) change some ordering or some menu-addition.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For me, Spree offers me too little benefits to overcome its downsides. Despites&amp;#xA;its promise, it is very much a ready-made application, which works&amp;#xA;according to various assumptions about workflow, features and even looks, that you can&amp;#xA;configure and beat into shape; mostly. The very same conceptual things that &amp;lt;a href=&amp;#34;http://www.berk.es/2012/10/01/farewell-drupal/&amp;#34;&amp;gt;Drove me&amp;#xA;from Drupal&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But overall, rolling a simple, Spree-site on Site5, gives you a&amp;#xA;well-designed, ready-to-go e-commerce environment for under €10 per&amp;#xA;month. And with a few hacks and tools, you can make the deployment to&amp;#xA;that host really easy. Whether Spree is the best solution for your&amp;#xA;e-commerce needs, depends on how much (and what exactly) you need to&amp;#xA;customize.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Why outside in Webdevelopment is Better; why Designs should be Leading.</title>
   <link href="https://berk.es/2012/10/15/why-outside-in-webdevelopment-is-better-why-designs-should-be-leading/"/>
   <updated>2012-10-15T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2012/10/15/why-outside-in-webdevelopment-is-better-why-designs-should-be-leading/</id>
   <content type="html">&amp;lt;p&amp;gt;Designs should be leading when building a website or -application.&amp;#xA;There! I said it.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Sure, form follows function; content is king and so on: a design should be a Good Design to begin&amp;#xA;with: it should carry the function, content and user-experience to a&amp;#xA;higher level. So, for the sake of the argument, let us assume you have the Perfect Design For Your Project,&amp;#xA;Audience and Branding. It is UX-tested. It is&amp;#xA;delivered as valid, clean HTML and CSS, backed with PSDs, Wireframes and looks stunning.&amp;#xA;It has passed numerous PR- and marketing sessions, and even the boss&amp;#xA;wants it implemented like this.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Enter &amp;amp;quot;the technical guys&amp;amp;quot;. People like me, a &amp;lt;em&amp;gt;backend-guy&amp;lt;/em&amp;gt;. &amp;lt;strong&amp;gt;Sorry, but&amp;#xA;the CMS we are going to build it with, makes it really hard to implement&amp;#xA;this design.&amp;lt;/strong&amp;gt;, sometimes followed with the really pragmatic solution to&amp;#xA;&amp;lt;strong&amp;gt;Have the CMS dicate the way the application works&amp;lt;/strong&amp;gt;. To &amp;lt;strong&amp;gt;redo the&amp;#xA;wireframes according to how the CMS works, because that will save lots&amp;#xA;of time and budget&amp;lt;/strong&amp;gt;. That last word, being the most important one:&amp;#xA;&amp;lt;strong&amp;gt;Budget&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;People think visual. People talk visual. And our delivery is mostly&amp;#xA;visual: people interact with our -technical- solutions through the &amp;amp;quot;interface&amp;amp;quot;,&amp;#xA;that which was in &amp;amp;quot;the Design&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But most important: the design, that interface, is most prone to change;&amp;#xA;from small changes in wording in a footer, to a grand&amp;#xA;rebranding-overhaul: mostly interface changes. When I calculate the&amp;#xA;tickets in the projects I am now involved in, 80% of the tickets are&amp;#xA;&amp;amp;quot;interface&amp;amp;quot;- or &amp;amp;quot;interface-related&amp;amp;quot;. First because this interface is&amp;#xA;where any problems below surface and second because this is what 80% of&amp;#xA;your and my clients care about: the interface.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Yet, Even though most of the communications and deliveries are&amp;#xA;outside-in; we work inside out; starting with database-designs,&amp;#xA;CMS-configurations from which we work towards the interfaces.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;but-how&amp;#34;&amp;gt;But how?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You&amp;#39;ll be developing the innards of the application (its models,&amp;#xA;controllers) and then, in the end, need a giant hammer to beat the&amp;#xA;views, glued to it, into shape to match as closely as possible to your&amp;#xA;designs.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It gets worse when these innards are fixed, because you use a CMS that&amp;#xA;has them built in, for example. You&amp;#39;ll be writing extensions, hacks and&amp;#xA;overrides all trough that CMS, just to make it output its interfaces as&amp;#xA;close as possible to designs.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The solution lies in &amp;lt;strong&amp;gt;outside-in development&amp;lt;/strong&amp;gt;. I encountered that term first in &amp;lt;a href=&amp;#34;http://www.pragprog.com/titles/achbd/errata&amp;#34;&amp;gt;The&amp;#xA;Rspec Book — A&amp;#xA;Comprehensive Handbook for Behaviour Driven Webdevelopment&amp;lt;/a&amp;gt;. And I am loving it.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The idea is as simple as pragmatic: &amp;lt;a href=&amp;#34;http://teachmetocode.com/screencasts/introduction-to-outside-in-development-with-cucumber&amp;#34;&amp;gt;you develop by making the views, the&amp;#xA;interface first&amp;lt;/a&amp;gt;. Then fixate them in tests (specs), so that the&amp;#xA;interface, the HTML is fixed in your project. From there on, you develop&amp;#xA;the underlying application, whether those are modules for a CMS, models&amp;#xA;and controllers for an MVC-framework or configurations of a&amp;#xA;point-and-click-tool.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Once the HTML (and thus the CSS and interaction) is fixed, a test will&amp;#xA;start to fail the moment someone touches that HTML. Developers can&amp;#xA;therefore develop, refactor, and rewrite the underlying application as&amp;#xA;often as they wish, without breaking the Designs. Sounds cool, not?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;flexibility&amp;#34;&amp;gt;Flexibility.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Well, maybe not if that underlying application lacks the flexibility to&amp;#xA;be rewritten, altered and changed without making these views-tests fail.&amp;#xA;If, the moment you install an extension, all your tests turn Red (they fail),&amp;#xA;your tests, the fixed interfaces, become a burden. It is then near&amp;#xA;impossible to keep them passing, without all the hacks, extensions and&amp;#xA;configurations. That is probably the moment that you decide to rather&amp;#xA;change the view-specs (i.e. Divert from the original Designs) just to&amp;#xA;get forward.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It means you have the wrong tool. It means you have a tool at hand where&amp;#xA;you&amp;#39;ll simply never get 100% according to design. Where the tool&amp;#xA;dictates the designs and workflows of the application. If that works for&amp;#xA;you, then outside-in development makes little sense; but in that case,&amp;#xA;be very aware of this all trough the process; It really makes no sense&amp;#xA;to have a UX-lab test all the wireframes and mockups, if two weeks&amp;#xA;later, you find you cannot implement these wireframes and mockups&amp;#xA;anyway, due to the underlying inflexible tools.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Most RAD frameworks like Rails, Django, and various or their PHP-clones,&amp;#xA;are flexible enough in this, because they don&amp;#39;t (ever) assume anything&amp;#xA;about your wireframes and designs.&amp;#xA;Some, very few actually, CMSes allow this too.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;an-example&amp;#34;&amp;gt;An example?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In Rails many people test with Rspec; Views are (arguably) best&amp;#xA;described in cucumber features. The exact syntax and setup goes way&amp;#xA;beyond this post, so let&amp;#39;s look at some pseudo-code:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;Scenario: I want to log in&amp;#xA;  Given I am signed in&amp;#xA;  When I click on my name&amp;#xA;  Then I should see my profile-page&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A simple feature, described in &amp;lt;a href=&amp;#34;http://cukes.info&amp;#34;&amp;gt;Gherkin&amp;lt;/a&amp;gt; and fixates the actual views&amp;#xA;with so-called step-definitions:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;Given /I am signed in/ do&amp;#xA;  @me ||= Factory(:valid_user)&amp;#xA;  When %(I go to the new user session page)&amp;#xA;  When %(I fill in &amp;amp;quot;Username&amp;amp;quot; with &amp;amp;quot;#{@me.username}&amp;amp;quot;)&amp;#xA;  When %(I fill in &amp;amp;quot;Password&amp;amp;quot; with &amp;amp;quot;#{@me.password}&amp;amp;quot;)&amp;#xA;  When %(I press &amp;amp;quot;Sign in&amp;amp;quot;)&amp;#xA;end&amp;#xA;When /^I click on my name$/ do&amp;#xA;  click_link(&amp;amp;quot;#{@me.first_name} #{@me.last_name}&amp;amp;quot;)&amp;#xA;end&amp;#xA;Then /^I should see my profile-page$/ do&amp;#xA;  page.should have_xpath(&amp;#39;//*/h1.name&amp;#39;, :text =&amp;amp;gt; &amp;amp;quot;#{@me.first_name} #{@me.last_name})&amp;#xA;end&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is a really simple test to ensure that a logged-in user, who clicks&amp;#xA;on a link with her name, gets to see the profile-edit page, containing&amp;#xA;an &amp;lt;em&amp;gt;H1-tag&amp;lt;/em&amp;gt;, with class &amp;amp;quot;name&amp;amp;quot; and contents of the users&amp;#39; name. Real&amp;#xA;world tests, with more detail &amp;lt;a href=&amp;#34;http://blog.carbonfive.com/2012/02/14/beginning-outside-in-rails-development-with-cucumber-and-rspec&amp;#34;&amp;gt;over at Jared Carroll&amp;#39;s blogpost on this&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;downsides&amp;#34;&amp;gt;Downsides?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Certainly. As you can see, just to describe a login-form, a link with my&amp;#xA;&amp;lt;em&amp;gt;username&amp;lt;/em&amp;gt;, and a generic &amp;lt;em&amp;gt;H1-tag&amp;lt;/em&amp;gt;, I already need one entire &amp;lt;em&amp;gt;story&amp;lt;/em&amp;gt;. Depending on your&amp;#xA;test-suite and your application, you may have to write far, far more test-code then &amp;amp;quot;actual&amp;amp;quot;&amp;#xA;code.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But outside-in development is just one of many reasons why one&amp;#xA;might choose for test-driven development. Writing tests requires&amp;#xA;effort, time and dedication. Without tests you have no way to describe&amp;#xA;and fixate the interfaces. And without that, there is no outside-in&amp;#xA;development (And you&amp;#39;d best tell your designers and clients right-away&amp;#xA;that you are not going to get 100% pixel-perfect interfaces, within&amp;#xA;agreed budget and time... :) ).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The upside, is that your application is tested, and can easily remain&amp;#xA;so. With each release, a few commands ensure you that your (ever&amp;#xA;growing) application works. That all the work by all your colleagues or&amp;#xA;contributors remains working. And that the interfaces, the one thing&amp;#xA;your users care most about, work and look the way they expect them to&amp;#xA;look.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Programming on a Non-English Project; best practices</title>
   <link href="https://berk.es/2012/10/05/programming-on-a-none-english-project-best-practices/"/>
   <updated>2012-10-05T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2012/10/05/programming-on-a-none-english-project-best-practices/</id>
   <content type="html">&amp;lt;p&amp;gt;As a Dutch webdeveloper, I see mostly Dutch websites being built. And I&amp;#xA;see many team struggling with how to do this well. I developed a set of&amp;#xA;best practices, which I want to share and discuss.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If your entire teams&amp;#39; first language is English, this post is probably&amp;#xA;not for you. And if you are the kind of person who thinks &amp;amp;quot;are there&amp;#xA;actually people out there who write Russian code, Swedish documentation&amp;#xA;or use Dutch classnames? WTF?&amp;amp;quot; this post is probably not for you either&amp;#xA;(But take this with you before leaving: there are. A lot).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Edit:&amp;lt;/strong&amp;gt; as &amp;lt;a href=&amp;#34;http://www.reddit.com/r/programming/comments/10zffv/programming_on_a_none_english_project_best/c6hy06s&amp;#34;&amp;gt;offbytwo, a&amp;#xA;redditor&amp;lt;/a&amp;gt; kindly points out, it is&amp;#xA;non-English, not &amp;lt;em&amp;gt;none-English&amp;lt;/em&amp;gt;. Guess that proves that I am none-English&amp;#xA;(sic) to begin with :).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;the-client&amp;#34;&amp;gt;The Client.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;They will most probably communicate in your local language. Not in&amp;#xA;English. They want to produce their specifications, requirements and even&amp;#xA;designs in their native language. This input is therefore not-English.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It makes no sense trying to enforce this in English, that only creates&amp;#xA;friction in an area (feedback, requirements) where you want as little&amp;#xA;friction as possible. If anything, you may want to designate one person&amp;#xA;to translating this input before it goes into your other systems. But&amp;#xA;often it is good enough to simply put the non-English input as-is into&amp;#xA;your project.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;the-team&amp;#34;&amp;gt;The Team.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You might have one or two developers whom you have to communicate in&amp;#xA;English with. Either because you outsourced stuff, or because you are&amp;#xA;collaborating in Open Source, or because you have people on your team&amp;#xA;from abroad.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But even when you don&amp;#39;t, having all the technical communication in&amp;#xA;English pays off: all your developers understand and can write some&amp;#xA;English; but not all your potential developers speak your native tongue.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Standardising on English now, allows you to pull in such developers&amp;#xA;later. Standardising on your native language now, shuts a giant pool of&amp;#xA;potential collaborators out. Forever.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;code&amp;#34;&amp;gt;Code&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;All code should be English. Always. No exceptions allowed.&amp;#xA;Programming-languages, libraries and tools are all in English, so if&amp;#xA;anything, using your native language will only add inconsistency.&amp;#xA;Sure, if there is an all-Dutch programming-language, you would be&amp;#xA;writing all your code in Dutch. But such languages are rare or&amp;#xA;nonexistent.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;class GebruikersController extends Controller {&amp;#xA;  //Maakt een nieuwe gebruiker aan op basis van zijn identifier.&amp;#xA;  def __construct($gebruikers_id) {&amp;#xA;    //@TODO: maak de eigenlijke code&amp;#xA;  }&amp;#xA;}&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;There. I just used 5 pure English words and statements, 2 mixed words&amp;#xA;(&amp;lt;em&amp;gt;Gebruikers&amp;lt;/em&amp;gt;-Controller and &amp;lt;em&amp;gt;gebruikers&amp;lt;/em&amp;gt;-id_entifier_) and a load of Dutch words that&amp;#xA;no-one but a Dutch speaking person can understand.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;All your objects, classes, variables, function and whatnot should be&amp;#xA;English. Always. When you are programming a shop, you don&amp;#39;t call that&amp;#xA;&amp;lt;code&amp;gt;class InnKaupaKörfu &amp;amp;lt; ActiveRecord&amp;lt;/code&amp;gt; but rather just &amp;lt;code&amp;gt;Cart &amp;amp;lt; ActiveRecord&amp;lt;/code&amp;gt;. Code, databasetables, tools and whatnot should be in one&amp;#xA;language only. Defaulting to English.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;documentation&amp;#34;&amp;gt;Documentation&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When your code is all in English, a large part of your documentation&amp;#xA;will automatically be in English. It makes no sense, therefore to have&amp;#xA;the other documentation in another language.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Just default to English.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With one exception, being the input you receive from your clients. If&amp;#xA;this input is passed on to English speaking colleagues, you will need to&amp;#xA;translate it, but in that case, keep the original with the translation.&amp;#xA;In that case, you will need to appoint one person, e.g. a project&amp;#xA;manager or lead developer with the task to do the translating. And&amp;#xA;you&amp;#39;ll need budget for that, obviously.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;configuration-and-integration&amp;#34;&amp;gt;Configuration and Integration&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In tools like Drupal, a lot of the building is done trough UI&amp;#39;s within&amp;#xA;Drupal itself. Things like fields (title, body, images, colors etc.) on&amp;#xA;articles, article-types and even entire pages (views) are configured&amp;#xA;trough the interface.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Often these are made in the native tongue. That is not a good practice.&amp;#xA;Just give all these components English names. So don&amp;#39;t call the&amp;#xA;color-field on your &amp;amp;quot;product&amp;amp;quot;, &amp;lt;em&amp;gt;couleur&amp;lt;/em&amp;gt;, or &amp;lt;em&amp;gt;χρώμα&amp;lt;/em&amp;gt;. But rather just color.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The reason is that in situations like these with Drupal, but also&amp;#xA;Expression-engine, Typo3 and such, these names are then used all over&amp;#xA;the place. Ranging from database names, column-names to variables and&amp;#xA;autogenerated classes.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It is very ugly having to work with template-files called &amp;lt;code&amp;gt;overzicht_block_content&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;les_plus_lus_page_header&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Because these names trickle through to your code, keep them English.&amp;#xA;Else you will be forced to break rule #1: all code is in English.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;interface-strings&amp;#34;&amp;gt;Interface strings&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Quite often, you will be building your application in one language only.&amp;#xA;It makes little sense to write it in English first, when you are 100%&amp;#xA;certain that it will never be used in English.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In that case, simply don&amp;#39;t use the full text for the interface. Rather&amp;#xA;than &amp;lt;code&amp;gt;&amp;amp;quot;Thank you for registering&amp;amp;quot;.t&amp;lt;/code&amp;gt;, simply use tokens or symbols:&amp;#xA;&amp;lt;code&amp;gt;&amp;amp;quot;registration.messages.thankyou&amp;amp;quot;.t&amp;lt;/code&amp;gt;. Many languages and platforms&amp;#xA;already require this. But some have the default standards to write the&amp;#xA;full texts throughout your application. In that case, just ignore that&amp;#xA;standard, when English is too much hassle. Anything is better than&amp;#xA;having your code filled with pieces like &amp;lt;code&amp;gt;&amp;amp;quot;ขอบคุณสำหรับการลงทะเบียน&amp;amp;quot;.t&amp;lt;/code&amp;gt;.&amp;#xA;Only a Thai developer has any idea what is going on there; whether it is&amp;#xA;one word, two, a sentence or a token?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Obviously, this means using some kind of translation framework. But&amp;#xA;nearly every language and platform has these either built-in or as&amp;#xA;default add-on. And if not, you are probably working with the wrong&amp;#xA;tool, considering you are building a localized application in the first&amp;#xA;place.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;revision-control&amp;#34;&amp;gt;Revision control&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;While many teams do adhere to the guidelines that code, and everything&amp;#xA;around it is English, their commit-messages are local.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The funny thing, however, is that I have never seen a team write&amp;#xA;&amp;lt;em&amp;gt;only-Dutch&amp;lt;/em&amp;gt; commit-messages. They often mix English messages with Dutch.&amp;#xA;And when upstream software is English, you&amp;#39;ll see that even more.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In all cases, it is best  to just use English for commit-messages. For&amp;#xA;one, because you will be referring to (English!) code in the messages,&amp;#xA;but also, because mixing English and non-English requires more mental&amp;#xA;energy from the reviewer, when she or he has to toggle all the time.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;There is only one reason to write commit-messages in your native tongue:&amp;#xA;when otherwise people don&amp;#39;t write them at all. &amp;lt;em&amp;gt;Anything&amp;lt;/em&amp;gt; lowering the&amp;#xA;barrier that makes people write good messages is good. &amp;lt;em&amp;gt;No&amp;lt;/em&amp;gt;&amp;#xA;commit-messages is definitely worse than &amp;lt;em&amp;gt;non-English messages&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;bug-tracking&amp;#34;&amp;gt;Bug tracking&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When bugs are only managed within the team, just stick to English here&amp;#xA;too. You already write most of your documentation in English, your&amp;#xA;commit-messages in English and obviously your code too. Why then, write&amp;#xA;your bugs in another language. It only adds inconsistency and might&amp;#xA;(will!) even lead to confusion and more bugs. Like when the&amp;#xA;shopping-cart is called &amp;amp;quot;shopping-cart&amp;amp;quot; all over your product, but&amp;#xA;suddenly a bug-report mentions that the &amp;amp;quot;&amp;lt;em&amp;gt;ostukorvi&amp;lt;/em&amp;gt;&amp;amp;quot; is not working.&amp;#xA;Suddenly! Confusion over already established concepts like &amp;amp;quot;the&amp;#xA;shopping-cart-icon&amp;amp;quot;, versus the &amp;amp;quot;shopping-cart-process&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Only when you clients write bug-reports, should you leave them in your&amp;#xA;native tongue. Here too, you want to remove any friction from your&amp;#xA;client submitting reports (you do, don&amp;#39;t you?).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I&amp;#39;ve seen three successful methods here:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The Bug-reporting was closed for&amp;#xA;the client, they emailed issues to their contact. The contact then&amp;#xA;translated it to English &amp;lt;em&amp;gt;and&amp;lt;/em&amp;gt; translated it to an actual usable&amp;#xA;bug-report (i.e. turn &amp;amp;quot;problem with site, clicking on header not well&amp;amp;quot;&amp;#xA;into &amp;amp;quot;JS-overlay placing DIV over clickable area in header&amp;amp;quot;). Two wins.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The Bug-reports were made in Dutch and followed up in Dutch too. But&amp;#xA;from there, the person working on it translated everything. In&amp;#xA;commit-messages, when referred to an issue the title was quoted. Like&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&amp;amp;quot;Fixed issue 1337 &amp;amp;quot;Problemen met video. METEEN REPAREREN!!!&amp;amp;quot; by&amp;#xA;adding new CDN-urls to javascript for players.&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bug-reports were made in a separate environment and followed up in an&amp;#xA;English-only workflow. Feedback and updates for the clients were in&amp;#xA;Dutch, in the &amp;amp;quot;clients-issue-tracking&amp;amp;quot;, yet in-house everything remained&amp;#xA;English.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;What do you think? Is English bringing your productivity down?&amp;#xA;Do you consider your cultural heritage important enough, to decide&amp;#xA;to write code in your native tongue? Are you in a team where people&amp;#xA;don&amp;#39;t speak a word of English? (and how can they program, in that case?)&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>After almost twelve years of Drupal, I am saying goodbye</title>
   <link href="https://berk.es/2012/10/01/farewell-drupal/"/>
   <updated>2012-10-01T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2012/10/01/farewell-drupal/</id>
   <content type="html">&amp;lt;p&amp;gt;Over &amp;lt;a href=&amp;#34;https://drupal.org/user/1783&amp;#34;&amp;gt;eleven years&amp;lt;/a&amp;gt; ago, I got &amp;lt;a href=&amp;#34;https://drupal.org/user/2663&amp;#34;&amp;gt;involved in Drupal&amp;lt;/a&amp;gt;, after running two&amp;#xA;personal blogs on Drupal. A few years later, to my surprise, I found myself to be&amp;#xA;amongst the first few people to &amp;lt;a href=&amp;#34;http://web.archive.org/web/20040606050259/http://drupal.org/services&amp;#34;&amp;gt;offer commercial and professional Drupal services&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And boy, it was a ride. With no IT-education, other than&amp;#xA;&amp;lt;a href=&amp;#34;http://webmonkey.com&amp;#34;&amp;gt;webmonkey&amp;lt;/a&amp;gt;, the fantastic Drupal-community allowed me to&amp;#xA;piggyback and stand on shoulders. Great, strong shoulders.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I grew. I learned. And I learned how to program somewhat decent.&amp;#xA;I then, gradually I learned there were books and systems that allowed one to program &amp;lt;em&amp;gt;The Proper Way[tm]&amp;lt;/em&amp;gt;.&amp;#xA;Mostly because I was rolling out Drupal in&amp;#xA;Real-world projects every day, bumping into issues that, as I found, had&amp;#xA;been &amp;amp;quot;solved&amp;amp;quot; ages ago in academical books and studies. I learned that&amp;#xA;&amp;amp;quot;talk is silver, code is gold&amp;amp;quot; is simply not true. Code that is&amp;#xA;discussed, thought about, refactored, and then discussed again, is of a&amp;#xA;much higher quality than code that is &amp;amp;quot;just done&amp;amp;quot;. I learned that&amp;#xA;properly architecturing something, turns it into far more than gold. I&amp;#xA;often complained about &amp;amp;quot;&amp;lt;a href=&amp;#34;https://en.wikipedia.org/wiki/Blinders&amp;#34;&amp;gt;horse-tack&amp;lt;/a&amp;gt;-coding&amp;amp;quot; in Drupal.&amp;#xA;Where working on small, isolated issues was (and is) always preferred over refactoring larger parts. This has led to a lot of repetition, inconsistent APIs and very unpredictable behaviour. I learned about encapsulation, separation of concerns, loose-coupling and more such well-known principals. All of which Drupal lacks, or ignores. I had the feeling I grew faster than Drupal.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I also came across &amp;lt;em&amp;gt;Ruby on Rails&amp;lt;/em&amp;gt; and found that there were actually real,&amp;#xA;technical solutions for several of my gripes with Drupal. We are talking 2005,&amp;#xA;by now. Remember, I have no educational background. At first, I knew&amp;#xA;nothing about  OOP, other than what the great folks, the great shoulders&amp;#xA;of the Giants in Drupal, told me about it on IRC and in forums.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I learned a lot of new terms, methods and concepts. They were, and still are, an eye-opener.&amp;#xA;MVC was something that actually existed! Something actually existed, actually got designed and invented, solely to solve most of the issues I had with Theming in Drupal! And these design-patterns were not just to make technical people happier (or something to flamewar about), they actually&amp;#xA;solve many management and planning-issue too.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But, I had also grew into something of a local Drupal-expert and goto-guy. Serving&amp;#xA;most of the Netherlands as freelancer and Drupal consultant payed for my&amp;#xA;mortgage. I got called in on many failing Drupal-project. Got to help&amp;#xA;large companies and organisation in their swich to Drupal.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But toggling between Rails and Drupal-work, only made me see all the issues with Drupal&amp;#xA;more clearly: there was a lot of work for me to do, in order to make&amp;#xA;Drupal something as elegant and nice to work with as Rails. In an ever&amp;#xA;growing Drupalcommunity, I decided that my voice and code in this was&amp;#xA;only noise; especially since that community &amp;lt;a href=&amp;#34;http://buytaert.net/views-in-drupal-8&amp;#34;&amp;gt;clearly has a different idea&amp;#xA;about webdevelopment&amp;lt;/a&amp;gt; than I have.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Around that time (beginning of 2009), I co-founded &amp;lt;a href=&amp;#34;http://wizzlern.nl/&amp;#34;&amp;gt;Wizzlern&amp;lt;/a&amp;gt;. We developed&amp;#xA;training and education for Drupal.&amp;#xA;Training people allows you to meet professionals with lots of different backgrounds. People who have formal training in IT. People with much more in-the-field experience than I will probably ever have. And people who are critical. About things in Drupal.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But developing several training-courses also required me to dive really deep into the what and&amp;#xA;the why behind things. I suddenly had to paint the big picture around an&amp;#xA;inconsistent and weird API, answer questions like: Why are modules so hard to find? How come there are so few pretty&amp;#xA;themes, compared to wordpress? Why is it so much harder to use than&amp;#xA;Wordpress? Why can&amp;#39;t we find a decent workflow to develop in a team and deploy? Feedback from experienced webdevelopers (in Java, Python, .net and PHP-frameworks) made me realize even more that there was something amiss for me.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It became harder for me to defend that, harder to explain my passion for the system, its quircks and its community. They say, that once you have looked in the kitchen of your favorite restaurant, you never want to eat there again. That could be the case here. Or maybe it is because I am a vegetarian.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The realisation came slow. It took years. Drupal actually wants to be&amp;#xA;what it is now, not what I thought, or hoped, it wanted to be. My idea of &amp;lt;em&amp;gt;a toolkit, developed by webdevelopers, for ourselves, wedevelopers, to create ever better websites for our clients&amp;lt;/em&amp;gt;, was a not going to be found in Drupal.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dries&amp;#39;s comment on Copenhagens&amp;#39; keynote made this even more clear for&amp;#xA;me. He pointed out that Drupal should not focus on developers.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;amp;quot;Drupal made the webmaster redundant. In future it will make the webdeveloper&amp;#xA;redundant&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Unfortunately, I cannot find the exact quote, this one is from my vague memory and scriiblish note I made back then. I can only find &amp;lt;a href=&amp;#34;http://www.youtube.com/watch?v=RGfQHu4QA6c&amp;amp;amp;t=2m19s&amp;#34;&amp;gt;Dries answering my question&amp;lt;/a&amp;gt; about that quote. So his exact words are most probably different from what I phrased here!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;However, the bigger picture became more clear to me: something we have seen happening in Drupal for a while&amp;#xA;now: It focuses on the click-and-point development of website, not on the&amp;#xA;programming of a website. It really wants to be a CMS, albeit a flexible one. Rather than what I consider the future: a developer-platform that allows me &amp;lt;a href=&amp;#34;http://labs.talkingpointsmemo.com/2011/07/the-twilight-of-the-cms.php&amp;#34;&amp;gt;to build a CMS&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Development in with click-and-point, offers little challenge for me. Learning, where and how to find, evaluate&amp;#xA;and configure the umpteenth gallery-slider-view-plugin offers no challenge, nor satisfaction.&amp;#xA;I also found this approach of clicking together a site, to not satisfy&amp;#xA;my clients; not being able to deliver 100% what they wish. And I found&amp;#xA;it inefficient: especially when my programming skills grew. I could&amp;#xA;churn out a few objects and a hook or two that output the exact JSON I&amp;#xA;want, much faster than I would ever be able to click together some&amp;#xA;&amp;lt;a href=&amp;#34;https://drupal.org/project/services&amp;#34;&amp;gt;Services&amp;lt;/a&amp;gt; configuration.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In my search to more challenging Drupal-work, I helped several large&amp;#xA;sites, to solve some of their performance-issues. Helped many project with&amp;#xA;their problematic Drupal-development and -deployment. I taught many developers how&amp;#xA;to write themes, modules and how to deploy. Unfortunately the challenges&amp;#xA;did not revive my love for Drupal, but only took me further from it. I&amp;#xA;came to realize that most of the problems stem from the way the Drupal&amp;#xA;community prefers to do stuff. I even wrote some &amp;lt;a href=&amp;#34;http://webwereld.nl/opinie/106086/drupal-verkeerde-keus-voor-overheidssites--opinie-.html&amp;#34;&amp;gt;controversial&amp;#xA;opinions on that (Dutch)&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And I decided that it was time to make a shift. Find new projects&amp;#xA;outside of Drupal, work on some pet-projects and see if I found more&amp;#xA;challenges and opportunities to grow again, outside of Drupal.&amp;#xA;After nearly one year of flipping between Drupal and other projects made me&amp;#xA;realize that I had to cut all ties, in order to progress. That Drupal&amp;#xA;was never going to be the developer-tool I hoped it to become.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I will put down all my Drupal-work and finish the remaining&amp;#xA;few of my running Drupal-projects. Both those with clients, and those things like a stable release of tagadelic2 for Drupal 7. I will obviously announce those here.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Moving on to &amp;lt;a href=&amp;#34;http://nodejs.org/&amp;#34;&amp;gt;exciting&amp;lt;/a&amp;gt; new &amp;lt;a href=&amp;#34;http://www.mongodb.org/&amp;#34;&amp;gt;technologies&amp;lt;/a&amp;gt;, &amp;lt;a href=&amp;#34;http://www.sinatrarb.com/&amp;#34;&amp;gt;tools&amp;lt;/a&amp;gt; and &amp;lt;a href=&amp;#34;http://rubyonrails.org/&amp;#34;&amp;gt;development platforms&amp;lt;/a&amp;gt; that fit better with my workflow and programming-experience.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Goodbye, it was a fantastic bunch of shoulders to be allowed to stand on!&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>git deploy or: How I Learned to Stop Worrying and Love Deployment</title>
   <link href="https://berk.es/2012/08/03/git-deploy-or-how-i-learned-to-stop-worrying-and-love-deployment/"/>
   <updated>2012-08-03T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2012/08/03/git-deploy-or-how-i-learned-to-stop-worrying-and-love-deployment/</id>
   <content type="html">&amp;lt;p&amp;gt;One of the most surprising things I learned when moving away from&amp;#xA;Drupal-development towards Rails development, is the impact of fast and&amp;#xA;low-barrier deployment.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We all know that &amp;lt;a href=&amp;#34;http://www.slideshare.net/eaton/drupal-deployment-presentation&amp;#34;&amp;gt;Drupal&amp;#39;s deployment is severely broken&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I always thought fast and low-barrier deployment was just a&amp;#xA;nice-to-have, because it would bring down the actual hours spent on deploying. But it gives you so much more:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Very quick response on client requests.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;The possibility to &amp;lt;a href=&amp;#34;http://www.flickr.com/photos/bertboerland/2217345100/&amp;#34;&amp;gt;fail fast and fail cheap&amp;lt;/a&amp;gt;.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Be sure about few regressions and no failures.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Provide guarantees about uptime.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Allows for canonical releases&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It allows for more code and less config.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;very-quick-response-on-client-requests&amp;#34;&amp;gt;Very quick response on client requests.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;There is nothing that makes a client happier then sending in a question or request and seeing&amp;#xA;it live and online a few minutes later. And nothing pays your bills so well as a deployment.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If you have contracts that only pay after &amp;amp;quot;the entire project is&amp;#xA;finished&amp;amp;quot;, it most certainly includes a deployment. But more often it&amp;#xA;requires far more, because &amp;amp;quot;finished&amp;amp;quot; implies that all bugs are fixed&amp;#xA;and the client gets (almost exactly) what she asked for. In a waterfall&amp;#xA;this means many small releases, often on some internal &amp;amp;quot;acceptance&amp;amp;quot;&amp;#xA;site. When you can deliver the bugfixes and improvements faster, you&amp;#xA;will finish your project faster; the actual time between your first&amp;#xA;preview-delivery and the final one (the one that gets the invoice payed) is much shorter.&amp;#xA;This is when you are not working &amp;amp;quot;Agile&amp;amp;quot;. For &amp;amp;quot;Agile&amp;amp;quot;, such&amp;#xA;fast-and-often deployments are a requirement.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;the-possibility-to-fail-fast-and-fail-cheap&amp;#34;&amp;gt;The possibility to Fail Fast and Fail Cheap.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If you can push out releases the way my rabbit has babies, you can&amp;#xA;afford to have some fail. And because you can release five minutes later, again, you can fix such&amp;#xA;a fail with lightning speed.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A failing release that needs days of fixing, rollbacks and recovery-work&amp;#xA;is bad. But when that means that a next release requires even more&amp;#xA;planning, people on-call, meetings, and so on, you only make things&amp;#xA;worse: the release becomes even more expensive and&amp;#xA;cumbersome.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Being able to quickly recover from a mistake helps the client. She or he&amp;#xA;sees less downtime and has to pay less for &amp;amp;quot;deployment-time&amp;amp;quot; and on-call&amp;#xA;hosters and sysadmins.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But it mostly allows you to try new stuff. If some new UI-idea, or a&amp;#xA;fancy payment-method can be released with little or no effort, it&amp;#xA;becomes less hard for everyone to roll it back, if it proved less&amp;#xA;successful then anticipated. It makes the investment in some feature&amp;#xA;smaller, and therefore the barrier to simply throw it away when it&amp;#xA;fails, much lower.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;be-sure-about-regressions-and-no-failures&amp;#34;&amp;gt;Be sure about regressions and no failures.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;One condition for fast releases, is that you are certain about its&amp;#xA;quality. Most often you will have a test-suite in place to insure yourself against&amp;#xA;regressions.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This allows you to hit a few buttons and when everything turns up green,&amp;#xA;you can deploy. With arrogance. You know it will go well. And you know&amp;#xA;everything continues working.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The average Drupal-deployment calls for click-frenzies: developers,&amp;#xA;clients and other stakeholders click around on the site manually, for&amp;#xA;many hours, to ensure everything continues working.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A client who sees an unrelated part failing because you bugfixed another&amp;#xA;part, is an unhappy client. Even if you can explain that this complex&amp;#xA;access-permission-module touches not just the Wiki (where they asked for&amp;#xA;some access control) but also the blogs and forums (whom you forgot to&amp;#xA;check throughly against the new access-control).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A client, whom learned to check the&amp;#xA;entire, and ever-growing site for failures, on each and every release, is an unhappy (and busy)&amp;#xA;client. A developer who manually walks trough the entire site after each&amp;#xA;and every change is a very unproductive developer.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Testing as a security against failures is not a result of fast deployment, but a&amp;#xA;requirement to have them. If you want fast deployments you must have&amp;#xA;tests.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But when testing is not an option, at least the fast, and low-barrier&amp;#xA;deployment, allows for quick rollbacks and makes such failures much&amp;#xA;cheaper.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;provide-guarantees-about-uptime&amp;#34;&amp;gt;Provide guarantees about uptime.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With Drupal, you &amp;lt;a href=&amp;#34;http://drupalcode.org/project/drupal.git/blob/refs/heads/7.x:/UPGRADE.txt#l54&amp;#34;&amp;gt;must bring your site down&amp;lt;/a&amp;gt; during deployment. When you consider that your average Deployment of Drupal takes an hour or&amp;#xA;more, then no-one can afford to have several releases each week. Even&amp;#xA;when you deploy at 03:00 at night.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With slow, manual deployments, especially in cases like Drupal&amp;#39;s where&amp;#xA;the site is offline during the entire process, the downtime is&amp;#xA;unacceptable for many. My clients have often postponed releases for&amp;#xA;weeks, because of this; because they were afraid to bring down the site&amp;#xA;for even one hour. My last bigger Drupal release took four(!) hours of&amp;#xA;manual labor. Half a working-day downtime is &amp;lt;em&amp;gt;Not an Option&amp;lt;/em&amp;gt; for most.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;That &amp;amp;quot;fear of downtime&amp;amp;quot; and postponing of releases, is actually the worst part&amp;#xA;of it. It means that after developing cool new features, you have to&amp;#xA;wait weeks before it can be released (and the project can be finished&amp;#xA;and billed).&amp;#xA;Or worse: it means that you continue development and squeeze hundreds&amp;#xA;more bugfixes and features. Making the release even bigger and harder.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;allows-for-canonical-releases&amp;#34;&amp;gt;Allows for canonical releases&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Releasing often, means that you can release after each and every change&amp;#xA;too. The advantages of that, are huge.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You detect mistakes faster, rolling back is a piece of cake, and the&amp;#xA;overall impact of a change is much easier to grasp.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;it-allows-for-more-code-and-less-config&amp;#34;&amp;gt;It allows for more code and less config.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When a release is cheap, &amp;amp;quot;hardcoding&amp;amp;quot; stuff is cheap too. Instead of&amp;#xA;writing large and complex &amp;amp;quot;on vacation-message-systems&amp;amp;quot; in a CMS, you&amp;#xA;can simply set a &amp;amp;quot;We&amp;#39;ll be back august 31&amp;amp;quot; in the template. And deploy.&amp;#xA;Four minutes work.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Yet when the deployment is hard and expensive, you&amp;#39;ll need to allow such&amp;#xA;things in your application. Quite recently, did we implement a large and&amp;#xA;complex layout-system, with drag-and-drop placement of content-snippets&amp;#xA;in a CMS. It had a horrible effect on the system: The design became&amp;#xA;extremely complex, it had to&amp;#xA;cater every possible placement, performance of the application dropped&amp;#xA;to snail-speed, the code behind it all was large and complex and the UI of this &amp;amp;quot;in the CMS layout system&amp;amp;quot; required&amp;#xA;large and expensive projects in itself. A disaster.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Yet the reason behind the request for this layout-system was that the&amp;#xA;client wanted to change the placement of some content once or twice a&amp;#xA;year. With the required downtime for a deployment, the overall costs of&amp;#xA;one such deployment and all the friction that caused, it was no longer an&amp;#xA;option to call the development-team twice a year with the request for&amp;#xA;changing the layout. Building this large and complex beast was actually&amp;#xA;cheaper then having some (frontend)developer change some HTML around&amp;#xA;twice a year.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With fast deployments, the option to hardcode things is a very valuable&amp;#xA;option. It not only allows you to keep the application and its backend&amp;#xA;simple and lean, it is mostly a self-amplifying-loop: large and complex&amp;#xA;configurable systems require hard and often manual labor on releases.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Which is the main problem in Drupal&amp;#39;s deployment: you don&amp;#39;t code stuff, you configure&amp;#xA;it. And everything configured, cannot be deployed with a&amp;#xA;deployment-system, but has to be re-applied manually on a production site.&amp;#xA;Off course you can think of many tricks (like exporting and importing the&amp;#xA;configuration) but they don&amp;#39;t solve the underlying problem:&amp;#xA;manually applied configuration is not deployable like code is&amp;#xA;deployable. And when that configuration (such as the layout) lives in&amp;#xA;the same database as where your content lives, like in Drupal, the&amp;#xA;disaster is complete. The chaos is complete when such a manual&amp;#xA;configuration (like a new content-field, say &amp;amp;quot;teaser&amp;amp;quot;, gets introduced) requires a change in code too.&amp;#xA;Or when a code-change requires manual configuration.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;learn-to-stop-worrying-too&amp;#34;&amp;gt;Learn to stop worrying too&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;By coding most of the stuff, in a framework that supports automated&amp;#xA;testing and has a good migration-framework, you lower all the barriers.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;start-testing&amp;#34;&amp;gt;Start testing&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Testing allows you to be sure about what you are to release. No need to&amp;#xA;manually click trough the site on some &amp;amp;quot;Acceptance&amp;amp;quot; server, for hours,&amp;#xA;before releasing. But a few clicks after every change to assure yourself&amp;#xA;(and your client) every that worked in the past, still works.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;write-migrations&amp;#34;&amp;gt;Write migrations&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Instead of manually inserting stuff after a deployment, you should&amp;#xA;automate that.&amp;#xA;In rails, I write migrations to change the database. And rake-tasks for&amp;#xA;most of the other work, which can then be called from &amp;lt;a href=&amp;#34;http://stackoverflow.com/a/2632865/73673&amp;#34;&amp;gt;within the&amp;#xA;migration&amp;lt;/a&amp;gt;. Rake-tasks are&amp;#xA;dead-easy to write, mostly because they were designed especially for&amp;#xA;automating tasks.&amp;#xA;Every task that can be automated, needs no UI, requires no manual labor&amp;#xA;and, most importantly can be tested trough and trough.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;deploy-just-do-it&amp;#34;&amp;gt;Deploy. Just do it.&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I write my blog in &amp;lt;a href=&amp;#34;http://jekyllrb.com/&amp;#34;&amp;gt;Jekyll&amp;lt;/a&amp;gt;, where publishing a new&amp;#xA;article is a deployment. I don&amp;#39;t blog that often, but experiencing how&amp;#xA;simple and fast deployments are, has brought some of my deployment-fear&amp;#xA;down.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For the other systems, I use&amp;#xA;&amp;lt;a href=&amp;#34;https://github.com/mislav/git-deploy&amp;#34;&amp;gt;git-deploy&amp;lt;/a&amp;gt;, which ties the&amp;#xA;deployment on top of git. The setup is simple, but the deployment is&amp;#xA;ridiculously easy: &amp;lt;code&amp;gt;git push production&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I have attempted to rewrite git-deploy for Drupal, but so far, not to&amp;#xA;satisfaction. Drush, the Drupal counterpart for Rake is hard to get&amp;#xA;configured on each and every (different) production server out there.&amp;#xA;And is not very scripting-friendly. But already, it lowers the barrier&amp;#xA;so far that deploying becomes fun again.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Deploy to acceptance, test or development on daily basis. Have at least&amp;#xA;one place where you and your entire team deploy several times a day.&amp;#xA;It brings experience and makes everyone aware of the benefits of good&amp;#xA;automation of the process.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Once you start deploying five times a week, as opposed to once every two&amp;#xA;months, you will be a happier developer. You clients will see far more&amp;#xA;progress, faster responses and all your sites will improve much faster.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;What is holding you back from deploying once a day?&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Standardising the technology stack (or CMS) is silly</title>
   <link href="https://berk.es/2012/07/02/standardising-the-technology-stack-or-cms-is-silly/"/>
   <updated>2012-07-02T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2012/07/02/standardising-the-technology-stack-or-cms-is-silly/</id>
   <content type="html">&amp;lt;p&amp;gt;I have been involved in many &amp;amp;quot;we want to move all our websites to one&amp;#xA;technology&amp;amp;quot; projects. Mostly Drupal. Mostly where people wanted to move&amp;#xA;from a wild range of CMSes, forum-systems, blogging-tools and so on,&amp;#xA;written in various languages towards everything-in-Drupal. Smart move,&amp;#xA;you might think. Not often, I know now.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We all like car analogies, when it comes to websites, so here is one: my&amp;#xA;1986 Volkswagen T3 camper. A great car. I know its engine (watercooled&amp;#xA;1.6 boxer, best engine sound ever), I have gone trough its electrical&amp;#xA;cirquits a few times and know them pretty well now.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I know where to find and buy secondhand or deadstock&amp;#xA;replacement parts. I know the support-forums. And mostly, I can safely invest in knowing&amp;#xA;the ins-and outs of the car, because I am pretty sure I will buy&amp;#xA;Volkswagen Classic T3 Camper Vans for all my needs, forever, from now on. Or not.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/busje.jpg&amp;#34; alt=&amp;#34;Busje&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Off course not! No-one in his or her sane mind would buy a second such&amp;#xA;camper-van to do the daily shopping, or for your daily commute. If I needed&amp;#xA;a car for my daily commute, I would not buy a second,  rather expensive, fuel-gobbling,&amp;#xA;always-something-needs-fixing van with all the camping-stuff built in,&amp;#xA;but one that suits the needs much better, like a small, cheap Japanese car.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Yet this is the argument that goes when people choose to &amp;amp;quot;Always use Foo for every of our sites from now on&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It is different if you are a mechanic, or auto-shop. Analogy for&amp;#xA;&amp;amp;quot;webbuilder&amp;amp;quot;. It makes more sense then, to become an expert on one&amp;#xA;brand, year, engine-type of car. Specialists have a good place: we, the&amp;#xA;consumers and clients will be able to find them when we need them. And I&amp;#xA;prefer a Volkswagen mechanic with good Knowledge of the old-timer&amp;#xA;transporter vans to fix my car.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This goes just as well for webdevelopment. A Drupal-only-shop will&amp;#xA;be able to provide better sites for their clients,&amp;#xA;because they know all the weird little quirks in that CMS inside out.&amp;#xA;A dedicated python-developer knows much better where to find the most&amp;#xA;up-to-date resources and tools. And so on.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But as a consumer, as a client, you are much better off to choose your&amp;#xA;CMS, framework, programming language as it best fits the site, service or app you&amp;#xA;want building. Rather then shoe-horning each and every website you want&amp;#xA;into, say, Wordpress. I have seen it all: e-commerce platforms built on&amp;#xA;top of Wordpress? Wordpress turned into a JSON-webservice system. Great ideas molded into&amp;#xA;nothing, because it had to fit within what Wordpress can offer.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Do you really want to sacrifice all that, simply because you chose to standardise on that one CMS?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;No, what you want, is the same as with your car, or cars: a good&amp;#xA;and trustworthy mechanic who helps you with your old-timer camper-van as&amp;#xA;well as your husbands commute-car. Or at the very least, two such&amp;#xA;mechanics, one for each. You want trustworthy contractors and&amp;#xA;service-managers who can help you with whatever problem you have.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You want a good partner, or set of partners who can offer you the best&amp;#xA;tool and solution for each problem you have. And here is the other&amp;#xA;problem: if that partner is a shop who standardised on one technology,&amp;#xA;&amp;lt;em&amp;gt;that is what they are going to offer you&amp;lt;/em&amp;gt;. There is no Ruby-on-Rails&amp;#xA;developer who is going to say, &amp;lt;em&amp;gt;no, bad idea, you should not hire us.&amp;#xA;Rails is really not the best tool for your job&amp;lt;/em&amp;gt;. That is why you need&amp;#xA;some basic knowledge.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Again, like with the car: you walk into a Volkswagen-dealer to ask what&amp;#xA;car you need for your new building-company and you will end up with a&amp;#xA;Volkswagen. Even though a secondhand Toyota pickup is probably a much&amp;#xA;better fit. You need some basic idea of what you want and what fits.&amp;#xA;Most people who buy a car, know that newer, smaller cars use less fuel.&amp;#xA;And that (in The Netherlands, at least) driving Diesel is cheaper then&amp;#xA;Gasoline. Most people also know that an €180k is far more&amp;#xA;luxurious then a €400 Suzuki Alto. But you really should avoid&amp;#xA;shoehorning all your needs into one system. Because that one system is&amp;#xA;best at one area, you end up with a large range of websites that are not&amp;#xA;working too well, too expensive (or too cheap); you will end up like me,&amp;#xA;commuting to work by train, because doing it with my 1 liter&amp;#xA;on 8.5 kilometer fuelgobbling van, is just too expensive ;). You will&amp;#xA;end up using technology that is far from the best fit for a certain&amp;#xA;problem.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Only when all your sites are near 100% equal, then it&amp;#xA;makes sense to pick one and build every site with that technology.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And only if you insist on doing everything yourself, in-house, then it&amp;#xA;makes sense to standardise on one technology. Just like I would have to&amp;#xA;buy a second Volkswagen-van for my commuting, if I would insist on doing&amp;#xA;all the maintenance myself; learning the ins-and-outs of a, say, Japanese&amp;#xA;Diesel is too much for my simpleton programmer-brain.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Don&amp;#39;t you just hate these car-analogies in IT :) ?&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Map capslock to escape in Ubuntu Linux</title>
   <link href="https://berk.es/2012/06/21/map-capslock-to-escape-in-ubuntu-linux/"/>
   <updated>2012-06-21T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2012/06/21/map-capslock-to-escape-in-ubuntu-linux/</id>
   <content type="html">&amp;lt;p&amp;gt;If you are a Vim user, you probably want the &amp;lt;a href=&amp;#34;http://federico.galassi.net/2012/06/20/the-vim-holy-grail/&amp;#34;&amp;gt;escape key more at hand&amp;lt;/a&amp;gt;. On Ubuntu (And probably every Gnome3 desktop) this is really very easy. It comes with point-and-click tools to map your key to about everything you can think of; and more. If you google this problem, you find all sorts of xmonad CLI commands. They work too, but this is far easier for the stupid Linux-user like me.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ol&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Open &amp;lt;code&amp;gt;System settings&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Click &amp;lt;code&amp;gt;Keyboard Layout&amp;lt;/code&amp;gt;.&amp;#xA;&amp;lt;img src=&amp;#34;/images/inline/keymap_system_settings.png&amp;#34; alt=&amp;#34;system settings&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Click &amp;lt;code&amp;gt;Options&amp;lt;/code&amp;gt;.&amp;#xA;&amp;lt;img src=&amp;#34;/images/inline/keymap_layout.png&amp;#34; alt=&amp;#34;Keyboard Layout&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Under &amp;lt;code&amp;gt;Capslock&amp;lt;/code&amp;gt;, choose whatever you want.&amp;#xA;&amp;lt;img src=&amp;#34;/images/inline/keymap_options.png&amp;#34; alt=&amp;#34;Options&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ol&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I chose &amp;lt;code&amp;gt;switch Escape and Capslock&amp;lt;/code&amp;gt;. So that if I REALLY WANT TO SHOUT AT PEOPLE I STILL CAN! :).&amp;#xA;And it forces me to relearn the escape key.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Overheden moeten APIs aanbieden</title>
   <link href="https://berk.es/2012/06/03/overheden-moeten-apis-aanbieden/"/>
   <updated>2012-06-03T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2012/06/03/overheden-moeten-apis-aanbieden/</id>
   <content type="html">&amp;lt;p&amp;gt;President Obama heeft alle federale overheidsorganisaties in de&amp;#xA;Verenigde Staten twee maanden gegeven om &amp;lt;a href=&amp;#34;http://blog.apievangelist.com/2012/06/01/barak-obama-directs-all-federal-agencies-to-have-an-api/&amp;#34;&amp;gt;hun informatie via APIs aan te&amp;#xA;bieden&amp;lt;/a&amp;gt;.&amp;#xA;Dat getuigt van een goede visie.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Overheden zijn helaas geen webbouwers. Iedereen kent voorbeelden van&amp;#xA;verschrikkelijke websites van overheden. Regelmatig lees je over&amp;#xA;dramatisch verlopende ICT-projecten in gemeentes, rijksoverheden en&amp;#xA;aanverwanten.  Zo lijkt het bijvoorbeeld de gemiddelde Nederlandse&amp;#xA;gemeenteambtenaar maar niet te lukken om simpele zaken als &amp;amp;quot;wanneer&amp;#xA;komen ze het oudpapier ophalen&amp;amp;quot; te beantwoorden op hun site.  Vaak moet&amp;#xA;je eerst een studie doen naar de interne inrichting van die&amp;#xA;overheidsorganisatie, om te weten bij welke afdeling, welke&amp;#xA;subcontracter of welke semi-overheidsinstelling en welke website je moet&amp;#xA;zijn voor die informatie. En weet je aan het einde van je zoektocht meer&amp;#xA;over de contracten tussen gemeentes vuilophalers en&amp;#xA;paprapluorganisaties, dan over het oudpapierophaalrooster in je zomervakantie.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Overheden geven soms miljoenen uit aan relatief eenvoudige websites.&amp;#xA;Enorme projecten met enorme overheadkosten, supercomplexe interne&amp;#xA;functionaliteit en de vreemdste interne eisen. Zaken waar jij als&amp;#xA;eindgebruiker van zo&amp;#39;n website in het beste geval niks van merkt, maar&amp;#xA;meestal zult ervaren als ridicuul lastige navigatie en vreemde indeling.&amp;#xA;De oorzaak van de ridicule kosten, is dan ook de ontwikkeltijd.&amp;#xA;Meestal duren zulke bouwprojecten maanden. Of soms zelfs jaren.  En&amp;#xA;tegen de tijd dat die mooie nieuwe desktopsite van het gemeenteloket, de&amp;#xA;douane-invoerprijzen-overzichtten of de&amp;#xA;werken-aan-de-weg-voorlichtingssite af is, wil iedereen met een&amp;#xA;smartphone je site lezen.  Overheden, log en traag als ze zijn, kunnen&amp;#xA;het web gewoon niet bijbenen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Het mantra waarmee je de afgelopen regeerperiodes doodgegooid werd is&amp;#xA;juist hier erg op zijn plaats:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Laat het aan de markt over.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dat is wat Obama nu juist doorziet en verplicht doorvoert. De&amp;#xA;overheidsinstelling moet zijn of haar data beschikbaar maken via API&amp;#39;s.&amp;#xA;Waarmee &amp;amp;quot;de markt&amp;amp;quot;, snelle webontwikkelaars, slimme app-bouwers of heel&amp;#xA;onverwachte andere instanties de content kunnen gebruiken en opnieuw&amp;#xA;aanbieden. Een voorlichtingssite over wegwerkzaamheden is aardig.&amp;#xA;Maar die data direct in je TomTom is nog veel handiger. Of een mashup&amp;#xA;die die data samen met treintijden en parkeerplekken bij stations aanbied,&amp;#xA;is voor anderen nóg handiger. De onbegrijpelijke in- en uitvoerprijzen van de&amp;#xA;douane direct in je e-commerce pakket ingebouwd. Belastingoverzichten&amp;#xA;direct in je accountant-pakket.&amp;#xA;Natuurlijk zou rijskwaterstaat allemaal apps, integraties en mashups kunnen (laten) bouwen. Maar&amp;#xA;als ze hun wegwerkzaamheden gewoon via APIs (XML, SOAP, JSON)&amp;#xA;aanbieden, dan kan iedereen daarmee aan de slag. En kan een snelle&amp;#xA;app-bouwer dat voor hun doen. Die app-bouwer kan haar AnaarBeter App beter maken,&amp;#xA;geld ermee verdienen, en het belastinggeld hoeft er niet voor ingezet te&amp;#xA;worden. Iedereen wint.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In de VS komt daarbij nog een ander belangrijk element kijken: alle door&amp;#xA;overheden gemaakte data is publiek domein. Dus er zit geen auteursrecht&amp;#xA;op. Je mag het gebruiken, verkopen, hergebruiken en verspreiden&amp;#xA;zonder dat je daarvoor nog een keer hoeft te betalen. Het idee is even&amp;#xA;simpel als logisch: De belastingbetaler die al betaalt heeft, voor het maken&amp;#xA;van voorlichtingsfilmpjes, informatie, rapporten en wat dies meer zij,&amp;#xA;is gewoon eigenaar van dat werk.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Want alleen via APIs aanbieden is niet genoeg, je moet ook wettelijk&amp;#xA;regelen dat de informatie in die APIs gebruikt mag worden. Ook in&amp;#xA;commerciële toepassingen en in andere vormen dan waarop je het aanbied.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Natuurlijk kost het verplicht aanbieden van APIs ook geld. Nóg een eis&amp;#xA;waaraan een website moet voldoen. Wordt het project nóg duurder. Maar&amp;#xA;daarna kost snelle doorontwikkeling niks meer. Sterker nog, in veel&amp;#xA;gevallen zou een overheid eigenlijk niets meer dan een API hoeven te&amp;#xA;bouwen. Geen sites laten ontwerpen. Kunnen ze afzien van dure en&amp;#xA;ingewikkelde content managementsystemen met plaatjes en ondersteuning voor filmpjes.&amp;#xA;Maar gewoon een simpele SOAP-interface. En een PDFje erbij hoe je dat&amp;#xA;kunt gebruiken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dus, overheden, geef ons nou gewoon je informatie in een herbruikbaar&amp;#xA;formaat, in plaats van op onhandige, peperdure websites.&amp;#xA;En laat het aan de markt om er een mooie site omheen te&amp;#xA;bouwen; laat Google het indexeren en op hun kaarten weergeven,&amp;#xA;TV-producten het in je TV importeren of app-bouwers het in hun&amp;#xA;specialistische pakketten opnemen.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>3D printen walst de auteursrechten discussie binnen</title>
   <link href="https://berk.es/2012/05/31/3d-printen-walst-de-auteursrechten-discussie-binnen/"/>
   <updated>2012-05-31T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2012/05/31/3d-printen-walst-de-auteursrechten-discussie-binnen/</id>
   <content type="html">&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;http://www.wired.com/design/2012/05/3-d-printing-patent-law/&amp;#34;&amp;gt;Wired&amp;lt;/a&amp;gt;&amp;#xA;heeft een verhaal over Thomas Valenti. Als fan, ontwierp en&amp;#xA;printte hij poppetjes voor &amp;lt;a href=&amp;#34;http://www.games-workshop.com/gws/&amp;#34;&amp;gt;Warhammer&amp;lt;/a&amp;gt;. En deelde hij die op &amp;lt;a href=&amp;#34;http://www.thingiverse.com/tag:wargame&amp;#34;&amp;gt;Thingiverse&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En dat mag niet. De advocaten van Games Workshop, de Britse fabrikant&amp;#xA;van het spel, hebben de bestanden waarmee je de poppetjes kon printen,&amp;#xA;offline laten halen. En zo begint de &amp;amp;quot;oorlog&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een fabrikant als Alessi, die plastic prullaria mooi maakt; en zo een&amp;#xA;zoutvaatje voor enkele tientjes per stuk weet te verkopen, moet een&amp;#xA;ander businessmodel gaan zoeken. Net als fabrikanten van poppetjes voor&amp;#xA;spelletjes, dus.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Tot enkele jaren terug moest je flink investeren, mallen maken,&amp;#xA;dure spuitgietmachines aanschaffen, enzovoort, om die Alessi zoutvaatjes&amp;#xA;na te maken. Dat deden en doen, vast een heel aantal fabriekjes (in China),&amp;#xA;maar met wat moeite en een klein legertje advocaten, kun je deze namaak&amp;#xA;best uit de lokale blokker weren. Duizenden fabriekjes die namaak maken,&amp;#xA;is heel wat anders dan tientallen miljoenen mensen die datzelfde doen.&amp;#xA;Ooit kon de muziekindustrie de paar professionele cd kopieerders nog wel&amp;#xA;aanpakken. Tot de Cd-brander thuis en daarna Napster en trawanten.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een jaartje of anderhalf, en iedereen kent wel iemand met een 3d printer&amp;#xA;in de bijkeuken. Je hebt er nu al kant-en-klare voor &amp;lt;a href=&amp;#34;http://www.solidoodle.com/&amp;#34;&amp;gt;onder de $500&amp;lt;/a&amp;gt;.&amp;#xA;En met twee rechterhanden en een soldeerbout ben je al voor twee of driehonderd&amp;#xA;euro aan het printen. Nog niet een jaar geleden was de enige optie, die&amp;#xA;zelfmaak-optie en kostte zo een printer aan materiaal alleen al een paarduizend&amp;#xA;euro.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;3D-printen kan zich nog lang niet meten met de bekende Chinese&amp;#xA;plastic-perser, die rubberen eendjes, pennen en alle andere plastic&amp;#xA;spullen uitspuugt voor een paar cent per kilo. En dat zal het&amp;#xA;waarschijnlijk ook nooit kunnen. Net zoals je de geboortekaartjes ook&amp;#xA;bij de drukker laat drukken, ondanks die kleurenprinter thuis op je&amp;#xA;bureau.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar voor maatwerk, onderzoek, kunst en dus ook dure design-prullen is&amp;#xA;het een zeer geschikt apparaat. &amp;lt;a href=&amp;#34;http://www.elsevier.nl/web/Artikel/163531/Ondernemen-Eureka-Maar-wat-dan.htm&amp;#34;&amp;gt;Bedrijven&amp;lt;/a&amp;gt; die daarop draaien&amp;#xA;, of zich beschermd denken door octrooien op hun triviale producten, die&amp;#xA;moeten vernieuwen, of net als de muziekindustrie, ten onder gaan aan de&amp;#xA;revolutie die het Internet ons heeft gebracht.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wat die bedrijfsmodellen zijn? Leer van de Open Source&amp;#xA;softwarefabrikanten: verkoop niet het product, maar de diensten&amp;#xA;eromheen. Of verkoop extra&amp;#39;s, zoals service, eenvoud, garantie en&amp;#xA;doorontwikkeling. Of een van de andere geweldige ideeën waar nu nog&amp;#xA;niemand zelfs van durft te dromen.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Leave some Ink in the Well</title>
   <link href="https://berk.es/2012/05/30/leave-some-ink-in-the-well/"/>
   <updated>2012-05-30T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2012/05/30/leave-some-ink-in-the-well/</id>
   <content type="html">&amp;lt;p&amp;gt;Many writers know Hemingway&amp;#39;s tip:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Leave some water in the well.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;From&amp;#xA;&amp;lt;a href=&amp;#34;http://artisticwhim.com/blog/2005/12/leaving-a-little-ink-in-the-well/&amp;#34;&amp;gt;Impulse&amp;lt;/a&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;It’s a great idea: stop work­ing when you’re writing your best and it’s easier to start writing next time.&amp;#xA;You leave the work excited to return. You only face the dreaded Blank Page in the middle of your writing session, fresh from a success.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I found the same goes for Coding, albeit for different reasons. And, as&amp;#xA;a coder, I created a silly little script to help me with that.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/ink.png&amp;#34; alt=&amp;#34;Screenshot showing where I left the ink in the&amp;#xA;well&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;you-get-disturbed&amp;#34;&amp;gt;You get disturbed.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;I&amp;lt;/em&amp;gt; get disturbed a lot, during work, anyway. When finally you have mapped the&amp;#xA;entire stack of some weird application in your brain, your phone rings,&amp;#xA;alarms for some server start blinking, or you get some new twitter reply.&amp;#xA;Woosh, the effort is lost; the energy put into mapping everythin in your&amp;#xA;brain: wasted.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But even more often you have planned interuptions. Such as the end of&amp;#xA;the day, lunch, or some meeting.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;you-have-many-projects-intermingling&amp;#34;&amp;gt;You have many projects intermingling.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ideally, as a programmer, you work on one problem at a time. Lucky&amp;#xA;people work on one technology, in one environment with only one language&amp;#xA;and toolset.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Luckily I don&amp;#39;t, because that would bore me to death. I love working on&amp;#xA;multiple projects simultaneaously. Most of us do, if the average github&amp;#xA;commitlog is any proof.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So, you are working on some problem in, say, Drupal. And suddenly your&amp;#xA;time is, up, or a more urgent, say, CSS-issue needs solving. Or some&amp;#xA;server configuration needs sorting out, because backups or builds are&amp;#xA;failing.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;What do you do? Commit the unfinished state? &amp;lt;code&amp;gt;git stash&amp;lt;/code&amp;gt; it? just leave&amp;#xA;it as it is?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You could leave some ink in the well. Using a simple &amp;lt;code&amp;gt;@INK&amp;lt;/code&amp;gt; marker.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For example:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;by_ranking&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;# @INK: the rank attribute is not updated or filled&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#       in the database, it seems. @TODO: make a &amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#       migration to add this field to the database,&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#       then an after_update hook to actually fill this value.&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  sort {&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;|&amp;lt;/span&amp;gt;a,b&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;|&amp;lt;/span&amp;gt; a&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;rank&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;to_f &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&amp;amp;lt;&amp;lt;/span&amp;gt; b&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;rank&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;to_f}&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;A simple &amp;#39;@INK&amp;#39; with a comment. &amp;lt;code&amp;gt;@INK&amp;lt;/code&amp;gt; is a different marker then&amp;#xA;&amp;lt;code&amp;gt;@FIXME&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;@TODO&amp;lt;/code&amp;gt;. Actually, &amp;lt;code&amp;gt;@INK&amp;lt;/code&amp;gt; is &amp;lt;em&amp;gt;also&amp;lt;/em&amp;gt; a TODO.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Then, whenever you pick up a project, you look for the @INK, have your&amp;#xA;&amp;lt;em&amp;gt;aha-moment&amp;lt;/em&amp;gt; and can jump right in where you left.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The only problem I have with this, is when you get disturbed, you often&amp;#xA;don&amp;#39;t have the time to dump your thoughts and mental-state into such a&amp;#xA;comment. But telling the person behind you to &amp;amp;quot;wait a sec till I finish&amp;#xA;this sentence&amp;amp;quot; is not too strange.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Some rules apply:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;There can be only one &amp;lt;code&amp;gt;@INK&amp;lt;/code&amp;gt;. Ever. (A project can have many &amp;lt;code&amp;gt;@FIXME&amp;lt;/code&amp;gt;&amp;#39;s or&amp;#xA;&amp;lt;code&amp;gt;@TODO&amp;lt;/code&amp;gt;&amp;#39;s)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;code&amp;gt;@INK&amp;lt;/code&amp;gt; marked code may never be pushed to other people&amp;#39;s or a central&amp;#xA;repository. They are your private markers.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Whenever you open a project, you &amp;lt;em&amp;gt;must&amp;lt;/em&amp;gt; search for the ink first. Then&amp;#xA;either remove it, replace it with a TODO or start where you left.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And with a simple script, you can find your ink in the well:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-bash&amp;#34; data-lang=&amp;#34;bash&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#!/bin/bash&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;echo &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;-- Last four commits --&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;git log --graph --pretty&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt;format:&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)&amp;amp;lt;%an&amp;amp;gt;%Creset&amp;amp;#39;&amp;lt;/span&amp;gt; --abbrev-commit --date&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt;relative -n4&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;echo &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;-- Git status --&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;git status -s&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;echo &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;-- The INK --&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;ack-grep -C &amp;lt;span style=&amp;#34;color:#ae81ff&amp;#34;&amp;gt;4&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;@INK&amp;amp;#34;&amp;lt;/span&amp;gt; --all&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;It gets some context with two git commands, one to render a short,&amp;#xA;&amp;lt;a href=&amp;#34;http://www.jukie.net/bart/blog/pimping-out-git-log&amp;#34;&amp;gt;pretty git log&amp;lt;/a&amp;gt;,&amp;#xA;to learn what you did last, before you left the project.&amp;#xA;The other to show the changes in the repository: uncomitted, changed and&amp;#xA;removed files and such.&amp;#xA;And the last shows you where you placed the &amp;lt;code&amp;gt;@INK&amp;lt;/code&amp;gt; marker, with a few&amp;#xA;lines of context.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Instead of &amp;lt;a href=&amp;#34;http://betterthangrep.com/&amp;#34;&amp;gt;ack-grep&amp;lt;/a&amp;gt;, you can use &amp;lt;code&amp;gt;grep&amp;lt;/code&amp;gt; if&amp;#xA;you prefer. I&amp;#39;ts just slower and needs additional &amp;amp;quot;--recursive *&amp;amp;quot;&amp;#xA;parameters.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;There, it works very &amp;lt;code&amp;gt;@INK [berkes wo may 30 15:09:51 CEST 2012]: write some catchy finishing line; the postman is ringing at the door&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>DrupalJam presentatie over Microframeworks</title>
   <link href="https://berk.es/2012/05/25/drupaljam-presentatie-over-microframeworks/"/>
   <updated>2012-05-25T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2012/05/25/drupaljam-presentatie-over-microframeworks/</id>
   <content type="html">&amp;lt;p&amp;gt;Voor &amp;lt;a href=&amp;#34;http://drupaljam.nl/&amp;#34;&amp;gt;DrupalJam 2012&amp;lt;/a&amp;gt; werd mij gevraagd een sessievoorstel in te&amp;#xA;dienen. Het leek me passend om een aan Drupal-gerelateerd onderwerp te&amp;#xA;zoeken; welke niet direct over Drupal zélf gaat.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Vandaar: &amp;lt;a href=&amp;#34;http://drupaljam.nl/sessie/microframeworks-queen-drupal-en-haar-onderdanen&amp;#34;&amp;gt;Microframeworks&amp;lt;/a&amp;gt;. En hoe je deze in een Drupalomgeving of -project kunt inzetten.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;update-resources&amp;#34;&amp;gt;Update: resources&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;/microframeworks/#/this-qr&amp;#34;&amp;gt;De presentatie&amp;lt;/a&amp;gt; (&amp;lt;a href=&amp;#34;https://github.com/berkes/microframeworks/tree/develop&amp;#34;&amp;gt;source&amp;lt;/a&amp;gt;)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;https://github.com/berkes/microframeworks-questions&amp;#34;&amp;gt;De voorbeeld applicatie, &amp;amp;quot;questions&amp;amp;quot;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;/microframeworks/#/voorbeelden&amp;#34;&amp;gt;Overzicht van frameworks voor PHP, Python en Ruby&amp;lt;/a&amp;gt;.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;h1 id=&amp;#34;microframenworks-queen-drupal-en-haar-onderdanen&amp;#34;&amp;gt;Microframenworks: Queen Drupal en haar onderdanen&amp;lt;/h1&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;amp;quot;MobileFooWizards heeft voor ons een iphone-app gemaakt, of we op de&amp;#xA;Drupalsite even een JSON-feed kunnen aanbieden van de nieuwsberichten&amp;#xA;met de data zoals in dit mailtje staat&amp;amp;quot;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Klinkt als een eitje. Toch? Beetje views klikken, klaar! Maar dan komt&amp;#xA;het: hoe bied je mobielvriendelijke plaatjes in die content aan? Hoe&amp;#xA;zorg je dat toekomstige versies van de app andere JSON kunnen lezen? Hoe&amp;#xA;scherm je het af? Schaalt het wel? Voor je het weet is het een enorm&amp;#xA;project, met allerhande afhankelijkheden, deployments enzovoort.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;het-microframework-eenvoud&amp;#34;&amp;gt;Het microframework: eenvoud&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een goede ontwikkelaar kan niet zozeer alle problemen oplossen, maar kan&amp;#xA;ieder probleem opdelen in makkelijk oplosbare, kleine probleempjes.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Ik, zojuist&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Microframeworks zijn gereedschappen waarmee je enorm simpele, piepkleine&amp;#xA;webapplicaties bouwt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Zo een web-applicatie kan perfect met Drupal samenwerken: Het kan Drupal&amp;#xA;werk uit handen nemen, voorgekauwde informatie aanbieden enzovoort. Een&amp;#xA;ideale onderdaan voor je Drupalsite.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Door een piepkleine website te bouwen naast Drupal dat bijvoorbeeld één&amp;#xA;enkele JSON-feed aanbied kun je Drupal veel werk uit handen nemen. Je&amp;#xA;verdeelt je project in onafhankelijke, losse componenten, die allemaal één ding doen en&amp;#xA;dat heel goed kunnen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;sessie&amp;#34;&amp;gt;Sessie&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In deze sessie laat ik verschillende scenario&amp;#39;s zien waar een&amp;#xA;microframework samen met Drupal een gouden combinatie blijkt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We kijken naar het iPhone-app-probleem, maar ook naar hoe we informatie&amp;#xA;Drupal ín kunnen krijgen. En we kijken hoe we Drupal kunnen koppelen aan&amp;#xA;externe informatie en diensten door er een microframework tussen te&amp;#xA;plaatsen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In de sessie zal hier en daar wat code voorbijkomen, maar dat is slechts ter illustratie. Uiteraard zul je moeten&amp;#xA;programmeren om een microframework in te kunnen gaan zetten; maar in&amp;#xA;deze sessie kijken we meer op een afstandje hiernaar.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>A new blog</title>
   <link href="https://berk.es/2012/05/24/a-new-blog/"/>
   <updated>2012-05-24T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2012/05/24/a-new-blog/</id>
   <content type="html">&amp;lt;p&amp;gt;There. Finally. A new blog. My old blog was found on&amp;#xA;&amp;lt;em&amp;gt;bler.webschuur.com&amp;lt;/em&amp;gt; and &amp;lt;em&amp;gt;webschuur.com&amp;lt;/em&amp;gt;. Which is also the name of my&amp;#xA;company: &amp;lt;em&amp;gt;webschuur.com&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For several times, I attempted to rewrite everything, but after 80% of the&amp;#xA;work done, I found out was doing it all wrong. And started over again.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;They say the cobbler&amp;#39;s children go the worst shod.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;My blogs and &amp;lt;em&amp;gt;bler&amp;lt;/em&amp;gt; &amp;lt;em&amp;gt;webschuur.com&amp;lt;/em&amp;gt;, respectively, my personal,&amp;#xA;Dutch and English business blog, had to be merged. The CMS powering&amp;#xA;them, Drupal, needed a complete rebuild. And it really needed a redesign. Oh. And I had a much cooler domain name: berk.es.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;All content has been migrated, but not everything has been repaired, there&amp;#xA;are thousands of articles dating back to 2001 in the database. In&amp;#xA;all kinds of formats, with all sorts of extra content, and many articles&amp;#xA;were broken for years. The coming weeks, I will have to repair them one&amp;#xA;at a time. By hand.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And the comments are not yet migrated. First, I need to weed out all&amp;#xA;spam and then migrate everything to&amp;#xA;&amp;lt;a href=&amp;#34;https://disqus.com/admin/moderate/&amp;#34;&amp;gt;Disqus&amp;lt;/a&amp;gt;. A hell of a job.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;jekyll&amp;#34;&amp;gt;Jekyll&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This blog runs on &amp;lt;a href=&amp;#34;http://jekyllrb.com/&amp;#34;&amp;gt;Jekyll&amp;lt;/a&amp;gt;. Jekyll is&amp;#xA;super simple: it uses a text file for each article and generates&amp;#xA;a site out of all these files. Which you then upload. This sounds old fashioned,&amp;#xA;but is actually super effective: no CMS, no database, no complex&amp;#xA;server software, no security upgrades, no possible intrusions in your CMS,&amp;#xA;and so on. Faster than a site so you can not get, as safer than&amp;#xA;CMS is not even in theory, and the simplicity is unimaginable. And&amp;#xA;hosting it on a professional environment is cheap (or free).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;That is, if you think editing text files is easy.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;More technical: you write the text in HTML or markdown. Which is&amp;#xA;then converted to clean HTML. You manage all text files in a&amp;#xA;revision control system (git, in my case) and that revision control&amp;#xA;provides the deployment: it generates and upload the site.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;why-not-drupal&amp;#34;&amp;gt;Why not Drupal?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Both sites were running Drupal. Both were FUBAR: totall loss.&amp;#xA;Upgrading was not possible (anymore) and troubleshooting or bugfixing&amp;#xA;yielded only but more problems:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Spam: I have tried all spamsolutions for Drupal, but with&amp;#xA;sometimes millions (!) of spam posts per day there is always some&amp;#xA;slipping through. 1% of 1 million is still 10000. Sometimes, after not&amp;#xA;looking at my site over a week, I found hundreds of thousands of spams&amp;#xA;that still seeped trough: a self-maintaining effect: published spam attracts spammers.&amp;#xA;&amp;lt;strong&amp;gt;Solution&amp;lt;/strong&amp;gt;: A new comment system Disqus. This requires a&amp;#xA;difficult migration.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Old modules, old content: During time I have updated Drupal&amp;#xA;hundreds of times and upgraded seven times. Always some minor thing&amp;#xA;broke; there was no upgrade for a certain module. A table did not update&amp;#xA;completely right and so on. The&amp;#xA;result is a broken database, lots of broken content and lint. The &amp;lt;strong&amp;gt;solution&amp;lt;/strong&amp;gt; is a complete rebuilding. And&amp;#xA;full export and import of all the old articles.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Drupal has become extremely big and bloated over time. Too heavy for a&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;small little blog. You notice this very well when you get a million&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;hits from spammers. But also briefly when the site has been busier&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;(e.g. a post on Reddit). My&amp;#xA;&amp;lt;a href=&amp;#34;https://www.linode.com/&amp;#34;&amp;gt;Tiny server&amp;lt;/a&amp;gt; may power five small drupalsite.&amp;#xA;Really, no more. That is ridiculous: a dedicated VPS for five tiny&amp;#xA;sites. An upgrade to Drupal7 of all five sites would mean that I have to&amp;#xA;order a larger or second VPS: ridiculous; an extra twohundredsomething&amp;#xA;Dollar per year for just five tiny sites. Or that I need to start&amp;#xA;fiddling with proxies and memcache. For five small sites: ridiculous.&amp;#xA;&amp;lt;strong&amp;gt;Solution&amp;lt;/strong&amp;gt; continue with the old, yet better perform-using Drupal,&amp;#xA;or find another CMS.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I also had some small, simple requirements for a new blog:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;HTML5 (and CSS3) for the layout.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Mobile Friendly.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Fine HTML.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;No / minimal administration and security updates.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Bilingual content.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Cheap hosting.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Drupal7 can, with lots of effort, serve somewhat clean, responsive&amp;#xA;HTML(5) and CSS3. But after implementing 80% of my design (and&amp;#xA;development of an entire theme-engine just for this), I decided that this&amp;#xA;is nonsense. So I decided to look for something better, that gives me&amp;#xA;full control over the layout.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;rails&amp;#34;&amp;gt;Rails?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I had converted my entire site in &amp;lt;a href=&amp;#34;http://rubyonrails.org/&amp;#34;&amp;gt;Ruby on Rails&amp;lt;/a&amp;gt;.&amp;#xA;The content migration was finished, a pretty secure commenting system&amp;#xA;was done. And it was full of fun gadgets (like Twitter, Reddit and&amp;#xA;Facebook scraper: copy the comments posted there onto my blog). And it&amp;#xA;even performed nearly as good as Drupal7. With some tweaking even better&amp;#xA;then Drupal 5. Just finish the last 20% and release it.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Then I made a few steps backwards, looked at it from a distance: a rather&amp;#xA;large, self-written CMS on Rails, just to publish a super simple blog. I&amp;#xA;must be mad: even that last 20% probably costs more work than a few&amp;#xA;evenings of Jekyll hacking.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;there-is-why-jekyll&amp;#34;&amp;gt;There is why. Jekyll.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And now some more blogging.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Een nieuw weblog</title>
   <link href="https://berk.es/2012/05/24/een-nieuw-weblog/"/>
   <updated>2012-05-24T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2012/05/24/een-nieuw-weblog/</id>
   <content type="html">&amp;lt;p&amp;gt;Zo. Eindelijk. Een nieuw weblog. Mijn oude blog stond op&amp;#xA;&amp;lt;em&amp;gt;bler.webschuur.com&amp;lt;/em&amp;gt; en op &amp;lt;em&amp;gt;webschuur.com&amp;lt;/em&amp;gt;. Zo heet ook mijn bedrijf:&amp;#xA;&amp;lt;em&amp;gt;webschuur.com&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Al diverse malen heb ik geprobeerd om alles om te gooien, om na 80% van&amp;#xA;het werk erachter te komen dat ik verkeerd bezig was.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;#39;t is bij de loodgieter dat het kraantje lekt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Mijn blogs &amp;lt;em&amp;gt;bler&amp;lt;/em&amp;gt; en &amp;lt;em&amp;gt;webschuur.com&amp;lt;/em&amp;gt;, respectievelijk mijn persoonlijke,&amp;#xA;Nederlandse en zakelijke Engelse blog, moesten samengevoegd. Waar ze op&amp;#xA;draaiden, Drupal moest geheel vernieuwd. En het design moest hoognodig&amp;#xA;op de schop. Oh. En ik had een veel cooler domein: berk.es.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Alle content is gemigreerd, maar nog niet alles is gerepareerd: er&amp;#xA;zitten duizenden artikelen die teruggaan tot 2001 in de database. In&amp;#xA;allerhande formats, met allerhande extra content, en veel artikelen&amp;#xA;bleken al jaren kapot. Dat moet met de hand aangepast gaan worden. Dat&amp;#xA;is een klus voor de komende weken.&amp;#xA;Alle reacties zijn nog niet gemigreerd. De spam moet er eerst helemaal&amp;#xA;uitgefilterd worden en dan moet alles geconverteerd en gemigreerd worden&amp;#xA;naar &amp;lt;a href=&amp;#34;https://disqus.com/admin/moderate/&amp;#34;&amp;gt;disqus&amp;lt;/a&amp;gt;. Een hels karwij,&amp;#xA;ook voor de komende weken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;jekyll&amp;#34;&amp;gt;Jekyll&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Deze blog draait op &amp;lt;a href=&amp;#34;http://jekyllrb.com/&amp;#34;&amp;gt;Jekyll&amp;lt;/a&amp;gt;. Jekyll is&amp;#xA;supereenvoudig: het gebruikt een tekstbestandje per artikel en genereert&amp;#xA;van al deze bestandjes een site. Die upload je dan. Klinkt ouderwets,&amp;#xA;maar is vooral supereffectief: geen CMS, geen database, geen complexe&amp;#xA;serversoftware, geen veiligheidsupgrades, mogelijke inbraken in je CMS,&amp;#xA;enzovoort. Sneller dan zo een site kun je niet krijgen; veiliger dan zo&amp;#xA;een CMS bestaat zelfs in theorie niet; en de eenvoud is onvoorstelbaar.&amp;#xA;En het hosten is zelfs op een professionele omgeving zo goed als&amp;#xA;gratis.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Althans, als je tekstbestandjes bewerken makkelijk vind.&amp;#xA;Wat technischer: je schrijft de tekst in HTML of in markdown. Dat wordt&amp;#xA;dan omgezet naar schone HTML. Je beheert alle tekstbestandjes met een&amp;#xA;revisiebeheersysteem (git, in mijn geval) en dat revisiebeheersysteem&amp;#xA;zorgt ook voor de deployment; het genereren en uploaden van de site.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;waarom-geen-drupal&amp;#34;&amp;gt;Waarom geen Drupal?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Allebei de sites draaiden Drupal. Beide waren FUBAR: totall loss.&amp;#xA;Upgraden ging niet (meer) en oplossen van problemen leverde alleen nog&amp;#xA;maar meer problemen op:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Spam: Ik heb alle spam-oplossingen voor Drupal geprobeerd, maar met&amp;#xA;soms miljoenen (!) spam-posts per dag slippen er altijd een paar&amp;#xA;doorheen. 1% van 1miljoen is nog altijd 10000. Als ik soms een weekje&amp;#xA;niet keek, had ik honderduizenden spams die toch doorgesijpeld waren:&amp;#xA;een versterkend effect: want gepubliceerde spam trekt spammers aan.&amp;#xA;&amp;lt;strong&amp;gt;Oplossing&amp;lt;/strong&amp;gt;: een nieuw commentsysteem: Disqus. Dat vergt een&amp;#xA;moeilijke migratie.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Oude modules, oude content: In de loop van de tijd heb ik Drupal&amp;#xA;honderden keren geüpdate en iets van zeven keer geüpgrade. Altijd ging&amp;#xA;er wel iets kleins mis, of was er voor een module geen upgrade. Het&amp;#xA;resultaat is een kapotte database, veel kapotte content en enorm veel&amp;#xA;achtergebleven puin. De &amp;lt;strong&amp;gt;oplossing&amp;lt;/strong&amp;gt; is een volledige herbouw. En een&amp;#xA;volledige export en import van alle oude artikelen.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Drupal is enorm zwaar geworden. Veel te zwaar voor een klein blogje.&amp;#xA;Dat merk je extra goed als je een miljoen hits van spammers krijgt.&amp;#xA;Maar dat merk je ook als het heel eventjes wat drukker is. Mijn&amp;#xA;&amp;lt;a href=&amp;#34;https://www.linode.com/&amp;#34;&amp;gt;servertje&amp;lt;/a&amp;gt; kan vijf drupalsitejes aan. Echt&amp;#xA;niet meer. Dat is belachelijk: één dedicated VPS voor vijf piepkleine&amp;#xA;sites. Upgraden naar Drupal7 van alle vijf zou betekenen dat ik een&amp;#xA;grotere of tweede VPS erbij moet bestellen: belachelijk. Of dat ik met&amp;#xA;proxies, memcache enzovoort in de weer moet. Voor vijf kleine sites:&amp;#xA;belachelijk.&amp;#xA;&amp;lt;strong&amp;gt;Oplossing&amp;lt;/strong&amp;gt; bij de oude, beter performende Drupal blijven, of een&amp;#xA;ander CMS.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bovendien had ik wat kleine, simpele eisen aan een nieuw Blog:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;HTML5 (en CSS3) voor de layout.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Mobielvriendelijk.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Schone HTML.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Geen/nauwelijks beheer en veiligheidupdates.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Tweetalige content.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Drupal7 kan met veel pijn en moeite enigszins schone HTML5 uitserveren.&amp;#xA;Maar na 80% van mijn design geïmplementeerd te hebben (en daarvoor een&amp;#xA;heel nieuwe theme engine geschreven te hebben) besloot ik dat dit&amp;#xA;onzinnig is. Ik kon beter naar iets op zoek dat mij meer controle over&amp;#xA;de layout enzo gaf.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;rails&amp;#34;&amp;gt;Rails?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik heb mijn hele site omgebouwd naar &amp;lt;a href=&amp;#34;http://rubyonrails.org/&amp;#34;&amp;gt;Ruby on Rails&amp;lt;/a&amp;gt;.&amp;#xA;De content-migratie was klaar, een mooi spamveilig commenting systeem&amp;#xA;was af. En het zat vol met leuke gadgets (zoals een twitter, reddit en&amp;#xA;facebook scraper: plaatst reacties aldaar op mijn blog). En het performde bijna net zo&amp;#xA;goed als Drupal7. Met wat tweaken zelfs nog beter. Nog even de laatste&amp;#xA;20% afronden en klaar.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Totdat ik wat stappen achteruit deed en nog eens goed naar het project&amp;#xA;keek: een vrij groot, zelfgeschreven CMS, op Rails, om een supersimpel&amp;#xA;blogje te publiceren. Ik ben gek ook: zelfs die laatste 20% is&amp;#xA;waarschijnlijk nog meer werk dan even wat aan Jekyll hacken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;vandaar-jekyll&amp;#34;&amp;gt;Vandaar. Jekyll.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En nu weer wat vaker bloggen.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Ultimaker 3D printer. </title>
   <link href="https://berk.es/2012/04/02/ultimaker-3d-printer/"/>
   <updated>2012-04-02T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2012/04/02/ultimaker-3d-printer/</id>
   <content type="html">&amp;lt;p&amp;gt;Yes! Hij is binnen, hij werkt en ik ben dolgelukkig: Mijn &amp;lt;a href=&amp;#34;https://shop.ultimaker.com/&amp;#34;&amp;gt;Ultimaker&amp;lt;/a&amp;gt; 3D-printer.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;[&amp;lt;img src=&amp;#34;http://berkes.openphoto.me/photo/12/create/68f27/600x950.jpg&amp;#34; alt=&amp;#34;Ultimaker printing black lines of plastic as bottom&amp;#34; title=&amp;#34;Ultimaker in actie&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;http://berkes.openphoto.me/photos/tags-ultimaker/list?size=440x292xCR&amp;#34;&amp;gt;Meer Foto&amp;#39;s »&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Voor diegenen die het concept helemaal niet kennen: dit zijn zelfbouw-apparaten waarmee je (vooral) plastic dingen kun printen. Daadwerkelijke, fysieke dingen. Ik ervan overtuigd dat we aan het begin van een tweede revolutie staan; en ik wil daar uiteraard dolgraag bijzijn.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Vandaar dus deze aankoop. Na lang zoeken bleek vooral vanwege importheffingen, en mijn zeer geringe ervaring met de materie. de bovengenoemde Ultimaker de beste koop. Bekender is de Amerikaanse &amp;lt;a href=&amp;#34;http://store.makerbot.com/&amp;#34;&amp;gt;Makerbot&amp;lt;/a&amp;gt;, maar die is alles bij elkaar duurder om naar Nederland te krijgen dat onze eigen Ultimaker. De goedkoopste, mooiste, maar ook meest gedurfde viriant printer is de &amp;lt;a href=&amp;#34;http://reprap.org/wiki/Mendel&amp;#34;&amp;gt;Mendel&amp;lt;/a&amp;gt;, uit de series van echte zelfbouwprinters. Gedurfd, want je koopt vooral losse onderdelen (pijpen, schroeven, draadjes, motoren enzovoort) en print dan de printer zélf uit!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maargoed. Mijn Ultimaker print. Het zwakste stuk is vooralsnog de software waarmee je alles aanstuurt en converteert; dat blijkt behoorlijk foutgevoelig, heeft versie-problemen, en werkt eigenlijk gewoon niet lekker. Gelukkig heb ik juist daar redelijk wat kennis, dus daar is voor mij nog een taak weggelegd.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik heb nog geen concreet plan. Genoeg ideeën om uit te zoeken, uit te proberen en mee te spelen. Al ben ik juist vooral op zoek naar andermans ideeën. Bijvoorbeeld het koppelen aan e-commerce-software. Of direct de verpakking, met verzendlabels om het &amp;amp;quot;product&amp;amp;quot; heen printen. Misschien software inrichten waarmee je maatwerk kunt printen. Zoals namen of logo&amp;#39;s direct vanuit een webshop in het 3Dmodel verwerken. En Windmolens. Fucking zelfprintbare windmolens. Natuurlijk. Waar ik echter vooral naar zoek, zijn nóg meer ideeën. En dan bedoel ik niet meteen een idee voor de zoveelste &amp;lt;a href=&amp;#34;http://www.thingiverse.com/search?q=opener&amp;#34;&amp;gt;leuke flesopener&amp;lt;/a&amp;gt; (mag natuurlijk ook), maar vooral &amp;lt;a href=&amp;#34;http://vimeo.com/16106427&amp;#34;&amp;gt;breder, groter&amp;lt;/a&amp;gt;! &amp;lt;a href=&amp;#34;http://artinfo.com/news/story/761597/redesigning-reality-how-3-d-printing-is-shaping-the-future-of-art-engineering-and-everything-else&amp;#34;&amp;gt;Nicemarkten&amp;lt;/a&amp;gt;, &amp;lt;a href=&amp;#34;http://www.thingiverse.com/search?q=opener&amp;#34;&amp;gt;alimme bedrijfsmodellen&amp;lt;/a&amp;gt;; handige inzet; vooral dingen die door deze 3D-&amp;lt;a href=&amp;#34;http://tpb.piratenpartij.nl/browse/605&amp;#34;&amp;gt;printerrevolutie&amp;lt;/a&amp;gt; opeens wél kunnen, maar die eerst niet konden, niet toegankelijk waren, of gewoon onbetaalbaar.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Voor al deze ideeën staat mijn printer (en mijn hulp en dus tijd) ook tot jou beschikking. Nu nog gewoon bij mij thuis, maar bij wat animo ga ik op zoek naar een wat meer openbare ruimte. Dus stuur me je idee, of kom het langsbrengen. Of kom gewoon even spelen :)&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Several reasons why I prefer Github over Drupal for Hosting my Drupal Projects.</title>
   <link href="https://berk.es/2012/03/23/several-reasons-why-i-prefer-github-over-drupal-for-hosting-my-drupal-projects/"/>
   <updated>2012-03-23T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2012/03/23/several-reasons-why-i-prefer-github-over-drupal-for-hosting-my-drupal-projects/</id>
   <content type="html">&amp;lt;p&amp;gt;Why I prefer Github over Drupal, a crosspost from &amp;lt;a href=&amp;#34;https://drupal.org/node/1494882#comment-5769558&amp;#34;&amp;gt;an issue on Drupal.org&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;I do more then &amp;amp;quot;only Drupal&amp;amp;quot;. Github allows me to maintain all these none-Drupal modules in the same environment. Just another angle to look at the &amp;amp;quot;but it&amp;#39;s good to have everything centralised&amp;amp;quot;. It is now centralised for me, the developer; arguably the most important person in a project.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Collaboration is far easier on Github trough its &amp;lt;a href=&amp;#34;http://help.github.com/send-pull-requests/&amp;#34;&amp;gt;forking and pulling mechanism&amp;lt;/a&amp;gt;. No fiddling with patches, continuous re-rolling them and so forth. The entire experience is just simply a lot better worked out on Github. &amp;amp;quot;It&amp;#39;s all about the details&amp;amp;quot;^1.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;I firmly believe that Drupal should ditch the entire project-hosting. And leave it to the community members to choose where they host: On their own (companies&amp;#39;) servers, on Github, Bitbucket, launchpad, whatever rocks your boat. And no, you can &amp;lt;a href=&amp;#34;https://www.ruby-toolbox.com/&amp;#34;&amp;gt;still have&amp;lt;/a&amp;gt; central places to find your modules then. In my believe: when there is a competition between hosters and contrib-search-engines, they will be a lot better then what we have now. ^2&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;I care for developers, not users. Users give me little in return (other then high-usage rates and self-esteem). Developers are my main target, for they have the tools and skills to help improve my work. Their &amp;amp;quot;payment&amp;amp;quot; comes in patches, bugfixes, performance-improvements, refactorings and so on. For me, the most valuable payment. Obviously, most developers are users themselves. And many user can become a developer. But in the end, I choose a &amp;lt;a href=&amp;#34;https://github.com/about&amp;#34;&amp;gt;project-manangement-environment aimed at developers&amp;lt;/a&amp;gt;, because they are my main target audience, because in my Open Source Projects, they &amp;amp;quot;pay&amp;amp;quot; best. And so, I prefer to lower the barrier to make such a payment.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In the end, It should be for the best for all of us.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Obviously, Github lacks a few things, most of which are easily solvable due to the distributed nature of Git. Depending on how much Drupal projects I will continue to maintain (I am evaluating that right now), I might release some of my tools which help me here.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Drupal automatically builds releases. You now need to push to two remotes if you wish nightly builds (the -dev version).&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Update and security infrastructure is built around hosting and maintaining on Drupal.org entirely. You host elsewhere? You won&amp;#39;t be able to push out new security releases to your users.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Drupal has a really strange (and anoyingly &amp;lt;a href=&amp;#34;http://nvie.com/posts/a-successful-git-branching-model/&amp;#34;&amp;gt;incompatible with de-facto standards&amp;lt;/a&amp;gt;) workflow and branching model. Aliasing and simply ignoring most Drupal standards helps a lot.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;BUT IF IT IS NOT ON DRUPLA.OGR IT IS NOT OFFICIAL!!11onone.&amp;lt;/strong&amp;gt; Sadly a module has to be released on Drupal.org to be taken in consideration in most projects. Personally, I find that smallminded. Since there are &amp;lt;a href=&amp;#34;https://twitter.com/#!/github_drupal&amp;#34;&amp;gt;great projects not on Drupal.org&amp;lt;/a&amp;gt;. But facing the facts: a module has to be on drupal.org, so if you host elsewhere, you still need to host on Drupal.org too. Meaning two environments, twice the fiddling and thrice the amount of Description/readme/changelog copypastig. By hosting on Drupal.org alone, you avoid most of this.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;1: The most hilarious and sad example is the fact that when I decided to move my tickets over from Drupal.org to Github, I closed the ticket-feature on Tagadelic; you should no longer be able to post tickets there.. But due to some bug, this feature does not work: So now I have to keep replying on tickets on drupal.org, telling people the tickets are closed there. Sigh.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;2: It will also solve another rediculous problem: that of &amp;amp;quot;too much modules&amp;amp;quot;. Right now, the solution to this is to hold back module-development! Hah! Because the mechanism to find The Best Contribs is broken, you simply say: we will stall creation of new contribs: because then people can find the oldest ones best. Edit: due to excessive spamming (my server almost crashed, recieving over a million! spam POSTS per day) comments can be posted &amp;lt;a href=&amp;#34;http://www.reddit.com/r/drupal/comments/r9sjz/several_reasons_why_i_prefer_github_over_drupal/&amp;#34;&amp;gt;over at reddit&amp;lt;/a&amp;gt;. I will reply there.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Why not to use -dev versions of Drupal-modules.</title>
   <link href="https://berk.es/2012/03/23/why-not-to-use-dev-versions-of-drupal-modules/"/>
   <updated>2012-03-23T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2012/03/23/why-not-to-use-dev-versions-of-drupal-modules/</id>
   <content type="html">&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;http://www.reddit.com/r/drupal/comments/r8p2b/to_drupal_newbies_if_youre_not_yet_using_boost_do/&amp;#34;&amp;gt;Cross post from a Reddit Thread&amp;lt;/a&amp;gt;. Comments are most welcome &amp;lt;a href=&amp;#34;http://www.reddit.com/r/drupal/comments/r8p2b/to_drupal_newbies_if_youre_not_yet_using_boost_do/c43zghj?context=3&amp;#34;&amp;gt;there&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Let us assume that a release is some form of agreement between the developers and her users. Usually a release indicates that:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;The releasable version is considered in a certain state. A state that can be communicated with the users (stable, beta, alpha, security-fix etceteras).&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;The release indicates an immutable point in time and development. Even twenty years from now, you can rewind to release XYZ and find it in the exact same state.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;A release is typically kept forever. Unless, off course, the entire project is removed, that release will exist, with the exactly predictable (and often documented) bugs, shortcomings and other incompatibility. In most complex software projects (and your Drupal-core &#43; 30 contribs is such a project) you will always choose predictability over newness.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;A release often runs trough test-cycles. This will be stated in the projects documentation. Most often trough simple &amp;amp;quot;we are beta, please install and report back&amp;amp;quot;, but sometimes trough entire &amp;lt;a href=&amp;#34;https://jenkins-ci.org/&amp;#34;&amp;gt;Continuous Integration&amp;lt;/a&amp;gt; cycles.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Documentation, Readme&amp;#39;s, third-party dependencies are most often developed parallel. A release is a point where they are all brought together and synchronised. This can, indeed, easily mean that a -dev version has less bugs then a release. In most situations that is very logical: you make a release. It contains 6 bugs, 3 are solved, not enough for a new release. Now the -dev release contains 3 bugs and the released version 6. To many people this is an indication that the -dev release is &amp;amp;quot;better&amp;amp;quot; then the released version.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dev versions may (and often are) be end of line versions. I have, for example, worked on a fully OOP, for users entirely backwards compatible, replacement for Tagadelic. (It is on hold, mostly due to my lessening Drupal involvement). If it is released, there will be an upgrade path from the various releases to the new version. But not from each and every nightly-build -dev. This is part of that &amp;amp;quot;agreement&amp;amp;quot;. Dev versions might stop working from one day to another. Often large refactorings mean that features have to pulled out for a few commits, or that entire subsets stop working. A rewrite will break compatibility with other modules for a while, at least. So even if it works now, you simply never know if it will tomorrow. Dev versions are aimed at developers. So all the nice tools to lower the barrier for the larger public, such as installers, integration, end-users documentation and so on, are often neglected during this period. &amp;amp;quot;It does not work -what does not work? -it shows nothing on the installer? -what does the debug-log show you? -the what, I don&amp;#39;t know how to look at the log&amp;amp;quot;. Such tickets and emails are all too common and are utter time-wasters. If you don&amp;#39;t know how to read and debug code, then a -dev version, being for developers, is not for you. You may try it, but certainly should not expect a smooth ride. And definitely not consume precious developers-time by filing already known- or duplicate bugs :).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A few more practical reasons not to use -dev versions are:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;You have not pinned a point in time and development: there is nothing more time-consuming and infuriating then attempting to find the exact time and date for some arbitrary -dev module in some arbitrary three year old Drupalsite that is handed to you.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;You cannot be certain of proper database updates. They are (or should be) guaranteed between releases, but not between -dev versions. chances are that some untested database change between -dev versions spoils your entire database. And if it can happen, it will happen.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;The state is uncertain: whereas a release has known issues and bugs, a -dev version is in a state of brokenness (or else it could be a release, now, shouldn&amp;#39;t it?) which is entirely unknown. Installing a -dev version is a little like trial-and-error debugging and coding: it might get you a working environment. But if you have no idea why it works, you are probably worse off then when you had a none-working environment but knew exactly why it did not work.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;The userbase is small and spread out very thin. There is only one Latest Release, making it the most used version. But there are new -dev version rolled out every night, making the amount of users who run the exact same version as you, very small. This fact, is actually the whole concept behind the &amp;amp;quot;agreement-thing&amp;amp;quot;.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;TL;DR&amp;lt;/em&amp;gt; You know exactly what you get when you pick a released version. You never know what you get or where it may take you, when you pick a -dev.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>How the Code Consultant Bot tought me about a potential solution to Drupals&amp;#39; deployment horrors.</title>
   <link href="https://berk.es/2012/03/15/how-the-code-consultant-bot-tought-me-about-a-potential-solution-to-drupals-deployment-horrors/"/>
   <updated>2012-03-15T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2012/03/15/how-the-code-consultant-bot-tought-me-about-a-potential-solution-to-drupals-deployment-horrors/</id>
   <content type="html">&amp;lt;p&amp;gt;I just learned about the &amp;lt;a href=&amp;#34;https://code-consultant.appspot.com/&amp;#34;&amp;gt;Code Consultant Bot&amp;lt;/a&amp;gt;, a chat-bot version of the &amp;lt;a href=&amp;#34;http://www.codinghorror.com/blog/2012/03/rubber-duck-problem-solving.html&amp;#34;&amp;gt;Rubber Duck&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The gist is that by explaining your question to someone (or on Stackoverflow, or to a Duck, or a chat-bot), you think about the question in more abstract, simpler or different ways. It very often solves my problem.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I took the bot out for a test-drive. Posing my favorite problem to it: &amp;lt;strong&amp;gt;How to properly deploy Drupal without downtime and tedious manual configuration&amp;lt;/strong&amp;gt; (answer: you cannot). Here is the transcript:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;#xA;berkes@jabber.org 11:20:28&amp;#xA;Hello&amp;#x9;&amp;#xA;&amp;#xA;code-consultant@appspot.com 11:20:32&amp;#xA;Hello, I&amp;#39;m your Code Consulatant&amp;#xA;Ever notice how just talking through an issue with a co-worker is enough to help you solve a problem, even if they don&amp;#39;t say a word? 11:20:32&amp;#xA;Let me help you with your problems so they can keep working. 11:20:33&amp;#xA;How do you do.  Please state your problem. 11:20:33&amp;#xA; &amp;#xA;berkes@jabber.org 11:20:49&amp;#xA;I want to deploy Drupal without downtime.&amp;#x9;&amp;#xA;&amp;#xA;code-consultant@appspot.com 11:20:49&amp;#xA;What would it mean to you if you got to deploy drupal without downtime  ?&amp;#x9;&amp;#xA;&amp;#xA;berkes@jabber.org 11:21:10&amp;#xA;then I would not need to take the site offline while rolling out a new release&amp;#x9;&amp;#xA;&amp;#xA;code-consultant@appspot.com 11:21:10&amp;#xA;You say then you would not need to take the site offline while rolling out a new release  ?&amp;#x9;&amp;#xA;&amp;#xA;berkes@jabber.org 11:21:18&amp;#xA;yes&amp;#x9;&amp;#xA;&amp;#xA;code-consultant@appspot.com 11:21:18&amp;#xA;You seem to be quite positive.&amp;#x9;&amp;#xA;&amp;#xA;berkes@jabber.org 11:21:43&amp;#xA;how would I release a drupalsite without taking the site offline?&amp;#x9;&amp;#xA;&amp;#xA;code-consultant@appspot.com 11:21:44&amp;#xA;Why do you ask ?&amp;#x9;&amp;#xA;&amp;#xA;berkes@jabber.org 11:22:26&amp;#xA;because Drupal stores its configuration in the database. The same database that contains the content.&amp;#x9;&amp;#xA;&amp;#xA;code-consultant@appspot.com 11:23:09&amp;#xA;What database are you using ?&amp;#x9;&amp;#xA;&amp;#xA;berkes@jabber.org 11:23:16&amp;#xA;MySQL&amp;#x9;&amp;#xA;&amp;#xA;code-consultant@appspot.com 11:23:16&amp;#xA;You&amp;#39;re going to have to tell me more.&amp;#x9;&amp;#xA;&amp;#xA;berkes@jabber.org 11:23:32&amp;#xA;MySQL relational database&amp;#x9;&amp;#xA;&amp;#xA;code-consultant@appspot.com 11:23:32&amp;#xA;Is your database well normalized ?&amp;#x9;&amp;#xA;&amp;#xA;berkes@jabber.org 11:23:37&amp;#xA;yes, it is.&amp;#x9;&amp;#xA;&amp;#xA;code-consultant@appspot.com 11:23:37&amp;#xA;You are sure.&amp;#x9;&amp;#xA;&amp;#xA;berkes@jabber.org 11:23:55&amp;#xA;Well, no. You are right, that is part of the proble,&amp;#x9;&amp;#xA;&amp;#xA;code-consultant@appspot.com 11:23:56&amp;#xA;Are you saying no just to be negative?&amp;#x9;&amp;#xA;&amp;#xA;berkes@jabber.org 11:24:27&amp;#xA;how would I normalise Drupals database?&amp;#x9;&amp;#xA;&amp;#xA;code-consultant@appspot.com 11:24:27&amp;#xA;Liquibase would help you manage your database better.&amp;#xA;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;https://en.wikipedia.org/wiki/LiquiBase&amp;#34;&amp;gt;LiquiBase&amp;lt;/a&amp;gt;. I am not sure about the practical application of this tool, and if it will fit well into a Drupal workflow. But from the description, it tries to solve &amp;lt;em&amp;gt;exactly&amp;lt;/em&amp;gt; the &amp;lt;em&amp;gt;Drupal Deployment Problem&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In the very least, it helps. &amp;lt;strong&amp;gt;a lot&amp;lt;/strong&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Rollback database to &amp;amp;quot;tag&amp;amp;quot;&amp;lt;/strong&amp;gt;. Woa. If your deployment fails, whether or not you did that with any of the tools such as &amp;lt;strong&amp;gt;hook_update_N&amp;lt;/strong&amp;gt;, &amp;lt;strong&amp;gt;Features&amp;lt;/strong&amp;gt;, or whatever, there is still no rollback. Yet. Here it is.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Database diff changelog generation&amp;lt;/strong&amp;gt;. Especially usefull on your development machine: it allows you to record all the changes you made while developing the new release for your site. Even if such a changelog helps you to write a release-script manually, it is very helpful. Did you never forget to set that one checkbox on live, because you forgot you had it checked on your development machine? Causing a Giant Shipment of Fail?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Funny. To learn a new tool from a bot. What is next? &amp;lt;a href=&amp;#34;http://www.goingfaster.com/term2029/skynet.html&amp;#34;&amp;gt;Bots solving our actual solutions for us&amp;lt;/a&amp;gt;?&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>A better way to variable_get() and t() in Drupal.</title>
   <link href="https://berk.es/2012/03/01/a-better-way-to-variable_get-and-t-in-drupal/"/>
   <updated>2012-03-01T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2012/03/01/a-better-way-to-variable_get-and-t-in-drupal/</id>
   <content type="html">&amp;lt;p&amp;gt;When programming in Drupal, &amp;lt;a href=&amp;#34;http://drupal.stackexchange.com/questions/23162/using-variable-get-in-multiple-places-without-duplicating-default&amp;#34;&amp;gt;repeating default values in variable_get&amp;lt;/a&amp;gt; and repeating strings in translations, all over the place, is a very strong &amp;lt;a href=&amp;#34;http://martinfowler.com/bliki/CodeSmell.html&amp;#34;&amp;gt;codesmell&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I have been playing with solutions for this, and during my last project decided to take these attempts and make it into a very simple system. A pattern.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But, first, let us identify the problems.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;persistent-variables&amp;#34;&amp;gt;Persistent variables&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-php&amp;#34; data-lang=&amp;#34;php&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;$html &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;Showing &amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;variable_get&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;mymodule_amount&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#ae81ff&amp;#34;&amp;gt;20&amp;lt;/span&amp;gt;) &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;items&amp;amp;#34;&amp;lt;/span&amp;gt;;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;$html &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;pager_query&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;SELECT * FROM {mymodule_items}&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;variable_get&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;mymodule_amount&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#ae81ff&amp;#34;&amp;gt;20&amp;lt;/span&amp;gt;));&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;if&amp;lt;/span&amp;gt; ($total &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&amp;amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;variable_get&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;mymodule_amount&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#ae81ff&amp;#34;&amp;gt;20&amp;lt;/span&amp;gt;)) {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  $html &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;there are more&amp;amp;#34;&amp;lt;/span&amp;gt;;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;}&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Not only is there the &amp;lt;em&amp;gt;magic number 20&amp;lt;/em&amp;gt; all over the place, it is a DRY violation all over the place.&amp;#xA;In above example, that DRY violation is not very visible, yet, but imagine a module called &amp;lt;em&amp;gt;project_magician_message_center&amp;lt;/em&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-php&amp;#34; data-lang=&amp;#34;php&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;variable_get&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;project_magician_message_center_amount_for_&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt; $node&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;type&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#ae81ff&amp;#34;&amp;gt;20&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;variable_get&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;project_magician_message_center_request_limit&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#ae81ff&amp;#34;&amp;gt;20&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Just open up your average &amp;lt;em&amp;gt;variables&amp;lt;/em&amp;gt; table in larger Drupalproject and look around. The horror! (And maybe you have been bitten by the length limit of 128 characters?). There is no pattern; just a list of unpredicable names.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;lt;em&amp;gt;magic number&amp;lt;/em&amp;gt; problem often gets solved by Drupal developers with constants. But as the name suggests,&amp;#xA;a constant is constant. And a variable is variable. It is very confusing to read this:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-php&amp;#34; data-lang=&amp;#34;php&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;define&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;MYMODULE_AMOUNT&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#ae81ff&amp;#34;&amp;gt;20&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;$items &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;pager_query&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;SELECT * FROM {mymodule_items}&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;variable_get&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;mymodule_amount&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;MYMODULE_AMOUNT&amp;lt;/span&amp;gt;));&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Especially when you clearly get 30 items in some list. Which is what happens when a variable gets another value. Suddenly the constant is no longer&amp;#xA;used; it acts like a variable. Naming your constants &amp;lt;em&amp;gt;MYMODULE_AMOUNT_DEFAULT&amp;lt;/em&amp;gt; is slightly better, but no real solution.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;translations-screentexts&amp;#34;&amp;gt;Translations, screentexts.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Translations, through &amp;lt;em&amp;gt;t()&amp;lt;/em&amp;gt; act even worse. Some examples:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-php&amp;#34; data-lang=&amp;#34;php&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;t&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;Hello World, today is %date&amp;amp;#34;&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;t&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;Hello world, today is %date&amp;amp;#34;&amp;lt;/span&amp;gt;); &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#note the intentional erronous lowercase world.&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;$actor &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;Marsellus&amp;amp;#34;&amp;lt;/span&amp;gt;;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;$subject &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;Antwone&amp;amp;#34;&amp;lt;/span&amp;gt;;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;t&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;Look, just because I don&amp;amp;#39;t be givin&amp;amp;#39; no man a foot massage don&amp;amp;#39;t make it right for %actor to throw %subject into a glass motherfuckin&amp;amp;#39; house, fuckin&amp;amp;#39; up the way the nigger talks. Motherfucker do that shit to me, he better paralyze my ass, &amp;amp;#39;cause I&amp;amp;#39;ll kill the motherfucker, know what I&amp;amp;#39;m sayin&amp;amp;#39;?&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;array&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;%actor&amp;amp;#34;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; $actor, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;%subject&amp;amp;#34;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; $subject));&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;$message &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;MESS&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;Well, the way they make shows is, they make one show. That show&amp;amp;#39;s called a pilot. &amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;Then they show that show to the people who make shows, and on the strength of that one show they decide if they&amp;amp;#39;re going to make more shows. Some pilots get picked and become television programs.&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;Some don&amp;amp;#39;t, become nothing. She starred in one of the ones that became nothing.&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;MESS&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;t&amp;lt;/span&amp;gt;($message);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;t&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;_mymodule_message_contents&amp;lt;/span&amp;gt;());&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;First and foremost problem with this is that it is not prefixed, namespaced if you will. Your &amp;lt;em&amp;gt;t(&amp;amp;quot;Submit&amp;amp;quot;)&amp;lt;/em&amp;gt; is the same as that other &amp;lt;em&amp;gt;t(&amp;amp;quot;Submit&amp;amp;quot;)&amp;lt;/em&amp;gt;. Translate this once to &amp;amp;quot;Create new&amp;amp;quot; and suddenly all sorts of labels, tabs, titles and links show the text &amp;amp;quot;Create new&amp;amp;quot;. We have all been, there, just admit it, already.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But The first two examples pose an ever greater problem, too many such sentences are very alike. Strings like &amp;amp;quot;A new %type was created&amp;amp;quot; show up next to &amp;amp;quot;New %type created!&amp;amp;quot;. Especially when there are many modules, built over time, by many different developers.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Then the larger texts become an even bigger issue, they range from plain ugly to cluttering and convoluted.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Mixing screentexts and logic, which is what we all do, is arguably as bad as mixing code and presentation.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;solution&amp;#34;&amp;gt;Solution&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Imagine you could say:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-php&amp;#34; data-lang=&amp;#34;php&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;Showing &amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;v&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;amount&amp;amp;#34;&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;Showing &amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;v&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;core.amount_per_page&amp;amp;#34;&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;t&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;core.hello_world&amp;amp;#34;&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;t&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;ecommerce.payment.thank_you&amp;amp;#34;&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;t&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;core.thank_you&amp;amp;#34;&amp;lt;/span&amp;gt;);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;t&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;footmassage&amp;amp;#34;&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;array&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;%actor&amp;amp;#34;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; $actor, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;%subject&amp;amp;#34;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; $subject));&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Where all your defaults are nicely set in a central place, your screen-texts are in single place, or even file. And everything gets prefixed with your modulename, unless you define it differently.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The solution is OOP. Just a little, don&amp;#39;t fret, and nicely tuck away so that you won&amp;#39;t need to program everything OOP suddenly. First a generic class, which we will built upon in our modules.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-php&amp;#34; data-lang=&amp;#34;php&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;DrupalHelper&amp;lt;/span&amp;gt; {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;protected&amp;lt;/span&amp;gt; $prefix &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;core&amp;amp;#34;&amp;lt;/span&amp;gt;;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;public&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;function&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;v&amp;lt;/span&amp;gt;($symbol) {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;return&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;variable_get&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;absolute_or_prefix&amp;lt;/span&amp;gt;($symbol), $$symbol);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  }&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;public&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;function&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;t&amp;lt;/span&amp;gt;($symbol, $params) {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    $translated &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;&amp;amp;#34;&amp;lt;/span&amp;gt;;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    $symbol &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;absolute_or_prefix&amp;lt;/span&amp;gt;($symbol);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    $function &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;symbol_to_function&amp;lt;/span&amp;gt;($symbol);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;if&amp;lt;/span&amp;gt; (&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;method_exists&amp;lt;/span&amp;gt;($this, $function)) {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      $untranslated &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; $this&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;$function();&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    }&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;else&amp;lt;/span&amp;gt; {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      $untranslated &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; $symbol;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    }&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;return&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;t&amp;lt;/span&amp;gt;($untranslated, $params);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  }&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;private&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;function&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;absolute_or_prefix&amp;lt;/span&amp;gt;($symbol) {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;if&amp;lt;/span&amp;gt; (&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;!&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;strstr&amp;lt;/span&amp;gt;($symbol, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;.&amp;amp;#34;&amp;lt;/span&amp;gt;)) {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      $symbol &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; $prefix &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;.&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt; $symbol;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    }&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;return&amp;lt;/span&amp;gt; $symbol;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  }&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;private&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;function&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;symbol_to_function&amp;lt;/span&amp;gt;($symbol) {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;return&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;t_&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;preg_replace&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;/\./&amp;amp;#34;&amp;lt;/span&amp;gt;, $symbol);&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  }&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;}&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;With the module, I inherit this helper:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-php&amp;#34; data-lang=&amp;#34;php&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;MyModuleHelper&amp;lt;/span&amp;gt;() &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;extends&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;DrupalHelper&amp;lt;/span&amp;gt; {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;protected&amp;lt;/span&amp;gt; $prefix &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;mymodule&amp;amp;#34;&amp;lt;/span&amp;gt;;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#defaults:&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;public&amp;lt;/span&amp;gt; $length &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ae81ff&amp;#34;&amp;gt;20&amp;lt;/span&amp;gt;;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#translations:&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;private&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;function&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;t_hello_world&amp;lt;/span&amp;gt;() {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;Hello World&amp;amp;#34;&amp;lt;/span&amp;gt;;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  }&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;}&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;In your module, you use this as follows:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-php&amp;#34; data-lang=&amp;#34;php&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;function&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;mymodule_form_alter&amp;lt;/span&amp;gt;($form_id, &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&amp;amp;amp;&amp;lt;/span&amp;gt;$form, &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&amp;amp;amp;&amp;lt;/span&amp;gt;$form_values) {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;if&amp;lt;/span&amp;gt; ($form_id &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;==&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;foo&amp;amp;#34;&amp;lt;/span&amp;gt;) {&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    $helper &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;new&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;MyModuleHelper&amp;lt;/span&amp;gt;();&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    $form[&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;field&amp;amp;#34;&amp;lt;/span&amp;gt;] &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;array&amp;lt;/span&amp;gt;(&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;#type&amp;amp;#34;&amp;lt;/span&amp;gt;   &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;textfield&amp;amp;#34;&amp;lt;/span&amp;gt;,&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;#title&amp;amp;#34;&amp;lt;/span&amp;gt;  &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; $helper&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;t&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;hello_world&amp;amp;#34;&amp;lt;/span&amp;gt;),&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;#length&amp;amp;#34;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; $helper&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;-&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;v&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;length&amp;amp;#34;&amp;lt;/span&amp;gt;)&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    );&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  }&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;}&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;The usage-example does not load the library files, but delegating code to separate files is not hard in Drupal, with helpers like &amp;lt;em&amp;gt;module_load_include()&amp;lt;/em&amp;gt;. This example assumes the file is already loaded, or that some autoloader is in place.&amp;#xA;This example-code does not yet handle the variable_del and variable_set functionality for variables, but that is left to the reader to implement.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Also note that I have simplified the code a little for readability. Like&amp;#xA;leaving out the variable_set and the very much simplified&amp;#xA;&amp;lt;code&amp;gt;symbol_to_function()&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Some other todo&amp;#39;s on my list are:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Introduce a fallback for core strings, we now have to either call &amp;lt;em&amp;gt;$helper-&amp;amp;gt;t(&amp;amp;quot;foo&amp;amp;quot;)&amp;lt;/em&amp;gt; for our symbol based translations, or &amp;lt;em&amp;gt;t(&amp;amp;quot;foo&amp;amp;quot;)&amp;lt;/em&amp;gt; for core or 3rd party module strings. Core messages need to be callable with symbols too.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Allow passing variables into t() instead of an keyed array. Like &amp;lt;em&amp;gt;t(&amp;amp;quot;footmassage&amp;amp;quot;, $actor, $subject)&amp;lt;/em&amp;gt;; parsing and cleaning should use sane defaults but would need to be overridable.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Format_plural implementation. I hardly ever need it, but it should be callable like *plural(&amp;amp;quot;footmassage&amp;amp;quot;, $actor, $subject, $count);&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Make it easier to place all screen texts in a separate file.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;More consistency. Maybe defaults for variables should be defined just like texts, with a private &amp;lt;em&amp;gt;v_var_name()&amp;lt;/em&amp;gt; function.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A real simple pattern, which requires a little understanding of OOP, but has almost only benefits in usage. And as far as I can see only one downside: it is &amp;amp;quot;Un-Drupal-ish&amp;amp;quot;; but that is not a reason, in itself.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;comments on &amp;lt;a href=&amp;#34;http://www.reddit.com/r/drupal/comments/qcuqa/a_better_way_to_variable_get_and_t_in_drupal/&amp;#34;&amp;gt;Reddit&amp;lt;/a&amp;gt;&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Wat kan wel en niet met Drupal: enkele vuistregels</title>
   <link href="https://berk.es/2012/01/11/wat-kan-wel-en-niet-met-drupal-enkele-vuistregels/"/>
   <updated>2012-01-11T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2012/01/11/wat-kan-wel-en-niet-met-drupal-enkele-vuistregels/</id>
   <content type="html">&amp;lt;p&amp;gt;Recent ontving ik weer twee mails met daarin de Gouden Vraag: &amp;amp;quot;Wanneer moet ik Drupal nu gebruiken, en wanneer niet&amp;amp;quot;. Van de laatste kreeg ik toestemming om de vraag en mijn antwoorden uit te werken tot deze blogpost; anoniem uiteraard.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;...&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik werk zelf al enige jaren met X [Een bekend ander CMS, of &amp;amp;quot;ons eigen systeem&amp;amp;quot;; BK] en dat systeem ken ik nu redelijk goed.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Door vragen van klanten/ontevredenheid met doorontwikkeling/zoektocht naar meer flexibiliteit ben ik eens naar Drupal gaan kijken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik denk er daarom aan om over te stappen maar daarvoor zou ik &amp;lt;em&amp;gt;graag willen weten wat niet kan met Drupal.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wie dat weet, en kan toepassen, heeft goud in handen en kan makkelijkprijzen van boven de $300/uur vragen. Ofwel: dat weet niemand.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik ben daarom op zoek naar de concrete restricties/beperkingen van Drupal.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Die zijn er niet!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Voor iedere restrictie die je aanbrengt, brengt iemandanders een oplossing aan. En iedere beperking die ergens is beschreven, wordt door iemand andersbeschreven als ofwel een bewuste keuze (Y kan niet, maar dat is juist goed. Jij zou Y eigenlijk helemaal niet moeten willen) ofwel wordt er een uitbreiding, module of truukje uitgelegd waarmee, met wat werk, deze beperking omzeild wordt. Meestal vind je allebei.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar zelf hanteer ik enkele vuistregels, helaas niet erg concreet;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;drupal-is-een-cms-geen-framework-of-zelfbouw&amp;#34;&amp;gt;Drupal is een CMS, geen framework of zelfbouw.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Drupal is een CMS, geen &amp;lt;a href=&amp;#34;http://www.riehle.org/computer-science/research/dissertation/diss-a4.pdf&amp;#34;&amp;gt;framework (pdf)&amp;lt;/a&amp;gt;. Helaas is &amp;amp;quot;&amp;lt;a href=&amp;#34;http://stackoverflow.com/questions/301240/whats-a-php-framework-and-whats-a-good-one&amp;#34;&amp;gt;framework&amp;lt;/a&amp;gt;&amp;amp;quot; een vage term, daarom enkele stellingen:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een framework doet geen aannames over het gedrag, de look en de feel van het te bouwen eindproduct.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een framework heeft een duidelijke doelgroep: de bouwers van applicaties, zoals websites (niet persé programmeurs).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een framework biedt een technische basis en infrastructuur.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een framework biedt een technische infrastructuur die het bouwen en/of programmeren efficiënter maakt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Drupal voldoet hier niet écht aan; het is niet alleen opnionated over hoe je moet ontwikkelen, het is vooral opinionated over hoe het gebouwde resultaat zal werken en er zal uitzien.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Vergeleken met een CMS als Joomla! voldoet Drupal hier wel meer aan. En is daarom meer een framework dan Joomla!Maar vergeleken met Codeignitor, Symfony, Rails of Django is het véél minder een framework. Drupal valt dan veel meer in decategorie bij Joomla! Typo3 en Wordpress, dan bij Symfony of Django.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Omdat een CMS al volledig functioneel is (na installatie kun je meteen aan de slag, is het een werkende site), heeft het vastgestelde &amp;amp;quot;manieren&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Immers, na installatie heb je een werkend CMS. Hoe dat CMS je content benaderd, de workflow heeft bepaald, menusystemen ingeregeld heeft en wat de look en de feel is, liggen vast in de basis van dit systeem.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Drupal heeft dus een eigen wijze. En je moet dus je projectmanagement, wireframes, designs en workflows inrichten volgens hoe Drupal dat &amp;amp;quot;wil&amp;amp;quot;. Niet andersom.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wil je een CMS, Drupal, &amp;lt;em&amp;gt;exact&amp;lt;/em&amp;gt; laten gedragen zoals in je functioneel ontwerpen of technisch ontwerpen is vastgelegd, dan moet je twee keer zoveel ontwikkelen en eindig je met een drie keer zo complex systeem. Core doet manier-A. Uitbreiding X wordt ontwikkeld om manier-A ongedaan te maken. Uitbreiding Y wordt ontwikkeld om manier-B te implementeren.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Ben in je in de positie om de TO&amp;#39;s, FO&amp;#39;s, wireframes en designs te maken met kennis van Drupal&amp;#39;s &amp;amp;quot;eigen wijze&amp;amp;quot;, dan zul je vooral mét Drupal kunnen werken, in plaats van tégen Drupal te moet werken.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;database-geörienteerd-geen-abstractie&amp;#34;&amp;gt;Database-geörienteerd, geen abstractie.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Drupal is zeer &amp;lt;a href=&amp;#34;http://upsitesweb.com/sites/upsites.co/files/drupal7_model_0.png&amp;#34;&amp;gt;dicht op de Database gebouwd&amp;lt;/a&amp;gt;. Dit is in Drupal 7 in theorie verbeterd, de praktijk moet nog uitwijzen of dit ook écht een verbetering is. Helaas is over Drupal 7 nog veel onbekend en zijn er weinig casestudies te vinden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In de praktijk moet dus alles in een door Drupal bepaalde MySQL Database, volgens een doorDrupal bepaalde Databaseopzet (DBA) opgeslagen worden. Wil je informatie elders vanbetrekken, of elders opslaan (legacy databases, zelfgedefinieerde databasestructuren, webservices, NoSQL, XML-files,etc.) dan zul je een groot deel van je budget/tijd opzij moetenzetten voor complexe synchronisaties, cron-scripts, en diverse hooks. Een centrale API, layer of zelfs een aangeraden design pattern, ontbreekt geheel.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ook zul je vooraf duidelijk moeten hebben dat zulke koppelingen daarom, in praktijk, altijd uitdraaien op een groot houtje-touwtje ducktape-en-paperclips systeem: het werkt, maar is verre van stabiel en overzichtelijk. Met bijkomende operationele risico&amp;#39;s.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Moet je koppelingen maken met externe systemen, bijvoorbeeld om daaruit content te halen of juist om daar data in te stoppen? Dan zit Drupal zeer waarschijnlijk vooral in de weg. Maak dan een duidelijke afweging of deze extra investering en complexiteit opweegt tegen de voordelen van Drupal.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;een-module-voor-alles-versus-het-gevaar-van-onhandelbare-complexiteit&amp;#34;&amp;gt;Een module voor alles versus het gevaar van onhandelbare complexiteit.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;http://isthereamoduleforthat.com&amp;#34;&amp;gt;There is a module for that&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Voor een middelmatig complexe site heb je al snel over de vijftigmodules nodig. Om bijvoorbeeld de functionaliteit waarmee Wordpressstandaard komt na te bouwen in een Drupalblogsite kun je rekenen opdertig modules of meer. Dat is een enorme payload die mede beheerd,geüpdate en geconfigureerd moet worden. Daargelaten dat een groterehoeveelheid modules bijna altijd een negatief effect op de performanceheeft. Hou hier rekening mee bij het ramen van de lopende kosten: eengrotere server, een tijdrovende upgrade, update en beheerprocedure eneen toenemende complexiteit bij het (door)ontwikkelen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Uiteraard is de correcte oplossing om simpelweg &amp;amp;quot;niet een exacte Wordpress te willen nabouwen&amp;amp;quot;. Waar Drupal standaard mee komt, is al genoeg om te kunnen gaan bloggen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ondanks dat dit een bekende vuistregel is, heeft het merendeel van de Drupalsites waar ik inzage in gehad heb veel meer dan die vijftig modules. Eerder rond de 100 modules, dan rond de 10.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ook hier draait het weer om essentiële keuzes maken: je kunt heel goed bloggen met een Drupal zonder énige extra module. Pas als je allerlei eisen aan je workflow gaat stellen heb je modules nodig. Pas als je allerlei toeters en bellen erbij wilt, moet je enkele tientallen modules integreren, opmaken en doorontwikkelen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Ben je in de positie om structurele en functionele keuzes te maken? Dan kun je het functioneel ontwerp goed bijsturen aan hoe Drupal dingen &amp;amp;quot;standaard doet en kan&amp;amp;quot;. En zijn weinig extra modules nodig. En wordt het project veel overzichtelijker en makkelijker beheerbaar.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;het-enorme-grijze-gebied-dat-themen-heet&amp;#34;&amp;gt;Het enorme grijze gebied dat Themen heet.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een Theme is, in theorie, niet veel werk, maar in de praktijk meestal de grootste klus van het bouwen van een site.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Veel hangt af van je eigen projectmatige inrichting van het enorme grijze gebied dat Drupal heeft tussen de&amp;amp;quot;View&amp;amp;quot;(de eigenlijke theme-files, de code), de configuratie (inregelen vancontenttypes, settings, views, panels, blokken enzovoort) en degebruikte modules. Zo kun je bijvoorbeeld kiezen om de &amp;amp;quot;Posted by&amp;amp;quot; opeen artikel in de configuratie uit te zetten, of om deze in de&amp;lt;code&amp;gt;node-article.tpl.php&amp;lt;/code&amp;gt; template file eenvoudigweg niet te renderen. Of om daarvoor een set modules in te zetten die dit op een zeer krachtige manier configureerbaar maken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een typisch maatwerk-theme, waarbij het design al rekening houd metDrupal, kost een ervaren themer op zijn minst drie volle werkdagen om tebouwen. Meestal veel meer, omdat behalve het bouwen van het theme, dezethemer continue moet wisselen tussen het inregelen en configureren vanonderdelen van Drupal en het bouwen van het theme en de CSS.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Beperk je het themen enkel en alleen tot het aanpassen van de code in je theme? Dan ben je welliswaar beperkt in de mogelijkheden, maar is de klus zeer overzichtelijk en weinig werk. Maar betrek je het introduceren van allerlei functionaliteit erbij; of wil je in het theme ook bepalen hoe zaken zich gedragen, dan wordt het veruit de grootste klus van het bouwen van je website.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;met-een-houten-kano-de-oceaan-oversteken&amp;#34;&amp;gt;Met een houten kano de oceaan oversteken.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Verder wil ik met nadruk wijzen op het feit dat dit vuistregels zijn, geen wetten van Meden en Perzen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik weet ook wel dat er altijd ergens een voorbeeld te vinden is van eensite die mijn ongelijk &amp;amp;quot;bewijst&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar als iemand in een houten kano de Atlantische oceaan overgestoken is,bewijst dat alleen maar dat je met een houten kano die oceaan over kunt steken. Het is geenszins eenbewijs tegen een algemene stelling zoals &amp;amp;quot;een houten kano is geen geschikt vaartuig om de oceaan over te steken&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Meer concreet: uiteraard zijn er mooie Drupalsites gebouwd die externe databases gebruiken voor hun content; maar daarmeeis nog niet gezegd dat &amp;lt;em&amp;gt;over het algemeen&amp;lt;/em&amp;gt;  het integreren van externe bronnen, een zeer moeilijke klus is, in Drupal.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Heb ik wat vuistregels over het hoofd gezien? Wat zijn jou vuistregels? Zijn er dingen die Drupal volgens jou absoluut niet kan? Of zijn er gebieden of cases waar Drupal juist het allerbeste inzetbaar blijkt?&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>CDPATH, add paths to your &amp;#34;cd&amp;#34; which are accessible from anywhere on your system for autocopletion.</title>
   <link href="https://berk.es/2012/01/03/cdpath-add-paths-to-your-cd-which-are-accessible-from-anywhere-on-your-system-for-autocopletion/"/>
   <updated>2012-01-03T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2012/01/03/cdpath-add-paths-to-your-cd-which-are-accessible-from-anywhere-on-your-system-for-autocopletion/</id>
   <content type="html">&amp;lt;p&amp;gt;Whenever you use the commandline a lot, you will be browsing to certain directories a lot. Most graphical filebrowsers offer some sort of bookmarking system. So that you can browse to the place where you have your invoices with only two clicks, instead of clicking all trough Documents » Administration » Finance » 2012 » 01.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bash has something similar, but as always with the commandline, more powerfull: &amp;lt;a href=&amp;#34;http://www.caliban.org/bash/#bashtips&amp;#34;&amp;gt;CDPATH&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I have several CDPATH entries set, for example on my desktop machine:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;export CDPATH=~/Documenten:~/Documenten/Administratie/Facturen/huidig_jaar/&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And on my webservers:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;export CDPATH=/var/www&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The first, for example allows me, regardless of the current active directory, to type &amp;lt;code&amp;gt;cd &amp;amp;lt;tab&amp;amp;gt;&amp;lt;/code&amp;gt; to show all active projects. Typing &amp;lt;code&amp;gt;cd MCD_&amp;amp;lt;tab&amp;amp;gt;&amp;amp;lt;enter&amp;amp;gt;&amp;lt;/code&amp;gt; will expand to the project &amp;lt;code&amp;gt;cd MCD_my_current_drupal&amp;lt;/code&amp;gt; and open the directory &amp;lt;code&amp;gt;/home/ber/Documenten/MCD_my_current_drupal/&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;One of those tiny settings that make a small thing a little more efficient. And because I type that several hundreds of times each week, it&amp;#39;s overall benefit is rather large.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>HJKL cheatsheet</title>
   <link href="https://berk.es/2011/12/08/hjkl-cheatsheet/"/>
   <updated>2011-12-08T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2011/12/08/hjkl-cheatsheet/</id>
   <content type="html">&amp;lt;p&amp;gt;Because I &amp;lt;a href=&amp;#34;https://twitter.com/#!/jkreeftmeijer/status/144406665779888128&amp;#34;&amp;gt;too&amp;lt;/a&amp;gt; always forget about &amp;lt;em&amp;gt;hjkl&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;http://webschuur.com/files/hjkl_cheatsheet.png&amp;#34; alt=&amp;#34;hjkl, Motherfucker. Do you type it?&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>I was wrong: It was not a leak in a Drupalsite.</title>
   <link href="https://berk.es/2011/09/18/i-was-wrong-it-was-not-a-leak-in-a-drupalsite/"/>
   <updated>2011-09-18T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2011/09/18/i-was-wrong-it-was-not-a-leak-in-a-drupalsite/</id>
   <content type="html">&amp;lt;p&amp;gt;I &amp;lt;a href=&amp;#34;https://twitter.com/#!/berkes/status/114399800132255744&amp;#34;&amp;gt;tweeted&amp;lt;/a&amp;gt; too fast, and wrong:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Site were the Dutch Government accidentally leaked its 2012 budget, is a Drupalsite. Yes #Drupal does not secure its files. Drupal for govs?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The mayor news outlets in the Netherlands did not link to the leaking site, but instead to the site that carried (a mirror of the) PDFS that were leaked as well as background information. I followed these links, without researching if these sites were the actual leaking sites. This site they, instead, linked to, is a &amp;lt;a href=&amp;#34;http://www.cpb.nl/sites/all/modules/cck/CHANGELOG.txt&amp;#34;&amp;gt;Drupalsite&amp;lt;/a&amp;gt;. The one with the unprotected files was not.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So much for not investigating a little myself! The site that leaked the file, was an ASP (.net?) site.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I am sorry for this misinformation. And as said, tweeted too fast, did too little investigation and that makes me look stupid. I am glad for those that told me my mistake. And because I got married the next morning, writing this errata took more time then is appropriate. Sorry for that too.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;As a bonus, and to make things up a little, some common Drupal leakages that I helped fix in clients projects. Obviously I have responsibilities (and even a few NDAs) so I don&amp;#39;t give names and urls.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;the-avatar-fiasco&amp;#34;&amp;gt;The avatar-fiasco.&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A group of people partook in a grassroots campaign, backed up by a closed (the permission access-content was only given to hand-picked people) forum.&amp;#xA;The party who the grassroots took action against new about that forum, but could not access it. They, however, wrote a silly script that&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ol&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;scanned URL patterns for user-profiles: if they gave a 404, that user did not exist, if they got a 401, they still could not access the content, but new the person existed.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;fetched avatars for all users that had one and used that to intimidate the partakers.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ol&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I did three things: migrated existing users and added some pseudorandom numbers to their uid. I hacked core, so it sent a 404 for access-denied pages too. I disabled avatars.&amp;#xA;And explained the users that their Drupalsite was not hacked, but instead leaking some minor privacy-information.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This was minor. But imagine this happening on a sexual-diversity, and/or civil-rights forum in, say, Iran?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;the-video-file-settlement&amp;#34;&amp;gt;The video-file settlement.&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A none-drupalsite ran a (very interesting) documentary on a person with a mental illness. After a Preliminary injunction they had to take the video offline (or pay a fine of X for each day it remained online). The publishers took the article offline, but some journalists/bloggers found that the video could still be accessed, by giving the urls to the video-file. Luckily a settlement was reached and the publishers did not have to pay the fine for all the days the video had remained active.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We were just then planning to migrate this site to Drupal. The incident caused us to find a solution for this in Drupal: when a node gets unpublished the attached files should no longer be servable. We decided upon a custom-built module with a hook_node that acted upon &amp;amp;quot;unpublishing&amp;amp;quot; and simply renamed the files to some obscure salted-hash-name. Yes, that is security-by-obscurity, but the only affordable solution here.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;the-imagecache-downloads&amp;#34;&amp;gt;The imagecache downloads&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A site that (re)sold images used imagecache to watermark the images, resize them and only present small resolutions to users.&amp;#xA;Someone found this out, probably new Drupal, and fiddled with the urls to fetch the original files. Those were &amp;amp;gt;5MB JPEGs, copyrighted and by contract, not allowed to be distributed. Ever.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;My client was warned (luckily) and hired me to write a (very ugly) imagemagick hack that moved the original files to a place outside the web-root, but accessible for re-building of the derivatives.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;the-multisite-jokes&amp;#34;&amp;gt;The Multisite jokes&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Back when I ran our &amp;lt;a href=&amp;#34;http://web.archive.org/web/20060202131110/http://sympal.nl/&amp;#34;&amp;gt;Drupal-hoster&amp;lt;/a&amp;gt; we thought that multisite was a good solution for hosting. It is not, for many reasons, but one is most interesting here.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Two domains, for the sake of the example: upload-your-xxx.com and some-brochureware-about-us.nl were multisites&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Some funny people found out that by switching the urls, one could present images uploaded on upload-your-xxx.com on the domain of some-brochureware-about-us.nl: and posted that on some forums:&amp;#xA;&amp;lt;a href=&amp;#34;http://some-brochureware-about-us.nl/files/upload-your-xxx.com/hardcore.jpg&amp;#34;&amp;gt;http://some-brochureware-about-us.nl/files/upload-your-xxx.com/hardcore.jpg&amp;lt;/a&amp;gt; with the messages: look company Y us dealing in pr0n. Embarrassing, in this case, but potentially harmfull, especially when one of both sites has user-generated-content, or when sites are tough opponents and involved in smear-campaigning.&amp;#xA;Also potentially harmfull when &amp;amp;quot;good&amp;amp;quot; sites get blocked on schools and in libraries, for &amp;amp;quot;having none-complient or adult content&amp;amp;quot;. Or when a multisite acts as a proxy to pass in disallowed content.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Our solution was to nuke multisite with a thousand flames.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;but-private-files&amp;#34;&amp;gt;But, private-files?&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Drupal has a private-file modus. That is fine for small sites, but it does not scale. You cannot deliver (very) large files that way, and certainly cannot deliver large amount of files concurrently that way.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I see no solution; And earlier research has made me believe this is simply not possible with a classic LAMP (Apache and PHP) stack. One needs a real document-management server, or application (things like Alfresco). Most probably a Java-based solution. Or some thin proxy that knows about who can access what files in front of the app.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When you need to deliver large (amounts of) files, keep away from simple asp, lamp and such solutions, including Drupal. Maybe, but I have never tested it, Alfresco behind Drupal can offer a real solution?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;again-sorry&amp;#34;&amp;gt;Again, sorry.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;As you know, I was wrong in my conclusion that (a badly configured) Drupal was leaking governmental information.&amp;#xA;But as you can see, it happens a lot. And it requires quite some effort to avoid Drupal leaking information. Including core hacks if you are really serious.&amp;lt;/p&amp;gt;</content>
 </entry>
 
 <entry>
   <title>Bitcoin inbraak,</title>
   <link href="https://berk.es/2011/06/21/bitcoin-inbraak/"/>
   <updated>2011-06-21T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2011/06/21/bitcoin-inbraak/</id>
   <content type="html">&amp;lt;p&amp;gt;Afgelopen weekeind, [werd ingebroken]((&amp;lt;a href=&amp;#34;http://ftalphaville.ft.com/blog/2011/06/21/600441/george-clooney-roils-the-bitcoin-market&amp;#34;&amp;gt;http://ftalphaville.ft.com/blog/2011/06/21/600441/george-clooney-roils-the-bitcoin-market&amp;lt;/a&amp;gt;) in het grootste wisselkantoor en handelsplatform mtGox. Een aantal mensen vroeg mij wat ik daarvan dacht, gezien mijn recente positieve verhalen over die BitCoin. Ik trek enkele conclusies, maar eerst wat achtergrondDe inbraak en crash is mooi te zien in een &amp;amp;quot;live verslag&amp;amp;quot; van een handelaar.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;iframe width=&amp;#34;560&amp;#34; height=&amp;#34;349&amp;#34; src=&amp;#34;http://www.youtube.com/embed/T1X6qQt9ONg&amp;#34; frameborder=&amp;#34;0&amp;#34; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&amp;#xA;In het kort: er werd ingebroken en de inbreker heeft bitcoins ter waarde van anderhalf miljoen Dollars vér onder de prijs verkocht. Waarmee deze persoon de hele beurs heeft laten crashen. Deze bitcoins stonden óp een of twee rekeningen in het handelskantoor opgeslagen.&amp;#xA;&amp;lt;p&amp;gt;MtGox heeft meteen het platform gesloten en aangegeven iedereen een nieuw wachtwoord te geven. Ze hebben ook aangegeven alle in- en verkoopacties terug te draaien. Balen voor veel mensen die tijdens de crash bitcoins inkochten voor minder dan één dollar. Maar aangezien het illegale handel was, wel zo eerlijk voor alle andere handelaars op het platform.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Over de wijze van inbreken is nog wat onduidelijkheid, maar alles wijst erop dat een database van de computer van één van de ontwikkelaars is gestolen en de inhoud van die database is gebruikt om in te breken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een aantal dingen kan ik nu concluderen:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ol&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;De Computers van de ontwikkelaars zijn een erg zwak punt in de beveiliging van websystemen. Ik ken genoeg webdevelopers met brakke operating systemen, slecht- of nietversleutelde hardeschijven, laffe wachtwoorden enzovoort. Ontwikkelaars die wél databases met tienduizenden gebruikersaccounts op hun laptopje in hun rugzak hebben zitten. Waarschijnlijk ook jou gegevens.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Bitcoin is nog erg onzeker. Beloftevol en technisch goed in orde, maar als economie en gemeenschap nog onervaren en zoekende. Een handelskantoor dat na een overval de hele economie doet crashen, is vergelijkbaar met een volledige crash van de Nederlandse economie na een kraak en enorme diefstal bij de Rabobank. Dat mág en kan niet gebeuren. De BitCoin economie mist nog allerlei veiligheidsnetten en mechanieken om dit te voorkomen.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Bitcoin is toch sterk. Want de beurzen die openblijven zijn weliswaar flink in het rood gedoken, maar inmiddels stabiel en weer opkrabbelend. We moeten nog zien wat er gebeurt als mtGox weer open gaat. Mogelijk dat heel veel mensen direct al hun bitcoins proberen te verkopen voor Euro&amp;#39;s of Dollars omdat ze geen vertrouwen meer in Bitcoin hebben. Maar ook goed mogelijk dat na een flinke waardedaling, diegenen die er nog wel in geloven (en er vaak veel in geïnvesteerd hebben) weer de goedkope bitcoins inkopen. Vooralsnog ziet het er niet erg slecht uit, en is &amp;amp;quot;het einde van de Bitcoin&amp;amp;quot; zeker nog niet in zicht.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;De Cloud is gevaarlijk. Mensen die hun bitcoins op de site van mtGox bewaarden en niet thuis, op een veilige (versleutelde of afkoppelbare USB) schijf vertrouwen op een extern bedrijf. Een bedrijf dat enerzijds juist vanwege die grote waarde in kas een belangrijk doelwit is voor overvallers en tegelijk haar veiligheid blijkbaar niet goed op orde had. Dat is de &amp;amp;quot;Cloud&amp;amp;quot; waar vooral marketeers zo lyrisch over zijn: je mail, je documenten, je administratie en dus nu ook je geld, niet tuis op een veilige computer bewaard maar bij allerlei hippe &amp;amp;quot;Cloud&amp;amp;quot; diensten op hun online computers. BitCoin is in principe decentraal, maar als we allemaal één centrale database kiezen om het geld op te slaan, is het gewoon weer centraal.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;BitCoin is spannend. Wat afgelopen weekeind gebeurde leest als het script van de James Bondfilm Goldfinger. Waarin Auric Goldfinger probeert al het Amerikaanse goud (de grootste hoeveelheid ter wereld) nucleair te besmetten, waarna het waardeloos wordt. En daarmee het goud van Auric Goldfinger zelf ineens meer waard is.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;BitCoin is voor mij vooral een langetermijn plan. Over twee, die of zelfs vier jaar kunnen we zeggen of het een success was, of is. Tot die tijd is het vooral enorm spannend. En zullen de voor- en tegenstanders bij iedere grote gebeurtenis meteen concluderen dat het &amp;amp;quot;dus&amp;amp;quot; prima - of juist nooit kan werken.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ol&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dus is het vooralsnog niet te concluderen dat BitCoin ten einde is of dat de waarde helemaal gecrashd is. We weten meer nadat de grootste beurs weer open gaat. Het is in ieder geval een belangrijk moment voor die Bitcoin. Wordt zeker vervolgd.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Mijn bitcoin avontuur, deel twee: handelen en accepteren (op marktplaats) als betaalmiddel.</title>
   <link href="https://berk.es/2011/06/02/mijn-bitcoin-avontuur-deel-twee-handelen-en-accepteren-op-marktplaats-als-betaalmiddel/"/>
   <updated>2011-06-02T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2011/06/02/mijn-bitcoin-avontuur-deel-twee-handelen-en-accepteren-op-marktplaats-als-betaalmiddel/</id>
   <content type="html">&amp;lt;p&amp;gt;Zoals eerder beschreven &amp;lt;a href=&amp;#34;http://bler.webschuur.com/bitcoins_de_revolutionaire_valuta_met_een_potentie_voor_de_teloorgang_van_ons_banksysteem&amp;#34;&amp;gt;ben ik in de bitcoin wereld gedoken&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En heb ik daar ook meteen al geld mee verdiend (jawel mijnheer de belastingmijnheer die dit ook misschien leest: ik voer dat gewoon netjes bij mijn inkomsten op). En zijn er een heleboel onduidelijkheden boven komen borrelen, de meeste worden besproken op het &amp;lt;a href=&amp;#34;http://webchat.freenode.net/?channels=bitcoin-nederland&amp;amp;amp;uio=d4&amp;#34;&amp;gt;Nederlandse bitcoin IRC kanaal #bitcoin-nederland&amp;lt;/a&amp;gt;. Zaken als facturering, BTW, omrekentarieven enzovoort.&amp;lt;a href=&amp;#34;http://bitcoincharts.com/markets/&amp;#34;&amp;gt;Valutahandel met bitcoins&amp;lt;/a&amp;gt; is niet moeilijk en ook niet onduidelijk. Door de omhoogschietende prijs, heb ik met mijn relatief kleine investering na een paar dagen al een royale waardevermeerdering gezien. Als ik het nu zou opnemen, heb ik een heel royale winst gemaakt. Zonder jullie meteen alle details over mijn inkomen te geven: ik zou daar een paar jaar google advertenties op mijn sties voor moeten draaien. Ik kijk de kat nog even uit de boom, hoop dat het nog meer waard wordt. En loop dus het risico dat het als een mooie ballon knapt, en mijn geld ineens niks meer waar is.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dat is ook het grootste risico op dit moment. De buzz is overal, iedereen probeert aan bitcoins te komen, dus hun waarde zoeft omhoog. Het is dus belangrijk dat er ook daadwerkelijke &amp;amp;quot;echte wereld&amp;amp;quot; goederen voor te koop zijn. Gelukkig zijn er al veel winkels te vinden, maar op wereldschaal gezien stelt dat nog bijna niks voor. Want als mensen ze accepteren, hebben ze een verankerde waarde. Als je voor 1 bitcoin een kop koffie kunt kopen, zou dat nu (2 juni 2011, 15:20) een duur bakkie zijn, met een wisselkoers van $10/1Btc. Maar is er een garantie dat je altijd nog kopjes koffie ter waarde van omgeveer €2 kunt krijgen. Dus dat de bitcoin verankerd is aan die €2 waarde.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Je moet echter ergens beginnen. Want in Nederland kun je nog nauwelijks ergens met bitcoins betalen. Ik heb &amp;lt;a href=&amp;#34;http://kopen.marktplaats.nl/search.php?tab_adcount=3&amp;amp;amp;ds=to%3A%3Bpu%3A0%3Bdi%3A%3Bpp%3A0%3Blt%3Azip%3Bsfds%3A1%3Bpt%3A0%3Bmp%3Anumeric%3Blx%3A186308%3Bly%3A428440%3Bkw%3Abitcoin%3Bosi%3A2&amp;amp;amp;srt=dd&amp;amp;amp;u=&amp;amp;amp;g=&amp;amp;amp;f=0&amp;amp;amp;pp=0&amp;amp;amp;ts_category_default=&amp;amp;amp;pts=1&amp;amp;amp;t=&amp;amp;amp;p=1&amp;amp;amp;stop_force_description_search=&amp;amp;amp;s=&amp;amp;amp;show_thumbnails=1&amp;amp;amp;show_summary=1&amp;amp;amp;from_searchbox_advanced=1&amp;amp;amp;q=bitcoin&amp;amp;amp;g=nol1&amp;amp;amp;qsf=0&amp;amp;amp;postcode=&amp;amp;amp;distance=&amp;amp;amp;ts=on&amp;#34;&amp;gt;mijn zolder opgeruimd en wat spul op markplaats gezet&amp;lt;/a&amp;gt;. Ik accepteer daar bitcoins voor. En Euros, want ik verwacht niet dat iemand met BTC gaat betalen: immers tegen de tijd dat je naar mijn huis gelopen bent om de fiets te bekijken is de afgesproken prijs in bitcoins alweer gestegen door die omhoog knallende koers.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik hoop dat ik van markplaats hier iets over te horen krijg. Er staat niks in hun voorwaarde over het accepteren van andere valuta (of ik heb eroverheen gelezen). Dus het lijkt gewoon te mogen. Maar laten we het experiment vooral doorzetten en zien wat ze er bij marktplaats van denken.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Bitcoins. De revolutionaire valuta met een potentie voor de teloorgang van ons banksysteem</title>
   <link href="https://berk.es/2011/05/29/bitcoins-de-revolutionaire-valuta-met-een-potentie-voor-de-teloorgang-van-ons-banksysteem/"/>
   <updated>2011-05-29T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2011/05/29/bitcoins-de-revolutionaire-valuta-met-een-potentie-voor-de-teloorgang-van-ons-banksysteem/</id>
   <content type="html">&amp;lt;p&amp;gt;Al verschillende keren is getracht om alternatieve economiën van de grond te krijgen. Sommigen zijn aardig gelukt. Maar nu, vooral afgelopen maanden, lijkt het dan eindelijk écht te lukken: een geldsysteem dat ons huidige op banken gebaseerde systeem kan vervangen. Klinkt eng? Dat is het ook; voor overheden en bankiers althans. Als je me niet gelooft, lees door en volg ook de links. &amp;lt;a href=&amp;#34;https://en.bitcoin.it/wiki/Introduction&amp;#34;&amp;gt;Bitcoin is een valuta&amp;lt;/a&amp;gt;. Net als de Euro (of de Dollar). In tegenstelling tot die Euro, hebben Bitcoins geen centrale organisatie achter zich staan die de waarde garandeert.     &amp;amp;gt; De peer-to-peer topologie van Bitcoin en het ontbreken van de centrale administratie maakt het praktisch onmogelijk voor een overheid, of ieder ander, om de waarde van Bitcoins te manipuleren of meer inflatie te induceren dan er van tevoren is vastgelegd. Het ontwerp van Bitcoin zorgt voor anoniem eigendom en overdracht van waarde.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ook ik ben met bitcoins aan het experimenteren en handelen geslagen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In tegenstelling tot Euro&amp;#39;s zijn bitcoins dus ook heel makkelijk &amp;amp;quot;over te maken&amp;amp;quot; en nauwelijks te traceren. Maar is het vooral geld dat &amp;amp;quot;van ons is&amp;amp;quot; en niet &amp;amp;quot;van de overheid&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bitcoins zijn goed &amp;lt;a href=&amp;#34;https://en.bitcoin.it/wiki/Myths#Bitcoins_don_t_solve_any_problems_that_fiat_and/or_gold_doesn_t_solve&amp;#34;&amp;gt;te vergelijken met goud&amp;lt;/a&amp;gt;. Goud is een onpraktisch, onhandig metaal; niemand maakt zijn schoffel, of fiets van goud, als zodanig is het dus nauwelijks waardevol. Maar omdat we met zijn allen hebben afgesproken dat goud iets waard is, ís het dat ook. Het feit dat er vrij moeilijk aan goud te komen is, maakt dat het deze waarde houdt en meestal zelfs meer waard wordt. Het feit dat je het goud vaak ongeregistreerd kunt bewaren maakt dat het een anoniem waardemiddel is. Als ik een fiets koop en dat betaal met goud, is die overdracht nauwelijks te traceren door overheden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Momenteel is ongeveer $50 miljoen in het Bitcoin netwerk gestopt. Er is dus ongeveer $50.000.000,- ingewisseld tegen Bitcoins. Er bestaan ongeveer &amp;lt;a href=&amp;#34;http://blockexplorer.com/q/totalbc&amp;#34;&amp;gt;6 miljoen bitcoins&amp;lt;/a&amp;gt; in omloop. Een Bitcoin is dus ongeveer $8 waard. En door de aanwas van geïnteresseerden, zal dit zeer waarschijnlijk enorm toenemen. Het is dus heel eenvoudig: omdat mensen Euro&amp;#39;s, Dollars en ander geld omwisselden in bitcoins, zijn die bitcoins wat waard.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Het aantal nieuw in omloop te brengen bitcoins &amp;lt;a href=&amp;#34;http://forum.bitcoin.org/?topic=3366.msg47522#msg47522&amp;#34;&amp;gt;is vastgesteld&amp;lt;/a&amp;gt;. Dit kan ook niet (meer) worden veranderd. Maar het aantal geïnteresseerden, mensen die &amp;amp;quot;echt&amp;amp;quot; geld inwisselen voor Bitcoins, zal alleen maar toenemen. Er ontstaat een &amp;amp;quot;tekort&amp;amp;quot; waardoor bitcoins meer waard worden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dat blijkt ook uit de korte geschiedenis. Anderhalf jaar geleden werd voor 1000 bitcoins slechts $50 geboden. Dat is $0,005 per bitcoin. Vandaag gaan ze van de hand voor ruim $8. In veertien maanden is de waarde dus &amp;lt;strong&amp;gt;verduizendvoudigd&amp;lt;/strong&amp;gt;. &amp;lt;a href=&amp;#34;http://falkvinge.net/2011/05/29/why-im-putting-all-my-savings-into-bitcoin/&amp;#34;&amp;gt;Als je toen €2.500 had omgezet naar Bitcoins&amp;lt;/a&amp;gt;, waren die nu ruim €2,5 miljoen waard geweest. &amp;lt;a href=&amp;#34;http://www.bitcoinmoney.com/post/3109720916/laszlos-pizza&amp;#34;&amp;gt;Multimiljonair dus&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Of het zo krankzinnig hard blijft groeien is uiteraard onduidelijk. Er zitten nog &amp;lt;a href=&amp;#34;https://en.bitcoin.it/wiki/FAQ#Economy&amp;#34;&amp;gt;risico&amp;#39;s aan&amp;lt;/a&amp;gt;. En het is vooral afwachten wat overheden gaan proberen te doen. Maar interessant is het zeker. Ik heb, om die reden ook al een paarhonderd Euro &amp;lt;a href=&amp;#34;https://www.mtgox.com/support&amp;#34;&amp;gt;omgezet in bitcoins&amp;lt;/a&amp;gt;. Omdat het vanuit mijn &amp;amp;quot;traditionele&amp;amp;quot; bank overgezet moet worden, duurt dat lang, want &amp;amp;quot;zo werkt het nu eenmaal&amp;amp;quot; maar zogauw het geld overgezet is, kan ik gaan handelen. Of het gewoon vasthouden. Ik zal hier beschrijven wat ik ermee ga doen en wat ik zoal tegenkom.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar laten we de risico&amp;#39;s een bekijken: Banken en overheden gaan zich roeren en proberen het te verbieden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bitcoin is peer to peer. Tussen jou en mijn computer. En die van tienduizenden anderen. Iedereen die bitcoins heeft is meteen de infrastructuur. Het geld staat op mijn computer (in de vorm van een versleuteld bestand, mijn &amp;amp;quot;wallet&amp;amp;quot;) en niet op een &amp;amp;quot;bankrekening&amp;amp;quot;. Vergelijk het met DSB en een matras. DSB kan door de overheden afgeschaft worden, waarna het geld dat bij DSB in beheer was, verdwenen is. Geld in mijn matras blijft daar liggen, ongeacht of banken omvallen. Overheden kunnen (onder druk van banken) best Bitcoin verbieden. Maar ze kunnen niet nagaan of we ons daaraan houden zonder al het internetkeerveer met enorme supercomputers af te luisteren. Alleen zo kunnen ze bitcoin-gebruikers afsluiten of bitcoinverkeer afknijpen. Net zomin als het overheden en film- en muziekindustrie lukt om bestandsdeling (filmpje downloaden) te verbieden of zelfs maar te verminderen, zal het ze lukken om dit de kop in te drukken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Het cryptografische algoritme achter het netwerk blijkt een zwakheid te hebben.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Als het een diepliggende fout is, eentje die ingrijpt op de basis van hoe dit systeem werkt, zal het vertrouwen wegvallen, zullen mensen hun bitcoins verkopen en stort het in. Vergelijkbaar met wanneer een bank fouten blijkt te maken en iedereen als een gek zijn spaargeld opneemt. Vooralsnog zijn er geen tekenen dat zo&amp;#39;n fout gevonden gaat worden. En zijn er veel wiskundigen en theoretici die beweren dat dit ook niet gevonden kán worden. en zijn er veel mensen als een gek aan het zoeken naar zulke lekken (om het systeem plat te gooien, of om enorm veel geld te verdienen met zo&amp;#39;n zwakheid).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Mensen verliezen interesse. Als de interesse wegebt en mensen hun bitcoins weer inruilen tegen Dollars of Euro&amp;#39;s zal het netwerk uitgehold raken: bitcoins worden weer minder waard en diensten eromheen verdwijnen. De meest voor de hand liggende oorzaak van zo&amp;#39;n verminderde aandacht is bijvoorbeeld een alternatief systeem. Vergelijk het met peer-to-peer muziekuitwisseling. iTunes en meer recent spotify bieden betaalde, legale muziekdownloads aan. Omdat dit goede alternatieven zijn, met grote namen erachter, haalt het veel mensen weg uit het bestandsdeling-netwerk. Waardoor dit illegale aanbod zelfs kan krimpen (in werkelijkheid groeit het iets minder hard).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wanneer bijvoorbeeld amazon, of paypal of zelfs de grote creditcard maatschappijen met alternatieven komen, die dezelfde voordelen bieden, kan het zijn dat mensen massaal weglopen uit het bitcoin netwerk. Wanneer echter een kritieke massa in dat bitcoin netwerk zit, zal dit risico klein zijn. Andere onbekenden. Bitcoin is nieuw. Een heel nieuw concept. Met een geheel nieuwe dynamiek. Niemand kan voorspellen wat er gebeuren kan, dus kan het meest onverwachte zomaar opeens dit netwerk onderuit halen. Enkele voorbeelden die critici al opvoerden, zijn: massadeflatie: met de kleinste betaaleenheid (een tiende cent) kun je enkel nog een huis kopen. Internet valt om door grote externe veranderingen (onrust of oorlog in de westerse wereld, energiecrisis et cetera). Iemand koopt ala &amp;lt;a href=&amp;#34;http://www.jamesbondwiki.com/page/Goldfinger&amp;#34;&amp;gt;goldfinger&amp;lt;/a&amp;gt; alle bitcoins op. Enzovoort.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar voorlopig zie ik een enorme potentie. Een (mogelijke) revolutie vergelijkbaar met het ontstaan van de banken in de renaissance. En de mogelijkheid om geld te verhandelen, bewaren en investeren, zonder mee te hoeven doen aan het, door velen verachtte, banksysteem dat we nu hebben.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wil je ook meedoen: installeer dan de &amp;lt;a href=&amp;#34;&amp;#34;&amp;gt;bitcoin client&amp;lt;/a&amp;gt;, en vraag &amp;lt;a href=&amp;#34;http://freebitcoins.appspot.com/&amp;#34;&amp;gt;gratis een piepkleine hoeveelheid bitcoins aan&amp;lt;/a&amp;gt;. Wil je bitcoins verdienen, dan heb je al &amp;lt;a href=&amp;#34;https://en.bitcoin.it/wiki/FAQ#How_can_I_get_Bitcoins?&amp;#34;&amp;gt;een hele serie opties&amp;lt;/a&amp;gt;. Waaronder ze gewoon kopen. En als je dit verhaaltje interessant vond, maak doneer dan, in bitcoins, op mijn rekening: &amp;lt;em&amp;gt;1Hga5LMhjrfSjwtwxhFQnUmfPNmCRpaVvX&amp;lt;/em&amp;gt; :)&amp;lt;/p&amp;gt;</content>
 </entry>
 
 <entry>
   <title>Mailcatcher for Drupal and other PHP-applications - The simple version</title>
   <link href="https://berk.es/2011/05/29/mailcatcher-for-drupal-and-other-php-applications-the-simple-version/"/>
   <updated>2011-05-29T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2011/05/29/mailcatcher-for-drupal-and-other-php-applications-the-simple-version/</id>
   <content type="html">&amp;lt;p&amp;gt;This is an updated version of my &amp;lt;a href=&amp;#34;/2011/05/28/mailcatcher-for-drupal-and-other-php-applications/&amp;#34;&amp;gt;earlier post&amp;lt;/a&amp;gt;. Since msmtp is no longer needed, things are a lot simpler, hence the new article.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Problem:&amp;lt;/strong&amp;gt; on development (and test) you don&amp;#39;t want to send out mail. But you &amp;lt;em&amp;gt;do&amp;lt;/em&amp;gt; want to test it. You certainly don&amp;#39;t want to be in my shoes when a client called me, telling she recieved dozens of confused and angry mails from users on her site, after I fired up cron on my local development machine. And sent out approximately 3000 notification mails to users, with stuff like &amp;amp;quot;new post for you: &amp;amp;quot;W000t, fieldz0rz developmentz in CCK is workinggggg!&amp;amp;quot; (I am making this up now. Allthough.... ;) )&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Problem:&amp;lt;/strong&amp;gt; when debugging mail, you want to inspect the headers and often (in case of multipart or HTML mail) the source too. Most emailclients are crap for that (and right so: who other then the odd mail/webdeveloper needs to inspect the source of a mail. ever?)&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Solution:&amp;lt;/strong&amp;gt; the brilliant Ruby application named &amp;lt;a href=&amp;#34;https://github.com/sj26/mailcatcher&amp;#34;&amp;gt;mailcatcher&amp;lt;/a&amp;gt;. This is a simple SMTP server and sendmail replacement that shows the mails sent to it in a handy webapplication. The webapplication features debug-tools such as headers, and source displaying.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/mailcatcher_031.png&amp;#34; alt=&amp;#34;Screenshot of a Drupal password recorvery mail in Mailcatcher&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Aside: Windows. It is probably possible, but since using even the most basic proper commandline on there requires lots of hassle, all this is far from as trivial as Mac and Linux. I am sorry, but please use the comments if you go mailcatcher running with PHP on Windows.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;installation&amp;#34;&amp;gt;Installation&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Mac comes with ruby installed. On Ubuntu Linux you may need to install it still:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-bash&amp;#34; data-lang=&amp;#34;bash&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;sudo apt-get install ruby rubygems&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Install mailcatcher (Use sudo for installing systemwide).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-bash&amp;#34; data-lang=&amp;#34;bash&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;gem install mailcatcher&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Configure PHP to use mailcatcher for delivering mail:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Edit &amp;lt;code&amp;gt;php.ini&amp;lt;/code&amp;gt; (Depending on your installation where this lives, but on Ubuntu this is &amp;lt;code&amp;gt;/etc/php4/apache2/php.ini&amp;lt;/code&amp;gt;)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Under &amp;lt;code&amp;gt;[mail function]&amp;lt;/code&amp;gt;, if available, change the &amp;lt;a href=&amp;#34;http://php.net/manual/en/mail.configuration.php#ini.sendmail-path&amp;#34;&amp;gt;sendmail_path&amp;lt;/a&amp;gt; to &amp;lt;code&amp;gt;/usr/bin/env catchmail&amp;lt;/code&amp;gt; and you&amp;#39;re set.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ini&amp;#34; data-lang=&amp;#34;ini&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;sendmail_path&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;/usr/bin/env /var/lib/gems/1.8/bin/catchmail &amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Find out where catchmail lives by invoking &amp;lt;code&amp;gt;$ which catchmail&amp;lt;/code&amp;gt;. On Ubuntu it was installed at &amp;lt;em&amp;gt;/var/lib/gems/1.8/bin/catchmail&amp;lt;/em&amp;gt;. Make sure you have the gems installed system wide, else apache (or the user running the webserver) does not have access to catcmail and the required libraries.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And restart apache.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-bash&amp;#34; data-lang=&amp;#34;bash&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;sudo service apache2 restart&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Start up mailcatcher.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-bash&amp;#34; data-lang=&amp;#34;bash&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;mailcatcher&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Open your browser and visit &amp;lt;a href=&amp;#34;http://localhost:1080&amp;#34;&amp;gt;http://localhost:1080&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;mailcatcher-gotchas-and-tips&amp;#34;&amp;gt;Mailcatcher gotcha&amp;#39;s and tips&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Just terminate (&amp;lt;code&amp;gt;^C-c&amp;lt;/code&amp;gt;) mailcatcher and restart it to flush the recieved mail.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Don´t forget to start up mailcatcher before you start hacking along on your site, If you forget it, mail will not be sent out, but will fail and PHP (Drupal) will give errors on mailing.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;** Happy Mailing on your development machine! **&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Mailcatcher for Drupal and other PHP-applications</title>
   <link href="https://berk.es/2011/05/28/mailcatcher-for-drupal-and-other-php-applications/"/>
   <updated>2011-05-28T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2011/05/28/mailcatcher-for-drupal-and-other-php-applications/</id>
   <content type="html">&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;UPDATE&amp;lt;/strong&amp;gt; Please see the &amp;lt;a href=&amp;#34;/2011/05/29/mailcatcher-for-drupal-and-other-php-applications-the-simple-version/&amp;#34;&amp;gt;newer version of this article&amp;lt;/a&amp;gt;, the latest malcatcher has its own sendmail replacement, making installation for PHP a lot simpler.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Problem:&amp;lt;/strong&amp;gt; on development (and test) you don&amp;#39;t want to send out mail. But you /do/ want to test it. You certainly don&amp;#39;t want to be in my shoes when a client called me, telling she recieved dozens of confused and angry mails from users on her site, after I fired up cron on my local development machine. And sent out approximately 3000 notification mails to users, with stuff like &amp;amp;quot;new post for you: &amp;amp;quot;W000t, fieldz0rz developmentz in CCK is workinggggg!&amp;amp;quot;. Not cool.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Problem:&amp;lt;/strong&amp;gt; when debugging mail, you want to inspect the headers and the source (in case of multipart or HTML mail). Most emailclients are crap for that (and right so: who other then the odd mail/webdeveloper needs to inspect the source of a mail. ever?)&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Solution:&amp;lt;/strong&amp;gt; the brilliant Ruby application named &amp;lt;a href=&amp;#34;https://github.com/sj26/mailcatcher&amp;#34;&amp;gt;mailcatcher&amp;lt;/a&amp;gt;. This is a simple SMTP server, which shows the mails sent to it, in a handy webapplication. The webapplication features debug-tools such as headers, and source displaying.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;img src=&amp;#34;/images/inline/mailcatcher.png&amp;#34; alt=&amp;#34;Screenshot of a Drupal password recorvery mail in Mailcatcher&amp;#34;&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Additional problem: PHP on none-windows machines, cannot deliver mail to an arbitrary SMTP server. It requires a sendmail program being invoked somewhere. Drupal does not allow sending mail to any smtp server without additional configuration. Solution for that is the ultralight sendmail alternative &amp;lt;a href=&amp;#34;http://msmtp.sourceforge.net&amp;#34;&amp;gt;msmtp&amp;lt;/a&amp;gt;. If we configure msmtp to act as sendmail and deliver mail to mailcatcher, we are fine: &amp;lt;em&amp;gt;Drupal » PHP mail() » /bin/msmtp --foo --bar » Mailcatcher&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Aside: Windows. It is probably possible, but since using even the most basic proper commandline on there requires lots of hassle, all this is far from as trivial as Mac and Linux. I am sorry, but please use the comments if you go mailcatcher running with PHP on Windows.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;mac&amp;#34;&amp;gt;Mac&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Install mailcatcher (Use sudo for installing systemwide).&amp;#xA;&amp;lt;code&amp;gt;$ gem install mailcatcher&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Install msmtp, using MacPorts.&amp;#xA;&amp;lt;code&amp;gt;$ sudo port install msmtp&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Configure PHP to use msmtp for delivering mail:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Edit &amp;lt;em&amp;gt;php.ini&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Under &amp;lt;em&amp;gt;[mail function]&amp;lt;/em&amp;gt;, if available, change the &amp;lt;em&amp;gt;sendmail_path&amp;lt;/em&amp;gt; variable. Else just add it to&amp;#xA;&amp;lt;em&amp;gt;php.ini&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;sendmail_path = &amp;amp;quot;/usr/bin/msmtp -t &amp;amp;quot;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And restart apache.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;http://masterleep.com/2010/configuring-msmtp-on-osx-snow-leopard-to-send-through-gmail&amp;#34;&amp;gt;Configure msmtp&amp;lt;/a&amp;gt; with some defaults (this file should probably be named &amp;lt;em&amp;gt;/etc/msmtprc&amp;lt;/em&amp;gt;)&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;account mailcatcher&amp;#xA;host localhost  &amp;#xA;port 1025       # MailCatcher will tell you the port it listens to.&amp;#xA;# Enable logfile for additional troubleshooting.&amp;#xA;# logfile /var/log/msmtp.log&amp;#xA;auto_from on    # From does not work 100% with me, yet, because the envolope-from &amp;#xA;                # is still wrong. But leaving this out makes msmtp fail with PHP.&amp;#xA;&amp;#xA;account default: mailcatcher &amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Start up mailcatcher&amp;#xA;&amp;lt;code&amp;gt;$ mailcatcher&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Open your browser and visit http://localhost:1080&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;linux-ubuntu-debian-and-derivatives&amp;#34;&amp;gt;Linux (Ubuntu, Debian and derivatives)&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;install ruby and rubygems first (if you don&amp;#39;t already have it)&amp;#xA;&amp;lt;code&amp;gt;$ sudo apt-get install ruby rubygems&amp;lt;/code&amp;gt;&amp;#xA;Then install mailcatcher.&amp;#xA;&amp;lt;code&amp;gt;$ gem install mailcatcher&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Install msmtp.&amp;#xA;&amp;lt;code&amp;gt;$ sudo port install msmtp&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Configure PHP to use msmtp for delivering mail:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Edit &amp;lt;em&amp;gt;/etc/php5/apache2/php.ini&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Under &amp;lt;em&amp;gt;[mail function]&amp;lt;/em&amp;gt;, change the &amp;lt;em&amp;gt;sendmail_path&amp;lt;/em&amp;gt; variable.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;sendmail_path = &amp;amp;quot;/usr/bin/msmtp -t &amp;amp;quot;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And restart apache (&amp;lt;em&amp;gt;$sudo service apache2 restart&amp;lt;/em&amp;gt;) to load the new php.ini.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;http://serverfault.com/questions/235723/testing-php-mail-in-localhost-problem&amp;#34;&amp;gt;Configure msmtp so it accepts PHPs sendmail calls&amp;lt;/a&amp;gt; with some defaults. Edit &amp;lt;em&amp;gt;/etc/msmtprc&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;account mailcatcher&amp;#xA;host localhost  &amp;#xA;port 1025       # MailCatcher will tell you the port it listens to.&amp;#xA;auto_from on    # From does not work 100% with me, yet, because the envolope-from &amp;#xA;                # is still wrong. But leaving this out makes msmtp fail with PHP.&amp;#xA;&amp;#xA;account default: mailcatcher &amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Start up mailcatcher&amp;#xA;&amp;lt;code&amp;gt;$ mailcatcher&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Open your browser and visit http://localhost:1080&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;troubleshooting&amp;#34;&amp;gt;Troubleshooting&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If things don&amp;#39;t work out, check the main apache error log. Errors by msmtp will show up there and tell you what went wrong. Use the Logs Luke, use the logs!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You can turn on msmtp logging with &amp;lt;em&amp;gt;logfile /var/log/msmtp.log&amp;lt;/em&amp;gt; in &amp;lt;em&amp;gt;msmtprc&amp;lt;/em&amp;gt;. No need to restart apache for that. Msmtp will tell you what calls it recieved and what parameters it got.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Cut out PHP and &amp;lt;a href=&amp;#34;http://serverfault.com/questions/235723/testing-php-mail-in-localhost-problem&amp;#34;&amp;gt;send a mail with msmtp&amp;lt;/a&amp;gt; to mailcatcher on the commandline&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;echo -e &amp;amp;quot;Subject: Test Mail\r\n\r\nThis is a test mail&amp;amp;quot; |msmtp --debug --from=default -t me@example.com&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If that works, then the problem is between Drupal/PHP and msmtp, and the apache-logs should give a hint (see above).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;mailcatcher-gotchas-and-tips&amp;#34;&amp;gt;Mailcatcher gotcha&amp;#39;s and tips&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Just terminate (^C-c) mailcatcher and restart it to flush the recieved mail.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;On firefox (4) the web interface from mailcatcher did not look that well. Chrome(ium) rendered it fine, though.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Don´t forget to start up mailcatcher before you start hacking along on your site, If you forget it, mail will not be sent out, but will fail and PHP (Drupal) will give errors on mailing.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Happy Mailing on your development machine!&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>&amp;#34;Ruby-on-rails is minder goed dan PHP want het is moeilijk te hosten&amp;#34;</title>
   <link href="https://berk.es/2011/03/31/ruby-on-rails-is-minder-goed-dan-php-want-het-is-moeilijk-te-hosten/"/>
   <updated>2011-03-31T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2011/03/31/ruby-on-rails-is-minder-goed-dan-php-want-het-is-moeilijk-te-hosten/</id>
   <content type="html">&amp;lt;p&amp;gt;Op mijn diverse artikelen over de geschikheid van Drupal ook veel reacties van mensen die de plank helemaal misslaan. Zoals &amp;amp;quot;Kan wel zijn, dat je in Rails makkelijker kan bouwen, maar Rails is moeilijk te hosten&amp;amp;quot;. Sidenote: Ik heb slechts heel weinig ervaring met Python-hosting (enkel trac projectmanagement), dus durft hoogstens te zeggen dat ik verwacht dat onderstaand daar ook voor geldt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ten eerste: Ja! Voor je Ruby on Rails project is minder hosting te vinden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ten tweede: Dat is volledig irrelevant voor de projecten waar we het over hadden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We praten niet over een site van de bakker op de hoek, maar over sites en projecten waar best een werkdag voor hosting uitgetrokken kan worden. Waar meestal zelfs gewoon professionele hostingpartijen betrokken zijn. Met hosting van enkele duizenden euro&amp;#39;s per jaar. De 1-eurohost.biz hoster ondersteunt trouwens meestal ook gewoon Rails, want dat komt al jaren standaard met plesk mee. Dus die bakker op de hoek kan ook heel goed een site in Rails opgeleverd krijgen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;De grootste uitdaging zit bij organisaties en bedrijven die al hostingomgevingen hebben ingeregeld. Meestal voor Java en/of PHP&#43;MySQL. Zelden voor Ruby of Python. Vooral als de beheerders van die omgeving inflexibel zijn, kunnen ze Ruby of Python nogal eens buiten de deur houden. Afhankelijk van de situatie (zoals windows-only hosting), volgens mij overigens volledig terecht, vaak; maar dat is een heel andere discussie. Voor een intranet kan het dan heel goed zijn, dat vanwege deze beperkingen Ruby of Python al direct afvallen. Terzijde: daarmee is de geschiktheid voor ontwikkelen in &amp;amp;quot;een framework&amp;amp;quot; niet minder, alleen de keuze aan frameworks is kleiner. Aan de andere kant heeft de Rubygemeenschap bijvoorbeeld een omgeving als &amp;lt;a href=&amp;#34;http://heroku.com/&amp;#34;&amp;gt;heroku&amp;lt;/a&amp;gt;. Voor Drupalmensen: stel je voor:   - &amp;amp;quot;drush hosting create bakkerophoek&amp;amp;quot;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;amp;quot;git push hosting&amp;amp;quot;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;amp;quot;drush hosting online&amp;amp;quot;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;amp;quot;drush hosting domains add bakkerophoek.nl&amp;amp;quot;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;de factuur volgt enkele minuten later per mail&amp;#xA;(patent pending... :) )Op heroku zet je voor een paar euro een SOLR aan bij je site (stel je voor dat &amp;amp;quot;drush hosting solr enable&amp;amp;quot; alles is dat je moet doen voor SOLR!). Voor een paar euro per maand wat extra CPU-power of draai je voor enkele tientjes per jaar volledige master-slave database omgevingen achter je site. En deploy je met enkele commando&amp;#39;s, geïntegreerd in je revisiebeheersysteem. Hosting is nog nooit zo makkelijk en betaalbaar geweest. Ik wou dat dit &amp;lt;a href=&amp;#34;https://phpfog.com/&amp;#34;&amp;gt;voor PHP zo makkelijk kon&amp;lt;/a&amp;gt;.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wanneer mensen dus roepen dat bijvoorbeeld &amp;lt;a href=&amp;#34;https://joindiaspora.com/&amp;#34;&amp;gt;Diaspora&amp;lt;/a&amp;gt; nooit iets kan worden, omdat het in Ruby gebouwd is en dus moeilijk de deployen, klopt dat maar deels. Het soort mensen dat Diaspora zal deployen, is bekend met hosting en servers. Dat zijn niet de mensen die al vastlopen op het installeren van FileZilla, maar de geeks die het leuk vinden om met ngix of apache-proxies te prutsen. Diaspora is eigenlijk eerder gericht op &amp;amp;quot;kleine&amp;amp;quot; lokale community-sites dan op &amp;amp;quot;iedereen en zijn moeder&amp;amp;quot; een eigen Diaspora: Eerder Hyves die op Diaspora haar community bouwt, dan op &amp;amp;quot;je neefje en zijn twee vrienden van de Geheime Piratenclub&amp;amp;quot;. Het probleem is inderdaad dat een Ruby-project als &amp;lt;a href=&amp;#34;http://rstat.us/&amp;#34;&amp;gt;rstat.us&amp;lt;/a&amp;gt; daarmee een nadeel heeft ten opzichte van het op PHP en MySQL gebaseerde &amp;lt;a href=&amp;#34;http://status.net&amp;#34;&amp;gt;status.net&amp;lt;/a&amp;gt;. Die laatste is op iedere 1-eurohost te installeren, door iedereen met een basiskennis webmastering of webdevelopment. Een grotere doelgroep, maar mogelijk niet de juiste doelgroep. Het bereik door het aantal installaties is groter, maar dat zegt (vooralsnog) niets over het bereik van gebruikers ervan.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Die Railsprojecten vereisen op zijn minst enige ervaring met de commandline. Maar eigenlijk gewoon kennis van (web)serverbeheer. Dat hoeft geen dure zeldzame ontwikkelaar te zijn, maar kan iedereen zijn die op een zondagmiddag een vps kan inregelen, voor enkele tientjes. Of iemand die bij een hoster werkt en voor jou de goede omgeving klaarzet.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Teruggrijpend op mijn eerdere betogen: een professioneel project heeft zo iemand erbij betrokken. Óók voor PHP-projecten, anders, zo durf ik te stellen, is het simpelweg geen professioneel of groot project.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Iemand die met moeite een goede Drupal- of wordpress-omgeving kan inrichten zal inderdaad erg teleurgesteld zijn in wat vereist wordt om een Django- of Rails- product uit te rollen. Maar zo iemand is nooit maatgevend voor de geschiktheid van die producten in een professioneel, groter project: zo iemand huurt dan iemand in die kennis van het uitrollen van servers heeft.&amp;lt;/p&amp;gt;</content>
 </entry>
 
 <entry>
   <title>Simplest authentication in Rails: Basic Authentication with a logged_in? helper.</title>
   <link href="https://berk.es/2011/03/29/simplest-authentication-in-rails-basic-authentication-with-a-logged_in-helper/"/>
   <updated>2011-03-29T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2011/03/29/simplest-authentication-in-rails-basic-authentication-with-a-logged_in-helper/</id>
   <content type="html">&amp;lt;p&amp;gt;The, by far, simplest solution to add some form of authentication in Rails is basic authentication. It has a lot of downsides, but the simplicity is such a benefit that it may just outweight.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Downsides are, amongst others:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;No users, no user-manangement.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Your username and password are hardcoded in the application.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;No fancy or good looking login screens: just the basic HTTP login provided by your browser.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;No logout, other then closing the browser.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Here is a simple implementation for a simple app I needed. Since I am the only editor, there is no need to introduce session controllers, user models and so on. If you are relatively new to Rails (like me) you may miss this most simple solution and dive right into &amp;lt;em&amp;gt;devise&amp;lt;/em&amp;gt; or &amp;lt;em&amp;gt;authlogic&amp;lt;/em&amp;gt; or start &amp;lt;a href=&amp;#34;http://ruby.railstutorial.org/chapters/sign-in-sign-out&amp;#34;&amp;gt;writing your own&amp;lt;/a&amp;gt;. And miss out that 10-minutes-and-you&amp;#39;re-done solution.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;First, we introduce a basic authenticate method, that can be used troughout our controllers.&amp;#xA;This method uses the Rails/Rack helper &amp;lt;a href=&amp;#34;http://apidock.com/rails/ActionController/HttpAuthentication/Basic/ControllerMethods/authenticate_or_request_with_http_basic&amp;#34;&amp;gt;authenticate_or_request_with_http_basic&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;ApplicationController&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&amp;amp;lt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;ActionController&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;::&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;Base&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  protect_from_forgery&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;protected&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;authenticate&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      authenticate_or_request_with_http_basic &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;do&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;|&amp;lt;/span&amp;gt;username, password&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;|&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;        username &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;==&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;USER_ID&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&amp;amp;amp;&amp;amp;amp;&amp;lt;/span&amp;gt; password &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;==&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;PASSWORD&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;In a controller, we can then add a &amp;lt;a href=&amp;#34;http://guides.rubyonrails.org/getting_started.html#security&amp;#34;&amp;gt;before_filter&amp;lt;/a&amp;gt; to require authentication for all methods but the &amp;lt;em&amp;gt;index&amp;lt;/em&amp;gt; and the &amp;lt;em&amp;gt;show&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;class&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;ImagesController&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&amp;amp;lt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;ApplicationController&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  before_filter &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:authenticate&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:except&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:index&amp;lt;/span&amp;gt;, &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;:show&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;]&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#75715e&amp;#34;&amp;gt;#...&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;A new file under &amp;lt;em&amp;gt;config/initializers&amp;lt;/em&amp;gt;, named &amp;lt;em&amp;gt;user.rb&amp;lt;/em&amp;gt; or anything else you want, contains the hardcoded username or password. Putting it in a separate file allows you to leave it out of your version-control, for example.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;USER_ID&amp;lt;/span&amp;gt;   &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;Sauron&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;PASSWORD&amp;lt;/span&amp;gt;  &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#34;s3cr3t&amp;amp;#34;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Furhtermore, we define a &amp;lt;em&amp;gt;logged_in?&amp;lt;/em&amp;gt; helper, usefull in our views. This checks if the authorization is a string (it is set) or &amp;lt;em&amp;gt;nil&amp;lt;/em&amp;gt; (user is not authorized):&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;module&amp;lt;/span&amp;gt; ApplicationHelper&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;def&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a6e22e&amp;#34;&amp;gt;logged_in?&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;   &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;not&amp;lt;/span&amp;gt; request&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;authorization&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;.&amp;lt;/span&amp;gt;nil?&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt; &amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#66d9ef&amp;#34;&amp;gt;end&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;Using that helper is simple too. E.g. &amp;lt;em&amp;gt;show.html.erb&amp;lt;/em&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ruby&amp;#34; data-lang=&amp;#34;ruby&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&amp;amp;lt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;% if &amp;lt;/span&amp;gt; logged_in? &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;%&amp;amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;  &amp;amp;lt;li&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&amp;amp;lt;%=&amp;lt;/span&amp;gt; link_to &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;&amp;amp;#39;Edit&amp;amp;#39;&amp;lt;/span&amp;gt;, edit_image_path(@image) &amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;%&amp;amp;gt;&amp;amp;lt;/li&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;&amp;amp;lt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#e6db74&amp;#34;&amp;gt;% end &amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#f92672&amp;#34;&amp;gt;%&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;#xA;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;I am not certain if this evaluation of &amp;lt;em&amp;gt;request.authorization.nil?&amp;lt;/em&amp;gt; performs all that well, but I would say, it being simple as possible, that the overhead is minimal.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Enkele goede reacties op mijn stelling &amp;#34;Drupal verkeerde keus voor overheidssites&amp;#34; uitgelicht</title>
   <link href="https://berk.es/2011/03/22/enkele-goede-reacties-op-mijn-stelling-drupal-verkeerde-keus-voor-overheidssites-uitgelicht/"/>
   <updated>2011-03-22T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2011/03/22/enkele-goede-reacties-op-mijn-stelling-drupal-verkeerde-keus-voor-overheidssites-uitgelicht/</id>
   <content type="html">&amp;lt;p&amp;gt;Behalve de vele reacties zonder enige onderbouwing, of zelfs enkele &amp;lt;a href=&amp;#34;http://twitter.com/#!/bertboerland/status/49933911840657410&amp;#34;&amp;gt;ad-hominem&amp;lt;/a&amp;gt; drogredenen, kreeg mijn &amp;lt;a href=&amp;#34;http://webwereld.nl/opinie/106086/drupal-verkeerde-keus-voor-overheidssites--opinie-.html&amp;#34;&amp;gt;http://webwereld.nl/opinie/106086/drupal-verkeerde-keus-voor-overheidssites--opinie-.html&amp;lt;/a&amp;gt; artikel op webwereld ook een paar heel goede argumenten.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;De meerderheid, was, tegen verwachting, positief. Tegen verwachting, omdat olifanten in porseleinkasten meestal weggejaagd worden :).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;De enige inhoudelijke reactie waar ik niet meteen een antwoord op heb, is van &amp;lt;a href=&amp;#34;http://webwereld.nl/article/comments/id/106086&amp;#34;&amp;gt;MexMast op Maandag 21 Maart 2011 17:07&amp;lt;/a&amp;gt; (helaas kan ik niet direct linken naar webwereld reacties, dus even zelf zoeken.)&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;mja lijkt mij een gevaarlijke veralgemening [zie hieronder, BK], als wanhoopsargument. dat het om veel geld gaat veranderd weinig aan het geheel. Tuurlijk kom je er niet door je neef 50 euro te geven voor een zondagje werk.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Je zegt letterlijk dat alle overheids websites die drupal gebruiken de verkeerde keuze hebben gemaakt. En dat is vrees ik, uw persoonlijke voorkeur ten spijt, manifest onjuist.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;De voorbeelden van overheids websites die op drupal draaien zijn veelvuldig aanwezig, tenzij je een paar voorbeelden kan aanhalen van drupal websites en vergelijkbare websites waar de drupal variant veel duurder uitdraaide qua total cost of ownership of functionaliteit. Ik betwijfel het ten zeerste.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dat ik &amp;lt;em&amp;gt;letterlijk&amp;lt;/em&amp;gt; zeg dat alle overheids websites die Drupal gebruiken een verkeerde keus maakten is niet waar. Ik insinueer dat wél, maar  blijf telkens terugkomen op &amp;amp;quot;grote Drupalprojecten&amp;amp;quot;. Mijn oorspronkelijke titel was ook &amp;lt;strong&amp;gt;Er zijn betere alternatieven voor Drupal&amp;lt;/strong&amp;gt;. De Webwereld redactie heeft dat wat aangescherpt; omdat ik dat toch in het artikel zélf goed onderbouw en nuanceer.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik zeg zeker niet dat overheden verre van Drupal moeten blijven. Ik herhaal: voor veel grote Drupalprojecten kan beter een andere omgeving worden gekozen. Wat ik verder onderbouw.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dat er daarmee dus ook veel overheids-website successvol op Drupal draaien is eenvoudig bewezen en herhaaldelijk aangekaart in reacties. Deze reacties hebben het artikel selectief gelezen, of eenvoudigweg niet gelezen. Want dan hadden ze zeker begrepen dat ik nergens beweer dat &amp;lt;em&amp;gt;ieder&amp;lt;/em&amp;gt; overheidsproject &amp;lt;em&amp;gt;bij Drupal vandaan moet blijven&amp;lt;/em&amp;gt;. Maar dat ik stel dat &amp;lt;em&amp;gt;grote, bouw-intensieve projecten beter af zijn met andere omgevingen dan Drupal&amp;lt;/em&amp;gt;. Deze nuance kan ik schijnbaar niet vaak genoeg herhalen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;De &amp;lt;em&amp;gt;veralgemening&amp;lt;/em&amp;gt; slaat op mijn eerdere reactie:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Helaas zijn dit soort projecten altijd voor het grootste deel &amp;amp;quot;ontwikkel&amp;amp;quot;-klussen. Minstends de helft van het budget gaat op aan development. En zelden hebben deze klussen budgetten met minder dan vier nullen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En dat is veel te kort door de bocht en verdient verdieping.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik heb heel veel offertes gezien en geëvalueerd. Het merendeel Drupalprojecten. Ik ken geen project waarin het geöffreerde bedrag voor &amp;lt;strong&amp;gt;development&amp;lt;/strong&amp;gt; onder de 80% komt of kwam. Het dichtbij komt een offerte waarbij het gevraagde bedrijf ook alle TO en FO&amp;#39;s moest schrijven, de designs moest opleveren en mee-deed met de brainstorms. Daarbij was het opstartbedrag zó substantieel dat het ontwikkelbudget slechts 60% van het geheel uitmaakte uiteindelijk. Een project om een site te bouwen, zal altijd een substantieel bedrag voor het bouwen van de site bevatten. Wanneer in een web-project van twee ton slechts vijfduizend Euro vrijgemaakt is voor het bouwen van de site (97,5% overhead) is er iets &amp;lt;strong&amp;gt;grondig&amp;lt;/strong&amp;gt; mis met het project, en maakt de gekozen techniek inderdaad niet uit.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Voor de tweede kamer staat een vacature online waarin een Drupalontwikkelaar gevraagd wordt. Een duidelijkere hint dat er in dit specifieke project ontwikkeld gaat worden kan ik niet bedenken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik kan vertrouwelijke informatie niet vrijgeven (en wil dat ook niet) maar het voldoet om te zeggen dat het budget voor deze tweedekamer-site &amp;amp;quot;groot&amp;amp;quot; is. Openbare informatie over andere gemeenteprojecten met Drupal laten allemaal zien dat daarin honderden uren is ontwikkeld.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Overheden en gemeentes hebben inderdaad met veel meer te maken dan een klein ontwikkelklusje:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;@berkes &amp;lt;a href=&amp;#34;http://twitter.com/#!/SqyD/status/49864730847870976&amp;#34;&amp;gt;The issues you mention&amp;lt;/a&amp;gt; could be about any cms/fw package. Imho most problems with governments sites lie in OSI-layer 8: politics&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar ook met veel complexe omgevingen. Met legacy-databases, ingewikkelde koppelingen, single sign-on, elder of eerder geïmplementeerde diensten enzovoort:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;http://twitter.com/#!/KrishnaKurvers/status/49944483000881152&amp;#34;&amp;gt;Krishna Kurvers&amp;lt;/a&amp;gt;@berkes @bertboerland binnen mijn sector worden diverse open source #CMS overwogen. past niet bij deze tijd van #nora en #sga. Mijn mening&amp;lt;img src=&amp;#34;http://twitter.com/#!/KrishnaKurvers/status/50073427574398976&amp;#34; alt=&amp;#34;Krishna Kurvers&amp;#34;&amp;gt;@berkes #OSS voorstander van, maar #CMS is achterhaalt. Werkt niet goed bij gekoppelde overheden met modulaire bouw en hergebruik data.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Nogmaals: hiermee is niet gezegd dat automatisch alle overheidsprojecten hiermee te maken hebben. Overheden zetten ook regelmatig tijdelijke, kleine of ingekapselde sites in. Voor wijkinformatie, evenementen, PR, acties, bouwprojecten enzovoort. In dit soort projecten kan veel vaker wel dan niet, een CMS ideaal ingezet worden. Voor de &amp;lt;a href=&amp;#34;http://www.nieuwevoorstad.nl&amp;#34;&amp;gt;site van het bouwproject naast mijn deur&amp;lt;/a&amp;gt;, bijvoorbeeld, is een CMS misschien zelfs al te groot en complex. Laat staan dat een raamwerk daar efficient ingezet kon worden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Kortom: eenieder die roept dat ik specs moet kennen, of dat een overheidsproject meer is dan ontwikkeling alleen, vraag ik te reageren. Geef me voorbeelden, indicaties, projecten en sites waarbij:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Er niet, of minimaal ontwikkeld is. Waar Drupal als eenvoudige blokkendoos is ingezet, met een leuk theme erbij en misschien een enkel custom module-tje. Kortom: dat Drupal is ingezet in een project waar het optimaal voor is. En of het totale budget hiervoor minder dan 4 nullen had.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Het ontwikkelen, optuigen, migreren en ander development-werk uiteindelijk minder dan 30% van het totale budget opneemt. Waar het, als geheel, om een groot project ging, maar het inzetten van Drupal slechts een klein onderdeeltje is. Waarbij binnen een &amp;amp;quot;groot&amp;amp;quot; project dus toch nauwelijks ontwikkeld is.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Voor die projecten zou inderdaad van de buitenkant lijken dat Drupal niet het goede systeem is, want teveel ontwikkeling. Maar zal bij nader inzien Drupal toch zeer geschikt blijken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Voor alle andere projecten is het veilig om heel kort door de bocht te stellen dat ze &amp;amp;quot;Substantiële ontwikkeltijd en -budget&amp;amp;quot; vergden. Dus dat het ontwikkelen een groot onderdeel was. Dus dat de keuze van het onderliggende ontwikkelplatform belangrijk is.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik heb namelijk, niet verbazend, geen enkele reactie gehad die het tegendeel beweerde van mijn stelling dat &amp;lt;strong&amp;gt;Drupal voor development niet optimaal is&amp;lt;/strong&amp;gt; en dat daar &amp;lt;strong&amp;gt;veel betere omgevingen voor zijn, zoals frameworks&amp;lt;/strong&amp;gt;. Dat is niet verbazend, omdat zelfs de meest verstokte Drupal-gelovers ook inzien dat een CMS hierin niet sterk is. Dat dat ook helemaal niet erg is: een CMS heeft immers niet tot doel een ontwikkelplatform te zijn.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik hoop te horen van alle mensen die het niet eens zijn met mijn stelling, en voorbeelden of argumenten aandragen waarbij overheidssites wel degelijk gebaat zijn bij een &amp;amp;quot;kant-en-klaar&amp;amp;quot; product als Drupal, en met een ontwikkelomgeving slechter af zijn.&amp;lt;/p&amp;gt;</content>
 </entry>
 
 <entry>
   <title>Val ik daar Drupal aan of af?</title>
   <link href="https://berk.es/2011/03/21/val-ik-daar-drupal-aan-of-af/"/>
   <updated>2011-03-21T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2011/03/21/val-ik-daar-drupal-aan-of-af/</id>
   <content type="html">&amp;lt;p&amp;gt;Nee. Dat doe ik niet. Reacties die ik kreeg (sommigen samengevat)&amp;amp;gt; Je gooit je eigen ruiten in!(Achtergrond: Ik ben vooral Drupalontwikkelaar, dus is het niet handig om het product dat brood op mijn plank brengt af te vallen.)&amp;amp;gt; ik vind het jammer dat je &amp;lt;a href=&amp;#34;http://bler.webschuur.com/drupal_is_ook_minder_geschikt_voor_afwijkende_custom_interactie_en_functionele_ontwerpen#comment-7478&amp;#34;&amp;gt;concreet Drupal noemt en niet &amp;#39;CMS in het algemeen&amp;#39;&amp;lt;/a&amp;gt;. Wat jij beschrijft is inherent aan een CMS. Elke CMS kent haar manier van workflows en registratieafhandelijk en dus kost het maatwerk om hiervan af te wijken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Of &amp;amp;gt; Als jij het project zo belicht, is dat ook slecht voor de Drupalgemeenschap.(Achtergrond: De Drupalcommunity is misschien wel het belangrijkste onderdeel van Drupal. Mogelijk belangrijker dan het product zélf).&amp;amp;gt; Jammer, want nu vallen gemeentes misschien weer terug op hun oude vertrouwde Closed Source CMSen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dit is allemaal, deels, waar. En heeft ook zeker meegespeeld bij het schrijven van deze artikelen en het herschrijven van het verhaal voor Webwereld.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Zoals iemand anders &amp;lt;a href=&amp;#34;http://bler.webschuur.com/geen_cms_en_al_zeker_geen_drupal_voor_grote_webprojecten_zoals_gemeentesites&amp;#34;&amp;gt;reageerde&amp;lt;/a&amp;gt;: &amp;lt;em&amp;gt;een gedurfd artikel waar je vast een tijd op hebt gebroed.&amp;lt;/em&amp;gt;. Inderdaad ik heb al diverse malen op het punt gestaan dit te publiceren en telkens afgezien daarvan, omdat ik teveel mogelijk negatief effect zag. Laat ik daarom mijn persoonlijke drijfveren van het voeren van mijn onderneming opvoeren, in willekeurige volgorde.* Geld verdienen (middellange termijn drijfveer).* Mooie, succesvolle en prettige projecten afronden.(dagelijkse, kortetermijn drijfveer).* (het gebruik van) Open Source, (daarmee) Transparantie en Open Standaarden bevorderen (ideologische en langetermijn drijfveer).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Beginnend met waarom ik &amp;lt;strong&amp;gt;concreet Drupal noem&amp;lt;/strong&amp;gt; en niet &amp;lt;strong&amp;gt;het concept CMS&amp;lt;/strong&amp;gt;. Drupal is momenteel de keuze bij steeds grotere (overheids) projecten. Overal, niet alleen in Nederland. Alle andere Open Source projecten lijken van de radar te verdwijnen. Tot ik gewezen werd op &amp;lt;a href=&amp;#34;http://twitter.com/#!/JoGnet/status/44715902809284608&amp;#34;&amp;gt;DevCMS&amp;lt;/a&amp;gt;. Dat systeem raakte precies de kern van Het Stuk Dat Ik Niet Durfde Te Plaatsen. DevCMS is mogelijk (maar mogelijk ook niet) een veel betere oplossing. Waar het mij echter vooral om gaat is dat deze &amp;lt;strong&amp;gt;werkwijze&amp;lt;/strong&amp;gt; van DevCMS, precies is wat ik mis zie gaan in zoveel Drupal projecten. De Open Source community is beter gebaad bij low-level oplossingen dan eindproducten: Aan het design van gemeente-kerkstraaaten.nl heeft niemand in de Drupalgemeenschap iets. Aan de exacte configuratie ook niet. Maar aan DigiD bibliotheken, aan geavanceerde workflow en dergelijke wél. Wanneer Drupal als framework ingezet wordt, is dat laatste ook het resultaat voor de gemeenschap. Ik ben van mening dat op dit moment, veel van deze &amp;amp;quot;enterprice Drupalprojecten&amp;amp;quot; niks opleveren voor de Drupal gemeenschap. Het enige dat ik zie is een heel mooie Rijkshuisstijl theme en een DigiD module. Als je uitrekent dat, heel grove schatting, er ruim een miljoen budget aan Drupalontwikkeling van overheidssites doorheen is gejaagd, is het resultaat voor de community abominabel. Het netto effect van zulke grote projecten, is dus enkel &amp;lt;em&amp;gt;naamsvermelding&amp;lt;/em&amp;gt; een &amp;lt;em&amp;gt;beeldvorming&amp;lt;/em&amp;gt;. Drupal heeft een natuurlijke habitat waar het zich goed voelt: projecten waar Drupal helemaal voor geschikt is. En Drupal heeft een habitat waar het maar moeizaam kan overleven. Wanneer we Drupal te vaak in die laatste omgeving inzetten, ontstaat er frictie. Klanten worden ontevreden (vaak terecht) over Drupal, of de site die met Drupal gebouwd werd. Het publiek ziet veel gerotzooi, en gaat &amp;lt;a href=&amp;#34;http://www.geenstijl.nl/mt/archieven/2011/01/de_wat_kostte_deze_website_qui.html&amp;#34;&amp;gt;FUD&amp;lt;/a&amp;gt;, verspreiden. De schuld wordt bij Drupal neergelegd. Dan blijkt die &amp;lt;em&amp;gt;naamsvermelding&amp;lt;/em&amp;gt; en &amp;lt;em&amp;gt;beeldvorming&amp;lt;/em&amp;gt; opeens vaak negatief. Ik moet er niet aan denken dat we de kop te lezen krijgen:  &amp;lt;strong&amp;gt;Noord Holland breekt Drupal-infrastructuur af, wegens budget en kostenoverschrijdingen&amp;lt;/strong&amp;gt; Dan is het netto resultaat helemaal negatief. Noord Holland doet, overigens, zover ik weet, niets met Drupal.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wanneer Drupal vaker de schuld krijgt van minder geslaagde projecten is dat vervelend voor Drupal. Maar nóg vervelender voor Open Source. Als we blijven roepen dat The Gimp het beste is wat Open Source heeft voortgebracht, kan ik me heel goed voorstellen dat zovelen zo geweldig negatief zijn over Open Source (in het algemeen). The Gimp is een heel complex, geavanceerd en goed fotobewerkingsprogramma. Maar het is niet voor de leek. En vergt ook enorme aanpassingen van de graficus die van Photoshop komt. Voor bepaalde omgevingen, is the Gimp ideaal. Maar voor veel omgevingen ook helemaal niet.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Als we van Drupal een soort tweede Gimp maken, door het overal in te zetten waar het negatief afstraalt op Open Source, zie ik bijna nog liever, helemaal geen Drupal meer ingezet worden. Omdat ik mijn drie drijfveren in balans wil houden, vertel ik klanten vaak dat ze niet Drupal moeten gebruiken: liever geen project dan een frustrerend, te duur, lelijk project. Maar wil ik ook alles in het werk stellen om Open Source goed en succesvol ingezet te zien worden. En hoop ik dat Drupal daarmee sterke kanten veel beter gebruikt ziet worden, in plaats van zich ingezet ziet worden in projecten waar het geen fatsoenlijke kans van slagen heeft, enkel omdat een Drupalontwikkelaar niet durft te zeggen dat Drupal hier ongeschikt voor is.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Liever tweehonderd kleinere, tevreden Drupalgebruikers, dan één duur overheidsproject waar Drupal zich niet in thuisvoelt.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Minor sidenotes for Tagadelic users, regarding SA-CONTRIB-2011-013</title>
   <link href="https://berk.es/2011/03/16/minor-sidenotes-for-tagadelic-users-regarding-sa-contrib-2011-013/"/>
   <updated>2011-03-16T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2011/03/16/minor-sidenotes-for-tagadelic-users-regarding-sa-contrib-2011-013/</id>
   <content type="html">&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;http://drupal.org/project/tagadelic&amp;#34;&amp;gt;Tagadelic&amp;lt;/a&amp;gt;, Drupals tag-cloud module, was found with a security vulnerability. From the &amp;lt;a href=&amp;#34;http://drupal.org/node/1095030&amp;#34;&amp;gt;advisory&amp;lt;/a&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The module does not sanitize some of the user-supplied data before&amp;#xA;displaying it on abovementioned cloud pages, leading to a Cross Site&amp;#xA;Scripting &amp;lt;a href=&amp;#34;http://en.wikipedia.org/wiki/Cross-site_scripting&amp;#34;&amp;gt;XSS&amp;lt;/a&amp;gt;&amp;#xA;vulnerability that may lead to a malicious user gaining full&amp;#xA;administrative access.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This vulnerability is mitigated by the fact that the attacker must have&amp;#xA;a role with the &amp;#39;administer taxonomy&amp;#39; permission which should generally&amp;#xA;only be granted to trusted roles.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;http://drupalcode.org/project/tagadelic.git/commitdiff/da68e63&amp;#34;&amp;gt;The fix simply escapes&amp;lt;/a&amp;gt; the description and the title before they are passed along.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This may cause problems to the people who &amp;amp;quot;abused&amp;amp;quot; this vulnerability. Admins who, for example, had embedded video, HTMl markup or javascript in the description of their tag cloud page, will no longer see this after upgrading.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For them, there is no simple solution, other then the strongly discouraged &amp;amp;quot;solution&amp;amp;quot; of not upgrading. I discourage this not only for security reasons, but also, because any future release will re-introduce this issue.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Taxonomy descriptions and titles were never meant to hold any markup in the first place, so if this upgrade hits people, they were abusing a Drupal-non-feature in the first place.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A better solution would be to place such markup in a block and embed that in the theme (in a region). That way you use the proper Drupal-tools for the proper job.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Also note that the &amp;lt;strong&amp;gt;unreleased Drupal 7 branch is not yet fixed&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Drupal is ook minder geschikt voor afwijkende, custom interactie en functionele ontwerpen</title>
   <link href="https://berk.es/2011/03/10/drupal-is-ook-minder-geschikt-voor-afwijkende-custom-interactie-en-functionele-ontwerpen/"/>
   <updated>2011-03-10T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2011/03/10/drupal-is-ook-minder-geschikt-voor-afwijkende-custom-interactie-en-functionele-ontwerpen/</id>
   <content type="html">&amp;lt;p&amp;gt;Op mijn artikel over &amp;lt;a href=&amp;#34;http://bler.webschuur.com/geen_cms_en_al_zeker_geen_drupal_voor_grote_webprojecten_zoals_gemeentesites&amp;#34;&amp;gt;waarom Drupal voor grote projecten niet de meest geschikte tool is&amp;lt;/a&amp;gt;, kreeg ik ook wat reacties in de trant van&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Jij bekijkt het alleen vanuit de technische kant. En vergeet het designen, functioneel ontwerpen en het beheer.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dit ben ik niet vergeten, maar heb ik expres weggelaten om het verhaal niet nóg langer te maken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ter volledigheid: Functioneel ontwerp, interaction design en natuurlijk het grafisch ontwerpen zijn zaken die allemaal vooraf gaan aan het bouwen van een site.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Hier doet zich in het geval van een CMS een interessant gegeven voor, namelijk dat het CMS erg beperkend werkt op de mogelijkheden binnen zo een ontwerp. Zo roep ik bij bijna alle Drupal-projecten:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Er moet dan wel een techneut bijzitten die kan bijsturen, zodat we Drupal optimaal gebruiken en geen zaken gaan bouwen die in Drupal heel moeilijk blijken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Klinkt redelijk: dat de gebruikte techniek optimaal ingezet wordt. Dat je al tijdens de eerste ideeënfase Drupals ins- en outs leert kennen en je daardoor laat leiden, is geen verkeerde werkwijze.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En zo voorkom je honderden uren ontwikkelen van een detail, dat, achter bezien, die honderden uren helemaal niet waard blijkt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ook een goed idee: om zaken die al voor jou uitgewerk werden gratis in te zetten: scheelt geld.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Als je uit gaat van &amp;amp;quot;standaard waar het kan, maatwerk als het echt moet&amp;amp;quot; en je bent als klant al tevreden met hetgeen out of the box kan worden geconfigureerd (verwachtingen-management!) dan ben je een framework of custom code al tonnen verder om na te bouwen wat je met Drupal en alle contribs al hebt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een meer concreet voorbeeld:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Gebruik gewoon de standaard Drupal login- en registratieprocedure, dan hoeven we daar niet eens over na te denken en ook niets voor te ontwikkelen (en te onderhouden).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar wat nu als je wél die vrijheid van ontwerpen wilt? Om dat login-verhaal erbij te halen: een goede businesscase, waarmee je conversie-ratio omhoog schijnt te schieten is: &amp;lt;em&amp;gt;mensen kunnen anoniem een comment posten en krijgen pas daarná een login- of registreer scherm. Met de optie om met openid, twitter, facebook enzovoort te registreren.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dan verdwijnt al het voordeel van een gratis, al uitgedacht systeem. En dat is precies wat ik overal zie gebeuren: duizenden euro&amp;#39;s in een offerte voor een systeem dat afwijkt van hoe het CMS het &amp;amp;quot;al deed&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Het argument &amp;amp;quot;standaard waar het kan, maatwerk als het echt moet&amp;amp;quot; onderschrijft daarmee mijn betoog alleen maar: een standaardproduct is simpel, goedkoop en snel uit te rollen (en als &amp;lt;em&amp;gt;dat&amp;lt;/em&amp;gt; al niet zo is, is er its anders, grondig, mis). Dan is een budget van enkele tonnen niet te verantwoorden: Die tonnen zitten in de praktijk altijd in dat &amp;amp;quot;maatwerk als het echt moet&amp;amp;quot;. En is de cirkel rond: &amp;amp;quot;standaard waar het kan, (een klein beetje) maatwerk als het echt moet en anders een tool die veel geschikter is voor maatwerk&amp;amp;quot;. Waarbij Drupal dus vooral ingezet kan worden voor kleinere, goedkopere projecten en de grote projecten (zoals overheids- of enterpricesites) meer geschikte tools moeten hebben.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Men zegt niet voor niets dat &amp;lt;a href=&amp;#34;http://developmentseed.org/blog/2009/oct/28/smallcore-manifesto-help-us-build-better-teddy-bear&amp;#34;&amp;gt;A large portion of time spent building [...] is spent undoing the assumptions that Drupal has baked into core directly.&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En daarmee hebben we design, ontwerp, en beheer ook meteen te pakken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;design&amp;#34;&amp;gt;Design:&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Designers moeten zich confirmeren naar het CMS en hoe daarin zaken gedaan worden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Nu moet gezegd worden dat Drupal misschien niet het makkelijkst te leren is voor ontwerpers (themers), maar zeker een van de (zo niet de aller-) flexibelste qua design.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Overigens heerst onder Drupal-ontwerpers ook veel ongenoegen over dat themen. Dat heeft voor een groot deel een architectuur-technische oorzaak: Drupal is niet MVC, heeft geen &amp;amp;quot;ontworpen&amp;amp;quot; theme-laag, maar grotendeels een organisch gegroeid &amp;amp;quot;gebied&amp;amp;quot;. Dat resulteerde in een inconsistente en rommelige &amp;amp;quot;interface&amp;amp;quot;, waarbij interface de gereedschapskist is, waarmee de ontwerper/themer aan de slag gaat.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;interactie-ontwerp&amp;#34;&amp;gt;(Interactie) Ontwerp&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een framework doet veel minder (tot geen) aannames over interactieontwerp. Een CMS doet dat wél. Een CMS biedt (duidelijk) afgebakende grenzen aan: zo doen we het en niet anders.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wanneer in het CMS deze interactie heel goed voor een bepaald doel op maat gemaakt is, heb je, wat ik eerder noemde, een &amp;lt;strong&amp;gt;hyper gefocused CMS&amp;lt;/strong&amp;gt;. PHPBB, een forum-tool, hoeft ook bijna geen aangepast interactieontwerp: het is immers al helemaal geöptimaliseerd voor het beheren van forums. Dat PHPBB je beperkt in de vrijheid zelf je admin- interfaces, workflows en dergelijke te ontwerpen, is nauwelijks van belang.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar een CMS dat fungeert als generieke blokkendoos, vereist dat wél. Daar moet je de vrijheid hebben om zélf je menu-structuren helemaal in te richten, om zélf optimale workflows op te zetten. Kortom, precies het interaction-design, de wireframes of het funcitoneel ontwerp te kunnen volgen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dan is &amp;amp;quot;Drupal optimaal gebruiken&amp;amp;quot; opeens veel minder waardevol, omdat je gewoon het ontwerp wilt kunnen volgen en niet rekening te moeten houden met de niet-passende ideeën van een modulebouwer.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;beheer&amp;#34;&amp;gt;Beheer.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wanneer je zo een site bouwt, waarin je heel veel moet &amp;amp;quot;undo-en&amp;amp;quot;, eindig je niet zelden met honderd, hondervijftig modules. Waarvan een groot deel heel, project- of casespecifiek gebouwd is.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Overigens kent het gemiddelde Rails-project waarmee ik bekend ben, ook ongeveer 50 externe, vereiste bibliotheken (gems). Maar vijftig bibliotheken is iets heel anders dan hondervijftig modules. Mijn ervaring met Django, Symfony, of .&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;NET projecten is te gering om hier een generieke uitspraak over te kunnen doen, maar ik verwacht ongeveer hetzelfde. Een recent CakePHP project dat ik bouwde had twee zulke bilbiotheken: een PDF-library en een Twitter-libary. Niks meer.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In Drupalprojecten kom ik niet zelden het volgende patroon tegen:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Core doet X1&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Contrib module maakt daarvan X3&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Eigen module maakt deel van contrib module ongedaan en maakt het gewenste resultaat X2&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Het theme en theme-preprocessors bouwen daarvan X2&amp;#39;.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dat is ook wat developmentseed bedoelde met &amp;amp;quot;undoing&amp;amp;quot;. En er zit dus twee keer zoveel code en twee keer zoveel features in dan nodig: eerst de features en daarvoor nodige code die niet gebruikt gaan worden. En vervolgens code om die features weer te verbergen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een ander, veelvoorkomend patroon is:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Contrib A doet X op zijn eigen maniertje, niet helemaal passend bij het interactieontwerp.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Contrib B doet Y op een ander, eigen maniertje.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Eigen module zorgt dat X en Y consistent zijn en samenwerken volgens het interactieontwerp.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Theme gebruikt deze data, verwerkt en past ze aan, tot een custom-interface.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dat heet in Drupal meestal &amp;amp;quot;gluecode&amp;amp;quot;. Omdat het een CMS is, en geen framework, hebben modules (en core) deze &amp;amp;quot;maniertjes&amp;amp;quot;. Een (goede) bibliotheek heeft geen maniertjes, maar is hoogstends &amp;amp;quot;opinionated&amp;amp;quot;; wat betekent dat het technische aannames doet, zoals bijvoorbeeld de naamgeving van je database.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dit patroon veroorzaakt ook een zogenaamde &amp;amp;quot;tight coupling&amp;amp;quot; tussen het theme en de implementatie. Een theme kan niet werken zonder dat alledrie modules beschikbaar zijn, én exact volgens een patroon ingeregeld. En andersom zal zonder het theme (of met een ander theme) de site heel anders (of helemaal niet) werken. &amp;amp;quot;Tight coupling&amp;amp;quot; is een bekende oorzaak van veel beheerproblemen en van enorm veel bugs.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een Drupalmodule is echter bibliotheek, implementatie en vaak nog design daarvan, in één. Een bibliotheek is enkel bibliotheek. De implementatie, en al helemaal het design is aan de bouwer. Er is dus geen undoing nodig. En geen gluecode (of: iemand zei me ooit: bouwen met Django is alléén maar gluecode schrijven).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wanneer je in Django een paar &amp;lt;a href=&amp;#34;http://djangopackages.com/&amp;#34;&amp;gt;Packages&amp;lt;/a&amp;gt; (bibliotheken) binnenhaalt, of in Rails een paar Gems opneemt, is het patroon heel anders:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Core doet niets.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Gem X biedt een aantal, geabstraheerde, helpers aan.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Jou Rails app gebruikt die helpers om, met custom code, het gewenste resultaat te krijgen.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een voorbeeld:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Rails heeft van zichzelf geen uploadfunctionaliteit.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;https://github.com/jnicklas/carrierwave&amp;#34;&amp;gt;Carrierwave&amp;lt;/a&amp;gt; bied de mogelijkheid om heel eenvoudig files aan &amp;amp;quot;objecten&amp;amp;quot; toe te voegen.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Het door jou ontwikkelde &amp;amp;quot;article&amp;amp;quot; model, gebruikt enkele regels code om plaatjes aan artikelen toe te voegen, deze te bewerken (thumbnails, watermarks enzovoort) en de resultaten beschikbaar te maken en weer te geven.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Voor beheer is dus belangrijk dat, om het gewenste &amp;amp;quot;ontwerp&amp;amp;quot; te bereiken, met een CMS vaak erg veel extra modules, addons of eigen code meegeleverd moet worden. Die veelal erg uiteenloopt qua implementatie, veiligheid en kwaliteit. 150 modules beheren is een hel. En helemaal als een groot deel daarvan gluecode en ondoing is.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bij een framework kunnen bibliotheken net zoveel uiteenlopen qua veiligheid en kwaliteit, maar de manier van inzetten is zodanig anders, dat beheer van deze bibliotheken nauwelijks moeite kost. De manier van inzetten volgt ook altijd heel duidelijk beschreven patronen, want dat is vastgelegd in het framework. Waardoor een willekeurige (nette) rails app door iedere rails-ontwikkelaar binnen enkele uren te begrijpen is.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bovendien zijn frameworks in essentie eigenlijk niet meer dan tools om al die bibliotheken te beheren en te implementeren. Bij een CMS is beheer van modules of addons vaak slechts een bijzaak, het is immers een Content management systeem en geen Code management systeem. In Drupal is dit welliswaar aan het veranderen met tools als &amp;lt;a href=&amp;#34;http://mustardseedmedia.com/podcast/episode43&amp;#34;&amp;gt;features&amp;lt;/a&amp;gt;, en drush. Maar het komt nog altijd niet in de buurt van een tool als &amp;lt;a href=&amp;#34;http://gembundler.com/rationale.html&amp;#34;&amp;gt;bundler&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Wanneer je je confirmeert aan het CMS heb je dus niet alleen veel minder ontwikkel- en denkwerk te verrichten, het beheer wordt ook nog eens stukken goedkoper.&amp;lt;/strong&amp;gt; Maar een project waarbij je nauwelijks ontwikkelt, je functioneel ontwerp helemaal laat leiden door de conventies van het CMS en het beheer een fluitje van een cent is, kost toch ook bijna niets?! Zo een site is in een week gemaakt. Kost een paarduizend euro aan design en themeing en kan per definitie nooit honderden uren werk kosten, want dat werk voorkwam je nu juist door alles standaard te doen!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Waarom kost een Drupalsite dan toch vaak enkele (tien)duizenden euro&amp;#39;s? Mis ik een belangrijke kostenpost? Of zijn we vooral &amp;amp;quot;Maatwerk waar het moet&amp;amp;quot; aan het bouwen met Drupal?&amp;lt;/p&amp;gt;</content>
 </entry>
 
 <entry>
   <title>Maar in praktijk valt dat toch allemaal wel mee, wordt Drupal toch erg vaak succesvol ingezet?</title>
   <link href="https://berk.es/2011/03/08/maar-in-praktijk-valt-dat-toch-allemaal-wel-mee-wordt-drupal-toch-erg-vaak-succesvol-ingezet/"/>
   <updated>2011-03-08T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2011/03/08/maar-in-praktijk-valt-dat-toch-allemaal-wel-mee-wordt-drupal-toch-erg-vaak-succesvol-ingezet/</id>
   <content type="html">&amp;lt;p&amp;gt;Een aantal mensen op twitter of per mail, vonden mijn &amp;lt;a href=&amp;#34;http://bler.webschuur.com/geen_cms_en_al_zeker_geen_drupal_voor_grote_webprojecten_zoals_gemeentesites&amp;#34;&amp;gt;artikel over waarom Drupal geen goede optie is voor grote projecten als gemeentesites&amp;lt;/a&amp;gt; niet erg concreet. Een reactie:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Er zijn recentelijk een aantal successvolle sites voor gemeentes gebouwd met Drupal. Dat is toch het bewijs dat je ongelijk hebt?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Uiteraard! Deze sites bewijzen dat Drupal wel degelijk gebruikt kan worden voor zulke projecten. Ik zeg ook nergens dat het niet kan. Daarom eerst nog eens de essentie van mijn betoog:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Drupal is een fijn platform om sites mee te bouwen. Ook grote sites. Maar Drupal is zeker niet het meest geschikte platform om die grote sites mee te bouwen. Drupal zal altijd duurder uitpakken (en mogelijk minder goede projecten afleveren) dan een meer geschikte tool. Frameworks als Django, Rails of Symfony zijn geschiktere tools.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En dan dat practische deel. Ik heb inmiddels wat tijd gehad om met &amp;lt;a href=&amp;#34;http://www.devcms.nl&amp;#34;&amp;gt;DevCMS&amp;lt;/a&amp;gt; te spelen. Merk op dat dit geen Drupal versus DevCMS artikel is, maar dat ik DevCMS opvoer als &amp;lt;em&amp;gt;een product dat een team kan bouwen met een framework&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Zoals alle Drupalontwikkelaars weten, is een accessmodel in Drupal erg lastig. Het is enorm ingewikkeld om een strak ingeregelde redactionele workflow te maken, waarbij artikelen enkel zichtbaar zijn nadat ze door een administratieve molen gehaald zijn. Iets schijnbaar simpels als (door mij vereenvoudigd citaat uit een recente offerteaanvraag): &amp;lt;em&amp;gt;Een groep artikelen wordt door de redactie in draft geschreven, dan geredigeerd, gaat dan naar afdeling Juridische zaken[...], bij goedkeuring naar eindverantwoordelijke en bij afkeuring terug naar de auteur(s)[...]. De eindverantwoordelijke kan de groep artikelen publiceren, waarna oude revisies bewaard blijven en de goedgekeurde revisieset online komt&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik zou, écht niet weten hoe ik dat makkelijk in Drupal gedaan kan worden. Ik schat, natte vingerwerk, voor deze feature minimaal 100 uur ontwikkeling in (maar zou een echte inschatting pas na een proof of concept durven geven). De crux zit hem overigens in het detail &amp;amp;quot;groep artikelen&amp;amp;quot;; wat een artikel met subartikeltjes zou kunnen zijn. En in &amp;amp;quot;strak ingeregelde&amp;amp;quot;, want iets wat hier ongeveer op lijkt is vrij eenvoudig in Drupal.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;DevCMS heeft dat dus wél. Ik weet uiteraard niet hoelang dat team daaraan heeft gebouwd, maar ik weet uit ervaring dat met Rails&amp;#39; statemachine addons zulk soort workflows niet enorm veel werk en tijd kosten. Uit de handleiding:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Nodes in the tree can be in several states, which is stored in the node’s status attribute:• unapproved: The node has been created or changed by an editor and is waiting for approval.• approved: The node has been approved, created or updated by an administrator or final editor. A new version of the content node is recorded.• rejected: An unapproved node has explicitly been rejected by an administrator or final editor. The editor responsiblefor the change will be notified when a node enters this state.• drafted: The node (unapproved or approved) is drafted, meaning a user has not finished changing this node andshould therefore not be shown on the website. The node will not be listed for approval.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When a content node is created by an administrator or final editor, it is automatically considered to be approved and therefore a new version of this node is recorded. [....] Conclusively, when an editor makes a change, the table of the content type will contain the new, unapproved version and the website should show the yaml-ized, approved version until the unapproved version has been approved by an administrator or final editor.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ze merken in de handleiding zélf nog op dat hier nog veel werk voor e DevMCS ontwikkelaars ligt. Dat ze dit nog lang niet naar eigen wens hebben afgerond. Maar mijn eerste indruk is dat dit al véle malen beter is dan welke redactionele workflow ik in Drupal ooit geïmplementeerd heb zien worden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Drupal kan iets essentieels als redactionele workflows best, maar vraagt een oplossing die zo goed als &amp;lt;em&amp;gt;van scratch&amp;lt;/em&amp;gt; opgebouwd moet worden: honderden uren bijelkaar klikken van standaardcomponenten en stukjes custom code, die ieder project telkens weer moet neertellen: voor iedere te implementeren Drupalsite weer opnieuw. En blijkt dna erg vaak op details slecht afgewerkt te zijn; resulterend in workflows die meestal wel redelijk, maar zelden &amp;lt;em&amp;gt;precies&amp;lt;/em&amp;gt; passen in een organisatie.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Terwijl in een framework vrij eenvoudig gebruik gemaakt wordt van componenten als een statemachine om een workflow te bouwen die &amp;lt;em&amp;gt;exact&amp;lt;/em&amp;gt; past bij het project. Die dus &amp;lt;em&amp;gt;ook&amp;lt;/em&amp;gt; van scratch gebouwd moet worden, maar die, vanwege veel geschikter hergebruik van anderssoortige bibliotheken, zowel veel nauwkeuriger ingebed kan worden in een bestaande workflow, alsook veel sneller en makkelijker te implementeren is.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Een CMS vraagt eigenlijk altijd dat de organisatie zich aanpast aan de redactionele workflow die ingebakken is in dat CMS, terwijl een framework de ontwikkelaars bouwstenen geeft om een precies passende workflow te ontwikkelen&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En dat is slechts één klein practisch voorbeeld. Ik weet zeker dat we met veel meer zulke voorbeelden kunnen komen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wat voor ogenschijnlijk simpel probleem heeft jou in Drupal uren uitzoek- en ontwikkeltijd gekost?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wat Doet Drupal, of een ander CMS out of the box (zonder enig werk) waar je uren ontwikkelen in een framework voor nodig hebt?&amp;#xA;&amp;lt;strong&amp;gt;edit: tweede vraag toegevoegd&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Geen CMS en al zeker geen Drupal voor grote web-projecten zoals gemeentesites.</title>
   <link href="https://berk.es/2011/03/07/geen-cms-en-al-zeker-geen-drupal-voor-grote-web-projecten-zoals-gemeentesites/"/>
   <updated>2011-03-07T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2011/03/07/geen-cms-en-al-zeker-geen-drupal-voor-grote-web-projecten-zoals-gemeentesites/</id>
   <content type="html">&amp;lt;p&amp;gt;Gemeentes en overheden zouden hun sites niet met Drupal moeten bouwen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;achtergrond&amp;#34;&amp;gt;Achtergrond.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik ben een fan van Drupal, ontwikkel er al jaren mee en heb veel ervaring met succesvolle en evenzovele gefaalde Drupalprojecten. Dat laatste vooral door mijn functie als &amp;amp;quot;probleemoplosser&amp;amp;quot; bij Drupalprojecten. Mensen huren mij vooral in om hun vastgelopen, of uitlopende Drupalprojecten te redden. Ik ben (misschien juist daardóór) ook een Drupal-scepticus. Drupal wordt teveel en te vaak ingezet voor projecten waar het helemaal niet geschikt voor is. Drupal mist ook zo ongeveer alles wat een goede &amp;amp;quot;architectuur&amp;amp;quot; vraagt. Het ontbeert een uitgekristalliseerd veiligheidsmodel, abstractie is geheel afwezig, rommel en broddelwerk in de community (de modules) zijn eerder regel dan uitzondering, enzovoort.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik ben ook pragmatist; werk moet af (binnen de deadline). Sites moeten mooi zijn (en niet te duur). Software moet gewoon werken (voor de eindgebruiker) enzovoort. Goede academische opzet is leuk, maar nooit het doel: een goed opgezet project is juist zo goed opgezet omdat daarmee bovenstaande doelen gehaald kunnen worden! En nooit om het &amp;amp;quot;goed opzetten&amp;amp;quot; an Sich. Drupal moet dus ingezet worden waar het succesvol kan zijn. Waar het goed tot zijn recht komt. En het moet vooral niet ingezet worden voor projecten waar het ongeschikt voor is.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;cmsen-en-hun-alternatieven&amp;#34;&amp;gt;CMSen en hun alternatieven.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Grofweg zijn er drie groepen software waarmee sites gemaakt kunnen worden: CMS, Framework of Barebones.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een CMS is een kant-en klaar pakket, welke zonder programmeerwerk ingezet kan worden om content te beheren. Voor het gemak wordt de C van Content meestal zeer breed gerekt: ook software om een webwinkel mee te draaien of pakketten om klanten in te beheren worden onder deze groep geschaard: zo lang het maar inzetbaar is zonder programmeerwerk.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een framework is een omgeving, of platform, of softwarepakket, waarmee programmeurs sites kunnen bouwen. Vaak hebben frameworks allerlei complexe zaken al voor de programmeurs geabstraheerd en is programmeren nauwelijks meer dan het correct instellen van allerlei instellingen, en het verder uitwerken van voorgebakken code.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Het is een enorm brede categorie, dus beperk ik, vanaf hier, tot enkele moderne, veelgebruikte frameworks: Symfony, Django en Ruby on Rails.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een barebone is eigenlijk geen systeem, platform of omgeving, maar juist het ontbreken ervan. Gewoon van de grond af, iets zelf bouwen. Welhaast ieder &amp;amp;quot;webdesignburo&amp;amp;quot; heeft zo zijn eigen CMS gebouwd. Bijna alle grote &amp;amp;quot;enterpriceomgevingen&amp;amp;quot; zijn op deze manier gebouwd.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;open-source&amp;#34;&amp;gt;Open Source&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;«Gemeente Grootezee heeft nu DuurBetaaldCMS X, dus Drupal is Open Source, dus beter.» Is een veelgehoord argument. Laat vooropstaan dat juist voor publieke diensten zoals gemeentesites twee dingen enorm belangrijk zijn:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Voor bezoekers en gebruikers: Toegankelijkheid en voldoen aan standaarden.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Voor overheden: Onafhankelijk zijn van bedrijven, contracten en licenties.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar Drupal is zeker niet de enige oplossing die &amp;lt;a href=&amp;#34;http://isoc.nl/consultatie/resultaten.odt&amp;#34;&amp;gt;hieraan kan voldoen&amp;lt;/a&amp;gt;. Open Source betekent ook niet, dat iets als Open Source ingekocht moet worden. Een gemeente kan best een bestaand closed source pakket opkopen of iets geheel van de grond af aan laten ontwikkelen en het dan Open Source vrijgeven!Ik zie zelfs veel overheidsprojecten waar men Drupal modules heeft laten ontwikkelen die niet vrijgegeven zijn!Gelukkig blijken ook enkele projecten &amp;lt;a href=&amp;#34;http://drupal.org/project/rijkshuisstijl&amp;#34;&amp;gt;wel&amp;lt;/a&amp;gt; het &amp;lt;a href=&amp;#34;https://drupal.org/project/digid&amp;#34;&amp;gt;geval&amp;lt;/a&amp;gt;. In elk geval is het gebruiken van Open Source nog geen garantie dat de investering ook bij de gemeenschap terugkomt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bij het gebruik van een framework is dat precies zo: het gebruiken van Open Source technologie garandeert niet dat het eindproduct ook aan de gemeenschap teruggegeven wordt. Maar dat kan wel, zie het voor Deventer op maat gemaakte CMS &amp;lt;a href=&amp;#34;http://www.devcms.nl/&amp;#34;&amp;gt;devCMS&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;return-on-investment-effectief-bouwen-van-een-site&amp;#34;&amp;gt;Return on Investment: Effectief bouwen van een site.&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Het belangrijkste argument blijft echter dat Drupal, als CMS, nauwelijks geschikt is om grote complexe maatwerk-projecten mee te bouwen. Dat heeft grotendeels met de technische opzet van deze CMSen te maken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Daarvoor zijn hieronder enkele grafieken opgenomen. Ze geven een globaal inzicht van hoeveel offert men moet steken in ontwikkeling van een site na een X-tal uren.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;De y-as geeft de &amp;amp;quot;effectiviteit&amp;amp;quot; aan. Het aantal uren dat met per delivery nodig heeft. 100% effectief betekent géén effort en toch iets opgeleverd (en bestaat dus niet). De onderkant is 0%: enorm veel werk gedaan en niets kunnen leveren. De grafieken gaan uit van ervaren ontwikkelaars. Dus de initiële leertijd (om een taal, of raamwerk te leren gebruiken is buiten beschouwing gelaten).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;De X-as geeft het aantal uren dat besteed is aan het gehele project. Bijvoorbeeld: na 100 uur Drupal-ontwikkelen wordt dóórontwikkelen steeds duurder. De ontwikkelaar geraakt dan in een domein waar hij of zij alles zelf moet doen, zelf modules moet bouwen en steeds minder makkelijk toegankelijke kennis nodig heeft (bij ontwikkelaars bekend als: de hele broncode van Views moeten lezen om dat stomme cartesiaans product op te lossen).&amp;#xA;&amp;lt;img src=&amp;#34;http://bler.webschuur.com/sites/bler.webschuur.com/files/Drupal_vs_frameworks_0.png&amp;#34; alt=&amp;#34;Grafiek van effectiviteit uitgezet tegen investering&amp;#34;&amp;gt;&amp;#xA;Overigens blijkt hier ook al meteen een ander veelgemaakte denkfout uit: een groot project is een project met lange doorlooptijd, veel manuren en grote budgetten. Het zegt niks over het uiteindelijke gebruik van een site. Drupal kan goed ingezet worden voor een site met miljoenen bezoekers. Precies andersom, kan een intranetomgeving voor slechts enkele tientallen onderzoekers mogelijk duizenden manuren opslorpen en daarmee een heel groot project zijn.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;wordpress-of-de-hypergefocuste-cmsen&amp;#34;&amp;gt;Wordpress, of de hypergefocuste CMSen&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wordpress is een CMS dat ontwikkeld werd voor één doel: Bloggen. Het is daarmee super geoptimaliseerd voor deze taak: gebruiksvriendelijk, doelgericht, afgestemd op de doelgroep enzovoort. Daarmee is ook meteen aangegeven wat het allemaal niet kan: namelijk: al het andere. Dit geldt uiteraard ook voor ontwikkelaars: zij bouwen met Wordpress altijd blog-achtige sites. Iets anders kán gewoon niet. Deze vorm van software opzetten heet ook wel &amp;lt;a href=&amp;#34;http://gettingreal.37signals.com/ch04_Make_Opinionated_Software.php&amp;#34;&amp;gt;opinionated software&amp;lt;/a&amp;gt;. Andere voorbeelden zijn PHPBB, het bekende forumsysteem, mediaWiki (software achter onder meer Wikipedia) of Status.net (je eigen twitter-community opzetten).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In enkele uurtjes heb je een site draaien, maar écht maatwerk aanpassingen buiten het duidelijke kader en doel van het CMS kosten enorm veel werk.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;drupal-of-de-blokkendozen&amp;#34;&amp;gt;Drupal, of de blokkendozen.&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Drupal wordt vaak, ten onrechte, een content management framework genoemd, waarmee men probeert aan te geven dat Drupal best aardig kan meekomen als framework. De oorzaak van deze verwarring is dat Drupal eigenlijk meer een soort blokkendoos is. In tegenstelling tot een CMS als Wordpress met maar één doel, heeft Drupal die focus juist helemaal niet. Drupal is daarmee niet gebruiksvriendelijk (vriendelijk te gebruiken waarvóór?) niet geöptimaliseerd en niet afgestemd op een doelgroep. Dat geeft flexibiliteit en vrijheid. Ontwikkelaars kunnen er veel meer mee bouwen dan alleen datgene waarvoor het ooit bedoeld werd.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar het is met nadruk géén framework, omdat het juist voor programmeurs weinig biedt: het is niet Object Georiënteerd, kent nauwelijks abstractie, vereist enorm veel duplicaatcode voor simpele aanpassingen, ontbeert iedere vorm van een ontwikkelomgeving, kent nauwelijks een migratiesysteem (Uitrollen van een site via een testomgeving naar de live omgeving). Enzovoort.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Voor de oorspronkelijke doelgroep is dat ook helemaal niet erg: Een kleine site heeft helemaal geen OTAP straat nodig. Mijn persoonlijke blog via een ISO9002, ITIL-gecertificeerde workflow prubliceren? Kom nou!Maar als je dit juist wél wilt? Als je juist je systeem wilt koppelen aan andere processen, tools of systemen?Dan moet je alles zelf bouwen, met nauwelijks enige technische infrastructuur. De effectiviteit van al dat extra werk is dan meestal nog lager dan wanneer alles &amp;amp;quot;barebone&amp;amp;quot; van scratch gebouwd werd. Drupal blijkt dan vaak meer &amp;amp;quot;in de weg&amp;amp;quot; te lopen dan dat het &amp;amp;quot;meehelpt&amp;amp;quot;. Dit is onafhankelijk van de ervaring en kennis van Drupal: iemand met veel ervaring spendeert net zo goed 100&#43; uren aan een eenvoudige Create Read Update Delete omgeving van custom &amp;amp;quot;dingen&amp;amp;quot; zoals, zeg, betalingen. Drupal biedt hier nauwelijks tools voor: alle pagina&amp;#39;s, lijstweergaves, workflows moeten met de hand geprogrammeerd worden, alsof het in een barebone PHP-omgeving gebouwd werd.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Drupal kent een steile leercurve, veel steiler dan de doelgerichte CMSen. Binnen een tiental uren staat er een basissite. Binnen anderhalve week is met standaardcomponenten een heel aardige maatwerk site op te zetten. Maar daarna zakt alle productiviteit in: Buiten het gebruik van standaardcomponenten is Drupal een slecht ontwikkelplatform.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;de-frameworks&amp;#34;&amp;gt;De frameworks&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Deze kennen een redelijk lange opstarttijd, welke eigenlijk vooral bepaald wordt door de ervaring die men ermee heeft.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Iemand die al enkele sites in Ruby on Rails bouwde, heeft binnen enkele uren al een basissite staan, maar een team dat nog nauwelijks ervaring heeft met frameworks, of de taal waarin ze gebouwd zijn (Ruby voor Rails en Python voor Django) zal, uiteraard, eerst de taal en de concepten moeten eigen maken.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dat hoeft bij een systeem als Drupal veel minder en bij een gefocust systeem als Wordpress helemaal niet. Iemand kan zonder programmeren een heel aardige site opzetten. Met Django kom je niet ver als &amp;amp;quot;Objecten en Classes&amp;amp;quot; enkel op de todolijst onder: &amp;amp;quot;moet ik nog eens induiken&amp;amp;quot; staan.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maar daarna gaat het snel. Wanneer het team, of de ontwikkelaar weet hoe zaken werken, hoe het conceptueel in elkaar steekt en de ontwikkelteams onder de knie heeft, blijft doorontwikkelen en maatwerk dezelfde effort kosten. Eigenlijk ontwikkelt men met een framework een CMS dat in de eerste categorie thuishoort: een hyper gefocust CMS.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een framework is ook veel beter geöptimaliseerd om met teams te werken. Drupal biedt nauwelijks abstractie (naar databases, services, diensten enzovoort), kent nauwelijks migraties, testomgevingen en deployment-tools. In frameworks kun je meestal niet eens zónder deze zaken ontwikkelen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Bovendien is de literatuur van frameworks ook zeer veel meer gericht op ontwikkelaars. Literatuur legt juist de structuur, filosofie en architectuur uit. Terwijl bij een CMS altijd het gebruik van het eindproduct op de voorgrond staat. De leercurve voor ontwikkelaars en -teams is daarmee meestal stukken lager dan bij een CMS dat eigenlijk niet bedoeld is voor ontwikkelaars maar voor eindgebruikers.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Verder zak een goed framework ook hergebruik van code toelaten. Meer nog dan een CMS, waar maatwerk juist precies dat is: maatwerk: dus per definitie niet of nauwelijks herbruikbaar. Een framework maakt het juist zo, dat dat maatwerk de minst mogelijke inzet kost. En zorgt voor structuur en standaarden, waardoor zelf ontwikkelde bibliotheken los van het maatwerk gemaakt kunnen worden: een koppeling met een extern systeem bestaat dan bijvoorbeeld uit een abstracte &amp;amp;quot;algemene Python digiD bibliotheek&amp;amp;quot; met een klein sausje maatwerk eroverheen (de integratie van die bibliotheek in je Django-project). Hierdoor is het vrijgeven van werk als Open Source ook veel aantrekkelijker dan bij veel CMSen het geval is. Een Drupal-DigiD-module is enkel in Drupal te gebruiken (waarmee de betreffende overheid zich dus &amp;amp;quot;ingesloten&amp;amp;quot; heeft in Drupal). Terwijl een DigiD Ruby-gem, een Java library, PHP of Python Package veel breder inzetbaar is. Eigenlijk zijn zulke projecten veel waardevoller voor de (open source) gemeenschap.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Wanneer een site gebouwd wordt door professionals, kan al binnen enkele uren een product klaarstaan. En kost het doorontwikkelen daarvan nauwelijks extra tijd per te ontwikkelen onderdeel. Tot het moment dat men ook buiten de kaders van dat platform wil gaan. Dan moeten plots eigen bibliotheken of diensten ontwikkeld worden. Maar dat geldt precies net zo voor geavanceerde features in een CMS. Ik bedoel dan vooral complexe zaken als worker-queues, load balancing, communicatie met andere systemen enzovoort. Een goed framework zal hier echter zeker niet in de weg lopen en mogelijk al allerlei tools (hooks, plugin-systemen, workflows) hebben klaarstaan om e.e.a. te kunnen integreren.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;conclusie&amp;#34;&amp;gt;Conclusie&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Drupal is zeer interessant voor projecten die onder de, ruwweg, tweehonderd uur blijven, projecten die ruwweg minder dan €5000 kosten. Daarboven zal een framework, mits gebruikt door ervaren web-ontwikkelaars, altijd efficiënter blijken. Drupal biedt dan geen enkel voordeel, anders dan dat het &amp;amp;quot;Open Source&amp;amp;quot; is. Maar Open Source kan net zo goed met een framework.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Overheden en grote projecten zouden dan ook best niet in Drupal gebouwd moeten worden, maar in een daarvoor veel geschikter framework.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Why I chose to disclose a security issue and not report it to Drupal securty team.</title>
   <link href="https://berk.es/2011/02/03/why-i-chose-to-disclose-a-security-issue-and-not-report-it-to-drupal-securty-team/"/>
   <updated>2011-02-03T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2011/02/03/why-i-chose-to-disclose-a-security-issue-and-not-report-it-to-drupal-securty-team/</id>
   <content type="html">&amp;lt;p&amp;gt;Okay. So I did not play nice. In fact, I probably brought quite some sites &amp;lt;em&amp;gt;out there&amp;lt;/em&amp;gt; in trouble, by disclosing a Drupal &amp;lt;a href=&amp;#34;http://twitter.com/#!/berkes/status/32125321654312960&amp;#34;&amp;gt;security issue on Twitter&amp;lt;/a&amp;gt;, without mentioning it to the security Team.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I had several reasons for doing this. * I was frustrated. With this module, its code and it causing several ugly bugs in an already frustrating site. Being frustrated and having access to Twitter is never a good idea. More on this below. * It has been one of many  security issues in contribs I stumbled upon off late. Some I have reported, quite some being hard to reproduce are not worth reporting. I am by no means a security expert. Hence the frustration.* It has been one of many, many more project-only security issues I came across off late. Some in custom code, some in themes, many, many more in crappy configuration and even crappier custom-gluecode. Hence the frustration: I often get the idea that it is way to easy to write crappy, insecure or bad-performing Drupal-code. I know of other projects where it is much harder to build insecure code.* This specific issue has been around since December 2007. That was the main point for me to vent my anger and disclose the issue. It is never smart to post such issues when frustrated. And I am very sorry if I brought the Drupalk security team in trouble by this. That was not intended. When I often see the quality of contributions, I get very sad. Or frustrated. I too, often make bad code; I too learn new things about writing proper code every day. And I try to improve my code by not allowing in features, code, or other stuff that misses Good Architecture, fails to fit in the Grand Scheme and so on.** If code is bad, people should not use it! At all. Bad code should not be allowed to exist. **Bad code will exist. Bugs will creep in. Security holes will open. That is reality. But we should not allow such things to be kept for long. Any software project should have processes in place to weed out bad code, security issues and such. Drupal has such processes; one of them is the security-team.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;However. This hole has existed (at time of writing) for probably over 3 years. No one has reported it, yet over 2000 sites are reportedly using this module. Here something is wrong. Had I reported it to the security team, then some patch would have been brought out. And all 2000 sites would have been patched (you patch, don&amp;#39;t you?). At least the choice to either close down the project, fix it, or anything else, whould have been that of the security team, not mine. I understand that fully. However, this time I chose the disclosure. For two (IMO) good reasons: * A project that is actively used, &amp;lt;strong&amp;gt;with a security hole&amp;lt;/strong&amp;gt;, by thousands of users for several years, is wrong. This is the proof that at least some trivial security holes will leak trough in the current process. We must be aware of that fact.* People should know their own responsibility. I am probably very optimistic if I say that all 2000&#43; reported users of this module have found that hole themselves, fixed it locally, but did not manage to report it to Drupals security team. Realistic to think that hardly any of these reported users have it fixed locally. To me, that is a good indication that many-eyeballs fail to find security holes. This too, must be known.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I should probably have taken another route to raise such awareness. But in the light of things, I find a full-disclosure a good way to raise this: ** Your Drupal (Or wordpress, Joomla! or proprietry) site with 100&#43; modules and custom code is probably insecure. Unless you have reviewed it and know for sure it is not. ** Be aware of that.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Clean and maintainable pattern for blocks development in Drupal 6</title>
   <link href="https://berk.es/2010/12/31/clean-and-maintainable-pattern-for-blocks-development-in-drupal-6/"/>
   <updated>2010-12-31T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2010/12/31/clean-and-maintainable-pattern-for-blocks-development-in-drupal-6/</id>
   <content type="html">&amp;lt;h1 id=&amp;#34;clean-and-maintainable-pattern-for-blocks-development-in-drupal-6&amp;#34;&amp;gt;Clean and maintainable pattern for blocks development in Drupal 6&amp;lt;/h1&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Drupal7, has finally &amp;lt;a href=&amp;#34;http://drupal.org/node/224333#remove_op&amp;#34;&amp;gt;removed the confusing $op parameters&amp;lt;/a&amp;gt; from hooks. And replaced them with a family of related hooks instead; one hook per &amp;lt;em&amp;gt;op&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Here is a way to achieve the same in Drupal 6; by building a simple router in &amp;lt;a href=&amp;#34;http://api.drupal.org/api/drupal/developer--hooks--core.php/function/hook_block/6&amp;#34;&amp;gt;hook_block()&amp;lt;/a&amp;gt;. We use a user function for this, a patter well known in Drupal as &amp;lt;em&amp;gt;hook&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;/**&amp;#xA; * Implementation of hook_block().&amp;#xA; */&amp;#xA;function example_block($op = &amp;#39;list&amp;#39;, $delta = &amp;#39;&amp;#39;, $edit = array()) {&amp;#xA;  if ($op == &amp;#39;list&amp;#39;) {&amp;#xA;    return _example_block_list();&amp;#xA;  }&amp;#xA;&amp;#xA;  $callback = &amp;amp;quot;_example_block_#{$op}_#{$delta}&amp;amp;quot;;&amp;#xA;  if (function_exists($callback)) {&amp;#xA;    if ($op == &amp;#39;save&amp;#39;) {&amp;#xA;      return call_user_func($callback, $edit);&amp;#xA;    }&amp;#xA;    else {&amp;#xA;      return call_user_func($callback);&amp;#xA;    }&amp;#xA;  }&amp;#xA;  # @TODO remove debug&amp;#xA;  # else {&amp;#xA;  #   dvm(&amp;amp;quot;block callback not found: #{$callback}&amp;amp;quot;)&amp;#xA;  # }&amp;#xA;}&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;From here on, we can implement a simple family of functions, instead of cramping everything in one huge and cluttered multifunctional hook implementation.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;For example, we implement the list callback as&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;function _example_block_list() {&amp;#xA;  # ...build blocks...&amp;#xA;  return $blocks;&amp;#xA;}&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Note the preceding _underscore before the function. This has no special technical meaning, but is a de-facto standard in PHP to indicate a function &amp;lt;a href=&amp;#34;http://drupal.org/node/70335&amp;#34;&amp;gt;should be considered private&amp;lt;/a&amp;gt;. This way, we tell other developers to never even consider using our callbacks; leaving us the freedom to change our functions at will.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But first, dissecting our hook_block will show what we actually do:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;if ($op == &amp;#39;list&amp;#39;) {&amp;#xA;  return _example_block_list();&amp;#xA;}&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This makes an exception for list. $op list, is the operator that does not recieve a delta, because it defines those &amp;lt;em&amp;gt;delta&amp;#39;s&amp;lt;/em&amp;gt;. A delta, is a severe misnoner in Drupal, because it is simply an identifier. However, Drupal calls it a delta, so should we. Another common misunderstanding, is that a delta must be numeric; beacause that is what the name delta implies. &amp;lt;strong&amp;gt;This implementation works best for simple textual identifiers&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;With the &amp;lt;strong&amp;gt;list&amp;lt;/strong&amp;gt; we define the blocks. For example to define two blocks, &amp;lt;em&amp;gt;foo&amp;lt;/em&amp;gt; and &amp;lt;em&amp;gt;bar&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;function _example_block_list() {&amp;#xA;  $blocks[&amp;#39;foo&amp;#39;] = array(&amp;#xA;    &amp;#39;info&amp;#39;       =&amp;amp;gt; t(&amp;#39;Renders block foo&amp;#39;),&amp;#xA;  );&amp;#xA;  $blocks[&amp;#39;bar&amp;#39;] = array(&amp;#xA;    &amp;#39;info&amp;#39;       =&amp;amp;gt; t(&amp;#39;Renders block bar&amp;#39;),&amp;#xA;  );&amp;#xA;  return $blocks;&amp;#xA;}&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;There are a lot more paramters you can define, they are all added in the final example implementation.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Back to the hook_block, we see&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;$callback = &amp;amp;quot;_example_block_#{$list}_#{$delta}&amp;amp;quot;;&amp;#xA;if (function_exists($callback)) {&amp;#xA;  if ($op == &amp;#39;save&amp;#39;) {&amp;#xA;    return call_user_func($callback, $edit);&amp;#xA;  }&amp;#xA;  else {&amp;#xA;    return call_user_func($callback);&amp;#xA;  }&amp;#xA;}&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A function callback is built, code checks if that exists and if so calls that function.&amp;#xA;We do make another exception, for save. Save takes another paramter, $edit, which all the others callbacks do not take. To keep things clean, we should only pass parameters to functions that actually have these implemented, hence the exception for save.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And, finally, adding a little piece of code that learns us of not implemented callbacks, so we can implement these, or ignore them.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;# @TODO remove debug&amp;#xA;# else {&amp;#xA;#   dvm(&amp;amp;quot;block callback not found: #{$callback}&amp;amp;quot;)&amp;#xA;# }&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Obviously to be removed before releasing.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;We can now implement some of the callbacks. To do that, say we want a setting on block &amp;lt;em&amp;gt;foo&amp;lt;/em&amp;gt;, but not in bar. This setting will allow us to toggle a &amp;amp;quot;Read more »&amp;amp;quot;-link on block &amp;lt;em&amp;gt;foo&amp;lt;/em&amp;gt; in the block&amp;#39;s configuration. For this, we need a &amp;lt;em&amp;gt;configure&amp;lt;/em&amp;gt; callback and a &amp;lt;em&amp;gt;save&amp;lt;/em&amp;gt; callback.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;/** Block callback for configure op, delta foo.&amp;#xA; *&amp;#xA; * @return Array&amp;#xA; *   Form api array.&amp;#xA; */&amp;#xA;function _example_block_configure_foo() {&amp;#xA;  $form = array();&amp;#xA;  $form[&amp;#39;read_more&amp;#39;] = array(&amp;#xA;    &amp;#39;#type&amp;#39;          =&amp;amp;gt; &amp;#39;checkbox&amp;#39;,&amp;#xA;    &amp;#39;#title&amp;#39;         =&amp;amp;gt; t(&amp;#39;Show &amp;amp;quot;Read more »&amp;amp;quot;&amp;#39;),&amp;#xA;    &amp;#39;#default_value&amp;#39; =&amp;amp;gt; variable_get(&amp;#39;example_foo_read_more&amp;#39;, FALSE),&amp;#xA;  );&amp;#xA;  return $form;&amp;#xA;}&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Configure simply returns a FAPI form, as per &amp;lt;a href=&amp;#34;http://api.drupal.org/api/drupal/developer--hooks--core.php/function/hook_block/6&amp;#34;&amp;gt;hook_block documentation&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;/** Block callback for save op, delta foo.&amp;#xA; *&amp;#xA; * @param $edit Array&amp;#xA; *   The submitted form values.&amp;#xA; *&amp;#xA; * @return Array&amp;#xA; *   Form api array.&amp;#xA; */&amp;#xA;function _example_block_save_foo($edit) {&amp;#xA;  ## Save values for block&amp;#xA;  variable_set(&amp;#39;example_foo_read_more&amp;#39;, $edit[&amp;#39;read_more&amp;#39;]);&amp;#xA;  return TRUE;&amp;#xA;}&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Save simply saves the configure to a variable.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Finally, we need a &amp;lt;em&amp;gt;view&amp;lt;/em&amp;gt; callback for each block: the function that actually renders the block.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;/** Block callback for view op, block foo.&amp;#xA; *&amp;#xA; * @return Array&amp;#xA; *   Block array with content and subject key.&amp;#xA; */&amp;#xA;function _example_block_view_foo() {&amp;#xA;  $block[&amp;#39;subject&amp;#39;] = t(&amp;#39;Title of block #1&amp;#39;);&amp;#xA;  $block[&amp;#39;content&amp;#39;] = &amp;#39;Content of block #1&amp;#39;;&amp;#xA;  if (variable_get(&amp;#39;example_foo_read_more&amp;#39;, FALSE)) {&amp;#xA;    $block[&amp;#39;content&amp;#39;] .= l(t(&amp;#39;Read more »&amp;#39;), &amp;#39;foo/more&amp;#39;);&amp;#xA;  }&amp;#xA;  return $block;&amp;#xA;}&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A similar &amp;lt;em&amp;gt;view&amp;lt;/em&amp;gt; for &amp;lt;em&amp;gt;bar&amp;lt;/em&amp;gt; is needed, this time without the optional &amp;amp;quot;Read more »&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;/** Block callback for view op, block bar.&amp;#xA; *&amp;#xA; * @return&amp;#xA; *   Block array with content and subject key.&amp;#xA; */&amp;#xA;function _example_block_view_bar() {&amp;#xA;  $block[&amp;#39;subject&amp;#39;] = t(&amp;#39;Title of block #2&amp;#39;);&amp;#xA;  $block[&amp;#39;content&amp;#39;] = &amp;#39;Content of block #2&amp;#39;;&amp;#xA;  return $block;&amp;#xA;}&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Once you get more and more blocks, you can even split them out over include-files. By using module_load include, we can read a new file for each delta. This becomes only usefull when your module has many different blocks, each block having many helper functions and -libraries.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;function example_block($op = &amp;#39;list&amp;#39;, $delta = &amp;#39;&amp;#39;, $edit = array()) {&amp;#xA;  if ($op == &amp;#39;list&amp;#39;) {&amp;#xA;    return _example_block_list();&amp;#xA;  }&amp;#xA;&amp;#xA; $lib_file = &amp;amp;quot;example_#{$delta}&amp;amp;quot;;&amp;#xA;  module_load_include(&amp;#39;inc&amp;#39;, &amp;#39;example&amp;#39;, );&amp;#xA;  # ...&amp;#xA;}&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This allows us to move all callbacks to this include. With exception of the &amp;lt;em&amp;gt;list&amp;lt;/em&amp;gt;, that one should remain in the module, since that is the one to define the available blocks. It would be possible to make that dynamic too, to call an info per include-file. The benefit would be, that the includefiles are completely self-contained; have all the information about a single block in a single file. The downside is complexity and overhead.&amp;#xA;We use &amp;lt;em&amp;gt;$delta&amp;lt;/em&amp;gt; to group the libraries, so you have one file for each block. The alternative would be to have a library per op. This makes things only worse, since your code will be spread all over the place, and a new block, removal of block or change in a block would often require you to change all four files, instead of just the module and the block-file.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;The final *example.module* looks like this:&amp;#xA;&amp;#xA;&amp;amp;lt;?php // $Id$&amp;#xA;/**&amp;#xA; * Module:      example for blocks pattern&amp;#xA; * Date:        2010-12-31  10:18&amp;#xA; * Author:      ber&amp;#xA; *&amp;#xA; * Description:&amp;#xA; *   Example blocks &amp;#xA; *&amp;#xA; * License:&amp;#xA; *&amp;#xA; *   Copyright (C) 2010  ber&amp;#xA; *&amp;#xA; *   This program is free software: you can redistribute it and/or modify&amp;#xA; *   it under the terms of the GNU General Public License as published by&amp;#xA; *   the Free Software Foundation, either version 3 of the License, or&amp;#xA; *   (at your option) any later version.&amp;#xA; *&amp;#xA; *   This program is distributed in the hope that it will be useful,&amp;#xA; *   but WITHOUT ANY WARRANTY; without even the implied warranty of&amp;#xA; *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.pro See the&amp;#xA; *   GNU General Public License for more details.&amp;#xA; *&amp;#xA; *   You should have received a copy of the GNU General Public License&amp;#xA; *   along with this program. If not, see &amp;amp;lt;http://www.gnu.org/licenses/&amp;amp;gt;.&amp;#xA; *&amp;#xA; */&amp;#xA;&amp;#xA;/**&amp;#xA; * Implementation of hook_block().&amp;#xA; */&amp;#xA;function example_block($op = &amp;#39;list&amp;#39;, $delta = &amp;#39;&amp;#39;, $edit = array()) {&amp;#xA;  if ($op == &amp;#39;list&amp;#39;) {&amp;#xA;    return _example_block_list();&amp;#xA;  }&amp;#xA;&amp;#xA;  $callback = &amp;amp;quot;_example_block_{$op}_{$delta}&amp;amp;quot;;&amp;#xA;  if (function_exists($callback)) {&amp;#xA;    if ($op == &amp;#39;save&amp;#39;) {&amp;#xA;      return call_user_func($callback, $edit);&amp;#xA;    }&amp;#xA;    else {&amp;#xA;      return call_user_func($callback);&amp;#xA;    }&amp;#xA;  }&amp;#xA;  #else { #TODO: remove debug!&amp;#xA;  #  dvr(&amp;amp;quot;block callback not found: {$callback}&amp;amp;quot;);&amp;#xA;  #}&amp;#xA;}&amp;#xA;&amp;#xA;/** Renderer lists all blocks.&amp;#xA; *&amp;#xA; * @return&amp;#xA; *   Block description array. see hook_block() documentation for details on array contents.&amp;#xA; */&amp;#xA;function _example_block_list() {&amp;#xA;  $blocks[&amp;#39;foo&amp;#39;] = array(&amp;#xA;    &amp;#39;info&amp;#39;       =&amp;amp;gt; t(&amp;#39;Renders foo&amp;#39;),&amp;#xA;    &amp;#39;cache&amp;#39;       =&amp;amp;gt; BLOCK_CACHE_PER_ROLE, # | BLOCK_CACHE_PER_USER | BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_GLOBAL | BLOCK_NO_CACHE,&amp;#xA;    &amp;#39;status&amp;#39;     =&amp;amp;gt; TRUE,&amp;#xA;    &amp;#39;weight&amp;#39;     =&amp;amp;gt; 0,&amp;#xA;    &amp;#39;region&amp;#39;     =&amp;amp;gt; &amp;#39;&amp;#39;,&amp;#xA;    &amp;#39;visibility&amp;#39; =&amp;amp;gt; 1,&amp;#xA;    &amp;#39;pages&amp;#39;      =&amp;amp;gt; &amp;#39;&amp;#39;,&amp;#xA;  );&amp;#xA;  $blocks[&amp;#39;bar&amp;#39;] = array(&amp;#xA;    &amp;#39;info&amp;#39;       =&amp;amp;gt; t(&amp;#39;Renders bar&amp;#39;),&amp;#xA;    &amp;#39;cache&amp;#39;       =&amp;amp;gt; BLOCK_CACHE_PER_ROLE, #| BLOCK_CACHE_PER_USER | BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_GLOBAL | BLOCK_NO_CACHE,&amp;#xA;    &amp;#39;status&amp;#39;     =&amp;amp;gt; TRUE,&amp;#xA;    &amp;#39;weight&amp;#39;     =&amp;amp;gt; 0,&amp;#xA;    &amp;#39;region&amp;#39;     =&amp;amp;gt; &amp;#39;&amp;#39;,&amp;#xA;    &amp;#39;visibility&amp;#39; =&amp;amp;gt; 1,&amp;#xA;    &amp;#39;pages&amp;#39;      =&amp;amp;gt; &amp;#39;&amp;#39;,&amp;#xA;  );&amp;#xA;  return $blocks;&amp;#xA;}&amp;#xA;&amp;#xA;/** Block callback for configure op, delta foo.&amp;#xA; *&amp;#xA; * @return Array&amp;#xA; *   Form api array.&amp;#xA; */&amp;#xA;function _example_block_configure_foo() {&amp;#xA;  $form[&amp;#39;read_more&amp;#39;] = array(&amp;#xA;    &amp;#39;#type&amp;#39;          =&amp;amp;gt; &amp;#39;checkbox&amp;#39;,&amp;#xA;    &amp;#39;#title&amp;#39;         =&amp;amp;gt; t(&amp;#39;Show &amp;amp;quot;Read more »&amp;amp;quot;&amp;#39;),&amp;#xA;    &amp;#39;#default_value&amp;#39; =&amp;amp;gt; variable_get(&amp;#39;example_foo_read_more&amp;#39;, FALSE),&amp;#xA;  );&amp;#xA;  return $form;&amp;#xA;}&amp;#xA;&amp;#xA;/** Block callback for save op, delta foo.&amp;#xA; *&amp;#xA; * @param $edit Array&amp;#xA; *   The submitted form values.&amp;#xA; *&amp;#xA; * @return Array&amp;#xA; *   Form api array.&amp;#xA; */&amp;#xA;function _example_block_save_foo($edit) {&amp;#xA;  variable_set(&amp;#39;example_foo_read_more&amp;#39;, $edit[&amp;#39;read_more&amp;#39;]);&amp;#xA;  return TRUE;&amp;#xA;}&amp;#xA;&amp;#xA;/** Block callback for view op, block foo.&amp;#xA; *&amp;#xA; * @return Array&amp;#xA; *   Block array with content and subject key.&amp;#xA; */&amp;#xA;function _example_block_view_foo() {&amp;#xA;  $block[&amp;#39;subject&amp;#39;] = t(&amp;#39;Title of block #1&amp;#39;);&amp;#xA;  $block[&amp;#39;content&amp;#39;] = &amp;#39;Content of block #1&amp;#39;;&amp;#xA;  if (variable_get(&amp;#39;example_foo_read_more&amp;#39;, FALSE)) {&amp;#xA;    $block[&amp;#39;content&amp;#39;] .= l(t(&amp;#39;Read more »&amp;#39;), &amp;#39;foo/more&amp;#39;);&amp;#xA;  }&amp;#xA;  return $block;&amp;#xA;}&amp;#xA;&amp;#xA;/** Block callback for view op, block bar.&amp;#xA; *&amp;#xA; * @return&amp;#xA; *   Block array with content and subject key.&amp;#xA; */&amp;#xA;function _example_block_view_bar() {&amp;#xA;  $block[&amp;#39;subject&amp;#39;] = t(&amp;#39;Title of block #2&amp;#39;);&amp;#xA;  $block[&amp;#39;content&amp;#39;] = &amp;#39;Content of block #2&amp;#39;;&amp;#xA;  return $block;&amp;#xA;}&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;From here on, you can start improving even more, e.g. by splitting out the theme functions that render blocks, the database or parsers that fetch and model the data and so on. But that is for later.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Lokale klokkenluiderssite Opennu is een farce</title>
   <link href="https://berk.es/2010/12/20/lokale-klokkenluiderssite-opennu-is-een-farce/"/>
   <updated>2010-12-20T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2010/12/20/lokale-klokkenluiderssite-opennu-is-een-farce/</id>
   <content type="html">&amp;lt;p&amp;gt;Vandaag releaste Michel Spekkers een &amp;amp;quot;&amp;lt;a href=&amp;#34;http://opennu.nl&amp;#34;&amp;gt;Lokale wikileaks&amp;lt;/a&amp;gt;&amp;amp;quot;. Ik ben enorm voorstander van transparantie en van wat wikileaks doet. Ik kan het daarom alleen maar toejuichen als het voorbeeld van wikileaks gevolgd wordt en de beweging Decentraliseert. Dat maakt de beweging alleen maar sterker. Maar wat hier gereleased is, is verre van een wikileaks. Ik durf zelfs te stellen: een farce. Een klokkenluiders-outlet (wikileaks) staat of valt bij één zeer belangrijke voorwaarde: • Anonimiteit moet gegarandeerd zijn. Nu én in de toekomst.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Die garantie vereist weer drie belangrijke voorwaarden:• De organisatie moet juridisch zeer sterk opgezet zijn• De infrastructuur moet technisch zeer sterk opgezet zijn• De marketing en PR moet goed opgezet zijnDat eerste is mij zeer onduidelijk, dus ik kan er weinig meer over zeggen dan dat het een éénpersoonsactie lijkt te zijn die juridisch helemaal niets uitgewerkt heeft. De reden dat dit belangrijk is, is heel eenvoudig: als justitie met een dwangbevel de servers en administratie komt ophalen, moet dat tegengegaan kunnen worden. Het kán niet zo zijn dat bij de eerste de beste tegenslag alle gelekte informatie in handen van het OM komt. Dat is geen garantie tot anonimiteit, dat is slechts een aardig probeersel.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Het tweede is mij wél duidelijk. Dat is gewoon enorm slecht. Technisch mist er enorm veel. Een klein onderzoekje leverde met het volgende op: De domeinnaam opennu.nl is beheerd door domein-direct.nl, onderdeel van &amp;lt;a href=&amp;#34;http://www.openkvk.nl/17153162&amp;#34;&amp;gt;web-direct&amp;lt;/a&amp;gt;. De server staat bij &amp;lt;a href=&amp;#34;http://www.openkvk.nl/17153162&amp;#34;&amp;gt;flexwebhosting&amp;lt;/a&amp;gt;, en lijkt op zijn beurt weer onderverhuurd aan &amp;lt;a href=&amp;#34;http://ingento.nl/&amp;#34;&amp;gt;ingento&amp;lt;/a&amp;gt;, die in eerste instantie niet ingeschreven lijkt te zijn bij de KvK. Op deze server draaien, volgens een korte analyse minstens dertien andere sites.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik ken die bedrijven niet, kan er zo snel even geen verdere informatie over vinden, maar dat geeft ook niet. Belangrijk is het simpele feit dat door jou gelekte data in het beste geval op een shared omgeving van een keten goedwillige bedrijven terecht komt. Slechts één van deze bedrijven hoeft een overheid een goede reden te geven om de servers in beslag te nemen, en jou gelekte informatie, inclusief je IP-adres enzovoort, komt precies daar terecht waar je dat niet wilt hebben.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een zo mogelijk nóg belangrijkere duiding dat deze zeer waarschijnlijk goedwillende mijnheer Spekkers zijn zaken niet voorelkaar heeft en jou anonimiteit absoluut niet kan garanderen, is het &amp;lt;a href=&amp;#34;https://opennu.nl/&amp;#34;&amp;gt;gebrek aan https&amp;lt;/a&amp;gt;, een veilige verbinding om over te uploaden. Het certificaat is ongeldig, de https site bestaat überhaupt niet, en de mogelijkheid om hierover te uploaden dus ook niet. Zoals het nu staat, kan  (en zal) iedereen op je netwerk en iedereen tussen jou en opennu.nl gewoon meelezen met wat je stuurt. Zelfs als je versleutelde bestandemeern stuurt, kan je IP adres, het gegeven dát jet iets naar opennu stuurt en alle bijbehorende data gewoon afgeluisterd worden. En zover ik weet gebeurt dit ook in iedere (middel)grote organisatie die haar netwerk een beetje beveiligd. Dus vanaf je werk dat ene PDFje uploaden, zorgt bijna direct dat je door de mand valt bij deze opzet. En vanaf thuis, kan nog iedereen tussen jou en opennu afluisteren. ziggo heeft gewoon logs waarin mensen kunnen nalezen dat jij een PDF stuurde in de nacht van N op M. Nee, zoals Spekkers het nu heeft opgezet is het hoogstends een goedwillend, maar erg naïef gebeuren. Een snel aangemaakt hotmail-accountje op naam van Piet Snot, kerkstraat 12, ons dorp, bied nog meer garanties.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En daarmee eerder een bedreiging voor de wikileaks beweging dan een toevoeging. Ik moet er niet aan denken dat het bijna onontkoombare ontslag van een Nederlandse ambtenaar, na lekken via opennu breed uitgemeten wordt in de pers. Dat doet de wikileaks beweging alleen maar onbetrouwbaar overkomen.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Counter queries for complex, none-distinct SQL in Drupals Pager system.</title>
   <link href="https://berk.es/2010/11/10/counter-queries-for-complex-none-distinct-sql-in-drupals-pager-system/"/>
   <updated>2010-11-10T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2010/11/10/counter-queries-for-complex-none-distinct-sql-in-drupals-pager-system/</id>
   <content type="html">&amp;lt;p&amp;gt;I think everyone knows these moments: You have a problem, a question. And just by asking that question, the answer pops up in your head. It happens to me often, when programming.&amp;#xA;It is obvious: by asking the question, you have to analyze and simplify the problem. And by doing so&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Today, there was another great way &amp;lt;a href=&amp;#34;http://stackoverflow.com/users/73673/berkes&amp;#34;&amp;gt;Stackoverflow helped me&amp;lt;/a&amp;gt;: I had a problem with a &amp;lt;em&amp;gt;Drupal pager-query on a none-distinct SQL query&amp;lt;/em&amp;gt;. And right when I was finishing up, the answer struck me. But because I spent so much effort in the question, and I don&amp;#39;t want to forget, I decided to share it.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;hr&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Drupal uses &amp;lt;a href=&amp;#34;http://api.drupal.org/api/drupal/includes--pager.inc/function/pager_query/6&amp;#34;&amp;gt;pager_query&amp;lt;/a&amp;gt; if you wish to get a limited result, for used as a paged, list.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A simple example would be (I am aware of my code not adhering to Drupal standards, done that for simplicity):&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;$nodes = pager_query(&amp;#39;SELECT title, created FROM node WHERE published = 1&amp;#39;, 20, 0);&amp;#xA;while ($node = db_fetch_object($nodes)) {&amp;#xA;   $html .= &amp;amp;quot;$node-&amp;amp;gt;title ($node-&amp;amp;gt;created)&amp;amp;quot;;&amp;#xA;}&amp;#xA;$html .= theme(&amp;#39;pager&amp;#39;); //This collects &amp;amp;quot;magic&amp;amp;quot; variables set by pager_query to build a string containing pagerlinks.&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Now, I need to tackle a much more complex query, one that is not distinct as above. &amp;lt;strong&amp;gt;I am not sure if I should solve this in the domain of SQL, or rather in the domain of Drupal/PHP.&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;session&amp;amp;lt;-&amp;amp;gt;node is an N:1 relation: any node has_many sessions. A session has_one node.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;$nodes = pager_query(&amp;#39;SELECT node.nid, node.title, node.created, sessions.time, sessions.sid FROM node INNER JOIN sessions ON session.nodes_nid = node.nid WHERE published = 1&amp;#39;, 20, 0);&amp;#xA;while ($node = db_fetch_object($nodes)) {&amp;#xA;   $n-&amp;amp;gt;title = $node-&amp;amp;gt;title;&amp;#xA;   $n-&amp;amp;gt;nid = $node-&amp;amp;gt;nid;&amp;#xA;   unset($node-&amp;amp;gt;title, $node-&amp;amp;gt;nid);&amp;#xA;   $n-&amp;amp;gt;sessions[$node-&amp;amp;gt;sid] = $node;&amp;#xA;   $items[$n-&amp;amp;gt;nid] = $n;&amp;#xA;}&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Above routine allows me to query the database ONCE, fetch nodes that have_many playdates, and collect them in a list that:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;has one row per $node.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;each $node row has a list of all its associated sessions under $node-&amp;amp;gt;sessions.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;However, pager_query lists one item for each row, instead of using a smarter counter query.&amp;lt;strong&amp;gt;This is where the answer became clear&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;and-so-the-answer-is-really-simple-counter-query&amp;#34;&amp;gt;And so, the answer is really simple: counter query&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;the last parameter of pager_query() is an alternative query to be used as counter. In the abovementioned example. that would be:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;$sql = &amp;#39;SELECT node.nid, node.title, node.created, sessions.time, sessions.sid FROM node INNER JOIN sessions ON session.nodes_nid = node.nid WHERE published = 1&amp;#39;;&amp;#xA;$counter = &amp;#39;SELECT COUNT(DISTINCT(node.nid) FROM node INNER JOIN sessions ON session.nodes_nid = node.nid WHERE published = 1&amp;#39;;&amp;#xA;pager_query($sql, 20, 0, $counter);&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Op Apple kun je geen kritiek hebben.</title>
   <link href="https://berk.es/2010/11/08/op-apple-kun-je-geen-kritiek-hebben/"/>
   <updated>2010-11-08T00:00:00&#43;01:00</updated>
   <id>https://berk.es/2010/11/08/op-apple-kun-je-geen-kritiek-hebben/</id>
   <content type="html">&amp;lt;p&amp;gt;Open een willekeurig forumtopic of blogpost met &amp;amp;quot;Mac&amp;amp;quot; &amp;amp;quot;gewoon werkt&amp;amp;quot; en &amp;amp;quot;virussen&amp;amp;quot; in de titel, en zie de discussies daar ontvouwen als ware religieuze oorlogen. Ik vermoed dat &amp;lt;a href=&amp;#34;http://www.nrcnext.nl/blog/2010/11/08/kritiek-op-geslotenheid-van-apple-is-gewoon-geneuzel-in-de-marge/&amp;#34;&amp;gt;Niels t Hooft, in zijn stuk in NRC-next&amp;lt;/a&amp;gt; dat voor ogen had, want anders had hij het niet zo vol tegenstellingen, drogredenen en feitelijke onjuistheden neergeschreven. Niels gaat uit van het idee dat virussen binnenkomen met programma&amp;#39;s. Daar is eigenlijk zijn hele stuk op gebouwd. Virussen komen alleen niet binnen doordat gebruikers deze installeren, virussen die jezelf binnenhaalt heten Trojaan horses en is een heel ander probleem. Dat is niet semantisch, maar écht een probleem van een heel andere orde. Ook Mac, ook Linux, zijn onveilig voor Trojan horses: als iemand zo dom is zelf de applicatie te installeren, kun je hoogstends beschermen wat zo een trojan horse kan aanrichten. Dat doet BSD en daarmee Mac ook heel goed. En Linux ook, trouwens. Belangrijk is om applicaties te (kunnen) screenen. Het grootste argument tegen de geslotenheid van de Appstore is dan ook dat niemand weet óf Apple eigenlijk wel controleert op Trojans. Blijkbaar niet altijd &amp;lt;a href=&amp;#34;http://www.boingboing.net/2009/11/05/iphone-game-dev-accu.html&amp;#34;&amp;gt;even goed&amp;lt;/a&amp;gt;. Een ander argument is mogelijk nog belangrijker: is geslotenheid eigenlijk wel nodig voor controle? Android, Firefox plugins, de Ubuntu Installer en nog veel meer voorbeelden van weinig tot zeer open systemen zijn bekend, waarbij ook gewoon veiligheidsproblemen onderzocht worden. Deze modellen zijn niet bewezen slechter (of beter), maar in elk geval wel opener. Niels gelóóft graag dat gesloten beter is, net als menig Open Source liefhebber gelóóft dat openheid altijd beter is. Maar daarmee is het nog niet waar. Zover mij bekend, zijn de geleerden het na vele -tig jaar onderzoeken nog altijd niet eens over welk model nu het beste is. Mac weet dat, maar doet alsof haar model het beste is. Zoals ieder commerciëel bedrijf met goede marketing moet doen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Niels is op zijn Mac goed tegen virussen (ik bedoel hier dus géén trojans) beschermd door de zeer goed gebouwde basis, het Operating Systeem. Een Open Source Unix variant genaamd BSD. Linux (bekend onder &amp;amp;quot;merknamen&amp;amp;quot; als Ubuntu, Debian, SuSE, Redhat enzovoort) is een andere bekende variant. Windows gebruikt een hele andere &amp;amp;quot;onderkant&amp;amp;quot; die in zichzelf een heel stuk minder veilig is. Dus dat Niels weinig last van virussen heeft, is vooral te danken aan de Open Source wereld. Niet aan de geslotenheid van de Mac. Het andere grote deel van virussen, komt voort uit gaten in (veel) gebruikte software. Adobe (bekend van de PDFs) en Internet Explorer zijn bekende voorbeelden van software met veel veiligheidsproblemen, gaten, via welke virussen binnenkomen. Ook hier is de oplossing van het probleem juist openheid gebleken. In het geval van Internet Explorer (IE), de webbrowser die standaard met Windows meekomt, had Microsoft een monopolie. Iedereen gebruikte deze standaardbrowser, waardoor ze er zelf niet aan doorontwikkelden; een zeer verouderd systeem, waar nauwelijks alternatief voor was werd niet meer verbeterd. De crackers en virusbouwers zaten niet stil, IE wel. Totdat Firefox kwam: veiliger, sneller beter. Dat was ongeveer de slogan waarmee zij het monopolie van IE doorbraken. Microsoft antwoordde met doorontwikkeling: IE7, IE8 en nu bijna 9. Ook met slogans als &amp;amp;quot;veiliger sneller beter&amp;amp;quot; gemarkt. Keuze bleek het medicijn voor de grootste digitale veiligheidsprobleem ooit, te zijn. Gebrek aan keuze, bleek de oorzaak van de kwaal. Adobe&amp;#39;s PDF reader problemen zijn van een andere aard en komen ook grotendeels, indirect, uit het gebrek van keuzevrijheid. Mac leverde deze (lekke) software ook gewoon voor haar Macs. Gelukkig hebben Macgebruikers de keuzevrijheid om andere PDF software te gebruiken. Nóg wel. Totdat Mac een deal met Adobe sluit en alle andere PDF-lezers weigert. Nu is dit specifieke voorbeeld haast ondenkbaar: Adobe en Mac hebben juist ruzie. Maar hypothetisch is het probleem enorm. Kiest Mac voor één App om X te doen, dan heeft de gebruiker niet de keuze om het veiligere Y te installeren. Zoals ik al zei: de geleerden zijn het nog altijd niet eens over welk model nu het veiligst is: open of gesloten, het Kapitalisme of Geleidde markt, eco-diversiteit of juist niet, marktwerking of overheidsbemoeienis. (of een van de vele tussenvormen en andere alternatieven). Maar in élk geval is het zo dat in veel gevallen júist keuzevrijheid en openheid tot betere software leidden. Als Niels gelijk had, en minder keuze iets goeds was, dan werkten we nu allemaal op windows95, belden we met een Nokia 3310. Gelukkig kon Niels kiezen en koos hij een Mac met MacOS en een iPhone.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dan wordt een derde partij door Niels aan de haren erbij gesleept. Eentje die volledig buiten het hele verhaal van &amp;amp;quot;waarmee houd je het beste virussen buiten de deur&amp;amp;quot; staat, Defective by Design. Die organisatie is opgericht om de strijd met DRM aan te gaan. Dus om kopieerbescherming en aanverwanten te bestrijden. Dat Mac DRM gebruikt om ervoor te zorgen dat u uw Apps niet kan delen met derden, is haar een doorn in het oog. Dat is een heel andere discussie voor een heel ander moment. Niels komt met een klassieke drogreden uit vele religieuze debatten &amp;amp;quot;If you are not with us, you are against us&amp;amp;quot;. Zoals alles is ook hier de wereld een stuk genuanceerder dan Defective by Design en Niels doen vermoeden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik zal niet ontkennen dat Mac gebruiksvriendelijk is, zaken goed doordenkt en enorm goed weet te marketen. In tegendeel: dat doen ze geweldig. Maar net zoals mijn Philips tandenborstel &amp;amp;quot;gewoon werkt&amp;amp;quot; deed mijn eerdere Braun dat ook; keuzevrijheid staat &amp;amp;quot;gewoon werken&amp;amp;quot; niet in de weg. Die twee sluiten elkaar niet uit. Al doet Mac dat graag zo lijken, want het is hun standaard antwoord op de klacht dat hun model gesloten is. Een drogreden, om de ware reden (gewoon, net als ieder ander gezond bedrijf geld verdienen aan klanten en marktaandeel veroveren) niet te hoeven noemen. Klinkt vriendelijker, maar is daarom nog niet waar.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Als voorbeeld komt Niels met de Android store. Een winkel die open is. Google voert wel enig toezicht, maar dat is, jaja, vooral om te voorkomen dat onveilige applicaties te weren. Maar daar zijn niet notoir meer onveilige Apps in de Mac winkel te  vinden dan in de Android winkel. Dat zegt Niels gelukkig ook niet, maar hij doet wel alsof dit een oorzaak gevolg is. Dat is het dus niet. De geslotenheid zorgt niet voor beter werkende producten. Op mijn Ubuntu-laptop werken applicaties vele malen beter samen, zijn ze veel beter &amp;amp;quot;usable&amp;amp;quot; dan op menig windowsomgeving. En Ubuntu werkt volgens een nóg veel opener manier dan Android. Niet alleen mag iedereen zijn App bijdragen in de &amp;lt;a href=&amp;#34;http://www.ubuntu.com/desktop/features#apps&amp;#34;&amp;gt;repositories&amp;lt;/a&amp;gt;, maar aan veel applicaties kan ook nog eens iedereen meewerken. Zomaar, zonder een bedrijf ertussen die bepaalt wie meedoet en waarom! Ik wil hier de discussie niet aangaan of Windows danwel Ubuntu beter is op bepaald gebied. Maar wél weerleggen dat het specifieke probleem dat Niels noemt, het onderling samenwerken van Apps, blijkbaar niet met het gesloten model te maken heeft. Waarschijnlijk gebruikt Niels zelf op zijn Mac ook heel veel &amp;lt;a href=&amp;#34;http://www.opensourcemac.org/&amp;#34;&amp;gt;open source software&amp;lt;/a&amp;gt;. De macgebruikers die ik ken, wel in elk geval. En die software werkt heel goed samen met andere software. Software die gebruiksvriendelijk is én goed met andere software samenwerkt. Oh, en die hij overigens niet meer kan gebruiken Niels als hij alleen nog maar via een Appstore software mag installeren.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik hoor je nu al de hele tijd denken: jamaar, de sales, de cijfers? Klopt, Mac verkoopt goed. Mac heeft minder last van virussen dan Windows PCs. Maar Linux, bijvoorbeeld Ubuntu, heeft er nóg minder. Is het daarmee beter? Is daarmee het Ubuntu-model bewezen beter? Ik gelóóf van wel, maar in tegenstelling tot Niels durf ik dat dan niet als bewijs op te voeren.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Veel mensen roepen dan meteen &amp;amp;quot;dat komt omdat er veel meer windows PCs zijn&amp;amp;quot;. Dat is niet waar, er zijn veel meer Linux Servers en toch zijn er nauwelijks virussen voor geschreven. Bovendien schreef ik in de eerste paragraaf al, zitten Mac (BSD), Linux en Windows gewoon anders in elkaar. Windows Vista heeft relatief gezien véél minder last van virussen dan haar windows 95 voorgangers. Gewoon omdat het technisch veel beter doordacht is. Maar ook dit is een heel andere discussie; wie er hier ook &amp;amp;quot;gelijk&amp;amp;quot; heeft, met Niels zijn argument dat je beter een gesloten model kunt hebben om veiliger software te verdelen, heeft het in elk geval niets van doen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En de cijfers dat er meer iPhone Apps verkocht worden dan Android Apps? Zoals Niels stelt?  Mac verkoopt toch veel meer Apps dan Android? Als Niels gelijk had, en keuzevrijheid slecht was voor het gebruiksgemat en de veiligheid, dan was Google toch juist veiliger en beter bruikbaar? Hoe meer applicaties, hoe minder veilig. En hoe minder gebruiksvriendelijkheid. Dat was juist het betoog van Niels. Ik begrijp hem niet meer. Maar dat heb ik wel vaker met religieuze fanatiekelingen. Dat ik ze niet begrijp.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;En als afsluiter de idee dat op een Mac überhaupt alles &amp;amp;quot;gewoon werkt&amp;amp;quot;. Ik houd niet van anekdotische bewijsvoering, maar we hebben hier ook maar één voorbeeld nodig om te bewijzen dat dit onwaar is. Dat een Mac dus hoogstens &amp;amp;quot;veel vaker dan X of Y gewoon werkt&amp;amp;quot;. Maar zeker niet &amp;amp;quot;altijd&amp;amp;quot;. Tijdens de trainingen die ik geef en bij de klanten die ik op weg help, moet ik vaak Macgebruikers helpen. Met computerproblemen. Programma&amp;#39;s die niet werken, firewalls die bepaalde applicaties laten crashen. Bestanden die niet geopend kunnen worden. Ach, &amp;lt;a href=&amp;#34;http://macwereld.nl/forum/apple_applicaties/&amp;#34;&amp;gt;kijk zelf maar&amp;lt;/a&amp;gt;, hoeveel gebruikers nog problemen hebben.&amp;lt;strong&amp;gt;Update&amp;lt;/strong&amp;gt; Tijdens het inkorten (jaja het eerdere stuk was nǵ langer) heb ik een belangrijk gegeven weggelaten, maar vergeten elders terug te laten komen: Niels nuanceert &amp;lt;a href=&amp;#34;http://nielsthooft.com/openheid-geslotenheid-vs-integratie-fragmentatie&amp;#34;&amp;gt;op zijn blog&amp;lt;/a&amp;gt; enorm veel van de stellingen. Waardoor hij dáár duidelijk laat merken wel degelijk de nuances te begrijpen en tegenstellingen in zijn artikel onderbouwt. Neemt niet weg dat het stuk dat in de krant verscheen gewoon vol drogreden en tegenstellingen zit.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Drupal filetypes for Ack grep.</title>
   <link href="https://berk.es/2010/10/27/drupal-filetypes-for-ack-grep/"/>
   <updated>2010-10-27T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/10/27/drupal-filetypes-for-ack-grep/</id>
   <content type="html">&amp;lt;p&amp;gt;How often did I not think &amp;amp;quot;sigh, I wish I could just &amp;lt;em&amp;gt;grep&amp;lt;/em&amp;gt; this pile of invoices for that date&amp;amp;quot;. But unfortunately, the all-powerfull search-tool &amp;lt;a href=&amp;#34;http://www.kingcomputerservices.com/unix_101/grep_this.htm&amp;#34;&amp;gt;grep&amp;lt;/a&amp;gt; is not available &amp;lt;acronym title=&amp;#34;In real life&amp;#34;&amp;gt;IRL&amp;lt;/acronym&amp;gt;. But it is available on most unices, including all Linux systems and OSX.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But life gets even better. With &amp;lt;a href=&amp;#34;http://betterthangrep.com/&amp;#34;&amp;gt;Ack-grep&amp;lt;/a&amp;gt;. A much faster, better and more targeted tool. For example , it will ignore all sorts of files you usually wanted to ignore, when &amp;amp;quot;grepping&amp;amp;quot; trough a pile of files. You know, searching for that line &amp;amp;quot;sent to &amp;lt;a href=&amp;#34;mailto:foo@example.com&amp;#34;&amp;gt;foo@example.com&amp;lt;/a&amp;gt;&amp;amp;quot;, but getting all sorts of results from backup files, revision-databases and what more. Ack does this. And more.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ack also allows you to define profiles. Sets of files to be searched trough and sets of files to ignore. It comes with &amp;lt;a href=&amp;#34;http://betterthangrep.com/&amp;#34;&amp;gt;lots of built in sets&amp;lt;/a&amp;gt;, but not with Drupal predefined.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;To get a Drupal profile, just &amp;lt;a href=&amp;#34;http://stackoverflow.com/questions/950755/how-can-i-search-the-contents-of-module-files/950855#950855&amp;#34;&amp;gt;add a .ackrc file to your home directory&amp;lt;/a&amp;gt; and add the profiles there.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;echo &amp;amp;quot;--type-set=drupal=.php,.inc,.module,.install,.info,.engine&amp;amp;quot; &amp;amp;gt;&amp;amp;gt; ~/.ackrc&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Now you can search trough Drupal with&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;ack &amp;amp;quot;implementation of hook_&amp;amp;quot;&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Or, if you want to ignore all none-drupal(ish) files, with&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;ack --drupal &amp;amp;quot;implementation of hook_&amp;amp;quot;&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Or, if you want to search trough all files, except Drupal-files&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;ack --nodrupal &amp;amp;quot;Licence&amp;amp;quot;&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Many additional tools, such as gedit addons, will use ack, when found (over grep). And will benefit from this drupal profile too.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Waarom ik spotify meteen alweer gedag zeg.</title>
   <link href="https://berk.es/2010/10/25/waarom-ik-spotify-meteen-alweer-gedag-zeg/"/>
   <updated>2010-10-25T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/10/25/waarom-ik-spotify-meteen-alweer-gedag-zeg/</id>
   <content type="html">&amp;lt;p&amp;gt;Ik ga spotify even een kans. Aangezien ik meer dan €10 per maand aan muziek uitgeef, leek mij het model van spotify ook wel te boeien: voor €10 per maand onbeperkt muziekluisteren. Nieuwe, oude, muziek uit eigen bibliotheek, muziek van vrienden enzovoort. Mooi model. Alleen niet voor mij.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Met wat crativiteit, is er zeker geen gebrek aan (legale) gratis muziek. Mijn &amp;amp;quot;Nooit beluisterd&amp;amp;quot; playlist bevat voor 4 weken, 2 dagen en 3 uur muziek, nog.&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Wanneer je jou spotify account opzegt (of het bedrijf spotify verdwijnt) ben je al &amp;amp;quot;jou&amp;amp;quot; muziek kwijt. Niet die al op je hardeschijf staat (eerder gekocht of gedownload), maar alle muziek die van spotify zélf is. Muziek die je koopt, betaal je eenmaal voor en kun je dan blijven luisteren. Of de winkel waar je hem kocht nu falliet gaat of niet, maakt niets uit. In principe kunt je deze muziek zelfs nog doorverkopen (in geval van CDs en LPs enzo, vooral). Muziek is écht van jou.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;De spotify muziekspeler voor linux is rotzooi. Hij &amp;lt;a href=&amp;#34;http://getsatisfaction.com/spotify/topics/spotify_for_linux_segfaults_under_ubuntu_maverick_beta_upcoming_10_10&amp;#34;&amp;gt;crached&amp;lt;/a&amp;gt;, is traag, past niet in mijn desktop (knopjes zitten allemaal verkeerd), werkt niet met de normale multimedia knoppen op mijn keyboard enzovoort. Als ik betaal voor software, dan moet het gewoon werken. Zónder allerlei trucs en moeilijkdoenderij. Spotify werkt niet &amp;amp;quot;gewoon&amp;amp;quot;, iets wat iedere andere (gratis), OpenSource muziekspeler wél gewoon doet.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Als je vanaf een andere computer (of mijn mobiel) luistert, stopt de andere client. Logisch, gezien hun bedrijfsmodel, dat je nooit vanaf meerdere plekken je tegelijk muziek kunt luisteren. Voor mij vervelend, omdat we in de huiskamer een multimedia PCtje hebben staan (IPV een TV), waarop we muziek luisteren, TV kijken enzovoort. Als mijn huisgenoot muziek wil luisteren op die PC moet ze eerst mij uitloggen, op haar spotify inloggen (die heeft ze overigens niet, dus dit is hypotetisch) en dan pas kan ze luisteren. Als ze dat per ongeluk vergeet, stopt bij mij de muziek met spelen. Veel gedoe en verre van ideaal, voor onze situatie.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Eigendomsrecht van de luisterdata is vaag. Spotify kan (en moet, deels) alles bijhouden wat ik speel. Dat vind ik niet heel erg, zolang maar duidelijk is wie de eigenaar van die data is. Het is immers (redelijk) vrij privacygevoelige data. Eerlijkheid gebied me te zeggen dat ik al tig jaar mijn muziek-speel informatie naar last.fm stuur en recent ook nog naar libre.fm. Dus in mijn geval maakt het toevallig niet zo veel uit. Last.fm en libre.fm kan ik echter opzeggen (en gewoon mijn muziek blijven luisteren) of verwijderen. Spotify kan ik ook opzeggen, maar, zie boven, dan ben ik wel mijn muziek &amp;amp;quot;kwijt&amp;amp;quot;. Privacy is en blijft een probleem met dit soort diensten.&amp;#xA;Dus rest mij niets anders dan Spotify waarwel te zeggen en ooit eens een OpenSource, distributed, vrij en mooi playlist-sharing-systeem op te zetten. Misschien bovenop libre.fm of in een alternatieve versie van Diaspora, wie weet. Mooi zomervakantieproject.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Even terugkomend op die gratis muziek: Dat zijn veel mixxes van bijvoorbeeld soundcloud, tig podcasts, een paar &amp;amp;quot;herbeluisterde&amp;amp;quot; shows van Studio Brussel, een grote hoeveelheid &amp;lt;a href=&amp;#34;http://www.archive.org/details/netlabels%22&amp;#34;&amp;gt;creative commons en andere vrije muziek&amp;lt;/a&amp;gt; en een flinke backlog aan dooie-muziekanten-torrents. Ik weet heus wel dat downloaden van een volledige discography van iemand, op piratebay niet volgens de regels is, maar als Micheal Jackson dood is, voel ik geen enkele scrupules om zijn discografie binnen te halen. Nouja, behalve misschien dan dat hij ook enorm slechte muziek gemaakt heeft :). Aan aanvoer van &amp;amp;quot;nieuwe&amp;amp;quot; muziek bij mij dus geen gebrek. En ik voel me nog minder schuldig omdat ik uiteindelijk wel heel veel muziek &amp;lt;a href=&amp;#34;https://one.ubuntu.com/basic/&amp;#34;&amp;gt;koop&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;</content>
 </entry>
 
 <entry>
   <title>Four troublesome notes with Drupal module-updates and upgrades.</title>
   <link href="https://berk.es/2010/10/13/four-troublesome-notes-with-drupal-module-updates-and-upgrades/"/>
   <updated>2010-10-13T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/10/13/four-troublesome-notes-with-drupal-module-updates-and-upgrades/</id>
   <content type="html">&amp;lt;p&amp;gt;On &amp;lt;a href=&amp;#34;http://stackoverflow.com/questions/3920629/how-to-upgrade-drupal-modules/3922112#3922112&amp;#34;&amp;gt;stackoverflow&amp;lt;/a&amp;gt;, I wrote an answer to someone having issues with Drupal module-upgrades. Something I thought worth noting down here too. There are many gotcha&amp;#39;s in module-upgrades, that people find out sooner or later. Often found out in not-so-nice-ways. :)&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ol&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Not all module developers think the same about dot (minor) releases: sometimes 5.x-1.2 and 5.x-1.3 are major rewrites or come with completely new features, themeable-functions, pages or APIs.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Not all upgrades are compatible with others. Sometimes you cannot update module B to 6.x-1.4, because of its dependency with A, when A is not compatible with 6.x-1.4 (yet). Drupal does not support dependencies on versions.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Major releases &amp;lt;em&amp;gt;imply&amp;lt;/em&amp;gt; (but do not guarantee) incompatibility, or even complete rewrites: Upgrading from 5.x-1.4 to 5.x-2.1 might force major rewrites of custom code, including your theme.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Security updates often are dependent on earlier releases: 6.x-1.2 might introduce new features (that you do not want, or wish to ignore), 6.x-1.3, can be a security-release that requires (some of the) the changes in 6.x-1.2 to be available. You must then either fiddle around with patches, or go trough that feature release anyway.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ol&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Off course there are all these other notes to take, such as database-migrations (that might go wrong and destroy or break all your heard-earned data), new-features-come-whith-new-bugs problems, your own, custom code breaking on a new version, etceteras. But you already knew that, did you not? :)&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Moet je Drupal7 gaan gebruiken voor een nieuwe Drupalsite?</title>
   <link href="https://berk.es/2010/09/27/moet-je-drupal7-gaan-gebruiken-voor-een-nieuwe-drupalsite/"/>
   <updated>2010-09-27T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/09/27/moet-je-drupal7-gaan-gebruiken-voor-een-nieuwe-drupalsite/</id>
   <content type="html">&amp;lt;p&amp;gt;In een recente mailcorrespondentie voorzag ik iemand van wat advies over Drupal 7. Drupal 7 is de Drupal die binnenkort gereleased zal worden als opvolger van Drupal 6. Drupal6 is daarmee niet ten einde, mogelijk blijft deze nog jaren onderhouden. Drupal 5 komt daarmee wél te vervallen. De mail vroeg: &amp;amp;gt; Ik wil eigenlijk gaan beginnen met D7 aangezien deze er nu bijna is en het project sowieso nog wel een 4-tal maanden zal duren.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Een medewerker van Dries bij Acquia duwde ons ook in deze richting voor onze community site. En ik antwoordde: Eerlijkgezegd geloof ik hier voor geen meter in. Tenzij je concrete voorbeelden boven water kunt krijgen waar Drupal7 &amp;lt;em&amp;gt;nu&amp;lt;/em&amp;gt; al beter is dan Drupal6. Ik zie die voor jullie project nog niet. Je zult hoe dan ook een technisch ontwerp (naar een functioneel ontwerp) moeten opzetten. Als in dat technisch-ontwerp grote problemen boven water komen, die met Drupal7 opgelost zouden worden, is dat natuurlijk een goede optie. Maar als dat niet zo is, is Drupal7 altijd een nadeel:  - &amp;lt;strong&amp;gt;Onbekend&amp;lt;/strong&amp;gt; (reken op maanden, mogelijk een jaar na release voordat er net zoveel experts en developers voor zijn als voor 6).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Onbekend&amp;lt;/strong&amp;gt; (marketeers roepen natuurlijk dat het getest en klaar is, maar dat moet eerst nog bewezen worden).&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Onaf&amp;lt;/strong&amp;gt;, third party modules worden grotendeels al vooraf klaargemaakt. Maar wat ik daarvan gezien heb, valt a) het aantal modules dat voor 7 klaat is tegen, maar is vooral b) de kwaliteit van die ports erg (erg) slecht vaak. Verwacht dat in het eerste half jaar van Drupal7 de helft van die modules vervangen wordt door alternatieven.&amp;#xA;Als je zo vroeg op de trein springt is dat uiteraard niet te voorkomen in geval drupal 7 nieuwe features heeft die in 6 niet te krijgen zijn, maar heel onverstandig, omdat je écht in de voorhoede meedraaft, en dus veel zelf moet uitvinden (in plaats van gebaande paden te volgen) en daarmee vaak doodlopende paden bewandelt, met onvermijdelijke pijnlijke migraties voor de boeg. Mijn advies, kortom was, om voorlopig nog niets met Drupal 7 te gaan doen. Het betreft hier een groot project (4 maanden ontwikkeling) en dus niet De Blog Van Mien. Voor een klein, goed te overzien project is Drupal 7 precies net zo goed als Drupal 6. En zijn kleine voordelen als &amp;amp;quot;Maar Drupal 7 heeft een heel coole interface&amp;amp;quot; geldige argumenten om ervoor te kiezen. Maar voor ieder groter project moet je je richten op de technische eisen en wensen. En daarvan heeft Drupal 7 mogelijk enkele kant-en-klaar, terwijl 6 die niet heeft. Maar andersom is de kans even zo groot, of groter, dat vor Drupal6 daar al een goed getestte en uitgewerkte oplossing is, terwijl in de wereld van Drupal 7 nog niets uitgewerkt is voor dat specifieke probleem.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Drupal needs you to conform, a framework will conform to you.</title>
   <link href="https://berk.es/2010/09/13/drupal-needs-you-to-conform-a-framework-will-conform-to-you/"/>
   <updated>2010-09-13T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/09/13/drupal-needs-you-to-conform-a-framework-will-conform-to-you/</id>
   <content type="html">&amp;lt;p&amp;gt;An answer on &amp;lt;a href=&amp;#34;http://stackoverflow.com/questions/3697789/why-use-symfony-not-drupal/3698456#3698456&amp;#34;&amp;gt;stackoverflow&amp;lt;/a&amp;gt; to the endless question &amp;amp;quot;should I choose Drupal or Foo&amp;amp;quot; gets the whole problem with many Drupalprojects in one sentence:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;  Drupal needs you to conform, symfony will conform to you - choose whichever you want.&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I would refrase that as:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;&amp;#xA;  Drupal needs you to conform, a framework will conform to you - choose whichever you want.&amp;#xA;&amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Now, that would not be a big problem, if you have that freedom: the &amp;lt;strong&amp;gt;freedom to conform&amp;lt;/strong&amp;gt;. But many projects (clients) have certain demands, or expectations.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;On those projects, you often cannot conform. And even if you can conform, you often should not conform; not every case is best served with The Drupal Way[tm].&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ask yourself these questions:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Do I want to conform to The Drupal Way?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Can I conform to The Way of The Drupal?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Will my client allow us to conform to the Drupal Way?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If the answer to any of these is &amp;lt;em&amp;gt;yes&amp;lt;/em&amp;gt;, the next step would be to find out, what That Drupal Way[tm] is; that Drupal Way often needs to be found out and described to in great detail.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If the answer to any of these questions is &amp;lt;em&amp;gt;no, certainly not&amp;lt;/em&amp;gt;, you might want to not use Drupal at all. Or, more correct, not use a CMS at all, but learn a real framework.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Toggle your webservers&amp;#39; production environment by using symlinks</title>
   <link href="https://berk.es/2010/09/08/toggle-your-webservers-production-environment-by-using-symlinks/"/>
   <updated>2010-09-08T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/09/08/toggle-your-webservers-production-environment-by-using-symlinks/</id>
   <content type="html">&amp;lt;p&amp;gt;I love simple. If I can do something in a simple way, that, is the way I will do it. Releasing new features, updates or upgrades of sites can be a PIASS, even when you use the whole shebang of version-control systems, release-management environments and what more.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I think it can be done very simple (okay, I don&amp;#39;t run bank-applications that are critical to the world economy, but still) with symlinks.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Say, I have a tool called &amp;amp;quot;foo&amp;amp;quot; that gets a critical update. I know most of you would just fire up ws_ftp (the more savvy would prolly fire up filezilla) and then overwrite the old code with the new code and be done with it.&amp;#xA;That, however, is the kind of &amp;lt;em&amp;gt;simple&amp;lt;/em&amp;gt; that is even too simple for me. It is so extremely error-prone, that I don&amp;#39;t recommend it for anyone. Not even if you have that site that is only visited by three people and an accidental cat, per week.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;What I do, is keep two direcories for my app, under &amp;lt;em&amp;gt;/var/www&amp;lt;/em&amp;gt;: &amp;lt;em&amp;gt;foo_r&amp;lt;/em&amp;gt; and &amp;lt;em&amp;gt;foo_l&amp;lt;/em&amp;gt;. The _r and _l stand for left and right. You could also call them one and two, or tinky and winky.&amp;#xA;One symlink points to them: &amp;lt;em&amp;gt;foo&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;  ber@luscious:/var/www$ ls -ahl&amp;#xA;  lrwxrwxrwx  1 www-data www-data 12 2010-09-08 19:31 foo -&amp;amp;gt; foo_l&amp;#xA;  drwxr-xr-x  6 www-data www-data 4,0K 2010-07-20 17:30 foo_l&amp;#xA;  drwxr-xr-x  6 www-data www-data 4,0K 2010-09-08 19:29 foo_r&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;My vhost (&amp;lt;em&amp;gt;/etc/apache2/sites-enabled/foo&amp;lt;/em&amp;gt;) points to &amp;lt;em&amp;gt;foo&amp;lt;/em&amp;gt;:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;  &amp;amp;lt;VirtualHost *:80&amp;amp;gt;&amp;#xA;ServerAdmin webmaster@foo.com&amp;#xA;    ServerName foo.com&amp;#xA;DocumentRoot /var/www/foo&amp;#xA;    &amp;amp;lt;Directory /var/www/foo&amp;amp;gt;&amp;#xA;     AllowOverride all&amp;#xA;     Options -MultiViews&amp;#xA;    &amp;amp;lt;/Directory&amp;amp;gt;&amp;#xA;  &amp;amp;lt;/VirtualHost&amp;amp;gt;&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And all you have to do is:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ol&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;update the code in the folder that is &amp;lt;em&amp;gt;not&amp;lt;/em&amp;gt; symlinked to: foo_r, in the above example.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;optionally test that code. (but you had your tests done on the test-environment, not?)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;switch the symlink: &amp;lt;code&amp;gt;rm /var/www/foo &amp;amp;amp;&amp;amp;amp; ln -s /var/www/foo_r /var/www/foo&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ol&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This way, you solve many problems, without brining in heavy shots, such as capistrano, or whatever-release-tool.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;During overwriting the code with new code, your users could (and will) hit a situation where half of the code is new, and the other half is old.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;If you work with a revision-controlsystem, you can solve merge conflicts before people get hit by them.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;You can test-drive your unreleased code, by introducing a vhost that uses the foo_r (or the other one, at least the one unused at that moment) as documentRoot.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;If you have your code spread over multiple servers, you can distribute it first, then switch the simlinks on all the servers at once, instead of waiting for code to be distributed and having a period during which parts of the balanced servers serve old code and parts serve the new stuff.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;This is /so/ simple, that it can be integrated in about every script and adminstrative frontend.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Uitgevers: maak van mij geen boekenpiraat</title>
   <link href="https://berk.es/2010/08/11/uitgevers-maak-van-mij-geen-boekenpiraat/"/>
   <updated>2010-08-11T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/08/11/uitgevers-maak-van-mij-geen-boekenpiraat/</id>
   <content type="html">&amp;lt;p&amp;gt;De Nederlandse e-book gaat hard de mp3 achterna. De volgende industrie, dit keer regionaal, staat op het punt kapotgepiraat te worden. Jammer? Nee, eigen schuld. Vandaag in de de Pers een &amp;lt;a href=&amp;#34;http://www.depers.nl/cultuur/500993/Waar-blijft-ons-digitale-boek.html&amp;#34;&amp;gt;zuur stuk over het uitblijven van de e-book&amp;lt;/a&amp;gt;. Niet de e-book reader, die is overal verkrijgbaar, maar het boek zélf. Sinds een kleine week lees ik mijn boeken ook digitaal, heerlijk. Ik ben vooral nog aan het hamsteren. Maar van de ruim 160 boeken, die ik er nu op heb staan, zijn er maar ongeveer 60 legaal. Het merendeel uit &amp;lt;a href=&amp;#34;http://mashable.com/2007/11/12/public-domain-ebook-sources/&amp;#34;&amp;gt;diverse&amp;lt;/a&amp;gt; public-domain, of &amp;lt;a href=&amp;#34;http://openboox.com/tags/creative_commons&amp;#34;&amp;gt;creative commons&amp;lt;/a&amp;gt; bronnen, enkele gekocht. Die gekochte versies zijn allemaal dure it-boeken, van mijn &amp;lt;a href=&amp;#34;http://bler.webschuur.com/wishlist&amp;#34;&amp;gt;wishlist&amp;lt;/a&amp;gt;. De rest is dus illegaal. Illegaal in de zin van: ik heb ook de papieren versie niet in de kast staan, ben niet van zins een legale versie te gaan aanschaffen en ik voel me geenszins shuldig over het &amp;amp;quot;stelen&amp;amp;quot; van het brood van de Schrijvers. Nou goed, een beetje schuldig, anders schreef ik dit stuk natuurlijk niet.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Dat zit zo: ik wíl graag ebooks kopen. Maar als het aanbod er niet is, kán ik weinig kopen. En Koop ik dus niet. Natuurlijk mag stelen dan nog altijd niet. Als de bakker zegt geen bruinbrood meer te hebben,heb ik pech. Ook al zie ik in de bakkerij nog rijen bruinbrood staan. Je kent het wel, op zaterdag staat de halve bakkerij vol met mensen die brood bestélden, mensen uit een generatie dat je vrijdag nog de bakker belde om op zaterdag een garantie op bruinbrood te hebben. Maar voor jou is het &amp;amp;quot;op&amp;amp;quot;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik mag dan natuurlijk niet gewoon via de achterdeur de bakkerij binnenlopen en dan zelf mijn brood pakken. Als de bakker zegt dat het bruinbrood op is, heb ik me daar maar naar te voegen, dat is zijn beslissing. En zijn goed recht om mij met lege handen de winkel uit te sturen.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Zonder toestemming downloaden, is echter iets heel anders dan stelen. Het duidelijkste argument hiervoor, is dat als ik dat brood bij de bakker steelt, hij een brood minder heeft (en mogelijk dus mevrouw Pollux, die &amp;amp;quot;besteld heeft&amp;amp;quot; moet teleurstellen), maar als ik een ebook download, de uitgever nog altijd precies evenveel boeken heeft. Nogmaals, ik wíl graag kopen. Voor zaken waar ik waarde aan hegt, leg ik graag geld neer. Zo kocht ik in 2009 voor bijna €600 aan muziek, en films online en offline. En heb ik een abonnement op het NRC, ondanks dat ik iedere ochtend in de trein allerhande gratis krantjes doorspit. Om verschillende redenen wil ik graag betalen, maar dan moet mij dat wél mogelijk gemaakt worden.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Sterker nog, dan moet het niet alleen mogelijk zijn, het moet ook nog makkelijk zijn. Liefst makkelijkér dan zonder betaling downloaden. Dat is het allesbehalve. Een &amp;lt;a href=&amp;#34;http://www.google.com/search?hl=en&amp;amp;amp;safe=off&amp;amp;amp;q=martin&#43;bril&#43;e-book&amp;amp;amp;aq=f&amp;amp;amp;aqi=&amp;amp;amp;aql=&amp;amp;amp;oq=&amp;amp;amp;gs_rfai=&amp;#34;&amp;gt;snelle google&amp;lt;/a&amp;gt;, doorklikken naar één van de vele downloads en klaar. Veel makkelijker dan tussen &amp;lt;a href=&amp;#34;http://www.google.com/search?hl=en&amp;amp;amp;safe=off&amp;amp;amp;q=Martin&#43;Bril&#43;e-book&amp;amp;amp;aq=f&amp;amp;amp;aqi=&amp;amp;amp;aql=&amp;amp;amp;oq=&amp;amp;amp;gs_rfai=&amp;#34;&amp;gt;lijsten met papieren boeken&amp;lt;/a&amp;gt; zoeken naar de digitale versie. Dan allerlei (op bol.com negen, om precies te zijn, waaronder de altijd-leuke &amp;amp;quot;zoek de random-reader voor ideal tussen de rommel in de rommella&amp;amp;quot;) betaalstappen doorlopen en, tien euro armer, datzelfde boek op mijn reader te hebben staan.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Ik ga niet én betalen én moeilijker doen én me neerleggen bij een beperkt aanbod. Dan zoek ik de &amp;lt;a href=&amp;#34;http://thepiratebay.org/torrent/5245229/Stieg_Larsson_-_The_Millennium_Trilogy_(text__english)&amp;#34;&amp;gt;makkelijke weg&amp;lt;/a&amp;gt;. Die toevallig ook nog eens goedkoper, en makkelijker is. En als ik daar eenmaal aan gewend ben, voert de uitgever een achterhoedegevecht, bij voorbaat verloren, net als die vermaledijde muziekindustrie, die -tig jaar later geen raad meer weet. Daar waar Dirk Knops in de Pers concludeert dat hij zijn ebookreader dan maar laat liggen, vrees ik dat met mij, velen, hun ebooks dan maar illegaal gaan uitwisselen. Schiet op, geef me dat makkelijke, goedkope aanbod. Anders ben ik voorgoed een boekenpiraat.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>The problem of Drupals exponential complexity</title>
   <link href="https://berk.es/2010/06/23/the-problem-of-drupals-exponential-complexity/"/>
   <updated>2010-06-23T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/06/23/the-problem-of-drupals-exponential-complexity/</id>
   <content type="html">&amp;lt;p&amp;gt;Over the last days, I helped a client with some bugs in a really complex Drupal site. The site is that complex, because clients &amp;amp;quot;needs&amp;amp;quot; and &amp;amp;quot;wishes&amp;amp;quot; were to be met. So gradually more and more modules were stacked onto this Drupal. Resulting in a site that no-one can really grasp. At all. Now, if modules in Drupal were entirely self-contained and very loosely coupled, something I &amp;lt;a href=&amp;#34;/publications/blogs/2010-06-10-design_principles_for_creating_good_classes_let_you_write_good_drupal_modules&amp;#34;&amp;gt;consider good practice&amp;lt;/a&amp;gt;, this would be of little problem.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The issue, however, lies at the conceptual side, not so much the technical side. Technically such systems suffer from what is called &amp;lt;a href=&amp;#34;http://en.wikipedia.org/wiki/Programming_Complexity&amp;#34;&amp;gt;Exponential Complexit&amp;lt;/a&amp;gt; _For every feature (module) added, the overall complexity increases exponential_Hence the amount of breaking features, bugs and regressions will grow exponentially too. For every feature introduced in your site, several new modules are required. For every new module, the complexity can grow N times. Let us say 5 times: an eaverage module contains about 5 hooks and overrides. A Drupal-site with 10 modules might suffer from 6 bugs; big change you won&amp;#39;t see any of them become a problem on your project. A Drupalsite with 12 modules would then suffer from 150 bugs, part of which &amp;lt;em&amp;gt;will&amp;lt;/em&amp;gt; become a problem at some time. The solution can be sought on the technical side, but frankly, I don&amp;#39;t believe there is a holy grail. A system built from self-contained, loosely coupled entities, will, typically, suffer &amp;lt;a href=&amp;#34;http://www.cs.umd.edu/projects/SoftEng/ESEG/papers/82.78.pdf&amp;#34;&amp;gt;far less bugs and related problems then tightly coupled entities&amp;lt;/a&amp;gt;In web-development you will see that e.g. a project in Django, due to its loosely coupled design will suffer from a lot less &amp;amp;quot;exponential complexity&amp;amp;quot;: if there is a bug in the blogs, that is where the bug is. &amp;amp;gt; A key advantage of such an approach is that components are loosely coupled. That is, each distinct piece of a Django-powered Web application has a single key purpose and can be changed independently without affecting the other pieces. For example, a developer can change the URL for a given part of the application without affecting the underlying implementation. A designer can change a page’s HTML without having to touch the Python code that renders it. A database administrator can rename a database table and specify the change in a single place, rather than having to search and replace through a dozen files.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The bug will not travel trough the entire site and pop-up in random other places. Drupals design philosophy is exactly the opposite: it is entirely horizontal. Due to this horizontal design bugs can travel troughout the entire project. When you introduce a bug in the messaging system and pow! all mail stops working: maybe (in the case of this clients projecte, that was true) the whole cron stops working: search indexes, sessions, garbage collection etceteras no longer work. One small bug, a misconfiguration, caused a PHP error that could have brought down the entire site in due time.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;One bug caused at least 7 things to break. These 7 things would cause again X new problems in due time. How to fix this? In Drupal, the only way to fix this is to use as few modules as possible. And even then, to select these modules on their &amp;amp;quot;loosely coupled-ness&amp;amp;quot;. So avoid modules that depend on certain Views configurations. Avoid modules that go sit inbetween all your mail transports, avoid modules that depend on other modules. In practice that would mean: just avoid all modules alltogether :). Not very practical. Again, this comes down to common sense: at the very least, avoid Drupal-projects that are so complex that no-one understands them. If you don&amp;#39;t understand the messaging-system-modules, then don&amp;#39;t use them. Look for an alternative. Even outside of Drupal. Choose the simplest solution. You can always let your site grow over time: add features when they are really needed. That way, at least, you will have to deal with the exponetial complexity only one-step a time: even if those steps will become bigger while your site grows, they are still smaller then the giant leap at a single delivery of a huge site.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Grid Systems, Drupal and Semantics (why CSS frameworks are not that bad in Drupal)</title>
   <link href="https://berk.es/2010/06/18/grid-systems-drupal-and-semantics-why-css-frameworks-are-not-that-bad-in-drupal/"/>
   <updated>2010-06-18T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/06/18/grid-systems-drupal-and-semantics-why-css-frameworks-are-not-that-bad-in-drupal/</id>
   <content type="html">&amp;lt;p&amp;gt;It seems Grid systems, or CSS frameworks, are being picked up by the Drupal themer community. I think this is a good thing. Some think it is a bad thing. So let us have a look at the downsides and upsides of grid systems in Drupal.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;grid-systems-force-you-to-change-your-html-that-is-bad&amp;#34;&amp;gt;Grid Systems force you to change your HTML, that is bad.&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This is wrong, for three reasons.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ol&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;It might be bad, if it were true. But not all grid systems, or their implementations require changes to the HTML, in theory. I love &amp;lt;a href=&amp;#34;http://sass-lang.com/&amp;#34;&amp;gt;SASS&amp;lt;/a&amp;gt; and its tool-kit &amp;lt;a href=&amp;#34;http://compass-style.org/docs/&amp;#34;&amp;gt;Compass&amp;lt;/a&amp;gt;. Compass pulls in grid systems such as &amp;lt;a href=&amp;#34;http://www.blueprintcss.org/&amp;#34;&amp;gt;Blueprint&amp;lt;/a&amp;gt; (native) or &amp;lt;a href=&amp;#34;http://960.gs&amp;#34;&amp;gt;960.gs&amp;lt;/a&amp;gt; (plugin), in such a way, that the CSS is the only thing you rewrite.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ol&amp;gt;&amp;#xA;&amp;lt;blockquote&amp;gt;... you can apply battle-tested styles from frameworks like Blueprint to your style sheets instead of your markup. &amp;lt;/blockquote&amp;gt;&amp;#xA;&amp;lt;ol&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The second reason, is that changing your HTML might not be bad at all. More on this in the next argument &amp;amp;quot;semantic by the way. But, summarized: only if you can afford being a purist, is this an argument. In all other cases, there is nothing wrong with changing and moving some HTML around. Off course, only to some extend (see Semantics). But GS usually require only minimal changes to your markup.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The third reason lies with Drupal. In order to control the exact outputted HTML, you need not only a huge amount of Drupal theming experience, you need patience. And a gigantic maintainance budget.&amp;#xA;Drupal works with overrides: it will output source X by default, untill you decide you want to change it into Y. Now, for a &amp;lt;a href=&amp;#34;http://api.drupal.org/api/function/theme_item_list&amp;#34;&amp;gt;theme_item_list&amp;lt;/a&amp;gt;, that is not too hard. But inherently complex functions such as theme_table, it is. And these themable &amp;amp;quot;things&amp;amp;quot; are themed globally: if you change the item-list-generator, all your lists throughout entire Drupal, will be changed. Not necessarily bad, but it takes a large part of full control of your source away from you.&amp;#xA;Drupal also works with nested theme-calls. Theme-page calls theme_foo, theme_foo calls theme_bar, calls theme_item-list. Imagine hunting down that one item-list where you insist on having a .first and .last class, or a .horizontal-list, as required by your CSS framework.&amp;#xA;And lastly, Drupal is modular and flexible. Depending on your enabled modules, configuration, context or situation the source will change drastically. A logged in user may see different source and elements then an anonymous visitor. An admin with certain quick-edit-module might get popup-links when hovering certain elements. And dynamic modules, such as Views or CCK allow you to configure not only the data, but also the way the data is shown. These are all examples of modules that alter your source drastically. Again: full control of what is outputted is nearly impossible. So Grid Systems in Drupal are hard to achieve and require a lot of work, especially in details such as smaller elements on your site. But it is not that, when Drupal requires a lot of changes for minor changes in source, the concept of &amp;amp;quot;making minor changes to source to force certain display&amp;amp;quot; is wrong all-together.&amp;#xA;In Drupal it may not be practical, true. But the concept itself is not that wrong.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ol&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;semantics&amp;#34;&amp;gt;Semantics.&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Changing the source may be bad because of semantics. Purists say that needing to change your markup (the meaning of the information) in order to change the display (the visualisation of the information) is wrong and was never the idea of HTML.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I agree.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;However, purists may not have to deal with Internet Explorer in their work (Unfortunately, I do). And purists will steer away from Drupal. Or should.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Taken from a random &amp;lt;a href=&amp;#34;http://drupal.nl&amp;#34;&amp;gt;Drupal site&amp;lt;/a&amp;gt;. Whitespace and identation deliberately left the way it is generated.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;                         &amp;#x9;&amp;#x9;&amp;amp;lt;div class=&amp;amp;quot;view-content&amp;amp;quot;&amp;amp;gt;&amp;#xA;                &amp;#x9;&amp;#x9;&amp;amp;lt;div class=&amp;amp;quot;views-row-1 views-row-odd views-row-first views-row-last&amp;amp;quot;&amp;amp;gt;&amp;#xA;  &amp;#xA;&amp;amp;lt;div class=&amp;amp;quot;views-field-body&amp;amp;quot;&amp;amp;gt;&amp;#xA;            &amp;#x9;&amp;amp;lt;div class=&amp;amp;quot;field-content&amp;amp;quot;&amp;amp;gt;&amp;amp;lt;p style=&amp;amp;quot;font-size:1.2em&amp;amp;quot;&amp;amp;gt;&amp;amp;lt;img src=&amp;amp;quot;sites/default/files/images/drupal.png&amp;amp;quot; style=&amp;amp;quot;float:right; margin-left:20px&amp;amp;quot; /&amp;amp;gt;Met Drupal: maken en beheren van simpele tot en met complexe websites. Dit is de site van de Belgische en Nederlandse Drupal-community. Lees hier over Drupal&amp;#39;s &amp;amp;lt;a href=&amp;amp;quot;over-drupal&amp;amp;quot; rel=&amp;amp;quot;nofollow&amp;amp;quot;&amp;amp;gt;sterke punten&amp;amp;lt;/a&amp;amp;gt;.&amp;amp;lt;a href=&amp;amp;quot;node/1819&amp;amp;quot; rel=&amp;amp;quot;nofollow&amp;amp;quot;&amp;amp;gt;&amp;amp;lt;br /&amp;amp;gt;&amp;amp;lt;/a&amp;amp;gt;&amp;amp;lt;/p&amp;amp;gt;&amp;#xA;&amp;#x9;&amp;amp;lt;/div&amp;amp;gt;&amp;#xA;&amp;amp;lt;/div&amp;amp;gt;&amp;#xA;&amp;amp;lt;a href=&amp;amp;quot;/sites/default/files/drupal-6.17.tar.gz&amp;amp;quot;&amp;amp;gt;&amp;amp;lt;img src=&amp;amp;quot;/sites/all/themes/lagelanden/images/download-drupal-btn.png&amp;amp;quot; alt=&amp;amp;quot;&amp;amp;quot; title=&amp;amp;quot;&amp;amp;quot; width=&amp;amp;quot;215&amp;amp;quot; height=&amp;amp;quot;32&amp;amp;quot; /&amp;amp;gt;&amp;amp;lt;/a&amp;amp;gt;  &amp;amp;lt;/div&amp;amp;gt;&amp;#xA;            &amp;#x9;&amp;amp;lt;/div&amp;amp;gt;&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Font sizes? Inline CSS? Field-content? empty alt tags? No alt tags at all? 4 nested Divs for a single paragraph? rel nofollow on something that clearly should be followed? Empty A-tags? How people, who work daily with a system that outputs such sources by default, dare mention the argument &amp;amp;quot;semantics&amp;amp;quot;, is beyond me.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This tagsoup in the example, is mostly the fault of views, which, in practice, adds gigantic loads of meaningless markup.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;A class like &amp;amp;quot;views-row-1 views-row-odd views-row-first views-row-last&amp;amp;quot; is debatable. Some say that these are correct semantics. Maybe. But even if they are, the way some classes are embedded and some are chained makes no meaningful sense.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Why is .view-content outside of views-row and its subclasses? Why are these subclasses, but us views-field-body not a subclass of field-content? Why do we need these in the first place?&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The answer is technical: because that markup it is dynamically generated with hilghly flexible and complex code, and we still want to provide enough handgrips for desingers to attach their CSS to.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Certainly not semantic. You cannot convince me that the subclasses  &amp;lt;em&amp;gt;views-row-odd views-row-first views-row-last&amp;lt;/em&amp;gt; make any semantic sense. Last and first together? It is the only item in the list, so it, technically is correct that it is both the last and the first item. And since it is the first, it is also the odd item. But semantic, meaning. Certainly not more then some additional grid-two-column class. Odd, even classes are just as semantic as classes used to identify columns in a grid.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Now, I will agree with you that the difference between:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&amp;amp;lt;p class=&amp;amp;quot;paragraph teaser&amp;amp;quot;&amp;amp;gt;&amp;#xA; &amp;amp;lt;img src=&amp;amp;quot;sites/default/files/images/drupal.png&amp;amp;quot; alt=&amp;amp;quot;Drupal screenshot showing the coolness of Drupal&amp;amp;quot; /&amp;amp;gt;&amp;#xA; Met Drupal: .... Lees hier over Drupal&amp;#39;s&amp;#xA; &amp;amp;lt;a href=&amp;amp;quot;over-drupal&amp;amp;quot;&amp;amp;gt;sterke punten&amp;amp;lt;/a&amp;amp;gt;&amp;#xA;&amp;amp;lt;/p&amp;amp;gt;&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;and:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&amp;amp;lt;p class=&amp;amp;quot;paragraph teaser grid-left&amp;amp;quot;&amp;amp;gt;&amp;#xA; &amp;amp;lt;img src=&amp;amp;quot;sites/default/files/images/drupal.png&amp;amp;quot; alt=&amp;amp;quot;Drupal screenshot showing the coolness of Drupal&amp;amp;quot; /&amp;amp;gt;&amp;#xA; Met Drupal: .... Lees hier over Drupal&amp;#39;s &amp;#xA; &amp;amp;lt;a href=&amp;amp;quot;over-drupal&amp;amp;quot; class=&amp;amp;quot;inline-button&amp;amp;quot;&amp;amp;gt;sterke punten&amp;amp;lt;/a&amp;amp;gt;&amp;#xA;&amp;amp;lt;/p&amp;amp;gt;&amp;#xA;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;is important. And that the latter is worse then the first. But Drupal&amp;#39;s markup does not even get close to my handcoded and cleaned example. Adding a .grid-left to the tagsoup from the example output of views makes absolutely no semantic difference. At all. Adding it to the corrected, and cleaned examples below does make a difference.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;My random example may be a particularly bad example. But before you comment with urls to examples that are cleaner, consider the heading-layout. Consider the source for logged in admins. And evaluate the entire source/markup ratio. It will be bad in Drupal. Please prove me wrong. :)&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The other point is that semantics are a little overvalued. Not that I think we should abandon the idea of putting meaning in our HTML and go for the dirty solutions such as table-based layouts. I just say we should be pragmatic.&amp;#xA;Source order, for example.&amp;#xA;Most screen-readers and Braille terminals &amp;amp;quot;look&amp;amp;quot; at CSS. That&amp;#39;s because, in practice, most sites change &amp;amp;quot;meaning&amp;amp;quot; by chaning the layout. A form-label that stands above the form-element (like, by default in Drupal), even if done with CSS, will force the Braille terminal to insert a linebreak&amp;amp;quot;: users must take an action to enter the form-field. Changing the CSS so that a label is not display:block, but display:inline will make your forms a lot more accessible.&amp;#xA;Being a purist gets you only halfway in this: you will still need to look at the entire picture: javascript, CSS and HTML.&amp;#xA;No (sane) web-indexer will ignore javascript entirely. The google bot may not execute all javascript, but will certainly evaluate it to see if the source is altered trough these scripts.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Good semantics are not just putting a navigation below the content and providing a &amp;amp;quot;skip to navigation&amp;amp;quot;-link. Good semantics are about the entire picture. From source order, via minimalistic source (four nested divs around a single paragraph, for goodness sake!), via correct weight of elements (heading layout etcetera&amp;#39;s), untill meaning-altering javascript or CSS. In practical Drupal this is as good as impossible; you can develop and design a minimalistic Drupal, but those are not the sites that stand for Drupal examples. It will, most probably, be considered an ugly, boring or not-very-representative site. Views is a de-facto standard. Zen a theme for theme-developers probably has the worst source/content ratio of all themes. And it is the most used theme.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Good semantics is about the big picture. And no Drupalsite will manage be semantically correct in that big picture. Not without a huge amount of work, that leaves you with a maintainance nightmare, overrides that, in lines of code, will be far larger then their originals. And a content- and editors- handbook that will make all editors depressed.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h2 id=&amp;#34;are-grid-systems-bad&amp;#34;&amp;gt;Are grid systems bad?&amp;lt;/h2&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In theory: yes. In practice: hardly; but only in a place where you control your source and therefore can afford to be entirely semantically correct. They require minimal changes to your source. Adding style to a place that should only contain meaning. Adding a class=&amp;amp;quot;horizontal-list&amp;amp;quot; to an UL, in order to make it horizontal is bad practice. &amp;lt;em&amp;gt;In a place where the rest of the source is perfect&amp;lt;/em&amp;gt;. But in a tagsoup like that of  Drupal, a single class=&amp;amp;quot;horizontal-list&amp;amp;quot; will not make anything worse. Provided you can add that class in the first place, without large code changes (that need to be maintained).&amp;#xA;And having to re-order some HTML, but keeping it valid, is always a lot better then getting into ugly IE6 CSS hacks, that not only make your CSS invalid, but often add huge amount of extra CSS complexity to your designs.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Any Drupal themer, who does not want a CSS framework, because it does not use HTML like it should be used, is acting silly. Drupal, by default, renders HTML that is so far from semantically correct, that the additional downside of a few extra non-semantic classes, or the downside of a few extra not-so-well-source-ordered blocks does exactly nothing: and certainly not make your source less correct. If you really care for semantics, start with the low-hanging fruit and make Drupal, or its contributions, a little more semantically correct.&amp;#xA;A Drupal themer who says that using a grid framework is not very practical, because Drupal is far too dynamic and full control of the outputted HTML, is more correct.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Mosterd na de maaltijd</title>
   <link href="https://berk.es/2010/06/14/mosterd-na-de-maaltijd/"/>
   <updated>2010-06-14T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/06/14/mosterd-na-de-maaltijd/</id>
   <content type="html">&amp;lt;p&amp;gt;Ook fijn: Nú opeens komen kranten (NRC next vandaag: prima stuk over verschillen tussen PVV en VVD) met diep inhoudelijke, politieke analyse. Dat moet toch juist tijdens de campagne: nu hebben één komma zoveel ongeïnformeerde mensen op de PVV (of juist de VVD) gestemd met allerlei &amp;amp;quot;Wij van WC-eend&amp;amp;quot;-adviesen en argumenten. Als ze uit de krant, TV of RTL-boulevard hadden vernomen hoe PVV en VVD inhoudelijk mijlenver van elkaar afliggen: dán had je écht een keuze gemaakt.  -- Jammer &amp;amp;quot;De media&amp;amp;quot; is missch. nog wel het meeste debet aan het failliet van Neerlandsch Progressief en Oopen Vrijdenken.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Design principles for creating Good Classes let you write Good Drupal Modules.</title>
   <link href="https://berk.es/2010/06/10/design-principles-for-creating-good-classes-let-you-write-good-drupal-modules/"/>
   <updated>2010-06-10T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/06/10/design-principles-for-creating-good-classes-let-you-write-good-drupal-modules/</id>
   <content type="html">&amp;lt;p&amp;gt;Drupal is not object oriënted (OO). No really, &amp;lt;a href=&amp;#34;http://drupal.org/node/547518&amp;#34;&amp;gt;it is not&amp;lt;/a&amp;gt;! It merely borrows some design principles from OO, and uses some design concepts (such as the &amp;lt;a href=&amp;#34;http://en.wikipedia.org/wiki/Observer_pattern&amp;#34;&amp;gt;Observer Pattern&amp;lt;/a&amp;gt;, or hooks, in Drupal) from OO.&amp;#xA;Though many Module developers actually use another design principle, that of Classes. When creating a module, one can borrow almost all ideas from the (good) design of classes, to create a good design for a module.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Maybe you think: &amp;amp;quot;Why should I design my module&amp;amp;quot;? (When I say design, I do not mean graphical, or UI design, but technical design, often called software architecture). If you ask yourself that, then get back to your developed modules. They may be so small, and welldesigned that you are a natural talent. But more often, you will let them grow, maintain them, add features and think everytime &amp;amp;quot;I should really rewrite this module from scratch&amp;amp;quot;. The problem is&amp;#39;t that the ad-hoc is a bad way of coding. But that good abstraction, good design, offers many benefits. It not only make your projects easier to run, it also makes your sites a lot more stable, predictable and -overall- better to use maintain and extend.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Good classes, and hence good modules, have several benefits:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;You can hide implementation details&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Changes don&amp;#39;t affect the whole Drupal environment&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;The Drupal environment is more obviously correct&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;You don&amp;#39;t have to pass data all over your entire Drupal&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;You are able to work with real-world (and website) entities rather then with low-level implementation structures.&amp;#xA;—Taken, and adapted from &amp;lt;a href=&amp;#34;http://cc2e.com&amp;#34;&amp;gt;Code Complete, second edition&amp;lt;/a&amp;gt; Working Classes, p127-128&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But first let me answer the question whether modules can be compared to classes, at all.&amp;#xA;The Drupal handbook, &amp;lt;a href=&amp;#34;http://drupal.org/node/292&amp;#34;&amp;gt;Introduction to Drupal modules&amp;lt;/a&amp;gt; writes:&amp;lt;/p&amp;gt;&amp;#xA;  &amp;lt;blockquote&amp;gt;...[a module] is more of &amp;lt;em&amp;gt;a concept that encourages good design principles&amp;lt;/em&amp;gt;. Modularity also suits the open-source development model, because it allows a number of developers to contribute functionality to Drupal &amp;lt;em&amp;gt;without risk of interference&amp;lt;/em&amp;gt;.&amp;lt;/blockquote&amp;gt; (emphasis added)&amp;#xA;&amp;lt;p&amp;gt;Those are reasons why classes were created in the first place: as a good design principle, to lower the risk of interference. Or, to avoid changes infecting your entire Drupal site.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But, more important, is that modules in Drupal are supposed to be highly focused: do one task and do that well.&amp;#xA;The general idea in Drupal, is that not a forum module offers all features phpBB offers, but that a phpBB-alike forum is achieved by pulling together many modules: often one module for each feature you wish to introduce. This mathces that other great benefit of Classes: &amp;lt;em&amp;gt;You are able to work with real-world (and website) entities rather then with low-level implementation structures.&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The &amp;lt;a href=&amp;#34;http://drupalmodules.com/module/user-display-api&amp;#34;&amp;gt;User Display api&amp;lt;/a&amp;gt;, offers a consistent, focused programming interface to deal with statii of users.  When I talk of interfaces, I mean programming interfaces, not user interfaces (UIs). Interfaces in Drupal are hooks, theme-functions, database-api functions, and public functions.&amp;#xA;Eventhough Drupal, or actually PHP, has no proper support for setting the scope of data and methods (functions), the Drupal convention is to prefix private functions with an _underscore().&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So, instead of a forum module that has several features to control the display of the online status of users, Drupal encourages the use of several modules, on top of Drupal core, to introduce such features &amp;lt;em&amp;gt;by themselves&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The answer therefore is: &amp;amp;quot;no, modules are not really classes&amp;amp;quot;, but rather &amp;amp;quot;Good Drupal modules follow a lot of design principles of classes in OO&amp;amp;quot;. You cn approach a module as you would approach a class. But you cannot use all the concepts from classes in a Drupal module.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I took the liberty to modify McConnells, the author of Code Complete, checklist from the book Code Complete and adjusted it to suit module development. He writes a checklist that you can use to see if your classes and their use is Good. Another book to read on this is &amp;lt;a href=&amp;#34;http://c2.com/cgi/wiki?DesignPatternsBook&amp;#34;&amp;gt;Design Patterns&amp;lt;/a&amp;gt; by the Band of Four. The latter is slightly more academic, but still great if you want to become a better Drupal developer.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I adjusted the checklist from Code Complete, so it becomes a checklist that shows you if your module is Good. It makes a great checklist for writing awesome modules:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;abstract-data-types&amp;#34;&amp;gt;Abstract Data Types&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Have you thought of the modules in your drupal implementation as Abstract Data Types and evaluated their interfaces from that point of view? (Where, again, interfaces are programming interfaces, not UI&amp;#39;s)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;abstraction&amp;#34;&amp;gt;Abstraction&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Does the module have a central purpose?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Is the module well named, and does its name describe its central purpose?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Does the modules&amp;#39;s interface present a consistent abstraction?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Does the modules&amp;#39;s interface make obvious how you should use the class?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Is the modules&amp;#39;s interface abstract enough that you don&amp;#39;t have to think about how its services are implemented? Can you treat the module as a black box?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Are the modules&amp;#39;s services complete enough that other modules don&amp;#39;t have to meddle with its internal data?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Has unrelated information, user interfaces and functionality been moved out of the module?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Have you thought about subdividing the module into smaller modules, and have you subdivided it as much as you can?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Are you preserving the integrity of the modules&amp;#39;s interface as you modify the class? (i.e: Can you provide backwards compatibility, without losing the ability to change the code in your module?)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;encapsulation&amp;#34;&amp;gt;Encapsulation&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Does the module minimize accessibility to its internal functions?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Does the modules avoid exposing data, such as global or accessible variables?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Does the module hide its implementation details from other modules as much as the used concepts (hooks, theme, etc.) permit?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Does the module avoid making assumptions about its users (the other modules using this module, not users visting the site), including its derived modules (modules depending on this module)?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Is the module independent of other modules? Is it loosely coupled? (i.e a form_alter that expects forms to be in an exact state are tightly coupled, a nodeapi inserting a new piece of data into a node is loosely coupled.)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;inheritance&amp;#34;&amp;gt;Inheritance&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Is inheritance used only to model &amp;amp;quot;is a&amp;amp;quot; relationships? (i.e. dog.module, depends on mammal.module, but never on user.module)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Does the class documentation describe the inheritance strategy? (i.e. when Module Cat depends on module feline, does it tell this to the users?)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Do derived modules avoid &amp;amp;quot;overriding&amp;amp;quot; non overridable routines? In PHP and Drupal only achievable by well-commented code.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Are inheritance trees fairly shallow?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;h3 id=&amp;#34;other-implementation-issues&amp;#34;&amp;gt;Other Implementation Issues&amp;lt;/h3&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Does the module contain about seven data members (public functions) or fewer?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Does the module minimize direct and indirect routine (function) calls to other modules?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Does the module collaborate with other modules only to the extent absolutely necessary?&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;If you want more in-depth information on these statements, please refer to Code Complete, second edition, Chapter 6. Or leave a comment below so that I can try to explain it in more detail.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;In general, the idea is that all rules of thumb that apply to designing good Classes, are usefull for designing good modules. Keep it small, simple, focused and try to hide as many as possible for others.&amp;#xA;In Drupal that would mean: provide hook_implementations, but keep all the other functions private. That function that iterates over the latest coffee-mugs to extract their avialability in the shop: should never be available to other modules.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;You can prefix private functions with an underscore, such as __coffeemugs_extract_availability(). Or stick them in include-files, and mention in the code-comments that others should keep away from your inc files, at all times!&amp;#xA;Avoid calling functions in include files. Avoid calling any function in any other module, unless it is explicitely advertised as &amp;amp;quot;usable by others&amp;amp;quot;.&amp;#xA;Try to avoid introducing such functions as often as possible, rather create a new hook, which, by its nature, is public.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Keep your module focused. A &amp;lt;em&amp;gt;print_and_pdf_and_mail_for_nodes.module&amp;lt;/em&amp;gt; is a bad module. An &amp;amp;quot;alternative_rendering.module&amp;amp;quot;, with inheriting modules &amp;amp;quot;print.module&amp;amp;quot;, &amp;amp;quot;pdf.module&amp;amp;quot; and mail.module&amp;amp;quot; is far, far better design.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;And go read &amp;lt;a href=&amp;#34;http://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670&amp;#34;&amp;gt;Code Complete&amp;lt;/a&amp;gt;. It will make you a happier Drupal developer.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Why I withdraw my Pledge to have Tagadelic ready for Drupal 7</title>
   <link href="https://berk.es/2010/06/04/why-i-withdraw-my-pledge-to-have-tagadelic-ready-for-drupal-7/"/>
   <updated>2010-06-04T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/06/04/why-i-withdraw-my-pledge-to-have-tagadelic-ready-for-drupal-7/</id>
   <content type="html">&amp;lt;p&amp;gt;Actually, it is very simple: I had a slot in February. And one in May. Both are gone now. But because of the lack of anything stable-ish, I decided to use that slot for diving into some documentation on the proposed (and some submitted) changes to 7. And on getting stuff synched and tagadelic migrated to &amp;lt;a href=&amp;#34;http://github.com/berkes/tagadelic&amp;#34;&amp;gt;git(hub)&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I planned a new slot in May. But again, there was no stable Drupal7 to work against. Off course, Tagadelic is simple, and could probably be migrated against a current unstable Drupal7, then work perfectly on release. But maybe not. And I really don&amp;#39;t have time, nor the will to go shoot at a moving target. Nor do i plan to upgrade my sites to Drupal7 anytime soon: never fix something that aint broken: they run just fine on Drupal5 and Drupal 6. As soon as a client, who uses Tagadelic, hires me to upgrade  a site, will I upgrade it and make it ready for 7. Or when someone steps up with a &amp;lt;em&amp;gt;properly tested&amp;lt;/em&amp;gt;  and &amp;lt;em&amp;gt;clean&amp;lt;/em&amp;gt; patch to migrate to 7, will I commit it, or grant that person commit rights. In fact: you already have these rights: just fork &amp;lt;a href=&amp;#34;http://github.com/berkes/tagadelic&amp;#34;&amp;gt;tagadelic&amp;lt;/a&amp;gt; and upgrade to seven. Then let me know about it, to review the code.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Small but Useful modules: are they worth the pain?</title>
   <link href="https://berk.es/2010/06/03/small-but-useful-modules-are-they-worth-the-pain/"/>
   <updated>2010-06-03T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/06/03/small-but-useful-modules-are-they-worth-the-pain/</id>
   <content type="html">&amp;lt;p&amp;gt;A &amp;lt;a href=&amp;#34;http://blog.merge.nl/2010/06/03/10-small-but-useful-drupal-modules-part-2/&amp;#34;&amp;gt;blogpost on Merge&amp;lt;/a&amp;gt; brought a question back that has haunted me for a while now. **What about all these small modules?**First part of that question is: &amp;lt;strong&amp;gt;How to deal with the many small modules, day-to-day?&amp;lt;/strong&amp;gt;. Quite often, I see sites that drag more then 50 modules along. Most often these are really very simple sites.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;That introduces several problems in itself: humongous effort on upgrade management and maintainance, lot of time spent on selection, gigantic complexity -to a level that debugging and troubleshooting becomes impossible- and last: performance. The latter is -imho- one of the least of problems. With 50&#43; modules, how small they may be, you can be sure of a security release every week, possibly more then one. With proper testing and management, that will mean a couple of hours technical maintainance every week. That is unacceptable for (out of thin air) over threequarters of the Drupalsites. Every time you see a module that does what you want, you should consider the impact of that module on the project as a whole. Not just how many minutes it saves while developing; but also how many hours upgrade pain it may cause.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Second part in this question is: &amp;lt;strong&amp;gt;UNIX has this philosophy with gazillion, tiny, focused and optimised libraries and apps, why is it not a problem there?&amp;lt;/strong&amp;gt;. The answer is probably: managment - and upgrade tools. Drupal has no APT, Gems, VersionTracker or Fink. It has drush that can resolve the minimum of requirements, but hardly more. It has makefiles that provide a good starter for- but are far from- a real package managment tool. The other part of this answer is that UNIX libraries offer no user interface, and that the majority of the tools offer only really low-level user interfaces, most often in the form of configuration options. A small subset offers user interfaces in the form of commandline options. And an even smaller subset offers a real graphical user interface, with options to click, buttons to press and objects to drag. To illustrate: From the 29 packages that help deal with printing (on paper) on my ubuntu machine, only two have a GUI: one to configure and manage printers, the other to print stuff and view the printqueue. Or at least: that is my knowledge, if there are more user interfaces, I do not know of them, nor should I. In Drupal, most modules offer some interaction, add stuff to configuration-pages, offer settings, cases, and so on. In Drupal they not only add technical complexity (dependencies of -, reliance on-, tight coupling with- other modules) they also offer complexity for the user. How often do you not read things like &amp;amp;quot;create a content type, then add a pathauto alias for these nodes, then select the hierarchy from the simple-hierarchy-based-on-aliases module s config interfaces&amp;amp;quot;? I have never read anything like this on ubuntu in order to print an invoice.[1] Small modules are hardly ever librries, they are applications. On UNIX stuff is managable, because probably less then 5% of the apps offer an interface to the user, the rest offers interfaces to software, not to the users.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Third part of the question is: &amp;lt;strong&amp;gt;Are you not better off, just hardcoding some stuff&amp;lt;/strong&amp;gt;? I know that, say, analytics module offers a handy tool to inject analytics code into your pages. But be honoust: is it &amp;lt;em&amp;gt;that&amp;lt;/em&amp;gt; hard to copy-paste it into your page.tpl.php, the page template? Is it really so hard that it is worth the overhead of upgrades, management, complexity and performance? Do you really need a module to add a javascript file to the header? So, small modules are usefull. And may come in handy at times. But most often you will find that they offer more pain on the long run, then they gain you on the short run.&amp;lt;small&amp;gt;[1] Actually, I work with Linux mostly, for over 10 years now, so I have seen the days when piping stuff trough ghostscript conversion filters. via lineprinter-tools into /dev/something devices. But Ubuntu really is of a whole new level :)&amp;lt;/small&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Toegankelijkheid gestest van 10&#43;1 partij-websites.</title>
   <link href="https://berk.es/2010/06/02/toegankelijkheid-gestest-van-101-partij-websites/"/>
   <updated>2010-06-02T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/06/02/toegankelijkheid-gestest-van-101-partij-websites/</id>
   <content type="html">&amp;lt;p&amp;gt;Hoe goed, of slecht voldoen de websites van politieke partijen aan de toegankelijkheidsrichtlijnen? Ik verklap het antwoord alvast: Slecht.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;De piratenpartij is de enige die de basisrichtlijnen op orde heeft. Maar ze heeft enkele details ook niet op orde. Het slechts scoren Groenlinks en SGP, met 8 fouten, waaronder vooral veel belangrijke fouten.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Waarom deze lijst? Websites die voldoen aan deze richtlijnen zijn toegankelijk voor iedereen, bijvoorbeeld mensen met voorleesapparaten of braille-terminals. Mogelijk kan dit lijstje iets laten zien over de aandacht die de partijen besteedden aan toegankelijkheid van hun sites. En dus wat ze nu al zélf doen voor toegankelijkheden van minderheden tot hun informatie. Uitereraard is er veel meer dan een validatie van een site. Dus zoek vooral door naar de standpunten van partijen over toegankelijkheid van minderheden tot informatie (en overheidsdiensten en dergelijke).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;De test is gedaan met een WAI validator. Een simpele check, waar ook de nederlandse stichting Drempelvrij gebruik van maakt.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://verkiezingen.cda.nl/&amp;#34;&amp;gt;CDA&amp;lt;/a&amp;gt; - &amp;lt;a href=&amp;#34;http://www.cynthiasays.com/mynewtester/cynthia.exe?rptmode=2&amp;amp;amp;url1=http%3A%2F%2Fverkiezingen.cda.nl%2F&amp;#34;&amp;gt;Faal, 4 fouten&amp;lt;/a&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://nu.pvda.nl/&amp;#34;&amp;gt;PVDA&amp;lt;/a&amp;gt; - &amp;lt;a href=&amp;#34;http://www.cynthiasays.com/mynewtester/cynthia.exe?rptmode=2&amp;amp;amp;url1=http%3A%2F%2Fnu.pvda.nl%2F&amp;#34;&amp;gt;Faal, 4 fouten. &amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://www.sp.nl/2010/campagnesite/&amp;#34;&amp;gt;SP&amp;lt;/a&amp;gt; - &amp;lt;a href=&amp;#34;http://www.cynthiasays.com/mynewtester/cynthia.exe?rptmode=2&amp;amp;amp;url1=http%3A%2F%2Fwww.sp.nl%2F2010%2Fcampagnesite%2F&amp;#34;&amp;gt;Faal, maar 3 fouten.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://www.vvd.nl/&amp;#34;&amp;gt;VVD&amp;lt;/a&amp;gt; - &amp;lt;a href=&amp;#34;http://www.cynthiasays.com/mynewtester/cynthia.exe?rptmode=2&amp;amp;amp;url1=http%3A%2F%2Fwww.vvd.nl%2F&amp;#34;&amp;gt;Faal, 5 fouten.&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://pvv.nl/&amp;#34;&amp;gt;PVV&amp;lt;/a&amp;gt; - &amp;lt;a href=&amp;#34;http://www.cynthiasays.com/mynewtester/cynthia.exe?rptmode=2&amp;amp;amp;url1=http%3A%2F%2Fpvv.nl%2F&amp;#34;&amp;gt;Faal, 5 fouten. &amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://groenlinks.nl/&amp;#34;&amp;gt;GroenLinks&amp;lt;/a&amp;gt; - &amp;lt;a href=&amp;#34;http://www.cynthiasays.com/mynewtester/cynthia.exe?rptmode=2&amp;amp;amp;url1=http%3A%2F%2Fgroenlinks.nl%2F&amp;#34;&amp;gt;Faal,8 fouten!&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://www.christenunie.nl/nl/&amp;#34;&amp;gt;ChristenUnie&amp;lt;/a&amp;gt; - &amp;lt;a href=&amp;#34;http://www.cynthiasays.com/mynewtester/cynthia.exe?rptmode=2&amp;amp;amp;url1=http%3A%2F%2Fwww.christenunie.nl%2Fnl%2F&amp;#34;&amp;gt;Faal, 5 fouten. &amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://www.d66.nl/&amp;#34;&amp;gt;D66&amp;lt;/a&amp;gt; - &amp;lt;a href=&amp;#34;http://www.cynthiasays.com/mynewtester/cynthia.exe?rptmode=2&amp;amp;amp;url1=http%3A%2F%2Fwww.d66.nl%2F&amp;#34;&amp;gt;Faal, maar 3 fouten. &amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://www.partijvoordedieren.nl/&amp;#34;&amp;gt;Partij voor de Dieren&amp;lt;/a&amp;gt; - &amp;lt;a href=&amp;#34;http://www.cynthiasays.com/mynewtester/cynthia.exe?rptmode=2&amp;amp;amp;url1=http%3A%2F%2Fwww.partijvoordedieren.nl%2F&amp;#34;&amp;gt;Faal, 5 fouten. &amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://www.sgp.nl/&amp;#34;&amp;gt;SGP&amp;lt;/a&amp;gt; - &amp;lt;a href=&amp;#34;http://www.cynthiasays.com/mynewtester/cynthia.exe?rptmode=2&amp;amp;amp;url1=http%3A%2F%2Fwww.sgp.nl%2F&amp;#34;&amp;gt;Faal, 8 fouten!&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;http://piratenpartij.nl/&amp;#34;&amp;gt;Piratenpartij&amp;lt;/a&amp;gt; - &amp;lt;a href=&amp;#34;&amp;#34;&amp;gt;Faal, maar 3 fouten. &amp;lt;strong&amp;gt;Echter de basis is in orde! de gevonden fouten zijn details.&amp;lt;/strong&amp;gt;&amp;lt;/a&amp;gt;&amp;#xA;De kleine partijen heb ik niet meegenomen, met uitzondering van de Piratenpartij. Deze scoret namelijk het beste van alle partijen. Met ook 3 fouten, maar dat zijn de minst kritische fouten. Waar &amp;lt;strong&amp;gt;iedere partij faalt op de meest basale toegankelijksrichtlijnen&amp;lt;/strong&amp;gt; heeft de piratenpartij die op orde. Enkele details missen ook zij. Momentopname, 2 juni, omstreeks 17.00.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;</content>
 </entry>
 
 <entry>
   <title>Status update-je Thailand (geen foto&amp;#39;s, alleen lettertjes)</title>
   <link href="https://berk.es/2010/05/01/status-update-je-thailand-geen-fotos-alleen-lettertjes/"/>
   <updated>2010-05-01T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/05/01/status-update-je-thailand-geen-fotos-alleen-lettertjes/</id>
   <content type="html">&amp;lt;p&amp;gt;We zijn terug uit Isan, het noorden (van Thailand, want daar ben ik op vakantie). Isan is het Thailse platteland. Dat stukje Thailand waar volgens de Lonely planet geen fsck te doen is (wat ook waar is). Best lekker dus. We hebben voor een paarhonderd Euro handgewoven(weven?) zijde ingeslagen, waar Anna &amp;amp;quot;iets&amp;amp;quot; mee wil maken, en de rest van wil verkopen. Verder hebben we wat bier gedronken. Naar een koe gekeken en daarna naar een andere koe gekeken. En we hebben twee rondjes gelopen om het huis, door het dorp. En cola gedronken.&amp;lt;iframe width=&amp;#34;425&amp;#34; height=&amp;#34;350&amp;#34; frameborder=&amp;#34;0&amp;#34; scrolling=&amp;#34;no&amp;#34; marginheight=&amp;#34;0&amp;#34; marginwidth=&amp;#34;0&amp;#34; src=&amp;#34;http://www.openstreetmap.org/export/embed.html?bbox=96.22,12.5,105.45,19.41&amp;amp;layer=mapnik&amp;amp;marker=16.82117,102.57479&amp;#34; style=&amp;#34;border: 1px solid black&amp;#34;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;small&amp;gt;&amp;lt;a href=&amp;#34;http://www.openstreetmap.org/?lat=15.955&amp;amp;amp;lon=100.835&amp;amp;amp;zoom=6&amp;amp;amp;layers=B000FTFTT&amp;amp;amp;mlat=16.82117&amp;amp;amp;mlon=102.57479&amp;#34;&amp;gt;View Larger Map&amp;lt;/a&amp;gt;&amp;lt;/small&amp;gt;Vandaag zijn we weer terug bij Pap, in Chonburi waar we morgen gaan duiken (in het zembad). Dat schijnt leuk te zijn. (Niet in het zwembad, dat is volgens mij niet zo leuk, maar in de zee). Dinsdag gaan we dan voor het echie; duiken in zee. Ik hoop dat ik een vis zie (denk het wel). En dat ik geen garnalen zie (denk van wel), want die vind ik vies. En dat mijn suiker zich een beetje in het gareel houd. Want onder water een boterham eten is wat moeilijk.&amp;lt;iframe width=&amp;#34;425&amp;#34; height=&amp;#34;350&amp;#34; frameborder=&amp;#34;0&amp;#34; scrolling=&amp;#34;no&amp;#34; marginheight=&amp;#34;0&amp;#34; marginwidth=&amp;#34;0&amp;#34; src=&amp;#34;http://www.openstreetmap.org/export/embed.html?bbox=100.276,13.222,101.429,14.096&amp;amp;layer=mapnik&amp;amp;marker=13.38113,100.99962&amp;#34; style=&amp;#34;border: 1px solid black&amp;#34;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;small&amp;gt;&amp;lt;a href=&amp;#34;http://www.openstreetmap.org/?lat=13.659&amp;amp;amp;lon=100.8525&amp;amp;amp;zoom=10&amp;amp;amp;layers=B000FTFTT&amp;amp;amp;mlat=13.38113&amp;amp;amp;mlon=100.99962&amp;#34;&amp;gt;View Larger Map&amp;lt;/a&amp;gt;&amp;lt;/small&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>The first rule of coding for Drupal: never forget about the option to write your own code.</title>
   <link href="https://berk.es/2010/04/19/the-first-rule-of-coding-for-drupal-never-forget-about-the-option-to-write-your-own-code/"/>
   <updated>2010-04-19T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/04/19/the-first-rule-of-coding-for-drupal-never-forget-about-the-option-to-write-your-own-code/</id>
   <content type="html">&amp;lt;p&amp;gt;&amp;lt;a href=&amp;#34;http://www.yelvington.com/content/first-rule-coding-drupal&amp;#34;&amp;gt;yelvington writes&amp;lt;/a&amp;gt;: &amp;amp;quot;The first rule of coding for Drupal: We do not write code for Drupal.&amp;amp;quot; I must say, that after years , I come to the exact opposite conclusion. Right now, I should be writing another webshop (instead I am writing a blog-post, but this article is not about procastrination :)).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I was one of the most outspoken for getting the first CCK, flexinode into core. Not that project itself, per sé, but the concept. I have been a firm believer of &amp;amp;quot;don&amp;#39;t duplicate code&amp;amp;quot;, as such I even introduced the rule in Drupal &amp;lt;a href=&amp;#34;http://drupal.org/node/23789&amp;#34;&amp;gt;Join forces with others&amp;lt;/a&amp;gt;. I consider myself a moderate programmer (speaking some Perl, Bash, rather good Ruby, almost Fluent PHP and rusty C, C&#43;&#43; and even som Java; hardly a hardcore programmer), I am lazy and tend to be pragmatic (and most often disguise the former by calling it &amp;#39;pragmatic&amp;#39; :)).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Why write yet another shop-system when you can pick from several e-commerce tools? Because face it: e-commerce is not ready. Übercart -no offence!- simply sucks, for extendability, usability and flexibility. But that was not the main reason, that was more technical. More on that later.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Views has a performance horror luring around the corner. It might not hit you, but often will -- Views is not bad performant per sé, but it can be. CCK - well, exactly the same. And panels. Don&amp;#39;t get me started on that! If you sincerely hate your frontend developers, give them panels. I have seriously had a person resign from his job because of panels used int their project (panels 2 in his defence, 3 is an improvement). But I do use Views in most projects, together with -obviously- CCK and about 20 other modules.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Views, CCK, Panels are all great tools for the average quick project. Typically projects where the 80/20 rule is applied as: we build 80 and forget about the 20.  And we all know the problem that &amp;lt;a href=&amp;#34;http://drupal.org/node/580026&amp;#34;&amp;gt;features&amp;lt;/a&amp;gt; tries to solve: you create CCK fields, use these in (dependant!) views, and override that in -PHP- templates. The always returning staging-horror. AKA &amp;#39;simply repeat the creation on LIVE all over again&amp;#39;.  But I do not want to go into more details on the technical downsides of these modules. However important, a far greater concern outshadows these by far.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;The problem that made me switch 180° was the development &amp;lt;em&amp;gt;for and in&amp;lt;/em&amp;gt; CCK, views, panels and all these high-level buildingbricks. To illustrate, let me give you some quit often repeating questions; try to build them with CCK, views and related modules:&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;An event-listing: next upcoming item, whose end-date is not yet passed (event is not yet finished), grouped by day.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;An article with some fields extracted (live) from a webservice: content lives not in the local database, but is pulled over SOAP, REST or similar. E.g. the editor fills in the &amp;amp;quot;trailer_id&amp;amp;quot; and the trailer is pulled from a filmtrailer service.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;On Cron, fill certain nodes with data from a service or an XML-file.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Validate a postal-code field against a city-field; a postal-code implies a certain city. (using, e.g. a local lookup-table or some provided library).&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;People must provide either a telephone OR an emailfield.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;After submitting a node (say a classified ad) people are redirected to the next node form (say, to add photo&amp;#39;s), of wich parts are pre-filled and which is related to the first (in database or ORM speak: one classified has_many photo&amp;#39;s)&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;A table, listing all profile-nodes, but where the fields Prefix, FirstName, middlename, SurName, Postfix are aggregated into one column, sortable by Surname.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Right? Off course, with the right combination of computed fields, custom template logic(!) and maybe some views and CCK addons  it is possible. But far from easy.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Now, I developed each and every of above in custom modules. Let me summarise how I did that, and how many code it required.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;ul&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Eventlisting: Custom node, defined in an event.module, with a (really simple) date-field, and a (slightly less simple) database query, pushing that to a theme(&amp;#39;table&amp;#39;). Done. Isolated code for this is less then 200 lines, one small module! The module became more complex, because we changed the model into &amp;amp;quot;event has_many playdates&amp;amp;quot; later. Now bearing less then 600 lines, still small.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Extraced content from a webservice: Very small custom node, defined in movie.module, on hook_insert etc. insert the ID into a local joined table, hook_load request external source using the value from the table. Tiny module, without  theme functions, less then 400 lines of PHP.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Fill from an external resource: On cron, fill some custom module-defined nodes. Before we filled CCK-nodes, but the dynamic use of database (database layout changes when reconfiguring fields) made us decide to simply push all data to our custom joined table. Simple. Effective. Less then a thousand lines code, with most code on the XML parsing and validation.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Telephone or mailfield: A custom node, joined table, with hook_validate checking existence of one of both fields and presenting user with a proper message. Less then 40 lines of PHP. 20 minutes development or so. Other fields on this custom node are added with CCK.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;A module with several custom nodes, extendable with CCK, but some fields are stored in the database (e.g. the abovementioned telephone/mail fields) module does redirecting, validating and pre-filling on several hooks provided by Drupal. One of the larger modules, still less then 1000 lines of PHP.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;li&amp;gt;Simple SQL pager-query, some PHP looping over the items and aggregating them at wish. Less then 50 lines of PHP. Less then an hour development.&amp;lt;/li&amp;gt;&amp;#xA;&amp;lt;/ul&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;I am not trying to look cool and say &amp;amp;quot;look how fast and small I can develop&amp;amp;quot;, nor do I want to thumb down CCK or views, or any of the other buidling blocks. I am trying to point out how an often forgotten, simple tool can aid.&amp;#xA;And that writing Views addons, CCK plugins and the likes requires far -FAR- more development, complexity. Will introduce a lot more (unhandled) edge cases (seen a module that does not handle multiple fields correct lately?). And offers hardly any benefit other then the -theoretically- better re-usability. Theoretically, because when being pragmatic, you can just as easy copy paste some code from an old project, then wrinting a perfectly flexible and generic solution.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;To illustrate: we spent a month on addons for übercart, views and CCK: simgle-click-checkout, insert barcode in invoices, hacking the Übercart interface in templates, writing complex -dependant and relating- fields for CCK, and so on. The client was not very happy with the workflow, we were far less happy with the enormous amount of (dependant!) code for all the addons and overrides. Loosing all the benefits of re-using code. A complex form-alter introduces just the same amount of tight coupling as a fork would: you have to maintain your form-alter code on every change of the altered form, just as well. An amount of override and template code that extends the amount of re-used code, defeats the purpose of getting a quick start.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Rewriting the entire thing in my own modules took less then 3 weeks. And we are far futher then then 80% now, nearing 90. While the generic solution left us entirely stuck at 80%. Not being able to get out, with the only solution &amp;amp;quot;convince the client that the last 20% is not very important&amp;amp;quot;. Well it was, and right so. We killed a project wich required over 30 modules and 3000&#43; lines of template code to be replaced by two custom modules (4000&#43;lines, so rather large) and no template logic.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;As if a carpenter only uses his completely computerised drilling robot, automated sawing machines and super-hightech-glue-gun. When often a handsaw, nail and hammer will get to planks together in less then 5 minutes. &amp;lt;em&amp;gt;A good carpenter might have all the hight-teck tools, but never forgets about the ease and speed of a hammer and a nail.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;So, yes. Using Views and CCK helps you forward. And will get your to the 100% if your 100% is not that demanding. Say, in rapid prototyping; get up a CCK&#43;views&#43;panels version in a few hours, see if the general idea is good, throw it out and rerite it in your own code.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;But when you&amp;#39;re requirements are slightly more specialised then a few simple modules, -developed in PHP-, are the quickest, cleanest and most pragmatic way. The only way that will make your client 100% happy. Especially when you are your own client!&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;edit&amp;lt;/strong&amp;gt; we had over 3000 lines, not over 300 lines of template code.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>&amp;#34;Pick up where you left last X&amp;#34; by not committing last changes, good branches and pseudocode.</title>
   <link href="https://berk.es/2010/04/15/pick-up-where-you-left-last-x-by-not-committing-last-changes-good-branches-and-pseudocode/"/>
   <updated>2010-04-15T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/04/15/pick-up-where-you-left-last-x-by-not-committing-last-changes-good-branches-and-pseudocode/</id>
   <content type="html">&amp;lt;p&amp;gt;When programming, you often need to carry a single task on to the next day, or till &amp;amp;quot;after the meeting&amp;amp;quot; and such. I experimented and found a good method, to pick up where I left. Working &amp;lt;a href=&amp;#34;http://getontracks.org/&amp;#34; title=&amp;#34;Tracks: an open source, webbased Getting Things Done application&amp;#34;&amp;gt;the GTD way&amp;lt;/a&amp;gt;, well documented code, a good software architecutre all help keeping a project in line, up to speed and manageble. But it does not solve one thing, though: &amp;amp;quot;picking up where you left&amp;amp;quot;. I have many clients, did many projects and have lots of strings attached. Hence I get disturbed very often. Even when you can focus entirely on one single programming task (lucky you!) five o&amp;#39;clock is the end of the day (or, in my case, 18:30).  Going on for another half hour or so is perfectly fine, but most often I cannot finish a single task and need to carry it on to the next day. Or till after that meeting. Getting back in the flow then, requires quite some effort. What I do, is twofolded: &amp;lt;em&amp;gt;work with pseudocode&amp;lt;/em&amp;gt; and &amp;lt;em&amp;gt;don&amp;#39;t commit the last run&amp;lt;/em&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;First, I outline my project in the usual diagrams, documents and such. Then, when working on a more micro-level (the methods), I first write out the inner workings in comments. Steve McConnell, in Code Complete, calls this &amp;lt;a href=&amp;#34;http://www.coderookie.com/2006/tutorial/the-pseudocode-programming-process/&amp;#34;&amp;gt;Pseudo Programming Process&amp;lt;/a&amp;gt;. When finished, I will commit this. When not finished, see below. You often see some meeting, break, or end-of-day coming closer, while working in several Classes, on several routines, or in the database, documenation and code all at once. You are in a certain flow, but really need to halt, because people are waiting, kids need to be picked up at school or your girlfriend needs attention. Valid enough to stop working for now. Most people I know, finish up hastily, and commit the work. I call these &amp;lt;em&amp;gt;five o&amp;#39;clock commits&amp;lt;/em&amp;gt; not only are they horrible from a revisioncontrol point (a commit should always describe a complete change) but they offer an opportunity for you: to keep in the flow. Do not commit this. Leave it as it is. Even if you have your work in a commitable state, leave it! After your break, or next day, you open the code, and when you left it commitable, the first thing you must do, is commit the work.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;This forces you to read through the diff, describe the changes and then commit. I found this exercise more then enough to get me right into the flow, to pick up where I left. If you left it in an uncommitable state, then somewhat the same applies. You can read through the diff, describe what was going on and pick up the work right away.&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;When combined with &amp;lt;a href=&amp;#34;http://nvie.com/git-model&amp;#34;&amp;gt;good branching practice&amp;lt;/a&amp;gt; this &amp;amp;quot;reading through the diff&amp;amp;quot; takes less then ten minutes. And, as is my experience, even helps when picking up work after a few days other work, or a long weekend or any other situation where simply the diff does not ring a bell anymore. In fact, after finishing this blogpost, I am going to pick up where I left friday (six days ago) and I am confident that the commented pseudocode, good branchnames and the diff leave me more then enough hints to be on track in less then ten minutes.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Load Rubygems environment variables on startup (requie &amp;#39;gemname&amp;#39; gives &amp;#39;not found&amp;#39;)</title>
   <link href="https://berk.es/2010/04/14/load-rubygems-environment-variables-on-startup-requie-gemname-gives-not-found/"/>
   <updated>2010-04-14T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/04/14/load-rubygems-environment-variables-on-startup-requie-gemname-gives-not-found/</id>
   <content type="html">&amp;lt;p&amp;gt;Small note to self (When I blog about something, I tend to remember it better).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;On my new testing environment, I spent over twenty minutes figuring out why some &amp;lt;a href=&amp;#34;http://api.rubyonrails.org/classes/ActiveResource/&amp;#34;&amp;gt;gem&amp;lt;/a&amp;gt; would not be available.&amp;#xA;Once I figured out that somehow I needed to add &amp;lt;em&amp;gt;require &amp;#39;rubygems&amp;#39;&amp;lt;/em&amp;gt; I came to realize that on the new machine, I did not add a certain line to my bashrc, an environment variable was not set. Since my development and production machines have this set, I forgot about it alltogether (when something &amp;amp;quot;just works&amp;amp;quot;, why  bother more mental effort on the matter?).&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;Here is the line to be added to your &amp;lt;em&amp;gt;~/.bashrc&amp;lt;/em&amp;gt;:&amp;#xA;&amp;lt;code&amp;gt;export RUBYOPT=rubygems&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;#xA;&amp;lt;p&amp;gt;After that, in all your ruby environments, gems are automatically available when &amp;#39;require&amp;#39;ing them.&amp;#xA;More on the matter at &amp;lt;a href=&amp;#34;http://stackoverflow.com/questions/132867/i-have-a-gem-installed-but-require-gemname-does-not-work-why&amp;#34;&amp;gt;stackoverflow&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&amp;#xA;</content>
 </entry>
 
 <entry>
   <title>Ubuntu 10.04 - countdown</title>
   <link href="https://berk.es/2010/04/12/ubuntu-10-04-countdown/"/>
   <updated>2010-04-12T00:00:00&#43;02:00</updated>
   <id>https://berk.es/2010/04/12/ubuntu-10-04-countdown/</id>
   <content type="html">&amp;lt;script type=&amp;#34;text/javascript&amp;#34; src=&amp;#34;http://www.ubuntu.com/files/countdown/display.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;Nog eventjes wachten en dan is er weer een nieuwe Ubuntu. Nóg beter, sneller, mooier, stabieler en fijner (hopen we dan maar)&amp;#xA;</content>
 </entry>
 
 
</feed>
