make_resourceful: The Basics of Publish

Posted September 10, 2007

make_resourceful has been coming along nicely, especially since I’ve left Microsoft and have had time to devote to it. I imagine we might be making an 0.2.0 release within the week. By the end of September at latest.

When it comes out, I’ll be sure to do a full writeup of it, like I did for 0.1.0. For now, though, I’d like to go over one of the most-anticipated and coolest features that you’ll see: publish.

Hampton actually talked about publish in his original m_r presentation. At the time, though, it wasn’t really in working condition. Because it required a fair amount of supporting code (and because I was off at Microsoft), it remained that way until recently.

Thanks to the help of James Golick, though, we’ve built up the necessary support and publish is up and running. And it’s very, very cool.

“Nathan!” you cry, “Get to the point! What does it do?” Well, the method name is pretty self-explanatory. publish publishes your resources. XML, Yaml, JSON, you name it, you can publish in it. This is useful for all sorts of AJAXy and APIriffic purposes.

“Now hold on,” you say testily, “I could already do that in one line.” Well, sort of. I imagine this is the line you’re thinking of:

response_for(:show) { |f| f.xml { render :xml => current_object.to_xml } }

That’s a pretty nice line. Extensible and such. But it’s got a few issues. It publishes all the information you don’t want published, and none of the information you do.

As an example, take the User model for this blog1:

create_table "users", :force => true do |t|
  t.column "name",      :string,  :default => "",    :null => false
  t.column "email",     :string
  t.column "link",      :string
  t.column "pass_hash", :string
end

Calling User#to_xml gives me a nice XML representation of all the attributes of the user. But I don’t want all the attributes on display! I don’t want to let anyone look at folks’ password hashes or emails!

And what if I were publishing a post, and I wanted to publish it with its comments? I’m out of luck there, too; to_xml doesn’t handle associations. Only attributes.

But publish does. publish lets you specify exactly which attributes you want published.

To start with, here’s a pretty complicated example:

# posts_controller.rb
make_resourceful do
  publish :yaml, :xml, :attributes => [
    :id, :title, :content, {
    :comments => [
      :id, :content, {
      :user => [:name, :link]
    }]
  }]
end

That should be reasonably self-explanitory. It takes a list of formats2, followed by a keyword hash.

:attributes is the only mandatory keyword. It specifies which attributes should be published.

The syntax for :attributes is a lot more straightforward than it looks in the above example. At its most basic, it’s just a list of attributes to publish. For example:

# users_controller.rb
publish :json, :attributes => [:id, :name, :link]

Then /users/1.json might be:

{
  user: {
    name: "Nathan",
    id: 1,
    link: "nex3.leeweiz.net" 
  }
}

The syntax gets a bit trickier with associations. Since these are full-fledged ActiveRecord models, you also need to specify their attributes. You do this by using a hash as the last element of :attributes:

# users_controller.rb
publish :yaml, :attributes => [
  :id, :name, :link, {
  :comments => [:id, :content]
}]

Then /users/1.yaml could render:

--- 
user: 
  name: Nathan
  comments: 
  - id: 10
    content: |-
      *Jon:* Oops, I was getting it confused with my shortening of "rectangle." I've fixed the post.

      *zverok:* Those are both excellent ideas.
  - id: 18
    content: I've added a section on it anyway.

The attributes array for :comments is the same as that for :associations. This means that it can have its own association hash, ad infinitum. Knowing this, let’s take another look at that first example:

# posts_controller.rb
make_resourceful do
  publish :yaml, :xml, :attributes => [
    :id, :title, :content, {
    :comments => [
      :id, :content, {
      :user => [:name, :link]
    }]
  }]
end

This is just publishing the id, title, and content of a post, as well as its comments. Each comment in turn has its id, content, and user published. Finally, the user’s name and URL are published. Here’s what /posts/47.xml might look like:

<?xml version="1.0" encoding="UTF-8"?>
<post>
  <title>I Say Stuff</title>
  <id type="integer">47</id>
  <content>This is where I say stuff! Blah blah blah!</content>
  <comments>
    <comment>
      <user>
        <name>Bill</name>
        <link>http://bi.ll</link>
      </user>
      <id type="integer">72</id>
      <content>I am commenting!</content>
    </comment>
    <comment>
      <user>
        <name>Jill</name>
        <link>http://ji.ll</link>
      </user>
      <id type="integer">73</id>
      <content>I, too, comment on this post!</content>
    </comment>
  </comments>
</post>

That’s all for today. If you want to play with publish, you can grab it from trunk (svn://hamptoncatlin.com/make_resourceful/trunk), or just wait until 0.2.0 is released.

Tune in tomorrow for a brief explanation of how publish works, as well as a few useful methods that come bundled with it.

1 I’ve simplified it a little for brevity’s sake. I also keep track of whether the user is an admin, a salt for the password hash, and the user’s ip, user agent, and referrer, for spam-blocking purposes.

2 Currently XML, YAML, and JSON work out-of-the-box; more on that tomorrow.

Don Petersen said September 10, 2007:

I have to say I’m very very excited to hear that you’re working on m_r again!

Oh, and syntax highlighting was just in time, eh?

Ben said September 14, 2007:

Cool.. But, just for the record, to_xml DOES handle associations… It handles them just like you would think: blog.to_xml(:include => :comments) If you didn’t know this I’m guessing the implementation doesn’t take advantage of it… Is that right?

Nathan said September 15, 2007:

That’s right, but now that I do know this, I think I still won’t take advantage of it. As I mentioned in my next post, the way we manage all the formats is to compile the model to a hash and transform that into the relevant format. This doesn’t really fit with using the built-in model transformation semantics.

Also, it appears that to_json doesn’t support them anyway.

Make your comments snazzy with Textile!