F

Fred Wu (@fredwu)

CTO by day, freelancing software consultant by night. Founder of Persumi.

Quick comparisons of Viture Pro and Xreal Air 2 Pro

for a while. I ordered the Viture Pro on the 23rd May, it shipped on the 28th May, and finally got delivered on the 9th June. I’m in Australia. Packaging The unboxing experience has been stellar, the packaging is one level above Xreal’s. Case 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. Build Quality I would say on par, both are very well built. Styling 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. Wearing Comfort 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. 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. Screen Brightness 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). Also, Viture Pro has OSD to display brightness and audio levels when you adjust them, Xreal doesn’t. Sound Quality Disappointingly, to my ear, the Xreal Air 2 Pro sounds better, it is slightly louder, and has significantly better clarity. Image Quality 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. Electrochromic Screens 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. Screen Edge Clarity 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. 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. 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. Myopia Adjustments 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. 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. Verdict 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. I’m hoping both of these issues will be fixed soon via firmware updates. P.S. Tested both glasses on both macOS (Macbook Pro) and Samsung Dex (S24 Ultra). Update a day later So last night I used the Viture Pro in bed to test two things: comfort and myopia dials. Myopia Adjustments 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. In Bed Comfort 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. Audio Control 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. Colour Tint 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). 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. The colour shift is way more pronounced and is very noticeable in real life. 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. ]]>

2024/6/9
articleCard.readMore

Three Versions of The Three Body Problems, Thoughts from A Native Mandarin Speaker

The Martian (loved the movie first, then went back to reading the novel) and Project Hail Mary. Therefore, since discovering Netflix’s 3 Body Problem 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. 三体, would translate to simply Three Body. However, the English translation of the book was published as The Three-Body Problem, and the Netflix adaptation 3 Body Problem. Three Body, Three Body II: The Dark Forest and Three Body III: Death’s End, in audio book format, totalling a whopping 85+ hours. here on Youtube by one of the official publishers. 9 | 9 | 10 | 9 | N/A | N/A | N/A | N/A | Tencent | 9 | 9 | 10 | 6 | 10 | 9 | 7 | 5 | Netflix | 7 | 7 | 8 | 7 | 7 | 7 | 6 | 7 9 | A very captivating story and with a world building that’s mostly grounded to reality. | Tencent | 9 | 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 | 7 | 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. 10 | The believable and multi-faceted characters is definitely a highlight of the series, especially from the lead characters of each book. | Tencent | 10 | 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 | 8 | 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. 9 | Like the main characters, each minor character comes with a believable background and motivation, and helps with the story and world building. | Tencent | 6 | 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 | 7 | Acting from the minor cast is fine, nothing remarkable but nothing terrible either. 10 | 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 | 7 | It’s mostly a standard sci-fi look and feel, it looks good but nothing too inspiring here. 9 | A collection of finely composed soundtracks covering a wide range of emotions. I was moved by many of them. Check them out here. | Netflix | 7 | As a fan of Ramin Djawadi and his work from Game of Thrones and Westworld, this is a fine collection of soundtracks, albeit a tad forgettable compared to the aforementioned masterpieces. 7 | 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 | 6 | 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. 5 | Another unfortunate weakness of the Tencent adaptation. Many sound bites (such as the eery music) were simply lifted from other TV shows like Dark 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 | 7 | Like the cinematography, there is nothing wrong or special here. It’s good. Extra: Languages As someone who speaks both Mandarin and English, it’s been an interesting experience. 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. 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. Final Thoughts 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. If you’ve watched the Netflix version and are curious about the source material - read the novel(s) or watch the Chinese TV adaptation. ]]>

2024/5/26
articleCard.readMore

Introducing Rizz.farm - An AI-Assisted Lead Generation Tool for Reddit

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: selling, it should focus on helping people. This is where Rizz.farm comes in. Rizz.farm Works Rizz.farm, 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. Rizz.farm 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. Rizz.farm 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, Rizz.farm is able to quickly deliver them to the inbox without fuss. Rizz.farm 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. Rizz.farm is able to help them automate and scale their lead generation and reputation building with ease. Rizz.farm to production, I started using it to promote Persumi 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. Rizz.farm 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 LAUNCH2024. Happy lead generation and growth hacking everyone! ]]>

2024/1/6
articleCard.readMore

Comparison of AI OCR Tools: Microsoft Azure AI Document Intelligence, Google Cloud Document AI, AWS Textract and Others

. 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. end-to-end intelligent document processing solution“ 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. custom data labelling and training. custom data labelling and training. this article 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. x10*9/L get recognised incorrectly despite all instances appearing similar in their appearance (i.e. very clear), and sometimes dashes won’t get recognised (e.g. 10.0 - 12.50 gets recognised as 10.0 12.50). Custom Labelling 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. Overall, Azure and Google each has its strengths and weaknesses. Let’s go through them in detail. Auto Layout On Azure, there is a “Run layout” step that would recognise all the text elements, as well as tabular data in a given document. Once “run layout” is performed, all the recognised text are highlighted in yellow, giving a quick and easy overview of the usable data elements. Google on the other hand, does not offer a similar function, therefore labelling needs to be performed with more effort, more on this later. Schema vs Schema-less 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. 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. There is a special field called “table field”, we can create tabular data using this field type. 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. 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. 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. Labelling Data 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. Microsoft Azure 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. 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. In the screenshot above, sensitive data elements are blacked out for the purpose of this blog post. Google Cloud 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”. Again, in the screenshot above, sensitive data elements are blacked out for the purpose of this blog post. 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. 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. 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. Auto-Label Accuracy 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. The results are very telling. Microsoft Azure 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. In this case, the Azure auto-label has only missed the first comment block. The screenshot above is blurred to protect the sensitive nature of it. Google Cloud Google’s result unfortunately falls short of expectation. 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. This suggests two possible things about Google’s OCR solution: 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 The neural network for language models is poor, therefore it incorrectly detected text that shouldn’t be part of any result Auto-Label Result Verification Microsoft Azure 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. Google Cloud 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. 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. But as mentioned before, Google does offer the ability to manually correct the text content, which is a big plus. Data Training Speed We have found training to be significantly faster on Azure. With about 45 documents, it takes about 30 minutes to train on Azure. 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. 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. There is also a required number of labelled elements (10 minimum, 50 recommended) before a model can be trained. Azure has no such limitations. Data Regions and Compliance 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. 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. Conclusion 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. ]]>

2023/9/17
articleCard.readMore

How I Built a Mostly Feature-Complete MVP in 3 Months Whilst Working Full-Time

an MVP - you are looking at it right now. isn’t embarrassing you’re releasing it too late, and also if the product is embarrassing, you’re not gonna make it. LiveView in production Crawler - a high performance web scraper. OPQ: One Pooled Queue - a simple, in-memory FIFO queue with back-pressure support, built for Crawler. Simple Bayes - a Naive Bayes machine learning implementation. Hey, I was doing machine learning before it was mainstream! 😆 Stemmer - an English (Porter2) stemming implementation, built for Simple Bayes. Petal Pro 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. Bulma - that was 2019. Since then Tailwind has gained a lot more traction, so I wanted an excuse to try finally give it a shot. Ecto library works wonders for Postgres. Algolia Meilisearch Typesense contributing to a community library to add the features I needed. Fly and Neon, for web and DB, respectively. CDN as well as R2 to serve asset files and audio files. Bunny 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. Neon. seconds to complete, yikes. distributed nature. After incorporating Fly Postgres in the app, all DB operations immediately became more responsive. Paired with LiveView, it feels like running the application locally. Coqui TTS. persistent volumes. I knew it wasn’t a great option, as that meant my infrastructure (other than the database) was no longer immutable. Google’s TTS. Honestly I think I would’ve been happy with any of the options, they all seem to have decent neural based TTS. PaLM 2 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. The Closing 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. Sign up for an account if you haven’t already, and leave a comment if you have any questions. Until next time! ]]>

2023/8/9
articleCard.readMore

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!

<![CDATA[]]>

2023/7/22
articleCard.readMore

Hey, welcome! If you're also building your own product I'd love to hear your stories. 😊

<![CDATA[]]>

2023/7/17
articleCard.readMore

Welcome to Persumi - A Modern Platform for Content Creation

and expressions are the enablers for showcasing people’s talents and interests. Persumi. enshittification. bring back MySpace! social personas for friends, family, work… interest personas as a gamer, traveller, photographer… functional personas for travel plan, marketing campaign… need 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. to turn long form text content into audio. And to make it easy to organically grow the platform and audience. during the short few months of developing Persumi, both Twitter and Reddit seemed to be actively imploding themselves just proved my initial hypothesis: To build a truly user-oriented platform, it needs to be built for users and content creators, rather than investors and advertisers. landing page then you probably would have noticed a few things either available now or later to help create more value for users, such as: how I managed to build Persumi in three months - the tech choices, architecture and feature prioritisation I went through to make this product a reality. sign up and try the platform yourself, the Pro plan is free during the alpha and beta testing stages. Also check out Persumi’s founder @fredwu’s profile here to see what personas and expressions look like and can achieve. How I Built a Mostly Feature-Complete MVP in 3 Months Whilst Working Full-Time, it covers the features, the tech stack, the global infrastructure and the machine learning… ]]>

