Modules, inheritance and classes

December 12th, 2007

I can be a bit of a purist when it comes to programming. I love to have my code DRY and go to lengths to achieve that. Today I wrote a piece of code which would enable the duplication of Rails models. This in order to support some templating. Well it’s a little bit more than ’some’ as I need to copy the entire model with all associated models(and their associated models, and their …). To keep things flexible I did not wanted to loop over a couple predefined fields and do all the copying there:


copy = Project.new
p = Project.find(:first)
copy.title = p.title
copy.description = p.description
p.members.each do |member|
  copy.members.build(:name => member.name, :function => member.function)
end

This is prone to errors not to mention ugly(and a lot of dumb work).

So I figured I’d write something better. This was quickly done:


class Project < AR:B
  cattr_accessor :non_duplicatable_columns
  non_duplicatable_columns = [primary_key, 'created_at', 'updated_at','created_on', 'updated_on'] + column_names.select {|column_name| column_name =~ /_id$/}      

  def copy
    copy_columns = self.class.column_names - self.class.non_duplicatable_columns
    a = Hash.new
    copy_columns.each {|cc| a[cc] = self.send(cc) }
    clone = Project.create(a)
  end
end

But I needed this piece of functionality in several models. So what’s a good programmer to do? Refactor the code into a module of course! But there I hit some snags. The method cattr_accessor, for example, is a bit of a strange beast. It is not documented but is should be read like ‘class attribute accessor’. The method creates class instance methods. This is something different from instance methods which you usually use. Instance methods are callable on an object which is instance of a class.


p = Project.new #class instance method
p.copy #instance method

Defining class instance methods goes with the

self

keyword. It reminded me of the Java static keyword.


def self.foo
  #do something, can be called as: FullClassName.foo
end

Keep the eye on the ball here, self points to the object on which this method is called. When defining the copy method the method column_names is called but this is a class instance method from the class subclassing AR:B. So the objects looks at itself, asks who the fathering class is and calls the desired method from that class.

The method

cattr_accessor

can no longer float freely in the class definition. It needs a container as the module is read at the moment the Rails stack is initialize and at that point the module is not necessarily hooked into a class in which a cattr_accessor makes sense. Luckily the module “http://ruby-doc.org/core/classes/Module.html”:Module defines a method included which is called whenever the module is, well, included. Using this we can still define the

non_duplicatable_columns

on a per model basis.

Finalized the module looks like this(stored in lib/duplicatable.rb):


module Duplicatable

  def Duplicatable.included(base)
    base.cattr_accessor :non_duplicatable_columns
    base.non_duplicatable_columns = [base.primary_key, 'created_at', 'updated_at','created_on', 'updated_on'] + base.column_names.select {|column_name| column_name =~ /_id$/}
  end

  def copy
    copy_columns = self.class.column_names - self.class.non_duplicatable_columns
    a = Hash.new
    copy_columns.each {|cc| a[cc] = self.send(cc) }
    clone = self.class.create(a)
  end
end

And a simple include Duplicatable adds all its functionality to a model.
Why not use a super class here? That would require calling a initialize function(to set the non_duplicatable_columns) in each model with would extend this super class. Which would result in more code and thus more places where this can break.

Living on the Edge

December 10th, 2007

As a Rails developer I am still a bit wet behind the ears. Following the lead of a more senior developer I picked up the habit of developing all(that is four so far) applications in Rails Edge with the help of RSpec, Edge RSpec.

Last weekend I picked up a pet project which a colleague and I came up with. Nothing difficult and we figured we could get this done within the week. Boy was I wrong. Setting the whole thing up took me the better part of the day. Of course partially due to my own inexperience but the fact that I was using Edge for everything didn’t help.

I’ll skip all the things which went horribly wrong, this is what I _should_ have done:


