Monday, January 21, 2008

STI Views Revisited or Polymorphic Paths

In my previous post I lamented the strange and inconsistent behavior of the the automatically generated view for a simple STI hierarchy. After many searches and several futile attempts to explain myself on the #rubyonrails IRC chat, I posted my question (slightly simplified) to the Ruby on Rails: Core list.

Mislav Marohnić was kind enough to shed light on my problem. He said (edited for context):

When you say link_to(text, record), a polymorphic url is constructed depending on the model. But when you say edit_parent_path, you are explicitly stating that you are dealing with Parent records and that no polymorphism should take place.

I understand that when using STI you have a need for a polymorphic helper, but I don't think changing the behavior of named routes (explicit or automatically generated) is a solution - it would just be the source of more confusion for others.

Your cup of tea would be the edit_polymorphic_path helper...
Essentially, I just need to change:
link_to 'Edit', edit_parent_path(parent)
to
link_to 'Edit', edit_polymorphic_path(parent)
and edit_polymorphic_path() will do the correct thing and generate the path based on the concrete record type.

Problem solved! Thank you Mislav!

On a side note, edit_polymorphic_path() used to be part of the simply_helpful plugin, but had been merged into core Rails more than 8 months ago. However it had not been documented, and my post seems to have sparked the documentation and exposure of this module and its kin. Excellent! My minor and indirect contribution to Rails. ... and, you won't read this in any Rails book!

If you found this post helpful, or you have more thought on this subject, please leave a message in the comments. If you want to read more about my exploration of polymorphism and STI on Rails, subscribe to the blog feed and stay updated.

Friday, January 11, 2008

STI Experiment 1 - Scaffolded STI Sub-Classes

I was having problem listing STI related objects from the same table.
Here are my experiment with trying to work through the problems.

My first experiment will try to create 2 completely scaffolded models, create a simple STI hierarchy between them and see how Rails handles it.

At the command line:

ruby script\generate scaffold Parent name:string description:text
rake db:migrate
ruby script\generate scaffold Son member:string
Make 2 changes to the generated code:
  1. Change the migration from creating a new sons table to adding 2 new columns to the parents table:
    • add_column :parents, :member, :string
    • add_column :parents, :type, :string # needed for STI
  2. Make Son (in app/models/son.rb) derive from Parent (instead of ActiveRecord::Base)
rake db:migrate again, and add a few Parents and a Son (through the console).

Result
At URL /parents all Parents and Sons are listed, since they are all saved in the same table.
This is the desired behavior which would be expected with an is-a relationship between Sons and Parents. (Note that I use parent and son names in the parent i.e. base class and son i.e. derived class meaning and not in the genealogical sense)
This view is generated by the scaffold generator script, and is actually app\views\parents\index.html.erb.

Since Sons are derived but different types from Parents it might be expected that the Show, Edit and Destroy links for actual concrete Sons will link to the (automatically generated) Son controller and the not to the Parent controller. This would essentially be equivalent to calling virtual methods.

This actually happens for the Show and Destroy links. However, the Edit link still redirects to the Parent edit page. The problem apparently stems from the Parent index view:
<%= link_to 'Show', parent %>
<%= link_to 'Edit', edit_parent_path(parent) %>
<%= link_to 'Destroy', parent, :confirm => 'Are you sure?', :method => :delete %>
the edit_parent_path(parent) seems to be not as smart as link_to in detecting the STI hierarchy on the parent object.

I don't know how to bypass this at the moment.
I will update this post if and when I have a working solution.

If you found this post helpful, or you can shed some light on this problem, please leave a message in the comments. If you want to read more about my exploration of polymorphism and STI on Rails, subscribe to the blog feed and stay updated.

Wednesday, January 9, 2008

Single Table Inheritance in Rails

I've been experimenting with Single Table Inheritance or STI.
STI documentation is rather sparse, distributed, confusing and lacking so I hope to make some sense out of it.

The Wiki describes Single Table Inheritance like this:

Single Table Inheritance represents an inheritance hierarchy of classes as a single table that has columns for all the fields of the various classes.
Martin Fowler explains:
Relational databases don't support inheritance, so when mapping from objects to databases we have to consider how to represent our nice inheritance structures in relational tables. ... Single Table Inheritance maps all fields of all classes of an inheritance structure into a single table.
What this means, is that at the DB table level all records (objects) from all levels of the type hierarchy (classes) have all the fields (members) of all the other classes (and direct access to them). In other words, the table fields are the union of all the members of all the classes in the hierarchy (plus one special field called :type).
Due to this fact, obviously, two classes in the hierarchy cannot have two differently typed members with the same name.