2023/7/17
articleCard.readMore

Tips for Job Interviews as a Junior Software Developer

and General Assembly 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. Software is eating the world. - Marc Andreessen If you enjoyed this article, checkout my other tips articles: Tips for Becoming a Better Software Developer Tips for Writing a Good CV / Résumé ]]>

2020/9/27
articleCard.readMore

Tips for Writing a Good CV / Résumé

therefore is in a very fortunate position to still be thinking about growth and hiring. our job ads. I’d say on average I spend about 30 seconds per applicant 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. ;) CRAP Principles - 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. extremely bored, he or she does not have time to read your War and Peace. fuck it up add their branding. A PDF formatted CV ensures the correct formatting and layout always get shown to the hiring managers. CV is about the facts of your experiences, your cover letter should be about your thoughts on why the company should hire you. Focus on the value you can bring to the table. a copy of my own CV, with contact details removed. If you enjoyed this article, checkout my other tips articles: Tips for Becoming a Better Software Developer Tips for Job Interviews as a Junior Software Developer ]]>

2020/5/20
articleCard.readMore

Tips for Becoming a Better Software Developer

and Github profile if you are interested. mindset, technical and people. 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. read more here. check it out here. 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. Dunning-Kruger effect is essentially the opposite of the Imposter Syndrome, both are very common in our field. overthink it, but also don’t do without think first. When in doubt, ask more experienced people for advice and guidance. Second System Syndrome 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. potential code smell. SOLID principles, but have you heard of the CRAP principles? \ developer”. expert yet cannot explain the Ruby object model and have never heard of eigenclasses, or senior developers who cannot explain the difference between git merge and git rebase. Sure, you may not need the deep understanding in order to do your job, but it will certainly help! read more about it here. 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”. covered in my talk. 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? 10x engineer 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. my tiny Youtube channel for more tech talks I’ve done in the past. video: https://www.youtube.com/watch?v=MBczdO7RgNo If you enjoyed this article, checkout my other tips articles: Tips for Writing a Good CV / Résumé Tips for Job Interviews as a Junior Software Developer ]]>

2020/5/3
articleCard.readMore

Launching Focussist Landing Page - An Upcoming Agile Project Management Tool

. Please check it out and give me your feedback, and don’t forget to sign up. ;) ]]>

2020/3/23
articleCard.readMore

Conference Talk: Adaptable Human @ RubyConf China 2019

. It was a technical talk on how to become a better developer, if you’re interested you can check out the video recording. RubyConf China. And this time around, it was a non-technical talk on how to develop one’s career. Check out the video recording below: video: https://www.youtube.com/watch?v=MBczdO7RgNo some photos of yours truly. :) ]]>

2019/9/14
articleCard.readMore

New Blog with New Design and Gatsby.js - Loving JavaScript Again

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: Sketch 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: Yahoo!, and to invest in modern technologies to make the blog function better by adding responsive design and a proper grid system, etc. Gatsby, 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 React and has a huge collection of useful plugins. rm -rf node_modules anyone?), the truth is I started off my career doing a lot of JavaScript, my most popular open source library by GitHub star count is in JavaScript, and I have always worked as a full stack dev until recent years. starter templates it is also very easy to hit the ground running. I used gatsby-starter-netlify-cms as it supports Netlify and is fairly simple. Tumblr’s own export functionality. I ended up having to use these two: Jekyll importer for converting all Tumblr posts into Markdown tumblr-utils for downloading all images in the posts (Tumblr’s own export tool and many others only download images hosted on Tumblr) gatsby-plugin-styled-components for supporting Styled Components gatsby-remark-embed-video for embedding videos gatsby-remark-prismjs for syntax highlighting gatsby-awesome-pagination for pagination gatsby-remark-reading-time for adding reading time gatsby-plugin-feed for RSS feed gatsby-plugin-google-analytics for Google Analytics gatsby-redirect-from for page redirects (of old Tumblr pages) gatsby-plugin-sitemap for sitemaps gatsby-plugin-robots-txt for robots.txt out-of-box support for GraphQL. I came across GraphQL a while back but never had the chance to work with it. At work 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. GraphiQL the in-browser GraphQL IDE, the GraphQL experience in Gatsby is extremely simple and satisfying. StaticQuery. gatsby-awesome-pagination which requires the GraphQL queries to have variables therefore incompatible with StaticQuery. StaticQuery, I still loved the GraphQL experience in Gatsby. React Components. Bulma - a modern and popular CSS framework. Bootstrap so it’s nice to get to experience another CSS framework. And it turns out, Bulma is quite nice and easy to use too. gatsby-plugin-purgecss 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 200Kb of CSS (before minifying and gzipping) from unused rules from Bulma. all.scss), 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: @import "~bulma"; /* purgecss start ignore */ .my-own-css-rules-here { } /* purgecss end ignore */ Prism.js due to the way some of the CSS rules are defined in Bulma. .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; } } GitHub Pages 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. a whole new level and I cannot be happier to have finally jumped on the bandwagon too. Disqus and social sharing. available on GitHub here. JQuery 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”. I hope you like my new blog, and if you have a similar experience to share, I’d love to hear from you! ]]>

2019/1/20
articleCard.readMore

Coding and Learning Should Never Stop, Open Sourcing is Caring

and Simple Bayes. It was a great, really enjoyable experience and I learnt a lot about the concepts of word stemming, naive Bayes classification and of course, functional programming. Crawler. At the time GenStage was just announced 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. the way I approached learning it. 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. OPQ: One Pooled Queue. GenEvent and RateLimiter 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”. OPQ you’ll notice that the heavy lifting logic was mostly inspired (or even copy-pasted) from those examples. Rails and Slim. ElixirRetry - a neat library that was cleverly built. retry/2 and retry/3, 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. retry/2 and retry/3 could be improved, by simply combining them and making the extra argument in retry/3 an option. https://github.com/safwank/ElixirRetry/pull/12 You’ll notice that I’ve also made some improvements to the test suite while I was at it. ;) Dialyzer - some code that actually ran and passed tests functionally, failed the type checking. opened an issue and phrased it more as a question seeking validity of the issue. I then jumped on Elixir’s Slack group and asked people in the group about this issue. Ben Wilson immediately came to aid by verifying my issue and validating my suspicion of it being an issue in Elixir’s typespec documentation. a pull request was created and approved shortly after. 滴水石穿. The literal translation is “dripping water penetrates the stone”, and what it means is “constant perseverance yields success“. ]]>

2017/8/27
articleCard.readMore

Elixir and Doctest - Help Writing Better Programs, One Function At A Time

, a Naive Bayes text classifier implementation, and Stemmer, an English (Porter2) stemming implementation. a web Crawler. If you are new to Elixir, feel free to follow this project as I am actively developing it. doctest. defmodule Greeting do @doc """ ## Examples iex> hello("world") "hello world" iex> hello("dear") "hello dear" """ def hello(input) do "hello #{input}" end end defmodule GreetingTest do use ExUnit.Case doctest Greeting end proxemics, proxemics between different components of a software code base also plays a role in improving the code clarity, and ultimately the code quality. SOLID principles all the time, it is often too easy to dig deep wholes in the midst of building things. an example when I did some refactorings on Crawler. wink), building software feels like a breeze. 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. ]]>

2017/8/7
articleCard.readMore

