RailsTails

Occasional encounters with Ruby on Rails.

Thursday, February 14, 2008

Come join us at the Upstate Ruby Brigade!


What little there was to find has been migrated over to the Upstate Ruby Brigade website. Please come join Ruby and Rails enthusiasts in the South Carolina Upstate over there. Hopefully we'll all be pushed through our work together.

Tuesday, February 05, 2008

Static thinking about dynamic languages


If you've approached Ruby from a background in programming with static (compiled) languages it's easy to get a lot of "static" in your thinking about Ruby. One of the most common places to lose sight of t dynamic nature of the Ruby language is in class definitions. A class definition is really a call to a method in Kernel that creates a global constant with a reference to the object that represents the class, but Ruby has so faithfully repeated the class-definition language of static languages that we forget that we're actually executing code.


Who cares if class definitions are dynamic?


The dynamic nature of class definitions in Ruby can be a big deal if you take advantage of it. Mike Clark points out a nice use of the dynamic nature of Ruby in the forthcoming Advanced Rails Recipes. The basic scenario is that you've got an ActiveRecord model that contains data that is primarily intended to be constant for the life of the application -- something like a table of states and provinces (so Clark) or menu items. Rather than look for performance improvements through caching, you could instead create a constant array on the ActiveRecord class that is populated as the class is created.



  1. class MenuItem < ActiveRecord::Base

  2. MENUS = { :start_menu=>1, ... }

  3. START_MENU_ITEMS = find(:all, :order=>"position, name", :conditions=>{:menu_id=>MENUS[:start_menu]})

  4. end



The MenuItem class above has already inherited the class-level find() method from ActiveRecord::Base we we make use of it during the class creation process. By doing this the constant data stays in memory and need not be cached. Of course, care should be taken to make sure that the amount of data stored in this constant array is not so large as to reduce performance elsewhere.



The dynamic nature of Ruby class definitions can also help to keep your class-definitions DRY. I've recently been working on a project in which a Person may have many Addresses. Simple enough. But the Addresses need to be subclassed, using single table inheritance, so that I can distinguish a home address from a work address. Oh, yeah, the Addresses also have a date range associated with them so that we can track when you plan on moving to a new Address, which in turn leads me to a need to know the current home address and work address and ... This could get very messy. But it's not if we take advantage of the dynamic nature of Ruby.



  1. class Address < ActiveRecord::Base

  2. self.inheritance_column = 'address_type'

  3. ADDRESS_TYPES = %w(HomeAddress WorkAddress SchoolAddress TemporaryAddress)

  4. belongs_to :addressable, :polymorphic=>true

  5. end


  6. Address::ADDRESS_TYPES.each do |address_type|

  7. eval "class #{address_type} < Address; end"

  8. end


  9. require 'address.rb'

  10. class Person < ActiveRecord::Base

  11. has_many :addresses, :as=>:addressable

  12. Address.subclass_names.each do |address_type|

  13. self.send(:has_one, "current_#{address_type.underscore}",

  14. :as=>:addressable,

  15. :class_name => address_type,

  16. :conditions=>['activated_on <= ? AND deactivated_on IS NULL OR deactivated_on>?', Date.today, Date.today])

  17. end

  18. end



In the code above I'm actually taking advantage of Ruby's dynamics twice. In lines 7-9 I'm iterating through each of the ADDRESS_TYPES declared in the Address class above and declaring a new subclass for each entry in the array. This may not work in every situation; certainly in most instances of subclassing you'll want to add code to the subclass. It works here, though, because the subclass name will be used in rendering (e.g., putting a nice home or work image next to the address with css).


Lines 14-18 is where I've used the dynamic nature of Ruby class declarations to keep things DRY. The Address.subclass_names method simply iterates over the subclassess of Address and returns an array of the names. This might seem overkill since ADDRESS_TYPES already has this information but it will allow me to know if classes are created in some way other than through the loop in lines 7-9. That said, lines 14-18 simply iterate over all the names of the subclasses and creates an association between person and one member of the addresses collection. Specifically, it creates an Address#current_home_address for the HomeAddress class and so on.

Friday, January 25, 2008

ActiveResource as full members of your business logic?


ActiveResource, introduced with Rails 2.x, allows Rails developers to interact with remote resources in a manner similar to ActiveRecord objects essentially bringing them into play for the business layer of the local application. Or that's what you'd hope. Out of the box, ActiveResource simply gives the developer a window into the attributes of the remote resource at the time it was requested. Associations to other resources in the remote application are left behind. So are the business rules that the resource implements. If an ActiveResource object is going to be a full participant in your local application you will need some of those associations and methods that were left behind.