Interestingly enough, this kind of inheritance actually goes against some of OOP's main tennets such as, data decoupling, data encapsulation/hiding, access control and separation of concerns.

These issues notwithstanding, STI can still be useful.

To build your hierarchy, say by sub-classing from Person, generate a migration script:
>>ruby script/generate migration AddColumnsForPersonHierarchy
then in the resulting migration file add all the additional columns you need for any or all the classes of your hierarchy. Additionally, add the special string column, conventionally called :type. Rails will automatically fill :type with the actual class name.

Then, add your class models by giving each class a separate file, e.g. create a new file app/models/employee.rb:
class Employee < Person 
validates_presence_of :department
end
You could do it all in one model file, as shown in various example on the web, (e.g. in app/models/person.rb) but that creates lookup problems for Rails, so that Employee will not be found before Person is first used.

For improving class data consistency we can, inside our class models, validates_presence_of any fields that are relevant just for this class, as shown in the example above. Other classes can accept nil.

In the next post in this series I will explore the how to add controllers to our STI hierarchy. I will also investigate how far Rails can allow true polymorphism with STI.

If you found this post helpful, please leave a message in the comments. If you want to read more about my exploration of polymorphism and STI on Rails, subscribe to the blog feed and you won't miss a thing.

Tuesday, January 8, 2008

Instant Rails 2.0

Instant Rails 2.0 was recently released.
It contains the following major changes:

  • Upgrades Ruby to version 1.8.6 Patch Level 111
  • Upgrades Rails to 2.0.2
  • Upgrades Mongrel to 1.1.2
  • Upgrades RubyGems to 1.0.1
  • Upgrades Rake to 0.8.1
  • Upgraded Cookbook app to support Rails 2.0.2
  • Added SQLite3 Support
  • Added Capistrano 2.1.0
You can download Instant Rails here and read the complete release notes here.
I have not tried it yet, but will probably do so soon.

Monday, December 31, 2007

Rails Editor - Take 2

RoRED is "a free Ruby on Rails Editor for Win32". After some tweaking of the options, I find it very pleasant to work with. It still has a few rough corners but free is a hard price to beat, and it is a decent piece of work. Do give it a try.

Also, my current favorite programming font is Droid Sans Mono. It comes from the set of fonts to be used by the Android mobile operating system. It looks great in small sizes especially with ClearType.

Saturday, December 22, 2007

AutoTest with InstantRails on Windows

Testing you app is important. A useful and recommended plugin is the autotest plugin. Here's what is says in the box:

  • Improves feedback by running tests continuously.
  • Continually runs tests based on files you've changed.
  • Get feedback as soon as you save. Keeps you in your editor allowing you to get stuff done faster.
  • Focuses on running previous failures until you've fixed them.
Cool. To get it type:
>>gem install ZenTest
to run cd into your app dir and type:
>>autotest -rails
Simple, isn't it?