I Accidentally Some Machine Learning - My Story of A Month of Learning Elixir

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 new, I naturally wanted to learn something a bit more different than Ruby, syntax-wise. Programming Elixir. Programming Ruby 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. Actor-based concurrency model. 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 ExUnit.DocTest. defmodule Stemmer.Step0 do @doc """ ## Examples iex> Stemmer.Step0.trim_apostrophes("'ok") "ok" iex> Stemmer.Step0.trim_apostrophes("o'k") "o'k" iex> Stemmer.Step0.trim_apostrophes("'o'k'") "o'k" """ def trim_apostrophes(word) do word |> String.replace_prefix("'", "") |> String.replace_suffix("'", "") end end defmodule Stemmer.Step0Test do use ExUnit.Case, async: true doctest Stemmer.Step0 end unit tests can be written with minimal friction and high visibility (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. :) hello world program, I thought I would dig out a code test and re-implement it in Elixir. Toy Robot 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. https://github.com/fredwu/toy-robot-elixir |>) Agent for managing states Phoenix, because most side projects fail, and when they do, how much learning can you extract out of those experiences? 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. Programming Phoenix 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. Channels as I don’t intend to build a real-time app). attempted to fix it. Ecto. One of my favourite features of Ecto is the concept of Changeset. defmodule MySecretApp.User do use MySecretApp.Web, :model schema "users" 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 |> cast(params, [:username, :email]) |> validate_required([:username, :email]) |> validate_length(:username, min: 3, max: 20) |> validate_format(:username, ~r/\A[a-zA-Z0-9_]+\z/, message: "alphanumeric and underscores only") |> validate_format(:email, ~r/@/) |> unique_constraint(:username) |> unique_constraint(:email) end def creation_changeset(struct, params \\ %{}) do struct |> changeset(params) |> password_changeset(params) end defp password_changeset(struct, params) do struct |> cast(params, [:password]) |> validate_required([:password]) |> validate_length(:password, min: 8, max: 200) |> encrypt_password end defp encrypt_password(changeset) do case changeset do %Ecto.Changeset{valid?: true, changes: %{password: password}} -> put_change(changeset, :encrypted_password, Comeonin.Bcrypt.hashpwsalt(password)) _ -> changeset end end end changset/2 function can be used when a user needs to be updated, whereas the creation_changeset/2 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. Hanami, Trailblazer and many other frameworks do: it introduces a “view model” layer. defmodule MySecretApp.UserView do use MySecretApp.Web, :view def full_name do "#{title} #{first_name} #{last_name}" end end Hex packages if you’re familiar with their ruby counterparts: credo rubocop espec rspec wallaby capybara excoveralls simplecov ex_machina factory_girl guardian devise Awesome Elixir for more community curated Elixir libraries. Bayesian inference. Naive Bayes and Random Forest. 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. Simple Bayes Simple Bayes. Additive smoothing TF-IDF Stemmer SimpleBayes.init |> SimpleBayes.train(:ruby, "I enjoyed using Rails and ActiveRecord for the most part.") |> SimpleBayes.train(:ruby, "The Ruby community is awesome.") |> SimpleBayes.train(:ruby, "There is a new framework called Hanami that's promising.") |> SimpleBayes.train(:ruby, "Please learn Ruby before you learn Rails.") |> SimpleBayes.train(:ruby, "We use Rails at work.") |> SimpleBayes.train(:elixir, "It has Phoenix which is a Rails-like framework.") |> SimpleBayes.train(:elixir, "Its author is a Rails core member, Jose Valim.") |> SimpleBayes.train(:elixir, "Phoenix and Rails are on many levels, comparable.") |> SimpleBayes.train(:elixir, "Phoenix has great performance.") |> SimpleBayes.train(:elixir, "I love Elixir.") |> SimpleBayes.train(:php, "I haven't written any PHP in years.") |> SimpleBayes.train(:php, "The PHP framework Laravel is inspired by Rails.") |> SimpleBayes.classify("I wrote some Rails code at work today.") # => [ # ruby: 0.20761437345986136, # elixir: 0.08101868169313056, # php: 0.019047884912605735 # ] Stemmer stemming. Let’s see another example: SimpleBayes.init(stem: false) |> SimpleBayes.train(:apple, "buying apple") |> SimpleBayes.train(:banana, "buy banana") |> SimpleBayes.classify("buy apple") # => [ # banana: 0.057143654737817205, # apple: 0.057143654737817205 # ] apple and banana 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: SimpleBayes.init(stem: true) |> SimpleBayes.train(:apple, "buying apple") |> SimpleBayes.train(:banana, "buy banana") |> SimpleBayes.classify("buy apple") # => [ # apple: 0.18096114003107086, # banana: 0.1505149978319906 # ] Stemmer - as of writing this is the only Porter2 stemmer available in Elixir. :) 5s to stem 29000+ words, compared to 25s with the ruby version). Ruby 3x3. :) ]]>

2016/7/24
articleCard.readMore

Developers, Being Treated Poorly? You Are Not Alone!

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. 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. 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. 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. ;) 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.” 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. 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. 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”. 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!” 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. 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. 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.” 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… 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. 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”. 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…” 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. 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?” 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. 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. 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. There you are, these were my moments. What were yours? Care to share? :) ]]>

2016/4/5
articleCard.readMore

History Text Analysis Over Spreadsheets - A Poker Player and Developer's Road to Agile Project Management

and to keep myself on the fringe of pushing the technical boundaries. measure, understand and improve our team’s agile process. DRY principle. Amaze Hands to help reduce the amount of waste I accumulate as a delivery lead. used to play a bit of online poker and one thing you do in online poker is to look at your hand history to understand the game and your opponents. in order to optimise for future gains, we need to understand what went wrong and what to improve on a case-by-case basis. LeanKit, 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. +---------------------+ | Text | the goal of Amaze Hands is to incrementally add intelligence to our agile process that matters to a particular project and its delivery team. REA Hackday 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. you have any interesting tools or techniques to help you lead projects? If so, I would love to hear about them! ]]>

2015/3/10
articleCard.readMore

Leading Snowflakes by Oren Ellenbogen - A Pragmatic Book on Software Team Leadership I Wish I Read Sooner

which I had subscribed to for a while, I discovered its curator, Oren Ellenbogen’s book - Leading Snowflakes. Leading Snowflakes. short, concise and pragmatic. To purchase only the ebook itself, Oren has kindly offered a 20% off discount, click to buy now. ]]>

2014/7/26
articleCard.readMore

Photos from JSConf AU 2014

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. Google+ Photo Album for larger versions. ]]>

2014/4/10
articleCard.readMore

On Hiring: Trial Week - Yay or Nay?

“ has made to the Hacker News homepage. I naively tweeted my dislike and now I feel obligated to share my thoughts in a more meaningful and constructive way. think carefully before committing to a given strategy. one week is not enough for a candidate to be productive and effective? Yes! And that’s why most places have a three-month probation. commitment. In my opinion, only when both parties are committed can you achieve great result. you think? :) Poll: Trial Week, Yay or Nay? ]]>

2013/10/29
articleCard.readMore

Protip: Unsync the "Index" Folder of Sublime Text 3 from Dropbox

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. 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. 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. 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. :) ]]>

2013/10/12
articleCard.readMore

Protip: Faster Ruby Tests with DatabaseCleaner and DatabaseRewinder

this blog post on tweaking your ruby GC settings. DatabaseCleaner, although historically I had never paid too much attention on the performance of its varies cleaning strategies - I’d always used truncation. the difference between DELETE and TRUNCATE, I ended up improving our test suite speed by about 30-40% simply by tweaking the cleaning strategies. 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 truncate the DB only once before the whole suite runs to ensure a clean slate DB, then we only want to use deletion on Capybara tests, everything else should just use transaction which is the fastest strategy. DatabaseRewinder which is a lightweight alternative that supports only ActiveRecord. It offers comparable performance with a much similar API. RSpec.configure do |config| config.before :suite do DatabaseRewinder.clean_all end config.after do DatabaseRewinder.clean end end parallel_tests to scale our test suite to multiple processes, even on Travis CI and Wercker. Hooray to faster tests! :) ]]>

2013/9/18
articleCard.readMore

Protip: Ruby Devs, Please Tweak Your GC Settings for Tests!

export RUBY_GC_MALLOC_LIMIT=90000000 export RUBY_FREE_MIN=200000 Travis CI, Wercker and other CI solutions, making instant speed gain for your test suite! Before After Local: 8m22s 5m52s Travis CI: 10m10s 6m0s Wercker: 8m45s 6m24s YMMV depending on your system and available RAM. ]]>

2013/9/6
articleCard.readMore

Writing Sensible Tests for Happiness

