views:

39

answers:

2

I'm working on a fairly complicated app and am trying to figure out how to implement some functionality in terms of rendering data to various formats.

In simple terms, the app is taking key value data and rendering it to html, or potentially to other formats. I know key value data sucks but for the app in question it is required. This will be a rails app and essentially I'm dealing with a model structure that looks something like:

Foo has many Bars Bar has many Fields Field consists of name, value, type

What I'd like to do in my controller is fetch a Foo and feed it to the view. The view would then call some kind of builder class with the Foo object as an argument, along with the render format (e.g. html, pdf, xml).

From here on how to structure it I'm a bit lost on, and I'm hoping someone can give some pointers in the right direction as I'm fairly new to ruby and figuring out how classes, modules and mixins fit into the picture is confusing me.

On a high level here's what I'm thinking... The builder class would say, ah, we want html output so pass this to the html builder. The html builder would say, ok, we have Foo and we need to render this output. It's logic would iterate through fields and try to figure out how to render each one, something like:

1) Do we have something that specifically knows how to render field 4 in Bar.id = 8 in Foo.id = 3?

2) Do we have something that knows how to render field.type = 'phone' for Bar.id = 8 on Foo.id = 3?

3) Do we have something that knows how to render field.type = 'phone' for Foo.id = 3?

4) Do we have something that knows how to render field.type = 'phone'?

5) Call default renderer

Ultimately my goal here is to write the least amount of code possible. For example, for more than half the fields the rendering for html will be nothing but plain text. However, the nature of the app requires that we be able to accomodate any edge case necessary. For example, it's entirely possible that in some cases we need to render a phone number as xxx-xxx-xxxx whereas other times it would need to be rendered as (xxx) xxx-xxxx. My overall idea is to create something that can take a model as an argument and go from specific to generic renderers so that in any special case I can specifically say for field 'phone' in Bar 3 and Foo 7 it should output in a special way. But obviously, I don't want to have to create a class heirarchy for every field of ever Bar of ever Foo, rather I'd like a method where I can implement some code if a special case is required and have the Builder routine find it.

Hopefully this is reasonably clear, if anyone has any tips on how I might structure this within a rails application or some pointers in the right direction I'd appreciate it.

A: 

you could set a thread variable to know what type of data you're rendering:

Thread.current[:render_type] = 'abc'

then you wouldn't have to pass the render style all the way down through each partial or what not.

You could just use rails' normal .rhtml rendering system, and use different partials where appropriate or what not. And maybe make the method names specific so you know what is going on :)

rogerdpack
A: 

So your goal is to use some single builder object, or collection of builder objects, that can emit similarly-structured data in different formats, such that you don't have to maintain too many separate code paths.

Unless I'm very much misinterpreting your situation here, I'd suggest sticking with vanilla Rails conventions. Let partials and helpers be your view builder. I'll outline the approach here…

Routing & modeling

config/routes.rb

map.resources :foos

This route sets you up with the ability to distinguish between different formats in your requests. That way the respond_to method in your controller action will check params[:format] to determine which view to render. A request to /foos/1.xml will set params[:format] to xml and so on.

app/models/foo.rb, app/models/bar.rb, app/models/field.rb

class Foo < ActiveRecord::Base
  has_many :bars
  has_many :fields, :through => :bars
end

class Bar < ActiveRecord::Base
  belongs_to :foo
  has_many :fields

  def quux?
    self[:quux] || foo.quux? # sample overridable property
  end
end

class Field < ActiveRecord::Base
  # t.string :name, :value, :type
  belongs_to :bar
  delegate :foo, :to => :bar

  def quux?
    self[:quux] || bar.quux? # sample overridable property
  end
end

My basic understanding of the modeling you described. Particularly interesting to note: if you have a Field object in scope, you have access to all of its parents in the hierarchy. You can create accessor methods on the model for any property which you'd like to optionally override at a higher level in the chain. An example of such a property is given above.

Controller

foos_controller.rb

class FoosController < ApplicationController
  def show
    @foo = Foo.find(params[:id])
    respond_to do |format|
      format.html
      format.xml
      format.pdf
    end
  end
end

Your controller really should not be more complicated than this. "Skinny controllers, fat models," and all of that. It's respond_to which does the heavy lifting of selecting a different view for the format requested (which you may already be familiar with).

The views:

This is where things get interesting and code paths start to diverge.

I am going to make the case that rather than jumping straight into some kind of builder object here, you should stick to partials and helpers. Using restful conventions (map.resources and respond_to) will select templates of the correct format. So, yes, you may end up with a lot of small files for each field type and each format. But from there, you can abstract shared logic into helpers.

This kind of approach would work particularly well if you need the builders primarily at the Field level, where your documents all share a similar structure above that.

An arbitrary example follows, just to outline the simplicity of the approach I'm advocating:

app/views/foos/show.html.erb

<%= render @foo.bars %>

app/views/bars/_bar.html.erb

<%= render bar.fields %>

app/views/fields/_field.html.erb

<%= render field.type, :field => field %>

Here is an important trick: use the value of field.type to select the appropriate partial. Remember to pass along the field object as well so it is available in the scope of that partial.

(I presume Rails will correctly construct the rest of that partial path for you — merits some experimenting. This method might get complicated enough to justify abstraction as a helper. render_field(field) or somesuch.)

app/views/fields/_telephone.html.erb

<!--
  This partial has access to field, which has access to both foo and bar.
  We know the format based on the context of the partial file name.
  Need further abstraction? Call a helper.
-->
<some_wrapper_code_for_this_format>
  <%= format_telephone_field(field) %>
</some_wrapper_code_for_this_format>

Finally: helpers

app/helpers/fields_helper.rb

class FieldsHelper
  def format_telephone_field(field)
    # don't forget, we have access to params here, too
    # including params[:format]
    if field.quux?
      number_to_phone(field.value)
    else
      number_to_phone(field.value, :area_code => true)
    end
  end
end

XML, PDF, etc

Supporting different formats would be a matter of creating new templates with the appropriate extensions. These templates and partials are where you can place format-specific markup. Shared document semantics should be pushed into helpers as much as possible.

In sum

I don't think I answered your exact question. Or perhaps I did. Point being: rather than start by building something complex from the outset, I would stick to simple conventions and diverge only to deal with excessive duplication as it happens. YAGNI, TDD, YMMV, and so on.

Have fun!

Nick Zadrozny
Good thorough answer!
Chad
Aw, shucks. It's just the markdown makin' it look fancy ;)
Nick Zadrozny