tag:blogger.com,1999:blog-21079065980998995092024-03-14T05:02:02.399+08:00fr@gmentized !!... non-optimized bits & piecesNg Tze Yanghttp://www.blogger.com/profile/09972389714691658817noreply@blogger.comBlogger14125tag:blogger.com,1999:blog-2107906598099899509.post-89780072455220094692010-07-23T08:49:00.002+08:002010-07-23T08:53:40.037+08:00Last EntryAs u should have noticed, this blog has not been updated for a LONG LONG time, and i'm now going to officially declare END-OF-LIFE for it. If u are interested in following my technical musings, do drop by my other blog <a href="http://tyenglog.blogspot.com">tyenglog</a> to take a look.<br /><br />Cheers & stay happy !!Ng Tze Yanghttp://www.blogger.com/profile/09972389714691658817noreply@blogger.com0tag:blogger.com,1999:blog-2107906598099899509.post-71402840118472904952009-02-13T09:27:00.001+08:002009-02-13T09:42:34.407+08:00Heading Back SG Soonit has been quite sometime since i last blogged, guess blogging just hasn't become a habit yet. anyway, today's entry is just to announce that i'm kicking :P<br /><br />btw, have been quite busy lately, as we are heading back sg (for good) very soon, before end of feb 09. it has been 2yrs since i last got back ... things must have changed quite abit (heard from my friend that the land is getting damn crowded), yet there are probably still alot that haven't changed (it still takes a long time to get a reply for an email to a civil service).<br /><br />anything i missed back in sg? yup, i missed my parents plus the swimming pool just down my hdb. oh yes, and the annual free for all jap film fest !!<br /><br />(written on my mobile)Ng Tze Yanghttp://www.blogger.com/profile/09972389714691658817noreply@blogger.com0tag:blogger.com,1999:blog-2107906598099899509.post-47041657158948480362009-01-15T22:26:00.010+08:002009-01-17T00:38:19.808+08:00dm_skinny_specs ... trimming the fat from dm models' specsJust released my 2nd gem <a href="http://github.com/ngty/dm_skinny_spec">dm_skinny_specs</a> last night, this gem aims to help u trim away extra fat from dm models' specs. Here's a quick example of how things work (for 0.1.0):<br /><br /><table class="ruby_highlighttable"><tr><td class="linenos"><pre> 1<br /> 2<br /> 3<br /> 4<br /> 5<br /> 6<br /> 7<br /> 8<br /> 9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br />22<br />23<br />24<br />25<br />26<br />27<br />28<br />29<br />30<br />31<br />32<br />33<br />34<br />35</pre></td><td class="codes"><div class="highlight"><pre><span class="nb">require</span> <span class="s1">'dm_skinny_spec'</span><br /><br /><span class="k">class</span> <span class="nc">User</span><br /> <span class="kp">include</span> <span class="no">DataMapper</span><span class="o">::</span><span class="no">Resource</span><br /><br /> <span class="n">property</span> <span class="ss">:id</span><span class="p">,</span> <span class="no">Serial</span><br /> <span class="n">property</span> <span class="ss">:name</span><span class="p">,</span> <span class="nb">String</span><span class="p">,</span> <span class="ss">:auto_validation</span> <span class="o">=></span> <span class="kp">false</span><br /> <span class="n">property</span> <span class="ss">:email</span><span class="p">,</span> <span class="nb">String</span><span class="p">,</span> <span class="ss">:auto_validation</span> <span class="o">=></span> <span class="kp">false</span><br /><br /> <span class="n">validates_length</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:min</span> <span class="o">=></span> <span class="mi">5</span><br /> <span class="n">validates_format</span> <span class="ss">:email</span><span class="p">,</span> <span class="ss">:as</span> <span class="o">=></span> <span class="ss">:email_address</span><span class="p">,</span> <span class="ss">:allow_nil</span> <span class="o">=></span> <span class="kp">false</span><br /><span class="k">end</span><br /><br /><span class="no">User</span><span class="o">.</span><span class="n">fix</span> <span class="p">{{</span><br /> <span class="ss">:name</span> <span class="o">=></span> <span class="sr">/\w{5,10}/</span><span class="o">.</span><span class="n">gen</span><span class="p">,</span><br /> <span class="ss">:email</span> <span class="o">=></span> <span class="sr">/\w{10}/</span><span class="o">.</span><span class="n">gen</span> <span class="o">+</span> <span class="s1">'@fmail.com'</span><span class="p">,</span><br /><span class="p">}}</span><br /><br /><span class="n">describe</span> <span class="no">User</span> <span class="k">do</span><br /><br /> <span class="n">before</span> <span class="k">do</span><br /> <span class="no">User</span><span class="o">.</span><span class="n">auto_migrate!</span><br /> <span class="vi">@instances</span> <span class="o">=</span> <span class="p">{}</span><br /> <span class="k">end</span><br /><br /> <span class="c1"># This MUST be provided since we do not mandate u to use any specific fixture </span><br /> <span class="c1"># utility.</span><br /> <span class="k">def</span> <span class="nf">instance</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="ss">:default</span><span class="p">)</span><br /> <span class="vi">@instances</span><span class="o">[</span><span class="nb">id</span><span class="o">]</span> <span class="o">||=</span> <span class="no">User</span><span class="o">.</span><span class="n">gen</span><br /> <span class="k">end</span><br /><br /> <span class="n">it_should_validate_length</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:min</span> <span class="o">=></span> <span class="mi">5</span><br /> <span class="n">it_should_validate_format</span> <span class="ss">:email</span><span class="p">,</span> <span class="ss">:as</span> <span class="o">=></span> <span class="ss">:email_address</span><span class="p">,</span> <span class="ss">:allow_nil</span> <span class="o">=></span> <span class="kp">false</span><br /><br /><span class="k">end</span><br /></pre></div><br /></td></tr></table><br />Hey !! Wait a minute, doesn't skinny_spec and the whole idea of these spec helpers sound familiar ?! Ok Ok, i admit it, i was inspired by the original <a href="http://github.com/rsl/skinny_spec">skinny_spec</a> for rails :P <br /><br/>Ng Tze Yanghttp://www.blogger.com/profile/09972389714691658817noreply@blogger.com0tag:blogger.com,1999:blog-2107906598099899509.post-16789378913158964212008-12-17T21:06:00.005+08:002008-12-17T21:29:11.863+08:00Anyss ... My 1st Gem :]Don't laugh ... i've just released my 1st gem today, many many thanks to Dr Nic's newgem, which makes creating this gem a breeze. This new gem is called <span style="font-style:italic;">anyss</span>, which abbreviates 'any spreadsheet', and is a damn basic wrapper for gnumeric's ssconvert binary, just enough to scratch my own itch though :]. You can find <span style="font-style:italic;">anyss</span> <a href="http://github.com/ngty/anyss">here</a>.<br /><br />Why anyss ? cos i need a way to convert *.ods (openoffice), *.gnumeric (gnumeric) and *.xls (ms excel) to *.csv. Since gnumeric's ssconvert does the job well, i decide to write a wrapper for it. I'm not sure of whether this is a good idea though, as using ssconvert binary as the convertor means that only systems with newer versions of gnumeric can run anyss, and btw, why would server install gnumeric in the 1st place ?! <br /><br />BUT if an itch needs to be scratched, it has to be scratched, rite ?!<br /><br />(i've found out that i need to have <a href="http://gist.github.com/37052">~/.hoerc</a> to ensure some of the new gem's rake tasks run).Ng Tze Yanghttp://www.blogger.com/profile/09972389714691658817noreply@blogger.com0tag:blogger.com,1999:blog-2107906598099899509.post-84452317680164575502008-11-29T19:26:00.005+08:002008-11-29T19:45:57.180+08:00U Don't Have to be Ashamed of Using Ur Own IdeasQuite recently, i've been reading the pragmatic bookshelf's <a href="http://www.pragprog.com/titles/ahptl/pragmatic-thinking-and-learning">Pragmatic Thinking & Learning</a> and have got to know abt <a href="http://www.rtqe.net/ObliqueStrategies/">Oblique Strategies</a>, which as quoted elsewhere: <i>The Oblique Strategies were originally a set of one-hundred cards, each bearing a short phrase. They were devised by Brian Eno and Peter Schmidt as ways of working through creative problems. When a blockage occurs, draw a card, and see if it can direct you in a tangential way that helps solve the problem.</i><br /><br />I must admit i'm not a creative person, but i do quite often encounter mental blocks when doing programming ... with a quick googling round, i've found these 2 sites that would show a strategy upon refresh:<br /><ul><li><a href="http://stoney.sb.org/eno/oblique.html">simple but well designed</a></li><li><a href="http://fawcett.medialab.uwindsor.ca/oblique">bare minimum no nonsense</a></li></ul>And the first strategy i see is <span style="font-weight: bold; font-style: italic;">"You don't have to be ashamed of using your own ideas"</span> ... I like this one :]<br /><br/>Ng Tze Yanghttp://www.blogger.com/profile/09972389714691658817noreply@blogger.com0tag:blogger.com,1999:blog-2107906598099899509.post-3202707696397839622008-11-24T20:26:00.004+08:002008-11-24T21:21:44.057+08:00Getting Selenium & Firefox3 to Work on Ubuntu(BACKGROUND: I've upgraded my notebook & since i hope to focus more time on software development, this time round, i've decided to install something that just work out of the box, and i've chosen to install ubuntu instead of debian, despite being a debian user for the past 5 years.)<br /><br />Since i'm a BDD fan these days, and i simply love writing <a href="http://github.com/aslakhellesoy/cucumber">cucumber's</a> features, so there i was trying to get the bundled selenium example to run:<br /><br />#$ git-clone git://github.com/aslakhellesoy/cucumber.git<br />#$ cd <cucumber_dir>/examples/selenium<br />#$ rake features<br /><br />And i got stack trace screaming <i>"/usr/lib/ruby/1.8/net/http.rb:560:in `initialize': Connection refused - connect(2) (Errno::ECONNREFUSED)"</i> ... OOPS, seems that i've forgotten to install selenium & run selenium:<br /><br />#$ sudo gem install Selenium<br />#$ selenium<br /><br />And i see the console displaying positive dump:<div class="console"><pre>java -jar /usr/lib/ruby/gems/1.8/gems/Selenium-1.1.14/lib/selenium/openqa/selenium-server.jar.txt <br />20:47:29.661 INFO - Java: Sun Microsystems Inc. 1.6.0_0-b12<br />20:47:29.662 INFO - OS: Linux 2.6.27-7-generic amd64<br />...</pre></div>So far so good, and rolling up my sleeves:<br /><br />#$ rake features<br /><br />After getting some green steps, i got a stack trace ... Switching to my selenium console shows:<div class="console"><pre>java.lang.RuntimeException: Firefox couldn't be found in the path!<br />Please add the directory containing 'firefox-bin' to your PATH environment<br />variable, or explicitly specify a path to Firefox like this:<br />*firefox /blah/blah/firefox-bin<br />...</pre></div>OK, its turns out that i need to specify the binary for firefox, as indicated by 'firefox-bin'. Having done some fishing round, i open up <cucumber_dir>/examples/selenium/features/step_definitons/stories_steps.rb to edit the "*chrome" string to "*chrome /usr/lib/firefox-3.0.4/firefox-3.0", thus getting the following block:<div class="code"><pre>Before do<br /> @browser = Selenium::SeleniumDriver.new(<br /> "localhost", 4444, "*chrome /usr/lib/firefox-3.0.4/firefox-3.0", <br /> "http://localhost", 15000 )<br /> @browser.start<br />end</pre></div>Confident that things should work as expected this time, i did:<br /><br />#$ rake features<br /><br />Arrgghh, this time selenium console hangs at:<div class="console"><pre>21:00:29.455 INFO - creating new remote session<br />21:00:29.627 INFO - Allocated session 202cd7534d044c81a66e027960e3d79b for http://localhost, launching...<br />21:00:29.745 INFO - Preparing Firefox profile...</pre></div>After some googling round, i've found a <a href="http://www.spacevatican.org/2008/9/27/selenium-and-firefox-3">solution</a>, it turns out that the selenium bundled selenium-server.jar.txt (yup, the *.txt is intentional) needs some tweaking, and fortunately for me, there is already a one available for me to use, and i simply did:<br /><br />#$ cd /usr/lib/ruby/gems/1.8/gems/Selenium-1.1.14/lib/selenium/openqa<br />#$ sudo mv selenium-server.jar.txt selenium-server.jar.txt.orig<br />#$ sudo wget http://edwardotis.net/public_files/selenium/selenium_server_FF3/selenium-server.jar<br /><br />And after restarting my selenium, and running the rake tasks, i'm assured i have the environment for writing features using cucumber+selenium :] ... 3 cheers !!<br /><br/>Ng Tze Yanghttp://www.blogger.com/profile/09972389714691658817noreply@blogger.com0tag:blogger.com,1999:blog-2107906598099899509.post-58160989868265787212008-11-12T22:40:00.000+08:002008-11-12T22:28:35.469+08:00Playing with Merb+Cucumber+Webrat (1)<div class='lang-en'>In my <a href="http://ngty77.blogspot.com/2008/11/setting-up-merbcucumberwebrat.html">previous post</a>, we have setup the environment for playing with merb+cucumber+webrat. Now lets start playing !! Under ur merb project directory, place the following code in features/users.feature:</div> <div class='lang-zh'>在<a href="http://ngty77.blogspot.com/2008/11/setting-up-merbcucumberwebrat.html">之前的文章</a>中,我们设好了我们的 merb+cucumber+webrat 的环境。现在,让我们开始玩玩吧!! 在你的 merb 项目中,把以下的代码复制进features/users.feature:</div> <div class='code'><pre>Feature: Manage Users<br /> To have the system support multiple users<br /> I should be able to manage users in the system<br /><br /> Scenario: View Existing Users<br /> Given the following 5 users exist in the system:<br /> | name | email |<br /> | Peter | peter@abc.com |<br /> | John | john@xyz.org |<br /> | Jane | jane@efg.net |<br /> | Mary | mary@hij.info |<br /> | Cathy | cathy@stu.us |<br /> When I go to /users<br /> Then I should see all 5 users<br /> And all users should be ordered by name<br /> And I should see Peter having "Email" as "peter@abc.com"</pre></div> <div class='lang-en'>Yup, pretty basic for any system with basic user support. To run the above feature, you can use one of the followings:</div><div class='lang-zh'>的确是非常简单的管理用户的 feature,接下来,让我们在终端中执行以下的指令:</div><br />#$ rake feature[/users]<br />#$ rake features<br /><br /><div class='lang-en'>The difference is that the 1st command runs only the specified feature, while the later runs all. Usually when u are focusing on a feature, you will run the 1st command. In ur console, you will get the following output (i'm leaving out the colors to keep my life simple):</div> <div class='lang-zh'>这两个指令的分别在于,前者只运行一个 feature 文件,而后者将运行所有的 features。当你正开发某个 feature 时,运行前者将帮你省下很多时间。让我们看看以下的终端的输出:</div> <br /><div class='console' ><pre>Feature: Manage Users # features/users.feature<br /> To have the system support multiple users<br /> I should be able to manage users in the system<br /> Scenario: View Existing Users # features/users.feature:5<br /> Given the following 5 users exist in the system: # features/users.feature:6<br /> When I go to /users # features/steps/common_webrat.rb:4<br /> Then I should see all 5 users # features/users.feature:14<br /> And all users should be ordered by name # features/users.feature:15<br /> And I should see Peter having email "peter@abc.com" # features/users.feature:16<br /><br />1 steps skipped<br />4 steps pending<br /><br />You can use these snippets to implement pending steps:<br /><br />Given /^the following 5 users exist in the system:$/ do<br />end<br /><br />Then /^I should see all 5 users$/ do<br />end<br /><br />Then /^all users should be ordered by name$/ do<br />end<br /><br />Then /^I should see Peter having email "peter@abc.com"$/ do<br />end<br /><br />Finished in 0.006268 seconds<br /><br />0 examples, 0 failures</pre></div> <br /><div class='lang-en'>From your console, you will probably notice some very nice and intuitive colorings, and since they are very intuitive, i shall skip over them. Notice that for pending (yellow) steps, cucumber tries to be helpful by suggesting how you are going to write them. Now, let's take cucumber's hint, copy-and-paste those suggested lines, and open features/steps/users_steps.rb:</div> <div class='lang-zh'>发现了没,输出的字句都用了非常直观的的颜色来帮你识别其中的意义,而我也不再这>里多加说明。看见了针对黄色的 pending 输出,cucumber 试着给了你一些提示,让你知道相关的 step 该怎么编写。那我们就接受它的提示,将这些提示代码复制到 features/steps/users_steps.rb:</div> <div class='code'><pre>Given /^the following 5 users exist in the system:$/ do<br />end<br />Then /^I should see all 5 users$/ do<br />end<br />Then /^all users should be ordered by name$/ do<br />end</pre></div> <div class='lang-en'>And do:</div> <div class='lang-zh'>接着运行:</div><br />#$ rake feature[/users]<br /><br /><div class='console' ><pre>Feature: Manage Users # features/users.feature<br /> To have the system support multiple users<br /> I should be able to manage users in the system<br /> Scenario: View Existing Users # features/users.feature:5<br /> Given the following 5 users exist in the system: # features/steps/users_steps.rb:1<br /> expected 0 block argument(s), got 1 (Cucumber::ArityMismatchError)<br /> features/steps/users_steps.rb:1:in `/^the following 5 users exist in the system:$/'<br /> features/users.feature:6:in `Given the following 5 users exist in the system:'<br /> When I go to /users # features/steps/common_webrat.rb:4<br /> Then I should see all 5 users # features/steps/users_steps.rb:4<br /> And all users should be ordered by name # features/steps/users_steps.rb:7<br /><br />1 steps failed<br />4 steps skipped</pre></div> <br /><div class='lang-en'>OH NO !! What have we done wrong ?? No worries, we are on the right track :]. The red failure lines are intended. Let's fix the problem by editing features/steps/users_steps.rb:</div> <div class='lang-zh'>真糟糕,我们做错了什么吗??别担心,一切都在掌握中。红色失败是意料中的,让我们改改 features/steps/users_steps.rb 来搞定它吧:</div> <div class='code'><pre>Given /^the following 5 users exist in the system:$/ do |attrs_table|<br />end<br />Then /^I should see all 5 users$/ do<br />end<br />Then /^all users should be ordered by name$/ do<br />end<br />Then /^I should see Peter having "Email" as "peter@abc.com"$/ do<br />end</pre></div> <div class='lang-en'>And do:</div> <div class='lang-zh'>再来运行:</div><br />#$ rake feature[/users]<br /><br /><div class='console' ><pre>Feature: Manage Users # features/users.feature<br /> To have the system support multiple users<br /> I should be able to manage users in the system<br /> Scenario: View Existing Users # features/users.feature:5<br /> Given the following 5 users exist in the system: # features/steps/users_steps.rb:1<br /> When I go to /users # features/steps/common_webrat.rb:4<br /> Then I should see all 5 users # features/steps/users_steps.rb:4<br /> And all users should be ordered by name # features/steps/users_steps.rb:7<br /> And I should see Peter having "Email" as "peter@abc.com" # features/steps/users_steps.rb:10<br /><br />5 steps passed</pre></div> <br /><div class='lang-en'>OH NO !! What have we done wrong ?? Just adding <i>attrs_table</i> and we get a 'passed' for all steps ?? We've not written any model/view/controller code yet, so how can ... One key point to note here is that as long as we have a matching step, and the step doesn't trigger a failure, it is considered a 'passed'. Anyway, there is a workaround for this. Since cucumber does not yet support any pending/todo in a step, we can rewrite features/steps/users_steps.rb:</div> <div class='lang-zh'>搞什么东东??怎么这下子我们都拿到了绿色的成功?我们好像连 model/view/controller 的代码都没写!!在这里,我们知道了,只要我们有个匹配的 step,不管>里头的内容是什么,只要没产生错误,cucumber 都会把它视为一个绿色成功的 step。就现在的 cucumber 版本,我们可以双点小聪明,修改一下 features/steps/users_steps.rb:</div> <div class='code'><pre>Given /^the following 5 users exist in the system:$/ do |attrs_table|<br /> pending<br />end<br />Then /^I should see all 5 users$/ do<br /> pending<br />end<br />Then /^all users should be ordered by name$/ do<br /> pending<br />end<br />Then /^I should see Peter having "Email" as "peter@abc.com"$/ do<br /> pending<br />end</pre></div> <div class='lang-en'>And do:</div> <div class='lang-zh'>再来运行:</div><br />#$ rake feature[/users]<br /><br /><div class='console' ><pre>Feature: Manage Users # features/users.feature<br /> To have the system support multiple users<br /> I should be able to manage users in the system<br /> Scenario: View Existing Users # features/users.feature:5<br /> Given the following 5 users exist in the system: # features/steps/users_steps.rb:1<br /> undefined local variable or method `pending' for #<Merb::Test::World::Webrat:0xb75da34c> (NameError)<br /> ./features/steps/users_steps.rb:2:in `Given /^the following 5 users exist in the system:$/'<br /> features/users.feature:6:in `Given the following 5 users exist in the system:'<br /> When I go to /users # features/steps/common_webrat.rb:4<br /> Then I should see all 5 users # features/steps/users_steps.rb:4<br /> And all users should be ordered by name # features/steps/users_steps.rb:7<br /> And I should see Peter having "Email" as "peter@abc.com" # features/steps/users_steps.rb:10<br /><br />1 steps failed<br />4 steps skipped</pre></div> <br /><div class='lang-en'>Pretty ugly, are we back to square one? Not really, as long as we are well aware of the actual reason. Let's take a break take a look at how Aslak (cucumber's author) <a href="http://github.com/aslakhellesoy/cucumber/wikis">sums up the style of development with BDD and cucumber</a>:<ol> <li> when u have a new feature to implement, start by writing a new feature (a <strong><i>Feature</i></strong> in features/*.feature file) or scenario (a <strong><i>Scenario</i></strong> under an existing features/*.feature)</li> <li> describe the feature/scenario by filling in <strong><i>Then</i></strong>, followed by <strong><i>When</i></strong>, and finally <strong><i>Given</i></strong> </li> <li> run the features (make sure the new steps are yellow or red) </li> <li> write code to address the yellow/red steps</li> <li> repeat 3-4 to get all green steps</li> <li> when it comes to the nitty gritty details of your classes, use rspec to write specs before writing code</li> </ol></div> <div class='lang-zh'>有点难看,我们又回到原点了吗?不一定,只要我们知道其背后的意义就暂时可以将就一下吧。让我们抛开代码,来看看 Aslak(cucumber 的作者)<a href="http://github.com/aslakhellesoy/cucumber/wikis">如何阐述使用 BDD 的开发模式和 cucumber 来开发:</a>:<ol> <li> 当你有一个新的功能需要实现时,先把它写在一个新的 feature (即一个 features/*.feature 文件里的 <strong><i>Feature</i></strong>)或 scenario (在现有的 features/*.feature 添加一个 <strong><i>Scenario</i></strong>)</li> <li> 编写/形容一下这个新的 feature/scenario,先从 <strong><i>Then</i></strong> 着手,让后使用 <strong><i>When</i></strong>,最后才用 <strong><i>Given</i></strong> </li> <li> 运行 features (确保它们是红色或黄色的)</li> <li> 针对这些红色或黄色的 steps,编写相关的代码</li> <li> 重负 3-4,知道你看见所有的 steps 都成绿色</li> <li> 针对一些非常小的细节,建议使用 rspec,不过,记得要先写 specs,让后才些代码</li> </ol></div> <div class='lang-en'>I shall not go into the iterating of writing codes and running the features since this will well go into many many pages, but before i end this post, let's take a look at how we can rewrite features/steps/users_steps.rb to make it more generic, less coupled to users:</div> <div class='lang-zh'>好啦,接下来就是完成这些 steps 的内容,让后再来写代码了。太烦琐了,我也不想花再多的篇幅来编写。不过,在我结束前,我们可以再看看 features/steps/users_steps.rb 里的 steps,或许可以改一改来让它们更不被 users 绑死:</div> <div class='code'><pre>Given /^the following (\d+) (\w+) exist in the system:$/ do | count, resource, attrs_table|<br /> pending<br />end<br />Then /^I should see all (\d+) (\w+)$/ do | count, resources |<br /> pending<br />end<br />Then /^all (\w+) should be ordered by (\w+)$/ do | resources, attribute |<br /> pending<br />end<br />Then /^all (\w+) should be ordered by (\w+)$/ do | resources, attribute |<br /> pending<br />end<br />Then /^I should see (\w+) having "(.*)" as "(.*)"$/ do | resource_nick, attribute, value |<br /> pending<br />end </pre></div> <div class='lang-en'>With the above, the steps are more generic and we can probably placed them into features/steps/common_result_steps.rb, and remove features/steps/users_steps.rb. Should we call it refactoring of our steps ?</div> <div class='lang-zh'>不错吧,程序员就是应该这样,在合理的范围里找捷径!!而既然这些 steps 已不再和 users 那么的紧紧相连,我们可以把它们放在 features/steps/common_result_steps.rb 里。 这算不算 steps 的重构?</div> <br /><div class='lang-en'>The sample codes for this exercise (tag 0.1.x) can be downloaded from:</div> <div class='lang-zh'>如果你想看看我是如何完成这个练习的,你可以下载相关代码(tag 为 0.1.x):</div> <a href="http://github.com/ngty/ty_cucumber_salad">http://github.com/ngty/ty_cucumber_salad</a><br /><br/>Ng Tze Yanghttp://www.blogger.com/profile/09972389714691658817noreply@blogger.com0tag:blogger.com,1999:blog-2107906598099899509.post-11177687704644159572008-11-10T21:37:00.000+08:002008-11-12T22:34:45.492+08:00Setting Up Merb+Cucumber+Webrat<div class='lang-en'>(BACKGROUND: Even though i've very much like to write rspec stories for my previous project, due to time constraint, i have eventually rely on the traditional click-and-observe on my browser, which i must admit is very INEFFICIENT and FRUSTRATING, and it makes me hate myself for resorting to such non-programmer approach. I HATE SUCH MANUAL TESTING. This time round, i'm starting on a new merb project and i swear to myself, i'm going to automate all tests !!! No more click-and-observe in my browser !! And i'm just in time for the new kid in the block ... CUCUMBER !!)</div> <div class='lang-zh'>(背景:虽然之前我一直想尝试 rspec stories,可是因为时间的紧迫,最终我还是使用了传统的‘鼠标点击/眼睛观察’方式,我必须承认,这是非常没有效率和让我气恼的方式,特别我是个程序员!!我最憎恨的就是这种耗时间,>耗人力的测试方式。这一会,我有机会使用 merb 来开发新的项目,而这一回,我告诉自己,我要全自动化的测试!!而正好,cucumber 出现了!!)</div><br /><div class='lang-en'>How much different is cucumber from rspec's stories, i never know since i've never work with stories. But it should be better, given the fact that rspec is phasing out its stories in favour of it. Now, lets get our dev environment ready. By following this merb google group thread, these are the steps i've done to get things up and running:</div> <div class='lang-zh'>其实到底 rspec stories 和 cucumber 有多大的差别,我不太晓得,毕竟我没用过前者。不过,cucumber 应该更好吧>,否则,rspec stories 也不会逐步让 cucumber 替代了。在我们开始玩 cucumber 前,让我们来准备一下我们的开发环境吧:</div><br />#$ gem install merb<br /><br /><div class='lang-en'>While writing this post, the above installs merb-1.0. Next, we are going to install cucumber, webrat and merb_cucumber from the following sources:</div> <div class='lang-zh'>在写这篇 blog 时,以上的指令将安装 merb-1.0。接下来,让我们安装 cucumber,webrat 和 merb_cucumber,这些的源代码可从以下 github 库中获取:</div><br />* git://github.com/brynary/webrat.git<br />* git://github.com/david/merb_cucumber.git<br />* git://github.com/aslakhellesoy/cucumber.git<br /><br /><div class='lang-en'>To install:</div> <div class='lang-zh'>安装:</div><br />#$ git-clone [project_src]<br />#$ cd [project_dir]<br />#$ rake install<br /><br /><div class='lang-en'>At the point of this writing, the above installs cucumber-0.1.9, webrat-0.3.1 and merb_cucumber-0.3.</div> <div class='lang-zh'>在写这篇 blog 时<br />,以上将安装:cucumber-0.1.9, webrat-0.3.1 和 merb_cucumber-0.3。</div><br /><div class='lang-en'>Just a quick check to ensure things are OK:</div> <div class='lang-zh'>让我们确保一切 OK:</div><br />#$ merb-gen app playground<br />#$ cd playground<br />#$ merb-gen cucumber --session-type webrat<br />#$ rake features<br /><br /><div class='lang-en'>And you should see something as followings (minus the difference in colors):</div> <div class='lang-zh'>你将看见一下的输出(当让颜色将更鲜艳):</div><br /><div class='console' ><pre>Feature: Login <span class='comments'># features/login.feature</span><br /> To ensure the safety of the application<br /> A regular user of the system<br /> Must authenticate before using the app<br /> <span class='passed'>Scenario: Failed Login</span> <span class='comments'># features/login.feature:6</span><br /> <span class='passed'>Given I am not authenticated</span> <span class='comments'># features/steps/login_steps.rb:1</span><br /> <span class='passed'>When I go to /login</span> <span class='comments'># features/steps/common_webrat.rb:4</span><br /> <span class='passed'>And I fill in "login" with "i_dont_exist"</span> <span class='comments'># features/steps/common_webrat.rb:16</span><br /> <span class='passed'>And I fill in "password" with "and_i_dont_have_a_password"</span> <span class='comments'># features/steps/common_webrat.rb:16</span><br /> <span class='passed'>And I press "Log In"</span> <span class='comments'># features/steps/common_webrat.rb:8</span><br /> <span class='passed'>Then the login request should fail</span> <span class='comments'># features/steps/common_result_steps.rb:13</span><br /> <span class='passed'>Then I should see an error message</span> <span class='comments'># features/steps/common_result_steps.rb:9</span><br /><br /><span class='passed'>7 steps passed</span><br /><br />Finished in 0.042779 seconds<br /><br />0 examples, 0 failures</pre></div><br /><div class='lang-en'>Well, that's all for today. The next post will be <a href="http://ngty77.blogspot.com/2008/11/playing-with-merbcucumberwebrat-1.html">Playing with Merb+Cucumber+Webrat (1)</a>.</div> <div class='lang-zh'>好啦,今天就到此为止。下一篇 blog 将为 <a href="http://ngty77.blogspot.com/2008/11/playing-with-merbcucumberwebrat-1.html">Playing with Merb+Cucumber+Webrat (1)</a>。</div><br/>Ng Tze Yanghttp://www.blogger.com/profile/09972389714691658817noreply@blogger.com0tag:blogger.com,1999:blog-2107906598099899509.post-62524956869877008832008-11-09T16:06:00.006+08:002008-11-09T17:03:40.720+08:00Inpage Translation by Javascript<div class="lang-en">Let's say u are a regular blogger who happens to know quite a number of languages, you have the chance to reach out to many more people, compared to another guy who happens to know just one language. Yet you are short of $$, meaning u can only use free blogs that don't support writing multi-language contents, and u hate the idea of writing multiple posts for the same idea ... here's a simple solution to give u a helping hand - inpage_translator.js !!<br /></div> <div class="lang-zh">或许你是个爱写博客的家伙,而你通晓好多不同的语言,相比较一个只懂得一个语言的家伙,你有机会让更多的人能够分享你的想法。可是,你的口袋有点紧,你只有免费的博客帐号,而往往这些帐号都无法支持不同语言的内容,而你无法想象为了同一个想法而写好多不同的文章,怎么办呢?或许有个好办法 - inpage_translator.js</div><br /><div class="lang-en">Using it is damn easy, in fact, this post is already supported by inpage_translator.js. The concept is very simple, for each content under a different language, simply rewrite it as:</div> <div class="lang-zh">使用‘inpage_translator.js’是非常的简单的,你只需把你的博客内容写成:</div><br /><div class="lang-en">this is some english content</div><br /><div class="lang-zh">这是中文内容</div><br /><br /><div class="lang-en">And base on ur reader's browser language, inpage_translator.js will figure out which content to show, which one to hide. And for cases where it cannot figure out (eg. for 'fr'), it will display the default content (by default, it will be 'en') .. cool ?!</div> <div class="lang-zh">根据读者的浏览器的语言设置,‘inpage_translator.js‘能够推算出那些内容该被显示,或隐藏。而在它无法正确推算的情况下,它会显示默认的语言(’en‘,即英文)。。。不错吧?!</div><br /><div class="lang-en">To find out more, goto:</div><div class="lang-zh">想了解更多?请到:</div> http://github.com/ngty/inpage_translator.js<br /><br/>Ng Tze Yanghttp://www.blogger.com/profile/09972389714691658817noreply@blogger.com0tag:blogger.com,1999:blog-2107906598099899509.post-24574091389723455052008-10-19T21:17:00.004+08:002008-10-19T21:44:40.958+08:00Upgrading Debian Etch to Testing on SlicehostWe just got a slice at www.slicehost.com. During the account setup, we have chosen debian etch (stable) as the OS for our slice. And as i sat down to set up the various services on it, i realized that many of the installed packages are not up-to-date !! Eg. ruby is still 1.8.5.<br /><br />Well, as a part-time linux server admin, i'm definitely not one to live on the edge. But i've always been using the testing branch on my thinkpad, and all my servers are running testing, and to tell you the truth, they are all running FINE. Yup, debian testing is definitely more stable then its name suggests.<br /><br />Of course, when facing my current etch box, i can opt for compiling from source just to get the newer versions of softwares to run on it. BUT, its a maintenance hell !! This approach is a NO-NO given debian has such a wonderful package manager, apt.<br /><br />After double checking that slicehost does offer an option to rebuild a slice (just in case something goes wrong, which i don't anticipate, just to be on the safe side), i updated my /etc/apt/sources.list to replace all 'etch' with 'testing' ... hehe ... and with a quick:<br /><br />#$ apt-get update<br />#$ apt-get upgrade<br /><br />I got the packages upgraded to the desired versions ... Hurray !!Ng Tze Yanghttp://www.blogger.com/profile/09972389714691658817noreply@blogger.com0tag:blogger.com,1999:blog-2107906598099899509.post-34780063225580206072008-10-13T23:46:00.008+08:002008-10-14T00:31:11.377+08:00irc.freenode.net#xyzI've never been into irc or icq when they are in their hottest many years back, as i just dismissed them as some passing trendy-thing-to-do among my youthful peers. And it isn't likely for me to use irc if not for the repeated mentioning of irc.freenode.net#xyz (where #xyz can be any chat room out there) on many of the open-source projects i've visited.<br /><br />I'm using debian linux with the almighty pidgin, which supports almost all im protocols under the sun. Yet i asked myself 'how the hack am i going to start chatting in irc.freenode.net#xyz ?!'. Well in turns out to be pretty easy. The keys steps can be summarized as:<br /><ol><li>in new account setup, set the irc server as irc.freenode.net</li><li>use your preferred user name (provided it doesn't conflict with any existing) but don't set ur passwd yet<br /></li><li>in the 'irc.freenode.net' window, use '<tt>/msg NickServ REGISTER [password] [email] to update ur password and email</tt></li><li><tt>check ur email and follow the verification instruction</tt></li><li><tt>update ur passwd in your account setup</tt></li><li><tt>add new chat '#xyz'<br /></tt></li></ol>The above is enough to remind me of what to do next time, just in case my memory fails me. <a href="http://www.rhitrobotics.org/wiki/internal/joining-irc" target="joining_irc">See here</a> if you need more details :]Ng Tze Yanghttp://www.blogger.com/profile/09972389714691658817noreply@blogger.com1tag:blogger.com,1999:blog-2107906598099899509.post-56511595307734889132008-10-10T22:49:00.005+08:002008-10-10T23:40:48.348+08:00Rules of ThumbWell, these aren't patterns, but just principles we should use when writing softwares, brought up by Russ Olsen in his <a href="http://www.designpatternsinruby.com/" target="design_patterns_in_ruby">Design Patterns in Ruby</a>:<br /><ul><li><span style="font-style: italic; font-weight: bold;">Separate out the things that change from those that stay the same</span> ... identify aspects of the system design that are likely to change, isolate them from the stable parts. Thus, when requirements change or bug fix comes along, even if we still need to modify the codes, the changes can be localized and the rest of the code can live on without changes.<br /></li></ul><ul><li><span style="font-style: italic; font-weight: bold;">Program to an interface, not an implementation</span> ... write code that uses the most general type possible. For example, by writing plane.fly & ship.sail, we are getting very specific, this results in tight coupling, the code using these objects needs to know the exact traveling mode. To loosen things up, we can generalize these vehicles by writing plane.travel & ship.travel instead, thus, the client code no longer needs to know the traveling mode, it just ask the objects to travel.<br /></li></ul><ul><li><span style="font-style: italic; font-weight: bold;">Prefer composition over inheritance</span> ... instead of saying an object is <span style="font-style: italic;">a kind of something</span>, we say that it <span style="font-style: italic;">has something</span>. We assemble the behaviors we need through composition, by supplying an object with objects that provide the needed functionalities.<br /></li></ul><ul><li><span style="font-style: italic; font-weight: bold;">Delegate, delegate, delegate</span> ... by preferring composition over inheritance, instead of being almighty (or inheriting from the almighty parent), an object can <span style="font-style: italic;">delegate </span>the required functionality to another object.<br /></li></ul><ul><li><span style="font-style: italic; font-weight: bold;">You ain't gonna need it (YAGNI)</span> ... if you are not sure that you need something right now, postpone its implementation until you really need it. Why? Cos we tend to be wrong when we try to anticipate exactly what we will need in the future. And we should focus our time and energy implementing things that we <span style="font-style: italic;">definitely need right now</span>.<br /></li></ul>Ng Tze Yanghttp://www.blogger.com/profile/09972389714691658817noreply@blogger.com0tag:blogger.com,1999:blog-2107906598099899509.post-70743523929363945652008-10-09T20:26:00.000+08:002008-10-10T00:06:12.376+08:00Template MethodIt has been said that in the software engineering field, the classic <a href="http://en.wikipedia.org/wiki/Design_Patterns" target="gof_book">GOF</a> book is a MUST READ bible. I never doubt this. However, i have always found it hard to digest. Not too long ago, i've found another book <a href="http://www.designpatternsinruby.com/" target="design_patterns_in_ruby">Design Patterns in Ruby</a>, and i love it. It really makes all the concepts easier to grasp.<br /><br />The first pattern it brought up is the 'template method', which is probably the easiest of all. Here's how it goes:<br /><pre>1. class Report<br />2. def initialize(content)<br />3. @content = content<br />4. end<br />5. def output # template/skeletal method<br />6. output_header<br />7. output_body<br />8. output_footer<br />9. end<br />10. def output_body # abstract method<br />11. raise "Subclass MUST implement output_body"<br />12. end<br />13. def output_header ; end # hook (non-abstract) method<br />14. def output_footer ; end # hook (non-abstract) method<br />15. end<br /></pre>Notice the followings:<br /><ul><li>lines 5-9 ... defines the <span style="font-style: italic;">template (aka skeletal) method</span> that delegates specific output actions individual methods</li><li>lines 10-12 ... defines the <span style="font-style: italic;">abstract method</span> that MUST be implemented by subclasses, throwing of error is a workaround for the absence of abstract method in ruby (actually this is unnecessary, since ruby throws an undefined method error if it cannot find, anyway, its good to be informative)</li><li>lines 13 ... defines <span style="font-style: italic;">a hook method</span>, which provides minimal default implementation and is meant to be overridden by subclasses (when appropriate)<br /></li></ul>Subclasses can be written and used as follows:<br /><pre>1. class TextReport < Report<br />2. def output_body<br />3. puts @content.body<br />4. end<br />5. end<br />6.<br />7. class HtmlReport < Report<br />8. def output_header<br />9. puts "<html>"<br />10. end<br />11. def output_body<br />12. puts "<body>#{@content.body}</body>"<br />13. end<br />14. def output_footer<br />15. puts "</html>"<br />16. end<br />17. end<br />18.<br />19. [ TextReport, HtmlReport ].each do |report_class|<br />20. # outputs the appropriate report depending on report_class<br />21. report_class.new(content).output<br />22. end<br /></pre>SIMPLE !?<br /><br />I've realized that without ever reading abt design patterns, i've already implemented this pattern in my php code, here's a fragment of my past php4 controller code:<br /><pre>1. class AbstractWebController {<br />2. function process() { // template method<br />3. $action = $_POST['action']; // eg. destroy<br />4. $this->$action(); // variable method<br />5. }<br />6. }<br />7.<br />8. class MonsterController extends AbstractWebController {<br />9. function destroy() {<br />10. // monster destroying something ...<br />11. }<br />12. }<br /></pre>Well, it differs slightly from the original template method, as there is no distinct abstract or hook method. Instead, depending on value of $_POST['action'] (line 3), we call a variable method (line 4) in any subclass (line 10). By applying this pattern in any web framework, we can support arbitrary posted actions. NEAT rite ?!Ng Tze Yanghttp://www.blogger.com/profile/09972389714691658817noreply@blogger.com0tag:blogger.com,1999:blog-2107906598099899509.post-56766717224951630132008-10-08T22:54:00.000+08:002008-10-08T23:05:38.476+08:00why 4th of jan ??Why call this blog { 4th of jan } ?? Well i guess it is a good way to remind people abt my birthday (haha) !! Well well, i guess it wouldn't be hard for you to understand why my wife always says that i have a weird sense of humour :P<br /><br />Why have this blog ?? I guess it is a good way for me to force myself to write down my thoughts ... let me tell you, i'm going to put down an entry everyday !! Yes, not kidding !! And if i do miss out on any day, PLS CHASE ME FOR IT !!<br /><br />Ok, time for bed, i'll start my first entry tomorrow ... nites !!<br /><br />zzzNg Tze Yanghttp://www.blogger.com/profile/09972389714691658817noreply@blogger.com0