best practices for testing, rather it collects a few tips I believe would benefit those who are not yet comfortable with writing tests. sigh every time you want to modify your tests. experience, but also intuition to write sensible tests. You have to remember that not all projects and programmers are equal - take what you get, practise, and reflect on your findings. once said: look for key areas of the system that need the test coverage the most. Typically, our systems would have: and 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. describe ApprovalStakeholder do it { should belong_to(:approval) } it { should_not validate_presence_of(:approval) } end why instead of what, these tests should be replaced by tests that cover actual functionalities, for instance the reason why an ApprovalStakeholder doesn’t need an Approval to be presence should be demonstrated in the tests: shared_examples_for "non-approval specific stakeholder" 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 => user.id, :role_id => role.id ) end context "with an approval" do before { subject.approval = approval } it_behaves_like "non-approval specific stakeholder" its(:action_that_does_care_about_approval) { should be_true } end context "without an approval" do it_behaves_like "non-approval specific stakeholder" its(:action_that_does_care_about_approval) { should be_false } end end ApprovalStakeholder object are allowed to be mass assignable, which is a recipe for disaster. describe ApprovalStakeholder do it { should allow_mass_assignment_of(:user_id) } it { should allow_mass_assignment_of(:role_id) } end describe ApprovalStakeholder do it "#traveller" do stakeholder = create(:approval_stakeholder, :approval => approval, :user_id => traveller.id ) stakeholder.stub(:user).and_return(traveller) approval.stub(:stakeholders_as).and_return([stakeholder]) approval.traveller.should == traveller end it "#authoriser" do stakeholder = create(:approval_stakeholder, :approval => approval, :user_id => authoriser.id ) stakeholder.stub(:user).and_return(authoriser) approval.stub(:stakeholders_as).and_return([stakeholder]) approval.authoriser.should == authoriser end end describe ApprovalStakeholder do let(:stakeholder) do create(:approval_stakeholder, :approval => approval, :user_id => user.id ) end subject { approval } before do stakeholder.stub(:user).and_return(user) approval.stub(:stakeholders_as).and_return([stakeholder]) end describe "#traveller" do let(:user) { traveller } its(:traveller) { should == traveller } end describe "#authoriser" do let(:user) { authoriser } its(:authoriser) { should == authoriser } end end describe ApprovalStakeholder do it "references a user" do approval_stakeholder = build :approval_stakeholder, :user_id => 1 User.should_receive(:find).with(1) approval_stakeholder.user end it "references a role" do approval_stakeholder = build :approval_stakeholder, :role_id => 1 Role.should_receive(:find).with(1) approval_stakeholder.role end end describe ApprovalStakeholder do subject do build(:approval_stakeholder, :approval => approval, :user_id => user.id, :role_id => role.id, ) end its(:name) { should == "#{user.first_name} #{user.last_name}" } its(:role_name) { should == role.name } end Service-oriented architecture, so do I. Bouncer service that is responsible for safeguarding resources - ensuring read-only attributes don’t get overridden. module Services class Bouncer def self.guard(resource, options = {}) if options[:existing_resource] resource.readonly_attributes.each do |attr_name| resource.send("#{attr_name}=", options[:existing_resource].send(attr_name)) end end resource end end end 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: 'Penny', age: 28, gender: 'female') } let(:existing_resource) { BouncerDude.new(name: 'Sheldon Cooper', age: 34, gender: 'male') } subject { Services::Bouncer.guard(resource, existing_resource: existing_resource) } describe "#guard" do its(:name) { should == 'Penny' } its(:age) { should == 34 } its(:gender) { should == 'male' } end end 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(&: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| ["#{cc.code} - #{cc.name}", cc.id] }.sort end end ShowGirl for fetching and presenting data collections: 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(&: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: -> (item) { "#{item.code} - #{item.name}" } ) end end BusBoy for just serving the data, and leaving ShowGirl for only presenting the data: 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(&: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 module Profiles class TravellersController { current_user.agency_id } end end require 'spec_helper' 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: '' } } let(:params_key) { :account } let(:redirect_path) { profiles_accounts_path } it_behaves_like 'datamappify resources controller' it_behaves_like 'searchable resources controller', :name, :profile_id, :branch_id, :activated describe "permission" do context 'as a manager' do before do sign_in_as :manager end it_behaves_like 'with write access' it_behaves_like 'with read access' it_behaves_like 'with index access' end context 'as a consultant' do before do sign_in_as :consultant end it_behaves_like 'without write access' it_behaves_like 'with read access' it_behaves_like 'with index access' end end end ApiTaster - 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. describe "API" do load 'db/seeds.rb' load 'spec/api_endpoints.rb' ApiTaster::Route.map_routes ApiTaster::Route.defined_definitions.each do |route| it "api endpoint #{route[:verb]} #{route[:path]}" 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 "api endpoint #{route[:verb]} #{route[:path]}" end end resource_response = ResponseHash[ :response => { :id => Integer, :name => String, :token => String } ] get '/:version/company', {}, { :expectation => resource_response } post '/:version/companies', { :model => FactoryGirl.attributes_for(:company) }, { :expectation => resource_response } put '/:version/companies/:id', { :id => 1, :model => { :name => 'New Company' } }, { :expectation => resource_response.with(:name => 'New Company') } delete '/:version/companies/:id', { :id => 1 }, { :expectation => resource_response } Mocha so we use Konacha in our Rails app. Though Mocha with Chai is really not that different to Jasmine. #= require spec_helper describe "form toggle", -> beforeEach -> $("body").append(JST["templates/form/toggle"]) it "hides the collapsible field by default", -> $(".control-group.branch_deactivation_date").hasClass('in').should.be.false it "does not override if there is already a value", -> value = $("input#agency_deactivation_date").val() $("input#agency_activated").click() $("input#agency_deactivation_date").val().should.equal(value) #= require spec_helper #= require bootstrap-datepicker describe "form dates", -> beforeEach -> @dateFormat = 'DD/MM/YYYY' $("body").append(JST["templates/form/dates"](dateFormat: @dateFormat)) it "has a placeholder", -> $("input").attr("placeholder").should.equal(@dateFormat) it "defaults to today's date", -> $("input#empty").focus() $("input#empty").focus() $("input#empty").val().should.equal(moment().format(@dateFormat)) it "does not override if there is already a value", -> value = $("input#filled").val() $("input#filled").focus() $("input#filled").val().should.equal(value) Turnip): @ui Feature: UI Background: Given I am signed in And I go to agency consultants page And I click on "Add New Consultant" Scenario: Calendar When I click "#agency_user_start_date" And I click ".day.active" within ".datepicker" Then I should see today as part of the date field Cucumber tests”, is a double-edged sword - it’s extremely useful, but very few developers can write good, maintainable Gherkin-style acceptance tests. Feature: Session Background: Given I visit "/" And there is a user "admin" "password" Scenario: Sign in with valid credentials When I fill in "Username" with "admin" And I fill in "Password" with "password" And I click "Sign In" Then I should be on "/dashboard" Scenario: Sign in with invalid credentials When I fill in "Username" with "admin" And I fill in "Password" with "invalid_password" And I click "Sign In" Then I should not be on "/dashboard" Scenario: Sign out When I fill in "Username" with "admin" And I fill in "Password" with "password" And I click "Sign In" And I click "Sign Out" Then I should be on "/sign_in" Feature: Session Background: Given I am on the homepage And there is a user "admin" with password "password" Scenario: Sign in with valid credentials When I sign in as "admin" with password "password" Then I should be signed in Scenario: Sign in with invalid credentials When I sign in as "admin" with password "invalid_password" Then I should not be signed in Scenario: Sign out Given I am signed in as "admin" with password "password" When I sign out Then I should be signed out For Happiness! :) Do you have any tips to share? If so please feel free to add a few comments! ]]>

2013/8/26
articleCard.readMore

Gotchas in the Ruby Sequel Gem

much therefore I am definitely a newbie. However, after days and nights of frustration, endless debugging and some search-fu during the development of Datamappify, I have finally arrived at the conclusion that Sequel is a capable library, as long as you are aware of the gotchas. select“/“select_all“, or your data records will mysteriously have wrong IDs! Post.joins(:author) join without specifying the keys: Not good: Post.join(:authors) # or Post.join(Author) Better: Post.join(:authors, :id => :author_id) will give you incorrect data - the IDs of the Post records will now contain the IDs from their corresponding Author records! This is because upon a join, Sequel merges attributes from both models into a single hash. The correct version: Post.join(:authors, :id => :author_id).select(:posts __id, :posts__ title, :posts__body) # or Post.join(:authors, :id => :author_id).select_all(:posts) all“ at the end of the chain, or the chain will present data in a different format. ActiveRecord::Relation collection: Post.where(:title => 'Hello world') Post.joins(:author) Post.includes(:author) first on any of them returns an object of class Post (assuming the result collection is not empty). Post.where(:title => 'Hello world').first.class #=> Post Post.joins(:author).first.class #=> Post Post.includes(:author).first.class #=> Post Sequel::DataSet collection: Post.where(:title => 'Hello world') Post.eager(:author) Post.eager_graph(:author) first.class on them: Post.where(:title => 'Hello world').first.class #=> Post Post.eager(:author).first.class #=> Post Post.eager_graph(:author).first.class #=> Hash Hash? It turns out, if you call all at the end of chains to convert them to Arrays, then the returned collections are consistent: Post.where(:title => 'Hello world').all.first.class #=> Post Post.eager(:author).all.first.class #=> Post Post.eager_graph(:author).all.first.class #=> Post ]]>

2013/8/21
articleCard.readMore

The Future of Computing, The Future of Computer Programmers - An Interview with Yukihiro "Matz" Matsumoto

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 Engineer Type. Since I don’t read Japanese, the translation is based on Turing Book’s Chinese translation. Ito: Thank you for doing an interview with us, Matz. I have just finished reading your latest book The Future of Computing, could you perhaps talk about the future of programming and software programmers in general? Matz: Hmmm, this is difficult to answer… but thanks for reading my book! Ito: 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? Matz: 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 Moore’s law. Although, it is possible that in the next year or two quantum computers 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. Matz: It was about twenty years ago (in 1993) I invented the Ruby programming language, yet it still runs surprisingly well on modern computers. Ito: And this is covered in the last chapter from the book, right? Matz: 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. Ito: So, how does that change software development? Matz: 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 mainframe computers, web developers are naturally more familiar with the concepts of multi-core and cloud computing. Ito: After interviewing many web and mobile startups, we realised that the number of software engineers working in PaaS and cloud computing have been increasing rapidly. Matz: 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. Matz: 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. Heroku is making “owning” a thing of the past. Ramen Profitable by Y Combinator’s founder Paul Graham. “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. Ito: What about the giant corporations in Japan? Matz: 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. Ito: What makes you concerned about software development? Matz: 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. Ito: 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? Matz: Nowadays the speed of development has always been a priority from big development projects in B2B to small development projects in many startups. Yahoo! Japan even coined a term “爆速化” (explosively high speed) to indicate the importance of development speed in the ever more competitive and engaging markets. Ito: Who are those engineers who have the capability and skills to create real value? Matz: 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. Ito: Do you mean the engineers who are capable from design to implementation? Matz: 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* Ito: Do you have any advice for those who do not wish to be in a “gloomy future”? What can they do? Matz: To innovate and to create new things, I suppose. Ito: What are these “new things”? Matz: I see three types of new things. mruby was released earlier this year on Github. Ito: 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? Matz: 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. Ito: True, but all products more or less reflect their producers’ preferences, right? Matz: 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. Ruby City MATSUE“ polo shirt. Matz: 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* Ito: Many people would predict their software future based on theories, but Matz you always use “happiness”. Matz: 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. Ito: Having read The Future of Computing, 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? Matz: People see things differently - and I believe the IT industry is progressing in a manner similar to the swing motion of a pendulum clock. Dart and Go. 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. Ito: I was going to ask why it is so important to study the past, now I know. Matz: I mentioned this in the beginning - computing has not seen major changes for years. The Future of Computing! *chuckles* Ito: Thank you Matz for talking to us today! ]]>

