Rails appears pretty strict about separation of the abstraction layers that make up its notion of a web application: models, view and controllers. If you were to suggest calling a presentation method, such as url_for, in your model, the stoic Rails advocate will have an allergic reaction. However, Rails thinks nothing of rendering a model directly as a view, such as:

format.json { render :json => @products }

Now, one might argue that this is controller code and the controller is allowed to interpret the model as a view. The controller’s job is to mediate this interaction. However, I feel that it is a dangerous shortcut, made even more so by how hard to seems to be to override. Perhaps the json implementation is simply incomplete.

In xml, this strange controller pattern is easily corrected by providing an xml view. The xml builder syntax is particularly readable, and it is easy to design your XML API effectively.

I haven’t found an equivalent for json. I tried to use a JSON API today to no avail. My model included image data which breaks when auto-rendered in JSON. What I really wanted was to include a URL instead of the image data, which I implement neatly in my xml.builder view:

xml.instruct!
xml.products("type"=>"array") do
  @products.each do |product|
    xml.product do
      xml.sku product.sku
      xml.name product.name
      xml.brand product.brand
      xml.img_url url_for(:controller => :products, :action => :show, :format=>:png, :id => product.id, :only_path => false)
    end
  end
end

The problem is that I want a similar view in JSON. The to_json API leads me to put this logic in my model (gasp!). In fact, the ActiveRecord::Serialization docs give an example of providing a method to generate JSON instead of a literal attribute. The example is of a “permalink” which seem suspiciously like something that belongs is the view layer.

  konata.to_json(:methods => :permalink)
  # => {"id": 1, "name": "Konata Izumi", "age": 16,
        "created_at": "2006/08/01", "awesome": true,
        "permalink": "1-konata-izumi"}

Today’s solution was to go back to using my comfortable old XML API, but I would prefer to consume JSON from the other side. I wonder if anyone is working on a JSON builder or if there is some clear solution that I haven’t yet stumbled upon.

6 thoughts on “rails models are views?

  1. I agree wholeheartedly about the inappropriateness of the to_json and to_xml methods. As soon as you want to start messing with them, you’re putting view code into the model layer, which is just wrong. I was surprised there wasn’t a JSON builder/template handler, so I hacked one out. It’s up on github at http://github.com/jbr/argonaut. I’m not certain that I feel the builder pattern is more elegant than just building up a Ruby hash and calling to_json on it, but it was certainly fun to build. Yay Rails!

  2. It definitely feels like a glaring omission that there is not a JSON builder similar to xml builder. The way I’ve gotten around this in an app where I had a similar issue was to create an erb template (i.e. index.json.erb) and build the json response inside by hand. Then just render the template in the format.json block instead. Seems like a kludge though.

  3. I think you’re misunderstanding MVC. It’s not the controller’s job to “mediate” the interaction between models and views. What you are describing is a model-view-adapter paradigm. In MVC the role of a model is to represent the data in a machine-readable format, the role of the view is to make the model easy for the user to understand by renderering data into a human-understandable form based directly on the models, and the role of the controller is to respond to events by telling the view and/or the model what to do with that event (what, not how). Note that the controller’s job is not to translate the model for the view, that’s the view’s job. Classically, you should not be joining strings or totaling numbers for the view in your controller.

    MVC was designed to be appropriate for creating end-user applications in the 70s. It was not designed with inter-process communication in mind, and so it’s not surprising that the concept doesn’t fit well. Therefore there are two appropriate ways to deal with this. The first is to consider the machine requesting data as a user, and give it a response as a view designed to represent its expected representation of the data. This is appropriate for example when you need to conform to an arbitrary xml doctype that is not well represented by the internal state of your model. For example, if you need to manipulate the data from the model to represent new things that are only useful to the consumer.

    When you have control over the consumer or are defining your own API, it’s more appropriate to expect that the internal representations of the model are/will be the same on both ends. With this in mind, when a consumer makes a REST API request the controller receives this event and understands it as a request for a machine-representation of the data. This is the model’s job, so it’s entirely appropriate for the controller to respond by returning the model. JSON or XML is just your chosen text-serialized representation of the internal data of your model, it’s not a “view” in this case.

    Note also, that the concept of a RESTful interface favors the second option when your resources are well represented by you models, which they are in most Rails apps. You should be thinking twice when you do this:
    xml.sku product.sku
    xml.name product.name
    xml.brand product.brand
    And it’s appropriate to expect that your client already knows how to find a basic action, since you’re using RESTful URIs, so you just need to give them a key (not necessarily the one in your database). You don’t need to offer them this:
    xml.img_url url_for(:controller => :products, :action => :show, :format=>:png, :id =>

    Also, you say URIs “belong in the view layer”. A URI is actually a request on the controller, not the view. Just because your .erb files are in the folders that correspond to your URIs doesn’t mean that the request for a URI is a request for a representation of that view. The fact that the structure of views might mimic the structure of your controllers and models is not something inherently MVC. It’s actually a sign that you might have a poor user interface, since the point of MVC is to let views represent data in a structure more appropriate for user interaction than the model representation can.

  4. Micheal,

    Thanks for your thoughtful response to my post. I agree that the Controller’s primary role is to mediate interaction with the user, but in Rails the standard pattern is for the controller to pick out the URL params and find which model to act on and then render a specific view for that model.

    The issue I was raising in the first code example was that there is no view if the controller simple calls .to_json on the model. You suggest that this is one viable option when we are creating machine-to-machine communication and we control both ends. You have a point that I think many would agree with. However, I believe that it is the better pattern to think of machine-to-machine communication as a user interface. In defining the ways in which machines communication, we define protocols. I would argue that it is never a good idea to directly expose an internal data structure in a protocol — that may be a starting point, but at minimum you will want to add a version field! By exposing the internal data structures of one subsystem, it can make it easy to accidentally break another subsystem when you optimize the implementation and make other non-functional changes.

    That said, in thinking about this further, I wonder if the right approach to the problem I describe above would be to have the model define a custom to_json implementation with an optional version argument, so it can maintain multiple representations of its data.

    Sarah

  5. What do you think to use something like that: http://github.com/nothingmuch/template-plugin-json

    Actually I have the same problem right now, I don’t want to use hard things like haml like style, argonaut or .json.erb. I want something simple, embedded in rails, but I think there aren’t anykind of possibility. What can I think it’s possible to create hash array in helper method, then add it into a format.json { render :json => json_generation_helper_method(@object) }

    Anyway I will use plugin that I have told you in the beginning. It will be easier to move to the rails3 in the future, because rails3 now have easier api to add such kind of things, like template handlers.

    Thanks,
    Dmitry

What do you think?