Not surprisingly, you can pass along those associated objects (has_one, has_many, etc) and the results of business logic with a little bit of work. It all starts with to_xml...



Serving it up with to_xml


In Rails 2.x (if not before), ActiveRecord::Base provides a handy to_xml method to help serialize your ARec as XML. Take a look at the default controller scaffolded by Rails 2.x and you'll see something like this:



  1. def show

  2.   @person = Person.find(params[:id])


  3.   respond_to do |format|

  4.     format.html # render show.html.erb

  5.     format.xml {render :xml => @person.to_xml}

  6. end



The @person.to_xml call will iterate through all the attributes read from the database build and build an xml envelope with them.



<?xml version="1.0" encoding="UTF-8"?>

<person>

<born-on type="date">1967-11-12</born-on>

<created-at type="datetime">2008-01-23T14:10:46-05:00</created-at>

<first-name>Andrew</first-name>

<id type="integer">1</id>

<last-name>Vanasse</last-name>

<middle-name nil="true"></middle-name>< /span>

< nickname>Andy</nickname>

< updated-at type="datetime">2008-01-23T14:10:46-05:00</updated-at>

</person>


The to_xml method also accepts some very helpful parameters.



:include

Just like ARec itself, this will cause the named associations to be included in the xml serialization.

:methods

The results of executing the specified instance method on the object will be included in the serialization.


We can use those two parameters to extend our resource into something more helpful to the 'local' application. For example, if Person has_many :addresses and has an instance method called #age, then @person.to_xml(:include=>:addresses, :methods=>:age) would return the following.



<person>

<born-on type="date">1967-11-12</born-on>

<created-at type="datetime">2008-01-23T14:10:46-05:00</created-at>

<first-name>Andrew</first-name>

<id type="integer">1</id>

<last-name>Vanasse</last-name>

<middle-name nil="true"></middle-name>

<nickname>Andy</nickname>

<updated-at type="datetime">2008-01-23T14:10:46-05:00</updated-at>

<age type="integer">41</age>

<addresses type="array">

<address type="HomeAddress">

<activated-on type="date" nil="true"></activated-on>

<addressable-id type="integer">1</addressable-id>

<addressable-type>Person</addressable-type>

<city>Mytown</city>

<created-at type="datetime">2008-01-24T15:10:23-05:00</created-at>

<deactivated-on type="date" nil="true"></deactivated-on>

<id type="integer">1</id>

<postal>23456</postal>

<state>SC</state>

<street>123 Someone Other Road</street>

<updated-at type="datetime">2008-01-24T15:10:23-05:00</updated-at>

</address>

</addresses>

</person>


And then some magic happens


Having rendered the object using to_xml and included the necessary associations and instance method call results does a number of things for the remote application. First, it helps solve a form of the infamous N+1 problem. The N+1 problem would be worse over the internet, of course, where all sorts of latency issues could crop up when accessing each of the N associated objects. Second, since neither the actual object nor a proxy is returned, returning the results of an instance method call gives the remote application some important information that it could not otherwise know without reproducing the logic.


There is something very important to keep in mind when rendering with the :methods option of to_xml: you should only render objects that reflect state. You are not returning a handle on the method only the results of executing the method so you should only expose calculated values in this way.


ActiveResource, of course, was developed with all this in mind. Since the default rendering of the XML includes a 'type' attribute, ActiveResource can reflect against the XML and instantiate a local object that looks like the remote object. Because of that, not only do the attributes appear the same on both ends but the instance method results rendered via the :methods parameter react the same way as well. Moreover, if ActiveResource objects exist on the local system to match the associated objects then the rendered associations will be re-hydrated on the local system using those ARes objects. In the example above, if the local system had an Address ActiveResource (subclassed as HomeAddress) then my_person.addresses would be an array of those Address ARes objects.



Putting it all together


ActiveResource.find(:first), find(id) and find([id1, id2, ...]) all perform a get on the singular resource path. If the remote system is a Rails solution this maps to the show action. Similarly, the find(:all) natively maps to the index action. To take advantage of the way ARec and ARes play together around to_xml, then, we should modify these two actions.




  1. class PersonController < ApplicationController

  2. find_options = {:include=>:addresses}

  3. xml_options = {:include=>:addresses, :methods=>:age}


  4. # GET /people.xml

  5. def index

  6. @people = Person.find(:all, find_options)


  7. respond_to do |format|

  8. format.html # index.html.erb

  9. format.xml { render :xml => @people.to_xml(xml_options) }

  10. end

  11. end


  12. # GET /people/1.xml

  13. def show

  14. @person = Person.find(params[:id])


  15. respond_to do |format|

  16. format.html # show.html.erb

  17. format.xml { render :xml => @person.to_xml(xml_options) }

  18. end

  19. end


  20. ...


  21. end