2013/6/29
articleCard.readMore

Datamappify - A New Take on Decoupling Domain, Form and Persistence in Rails

Datamappify, please go check it out. Locomote 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. Curator, Minimapper, Edr and later on Reform. They are all wonderful gems but unfortunately none of them has everything we need. Devise, SimpleForm and CarrierWave. Datamappify. Repository pattern and Entity Aggregation pattern. README. 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. Gemfile: gem 'datamappify' app: UserAccount model that handles authentication: class UserAccount UserAccount has one and only one purpose - user account authentication. Other user behaviours would be contained in either the User entity itself or service objects. Speaking of the User entity, it looks like this: 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 => true attributes_from Contact, prefix_with: :work validates :first_name, presence: true validates :last_name, presence: true references :agency def full_name "#{first_name} #{last_name}" end end Datamappify::Entity in the User class to make it an entity. We set the :user_account accessor is so that we could attach the UserAccount object onto the entity. attribute DSL is from Virtus - we get attribute type coercion for free, awesome! attributes_from is a DSL provided by Datamappify - it essentially “mounts” all the attributes from another entity, in this case the Contact entity, which looks like this: 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 Contact are now “mounted” on User. attributes_from Contact, prefix_with: :work is equivalent to: 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 ActiveModel::Validations. references :agency is a convenient DSL provided by Datamappify. It is equivalent to: attr_accessor :agency attribute :agency_id, Integer class UserRepository include Datamappify::Repository for_entity User default_provider :ActiveRecord map_attribute :account_username, 'ActiveRecord::UserAccount#username' map_attribute :account_email, 'ActiveRecord::UserAccount#email' map_attribute :work_email, 'ActiveRecord::Contact#email' map_attribute :work_phone_number, 'ActiveRecord::Contact#phone_number' map_attribute :work_fax_number, 'ActiveRecord::Contact#fax_number' end Datamappify::Repository to make the class a repository. We specify for_entity to link the repository to an entity, and default_provider to use a specific data provider for unmapped attributes. map_attribute, in this case they are first_name, last_name and activated. Unmapped attributes are actually automatically mapped by Datamappify, so the user repository essentially does this: map_attribute :first_name, 'ActiveRecord::User#first_name' map_attribute :last_name, 'ActiveRecord::User#last_name' map_attribute :activated, 'ActiveRecord::User#activated' map_attribute is the name of the attribute from the User entity (e.g. :first_name). ActiveRecord is the data provider. ::User is the ActiveRecord model class. #first_name is the ActiveRecord attribute from the model class. User 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 :account_username and :account_email to the UserAccount ActiveRecord model we’ve seen before. And we have a bunch of contact details attributes mapped to the Contact ActiveRecord model. create_table "contacts", force: true do |t| t.string "email" t.string "phone_number" t.string "fax_number" t.integer "user_id" end create_table "user_accounts", force: true do |t| t.string "username", default: "", null: false t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" end create_table "users", force: true do |t| t.string "first_name" t.string "last_name" t.boolean "activated" t.integer "agency_id" end Contact entity is mounted on the User entity, we need a foreign key user_id in the contacts table to link them. ActiveRecord) for each attribute, we can map attributes to entirely different data providers! For instance, we could have: map_attribute :first_name, 'ActiveRecord::User#first_name' map_attribute :last_name, 'ActiveRecord::User#last_name' map_attribute :activated, 'Sequel::UserActivation#activated' activated attribute is mapped to the UserActivation Sequel model. This powerful feature would allow us to develop data providers that communicate with remote web services. :) user = UserRepository.find(1) users = UserRepository.all users = UserRepository.find(:first_name => 'Fred', :activated => true) UserRepository.save(user) UserRepository.destroy(user) read more about them here. class User include Datamappify::Entity include Datamappify::Lazy end find). project’s README. UserAccount for authentication? So how does that work? Well, here’s the piece of code in our application controller: class ApplicationController Slim templates): # our standard form layout = simple_form_for submission_target, url: submission_path, signed: true do |f| = yield(f) .form-actions .pull-left - if archivable? && resource.persisted? = link_to 'Archive', '#', method: :delete, class: 'btn-archive' = f.submit 'Save' = link_to 'Cancel', cancel_path # actual form content = render layout: 'forms/resource' 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: "Bank BSB" .span4 = f.input :activated, as: :boolean h3 Branch Details .row-fluid .span4 = f.input :branch_address_line_1, label: "Address 1" .span4 = f.input :branch_address_line_2, label: "Address 2" .span4 = f.input :branch_address_line_3, label: "Address 3" .row-fluid .span4 = f.input :branch_state, label: "State" .span4 = f.input :branch_postcode, label: "Postcode" .span4 = f.input :branch_country_code, label: "Country" do = f.country_select :branch_country_code, ['au', 'us', 'gb'], value: 'au' - f.add_signed_fields :branch_country_code .row-fluid .span4 = f.input :branch_phone_number, label: "Phone number" .span4 = f.input :branch_fax_number, label: "Fax number" 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: "Biller code" .span4 = f.input :merchant_bpay_method, collection: Merchant::BPAY_METHODS.invert, label: "BPAY method" h3 Payment Details .row-fluid .span4 = f.input :payment_number .span4 = f.input :payment_merchant, label: "Merchant description" .span4 = f.input :payment_reference_type, collection: PaymentAccount::REFERENCE_TYPES.invert, label: 'Reference type' bank_accounts, addresses, contacts, merchants and payment_accounts tables in our database. Yet, the form still remains flat-structured and submits via simple_form_for. BankAccount entity and BankAccountRepository repository: class BankAccount include Datamappify::Entity include Concerns::Archivable ACCOUNT_TYPES = { 'general' => 'General', 'trust' => 'Trust' } 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, 'ActiveRecord::Address#address_line_1' map_attribute :branch_address_line_2, 'ActiveRecord::Address#address_line_2' map_attribute :branch_address_line_3, 'ActiveRecord::Address#address_line_3' map_attribute :branch_state, 'ActiveRecord::Address#state' map_attribute :branch_postcode, 'ActiveRecord::Address#postcode' map_attribute :branch_country_code, 'ActiveRecord::Address#country_code' map_attribute :branch_phone_number, 'ActiveRecord::Contact#phone_number' map_attribute :branch_fax_number, 'ActiveRecord::Contact#fax_number' map_attribute :branch_email, 'ActiveRecord::Contact#email' map_attribute :branch_website, 'ActiveRecord::Contact#website' map_attribute :merchant_number, 'ActiveRecord::Merchant#number' map_attribute :merchant_name, 'ActiveRecord::Merchant#name' map_attribute :merchant_address, 'ActiveRecord::Merchant#address' map_attribute :merchant_biller_code, 'ActiveRecord::Merchant#biller_code' map_attribute :merchant_bpay_method, 'ActiveRecord::Merchant#bpay_method' map_attribute :payment_number, 'ActiveRecord::PaymentAccount#number' map_attribute :payment_merchant, 'ActiveRecord::PaymentAccount#merchant' map_attribute :payment_reference_type, 'ActiveRecord::PaymentAccount#reference_type' end your application’s domain layer more decoupled. Thanks for reading, and if you have any feedback for Datamappify please get in touch with me! ]]>

2013/6/27
articleCard.readMore

Would you be interested in reading such a book?

? ]]>

2013/4/29
articleCard.readMore

ActiveRecord and DB Migration Ate My Model Attributes!