Well... not really.
When I did this I got a whole bunch of error messages:
.../InstantRails/ruby/lib/ruby/gems/1.8/gems/ZenTest-3.6.1/lib/autotest.rb:435:in `expand_path': couldn't find HOME environment -- expanding `~/.autotest' (ArgumentError)
from .../InstantRails/ruby/lib/ruby/gems/1.8/gems/ZenTest-3.6.1/lib/autotest.rb:435
from .../InstantRails/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require'
from .../InstantRails/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'
from .../InstantRails/ruby/lib/ruby/gems/1.8/gems/ZenTest-3.6.1/bin/autotest:35
from .../InstantRails/ruby/bin/autotest:19:in `load'
from .../InstantRails/ruby/bin/autotest:19
Turns out that it's looking for an environment variable called HOME. From my tests and from what I read you can put any valid path there. I just used the local path.
Actually, I just set the variable before running autotest.
>>set HOME=.
>>autotest -rails
However, a fresh new list of errors are now generated, the gist of them is this:
.../InstantRails/ruby/lib/ruby/gems/1.8/gems/ZenTest-3.6.1/lib/unit_diff.rb:187:in ''': No such file or directory - diff.exe -u D:/temp/expect.1480.0 D:/temp/butwas.1480.0n (Errno::ENOENT)
1. from .../InstantRails/ruby/lib/ruby/gems/1.8/gems/ZenTest-3.6.1/lib/unit_diff.rb:187:in 'unit_diff'
...
Basically, it's complaining that it cannot find diff.exe. Of course, most (all?) Unix systems have this installed but my Windows machine with InstantRails does not, so therein lies the problem. This post explains it simply. Go here to get diff.exe for Windows.

Once installed (or unzipped), just set your path to wherever you put it, and run autotest. e.g.:
>>set HOME=.
>>set path=%path%;"C:\Program Files\GnuWin32\bin\"
>>autotest -rails
Right? Wrong.
diff.exe is still not found. For some reason it's not enough to set the environment like this.

You will need to set it in the Windows settings (Start-> Settings-> Control Panel-> System-> Advanced-> Environment Variables...) and change the PATH variable to include the diff.exe path.

Now you can begin enjoying the joys of autotest.

If you found this post helpful, please leave a message in the comments. If you want to hear more about my adventures on the Rails, subscribe to the blog feed and you won't miss a thing.

Thursday, December 20, 2007

Testing Headaches

I was planning some additional posts before this one, but this really got on my nerves.

Rails really wants to make it easy for you to test your programs. It provides a whole framework for testing. That's great and commendable.

So, I finally took the plunge and started my project. I built some models, controllers and views using Rails 2.0's spiffy new RESTful scaffold generator, added user login management using the restful_authentication plugin (more on this in a later post), and tried running all the generated tests.

Errors, failures and what not, and I haven't written any of these tests, they were all generated by the generators!

The first problem was that all the functional tests should login before the test is run. OK, that makes sense, the generator couldn't know I was behind the authentication "wall". Fine, but the restful_authentication plugin's documentation is practically non-existent.
After searching around I found several forum posts that point to the login_as helper method. Just stick it in your setup method, they say, and you're home free:

def setup
login_as :quentin
end
the rest of the test should work as before (:quentin and :aaron are the automatically generated users, they apparently work for example.com).
However, no such thing happened.

After, endless trials and errors, detours and hacks, I tried adding login_as to each method and not through setup. Suddenly some of the tests passed without a problem. Those that didn't failed due to some other strange reason.
Now, remember, these are all auto-generated tests!

Anyway, it seemed there was some problems accessing a model with :id of 1. I checked the automatically generated fixtures, and, indeed, :id was not set! Didn't anyone test this Rails release?! This is the newest public release of Rails.

So, I added the :ids and lo and behold, most of the tests now passed.
I later discovered that one of the fixes done in Rails 2.0.2 were:
  • Fixed that functional tests generated for scaffolds should use fixture calls instead of hard-coded IDs
scroll down, it's the forth item from the end. Of course, this doesn't help me, since I scaffolded using ver. 2.0.1. Arghhhh!

By adding the mandatory record fields in the test_should_create_* tests, I got all the test to run properly. Being a rookie this also took me a while to figure out thanks to the "helpful" error messages.

What still bugged me was why I couldn't use the setup method. After some serious and associative digging I found this little post:
Rails 2.0 introduces a new (better) way to create your functional test cases. Instead of subclassing Test::Unit::TestCase directly, you can now subclass ActionController::TestCase, which will take care of the setting up the request/response environment for you.
...
And therein lies the gotcha: if you go ahead and create your own setup method not realizing that you’ve inherited one, you’ll clobber the one defined in the superclass. So, when you’re defining your own setup method, remember to call the original one using super...
so when I changed my setup method to look like this:
def setup
super
login_as :quentin
end
everything suddenly worked.

Now, it took Jeffrey Allan Hardy 15 minutes to figure this out and he wrote a book about Rails and is a partner at a boutique Rails consulting shop (whatever that means). It took me hours!
He updated his post to mention that this "gotcha" has been fixed on the Rails Trac (the Rails source repository) but that isn't much consolation nor use to me.

All my tests now pass and I'm going to sleep.

If you found this post helpful, please leave a message in the comments.
If you want to hear more about my mis-adventures with Rails, subscribe to the blog feed and you just might not have to go through it yourself.

Monday, December 17, 2007

Editor for Rails

Many Rails developers use TextMate (€39) for developing Rails apps. TextMate seems like a very powerful editor with auto-completion bordering on the telepathic. You can see TextMate in action in many Rails screencasts, for example on the excellent Rails casts at Railscasts.
I say seems because TextMate is only available for Macs and I'm a Windows user.

E-TextEditor ($34.95) is promoted as "The Power of TextMate on Windows", and it's pretty nice, though its still a bit rough in my opinion. You can get the look of TextMate with some real pretty color schemes.

If you really want to go for the TextMate look, you can even download the Monaco font (via WebDevKungfu), though some symbols are sometimes rendered over each other.
Also, my Win2K system doesn't do any ClearType font rendering so it also doesn't look as good in small sizes.

I suggest you stick with Courier, Lucida Console or Bitstream Vera Sans Mono. If there is interest I might write up a post about mono-spaced programming fonts.

Another useful editor is Notepad++. This is an Open Source project and it is pretty decent. Do give it a try. Although it supports Ruby syntax highlighting it does not recognize Rails extensions like .rhtml.

That said, neither Notepad++ nor E support the new Rails 2.0 file extensions like .erb files (which are really just .rhtml files).

If you found this post helpful, please leave a message in the comments.
If you want to hear more about my Rails trials and tribulations, subscribe to the blog feed and you just might be spared.

Sunday, December 16, 2007

Rails 2.0

On Dec. 7th Rails 2.0 was unleashed onto the world.
We all know that Rails is very Web 2.0, though this 2.0 is not really that kind of 2.0, it's just Rails ver. 2.0.

Anyway, there are some cool things about it and some annoying things about it.

Most of the basic tutorials and books that I used to start learning RoR are now somewhat incompatible with Rails 2.0. This is especially true for the very basic stuff when getting started.
On the other hand, Rails 2.0 makes some very basic things easier to do.

For example, before Rails 2.0, you had to write some simple but obscure MySQL commands to set up the DB, before you even got started with your app.

In Rails 2.0 you can now create the DBs directly with Rails using:

rake db:create
This is cool and consistent with the fact that the rest of the DB interaction is done with Rails (via migrations).

Upgrading to Rails 2.0 with InstantRails didn't go smoothly.
When I ran the recommended command:
gem install rails -y
The install failed with the following message:
ERROR:  While executing gem ... (Zlib::BufError)
A little Googling revealed the following suggestion:
Try this command:
gem update --system
It will update your RubyGems to 0.9.5, and you will be able to install
Rails 2.0 without problem.
and indeed this worked for me, and properly updated InstantRails to Rails 2.0.

Update: as I was writing this post (somewhat retrospectively), I noticed that Rails 2.0.2 is out.
Although it is recommended to upgrade, what worries me is that the default DB now is SQLite3 and not MySQL. This means that new projects in InstantRails will probably not run out of the box since InstantRails is shipped with MySQL and not SQLite3. Changing the DB for a new project is apparently simple, but I have not done it so I cannot vouch for that:
If you want to preconfigure your database for MySQL (or any of the other adapters), you simply do “rails -d mysql myapp” and everything is the same as before. But if you’re just playing with a new application or building a smallish internal tool, then I strongly recommend having a look at SQLite3. Thanks to the agnostic db/schema.rb, it’s as easy as changing your config/database.yml to switch from SQLite3 to MySQL (or another database) as soon as your load warrants it.
Note that to upgrade you run the same command:
gem install rails
The -y switch (which appeared before) is to "Unconditionally install the required dependent gems". For more help on gem type gem on the command line (Ruby Console Window in InstantRails). For more help on gem install type:
gem help install

If you found this post helpful, please leave a message in the comments.
If you want to hear more about a Rookie and Rails, you can subscribe to the blog feed never miss out.

Setting Up Ruby on Rails

I use Windows (2000). When I started this project a few weeks ago the recommended option for Windows was installing InstantRails.

Instant Rails is a one-stop Rails runtime solution containing Ruby, Rails, Apache, and MySQL, all pre-configured and ready to run. No installer, you simply drop it into the directory of your choice and run it. It does not modify your system environment.
That's what I installed and it seems to work just fine and dandy.

A few days ago, while trying to read about upgrading to Rails 2.0, the InstantRails web site said:
Instant Rails is no longer being maintained and has been replaced by the BitNami RubyStack.
I actually tried installing the BitNami RubyStack but the installation failed on my system.

Anyway, today that note has changed and it now reads:
Rob Bazinet has offered to take over the Instant Rails project and will soon be releasing a new version feature Rails 2.0. [Instant Rails Lives On]
Way to go Rob.

So, that's one unnecessary annoying detour I took but is hopefully now taken care of.
InstantRails does just what it says on the box, unzip it and use it.

Saturday, December 15, 2007

Rookie Indeed!

I'll come out and say it right away:
I have no web development experience.
I have no web development experience.
Zero. Nada. Zilch.
No CSS, XML, SQL or any other acronym. Yeah, I can do basic HTML stuff circa 1997, but nothing beyond basic linking. I don't know PHP, Perl, Python, JavaScript or Ruby for that matter.

There, I said it.


but... I have an itch.

I have this idea for a cool little web site, I guess you could say a Web 2.0 web application (nothing for Google to worry about).

So, says I, why not do it myself. I'm a capable programmer, and I always like to learn something new. Ruby on Rails (RoR) seems to be all the rage these days, so why not?

A professional RoR developer can probably whip it up in a few hours or less, but why should s/he have all the fun.

Thus, I shall be spending my precious 1-2 evening hours of spare time (when available), over the coming months, pursuing this worthy goal and hopefully learning something in the process.

I'll be documenting my torments, stumblings and victories here, mostly as a log for me but also perhaps to help others in similar situations.