Thursday, February 22, 2007

Nested Polymorphic Active Resources (Revisited)


Quick update on my previous post regarding Nested Polymorphic Active Resources. I found that in some cases it was really handy to have the find_polymorphic_resource method in actions other than index and create. When I tried making the call, however, it didn't work. It turns out that the logic at the end of the find_polymorphic_type_and_id of the PolymorphicResource module was flawed.

Originally the method closed with:

sections.pop

That works great for nested resources that need to know their owner for index and create because there is only one element in the sections array. It does not work at all if you're using one of the other RESTful methods like show or edit. In those cases both the owner and the child show up on the url as

GET /owner/:owner_id/child/:child_id

To work around this I've simply changed the last line of the find_polymorphic_type_and_id method to read as follows:

sections.first

The logic behind this is simple. When you have only one element in the sections array (index/create) then you return the one object -- that's the parent and the same thing returned by sections.pop. When you have a nested path (show/edit) it's the first element in the array that has the parent.

This clearly has its limitations, but they are the same limitations as those imposed by the original solution -- it only supports one level of nesting. If you wanted to support multiple levels of nesting you could probably just index the sections array more explicity. For example, the immediate parent could be found at

sections[sections.length-2]

Okay, I didn't realize that last bit was quite so simple. I was lazy and went for the quick way out. Time to go up date my module...

Friday, February 02, 2007

Nested Polymorphic Active Resources



As has been mentioned elsewhere, it'd be awfully nice for Rails' ActiveResources to deal with Polymorphic nested relationships. Huh? Say you wanted to do something like acts_as_commentable, which allows you to add comments to any object through polymorphism. How do you make that RESTful, so that you could have a nice URL like blog/123/comment/127 and another like books/987/comment/29?

To start, you've obviously got to nest the resources. For the examples urls above you would add the following to your routes.rb:


map.resources :blogs do |blog|
blog.resources :comments, :name_prefix => 'blog_'
end
map.resources :books do |book|
book.resources :comments, :name_prefix => 'book_'
end


The :name_prefix option serves two purposes. First, it makes sure that the routes don't collide. Without it the second mapping would override the first and you'd never get to the blog comments. Second, it makes it possible to have clearly named routes. If you want to generate the url for the book comments you'll use book_comments_path().

Okay, that makes creating the routes easy enough, but what about processing them from the controller. How does the Comments controller, for example, know whether to find a Book object or a Blog object when it wants to list comments? And how does it figure out which Book or Blog object to instantiate? There was a good conversation about this on RailsWeenie. The solution -- determining the 'parent' object from the mappings -- was good but but could be run through the DRYer because, chances are, if you're using polymorphism then you're using it in more than one place.

To that end I created a module so that I did not need to recreate the code every time I wanted to get polymorphic. It's not much, but it's effective.


module PolymorphicResource
def self.included(base)
base.class_eval do
private
def find_polymorphic_resource
poly_type, poly_id = find_polymorphic_type_and_id
eval("%s.find(poly_id)" % poly_type)
end

def find_polymorphic_type_and_id
# just finds instances of /controller/id
sections = request.env['REQUEST_URI'].scan(%r{/(\w+)/(\d*)})

sections.map! do |controller_name, id|
[controller_name.singularize.camelize, id]
end

sections.pop # support one-level of polymorphism
end
end
end
end


With that in place, we've got to do a little work in the controller. First, we've got to extend the controller by adding the module so we add 'require PolymorphicResource' to the top of the controller class definition. Now my controller can now find the 'parent' with a simple call to find_polymorphic_resource. For example, the comments_controller#index method could be modified like this:

def index
@commentable = find_polymorphic_resource
@comments = @commentable.comments.find(:all)
...
end


And the comments_controller#create would be

def create
@commentable = find_polymorphic_resource
@comment = @commentable.comments.build(params[:comment])
...
end


The RailsWeenie conversation mentioned above makes a good point that the only places in which you need to add the call to find_polymorphic_resource are in index and create. Everywhere else you can find the polymorphic entity through a reference to the referenced object. For example, the comments_controller#destroy method would now look like this:

def destroy
@comment = Comment.find(params[:id])
@comment.destroy

respond_to do |format|
format.html { redirect_to @comment.commentable.class.is_a?(Book) ? book_comments_url(@comment.commentable) : blog_comments_url(@comment.commentable)}
format.xml { head :ok }
end
end