reset_column_information. NoMethodError: undefined method `attack_power=' for # attack_power is a new attribute we recently added to the Ironman ActiveRecord model. Ironman # => Ironman(id: integer, created_at: datetime, updated_at: datetime) Ironman.column_names # => ["id", "created_at", "updated_at"] ActiveRecord::Base.connection.structure_dump # => "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" attack_power attribute! Ironman class, then any subsequent attribute changes to the class will not be recognised by ActiveRecord. Rake::Task['spec:something'].invoke Rake::Task['spec:something_else'].invoke Rakefile: Rake::Task['db:reset'].invoke Ironman) is called during the migration. system 'rake db:reset' ]]>

2013/2/27
articleCard.readMore

"Become A Better Developer You Can" - Video of My RubyConf China 2012 Talk

my slides are available too. view the talk at Railscasts China. ]]>

2012/12/3
articleCard.readMore

An Interview with Yukihiro "Matz" Matsumoto

this new interview with Matz, done by Engineer Type. RubyConf China. The day before the conference’s first day a bunch of us were invited to a VIP dinner where we met with Matz and got to play with a device running MRuby. And I heard that earlier on that day Matz was ‘adopted’ by a book publisher to do an interview. in Chinese), and found it to be really useful. So I translated it to English. Hope more people will like it. :) RubyConf China, a Chinese book publisher Turing Book has done an interview with Matz on his new book The Future of Computing as well as about a few topics interested to Chinese readers. The interview was conducted by _The Future of Computing_’s Chinese translator Zi Heng Zhou. Zhou: Mr. Matsumoto’s new book The Future of Computing 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 The World of Code has received high praises amongst the readers in China, so what are the differences between the last and the new book? Matz: The World of Code 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. Zhou: Speaking of the history of computing, you have touched on a few things about Moore’s law in the book? Matz: 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. Zhou: On the topic of the evolution of programming languages, Paul Graham said in The Hundred-Year Language that the main branches of the evolutionary tree pass through the languages that have the smallest, cleanest cores. 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? Matz: 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 human friendly languages and languages that have smallest, cleanest cores , 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 and be easily understandable by humans. All of a sudden, Ruby looks a lot closer to that, doesn’t it? Zhou: 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? Matz: 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. Zhou: Looking back, is there anything in Ruby you wish you did differently? Matz: 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. Zhou: 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? Matz: 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. Zhou: It is said that DHH was going to create a web framework using PHP, but eventually moved to Ruby? Matz: 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. Zhou: 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? Matz: 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: Zhou: 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? Matz: 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. Zhou: 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? Matz: 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. Zhou: 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? Matz: 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. Zhou: 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? Matz: 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. Zhou: 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. programming languages are created for us, we just passively accept them, and think to create a new programming language can also be fun, then I am sure some of them will succeed. Zhou: And GitHub is also difficult to use?) Haha, is GitHub usable in China? ( Zhou: 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? ( Zhou: 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. Zhou: 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? Matz: 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 making websites using Rails is really productive. 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. Zhou: Thank you very much Matz! Do you have anything else to say to Chinese programmers? Matz: In the book The World of Code 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. ]]>

2012/11/25
articleCard.readMore

"Become A Better Developer You Can" - Slides of My RubyConf China 2012 Talk

https://speakerdeck.com/fredwu/2012-uncut-become-a-better-developer-you-can Update: The video of my talk is up! :) ]]>

2012/11/25
articleCard.readMore

Photos from RubyConf China 2012

! So here are some photos from the conference. :) photos of yours truly on stage: photos I took mostly on the VIP dinner event the day before the conference: ]]>

2012/11/24
articleCard.readMore

SimpleCov: Test Coverage for Changed Files Only

return a group that only contains uncommitted changes. yes! After some digging around, we found the following way: # in spec_helper.rb SimpleCov.start 'rails' do add_group 'Changed' do |source_file| `git ls-files --exclude-standard --others \ && git diff --name-only \ && git diff --name-only --cached`.split("\n").detect do |filename| source_file.filename.ends_with?(filename) end end end git ls-files --exclude-standard --others for untracked files, git diff --name-only for unstaged files and git diff --name-only --cached for staged files. ]]>

2012/11/13
articleCard.readMore

Skinny Coffee Machine - A Simple State Machine with Observers

, I am now releasing one of the tools I built for the project: Skinny Coffee Machine. @coffeeMachine.power = new SkinnyCoffeeMachine default: 'off' events: turnOn: off: 'on' turnOff: on: 'off' on: turnOn: (from, to) -> "#{from.toUpperCase()} to #{to.toUpperCase()}" turnOff: (from, to) -> "#{from.toUpperCase()} to #{to.toUpperCase()}" before: turnOff: (from, to) -> "Before switching to #{to.toUpperCase()}" after: turnOn: (from, to) -> "After switching to #{to.toUpperCase()}" turnOff: (from, to) -> "After switching to #{to.toUpperCase()}" @coffeeMachine.mode = new SkinnyCoffeeMachine default: 'latte' events: next: latte: 'cappuccino' cappuccino: 'espresso' espresso: 'lungo' lungo: 'latte' last: latte: 'lungo' lungo: 'espresso' espresso: 'cappuccino' cappuccino: 'latte' @coffeeMachine.power.currentState() #=> "off" @coffeeMachine.power.switch('turnOn') @coffeeMachine.power.currentState() #=> "on" @coffeeMachine.mode.currentState() #=> "latte" @coffeeMachine.mode.change('next', 3) @coffeeMachine.mode.currentState() #=> "cappuccino" @coffeeMachine.power.observeBefore('turnOn').start 'labelA', (from, to) => "Observer A before switching to #{to.toUpperCase()}" @coffeeMachine.power.observeOn( 'turnOn').start 'labelB', (from, to) => "Observer B on switching to #{to.toUpperCase()}" @coffeeMachine.power.observeAfter( 'turnOn').start 'labelC', (from, to) => "Observer C after switching to #{to.toUpperCase()}" @coffeeMachine.power.observeBefore('turnOn').stop('labelA') source code now! :) ]]>

2012/9/6
articleCard.readMore

Brewing Node.js on OS X without Xcode

or the osx-gcc-installer. homebrew if you don’t have Xcode installed. brew install nodejs, you will get the following error: \> Error: Failed executing: make install (node.rb:28) brew install -v nodejs, you will discover this line: \> xcode-select: Error: No Xcode is selected. Use xcode-select -switch , or see the xcode-select manpage (man xcode-select) for further information. sudo xcode-select --switch /usr/bin And voila! You can now install Node.js just fine. :) ]]>

2012/8/25
articleCard.readMore

Fix OpenSSL Error on Mountain Lion (and RVM)

error message: curl http://curl.haxx.se/ca/cacert.pem -o ~/.rvm/usr/ssl/cert.pem And that’s it! Enjoy! ]]>

2012/8/6
articleCard.readMore

API Taster: Visually Test Rails Application API

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. Postman solves part of the issue: they allow us to quickly test API endpoints without messing with cURL. API Taster was born. Please check it out to see how you can use it. ]]>

2012/7/2
articleCard.readMore

PHP Devs: Have You Started Playing with More Toys?

The PHP Singularity“ and Marco Arment’s “PHP Addiction“ have started another round of heated discussion on PHP. intention of doing programming for me was different. I had no intention (or awareness) to create maintainable software , instead I simply wanted to create an end product that works. PHP Objects, Patterns, and Practice“. But even then, my knowledge was still extremely limited, and my desire at that time was to increase my PHP knowledge and become a better developer. CodeIgniter made me feel good about being a competent web developer. After all, I was doing quite a fine job churning out websites. I was not yet capable to tell the differences it has compared to PHP (frameworks). software craftsmanship and other programming languages. And I was lucky enough to be surrounded by many brilliant developers who taught me how to write better code. changed everything. From a PHP developer who just wanted to churn out websites quickly, to a developer who wanted to use software development to create value - in the sense of both business value and technical value. it’s not about Ruby , and it certainly is not about looking down on PHP. 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. double-clawed hammer, 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, many other kids have started playing with all sorts of toys, why haven’t you? :) ]]>

2012/6/30
articleCard.readMore

[Rails Tip] Render views outside of Controllers or Views

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? module Awesome class FormBuilder render method, but also ensuring the view buffer is correctly reset with flush_output_buffer. Hope that helps. :) ]]>

2012/6/20
articleCard.readMore

Agile is not a Sham

“ offends me a little bit. The post screams hey, I am a cowboy programmer, and it almost implies that if you employ processes then you are stupid. finding the right tool for the right job is common sense. Apparently not. to experiment and find what works for you, your team and your company. 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: interesting developer to manage. as a team. Having one or two ace developers aren’t going to help a lot if they can’t get along with the others. proving that techniques such as TDD work for certain projects. Keep an open mind is important, and a lot of times even necessary. To dismiss agile all together is in my opinion childish, and offensive to others who try to improve things and create more value. ]]>

2012/3/28
articleCard.readMore

Zend Framework - From Extreme Simplicity to Enterprise!

has made to the Hacker News front page. I have to agree with the author - Zend Framework is an over-engineered piece of software. CakePHP, CodeIgniter, Kohana, Yii, Symfony and obviously, Zend Framework. It was dreadful. It, felt, Java. Extreme simplicity! read Andi Gutsmans’ post on this topic. Let me quote the relevant part (emphasis mine): Extreme Simplicity “. 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. I believe this is what the PHP spirit is all about and the idea behind it is that it’s possible to create very simple & easy-to-use languages&frameworks which still remain powerful and flexible. trainings and certifications, something has to give. ]]>

2012/3/25
articleCard.readMore

On Hiring: Use Kanban for Managing Candidates and the Hiring Process

to manage the whole process. I believe, hiring should be as lean and agile as our development process. we just started hiring at SitePoint!): Using a Kanban board offers a number of advantages: a clear picture of the candidates with their feedback from code tests and interviews a straightforward view of where a candidate is at in the hiring process limited number of candidates in some stages to prevent chaos visual reminders to get in touch with the candidates, it’s always a good idea to keep them in the loop a tight WIP limit for shortlisted candidates, there’s no point to shortlist too many candidates What do you think? ]]>

2012/2/21
articleCard.readMore

On Hiring: How To Be a Non-Technical Co-Founder

check out my article on this subject. such as Dropbox, you probably need one or more co-founders to work with you on The Next Big Thing ™. you instead of some other billion-dollar ideas? is there a billion-dollar idea? The short answer is: NO. idea, by itself, is worthless. just have ideas. Very early on, he persuaded Wozniak to produce and sell Apple I so they have some capital. Jobs was building the foundation. Without the foundation, there will be no failure or success to come. key attributes I look for in a co-founder (technical or otherwise). you the technical co-founder? Why are you looking for my technical ability?” You ask. CodeYear and Khan Academy a try? you obsessed with? an ealier article: just the photo uploading and sharing features?” PlayBook, 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. key 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)? ]]>

2012/1/28
articleCard.readMore

On Hiring: How Not to Annoy Developers

check out my article on this subject. not what functions or libraries a candidate may or may not remember from the API documentation. Hacker News comments here. ]]>

2012/1/26
articleCard.readMore

The Lean Startup - The Book Every Entrepreneur Should Read

is one of the most exciting and joyful things I’ve done during the holidays. 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. 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. :) ]]>

2011/12/31
articleCard.readMore

Blog Redesigned For 2012, And New Challenges Ahead

, but with a fresh header and better use of space. SitePoint - dozens of interesting and challenging projects ahead! 2012 will be an awesome year! :) ]]>

2011/12/27
articleCard.readMore

Startup, VC, and the Things I Learnt from Open-sourcing A 200+ Hour Client Project

has made to the Hacker News front page and has spawned some great discussion and debate. I had just been screwed recently, I thought it might be a good opportunity for me to share what I’ve learnt. picked up by Hacker News and some other sites, I was contacted by my client. It was a very interesting and surreal experience which included keywords such as sue, settle, donation and disappearance. I am however going to spare you the details and instead, going to focus on the things I have learnt. attached to the opportunity. wanted it to work out. I wanted to be involved early in a startup, I wanted to create a product that will have large impact, I wanted to have uncertainties and excitement, and I wanted to force myself to be busy. $2mil 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. for myself). they started Apple because they didn’t know any better. I can comfortably say that, I am very much looking forward to my next adventure! :) ]]>

2011/9/2
articleCard.readMore

Skype.com - A Quick Example of UX Failure

Today I noticed that I don’t have Skype installed, so naturally, I went to Skype.com. Then I was presented with their homepage: The problem? No “Download” action button above the fold, or below the fold for that matter. That is, quite frankly, shocking. 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 … 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. Is this some kind of prank from the Microsoft enterprise? *shakes head* ]]>

2011/8/31
articleCard.readMore

Open-sourcing A 200+ Hour Project - The Story Behind It

the things I have learnt from this. Angel Nest - an online platform for connecting entrepreneurs and investors, similar to AngelList. got picked up by Hacker News and went onto its front page. (Update: I was just informed that the story was picked up by Reddit as well.) The Github repository has since been followed by 250+ people and forked by 60+ times - it became one of the Github’s daily trending repositories. do not like 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. my own adventures to pursue. What about other work and/or startup opportunities? You may find out by dropping me a line, all my contact details can be found on this blog. :) I am interested, but who are you? Scroll down to the bottom of the page and you will find my bio with a couple of links. :) ]]>

2011/8/17
articleCard.readMore

It's Year 2011, Why Aren't People More Open-minded?

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. Being happy with where you are is one thing, being ignorant is another. Inventions and innovations aren’t born out of happiness, they are born out of frustration, anger and sometimes, curiosity. Being open-minded about how things work, in my opinion is one of the key attributes an entrepreneur should have. 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. Successes are built on many failures, there is no doubt about that. However, failures should be the outcome of experimentation rather than ignorance. 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. Blindly following some rare exceptions such as Apple and 37signals is probably not going to work for most of us. But heck, what do I know, I am just a web developer, right? ]]>

2011/7/30
articleCard.readMore

comic because activerecord is slow

Comic: Because ActiveRecord is Slow! ]]>

2011/7/6
articleCard.readMore

Moving On From Envato, What's Next?

. 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. the 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. Tuts+ network) and I sincerely hope the project will be taken to the next level. Rockable Press from Envato. If you are a web developer, expect to see a (hopefully useful) web development book this year. ;) PlayUp 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! Wuit) as well as on some of the open source projects such as Slim. Exciting times ahead! :) ]]>

2011/3/17
articleCard.readMore

my entry to the rubycommitters design contest

:-) ]]>

2011/1/19
articleCard.readMore

sneak peak of the redesign of slim langcom

Sneak peak of the redesign of slim-lang.com. :) ]]>

2010/12/23
articleCard.readMore

[Ruby] Haml2Slim, Convert Your HAML Templates to Slim Templates

because it’s awesome. ;) Check out the source code. ]]>

2010/12/16
articleCard.readMore

[Ruby] Releasing Ssync - An Optimised S3 Sync Tool Using the Power of Unix!

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 Amazon S3. Ryan Allen has worked on a script called Sir Sync-A-Lot which syncs the data to S3. This was done after evaluating a bunch of scripts including s3sync. a little Rubygem and added a bit more features. check out the source code! ]]>

2010/11/12
articleCard.readMore

Slim, a Fast and Lightweight Rails Template Engine!

. Haml and Jade. Andrew Stone who is the author of the project has posted a quick update on the latest feature additions to Slim. Please go check it out. available on Github. ]]>

2010/10/18
articleCard.readMore

A Speedier Rails App using Rails 3.1 + Arel 2.0

After (Rails 3.1.0 master branch + Arel 2.0.0dev master branch: @tenderlove) and others. :-) UPDATE: tweet, I ran the tests again on Rails 3.0.1pre stable branch + Arel 2.0.0dev master branch, and the result blew my mind: ]]>

2010/9/29
articleCard.readMore

`bundle: command not found` or `Could not find RubyGem bundler (>= 0)` During Capistrano Deployment? No Problems!

deployment? bundle: command not found Could not find RubyGem bundler (>= 0) (Gem::LoadError) bundler, you might still get errors like this: rake: command not found Could not find RubyGem rake (>= 0) (Gem::LoadError) $PATH and $GEM_HOME variables. /etc/ssh/sshd_config: PermitUserEnvironment yes ssh: /etc/init.d/ssh restart 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 * 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 GEM_HOME’s path matches one from the PATH. cap shell to start a new shell session and try out your commands. ]]>

2010/9/16
articleCard.readMore

wuit trademarked in australia now the

is my soon to be launched studio identity. ]]>

2010/8/27
articleCard.readMore

[Rails] RailsConfig, A New Iteration of AppConfig for Rails 3

in your Rails project, well, you should! RailsConfig. I was invited to join the development of this new tool, so make sure you go check it out. :-) ]]>

2010/8/11
articleCard.readMore

[Rails] Inherited Resources Views Now Supports Rails 2.x

that added Rails 2.x compatibility to Inherited Resources Views. Please give it a spin! :-) P.S. I’ve only tested it on Rails 2.3.8. ]]>

2010/8/7
articleCard.readMore

Reinvigorate - Realtime Website Traffic Analysis

time realtime website traffic analysis has been introduced. Reinvigorate is one of the services that provides realtime traffic tracking and analysis. The thing I like it most though, is in fact the heatmap. :-) Heatmap is a great tool to help identify convoluted interface and improve the user experience. ]]>

2010/8/6
articleCard.readMore

Poke me on Github! :D

ribbon to my blog (look at the top left corner!). Please feel free to poke me! :D Redoing the GitHub Ribbon in CSS GitHub Ribbon Using CSS Transforms ]]>

2010/8/6
articleCard.readMore

[Rails] Releasing Inherited Resources Views - DRY Your View Files

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 Inherited Resources Views! Go check out the code! :-) ]]>

2010/8/5
articleCard.readMore

released my first ruby gem

Released my first ruby gem. :-) ]]>

2010/8/4
articleCard.readMore

looks like its time for a reboot

Looks like it’s time for a reboot… ]]>

2010/8/4
articleCard.readMore

[jQuery] Releasing Inline Confirmation, Confirm Actions Done Right

Inline Confirmation - a jQuery plugin for creating easy, less obtrusive confirmation dialogues! Feel free to give it a spin. I will add more documentation and a demo when and if I have time. ;) ]]>

2010/8/3
articleCard.readMore

I'm Now a Ruby on Rails Contributor

, so I am now one of the 1600 odd people who have contributed to the Rails project! :D ]]>

2010/8/3
articleCard.readMore

[PHP] Releasing KThrottler, A Kohana Module for Throttling Actions

, KThrottler is an easy to use Kohana module to quickly throttle application actions based on configurable duration and limit. check out the code now! :) ]]>

2010/7/28
articleCard.readMore

#whatdatingislike What Dating Is Like

Dating girls is like running prerelease ruby gems. Things might not be compatible, there might be memory leaks and other weirdnesses. Dating girls is like reading a tutorial in a foreign language. You think you got it, but you don’t. 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. 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. 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!! Dating girls is like using CakePHP. It might look like Rails, but it’s not, unless you’re happy with seeing arrays everywhere. Dating girls is like running production PHP sites on IIS. Sometimes you wonder, why bother? Dating girls is like writing a Capistrano recipe, you don’t know what it’s going to happen until you run it. Dating girls is like buying an iPhone 4. You know it has flaws, but you are gonna buy it anyway. Dating girls is like coding in Obj-C. It’s hot right now, but you wonder how long is it going to last? Dating is like using Zend Framework. It looks so impressive from distance, then when you actually started using it… Dating girls is like making a huge deployment. As confident as you are, you still need to cross fingers! Dating girls is like developing for Wordpress. It looks so pretty on the surface, but the roots are broken beyond repair. ]]>

2010/7/26
articleCard.readMore

[Rails] Introducing Datamappify - ActiveRecord Without DB Migrations

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. Go check out the code! ]]>

2010/7/22
articleCard.readMore

Fuck GPL!

]]>

2010/7/15
articleCard.readMore

just some late night sketches done with a finger

Just some late night sketches, done with a finger, an iPad and some wine. ]]>

2010/7/14
articleCard.readMore

Wuit.com Now Runs on Padrino

, an excellent ruby framework built on top of Sinatra. What can be a better than experimenting with it? Build an actual website with it! Wuit.com from using vanilla PHP + Flourish to using Padrino + DataMapper + Haml. Capistrano though - both current_release and latest_release were giving me strange results. In the end I had to modify my deployment recipe to overcome this. 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. ]]>

2010/7/2
articleCard.readMore

[PHP] Releasing PHamlP Module for Kohana, Use Haml and Sass with Kohana 3.0!

and Sass are two extremely useful template engines. They are very popular amongst the Ruby and Rails community. Kohana v3 that uses the PHamlP library to offer Haml/Sass support for Kohana. check out the source code. :) ]]>

2010/6/27
articleCard.readMore

Deploy PHP Websites Using Capistrano (and Git)

deployment flow is really smooth thanks to the powerful (and easy to use) Capistrano. PHP! Ruby and Rubygems installed, install them! ruby and rubygems, install Capistrano and its related gems - gem install capistrano-ext railsless-deploy capify ., this will generate some necessary files for Capistrano to recognise your app. Capfile file and replace the content of the file with: require 'rubygems' require 'railsless-deploy' load 'config/deploy' config/deploy.rb file. But before doing so, we need to set up our deployment server with proper user and deployment permission. deploy specifically for deployment purpose, for example - useradd deploy su deploy) and - ssh-keygen GitHub, so simply copy the content of the public key (~/.ssh/id_rsa.pub) and add it to the deploy keys section of the Github repository. .ssh/known_hosts file, you can do this by manually cloning your repository on the deployment server. config/deploy.rb file and you will see some default deployment tasks. Everyone has different needs, so I’m going to paste our deploy.rb file (masked with added comments) for your reference. set :user, "deploy" set :application, "YOUR_APPLICATION_NAME" set :domain, "YOUR_APPLICATION_DOMAIN_NAME" set :repository, "THE_ADDRESS_OF_THE_APPLICATION_REPOSITORY" set :deploy_to, "/var/www/#{domain}" set :shared_path, "#{deploy_to}/shared" set :use_sudo, false set :scm, :git set :branch, 'master' set :deploy_via, :remote_cache role :web, "ADDRESS_OF_YOUR_WEB_SERVER" role :app, "ADDRESS_OF_YOUR_APP_SERVER" # this can be the same as the web server role :db, "ADDRESS_OF_YOUR_DB_SERVER", :primary => true # this can be the same as the web server namespace :deploy do task :start do ; end task :stop do ; end task :restart, :roles => :app, :except => { :no_release => true } do run "#{try_sudo} /etc/init.d/lsws reload" # 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 => :web do run "ln -s #{shared_path}/uploads #{current_release}/uploads" run "ln -s #{shared_path}/zb #{current_release}/zb" end # Let's run the task immediately after the deployment is finalised. after "deploy:finalize_update", :create_symlinks cap deploy:setup cap deploy ]]>

2010/6/21
articleCard.readMore

[jQuery] Endless Scroll Updated, Now Works with Any DOM Elements

has now been updated to work with any DOM elements, not just $(document). Click here for the project page (with usage examples). Click here for a simple demo. Enjoy! ]]>

2010/6/18
articleCard.readMore

[Rails] Releasing Action Throttler, A Rails Plugin for Throttling Actions

, an easy to use Rails plugin to quickly throttle application actions based on configurable duration and limit. Go check out the code now! :) There is currently no tests for the plugin but I will be adding rspec specs to it soon. ]]>

2010/6/3
articleCard.readMore

A Whole World of Difference: Phusion Passenger Apache to Nginx, Ruby Enterprise Edition 1.8.6 to 1.8.7

. It runs well during the testing and staging phase, unfortunately the server quickly became overloaded and unresponsive after the relaunch was made public. Ruby Enterprise Edition from 1.8.6 to 1.8.7 and switching from using Apache + Passenger to Nginx + Passenger, the site runs so much smoother. Don’t believe me? Take a look at these screenshots below. Switching from REE 1.8.7 to Ruby 1.9.1 did not yield any significant result though. ]]>

2010/5/27
articleCard.readMore

busy at work

Busy at work… ]]>

2010/5/27
articleCard.readMore

[Rails] Use App_Config For Your Application Specific Configuration

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 the one by Christopher J. Bottaro. rails plugin install). read the instructions on how to use this handy plugin. ]]>

2010/5/26
articleCard.readMore

[Rails Tip] Model Attributes Not Updating? `reset_column_information` To the Rescue!

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: class CreateJobLevels type) end end def self.down drop_table :job_levels end end ]]>

2010/5/25
articleCard.readMore

[jQuery Tip] Traverse/Parse HTML String

Hello World paragraph element by using: // data is the HTML source $('span#first', data) p tags are at the top level. Instead, we can simply wrap the HTML source with a div tag and that’ll do it. :) ]]>

2010/4/28
articleCard.readMore

[Rails Tip] DataMapper M:M Association Bug and Workaround

. type.jobs.all(:"country.name".like => "%#{params[:location]}%") type.jobs.all.reject do |job| ! job.country.name.downcase.include?(params[:location].downcase) end But at least it works. ;) ]]>

2010/4/27
articleCard.readMore

[Rails Tip] DataMapper Timestamps Bug and Workaround

by DataMapper’s core developer Martin Gamsjaeger (snusnu) that it is a bug. created_[at|on] can be manually overridden, but updated_[at|on] cannot. job = Job.create( title: "Web Developer", created_at: Time.now - 2 ) job.update!(updated_at: Time.now - 1) bug ticket to see when it’s getting fixed. ]]>

2010/4/19
articleCard.readMore

[Rails Tip] Run Specs Faster!

to run the specs. Try using spec spec instead! It avoids doing some preliminary tasks and therefore is quicker to execute. time command, i.e. time spec spec and time rake spec. ]]>

2010/4/9
articleCard.readMore

[Rails] Run Queued Tasks Using 'delayed_job', Now With Intervals!

’ is a database based asynchronously priority queue system extracted from Shopify. job’ fits the bill _almost perfectly. ;) Envato, 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. made a couple of commits to my fork to add this functionality as well as to fix some rails 3 compatibility issues. config/initializers/delayed_job_config.rb file to have the tasks run at 1 minute interval. Delayed::Worker.run_interval = 60 A pull request has been issued, hopefully it’ll come as a standard feature soon. :) ]]>

2010/3/25
articleCard.readMore

i need textmate 2 or a 3d monitorprojector to

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. ]]>

2010/3/17
articleCard.readMore

[Rails] MongoMapper on Rails 3 Master, Undefined 'to_key' Method

key method to to_key, which consequently rendered MongoMapper unusable. ActionView::Template::Error (undefined method `to_key' for #) submitted a patch to address this issue. Feel free to pull my fork until the fix makes its way into John Nunemaker’s main repository. ]]>

