This post started as a comment to a recent post on KillerPHP.com entitled, “What happened to Ruby? And why PHP is KING of the Web.” The longer my comment became the more I thought I could use a little Google juice myself and why let someone else have all the fun?!
A Twitter post by the normally well-reasoned Cal Evans, Director of the PHP Center for Expertise for Ibuildings lead to my discovery of the KillerPHP.com post that seems to be only the latest in a long line of PHP versus Ruby/Rails rants. I can’t imagine Cal is trolling near as much as it appears the post’s author, Stefan Mischook, but I digress…
I have been a PHP developer since 2001 and founded OINK-PUG in 2005, a month before I discovered Rails and came to enjoy writing Ruby. I prefer Ruby but cannot escape PHP because there is just so much PHP work out there! I am active in both communities locally and would be remiss not to send a shout out to the folks at CART and CincyRB too.
It has been my experience that there is a vast difference in the two communities of developers. Due to PHP’s popularity and low barrier of entry, many developers find it very inviting, especially those with little or no experience. Without much intellectual investment you can produce some rather impressive results, especially using products like Drupal and Wordpress.
Where Ruby shines is when the job calls for something that falls outside of PHP’s sweet spot. Personally, I think it does an equally good job in PHP’s sweet spot but that’s another post. These more challenging cases require more than parochial knowledge of software development; even seemingly simple concepts like a Web UI for SMS can benefit from the skills of a craftsman. That’s where the Ruby community shines and the PHP community falls a bit short.
I suggest that the primary indicator of this is the Ruby community’s embrace of test driven development (TDD) and its relative absence from the PHP community. I would ask those readers who have inherited someone else’s code for both PHP and Ruby projects which they’ve found more maintainable, PHP or Ruby? I always swallow hard when looking at someone else’s PHP code for the first time whereas I know in a minute whether or not a Ruby/Rails project I’ve inherited is going to be trouble. I just look in the “test” directory in the project’s root directory or run rake, created by fellow Cincinnati Rubyist, Jim Weirich by the way!
TDD is only one example of what differentiates the two communities. I can’t count the number of PHP projects I have inherited that were clearly devoid of source control management, or even simple code convention. Excluding the never ending “Spaces vs. Tabs” debate, I’ve never encountered either of these problems with “legacy” Ruby/Rails code. I invite everyone with more than a couple years experience IN BOTH LANGUAGES to add their thoughts about my claims in the comments below. Everyone else, knock yourselves out. Beat me up. Beat on Stephan. You won’t, nor should you expect to change either of our opinions.
Yes, Twitter could have been written in PHP, Wordpress could have been written in Ruby, and Drupal in Python. If that’s all you’ve taken from this post then you’re not “getting it” or more likely, I’ve poorly stated my argument. Nonetheless, clearly Mischook has a bone to pick with Ruby but I find nothing new in his rant and took the bait anyway. I’m overjoyed that he loves PHP so much. I did too, until I found something that “I” like better. I don’t feel the need to justify my decision to others, although I think I just did. Anyone (including me) can write bad Ruby code. Some can write beautiful PHP. In fact, some of the brightest people I know still write PHP but I say, let them eat CakePHP.
During a recent Cincinnati Ruby Brigade meeting a new member who was running Windows would have benefited from a minimalist guide to installing Ruby on Rails on Windows. A few days later I was forced to press my old Windows desktop system into service as an emergency Rails development platform. So I decided to write this post.
Thanks to All About Ruby for providing a starting point for my research for this post.
Nota Bene: These instructions are Windows XP specific. If you’re using another flavor of Windows YMMV. Also, all references to software version numbers are current only at the date of this posting.
C:\>ruby -v ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-mswin32]
C:\>gem update --system Updating RubyGems... Attempting remote update of rubygems-update Successfully update rubygems-update-1.3.1 Updating version of RubyGems to 1.3.1 Installing RubyGems 1.3.1 [...] RubyGems system software updated
C:\>gem -v 1.3.1
WARNING! Be careful that you do not corrupt or delete the existing PATH string.
C:\>gem install sqlite3-ruby Select which gem to install for your platform (i386-mswin32) 1. sqlite3-ruby 1.2.4 (ruby) 2. sqlite3-ruby 1.2.3 (x86-mingw32) 3. sqlite3-ruby 1.2.3 (mswin32) 4. sqlite3-ruby 1.2.3 (ruby) 5. Skip this gem 6. Cancel installation > 3 Successfully installed sqlite3-ruby-1.2.3-mswin32 Installing ri documentation for sqlite3-ruby-1.2.3-mswin32... Installing RDoc documentation for sqlite3-ruby-1.2.3-mswin32...
C:\>gem install rails Successfully installed rails-2.2.2 1 gem installed
C:\>rails -v Rails 2.2.2
Let’s mimic the first couple of minutes of the famous Creating a weblog in 15 minutes video to test things out.
C:\dev>rails blog create create app/controllers create app/helpers create app/models create app/views/layouts [...]
C:\dev\blog>ruby script\server -> Booting WEBrick... -> Rails 2.2.2 application started on http://localhost:3000 -> Ctrl-C to shutdown server; call with --help for options [2009-03-14 13:55:45] INFO WEBrick 1.3.1 [2009-03-14 13:55:45] INFO ruby 1.8.6 (2007-03-13) [i386-mswin32] [2009-03-14 13:55:45] INFO WEBrick::HTTPServer#start pid-192 port-3000
C:\dev\blog>ruby script\generate scaffold Post title:string body:text exists app/models exists app/controllers exists app/helpers create app/views/posts [...] dependency model exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/post.rb create test/unit/post_test.rb create test/fixtures/posts.yml create db/migrate create db/migrate/20090315055640_create_posts.rb
C:\dev\blog>rake db:migrate (in C:\dev\blog) == CreatePosts: migrating ====================================================== -- create_table(:posts) -> 0.0940s == CreatePosts: migrated (0.0940s) =============================================
There you have it. Go ahead and finish the rest of the Creating a weblog in 15 minutes video and leave me a comment if you encounter any trouble.
Coming Soon! Git on Windows.
For my part-time gig I needed an elegent way to nudge an absent minded customer of his past due account balance. I typically invoice via an emailed PDF which I thought would be cool because one day I could create a “second notice” version by placing an appropriate watermark behind the original invoice. That at least was the plan…
Well, after months of forgetting to play with this idea, or playing with it on and off, I stumbled upon this. Now, thanks to Colin, and especially the creator(s) of pdftk the process of creating my second notice invoices is a simple as this:
pdftk invoice.pdf background notice.pdf output notice_invoice.pdf
What’s even better is that pdftk is available for Linux, OS X, FreeBSD, Solaris, and Windoze.
Greetings from Ruby Code Camp in Columbus, OH and thanks to the Columbus Ruby Brigade for spreading the word and to all the sponsors, especially Agile Consulting and EdgeCase. Here’s what I’ve learned…
Beside many other brilliant things he said, Rob Stevenson demonstrated the following (figurative – not literal) gem that will display all methods to which an object will respond:
>> 1.methods.each { |i| printf "Method name: %s\n", i.to_s } Method name: % Method name: inspect Method name: << Method name: singleton_method_added Method name: & Method name: clone Method name: >> Method name: method Method name: round Method name: rpower Method name: public_methods ...
As Rob Biedenharn was setting up for the next session he had the contents of his .irbrc file which among other things contained the following:
IRB.conf[:PROMPT_MODE] = :SIMPLE IRB.conf[:AUTO_INDENT] = true require 'rubygems' def clear system "clear" end alias :cls :clear
Setting the irb prompt to simple removes everything but the two “greater than” characters. The auto indent feature indents your input automatically (duh!) according to Ruby code conventions which is something Jim Weirich apparently didn’t know and although I would love to be able to tell folks that I taught Jim something about Ruby i only learned it by googling it two minutes prior to showing Jim. Since I use rubygems a lot I add those in by default, and since I like to keep my screen clean so I stole, eh…, I mean re-used Rob’s “clean” method setting an alias for called “cls”. I combined this knowledge to create the following means to output a sorted list of all methods to which a given object will respond:
def pretty_methods obj obj.methods.sort.each { |i| printf "%s\n", i.to_s } end alias :pm :pretty_methods
During the second hour of Ruby Introduction taught by Ron McCamish and coded by Rob Biedenharn I learned a new thing or three as the case may be about quickly getting at regular expression match data:
irb >> b = /v(olly)?ball/ => /v(olly)?ball/ >> str = "My vollyball is Tachikara" => "My vollyball is Tachikara" >> str =~ b => 3 >> $` => "My " >> $& => "vollyball" >> $' => " is Tachikara"
Finally, I learned that I need to better understand closures, the difference between “detect” and “select”, and that I will never be able to code as well as Jim Weirich.
In the midst of my Rails based Web development I was tasked with the creation of a simple, one page Web form. I knew it would be a snap to write given my background in PHP and the broad support for PHP in OS X but I decided to “teach myself” how I could do the same thing in Ruby without the crutch of Rails.
The first step was to create the Web form with static HTML which took about 15 minutes, start to finish. I configured the form action to point to my Ruby script that would simply echo “Thanks!”.
Clicking the “Submit” button on my form produced the “Opening form_handler.rb” window (Windows readers your mileage may vary from this point forward) meaning that Apache did not know what to do with files with “.rb” extensions. A quick visit to /etc/httpd/httpd.conf to add the following line beneath the other AddHandler options did the trick.
AddHandler cgi-script .rb
After restarting Apache, and clicking the “Submit” button again I was greeted with a 500 error. A peek at /var/log/httpd/error_log revealed the following:
Options ExecCGI is off in this directory: /Users/sjobs/Sites/cgi-bin/form_handler.rb
That’s easy enough to fix. Placing a .htaccess file containing the following line resolved that issue:
Options ExecCGI
Finally, with no special modification to Apache included out-of-the-box with OS X except those changes noted above my Ruby CGI script was performing as expected. Total time invested: 30 minutes. Not bad. The basic script, which just chunks out the form values and some other stuff appears below. An excellent reference to Ruby’s CGI library is, of course, located in RDoc.
#!/usr/bin/env ruby require 'cgi' cgi = CGI.new("html4") params = cgi.params cgi.out() do cgi.html() do cgi.head{ cgi.title{"TITLE"} } + cgi.body() do cgi.pre() do CGI::escapeHTML( "params: " + cgi.params.inspect + "\n" + "cookies: " + cgi.cookies.inspect + "\n" + ENV.collect() do |key, value| key + " --> " + value + "\n" end.join("") ) end end end end
The “Rails” components of the website were completed in about 20 minutes and consist of nothing more than a form which provides the user a means to supply a date and returns an appropriate (some would argue) list of the deceased.
After quite a bit of experimentation and refactoring I have refined the script which parses Wikipedia’s Persondata from the XML dump to the following:
#!/usr/bin/env ruby require 'ParseDate' fp = "/Users/sjobs/data/enwiki-20061130-pages-articles.xml" page_start = /\ / page_end = /\<\/page\>/ has_persondata = /\{\{Persondata/ regex_name = /\|NAME=(.*)/ regex_page_title = /\(.*)\<\/title\>/ regex_page_id = /\(.*)\<\/id\>/ regex_date_death = /\|DATE OF DEATH=(.*)/ def cleanse data wiki_marks = /\[\[|\]\]|\{\{|\}\}/ clean = data.strip.gsub(wiki_marks, '') clean.gsub(/'/, "\\\\'") end def process_date date if !date.nil? date = ParseDate.parsedate(date) if !date[0].nil? and !date[1].nil? and !date[2].nil? sprintf("%04d-%02d-%02d", date[0], date[1], date[2]) else nil end end end File.exists? fp and File.readable? fp File.open(fp, "r") do |file| i = 0 concat = false page_text = '' while line = file.gets if !page_start.match(line).nil? or concat concat = true page_text = page_text + line if !page_end.match(line).nil? if !has_persondata.match(page_text).nil? i = i + 1 date_death = regex_date_death.match(page_text)[1] unless ↵ regex_date_death.match(page_text).nil? name = regex_name.match(page_text)[1] unless ↵ regex_name.match(page_text).nil? page_title = regex_page_title.match(page_text)[1] unless ↵ regex_page_title.match(page_text).nil? page_id = regex_page_id.match(page_text)[1] unless ↵ regex_page_id.match(page_text).nil? date_of_death = process_date(cleanse(date_death)) if !date_of_death.nil? puts "- !ruby/object:Person" puts " attributes:" puts " date_of_death: " + date_of_death unless date_of_death.nil? puts " name: " + cleanse(name) unless name.nil? puts " page_title: " + cleanse(page_title) unless page_title.nil? puts " page_id: " + cleanse(page_id) unless page_id.nil? puts " id: " + i.to_s end end concat = false page_text = '' end end end end
Much of the data I was parsing is no longer being collected as I am re-evaluating the schema since much of the data within the Wikipedia database is void of any consistent form (as expected).
The keen observer will note that this script generates YaML which I import painlessly via Geoffrey Grosenbach’s ar_fixtures plugin for Rails.
The next problem to solve is the parse the free-form date to be entered by the user. I had this working using Ruby’s ParseDate but I’m unable to reliably create a date that will then be feed to MySQL or generate a nil value. I am passing the output from ParseDate’s sole method, parsedate, to the new method of Ruby’s Date class but after working for a while the method is now returning an “invalid date” error. More later…
OK, need proof that I’m a nuby? How’s this for proof?
#!/usr/bin/env ruby fp = "/Users/sjobs/data/enwiki-20061130-pages-articles.xml" page_start = /\ / page_end = /\<\/page\>/ has_persondata = /\{\{Persondata/ regex_name = /\|NAME=(.*)/ regex_alter_names = /\|ALTERNATIVE NAMES=(.*)/ regex_description = /\|SHORT DESCRIPTION=(.*)/ regex_page_title = /\(.*)\<\/title\>/ regex_page_id = /\(.*)\<\/id\>/ regex_date_birth = /\|DATE OF BIRTH=(.*)/ regex_place_birth = /\|PLACE OF BIRTH=(.*)/ regex_date_death = /\|DATE OF DEATH=(.*)/ regex_place_death = /\|PLACE OF DEATH=(.*)/ regex_revision_timestamp = ↵ /\.*\(.*)\<\/timestamp\>.*\<\/revision\>/m File.exists? fp and File.readable? fp File.open(fp, "r") do |file| concat = false page_text = '' while line = file.gets if !page_start.match(line).nil? or concat concat = true page_text = page_text + line if !page_end.match(line).nil? if !has_persondata.match(page_text).nil? name = regex_name.match(page_text)[1] unless ↵ regex_name.match(page_text).nil? alternative_names = regex_alter_names.match(page_text)[1] unless ↵ regex_alter_names.match(page_text).nil? description = regex_description.match(page_text)[1] unless ↵ regex_description.match(page_text).nil? page_title = regex_page_title.match(page_text)[11] unless ↵ regex_page_title.match(page_text).nil? page_id = regex_page_id.match(page_text)[1] unless ↵ regex_page_id.match(page_text).nil? date_birth = regex_date_birth.match(page_text)[1] unless ↵ regex_date_birth.match(page_text).nil? place_birth = regex_place_birth.match(page_text)[1] unless ↵ regex_place_birth.match(page_text).nil? date_death = regex_date_death.match(page_text)[1] unless ↵ regex_date_death.match(page_text).nil? place_death = regex_place_death.match(page_text)[1] unless ↵ regex_place_death.match(page_text).nil? revision_timestamp = regex_revision_timestamp.match(page_text)[1] unless ↵ regex_revision_timestamp.match(page_text).nil? end concat = false page_text = '' end end end end
I decided that STX would require too significant an investment in time and while I may integrate hpricot at some point the solution above seems sufficient for the moment.
I have yet to begin importing any of the data into the database. I’ll want to format the data consistently and remove the wiki specific mark-up. Feel free to rip the code apart and please do make suggestions. I am after all a nuby.
I became interested in hpricot while experimenting during the creation of a prospecting system for my employer. Driving home from work one day I hatched an idea for a website that would employ hpricot to pull its data from Wikipedia’s Persondata.
Due to the extremely large size of the associated data file I soon determined that an hpricot-based solution would not scale. Digging a bit on Wikipedia I discovered STX which appears to be a more promising means of proceeding. I’ll keep you posted.