What's New in Edge Rails: ActiveResource Gets Custom Methods

Posted by ryan
at 5:51 PM on Thursday, April 26, 2007

ActiveResource got a pretty major update today with the ability to invoke custom methods without having to go rooting around to manually build the invocation URI. This feature is close to my heart not only because of my affinity for ARes, but also because this patch was one I submitted as part of the Rails Hackfest. There was initially a lot of back and forth regarding the implementation and I didn’t know if it would ever make it in. Thankfully, it has. But enough about me, let’s talk about you.

So say you’ve got your RESTable web service humming along nicely. It has your standard compliment of CRUD based actions for a Person resource, along with a few custom element methods like promote and deactivate and a custom collection method, sort. Without custom method support in ARes, you would have to manually build the URI you want to call and then start man-handling the underlying http connection to actually invoke the method call. Yeah, not cool. Enter custom method support.

Let’s assume a remote service that supports the following:

1
2
3
4
5
6
# Collection custom method
GET /people/sorted.xml?by=sort_field

# Element custom methods
PUT /people/1/promoted.xml?position=position_name
DELETE /people/1/deactivated.xml

And we’ve got our bare-bones ARes Person class:

1
2
3
class Person < ActiveResource::Base
  self.site = "http://mypeeps.com/"
end

The syntax for invoking a custom method in ARes is pretty simple. Just call one of get, post, put, or delete on either the class (for collection custom methods) or the instance (for element custom methods) that matches the Http method you want to use. Pass in the name of the custom method to invoke as the first argument with any parameters that need to be passed along as the second. That description sounded like poo so let me use an example to get the point across a little better.

Since we’ve got a well-behaved remote REST service, we can do the following:

ActiveResource Collection Custom Method Calls

A collection call is one that is invoked against the collection of resources. An easier way to think of this is any call that is not operating on an existing resource is a collection call. They’re usually pretty easy to spot as their URI does not reference a resource id.

A custom method call is one that is not one of the default CRUD actions that you get out of the box with RESTful routing. In our example we’ve got a collection custom method called sorted that returns the resources sorted by a particular field (In my original example this was called sort, but after a recommendation by DHH, was changed to the adjective form sorted. You’ll see that this makes for much more readable, sentence-like URIs and ARes invocations.)

The remote service supports this URI:

1
2
# Collection custom method
GET /people/sorted.xml?by=sort_field

And this is how you invoke it from ARes:

1
2
3
# Get all people sorted by first name
people = Person.get(:sorted, :by => 'first_name') # =>
   # GET /people/sorted.xml?by=first_name

If your company has hit hard times, and you’ve got a layoff REST action supported by the remote service that expects the put method your invocation would look like this:

1
2
# Fire everybody
Person.put(:layoff) # => PUT /people/layoff.xml

I hope by now it’s clear that you invoke the Http method you want to call the remote service with on your ARes class, and pass in the name of the custom method as the first argument. Quite simple. Custom methods called against the class are known to be collection methods (Since it’s not referencing an in existence resource) and so the URI is properly built for the resource collection.

ActiveResource Element Custom Method Calls

If you are manipulating a specific resource, you are invoking an element call. The only real differentiation in ARes between a collection and element custom method is that with the element call, you’re operating on an instance of the ARes model. The URIs that are built are then appropriate for a specific resource. Onto our example.

The remote service supports this URI:

1
2
# Element custom method to promote a person to specific position
PUT /people/1/promoted.xml?position=position_name

And this is how you invoke it from ARes:

1
2
3
4
# Promote our star developer
ryan = Person.find(1)
ryan.put(:promoted, :position => 'Manager')  # Is this really a promotion?
    # PUT /people/1/promoted.xml?position=Manager

The remote service expects the promoted call to be used with the PUT http method, so all you do is invoke put on the instance of Person that you want to promote and the parameters hash to send along with it. Similarly, for a delete:

On the server…


DELETE /people/1/deactivated.xml

And in ARes…

1
2
3
# He turned out to not be so hot
ryan = Person.find(1)
ryan.delete(:deactivated)  # DELETE /people/1/deactivate.xml

Other Bits

One thing to note is that all custom method calls except get return the remote service response, from which you can check out the response codes and spin your own response handling. get returns a hash (or array of hashes for multiple resources) representing the XML data that was returned in the body of the response. If you want to get actual objects back from a get call, you can use the find method and pass in a symbol:

1
2
3
4
# Get all managers
Person.get(:managers) #=> [{:name => "Ryan"}, {:name => "David"}]
Person.find(:managers) #=> <#Person...><#Person ...>
   #=> GET /people/managers.xml

And there you have it, custom methods with ActiveResource.

tags: ruby, rubyonrails, REST, activeresource

ActiveResource Available as a Gem 9

Posted by ryan
at 9:30 AM on Thursday, April 26, 2007

In the past, whenever I’ve spoken to a group about ActiveResource, one of the questions that regularly comes up is if ActiveResource is available as a gem. Trying to install it from the svn tree is a little less than straight forward. With ARes now scheduled to be a part of Rails it will be installable as a gem. For now, though, prior to the official Rails release you can get ARes installed with:


gem install activeresource --source http://gems.rubyonrails.org

(Thanks to DHH for pointing this out in my last post)

When Rails is released you will be able to use the more conventional:


gem install activeresource

tags: ruby, rubyonrails, REST

What I'll Be Attending at RailsConf

Posted by ryan
at 8:46 PM on Wednesday, April 25, 2007

It’s interesting to see that, from a non-scientific summary of the sessions available at rails conf, scalability and REST are the topics du jour for the Rails community.

Here’s what I think I’ll be attending come May 18th, what about you?

Ping me over at conferencemeetup.com if you want to say hello, or share a brew, during the aforementioned festivities.

rubyonrails, REST, railsconf2007

What's New in Edge Rails: render Now 70% More Betterer

Posted by ryan
at 9:30 AM on Wednesday, April 25, 2007

If you’re anything like me, one of the things that really spins your propeller hat is trying to reduce your code into the least amount of lines possible, while still being completely legible. If you’re not like me, well, ya should be.

To that end, calls to render now have some new possibilities. The first is the ability to specify the Location header value inline with the render call. This is often used when building RESTful services – when an item is created the URI of that new item needs to be sent back in the Location response header:

1
2
3
4
5
@contact = Contact.create(params[:contact])
respond_to do |wants|
  wants.xml { render :xml => @contact.to_xml, :status => :created,
                     :location => contact_url(@contact) }
end

Compare this to what you had to do before, which was manually set the location header prior to calling render:


response.headers["Location"] = contact_url(@contact)

And to compress our render line a little bit more, the to_xml conversion on your model is a bit redundant right? I mean, we’re already telling it to render :xml, shouldn’t it know how to convert the model to render as xml? Yeah, it should, and it does now:


render :xml => @contact, :status => :created, :location => contact_url(@contact)

See how we lost the unnecessary to_xml there? If your model supports to_xml (which all ActiveRecord models do), render :xml will automatically convert it to xml. Ka-boo-ya, 7 characters gone.

tags: ruby, rubyonrails, REST

What's New in Edge Rails: ActiveResource Becomes a First Class Citizen

Posted by ryan
at 8:54 PM on Monday, April 23, 2007

Despite the previous allusions to the fact that ActiveResource might never be an official part of Rails, it now looks like it will. The edge Rails source tree has been updated to auto-load the ActiveResource framework in environment.rb (and does so at the expense of Action Web Service, no less).

There have been some minor updates to ActiveResource in the last few days by DHH himself, so it appears that the framework has been getting some TLC. This is a good thing as ARes is a great little framework that just needs a little nudge into the lime-light. 37 Signals’ Highrise might just be that nudge.

tags: ruby, rubyonrails, REST

What's New in Edge Rails: A More Flexible to_xml

Posted by ryan
at 10:26 AM on Friday, April 13, 2007

The to_xml method on your ActiveRecord classes has a bunch ‘o options that let you refine how you want your models to be serialized to xml. You’ve got your :only and :except options which let you filter in/out certain attributes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
user = User.find(1)

user.to_xml(:except => [:id, :created_at])
#=>
# <user>
#   <name>Ryan</name>
#   <email>ryan@spamme.com</email>
# </user>

user.to_xml(:only => [:email])
#=>
# <user>
#   <email>ryan@spamme.com</email>
# </user>

(Want to have the result of methods serialized along side member attributes? Take a look at the :methods option).

You can also include first-level associations with :include:

1
2
3
4
5
6
7
8
9
user.to_xml(:except => [:id, :created_at], :include => :posts)
#=>
# <user>
#   <name>Ryan</name>
#   <email>ryan@spamme.com</email>
#   <posts>
#     <post><title>What's New in Edge Rails</title></post>
#   </posts>
# </user>

You can also manipulate the XML builder directly as to_xml now yields the builder, allowing you to add arbitrary elements to the resulting serialized XML representation:

1
2
3
4
5
6
7
8
9
user.to_xml(:except => [:id, :created_at]) do |xml|
  xml.serialize_version 1.1
end
#=>
# <user>
#   <name>Ryan</name>
#   <email>ryan@spamme.com</email>
#   <serialize_version>1.1</serialize_version>
# </user>

Note that the xml builder received in the block already contains the standard constructed XML representation – but you can now add to that with custom elements and other constructs. Just another easy way to massage the serialization to suit your needs.

tags: ruby, rubyonrails

Get Involved in the 2007 Ruby Hoedown

Posted by ryan
at 2:58 PM on Tuesday, April 03, 2007

It’s official, the 2007 Ruby Hoedown is in full swing now. The venue and dates are set and we’ve already made our call for proposals. Now starts the heavy lifting – heavy lifting best shared amongst the community. If you’re the philanthropic type, or even if you’re not, we’d love for you to volunteer to review the proposals we’ll be getting for the conference. Besides contributing to a worthy cause, members of the program committee will get free registration to the event as well.

So, if you’re interested in helping us review the many worthy proposals that will begin filtering in shortly, drop me a line at proposals [at] rubyhoedown.com. Thanks in advance!