2010/3/17
articleCard.readMore

Use the 'textmate' Command to Quickly Install Bundle Files

is an extremely handy tool for installing and managing your Textmate bundle files. reported here, but today I discovered a workaround that solves this issue. on my fork. gem install textmate --source git://github.com/ddollar/textmate.git Enjoy! :) ]]>

2010/3/14
articleCard.readMore

i hate it when this happens and i have a dozen of

I hate it when this happens and I have a dozen of Textmate and Terminal windows open… ]]>

2010/3/14
articleCard.readMore

[Rails] Use HAML templates with Devise

or HAML in your projects. committed some changes that enable you to do so! The changes are already merged back to the primary repository. rails g devise_views -t=haml Gemfile: gem "devise", :git => "git://github.com/plataformatec/devise.git" the edge version of HAML, as the stable versions do not parse ruby code correctly. ]]>

2010/3/13
articleCard.readMore

first photoshop now textmate whats next

First Photoshop, now Textmate, what’s next? ]]>

2010/3/9
articleCard.readMore

Advanced Search Query on GitHub

? ruby AND (textmate OR tmbundle) ruby, as well as either textmate or tmbundle. GitHub API does not support it. I’ve opened a ticket here. ]]>

2010/3/9
articleCard.readMore

[Rails Tip] Making i18n Forms, the Easy Way

, such as Formtastic. Haml instead of ERb. Already, I got the out-of-box clean looking Haml markup. -form_for(@post) do |f| =f.label :title =f.text_field :title =f.label :body =f.text_area :body =f.submit config/locales/en.yml. helpers: label: post: title: 'Post Title' body: 'Post Content' r18n instead of i18n, so I can instead translate them in app/i18n/en.yml. helpers: label: post: title: Post Title body: Post Content helpers.label, you may place them directly under your models, i.e. activemodel.attribute. Rails 2.3 stable branch from Github, you can also use the built-in i18n support. Although instead of helpers.label, you use views.labels, as seen in this commit. ]]>

2010/3/8
articleCard.readMore

this is from an email newsletter i received in my

. 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! Here’s a great table of contents on what you can and cannot use for different email clients. ]]>

2010/3/6
articleCard.readMore