$ rails new_prj
$ cd new_prj
$ piston import http://dev.rubyonrails.org/svn/rails/trunk vendor/rails
$ piston import svn://rubyforge.org/var/svn/rspec/trunk/rspec vendor/plugins/rspec
$ piston import svn://rubyforge.org/var/svn/rspec/trunk/rspec_on_rails vendor/plugins/rspec_on_rails
$ ./script/generate rspec

Don’t try to freeze rails using rake rails:freeze:edge. This leads to pain and the absence of a framework dependency causing the ./script/server command to fail.
Don’t try to use the current release of RSpec in combination with the edge version of Rails. This again leads to suffering and mysteriously failing tests.
Don’t forget that running ./script/generate rspec_model is not enough to get the entire RSpec framework to behave. It’ll work to some degree but has some crucial functionality missing. For example the ./script/spec command is missing.
Don’t expect Restful authentication to play nice with RSpec through Vaporbase’s spec files. Don’t get me wrong the guys of Vaporbase did a great job it just didn’t work for me. The class UserNotifier should be UserMailer in my instance and the protected helper methods in the sessions_controller_spec.rb are ran by rspec throwing weird errors. So these should be moved to the spec_helper.rb file.

As a final bonus, ever wanted to spec a single file?


$ ./script/spec spec/controllers/foo_controller_spec.rb --color -b

Nested namespaces in RESTful Rails

November 29th, 2007

Using RESTful Rails definitely has it advantages. The clean code was the primary reason why I switched all my projects toward REST. But I found that the nesting of resources broke down when using modules. I’d like to keep my code base nice and logically separated. For example the admin controller should reside in the app/controllers/admin/ directory. This will make your code base clearer and thus easier to maintain. And beside that it will generate pretty URL’s in which it is instantly clear where one is.

In the current project I integrated Beast and thought it was a good idea to keep this in a separate module as well. But the application I was working on required that forums only had relevance in the context of a project(there are many many different projects). So the obvious thing to do is to create a namespace nested in the projects resource.


map.resources :projects do |projects|
projects.resources :members
projects.namespace(:wiki) do |wiki_namespace|
wiki_namespace.resources :pages, :member => {:auto_save => :put}
end
end

And of course I wouldn’t be writing this post if this would work. Somehow the routes generated completely omitted the ‘wiki’ part:


new_project_page GET    /projects/:project_id/pages/new                                                        {:action=>"new", :controller=>"pages"}
formatted_new_project_page GET    /projects/:project_id/pages/new.:format                                                {:action=>"new", :controller=>"pages"}
edit_project_page GET    /projects/:project_id/pages/:id/edit                                                   {:action=>"edit", :controller=>"pages"}
formatted_edit_project_page GET    /projects/:project_id/pages/:id/edit.:format                                           {:action=>"edit", :controller=>"pages"}
auto_save_project_page PUT    /projects/:project_id/pages/:id/auto_save                                              {:action=>"auto_save", :controller=>"pages"}
formatted_auto_save_project_page PUT    /projects/:project_id/pages/:id/auto_save.:format                                      {:action=>"auto_save", :controller=>"pages"}
project_page GET    /projects/:project_id/pages/:id                                                        {:action=>"show", :controller=>"pages"}

Weird. Not to mention inconvenient. Eventually I got it to work:


map.resources :projects do |projects|
projects.resources :members
projects.resources :pages, :controller => "Wiki::Pages", :path_prefix => "/projects/:project_id/wiki", :name_prefix => "project_wiki_", :member => {:auto_save => :put}
end

The :path_prefix parameter appears to overwrite the path_prefix defined by the block defining the scope. I would have preferred to write :path_prefix => "wiki". As one can see this solution is very unbecoming. And my RSpec tests started failing… With RoutingErrors. Rails worked just fine but RSpec did not play nice with this hack(because that is what this is, a hack). The RSpec mailinglist nor the Rails mailinglist could help.

