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