<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <generator>Persumi - Level up your writing and blogging with AI</generator>
  <link href="http://persumi.com/u/fredwu"/>
  <link href="http://persumi.com/u/fredwu/feed/rss"/>
  <link rel="self" href="http://persumi.com/u/fredwu/feed/atom"/>
  <author>
    <name>Fred Wu</name>
    <email>ifredwu@gmail.com</email>
    <uri>http://persumi.com/u/fredwu</uri>
  </author>
  <subtitle>CTO by day, freelancing software consultant by night. Founder of Persumi.</subtitle>
  <id>http://persumi.com/u/fredwu</id>
  <title>Fred Wu (@fredwu)</title>
  <updated>2026-03-10T21:32:19.611832Z</updated>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
I’ve had the &lt;a href=&quot;https://www.xreal.com/air2/&quot;&gt;Xreal Air 2 Pro&lt;/a&gt; for a while. I ordered the &lt;a href=&quot;https://pro.viture.com/&quot;&gt;Viture Pro&lt;/a&gt; on the 23rd May, it shipped on the 28th May, and finally got delivered on the 9th June. I’m in Australia.&lt;/p&gt;
&lt;h2&gt;
Packaging&lt;/h2&gt;
&lt;p&gt;
The unboxing experience has been stellar, the packaging is one level above Xreal’s.&lt;/p&gt;
&lt;h2&gt;
Case&lt;/h2&gt;
&lt;p&gt;
I do like Xreal’s case not needing a zipper, but the utility of Viture’s case is better, with lining for both the glasses and the cable, whereas Xreal’s is simply a plain case without any inserts.&lt;/p&gt;
&lt;h2&gt;
Build Quality&lt;/h2&gt;
&lt;p&gt;
I would say on par, both are very well built.&lt;/p&gt;
&lt;h2&gt;
Styling&lt;/h2&gt;
&lt;p&gt;
This is highly subjective. I’ve seen many many reviews on both. For someone with a small head, I thought the Viture would look better on me… hmmm… not quite, I think I still prefer the Xreal. Having said that it’s not a big deal for me, I’d be mostly wearing them at home, and perhaps on flights.&lt;/p&gt;
&lt;h2&gt;
Wearing Comfort&lt;/h2&gt;
&lt;p&gt;
This one goes to the Xreal, by a large margin. Xreal’s leg ends are soft, bendable and very thin, making putting on and off the glasses a breeze. The Viture on the other hand, has hard leg ends, which requires more force to bend the whole legs outwards when putting on and off.&lt;/p&gt;
&lt;p&gt;
When the glasses are on my face, I can feel the plastic legs clamping on me with the Viture, whereas with the Xreal I almost don’t feel anything.&lt;/p&gt;
&lt;h2&gt;
Screen Brightness&lt;/h2&gt;
&lt;p&gt;
Xreal Air 2 Pro technically has lower nits (500 vs 1000 perceived), but in reality I couldn’t tell much of a difference. Viture Pro might appear ever so slightly brighter. Although, interestingly, the Viture Pro’s lowest brightness is lower than Xreal’s (although with significant contrast drop).&lt;/p&gt;
&lt;p&gt;
Also, Viture Pro has OSD to display brightness and audio levels when you adjust them, Xreal doesn’t.&lt;/p&gt;
&lt;h2&gt;
Sound Quality&lt;/h2&gt;
&lt;p&gt;
Disappointingly, to my ear, the Xreal Air 2 Pro sounds better, it is slightly louder, and has significantly better clarity.&lt;/p&gt;
&lt;h2&gt;
Image Quality&lt;/h2&gt;
&lt;p&gt;
This might be the most disappointing part, and I’m really hoping a new firmware will address this. As is, on the latest 0603 firmware, the Viture Pro has a significant green/yellow tint - like, not a little bit, but very very significant. This makes movie watching (my primary use case) a no-go.&lt;/p&gt;
&lt;h2&gt;
Electrochromic Screens&lt;/h2&gt;
&lt;p&gt;
Very similar between the two. Xreal has three levels, Viture Pro has two, but realistically only the 0% and 100% are useful anyway. One small thing I noticed is that when you push the button, Xreal changes the level immediately, Viture Pro has half a second delay, no biggy.&lt;/p&gt;
&lt;h2&gt;
Screen Edge Clarity&lt;/h2&gt;
&lt;p&gt;
I used to own a pair of Xrea Air (the 1st gen), and from memory, I think that one has the best screen edge clarity.&lt;/p&gt;
&lt;p&gt;
The Xreal Air 2 Pro notoriously has blurry edges, which I can confirm. After swapping to using the smallest nose pads, I was able to make the edges clear enough, but it still has a slight colour shift.&lt;/p&gt;
&lt;p&gt;
The Viture Pro is better, however the edges I would say still have a little bit of colour shift. Although, can’t say it effects my use case at all. I’ve tried to look at text, and they are fine.&lt;/p&gt;
&lt;h2&gt;
Myopia Adjustments&lt;/h2&gt;
&lt;p&gt;
This is one of the main reasons I bought the Viture Pro despite already owning the Xreal. As I’m a contact lens wearer, I’d like to use the glasses in bed without using contacts.&lt;/p&gt;
&lt;p&gt;
Obviously the Xreal doesn’t have this functionality. However, this is another major disappointment that I’m hoping a firmware could fix. When the myopia dials are set to 0, the screen is actually blurry!! I thought it was my eyes at first, but nope, I had to dial them both up slightly in order for the screen clarity to equal that of Xreal’s.&lt;/p&gt;
&lt;h2&gt;
Verdict&lt;/h2&gt;
&lt;p&gt;
As you can probably tell, I am very disappointed. As is, the Viture Pro is unusable with the significant green tint. And the usability is impacted by the miscalibrated myopia adjustments.&lt;/p&gt;
&lt;p&gt;
I’m hoping both of these issues will be fixed soon via firmware updates.&lt;/p&gt;
&lt;p&gt;
P.S. Tested both glasses on both macOS (Macbook Pro) and Samsung Dex (S24 Ultra).&lt;/p&gt;
&lt;h2&gt;
Update a day later&lt;/h2&gt;
&lt;p&gt;
So last night I used the Viture Pro in bed to test two things: comfort and myopia dials.&lt;/p&gt;
&lt;h3&gt;
Myopia Adjustments&lt;/h3&gt;
&lt;p&gt;
The myopia dials work. But as I suspected, it’s miscalibrated. As mentioned before, when I’m wearing contacts (new prescription, also tested with Xreal), I have to slightly dial them up to get a clear picture. Now when I’m in naked eyes without contacts, both of my eyes are -4.75, but I have to dial them all the way to the end (-5) to get the most clear picture. This indicates to me that the dials are miscalibrated.&lt;/p&gt;
&lt;h3&gt;
In Bed Comfort&lt;/h3&gt;
&lt;p&gt;
Comfort unfortunately is a step down compared to the Xreal. Mostly due to the rigid legs that stick out longer compared to the Xreal - they would push into the pillow. Of course, if you don’t plan on wearing them in bed this won’t effect you. Or if you have a larger head, it probably will be okay too.&lt;/p&gt;
&lt;h3&gt;
Audio Control&lt;/h3&gt;
&lt;p&gt;
Another thing I noticed is that Viture Pro’s volume control is separate to the system. When connected to the Mac, it is connected as a DisplayPort device type, whereas Xreal is connected as a USB device type. So when changing volume, Xreal directly changes the system volume whereas with Viture Pro, you can either change the volume on both the system and on the glasses or only on the glasses depending on the host device (on a Mac, you can’t change the system volume, on an Android you can). Personally I prefer the Xreal’s approach.&lt;/p&gt;
&lt;h3&gt;
Colour Tint&lt;/h3&gt;
&lt;p&gt;
Finally, I’ve taken some comparison shots of the colours between my Macbook Pro, Xreal Air 2 Pro and Viture Pro (this is also sent to Viture support).&lt;/p&gt;
&lt;p&gt;
Upon first glance they look very similar. But please zoom in and take a closer look. I’ve used the same camera and manually set the white balance to ensure they are consistent across the devices.&lt;/p&gt;
&lt;p&gt;
The colour shift is way more pronounced and is very noticeable in real life.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/86ce4e74-ffb1-4efa-b689-48bb83684310.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
I think I’ve got a better comparison. The backgrounds are all close to black in real life, and pay no attention to the blurriness it’s just my camera’s focus. Again, set with the same white balance, you can now clearly see the yellowish tint on the Viture Pro. In real life the Macbook Pro and Xreal screens are very very similar.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/d106c41d-9cb1-4622-bc56-c1343c49dcc6.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
]]&gt;</content>
    <published>2024-06-09T10:08:39.269242Z</published>
    <link href="http://persumi.com/u/fredwu/gadgets/e/reviews/p/quick-comparisons-of-viture-pro-and-xreal-air-2-pro"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/gadgets/e/reviews/p/quick-comparisons-of-viture-pro-and-xreal-air-2-pro</id>
    <title>Quick comparisons of Viture Pro and Xreal Air 2 Pro</title>
    <updated>2024-06-09T10:08:39.269242Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;em&gt;Warning: Minor spoilers on the characters, but no spoilers on the story.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
As a casual Sci-Fi enjoyer who mostly enjoys them through movies and TV shows, I’ve only occasionally read Sci-Fi novels. But I loved Andy Weir’s &lt;em&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/The_Martian_(Weir_novel)&quot;&gt;The Martian&lt;/a&gt;&lt;/em&gt; (loved the movie first, then went back to reading the novel) and &lt;em&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Project_Hail_Mary&quot;&gt;Project Hail Mary&lt;/a&gt;&lt;/em&gt;. Therefore, since discovering Netflix’s &lt;em&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/3BodyProblem(TVseries)&quot;&gt;3 Body Problem&lt;/a&gt;&lt;/em&gt; not too long ago, I realised I had to start from the origin, and follow the thread from there, especially since I am a native Mandarin speaker.&lt;/p&gt;
&lt;p&gt;
The original novel, &lt;em&gt;三体&lt;/em&gt;, would translate to simply &lt;em&gt;Three Body&lt;/em&gt;. However, the English translation of the book was published as &lt;em&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/TheThree-BodyProblem(novel)&quot;&gt;The Three-Body Problem&lt;/a&gt;&lt;/em&gt;, and the Netflix adaptation &lt;em&gt;3 Body Problem&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;
Within the span of two months, I consumed, in order:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
The original Chinese trilogy, which includes &lt;em&gt;Three Body&lt;/em&gt;, &lt;em&gt;Three Body II: The Dark Forest&lt;/em&gt; and &lt;em&gt;Three Body III: Death’s End&lt;/em&gt;, in audio book format, totalling a whopping 85+ hours.  &lt;/li&gt;
  &lt;li&gt;
The Chinese TV series adaptation produced by Tencent Video, which consists of 30 episodes covering the first book, totalling 20 hours. You can watch the full series &lt;a href=&quot;https://www.youtube.com/playlist?list=PLDWJ213d2Ucr-3q9LDF9P1_j3Rr3GMJeS&quot;&gt;here on Youtube&lt;/a&gt; by one of the official publishers.  &lt;/li&gt;
  &lt;li&gt;
The Netflix TV series adaptation, which consists of 8 episodes covering the first book and parts of the second book, totalling 8 hours.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
For the Chinese TV series, there is also a 26-episode Director’s Cut version, which apparently has better pacing by removing some original sub-plots and tightening the overall story flow. I actually didn’t mind the slower pace and the original sub-plots so I stuck with the original 30-episode version.&lt;/p&gt;
&lt;p&gt;
I’m going to rate all three versions, but as a benchmark, I’d give The Martian (both the novel and the movie) a 10/10, and Project Hail Mary also a 10/10. This is from the perspective of someone who grew up in China then spent more than 20 (and counting) years living in and breathing the Western culture.&lt;/p&gt;
&lt;h2&gt;
TL;DR: Ratings Summary&lt;/h2&gt;
&lt;p&gt;
| Version | Overall | Story | Main Characters or Cast | Minor Characters or Cast | Cinematography | Music / Soundtrack | VFX | Audio and SFX
| — | — | —  | — | — | —  | — | — | — 
| Novel | &lt;strong&gt;9&lt;/strong&gt; | &lt;strong&gt;9&lt;/strong&gt; | &lt;strong&gt;10&lt;/strong&gt; | &lt;strong&gt;9&lt;/strong&gt; | N/A | N/A | N/A | N/A
| Tencent | &lt;strong&gt;9&lt;/strong&gt; | &lt;strong&gt;9&lt;/strong&gt; | &lt;strong&gt;10&lt;/strong&gt; | &lt;strong&gt;6&lt;/strong&gt; | &lt;strong&gt;10&lt;/strong&gt; | &lt;strong&gt;9&lt;/strong&gt; | &lt;strong&gt;7&lt;/strong&gt; | &lt;strong&gt;5&lt;/strong&gt;
| Netflix | &lt;strong&gt;7&lt;/strong&gt; | &lt;strong&gt;7&lt;/strong&gt; | &lt;strong&gt;8&lt;/strong&gt; | &lt;strong&gt;7&lt;/strong&gt; | &lt;strong&gt;7&lt;/strong&gt; | &lt;strong&gt;7&lt;/strong&gt; | &lt;strong&gt;6&lt;/strong&gt; | &lt;strong&gt;7&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
Story&lt;/h2&gt;
&lt;p&gt;
| Version | Rating | Commentary
| — | — | —
| Novel | &lt;strong&gt;9&lt;/strong&gt; | A very captivating story and with a world building that’s mostly grounded to reality.
| Tencent | &lt;strong&gt;9&lt;/strong&gt; | A condensed version that is faithful to the original novel (1st book), with some added minor characters and sub-plots. The story-telling focuses more on character building to push the story forward, just like in the novel.
| Netflix | &lt;strong&gt;7&lt;/strong&gt; | A “re-engineered” story with a much smaller, mishmashed ensemble, much faster pace and less convincing world building. It is the fast food version - not necessarily inferior, but much less nuanced and is produced for mass consumption by the Western audience. Remember, it speeds through the entire book 1 and part of book 2 in merely 8 hours, compared to the 20 hours of the Tencent adaptation for only book 1.&lt;/p&gt;
&lt;h2&gt;
Main Characters or Cast&lt;/h2&gt;
&lt;p&gt;
| Version | Rating | Commentary
| — | — | —
| Novel | &lt;strong&gt;10&lt;/strong&gt; | The believable and multi-faceted characters is definitely a highlight of the series, especially from the lead characters of each book.
| Tencent | &lt;strong&gt;10&lt;/strong&gt; | Superb acting from the main cast. Shi Qiang is a highlight, the portray of this version of Shi Qiang adds more humour and lightens much of the heaviness of the story beats. Wang Miao has a calm mannerism and deep and soothing voice. Both the young and old Ye Wenjie are portrayed with consistency, and nuances that beyond words.
| Netflix | &lt;strong&gt;8&lt;/strong&gt; | The acting is decent for what the show is. Due to the fast pace nature, there is only so much that can be done by the otherwise fine cast. The characters themselves however, take a major hit. Many characters from the novel are combined. Worryingly, Ye Wenjie has been portrayed as simply bitter, missing the nuances entirely as a result of the many hours of character building in the novel and the Tencent adaptation.&lt;/p&gt;
&lt;h2&gt;
Minor Characters or Cast&lt;/h2&gt;
&lt;p&gt;
| Version | Rating | Commentary
| — | — | —
| Novel | &lt;strong&gt;9&lt;/strong&gt; | Like the main characters, each minor character comes with a believable background and motivation, and helps with the story and world building.
| Tencent | &lt;strong&gt;6&lt;/strong&gt; | Unfortunately, this is a weak point of the Chinse adaptation. Whilst the characters themselves are fine, the cast unfortunately is a big hit and miss, with a few of them definitely miscast. Wei Cheng in particular, has been reduced to a one-dimensional maths nerd with a heavy Shandong accent unnecessarily. Pan Han’s portray is also wooden and uninspired. To top it off, a super minor character with only a minute or two of screen time has an extremely bizarre, over-the-top acting style that nearly ruined the entire scene. Luckily, most of the other cast ranges from okay (e.g. Shen Yufei) to superb (e.g. Chang Weisi).
| Netflix | &lt;strong&gt;7&lt;/strong&gt; | Acting from the minor cast is fine, nothing remarkable but nothing terrible either.&lt;/p&gt;
&lt;h2&gt;
Cinematography&lt;/h2&gt;
&lt;p&gt;
| Version | Rating | Commentary
| — | — | —
| Tencent | &lt;strong&gt;10&lt;/strong&gt; | It’s a masterpiece of an art that left a huge mark on me. The cinematography is simply breathtaking. Even more, each episode includes a bunch of photo montages during credits.
| Netflix | &lt;strong&gt;7&lt;/strong&gt; | It’s mostly a standard sci-fi look and feel, it looks good but nothing too inspiring here.&lt;/p&gt;
&lt;h2&gt;
Music / Soundtrack&lt;/h2&gt;
&lt;p&gt;
| Version | Rating | Commentary
| — | — | —
| Tencent | &lt;strong&gt;9&lt;/strong&gt; | A collection of finely composed soundtracks covering a wide range of emotions. I was moved by many of them. &lt;a href=&quot;https://www.youtube.com/watch?v=ecmTf_gI4LU&quot;&gt;Check them out here&lt;/a&gt;.
| Netflix | &lt;strong&gt;7&lt;/strong&gt; | As a fan of &lt;a href=&quot;https://en.wikipedia.org/wiki/Ramin_Djawadi&quot;&gt;Ramin Djawadi&lt;/a&gt; and his work from &lt;em&gt;Game of Thrones&lt;/em&gt; and &lt;em&gt;Westworld&lt;/em&gt;, this is a fine collection of soundtracks, albeit a tad forgettable compared to the aforementioned masterpieces.&lt;/p&gt;
&lt;h2&gt;
VFX (Visual Effects)&lt;/h2&gt;
&lt;p&gt;
| Version | Rating | Commentary
| — | — | —
| Tencent | &lt;strong&gt;7&lt;/strong&gt; | Some VFX are a bit janky. Though in most cases the producers have hidden them well. The “in-game” CG and animation are very janky but that can simply be attributed to them being “in-game” therefore to be expected, besides, they are actually quite charming.
| Netflix | &lt;strong&gt;6&lt;/strong&gt; | This surprised me. I thought for sure Netflix would have the budget for higher quality VFX. In most cases this is true, unfortunately this is let down by some key moments having very questionable VFX and/or artistic choice.&lt;/p&gt;
&lt;h2&gt;
Audio and SFX (Sound Effects)&lt;/h2&gt;
&lt;p&gt;
| Version | Rating | Commentary
| — | — | —
| Tencent | &lt;strong&gt;5&lt;/strong&gt; | Another unfortunate weakness of the Tencent adaptation. Many sound bites (such as the eery music) were simply lifted from other TV shows like &lt;em&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Dark_(TV_series)&quot;&gt;Dark&lt;/a&gt;&lt;/em&gt; which takes me out of the immersion. What was also baffling, is the audio mixing - in more than one occasion, lines from different takes are stitched together with vastly different audio levels and uniformity.
| Netflix | &lt;strong&gt;7&lt;/strong&gt; | Like the cinematography, there is nothing wrong or special here. It’s good.&lt;/p&gt;
&lt;h2&gt;
Extra: Languages&lt;/h2&gt;
&lt;p&gt;
As someone who speaks both Mandarin and English, it’s been an interesting experience.&lt;/p&gt;
&lt;p&gt;
The Netflix version is easier to consume in this regard. Other than the character Mike Evan’s borderline gibberish Mandarin, for the most part I only needed subtitles for the few lines of French and Spanish that didn’t really impact the story. Although, Ye Zhetai speaking with a heavy southern Chinese accent is a bizarre casting choice.&lt;/p&gt;
&lt;p&gt;
The Tencent version on the other hand is much more difficult to consume. A few episodes in I had to rely on subtitles to make out both the Mandarin and the English spoken by the non-native speakers.&lt;/p&gt;
&lt;h2&gt;
Final Thoughts&lt;/h2&gt;
&lt;p&gt;
I liked the original novel trilogy and loved the cinematography of the Chinese TV adaptation. I probably would’ve liked the Netflix adaptation more if I had no point of reference from the other two.&lt;/p&gt;
&lt;p&gt;
If you’ve watched the Netflix version and are curious about the source material - read the novel(s) or watch the Chinese TV adaptation.&lt;/p&gt;
]]&gt;</content>
    <published>2024-05-26T07:51:38.204617Z</published>
    <link href="http://persumi.com/u/fredwu/art/e/thoughts/p/three-versions-of-the-three-body-problems-thoughts-from-a-native-mandarin-speaker"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/art/e/thoughts/p/three-versions-of-the-three-body-problems-thoughts-from-a-native-mandarin-speaker</id>
    <title>Three Versions of The Three Body Problems, Thoughts from A Native Mandarin Speaker</title>
    <updated>2024-05-26T07:51:38.204617Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
After having soft launched &lt;a href=&quot;https://persumi.com&quot;&gt;Persumi&lt;/a&gt; in 2023, I quickly found myself wanting to explore better ways to promote the product. Unfortunately, most of digital marketing products are focused on three main areas:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
Keyword bidding, e.g. Google Ads or Facebook Ads  &lt;/li&gt;
  &lt;li&gt;
Cold outreach, e.g. Apollo  &lt;/li&gt;
  &lt;li&gt;
Search Engine Optimisation (SEO)  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
They each has its own pros and cons. As an indie developer without a huge budget, none of these tools work effectively.&lt;/p&gt;
&lt;h2&gt;
The Challenges&lt;/h2&gt;
&lt;h3&gt;
Keyword bidding and targeted ads are expensive&lt;/h3&gt;
&lt;p&gt;
Bidding wars drive up the cost of targeted ads, sometimes so significantly they price out many small businesses.&lt;/p&gt;
&lt;p&gt;
Users are increasingly using adblockers, making targeted ads ineffective.&lt;/p&gt;
&lt;h3&gt;
Cold outreach are rarely answered&lt;/h3&gt;
&lt;p&gt;
Highly sought-after leads are bombarded with cold emails every day, making the tactic ineffective.&lt;/p&gt;
&lt;p&gt;
Most cold emails end up in users’ spam bin, never to be seen again.&lt;/p&gt;
&lt;h3&gt;
Search Engine Optimisation is a lost cause&lt;/h3&gt;
&lt;p&gt;
SEO’ed content are being produced hastily, making them low quality and untrustworthy.&lt;/p&gt;
&lt;p&gt;
Sophisticated users have learnt to ignore highly SEO’ed content from untrusted sources.&lt;/p&gt;
&lt;h2&gt;
The Epiphany&lt;/h2&gt;
&lt;p&gt;
Reflecting on my own behaviour in choosing a product or service, I realised that over the years I have developed a good sensory to weed out the noise. Namely, I am very skeptical with highly ranked websites, I always use an adblocker (uBlock) to block annoying ads, and both my personal and work email inboxes are overflowed with cold emails I never read them.&lt;/p&gt;
&lt;p&gt;
So how do I do research, say when I’m buying a new TV, or subscribing to a new software package? I read user reviews. And not just questionable reviews on Amazon or review sites, but on Reddit, where (mostly) real users discuss and review products and services.&lt;/p&gt;
&lt;p&gt;
Of course, like any other place, Reddit can be filled with bots and fake content too. However, in my experience these are easily distinguishable, especially paired with content’s upvotes and user’s post history. With Reddit’s popularity, this makes it a highly desirable place for people to both look for answers, as well as to provide answers.&lt;/p&gt;
&lt;p&gt;
So I asked myself, what if there was a smart lead generation tool to help me promote my product on Reddit? What should the tool be capable of?&lt;/p&gt;
&lt;h2&gt;
The Solution&lt;/h2&gt;
&lt;p&gt;
In order to really stand out, and provide tangible value, I’ve focused on the following areas for the lead gen tool.&lt;/p&gt;
&lt;h3&gt;
Warm leads over cold leads&lt;/h3&gt;
&lt;p&gt;
By searching for and engaging with relevant content, the leads the tool finds for you are warm and targeted.&lt;/p&gt;
&lt;p&gt;
Many of the leads are already highly ranked on search engines, further boosting the content’s reach.&lt;/p&gt;
&lt;h3&gt;
Personalised and public&lt;/h3&gt;
&lt;p&gt;
Content the tool produces for the user are context-aware and personalised, making them highly relevant and trustworthy.&lt;/p&gt;
&lt;p&gt;
Even better, the content are made available publicly, boosting the user’s reputation and reach even further over time.&lt;/p&gt;
&lt;h3&gt;
Infinite scale at a fixed cost&lt;/h3&gt;
&lt;p&gt;
The tool finds leads and suggest responses for the user 24/7 non-stop, at a fraction of the usual marketing costs.&lt;/p&gt;
&lt;p&gt;
The pricing is fixed and transparent, so the user can scale their business without worrying about blowing the budget.&lt;/p&gt;
&lt;h2&gt;
The Bottom Line&lt;/h2&gt;
&lt;p&gt;
Ultimately, I want a lead generation tool that really provides value to not only its user (the marketer), but its user’s users (the target audience). Instead of focusing on &lt;strong&gt;selling&lt;/strong&gt;, it should focus on &lt;strong&gt;helping people&lt;/strong&gt;. This is where &lt;a href=&quot;https://rizz.farm&quot;&gt;Rizz.farm&lt;/a&gt; comes in.&lt;/p&gt;
&lt;h2&gt;
How &lt;a href=&quot;https://rizz.farm&quot;&gt;Rizz.farm&lt;/a&gt; Works&lt;/h2&gt;
&lt;p&gt;
With &lt;a href=&quot;https://rizz.farm&quot;&gt;Rizz.farm&lt;/a&gt;, machine learning is put to good use. It’s not just another AI wrapper, it actually leverages many of the latest AI innovation to really take lead generation to the next level.&lt;/p&gt;
&lt;h3&gt;
Smart search&lt;/h3&gt;
&lt;p&gt;
&lt;a href=&quot;https://rizz.farm&quot;&gt;Rizz.farm&lt;/a&gt; widens the search radius to find the most relevant or highly ranked content for the user. It may find leads a simple Google search couldn’t. Behind the scenes, keywords are expanded and multiple search queries are submitted to search engines.&lt;/p&gt;
&lt;h3&gt;
Continuous monitor&lt;/h3&gt;
&lt;p&gt;
&lt;a href=&quot;https://rizz.farm&quot;&gt;Rizz.farm&lt;/a&gt; continuously monitors the social media platforms (Reddit to start off with) to find new leads 24/7, delivering them right to the user’s inbox. Whether they are highly ranked popular posts or the latest post in a particular subreddit, &lt;a href=&quot;https://rizz.farm&quot;&gt;Rizz.farm&lt;/a&gt; is able to quickly deliver them to the inbox without fuss.&lt;/p&gt;
&lt;h3&gt;
Leads inbox, as simple as Gmail&lt;/h3&gt;
&lt;p&gt;
Leads are automatically sent to the user’s inbox, where they can review and decide whether to engage with them. It’s as simple as using email.&lt;/p&gt;
&lt;h3&gt;
“Rizz Score”&lt;/h3&gt;
&lt;p&gt;
As mentioned earlier, the tools should focus on helping people, rather than hard selling. To make it easier for the user, &lt;a href=&quot;https://rizz.farm&quot;&gt;Rizz.farm&lt;/a&gt; calculates a score based on how relevant, helpful and spammy the generated or user edited response is. This ensures it always publishes high quality content for the target audience, making it a win-win.&lt;/p&gt;
&lt;h3&gt;
Self-Learning AI&lt;/h3&gt;
&lt;p&gt;
Last but not least, by using state of the art AI technologies that adapts to the users, &lt;a href=&quot;https://rizz.farm&quot;&gt;Rizz.farm&lt;/a&gt; is able to help them automate and scale their lead generation and reputation building with ease.&lt;/p&gt;
&lt;h2&gt;
Taking It For a Spin&lt;/h2&gt;
&lt;p&gt;
As soon as I’ve deployed &lt;a href=&quot;https://rizz.farm&quot;&gt;Rizz.farm&lt;/a&gt; to production, I started using it to promote &lt;a href=&quot;https://persumi.com&quot;&gt;Persumi&lt;/a&gt; and dare I say I am very happy and impressed with its results. Not only does it find relevant leads right away, it drafts up responses that are super helpful and relevant to the OP’s questions or posts, and is very subtle in pushing the “promotion” agenda. I know a lot of subreddits shun or outright ban self-promotion, so it’s extremely important to have helpful posts.&lt;/p&gt;
&lt;p&gt;
If you are looking for an affordable way to promote your product, service, event or anything really, please give &lt;a href=&quot;https://rizz.farm&quot;&gt;Rizz.farm&lt;/a&gt; a try. It comes with 7 days of free trial and 30 days of no-question-asked money back guarantee. For a limited time, you can also get 50% off for six months using the coupon code &lt;code class=&quot;inline&quot;&gt;LAUNCH2024&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
Happy lead generation and growth hacking everyone!&lt;/p&gt;
]]&gt;</content>
    <published>2024-01-06T13:10:47.679944Z</published>
    <link href="http://persumi.com/c/product-builders/u/fredwu/p/introducing-rizz-farm-an-ai-assisted-lead-generation-tool-for-reddit"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/c/product-builders/u/fredwu/p/introducing-rizz-farm-an-ai-assisted-lead-generation-tool-for-reddit</id>
    <title>Introducing Rizz.farm - An AI-Assisted Lead Generation Tool for Reddit</title>
    <updated>2024-01-06T13:10:47.679944Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
For a project I’m working on, we are looking at different AI OCR (Optical Character Recognition) options that would allow us to import documents with various layouts and extract relevant data from them with high enough accuracy. Due to the nature of these documents and the information contained in them, it is paramount that there would be an easy way for us to train the AI models using our documents.&lt;/p&gt;
&lt;p&gt;
Without revealing exactly what types of documents we are working with, as they’re commercially sensitive, the basic premise is to:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Import a document that might be one or more pages, the document itself may be high quality PDF, or low quality scanned documents that may or may not be skewed  &lt;/li&gt;
  &lt;li&gt;
Each page may or may not contain relevant information we want to extract  &lt;/li&gt;
  &lt;li&gt;
Relevant information may be structured in different ways, but usually in a tabular form (but each “line item” can either be horizontal or vertical)  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
In our case, a traditional OCR solution wouldn’t work as the documents often contain heaps of irrelevant data, and for the relevant data, it needs to understand the context in order to work out which data elements to extract, and how they relate to each other. Therefore, a machine learning based OCR solution that could adopt to our documents is highly desirable.&lt;/p&gt;
&lt;p&gt;
The success of the project is measured by the end results of imported data:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
  How much data is detected and recognised  &lt;/li&gt;
  &lt;li&gt;
  How accurate the recognised data elements are  &lt;/li&gt;
  &lt;li&gt;
  At a minimum, 80%+ accuracy is needed in order to save time in manual data entry, and to reduce/avoid incorrect data from being checked by a human operator before entering the system  &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
Available OCR Tools&lt;/h2&gt;
&lt;p&gt;
There are a number of OCR tools available, for our project the minimum criteria are:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
AI powered solution so it can be further developed and tweaked by us for our documents  &lt;/li&gt;
  &lt;li&gt;
A UI solution for data training, preferably easy to use so that non-technical team members can help with data training too  &lt;/li&gt;
  &lt;li&gt;
A quick and easy way to test and verify trained models  &lt;/li&gt;
  &lt;li&gt;
A solution that is compliant with the ISO standards relevant for us  &lt;/li&gt;
  &lt;li&gt;
Pay by usage pricing model that’s not cost prohibitive  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
The tools I have discovered and considered were:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
AWS Textract  &lt;/li&gt;
  &lt;li&gt;
Microsoft Azure Document Intelligence (formerly known as Azure Form Recognizer)  &lt;/li&gt;
  &lt;li&gt;
Google Cloud Document AI  &lt;/li&gt;
  &lt;li&gt;
Rossum.ai  &lt;/li&gt;
  &lt;li&gt;
Super.ai  &lt;/li&gt;
  &lt;li&gt;
Eden.ai  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
All six options are AI based OCR solutions. Amazon Web Services (AWS), Microsoft Azure and Google Cloud Platform (GCP) are the obvious “big three”, having their own AI models, whereas Super and Eden are aggregators that farms AI calls to providers like AWS, Azure and Google, and provides a middle layer that could potentially make the end result better (or worse). It’s unclear what model Rossum uses.&lt;/p&gt;
&lt;p&gt;
After some consideration, Rossum, Super and Eden were all taken off the table due to:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
  Rossum and Super not having transparent pricing  &lt;/li&gt;
  &lt;li&gt;
  Eden being an API aggregator built by a small team, it’s unclear on its usefulness and longevity  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
With the longevity of the company and product in mind, as well as transparent pricing and commercial support, AWS, Azure and Google remain to be the leading choices.&lt;/p&gt;
&lt;h2&gt;
The Big Three: AWS, Azure and Google&lt;/h2&gt;
&lt;h3&gt;
AWS Textract&lt;/h3&gt;
&lt;p&gt;
Out of the big three, AWS is our preferred vendor due to its market leading position as a cloud service provider, as well as our existing projects already using AWS.&lt;/p&gt;
&lt;p&gt;
However, upon closer inspection, it appears Textract &lt;a href=&quot;https://stackoverflow.com/questions/68044070/how-to-customise-aws-textract&quot;&gt;does not allow custom data training&lt;/a&gt;. Textract is provided “as-is” using Amazon’s pre-trained models, it does not allow customers like us to provide their own training data to improve on the detection and recognition of different types of documents.&lt;/p&gt;
&lt;p&gt;
AWS’ machine learning blog has published an article on “&lt;a href=&quot;https://aws.amazon.com/blogs/machine-learning/building-an-end-to-end-intelligent-document-processing-solution-using-aws/&quot;&gt;end-to-end intelligent document processing solution&lt;/a&gt;“ in 2020, however it does not explain in detail exactly what the capabilities are, and the solution requires a complex architecture that needs significant amount of time and effort to set up and maintain.&lt;/p&gt;
&lt;h3&gt;
Azure Document Intelligence&lt;/h3&gt;
&lt;p&gt;
Azure’s offering, formerly known as Form Recognizer, is an end-to-end solution that offers &lt;a href=&quot;https://learn.microsoft.com/en-au/azure/ai-services/document-intelligence/concept-custom&quot;&gt;custom data labelling and training&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
Google Cloud Document AI&lt;/h3&gt;
&lt;p&gt;
Google’s offering, similar to Azure’s, is also an end-to-end solution that offers &lt;a href=&quot;https://cloud.google.com/document-ai/docs/workbench/build-custom-processor&quot;&gt;custom data labelling and training&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
Comparison: Azure vs Google&lt;/h2&gt;
&lt;p&gt;
With the initial assessment done, we are left with Azure and Google, so let’s dive into the deep comparison of the two platforms.&lt;/p&gt;
&lt;p&gt;
We will compare the two platforms in several aspects:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
The ease of initial set up, i.e. how quickly can we get up and running right away  &lt;/li&gt;
  &lt;li&gt;
Whether a “base” pre-trained model is provided for documents with high enough accuracy so we save time in customisation and training  &lt;/li&gt;
  &lt;li&gt;
The detection and recognition – how much data is accurately detected and recognised  &lt;/li&gt;
  &lt;li&gt;
The custom training process, how easy is it to label custom data  &lt;/li&gt;
  &lt;li&gt;
How fast or slow the training is  &lt;/li&gt;
  &lt;li&gt;
The end result – provided with the same training dataset, which platform offers better results  &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
The Initial Setup&lt;/h3&gt;
&lt;h4&gt;
Microsoft Azure&lt;/h4&gt;
&lt;p&gt;
Azure’s initial set up is relatively straightforward. There is a four-step dialog window to set up the required Azure resources, etc. After which, documents can simply be uploaded for immediate consumption - labelling and training, which will be touched on in later sections.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/08d15780-8791-4671-b162-0261dc1bbf65.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/137edab2-4967-426a-b459-4f0760526274.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h4&gt;
Google Cloud&lt;/h4&gt;
&lt;p&gt;
Google works slightly differently in this case. To set up a project it’s very simple, there is literally only the “Processor name” to fill out. However, it does require a separate step to set up the storage bucket required.&lt;/p&gt;
&lt;p&gt;
Also, instead of allowing files to be uploaded within the same UI like Azure, Google requires files to be uploaded to the cloud storage bucket first, then imported into Document AI.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/123124bc-8fc5-4204-9cd8-50385496603f.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/a0eced5c-9c94-4a62-8322-3f9ca3eae8c1.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h3&gt;
Auto Labelling Data&lt;/h3&gt;
&lt;h4&gt;
Microsoft Azure&lt;/h4&gt;
&lt;p&gt;
“Auto-labelling” refers to the AI OCR’s ability to do the initial labelling automatically, to save time and effort in the manual labelling work. It can be seen as the starting point of training the AI model.&lt;/p&gt;
&lt;p&gt;
In this case, the Azure experience is significantly better. It allows files to be uploaded, then auto-labelled by “current”, “unlabelled” or “all” documents. This gives the flexibility to users (i.e. us) to choose when a document should be auto-labelled – as it does take a bit of time, sometimes if a document is not very well structured and would require more manual labelling anyway, then it’s best to skip the auto-label step.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/380e737f-29ba-4b37-bdc6-809e2b308cc3.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h4&gt;
Google Cloud&lt;/h4&gt;
&lt;p&gt;
Google on the other hand, has a more cumbersome process. We first need to upload the files to the storage bucket, as mentioned before. Then, we select the path/folder of the uploaded files, and the “auto-labelling” is enabled or disabled for the entire import. Also, sometimes the import takes a long time, whereas on Azure it’s always instant.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/5e4f3304-92b0-42de-bde1-fc1fd4eb9a06.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
More on the accuracy of auto-labelling later.&lt;/p&gt;
&lt;h3&gt;
Text Detection and Recognition&lt;/h3&gt;
&lt;p&gt;
A big part of OCR is obviously text detection and recognition. Even without training, a good OCR solution should detect and recognise clear text effortlessly.&lt;/p&gt;
&lt;p&gt;
During our initial research, we’ve found &lt;a href=&quot;https://cazton.com/blogs/executive/handwriting-recognition-azure-aws-gcp&quot;&gt;this article&lt;/a&gt; that compared the text recognition accuracy between AWS, Azure and Google. Their finding was that Azure and Google were comparable with a slight edge to Azure (Azure performed better on 2 out of 3 documents, and Google performed better on 1 out of 3 documents), both were way ahead of AWS in terms of accuracy.&lt;/p&gt;
&lt;p&gt;
Our own testing has somewhat mirrored their experience. We found that in general, Azure performed better in the number of detected text elements, as well as the correctness of these text.&lt;/p&gt;
&lt;p&gt;
On Azure, it’s very rare for text to be undetectable, whereas on Google, we’ve found several instances where the text is very clear, yet Google was unable to detect the text at all.&lt;/p&gt;
&lt;p&gt;
Similarly on accuracy, Azure has rarely detected clear text incorrectly, whereas on Google, a lot of times units such as &lt;code class=&quot;inline&quot;&gt;x10*9/L&lt;/code&gt; get recognised incorrectly despite all instances appearing similar in their appearance (i.e. very clear), and sometimes dashes won’t get recognised (e.g. &lt;code class=&quot;inline&quot;&gt;10.0 - 12.50&lt;/code&gt; gets recognised as &lt;code class=&quot;inline&quot;&gt;10.0 12.50&lt;/code&gt;).&lt;/p&gt;
&lt;h3&gt;
Custom Labelling&lt;/h3&gt;
&lt;p&gt;
During training, a big portion of time is spent on custom labelling, meaning we look for and assign text to our pre-defined data elements such as name and occupation - these are just fictional examples.&lt;/p&gt;
&lt;p&gt;
Overall, Azure and Google each has its strengths and weaknesses. Let’s go through them in detail.&lt;/p&gt;
&lt;h4&gt;
Auto Layout&lt;/h4&gt;
&lt;p&gt;
On Azure, there is a “Run layout” step that would recognise all the text elements, as well as tabular data in a given document.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/dfcad327-d48d-4a66-be65-8a4661ffa5af.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Once “run layout” is performed, all the recognised text are highlighted in yellow, giving a quick and easy overview of the usable data elements.&lt;/p&gt;
&lt;p&gt;
Google on the other hand, does not offer a similar function, therefore labelling needs to be performed with more effort, more on this later.&lt;/p&gt;
&lt;h3&gt;
Schema vs Schema-less&lt;/h3&gt;
&lt;p&gt;
One key difference between Azure and Google’s solutions, is that the Azure solution is schema-less whereas the Google solution is based on defined schemas.&lt;/p&gt;
&lt;p&gt;
On Azure, we can add a “field”, and then assign the field a type (string, number, etc). A field can then be renamed or re-assigned with a different type at any time.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/940cc894-20ba-4d22-8ecc-3b156adf1859.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/1514d2c8-1ed0-49ca-8669-68318db39e1a.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
There is a special field called “table field”, we can create tabular data using this field type.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/577d29d1-ff02-4eff-959c-70f5cd1a898b.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/9875641e-469a-4511-9230-fb949661f655.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Google works very differently. Instead of adding fields on the fly, Google requires a schema (a.k.a. field definitions) to be created first. Once a field is created, and data trained, it cannot be edited or deleted.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/00a08a47-6ff7-41a0-b970-9ece1642237a.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
One advantage of Google’s approach is a more definitive structure of OCR’ed data. When creating a label, not only do we have to choose a type (similar to Azure), we also can choose its occurrence logic, see the screenshot below.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/6390ae94-f3dc-4a96-8e5a-331f0e144032.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Despite this, we found that the schema-less approach on Azure offers far better versatility without being confined to a pre-defined schema, making training new revisions of models far easier.&lt;/p&gt;
&lt;h3&gt;
Labelling Data&lt;/h3&gt;
&lt;p&gt;
Most of the time spent on training, is the manual labelling of data elements on the documents. Azure’s approach in this case is significantly faster and more accurate, compared to Google’s.&lt;/p&gt;
&lt;h4&gt;
Microsoft Azure&lt;/h4&gt;
&lt;p&gt;
On Azure, because of the “run layout” step, all text elements are already detected. So labelling them as part of the test results is very simple, you click on the yellow elements to select the text you want, then click on the table cell on the right to assign them.&lt;/p&gt;
&lt;p&gt;
However, if somehow Azure detects the data incorrectly, there is no way for us to provide a correction. In this case, we could choose to either skip the cell, or simply assign the cell with the incorrect data – if more training data is provided over time then these edge cases will not have a big impact on the final accuracy of the model.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/d0a08e48-73a1-4757-9028-ef566773abe8.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
In the screenshot above, sensitive data elements are blacked out for the purpose of this blog post.&lt;/p&gt;
&lt;h4&gt;
Google Cloud&lt;/h4&gt;
&lt;p&gt;
On Google, this process is significantly more involved. Due to not having a “run layout”-like step, we need to draw over the text to select it, and there’s no way to tell whether Google has detected the text before using the tool, so sometimes you’ll draw over some text and get an error saying, “Cannot create labels with empty values”.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/f858e169-3f1b-47b5-a9cf-f7c5dc836d83.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Again, in the screenshot above, sensitive data elements are blacked out for the purpose of this blog post.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/f057079c-bd70-490d-9e8f-3c72bdd5fd8f.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
And because we can’t simply click on the highlighted text already detected, like on Azure, this makes the whole process significantly longer as we have to carefully draw over the text to ensure the boundary is accurate (for training purpose), sometimes we have to zoom in and reposition the document for accurate drawing.&lt;/p&gt;
&lt;p&gt;
The schema assignment is also less intuitive compared to Azure’s. Instead of having a table to easily see tabular data, Google’s schema is more like a series of key-value pairs, making glancing at assigned data much harder, and the key-value elements are ordered alphabetically, instead of logically like on Azure, making assigning data unnatural.&lt;/p&gt;
&lt;p&gt;
The one advantage of Google’s approach though, is that when the recognised data is incorrect, we can manually override it with our own correct data. However, after using both solutions for a while, we’ve found that Google’s has a much higher error rate compared to Azure’s to begin with.&lt;/p&gt;
&lt;h3&gt;
Auto-Label Accuracy&lt;/h3&gt;
&lt;p&gt;
In order to assess the auto-labelling accuracy, we have trained several of our documents with varying quality and layout. We then uploaded a new document with a layout that has not been trained.&lt;/p&gt;
&lt;p&gt;
The results are very telling.&lt;/p&gt;
&lt;h4&gt;
Microsoft Azure&lt;/h4&gt;
&lt;p&gt;
Azure has pretty much labelled things perfectly - all the data elements were detected correctly and within the correct contexts. The second comment block is also detected correctly.&lt;/p&gt;
&lt;p&gt;
In this case, the Azure auto-label has only missed the first comment block.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/27360181-ec22-46df-abc1-215124f568cf.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
The screenshot above is blurred to protect the sensitive nature of it.&lt;/p&gt;
&lt;h4&gt;
Google Cloud&lt;/h4&gt;
&lt;p&gt;
Google’s result unfortunately falls short of expectation.&lt;/p&gt;
&lt;p&gt;
As seen in the screenshot below, Google has missed several data elements, and incorrectly detected elements that should not be part of the result. Again, the screenshot is blurred to protect the sensitive nature of it.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/b0fdd124-69a1-4df6-8008-401c03cc02eb.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
This suggests two possible things about Google’s OCR solution:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
  The default text recognition is poor, therefore it’s missing data elements even though they are clearly presented, in the same way as other elements  &lt;/li&gt;
  &lt;li&gt;
  The neural network for language models is poor, therefore it incorrectly detected text that shouldn’t be part of any result  &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
Auto-Label Result Verification&lt;/h3&gt;
&lt;h4&gt;
Microsoft Azure&lt;/h4&gt;
&lt;p&gt;
Once Azure’s performed the auto-labelling, it’s very easy to check the results, as seen in the blurred screenshot below, we simply look at the table to ensure the correctness. However, as mentioned before, we can only correct detected regions of text, not the content itself.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/28acf5ba-e055-4269-989b-bc144a97f0a0.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h4&gt;
Google Cloud&lt;/h4&gt;
&lt;p&gt;
On Google, it is more involved. As seen in the blurred screenshot below, it requires a lot of scrolling to find the auto-labelled data. Alternatively, we can hover over the highlighted text to look at the auto-labelled text, although in this case, the pop-up gets in the way of other data which makes it an annoyance at times.&lt;/p&gt;
&lt;p&gt;
Curiously, despite having a schema, and setting certain elements to “required once” (per schema), Google still went ahead and falsely detected multiple results per schema.&lt;/p&gt;
&lt;p&gt;
But as mentioned before, Google does offer the ability to manually correct the text content, which is a big plus.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/6602041a-1c8a-4eb4-8ccd-d0c6008ca98c.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h3&gt;
Data Training Speed&lt;/h3&gt;
&lt;p&gt;
We have found training to be significantly faster on Azure.&lt;/p&gt;
&lt;p&gt;
With about 45 documents, it takes about 30 minutes to train on Azure.&lt;/p&gt;
&lt;p&gt;
Google takes about twice as long, about an hour to train, and another several minutes to deploy the trained model. Azure does not need a separate deployment step.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/9ef5c376-5136-4e88-a989-0c1e5e244833.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Interestingly, on Google, training a model requires having enough test data. So documents need to be split into training and test groups. It is unclear whether the test data is used for actual training.&lt;/p&gt;
&lt;p&gt;
There is also a required number of labelled elements (10 minimum, 50 recommended) before a model can be trained. Azure has no such limitations.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/images/0b1b9f24-76d5-4b5b-8997-6982e0d17fe5.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h3&gt;
Data Regions and Compliance&lt;/h3&gt;
&lt;p&gt;
While both Azure and Google are compliant to many standards (ISO, GDPR, etc), Google’s Document AI can only be hosted out of their US or EU regions. Whereas Azure has no such limitation and can be hosted in any of their available regions.&lt;/p&gt;
&lt;p&gt;
This has a side effect on the performance of UI operations. Azure is very quick and snappy due to it being deployed in our local AU region, whereas Google due to it being deployed in the US region, is a little slow every time you open and close a document for example.&lt;/p&gt;
&lt;h2&gt;
Conclusion&lt;/h2&gt;
&lt;p&gt;
With the in-depth analysis done, it is no surprise that in the end we went with Microsoft Azure’s Document Intelligence for our AI OCR needs. I hope these findings are useful to other people too.&lt;/p&gt;
]]&gt;</content>
    <summary>The article compares six AI OCR tools: AWS Textract, Microsoft Azure Document Intelligence, Google Cloud Document AI, Rossum.ai, Super.ai and Eden.ai. It then does a deep comparison between Azure and Google, the two leading choices, in several aspects: initial setup, auto labelling data, text detection and recognition, custom labelling, auto-label accuracy, auto-label result verification, data training speed, data regions and compliance. The article concludes that Azure is the better choice.</summary>
    <published>2023-09-17T03:44:24.872189Z</published>
    <link href="http://persumi.com/c/product-builders/u/fredwu/p/comparison-of-ai-ocr-tools-microsoft-azure-ai-document-intelligence-google-cloud-document-ai-aws-textract-and-others"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/c/product-builders/u/fredwu/p/comparison-of-ai-ocr-tools-microsoft-azure-ai-document-intelligence-google-cloud-document-ai-aws-textract-and-others</id>
    <title>Comparison of AI OCR Tools: Microsoft Azure AI Document Intelligence, Google Cloud Document AI, AWS Textract and Others</title>
    <updated>2023-09-17T03:44:24.872189Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
A few weeks ago I &lt;a href=&quot;welcome-to-persumi-a-modern-platform-for-content-creation&quot;&gt;soft launched&lt;/a&gt; an MVP - you are looking at it right now.&lt;/p&gt;
&lt;p&gt;
In this post I’ll talk about the features, the tech stack and the globally distributed infrastructure behind building this MVP, and of course, with a sprinkle of learnings too.&lt;/p&gt;
&lt;h2&gt;
The “MVP”&lt;/h2&gt;
&lt;p&gt;
Deciding what makes up an “MVP” is always interesting - I’ve heard some saying if the product &lt;em&gt;isn’t&lt;/em&gt; embarrassing you’re releasing it too late, and also if the product &lt;em&gt;is&lt;/em&gt; embarrassing, you’re not gonna make it.&lt;/p&gt;
&lt;p&gt;
For me, I’ve always had the idea of building all the essential features as part of the MVP, with one or two “hero” features that would differentiate the product from the competitions, and then build out more premium features over time.&lt;/p&gt;
&lt;p&gt;
If you can’t already tell, Persumi is a content creation platform with some social networking features. It may sound bland, but what I believe makes it stand out, is the desire of putting the focus back onto the content, rather than the VC-fuelled, ever increasing appetite for more ads and user hostile features.&lt;/p&gt;
&lt;h2&gt;
The Long Nights and Weekends&lt;/h2&gt;
&lt;p&gt;
There is no magic beans for productivity - especially when I have a full time job. Working on a side hustle means giving up on almost all social and entertainment activities. It’s not for everyone, but I didn’t mind it too much. Being an introvert definitely helped - I was happy to see night by night the MVP gradually taking shape to become more and more real.&lt;/p&gt;
&lt;p&gt;
Looking back, I spent about three months to build out most of the MVP, then another week or two on infrastructure, and another week or two for polishing, all whilst having a full time job.&lt;/p&gt;
&lt;p&gt;
It’s been a journey, I’m glad that it “only” took me 3-4 months to get to this stage, as initially I estimated for a 6+ months MVP build.&lt;/p&gt;
&lt;h2&gt;
The Features&lt;/h2&gt;
&lt;p&gt;
With all that in mind, I’ve set out to build the essential features that make a blogging and social networking platform:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Short form content like a tweet  &lt;/li&gt;
  &lt;li&gt;
Long form content like a blog post or a book chapter  &lt;/li&gt;
  &lt;li&gt;
RSS feeds  &lt;/li&gt;
  &lt;li&gt;
Communities similar to forums and sub-reddits  &lt;/li&gt;
  &lt;li&gt;
Direct messaging between users  &lt;/li&gt;
  &lt;li&gt;
A voting (like/dislike) system  &lt;/li&gt;
  &lt;li&gt;
A bunch of CRUD glue pieces to make all these things work  &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
The “Hero” Features&lt;/h3&gt;
&lt;p&gt;
Beyond these seemly unremarkable features, I’ve also had in mind two key features that would differentiate the platform from the rest:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
The “persona” concept, whereby each user is allowed to have multiple personas to hold different content or topics of interest, e.g. a persona for professional stuff, a persona for gaming stuff and a persona for travel stuff, etc  &lt;/li&gt;
  &lt;li&gt;
AI generated audio content for text (also known as Text-to-Speech)  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
These two “hero” features are what drove me to build Persumi in the first place. Together, they solve some very real pain points for me, namely:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Following specific topics of interest from people is difficult, with the algorithms taking over people’s home feeds, there are simply way too much noise, thanks to VC-fuelled “user engagement” metrics  &lt;/li&gt;
  &lt;li&gt;
Content consumption on the go (e.g. during commute or during workouts, etc) is becoming more and more prevalent, but the traditional platforms haven’t adopted to this new lifestyle other than shoving short form content down our throats  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
There’s also a third “hero” feature: the Aura system. Unlike the upvote/downvote or like/dislike buttons in many social platforms that only serve the algorithm to push more content to you, Persumi’s Aura system keeps track of user’s content quality over time, and would punish the low quality content and promote high quality content using visual cues - lower quality content has a much lower contrast making them easy to ignore. In the age of social media, self-curating content becomes essential to keep a platform healthy, engaging and usable.&lt;/p&gt;
&lt;h3&gt;
The Non-MVP Features, a.k.a. The Future&lt;/h3&gt;
&lt;p&gt;
There are many features that didn’t make the MVP cut, most of these are value-added features that will eventually make their way into paid subscriptions - if Persumi gains enough traction to attract users who don’t mind paying for premium features.&lt;/p&gt;
&lt;p&gt;
A prime example of such paid features is ones that help users monetise their content, e.g. ad revenue sharing and paid subscribers (like Patreon).&lt;/p&gt;
&lt;p&gt;
I also have the ambition of building out Persumi’s features so it can eventually compete against the likes of LinkedIn and Tinder.&lt;/p&gt;
&lt;p&gt;
Wouldn’t it be better for the world to have a platform like Persumi that doesn’t focus on dark patterns and exploiting users? 😉&lt;/p&gt;
&lt;h2&gt;
The Tech Stack&lt;/h2&gt;
&lt;p&gt;
Over the past decade or so I’ve mainly worked with two tech stacks: Ruby and Elixir. So naturally, Persumi was going to be built using one of them.&lt;/p&gt;
&lt;p&gt;
After some consideration, I’ve decided to go ahead with Elixir, the main reasons were:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Elixir and Erlang/OTP support distributed systems out of box  &lt;/li&gt;
  &lt;li&gt;
I’ve been writing more Elixir than Ruby lately, so I’m more productive in Elixir  &lt;/li&gt;
  &lt;li&gt;
I really wanted to try and use &lt;a href=&quot;https://github.com/phoenixframework/phoenix_live_view&quot;&gt;LiveView&lt;/a&gt; in production  &lt;/li&gt;
  &lt;li&gt;
I prefer Phoenix’s application architecture more than Rails’  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
On top of Elixir, I’ve decided early on a few other things to go with it:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Tailwind for CSS  &lt;/li&gt;
  &lt;li&gt;
Postgres for database, preferably a serverless option  &lt;/li&gt;
  &lt;li&gt;
A search engine  &lt;/li&gt;
  &lt;li&gt;
An easy to maintain infrastructure that doesn’t cost an arm and a leg  &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
Elixir&lt;/h3&gt;
&lt;p&gt;
I first discovered Elixir in 2014 while I was still actively involved in the Ruby and Rails communities, but it was two years later that I had the opportunity to really dive into it. I built a few open source libraries to help me learn Elixir and OTP:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
&lt;a href=&quot;https://github.com/fredwu/crawler&quot;&gt;Crawler&lt;/a&gt; - a high performance web scraper.  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://github.com/fredwu/opq&quot;&gt;OPQ: One Pooled Queue&lt;/a&gt; - a simple, in-memory FIFO queue with back-pressure support, built for Crawler.  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://github.com/fredwu/simple_bayes&quot;&gt;Simple Bayes&lt;/a&gt; - a Naive Bayes machine learning implementation. Hey, I was doing machine learning before it was mainstream! 😆  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://github.com/fredwu/stemmer&quot;&gt;Stemmer&lt;/a&gt; - an English (Porter2) stemming implementation, built for Simple Bayes.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
With a decent amount of experience building Phoenix web apps over the years, I unfortunately never had the opportunity to use LiveView…&lt;/p&gt;
&lt;p&gt;
I guess that’s finally changed now.&lt;/p&gt;
&lt;p&gt;
LiveView has been amazing, not only does it drastically reduce the amount of front-end code you have to write, make the entire web app feel super snappy, but it also virtually eliminates any front-end and back-end code/logic duplications. It is such an awesome piece of technology that improves both the user experience and the developer experience.&lt;/p&gt;
&lt;h4&gt;
Petal Pro&lt;/h4&gt;
&lt;p&gt;
During my initial tech research, I came across &lt;a href=&quot;https://petal.build/&quot;&gt;Petal Pro&lt;/a&gt; which is a boilerplate starter template built on top of Phoenix. It handles things like user authentication which almost every web app needs, but is somewhat tedious to build.&lt;/p&gt;
&lt;p&gt;
Petal Pro isn’t free but it ended up saving me so much time. I also started contributing small bug fixes and features to it too. If you are about to build something in Phoenix, check it out!&lt;/p&gt;
&lt;h3&gt;
Tailwind CSS&lt;/h3&gt;
&lt;p&gt;
As I kept progressing my career, there have been fewer and fewer opportunities for me to write front-end and CSS code. Last time I rebuilt my blog, I used &lt;a href=&quot;https://bulma.io/&quot;&gt;Bulma&lt;/a&gt; - that was 2019. Since then Tailwind has gained a lot more traction, so I wanted an excuse to try finally give it a shot.&lt;/p&gt;
&lt;p&gt;
There is a debate on how many new things you should try for building your MVP - the more you have to learn, the slower your MVP progresses. That said, given CSS is reasonably straightforward, I figured it wouldn’t slow me down too much, if anything, Tailwind’s flexibility might just eventually make up any time lost in learning.&lt;/p&gt;
&lt;p&gt;
I’m happy to report that it is indeed true - by using Tailwind, it became significantly easier for me to customise my components and elements. I can see why it became so popular. It’s not for everyone, but I like it.&lt;/p&gt;
&lt;h3&gt;
Postgres&lt;/h3&gt;
&lt;p&gt;
Choosing Postgres as a database was a no brainer, given how popular and versatile it is. I did briefly consider NoSQL options like DynamoDB but quickly wrote them off as I needed an RDBMS to get things off the ground quickly, and the DB is unlikely to be the bottleneck for a long time anyway.&lt;/p&gt;
&lt;p&gt;
In Elixir, the &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.html&quot;&gt;Ecto library&lt;/a&gt; works wonders for Postgres.&lt;/p&gt;
&lt;p&gt;
Later in the post I’ll touch on how I deploy and run Postgres in production.&lt;/p&gt;
&lt;h3&gt;
Search Engine&lt;/h3&gt;
&lt;p&gt;
For a search engine, my requirements were:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
The ability to search across multiple fields of a schema  &lt;/li&gt;
  &lt;li&gt;
The ability to rank them  &lt;/li&gt;
  &lt;li&gt;
The ability to have typo tolerance, word stemming and other similar language features to make search more intuitive  &lt;/li&gt;
  &lt;li&gt;
The ability to search multiple languages, including CJK (Chinese/Japanese/Korean) characters  &lt;/li&gt;
  &lt;li&gt;
Simple to run  &lt;/li&gt;
  &lt;li&gt;
Cheap to run  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Using Postgres’ full text search was probably going to be the simplest but it doesn’t offer all the functionalities I need without a bunch of set up and manual SQL queries so I didn’t pursue it.&lt;/p&gt;
&lt;p&gt;
Elasticsearch on the other hand, offers good search functionalities but takes a bit of effort to set up and maintain, and can be costly to run.&lt;/p&gt;
&lt;p&gt;
After doing some more research, I found the following three options that would fit my needs:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
&lt;a href=&quot;https://www.algolia.com/&quot;&gt;Algolia&lt;/a&gt;  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://www.meilisearch.com/&quot;&gt;Meilisearch&lt;/a&gt;  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://typesense.org/&quot;&gt;Typesense&lt;/a&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Both Meilisearch and Typesense are open source, with commercial SaaS offerings, whilst Algolia is SaaS-only.&lt;/p&gt;
&lt;p&gt;
It’s been an interesting journey. I started with Typesense as I liked what I read, but I quickly discovered that it doesn’t search Chinese characters properly.&lt;/p&gt;
&lt;p&gt;
I then turned to Meilisearch. I especially liked the fact that they offered a generous free tier SaaS to get you off the ground running. Spoiler: during my implementation they did a bait and switch and removed the free tier.&lt;/p&gt;
&lt;p&gt;
At the time the Elixir support for Meilisearch wasn’t up to date, so I ended up &lt;a href=&quot;https://github.com/nutshell-lab/meilisearch-ex/pull/5&quot;&gt;contributing to a community library&lt;/a&gt; to add the features I needed.&lt;/p&gt;
&lt;p&gt;
Curious timing, after Meilisearch removed their free tier, I discovered that even though they officially support searching for Chinese characters, the implementation wasn’t perfect. I found some edge cases where characters weren’t detected properly, making the search results unreliable.&lt;/p&gt;
&lt;p&gt;
So, my last hope was Algolia. Despite them being the more expensive option out of the three, it does offer a free tier. It turns out, their search results for Chinese characters were much better than Meilisearch’s. Luckily, re-implementing the search from Meilisearch to Algolia didn’t take too much effort, it was pretty much done in one night.&lt;/p&gt;
&lt;h3&gt;
Infrastructure&lt;/h3&gt;
&lt;p&gt;
Early on during the development I’d already determined I wanted to try &lt;a href=&quot;https://fly.io/&quot;&gt;Fly&lt;/a&gt; and &lt;a href=&quot;https://neon.tech/&quot;&gt;Neon&lt;/a&gt;, for web and DB, respectively.&lt;/p&gt;
&lt;p&gt;
I am in no way associated with either company, I was curious about Fly due to its tie-in with the Elixir community (Phoenix Framework’s author Chris McCord works there), and Neon due to its serverless nature.&lt;/p&gt;
&lt;h4&gt;
Globally Distributed Infra&lt;/h4&gt;
&lt;p&gt;
With Fly, the infrastructure automatically becomes globally distributed as soon as I started provisioning servers in more than one region. As of the time of writing, Persumi is deployed to US West, Australia and EU.&lt;/p&gt;
&lt;p&gt;
Despite being simple to use, making Fly work initially actually took quite a bit of finessing due to its incomplete official documentation and flakiness. Some of the services were having issues during the course of my MVP development. Worse, they don’t report (or sometimes even acknowledge) the issues unless they are region-wide outages. To this date, I believe their blue/green deployment strategy which was recently introduced, is still buggy, I often have to use their rolling deployment strategy instead. Deployment logs were provided to Fly but I think they’re too busy with other things…&lt;/p&gt;
&lt;p&gt;
Still, I’m sticking with them for now due to the ease of use after the initial hurdle, and their globally distributed infrastructure without asking for my kidney.&lt;/p&gt;
&lt;p&gt;
To augment Fly’s web servers, I also use Cloudflare’s &lt;a href=&quot;https://www.cloudflare.com/application-services/products/cdn/&quot;&gt;CDN&lt;/a&gt; as well as &lt;a href=&quot;https://www.cloudflare.com/developer-platform/r2/&quot;&gt;R2&lt;/a&gt; to serve asset files and audio files.&lt;/p&gt;
&lt;p&gt;
Funny tangent, initially I used &lt;a href=&quot;https://bunny.net/&quot;&gt;Bunny&lt;/a&gt; for asset files and CDN, as I misread Cloudflare’s terms and thought I couldn’t serve audio files from Cloudflare. Bunny worked okay but their dashboard for some reason was painfully slow - not a good look for a CDN company. Like the search engine switch, it didn’t take me too long to switch over to Cloudflare.&lt;/p&gt;
&lt;h4&gt;
Serverless Postgres&lt;/h4&gt;
&lt;p&gt;
There are a few options to run Postgres:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
Run on a standard server for maximum portability, but it requires more server maintenance overhead  &lt;/li&gt;
  &lt;li&gt;
Run on AWS RDS/Aurora or a similar managed service, easy but can be costly  &lt;/li&gt;
  &lt;li&gt;
Run on a serverless option such as Aurora Serverless or Neon  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
For my use case, I think option 2 or 3 are better fitting. As I mentioned earlier, I started the experiment with &lt;a href=&quot;https://neon.tech/&quot;&gt;Neon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Neon worked well initially, until I started deploying Fly instances in multiple regions. Due to Neon being only available in one region (I chose US West), and I live in Australia, the round trips between Fly’s Australian instance and Neon’s US instance were a show stopper - especially when complex DB transactions were involved. Actions sometimes took &lt;em&gt;seconds&lt;/em&gt; to complete, yikes.&lt;/p&gt;
&lt;p&gt;
Despite Fly not offering a managed Postgres service, I ended up trying it anyway due to its &lt;a href=&quot;https://fly.io/docs/postgres/advanced-guides/high-availability-and-global-replication/&quot;&gt;distributed nature&lt;/a&gt;. After incorporating &lt;a href=&quot;https://github.com/superfly/fly_postgres_elixir&quot;&gt;Fly Postgres&lt;/a&gt; in the app, all DB operations immediately became more responsive. Paired with LiveView, it feels like running the application locally.&lt;/p&gt;
&lt;p&gt;
The current Persumi infra looks like:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
1 x Fly instance in US West, always on  &lt;/li&gt;
  &lt;li&gt;
1 x Fly instance in Australia, auto-shutdown when there’s no traffic  &lt;/li&gt;
  &lt;li&gt;
1 x Fly instance in Netherlands, auto-shutdown when there’s no traffic  &lt;/li&gt;
  &lt;li&gt;
1 x Fly Postgres writer instance in US West, always on  &lt;/li&gt;
  &lt;li&gt;
1 x Fly Postgres read replica instance in Australia, always on  &lt;/li&gt;
  &lt;li&gt;
1 x Fly Postgres read replica instance in Netherlands, always on  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
With this setup, I think I’m quite happy with the cost and scalability balance - it costs ~$20/m to run, with the potential of both vertical and horizontal scaling with ease.&lt;/p&gt;
&lt;h2&gt;
The Missteps&lt;/h2&gt;
&lt;p&gt;
The search engine and CDN swaps mentioned earlier certainly took away some of my time, but they were nothing compared to a major misstep I encountered.&lt;/p&gt;
&lt;p&gt;
And that was:  the choice of how machine learning is done.&lt;/p&gt;
&lt;p&gt;
Let me explain.&lt;/p&gt;
&lt;h3&gt;
Machine Learning, and Inference&lt;/h3&gt;
&lt;p&gt;
Even before I started the first line of code, I already painted a picture in my head on the machine learning needed: a TTS (text-to-speech) model that I could run inference locally on the instance.&lt;/p&gt;
&lt;p&gt;
The reason being I believed it was the more flexible approach to gradually improve the inference  and therefore the end result by training my own AI models over time.&lt;/p&gt;
&lt;p&gt;
Given I didn’t want to rent expensive GPU instances, I opted for fast TTS models that could do near real-time inference on CPUs. I used &lt;a href=&quot;https://github.com/coqui-ai/TTS&quot;&gt;Coqui TTS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
The resulting out-of-box audio wasn’t great, but I kept pressing on.&lt;/p&gt;
&lt;p&gt;
The show stopper came when it was time to deploy everything onto Fly. Due to Fly’s architecture (they deploy small-ish Docker images, &lt; 2GB each, onto their global network), I struggled to keep the Docker image file small enough to be able to deploy. With Coqui TTS, I would need Python and all the dependencies that resulted in a Docker image around 4-5GB in size.&lt;/p&gt;
&lt;p&gt;
With my tunnel vision, I then chose to offload the entire Python and Coqui TTS dependency tree onto Fly’s &lt;a href=&quot;https://fly.io/docs/reference/volumes/&quot;&gt;persistent volumes&lt;/a&gt;. I knew it wasn’t a great option, as that meant my infrastructure (other than the database) was no longer immutable.&lt;/p&gt;
&lt;p&gt;
Sometimes it’s necessary to take a step back, re-evaluate, and then press on in a different direction. Which thankfully I did.&lt;/p&gt;
&lt;p&gt;
The new direction is quite simple really: instead of performing inference locally, use an external service instead.&lt;/p&gt;
&lt;p&gt;
After doing a quick comparison between the offerings from AWS, Azure and GCP, I ended up using &lt;a href=&quot;https://cloud.google.com/text-to-speech&quot;&gt;Google’s TTS&lt;/a&gt;. Honestly I think I would’ve been happy with any of the options, they all seem to have decent neural based TTS.&lt;/p&gt;
&lt;p&gt;
In hindsight, these giant corporations have much more resources and expertise to train better models than I ever could on my own.&lt;/p&gt;
&lt;p&gt;
The end result:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
The TTS sounds significantly better than before  &lt;/li&gt;
  &lt;li&gt;
It’s just as cheap to run (Google offers a certain amount of free TTS API calls per month)  &lt;/li&gt;
  &lt;li&gt;
It no longer needs complex Python calls and FFmpeg calls to make local TTS work  &lt;/li&gt;
  &lt;li&gt;
The Fly infrastructure is simple and immutable again  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
In hindsight, I never should’ve even entertained the idea of running ML locally on CPUs, no matter how simple and efficient a model might be.&lt;/p&gt;
&lt;p&gt;
That said, with TTS, it wasn’t as simple as just calling the APIs and getting the perfect resulting audio back. Some pre and post processing were needed, but that’s a topic for another time.&lt;/p&gt;
&lt;h3&gt;
More Machine Learning&lt;/h3&gt;
&lt;p&gt;
The cherry on top - now that Google’s APIs were integrated into the app, I ended up also using Google’s &lt;a href=&quot;https://ai.google/discover/palm2/&quot;&gt;PaLM 2&lt;/a&gt; to do text summarisation (it was initially done locally too) as well as for a ChatGPT-like AI prompt service, to power Persumi’s AI writing assistance feature.&lt;/p&gt;
&lt;h2&gt;
The Closing&lt;/h2&gt;
&lt;p&gt;
If you read this far, thank you! I hope you enjoyed reading (or listening) to this post. Please look around and kick tyres, I would love your feedback on how to improve Persumi.&lt;/p&gt;
&lt;p&gt;
Sign up for an account if you haven’t already, and leave a comment if you have any questions. Until next time!&lt;/p&gt;
]]&gt;</content>
    <published>2023-08-09T09:28:12.135201Z</published>
    <link href="http://persumi.com/c/persumi/u/fredwu/p/how-i-built-a-mostly-feature-complete-mvp-in-3-months-whilst-working-full-time"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/c/persumi/u/fredwu/p/how-i-built-a-mostly-feature-complete-mvp-in-3-months-whilst-working-full-time</id>
    <title>How I Built a Mostly Feature-Complete MVP in 3 Months Whilst Working Full-Time</title>
    <updated>2023-08-09T09:28:12.135201Z</updated>
  </entry>
  <entry>
    <summary>Some changes to improve the user onboarding experience are coming. There will be a guided tour to explain the concepts of personas and expressions better. Stay tuned!</summary>
    <published>2023-07-22T00:33:58.092811Z</published>
    <link href="http://persumi.com/c/persumi/u/fredwu/p/wy4q4xj7c2wz56i4q7t63jej6"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/c/persumi/u/fredwu/p/wy4q4xj7c2wz56i4q7t63jej6</id>
    <title>Some changes to improve the user onboarding experience are coming. There will be a guided tour to explain the concepts of personas and expressions better. Stay tuned!</title>
    <updated>2023-07-22T00:33:58.092811Z</updated>
  </entry>
  <entry>
    <summary>Hey, welcome! If you&apos;re also building your own product I&apos;d love to hear your stories. 😊</summary>
    <published>2023-07-17T04:56:31.876027Z</published>
    <link href="http://persumi.com/c/product-builders/u/fredwu/p/nepd2vrb2nb8vkh5feo98yv99"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/c/product-builders/u/fredwu/p/nepd2vrb2nb8vkh5feo98yv99</id>
    <title>Hey, welcome! If you&apos;re also building your own product I&apos;d love to hear your stories. 😊</title>
    <updated>2023-07-17T04:56:31.876027Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Like many software engineers and indie hackers, I dream of and experiment with different product ideas every now and then.&lt;/p&gt;
&lt;p&gt;
Five years ago, in 2018, I toyed with an idea where &lt;strong&gt;personas&lt;/strong&gt; and &lt;strong&gt;expressions&lt;/strong&gt; are the enablers for showcasing people’s talents and interests.&lt;/p&gt;
&lt;p&gt;
I called it, &lt;strong&gt;Persumi&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
The Origin&lt;/h2&gt;
&lt;p&gt;
The original idea was to break the pattern of modern day social media platforms like Facebook, Instagram and Twitter, where every person is hidden behind a two-dimensional facade. Words and photos are often used to skew the way people see who we are.&lt;/p&gt;
&lt;p&gt;
After a while, everything becomes bland as information is being overloaded and people’s attention span starts to decrease drastically. The signal to noise ratio is so low that more and more algorithms and tracking are invented to help curate content so advertisers would keep spending their dime on those social media platforms.&lt;/p&gt;
&lt;p&gt;
It’s a vicious cycle. There is now even a term for it: &lt;a href=&quot;https://knowyourmeme.com/memes/enshittification&quot;&gt;&lt;strong&gt;enshittification&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
Enshittification is when an online platform becomes more monetized and less user-oriented the longer it lasts.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
The Goals&lt;/h2&gt;
&lt;p&gt;
With Persumi, personas and expressions are the key concepts to modernise content creation, curation and consumption in a simple yet meaningful way.&lt;/p&gt;
&lt;p&gt;
I set out a few goals for Persumi, in the hope to change the social media landscape for the better:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Find ways to encourage the creation and consumption of long form content  &lt;/li&gt;
  &lt;li&gt;
Make it easy for people to create vastly different topics without confusing their audiences  &lt;/li&gt;
  &lt;li&gt;
Make it easy for people to follow different topics, rather than relying on following users  &lt;/li&gt;
  &lt;li&gt;
Allow people to decorate their pages with their own personality and style via HTML + CSS, &lt;em&gt;bring back &lt;a href=&quot;https://en.wikipedia.org/wiki/Myspace&quot;&gt;MySpace&lt;/a&gt;!&lt;/em&gt;  &lt;/li&gt;
  &lt;li&gt;
An API-driven, open marketplace that allows developers to build and extend advanced functionalities  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Some example use cases outlined in my original “pitch deck”:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;social personas&lt;/strong&gt; for friends, family, work…  &lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;interest personas&lt;/strong&gt; as a gamer, traveller, photographer…  &lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;functional personas&lt;/strong&gt; for travel plan, marketing campaign…  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
It kind of makes sense, but there is one problem.&lt;/p&gt;
&lt;h2&gt;
The Problem&lt;/h2&gt;
&lt;p&gt;
The age-old “chicken-and-egg” problem of starting a platform that thrives on user-generated content - how do I get early-adopter users without a decent amount of users and content already?&lt;/p&gt;
&lt;p&gt;
These goals might be nice, but they probably aren’t enough to convince users to drop what they are used to for something experimental and without users or traction.&lt;/p&gt;
&lt;p&gt;
So I parked this idea…&lt;/p&gt;
&lt;h2&gt;
Revisit the Problem&lt;/h2&gt;
&lt;p&gt;
A few years have gone by, and with a whole pandemic thrown into the mix, this Persumi idea has all of a sudden awakened me yet again.&lt;/p&gt;
&lt;p&gt;
In early 2023, after wrapping up my last freelancing project I decided to tackle Persumi’s chicken-and-egg problem once more.&lt;/p&gt;
&lt;p&gt;
I reflected on how my own content consumption behaviour has changed over the past few years. Instead of reading physical books, e-books or blogs, I consume more and more podcasts and audio-books. Instead of consuming content fully concentrated, I often consume them while on the go or during my workouts.&lt;/p&gt;
&lt;p&gt;
Then a light bulb moment occurred - I don’t &lt;em&gt;need&lt;/em&gt; to build a massive social network platform to compete with the likes of Twitter. I just need to build a “simple” blogging platform for my own use and my own content. If I find it useful, other people will find it useful too. It doesn’t need to go viral, it just needs to be steadly discovered by like-minded people who share my frustration and vision in content creation and consumption.&lt;/p&gt;
&lt;h2&gt;
Iterate the Idea&lt;/h2&gt;
&lt;p&gt;
With a few more years of experience of leading teams and building products under my belt, I thought about how to iterate on the original idea, and came up with three main “twists” if you will:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
Long form text content needs to be consumable in audio form, to suit the modern lifestyle  &lt;/li&gt;
  &lt;li&gt;
An API-driven open marketplace is way too ambitious for a new platform, build the core concept first, then iterate  &lt;/li&gt;
  &lt;li&gt;
An aura system that helps in both curating content as well as driving away bad actors  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
The rest of the original goals are still as relevant as before. In hindsight, these are pretty obvious product direction pivots, the result is an MVP that builds on a simple core concept - &lt;strong&gt;to turn long form text content into audio&lt;/strong&gt;. And to make it easy to organically grow the platform and audience.&lt;/p&gt;
&lt;h2&gt;
Build a Sustainable Business&lt;/h2&gt;
&lt;p&gt;
With the chicken-and-egg problem behind me, it’s time for me to think about how to build a sustainable business to support my product ambition.&lt;/p&gt;
&lt;p&gt;
Interestingly, in Feb/March 2023, I was thinking about what the VC funding model did to the social media platforms we all love and hate, and why it’s a bad idea. The fact that then &lt;em&gt;during&lt;/em&gt; the short few months of developing Persumi, both Twitter and Reddit seemed to be actively imploding themselves just proved my initial hypothesis:&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;To build a truly user-oriented platform, it needs to be built for users and content creators, rather than investors and advertisers&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;
That means, no obnoxious ads, no annoying pop ups, every visitor can view content without being forced to sign in, etc, etc… We do what VC-backed companies don’t.&lt;/p&gt;
&lt;p&gt;
If you’ve seen the &lt;a href=&quot;/&quot;&gt;landing page&lt;/a&gt; then you probably would have noticed a few things either available now or later to help create more value for users, such as:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
The hero feature - AI audio, will be a paid feature, however, every user will be able to listen to audio posts, as well as to preview their own content with audio  &lt;/li&gt;
  &lt;li&gt;
Advanced expressions such as HTML and CSS for those who want to customise their pages to their own style  &lt;/li&gt;
  &lt;li&gt;
Ad-revenue share and paid subscribers - coming later, to make Persumi a platform that shares its success with content creators  &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
Keep on Building&lt;/h2&gt;
&lt;p&gt;
The MVP was essentially built in three months in my spare time. It wasn’t easy juggling between having a day job as a CTO, and moonlighting on my side hustle every evening and weekend.&lt;/p&gt;
&lt;p&gt;
This is just the beginning. I have many things planned for Persumi, some of which are already mentioned on the landing page, but there are much more to come. Early alpha and beta users are also able to help shape what this platform can become.&lt;/p&gt;
&lt;p&gt;
In the next week or two I will write about &lt;em&gt;how&lt;/em&gt; I managed to build Persumi in three months - the tech choices, architecture and feature prioritisation I went through to make this product a reality.&lt;/p&gt;
&lt;p&gt;
Like what you’ve read or listened to? Why not &lt;a href=&quot;/auth/register&quot;&gt;sign up&lt;/a&gt; and try the platform yourself, the Pro plan is free during the alpha and beta testing stages. Also check out Persumi’s founder &lt;a href=&quot;/u/fredwu&quot;&gt;@fredwu’s profile here&lt;/a&gt; to see what personas and expressions look like and can achieve.&lt;/p&gt;
&lt;p&gt;
Update: Read about &lt;a href=&quot;how-i-built-a-mostly-feature-complete-mvp-in-3-months-whilst-working-full-time&quot;&gt;How I Built a Mostly Feature-Complete MVP in 3 Months Whilst Working Full-Time&lt;/a&gt;, it covers the features, the tech stack, the global infrastructure and the machine learning…&lt;/p&gt;
]]&gt;</content>
    <published>2023-07-17T04:18:07.386547Z</published>
    <link href="http://persumi.com/c/persumi/u/fredwu/p/welcome-to-persumi-a-modern-platform-for-content-creation"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/c/persumi/u/fredwu/p/welcome-to-persumi-a-modern-platform-for-content-creation</id>
    <title>Welcome to Persumi - A Modern Platform for Content Creation</title>
    <updated>2023-07-17T04:18:07.386547Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
As coding bootcamps such as &lt;a href=&quot;https://coderacademy.edu.au/&quot;&gt;Coder Academy&lt;/a&gt; and &lt;a href=&quot;https://generalassemb.ly/&quot;&gt;General Assembly&lt;/a&gt; churn out more and more software developers, and as more and more people start to realise the importance of software, companies these days are facing an increased amount of candidates applying for junior dev roles.&lt;/p&gt;
&lt;p&gt;
Recently we had to take down our job ad for a junior full stack React and Elixir role only a few days after posting it due to having received about 300 applications. Suffice to say, the competition is fierce at the entry-level end of the software engineer spectrum.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;a href=&quot;https://a16z.com/2011/08/20/why-software-is-eating-the-world/&quot;&gt;Software is eating the world.&lt;/a&gt; - Marc Andreessen  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
In the past three months alone I’ve had to sit through over a hundred interviews, many of which were for junior roles. Some candidates did really well but unfortunately the vast majority did quite poorly. In this article I am hoping to offer some tips and insights as a seasoned interviewer and hiring manager, to those who might be struggling landing an entry-level software engineer role.&lt;/p&gt;
&lt;p&gt;
I’ll break the tips into three sections: before an interview, during an interview and after an interview.&lt;/p&gt;
&lt;h2&gt;
Before an Interview&lt;/h2&gt;
&lt;p&gt;
This is arguably the most important stage of your interview - before it happens! Prepare yourself with enough knowledge and confidence in both software development and conversational skills in order to ace the interview and stand out amongst a sea of candidates.&lt;/p&gt;
&lt;h3&gt;
Study Software Engineering Fundamentals&lt;/h3&gt;
&lt;p&gt;
I get it, bootcamps and many online tutorials and courses focus on teaching the practical things to turn code into products. But just like running or even walking without correct postures, it can be incredibly dangerous over time.&lt;/p&gt;
&lt;p&gt;
My recommendation is to read on the basics of object-oriented programming, functional programming and some design patterns. For instance, build up some basic understanding of how the JavaScript prototype works or how the Ruby object model works can be extremely beneficial in progressing your technical capability on application design and architecture.&lt;/p&gt;
&lt;h3&gt;
Get More Project Experience&lt;/h3&gt;
&lt;p&gt;
Not having any substantial project experience on your CV is a sure way to significantly lower your chance of getting an interview.&lt;/p&gt;
&lt;p&gt;
Do junior developers really need substantial project experience, you may ask? Abso-fucking-lutely!&lt;/p&gt;
&lt;p&gt;
Build your own side projects, participate in open source projects, pick up some freelancing work - the choice is yours. These won’t be your most technically accomplished work, but they will help you accumulate experience, and more importantly to demonstrate your capability to your interviewers with real world experience.&lt;/p&gt;
&lt;h3&gt;
Why Software Development?&lt;/h3&gt;
&lt;p&gt;
I want you to put your hand on heart and answer this question honestly. If your answer is more money or FOMO, that’s okay, but be prepared to be disappointed with the reality - if you are not in this field because of your passion for building products or solving problems, you will find this a long and hard slog.&lt;/p&gt;
&lt;p&gt;
If you are passionate about building software, then let me tell you this - when you are starting your career you will have to give up your work life balance for a bit. That is, if you want to boost your career and progress quickly beyond being a junior developer.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
My interest is in the future, because I am going to spend the rest of my life there. - Charles Kettering  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
Looking back at when I started my software career many moons ago, I never regretted giving up my social life early on in my career: I worked extremely hard, often did many jobs - a day job as a dev, a night job as a dev, freelancing and doing open source work all at the same time. Some would probably frown and say that I had no life, but I choose to believe that I was making sacrifices so I can become good at what I love doing in the shortest amount of time.&lt;/p&gt;
&lt;h3&gt;
Research the Company You Apply For&lt;/h3&gt;
&lt;p&gt;
If you are one of those who spam your CVs to every job opening, please consider changing your tactic. A well written cover letter will not only increase your chance of getting an interview, but also allow you to ask relevant questions during the interview.&lt;/p&gt;
&lt;p&gt;
Check out the company’s website and any social media pages on LinkedIn, Facebook and Instagram, etc. Do a bit of “stalking” on the people who work in the company, sometimes this will give you some insights into the type of work environment and culture the company has.&lt;/p&gt;
&lt;p&gt;
Doing enough research is not only for your own good - knowing whether you will enjoy working there should you end up getting a job offer, but also demonstrates your ability to do research and independent thinking to your interviewers.&lt;/p&gt;
&lt;h3&gt;
Read, Listen and Communicate&lt;/h3&gt;
&lt;p&gt;
This one might not be immediately obvious because it’s very high level and somewhat vague, bear with me.&lt;/p&gt;
&lt;p&gt;
Some people might be deep thinkers but really struggle to get their points across, and to communicate their ideas and thought process in a clear, concise and useful way. My advice is to read blogs, listen to talks and learn from other people on how they communicate. When you read and listen, pay more attention to how sentences are structured and how titbits of information are given.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
The most powerful person in the world is the story teller. - Steve Jobs  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
And don’t forget to practice! Get comfortable talking to others on various topics in software development.&lt;/p&gt;
&lt;h2&gt;
During an Interview&lt;/h2&gt;
&lt;p&gt;
Hopefully the tips given thus far would help you secure an interview. Now it’s show time!&lt;/p&gt;
&lt;h3&gt;
Turn Up On time, Have Equipment Sorted&lt;/h3&gt;
&lt;p&gt;
Whether you are attending an on-location interview, or more likely during Covid, an online interview, make sure you turn up on time!&lt;/p&gt;
&lt;p&gt;
If it’s an online interview, also make sure you have whatever software needed (Zoom, etc) installed and tested. I’ve had a few occasions where the first five minutes of the interview was spent on the candidate trying to get their mic or speaker working - it’s not a huge deal, but it does show the lack of preparation and organisational skills.&lt;/p&gt;
&lt;h3&gt;
A Smile Goes a Long Way&lt;/h3&gt;
&lt;p&gt;
Understandably, interviewing is stressful. However, remember to put on a smile when you greet your interviewer and when you do small talks. Everything needs a balance of course, when you’re giving technical answers it’s probably not a good idea to smile.&lt;/p&gt;
&lt;p&gt;
If you know you get uncomfortable talking to strangers or when under pressure - do more practices! If you can’t find a friend or don’t have a partner to practice with, use a mirror!&lt;/p&gt;
&lt;h3&gt;
Learn to Say “No, I don’t know”&lt;/h3&gt;
&lt;p&gt;
When you don’t know something or can’t recall the details of something, just say it. As an interviewer, when I ask you a technical question I can see through your hmms and ahs. A “sorry, I don’t know” is a much better answer than one that doesn’t make sense.&lt;/p&gt;
&lt;p&gt;
Bonus point: “Sorry I don’t know, but I will look it up after this interview.”&lt;/p&gt;
&lt;h3&gt;
Read Social Cues&lt;/h3&gt;
&lt;p&gt;
This one can be extremely easy or extremely hard depending on your personality. For those who might struggle with reading social cues, try pay more attention to what type of questions you were asked.&lt;/p&gt;
&lt;p&gt;
If it was an introduction of yourself to kick off the interview, make it more than just two sentences but also under five minutes - this is usually a warm up for you to find your feet and calm your nerve, and for the interviewer to extract any interesting information as a discussion point later. It doesn’t have to be your memoir and you shouldn’t robotically talk through your entire education and work history.&lt;/p&gt;
&lt;p&gt;
If it was a technical question, always try to time-box it. Don’t drag on and repeat yourself just because you have already answered it quickly and concisely. When in doubt, ask the interviewers whether they have anything in particular they were looking for.&lt;/p&gt;
&lt;p&gt;
If it was an open ended question, try to explain your thought process as you explain it. As an interviewer, I’d hate to ask an open ended question and get a short yes/no back. At the same time, give interviewers room to interject and ask follow-up questions by pacing yourself.&lt;/p&gt;
&lt;h3&gt;
Don’t Speak Too Fast&lt;/h3&gt;
&lt;p&gt;
Okay, you might be very nervous, or English might not be your first language, so you tend to speak fast to “get through it”.&lt;/p&gt;
&lt;p&gt;
If that’s the case, work on your speech pattern, make it concious enough that you will recognise it when you speak too fast. If interviewers can’t get your points clearly, they might not always ask you to clarify - because if you kept doing it repeatedly, you would already be written off as a suitable candidate.&lt;/p&gt;
&lt;h3&gt;
Be Humble&lt;/h3&gt;
&lt;p&gt;
I totally understand that in a competitive job market you want to present yourself in the best light. Just be confident and speak the truths.&lt;/p&gt;
&lt;p&gt;
As someone who’s been doing interviews for years, I really dislike people who overstate their capability either on their CV or during the interview because these are often very obvious. Claiming to be an Elixir expert but not knowing how the supervision tree works is a sure way to score a black mark, for example.&lt;/p&gt;
&lt;h3&gt;
Show Passion and Drive&lt;/h3&gt;
&lt;p&gt;
To counter the point on over-selling yourself, what works better is to show interviewers your willingness and determination to learn. Remember, you are still a junior therefore we don’t expect you to know everything we know.&lt;/p&gt;
&lt;p&gt;
What we do expect, is your commitment to learning - not by saying it, but by demonstrating it. Remember the earlier tip on getting more project experience?&lt;/p&gt;
&lt;h3&gt;
Ask Questions&lt;/h3&gt;
&lt;p&gt;
Usually towards the end of the interview, we’d ask you if you have any questions. Don’t say “no”.&lt;/p&gt;
&lt;p&gt;
Doesn’t matter how much research you’ve done on a company, there are always insights someone who’s working at the company can give you. How do they manage the delivery? How do they support learning? What makes them enjoy working at the company?&lt;/p&gt;
&lt;p&gt;
Use this opportunity to gain insights into why you should be working at this company, and to demonstrate your interests in working at the company to the interviewers too.&lt;/p&gt;
&lt;h2&gt;
After an Interview&lt;/h2&gt;
&lt;p&gt;
After completing an interview, it’s an opportunity to do a little retrospective and do a follow up when necessary.&lt;/p&gt;
&lt;h3&gt;
Close the Knowledge Gap&lt;/h3&gt;
&lt;p&gt;
Don’t over-analyse on what you could have or should have said. Instead, spend your energy on solidifying the topics you couldn’t or didn’t provide good answers for. If you’ve said “don’t know” or “not sure” during the interview, now is your chance to make it “sure”.&lt;/p&gt;
&lt;h3&gt;
Follow Up&lt;/h3&gt;
&lt;p&gt;
If during the interview there have been things mentioned that you could provide links or more information on, be sure to act on it. You never know, maybe the clarification is just what the interviewers needed to help them make a decision.&lt;/p&gt;
&lt;p&gt;
~&lt;/p&gt;
&lt;p&gt;
To all the junior developers out there - enjoy the journey! You are in for an amazing ride, make every second count.&lt;/p&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;p&gt;
&lt;em&gt;If you enjoyed this article, checkout my other tips articles:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
&lt;em&gt;&lt;a href=&quot;tips-for-becoming-a-better-software-developer/&quot;&gt;Tips for Becoming a Better Software Developer&lt;/a&gt;&lt;/em&gt;  &lt;/li&gt;
  &lt;li&gt;
&lt;em&gt;&lt;a href=&quot;tips-for-writing-a-good-cv-resume/&quot;&gt;Tips for Writing a Good CV / Résumé&lt;/a&gt;&lt;/em&gt;  &lt;/li&gt;
&lt;/ul&gt;
]]&gt;</content>
    <published>2020-09-27T05:56:03.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/tips-for-job-interviews-as-a-junior-software-developer"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/tips-for-job-interviews-as-a-junior-software-developer</id>
    <title>Tips for Job Interviews as a Junior Software Developer</title>
    <updated>2020-09-27T05:56:03.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Due to COVID-19, not many companies are hiring at the moment. The company &lt;a href=&quot;https://ourxplor.com&quot;&gt;I work for&lt;/a&gt; therefore is in a very fortunate position to still be thinking about growth and hiring.&lt;/p&gt;
&lt;p&gt;
As a hiring manager for almost a decade now, I’ve personally reviewed thousands of job applications and CVs, and many hiring managers would probably agree, the vast majority of CVs are terrible. Let’s change that!&lt;/p&gt;
&lt;p&gt;
During COVID-19 where more and more people are either losing jobs or having their work hours cut, we are experiencing an increased amount of applicants to &lt;a href=&quot;https://apply.workable.com/xplor/&quot;&gt;our job ads&lt;/a&gt;. I’d say on average &lt;strong&gt;I spend about 30 seconds per applicant&lt;/strong&gt; due to my busy schedule - most hiring managers are busy people, it is therefore crucial for candidates to realise the importance of having a CV that is clear, easy to read and most importantly sells yourself. And if you have a cover letter, which I highly encourage that you do, congratulations you just bought yourself another 30 seconds. ;)&lt;/p&gt;
&lt;p&gt;
I’m writing this post mostly from my own perspective - as a hiring manager in a tech company in the western culture (we’re based in Australia). Understandably, different cultural backgrounds and regions may have their own conventions, but certainly in Australia and many similar western cultures, there are things that you do and don’t do on a CV, and there are things that may help your CV stand out. Let’s talk about these things.&lt;/p&gt;
&lt;p&gt;
At the end of this post I will also share a copy of my own CV to help illustrate my points.&lt;/p&gt;
&lt;h2&gt;
30 Seconds? Surely It’s Unfair to the Candidates&lt;/h2&gt;
&lt;p&gt;
Yes, I agree, to think that you are only given 30 seconds for your perhaps carefully crafted CV and cover letter is definitely soul-crushing. But it is unfortunately the reality. I work for a company where I can still do the first round of vetting myself, many large corporations would use algorithms and/or HR people to reject your applications based on keywords and other things.&lt;/p&gt;
&lt;p&gt;
Knowing the reality and the constraints, there are a few things I’d like to address in the hope of improving your CV and your chance of scoring an interview, and in turn, helping myself and other hiring managers out there to have a better candidate CV screening experience.&lt;/p&gt;
&lt;h2&gt;
Have a Pronounceable Name or Alias&lt;/h2&gt;
&lt;p&gt;
This one surely would raise some eyebrows - you might think that your name is your identity and you should not change it for anyone. True, however, the reality is that a hard-to-pronounce name discourages your profile to be shared and spoken about. Why not add a pronounceable alias if means there’s an increased chance of getting an interview?&lt;/p&gt;
&lt;p&gt;
For clarity, I personally would never reject a candidate based on their name (or their cultural background for that matter), but I know some hiring managers might, and for some of them, they are NOT doing it on purpose. However, I have on several occasions had to ask a candidate how to correctly pronounce their name.&lt;/p&gt;
&lt;h2&gt;
A Short Blurb on Who You Are&lt;/h2&gt;
&lt;p&gt;
As a hiring manager, I care about who you are as a person - if you can summarise who you are as a professional in a sentence or two, it will help me determine whether you might be a good fit or not.&lt;/p&gt;
&lt;p&gt;
As an example, here’s a blurb about me:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
A passionate and hands-on software executive with two decades of experience and an entrepreneurial mindset.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
  &lt;p&gt;
A long time open source developer who has created and contributed to a few dozens of projects, including Ruby on Rails.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
In two sentences, I explained my industry experience as well as my open source contributions - two things that help define who I am as a working professional. It also invites more questions from hiring managers: what kind of things have I done as an entrepreneur; what other open source projects have I contributed to?&lt;/p&gt;
&lt;h2&gt;
Work Rights&lt;/h2&gt;
&lt;p&gt;
Many companies have restrictions or policies around who they can hire based on their residency and visa status. If you are not a resident or are on a particular visa, make it clear in your job application so you don’t end up wasting time for the employer and for yourself.&lt;/p&gt;
&lt;h2&gt;
List Keywords, But Don’t Overdo It&lt;/h2&gt;
&lt;p&gt;
In the tech space it is important to have keywords visible to highlight your skills. If you are a software developer, your tech stacks should be clearly stated in your CV. As a hiring manager, if I am hiring a PHP developer, I expect to see PHP mentioned in your CV. There are of course exceptions, for example when we were hiring Elixir developers I did not expect to see Elixir as a keyword simply due to the supply constraint.&lt;/p&gt;
&lt;p&gt;
It is a balancing act however - I’ve seen CVs where candidates put 20-50 keywords on their CVs. I’m sorry but unless you are extremely gifted, you cannot possibly be good at all those things. Do not put keywords on your CV simply because you’ve read an article on the subject.&lt;/p&gt;
&lt;p&gt;
Oh, and unless you’re going for a data entry role, I honestly don’t care about your Excel skills…&lt;/p&gt;
&lt;h2&gt;
Do Not Overstate Your Capability&lt;/h2&gt;
&lt;p&gt;
Similarly, try to avoid overselling your capability. I once interviewed a candidate who claimed to be an “expert” on Ruby. We were actually hiring for a non-Ruby position, but given the candidate’s CV, I questioned him on some advanced Ruby subjects during our interview and he struggled all the way through and was sweating bullets. Suffice to say that he did not get the job.&lt;/p&gt;
&lt;p&gt;
Be confident, but also be honest and be humble. Lying on your CV to get an interview is a waste of everyone’s time.&lt;/p&gt;
&lt;h2&gt;
Keep Things Short&lt;/h2&gt;
&lt;p&gt;
As I mentioned in the beginning, I spend on average 30 seconds on each CV. Keep things short and easy to read! I really don’t care about how awesome you were in your last dozens of projects - these will get covered during interviews.&lt;/p&gt;
&lt;p&gt;
On a CV I expect short and concise blurbs on what you did in each role. Also, take recency into account too - if you’ve been working in the industry for a decade or two, what you did 20 years ago really doesn’t matter as much, so save yourself some time and cut things short.&lt;/p&gt;
&lt;p&gt;
For example, here’s the blurb for my current role:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
Leading a department of 25+ engineers to make great child care and education software. As part of the leadership team and reporting to the CEO, helping building and turning the company into a market leader.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
And here are the blurbs for my older roles:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&amp;nbsp;  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
Yes, the blurbs for my older roles are left empty intentionally.&lt;/p&gt;
&lt;p&gt;
Now, again there are exceptions. If something happened a while ago but is interesting and relevant, do tell! For example, here’s the blurb for my oldest “role”:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
Built my first ever website using Microsoft FrontPage Express, on a Pentium 166Mhz computer, uploaded via a 33.6kbps modem.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
Explain Over-Qualified Titles&lt;/h2&gt;
&lt;p&gt;
There were a few times where a “CTO” or even a “CEO” applied for a developer role. In most cases it wasn’t about over-qualification, but about what the candidate wanted to achieve professionally. So, either in the CV or in the cover letter, explain what you are looking for in your next role, otherwise you run the risk of being assessed as over-qualified.&lt;/p&gt;
&lt;h2&gt;
Spare the Personal Details That Are Too Personal&lt;/h2&gt;
&lt;p&gt;
This is predominantly a culture thing as I’ve seen it from mostly candidates of certain cultural backgrounds. I really don’t care about your age, gender, marital status or favourite sport. These things do not define who you are as a professional - we might talk about your favourite sport and food during the interview but they are irrelevant on your CV.&lt;/p&gt;
&lt;h2&gt;
Space Things Out&lt;/h2&gt;
&lt;p&gt;
Look up &lt;a href=&quot;https://www.google.com/search?q=crap+principles&quot;&gt;CRAP Principles&lt;/a&gt; - make sure your CV has enough white spaces and contrast, and has fonts that are readable! Scrolling through walls of text is no fun and is a sure way to get your CV dismissed.&lt;/p&gt;
&lt;h2&gt;
2-4 Pages&lt;/h2&gt;
&lt;p&gt;
This is not scientific, for me personally I prefer to see CVs of 2-4 pages. Use the length as a constraint to cut things down. There were several occasions where I ran into CVs with 10+ pages. I guarantee you, unless a hiring manager is &lt;em&gt;extremely&lt;/em&gt; bored, he or she does not have time to read your War and Peace.&lt;/p&gt;
&lt;h2&gt;
PDF Over Word&lt;/h2&gt;
&lt;p&gt;
When possible, submit your CV in PDF format instead of Word format. Now, sometimes if you use a recruiter you’ll be asked to submit your CV in Word format so they can &lt;del&gt;fuck it up&lt;/del&gt; add their branding. A PDF formatted CV ensures the correct formatting and layout always get shown to the hiring managers.&lt;/p&gt;
&lt;h2&gt;
Cover Letter&lt;/h2&gt;
&lt;p&gt;
Always attach a cover letter when possible, but keep it short too. Given the amount of CVs a hiring manager needs to go through, having a crafted cover letter is another way to grab their attention and increase your chance of getting an interview.&lt;/p&gt;
&lt;p&gt;
Don’t repeat the same information in the cover letter though. Your &lt;strong&gt;CV is about the facts of your experiences&lt;/strong&gt;, your &lt;strong&gt;cover letter should be about your thoughts on why the company should hire you&lt;/strong&gt;. Focus on the value you can bring to the table.&lt;/p&gt;
&lt;h2&gt;
Find A Referral&lt;/h2&gt;
&lt;p&gt;
When possible, find someone who can refer you. A referral gets preferential treatment during the CV screening stage and does not suffer from the same 30-second fate.&lt;/p&gt;
&lt;h2&gt;
Pleasing Design&lt;/h2&gt;
&lt;p&gt;
This one is a “nice-to-have”: if your CV is really well designed, you would earn another 30 seconds of my attention. ;)&lt;/p&gt;
&lt;p&gt;
~&lt;/p&gt;
&lt;p&gt;
These are the main points, hopefully they are helpful. To help illustrate, here is &lt;a href=&quot;/img/posts/2020-05-20/cv-example.pdf&quot;&gt;a copy of my own CV&lt;/a&gt;, with contact details removed.&lt;/p&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;p&gt;
&lt;em&gt;If you enjoyed this article, checkout my other tips articles:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
&lt;em&gt;&lt;a href=&quot;tips-for-becoming-a-better-software-developer/&quot;&gt;Tips for Becoming a Better Software Developer&lt;/a&gt;&lt;/em&gt;  &lt;/li&gt;
  &lt;li&gt;
&lt;em&gt;&lt;a href=&quot;tips-for-job-interviews-as-a-junior-software-developer/&quot;&gt;Tips for Job Interviews as a Junior Software Developer&lt;/a&gt;&lt;/em&gt;  &lt;/li&gt;
&lt;/ul&gt;
]]&gt;</content>
    <published>2020-05-20T08:59:24.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/tips-for-writing-a-good-cv-resume"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/tips-for-writing-a-good-cv-resume</id>
    <title>Tips for Writing a Good CV / Résumé</title>
    <updated>2020-05-20T08:59:24.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Over the past decade or two, both as a software developer and as a manager I have accumulated a few tips for becoming a better software developer.&lt;/p&gt;
&lt;p&gt;
For context, I’ve been working as a software developer for more than a decade, mostly in small product-based companies; over the last decade or so I’ve worked as a team lead or manager in various capacities; and I’ve moonlighted as a consultant, freelancer and open source contributor in-between. Check out &lt;a href=&quot;https://www.linkedin.com/in/wufred/&quot;&gt;my LinkedIn profile&lt;/a&gt; and &lt;a href=&quot;https://github.com/fredwu&quot;&gt;Github profile&lt;/a&gt; if you are interested.&lt;/p&gt;
&lt;p&gt;
Now, let’s break down the tips into three main areas: &lt;strong&gt;mindset&lt;/strong&gt;, &lt;strong&gt;technical&lt;/strong&gt; and &lt;strong&gt;people&lt;/strong&gt;. The goal of this article is to raise awareness of the topics mentioned, if you want to understand more on a particular topic I encourage you to do your own research.&lt;/p&gt;
&lt;h2&gt;
Mindset&lt;/h2&gt;
&lt;h3&gt;
Have a Growth Mindset&lt;/h3&gt;
&lt;p&gt;
This one hopefully should be obvious, if you haven’t heard of it, please &lt;a href=&quot;https://www.google.com/search?q=growth+mindset&quot;&gt;read more here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
Have a StarCraft Mindset&lt;/h3&gt;
&lt;p&gt;
This one is “new”, I’ve given a talk that includes more details on what it means, &lt;a href=&quot;https://www.youtube.com/watch?v=MBczdO7RgNo&quot;&gt;check it out here&lt;/a&gt;. In essence, it’s a mindset that requires you to always consider things around you, and forces you to think beyond your narrowly focused task at hand.&lt;/p&gt;
&lt;h3&gt;
Recognise the Dunning–Kruger Effect&lt;/h3&gt;
&lt;p&gt;
I once interviewed a mid-level developer who rated himself as a 9.9 out of 10. The &lt;a href=&quot;https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect&quot;&gt;Dunning-Kruger effect&lt;/a&gt; is essentially the opposite of the &lt;a href=&quot;https://en.wikipedia.org/wiki/Impostor_syndrome&quot;&gt;Imposter Syndrome&lt;/a&gt;, both are very common in our field.&lt;/p&gt;
&lt;h3&gt;
Agile is Not a Process, It’s a Mindset&lt;/h3&gt;
&lt;p&gt;
Some people are overly obsessed with the processes of “being agile”. In truth, every organisation, every project and every team is different, there is no point to follow a set of processes that might slow you down and not add any value. Discover what being agile means to your team, your project and your organisation is an ongoing effort and should be treated as such.&lt;/p&gt;
&lt;h3&gt;
Different Organisations Require Different Engineering Approaches&lt;/h3&gt;
&lt;p&gt;
Some projects and organisations require tech-driven decisions be made upfront, some on the other hand require product-driven decisions instead. Knowing how your organisation operates and what it values plays a key part in understanding how software development should be approached.&lt;/p&gt;
&lt;h3&gt;
A Sense of Entitlement Only Gets You So Far&lt;/h3&gt;
&lt;p&gt;
Let’s be honest, as a software developer we are extremely lucky to be in a high demand field. As a result, many have started building a sense of entitlement - some ex-colleagues of mine even complained about the lack of tissue boxes in the office. Starting and running a business is extremely hard work, let’s all appreciate what we have before throwing a tantrum.&lt;/p&gt;
&lt;h3&gt;
9-5 is Not Evil, Nor is Crazy Work Hours&lt;/h3&gt;
&lt;p&gt;
Every now and then I see conflicts between people who share different philosophies on what work and career mean to them. We should embrace the different ways people prefer to spend their time and energy on, rather than force our own ideology on them. Discovering and knowing the difference between working at a giant corporation versus working at a small start-up is key to our happiness.&lt;/p&gt;
&lt;h3&gt;
Pain and Gain, There’s Always a Trade-off&lt;/h3&gt;
&lt;p&gt;
An extension of the 9-5 vs crazy work hours: adjust your expectation based on the effort you put in. You can’t have everything, prioritising family life over career building is 100% okay, as long as you know what you are giving up on, vice versa. In my 20s, many of my similarly aged friends and colleagues would go out and have fun after work and on weekends while I was in front of my computer building open source projects, is an example of a conscious trade-off I made.&lt;/p&gt;
&lt;h3&gt;
Speed vs Quality&lt;/h3&gt;
&lt;p&gt;
Internalising the need for speed and the need for quality should always be on our mind. Pursuing only quality or only speed will significantly limit your problem-solving capability as well as your career progression.&lt;/p&gt;
&lt;h3&gt;
Think, Then Do&lt;/h3&gt;
&lt;p&gt;
It’s a balance - don’t &lt;a href=&quot;https://en.wikipedia.org/wiki/Analysis_paralysis&quot;&gt;overthink it&lt;/a&gt;, but also don’t do without think first. When in doubt, ask more experienced people for advice and guidance.&lt;/p&gt;
&lt;h2&gt;
Technical&lt;/h2&gt;
&lt;h3&gt;
Avoid Second System Syndrome&lt;/h3&gt;
&lt;p&gt;
I’ve seen &lt;a href=&quot;https://en.wikipedia.org/wiki/Second-system_effect&quot;&gt;Second System Syndrome&lt;/a&gt; over and over again throughout my career. Some people always assume a rewrite is a much better approach than alternatives, more often than not though, it is not the case.&lt;/p&gt;
&lt;h3&gt;
Micro-Services is Neither New nor the Holy Grail&lt;/h3&gt;
&lt;p&gt;
There’s the age-long debate of monolith vs micro-services architecture, and SOA (Service-Oriented Architecture) has been around for decades. Each has its own pros and cons, don’t get bought into the hype because a blog post says so, or a particular product and company found it successful. Use the right tool for the right job.&lt;/p&gt;
&lt;h3&gt;
TDD (Test-Driven Development) is not the Holy Grail&lt;/h3&gt;
&lt;p&gt;
Just like micro-services, TDD is not the holy grail and should not be treated as much. Chasing the TDD dream without knowing its pros and cons is just as counter-productive as not doing TDD when necessary.&lt;/p&gt;
&lt;h3&gt;
Code Aesthetics Matter&lt;/h3&gt;
&lt;p&gt;
A lot of people treat coding as more of a scientific endeavour: they focus purely on the algorithms and results. Software development to me is a creative endeavour, and I often use white spaces and the general look and feel of the code base to determine how well-organised a project is - deeply nested code and long functions for instance are obvious ways to determine &lt;em&gt;potential&lt;/em&gt; code smell.&lt;/p&gt;
&lt;p&gt;
You’ve all heard of the &lt;a href=&quot;https://en.wikipedia.org/wiki/SOLID&quot;&gt;SOLID principles&lt;/a&gt;, but have you heard of the &lt;a href=&quot;https://www.google.com/search?q=CRAP+principles&quot;&gt;CRAP principles&lt;/a&gt;?&lt;/p&gt;
&lt;h3&gt;
Read As Much Code As Possible&lt;/h3&gt;
&lt;p&gt;
Don’t get me wrong, writing code is important, practice is important, but time and time again I come across developers who clearly have little exposure to established patterns and conventions.&lt;/p&gt;
&lt;h3&gt;
Tech Stack Exposure and Diversity are Always a Good Thing&lt;/h3&gt;
&lt;p&gt;
No matter whether you are in a highly specialised field or being a generalist, having exposure to different tech stacks and paradigms is always a good thing - it widens your field of vision and increases the boundary of your technical understanding. Don’t be the guy or gal who is known as “a &lt;em&gt;\&lt;insert tech stack&gt;&lt;/em&gt; developer”.&lt;/p&gt;
&lt;h3&gt;
Have a Deeper Technical Understanding Helps&lt;/h3&gt;
&lt;p&gt;
This is strictly speaking a “nice to have”, but I lost count of the number of candidates I interviewed who claimed to be a Ruby &lt;em&gt;expert&lt;/em&gt; yet cannot explain the &lt;a href=&quot;https://www.google.com/search?q=the+ruby+object+model&quot;&gt;Ruby object model&lt;/a&gt; and have never heard of &lt;a href=&quot;https://en.wiktionary.org/wiki/eigenclass&quot;&gt;eigenclasses&lt;/a&gt;, or senior developers who cannot explain the difference between &lt;code class=&quot;inline&quot;&gt;git merge&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;git rebase&lt;/code&gt;. Sure, you may not need the deep understanding in order to do your job, but it will certainly help!&lt;/p&gt;
&lt;h2&gt;
People&lt;/h2&gt;
&lt;h3&gt;
Always Assume Incompetence over Malice&lt;/h3&gt;
&lt;p&gt;
If you haven’t, &lt;a href=&quot;https://en.wikipedia.org/wiki/Hanlon%27s_razor&quot;&gt;read more about it here&lt;/a&gt;. Knowing this, and paired with having a growth mindset, you should therefore be encouraged to teach, educate and influence, rather than being defensive and participate in “us vs them”.&lt;/p&gt;
&lt;h3&gt;
Truth = Visibility x Tolerance&lt;/h3&gt;
&lt;p&gt;
This is another topic &lt;a href=&quot;https://www.youtube.com/watch?v=MBczdO7RgNo&quot;&gt;covered in my talk&lt;/a&gt;. If you find yourself regularly dissatisfied with the information you receive, ask yourself: what information is and isn’t available to you, and are you sure you have the stomach to tolerate the new information?&lt;/p&gt;
&lt;h3&gt;
Rapport and Productivity are Built Over Time&lt;/h3&gt;
&lt;p&gt;
Some people assume a team’s productivity is determined largely by team member’s individual capability, some also assume a &lt;a href=&quot;https://www.google.com/search?q=10x+engineer&quot;&gt;10x engineer&lt;/a&gt; is a reflection on one’s technical capability. It’s not. Productivity evolves over time - building rapport with those around you is often the unsung hero in productivity.&lt;/p&gt;
&lt;h3&gt;
Champion Team Over Champion Individual&lt;/h3&gt;
&lt;p&gt;
This one hopefully is obvious, but I just have to emphasis its importance. Please, if you struggle to work with your team, ask for help! If you have a team mate who is difficult to work with, reach out and help them!&lt;/p&gt;
&lt;h2&gt;
Final Thoughts&lt;/h2&gt;
&lt;p&gt;
Software development is evolving rapidly, some fundamentals however are always useful and relevant. I hope you find this article useful.&lt;/p&gt;
&lt;p&gt;
I’ve mentioned my talk a few times, but if you haven’t already, check it out below, it covers a few topics mentioned in this article, and some more. Also check out &lt;a href=&quot;https://www.youtube.com/channel/UCYzkbDiuqtiNkQvQBzStVqQ&quot;&gt;my tiny Youtube channel&lt;/a&gt; for more tech talks I’ve done in the past.&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;inline&quot;&gt;video: https://www.youtube.com/watch?v=MBczdO7RgNo&lt;/code&gt;&lt;/p&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;p&gt;
&lt;em&gt;If you enjoyed this article, checkout my other tips articles:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
&lt;em&gt;&lt;a href=&quot;tips-for-writing-a-good-cv-resume/&quot;&gt;Tips for Writing a Good CV / Résumé&lt;/a&gt;&lt;/em&gt;  &lt;/li&gt;
  &lt;li&gt;
&lt;em&gt;&lt;a href=&quot;tips-for-job-interviews-as-a-junior-software-developer/&quot;&gt;Tips for Job Interviews as a Junior Software Developer&lt;/a&gt;&lt;/em&gt;  &lt;/li&gt;
&lt;/ul&gt;
]]&gt;</content>
    <published>2020-05-03T06:34:25.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/tips-for-becoming-a-better-software-developer"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/tips-for-becoming-a-better-software-developer</id>
    <title>Tips for Becoming a Better Software Developer</title>
    <updated>2020-05-03T06:34:25.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Over the past few years as I gain more and more experience in not only building software and products but also in leading teams and projects, I realised that my desire of wanting an agile tool that’s super simple to use yet super flexible to adapt to different needs has grown stronger and stronger.&lt;/p&gt;
&lt;p&gt;
Given the COVID-19 situation that’s going on at the moment, I’ve finally decided to spend a few nights working on a pitch deck for myself to validate the value proposition, and some high level wireframes to visualise the ideas.&lt;/p&gt;
&lt;p&gt;
Over the weekend I’ve put together a landing page to “market” my ideas. It’s a quick job, but the underlying thinking has been on the back of my mind for years now.&lt;/p&gt;
&lt;p&gt;
Introducing Focussist, or rather Focussist’s landing page: &lt;a href=&quot;https://focussist.com/&quot;&gt;Focussist.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Please check it out and give me your feedback, and don’t forget to sign up. ;)&lt;/p&gt;
]]&gt;</content>
    <published>2020-03-23T11:04:55.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/launching-focussist-landing-page-an-upcoming-agile-project-management-tool"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/launching-focussist-landing-page-an-upcoming-agile-project-management-tool</id>
    <title>Launching Focussist Landing Page - An Upcoming Agile Project Management Tool</title>
    <updated>2020-03-23T11:04:55.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Seven years ago in 2012 I spoke at &lt;a href=&quot;http://2012.rubyconfchina.org/&quot;&gt;RubyConf China 2012&lt;/a&gt;. It was a technical talk on how to become a better developer, if you’re interested you can check out &lt;a href=&quot;https://www.youtube.com/watch?v=DeBsmdDmB9A&quot;&gt;the video recording&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Seven years later in August 2019, I headed back to Shanghai again to speak at this year’s &lt;a href=&quot;http://rubyconfchina.org/&quot;&gt;RubyConf China&lt;/a&gt;. And this time around, it was a non-technical talk on how to develop one’s career. Check out the video recording below:&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;inline&quot;&gt;video: https://www.youtube.com/watch?v=MBczdO7RgNo&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;
And, here are &lt;a href=&quot;https://photos.app.goo.gl/VavY5PgBGezZaqMr6&quot;&gt;some photos&lt;/a&gt; of yours truly. :)&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://lh3.googleusercontent.com/5WAF3kG7lcjoHBJyW1cZXjyfQ50CtiAFtE9jDZQNVxqv3Vx8poJ2pkJ0gN-iRy8Xll0WmQLbnNdu8ackq5QIyNi4U6pP8YLSOjeD99azl4Id_nQZ6-ZDIEF5yg7NWdoKinEKZP9umzdhPsDP4E9eilxOctHWG9BZ5oWS0NcYoGyWCx6wgv0zV2VQxX_ND49YkAz7VLe0RvoGkqVBBsGiqaNXMePFVp3yiS9QkVkp866duSJk1o8kBd3eUgP54znSoFG3G7zKgPmsyRcPkJiR4TjFQX_EcLY4EhGPPhJBy371E_h3jdA88Y_W2Y-qQLqmrTtRzx_c6zVJonVLSOZRPg3H4ZZU3ZpwbeWq09wzv6XgI_obzvRX9uWThj43TXbJGbAym_pwagzurao08D5i0-EfM0P2StjJ72Fl2pissiMsxlKm5m3j5SBmKiO_mp5hXIVuOe0W81sTIv7DB4yKGC-_meQgrKZDV2jFObdAIJ0pLR8TbsLYra2ljl9JBKi_1m0Q2kNhN3EX9eqxV4-i1yabOkl132DzVHg81MXBHt58oi2byfqmtv5Nzibf2UdGWku7up_71N4jl-hP9ZiRE5vxjvpx1FMuX_g9JSXAjQpbFIOxoYNG98TuP6i1zUwy-ItUfdK-KpTgd6tvtR0eU7tVnbdfGF4uQgpgTWMYxsRHT98xflCS0twqnoDxMH1UhTLMbjyVbBFlTihtl5dvMRWiwB8v6if74HA5b3jcmJTLjN0CWQ=w800-no&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://lh3.googleusercontent.com/MqS8YsePpXrxE1x6ZdUTexS_YkFXV3ivU5deks73l-hdXjrNsg3qu6dU8ykdjnfrIGI5o9aScbHNDe6soOF3tp8-k7ho340cj0kU9tbQSxr7A45WdF0Vc1DjiGlTOeTq8m0pDdCW_FsE5hejK0Ojv9Gmmv6AE9jfv-9ny1xtD3aYc-TE26x8RMZImPWVcASX-Q4j7vxwL7QkCXGSJqxk__fsJBST4_DAm9SAJckJURRsSLrxMELejTIgklg89Ku57n8ErpPyQLZeII1qtMzP71D_IZpYw4IbkXnRdEkmgtAoKlnkbhfm9dTeNaLA3cbbQH7MdhyjCA1BHV4Pb-GS0WmlJz0UznC97240N7NYwc9eydZIRl7XIWSIpQa422MjYexp9HaXE_0kigj4im6r1F1yyXUa4nZZl27IX4dSDNUuDeswXOHjlhZ6LwPzCU38ybsP3N-D5DmAwBfxZ4HceiC4TIw0h-TASjdO9GiFT5L0kQWdlYCyodG_PTiRcH66C8eKMbFpNRo4kHyC9xv9cpCitpuo1zpK71iG0qGted8RqKumc1IUmOVmixLUGMBn4N8fLzH9bHWI5-QgIFcAwjX-KpBF66bOQ8KFu80AfYvrjj_-uuLiJMAslQrdTH4Emke_GTiB33TqO-Sg_PlVtlxIY02apmukpgvA6g_zr8UBSNCeZBEGXFR55JuIXt6sQJyuqKJf5kwWiUNTB3dGK_VUxz8zQQ6OvZO2hAIkOzYnh1TwSA=w800-no&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://lh3.googleusercontent.com/GwcPQdQwUj4EZ5phpPq7oQSrsxz5xMPCNHzrcHx9NDTGsVvFJigPPgH_9Nomo3dfSnMjgZvVNWiOUA6schD2N_yiEYU9jVBDitTQjop3OLCTjOGgU-9Q_JFP6YWwW4qvcKr4E_WkUR2ncZXicpMlVTF9vDHteCUK03-h-0SQQklQRSyP4CZsm7CJUqPAieV5TlsGspbIaZ0HncXp1IHCcsA9hcsZszDLwI5XclX2ZYHWpccbYc8EngNP61wDDL6Jj8USkxlPBN_J2fjkpexA_RU16GN_PzP_mHcWOrX20Qo_U1ftx1O2BR47C_EMk_7eQBYFeyISV6Njvc7_rmoTFme01SK8rvpJhpaY2FjhmwaiwnTG_QxaPOezLq20N4LbNGJjJAspkjHoGmB_VRl2WzGTGv-sizvRHltpcZBhLFI2NaKBkjxbdCbGbmW-fwa192jx5SW_rqDxMRuGf0F9GDAWPNzI46-gfLIeMUbKZiq6D5FRqU1LBnhyK0BKX6QxtWiqY_cIHk1BThjMUuMCAUBvVJKaRz3-k7de-JH0YV8Wg6d-yHMU0nclpShtnHKKH-ZABLxF8S_6GC6lsGyObm8BwxA-eZesp4I3_pM2-GSn5YYbYxp5cf5mS34TcrnHbWBORYENVddDX9RVZBf6J9Hn42rO0z8lKEK4O3K7xkLXh3ntmfeJikM6RB-xXx-VZYMdLx_IZkjKP1NhnpSWaJhrBiXeAk9pdNRzbzz7JBxrAOFTRA=w800-no&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://lh3.googleusercontent.com/o629eWYguKoS9bjBAWpeQ05j2I_ulKDUDx7mBoigOaQAumd7ALULmPJ0rMDmkeSJOjm17lj4s_HPM2Xdz5aS8IqCDqDPHfIBOaVD2xw-5TwWMN3WrKMWC3sPArh2R5HHmw8XApO3SpgNCoLPJirwg-WsJaVzqdWKoJmLZIfPMf-aT-z0jJFTAUyLJzmxxJ_GNUtiX0BBuKLaS6ae7J-kWGLeeJ3Wodli7jWzOwdTQi0inem5ro9inHfS-HcOsOUn2bofObday9QGVnDmG4h5cuc7jwvUUUNtx1tw__HHvaPs5wF_lPSitwhBGmUlvR_DWe-o_ZIphXPtUE3N5rA_GitUxI_BAYpMG_AYq4GYUXwoyTotrtZdqn4cD1Vc9DE3bWfGeIEWV4Rk3XkMSFfLCGIta6LWoPYYtU0onJPNERgTgog4blAmoLPNotUFH_JRLji3VOAl3dwksL4FPVHH6huqDyYfcBKGZ8714_-8qvOZjs5D-vuTWMjZ5ggxeHceORfHROZK8uBGAF0Dfwd8ySZi1ZQMkU0fTjGpUu6Vw0HX91xpoyTuDimDkdpfHD5KSIS4EaWsvfsDh1bScFi3wb0fZNTDbNeXTM2gbLE5kzcXkLC_bB27UggVXNJHcgyjEvKrO6WohKVUyk0S3TMhuQzebBNA-cKAwjg6uNjAc7iw9dunJy9o685Pn4i9C0Th1V6hT-ECp__QkDLnP4EMPsQmhtCVdKad8NFTJljl82Wnm71vyg=w800-no&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://lh3.googleusercontent.com/0-9oruOGEu8TI9ziGr7Wjyu3p6i_yct3-cpoUAaUEcRXcVOD0g4M0Wi23eGsYJaG-JclUVm8gSEvbZRmIOrV2ypJ_dbfp7ew6BNThn5XXYz8is3whtfsF0us0Aoyge3HKppHGPFbHelWLPCw4mx2DuW5JYFMC0LyP4GxSj0s0rd08BmYrWQ82UY87gpGCUV1NrrS5PqasdUceXu0kJkQZ2zrnFyTIe0fgR74y1wPwJCFjEBZ-trO1trCztMyOdGvYjimSYSJ7NC4c-MaBcKfGVt96wdqm91FBmPW65msShxW_qJclHwxbJnHYbJ27vEbl6LxF2Cq67zb4X5zQlxxPVaYUJDiEE98e1cInOAFa0p3DGey8kiefhJJLLXBz4hss-IjyruuyC5O4l9uWdpkJ4oSGMp4wQlTli6DHrx08BRxW-JbBsjxxFjl5DoG4wViNNRhz2aCP1F4Vi2Y9-2O8RWHZvZr1wPOwzItrlV8yWSd1R2piOCbsz5I86mC7JcdhAAcOHjz-x8WkBW2l48hWDyArUmF3GQJXPs-c7LOMusb7dFSkDNfMQbpz9gMX-MGPHKXBU4D0F79kpK2IlucKXqJ0dwNnv3tJHaxA3CO1GNlvl0CbfnZ75SbewmnJ8p-hh4laRWsprzYEa8lzV5BpdUT2irF2hol370SIAyfYPo_YXI-FWJtxjQqPm37AodQFAEiVknbsLb7gHGSPwJXbVXkbkikmgGoo2izpmHNN1qmSNJCmQ=w800-no&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://lh3.googleusercontent.com/QWWFWmgn9SI9hc-F9xM9xydgPCiSW2VWVmDjRg435ajhVogEiAoB9PX-1RKaVUjDiKRyhc0mj2qkbA4PLt-zwjFZJv4NdisLfrTkrq5nIJNyF3tCbqnSCbccv7Z5RVTDS9Mooq7NuB81pvvF-S9JTXkJSwsXBv6VNY3x-dJy0Dhv_GM7rglLs3z5wqQNExtoMLaNZ7nly0ZXUom1AD-bGMOk1e0HXNe6YPiROS5FS6fvkZpNq4mPJXOYrAO7TYNpxGJ_8lL5-cWEWz881u4dtqC00V9jjYmB3Zv3GIcg_BNzJ-chAeVxYVNWtSbjSbVrIDv3c6-2toJy3q7RZsIlnJIzEsq2qo9zhAxVrBDMt4kJ50rqLvIJp4aYKDIbKH_YrYjWKXG3Nz9h1pkqyYw93J6oRq_8JS2W8k8aFzbKM4ASBCcCSvOHrfQLW4F9Jda743roruGOP7O0vMS-9gUZEGwWY-l-KM1pye7Mk93heJFgO38LVTxbHprsqAoxJ3YsegKVK3iHR54Roh4Ad8DZT_3egIsoM4RDas3a58nb8u3i7v9knNRMcXcdRiAHXVjDOLDEcQ1uxWYZehG9sUhfXHB85bvkbzPruTsMJRNJNUHv-XxDy4qN4Mp-KMiWXa5yQcajZ18znq8T5Fh8MwNvZ-n7V7fmR12NzIs6P4QFU7pV21W6dIFLMftdVMHg5Tyb7cgnhrPFA9xIllzC6uU5LpwpQ-F8Mul0zj4NuX_eQdgM4CRa9g=w800-no&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://lh3.googleusercontent.com/gmv02CzGXpgNFo9j7TSxNJSBPQ1SHS0ezY6KLnKVcOMuSY0BciIr00Q9eQovmNZsKn8k_7c6BZZKdKd42hIutQEY9LWOW0eiHrI6ZaKLB5S5QBkvTfUR1MGWlWPO_04EeRNi5dE8SLc-1gjjpS3eAv0HiHVyDWc2P3SEslkFX94-qXB_5HjQgny8H-wIdpdYOzjwfjIqnVaePN-MOVCZooQUNRSu760DSegdU2gdSAisAVoQqMzejevk3XkiAHXNgCeQMwUCt33qMh9BhQWjPLP6r1DRFUAQ9SWKFodVe_pTxAIgxky4KS5dfGC9uhE6A5wefxiXFLJLtyJJT7uG3-v0Bp7fCxt_8SWqK0F7iqTUlv_qjIcaDt6DtBdDTFX96A2QZYArAUH9f9boiQO-TmERuc4faN6g2o0d9JgAD5DsLFmO_O6i6hgD1ONP-vzoKd0ZFQHMeVX9xhPIMV40uqUr0w72_S9LFlSIKnHmEM0JyHM_v-e4n2EnkQwKWt5W4IgQdmSDiBmnxX1KTjAoo9ddkPhXoXmzhezma0z8uRAO2M3aM_jr67sAhfSZMvPBUkwTwS7h0_KLqjJoVKmXGcNBVM9zxVWipSiu1Ny07wNdixDkS27oGY2aejzudYp2L2dNrQJuN16cesP97oodYCSOs6Qy1CF4N_7i6wrCtd9bMEK7CxEljc7gGrV9hhP-3PNDLePt3Lcb8B8RNgRdsOhTJIs5v8hsQj_VzyOTxfqzTrmy6g=w800-no&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://lh3.googleusercontent.com/22Jb93H__Xpik_DJBWm1ulTks-BG0v0jwpmZj3rQzWG_cvmjZfrFrGA9y-XhddG5-9l7pe0lMLRK8UsX3eBr7wKb8ShNX73S_kCGdMSsJ1JvL4hEatyOQ7-0RhRCkZ0jot9KPJV2zJRM764kT2KR_IicD_EhTzXgoA-RoGu9C3NcQ9rAX5uWF3pleHLJ1LNmu0DEeBxF3XuoF3IVW9dbkfioyBMF-kn82RDe1f9RIIRabJSOWXl_QIYI47aE07XrCU7SRlf9jnF-W0-YxiMYHmElDKoD7HP8ySLx6QCkjQNF5lMC7dF6R25jcwrK4co7rCU20gsMBb2vkalwHvfFxcsIgShugRDzjUjdqzoZ3GT_4WFA9MoYBAShvNAn0L-eWMrAhv-XOXpkjfeeS-BVP2SwusFHK0wLkCzsmH5FD7d0J44QpkgvFojZX0zj6x-KbIsGD-yl2cEqsUkzYGZufBlo2tRobAyNOv89E9aT1BeQs76cvwykybO5AMWCsV6ikr_fExZQMPwqMTg-8FoOhpff6mTUyKVqMXlbk0-8JP-IkaCr8V8rqUUpDtDrZLNVXxIDj0rEXp9MzIZgiSe7MDViHKnEpOGY6vP_GGGpvWkZY8mAEpTTyRH0cJFAZ4jVV6hQmzqMvREmZcOLUDKIFdG_fM-bJOO-bGMx1Bi3371GJch81K273_pdJka7AUB391dOPv0wZCp3y_qRWeabrhfhpYSD_87ncMY7pgvMqtJMMmwDaQ=w800-no&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
]]&gt;</content>
    <published>2019-09-14T08:05:09.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/conference-talk-adaptable-human-rubyconf-china-2019"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/conference-talk-adaptable-human-rubyconf-china-2019</id>
    <title>Conference Talk: Adaptable Human @ RubyConf China 2019</title>
    <updated>2019-09-14T08:05:09.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
In 2010 I set up this blog on &lt;a href=&quot;https://tumblr.com&quot;&gt;Tumblr&lt;/a&gt; mostly due to the ease of publishing and not having to worry about the hosting. I also went through two design iterations done in Photoshop:&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/2019-01-20/blog-layouts.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
You can probably tell that these designs were done before the flat and minimalist design trend we are seeing in recent years.&lt;/p&gt;
&lt;p&gt;
As I decided to revamp my blog once again, I thought I’d use &lt;a href=&quot;https://www.sketchapp.com/&quot;&gt;Sketch&lt;/a&gt; this time around and aim for a simpler, cleaner and more mature design approach that’s quicker to design and to build. And this is exactly what I did:&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/2019-01-20/sketch.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Thanks to Sketch, the design process was much better than I would’ve had in Photoshop.&lt;/p&gt;
&lt;h2&gt;
Why the Revamp?&lt;/h2&gt;
&lt;p&gt;
A big part of the reason for the blog revamp was to move away from Tumblr, now that it is part of the dying &lt;a href=&quot;https://yahoo.com&quot;&gt;Yahoo!&lt;/a&gt;, and to invest in modern technologies to make the blog function better by adding responsive design and a proper grid system, etc.&lt;/p&gt;
&lt;p&gt;
As I was reading up on and researching technologies as I often do, I came across &lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;Gatsby&lt;/a&gt;, and I was immediately convinced that this was going to power my new blog as it appears super fast (by building static content and pre-fetching), is based on &lt;a href=&quot;https://reactjs.org/&quot;&gt;React&lt;/a&gt; and has &lt;a href=&quot;https://www.gatsbyjs.org/docs/plugins/&quot;&gt;a huge collection of useful plugins&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
At work I often joke about how I hate JavaScript (&lt;code class=&quot;inline&quot;&gt;rm -rf node_modules&lt;/code&gt; anyone?), the truth is I started off my career doing a lot of JavaScript, &lt;a href=&quot;https://github.com/fredwu/jquery-endless-scroll&quot;&gt;my most popular open source library&lt;/a&gt; by GitHub star count is in JavaScript, and I have always worked as a full stack dev until recent years.&lt;/p&gt;
&lt;p&gt;
Due to the shift of my responsibilities more into leadership over the past few years, I have opted to focus more on the backend, but deep down I love software in general and given the frontend scene has seen some major improvements in the past few years, not to mention that at work we have several React code bases, it’s time I dig my teeth into it again.&lt;/p&gt;
&lt;p&gt;
Disclaimer: until recently I had only done two React projects before and my exposure to ES6 has been somewhat limited.&lt;/p&gt;
&lt;h2&gt;
Gatsby&lt;/h2&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/2019-01-20/gatsby.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
The thing about Gatsby that impressed me the most is how fast it is due to its out-of-box support for pre-fetching. With a wealth of different &lt;a href=&quot;https://www.gatsbyjs.org/starters/&quot;&gt;starter templates&lt;/a&gt; it is also very easy to hit the ground running. I used &lt;a href=&quot;https://www.gatsbyjs.org/starters/netlify-templates/gatsby-starter-netlify-cms/&quot;&gt;gatsby-starter-netlify-cms&lt;/a&gt; as it supports &lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt; and is fairly simple.&lt;/p&gt;
&lt;h2&gt;
Export Tumblr Posts&lt;/h2&gt;
&lt;p&gt;
First thing first, I will need to export all my tumblr posts into Markdown files so that Gatsby can consume them. There are heaps of such tools including &lt;a href=&quot;https://tumblr.zendesk.com/hc/en-us/articles/360005118894-Export-your-blog&quot;&gt;Tumblr’s own export functionality&lt;/a&gt;. I ended up having to use these two:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
&lt;a href=&quot;https://import.jekyllrb.com/docs/tumblr/&quot;&gt;Jekyll importer&lt;/a&gt; for converting all Tumblr posts into Markdown  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://github.com/bbolli/tumblr-utils&quot;&gt;tumblr-utils&lt;/a&gt; for downloading all images in the posts (Tumblr’s own export tool and many others only download images hosted on Tumblr)  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Of course, many search-and-replace as well as manual massaging were required to get the new Markdown files to a useable state.&lt;/p&gt;
&lt;h2&gt;
Add More Gatsby Plugins&lt;/h2&gt;
&lt;p&gt;
The starter template was great, but I needed more functionalities so I ended up adding these over time:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-styled-components/&quot;&gt;gatsby-plugin-styled-components&lt;/a&gt; for supporting &lt;a href=&quot;https://www.styled-components.com/&quot;&gt;Styled Components&lt;/a&gt;  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-remark-embed-video/&quot;&gt;gatsby-remark-embed-video&lt;/a&gt; for embedding videos  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-remark-prismjs/&quot;&gt;gatsby-remark-prismjs&lt;/a&gt; for syntax highlighting  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-awesome-pagination/&quot;&gt;gatsby-awesome-pagination&lt;/a&gt; for pagination  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-remark-reading-time/&quot;&gt;gatsby-remark-reading-time&lt;/a&gt; for adding reading time  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-feed/&quot;&gt;gatsby-plugin-feed&lt;/a&gt; for RSS feed  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-google-analytics/&quot;&gt;gatsby-plugin-google-analytics&lt;/a&gt; for Google Analytics  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-redirect-from/&quot;&gt;gatsby-redirect-from&lt;/a&gt; for page redirects (of old Tumblr pages)  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-sitemap/&quot;&gt;gatsby-plugin-sitemap&lt;/a&gt; for sitemaps  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-robots-txt/&quot;&gt;gatsby-plugin-robots-txt&lt;/a&gt; for &lt;code class=&quot;inline&quot;&gt;robots.txt&lt;/code&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Using many of these plugins only requires you to add the plugin itself and some config, then off you go - it could not be any easier!&lt;/p&gt;
&lt;h2&gt;
GraphQL&lt;/h2&gt;
&lt;p&gt;
One other reason why I was interested in Gatsby was because of its &lt;a href=&quot;https://www.gatsbyjs.org/docs/querying-with-graphql/&quot;&gt;out-of-box support&lt;/a&gt; for &lt;a href=&quot;https://graphql.org/&quot;&gt;GraphQL&lt;/a&gt;. I came across GraphQL a while back but never had the chance to work with it. &lt;a href=&quot;https://www.ourxplor.com/&quot;&gt;At work&lt;/a&gt; one of our projects started using GraphQL and the team behind it rates it highly, so I really wanted to get some first hand experience using it.&lt;/p&gt;
&lt;p&gt;
With the inclusion of &lt;a href=&quot;https://github.com/graphql/graphiql&quot;&gt;GraphiQL&lt;/a&gt; the in-browser GraphQL IDE, the GraphQL experience in Gatsby is extremely simple and satisfying.&lt;/p&gt;
&lt;h2&gt;
Querying Data using Gatsby’s Page Components and StaticQuery&lt;/h2&gt;
&lt;p&gt;
It’s been a bit of journey.&lt;/p&gt;
&lt;p&gt;
The starter template I used has GraphQL queries in the page components, but I wanted the GraphQL queries in shareable components so I opted to using Gatsby’s &lt;a href=&quot;https://www.gatsbyjs.org/docs/static-query/&quot;&gt;StaticQuery&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Until I decided to add &lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-awesome-pagination/&quot;&gt;gatsby-awesome-pagination&lt;/a&gt; which requires the GraphQL queries to have variables therefore &lt;a href=&quot;https://github.com/gatsbyjs/gatsby/issues/9047&quot;&gt;incompatible with StaticQuery&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Despite the back and forth between page components and &lt;code class=&quot;inline&quot;&gt;StaticQuery&lt;/code&gt;, I still loved the GraphQL experience in Gatsby.&lt;/p&gt;
&lt;h2&gt;
It’s All Just React (In a Good Way)&lt;/h2&gt;
&lt;p&gt;
The great thing about Gatsby is that despite making things easier by including a bunch of useful functionalities and plugins, down to its core, everything is based on &lt;a href=&quot;https://reactjs.org/docs/components-and-props.html&quot;&gt;React Components&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
What this means is that as long as you have a working knowledge of React and React Components, extending Gatsby’s capability is simple and straightforward.&lt;/p&gt;
&lt;h2&gt;
CSS Powered by Bulma&lt;/h2&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/2019-01-20/bulma.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
The starter template also includes &lt;a href=&quot;https://bulma.io/&quot;&gt;Bulma&lt;/a&gt; - a modern and popular CSS framework.&lt;/p&gt;
&lt;p&gt;
Until now I’ve always relied mostly on &lt;a href=&quot;https://getbootstrap.com/&quot;&gt;Bootstrap&lt;/a&gt; so it’s nice to get to experience another CSS framework. And it turns out, Bulma is quite nice and easy to use too.&lt;/p&gt;
&lt;p&gt;
Thanks to Bulma, supporting responsive design was a breeze.&lt;/p&gt;
&lt;h2&gt;
Gotcha: CSS Rules and “gatsby-plugin-purgecss”&lt;/h2&gt;
&lt;p&gt;
&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-purgecss/&quot;&gt;gatsby-plugin-purgecss&lt;/a&gt; is included as part of the starter template to reduce the size of the CSS files by removing unused CSS rules. It’s wonderful as it saves about &lt;code class=&quot;inline&quot;&gt;200Kb&lt;/code&gt; of CSS (before minifying and gzipping) from unused rules from Bulma.&lt;/p&gt;
&lt;p&gt;
One gotcha I soon realised though, is that it also purges any CSS rules that are only rendered in the Markdown components. My guess is that the purging happens before the static contents were rendered therefore it cannot detect them.&lt;/p&gt;
&lt;p&gt;
The fix was quite simple, given that by default it only purges the main css file (in my case, &lt;code class=&quot;inline&quot;&gt;all.scss&lt;/code&gt;), and all the specific rules in this file were defined by me, all I had to do was to add the comment lines to tell the plugin not to purge these rules:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;scss language-scss&quot;&gt;@import &quot;~bulma&quot;;

/* purgecss start ignore */
.my-own-css-rules-here {
}
/* purgecss end ignore */&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
Gotcha: CSS Conflicts Between Bulma and Prism.js&lt;/h2&gt;
&lt;p&gt;
One more issue with CSS was the conflicts between Bulma and &lt;a href=&quot;https://prismjs.com/&quot;&gt;Prism.js&lt;/a&gt; due to the way some of the CSS rules are defined in Bulma.&lt;/p&gt;
&lt;p&gt;
What I had to do was to “reset” those CSS rules so they can inherit from Prism.js instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;scss language-scss&quot;&gt;.content {
  .number,
  .tag {
    align-items: inherit;
    background-color: inherit;
    border-radius: inherit;
    color: inherit;
    display: inherit;
    font-size: inherit;
    height: inherit;
    justify-content: inherit;
    line-height: inherit;
    margin-right: inherit;
    min-width: inherit;
    padding: inherit;
    text-align: inherit;
    vertical-align: inherit;
    white-space: inherit;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
Netlify: Deployment and Hosting Made Easy (and Free!)&lt;/h2&gt;
&lt;p&gt;
Before Netlify and similar services were available, many people used &lt;a href=&quot;https://pages.github.com/&quot;&gt;GitHub Pages&lt;/a&gt; to host their static content sites. It worked reasonably well but in the end this isn’t GitHub’s core competency and the features are limited.&lt;/p&gt;
&lt;p&gt;
Netlify takes deployment of static content sites to &lt;a href=&quot;https://www.netlify.com/github-pages-vs-netlify/&quot;&gt;a whole new level&lt;/a&gt; and I cannot be happier to have finally jumped on the bandwagon too.&lt;/p&gt;
&lt;h2&gt;
Closing&lt;/h2&gt;
&lt;p&gt;
Suffice to say, I am very satisfied with this revamp experience - Sketch, Gatsby, GraphQL, Netlify and many other software have made it a walk in the park.&lt;/p&gt;
&lt;p&gt;
It only took me around 50 hours to get to 99%, and a few hours more to add in things like &lt;a href=&quot;https://github.com/disqus/disqus-react&quot;&gt;Disqus&lt;/a&gt; and &lt;a href=&quot;https://github.com/nygardk/react-share&quot;&gt;social sharing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Oh, and for those who are curious, the source code of this blog is &lt;a href=&quot;https://github.com/fredwu/fredwu.me-v3&quot;&gt;available on GitHub here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Let me just say:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
The last time I fell in love with JavaScript was when &lt;a href=&quot;https://jquery.com/&quot;&gt;JQuery&lt;/a&gt; first came to the scene many moons ago. Ever since then I dread working on the JS stack mostly because I’ve been spoiled by the Ruby and Elixir ecosystems. It’s nice to fall in love JavaScript again after so many years of simply “getting by”.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
I hope you like my new blog, and if you have a similar experience to share, I’d love to hear from you!&lt;/p&gt;
]]&gt;</content>
    <published>2019-01-20T11:27:22.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/new-blog-with-new-design-and-gatsby-js-loving-javascript-again"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/new-blog-with-new-design-and-gatsby-js-loving-javascript-again</id>
    <title>New Blog with New Design and Gatsby.js - Loving JavaScript Again</title>
    <updated>2019-01-20T11:27:22.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
I’ve had a productive coding weekend, and so I decided to share my experience. Now, many developers choose to treat their career as a series of 9-5 jobs, but if you’re reading this, I assume you’re like the rest of us who love continuous learning and self improvement.&lt;/p&gt;
&lt;h2&gt;
Preface&lt;/h2&gt;
&lt;p&gt;
About a year ago I started learning Elixir. So as part of the learning experience, I wrote two matching learning related libraries: &lt;a href=&quot;https://github.com/fredwu/stemmer&quot;&gt;Stemmer&lt;/a&gt; and &lt;a href=&quot;https://github.com/fredwu/simple_bayes&quot;&gt;Simple Bayes&lt;/a&gt;. It was a great, really enjoyable experience and I learnt a lot about the concepts of &lt;a href=&quot;https://en.wikipedia.org/wiki/Stemming&quot;&gt;word stemming&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Naive_Bayes_classifier&quot;&gt;naive Bayes classification&lt;/a&gt; and of course, functional programming.&lt;/p&gt;
&lt;p&gt;
These topics have been interesting to learn about, but until one gets to use them on a daily basis for a while, key concepts are less likely to convert from short term memory to long term memory. Given my day job is not about writing Elixir (yet), I needed to find other ways to keep my skill-level up and to continue exploring new things.&lt;/p&gt;
&lt;p&gt;
So about a month ago, I picked up a project I started a year ago but gave up shortly after: &lt;a href=&quot;https://github.com/fredwu/crawler&quot;&gt;Crawler&lt;/a&gt;. At the time &lt;a href=&quot;https://elixir-lang.org/blog/2016/07/14/announcing-genstage/&quot;&gt;GenStage was just announced&lt;/a&gt; and I was interested in incorporating it into my project as I thought it’d be a great fit. But due to varies reasons - mostly not having a firm grasp of the GenStage concept and implementation, as well as taking on a CTO role at a startup, I couldn’t find enough time and patience to make it work so I had to let it go.&lt;/p&gt;
&lt;p&gt;
Until now.&lt;/p&gt;
&lt;h2&gt;
Crawler, on Steroids&lt;/h2&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/crawler-architecture.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
I’d realised that the way I was trying to incorporate GenStage into my project was never going to work. Not because of GenStage itself, but because of &lt;strong&gt;the way I approached learning it&lt;/strong&gt;. At the time I was so eager to make use of GenStage, and coming off the back of my good streak of releasing the aforementioned machine learning libraries, I thought I could take shortcuts and things would all work out perfectly.&lt;/p&gt;
&lt;p&gt;
No.&lt;/p&gt;
&lt;p&gt;
So I licked the wounds, learnt my mistakes and changed tactics. This time, I scoped out and encapsulated my learnings (just as one would in designing a software system), and eventually I came up with another library - &lt;a href=&quot;https://github.com/fredwu/opq&quot;&gt;OPQ: One Pooled Queue&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
OPQ: One Pooled Queue&lt;/h2&gt;
&lt;p&gt;
I knew Crawler could technically work without any queueing system, or GenStage. So I kept on building Crawler, until I felt productive in writing Elixir again, and touched enough areas and concepts that I knew exactly what I needed from GenStage.&lt;/p&gt;
&lt;p&gt;
And luckily for me too, GenStage over the past year has matured and more importantly, has better documentation with more code examples. Upon closer investigation of the code examples, I found that their &lt;a href=&quot;https://github.com/elixir-lang/gen_stage/blob/v0.12.2/examples/gen_event.exs&quot;&gt;GenEvent&lt;/a&gt; and &lt;a href=&quot;https://github.com/elixir-lang/gen_stage/blob/v0.12.2/examples/rate_limiter.exs&quot;&gt;RateLimiter&lt;/a&gt; examples were almost exactly what I needed. It was an epiphany moment for me after reading and understanding these examples, all of a sudden I “get it”.&lt;/p&gt;
&lt;p&gt;
If you take a look at the source code of &lt;a href=&quot;https://github.com/fredwu/opq&quot;&gt;OPQ&lt;/a&gt; you’ll notice that the heavy lifting logic was mostly inspired (or even copy-pasted) from those examples.&lt;/p&gt;
&lt;h2&gt;
Open Sourcing is Contributing and Caring&lt;/h2&gt;
&lt;p&gt;
Up until this point, as far as writing open source Elixir code goes, it had mostly been me writing my own code for my own projects. But open sourcing is much more than just writing one’s own code and publishing them on Github.&lt;/p&gt;
&lt;p&gt;
If you’ve followed my work you’ll know that I’m a big fan of contributing to other projects, some of which are well-known ones like &lt;a href=&quot;http://contributors.rubyonrails.org/contributors/fred-wu/commits&quot;&gt;Rails&lt;/a&gt; and &lt;a href=&quot;https://github.com/slim-template&quot;&gt;Slim&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
It just so happens, that over the weekend I ran into situations where I needed to contribute to other Elixir projects - and spoiler alert: one of which is Elixir itself.&lt;/p&gt;
&lt;h2&gt;
ElixirRetry&lt;/h2&gt;
&lt;p&gt;
One of the features I set out to add to Crawler, was to allow failed crawls to retry before giving up. Naturally, the first thing I did was to try find an existing package to support the retry functionality.&lt;/p&gt;
&lt;p&gt;
After some googling and digging into some source code, I’ve found and settled on &lt;a href=&quot;https://github.com/safwank/ElixirRetry&quot;&gt;ElixirRetry&lt;/a&gt; - a neat library that was cleverly built.&lt;/p&gt;
&lt;p&gt;
Soon I found out that since its last release, it offered both &lt;code class=&quot;inline&quot;&gt;retry/2&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;retry/3&lt;/code&gt;, the latter of which was to support an extra argument that specifies which exceptions to allow as part of the retry flow. It was a great addition, but it doesn’t effect me as Crawler doesn’t need it.&lt;/p&gt;
&lt;p&gt;
As a developer who cares deeply about code quality and clarity, I immediately thought about how the interfaces for &lt;code class=&quot;inline&quot;&gt;retry/2&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;retry/3&lt;/code&gt; could be improved, by simply combining them and making the extra argument in &lt;code class=&quot;inline&quot;&gt;retry/3&lt;/code&gt; an option.&lt;/p&gt;
&lt;p&gt;
So I did, and issued a pull request here: &lt;a href=&quot;https://github.com/safwank/ElixirRetry/pull/12&quot;&gt;https://github.com/safwank/ElixirRetry/pull/12&lt;/a&gt; You’ll notice that I’ve also made some improvements to the test suite while I was at it. ;)&lt;/p&gt;
&lt;h2&gt;
Elixir Typespec&lt;/h2&gt;
&lt;p&gt;
One issue I ran into while building Crawler, was the static analysis via &lt;a href=&quot;http://erlang.org/doc/man/dialyzer.html&quot;&gt;Dialyzer&lt;/a&gt; - some code that actually ran and passed tests functionally, failed the type checking.&lt;/p&gt;
&lt;p&gt;
As someone who isn’t as experienced in Elixir, I first &lt;a href=&quot;https://github.com/elixir-lang/elixir/issues/6507&quot;&gt;opened an issue&lt;/a&gt; and phrased it more as a question seeking validity of the issue. I then jumped on &lt;a href=&quot;https://elixir-slackin.herokuapp.com/&quot;&gt;Elixir’s Slack group&lt;/a&gt; and asked people in the group about this issue.&lt;/p&gt;
&lt;p&gt;
Fortunately, &lt;a href=&quot;https://github.com/benwilson512&quot;&gt;Ben Wilson&lt;/a&gt; immediately came to aid by verifying my issue and validating my suspicion of it being an issue in Elixir’s typespec documentation.&lt;/p&gt;
&lt;p&gt;
And so, &lt;a href=&quot;https://github.com/elixir-lang/elixir/pull/6508&quot;&gt;a pull request was created&lt;/a&gt; and approved shortly after.&lt;/p&gt;
&lt;h2&gt;
Sharing is Caring&lt;/h2&gt;
&lt;p&gt;
The bulk of Crawler as well as the entirety of OPQ were built in the past month or so. I hope some people will benefit from having these libraries around. And, I hope people also enjoyed me sharing my experience, and perhaps be inspired to start sharing more too.&lt;/p&gt;
&lt;p&gt;
I will leave you all with a Chinese saying: &lt;a href=&quot;https://chinese.yabla.com/chinese-english-pinyin-dictionary.php?define=%E6%BB%B4%E6%B0%B4%E7%A9%BF%E7%9F%B3&quot;&gt;滴水石穿&lt;/a&gt;. The literal translation is “dripping water penetrates the stone”, and what it means is “&lt;strong&gt;constant perseverance yields success&lt;/strong&gt;“.&lt;/p&gt;
]]&gt;</content>
    <published>2017-08-27T11:21:01.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/coding-and-learning-should-never-stop-open-sourcing-is-caring"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/coding-and-learning-should-never-stop-open-sourcing-is-caring</id>
    <title>Coding and Learning Should Never Stop, Open Sourcing is Caring</title>
    <updated>2017-08-27T11:21:01.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;h2&gt;
Preface&lt;/h2&gt;
&lt;p&gt;
If memory serves right, it’s been several years since I first dabbled in Elixir, but it was about a year ago I really started putting some serious effort into learning Elixir, and as a result I made two libraries in the machine learning space: &lt;a href=&quot;https://github.com/fredwu/simple_bayes&quot;&gt;Simple Bayes&lt;/a&gt;, a Naive Bayes text classifier implementation, and &lt;a href=&quot;https://github.com/fredwu/stemmer&quot;&gt;Stemmer&lt;/a&gt;, an English (Porter2) stemming implementation.&lt;/p&gt;
&lt;p&gt;
Unfortunately after I’ve released those two libraries, I hadn’t had much opportunities to work with Elixir. My day jobs have been mostly Ruby, JavaScript, PHP and a dash of Golang. And so, after being silent for a year, I’ve decided to pick up something I had started a year ago - &lt;a href=&quot;https://github.com/fredwu/crawler&quot;&gt;a web Crawler&lt;/a&gt;. If you are new to Elixir, feel free to follow this project as I am actively developing it.&lt;/p&gt;
&lt;h2&gt;
Learnings&lt;/h2&gt;
&lt;p&gt;
The preface is to give a bit of background of when and how I started learning Elixir, now, let me talk about one of my favourite features of Elixir, and how it helps me write better code not just in Elixir, but in virtually any other language.&lt;/p&gt;
&lt;p&gt;
Introducing the topic of today, a really simple feature, and in fact it has been part of Python for years - the &lt;a href=&quot;https://en.wikipedia.org/wiki/Doctest&quot;&gt;doctest&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
Doctest&lt;/h2&gt;
&lt;p&gt;
In short, a doctest is pieces of code examples that run as part of the test suite, and show up as part of the documentation. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;defmodule Greeting do
  @doc &quot;&quot;&quot;
  ## Examples

      iex&gt; hello(&quot;world&quot;)
      &quot;hello world&quot;

      iex&gt; hello(&quot;dear&quot;)
      &quot;hello dear&quot;
  &quot;&quot;&quot;
  def hello(input) do
    &quot;hello #{input}&quot;
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
And then all you need is in your corresponding test file, to enable doctest:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;defmodule GreetingTest do
  use ExUnit.Case

  doctest Greeting
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Even though I knew about Python’s doctest for years, I’d never realised how impactful it can be given its simplistic nature, probably due to the fact that I’ve never used Python for anything serious.&lt;/p&gt;
&lt;p&gt;
Until Elixir.&lt;/p&gt;
&lt;p&gt;
There are three things I find the most impactful as I write more doctests: clarity, scope, and design.&lt;/p&gt;
&lt;h2&gt;
Clarity&lt;/h2&gt;
&lt;p&gt;
As a ruby programmer, I appreciate greatly the beauty of not just the main code base, but also its test suite. Hence I’ve always preferred to use RSpec in larger code bases. However, as the application gets more complex, and as the number of test files grows, the cognitive overhead of reading and processing all the files and lines becomes higher and higher.&lt;/p&gt;
&lt;p&gt;
Doctest solves this perfectly - no longer do we have to crawl through the right file and the right line for a particular test case, all the test cases are neatly presented right in front of you as you read the function itself.&lt;/p&gt;
&lt;p&gt;
You might think this as trivial, but just like many organisations spend time and effort to optimise for effective communication by studying &lt;a href=&quot;https://en.wikipedia.org/wiki/Proxemics&quot;&gt;proxemics&lt;/a&gt;, proxemics between different components of a software code base also plays a role in improving the code clarity, and ultimately the code quality.&lt;/p&gt;
&lt;h2&gt;
Scope&lt;/h2&gt;
&lt;p&gt;
Doctest is purposely simple, and is designed for unit tests. There have been many times when I found myself realising my function was too dependant on external states, or are doing too many things because it was hard to write simple doctests. In a way, the constraints of doctests have forced me to rethink the scope of my function, and that would often lead to an overall better designed system.&lt;/p&gt;
&lt;h2&gt;
Design&lt;/h2&gt;
&lt;p&gt;
As much as I’d like to think about the &lt;a&gt;SOLID&lt;/a&gt; principles all the time, it is often too easy to dig deep wholes in the midst of building things.&lt;/p&gt;
&lt;p&gt;
Every now and then I find myself extracting a piece of logic to a private function and calling it a day. In Elixir, only public functions can have doctests - again, this constraint pushes you to think about the importance and the role of a particular function, perhaps it is better to be moved to another module as a public function therefore can have its own doctests. Here is &lt;a href=&quot;https://github.com/fredwu/crawler/commit/7ac06b40a6c75b5e01311e70710185011f82471f&quot;&gt;an example&lt;/a&gt; when I did some refactorings on Crawler.&lt;/p&gt;
&lt;h2&gt;
Closing&lt;/h2&gt;
&lt;p&gt;
I wanted to write this article for a while now - as I truly love and appreciate Elixir’s asthetics and features. Many developers might find functional programming as a barrier, but I can assure you that with Elixir’s tooling and ecosystem, and of course doctest (&lt;em&gt;wink&lt;/em&gt;), building software feels like a breeze.&lt;/p&gt;
&lt;p&gt;
Last time when I was this happy building software was when I first discovered ruby. If you haven’t given Elixir a try yet, I encourage you to do so sooner rather than later, it will not only give you a functional programming perspective, but will also help you write better code in other languages.&lt;/p&gt;
]]&gt;</content>
    <published>2017-08-07T10:00:20.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/elixir-and-doctest-help-writing-better-programs-one-function-at-a-time"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/elixir-and-doctest-help-writing-better-programs-one-function-at-a-time</id>
    <title>Elixir and Doctest - Help Writing Better Programs, One Function At A Time</title>
    <updated>2017-08-07T10:00:20.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
About a month ago I was in-between jobs - I had two weeks to rest up, recharge and get ready for my new job. So I thought,&amp;nbsp;I should use those two weeks to learn something new.&lt;/p&gt;
&lt;p&gt;
Years ago I briefly looked into &lt;a href=&quot;http://elixir-lang.org/&quot;&gt;Elixir&lt;/a&gt; when it was first released to the wild, at the time I wasn’t interested in picking it up due to its syntax similarity to Ruby, despite their vastly different underlying semantics. I love Ruby, and it’s been my weapon of choice for the past 6-7 years, so when it came time for me to learn something &lt;em&gt;new&lt;/em&gt;, I naturally wanted to learn something a bit more different than Ruby, syntax-wise.&lt;/p&gt;
&lt;p&gt;
Fast-forward a few years, I am more mature and open-minded, and are now in a position to welcome Elixir and to embrace the Ruby-like syntax as well as the functional programming mindset with open arms.&lt;/p&gt;
&lt;h2&gt;
Learning Elixir&lt;/h2&gt;
&lt;p&gt;
Given the strong influence of Ruby in Elixir, I can’t help but get nostalgic about learning it by reading another great book by Dave Thomas: &lt;a href=&quot;https://pragprog.com/book/elixir12/programming-elixir-1-2&quot;&gt;Programming Elixir&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Dave Thomas’ original &lt;a href=&quot;https://pragprog.com/book/ruby/programming-ruby&quot;&gt;Programming Ruby&lt;/a&gt; was instrumental in the success of the Ruby programming language and its communities in the west, and it certainly has helped me to greatly widen my exposure to not only the wonderful world of Ruby but object-oriented programming in general as a PHP developer.&lt;/p&gt;
&lt;p&gt;
Learning Elixir has been really fun, not only does it share the same developer-friendliness championed by Ruby, it also does so with remarkable strength in concurrency thanks to BEAM (Erlang’s VM).&lt;/p&gt;
&lt;p&gt;
Many people are drawn to Elixir due to its Ruby influence, its functional and immutability nature, and its &lt;a href=&quot;https://en.wikipedia.org/wiki/Actor_model&quot;&gt;Actor-based concurrency model&lt;/a&gt;. I would however like to call out one of my favourite features of Elixir that sometimes gets overlooked, and that is how you could write unit tests using &lt;a href=&quot;http://elixir-lang.org/docs/stable/ex_unit/ExUnit.DocTest.html&quot;&gt;&lt;code class=&quot;inline&quot;&gt;ExUnit.DocTest&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
Elixir Makes Writing Unit Tests Effortless&lt;/h3&gt;
&lt;p&gt;
Python programmers have been writing doctests for years, but Ruby due to not having an equivalent standard library, has never had writing doctests taken off in its community. I’m glad Elixir has.&lt;/p&gt;
&lt;p&gt;
Take a look at the code snippet below:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;defmodule Stemmer.Step0 do
  @doc &quot;&quot;&quot;
  ## Examples

      iex&gt; Stemmer.Step0.trim_apostrophes(&quot;&apos;ok&quot;)
      &quot;ok&quot;

      iex&gt; Stemmer.Step0.trim_apostrophes(&quot;o&apos;k&quot;)
      &quot;o&apos;k&quot;

      iex&gt; Stemmer.Step0.trim_apostrophes(&quot;&apos;o&apos;k&apos;&quot;)
      &quot;o&apos;k&quot;
  &quot;&quot;&quot;
  def trim_apostrophes(word) do
    word
    |&gt; String.replace_prefix(&quot;&apos;&quot;, &quot;&quot;)
    |&gt; String.replace_suffix(&quot;&apos;&quot;, &quot;&quot;)
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
The three test cases given will actually be tested by ExUnit, provided you ask it to do so as below:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;defmodule Stemmer.Step0Test do
  use ExUnit.Case, async: true

  doctest Stemmer.Step0
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
You may think having doctests is not a big deal, what I can say is that I am really enjoying the fact that &lt;strong&gt;unit tests can be written with minimal friction and high visibility&lt;/strong&gt; (as they sit with the implementation, rather than within a big test suite with a sea of files). Besides, not all of us practice TDD religiously or 100% of the time, and when I don’t or can’t do TDD, having doctests ensures I don’t forget adding test cases. :)&lt;/p&gt;
&lt;h3&gt;
Toy Robot in Elixir&lt;/h3&gt;
&lt;p&gt;
The best way to learn something new is to practice it as you learn it. Instead of diving straight into a complicated system, or to write a &lt;code class=&quot;inline&quot;&gt;hello world&lt;/code&gt; program, I thought I would dig out a code test and re-implement it in Elixir.&lt;/p&gt;
&lt;p&gt;
The code test is the rather infamous &lt;a href=&quot;https://github.com/search?q=toy+robot&quot;&gt;Toy Robot&lt;/a&gt; test. As an interviewer, I must have reviewed a few hundreds of these tests, most of which in Ruby. I know what a good OO solution looks like, so naturally I was looking forward to “rethink” the problem and have a crack at it using Elixir.&lt;/p&gt;
&lt;p&gt;
Here is the result: &lt;a href=&quot;https://github.com/fredwu/toy-robot-elixir&quot;&gt;https://github.com/fredwu/toy-robot-elixir&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
As a learner, I wanted to practice as many language features as possible, so included in the Toy Robot test code I tried:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Different data structures such as Struct and List  &lt;/li&gt;
  &lt;li&gt;
Pattern matching  &lt;/li&gt;
  &lt;li&gt;
Data piping (&lt;code class=&quot;inline&quot;&gt;|&gt;&lt;/code&gt;)  &lt;/li&gt;
  &lt;li&gt;
&lt;code class=&quot;inline&quot;&gt;Agent&lt;/code&gt; for managing states  &lt;/li&gt;
  &lt;li&gt;
Macros for validation rules  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
I kept the test code small and nimble on purpose to illustrate the readability of a highly expressive language. I may not have exercised all the language features (such as supervisors and protocols), but at that point I felt I could start the Elixir journey with a big smile on my face already.&lt;/p&gt;
&lt;h2&gt;
Learning Phoenix&lt;/h2&gt;
&lt;p&gt;
I have to admit, a big part of the reason that got me started on learning Elixir is to build a side project. Initially I wanted to simply use Ruby and Rails because I can be very productive using them, but in the end I decided on learning Elixir and &lt;a href=&quot;http://www.phoenixframework.org/&quot;&gt;Phoenix&lt;/a&gt;, because &lt;strong&gt;most side projects fail, and when they do, how much learning can you extract out of those experiences?&lt;/strong&gt; My answer to this question, is to learn a new language, a new framework and most importantly a new programming paradigm to drastically increase the amount of learning I could gain.&lt;/p&gt;
&lt;p&gt;
But please, before you jump on Phoenix, learn Elixir properly first! Over the years I’ve seen far too many cases of people jumping on Rails without understanding the language features of Ruby…&lt;/p&gt;
&lt;p&gt;
To learn Phoenix, the &lt;a href=&quot;https://pragprog.com/book/phoenix/programming-phoenix&quot;&gt;Programming Phoenix&lt;/a&gt; book is pretty much the bible on this subject aside from the official guide, and it is written by Chris McCord, the author of Phoenix, Bruce Tate, and Jose Valim, the author of Elixir.&lt;/p&gt;
&lt;p&gt;
As an experienced Rails developer, it only took me a day or two to go through the book (I largely skimmed the chapters on &lt;code class=&quot;inline&quot;&gt;Channels&lt;/code&gt; as I don’t intend to build a real-time app).&lt;/p&gt;
&lt;p&gt;
Now, as a Ruby developer, the biggest pain point when working on Rails or other sizeable Ruby MVC projects for me, is ActiveRecord. I got so frustrated to the status quo I even &lt;a href=&quot;https://github.com/fredwu/datamappify&quot;&gt;attempted to fix it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Fortunately, in Elixir and Phoenix we have &lt;a href=&quot;https://github.com/elixir-ecto/ecto&quot;&gt;Ecto&lt;/a&gt;. One of my favourite features of Ecto is the concept of &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.Changeset.html&quot;&gt;Changeset&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Take a look at the code snippet below:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;defmodule MySecretApp.User do
  use MySecretApp.Web, :model

  schema &quot;users&quot; do
    field :username, :string
    field :email, :string
    field :password, :string, virtual: true
    field :encrypted_password, :string

    has_one :profile, MySecretApp.Profile

    timestamps()
  end

  def changeset(struct, params \\ %{}) do
    struct
    |&gt; cast(params, [:username, :email])
    |&gt; validate_required([:username, :email])
    |&gt; validate_length(:username, min: 3, max: 20)
    |&gt; validate_format(:username, ~r/\A[a-zA-Z0-9_]+\z/, message: &quot;alphanumeric and underscores only&quot;)
    |&gt; validate_format(:email, ~r/@/)
    |&gt; unique_constraint(:username)
    |&gt; unique_constraint(:email)
  end

  def creation_changeset(struct, params \\ %{}) do
    struct
    |&gt; changeset(params)
    |&gt; password_changeset(params)
  end


  defp password_changeset(struct, params) do
    struct
    |&gt; cast(params, [:password])
    |&gt; validate_required([:password])
    |&gt; validate_length(:password, min: 8, max: 200)
    |&gt; encrypt_password
  end

  defp encrypt_password(changeset) do
    case changeset do
      %Ecto.Changeset{valid?: true, changes: %{password: password}} -&gt;
        put_change(changeset, :encrypted_password, Comeonin.Bcrypt.hashpwsalt(password))
      _ -&gt;
        changeset
    end
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
The &lt;code class=&quot;inline&quot;&gt;changset/2&lt;/code&gt; function can be used when a user needs to be updated, whereas the &lt;code class=&quot;inline&quot;&gt;creation_changeset/2&lt;/code&gt; is to be used only when a user is first created. Sure, you can achieve similar result in Rails by using custom validators, but the fact that this practice is enforced by the library and the framework, is encouraging.&lt;/p&gt;
&lt;p&gt;
One other thing that I’ve seen in larger Rails apps, is the leaky abstraction of view-level logic, they typically sit in controllers, helpers (which are globally available) or worse, models. Phoenix in this case follows what &lt;a href=&quot;http://hanamirb.org/&quot;&gt;Hanami&lt;/a&gt;, &lt;a href=&quot;http://trailblazer.to/&quot;&gt;Trailblazer&lt;/a&gt; and many other frameworks do: it introduces a “view model” layer.&lt;/p&gt;
&lt;p&gt;
Something along the lines of:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;defmodule MySecretApp.UserView do
  use MySecretApp.Web, :view

  def full_name do
    &quot;#{title} #{first_name} #{last_name}&quot;
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
In a nutshell, Phoenix is just like Rails, but less magical (in a good way) and faster. :) Like the Elixir eco-system, most libraries and concepts feel like their ruby/rails counterparts, but more refined.&lt;/p&gt;
&lt;p&gt;
There are a few popular &lt;a href=&quot;https://hex.pm/&quot;&gt;Hex&lt;/a&gt; packages if you’re familiar with their ruby counterparts:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left;&quot;&gt;
      &lt;/th&gt;
      &lt;th style=&quot;text-align: left;&quot;&gt;
Elixir      &lt;/th&gt;
      &lt;th style=&quot;text-align: left;&quot;&gt;
Ruby      &lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
Code analysis      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://hex.pm/packages/credo&quot;&gt;credo&lt;/a&gt;      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://rubygems.org/gems/rubocop&quot;&gt;rubocop&lt;/a&gt;      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
Testing      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://hex.pm/packages/espec&quot;&gt;espec&lt;/a&gt;      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://rubygems.org/gems/rspec&quot;&gt;rspec&lt;/a&gt;      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
Browser testing      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://hex.pm/packages/wallaby&quot;&gt;wallaby&lt;/a&gt;      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://rubygems.org/gems/capybara&quot;&gt;capybara&lt;/a&gt;      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
Test coverage      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://hex.pm/packages/excoveralls&quot;&gt;excoveralls&lt;/a&gt;      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://rubygems.org/gems/simplecov&quot;&gt;simplecov&lt;/a&gt;      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
Test factory      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://hex.pm/packages/ex_machina&quot;&gt;ex_machina&lt;/a&gt;      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://rubygems.org/gems/factory_girl&quot;&gt;factory_girl&lt;/a&gt;      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
Authentication      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://hex.pm/packages/guardian&quot;&gt;guardian&lt;/a&gt;      &lt;/td&gt;
      &lt;td style=&quot;text-align: left;&quot;&gt;
&lt;a href=&quot;https://rubygems.org/gems/devise&quot;&gt;devise&lt;/a&gt;      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;
And the list goes on and on… Be sure to check out &lt;a href=&quot;https://github.com/h4cc/awesome-elixir/&quot;&gt;Awesome Elixir&lt;/a&gt; for more community curated Elixir libraries.&lt;/p&gt;
&lt;h2&gt;
Learning Machine Learning&lt;/h2&gt;
&lt;p&gt;
As I started building the foundation of my side project, you know, the usual user management and session management, etc, etc, by chance I came across mentions of &lt;a href=&quot;https://en.wikipedia.org/wiki/Bayesian_inference&quot;&gt;Bayesian inference&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
One thing led to another, very soon I started looking into varies different machine learning algorithms such as &lt;a href=&quot;https://en.wikipedia.org/wiki/Naive_Bayes_classifier&quot;&gt;Naive Bayes&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Random_forest&quot;&gt;Random Forest&lt;/a&gt;. These are all subjects I had never come across or even heard of before, as I have no strong mathematics, statistics or computer science background. I was however, intrigued and inspired nonetheless, and wanted to employ some machine learning in my side project.&lt;/p&gt;
&lt;p&gt;
And the best way to understand and learn about machine learning algorithms? You guessed, is to write one! After some research, I came to the conclusion that Naive Bayes is one of the simplest to implement, is great for text classification which is useful for my side project, and has good accuracy given its simplicity and fast speed.&lt;/p&gt;
&lt;h3&gt;
Introducing &lt;a href=&quot;https://github.com/fredwu/simple_bayes&quot;&gt;Simple Bayes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
So, after a few train rides to and from work, on my newly purchased Macbook (yes, the one with only a single USB-C port), I built a Naive Bayes library: &lt;a href=&quot;https://github.com/fredwu/simple_bayes&quot;&gt;Simple Bayes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
As of writing, this is the only Naive Bayes library in Elixir that supports the following features:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Naive Bayes algorithm with different models    &lt;ul&gt;
      &lt;li&gt;
Multinomial      &lt;/li&gt;
      &lt;li&gt;
Binarized (boolean) multinomial      &lt;/li&gt;
      &lt;li&gt;
Bernoulli      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
No external dependencies  &lt;/li&gt;
  &lt;li&gt;
Ignores stop words  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Additive_smoothing&quot;&gt;Additive smoothing&lt;/a&gt;  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Tf-idf&quot;&gt;TF-IDF&lt;/a&gt;  &lt;/li&gt;
  &lt;li&gt;
Optional keywords weighting  &lt;/li&gt;
  &lt;li&gt;
Optional word stemming via &lt;a href=&quot;https://github.com/fredwu/stemmer&quot;&gt;Stemmer&lt;/a&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Let’s see it in action shall we? :)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;SimpleBayes.init
|&gt; SimpleBayes.train(:ruby, &quot;I enjoyed using Rails and ActiveRecord for the most part.&quot;)
|&gt; SimpleBayes.train(:ruby, &quot;The Ruby community is awesome.&quot;)
|&gt; SimpleBayes.train(:ruby, &quot;There is a new framework called Hanami that&apos;s promising.&quot;)
|&gt; SimpleBayes.train(:ruby, &quot;Please learn Ruby before you learn Rails.&quot;)
|&gt; SimpleBayes.train(:ruby, &quot;We use Rails at work.&quot;)
|&gt; SimpleBayes.train(:elixir, &quot;It has Phoenix which is a Rails-like framework.&quot;)
|&gt; SimpleBayes.train(:elixir, &quot;Its author is a Rails core member, Jose Valim.&quot;)
|&gt; SimpleBayes.train(:elixir, &quot;Phoenix and Rails are on many levels, comparable.&quot;)
|&gt; SimpleBayes.train(:elixir, &quot;Phoenix has great performance.&quot;)
|&gt; SimpleBayes.train(:elixir, &quot;I love Elixir.&quot;)
|&gt; SimpleBayes.train(:php, &quot;I haven&apos;t written any PHP in years.&quot;)
|&gt; SimpleBayes.train(:php, &quot;The PHP framework Laravel is inspired by Rails.&quot;)
|&gt; SimpleBayes.classify(&quot;I wrote some Rails code at work today.&quot;)
# =&gt; [
# ruby: 0.20761437345986136,
# elixir: 0.08101868169313056,
# php: 0.019047884912605735
# ]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
As you can see, provided with reasonable training data, Naive Bayes can work extremely well.&lt;/p&gt;
&lt;h3&gt;
Introducing &lt;a href=&quot;https://github.com/fredwu/stemmer&quot;&gt;Stemmer&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
Something I discovered as I was building Simple Bayes, is something called &lt;a href=&quot;https://en.wikipedia.org/wiki/Stemming&quot;&gt;stemming&lt;/a&gt;. Let’s see another example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;SimpleBayes.init(stem: false)
|&gt; SimpleBayes.train(:apple, &quot;buying apple&quot;)
|&gt; SimpleBayes.train(:banana, &quot;buy banana&quot;)
|&gt; SimpleBayes.classify(&quot;buy apple&quot;)
# =&gt; [
# banana: 0.057143654737817205,
# apple: 0.057143654737817205
# ]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Oops, the probabilities of the sentence “buy apple” of being &lt;code class=&quot;inline&quot;&gt;apple&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;banana&lt;/code&gt; are the same, that’s not good, as we know “buying” and “buy” should mean the same thing. This is where stemming comes in handy. Let’s enable stemming and run the example again:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;elixir language-elixir&quot;&gt;SimpleBayes.init(stem: true)
|&gt; SimpleBayes.train(:apple, &quot;buying apple&quot;)
|&gt; SimpleBayes.train(:banana, &quot;buy banana&quot;)
|&gt; SimpleBayes.classify(&quot;buy apple&quot;)
# =&gt; [
# apple: 0.18096114003107086,
# banana: 0.1505149978319906
# ]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Stemming in this case correctly identified “buy” and “buying” as the same thing (they both have the stemmed root of “buy”).&lt;/p&gt;
&lt;p&gt;
How did I include stemming in Simple Bayes you wonder? Let me introduce you to &lt;a href=&quot;https://github.com/fredwu/stemmer&quot;&gt;Stemmer&lt;/a&gt; - as of writing this is the only Porter2 stemmer available in Elixir. :)&lt;/p&gt;
&lt;p&gt;
Elixir and BEAM may not be known for their raw performance, but compared to a similar Porter2 stemmer implemented in pure ruby, my Elixir version runs about five times faster (under &lt;code class=&quot;inline&quot;&gt;5&lt;/code&gt;s to stem 29000+ words, compared to &lt;code class=&quot;inline&quot;&gt;25&lt;/code&gt;s with the ruby version).&lt;/p&gt;
&lt;h2&gt;
The Learning Continues…&lt;/h2&gt;
&lt;p&gt;
I’ve only been writing Elixir for a month, and there are still heaps to learn and to practice. So far though, I have to say my experience with both Elixir and Phoenix has been fantastic - I get the same satisfying and pumped feeling I got when I was learning Ruby and Rails years ago.&lt;/p&gt;
&lt;p&gt;
If you haven’t checked out Elixir, I strongly encourage you to do so, after all, the world seems to be moving in the concurrency direction at an increasingly rapid speed, including &lt;a href=&quot;http://engineering.appfolio.com/appfolio-engineering/2015/11/18/ruby-3x3&quot;&gt;Ruby 3x3&lt;/a&gt;. :)&lt;/p&gt;
]]&gt;</content>
    <published>2016-07-23T18:33:29.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/i-accidentally-some-machine-learning-my-story-of-a-month-of-learning-elixir"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/i-accidentally-some-machine-learning-my-story-of-a-month-of-learning-elixir</id>
    <title>I Accidentally Some Machine Learning - My Story of A Month of Learning Elixir</title>
    <updated>2016-07-23T18:33:29.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
UPDATE: Reading Hacker News &lt;a href=&quot;https://news.ycombinator.com/item?id=11429590&quot;&gt;comments&lt;/a&gt; it is apparent to me that I have not explained my position and intention very well. Please allow me to clarify - I do not resent the people involved in these moments, if anything, I thought those encounters were good learning experience for me, as I know there are things I would do and say differently so others around me won’t feel I’ve been too harsh on them. I did not share these moments to complain, but to raise the awareness that kinder communication styles might be better received.&lt;/p&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;p&gt;
As a software professional who’s been in the industry long enough to have accumulated quite a few battle scars, I tend not to think back about the unpleasant, unfair and sometimes bizarre moments that make me feel depressed or angry. If there is one thing I learnt over the years, it’s to let go and move on.&lt;/p&gt;
&lt;p&gt;
Despite the effort in staying positive, I still believe it is valuable to share these moments. If you work in software and feel like you are being treated poorly, you are definitely not alone. By openly sharing these moments, I am hoping more people will start taking notice, and perhaps will lend a hand or a shoulder when a colleague is in need of support.&lt;/p&gt;
&lt;p&gt;
So, I’d like to share a few moments that have happened in my career, in no particular order. There are a few of these moments that I still find hard to swallow, but most of them I simply chuckle whenever I think about them. ;)&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
One morning I came to work, noticed a few server alerts that required attention. This was pre AWS and the whole Devops automation movement days so I needed to log onto the server. For some reason my access was revoked so I had to log into the hosting dashboard to reset the root password. The senior dev on the team turned up, and when asked about my access, casually told me: “oh you did something on the servers the other day, so I removed your access.”  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
CTO asked me to help building a new mission critical PHP and MySQL service, upon discovering an existing service already built on Ruby and MongoDB by three very senior developers and consultants, CTO said the reason for the rebuild was because “ActiveRecord is too slow.” Let me remind you that the initial Ruby solution was built using MongoDB. At the time the company had about 30-40 experienced Ruby developers, and only 2 junior to mid level PHP developers who were specifically hired to build the PHP/MySQL service which turned out to be a disaster.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
As the team lead who was responsible for evaluating performance and setting salary, I was told by one of my team members that they had gotten a pay rise. Surprised, I asked one of the founders of the company who is non-technical what was going on, during the conversation I discovered another pay rise was given to one other member of my team - all without consulting or even informing me.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
Excited to have had my overseas conference talk proposal accepted, I asked the new head of our department who had joined our company for only a month or two for travel approval. Fully expected a pad on the back, I was surprised to have been told that I cannot go, with the reason being “it is just not a good time”.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
One of our servers needed to be rebuilt, so trying to be a team player and help, I started installing some basic packages. The senior dev on the team turned to me, straight faced and enunciated in a deep and cold voice: “don’t touch anything on it, this is my server!”  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
The new development manager walked into our meeting, pulled me out, guided me to another meeting room where the new CTO was waiting. The new CTO opened with “so it’s time for us to go separate ways”, and implied the reason being something that I could definitely have pushed for unfair dismissal. Trying to be professional, I walked back into my original meeting, but the new development manager quickly pulled me out and said “you need to go right now”. He then stood behind me, saw me formatted my work laptop, then literally escorted me out of the office. The new development manager and CTO soon drove the company to the ground and left the country.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
After submitting the salary increases for the all my direct reports, I was delighted that the CEO was happy with them all. Given I have had no pay increases in over one and half years I asked for a modest pay increase for myself. I was told “no, we have to discuss this after your project delivery.” My direct reports and I were in the same delivery team.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
It was near the end of the working day, around 5pm, as the person coordinating the developer recruitment in our area I ping’ed our Slack channel with a lighthearted message to encourage our developers to start reviewing some code tests from job candidates if they’re free. One person replied: “My end of day activity is doing the stuff I should have been doing all day instead of the other things that came up.”  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
One day I got really really sick - difficult to walk or even stand still, dizzy and having fever. Upon informing one of the founders, I was told to come into the office, even though there was no urgent tasks need to be done. Reluctantly obliged, I came into the office, went out for lunch with colleagues but I was so sick I could not eat anything and can barely sit straight. I then went home, and was sick for the rest of the week…  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
My manager, who was the general manager, wanted to fire two of my developers. His tactic for firing the senior developer was to make the senior developer role redundant and offering the dev a junior developer role instead.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
I was tasked to investigate setting up a Cassandra database cluster. Upon discovering certain network limitations, I approached the then head of network operations, when asked about the inability to access the Cassandra cluster, he replied confidently that “it works”. Later on I discovered that he successfully telnet’ed to the ports and declared it “working”.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
The head of HR who is female and a big advocate for workplace diversity walked into my exit interview. The opening line was “Ordinarily I only do exit interviews with female employees…”  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
New to the big corporation environment, after asking around a few people and could not figure out how to provision an iPad for my team (but knowing some other teams do have company iPads), in an email reply by the head of enterprise IT: “Hey Gents, Fred has escalated this through multiple channels today. Awesome effort on his behalf, to seems like he didn’t like our response.” To which a colleague had to gently remind him that it wasn’t my fault there was no documented process for provisioning hardware.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
After having some discussions on our MySQL database, the dev who had recently joined my team all of a sudden raised his voice in an extremely arrogant and dismissive tone: “you clearly don’t understand this feature, do you?”  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
A senior software architect who I used to respect walked close by in a meetup. We worked for the same company a while back so I smiled, said hi and was about to start a conversation, he quickly cut me off with “I need to get a drink” without looking at me and wondered off.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
The general manager who is non-technical, asked me to investigate options to uplift our ageing bespoke ecommerce solution. Upon delivering my findings, I was told that “your findings are biased.” Later it was clear to me that he wanted confirmation of his agenda from the technical perspective and his mind had already been made on the approach we should take.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr class=&quot;thin&quot; /&gt;
&lt;blockquote&gt;
  &lt;p&gt;
A new big shot executive joined the company as our new CTO. He had no agile background and our company was transforming and pushing for a lot of agile principles at the time. Two weeks into his new appointment, the new CTO published an internal document titled “Controlled Chaos”. After reading the document everyone immediately realised that he was describing waterfall. The document was shared as a Google Doc and was open for comments, so people started asking hard questions. Weeks later, many of us who were vocal about his document were let go.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
There you are, these were my moments. What were yours? Care to share? :)&lt;/p&gt;
]]&gt;</content>
    <published>2016-04-05T11:40:45.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/developers-being-treated-poorly-you-are-not-alone"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/developers-being-treated-poorly-you-are-not-alone</id>
    <title>Developers, Being Treated Poorly? You Are Not Alone!</title>
    <updated>2016-04-05T11:40:45.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Ever since I started transitioning into a team leadership role over three years ago, I had been trying to find ways to &lt;a href=&quot;https://github.com/fredwu/security_guard&quot;&gt;eliminate waste caused by repetitive work&lt;/a&gt; and to &lt;a href=&quot;/blog/2013-06-27-datamappify-a-new-take-on-decoupling-domain/&quot;&gt;keep myself on the fringe of pushing the technical boundaries&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Four months ago I started my current role where my official job title is Delivery Lead. People don’t often know what a delivery lead is, but in my mind it is a role to ensure the success of the project delivery by identifying and closing the gaps in the team and in the organisation. And in order to do that, one of our responsibilities is to &lt;strong&gt;measure, understand and improve our team’s agile process&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;
It is very tempting to rely on the wonderful and powerful Excel formulas to help record and analyse data points and generate metrics such as cycle time. However, punching things into a spreadsheet is tedious, error-prone, time consuming and violates the &lt;a href=&quot;http://en.wikipedia.org/wiki/Don%27t_repeat_yourself&quot;&gt;DRY&lt;/a&gt; principle.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_nkwh303UV51qb7ot5o2_r1_1280.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
The spreadsheet I used to use for tracking cards.&lt;/p&gt;
&lt;h2&gt;
Introducing Amaze Hands&lt;/h2&gt;
&lt;p&gt;
As someone who strives to keep writing code even in a non-technical role, I started building a tool called &lt;strong&gt;&lt;a href=&quot;https://github.com/fredwu/amaze_hands&quot;&gt;Amaze Hands&lt;/a&gt;&lt;/strong&gt; to help reduce the amount of waste I accumulate as a delivery lead.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_nkwh303UV51qb7ot5o3_r1_1280.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Amaze Hands’ simple Web UI.&lt;/p&gt;
&lt;h2&gt;
Analyse Cards Like Poker Hands&lt;/h2&gt;
&lt;p&gt;
I &lt;a href=&quot;http://www.officialpokerrankings.com/fulltiltpoker/fredwu/poker/results/951A4EB3CE854778A584E3E92B2C7B1B.html?t=2&quot;&gt;used to play a bit of online poker&lt;/a&gt; and one thing you do in online poker is to look at your hand history to understand the game and your opponents.&lt;/p&gt;
&lt;p&gt;
If you think about it, agile boards are just like poker games - there is history to what has happened in the past, and &lt;strong&gt;in order to optimise for future gains, we need to understand what went wrong and what to improve on a case-by-case basis&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;
One of the teams I am apart of uses &lt;a href=&quot;http://leankit.com/&quot;&gt;LeanKit&lt;/a&gt;, whilst it is a good tool, its reporting functionalities are very limited and its XML export function is completely broken. As a result I started building Amaze Hands to parse the copy-pasted card history from LeanKit, and to eventually generate the metrics I care about.&lt;/p&gt;
&lt;p&gt;
The LeanKit strategy which consists of a parser and a transformer is able to parse the copy-pasted text from a card as shown below:&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_nkwh303UV51qb7ot5o1_1280.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
And below is the high level architecture of Amaze Hands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    +---------------------+
    |        Text         | &lt;- Raw text input.
    +----------+----------+
               |
+--------------v--------------+
|         Strategies          |
+-----------------------------+
|   +---------------------+   |
|   |       Parser        |   | &lt;- Parses text into an AST.
|   +----------+----------+   |
|              |              |
|   +----------v----------+   |
|   |     Transformer     |   | &lt;- Transforms the AST into a common AST.
|   +---------------------+   |
+--------------+--------------+
               |
    +----------v----------+
    |       Builder       | &lt;- Builds the dataset from the common AST.
    +----------+----------+
               |
    +----------v----------+
    |       Reducer       | &lt;- Filters the dataset.
    +----------+----------+
               |
    +----------v----------+
    |      Analyser       | &lt;- Analyses the dataset for metrics.
    +----------+----------+
               |
    +----------v----------+
    |      Producer       | &lt;- Produces metrics.
    +----------+----------+
               |
    +----------v----------+
    |      Presenter      | &lt;- Presents metrics.
    +---------------------+&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
Incremental Analysis - Zero In On Metrics That Matter&lt;/h2&gt;
&lt;p&gt;
No projects are equal, and no project teams are equal - &lt;strong&gt;the goal of Amaze Hands is to incrementally add intelligence to our agile process&lt;/strong&gt; that matters to a particular project and its delivery team.&lt;/p&gt;
&lt;p&gt;
By incrementally adding and/or filtering data points for analysis, we will be able to zero in on the problematic areas of our agile process. The following is a list of potential areas we could perform analysis on:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
cycle time  &lt;/li&gt;
  &lt;li&gt;
wait time  &lt;/li&gt;
  &lt;li&gt;
blocked time  &lt;/li&gt;
  &lt;li&gt;
knocked-back time  &lt;/li&gt;
  &lt;li&gt;
context switch (between different streams of work)  &lt;/li&gt;
  &lt;li&gt;
other factors such as meetings, attrition, etc  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
As of the time of writing, Amaze Hands supports the following common metrics:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
cycle time (mean and median)  &lt;/li&gt;
  &lt;li&gt;
cycle time rolling average (mean and median)  &lt;/li&gt;
  &lt;li&gt;
wait time (mean and median)  &lt;/li&gt;
  &lt;li&gt;
wait time rolling average (mean and median)  &lt;/li&gt;
  &lt;li&gt;
standard deviation rolling average  &lt;/li&gt;
  &lt;li&gt;
cycle time scatter  &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
It’s Just the Beginning&lt;/h2&gt;
&lt;p&gt;
Amaze Hands started off as an &lt;a href=&quot;http://hackday.com.au/&quot;&gt;REA Hackday&lt;/a&gt; project - on the technical level (hey I still see myself as a developer!), the tool was built in a way that it isn’t over-engineered (a.k.a. slow to get it out the door and validate its usefulness) but at the same time has multiple layers as shown in the architecture diagram above so I could refactor and optimise each layer independently when necessary.&lt;/p&gt;
&lt;p&gt;
It is still early stage, but I thought I’d share what I have right now to gather some feedback and perhaps inspire fellow project leaders to look into optimising your own workflow.&lt;/p&gt;
&lt;p&gt;
When I started Amaze Hands I was only leading one project team that uses LeanKit, but since last week I started leading another team that uses a physical wall - I can’t wait to adapt Amaze Hands to support the new input stream.&lt;/p&gt;
&lt;p&gt;
So, do &lt;em&gt;you&lt;/em&gt; have any interesting tools or techniques to help you lead projects? If so, I would love to hear about them!&lt;/p&gt;
]]&gt;</content>
    <published>2015-03-09T21:50:23.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/history-text-analysis-over-spreadsheets-a-poker-player-and-developers-road-to-agile-project-management"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/history-text-analysis-over-spreadsheets-a-poker-player-and-developers-road-to-agile-project-management</id>
    <title>History Text Analysis Over Spreadsheets - A Poker Player and Developer&apos;s Road to Agile Project Management</title>
    <updated>2015-03-09T21:50:23.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Two weeks ago as I was reading &lt;a href=&quot;http://softwareleadweekly.com/&quot;&gt;Software Lead Weekly&lt;/a&gt; which I had subscribed to for a while, I discovered its curator, &lt;a href=&quot;http://lnbogen.com/&quot;&gt;Oren Ellenbogen&lt;/a&gt;’s book - &lt;a href=&quot;http://leadingsnowflakes.com/&quot;&gt;Leading Snowflakes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
It was a moment of discovery that lead to a stream of delightfulness.&lt;/p&gt;
&lt;h2&gt;
It Started with a Long Day at Work…&lt;/h2&gt;
&lt;p&gt;
After a long day at work, I was so beat I couldn’t even listen to audio books like I always do on my way to and from work. So I drove home that night in total silence. One thing that was on my mind at the time was - who should I reach out to for some advice and mentorship?&lt;/p&gt;
&lt;p&gt;
“Probably no one.” I shrugged, and decided to simply suck it up and sleep it off, hoping solutions to the problems I had would come to the light of the day.&lt;/p&gt;
&lt;p&gt;
I got home, opened up my laptop and started reading unread emails. “Software Lead Weekly, ah I haven’t read the latest issue yet,” I thought to myself, “I wonder who the curator is.”&lt;/p&gt;
&lt;h2&gt;
Discovered “Leading Snowflakes”&lt;/h2&gt;
&lt;p&gt;
And that had lead me to discover Oren Ellenbogen’s book &lt;em&gt;Leading Snowflakes&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;
On the book’s landing page it says:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
This isn’t another 350-page book you’ll agonize yourself over not reading. It’s short, concise and pragmatic. You’re busy, I get that.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
“This is great!” I thought, with a sense of uncontrollable excitement. After all, these days I found myself hard to justify spending hours and hours on books that talk about subjects in a hand-wavy manner. A short, concise and pragmatic book on leadership was exactly what I needed.&lt;/p&gt;
&lt;p&gt;
There was a free sample chapter for download, so I clicked on it without hesitation. To my surprise, after downloading the sample chapter, Oren reached out to me with a personal email, and that had quickly lead to a chain of emails of me writing about the things at work that I found challenging, and Oren providing sound advice and suggestions.&lt;/p&gt;
&lt;p&gt;
I felt not only grateful, but lucky. Over the years I had worked at varies places where I was fortunate enough to have met quite a few people who I consider my mentor. But to run into one who I had never met before, on the same night my spirit was crushed, and who were willing to not only listen to my rant but also to walk me through my problems and offer advice - it was like finding a needle in a haystack.&lt;/p&gt;
&lt;p&gt;
My decision to purchase the book was made half way through our email exchanges, as I was browsing through the links Oren provided on his book landing page (under the section of “Sharing my lessons learned”), I found them to be very insightful.&lt;/p&gt;
&lt;p&gt;
Convinced that Oren’s book would offer even more value, I quickly bought the complete package with the book and all 10 interviews.&lt;/p&gt;
&lt;h2&gt;
Leading Snowflakes - A Book Every Software Team Manager Should Read&lt;/h2&gt;
&lt;p&gt;
Fast forward to today, I have finally finished reading the book, and I could confidently say that this is a book every software team manager should read.&lt;/p&gt;
&lt;p&gt;
There is no hand-wavy theories or lectures, what the book offers is exactly what is said on the landing page - &lt;strong&gt;short, concise and pragmatic&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;
The book contains a lot of sound advice, practical tools and techniques as well as a ton of useful links. Reading through it, I can recall many moments where I thought to myself, “hey, that indeed is a very cool technique, I can totally see how I could use it and be better at leading.”&lt;/p&gt;
&lt;p&gt;
I am now ready to implement those techniques and I am hopeful that I would become a better team leader and my team would become a better team as a result. :)&lt;/p&gt;
&lt;p&gt;
If you are a software team manager, or aspire to be one, I highly recommend this book!&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;To purchase only the ebook itself, Oren has kindly offered a 20% off discount, &lt;a href=&quot;https://gumroad.com/l/engineeringmanager/FredWu&quot;&gt;click to buy now&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
]]&gt;</content>
    <published>2014-07-26T15:49:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/leading-snowflakes-by-oren-ellenbogen-a-pragmatic-book-on-software-team-leadership-i-wish-i-read-sooner"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/leading-snowflakes-by-oren-ellenbogen-a-pragmatic-book-on-software-team-leadership-i-wish-i-read-sooner</id>
    <title>Leading Snowflakes by Oren Ellenbogen - A Pragmatic Book on Software Team Leadership I Wish I Read Sooner</title>
    <updated>2014-07-26T15:49:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;a href=&quot;http://au.jsconf.com/&quot;&gt;JSConf AU&lt;/a&gt; has been fun - great talks and great venue! It was also a wonderful opportunity for me to test out my new camera kit: SONY A7r + Sony Sonnar T* FE 35mm f/2.8 ZA + Voigtlander Color Skopar 20mm f/3.5 SL-II with Novoflex SONY E-Mount to Nikon adapter.&lt;/p&gt;
&lt;p&gt;
Here’s the kit (taken by Nikon D800 + Tamron SP 24-70mm F/2.8 Di VC USD):&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_n3tdegPHgL1qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;
Photos from JSConf AU 2014:&lt;/h2&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_n3tdjop2fF1qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h3&gt;
Breakfast in North Melbourne:&lt;/h3&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_n3tdf866bQ1qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_n3tdkesdZO1qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h3&gt;
Conference:&lt;/h3&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_n3tdkt8Ssw1qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_n3tdlaiUXK1qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_n3tdlppG2q1qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_n3tdm2Cjp81qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_n3tdmhm7WE1qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_n3tdmsWQYL1qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_n3tdn3tC2q1qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_n3tdncTsGT1qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_n3tdnnF5Of1qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_n3tdnxcahR1qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Check out my &lt;a href=&quot;https://plus.google.com/photos/+FredWu/albums/6000598028937066865?sort=1&quot;&gt;Google+ Photo Album&lt;/a&gt; for larger versions.&lt;/p&gt;
]]&gt;</content>
    <published>2014-04-10T12:09:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/photos-from-jsconf-au-2014"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/photos-from-jsconf-au-2014</id>
    <title>Photos from JSConf AU 2014</title>
    <updated>2014-04-10T12:09:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Today a blog post titled “&lt;a href=&quot;http://www.sequoiacap.com/grove/posts/akzj/trial-week-our-hiring-secret&quot;&gt;Trial Week: Our Hiring Secret&lt;/a&gt;“ has made to the Hacker News homepage. I naively &lt;a href=&quot;https://twitter.com/fredwu/status/395097315913916416&quot;&gt;tweeted my dislike&lt;/a&gt; and now I feel obligated to share my thoughts in a more meaningful and constructive way.&lt;/p&gt;
&lt;p&gt;
First of all, congratulations to the Weebly team, as this trial week strategy is clearly working very well for them.&lt;/p&gt;
&lt;p&gt;
I, on the other hand, am against using a trial week for vetting candidates, and I am going to share my thoughts.&lt;/p&gt;
&lt;p&gt;
Let this serve as a reminder to the rest of us: every organisation and team is different, so &lt;strong&gt;think carefully before committing to a given strategy&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
One Week is a Major Commitment for the Candidate&lt;/h2&gt;
&lt;p&gt;
In Australia, a full time employee typically gets four weeks of annual leave, with one or two weeks of which used up for the Christmas / New year down time. We are looking at asking candidates to spend 33-50% of their vacation time to commit to a trial week for one company - a terrible ROI (Return On Investment) from the candidate’s perspective if you ask me.&lt;/p&gt;
&lt;p&gt;
Candidates who are currently employed, with multiple offers from other organisations are more likely to skip the trial week - from experience, this is often the higher quality candidate pool.&lt;/p&gt;
&lt;h4&gt;
Side Effects&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
Increases the likelihood of burnout due to the reduced vacation time  &lt;/li&gt;
  &lt;li&gt;
Shrinks the candidate pool  &lt;/li&gt;
  &lt;li&gt;
Misses top talents who are unable to make the one-week commitment  &lt;/li&gt;
  &lt;li&gt;
As a result, the overall quality of the candidate pool drops  &lt;/li&gt;
  &lt;li&gt;
Paints an image of “not-caring (enough) about the employee’s well being”  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Of course, since the trial week is paid for, the employee could always take unpaid leave from their current employer.&lt;/p&gt;
&lt;h4&gt;
Side Effect&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
Raises alarm bells at current workplace since one week of unpaid leave is significant  &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
One Week is a Major Commitment for the Team&lt;/h2&gt;
&lt;p&gt;
Given the trial only lasts a week - we better make it count! That means one or more current developers need to be assigned to take care of the trial developer - pairing and walking through existing systems, etc. This is assuming we are going to act responsibly, and not simply just direct the trial developers to their desks and ask them to “go for it”.&lt;/p&gt;
&lt;h4&gt;
Side Effects&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
Higher pressure for the team  &lt;/li&gt;
  &lt;li&gt;
More difficult to act on other priority tasks  &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
Developer Productivity Curve (One Week is Not Enough)&lt;/h2&gt;
&lt;p&gt;
From my experience of on-boarding new developers, it typically takes 4-8 weeks for a developer to become productive and effective in a new work environment.&lt;/p&gt;
&lt;p&gt;
According to Weebly, candidates are assigned with a project that is small enough to do in a week, but still resembles what the candidate would be doing if hired. It sounds great if it works, but for many organisations this is unfeasible, for instance:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
There is no small projects to assign, unless invented  &lt;/li&gt;
  &lt;li&gt;
Navigating documentation and source code would take days, if not weeks  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Either way, with one week of trial, the candidate is unlikely to have enough time to contribute as well as to be integrated into the team culture.&lt;/p&gt;
&lt;h4&gt;
Side Effects&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;
Higher chance of misjudging the candidate’s ability and productivity  &lt;/li&gt;
  &lt;li&gt;
Significantly higher chance of creating solutions misaligned with the team and/or the organisation  &lt;/li&gt;
  &lt;li&gt;
Higher maintenance cost should the team decides to keep the solutions created  &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
66% Hire Rate Suggests Deeper Hiring Issue&lt;/h2&gt;
&lt;p&gt;
Weebly at the end of their blog post writes:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
Our hire rate out of trial week is around 66%, which feels like the right level.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
I respectfully disagree. A 66% hire rate from the trial week is a 34% failure rate on the pre-trial week recruitment process, and this is significant.&lt;/p&gt;
&lt;p&gt;
Which brings us to…&lt;/p&gt;
&lt;h2&gt;
More Effective Ways to Vet a Candidate&lt;/h2&gt;
&lt;p&gt;
Where I work, we have a simple, three-step recruitment process:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
Complete a small and fun code challenge, in your own time and with your own pace. The code challenge usually takes 2-4 hours to complete.  &lt;/li&gt;
  &lt;li&gt;
Invited to our office to chat with our developers and founders, optionally done via video chat. This usually takes an hour or so.  &lt;/li&gt;
  &lt;li&gt;
Pairing session, usually takes 30-60min.  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
Step 2 and 3 are sometimes swapped. And we also check out the candidate’s Github account if available, and their past projects if public.&lt;/p&gt;
&lt;p&gt;
In the code challenge we vet the candidate’s problem-solving ability, software design sense, code quality, code style and ethics (it’s easy to tell whether they cheated).&lt;/p&gt;
&lt;p&gt;
During the chat we vet the candidate’s project experience, depth of knowledge, breadth of knowledge, communication skill and culture fit.&lt;/p&gt;
&lt;p&gt;
In the pairing session we vet the candidate’s development practice, thought process and the ability to articulate.&lt;/p&gt;
&lt;p&gt;
By the end of the three steps we are usually pretty confident on +1 or -1 to hire the candidate. If we aren’t, it’s a -1.&lt;/p&gt;
&lt;p&gt;
But hold on, didn’t I mention &lt;strong&gt;one week is not enough&lt;/strong&gt; for a candidate to be productive and effective? Yes! And that’s why &lt;strong&gt;most places have a three-month probation&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;
The difference between the long probation period and the short trial period, is not only in duration, but more importantly in &lt;strong&gt;commitment&lt;/strong&gt;. In my opinion, only when both parties are committed can you achieve great result.&lt;/p&gt;
&lt;p&gt;
So, let’s hear your say, what do &lt;em&gt;you&lt;/em&gt; think? :)&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://poll.fm/7516426&quot;&gt;Poll: Trial Week, Yay or Nay?&lt;/a&gt;&lt;/p&gt;
]]&gt;</content>
    <published>2013-10-29T12:09:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/on-hiring-trial-week-yay-or-nay"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/on-hiring-trial-week-yay-or-nay</id>
    <title>On Hiring: Trial Week - Yay or Nay?</title>
    <updated>2013-10-29T12:09:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
If you’re like me who uses both Sublime Text 3 and Dropbox, chances are you have your Sublime Text 3 folder synced in Dropbox.&lt;/p&gt;
&lt;p&gt;
I use my laptop as my primary workstation so most of the time it’s docked and charged. Occasionally when I do use it on battery power however I notice the extremely poor battery life - typically only 2-3 hours.&lt;/p&gt;
&lt;p&gt;
Eventually I realised the power consumption was caused by Sublime Text 3 generating a temp file in its “Index” folder every second or so, and that triggers Dropbox to do the syncing - causing it to take 40-50% of CPU constantly.&lt;/p&gt;
&lt;p&gt;
So, here’s a simple fix. Click on the Dropbox icon, then click on the gear icon and select “Preferences…”, select the “Advanced” tab, and select “Change Settings…” for Selective Sync to bring up the column-based file dialog, now simply find and untick the “Index” folder and you’re all set. :)&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_mujg5idzne1qalr27.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
]]&gt;</content>
    <published>2013-10-12T04:26:30.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/protip-unsync-the-index-folder-of-sublime-text-3-from-dropbox"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/protip-unsync-the-index-folder-of-sublime-text-3-from-dropbox</id>
    <title>Protip: Unsync the &quot;Index&quot; Folder of Sublime Text 3 from Dropbox</title>
    <updated>2013-10-12T04:26:30.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;em&gt;Please also see &lt;a href=&quot;/blog/2013-09-06-protip-ruby-devs-please-tweak-your-gc-settings/&quot;&gt;this blog post on tweaking your ruby GC settings&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
I use and love &lt;a href=&quot;https://github.com/bmabey/database_cleaner&quot;&gt;DatabaseCleaner&lt;/a&gt;, although historically I had never paid too much attention on the performance of its varies cleaning strategies - I’d always used &lt;code class=&quot;inline&quot;&gt;truncation&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
We use Postgres, and after digging around and finding out &lt;a href=&quot;http://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886&quot;&gt;the difference between DELETE and TRUNCATE&lt;/a&gt;, I ended up improving our test suite speed by about 30-40% simply by tweaking the cleaning strategies.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;RSpec.configure do |config|
  config.before :suite do
    DatabaseCleaner.clean_with :truncation
    DatabaseCleaner.strategy = :transaction
  end

  config.before do
    if example.metadata[:js] || example.metadata[:type] == :feature
      DatabaseCleaner.strategy = :deletion
    else
      DatabaseCleaner.strategy = :transaction
      DatabaseCleaner.start
    end
  end

  config.after do
    DatabaseCleaner.clean
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Essentially, we want to &lt;code class=&quot;inline&quot;&gt;truncate&lt;/code&gt; the DB only once before the whole suite runs to ensure a clean slate DB, then we only want to use &lt;code class=&quot;inline&quot;&gt;deletion&lt;/code&gt; on Capybara tests, everything else should just use &lt;code class=&quot;inline&quot;&gt;transaction&lt;/code&gt; which is the fastest strategy.&lt;/p&gt;
&lt;p&gt;
Now, as a bonus, I have just discovered @amatsuda’s &lt;a href=&quot;https://github.com/amatsuda/database_rewinder&quot;&gt;DatabaseRewinder&lt;/a&gt; which is a lightweight alternative that supports only ActiveRecord. It offers comparable performance with a much similar API.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;RSpec.configure do |config|
  config.before :suite do
    DatabaseRewinder.clean_all
  end

  config.after do
    DatabaseRewinder.clean
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
By the way, we also use &lt;a href=&quot;https://github.com/grosser/parallel_tests&quot;&gt;parallel_tests&lt;/a&gt; to scale our test suite to multiple processes, even on Travis CI and Wercker.&lt;/p&gt;
&lt;p&gt;
Hooray to faster tests! :)&lt;/p&gt;
]]&gt;</content>
    <published>2013-09-18T06:50:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/protip-faster-ruby-tests-with-databasecleaner-and-databaserewinder"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/protip-faster-ruby-tests-with-databasecleaner-and-databaserewinder</id>
    <title>Protip: Faster Ruby Tests with DatabaseCleaner and DatabaseRewinder</title>
    <updated>2013-09-18T06:50:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
It was made apparent to me that many ruby devs either aren’t aware or couldn’t be bothered to tweak their ruby garbage collector settings.&lt;/p&gt;
&lt;p&gt;
Well, if you are using MRI, please start tweaking your GC settings. Here’s what I use (on my 15” Macbook Pro Retina):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;export RUBY_GC_MALLOC_LIMIT=90000000
export RUBY_FREE_MIN=200000&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Not only can you tweak it for your local dev machine, you can also tweak Jenkins, &lt;a href=&quot;http://travis-ci.com/&quot;&gt;Travis CI&lt;/a&gt;, &lt;a href=&quot;http://wercker.com/&quot;&gt;Wercker&lt;/a&gt; and other CI solutions, making instant speed gain for your test suite!&lt;/p&gt;
&lt;p&gt;
Here’s what we get:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;            Before After
Local:      8m22s 5m52s
Travis CI:  10m10s 6m0s
Wercker:    8m45s 6m24s&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
YMMV depending on your system and available RAM.&lt;/p&gt;
]]&gt;</content>
    <published>2013-09-06T11:13:37.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/protip-ruby-devs-please-tweak-your-gc-settings-for-tests"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/protip-ruby-devs-please-tweak-your-gc-settings-for-tests</id>
    <title>Protip: Ruby Devs, Please Tweak Your GC Settings for Tests!</title>
    <updated>2013-09-06T11:13:37.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Writing good, sensible tests is hard. As a Rubyist, I feel lucky to be part of a community that embraces tests. Though at the same time, I have come across too many projects that suffered from not having sensible tests.&lt;/p&gt;
&lt;h2&gt;
What are Sensible Tests?&lt;/h2&gt;
&lt;p&gt;
There often isn’t a silver bullet when it comes to software development. Technical stuff aside, many things contribute to the solution to a given problem - the team, the project and the business to name a few. This article does not attempt to present any insights into &lt;em&gt;the&lt;/em&gt; best practices for testing, rather it collects a few tips I believe would benefit those who are not yet comfortable with writing tests.&lt;/p&gt;
&lt;p&gt;
To me, sensible tests often have the following characteristics:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
it does not replicate implementation details;  &lt;/li&gt;
  &lt;li&gt;
it does not provide false sense of security;  &lt;/li&gt;
  &lt;li&gt;
it runs reasonably quickly;  &lt;/li&gt;
  &lt;li&gt;
it does not slow down the development significantly;  &lt;/li&gt;
  &lt;li&gt;
it guides the programmer towards a better architecture;  &lt;/li&gt;
  &lt;li&gt;
and, it does not make you &lt;em&gt;sigh&lt;/em&gt; every time you want to modify your tests.  &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
Art and Science, TDD or Not TDD&lt;/h2&gt;
&lt;p&gt;
Just like writing production code, writing tests is also a combined form of art and science. It takes not only &lt;em&gt;experience&lt;/em&gt;, but also &lt;em&gt;intuition&lt;/em&gt; to write sensible tests. You have to remember that not all projects and programmers are equal - &lt;strong&gt;take what you get, practise, and reflect on your findings&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;
Many times I had come across seasoned programmers practising TDD, only to find themselves cornered into a bad design that ultimately had to be thrown away. TDD does not save you from writing bad code, this article is not about TDD, it’s about testing in general.&lt;/p&gt;
&lt;p&gt;
I am most comfortable with using RSpec, FactoryGirl, Capybara and Turnip, so I’m going to use these tools in the code. The principles however apply to any testing framework.&lt;/p&gt;
&lt;h2&gt;
Test as Little as Possible to Reach a Given Level of Confidence&lt;/h2&gt;
&lt;p&gt;
Kent Beck, the inventor (or more correctly, ‘rediscoverer’) of TDD &lt;a href=&quot;http://stackoverflow.com/questions/153234/how-deep-are-your-unit-tests/153565#153565&quot;&gt;once said&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
I get paid for code that works, not for tests, so my philosophy is to test as little as possible to reach a given level of confidence.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
I used to prefer testing almost everything, but over the recent years I find myself increasingly &lt;strong&gt;look for key areas of the system that need the test coverage the most&lt;/strong&gt;. Typically, our systems would have:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
unit and functional tests for model behaviours  &lt;/li&gt;
  &lt;li&gt;
unit and functional tests for services  &lt;/li&gt;
  &lt;li&gt;
integration tests for controller actions  &lt;/li&gt;
  &lt;li&gt;
request tests for API endpoints  &lt;/li&gt;
  &lt;li&gt;
isolated JavaScript tests  &lt;/li&gt;
  &lt;li&gt;
high level integration/acceptance tests in Gherkin  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
Model and service level tests are arguably the most important ones so we make sure we have really good test coverage for those. For controller tests we rely heavily on reusable production &lt;em&gt;and&lt;/em&gt; test code for maintainability and sanity. For API endpoints we mostly test presented data structure - as business logic and data integrity should have been covered in model, service and controller layers. Isolated JavaScript tests take care of both presentational business logic and tricky UI tasks. And finally, acceptance tests handle happy-path user interactions.&lt;/p&gt;
&lt;h2&gt;
Do Not Test Framework and Library Code&lt;/h2&gt;
&lt;p&gt;
Writing application-specific business logic is difficult enough, you really should not test functionalities provided by the framework or libraries. Below is an example of such bad tests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;describe ApprovalStakeholder do
  it { should belong_to(:approval) }
  it { should_not validate_presence_of(:approval) }
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Similarly to how you would add useful comments, i.e. describe &lt;em&gt;why&lt;/em&gt; instead of &lt;em&gt;what&lt;/em&gt;, these tests should be replaced by tests that cover actual functionalities, for instance the reason why an &lt;code class=&quot;inline&quot;&gt;ApprovalStakeholder&lt;/code&gt; doesn’t need an &lt;code class=&quot;inline&quot;&gt;Approval&lt;/code&gt; to be presence should be demonstrated in the tests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;shared_examples_for &quot;non-approval specific stakeholder&quot; do
  its(:action_that_does_not_care_about_approval) { should be_true }
end

describe ApprovalStakeholder do
  let(:approval) { create(:approval) }
  let(:user) { create(:user) }
  let(:role) { create(:role) }

  subject do
    build(:approval_stakeholder,
      :user_id =&gt; user.id,
      :role_id =&gt; role.id
    )
  end

  context &quot;with an approval&quot; do
    before { subject.approval = approval }

    it_behaves_like &quot;non-approval specific stakeholder&quot;

    its(:action_that_does_care_about_approval) { should be_true }
  end

  context &quot;without an approval&quot; do
    it_behaves_like &quot;non-approval specific stakeholder&quot;

    its(:action_that_does_care_about_approval) { should be_false }
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
Ensure What You are Testing Makes Sense&lt;/h2&gt;
&lt;p&gt;
The test case below showcases the original developer’s lack of attention and awareness on designing a functional and secure system. It actually tests the reference keys for the &lt;code class=&quot;inline&quot;&gt;ApprovalStakeholder&lt;/code&gt; object are allowed to be mass assignable, which is a recipe for disaster.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;describe ApprovalStakeholder do
  it { should allow_mass_assignment_of(:user_id) }
  it { should allow_mass_assignment_of(:role_id) }
end&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
De-Duplicate Test Cases&lt;/h2&gt;
&lt;p&gt;
Looking at the example below, the first thing you’d notice is the amount of duplication.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;describe ApprovalStakeholder do
  it &quot;#traveller&quot; do
    stakeholder = create(:approval_stakeholder,
      :approval =&gt; approval,
      :user_id =&gt; traveller.id
    )
    stakeholder.stub(:user).and_return(traveller)
    approval.stub(:stakeholders_as).and_return([stakeholder])

    approval.traveller.should == traveller
  end

  it &quot;#authoriser&quot; do
    stakeholder = create(:approval_stakeholder,
      :approval =&gt; approval,
      :user_id =&gt; authoriser.id
    )
    stakeholder.stub(:user).and_return(authoriser)
    approval.stub(:stakeholders_as).and_return([stakeholder])

    approval.authoriser.should == authoriser
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
It’s true that tests act as a form of specification therefore should be optimised for clarity, in this case however, we could still maintain the clarity with significantly reduced duplication:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;describe ApprovalStakeholder do
  let(:stakeholder) do
    create(:approval_stakeholder,
      :approval =&gt; approval,
      :user_id =&gt; user.id
    )
  end

  subject { approval }

  before do
    stakeholder.stub(:user).and_return(user)
    approval.stub(:stakeholders_as).and_return([stakeholder])
  end

  describe &quot;#traveller&quot; do
    let(:user) { traveller }

    its(:traveller) { should == traveller }
  end

  describe &quot;#authoriser&quot; do
    let(:user) { authoriser }

    its(:authoriser) { should == authoriser }
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
Do Not Replicate Implementation Details&lt;/h2&gt;
&lt;p&gt;
I am often surprised to see many seasoned developers “enjoy” writing tests that essentially replicate the production code logic without much benefit. See below:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;describe ApprovalStakeholder do
  it &quot;references a user&quot; do
    approval_stakeholder = build :approval_stakeholder, :user_id =&gt; 1
    User.should_receive(:find).with(1)
    approval_stakeholder.user
  end

  it &quot;references a role&quot; do
    approval_stakeholder = build :approval_stakeholder, :role_id =&gt; 1
    Role.should_receive(:find).with(1)
    approval_stakeholder.role
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Rather than creating noisy tests, tests with actual assertions seem much more meaningful and readable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;describe ApprovalStakeholder do
  subject do
    build(:approval_stakeholder,
      :approval =&gt; approval,
      :user_id =&gt; user.id,
      :role_id =&gt; role.id,
    )
  end

  its(:name) { should == &quot;#{user.first_name} #{user.last_name}&quot; }
  its(:role_name) { should == role.name }
end&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
Reduce the Reliance on Mocks and Stubs&lt;/h2&gt;
&lt;p&gt;
This is a difficult and often-debated subject. In my experience, having too many mocks and stubs even though speeds up the test suite, usually leaves too many holes in your tests and makes the test suite less accurate and effective. Fortunately, by using more service objects (described below), mocking and stubbing become more manageable as you use them mostly on external objects and interfaces.&lt;/p&gt;
&lt;h2&gt;
Take Apart the System, One Service at a Time&lt;/h2&gt;
&lt;p&gt;
If you’re a Rails developer, you are already familiar with MVC. But just relying on MVC to hold your application architecture is probably not going to be sufficient for an average modern day web application. Many people like &lt;a href=&quot;http://en.wikipedia.org/wiki/Service-oriented_architecture&quot;&gt;Service-oriented architecture&lt;/a&gt;, so do I.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
Services are unassociated, loosely coupled units of functionality that are self-contained.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
In my experience, as long as you are disciplined in having services do one and only one thing really well, testing becomes much easier.&lt;/p&gt;
&lt;p&gt;
For instance, we have a &lt;code class=&quot;inline&quot;&gt;Bouncer&lt;/code&gt; service that is responsible for safeguarding resources - ensuring read-only attributes don’t get overridden.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;module Services
  class Bouncer
    def self.guard(resource, options = {})
      if options[:existing_resource]
        resource.readonly_attributes.each do |attr_name|
          resource.send(&quot;#{attr_name}=&quot;, options[:existing_resource].send(attr_name))
        end
      end

      resource
    end
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
The corresponding tests for this service are both fast and self-contained:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;describe Services::Bouncer do
  class BouncerDude
    include Mos::Entity

    set_readonly_attributes :age, :gender

    attribute :name
    attribute :age
    attribute :gender
  end

  let(:resource) { BouncerDude.new(name: &apos;Penny&apos;, age: 28, gender: &apos;female&apos;) }
  let(:existing_resource) { BouncerDude.new(name: &apos;Sheldon Cooper&apos;, age: 34, gender: &apos;male&apos;) }
  subject { Services::Bouncer.guard(resource, existing_resource: existing_resource) }

  describe &quot;#guard&quot; do
    its(:name) { should == &apos;Penny&apos; }
    its(:age) { should == 34 }
    its(:gender) { should == &apos;male&apos; }
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
Recognise Common Patterns and Refactor Them into Services&lt;/h2&gt;
&lt;p&gt;
One of the reasons why service-oriented architecture is so popular is because things are broken down into smaller, more manageable and more testable pieces. It is especially helpful for TDD practitioners as it significantly reduces the amount of coupling between your production code and your tests due to having simpler internals per test subject.&lt;/p&gt;
&lt;p&gt;
Take a look at the below example, which is hard to read, hard to test and error-prone:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;module ApplicationHelper
  def branch_logo_options(branch)
    BranchLogo.where(branch_id: branch.id).map { |logo| [logo.file, logo.id] }
  end

  def branch_options(agency)
    BranchRepository.find(agency_id: agency.id, archived: false).map do |b|
      [b.name, b.id]
    end
  end

  def agency_user_options(agency, filtered_users)
    filtered_user_ids = filtered_users.compact.map(&amp;:id) || []
    AgencyUserRepository.find(agency_id: agency.id, archived: false).select do |u|
      !filtered_user_ids.include?(u.id)
    end.map { |u| [u.full_name, u.id] }
  end

  def current_agency_user_options(filtered_users = [])
    agency_user_options(current_agency, filtered_users)
  end

  def current_agency_trust_bank_account_options
    BankAccountRepository.find(
      agency_id: current_agency.id,
      archived: false,
      account_type: BankAccount::TRUST_ACCOUNT).map do |b|
      [b.account_name, b.id]
    end
  end

  def code_options_for(klass)
    klass.all.map { |cc| [&quot;#{cc.code} - #{cc.name}&quot;, cc.id] }.sort
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Let’s refactor it into something more manageable, by introducing a service &lt;code class=&quot;inline&quot;&gt;ShowGirl&lt;/code&gt; for fetching and presenting data collections:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;module CollectionOptionsHelper
  def branch_logo_options(branch)
    Services::ShowGirl.present(branch, from: BranchLogo, show: :file)
  end

  def branch_options
    Services::ShowGirl.present(current_agency, from: BranchRepository)
  end

  def consultant_options(excluded_users = [])
    Services::ShowGirl.present(
      current_agency,
      from: AgencyUserRepository,
      show: :full_name
    ) do |collection|
      collection.reject { |user| user.id.in?(Array.wrap(excluded_users).map(&amp;:id)) }
    end
  end

  def trust_bank_account_options
    Services::ShowGirl.present(
      current_agency,
      from: BankAccountRepository,
      show: :account_name,
      filters: { account_type: BankAccount::TRUST_ACCOUNT },
    )
  end

  def code_options_for(name)
    Services::ShowGirl.present(
      current_agency,
      from: Admin::Configurations::Essential.descendants.find { |d| d.name =~ /::#{name.to_s.classify}/ },
      show: -&gt; (item) { &quot;#{item.code} - #{item.name}&quot; }
    )
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Better yet, we can clean it up even further by introducing another service, &lt;code class=&quot;inline&quot;&gt;BusBoy&lt;/code&gt; for just serving the data, and leaving &lt;code class=&quot;inline&quot;&gt;ShowGirl&lt;/code&gt; for only presenting the data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;module CollectionOptionsHelper
  def branch_logo_options(branch)
    Services::ShowGirl.present(
      Services::BusBoy.serve(:branch_logos, branch: branch)
    )
  end

  def branch_options
    Services::ShowGirl.present(
      Services::BusBoy.serve(:branches, agency: current_agency)
    )
  end

  def consultant_options(excluded_users = [])
    Services::ShowGirl.present(
      Services::BusBoy.serve(:consultants, agency: current_agency),
      show: :full_name
    ) do |collection|
      collection.reject { |user| user.id.in?(Array.wrap(excluded_users).map(&amp;:id)) }
    end
  end

  def trust_bank_account_options(account_type)
    Services::ShowGirl.present(
      Services::BusBoy.serve(:bank_accounts,
        { agency: current_agency, BankAccount::TRUST_ACCOUNT }
      ),
      show: :account_name
    )
  end

  def code_options_for(name, options = {})
    Services::ShowGirl.present(
      Services::BusBoy.serve(name, agency: current_agency), options
    )
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
Basic Controller CRUD Actions&lt;/h2&gt;
&lt;p&gt;
In one of our projects we have lots and lots of forms. Consequently we have lots and lots of CRUD actions. In order to keep our sanity as well as to make basic CRUD controllers maintainable, we have a custom DSL to make CRUD actions portable and testable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;module Profiles
  class TravellersController &lt; BaseController
    authorize_resource class: Traveller

    datamappify_resources entity: Traveller,
                          repository: TravellerRepository,
                          filter_by: :agency_id,
                          filter_value: -&gt; { current_user.agency_id }
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Most of our controller tests look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;require &apos;spec_helper&apos;

describe Profiles::AccountsController do
  let(:existing_resources) { [] }
  let(:create_resource) { Mos::Data.create_account }
  let(:create_resources) { Mos::Data.create_accounts(2) }
  let(:a_resource) { assigns(:resource) }
  let(:invalid_param) { { name: &apos;&apos; } }
  let(:params_key) { :account }
  let(:redirect_path) { profiles_accounts_path }

  it_behaves_like &apos;datamappify resources controller&apos;
  it_behaves_like &apos;searchable resources controller&apos;, :name,
                                                      :profile_id,
                                                      :branch_id,
                                                      :activated

  describe &quot;permission&quot; do
    context &apos;as a manager&apos; do
      before do
        sign_in_as :manager
      end

      it_behaves_like &apos;with write access&apos;
      it_behaves_like &apos;with read access&apos;
      it_behaves_like &apos;with index access&apos;
    end

    context &apos;as a consultant&apos; do
      before do
        sign_in_as :consultant
      end

      it_behaves_like &apos;without write access&apos;
      it_behaves_like &apos;with read access&apos;
      it_behaves_like &apos;with index access&apos;
    end
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
API Endpoint Tests&lt;/h2&gt;
&lt;p&gt;
One of our projects at work is an API service that is essential to our platform. Naturally, we not only need to test the models, services and controllers, we also need to ensure the API endpoints do what they are supposed to do - mostly exposing the correct data structure.&lt;/p&gt;
&lt;p&gt;
During the early stage of the development, I had come up with &lt;a href=&quot;https://github.com/fredwu/api_taster&quot;&gt;ApiTaster&lt;/a&gt; - a super useful gem for visually testing our Rails application’s APIs. Later on, as we continued to grow our API endpoints, we started utilising ApiTaster for our automated test suite too.&lt;/p&gt;
&lt;p&gt;
In essence, we have one API spec file responsible for describing which endpoints are tested and missed according to the information given by ApiTaster:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;describe &quot;API&quot; do
  load &apos;db/seeds.rb&apos;
  load &apos;spec/api_endpoints.rb&apos;

  ApiTaster::Route.map_routes

  ApiTaster::Route.defined_definitions.each do |route|
    it &quot;api endpoint #{route[:verb]} #{route[:path]}&quot; do
      params = ApiTaster::Route.params_for(route).first
      expectation = ApiTaster::Route.metadata_for(route)[:expectation]
      setup = ApiTaster::Route.metadata_for(route)[:setup]
      verb = route[:verb].downcase
      path = parse_path_with_url_params(route[:path], params[:url_params])

      setup.call if setup

      send verb, path, params[:post_params]

      response.body.should match_json_expression(expectation)
    end
  end

  # warn about undefined definitions
  ApiTaster::Route.missing_definitions.each do |route|
    pending &quot;api endpoint #{route[:verb]} #{route[:path]}&quot;
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Then, we have a bunch of endpoint test files to do the actual testing, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;resource_response = ResponseHash[
  :response =&gt; {
    :id =&gt; Integer,
    :name =&gt; String,
    :token =&gt; String
  }
]

get &apos;/:version/company&apos;, {}, {
  :expectation =&gt; resource_response
}

post &apos;/:version/companies&apos;, {
  :model =&gt; FactoryGirl.attributes_for(:company)
}, {
  :expectation =&gt; resource_response
}

put &apos;/:version/companies/:id&apos;, {
  :id =&gt; 1,
  :model =&gt; { :name =&gt; &apos;New Company&apos; }
}, {
  :expectation =&gt; resource_response.with(:name =&gt; &apos;New Company&apos;)
}

delete &apos;/:version/companies/:id&apos;, {
  :id =&gt; 1
}, {
  :expectation =&gt; resource_response
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Notice that for API endpoint tests we don’t test the business logic or data integrity - these should be tested in models, services and controllers. What we do test are correct endpoints are exposed, correct parameters are accepted and correct data structures are returned.&lt;/p&gt;
&lt;h2&gt;
Isolated JavaScript Tests&lt;/h2&gt;
&lt;p&gt;
Many developers prefer to rely on their integration test suite to do JavaScript / UI testing. This approach is fine until you start making lots of front-end changes and constantly need to pinpoint the relevant feature spec.&lt;/p&gt;
&lt;p&gt;
Having an isolated JavaScript test suite (which should be run as part of your continuous integration process) is extremely beneficial and often saves debugging time.&lt;/p&gt;
&lt;p&gt;
I like &lt;a href=&quot;https://github.com/visionmedia/mocha&quot;&gt;Mocha&lt;/a&gt; so we use &lt;a href=&quot;https://github.com/jfirebaugh/konacha&quot;&gt;Konacha&lt;/a&gt; in our Rails app. Though Mocha with &lt;a href=&quot;http://chaijs.com/&quot;&gt;Chai&lt;/a&gt; is really not that different to &lt;a href=&quot;https://github.com/pivotal/jasmine&quot;&gt;Jasmine&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Custom JavaScript behaviour is obviously a good candidate for isolated testing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;coffeescript language-coffeescript&quot;&gt;#= require spec_helper

describe &quot;form toggle&quot;, -&gt;
  beforeEach -&gt;
    $(&quot;body&quot;).append(JST[&quot;templates/form/toggle&quot;])

  it &quot;hides the collapsible field by default&quot;, -&gt;
    $(&quot;.control-group.branch_deactivation_date&quot;).hasClass(&apos;in&apos;).should.be.false

  it &quot;does not override if there is already a value&quot;, -&gt;
    value = $(&quot;input#agency_deactivation_date&quot;).val()
    $(&quot;input#agency_activated&quot;).click()
    $(&quot;input#agency_deactivation_date&quot;).val().should.equal(value)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Sometimes it’s also useful to ensure library code is initiated and triggered correctly, if you have other custom JS interact with it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;coffeescript language-coffeescript&quot;&gt;#= require spec_helper
#= require bootstrap-datepicker

describe &quot;form dates&quot;, -&gt;
  beforeEach -&gt;
    @dateFormat = &apos;DD/MM/YYYY&apos;
    $(&quot;body&quot;).append(JST[&quot;templates/form/dates&quot;](dateFormat: @dateFormat))

  it &quot;has a placeholder&quot;, -&gt;
    $(&quot;input&quot;).attr(&quot;placeholder&quot;).should.equal(@dateFormat)

  it &quot;defaults to today&apos;s date&quot;, -&gt;
    $(&quot;input#empty&quot;).focus()
    $(&quot;input#empty&quot;).focus()
    $(&quot;input#empty&quot;).val().should.equal(moment().format(@dateFormat))

  it &quot;does not override if there is already a value&quot;, -&gt;
    value = $(&quot;input#filled&quot;).val()
    $(&quot;input#filled&quot;).focus()
    $(&quot;input#filled&quot;).val().should.equal(value)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
“Real” UI Tests&lt;/h2&gt;
&lt;p&gt;
Isolated JavaScript tests are super fast and useful. However, there are times when having pure JavaScript tests simply isn’t enough, due to the complicated nature of DOM interaction and template rendering.&lt;/p&gt;
&lt;p&gt;
A while ago our calendar widget was broken due to a production and UAT environment issue that was not picked up by our JavaScript test suite. Since then we started adding dedicated UI tests in our acceptance test suite (we use &lt;a href=&quot;https://github.com/jnicklas/turnip&quot;&gt;Turnip&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;gherkin language-gherkin&quot;&gt;@ui
Feature: UI
  Background:
    Given I am signed in
      And I go to agency consultants page
      And I click on &quot;Add New Consultant&quot;

  Scenario: Calendar
      When I click &quot;#agency_user_start_date&quot;
      And I click &quot;.day.active&quot; within &quot;.datepicker&quot;
      Then I should see today as part of the date field&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
Effective Acceptance Tests&lt;/h2&gt;
&lt;p&gt;
Writing acceptance tests - also known to many Rubyists as “&lt;a href=&quot;https://github.com/cucumber/cucumber&quot;&gt;Cucumber&lt;/a&gt; tests”, is a double-edged sword - it’s extremely useful, but very few developers can write good, maintainable &lt;a href=&quot;https://github.com/cucumber/gherkin&quot;&gt;Gherkin&lt;/a&gt;-style acceptance tests.&lt;/p&gt;
&lt;p&gt;
Here’s an example of a badly written feature spec with too much implementation details and noise:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;gherkin language-gherkin&quot;&gt;Feature: Session
  Background:
    Given I visit &quot;/&quot;
      And there is a user &quot;admin&quot; &quot;password&quot;

  Scenario: Sign in with valid credentials
      When I fill in &quot;Username&quot; with &quot;admin&quot;
      And I fill in &quot;Password&quot; with &quot;password&quot;
      And I click &quot;Sign In&quot;
      Then I should be on &quot;/dashboard&quot;

  Scenario: Sign in with invalid credentials
      When I fill in &quot;Username&quot; with &quot;admin&quot;
      And I fill in &quot;Password&quot; with &quot;invalid_password&quot;
      And I click &quot;Sign In&quot;
      Then I should not be on &quot;/dashboard&quot;

  Scenario: Sign out
      When I fill in &quot;Username&quot; with &quot;admin&quot;
      And I fill in &quot;Password&quot; with &quot;password&quot;
      And I click &quot;Sign In&quot;
      And I click &quot;Sign Out&quot;
      Then I should be on &quot;/sign_in&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
A much cleaner version with only high level, descriptive steps:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;gherkin language-gherkin&quot;&gt;Feature: Session
  Background:
    Given I am on the homepage
      And there is a user &quot;admin&quot; with password &quot;password&quot;

  Scenario: Sign in with valid credentials
      When I sign in as &quot;admin&quot; with password &quot;password&quot;
      Then I should be signed in

  Scenario: Sign in with invalid credentials
      When I sign in as &quot;admin&quot; with password &quot;invalid_password&quot;
      Then I should not be signed in

  Scenario: Sign out
    Given I am signed in as &quot;admin&quot; with password &quot;password&quot;
      When I sign out
      Then I should be signed out&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
Final Thoughts&lt;/h2&gt;
&lt;p&gt;
Writing good, sensible tests is hard. These examples and tips are by no means the silver bullet, and you might actually find some of them counter-intuitive in your particular situation. So again, take what you get, practise, and reflect on your findings. &lt;strong&gt;For Happiness!&lt;/strong&gt; :)&lt;/p&gt;
&lt;p&gt;
Do you have any tips to share? If so please feel free to add a few comments!&lt;/p&gt;
]]&gt;</content>
    <published>2013-08-26T14:11:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/writing-sensible-tests-for-happiness"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/writing-sensible-tests-for-happiness</id>
    <title>Writing Sensible Tests for Happiness</title>
    <updated>2013-08-26T14:11:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
I haven’t really used &lt;a href=&quot;http://sequel.rubyforge.org/&quot;&gt;Sequel&lt;/a&gt; much therefore I am definitely a newbie. However, after days and nights of frustration, endless debugging and some search-fu during the development of &lt;a href=&quot;https://github.com/fredwu/datamappify&quot;&gt;Datamappify&lt;/a&gt;, I have finally arrived at the conclusion that Sequel is a capable library, as long as you are aware of the gotchas.&lt;/p&gt;
&lt;h3&gt;
Gotcha 1: Always use “&lt;code class=&quot;inline&quot;&gt;select&lt;/code&gt;“/“&lt;code class=&quot;inline&quot;&gt;select_all&lt;/code&gt;“, or your data records will mysteriously have wrong IDs!&lt;/h3&gt;
&lt;p&gt;
In ActiveRecord, joining an associated model couldn’t be simpler:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;Post.joins(:author)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
In Sequel, despite having a similar API for models to declare associations and their corresponding primary and foreign keys, you cannot do a &lt;code class=&quot;inline&quot;&gt;join&lt;/code&gt; without specifying the keys:&lt;/p&gt;
&lt;p&gt;
&lt;em&gt;Not good:&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;Post.join(:authors)
# or
Post.join(Author)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
&lt;em&gt;Better:&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;Post.join(:authors, :id =&gt; :author_id)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
You would think the version above works - it doesn’t. Even worse, the above example &lt;strong&gt;will give you incorrect data&lt;/strong&gt; - the IDs of the Post records will now contain the IDs from their corresponding Author records! This is because upon a &lt;code class=&quot;inline&quot;&gt;join&lt;/code&gt;, Sequel merges attributes from both models into a single hash.&lt;/p&gt;
&lt;p&gt;
&lt;em&gt;The correct version:&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;Post.join(:authors, :id =&gt; :author_id).select(:posts __id, :posts__ title, :posts__body)
# or
Post.join(:authors, :id =&gt; :author_id).select_all(:posts)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_mrvsa2sBB81qz4rgp.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h3&gt;
Gotcha 2: Always call “&lt;code class=&quot;inline&quot;&gt;all&lt;/code&gt;“ at the end of the chain, or the chain will present data in a different format.&lt;/h3&gt;
&lt;p&gt;
In ActiveRecord, all of the below examples return an &lt;code class=&quot;inline&quot;&gt;ActiveRecord::Relation&lt;/code&gt; collection:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;Post.where(:title =&gt; &apos;Hello world&apos;)
Post.joins(:author)
Post.includes(:author)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
And indeed, calling &lt;code class=&quot;inline&quot;&gt;first&lt;/code&gt; on any of them returns an object of class &lt;code class=&quot;inline&quot;&gt;Post&lt;/code&gt; (assuming the result collection is not empty).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;Post.where(:title =&gt; &apos;Hello world&apos;).first.class #=&gt; Post
Post.joins(:author).first.class #=&gt; Post
Post.includes(:author).first.class #=&gt; Post&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
In Sequel, the below examples all return a &lt;code class=&quot;inline&quot;&gt;Sequel::DataSet&lt;/code&gt; collection:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;Post.where(:title =&gt; &apos;Hello world&apos;)
Post.eager(:author)
Post.eager_graph(:author)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
But let’s see what we get from calling &lt;code class=&quot;inline&quot;&gt;first.class&lt;/code&gt; on them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;Post.where(:title =&gt; &apos;Hello world&apos;).first.class #=&gt; Post
Post.eager(:author).first.class #=&gt; Post
Post.eager_graph(:author).first.class #=&gt; Hash&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Huh? Last one is a &lt;code class=&quot;inline&quot;&gt;Hash&lt;/code&gt;? It turns out, if you call &lt;code class=&quot;inline&quot;&gt;all&lt;/code&gt; at the end of chains to convert them to &lt;code class=&quot;inline&quot;&gt;Array&lt;/code&gt;s, then the returned collections are consistent:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;Post.where(:title =&gt; &apos;Hello world&apos;).all.first.class #=&gt; Post
Post.eager(:author).all.first.class #=&gt; Post
Post.eager_graph(:author).all.first.class #=&gt; Post&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_mrvsa2sBB81qz4rgp.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
]]&gt;</content>
    <published>2013-08-21T12:39:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/gotchas-in-the-ruby-sequel-gem"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/gotchas-in-the-ruby-sequel-gem</id>
    <title>Gotchas in the Ruby Sequel Gem</title>
    <updated>2013-08-21T12:39:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
A while ago I &lt;a href=&quot;/blog/2012-11-25-an-interview-with-yukihiro-matz-matsumoto/&quot;&gt;translated an interview with Matz&lt;/a&gt; done by a Chinese book publisher. The interview and the translation were well received, so this time I am translating another interview with Matz, done by Ito, the editor-in-chief from Japanese website &lt;a href=&quot;http://engineer.typemag.jp/article/matz&quot;&gt;Engineer Type&lt;/a&gt;. Since I don’t read Japanese, the translation is based on &lt;a href=&quot;http://www.ituring.com.cn/article/45484&quot;&gt;Turing Book’s Chinese translation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
The Chinese translator has done a great job translating the interview, but there are still many words and sentences lack sufficient context and therefore are difficult to grasp. I have put in many hours translating the text as well as doing researches to ensure the final article is readable. I hope you will enjoy it! :)&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_mp4x3eZbVc1qz4rgp.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; Thank you for doing an interview with us, Matz. I have just finished reading your latest book &lt;em&gt;&lt;a href=&quot;http://www.amazon.co.jp/dp/4822234630/&quot;&gt;The Future of Computing&lt;/a&gt;&lt;/em&gt;, could you perhaps talk about the future of programming and software programmers in general?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; Hmmm, this is difficult to answer… but thanks for reading my book!  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; In the book you’ve shared your thoughts on the past, present and future of different programming languages and software design patterns. Would you like to talk about the current state of the software industry? And is there going to be another paradigm shift in software development?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; As discussed in my book, to predict the future of a high tech industry such as computing is not particularly difficult. I believe in the foreseeable future the computing industry is still going to advance based on &lt;a href=&quot;http://en.wikipedia.org/wiki/Moore%27s_law&quot;&gt;Moore’s law&lt;/a&gt;. Although, it is possible that in the next year or two &lt;a href=&quot;https://en.wikipedia.org/wiki/Quantum_computer&quot;&gt;quantum computers&lt;/a&gt; become a practical reality, in that case it will change everything! *chuckles* On a serious note, according to Moore’s law, the cost of computing will decrease and the performance and capacity of computing will increase - this basic principle is unlikely to change. One thing I did notice in recent years is that due to the advancement in computer hardware, the software industry is subtly changing too.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
Software Development in the Era of Multi-core and Cloud Computing&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; It was about twenty years ago (in 1993) I invented the Ruby programming language, yet it still runs surprisingly well on modern computers.  &lt;/p&gt;
  &lt;p&gt;
What this means is that in the past twenty years the computing environment which the software runs on did not see any fundamental changes. In recent years, we started seeing computing power being shifted from having higher CPU frequencies to being distributed over more CPU cores. And that means software needs to move in that direction too.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_mp4x3p6yvF1qz4rgp.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Matz: Software has not seen major changes for years.&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; And this is covered in the last chapter from the book, right?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; Yes. Similarly to multi-core, cloud computing is advancing in the same direction. The future of computing is all about utilise multiple CPUs or computers effectively.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; So, how does that change software development?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; In the past ten years or so we have been seeing more and more things happen on the Internet, and the Internet is an amazing application platform for extension and distribution. Compared to software engineers working on &lt;a href=&quot;http://en.wikipedia.org/wiki/Mainframe_computer&quot;&gt;mainframe computers&lt;/a&gt;, web developers are naturally more familiar with the concepts of multi-core and cloud computing.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; After interviewing many web and mobile startups, we realised that the number of software engineers working in &lt;a href=&quot;http://en.wikipedia.org/wiki/Platform_as_a_service&quot;&gt;PaaS&lt;/a&gt; and cloud computing have been increasing rapidly.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; Absolutely. And I do believe that “not needing to purchase and own dedicated hardware” is going to be the mainstream. The idea and thought process of “not owning” is not only important for software development, but also important for business development.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
“Owning” Becoming a Liability, Not Asset&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; In the past, “owning” was seen as the source of vitality of a corporation - those who own high performance mainframe computers were able to do business transactions in high volume whereas those who do not were not able to compete.  &lt;/p&gt;
  &lt;p&gt;
These days the landscape is changing - those corporations who do not “own” expensive hardware have more competitive edge. Let’s say it takes five years to break even from the expensive investment of servers, during that time those machines are being put in use to realise their full potential and to justify their cost. It may appear to have saved the business cost but it is not, simply because the value of the hardware decreases as each day passes by.  &lt;/p&gt;
  &lt;p&gt;
To put simply, we are now entering the era of “owning” being a liability rather than asset. If you had the most advanced hardware, software engineers were able to develop efficiently. On the contrary, if you didn’t, then you might want to get used to the hours-long waiting for the code to compile. *chuckles* The rise of cloud computing platforms like &lt;a href=&quot;http://www.2gocn.com/&quot;&gt;Heroku&lt;/a&gt; is making “owning” a thing of the past.  &lt;/p&gt;
  &lt;p&gt;
Also, “not owning” has several advantages on the development as well as the commercial front. For instance, it allows many startups to rise. In the past, in order to start a new business you would need capital for purchasing servers and/or renting servers in a data centre. These days, to get started on a platform like Heroku couldn’t be easier, for example on Heroku you could start with just one dyno for free. This new way of developing software significantly reduces costs and risks.  &lt;/p&gt;
  &lt;p&gt;
Years ago I read an essay called &lt;a href=&quot;http://www.paulgraham.com/ramenprofitable.html&quot;&gt;Ramen Profitable&lt;/a&gt; by &lt;a href=&quot;http://ycombinator.com/&quot;&gt;Y Combinator&lt;/a&gt;’s founder &lt;a href=&quot;http://en.wikipedia.org/wiki/Paul_Graham_%28computer_programmer%29&quot;&gt;Paul Graham&lt;/a&gt;. “Not owning”’s flexibility and agility contribute a great deal to it. And this trend has now grown beyond just relevant to startups, in fact in the recent years many large corporations have begun adapting this approach too.  &lt;/p&gt;
  &lt;p&gt;
In the United States, corporations like Disney and Best Buy are indeed utilising Ruby, Rails and Heroku to rapidly grow their internal infrastructure in a cost-effective fashion. What was once considered competitive edges to venture capitalists, like “rapid development” and “development flexibility” are now also possible for these giant corporations.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; What about the giant corporations in Japan?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; I have never worked in a big corporation so I can’t tell where they are heading. People have been optimistic, though as an observer I am concerned.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_mp4x3tRPEw1qz4rgp.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;
Real Benefits of Innovation in Cloud Computing Not to Be Overlooked&lt;/h2&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; What makes you concerned about software development?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; The traditional approach of developing software is still the norm. For example, some corporations, even though use Amazon Web Services, still rely on system administrators to handle their infrastructure. It is too common to see a software development team consists of over a dozen people.  &lt;/p&gt;
  &lt;p&gt;
This in my opinion defeats the purpose and forfeits the benefits of “not owning” servers. There are simply too many of these case studies whereby only on the surface of cloud computing is explored and understood.  &lt;/p&gt;
  &lt;p&gt;
I have to say I am disappointed by some of the so-called “private clouds” owned by large corporations. The advantage of cloud computing is to utilise multiple computers in the cloud, but those private clouds are essentially their internal data centres. Isn’t that the same as owning a bunch of servers?  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_mp4x3ym1v61qz4rgp.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Matz: Many companies barely scratch the surface of emerging technologies.&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; Indeed, it is too often to see the real benefits of emerging technologies overlooked or misunderstood. Anything else that makes you concerned about the future?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; Nowadays the speed of development has always been a priority from big development projects in &lt;a href=&quot;http://en.wikipedia.org/wiki/Business-to-business&quot;&gt;B2B&lt;/a&gt; to small development projects in many startups. &lt;a href=&quot;http://www.yahoo.co.jp/&quot;&gt;Yahoo! Japan&lt;/a&gt; even coined a term “爆速化” (explosively high speed) to indicate the importance of development speed in the ever more competitive and engaging markets.  &lt;/p&gt;
  &lt;p&gt;
Looking at things this way, those so-called “system integrators” are becoming obsolete. Should they just give up what they do or continue? I don’t know, but I do know that the gap between them and engineers who have the capability and skills to create real value is increasing.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
Career Longevity of Software Engineering&lt;/h2&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; Who are those engineers who have the capability and skills to create real value?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; The ones who would put in effort to create software or systems from a prototype to a final product. And this has nothing to do with whether they work in web or system integration, or whether it’s consumer oriented or corporate oriented.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_mp4x42bl5b1qz4rgp.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Matz: Unbalanced skill combination leads to a gloomy future.&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; Do you mean the engineers who are capable from design to implementation?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; Yes. Speaking of which, software developers have to know more than just system design - they cannot survive without knowing how to code. Just like in life, you cannot survive without being down to earth. *chuckles*  &lt;/p&gt;
  &lt;p&gt;
Despite the fact that it is pointless to have someone doing only the system design, and not the development as well, the System Integration industry is still going strong in Japan - and it is in fact an industry with high profit margin.  &lt;/p&gt;
  &lt;p&gt;
Even if the system designers came up with questionable specifications, or if the programmers were sloppy so the software was terrible to use, users would still use it despite whining. Flaws are easily glossed over under high profit margins.  &lt;/p&gt;
  &lt;p&gt;
But just as discussed before, as development speed increases, profit margins would undoubtedly become smaller. Flaws are therefore harder to gloss over.  &lt;/p&gt;
  &lt;p&gt;
In my opinion, if things don’t change, those run-of-the-mill software engineers might not survive in five years. Worse, the junior to mid-level to senior programmer corporate ladder is going to collapse.  &lt;/p&gt;
  &lt;p&gt;
Say, you wouldn’t want to start a VHS rental shop when DVDs were on the rise, would you?  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
Difference Between Those Who Control Their Destiny to Those Who Don’t&lt;/h2&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; Do you have any advice for those who do not wish to be in a “gloomy future”? What can they do?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; To innovate and to create new things, I suppose.  &lt;/p&gt;
  &lt;p&gt;
It’s not all doom and gloom. Even though many ageing technologies have been or are being replaced by the web, jobs will not disappear overnight. I think many software developers will still be employed in those jobs.  &lt;/p&gt;
  &lt;p&gt;
Having said that, it is always good to create new things or even invent new programming languages.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; What are these “new things”?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; I see three types of new things.  &lt;/p&gt;
  &lt;p&gt;
First of all, new services. If you can create a new service, or a service that offers superior user experience - it would be an innovation.  &lt;/p&gt;
  &lt;p&gt;
Secondly, new technologies. To come up with technologies better than the existing ones - and this is what I have been doing.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;a href=&quot;https://github.com/mruby/mruby&quot;&gt;  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_mp4x45MsHv1qz4rgp.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://github.com/mruby/mruby&quot;&gt;mruby&lt;/a&gt; was released earlier this year on Github.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
Or, another way is to invent new algorithms.  &lt;/p&gt;
  &lt;p&gt;
The three ways I mentioned have different difficulties, but they share the same goal - to create something that hasn’t existed yet. Those who keep working on these kinds of challenges are the true outstanding software engineers.  &lt;/p&gt;
  &lt;p&gt;
The ones who do not challenge themselves to create new things are often falling behind - they learn a hip new language today and try a new web framework tomorrow, but still lack the foresight to invent and to improve.  &lt;/p&gt;
  &lt;p&gt;
Of course, it is important to learn and try new things, but if you see them as your ultimate goal then you will lose control of your destiny. I believe that the ones who do not get boggled down in every new trendy thing will ultimately be happier.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_mp4x48UgIX1qz4rgp.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;
Software Development is a Punch to Deficiency&lt;/h2&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; Here is a sharp question: be a follower rather than an inventor is always easier and perhaps makes more money too. What makes you keep inventing?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; My standard answer would be “because writing and running new programs make me happy”. But the real reason is because I don’t like deficiency.  &lt;/p&gt;
  &lt;p&gt;
There are people who have different opinions and thought processes, I would often come up with questions like “why was it done this way” or “this will be too hard to use”.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_mp4x4bIpWG1qz4rgp.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Matz dislikes deficiency, so he invented the ruby programming language.&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; True, but all products more or less reflect their producers’ preferences, right?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; Absolutely, and I am not saying that this is bad or anything. I just hate to point fingers at other people’s preferences - if you don’t like something, make your own! This is a basic trait of a good software engineer, and is what makes open source sustainable.  &lt;/p&gt;
  &lt;p&gt;
In open source projects, all the source code is publicly available therefore it is very easy to see how a program is designed. As long as you have ideas on how to improve and optimise the design, you are welcome to do so.  &lt;/p&gt;
  &lt;p&gt;
Now it is an entirely different story for certain things in the society. *chuckles* At least in software development, we can rely on our skills and knowledge to improve and to change. If it’s your own creation, it can be adjusted and adapted to suit the ever changing needs.  &lt;/p&gt;
  &lt;p&gt;
This is same for Ruby - I like programming languages and more importantly I like improving programming languages myself, and that’s why I still work on Ruby till this day.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
Software Development, One of the Rare Careers that Could Make a Change on Your Own&lt;/h2&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_mp4x4fRpd91qz4rgp.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Matz talking about developer happiness, wearing his “&lt;a&gt;Ruby City MATSUE&lt;/a&gt;“ polo shirt.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; I think I have the right personality for developing software. Only the software industry can tolerate my carefreeness - am I too arrogant for saying that? *chuckles*  &lt;/p&gt;
  &lt;p&gt;
In all honesty, software development is one of the rare careers that could bring positive changes to the society on your own. It’s a wonderful occupation that brings happiness and fulfillment!  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; Many people would predict their software future based on theories, but Matz you always use “happiness”.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; That’s right. Because only you can control your own destiny. It doesn’t matter if you were told to do things in a way just becasue “Matz said so” - ultimately, I cannot be responsible for your destiny. You should make your own decisions.  &lt;/p&gt;
  &lt;p&gt;
I would still say things like “the future might look like this”, but these are just my personal opinions.  &lt;/p&gt;
  &lt;p&gt;
And this is the same even for today’s discussion - if someone thinks he does not agree with what Matz has said, he should follow his own decision and the path he chooses.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
Exploring the Future: “You” Are the Only Constant&lt;/h2&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; Having read &lt;em&gt;The Future of Computing&lt;/em&gt;, I remember you talked about the inception and development of varies programming languages. But we all know that the IT industry is moving in a rapid pace, it is difficult to rely on history to guide us through to the future. If multi-core and cloud computing are only just the beginning of a paradigm shift, why did you write about the things happened in the past?&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_mp4x4ismcI1qz4rgp.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Matz: Technologies progress just like a pendulum clock.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; People see things differently - and I believe the IT industry is progressing in a manner similar to the swing motion of a pendulum clock.  &lt;/p&gt;
  &lt;p&gt;
As more and more new programming languages, techniques and frameworks pop up, software development related technologies are progressing whilst seeking for balance.  &lt;/p&gt;
  &lt;p&gt;
So, how does “the most balanced from the past” become “the most balanced right now”? Think about how pendulum clock swings and in the past how technologies have emerged - you could then predict roughly what would constitute “the most balanced in the future”.  &lt;/p&gt;
  &lt;p&gt;
Use “centralised computing vs distributed computing” as an example, in the past there was usually only one centralised mainframe computer, later on to increase the processing capability commodity server farms were utilised, and now we are moving towards cloud computing.  &lt;/p&gt;
  &lt;p&gt;
There is no point to look at a particular past event. If you wanted to predict a technology in the future, knowing what has contributed to the balance of a past technology’s rise and fall is going to help.  &lt;/p&gt;
  &lt;p&gt;
Human’s ability is one of the factors too, because we have limited capability as a language designer it is useful to look at what others have done to cater for our ability, and therefore improve and evolve the technology.  &lt;/p&gt;
  &lt;p&gt;
In the book I briefly talked about &lt;a href=&quot;http://www.dartlang.org/&quot;&gt;Dart&lt;/a&gt; and &lt;a href=&quot;http://golang.org/&quot;&gt;Go&lt;/a&gt;. As a programming language inventor I find it really fascinating to explore the thought processes behind those language designers. And it has helped me to gain a deeper understanding of human behaviour.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; I was going to ask why it is so important to study the past, now I know.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; I mentioned this in the beginning - computing has not seen major changes for years.  &lt;/p&gt;
  &lt;p&gt;
Programming languages invented over fifty years ago are still in use today, and Ruby has been around for twenty years now. This proves that computing is progressing slower than what a lot of people believe.  &lt;/p&gt;
  &lt;p&gt;
On that note, there are many past cases whereby focuses were put on what was cool and new without understanding why. Compared to those “follower” software developers, the ones who command and understand the principles and theories behind changes and progresses have a much longer career longevity.  &lt;/p&gt;
  &lt;p&gt;
If you are a software developer who wants a longer career longevity, please read &lt;em&gt;The Future of Computing&lt;/em&gt;! *chuckles*  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Ito:&lt;/strong&gt; Thank you Matz for talking to us today!&lt;/p&gt;
]]&gt;</content>
    <published>2013-06-29T12:04:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/the-future-of-computing-the-future-of-computer-programmers-an-interview-with-yukihiro-matz-matsumoto"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/the-future-of-computing-the-future-of-computer-programmers-an-interview-with-yukihiro-matz-matsumoto</id>
    <title>The Future of Computing, The Future of Computer Programmers - An Interview with Yukihiro &quot;Matz&quot; Matsumoto</title>
    <updated>2013-06-29T12:04:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;em&gt;This post is about the ruby library we are building - &lt;a href=&quot;https://github.com/fredwu/datamappify&quot;&gt;Datamappify&lt;/a&gt;, please go check it out.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
At &lt;a href=&quot;http://www.locomote.com.au/&quot;&gt;Locomote&lt;/a&gt; we are building a relatively large web application using Rails. Before we began to lay the foundation, we knew very well that if we wanted the project to be maintainable we had to architect the system with extra care and attention. More specifically, we can’t rely on simply using ActiveRecord which combines behaviour and persistence as our domain models.&lt;/p&gt;
&lt;p&gt;
We began our search for something that would help us decouple our application from the domain layer down to the form handling. We’ve found a couple of gems that are close to what we were after - &lt;a href=&quot;https://github.com/braintree/curator&quot;&gt;Curator&lt;/a&gt;, &lt;a href=&quot;https://github.com/joakimk/minimapper&quot;&gt;Minimapper&lt;/a&gt;, &lt;a href=&quot;https://github.com/nulogy/edr&quot;&gt;Edr&lt;/a&gt; and later on &lt;a href=&quot;https://github.com/apotonick/reform&quot;&gt;Reform&lt;/a&gt;. They are all wonderful gems but unfortunately none of them has everything we need.&lt;/p&gt;
&lt;p&gt;
Here are the things we need:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
Decouple domain logic and data persistence.  &lt;/li&gt;
  &lt;li&gt;
Decouple models and forms.  &lt;/li&gt;
  &lt;li&gt;
Support ActiveRecord (or at the very least, ActiveModel) so we can still use many of the awesome gems like &lt;a href=&quot;https://github.com/plataformatec/devise&quot;&gt;Devise&lt;/a&gt;, &lt;a href=&quot;https://github.com/plataformatec/simple_form&quot;&gt;SimpleForm&lt;/a&gt; and &lt;a href=&quot;https://github.com/carrierwaveuploader/carrierwave&quot;&gt;CarrierWave&lt;/a&gt;.  &lt;/li&gt;
  &lt;li&gt;
Support attributes mapped from different data sources (e.g. remote web services from third-party vendors).  &lt;/li&gt;
  &lt;li&gt;
Support lazy loading so that attributes stored on remote data sources will not get triggered upon loading the entities.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
All things considered, we bit the bullet and started working on &lt;a href=&quot;https://github.com/fredwu/datamappify&quot;&gt;Datamappify&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Before I go into details and examples, here is an extremely simplified overview of Datamappify’s architecture:&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/I9GpLds.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
As you can see, Datamappify is loosely based on &lt;a href=&quot;http://martinfowler.com/eaaCatalog/repository.html&quot;&gt;Repository pattern&lt;/a&gt; and &lt;a href=&quot;http://msdn.microsoft.com/en-au/library/ff649505.aspx&quot;&gt;Entity Aggregation pattern&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Datamappify has three main design goals:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
To utilise the powerfulness of existing ORMs so that using Datamappify doesn’t interrupt too much of your current workflow. For example, Devise would still work if you use it with a UserAccount ActiveRecord model that is attached to a User entity managed by Datamappify.  &lt;/li&gt;
  &lt;li&gt;
To have a flexible entity model that works great with dealing with form data. For example, SimpleForm would still work with nested attributes from different ORM models if you map entity attributes smartly in your repositories managed by Datamappify.  &lt;/li&gt;
  &lt;li&gt;
To have a set of data providers to encapsulate the handling of how the data is persisted. This is especially useful for dealing with external data sources such as a web service. For example, by calling UserRepository.save(user), certain attributes of the user entity are now persisted on a remote web service. Better yet, dirty tracking and lazy loading are supported out of the box!  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
You can read more about Datamappify in the project’s &lt;a href=&quot;https://github.com/fredwu/datamappify/blob/master/README.md&quot;&gt;README&lt;/a&gt;. For this blog post I will focus on how we use Datamappify in our Rails application. We are still early days in our application development and Datamappify still has quirks and issues, but I am hoping this post will illustrate some of the key benefits of Datamappify.&lt;/p&gt;
&lt;h3&gt;
Getting Started&lt;/h3&gt;
&lt;p&gt;
Getting started with using Datamappify is really easy. We simply include it in the &lt;code class=&quot;inline&quot;&gt;Gemfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;gem &apos;datamappify&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
To keep things organised, we put entities and repositories in their respective directories under &lt;code class=&quot;inline&quot;&gt;app&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_inline_mp1vigkh0f1qz4rgp.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h3&gt;
Entities&lt;/h3&gt;
&lt;p&gt;
The reason we wanted Datamappify to utilise existing ORMs like ActiveRecord, is so that we could still use gems like Devise.&lt;/p&gt;
&lt;p&gt;
So, we have a &lt;code class=&quot;inline&quot;&gt;UserAccount&lt;/code&gt; model that handles authentication:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;class UserAccount &lt; ActiveRecord::Base
  devise :database_authenticatable,
          :recoverable, :rememberable, :trackable, :validatable
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
&lt;code class=&quot;inline&quot;&gt;UserAccount&lt;/code&gt; has one and only one purpose - user account authentication. Other user behaviours would be contained in either the &lt;code class=&quot;inline&quot;&gt;User&lt;/code&gt; entity itself or service objects. Speaking of the &lt;code class=&quot;inline&quot;&gt;User&lt;/code&gt; entity, it looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;class User
  include Datamappify::Entity

  attr_accessor :user_account

  attribute :account_username, String
  attribute :account_email, String
  attribute :first_name, String
  attribute :last_name, String
  attribute :activated, Boolean, :default =&gt; true

  attributes_from Contact, prefix_with: :work

  validates :first_name, presence: true
  validates :last_name, presence: true

  references :agency

  def full_name
    &quot;#{first_name} #{last_name}&quot;
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
We include &lt;code class=&quot;inline&quot;&gt;Datamappify::Entity&lt;/code&gt; in the &lt;code class=&quot;inline&quot;&gt;User&lt;/code&gt; class to make it an entity. We set the &lt;code class=&quot;inline&quot;&gt;:user_account&lt;/code&gt; accessor is so that we could attach the &lt;code class=&quot;inline&quot;&gt;UserAccount&lt;/code&gt; object onto the entity.&lt;/p&gt;
&lt;p&gt;
The &lt;code class=&quot;inline&quot;&gt;attribute&lt;/code&gt; DSL is from &lt;a href=&quot;https://github.com/solnic/virtus&quot;&gt;Virtus&lt;/a&gt; - we get attribute type coercion for free, awesome!&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;inline&quot;&gt;attributes_from&lt;/code&gt; is a DSL provided by Datamappify - it essentially “mounts” all the attributes from another entity, in this case the &lt;code class=&quot;inline&quot;&gt;Contact&lt;/code&gt; entity, which looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;class Contact
  include Datamappify::Entity

  attribute :email, String
  attribute :phone_number, String
  attribute :fax_number, String

  validates :phone_number, presence: true
  validates :fax_number, presence: true
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
All the attributes and validation rules from &lt;code class=&quot;inline&quot;&gt;Contact&lt;/code&gt; are now “mounted” on &lt;code class=&quot;inline&quot;&gt;User&lt;/code&gt;. &lt;code class=&quot;inline&quot;&gt;attributes_from Contact, prefix_with: :work&lt;/code&gt; is equivalent to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;attribute :work_email, String
attribute :work_phone_number, String
attribute :work_fax_number, String

validates :work_phone_number, presence: true
validates :work_fax_number, presence: true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Validation DSL is provided by &lt;a href=&quot;http://api.rubyonrails.org/classes/ActiveModel/Validations.html&quot;&gt;ActiveModel::Validations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;inline&quot;&gt;references :agency&lt;/code&gt; is a convenient DSL provided by Datamappify. It is equivalent to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;attr_accessor :agency

attribute :agency_id, Integer&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Now that we have entities, we need a way to retrieve and store them. For that we need repositories.&lt;/p&gt;
&lt;h3&gt;
Repositories&lt;/h3&gt;
&lt;p&gt;
Here is the user repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;class UserRepository
  include Datamappify::Repository

  for_entity User

  default_provider :ActiveRecord

  map_attribute :account_username, &apos;ActiveRecord::UserAccount#username&apos;
  map_attribute :account_email, &apos;ActiveRecord::UserAccount#email&apos;

  map_attribute :work_email, &apos;ActiveRecord::Contact#email&apos;
  map_attribute :work_phone_number, &apos;ActiveRecord::Contact#phone_number&apos;
  map_attribute :work_fax_number, &apos;ActiveRecord::Contact#fax_number&apos;
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Similarly to an entity, we include &lt;code class=&quot;inline&quot;&gt;Datamappify::Repository&lt;/code&gt; to make the class a repository. We specify &lt;code class=&quot;inline&quot;&gt;for_entity&lt;/code&gt; to link the repository to an entity, and &lt;code class=&quot;inline&quot;&gt;default_provider&lt;/code&gt; to use a specific data provider for unmapped attributes.&lt;/p&gt;
&lt;p&gt;
Unmapped attributes are the ones not specified in &lt;code class=&quot;inline&quot;&gt;map_attribute&lt;/code&gt;, in this case they are &lt;code class=&quot;inline&quot;&gt;first_name&lt;/code&gt;, &lt;code class=&quot;inline&quot;&gt;last_name&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;activated&lt;/code&gt;. Unmapped attributes are actually automatically mapped by Datamappify, so the user repository essentially does this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;map_attribute :first_name, &apos;ActiveRecord::User#first_name&apos;
map_attribute :last_name, &apos;ActiveRecord::User#last_name&apos;
map_attribute :activated, &apos;ActiveRecord::User#activated&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
The first argument of &lt;code class=&quot;inline&quot;&gt;map_attribute&lt;/code&gt; is the name of the attribute from the &lt;code class=&quot;inline&quot;&gt;User&lt;/code&gt; entity (e.g. &lt;code class=&quot;inline&quot;&gt;:first_name&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;
The second argument is a string containing three things:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
&lt;code class=&quot;inline&quot;&gt;ActiveRecord&lt;/code&gt; is the data provider.  &lt;/li&gt;
  &lt;li&gt;
&lt;code class=&quot;inline&quot;&gt;::User&lt;/code&gt; is the ActiveRecord model class.  &lt;/li&gt;
  &lt;li&gt;
&lt;code class=&quot;inline&quot;&gt;#first_name&lt;/code&gt; is the ActiveRecord attribute from the model class.  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
Even though the &lt;code class=&quot;inline&quot;&gt;User&lt;/code&gt; entity is a representation of a user on the domain level, the underlying data structure does not necessary have to be. As you can see from the user repository example, we are mapping &lt;code class=&quot;inline&quot;&gt;:account_username&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;:account_email&lt;/code&gt; to the &lt;code class=&quot;inline&quot;&gt;UserAccount&lt;/code&gt; ActiveRecord model we’ve seen before. And we have a bunch of contact details attributes mapped to the &lt;code class=&quot;inline&quot;&gt;Contact&lt;/code&gt; ActiveRecord model.&lt;/p&gt;
&lt;p&gt;
The database schema therefore looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;create_table &quot;contacts&quot;, force: true do |t|
  t.string &quot;email&quot;
  t.string &quot;phone_number&quot;
  t.string &quot;fax_number&quot;
  t.integer &quot;user_id&quot;
end

create_table &quot;user_accounts&quot;, force: true do |t|
  t.string &quot;username&quot;, default: &quot;&quot;, null: false
  t.string &quot;email&quot;, default: &quot;&quot;, null: false
  t.string &quot;encrypted_password&quot;, default: &quot;&quot;, null: false
  t.string &quot;reset_password_token&quot;
  t.datetime &quot;reset_password_sent_at&quot;
  t.datetime &quot;remember_created_at&quot;
  t.integer &quot;sign_in_count&quot;, default: 0
  t.datetime &quot;current_sign_in_at&quot;
  t.datetime &quot;last_sign_in_at&quot;
  t.string &quot;current_sign_in_ip&quot;
  t.string &quot;last_sign_in_ip&quot;
  t.integer &quot;user_id&quot;
  t.datetime &quot;created_at&quot;
  t.datetime &quot;updated_at&quot;
end

create_table &quot;users&quot;, force: true do |t|
  t.string &quot;first_name&quot;
  t.string &quot;last_name&quot;
  t.boolean &quot;activated&quot;
  t.integer &quot;agency_id&quot;
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Note that because the &lt;code class=&quot;inline&quot;&gt;Contact&lt;/code&gt; entity is mounted on the &lt;code class=&quot;inline&quot;&gt;User&lt;/code&gt; entity, we need a foreign key &lt;code class=&quot;inline&quot;&gt;user_id&lt;/code&gt; in the &lt;code class=&quot;inline&quot;&gt;contacts&lt;/code&gt; table to link them.&lt;/p&gt;
&lt;h4&gt;
Data providers&lt;/h4&gt;
&lt;p&gt;
Because we are allowed to specify a data provider (i.e. &lt;code class=&quot;inline&quot;&gt;ActiveRecord&lt;/code&gt;) for each attribute, we can map attributes to entirely different data providers! For instance, we could have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;map_attribute :first_name, &apos;ActiveRecord::User#first_name&apos;
map_attribute :last_name, &apos;ActiveRecord::User#last_name&apos;
map_attribute :activated, &apos;Sequel::UserActivation#activated&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Now the &lt;code class=&quot;inline&quot;&gt;activated&lt;/code&gt; attribute is mapped to the &lt;code class=&quot;inline&quot;&gt;UserActivation&lt;/code&gt; Sequel model. This powerful feature would allow us to develop data providers that communicate with remote web services. :)&lt;/p&gt;
&lt;h4&gt;
Repository APIs&lt;/h4&gt;
&lt;p&gt;
Datamappify provides a bunch of APIs for retrieving and storing data. These APIs are being developed as needed during our application development.&lt;/p&gt;
&lt;p&gt;
For instance, to find a particular user entity by ID:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;user = UserRepository.find(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
To get all the users:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;users = UserRepository.all&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
To search for users:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;users = UserRepository.find(:first_name =&gt; &apos;Fred&apos;, :activated =&gt; true)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
To save a user:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;UserRepository.save(user)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
To delete a user:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;UserRepository.destroy(user)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
There are some limitations for certain APIs, you can &lt;a href=&quot;https://github.com/fredwu/datamappify/blob/master/README.md#repository-apis&quot;&gt;read more about them here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
When saving an entity, because Datamappify has an internal pool for tracking dirty attributes, only those dirty attributes will get saved.&lt;/p&gt;
&lt;p&gt;
Also, Datamappify supports attribute lazy loading, all we have to do is to tell our entity to become lazy aware:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;class User
  include Datamappify::Entity
  include Datamappify::Lazy
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Once an entity is lazy aware, a repository will inject the lazy loading mechanism onto the entity when it retrieves such entity (via &lt;code class=&quot;inline&quot;&gt;find&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;
Both repositories and entities support inheritance, again, you may read more about them in the &lt;a href=&quot;https://github.com/fredwu/datamappify/blob/master/README.md&quot;&gt;project’s README&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
Putting Things Together With A Controller&lt;/h3&gt;
&lt;p&gt;
Remember we have the &lt;code class=&quot;inline&quot;&gt;UserAccount&lt;/code&gt; for authentication? So how does that work? Well, here’s the piece of code in our application controller:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;class ApplicationController &lt; ActionController::Base
  before_filter :authenticate_user_account!

  private

  def current_user
    unless current_user_account.blank?
      @current_user ||= UserRepository.find(current_user_account.user_id)
    end
  end

  helper_method :current_user
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Not too different from the normal Devise workflow hey? ;)&lt;/p&gt;
&lt;h3&gt;
Forms&lt;/h3&gt;
&lt;p&gt;
Web applications typically have lots of forms - ours is no different. It turns out, Datamappify can help build form objects too!&lt;/p&gt;
&lt;p&gt;
Here’s the view portion of one of our forms (we use &lt;a href=&quot;https://github.com/slim-template/slim&quot;&gt;Slim&lt;/a&gt; templates):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;# our standard form layout

= simple_form_for submission_target, url: submission_path, signed: true do |f|

  = yield(f)

  .form-actions
    .pull-left
      - if archivable? &amp;&amp; resource.persisted?
        = link_to &apos;Archive&apos;, &apos;#&apos;, method: :delete, class: &apos;btn-archive&apos;

    = f.submit &apos;Save&apos;
    = link_to &apos;Cancel&apos;, cancel_path

# actual form content

= render layout: &apos;forms/resource&apos; do |f|
  h3 Account Details
  .row-fluid
    .span4 = f.input :account_type, collection: BankAccount::ACCOUNT_TYPES.invert
    .span4 = f.input :account_code
    .span4 = f.input :account_name
  .row-fluid
    .span4 = f.input :account_number
    .span4 = f.input :bank_name
    .span4 = f.input :bank_branch
  .row-fluid
    .span4 = f.input :bank_bsb, label: &quot;Bank BSB&quot;
    .span4 = f.input :activated, as: :boolean

  h3 Branch Details
  .row-fluid
    .span4 = f.input :branch_address_line_1, label: &quot;Address 1&quot;
    .span4 = f.input :branch_address_line_2, label: &quot;Address 2&quot;
    .span4 = f.input :branch_address_line_3, label: &quot;Address 3&quot;
  .row-fluid
    .span4 = f.input :branch_state, label: &quot;State&quot;
    .span4 = f.input :branch_postcode, label: &quot;Postcode&quot;
    .span4
      = f.input :branch_country_code, label: &quot;Country&quot; do
        = f.country_select :branch_country_code, [&apos;au&apos;, &apos;us&apos;, &apos;gb&apos;], value: &apos;au&apos;
      - f.add_signed_fields :branch_country_code
  .row-fluid
    .span4 = f.input :branch_phone_number, label: &quot;Phone number&quot;
    .span4 = f.input :branch_fax_number, label: &quot;Fax number&quot;

  h3 Merchant Details
  .row-fluid
    .span4 = f.input :merchant_number
    .span4 = f.input :merchant_name
    .span4 = f.input :merchant_address
  .row-fluid
    .span4 = f.input :merchant_biller_code, label: &quot;Biller code&quot;
    .span4 = f.input :merchant_bpay_method, collection: Merchant::BPAY_METHODS.invert, label: &quot;BPAY method&quot;

  h3 Payment Details
  .row-fluid
    .span4 = f.input :payment_number
    .span4 = f.input :payment_merchant, label: &quot;Merchant description&quot;
    .span4 = f.input :payment_reference_type, collection: PaymentAccount::REFERENCE_TYPES.invert, label: &apos;Reference type&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
As you can see, it’s a form containing details from bank account, branch address, branch contact information, merchant and payment. And indeed we do have &lt;code class=&quot;inline&quot;&gt;bank_accounts&lt;/code&gt;, &lt;code class=&quot;inline&quot;&gt;addresses&lt;/code&gt;, &lt;code class=&quot;inline&quot;&gt;contacts&lt;/code&gt;, &lt;code class=&quot;inline&quot;&gt;merchants&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;payment_accounts&lt;/code&gt; tables in our database. Yet, the form still remains flat-structured and submits via &lt;code class=&quot;inline&quot;&gt;simple_form_for&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
This is thanks to our &lt;code class=&quot;inline&quot;&gt;BankAccount&lt;/code&gt; entity and &lt;code class=&quot;inline&quot;&gt;BankAccountRepository&lt;/code&gt; repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;class BankAccount
  include Datamappify::Entity
  include Concerns::Archivable

  ACCOUNT_TYPES = {
    &apos;general&apos; =&gt; &apos;General&apos;,
    &apos;trust&apos; =&gt; &apos;Trust&apos;
  }

  attribute :account_type, String
  attribute :account_code, String
  attribute :account_name, String
  attribute :account_number, String
  attribute :bank_name, String
  attribute :bank_branch, String
  attribute :bank_bsb, String
  attribute :activated, Boolean, default: true

  attributes_from Address, prefix_with: :branch
  attributes_from Contact, prefix_with: :branch
  attributes_from Merchant, prefix_with: :merchant
  attributes_from PaymentAccount, prefix_with: :payment

  references :agency

  validates :account_type, Mos::Validation.in(ACCOUNT_TYPES)
  validates :account_code, Mos::Validation::STANDARD_TEXT
  validates :account_name, Mos::Validation::STANDARD_TEXT
  validates :account_number, Mos::Validation::STANDARD_TEXT
  validates :bank_name, Mos::Validation::STANDARD_TEXT
  validates :bank_branch, Mos::Validation::STANDARD_TEXT_OPTIONAL
  validates :bank_bsb, Mos::Validation::BSB
  validates :merchant_bpay_method, Mos::Validation.presence_depend_on(:merchant_biller_code)
  validates :payment_reference_type, Mos::Validation.presence_depend_on(:payment_number)
end

class BankAccountRepository
  include Datamappify::Repository

  for_entity BankAccount

  default_provider :ActiveRecord

  map_attribute :branch_address_line_1, &apos;ActiveRecord::Address#address_line_1&apos;
  map_attribute :branch_address_line_2, &apos;ActiveRecord::Address#address_line_2&apos;
  map_attribute :branch_address_line_3, &apos;ActiveRecord::Address#address_line_3&apos;
  map_attribute :branch_state, &apos;ActiveRecord::Address#state&apos;
  map_attribute :branch_postcode, &apos;ActiveRecord::Address#postcode&apos;
  map_attribute :branch_country_code, &apos;ActiveRecord::Address#country_code&apos;

  map_attribute :branch_phone_number, &apos;ActiveRecord::Contact#phone_number&apos;
  map_attribute :branch_fax_number, &apos;ActiveRecord::Contact#fax_number&apos;
  map_attribute :branch_email, &apos;ActiveRecord::Contact#email&apos;
  map_attribute :branch_website, &apos;ActiveRecord::Contact#website&apos;

  map_attribute :merchant_number, &apos;ActiveRecord::Merchant#number&apos;
  map_attribute :merchant_name, &apos;ActiveRecord::Merchant#name&apos;
  map_attribute :merchant_address, &apos;ActiveRecord::Merchant#address&apos;
  map_attribute :merchant_biller_code, &apos;ActiveRecord::Merchant#biller_code&apos;
  map_attribute :merchant_bpay_method, &apos;ActiveRecord::Merchant#bpay_method&apos;

  map_attribute :payment_number, &apos;ActiveRecord::PaymentAccount#number&apos;
  map_attribute :payment_merchant, &apos;ActiveRecord::PaymentAccount#merchant&apos;
  map_attribute :payment_reference_type, &apos;ActiveRecord::PaymentAccount#reference_type&apos;
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Pretty clean and straightforward. What do you think?&lt;/p&gt;
&lt;h3&gt;
Datamappify - Keeping Your Domain Layer Sane&lt;/h3&gt;
&lt;p&gt;
Datamappify’s concept isn’t new, but there simply isn’t anything in the ruby community that solves everything Datamappify tries to solve.&lt;/p&gt;
&lt;p&gt;
I hope that this post has not only introduced you to Datamappify, but has also made you think about how to make &lt;strong&gt;your&lt;/strong&gt; application’s domain layer more decoupled.&lt;/p&gt;
&lt;p&gt;
Thanks for reading, and if you have any feedback for Datamappify please get in touch with me!&lt;/p&gt;
]]&gt;</content>
    <published>2013-06-27T12:09:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/datamappify-a-new-take-on-decoupling-domain-form-and-persistence-in-rails"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/datamappify-a-new-take-on-decoupling-domain-form-and-persistence-in-rails</id>
    <title>Datamappify - A New Take on Decoupling Domain, Form and Persistence in Rails</title>
    <updated>2013-06-27T12:09:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_mm0nuld5Ur1qb7ot5o1_1280.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Would you be interested in reading &lt;a href=&quot;https://leanpub.com/baaad&quot;&gt;such a book&lt;/a&gt;?&lt;/p&gt;
]]&gt;</content>
    <published>2013-04-29T12:25:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/would-you-be-interested-in-reading-such-a-book"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/would-you-be-interested-in-reading-such-a-book</id>
    <title>Would you be interested in reading such a book?</title>
    <updated>2013-04-29T12:25:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;em&gt;Update: You might also want to check out &lt;a href=&quot;/blog/2010-05-25-rails-tip-model-attributes-not-updating/&quot;&gt;&lt;code class=&quot;inline&quot;&gt;reset_column_information&lt;/code&gt;&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
So a few days ago we started seeing the following errors on our Jenkins builds (swapped with fictional model and attribute names):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NoMethodError:
  undefined method `attack_power=&apos; for #&lt;Ironman:0x00000008525d20&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
&lt;code class=&quot;inline&quot;&gt;attack_power&lt;/code&gt; is a new attribute we recently added to the &lt;code class=&quot;inline&quot;&gt;Ironman&lt;/code&gt; ActiveRecord model.&lt;/p&gt;
&lt;p&gt;
I was baffled, as the table column is clearly there but ActiveRecord couldn’t see it.&lt;/p&gt;
&lt;p&gt;
This weird behaviour is confirmed by debugging the model:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;Ironman
# =&gt; Ironman(id: integer, created_at: datetime, updated_at: datetime)

Ironman.column_names
# =&gt; [&quot;id&quot;, &quot;created_at&quot;, &quot;updated_at&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
And by debugging the schema:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;ActiveRecord::Base.connection.structure_dump
# =&gt; &quot;CREATE TABLE `ironmans` (\n `id` int(11) NOT NULL AUTO_INCREMENT,\n `created_at` datetime NOT NULL,\n `updated_at` datetime NOT NULL,\n `attack_power` int(11) NOT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;\n\n&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
So apparently somehow ActiveRecord ate our &lt;code class=&quot;inline&quot;&gt;attack_power&lt;/code&gt; attribute!&lt;/p&gt;
&lt;p&gt;
After some struggling and head-scratching, finally I’ve discovered that during the migration if we call the &lt;code class=&quot;inline&quot;&gt;Ironman&lt;/code&gt; class, then any subsequent attribute changes to the class will not be recognised by ActiveRecord.&lt;/p&gt;
&lt;p&gt;
It turns out, because we run our test suites by invoking rake tasks:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;Rake::Task[&apos;spec:something&apos;].invoke
Rake::Task[&apos;spec:something_else&apos;].invoke&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
And we manually reset our database beforehand too within the same &lt;code class=&quot;inline&quot;&gt;Rakefile&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;Rake::Task[&apos;db:reset&apos;].invoke&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
ActiveRecord will cache its attributes as soon as a model class (&lt;code class=&quot;inline&quot;&gt;Ironman&lt;/code&gt;) is called during the migration.&lt;/p&gt;
&lt;p&gt;
The fix? Simple! Simply use another process to run the database reset:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;system &apos;rake db:reset&apos;&lt;/code&gt;&lt;/pre&gt;
]]&gt;</content>
    <published>2013-02-27T00:12:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/activerecord-and-db-migration-ate-my-model-attributes"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/activerecord-and-db-migration-ate-my-model-attributes</id>
    <title>ActiveRecord and DB Migration Ate My Model Attributes!</title>
    <updated>2013-02-27T00:12:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
It was the first time I presented in front of 400+ people so I was really nervous - I blasted through the talk in under 30 minutes even though I was supposed to talk for 45 minutes, oh well. ;)&lt;/p&gt;
&lt;p&gt;
&lt;code class=&quot;inline&quot;&gt;video: https://www.youtube.com/embed/DeBsmdDmB9A&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;
Also, &lt;a href=&quot;/blog/2012-11-25-become-a-better-developer-you-can-slides-of-my/&quot;&gt;my slides are available too&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
P.S. If you’re located in China, you may &lt;a href=&quot;http://railscasts-china.com/episodes/rubyconf-2012-fred-wu&quot;&gt;view the talk at Railscasts China&lt;/a&gt;.&lt;/p&gt;
]]&gt;</content>
    <published>2012-12-03T11:44:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/become-a-better-developer-you-can-video-of-my-rubyconf-china-2012-talk"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/become-a-better-developer-you-can-video-of-my-rubyconf-china-2012-talk</id>
    <title>&quot;Become A Better Developer You Can&quot; - Video of My RubyConf China 2012 Talk</title>
    <updated>2012-12-03T11:44:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;em&gt;Update: Please check out &lt;a href=&quot;/blog/2013-06-29-the-future-of-computing-the-future-of-computer/&quot;&gt;this new interview with Matz&lt;/a&gt;, done by Engineer Type.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
A week ago I went to Shanghai, China to attend and to give a talk at &lt;a href=&quot;http://rubyconfchina.org/&quot;&gt;RubyConf China&lt;/a&gt;. The day before the conference’s first day a bunch of us were invited to a VIP dinner where we &lt;a href=&quot;http://www.flickr.com/photos/76172385@N07/8212328817/in/set-72157632083056277/lightbox/&quot;&gt;met with Matz&lt;/a&gt; and got to play with &lt;a href=&quot;http://www.flickr.com/photos/76172385@N07/8213422808/in/set-72157632083056277/lightbox/&quot;&gt;a device running MRuby&lt;/a&gt;. And I heard that earlier on that day Matz was ‘adopted’ by a book publisher to do an interview.&lt;/p&gt;
&lt;p&gt;
I have found the interview (&lt;a href=&quot;http://www.ituring.com.cn/article/17487&quot;&gt;in Chinese&lt;/a&gt;), and found it to be really useful. So I translated it to English. Hope more people will like it. :)&lt;/p&gt;
&lt;p&gt;
On November 16th, 2012 - a day before &lt;a href=&quot;http://rubyconfchina.org/&quot;&gt;RubyConf China&lt;/a&gt;, a Chinese book publisher &lt;a href=&quot;http://www.ituring.com.cn/&quot;&gt;Turing Book&lt;/a&gt; has done an interview with Matz on his new book &lt;em&gt;&lt;a href=&quot;http://www.amazon.co.jp/dp/4822234630/&quot;&gt;The Future of Computing&lt;/a&gt;&lt;/em&gt; as well as about a few topics interested to Chinese readers. &lt;a href=&quot;http://www.ituring.com.cn/article/17487&quot;&gt;The interview&lt;/a&gt; was conducted by _The Future of Computing_’s Chinese translator &lt;a href=&quot;http://www.ituring.com.cn/users/86141&quot;&gt;Zi Heng Zhou&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Zhou:&lt;/strong&gt; Mr. Matsumoto’s new book &lt;em&gt;The Future of Computing&lt;/em&gt; is published earlier this year and the Chinese version is being translated by yours truly, and that is going to be released some time next year in China. Your last book &lt;em&gt;&lt;a href=&quot;http://www.amazon.co.jp/dp/4822234312/&quot;&gt;The World of Code&lt;/a&gt;&lt;/em&gt; has received high praises amongst the readers in China, so what are the differences between the last and the new book?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; &lt;em&gt;The World of Code&lt;/em&gt; has 14 topics in total, and each topic covers the basics - they covered more breadth than depth. The new book on the other hand has one set topic - thoughts on the emerging technologies in the future, therefore the ground covered would be narrower and more in depth than the last book. On top of that, the new book discusses several things by timescale, such as the history and the changes since the invention of computing, and computing’s impact on our future lives. Therefore it’s the thoughts of both the past and the future. The computing world is changing rapidly, and this book’s purpose is to discuss the direction of computing heading into the future.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Zhou:&lt;/strong&gt; Speaking of the history of computing, you have touched on a few things about Moore’s law in the book?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; Moore’s law describes the rule of changes over the history of computing hardware. The book discusses not only the changes on computing itself, but also the changes on its surrounding environment.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Zhou:&lt;/strong&gt; On the topic of the evolution of programming languages, Paul Graham said in &lt;em&gt;&lt;a href=&quot;http://paulgraham.com/hundred.html&quot;&gt;The Hundred-Year Language&lt;/a&gt;&lt;/em&gt; that &lt;em&gt;the main branches of the evolutionary tree pass through the languages that have the smallest, cleanest cores&lt;/em&gt;. In the new book you seem to hold a different opinion, can you tell us why? And what’s your take on the evolution of programming languages?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; Paul loves Lisp, and Lisp perfectly matches the characteristics of the programming languages described in his essay, and so Paul reckons the programming languages in a hundred years from now will look like Lisp. In reality though, Lisp has been around for over 50 years, and to be honest it isn’t one of the mainstream programming languages. In my opinion this may have been because most programmers don’t find Lisp charming enough. In other words, there is a gap between the so-called “smallest, cleanest cores”, “beautiful” languages and the expectation of programmers. It would be understandable if Lisp’s charm had not been accepted by everyone in a year or two, but for 50+ years it hasn’t reached the mainstream, could it be because it fundamentally does not match our expectation? There is a huge difference between &lt;strong&gt;human friendly&lt;/strong&gt; languages and languages that have &lt;strong&gt;smallest, cleanest cores&lt;/strong&gt; , and I am afraid the gap between them might not close even in a hundred years. As for what future programming languages should look like, I think they should have a runtime model similar to that of Lisp &lt;strong&gt;and&lt;/strong&gt; be easily understandable by humans. All of a sudden, Ruby looks a lot closer to that, doesn’t it?  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Zhou:&lt;/strong&gt; Mr. Matsumoto you are the father of Ruby. We know that during the design of a programming language there might be a lot of different choices to be made, e.g. dynamically typed vs statically typed, prototype based vs class based, etc. When you were designing Ruby, what was the most difficult choice you made?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; Before Ruby I actually have designed another language when I was in university, and that language was statically typed, similar to Eiffel. I liked statically typed languages, but the language I designed during university was more for academic purposes. Several years later, when I wanted to design a language as a tool I could use, I preferred to have it dynamically typed as I think they were more practical. So I designed Ruby. And I think it was the correct decision - it might not have been the most difficult decision, but it certainly was the biggest decision. Now that the decision of being a dynamic language is made, languages such as Smalltalk, Lisp and to an extent Perl have all had influences on Ruby. One of the features of Ruby is “mixins”, and mixins were not very common at the time Ruby was created. But because I don’t like multiple inheritance, I always believed that there must be an easier way to achieve similar results, so I designed mixins in Ruby.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Zhou:&lt;/strong&gt; Looking back, is there anything in Ruby you wish you did differently?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; In the beginning my goal was to replace Perl as my tool therefore I have borrowed many ideas from Perl, such as using the dollar sign ($) to indicate special variables. Looking at it now it seems to be a little bit too much and too similar to Perl. There are a few other things but mainly I think it is too similar to Perl. Back then before the ruby idioms were formed, there were many things that were borrowed from Perl - nowadays I think many of them weren’t necessary thanks to the ruby and rails idioms.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Zhou:&lt;/strong&gt; Many believe Ruby’s popularity was because of Ruby on Rails, and you agreed with this in the book. So what do you think made Rails so successful?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; The first and foremost, it benefitted from the rapid growth of the web - almost every software development platform is eyeing on the web field. What was developed using the traditional Client-Server architecture can now be implemented on the web. The number of applications that can be developed for the web has grown, and that’s important. Secondly, Ruby is optimised for easier development and higher development productivity. I think these two reasons combined, is what made Rails so successful.  &lt;/p&gt;
  &lt;p&gt;
Also, Ruby has many powerful features such as meta-programming and extendability via monkey patching. Through these features, basic classes can be enhanced, and DHH created Rails using these powerful features of Ruby. For people who have never touched Ruby, for example those Java programmers who like “inflexible” languages, they will be like, “Huh? You can do that?” - and I think that’s also one of the reasons why Rails is so popular.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Zhou:&lt;/strong&gt; It is said that DHH was going to create a web framework using PHP, but eventually moved to Ruby?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; Yes, and I think the reason for that might be the limitation of meta-programming in PHP. After Rails was released there were a bunch of PHP frameworks came out that are inspired by Rails, such as Symfony and CakePHP. PHP however does not have many of the powerful features found in Ruby, and purely from the development perspective I still believe Rails is the more powerful one out of the bunch. By the way, I actually met with DHH before, in Denmark. Back then he hadn’t started learning Ruby yet, perhaps that had a small impact on him too.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Zhou:&lt;/strong&gt; Ruby is being used widely for the web, but one hot topic amongst Chinese readers is the development direction of Ruby outside of the web. Any thoughts?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; True, many web based projects use Ruby, for instance web frameworks like Rails and Sinatra, etc. The world of programming however is far beyond simply web, and I have always wanted Ruby to break out from the web to other areas. In the near future, I would like to see Ruby being used in three fields:  &lt;/p&gt;
  &lt;p&gt;
1.  &lt;/p&gt;
  &lt;p&gt;
Scientific computing. In this field, languages like Python, R and Matlab are popular choices. I hope Ruby will soon be one of them. In fact, we are developing a project called SciRuby, and we hope it will help us with the adoption in scientific computing.  &lt;/p&gt;
  &lt;p&gt;
2.  &lt;/p&gt;
  &lt;p&gt;
High performance computing. This is somewhat related to scientific computing - to use super computers for doing computational work. Compared to C++, Ruby indeed is very slow, and that’s why people think Ruby isn’t suitable for doing high performance computing. In University of Tokyo a research student is working on an academic research project that compiles Ruby code to C code before compiling the binary code. The process involves techniques such as type inference, and in optimal scenarios the speed could reach up to 90% of typical hand-written C code. So far there is only a paper published, no open source code yet, but I’m hoping next year everything will be revealed.  &lt;/p&gt;
  &lt;p&gt;
3.  &lt;/p&gt;
  &lt;p&gt;
Embedded systems. For micro devices such as cell phones, medical equipment and robots, Ruby isn’t really suitable due to its memory use and APIs. Therefore we need something that’s more suitable for these embedded systems where memory consumption has to be low and APIs have to be optimised. And that’s what MRuby is designed for.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Zhou:&lt;/strong&gt; Twitter is mainly built using Rails, but I read a news recently about the traffic surge during the US president election period and Twitter is now migrating to other platforms to help with the scaling. What’s your view on this?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; There are many reasons I believe. First of all, nobody could have predicted Twitter’s huge success - who would’ve thought a service that essentially provides 140 characters of text for blogging to become one of the largest social networks. During their rapid growth they have added a lot of new features, and I think Ruby has contributed to that - it allowed many new features to be thought and implemented in very short amount of time. Twitter could not have anticipated the traffic it is now handling, so it’s fair to say that they have now hit the limit of their architecture designed from way back.  &lt;/p&gt;
  &lt;p&gt;
To solve the issue, Twitter needs to design a new architecture from the ground up in order to handle the ever increasing traffic. Although, Ruby can still be used for rewriting the architecture (chuckles). More seriously though, when a platform hits its limit, there are a few ways of solving it such as rewriting the architecture and changing to another language/platform, etc. I believe the most effective way is to rewrite the architecture, and this is exactly what Twitter’s been doing. During the rewriting process the Twitter engineers wanted to take on more challenges so they picked Scala. Because Scala is a compiled language it has great performance, so it is a fine choice for the new architecture.  &lt;/p&gt;
  &lt;p&gt;
My opinion is that when your system is still in its growing stage, it is far more important to have the ability to react quickly to changes, and that’s what a highly flexible language such as Ruby offers. Once your system reaches to a point of maturity, stability and success, then to have a new architecture that saves on resources makes sense. Twitter only chose to use Scala for its core components, the web front-end and many of their internal tools are still using Ruby. As a matter of fact, I paid Twitter a visit last month and talked to many of their engineers there - Ruby is still in great use (chuckles).  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Zhou:&lt;/strong&gt; Mobile platforms become hotter and hotter in recent years due the increased use of smartphones, tablets and other mobile devices. As far as programming language is concerned, Android uses Java and iOS uses Objective-C. What about scripting languages like Ruby, how do you see them fit in this picture?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; It is no doubt that so far in order to develop for Android you use Java, and for iOS you use Objective-C. But that also creates a huge barrier because you would need to rewrite your application if you wanted to port from one platform to another. Projects like PhoneGap and Titanium try to solve this very issue, via languages such as JavaScript and Lua to offer cross-platform compatibility. For Ruby, there is a project called Rhodes - you can use Ruby to write applications that will run on iOS, Android and Blackberry.  &lt;/p&gt;
  &lt;p&gt;
In the old days, mobile devices are much less powerful than PCs, and if applications can’t run in full speed then they are useless. Nowadays though, mobile devices have gotten significantly faster, and the use of cross-platform frameworks becomes far more practical than ever before. I think this way of developing for mobile platforms will become more and more popular. And by the way, we spoke about MRuby before, and using MRuby for iOS and Android is already underway, hopefully more applications will get developed using MRuby in the near future.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Zhou:&lt;/strong&gt; You mentioned MRuby, and the topic of your keynote presentation tomorrow is Ruby 2.0. Could you talk about MRuby and Ruby 2.0’s highlights a little bit?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; As we discussed earlier, MRuby is designed to run on embedded devices, it does not have everything from Ruby. As a result, many devices which traditionally can’t use Ruby, such as vendor machines, controllers and robots can soon utilise MRuby. Perhaps in a few years time, we could see Ruby used in televisions and cars.  &lt;/p&gt;
  &lt;p&gt;
Ruby 2.0 indicates two messages. Firstly, by next year it will be 20 years since the development of Ruby started. What better version number can there be to celebrate this special moment! Contrary to what it might seem like, in terms of the changes, from 1.9 to 2.0 is not as big as from 1.8 to 1.9. In fact, our goal for 2.0 is to be fully backward compatible with 1.9. Your 1.9 applications should run just fine on 2.0.  &lt;/p&gt;
  &lt;p&gt;
Secondly, Ruby 2.0 does offer a few new features. For instance, use monkey patching to add new or replace features has global effects and it might cause conflicts and bugs. In order to avoid situations like this, Ruby 2.0 limits the scope of monkey patching via Refinements. As bigger teams and bigger projects are adopting Ruby, scoped monkey patching will increasingly show its importance. There are many other features in Ruby 2.0 that are designed to cater for bigger teams and projects.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Zhou:&lt;/strong&gt; As far as I know most of the mainstream programming languages are from America and Europe - though there are Lua from Brazil and Ruby from Japan. You too mentioned this in your book and you said this feels “lonely”. So what is the cause of this, and what can we do about it?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; Well, for Lua you can include it in Europe/America too because Brazil is part of South America (chuckles). In the south eastern Asia region though there is only Ruby, and it is lonely. Europe and America still remain the most powerful regions as far as programming languages go. Asia, although has massive population, does not compete in this regard, that indeed feels lonely.  &lt;/p&gt;
  &lt;p&gt;
I am not sure about other countries, but at least in Japan there are many people working on programming languages, unfortunately other than Ruby none of them are well known. If more people are interested in programming and designing programming languages, there bound to be one or two that’ll break out, right? There is another hurdle in Japan - language. Most Japanese people only speak Japanese and they cannot speak English well. Funnily enough there are programming languages written entirely in Japanese. ( &lt;strong&gt;Zhou:&lt;/strong&gt; In China there also are programming languages written entirely in Chinese.) In China too? I knew it! No matter how interesting these programming languages are, they will never influence anyone beyond the ones in their own country.  &lt;/p&gt;
  &lt;p&gt;
On a side note, I once received an email from an American. He said that you are Japanese, but Ruby looks like English because it’s written in English, why isn’t there any Japanese-written languages? I replied saying that there are, you just don’t know them, and even if you did, you wouldn’t be able to use them.  &lt;/p&gt;
  &lt;p&gt;
In Japan, more and more people are interested in programming, maybe because both online and in my books I always talk about how fun programming can be. Many people are now taking on the challenge of designing new programming languages. Out of these new languages, even only 0.1% of them ever get any success, I think it’s a win. I don’t know how many people want to take on the same challenge in China, Korea and other countries in Asia, but if people could look beyond &lt;em&gt;programming languages are created for us, we just passively accept them&lt;/em&gt;, and think &lt;em&gt;to create a new programming language can also be fun&lt;/em&gt;, then I am sure some of them will succeed.  &lt;/p&gt;
  &lt;p&gt;
Talking about open source projects, not many of them are from Japan, China and Korea, and I think this could be an entry point for many. There are many reasons why this is the case though, for example English is hard to learn… ( &lt;strong&gt;Zhou:&lt;/strong&gt; And GitHub is also difficult to use?) Haha, is GitHub usable in China? ( &lt;strong&gt;Zhou:&lt;/strong&gt; It is, it is…) Oh, that’s not too bad then. But, China’s Great Firewall still has a huge impact, many resources can’t be accessed here, right? ( &lt;strong&gt;Zhou:&lt;/strong&gt; That’s right, for instance the Go programming language’s website is blocked.) Ah really? Is it because it’s made by Google? (Chuckles.) In any case, I think there are still many difficulties to face. Also, in Japan many programmers still spend most of the time at work (to put food on the table), it’s very difficult for them to contribute to open source projects. Ten years ago nobody cares about open source in Japan, but nowadays people start to realise the importance of open source, and the number of open source projects is growing. I believe China will soon follow this pattern as well, I am looking forward to it.  &lt;/p&gt;
  &lt;p&gt;
In the beginning no one knows what will succeed. When I started with Ruby I could not possibly have predicted its success. So I think for a programming language, timing is really important - and you’ll never know until you tried. I think in China there might also be languages that emerge from the right time that will eventually be a global success.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Zhou:&lt;/strong&gt; In the book when you were talking about Dart, you mentioned that the ecological environment is really important for a programming language. So what do we need to do to ensure a good ecological environment?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; From a user’s perspective, the most important thing is what benefits a programming language can offer. Before Rails came along, many Ruby users including myself, believe Ruby is a user-friendly language, and that’s the reason we like to use it. After Rails was born, more and more people like to use Ruby because &lt;em&gt;making websites using Rails is really productive&lt;/em&gt;. So I think if you could communicate to your users what the benefits are by using your new programming language, it will help with its adoption and increase the chance of success.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;strong&gt;Zhou:&lt;/strong&gt; Thank you very much Matz! Do you have anything else to say to Chinese programmers?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
&lt;strong&gt;Matz:&lt;/strong&gt; In the book &lt;em&gt;The World of Code&lt;/em&gt; I have mentioned that programming will likely to develop and evolve in the form of open source. New generations of programming languages and software are likely to emerge as open source projects. People are happy to freely use the software developed by others, and to take from that, and work on your own open source projects and make a little impact on the world - then you would become a world class software engineer. The software engineers I met in China are all very hard working and enjoying learning. I hope more of these people will step up and take on the challenge, and become world class programmers who will make a decent impact.  &lt;/p&gt;
&lt;/blockquote&gt;
]]&gt;</content>
    <published>2012-11-25T06:45:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/an-interview-with-yukihiro-matz-matsumoto"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/an-interview-with-yukihiro-matz-matsumoto</id>
    <title>An Interview with Yukihiro &quot;Matz&quot; Matsumoto</title>
    <updated>2012-11-25T06:45:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Contrary to what the title of the talk suggests, I am by no means a superstar developer myself. Though I would like to think that I am extremely passionate about what I do, therefore I love to share my thoughts and tips on web development and software engineering.&lt;/p&gt;
&lt;p&gt;
Below is the version I used for my actual talk:&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://speakerdeck.com/fredwu/2012-become-a-better-developer-you-can&quot;&gt;https://speakerdeck.com/fredwu/2012-become-a-better-developer-you-can&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
Since I had been preparing for my talk for over a month, I’ve done many revisions to my slides. Here’s an uncut version that contains every single slide since day one:&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://speakerdeck.com/fredwu/2012-uncut-become-a-better-developer-you-can&quot;&gt;https://speakerdeck.com/fredwu/2012-uncut-become-a-better-developer-you-can&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
Hope you will enjoy them! :)&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Update:&lt;/strong&gt; &lt;a href=&quot;/blog/2012-12-03-become-a-better-developer-you-can-video-of-my/&quot;&gt;The video of my talk is up!&lt;/a&gt; :)&lt;/p&gt;
]]&gt;</content>
    <published>2012-11-25T05:43:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/become-a-better-developer-you-can-slides-of-my-rubyconf-china-2012-talk"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/become-a-better-developer-you-can-slides-of-my-rubyconf-china-2012-talk</id>
    <title>&quot;Become A Better Developer You Can&quot; - Slides of My RubyConf China 2012 Talk</title>
    <updated>2012-11-25T05:43:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
I had such a wonderful time at &lt;a href=&quot;http://rubyconfchina.org/&quot;&gt;RubyConf China&lt;/a&gt;! So here are some photos from the conference. :)&lt;/p&gt;
&lt;p&gt;
Gallery of &lt;a href=&quot;https://www.flickr.com/photos/ifredwu/sets/72157658573488481&quot;&gt;photos of yours truly on stage&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/21219812889_ffd7fcbf96_c.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Gallery of &lt;a href=&quot;https://www.flickr.com/photos/ifredwu/sets/72157658621968985&quot;&gt;photos I took mostly on the VIP dinner event&lt;/a&gt; the day before the conference:&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/21224845700_224e8c76e3_c.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
]]&gt;</content>
    <published>2012-11-24T15:47:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/photos-from-rubyconf-china-2012"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/photos-from-rubyconf-china-2012</id>
    <title>Photos from RubyConf China 2012</title>
    <updated>2012-11-24T15:47:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
The other day a colleague asked whether or not it’s possible to have &lt;a href=&quot;https://github.com/colszowka/simplecov&quot;&gt;SimpleCov&lt;/a&gt; return a group that only contains uncommitted changes.&lt;/p&gt;
&lt;p&gt;
The answer is &lt;strong&gt;yes&lt;/strong&gt;! After some digging around, we found the following way:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;# in spec_helper.rb
SimpleCov.start &apos;rails&apos; do
  add_group &apos;Changed&apos; do |source_file|
    `git ls-files --exclude-standard --others \
      &amp;&amp; git diff --name-only \
      &amp;&amp; git diff --name-only --cached`.split(&quot;\n&quot;).detect do |filename|
      source_file.filename.ends_with?(filename)
    end
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Basically use &lt;code class=&quot;inline&quot;&gt;git ls-files --exclude-standard --others&lt;/code&gt; for untracked files, &lt;code class=&quot;inline&quot;&gt;git diff --name-only&lt;/code&gt; for unstaged files and &lt;code class=&quot;inline&quot;&gt;git diff --name-only --cached&lt;/code&gt; for staged files.&lt;/p&gt;
]]&gt;</content>
    <published>2012-11-13T07:34:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/simplecov-test-coverage-for-changed-files-only"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/simplecov-test-coverage-for-changed-files-only</id>
    <title>SimpleCov: Test Coverage for Changed Files Only</title>
    <updated>2012-11-13T07:34:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
After a few nights of working on the 2.0 rewrite of &lt;a href=&quot;https://github.com/fredwu/jquery-endless-scroll&quot;&gt;jQuery Endless Scroll&lt;/a&gt;, I am now releasing one of the tools I built for the project: &lt;strong&gt;&lt;a href=&quot;https://github.com/fredwu/skinny-coffee-machine&quot;&gt;Skinny Coffee Machine&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;
Skinny Coffee Machine is a simple JavaScript state machine written in CoffeeScript.&lt;/p&gt;
&lt;p&gt;
It is fairly simple to use, with the flexibility of adding and removing observers for state transitions.&lt;/p&gt;
&lt;h3&gt;
Define State Machines&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;coffeescript language-coffeescript&quot;&gt;@coffeeMachine.power = new SkinnyCoffeeMachine
  default: &apos;off&apos;
  events:
    turnOn:
      off: &apos;on&apos;
    turnOff:
      on: &apos;off&apos;
  on:
    turnOn: (from, to) -&gt; &quot;#{from.toUpperCase()} to #{to.toUpperCase()}&quot;
    turnOff: (from, to) -&gt; &quot;#{from.toUpperCase()} to #{to.toUpperCase()}&quot;
  before:
    turnOff: (from, to) -&gt; &quot;Before switching to #{to.toUpperCase()}&quot;
  after:
    turnOn: (from, to) -&gt; &quot;After switching to #{to.toUpperCase()}&quot;
    turnOff: (from, to) -&gt; &quot;After switching to #{to.toUpperCase()}&quot;

@coffeeMachine.mode = new SkinnyCoffeeMachine
  default: &apos;latte&apos;
  events:
    next:
      latte: &apos;cappuccino&apos;
      cappuccino: &apos;espresso&apos;
      espresso: &apos;lungo&apos;
      lungo: &apos;latte&apos;
    last:
      latte: &apos;lungo&apos;
      lungo: &apos;espresso&apos;
      espresso: &apos;cappuccino&apos;
      cappuccino: &apos;latte&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;
Switch/Change States&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;coffeescript language-coffeescript&quot;&gt;@coffeeMachine.power.currentState() #=&gt; &quot;off&quot;
@coffeeMachine.power.switch(&apos;turnOn&apos;)
@coffeeMachine.power.currentState() #=&gt; &quot;on&quot;

@coffeeMachine.mode.currentState() #=&gt; &quot;latte&quot;
@coffeeMachine.mode.change(&apos;next&apos;, 3)
@coffeeMachine.mode.currentState() #=&gt; &quot;cappuccino&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;
Observers&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;coffeescript language-coffeescript&quot;&gt;@coffeeMachine.power.observeBefore(&apos;turnOn&apos;).start &apos;labelA&apos;, (from, to) =&gt; &quot;Observer A before switching to #{to.toUpperCase()}&quot;
@coffeeMachine.power.observeOn( &apos;turnOn&apos;).start &apos;labelB&apos;, (from, to) =&gt; &quot;Observer B on switching to #{to.toUpperCase()}&quot;
@coffeeMachine.power.observeAfter( &apos;turnOn&apos;).start &apos;labelC&apos;, (from, to) =&gt; &quot;Observer C after switching to #{to.toUpperCase()}&quot;

@coffeeMachine.power.observeBefore(&apos;turnOn&apos;).stop(&apos;labelA&apos;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Go check out the &lt;a href=&quot;https://github.com/fredwu/skinny-coffee-machine&quot;&gt;source code&lt;/a&gt; now! :)&lt;/p&gt;
]]&gt;</content>
    <published>2012-09-06T11:47:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/skinny-coffee-machine-a-simple-state-machine-with-observers"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/skinny-coffee-machine-a-simple-state-machine-with-observers</id>
    <title>Skinny Coffee Machine - A Simple State Machine with Observers</title>
    <updated>2012-09-06T11:47:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
If you are like me who has no need for a full Xcode installation just to get the command line tools, chances are you are using one of these: Apple’s &lt;a href=&quot;https://developer.apple.com/downloads/&quot;&gt;Command Line Tools&lt;/a&gt; or the &lt;a href=&quot;https://github.com/kennethreitz/osx-gcc-installer&quot;&gt;osx-gcc-installer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Recently Node.js has made some changes so that it no longer installs on OS X via &lt;a href=&quot;http://mxcl.github.com/homebrew/&quot;&gt;homebrew&lt;/a&gt; if you don’t have Xcode installed.&lt;/p&gt;
&lt;p&gt;
If you run &lt;code class=&quot;inline&quot;&gt;brew install nodejs&lt;/code&gt;, you will get the following error:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;\&gt; Error: Failed executing: make install (node.rb:28)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
And if you run &lt;code class=&quot;inline&quot;&gt;brew install -v nodejs&lt;/code&gt;, you will discover this line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;\&gt; xcode-select: Error: No Xcode is selected. Use xcode-select -switch , or see the xcode-select manpage (man xcode-select) for further information.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
The fix? It’s actually quite easy, simply do:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;sudo xcode-select --switch /usr/bin&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
And voila! You can now install Node.js just fine. :)&lt;/p&gt;
]]&gt;</content>
    <published>2012-08-25T14:43:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/brewing-node-js-on-os-x-without-xcode"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/brewing-node-js-on-os-x-without-xcode</id>
    <title>Brewing Node.js on OS X without Xcode</title>
    <updated>2012-08-25T14:43:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Don’t you just hate it when you have a fresh intall of Mountain Lion, RVM and some rubies - then all of a sudden you hit this &lt;code class=&quot;inline&quot;&gt;OpenSSL::SSL::SSLError&lt;/code&gt; error message:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
The fix is quite simple actually, all you need to do is to download a CA root certificate:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;curl http://curl.haxx.se/ca/cacert.pem -o ~/.rvm/usr/ssl/cert.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
And that’s it! Enjoy!&lt;/p&gt;
]]&gt;</content>
    <published>2012-08-06T13:25:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/fix-openssl-error-on-mountain-lion-and-rvm"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/fix-openssl-error-on-mountain-lion-and-rvm</id>
    <title>Fix OpenSSL Error on Mountain Lion (and RVM)</title>
    <updated>2012-08-06T13:25:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Like a lot of places, at &lt;a href=&quot;http://locomote.com.au&quot;&gt;Locomote&lt;/a&gt; we are building a platform that is API-based. As much as I like having comprehensive test suites, I often feel the need to manually test API endpoints to see exactly what the responses are.&lt;/p&gt;
&lt;p&gt;
Tools such as &lt;a href=&quot;https://chrome.google.com/webstore/detail/fdmmgilgnpjigdojojpjoooidkmcomcm&quot;&gt;Postman&lt;/a&gt; solves part of the issue: they allow us to quickly test API endpoints without messing with &lt;a href=&quot;http://curl.haxx.se/&quot;&gt;cURL&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
But as a lazy developer, I want more. ;)&lt;/p&gt;
&lt;p&gt;
I want something that:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
automatically generates API endpoints from Rails routes definition  &lt;/li&gt;
  &lt;li&gt;
defines input params as easy as defining routes  &lt;/li&gt;
  &lt;li&gt;
has input params that can be shared with test factories  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
And so &lt;a href=&quot;https://github.com/fredwu/api_taster&quot;&gt;API Taster&lt;/a&gt; was born. Please check it out to see how you can use it.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/8Dnto.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
]]&gt;</content>
    <published>2012-07-02T04:59:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/api-taster-visually-test-rails-application-api"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/api-taster-visually-test-rails-application-api</id>
    <title>API Taster: Visually Test Rails Application API</title>
    <updated>2012-07-02T04:59:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;strong&gt;TL;DR - PHP is still a useful tool, but as a PHP developer, have you started playing with other useful tools? Here’s my story.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;
Today Jeff Atwood’s new piece “&lt;a href=&quot;http://www.codinghorror.com/blog/2012/06/the-php-singularity.html&quot;&gt;The PHP Singularity&lt;/a&gt;“ and Marco Arment’s “&lt;a href=&quot;http://www.marco.org/2012/06/29/php-addiction&quot;&gt;PHP Addiction&lt;/a&gt;“ have started another round of heated discussion on PHP.&lt;/p&gt;
&lt;p&gt;
As someone who started his career as a PHP developer, I feel like sharing my thoughts from a different perspective.&lt;/p&gt;
&lt;p&gt;
I began my career as a freelancer - since JavaScript and PHP were the two programming languages I learnt at school, they naturally became my weapon of choice for freelancing work.&lt;/p&gt;
&lt;p&gt;
At that time I knew very little about software development. My desire was simple: I liked making websites, therefore I decided I was going to do it for a living.&lt;/p&gt;
&lt;p&gt;
Over the years I came across many people who shared a similar background - people who do not have a strong (or any) computer science / software engineering background, were making websites.&lt;/p&gt;
&lt;p&gt;
Compared to most software developers, the &lt;strong&gt;intention&lt;/strong&gt; of doing programming for me was different. I had no intention (or awareness) to &lt;strong&gt;create maintainable software&lt;/strong&gt; , instead I simply wanted to &lt;strong&gt;create an end product that works&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;
Was I wrong? According to the market, no. There are many clients who want affordable websites, just like there are many people who want affordable Android phones. On one end you have Android phones that compete with the iPhone and take time to get released, on the other end you have Android phones that use readily available, often less desirable components and are quick to market. Each serves its purpose and fulfils customer demands.&lt;/p&gt;
&lt;p&gt;
As I kept developing websites and educating myself, I became more and more interested in software development. That’s when I started reading books like “&lt;a href=&quot;http://www.apress.com/9781590599099/&quot;&gt;PHP Objects, Patterns, and Practice&lt;/a&gt;“. But even then, my knowledge was still extremely limited, and my desire at that time was to &lt;strong&gt;increase my PHP knowledge&lt;/strong&gt; and &lt;strong&gt;become a better developer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;
For the first desire I made it happen by reading more books and articles, but for the second desire - I thought I made it happen, but in reality I didn’t because I did not correctly identify my weakness. It wasn’t just about the depth of knowledge, but also the breadth.&lt;/p&gt;
&lt;p&gt;
Spending most of my time writing PHP made me too comfortable in a little corner. The discovery of tools such as &lt;a href=&quot;http://codeigniter.com/&quot;&gt;CodeIgniter&lt;/a&gt; made me feel good about being a competent web developer. After all, I was doing quite a fine job churning out websites.&lt;/p&gt;
&lt;p&gt;
In 2006 I discovered Ruby on Rails, but that did not have too much impact on me, because &lt;strong&gt;I was not yet capable to tell the differences it has compared to PHP (frameworks)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;
In my mind, PHP and Ruby are both scripting languages that can be used for developing websites and web applications. Although the statement is true, not knowing the specifics made me very ignorant. It is almost like not knowing the difference between travelling from one continent to another by an airplane and by a ship - either will get you to the destination, right?&lt;/p&gt;
&lt;p&gt;
And this is exactly why my first ruby program looked just like a PHP program.&lt;/p&gt;
&lt;p&gt;
It was (and still is) an interesting and rewarding journey to slowly discover why my Ruby code sucked.&lt;/p&gt;
&lt;p&gt;
So I started reading on design patterns, &lt;a href=&quot;http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882&quot;&gt;software craftsmanship&lt;/a&gt; and &lt;a href=&quot;http://pragprog.com/book/btlang/seven-languages-in-seven-weeks&quot;&gt;other programming languages&lt;/a&gt;. And I was lucky enough to be surrounded by &lt;a href=&quot;http://envato.com/#about-envato&quot;&gt;many brilliant developers&lt;/a&gt; who taught me how to write better code.&lt;/p&gt;
&lt;p&gt;
At that point, my intention for learning more about software development had shifted from simply making an end product, to making an end product that can be maintained.&lt;/p&gt;
&lt;p&gt;
And that &lt;a href=&quot;https://github.com/fredwu&quot;&gt;changed everything&lt;/a&gt;. From a PHP developer who just wanted to churn out websites quickly, to a developer who wanted to use software development to create &lt;em&gt;value&lt;/em&gt; - in the sense of both business value and technical value.&lt;/p&gt;
&lt;p&gt;
An important thing to remember is though, &lt;strong&gt;it’s not about Ruby&lt;/strong&gt; , and it certainly &lt;strong&gt;is not about looking down on PHP&lt;/strong&gt;. Rather, the point is to see whether or not there’s something out there that might enable you to look at things differently, and do things differently, for better or for worse.&lt;/p&gt;
&lt;p&gt;
PHP for some is a &lt;em&gt;double-clawed hammer&lt;/em&gt;, but there is no doubt in my mind that for some others it is a near perfect tool, for now at least. Having said that, &lt;strong&gt;many other kids have started playing with all sorts of toys, why haven’t you?&lt;/strong&gt; :)&lt;/p&gt;
]]&gt;</content>
    <published>2012-06-29T19:03:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/php-devs-have-you-started-playing-with-more-toys"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/php-devs-have-you-started-playing-with-more-toys</id>
    <title>PHP Devs: Have You Started Playing with More Toys?</title>
    <updated>2012-06-29T19:03:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Ever wondered how you could utilise the &lt;code class=&quot;inline&quot;&gt;render&lt;/code&gt; method outside the context of Rails controllers and views? If you wonder why anyone would do that. Well, imagine you are building an awesome form builder, you need to output and/or store rendered partials in the buffer. How do you do that?&lt;/p&gt;
&lt;p&gt;
For example, what if you want to do this in your view?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;&lt;%=raw Awesome::FormBuilder.new(some_options).html %&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
You could do something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;module Awesome
  class FormBuilder &lt; AbstractController::Base
    include AbstractController::Rendering
    include ActionView::Context
    include ActionView::Helpers::CaptureHelper

    # set the view paths from your engine or from your application root, i.e. Rails.root
    self.view_paths = Awesome::Engine.root.join(&apos;app/views&apos;)

    def initialize(params)
      flush_output_buffer
      @_buffer = &apos;&apos;
      add_to_buffer(params)
    end

    def html
      @_buffer
    end

    private

    def add_to_buffer(params)
      # some logic to add rendered content to @_buffer
    end
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
The idea is to mixin the &lt;code class=&quot;inline&quot;&gt;render&lt;/code&gt; method, but also ensuring the view buffer is correctly reset with &lt;code class=&quot;inline&quot;&gt;flush_output_buffer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
Hope that helps. :)&lt;/p&gt;
]]&gt;</content>
    <published>2012-06-20T09:09:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/rails-tip-render-views-outside-of-controllers-or-views"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/rails-tip-render-views-outside-of-controllers-or-views</id>
    <title>[Rails Tip] Render views outside of Controllers or Views</title>
    <updated>2012-06-20T09:09:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
This blog post titled “&lt;a href=&quot;http://williamedwardscoder.tumblr.com/post/20054342100/agile-is-a-sham&quot;&gt;Agile is a Sham&lt;/a&gt;“ offends me a little bit. The post screams &lt;em&gt;hey, I am a cowboy programmer&lt;/em&gt;, and it almost implies that if you employ processes then you are stupid.&lt;/p&gt;
&lt;p&gt;
In this day and age, I would have thought &lt;strong&gt;finding the right tool for the right job&lt;/strong&gt; is common sense. Apparently not.&lt;/p&gt;
&lt;p&gt;
Agile, among many other things, is not a silver bullet - it never was and it never will be. The key thing is &lt;strong&gt;to experiment and find what works for you, your team and your company&lt;/strong&gt;. Most software projects are done in a team environment - putting a bunch of talented developers and designers together actually isn’t as simple as many seem to think. One example is right out of that original blog post:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
Given that I am not a fan of prescribed process as the solution to our problems, you can infer my opinion of the general quality and effectiveness of those programmers who teach these courses.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
From that attitude, I can already see that he is an &lt;em&gt;interesting&lt;/em&gt; developer to manage.&lt;/p&gt;
&lt;p&gt;
A team needs to function efficiently &lt;strong&gt;as a team&lt;/strong&gt;. Having one or two ace developers aren’t going to help a lot if they can’t get along with the others.&lt;/p&gt;
&lt;p&gt;
Not to mention that people have spent time and effort &lt;a href=&quot;http://research.microsoft.com/en-us/news/features/nagappan-100609.aspx&quot;&gt;proving that techniques such as TDD work for certain projects&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
As many things in life, there are always people who try to take advantage of others. No doubt there are questionable ‘agile consultants’ who try to make a fortune out of uninformed clients. Just because some people aren’t using agile effectively or even correctly, doesn’t mean agile itself is a sham. Developers constantly make mistakes and write shitty code - should we say all the programming languages out there are a sham?&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Keep an open mind is important, and a lot of times even necessary.&lt;/strong&gt; To dismiss agile all together is in my opinion childish, and offensive to others who try to improve things and create more value.&lt;/p&gt;
]]&gt;</content>
    <published>2012-03-28T11:35:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/agile-is-not-a-sham"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/agile-is-not-a-sham</id>
    <title>Agile is not a Sham</title>
    <updated>2012-03-28T11:35:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Today &lt;a href=&quot;http://www.alexhudson.com/2012/03/24/a-fond-farewell-to-zend-framework/&quot;&gt;an interesting blog post on Zend Framework&lt;/a&gt; has made to the Hacker News front page. I have to agree with the author - Zend Framework is an over-engineered piece of software.&lt;/p&gt;
&lt;p&gt;
A few years ago I was working full time as a PHP developer. Naturally, I had experimented with lots of frameworks, including &lt;a href=&quot;http://cakephp.org/&quot;&gt;CakePHP&lt;/a&gt;, &lt;a href=&quot;http://codeigniter.com/&quot;&gt;CodeIgniter&lt;/a&gt;, &lt;a href=&quot;http://kohanaframework.org/&quot;&gt;Kohana&lt;/a&gt;, &lt;a href=&quot;http://www.yiiframework.com/&quot;&gt;Yii&lt;/a&gt;, &lt;a href=&quot;http://www.symfony-project.org/&quot;&gt;Symfony&lt;/a&gt; and obviously, &lt;a href=&quot;http://framework.zend.com/&quot;&gt;Zend Framework&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Over the years, I had developed many projects primarily using CodeIgniter and later on, Kohana. There was one project that was larger in scale compared to an average PHP project (whatever that means), so I decided to use Zend Framework to take advantage of its component-based structure.&lt;/p&gt;
&lt;p&gt;
The result? &lt;strong&gt;It was dreadful.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;
At that time there wasn’t any built-in or quick n’ easy way of bootstrapping a Zend Framework project, so we had to cook up our own. The documentation was absolutely horrible despite its completeness viewed from a distance - to find anything useful, Google is more likely to provide you with the answer, even from the results on the documentation site. And of course, the framework felt so heavy and long-winded. &lt;strong&gt;It, felt, Java.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;
In terms of the code structure, Zend Framework is almost the complete opposite of CakePHP - no offense to the CakePHP developers and users, but if you take a look at each framework’s source code, you’ll know what I mean.&lt;/p&gt;
&lt;p&gt;
Having layers and layers of structures might appeal to certain users (Enterprise baby!), but does anyone still remember back in the day, how Zend was announcing/marketing the Zend Framework?&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Extreme simplicity!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;
That’s right, Zend Framework was marketed as an extremely simple to use web framework. In fact, we can still &lt;a href=&quot;http://andigutmans.blogspot.com.au/2005/10/zend-framework-post-is-too-long-so.html&quot;&gt;read Andi Gutsmans’ post on this topic&lt;/a&gt;. Let me quote the relevant part (emphasis mine):&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
As some know, our ultimate goal for the framework is what we call “ &lt;strong&gt;Extreme Simplicity&lt;/strong&gt; “. Some might have heard me use that term in some of the talks regarding PHP, and I’d like to bring this concept over to the framework. &lt;strong&gt;I believe this is what the PHP spirit is all about&lt;/strong&gt; and the idea behind it is that it’s possible to create very simple &amp; easy-to-use languages&amp;frameworks which still remain powerful and flexible.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
Can anyone, including the Zend Framework developers, honestly say that Zend Framework is an extremely easy to use framework?&lt;/p&gt;
&lt;p&gt;
I guess at the end of the day, in order to market PHP and Zend Framework as ‘enterprise-ready’ with &lt;a href=&quot;http://www.zend.com/services/training/course-catalog/zend-framework&quot;&gt;trainings&lt;/a&gt; and &lt;a href=&quot;http://www.zend.com/services/certification/framework/&quot;&gt;certifications&lt;/a&gt;, something has to give.&lt;/p&gt;
]]&gt;</content>
    <published>2012-03-25T12:09:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/zend-framework-from-extreme-simplicity-to-enterprise"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/zend-framework-from-extreme-simplicity-to-enterprise</id>
    <title>Zend Framework - From Extreme Simplicity to Enterprise!</title>
    <updated>2012-03-25T12:09:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Hiring is usually a long and difficult process - in order to streamline and simply it, I use &lt;a&gt;Kanban&lt;/a&gt; to manage the whole process. I believe, hiring should be as lean and agile as our development process.&lt;/p&gt;
&lt;p&gt;
My Kanban board for hiring (&lt;a href=&quot;http://bit.ly/spdevjob&quot;&gt;we just started hiring at SitePoint!&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_lzq4l7RaaJ1qalr27.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Using a Kanban board offers a number of advantages:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
a clear picture of the candidates with their feedback from code tests and interviews  &lt;/li&gt;
  &lt;li&gt;
a straightforward view of where a candidate is at in the hiring process  &lt;/li&gt;
  &lt;li&gt;
limited number of candidates in some stages to prevent chaos  &lt;/li&gt;
  &lt;li&gt;
visual reminders to get in touch with the candidates, it’s always a good idea to keep them in the loop  &lt;/li&gt;
  &lt;li&gt;
a tight WIP limit for shortlisted candidates, there’s no point to shortlist too many candidates  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
What do you think?&lt;/p&gt;
]]&gt;</content>
    <published>2012-02-21T04:20:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/on-hiring-use-kanban-for-managing-candidates-and-the-hiring-process"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/on-hiring-use-kanban-for-managing-candidates-and-the-hiring-process</id>
    <title>On Hiring: Use Kanban for Managing Candidates and the Hiring Process</title>
    <updated>2012-02-21T04:20:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;em&gt;If you are looking at hiring developers, &lt;a href=&quot;/blog/2012-01-26-on-hiring-how-not-to-annoy-developers/&quot;&gt;check out my article on this subject&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
The goal or the dream of working on your own startup is always full of excitement. And apart from some rare cases &lt;a href=&quot;http://www.forbes.com/sites/bruceupbin/2011/10/18/paul-graham-dropbox-and-the-single-founder-exception/&quot;&gt;such as Dropbox&lt;/a&gt;, you probably need one or more co-founders to work with you on The Next Big Thing ™.&lt;/p&gt;
&lt;p&gt;
Problem is, how do you (as a non-technical co-founder) find us? Or more specifically, how do you talk us into working with &lt;em&gt;you&lt;/em&gt; instead of some other billion-dollar ideas?&lt;/p&gt;
&lt;p&gt;
To answer this question, we need to first ask, &lt;strong&gt;is there a billion-dollar idea&lt;/strong&gt;? The short answer is: &lt;strong&gt;NO&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
Idea is worthless.&lt;/h2&gt;
&lt;p&gt;
Well, that’s not entirely true. I believe - &lt;strong&gt;idea, by itself, is worthless&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;
You will be surprised by the number of people contacting us and wanting to build a better Paypal or a better Amazon, without a concrete plan.&lt;/p&gt;
&lt;p&gt;
A more worthwhile idea should contain not only the end goal of the project, but also a plan to reach the goal. What should we ship in the Minimal Viable Product? What are our marketing channels? What metrics should we look at? How do we use social media to our advantage? etc, etc.&lt;/p&gt;
&lt;h2&gt;
We Are Not Just Wozniak, Are You Like Steve Jobs?&lt;/h2&gt;
&lt;p&gt;
Apple is pretty much started by Wozniak as the technical co-founder and Steve Jobs as the idea/business co-founder.&lt;/p&gt;
&lt;p&gt;
Let’s think about this for a second.&lt;/p&gt;
&lt;p&gt;
Steve Jobs did not &lt;em&gt;just&lt;/em&gt; have ideas. Very early on, he persuaded Wozniak to produce and sell &lt;em&gt;Apple I&lt;/em&gt; so they have some capital. Jobs was building the foundation. Without the foundation, there will be no failure or success to come.&lt;/p&gt;
&lt;p&gt;
On the other hand, Wozniak had no intention to become an entrepreneur, he was happy to stay as an engineer even after the early Apple success. Nowadays though, most of us techies are much more ambitious than that.&lt;/p&gt;
&lt;p&gt;
Ideally, as the technical co-founder, I would be doing most of Wozniak’s work, and both you and I would be doing Steve Job’s work.&lt;/p&gt;
&lt;p&gt;
Drawing from my personal experience, as a technical person, there are a few &lt;em&gt;key&lt;/em&gt; attributes I look for in a co-founder (technical or otherwise).&lt;/p&gt;
&lt;h2&gt;
Technical Ability&lt;/h2&gt;
&lt;p&gt;
“Excuse me? Aren’t &lt;em&gt;you&lt;/em&gt; the technical co-founder? Why are you looking for &lt;em&gt;my&lt;/em&gt; technical ability?” You ask.&lt;/p&gt;
&lt;p&gt;
That is right. Even if you are not a developer by trade, having a certain degree of understanding of technologies is still crucial to most modern, web-based projects.&lt;/p&gt;
&lt;p&gt;
There has never been a better time to start learning to code. Why not give &lt;a href=&quot;http://codeyear.com/&quot;&gt;CodeYear&lt;/a&gt; and &lt;a href=&quot;http://www.khanacademy.org/#computer-science&quot;&gt;Khan Academy&lt;/a&gt; a try?&lt;/p&gt;
&lt;p&gt;
We all learnt physics and chemistry in high school even though most of us don’t require the knowledge in our day to day life. Let’s treat coding the same. Learn how to code will not only give you insights to how we solve problems, but will also close the communication gap between you and your technical co-founder.&lt;/p&gt;
&lt;h2&gt;
Obsession&lt;/h2&gt;
&lt;p&gt;
Wozniak is obsessed with electrical engineering and gadgets, Steve Jobs was obsessed with computer typefaces, good user experience and beautiful hardware.&lt;/p&gt;
&lt;p&gt;
What are &lt;em&gt;you&lt;/em&gt; obsessed with?&lt;/p&gt;
&lt;p&gt;
Only when you are obsessed with something, can you answer questions like “what annoys you so much?”&lt;/p&gt;
&lt;p&gt;
As I wrote in &lt;a href=&quot;/blog/2011-07-30-its-year-2011-why-arent-people-more/&quot;&gt;an ealier article&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
Inventions and innovations aren’t born out of happiness, they are born out of frustration, anger and sometimes, curiosity.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
Curiosity&lt;/h2&gt;
&lt;p&gt;
In web-based projects, it is surprisingly easy to have “what if …?” scenarios. Not sure which sign up form will have a higher conversion rate? Easy, just make two or more of them and run A/B tests.&lt;/p&gt;
&lt;p&gt;
Sometimes, as developers, we are so in the zone that we would keep on building stuff the way we envisioned. You will need to step in, pull us out, and say “hey, have you thought about …? What if …?”&lt;/p&gt;
&lt;p&gt;
Flickr as it is today would never have existed if the founders didn’t raise the question of “hey, how about doing &lt;em&gt;just&lt;/em&gt; the photo uploading and sharing features?”&lt;/p&gt;
&lt;h2&gt;
High Expectation&lt;/h2&gt;
&lt;p&gt;
“This is shit!” “We can’t ship this!” If the product stinks, say so, and find ways to improve it. An MVP should always be half-polished, not half-arsed.&lt;/p&gt;
&lt;p&gt;
The original iPhone was shipped without 3rd party native apps support, or multi-tasking - it wasn’t ideal, but they didn’t effect the core user experience. Now look at &lt;a href=&quot;http://en.wikipedia.org/wiki/BlackBerry_PlayBook#Reception_and_sales&quot;&gt;PlayBook&lt;/a&gt;, it has the features most Android devices have, but the core user experience is so bad that the product never took off. If someone at RIM’s top management had the same obsession on user experience as Steve Job’s, PlayBook would never have shipped in such a bad shape.&lt;/p&gt;
&lt;h2&gt;
Passion&lt;/h2&gt;
&lt;p&gt;
Are you in this for the money? Or for something else? Wealth is rarely a good motivation for creating great products.&lt;/p&gt;
&lt;p&gt;
“It can potentially generate massive revenue and profit” is a big red flag to me when someone pitches their projects.&lt;/p&gt;
&lt;p&gt;
These are the &lt;em&gt;key&lt;/em&gt; attributes I look for. Things like people connections and experience are also important but not essential. What about you? Do you look for any particular attributes in your potential co-founder(s)?&lt;/p&gt;
]]&gt;</content>
    <published>2012-01-28T06:16:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/on-hiring-how-to-be-a-non-technical-co-founder"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/on-hiring-how-to-be-a-non-technical-co-founder</id>
    <title>On Hiring: How To Be a Non-Technical Co-Founder</title>
    <updated>2012-01-28T06:16:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;em&gt;If you are looking at finding technical co-founders, &lt;a href=&quot;/2012-01-28-on-hiring-how-to-be-a-non-technical-co-founder/&quot;&gt;check out my article on this subject&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
In recent years developers become hotter and hotter - especially the good ones - they are hard to find, and they have plenty of employment options to choose from.&lt;/p&gt;
&lt;p&gt;
Some companies (or individuals who are seeking freelancers) go the extra miles to impress developers with attractive salary/rate and perks, which is nice. But surprisingly, many companies and individuals seem to have a habit of keep doing things that will annoy developers.&lt;/p&gt;
&lt;p&gt;
Over the years, I have personally encountered many situations that annoyed me as a developer. If you are looking for developers, here are a few things that I think you should be aware of.&lt;/p&gt;
&lt;h2&gt;
Budget Gap&lt;/h2&gt;
&lt;p&gt;
Find out what the developer’s salary/rate expectation is, there is no point if the developer is looking for $120k when you could only afford $80k. It’s absurd to think that you might be able to persuade them with a significant pay cut.&lt;/p&gt;
&lt;h2&gt;
Timing of Hiring&lt;/h2&gt;
&lt;p&gt;
Don’t advertise the position until you are fully ready to hire. Put a job ad up then go on holidays immediately after is a bizarre thing to do.&lt;/p&gt;
&lt;h2&gt;
Recruiters&lt;/h2&gt;
&lt;p&gt;
If you use recruitment agencies, please make sure you know them well enough and if not, do your fair share of research on them. Bad recruitment practices will cost you and damage your reputation. I once dealt with a recruiter who did not even bother to pass along my response to the offer back to the employer.&lt;/p&gt;
&lt;h2&gt;
Technical Assessment&lt;/h2&gt;
&lt;p&gt;
Unless you are Google or Facebook who have a huge pool of candidates to filter, it is probably a bad idea to test your candidate’s technical skills with brainfuck coding questions on paper or on whiteboards.&lt;/p&gt;
&lt;p&gt;
I am in favour of having candidates to do one or two coding tests (on their own, not during the interview) that will demonstrate their technical abilities without spending too much time or be under interview pressure. If a question is going to take a candidate more than a couple of hours to do, it is too much.&lt;/p&gt;
&lt;p&gt;
The technical questions should focus on the logic behind a candidate’s solutions, &lt;em&gt;not&lt;/em&gt; what functions or libraries a candidate may or may not remember from the API documentation.&lt;/p&gt;
&lt;p&gt;
Also, pair programming is an excellent way for both parties to get a sense of what it is like to work together.&lt;/p&gt;
&lt;h2&gt;
Communication Channel&lt;/h2&gt;
&lt;p&gt;
This applies more to hiring remote workers and freelancers. If you are pitching your project to a developer, please at the very least write a sentence or two to explain what your project is.&lt;/p&gt;
&lt;p&gt;
“Would you have time to have a quick chat?” is simply not good enough - good developers are all very busy and are likely to have many projects or potential projects to work on. Even if the developers have the time to chat to you (without knowing what your project is), it is still better to have the conversation via emails so the developers can keep track of things.&lt;/p&gt;
&lt;h2&gt;
Developer Relationship&lt;/h2&gt;
&lt;p&gt;
Recently there is a company in Melbourne who burnt a huge bridge with some of the most respected and talented local developers. Now they are having trouble finding talents even with inflated salary/rate. Moral of the story is, please always treat developers properly.&lt;/p&gt;
&lt;p&gt;
So, these are the few things I can think of for now. How about you? I would love to hear your thoughts and stories on hiring developers.&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://news.ycombinator.com/item?id=3513060&quot;&gt;Hacker News comments here.&lt;/a&gt;&lt;/p&gt;
]]&gt;</content>
    <published>2012-01-26T05:42:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/on-hiring-how-not-to-annoy-developers"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/on-hiring-how-not-to-annoy-developers</id>
    <title>On Hiring: How Not to Annoy Developers</title>
    <updated>2012-01-26T05:42:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Holidays period is the perfect time to gear up and learn a thing or two from the masters - and as it turned out, reading Eric Ries’ &lt;a href=&quot;http://theleanstartup.com/&quot;&gt;The Lean Startup&lt;/a&gt; is one of the most exciting and joyful things I’ve done during the holidays.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_lx26lsQ7Hm1qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
In this book you won’t find long-winded and boring theories, instead the book is full of real world use cases and practical advices.&lt;/p&gt;
&lt;p&gt;
If you are an entrepreneur, or if you are responsible for product development, I urge you to read this book if you haven’t already. :)&lt;/p&gt;
]]&gt;</content>
    <published>2011-12-31T08:18:19.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/the-lean-startup-the-book-every-entrepreneur-should-read"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/the-lean-startup-the-book-every-entrepreneur-should-read</id>
    <title>The Lean Startup - The Book Every Entrepreneur Should Read</title>
    <updated>2011-12-31T08:18:19.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
After four days of sketching, designing and cutting up HTML/CSS, the new design (as you are seeing now) is finally live!&lt;/p&gt;
&lt;p&gt;
The new design is structurally similar to &lt;a href=&quot;/blog/2010-03-02-blog-redesigned/&quot;&gt;the old design&lt;/a&gt;, but with a fresh header and better use of space.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_lwtph23mnm1qalr27.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
A new year warrants a fresh start. Apart from the redesign, I have also started heading up the development effort at &lt;a href=&quot;http://sitepoint.com/&quot;&gt;SitePoint&lt;/a&gt; - dozens of interesting and challenging projects ahead!&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_lwtpuklt5o1qalr27.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
2012 will be an awesome year! :)&lt;/p&gt;
]]&gt;</content>
    <published>2011-12-26T18:28:29.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/blog-redesigned-for-2012-and-new-challenges-ahead"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/blog-redesigned-for-2012-and-new-challenges-ahead</id>
    <title>Blog Redesigned For 2012, And New Challenges Ahead</title>
    <updated>2011-12-26T18:28:29.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
A couple of days ago, a blog post titled &lt;a href=&quot;http://blog.itlater.com/first-employee-of-startup-you-are-probably-getting-screwed/&quot;&gt;“First employee of startup? You are probably getting screwed!”&lt;/a&gt; has made to the Hacker News front page and has &lt;a href=&quot;http://news.ycombinator.com/item?id=2949323&quot;&gt;spawned some great discussion and debate&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Since &lt;a href=&quot;/blog/2011-08-17-open-sourcing-a-200-hour-project-the-story/&quot;&gt;I had just been screwed recently&lt;/a&gt;, I thought it might be a good opportunity for me to share what I’ve learnt.&lt;/p&gt;
&lt;p&gt;
After my story got &lt;a href=&quot;http://news.ycombinator.com/item?id=2891907&quot;&gt;picked up by Hacker News&lt;/a&gt; and some other sites, I was contacted by my client. It was a very interesting and surreal experience which included keywords such as &lt;em&gt;sue&lt;/em&gt;, &lt;em&gt;settle&lt;/em&gt;, &lt;em&gt;donation&lt;/em&gt; and &lt;em&gt;disappearance&lt;/em&gt;. I am however going to spare you the details and instead, going to focus on the things I have learnt.&lt;/p&gt;
&lt;h3&gt;
Be Strong, Be Emotionless&lt;/h3&gt;
&lt;p&gt;
The biggest mistake I made, was being too emotional - not in the sense of being emotionally attached to the project, but in the sense of being emotionally &lt;em&gt;attached to the opportunity&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;
Despite having detected some negative signs (which I will cover later), I willingly chose to continue on with the project because I &lt;em&gt;wanted&lt;/em&gt; it to work out. I &lt;em&gt;wanted&lt;/em&gt; to be involved early in a startup, I &lt;em&gt;wanted&lt;/em&gt; to create a product that will have large impact, I &lt;em&gt;wanted&lt;/em&gt; to have uncertainties and excitement, and I &lt;em&gt;wanted&lt;/em&gt; to force myself to be busy.&lt;/p&gt;
&lt;p&gt;
I was blindsided because of my desires - even though deep in my heart, I already felt the failure approaching.&lt;/p&gt;
&lt;h3&gt;
Be Informed Who You Are Dealing With&lt;/h3&gt;
&lt;p&gt;
My client, who was referred to me by a strong and reputable PHP developer, is well educated, has an MBA, and has worked as a VP of a major US corporation before he decided to start his own startup.&lt;/p&gt;
&lt;p&gt;
But didn’t that just prove doing background-checking is useless, you might wonder? No. It is still a useful exercise - it proves the fact that, even legit clients can do dodgy things. Therefore you would want to pay more attention to picking up negative signs rather than being impressed by their reputation and/or portfolio.&lt;/p&gt;
&lt;h3&gt;
Be Alert, Pick Up Tells&lt;/h3&gt;
&lt;p&gt;
Just like playing poker, you need to stay alert and try to pick up tells. What are the tells? Well, in my client’s case:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
He often emphasise the importance of communication, but he communicated poorly via emails.  &lt;/li&gt;
  &lt;li&gt;
He had unrealistic deadlines.  &lt;/li&gt;
  &lt;li&gt;
He was overly optimistic about the success of his startup.  &lt;/li&gt;
  &lt;li&gt;
He tried to lower my compensation expectation by using poor arguments.  &lt;/li&gt;
  &lt;li&gt;
He would call me up and talk for 20+ minutes when he was fully aware that I was at work (at my day job).  &lt;/li&gt;
  &lt;li&gt;
He was not being transparent and upfront about the VC side of things.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
The list goes on.&lt;/p&gt;
&lt;p&gt;
No one is perfect, of course. A minor tell or two might just be considered quirks, but half a dozen or more - you run.&lt;/p&gt;
&lt;h3&gt;
Be Informed About Market Rates&lt;/h3&gt;
&lt;p&gt;
Before negotiating my rates, I had done quite a lot of research online - it provided me with context.&lt;/p&gt;
&lt;p&gt;
As a non-full time, principal developer and designer, my compensation package was approximately:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
An hourly rate that is equivalent to 50% of my full time salary, or 30% of my contract/freelance rate.  &lt;/li&gt;
  &lt;li&gt;
2% of the company, after the seed round.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
In return, a 20-40 hours per week sweat investment was expected from me. To me, it was not a bad deal since I don’t rely on the success of the startup in order to feed myself - I still have my day job.&lt;/p&gt;
&lt;p&gt;
One thing to note, is that the agreed package was actually a result of me being too emotionally invested in the opportunity. The offer from my client was originally:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
An hourly rate that is equivalent to 90%+ of my full time salary, or 50% of my contract/freelance rate.  &lt;/li&gt;
  &lt;li&gt;
1% of the company, after the seed round.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
See what I did there? I was emotional enough to trade immediate return for potential future return.&lt;/p&gt;
&lt;p&gt;
One of the best advices I got from more experienced people in this field is that, always treat equities (or employee options) as a bonus that is likely to never happen.&lt;/p&gt;
&lt;h3&gt;
Be Cautious About the Information Received&lt;/h3&gt;
&lt;p&gt;
Until written on paper and signed, any information you were told should be taken with a grain of salt. Often than not, people would say things to favour and/or support their cases.&lt;/p&gt;
&lt;p&gt;
The &lt;em&gt;$2mil&lt;/em&gt; seed around valuation figure that was told (but was never proved) by my client has indeed influenced my decision of trading my hourly rate for equity.&lt;/p&gt;
&lt;h3&gt;
Be Positive, No Matter What Happens&lt;/h3&gt;
&lt;p&gt;
Positive thinking - one of the most powerful attitudes to ensure your happiness, is the key and morale of the story. You see, even though I went through a lot of stressful days and nights, I still consider the whole experience being positive.&lt;/p&gt;
&lt;p&gt;
Why? Because I learnt a lot. I learnt many things that I would not have learnt from working at my day job or freelancing. It gives me more confidence for my next startup adventure (whether it’s for someone else, or &lt;a href=&quot;http://wuit.com/&quot;&gt;for myself&lt;/a&gt;).&lt;/p&gt;
&lt;h3&gt;
Be Adventurous&lt;/h3&gt;
&lt;p&gt;
Don’t be scared by other people’s experience. Steve Jobs once said something along the line of - &lt;a href=&quot;http://youtu.be/3LEXae1j6EY?t=28m50s&quot;&gt;they started Apple because they didn’t know any better&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
I can comfortably say that, I am very much looking forward to my next adventure! :)&lt;/p&gt;
]]&gt;</content>
    <published>2011-09-02T12:49:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/startup-vc-and-the-things-i-learnt-from-open-sourcing-a-200-hour-client-project"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/startup-vc-and-the-things-i-learnt-from-open-sourcing-a-200-hour-client-project</id>
    <title>Startup, VC, and the Things I Learnt from Open-sourcing A 200+ Hour Client Project</title>
    <updated>2011-09-02T12:49:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Today I noticed that I don’t have Skype installed, so naturally, I went to Skype.com. Then I was presented with their homepage:&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_lqsaqvMauJ1qalr27.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
The problem? No “Download” action button above the fold, or below the fold for that matter. That is, quite frankly, shocking.&lt;/p&gt;
&lt;p&gt;
So I hovered on the “Get Skype” drop down menu and clicked on the one for Mac. On the new page I was presented with, I clicked on the “Download Skype” button. And then …&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_lqsatrI5XY1qalr27.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Oh snap! You’d have to either create an account or sign in before you could download Skype! Worse, by default it shows you the create an account section, and that section took a good 10 seconds to load for me for the first time.&lt;/p&gt;
&lt;p&gt;
Is this some kind of prank from the Microsoft enterprise? *shakes head*&lt;/p&gt;
]]&gt;</content>
    <published>2011-08-31T08:46:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/skype-com-a-quick-example-of-ux-failure"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/skype-com-a-quick-example-of-ux-failure</id>
    <title>Skype.com - A Quick Example of UX Failure</title>
    <updated>2011-08-31T08:46:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;em&gt;Update: I have blogged about &lt;a href=&quot;/blog/2011-09-02-startup-vc-and-the-things-i-learnt-from/&quot;&gt;the things I have learnt&lt;/a&gt; from this.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
Last night, after some long overdue frustration, I have open sourced &lt;a href=&quot;https://github.com/fredwu/angel_nest&quot;&gt;Angel Nest&lt;/a&gt; - an online platform for connecting entrepreneurs and investors, similar to &lt;a href=&quot;http://angel.co/&quot;&gt;AngelList&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Shortly after I open sourced my work, the news &lt;a href=&quot;http://news.ycombinator.com/item?id=2891907&quot;&gt;got picked up by Hacker News&lt;/a&gt; and went onto its front page. (Update: I was just informed that the story &lt;a href=&quot;http://www.reddit.com/r/programming/comments/jkr8r/developer_opensources_200_hr_project_after_client/&quot;&gt;was picked up&lt;/a&gt; by Reddit as well.) The &lt;a href=&quot;https://github.com/fredwu/angel_nest&quot;&gt;Github repository&lt;/a&gt; has since been followed by 250+ people and forked by 60+ times - it became one of the Github’s daily trending repositories.&lt;/p&gt;
&lt;p&gt;
Since then I have received many warm regards from fellow developers and entrepreneurs. Thank you so much guys! I will reply to each one of you as soon as I have a chance. :)&lt;/p&gt;
&lt;p&gt;
From the Hacker News comments as well as some of the comments received privately, there were a few questions raised, and I hope to address some of them in this blog post.&lt;/p&gt;
&lt;h2&gt;
Why didn’t you charge upfront or by milestone?&lt;/h2&gt;
&lt;p&gt;
Well, because it wasn’t a simple freelance or contract job. I was approached and asked to become the then to-be-set-up company’s first employee (yes, an employee, not a technical co-founder). At the time of our work agreement negotiation, the company was still to be formed and the investors were still to be signed on - no term sheets or contracts have been signed by any of the investors.&lt;/p&gt;
&lt;p&gt;
As a result, I agreed to put the invoices on hold for a while (i.e. a couple of weeks, according to him), then I will start invoicing the company fortnightly.&lt;/p&gt;
&lt;p&gt;
Oh, and many of you think I am a freelancer. Well, yes and no, I freelance, but my income is mostly from my full time job (as a web developer, of course). :)&lt;/p&gt;
&lt;h2&gt;
Still, why did you trust him so easily?&lt;/h2&gt;
&lt;p&gt;
Before I agreed to work on the project, I did some background-checking of this so-called entrepreneur - everything seemed legit - he was a developer himself a few years ago; he worked as a VP in a major corporation in the States before committing himself to this project; and from the few Skype conversations we had, he seemed to understand how to keep a developer happy. As a matter of fact, he had stressed many times that he would pay the developers a fair deal in order to keep them happy.&lt;/p&gt;
&lt;h2&gt;
And how could you have agreed to work for him without any contracts?&lt;/h2&gt;
&lt;p&gt;
Being a person with goodwill, I agreed to work for him on this project - essentially as the principal developer. My contribution was supposed to be compensated by a low hourly rate + equity in the company - the figures were agreed both verbally and via written Email.&lt;/p&gt;
&lt;p&gt;
Ordinarily I value equity very little. However, the way he described the investment situation - the company being valued at $2M, with investment from several top VCs and angels in China - got me believed that the company had a very strong lineup of investors and a reasonably large sum of first round investment.&lt;/p&gt;
&lt;p&gt;
To this day I still do not know what the actual investment situation is. It seems to be a sensitive topic whenever I brought it up during our conversations.&lt;/p&gt;
&lt;h2&gt;
Why didn’t you stop working sooner?&lt;/h2&gt;
&lt;p&gt;
I wanted to be professional - I cared more about building an awesome product than receiving a fat cheque (not that the cheque will be fat anyway, given the low hourly rate).&lt;/p&gt;
&lt;p&gt;
He stressed many times that the project is very time sensitive and we needed to launch as soon as possible.&lt;/p&gt;
&lt;p&gt;
And in order to speed up the development progress, I even took two weeks off work (with many unpaid leave days) and worked on this project. Looking back, I lost not only the compensation I am entitled to, but also a fraction of the salary that puts food on my table.&lt;/p&gt;
&lt;h2&gt;
Are you upset?&lt;/h2&gt;
&lt;p&gt;
Actually, not really. As I said before, I care more about building fine products than receiving more money - just the fact that I worked on a real Rails 3.1 project is very fulfilling in itself. And the fact that I received so many warm regards and comments - I am :) rather than :(.&lt;/p&gt;
&lt;p&gt;
As for the non-paying client, well, this isn’t my first time, and I usually just choose to forget about them and move on. I have too many more important things to worry about than chasing after payments.&lt;/p&gt;
&lt;h2&gt;
Can I donate?&lt;/h2&gt;
&lt;p&gt;
I sincerely thank you for your offer, but no I do not need donation.&lt;/p&gt;
&lt;h2&gt;
What about the intellectual property? Any NDA?&lt;/h2&gt;
&lt;p&gt;
We have no contracts or any paper signed whatsoever, despite I requesting them repeatedly - hence why I could release the source code at my will.&lt;/p&gt;
&lt;h2&gt;
Why release the source code in MIT?&lt;/h2&gt;
&lt;p&gt;
Because I am a big fan of total freedom in open source. I &lt;a href=&quot;http://fuckgpl.com/&quot;&gt;do not like&lt;/a&gt; the GPL family of licenses. Having said that, I respect the opinion of other peoples’, so I dual licensed the source code under GPL as well.&lt;/p&gt;
&lt;h2&gt;
But open-sourcing the system gives them access?&lt;/h2&gt;
&lt;p&gt;
They (the entrepreneur himself, and another account who I suspect is the new developer) already have access to the Github repository. Their access was revoked just before I open sourced the work.&lt;/p&gt;
&lt;p&gt;
To be honest, I could not care less what they do with the source code. Just the fact that he chose to shaft me is a strong enough indication that he will not succeed.&lt;/p&gt;
&lt;h2&gt;
Again, why open source now?&lt;/h2&gt;
&lt;p&gt;
I would have waited a little longer if it wasn’t for the fact that his new developers don’t seem to know what they were doing. A while ago I received a bunch of broken test emails sent to my inbox - indicating that he’s already hired someone else to continue the development work. Last night, a bunch of test emails came through again - so that was when I decided enough is enough.&lt;/p&gt;
&lt;h2&gt;
Is it wise for you to do this?&lt;/h2&gt;
&lt;p&gt;
I don’t know, you decide! :) Is gifting the source code to someone who might find it useful a good thing? I sure hope so.&lt;/p&gt;
&lt;h2&gt;
Are you looking for freelance work?&lt;/h2&gt;
&lt;p&gt;
Nope. Not because I just got burned, but because I have &lt;a href=&quot;http://wuit.com/&quot;&gt;my own adventures&lt;/a&gt; to pursue.&lt;/p&gt;
&lt;h2&gt;
What about other work and/or startup opportunities?&lt;/h2&gt;
&lt;p&gt;
You may find out by dropping me a line, all my contact details can be found on this blog. :)&lt;/p&gt;
&lt;h2&gt;
I am interested, but who are you?&lt;/h2&gt;
&lt;p&gt;
Scroll down to the bottom of the page and you will find my bio with a couple of links. :)&lt;/p&gt;
]]&gt;</content>
    <published>2011-08-17T11:55:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/open-sourcing-a-200-hour-project-the-story-behind-it"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/open-sourcing-a-200-hour-project-the-story-behind-it</id>
    <title>Open-sourcing A 200+ Hour Project - The Story Behind It</title>
    <updated>2011-08-17T11:55:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;em&gt;On a cold windy day, we are comfortably sitting in our chairs beside the fireplace, holding a mug full of hot tasty latte. We are slowly and calmly sipping on our coffee, imagining that things could be far worse outside of our safe and warm house.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
Exactly how do we know that things could be far worse? We don’t, because we are too comfortable and too familiar with what we currently have.&lt;/p&gt;
&lt;p&gt;
Being happy with where you are is one thing, being ignorant is another.&lt;/p&gt;
&lt;p&gt;
Inventions and innovations aren’t born out of happiness, they are born out of frustration, anger and sometimes, curiosity.&lt;/p&gt;
&lt;p&gt;
Being open-minded about how things work, in my opinion is one of the key attributes an entrepreneur should have.&lt;/p&gt;
&lt;p&gt;
All decisions come with implications and consequences, to dismiss an alternative approach, or worse, to not have any alternative approaches at all because of ignorance is often one of the first signs of failure.&lt;/p&gt;
&lt;p&gt;
Successes are built on many failures, there is no doubt about that. However, failures should be the outcome of experimentation rather than ignorance.&lt;/p&gt;
&lt;p&gt;
Frustrations and concerns arise when businesses make decisions (or should I say, mistakes) because the decision makers are close-minded - whether it’s a decision about choosing a piece of technology, or it’s a decision about sticking to a fixed launch date.&lt;/p&gt;
&lt;p&gt;
Blindly following some rare exceptions such as Apple and 37signals is probably not going to work for most of us.&lt;/p&gt;
&lt;p&gt;
But heck, what do I know, I am just a web developer, right?&lt;/p&gt;
]]&gt;</content>
    <published>2011-07-30T12:01:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/its-year-2011-why-arent-people-more-open-minded"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/its-year-2011-why-arent-people-more-open-minded</id>
    <title>It&apos;s Year 2011, Why Aren&apos;t People More Open-minded?</title>
    <updated>2011-07-30T12:01:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_lnwv8teVP01qb7ot5o1_r3_1280.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Comic: Because ActiveRecord is Slow!&lt;/p&gt;
]]&gt;</content>
    <published>2011-07-06T12:12:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/comic-because-activerecord-is-slow"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/comic-because-activerecord-is-slow</id>
    <title>comic because activerecord is slow</title>
    <updated>2011-07-06T12:12:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Today is my last day at &lt;a href=&quot;http://envato.com/&quot;&gt;Envato&lt;/a&gt;. I have been working here for a year and half and it has been, without a doubt the most fulfilling experience I have ever had in my professional career.&lt;/p&gt;
&lt;p&gt;
I was hired as a PHP developer initially. Who knew, several months into the job I was ‘converted’ voluntarily to a full time Ruby developer - and it has been &lt;em&gt;the&lt;/em&gt; highlight of my career progression. It is hard to imagine what I would have become if it wasn’t for Envato’s support behind my conversion.&lt;/p&gt;
&lt;p&gt;
During my time at Envato, I had the opportunity to work with some of the most talented people in the web industry, from both inside Envato and externally. I am extremely grateful for the experience and I hope to work with them again in the future.&lt;/p&gt;
&lt;p&gt;
The decision of moving on from Envato was not made easily. As a matter of fact, I have always been passionate about online education (e.g. the &lt;a href=&quot;http://tutsplus.com/&quot;&gt;Tuts+ network&lt;/a&gt;) and I sincerely hope the project will be taken to the next level.&lt;/p&gt;
&lt;p&gt;
Oh hey, I am still working on a book project with &lt;a href=&quot;http://rockablepress.com/&quot;&gt;Rockable Press&lt;/a&gt; from Envato. If you are a web developer, expect to see a (hopefully useful) web development book this year. ;)&lt;/p&gt;
&lt;p&gt;
10 days from now I will be joining &lt;a href=&quot;http://playup.com/&quot;&gt;PlayUp&lt;/a&gt; as a senior web developer - to work on more awesome Ruby/Rails stuff! :) The colleagues I have already met at PlayUp are extremely talented and friendly. I cannot wait to start my first day there!&lt;/p&gt;
&lt;p&gt;
Aside from my day job, I am also working with some very experienced professionals on a startup project - hopefully you will get to hear more about it soon.&lt;/p&gt;
&lt;p&gt;
Last but not least, I am finally out of some personal and relationship crisis, and I am able to regain my focus and spend more time working on my varies side projects (to be branded under &lt;a href=&quot;http://wuit.com/&quot;&gt;Wuit&lt;/a&gt;) as well as on some of the open source projects such as &lt;a href=&quot;http://slim-lang.com/&quot;&gt;Slim&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Exciting times ahead! :)&lt;/p&gt;
]]&gt;</content>
    <published>2011-03-17T03:04:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/moving-on-from-envato-whats-next"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/moving-on-from-envato-whats-next</id>
    <title>Moving On From Envato, What&apos;s Next?</title>
    <updated>2011-03-17T03:04:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_lf8ozrtSef1qb7ot5o1_1280.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
My entry to the RubyCommitters design contest. View it in action at &lt;a href=&quot;http://heroesofruby.heroku.com/&quot;&gt;http://heroesofruby.heroku.com/&lt;/a&gt; :-)&lt;/p&gt;
]]&gt;</content>
    <published>2011-01-18T22:26:15.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/my-entry-to-the-rubycommitters-design-contest"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/my-entry-to-the-rubycommitters-design-contest</id>
    <title>my entry to the rubycommitters design contest</title>
    <updated>2011-01-18T22:26:15.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_lduoh5xH981qb7ot5o1_r1_1280.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Sneak peak of the redesign of slim-lang.com. :)&lt;/p&gt;
]]&gt;</content>
    <published>2010-12-22T22:15:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/sneak-peak-of-the-redesign-of-slim-langcom"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/sneak-peak-of-the-redesign-of-slim-langcom</id>
    <title>sneak peak of the redesign of slim langcom</title>
    <updated>2010-12-22T22:15:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
So, you are using Slim, right? If not, go &lt;a href=&quot;https://github.com/stonean/slim&quot;&gt;check it out&lt;/a&gt; because it’s awesome. ;)&lt;/p&gt;
&lt;p&gt;
Now, I don’t know about you but prior to Slim I use Haml quite a bit, and even though syntax-wise Haml and Slim have a lot in common, it’s still quite a challenge to convert all Haml templates to Slim templates.&lt;/p&gt;
&lt;p&gt;
As a result, let me present you with a quick n’ dirty Haml2Slim converter! &lt;a href=&quot;https://github.com/fredwu/haml2slim&quot;&gt;Check out the source code.&lt;/a&gt;&lt;/p&gt;
]]&gt;</content>
    <published>2010-12-16T09:08:51.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/ruby-haml2slim-convert-your-haml-templates-to-slim-templates"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/ruby-haml2slim-convert-your-haml-templates-to-slim-templates</id>
    <title>[Ruby] Haml2Slim, Convert Your HAML Templates to Slim Templates</title>
    <updated>2010-12-16T09:08:51.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
At &lt;a href=&quot;http://envato.com/&quot;&gt;Envato&lt;/a&gt; we have a few dozen sites residing on multiple servers. The data on a portion of the servers need to be regularly backed up to &lt;a href=&quot;http://aws.amazon.com/s3/&quot;&gt;Amazon S3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
The Envato Mad Scientist &lt;a href=&quot;http://github.com/ryan-allen&quot;&gt;Ryan Allen&lt;/a&gt; has worked on a script called &lt;a href=&quot;http://github.com/ryan-allen/sir-sync-a-lot&quot;&gt;Sir Sync-A-Lot&lt;/a&gt; which syncs the data to S3. This was done after evaluating a bunch of scripts including &lt;a href=&quot;http://www.s3sync.net/&quot;&gt;s3sync&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Today I turned Ryan’s original script into &lt;a href=&quot;http://rubygems.org/gems/ssync&quot;&gt;a little Rubygem&lt;/a&gt; and added a bit more features.&lt;/p&gt;
&lt;p&gt;
Go &lt;a href=&quot;https://github.com/fredwu/ssync&quot;&gt;check out the source code&lt;/a&gt;!&lt;/p&gt;
]]&gt;</content>
    <published>2010-11-12T08:15:41.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/ruby-releasing-ssync-an-optimised-s3-sync-tool-using-the-power-of-unix"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/ruby-releasing-ssync-an-optimised-s3-sync-tool-using-the-power-of-unix</id>
    <title>[Ruby] Releasing Ssync - An Optimised S3 Sync Tool Using the Power of Unix!</title>
    <updated>2010-11-12T08:15:41.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
For the past few weeks I have started contributing to a small project - &lt;a href=&quot;http://github.com/stonean/slim&quot;&gt;Slim&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;
Slim is a fast, lightweight templating engine for Rails 3. It has been tested on Ruby 1.9.2 and Ruby/REE 1.8.7. Slim is heavily influenced by &lt;a href=&quot;http://github.com/nex3/haml&quot;&gt;Haml&lt;/a&gt; and &lt;a href=&quot;http://github.com/visionmedia/jade&quot;&gt;Jade&lt;/a&gt;.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;a href=&quot;http://github.com/stonean&quot;&gt;Andrew Stone&lt;/a&gt; who is the author of the project has posted &lt;a href=&quot;http://stonean.com/slim-update&quot;&gt;a quick update&lt;/a&gt; on the latest feature additions to Slim. Please go check it out.&lt;/p&gt;
&lt;p&gt;
The source code of Slim is &lt;a href=&quot;http://github.com/stonean/slim&quot;&gt;available on Github&lt;/a&gt;.&lt;/p&gt;
]]&gt;</content>
    <published>2010-10-17T21:40:20.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/slim-a-fast-and-lightweight-rails-template-engine"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/slim-a-fast-and-lightweight-rails-template-engine</id>
    <title>Slim, a Fast and Lightweight Rails Template Engine!</title>
    <updated>2010-10-17T21:40:20.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;strong&gt;Before (Rails 3.0.1pre stable branch + Arel 1.0.1):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_l9hgzuhG7b1qalr27.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;After (Rails 3.1.0 master branch + Arel 2.0.0dev master branch:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_l9hh02dWtv1qalr27.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Thanks to the awesome work done by Aaron Patterson (&lt;a href=&quot;http://github.com/tenderlove&quot;&gt;@tenderlove&lt;/a&gt;) and others. :-)&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;UPDATE:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;
After Aaron Patteron’s &lt;a href=&quot;http://twitter.com/tenderlove/status/25837698499&quot;&gt;tweet&lt;/a&gt;, I ran the tests again on Rails 3.0.1pre stable branch + Arel 2.0.0dev master branch, and the result blew my mind:&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_l9hhxw2fkl1qalr27.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
]]&gt;</content>
    <published>2010-09-29T00:51:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/a-speedier-rails-app-using-rails-3-1-arel-2-0"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/a-speedier-rails-app-using-rails-3-1-arel-2-0</id>
    <title>A Speedier Rails App using Rails 3.1 + Arel 2.0</title>
    <updated>2010-09-29T00:51:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Don’t you just hate it when you get the following errors during a &lt;a href=&quot;http://www.capify.org/&quot;&gt;Capistrano&lt;/a&gt; deployment?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;bundle: command not found
Could not find RubyGem bundler (&amp;gt;= 0) (Gem::LoadError)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
In fact, even if you don’t use &lt;a href=&quot;http://gembundler.com/&quot;&gt;bundler&lt;/a&gt;, you might still get errors like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;rake: command not found
Could not find RubyGem rake (&amp;gt;= 0) (Gem::LoadError)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
It turns out this has something to do with the &lt;code class=&quot;inline&quot;&gt;$PATH&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;$GEM_HOME&lt;/code&gt; variables.&lt;/p&gt;
&lt;p&gt;
So here’s the quick fix.&lt;/p&gt;
&lt;p&gt;
Log in to your deployment server, as a root user, add the following line to &lt;code class=&quot;inline&quot;&gt;/etc/ssh/sshd_config&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PermitUserEnvironment yes&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Don’t forget to restart &lt;code class=&quot;inline&quot;&gt;ssh&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;/etc/init.d/ssh restart&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Now, log in as the deployment user, and create ’~/.ssh/environment’ with the following content:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;PATH=/usr/local/rvm/gems/ruby-1.9.2-p0/bin:/bin:/usr/local/rvm/rubies/ruby-1.9.2-p0/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
GEM_HOME=/usr/local/rvm/gems/ruby-1.9.2-p0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
&lt;em&gt;* The above paths are for your reference only, obviously you need to work them out for your server environment. The only thing you need to make sure is that the &lt;code class=&quot;inline&quot;&gt;GEM_HOME&lt;/code&gt;’s path matches one from the &lt;code class=&quot;inline&quot;&gt;PATH&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
Now, to verify this all work, you may use &lt;code class=&quot;inline&quot;&gt;cap shell&lt;/code&gt; to start a new shell session and try out your commands.&lt;/p&gt;
]]&gt;</content>
    <published>2010-09-16T05:59:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/bundle-command-not-found-or-could-not-find-rubygem-bundler-0-during-capistrano-deployment-no-problems"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/bundle-command-not-found-or-could-not-find-rubygem-bundler-0-during-capistrano-deployment-no-problems</id>
    <title>`bundle: command not found` or `Could not find RubyGem bundler (&gt;= 0)` During Capistrano Deployment? No Problems!</title>
    <updated>2010-09-16T05:59:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_l7szu5WSQ51qb7ot5o1_1280.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Wuit trademarked in Australia now! :-)&lt;/p&gt;
&lt;p&gt;
The trademark application in the US is being progressed as well.&lt;/p&gt;
&lt;p&gt;
If you don’t already know, &lt;a href=&quot;http://wuit.com/&quot;&gt;Wuit&lt;/a&gt; is my soon to be launched studio identity.&lt;/p&gt;
]]&gt;</content>
    <published>2010-08-27T09:03:41.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/wuit-trademarked-in-australia-now-the"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/wuit-trademarked-in-australia-now-the</id>
    <title>wuit trademarked in australia now the</title>
    <updated>2010-08-27T09:03:41.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
If you haven’t already been using &lt;a href=&quot;http://github.com/fredwu/app_config&quot;&gt;AppConfig&lt;/a&gt; in your Rails project, well, you should!&lt;/p&gt;
&lt;p&gt;
Jacques Crocker has recently released his new version of the original AppConfig - &lt;a href=&quot;http://github.com/railsjedi/rails_config&quot;&gt;&lt;strong&gt;RailsConfig&lt;/strong&gt;&lt;/a&gt;. I was invited to join the development of this new tool, so make sure you go &lt;a href=&quot;http://github.com/railsjedi/rails_config&quot;&gt;check it out&lt;/a&gt;. :-)&lt;/p&gt;
]]&gt;</content>
    <published>2010-08-11T01:50:14.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/rails-railsconfig-a-new-iteration-of-appconfig-for-rails-3"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/rails-railsconfig-a-new-iteration-of-appconfig-for-rails-3</id>
    <title>[Rails] RailsConfig, A New Iteration of AppConfig for Rails 3</title>
    <updated>2010-08-11T01:50:14.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
I have just &lt;a href=&quot;http://github.com/fredwu/inherited_resources_views/commit/5fd1f928a0ee1f31f413794b8569505ac072f814&quot;&gt;pushed a commit&lt;/a&gt; that added Rails 2.x compatibility to &lt;a href=&quot;http://github.com/fredwu/inherited_resources_views&quot;&gt;Inherited Resources Views&lt;/a&gt;. Please give it a spin! :-)&lt;/p&gt;
&lt;p&gt;
P.S. I’ve only tested it on Rails 2.3.8.&lt;/p&gt;
]]&gt;</content>
    <published>2010-08-07T07:35:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/rails-inherited-resources-views-now-supports-rails-2-x"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/rails-inherited-resources-views-now-supports-rails-2-x</id>
    <title>[Rails] Inherited Resources Views Now Supports Rails 2.x</title>
    <updated>2010-08-07T07:35:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
It isn’t the &lt;a href=&quot;http://www.woopra.com/&quot;&gt;first&lt;/a&gt; &lt;a href=&quot;http://chartbeat.com/&quot;&gt;time&lt;/a&gt; realtime website traffic analysis has been introduced. &lt;a href=&quot;http://reinvigorate.net/&quot;&gt;Reinvigorate&lt;/a&gt; is one of the services that provides realtime traffic tracking and analysis.&lt;/p&gt;
&lt;p&gt;
The thing I like it most though, is in fact the heatmap. :-)&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_l6pfuqOuAp1qalr27.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Heatmap is a great tool to help identify convoluted interface and improve the user experience.&lt;/p&gt;
]]&gt;</content>
    <published>2010-08-06T00:33:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/reinvigorate-realtime-website-traffic-analysis"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/reinvigorate-realtime-website-traffic-analysis</id>
    <title>Reinvigorate - Realtime Website Traffic Analysis</title>
    <updated>2010-08-06T00:33:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
I have just added a &lt;a href=&quot;http://github.com/&quot;&gt;Github&lt;/a&gt; ribbon to my blog (look at the top left corner!). Please feel free to &lt;a href=&quot;http://github.com/fredwu&quot;&gt;poke me&lt;/a&gt;! :D&lt;/p&gt;
&lt;p&gt;
References:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
&lt;a href=&quot;http://people.mozilla.com/~jbalogh/ribbon/ribbon.html&quot;&gt;Redoing the GitHub Ribbon in CSS&lt;/a&gt;  &lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;http://unindented.org/articles/2009/10/github-ribbon-using-css-transforms/&quot;&gt;GitHub Ribbon Using CSS Transforms&lt;/a&gt;  &lt;/li&gt;
&lt;/ul&gt;
]]&gt;</content>
    <published>2010-08-06T00:03:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/poke-me-on-github-d"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/poke-me-on-github-d</id>
    <title>Poke me on Github! :D</title>
    <updated>2010-08-06T00:03:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Using &lt;a href=&quot;http://github.com/josevalim/inherited_resources&quot;&gt;Inherited Resources&lt;/a&gt; is an excellent way to reduce the amount of repetition in your controllers. But what about views? A lot of times resources share the same views, so why not DRY ‘em up using &lt;strong&gt;Inherited Resources Views&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://github.com/fredwu/inherited_resources_views&quot;&gt;Go check out the code!&lt;/a&gt; :-)&lt;/p&gt;
]]&gt;</content>
    <published>2010-08-05T11:16:53.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/rails-releasing-inherited-resources-views-dry-your-view-files"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/rails-releasing-inherited-resources-views-dry-your-view-files</id>
    <title>[Rails] Releasing Inherited Resources Views - DRY Your View Files</title>
    <updated>2010-08-05T11:16:53.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_l6mleacQXa1qb7ot5o1_1280.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Released my first ruby gem. :-)&lt;/p&gt;
]]&gt;</content>
    <published>2010-08-04T11:32:34.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/released-my-first-ruby-gem"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/released-my-first-ruby-gem</id>
    <title>released my first ruby gem</title>
    <updated>2010-08-04T11:32:34.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_l6mb9eIQfL1qb7ot5o1_500.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Looks like it’s time for a reboot…&lt;/p&gt;
]]&gt;</content>
    <published>2010-08-04T07:53:38.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/looks-like-its-time-for-a-reboot"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/looks-like-its-time-for-a-reboot</id>
    <title>looks like its time for a reboot</title>
    <updated>2010-08-04T07:53:38.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
In a web app, it is very common to have actions that destroy (delete/remove) data. These actions, if you don’t already know, should always map to POST methods. On top of that, because these actions are destructive, the UI should always ask the user for confirmation.&lt;/p&gt;
&lt;p&gt;
But how do we actually implement the confirmation dialogue though? The vanilla JavaScript confirm box would be the easiest but at the same time the ugliest - this thing stalls most web browsers until the user acts on it.&lt;/p&gt;
&lt;p&gt;
An inline popup/modal box? Perhaps, but it is still obtrusive, in the sense that the popup/model boxes are usually in the way of other tasks.&lt;/p&gt;
&lt;p&gt;
Meet &lt;a href=&quot;http://github.com/fredwu/jquery-inline-confirmation&quot;&gt;&lt;strong&gt;Inline Confirmation&lt;/strong&gt;&lt;/a&gt; - a jQuery plugin for creating easy, less obtrusive confirmation dialogues!&lt;/p&gt;
&lt;p&gt;
Feel free to give it a spin. I will add more documentation and a demo when and if I have time. ;)&lt;/p&gt;
]]&gt;</content>
    <published>2010-08-03T05:41:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/jquery-releasing-inline-confirmation-confirm-actions-done-right"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/jquery-releasing-inline-confirmation-confirm-actions-done-right</id>
    <title>[jQuery] Releasing Inline Confirmation, Confirm Actions Done Right</title>
    <updated>2010-08-03T05:41:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
I am extremely happy that &lt;a href=&quot;http://github.com/rails/rails/commit/cdad483dff4fef1b640dc3c750719c325b252f89&quot;&gt;my patch was accepted&lt;/a&gt;, so I am now &lt;a href=&quot;http://contributors.rubyonrails.org/contributors/fred-wu/commits&quot;&gt;one of the 1600 odd people&lt;/a&gt; who have contributed to the Rails project! :D&lt;/p&gt;
]]&gt;</content>
    <published>2010-08-02T22:40:46.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/im-now-a-ruby-on-rails-contributor"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/im-now-a-ruby-on-rails-contributor</id>
    <title>I&apos;m Now a Ruby on Rails Contributor</title>
    <updated>2010-08-02T22:40:46.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Similarly to &lt;a href=&quot;/blog/2010-06-03-rails-releasing-action-throttler-a-rails-plugin/&quot;&gt;Action Throttler for Rails&lt;/a&gt;, &lt;a href=&quot;http://github.com/fredwu/kthrottler&quot;&gt;KThrottler&lt;/a&gt; is an easy to use &lt;a href=&quot;http://kohanaframework.org/&quot;&gt;Kohana&lt;/a&gt; module to quickly throttle application actions based on configurable duration and limit.&lt;/p&gt;
&lt;p&gt;
Go &lt;a href=&quot;http://github.com/fredwu/kthrottler&quot;&gt;check out the code&lt;/a&gt; now! :)&lt;/p&gt;
]]&gt;</content>
    <published>2010-07-28T13:29:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/php-releasing-kthrottler-a-kohana-module-for-throttling-actions"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/php-releasing-kthrottler-a-kohana-module-for-throttling-actions</id>
    <title>[PHP] Releasing KThrottler, A Kohana Module for Throttling Actions</title>
    <updated>2010-07-28T13:29:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;a href=&quot;http://twitter.com/fredwu/status/19571058314&quot;&gt;Dating girls is like calling a callback function without knowing the accepted arguments. Random exceptions would throw right in your face.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://twitter.com/fredwu/status/19571385471&quot;&gt;Dating girls is like running prerelease ruby gems. Things might not be compatible, there might be memory leaks and other weirdnesses.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://twitter.com/fredwu/status/19571746547&quot;&gt;Dating girls is like reading a tutorial in a foreign language. You &lt;em&gt;think&lt;/em&gt; you got it, but you don’t.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://twitter.com/fredwu/status/19571889870&quot;&gt;Dating girls is like using a ruby class with lots of mixins. You’re looking right at a class method, but you don’t know where it comes from.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://twitter.com/fredwu/status/19572001916&quot;&gt;Dating girls is like running rails on Webrick. It works, for the most part, but you don’t know when it will fall into pieces.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://twitter.com/fredwu/status/19572293695&quot;&gt;Dating girls is like using Zend Server for PHP - it might have impressive features, but it’s expensive and the shit still runs on Apache!!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://twitter.com/fredwu/status/19572777145&quot;&gt;Dating girls is like using CakePHP. It might look like Rails, but it’s not, unless you’re happy with seeing arrays &lt;em&gt;everywhere&lt;/em&gt;.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://twitter.com/fredwu/status/19573874114&quot;&gt;Dating girls is like running production PHP sites on IIS. Sometimes you wonder, why bother?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://twitter.com/fredwu/status/19573956115&quot;&gt;Dating girls is like writing a Capistrano recipe, you don’t know what it’s going to happen until you run it.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://twitter.com/fredwu/status/19574037164&quot;&gt;Dating girls is like buying an iPhone 4. You know it has flaws, but you are gonna buy it anyway.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://twitter.com/fredwu/status/19574359795&quot;&gt;Dating girls is like coding in Obj-C. It’s hot right now, but you wonder how long is it going to last?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://twitter.com/fredwu/status/19574775353&quot;&gt;Dating is like using Zend Framework. It looks so impressive from distance, then when you actually started using it…&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://twitter.com/fredwu/status/19574944487&quot;&gt;Dating girls is like making a huge deployment. As confident as you are, you still need to cross fingers!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://twitter.com/fredwu/status/19575255587&quot;&gt;Dating girls is like developing for Wordpress. It looks so pretty on the surface, but the roots are broken beyond repair.&lt;/a&gt;&lt;/p&gt;
]]&gt;</content>
    <published>2010-07-26T14:21:11.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/whatdatingislike-what-dating-is-like"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/whatdatingislike-what-dating-is-like</id>
    <title>#whatdatingislike What Dating Is Like</title>
    <updated>2010-07-26T14:21:11.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;h2&gt;
Introduction&lt;/h2&gt;
&lt;p&gt;
ActiveRecord is without doubt the &lt;em&gt;de facto&lt;/em&gt; ORM library for Rails and many Ruby web frameworks. Many developers however, do not like database migrations and prefer to use DSL for data mapping. Datamappify is created with the sole purpose of getting rid of the DB migration headaches.&lt;/p&gt;
&lt;h2&gt;
Why Not DB Migrations?&lt;/h2&gt;
&lt;p&gt;
Well, depending on your specific project, DB migrations might create more trouble than it’s worth. Besides, your code is already version controlled, so why create a separate version control for your DB schema?&lt;/p&gt;
&lt;h2&gt;
Why Not Use DataMapper, Sequel, etc?&lt;/h2&gt;
&lt;p&gt;
As stated in the introduction, ActiveRecord is the most popular ORM in the rails community, it is actively developed and battle-tested. If your only grief with ActiveRecord is the DB migrations, why not just eliminate it be happy? ;)&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;&lt;a href=&quot;http://github.com/fredwu/datamappify&quot;&gt;Go check out the code&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;
]]&gt;</content>
    <published>2010-07-22T12:30:50.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/rails-introducing-datamappify-activerecord-without-db-migrations"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/rails-introducing-datamappify-activerecord-without-db-migrations</id>
    <title>[Rails] Introducing Datamappify - ActiveRecord Without DB Migrations</title>
    <updated>2010-07-22T12:30:50.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;a href=&quot;http://fuckgpl.com/&quot;&gt;Fuck GPL!&lt;/a&gt;&lt;/p&gt;
]]&gt;</content>
    <published>2010-07-15T13:56:17.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/fuck-gpl"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/fuck-gpl</id>
    <title>Fuck GPL!</title>
    <updated>2010-07-15T13:56:17.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_l5jx86t1e41qb7ot5o1_1280.png&quot; alt=&quot;&quot; /&gt;

  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_l5jx86t1e41qb7ot5o2_1280.png&quot; alt=&quot;&quot; /&gt;

  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_l5jx86t1e41qb7ot5o3_1280.png&quot; alt=&quot;&quot; /&gt;

  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_l5jx86t1e41qb7ot5o4_1280.png&quot; alt=&quot;&quot; /&gt;

  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_l5jx86t1e41qb7ot5o5_1280.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Just some late night sketches, done with a finger, an iPad and some wine.&lt;/p&gt;
]]&gt;</content>
    <published>2010-07-14T14:21:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/just-some-late-night-sketches-done-with-a-finger"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/just-some-late-night-sketches-done-with-a-finger</id>
    <title>just some late night sketches done with a finger</title>
    <updated>2010-07-14T14:21:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
A few days ago I discovered &lt;a href=&quot;http://www.padrinorb.com/&quot;&gt;Padrino&lt;/a&gt;, an excellent ruby framework built on top of &lt;a href=&quot;http://www.sinatrarb.com/&quot;&gt;Sinatra&lt;/a&gt;. What can be a better than experimenting with it? Build an actual website with it!&lt;/p&gt;
&lt;p&gt;
So last night I ported &lt;a href=&quot;http://wuit.com/&quot;&gt;Wuit.com&lt;/a&gt; from using vanilla PHP + &lt;a href=&quot;http://flourishlib.com/&quot;&gt;Flourish&lt;/a&gt; to using Padrino + &lt;a href=&quot;http://datamapper.org/&quot;&gt;DataMapper&lt;/a&gt; + &lt;a href=&quot;http://haml-lang.com/&quot;&gt;Haml&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
The entire porting experience was positive, albeit the website only has two pages.&lt;/p&gt;
&lt;p&gt;
I did encounter a strange problem with &lt;a href=&quot;http://www.capify.org/&quot;&gt;Capistrano&lt;/a&gt; though - both &lt;code class=&quot;inline&quot;&gt;current_release&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;latest_release&lt;/code&gt; were giving me strange results. In the end I had to modify my deployment recipe to overcome this.&lt;/p&gt;
&lt;p&gt;
For building small to medium sized projects, I think Padrino is an excellent choice, as it offers a more complete foundation than Sinatra, and is not as heavy as Rails.&lt;/p&gt;
]]&gt;</content>
    <published>2010-07-01T21:59:28.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/wuit-com-now-runs-on-padrino"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/wuit-com-now-runs-on-padrino</id>
    <title>Wuit.com Now Runs on Padrino</title>
    <updated>2010-07-01T21:59:28.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;a href=&quot;http://haml-lang.com/&quot;&gt;Haml&lt;/a&gt; and &lt;a href=&quot;http://sass-lang.com/&quot;&gt;Sass&lt;/a&gt; are two extremely useful template engines. They are very popular amongst the Ruby and Rails community.&lt;/p&gt;
&lt;p&gt;
I am happy to release a module for &lt;a href=&quot;http://kohanaframework.org/&quot;&gt;Kohana v3&lt;/a&gt; that uses the &lt;a href=&quot;http://code.google.com/p/phamlp/&quot;&gt;PHamlP library&lt;/a&gt; to offer Haml/Sass support for Kohana.&lt;/p&gt;
&lt;p&gt;
Right now the module only supports Haml but I will be adding Sass support in the very near future.&lt;/p&gt;
&lt;p&gt;
Head over to GitHub to &lt;a href=&quot;http://github.com/fredwu/kohana-phamlp&quot;&gt;check out the source code&lt;/a&gt;. :)&lt;/p&gt;
]]&gt;</content>
    <published>2010-06-27T14:42:52.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/php-releasing-phamlp-module-for-kohana-use-haml-and-sass-with-kohana-3-0"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/php-releasing-phamlp-module-for-kohana-use-haml-and-sass-with-kohana-3-0</id>
    <title>[PHP] Releasing PHamlP Module for Kohana, Use Haml and Sass with Kohana 3.0!</title>
    <updated>2010-06-27T14:42:52.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
The &lt;a href=&quot;http://rubyonrails.org/&quot;&gt;Rails&lt;/a&gt; deployment flow is really smooth thanks to the powerful (and easy to use) &lt;a href=&quot;http://www.capify.org/&quot;&gt;Capistrano&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Capistrano not only works with Rails and other Ruby code bases, but also code bases in any programming languages, such as &lt;a href=&quot;http://php.net/&quot;&gt;PHP&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;
Here is an overview of what I did to get one of our PHP production sites up and running with Capistrano.&lt;/p&gt;
&lt;p&gt;
If you don’t already have &lt;a href=&quot;http://www.ruby-lang.org/en/&quot;&gt;Ruby&lt;/a&gt; and &lt;a href=&quot;http://rubygems.org/&quot;&gt;Rubygems&lt;/a&gt; installed, install them!&lt;/p&gt;
&lt;p&gt;
After you got &lt;code class=&quot;inline&quot;&gt;ruby&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;rubygems&lt;/code&gt;, install Capistrano and its related gems -&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;gem install capistrano-ext railsless-deploy&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Now, navigate to your application’s directory and run &lt;code class=&quot;inline&quot;&gt;capify .&lt;/code&gt;, this will generate some necessary files for Capistrano to recognise your app.&lt;/p&gt;
&lt;p&gt;
Open up the generated &lt;code class=&quot;inline&quot;&gt;Capfile&lt;/code&gt; file and replace the content of the file with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;require &apos;rubygems&apos;
require &apos;railsless-deploy&apos;
load &apos;config/deploy&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Okay, now we need to set up the actual deployment recipe in the &lt;code class=&quot;inline&quot;&gt;config/deploy.rb&lt;/code&gt; file. But before doing so, we need to set up our deployment server with proper user and deployment permission.&lt;/p&gt;
&lt;p&gt;
We chose to set up a new user called &lt;code class=&quot;inline&quot;&gt;deploy&lt;/code&gt; specifically for deployment purpose, for example -&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;useradd deploy&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Then of course, we need the SSH keys for this deployment user, so switch to this user (&lt;code class=&quot;inline&quot;&gt;su deploy&lt;/code&gt;) and -&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;ssh-keygen&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
We use &lt;a href=&quot;http://github.com/&quot;&gt;GitHub&lt;/a&gt;, so simply copy the content of the public key (&lt;code class=&quot;inline&quot;&gt;~/.ssh/id_rsa.pub&lt;/code&gt;) and add it to the deploy keys section of the Github repository.&lt;/p&gt;
&lt;p&gt;
Don’t forget to also add GitHub to the &lt;code class=&quot;inline&quot;&gt;.ssh/known_hosts&lt;/code&gt; file, you can do this by manually cloning your repository on the deployment server.&lt;/p&gt;
&lt;p&gt;
Great! Now let’s do the last step - create the actual deployment recipe!&lt;/p&gt;
&lt;p&gt;
Open up &lt;code class=&quot;inline&quot;&gt;config/deploy.rb&lt;/code&gt; file and you will see some default deployment tasks. Everyone has different needs, so I’m going to paste our &lt;code class=&quot;inline&quot;&gt;deploy.rb&lt;/code&gt; file (masked with added comments) for your reference.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;set :user, &quot;deploy&quot;
set :application, &quot;YOUR_APPLICATION_NAME&quot;
set :domain, &quot;YOUR_APPLICATION_DOMAIN_NAME&quot;
set :repository, &quot;THE_ADDRESS_OF_THE_APPLICATION_REPOSITORY&quot;
set :deploy_to, &quot;/var/www/#{domain}&quot;
set :shared_path, &quot;#{deploy_to}/shared&quot;
set :use_sudo, false

set :scm, :git
set :branch, &apos;master&apos;
set :deploy_via, :remote_cache

role :web, &quot;ADDRESS_OF_YOUR_WEB_SERVER&quot;
role :app, &quot;ADDRESS_OF_YOUR_APP_SERVER&quot; # this can be the same as the web server
role :db, &quot;ADDRESS_OF_YOUR_DB_SERVER&quot;, :primary =&gt; true # this can be the same as the web server

namespace :deploy do
  task :start do ; end
  task :stop do ; end
  task :restart, :roles =&gt; :app, :except =&gt; { :no_release =&gt; true } do
  run &quot;#{try_sudo} /etc/init.d/lsws reload&quot; # we use LiteSpeed Web Server
  end
end

# The task below serves the purpose of creating symlinks for asset files.
# Large asset files like user uploaded contents and images should not be checked into the repository anyway, so you should move them to a shared location.

task :create_symlinks, :roles =&gt; :web do
  run &quot;ln -s #{shared_path}/uploads #{current_release}/uploads&quot;
  run &quot;ln -s #{shared_path}/zb #{current_release}/zb&quot;
end

# Let&apos;s run the task immediately after the deployment is finalised.

after &quot;deploy:finalize_update&quot;, :create_symlinks&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
That’s it! Now you should be able to deploy your application by -&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;cap deploy:setup
cap deploy&lt;/code&gt;&lt;/pre&gt;
]]&gt;</content>
    <published>2010-06-21T04:45:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/deploy-php-websites-using-capistrano-and-git"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/deploy-php-websites-using-capistrano-and-git</id>
    <title>Deploy PHP Websites Using Capistrano (and Git)</title>
    <updated>2010-06-21T04:45:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
&lt;a href=&quot;http://github.com/fredwu/jquery-endless-scroll&quot;&gt;jQuery Endless Scroll&lt;/a&gt; has now been updated to work with any DOM elements, not just &lt;code class=&quot;inline&quot;&gt;$(document)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://www.beyondcoding.com/2009/01/15/release-jquery-plugin-endless-scroll/&quot;&gt;Click here&lt;/a&gt; for the project page (with usage examples).&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;http://www.beyondcoding.com/demos/endless-scroll/&quot;&gt;Click here&lt;/a&gt; for a simple demo.&lt;/p&gt;
&lt;p&gt;
Enjoy!&lt;/p&gt;
]]&gt;</content>
    <published>2010-06-18T06:48:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/jquery-endless-scroll-updated-now-works-with-any-dom-elements"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/jquery-endless-scroll-updated-now-works-with-any-dom-elements</id>
    <title>[jQuery] Endless Scroll Updated, Now Works with Any DOM Elements</title>
    <updated>2010-06-18T06:48:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Sometimes we would want to throttle certain application actions. For instance, a user should only be allowed to send x amount of emails to other members of the site in order to reduce the spam risk.&lt;/p&gt;
&lt;p&gt;
Since this is a quite common task, I thought I might as well make it as a Rails plugin for better code reusability.&lt;/p&gt;
&lt;p&gt;
I am now releasing &lt;a href=&quot;http://github.com/fredwu/action_throttler&quot;&gt;Action Throttler&lt;/a&gt;, an easy to use Rails plugin to quickly throttle application actions based on configurable duration and limit. &lt;a href=&quot;http://github.com/fredwu/action_throttler&quot;&gt;Go check out the code now!&lt;/a&gt; :)&lt;/p&gt;
&lt;p&gt;
There is currently no tests for the plugin but I will be adding rspec specs to it soon.&lt;/p&gt;
]]&gt;</content>
    <published>2010-06-03T03:21:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/rails-releasing-action-throttler-a-rails-plugin-for-throttling-actions"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/rails-releasing-action-throttler-a-rails-plugin-for-throttling-actions</id>
    <title>[Rails] Releasing Action Throttler, A Rails Plugin for Throttling Actions</title>
    <updated>2010-06-03T03:21:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Yesterday we have launched the new design of &lt;a href=&quot;http://creattica.com/&quot;&gt;Creattica&lt;/a&gt;. It runs well during the testing and staging phase, unfortunately the server quickly became overloaded and unresponsive after the relaunch was &lt;a href=&quot;http://psd.tutsplus.com/articles/contests/win-1000-by-submitting-work-to-creattica/&quot;&gt;made public&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
The site was running fine with its old design, but because the new design has many added features such as search filters, followers and more, system resources were very quickly drained.&lt;/p&gt;
&lt;p&gt;
After some optimisation of the database (which itself has increased the site performance by up to 300%+), queries as well as upgrading &lt;a href=&quot;http://www.rubyenterpriseedition.com/&quot;&gt;Ruby Enterprise Edition&lt;/a&gt; from 1.8.6 to 1.8.7 and switching from using Apache + &lt;a href=&quot;http://www.modrails.com/&quot;&gt;Passenger&lt;/a&gt; to Nginx + Passenger, the site runs so much smoother.&lt;/p&gt;
&lt;p&gt;
Don’t believe me? Take a look at these screenshots below.&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_l32l0mXqZJ1qalr27.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_l32l0znIlE1qalr27.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Switching from REE 1.8.7 to Ruby 1.9.1 did not yield any significant result though.&lt;/p&gt;
]]&gt;</content>
    <published>2010-05-27T08:35:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/a-whole-world-of-difference-phusion-passenger-apache-to-nginx-ruby-enterprise-edition-1-8-6-to-1-8-7"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/a-whole-world-of-difference-phusion-passenger-apache-to-nginx-ruby-enterprise-edition-1-8-6-to-1-8-7</id>
    <title>A Whole World of Difference: Phusion Passenger Apache to Nginx, Ruby Enterprise Edition 1.8.6 to 1.8.7</title>
    <updated>2010-05-27T08:35:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_l32djaiUtl1qb7ot5o1_1280.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
Busy at work…&lt;/p&gt;
]]&gt;</content>
    <published>2010-05-27T05:49:58.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/busy-at-work"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/busy-at-work</id>
    <title>busy at work</title>
    <updated>2010-05-27T05:49:58.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
A simple &lt;a href=&quot;http://www.google.com/search?q=rails+app_config&quot;&gt;Google search&lt;/a&gt; will reveal that there are a number of different App_Config plugins for Rails. After comparing them side by side, I have decided to use &lt;a href=&quot;http://github.com/cjbottaro/app_config&quot;&gt;the one by Christopher J. Bottaro&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
It features:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
simple YAML config files  &lt;/li&gt;
  &lt;li&gt;
config files support ERB  &lt;/li&gt;
  &lt;li&gt;
config files support inheritance  &lt;/li&gt;
  &lt;li&gt;
access config information via convenient object member notation  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
I just fixed a bug last night (which was pulled in to the main repository) where it could throw errors when used as a Rails plugin (i.e. via &lt;code class=&quot;inline&quot;&gt;rails plugin install&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;
Make sure you go &lt;a href=&quot;http://github.com/cjbottaro/app_config&quot;&gt;read the instructions&lt;/a&gt; on how to use this handy plugin.&lt;/p&gt;
]]&gt;</content>
    <published>2010-05-25T23:50:46.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/rails-use-app-config-for-your-application-specific-configuration"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/rails-use-app-config-for-your-application-specific-configuration</id>
    <title>[Rails] Use App_Config For Your Application Specific Configuration</title>
    <updated>2010-05-25T23:50:46.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
So you were wondering why some of your model attributes weren’t updating properly? Well, it is perhaps because the db schema has changed but the changed schema has not been passed onto ActiveRecord, as is often the case in DB migration.&lt;/p&gt;
&lt;p&gt;
Taken from the ActiveRecord documentation:&lt;/p&gt;
&lt;p&gt;
&lt;em&gt;Resets all the cached information about columns, which will cause them to be reloaded on the next request.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
&lt;em&gt;The most common usage pattern for this method is probably in a migration, when just after creating a table you want to populate it with some default values, eg:&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;class CreateJobLevels &lt; ActiveRecord::Migration
  def self.up
    create_table :job_levels do |t|
      t.integer :id
      t.string :name

      t.timestamps
    end

    JobLevel.reset_column_information
    %w{assistant executive manager director}.each do |type|
      JobLevel.create(:name =&gt; type)
    end
  end

  def self.down
    drop_table :job_levels
  end
end&lt;/code&gt;&lt;/pre&gt;
]]&gt;</content>
    <published>2010-05-25T10:58:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/rails-tip-model-attributes-not-updating-reset-column-information-to-the-rescue"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/rails-tip-model-attributes-not-updating-reset-column-information-to-the-rescue</id>
    <title>[Rails Tip] Model Attributes Not Updating? `reset_column_information` To the Rescue!</title>
    <updated>2010-05-25T10:58:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
When you are getting an HTML string from an external source (e.g. from an AJAX get result) and you want to rip out a certain part of the HTML source, you need to make sure that the ‘certain part’ is not at the top level of the HTML source.&lt;/p&gt;
&lt;p&gt;
For example, we have the following HTML string:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Hello
World&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
If we want to get the first &lt;code class=&quot;inline&quot;&gt;paragraph&lt;/code&gt; element by using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;js language-js&quot;&gt;// data is the HTML source
$(&apos;span#first&apos;, data)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
The above code won’t work, because the &lt;code class=&quot;inline&quot;&gt;p&lt;/code&gt; tags are at the top level. Instead, we can simply wrap the HTML source with a &lt;code class=&quot;inline&quot;&gt;div&lt;/code&gt; tag and that’ll do it. :)&lt;/p&gt;
]]&gt;</content>
    <published>2010-04-28T01:42:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/jquery-tip-traverse-parse-html-string"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/jquery-tip-traverse-parse-html-string</id>
    <title>[jQuery Tip] Traverse/Parse HTML String</title>
    <updated>2010-04-28T01:42:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
It was confirmed that DataMapper &lt;a href=&quot;http://datamapper.lighthouseapp.com/projects/20609/tickets/1249&quot;&gt;is incorrectly setting table names in SQL JOINs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
So for instance, the following code would generate an SQL error:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;type.jobs.all(:&quot;country.name&quot;.like =&gt; &quot;%#{params[:location]}%&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
There is a workaround, however the workaround requires manual looping of the dataset thus produces N+1 queries.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;type.jobs.all.reject do |job|
! job.country.name.downcase.include?(params[:location].downcase)
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
But at least it works. ;)&lt;/p&gt;
]]&gt;</content>
    <published>2010-04-26T23:35:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/rails-tip-datamapper-m-m-association-bug-and-workaround"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/rails-tip-datamapper-m-m-association-bug-and-workaround</id>
    <title>[Rails Tip] DataMapper M:M Association Bug and Workaround</title>
    <updated>2010-04-26T23:35:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
It has been &lt;a href=&quot;http://datamapper.lighthouseapp.com/projects/20609/tickets/1245-inconsistent-timestamps-behaviour-when-hard-setting-the-aton-values&quot;&gt;confirmed&lt;/a&gt; by &lt;a href=&quot;http://datamapper.org/&quot;&gt;DataMapper&lt;/a&gt;’s core developer &lt;a href=&quot;http://github.com/snusnu&quot;&gt;Martin Gamsjaeger (snusnu)&lt;/a&gt; that it is a bug.&lt;/p&gt;
&lt;p&gt;
In short, &lt;code class=&quot;inline&quot;&gt;created_[at|on]&lt;/code&gt; can be manually overridden, but &lt;code class=&quot;inline&quot;&gt;updated_[at|on]&lt;/code&gt; cannot.&lt;/p&gt;
&lt;p&gt;
The workaround is simple, do it in two steps, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;job = Job.create(
  title: &quot;Web Developer&quot;,
  created_at: Time.now - 2
)
job.update!(updated_at: Time.now - 1)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Please check the &lt;a href=&quot;http://datamapper.lighthouseapp.com/projects/20609/tickets/1245-inconsistent-timestamps-behaviour-when-hard-setting-the-aton-values&quot;&gt;bug ticket&lt;/a&gt; to see when it’s getting fixed.&lt;/p&gt;
]]&gt;</content>
    <published>2010-04-19T08:22:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/rails-tip-datamapper-timestamps-bug-and-workaround"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/rails-tip-datamapper-timestamps-bug-and-workaround</id>
    <title>[Rails Tip] DataMapper Timestamps Bug and Workaround</title>
    <updated>2010-04-19T08:22:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
If you are using &lt;code class=&quot;inline&quot;&gt;rake spec&lt;/code&gt; to run the specs. Try using &lt;code class=&quot;inline&quot;&gt;spec spec&lt;/code&gt; instead! It avoids doing some preliminary tasks and therefore is quicker to execute.&lt;/p&gt;
&lt;p&gt;
You can verify the difference using Unix’s &lt;code class=&quot;inline&quot;&gt;time&lt;/code&gt; command, i.e. &lt;code class=&quot;inline&quot;&gt;time spec spec&lt;/code&gt; and &lt;code class=&quot;inline&quot;&gt;time rake spec&lt;/code&gt;.&lt;/p&gt;
]]&gt;</content>
    <published>2010-04-09T04:03:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/rails-tip-run-specs-faster"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/rails-tip-run-specs-faster</id>
    <title>[Rails Tip] Run Specs Faster!</title>
    <updated>2010-04-09T04:03:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
If you don’t already know, ’&lt;a href=&quot;http://github.com/collectiveidea/delayed_job&quot;&gt;delayed_job&lt;/a&gt;’ is a database based asynchronously priority queue system extracted from &lt;a href=&quot;http://www.shopify.com/&quot;&gt;Shopify&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
I was seeking for a lightweight, easy to use solution for queueing tasks, ‘delayed&lt;em&gt;job’ fits the bill _almost&lt;/em&gt; perfectly. ;)&lt;/p&gt;
&lt;p&gt;
One thing we needed to do for a Rails 2 project at &lt;a href=&quot;http://envato.com/&quot;&gt;Envato&lt;/a&gt;, was to queue the tasks and to run them one after another, at a set interval. ‘delayed_job’ unfortunately doesn’t come with this feature.&lt;/p&gt;
&lt;p&gt;
So, instead of searching for more complicated solutions or reinventing the wheels, I&lt;a href=&quot;http://github.com/fredwu/delayed_job&quot;&gt;made a couple of commits to my fork&lt;/a&gt; to add this functionality as well as to fix some rails 3 compatibility issues.&lt;/p&gt;
&lt;p&gt;
Now you can simply add the following line to your &lt;code class=&quot;inline&quot;&gt;config/initializers/delayed_job_config.rb&lt;/code&gt; file to have the tasks run at 1 minute interval.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;Delayed::Worker.run_interval = 60&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
A pull request has been issued, hopefully it’ll come as a standard feature soon. :)&lt;/p&gt;
]]&gt;</content>
    <published>2010-03-25T11:53:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/rails-run-queued-tasks-using-delayed-job-now-with-intervals"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/rails-run-queued-tasks-using-delayed-job-now-with-intervals</id>
    <title>[Rails] Run Queued Tasks Using &apos;delayed_job&apos;, Now With Intervals!</title>
    <updated>2010-03-25T11:53:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_kzf65svXDW1qb7ot5o1_1280.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
I need Textmate 2 (or a 3D monitor/projector) to come out sooner, otherwise there will be a day where I will just be totally lost in dozens of windows.&lt;/p&gt;
]]&gt;</content>
    <published>2010-03-17T09:25:04.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/i-need-textmate-2-or-a-3d-monitorprojector-to"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/i-need-textmate-2-or-a-3d-monitorprojector-to</id>
    <title>i need textmate 2 or a 3d monitorprojector to</title>
    <updated>2010-03-17T09:25:04.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Rails 3 has recently &lt;a href=&quot;http://github.com/rails/rails/commit/f81c6bc0404ba2a03eed0ec6c08bbac45661305f&quot;&gt;renamed &lt;code class=&quot;inline&quot;&gt;key&lt;/code&gt; method to &lt;code class=&quot;inline&quot;&gt;to_key&lt;/code&gt;&lt;/a&gt;, which consequently rendered &lt;a href=&quot;http://github.com/jnunemaker/mongomapper&quot;&gt;MongoMapper&lt;/a&gt; unusable.&lt;/p&gt;
&lt;p&gt;
You might see an error that looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ActionView::Template::Error (undefined method `to_key&apos; for #&lt;Post:0x000001066e4020&gt;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
After some twiddling I’ve &lt;a href=&quot;http://github.com/fredwu/mongomapper/commit/6e5e531312ef98c30e33286a1f80dd000468ed07&quot;&gt;submitted a patch&lt;/a&gt; to address this issue. Feel free to pull my fork until the fix makes its way into &lt;a href=&quot;http://github.com/jnunemaker/mongomapper&quot;&gt;John Nunemaker’s main repository&lt;/a&gt;.&lt;/p&gt;
]]&gt;</content>
    <published>2010-03-17T08:54:34.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/rails-mongomapper-on-rails-3-master-undefined-to-key-method"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/rails-mongomapper-on-rails-3-master-undefined-to-key-method</id>
    <title>[Rails] MongoMapper on Rails 3 Master, Undefined &apos;to_key&apos; Method</title>
    <updated>2010-03-17T08:54:34.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Initially developed by Yehuda Katz, the &lt;a href=&quot;http://yehudakatz.com/2008/05/19/textmate-gem/&quot;&gt;Textmate gem&lt;/a&gt; is an extremely handy tool for installing and managing your Textmate bundle files.&lt;/p&gt;
&lt;p&gt;
The tool works very well, for the most part. But due to the way the GitHub search query was built, the tool failed at finding Textmate bundles that do not honour the ’-tmbundle’ suffix in the name of their repositories.&lt;/p&gt;
&lt;p&gt;
I was waiting for GitHub to fix their API bug which I &lt;a href=&quot;/blog/2010-03-09-advanced-search-query-on-github/&quot;&gt;reported here&lt;/a&gt;, but today I discovered a workaround that solves this issue.&lt;/p&gt;
&lt;p&gt;
So, here, I present you with the improved code &lt;a href=&quot;http://github.com/fredwu/textmate&quot;&gt;on my fork&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Update: The changes are now merged back to Ddollar’s repo, so you may install the Textmate gem by:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;gem install textmate --source git://github.com/ddollar/textmate.git&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Enjoy! :)&lt;/p&gt;
]]&gt;</content>
    <published>2010-03-14T10:46:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/use-the-textmate-command-to-quickly-install-bundle-files"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/use-the-textmate-command-to-quickly-install-bundle-files</id>
    <title>Use the &apos;textmate&apos; Command to Quickly Install Bundle Files</title>
    <updated>2010-03-14T10:46:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_kz941b1bHr1qb7ot5o1_500.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
I hate it when this happens and I have a dozen of Textmate and Terminal windows open…&lt;/p&gt;
]]&gt;</content>
    <published>2010-03-14T02:53:35.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/i-hate-it-when-this-happens-and-i-have-a-dozen-of"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/i-hate-it-when-this-happens-and-i-have-a-dozen-of</id>
    <title>i hate it when this happens and i have a dozen of</title>
    <updated>2010-03-14T02:53:35.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
If you are a Rails developers, chances are you have heard of, or are using either &lt;a href=&quot;http://github.com/plataformatec/devise&quot;&gt;Devise&lt;/a&gt; or &lt;a href=&quot;http://haml-lang.com/&quot;&gt;HAML&lt;/a&gt; in your projects.&lt;/p&gt;
&lt;p&gt;
And if you’re like me who uses both, then surely you’d wish these two worked together, i.e. generate devise views in HAML.&lt;/p&gt;
&lt;p&gt;
Today is your lucky day! I’ve just &lt;a href=&quot;http://github.com/fredwu/devise/commit/94513303d345c63b267fc666ffe03b71261e2199&quot;&gt;committed some changes&lt;/a&gt; that enable you to do so! The changes are already merged back to the primary repository.&lt;/p&gt;
&lt;p&gt;
In order to generate HAML views instead of ERb views, simply do:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell language-shell&quot;&gt;rails g devise_views -t=haml&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
You can use the master branch of Devise, i.e. in your &lt;code class=&quot;inline&quot;&gt;Gemfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;gem &quot;devise&quot;, :git =&gt; &quot;git://github.com/plataformatec/devise.git&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
You will also need &lt;a href=&quot;http://haml-lang.com/download.html&quot;&gt;the edge version of HAML&lt;/a&gt;, as the stable versions do not parse ruby code correctly.&lt;/p&gt;
]]&gt;</content>
    <published>2010-03-13T14:40:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/rails-use-haml-templates-with-devise"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/rails-use-haml-templates-with-devise</id>
    <title>[Rails] Use HAML templates with Devise</title>
    <updated>2010-03-13T14:40:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_kz0ggnkBa21qb7ot5o1_540.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
First Photoshop, now Textmate, what’s next?&lt;/p&gt;
]]&gt;</content>
    <published>2010-03-09T10:43:35.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/first-photoshop-now-textmate-whats-next"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/first-photoshop-now-textmate-whats-next</id>
    <title>first photoshop now textmate whats next</title>
    <updated>2010-03-09T10:43:35.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Did you know, you can perform advanced search queries on &lt;a href=&quot;http://github.com/&quot;&gt;GitHub&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;
For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ruby AND (textmate OR tmbundle)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
The above query will search everything containing &lt;code class=&quot;inline&quot;&gt;ruby&lt;/code&gt;, as well as either &lt;code class=&quot;inline&quot;&gt;textmate&lt;/code&gt; or &lt;code class=&quot;inline&quot;&gt;tmbundle&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
Unfortunately at this stage the &lt;a href=&quot;http://develop.github.com/&quot;&gt;GitHub API&lt;/a&gt; does not support it. I’ve opened a &lt;a href=&quot;http://support.github.com/discussions/feature-requests/740-api-query-language-support-for-and-and-or&quot;&gt;ticket&lt;/a&gt; here.&lt;/p&gt;
]]&gt;</content>
    <published>2010-03-09T08:54:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/advanced-search-query-on-github"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/advanced-search-query-on-github</id>
    <title>Advanced Search Query on GitHub</title>
    <updated>2010-03-09T08:54:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
Web applications normally have many forms. Building forms is always a mind-boggling task because it involves repetition and chaos.&lt;/p&gt;
&lt;p&gt;
A shortcut is to use a form builder / &lt;a href=&quot;http://en.wikipedia.org/wiki/Domain-specific_language&quot;&gt;DSL&lt;/a&gt;, such as &lt;a href=&quot;http://github.com/justinfrench/formtastic&quot;&gt;Formtastic&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
As I am using Rails 3, and the Rails 3 port of Formtastic isn’t complete yet, I thought I’d just use the plain vanilla Rails built-in form helper.&lt;/p&gt;
&lt;p&gt;
First of all, I am using &lt;a href=&quot;http://haml-lang.com/&quot;&gt;Haml&lt;/a&gt; instead of ERb. Already, I got the out-of-box clean looking Haml markup.&lt;/p&gt;
&lt;p&gt;
Some of you might not be aware of the fact that Rails’ built-in form helper already does i18n support.&lt;/p&gt;
&lt;p&gt;
If you have the following form:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;ruby language-ruby&quot;&gt;-form_for(@post) do |f|
  =f.label :title
  =f.text_field :title
  =f.label :body
  =f.text_area :body
  =f.submit&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
You can simply translate the labels as such in &lt;code class=&quot;inline&quot;&gt;config/locales/en.yml&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;yaml language-yaml&quot;&gt;helpers:
  label:
    post:
      title: &apos;Post Title&apos;
      body: &apos;Post Content&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Better yet, I am using &lt;a href=&quot;http://r18n.rubyforge.org/&quot;&gt;r18n&lt;/a&gt; instead of i18n, so I can instead translate them in &lt;code class=&quot;inline&quot;&gt;app/i18n/en.yml&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;yaml language-yaml&quot;&gt;helpers:
  label:
    post:
      title: Post Title
      body: Post Content&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
Alternatively, instead of placing the translation strings under &lt;code class=&quot;inline&quot;&gt;helpers.label&lt;/code&gt;, you may place them directly under your models, i.e. &lt;code class=&quot;inline&quot;&gt;activemodel.attribute&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
If you’re using the &lt;a href=&quot;http://github.com/rails/rails/tree/2-3-stable&quot;&gt;Rails 2.3 stable branch&lt;/a&gt; from Github, you can also use the built-in i18n support. Although instead of &lt;code class=&quot;inline&quot;&gt;helpers.label&lt;/code&gt;, you use &lt;code class=&quot;inline&quot;&gt;views.labels&lt;/code&gt;, as seen in &lt;a href=&quot;http://github.com/rails/rails/commit/bef968d37942bfb2b7a59fca0e4451e096197c0a&quot;&gt;this commit&lt;/a&gt;.&lt;/p&gt;
]]&gt;</content>
    <published>2010-03-08T05:41:00.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/rails-tip-making-i18n-forms-the-easy-way"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/rails-tip-making-i18n-forms-the-easy-way</id>
    <title>[Rails Tip] Making i18n Forms, the Easy Way</title>
    <updated>2010-03-08T05:41:00.000000Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;![CDATA[&lt;p&gt;
  &lt;img src=&quot;https://cdn.persumi.com/uploads/images/posts/1ee22517-8bfc-676a-b1f2-ce61dc92750f/imported/img/posts/old/tumblr_kyu21vVFBR1qb7ot5o1_400.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
This is from an email newsletter I received in my Gmail inbox from &lt;a href=&quot;http://www.macheist.com/&quot;&gt;Macheist&lt;/a&gt;. Note to all the designers out there, please don’t use fancy CSS, not even some of the basic ones, because they don’t work in email clients! &lt;a href=&quot;http://www.campaignmonitor.com/css/&quot;&gt;Here’s a great table of contents&lt;/a&gt; on what you can and cannot use for different email clients.&lt;/p&gt;
]]&gt;</content>
    <published>2010-03-05T23:46:43.000000Z</published>
    <link href="http://persumi.com/u/fredwu/tech/e/blog/p/this-is-from-an-email-newsletter-i-received-in-my"/>
    <author>
      <name>Fred Wu</name>
      <email>ifredwu@gmail.com</email>
      <uri>http://persumi.com/u/fredwu</uri>
    </author>
    <id>http://persumi.com/u/fredwu/tech/e/blog/p/this-is-from-an-email-newsletter-i-received-in-my</id>
    <title>this is from an email newsletter i received in my</title>
    <updated>2010-03-05T23:46:43.000000Z</updated>
  </entry>
</feed>