This led me to abandon testing. Yes, I know, bad idea. Which I soon found out. This time I dove in the source code and found out that the namespace method takes an argument called namespace. Fiddling around with this parameter I got to the following solution:


map.resources :projects do |projects|
projects.resources :members
projects.namespace(:wiki, :namespace => '') do |wiki_namespace|
wiki_namespace.resources :pages, :member => {:auto_save => :put}
end
end

This generated the correct routes and satisfied RSpec! Don’t ask me for the rational behind this obviously strange behavior.

attachment_fu dances with Capistrano

November 13th, 2007

It is one of these joyous revelations. Today I was having a discussion with a fellow developer on attachment_fu. I am very much a fan of plugins, this means less work for me. In which case I win. He, however, refrained from using attachment_fu. Instead writing his own file upload mechanism. This seemed strange to me and I asked him why. He then correctly pointed out that with attachment_fu he was unable to save files outside his application root directory.

So why is this a problem? Everyone I know is using Capistrano as their preferred way of deploying. The way Capistrano does subsequent deploys is by linking the new release to the current directory and unlinking the old one. Everything in the old release suddenly becomes unavailible! And that includes the files by attachment_fu. And article on Almost Effortless pointed this out several months ago and posted a solution in the form of a Capistrano recipe. But who wants to write their own recipes?

However I seemed to remember seeing a @system@ directory on our live server. And there is! In Capistrano 2.0 every public directory has a link to a system directory which is unaffected by deploys. How well these Capistrano people thought this out. Brilliant. Now it is only a matter of using the :path_prefix argument.


has_attachment :content_type => :image,
:storage => :file_system,
:path_prefix => 'public/system',
:max_size => 500.kilobytes,
:resize_to => '320x200>',
:thumbnails => { :thumb => '100x100>' }

Set svn propset svn:ignore "*" public/system/ and you are ready to go!

Acts_as_versioned quirks

November 11th, 2007

The Rails plugin acts_as_versioned by Rick Olson is a blessing when one wants to do some simple versioning. Building a wiki springs into mind which was exactly what I wanted to use it for. Following the learn_to post I was able to get most of it working. However I my case, and I imagine others, I did not want to version control my entire pages table. I do not care about the @created_at@ and @updated_at@ fields to be versioned. But using the default migration tactic of building a versioned table did not work (passing a block to the Page.create_versioned_table). Instead I needed to configure my model with:

self.non_versioned_columns << 'project_id' << 'project_user_id' << 'locked_by' << 'locked_at' << 'created_at' << 'updated_at'

Not something I would expect.

And it turned out there were more unexpected ‘features’. The inclusion of the timestamps in my Page model seemed superfluous. The generated @page_versions@ table included them by default. But there is no easy way for dropping these columns from the @pages@ table. Sure I could write a migration doing just that but by then the generated @page_versions@ table already contained these columns and apparently the generated class Page::Version referenced to these column. This obviously led to errors. Instead I ended up dropping and recreating the entire table. Unfortunately MySQL died complaining that the column @version@ already exists in the @pages@ table. You’ll have to remove that column too.

Finding a previous version has changed somewhat too. According to Rails Recipes;


>> p = Page.find(:first)
>> p = p.find_version(3)
NoMethodError: undefined method `find_version' for #<Page:0x329a5d8>

This should work but it does not. Actually I would love for it to work like that. The alternative is;


>> p = Page.find(:first)
>> array = Page.find_versions(:first)
>> p = p.revert_to(arr[2])

Not as pretty but it works.

On the bright side there is this neat feature called @:if_changed@. This allows you to control when a new version should be created. The directive is given as an argument in the acts_as_versioned declaration:

acts_as_versioned :if_changed => [:title, :text, :member_id]

Sweet now a new version is created only when the title, the text or the owner changes.

In conclusion, the acts_as_versioned plugin is a great extension but seems a bit outdated as not all functionality works(at least not in Rails 2.0RC1). Also it is better to create the documentation from the source as all the online documentation is very old.