polymorphism & REST

I have a polymorphic model:

class Note < ActiveRecord::Base   belongs_to :notable, :polymorphic => true end

And some models that use notes:

class User < ActiveRecord::Base   has_many :notes, :as => :notable end

class Blog < ActiveRecord::Base   has_many :notes, :as => :notable end

My routes are set up:

map.resources :users :notes map.resources :blogs :notes etc

REST says I should GET /users/3/notes or /blogs/243/notes if I want to see the notes associated with the objects. All fine and good, but how do I implement the index method of the NotesController?

Thanks, --Dean

def index   parent = User.find(params[:user_id]) if params[:user_id]   parent = Blog.find(params[:blog_id]) if params[:blog_id]

  @notes = parent.notes end

I'd rather have a more general way to find @notes so I can add more objects with notes without going back to the notes_controller every time. Is there a good way to inspect params for anything_id?


I have a similar setting with audits where all of my resources have an audit log. As this functionality cuts across the resources, I handle display of the audit log by a separate controller. I haven't come up with a way to do this neatly with map.resources, rather, I add the routes like this

  map.with_options(:controller => 'audits', :action => 'show') do | audits_map|     audits_map.audits ':resources/:id/audits.:format'     audits_map.audits ':resources/:id/audits', :format => 'html'   end

The AuditsController looks (partially) like this

class AuditsController < ApplicationController   session :off, :if => proc { |req| req.parameters[:format] != 'html' }

  def show     respond_to do |format|       format.html { show_html }       format.atom { show_atom }       format.xml { show_xml }     end   end


  def show_html     setup_objects     render :action => 'show'   end

  def setup_objects     audited_class = assert_valid_resource_name(params[:resources])     auditable_type = audited_class ? audited_class.singularize.camelize : nil     auditable_id = params[:id]     @audits = Audit.find_all_by_auditable_type_and_auditable_id(auditable_type, auditable_id)   end


To ensure that audit logs can only be displayed for valid resources, I've monkey patched resource route registration to collect resource names and check against these names. Here, too, I'm all in favor of a more elegant solution.

In ApplicationController

  def assert_valid_resource_name(name)     raise ArgumentError, "Resource does not exist: #{name}" unless ActionController::Resources.valid_resource?(name)     name   end

loaded on startup from a file in lib

ActionController::Resources.module_eval do   mattr_accessor :valid_resources   ActionController::Resources.valid_resources =

  def self.valid_resource?(name) #:doc:     ActionController::Resources.valid_resources.include?(name.to_sym)   end

  private      def map_resource_with_collecting(entities, *args, &block)     ActionController::Resources.valid_resources |= [ entities ]     map_resource_without_collecting(entities, *args, &block)   end   alias_method_chain :map_resource, :collecting end

HTH, Michael

Based on another post here, this is what I'm using for restfull nested routing. It's a work in progress and I'm only a few weeks into ruby/rails so YMMV. No error checking yet here. That'll come later.

This routine automatically pulls both the parent/child objects from the DB. Not sure whether I'll leave that or not.

This gets called as a global before_filter, populating an instance variable chock full of crap that gets consumed downstream. I have another helper method that generates proper routes for add/edit/show/etc that I might roll into this as well. I've also considered making this a class, but for now I'm still tweaking it.

My design will only (currently) allow for nesting 1 level deep, as I plan to have redundant routes, ala:

Customers Customers/PO PO/ PO/LineItems

I've come up with a way to display nested resources within their parents, via a (still-in-progress) tabular paging thingymabob.

def find_type_and_id
  sections = request.env['REQUEST_URI'].scan(%r{/(\w+)/*(\d*)})   @page = {} unless defined?(@page)      @page[:isnested] = false   if sections.length > 0     @page[:base_class_name] = sections[0][0].singularize     @page[:base_class] = eval(@page[:base_class_name].camelize)     @page[:base_id] = sections[0][1].blank? ? nil : sections[0][1]     @page[:base_object] = @page[:base_class].find(@page[:base_id]) if !@page[:base_id].blank?
  end      if sections.length > 1     @page[:sub_class_name] = sections[1][0].singularize     @page[:sub_class] = eval(@page[:sub_class_name].camelize)     @page[:base_prefix] = @page[:base_class_name] + "_"     @page[:base_show_path] = sections[0][0] + '/show'     @page[:sub_new_post_path] = eval("#{@page[:base_prefix]}#{@page[:sub_class_name].pluralize}_path(#{@page [:base_id]})")
    @page[:isnested] = true          if !sections[1][1].blank?       @page[:sub_id] = sections[1][1]       @page[:sub_object] = @page[:sub_class].find(@page[:sub_id])       @page[:sub_edit_post_path] = eval("#{@page[:base_prefix]}#{@page[:sub_class_name]}_path(#{@page[:base_id] },#{@page[:sub_id]})")
  logger.debug("Page-> #{dumphash(@page)}")