Polymorphism & REST


#1

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


#2

On 2/22/07, Dean removed_email_address@domain.invalid wrote:

has_many :notes, :as => :notable
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?

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


#3

On Feb 22, 2:19 pm, “Pat M.” removed_email_address@domain.invalid wrote:

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?

–Dean


#4

On Friday 23 February 2007, Dean wrote:

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

private

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

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


Michael S.
mailto:removed_email_address@domain.invalid
http://www.schuerig.de/michael/


#5

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.

private
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]})")
end
end
logger.debug(“Page-> #{dumphash(@page)}”)
end