Categorized: Rails

Basic CRUD for Nested Elements using RESTful Rails

Continuing from where we left off

(This all assumes you’ve set your app up using script/generate scaffold_resource with all the bells and whistles of spelling out your table columns in the command line.)

Make sure you’ve got your model relationships set up right:

# app/models/manufacturer.rb
has_many :appliances

# app/models/appliance.rb
belongs_to :manufacturer

Set up your nested routes:

map.resources :manufacturers do |manufacturers|
    manufacturers.resources :appliances
end

In the controller for the dependent item, at the top of the file:

# app/controllers/appliances_controller.rb
before_filter :load_manufacturers

Then at the bottom of the same file:

private

def load_manufacturers
    @manufacturer = manufacturer.find(params[:manufacturer_id])
end

Then back at the top of that file, for the index action:

@appliances = @manufacturer.appliances.find(:all)

Now for the views. To get started: in app/views/appliances/index.rhtml you should make sure that calls to appliance_path get the manufacturer id passed to them:

<%= link_to 'Show', appliance_path(@manufacturer, appliance) %>

In edit.rhtml and new.rhtml (or your _form.rhtml partial), you can remove the part of the form that lets you specify what manufacturer you want within the new/edit appliance form. You’ll get that from the params[:manufacturer_id] anyway. In the edit template, you’ll need to change the form a little bit for it to work:

# app/views/appliances/edit.rhtml

<% form_for(:appliance, :url => appliance_path(@manufacturer, @appliance), :html => { :method => :put }) do |f| %>

Notice we have to specifically specify the @manufacturer. Now back to the appliances controller:

def create
    @appliance = appliance.new(params[:appliance])
    @appliance.manufacturer_id = params[:manufacturer_id]

    respond_to do |format|
      if @appliance.save
        flash[:notice] = 'Appliance was successfully created.'

        format.html { redirect_to appliance_url(@appliance.manufacturer_id, @appliance) }
    ...

It’s not smart enough to know that it needs the manufacturer_id param that’s just sitting there in the URL waiting to be used for something. Just about anywhere you need the appliance_url(), you have to specify the manufacturer_id. Whether it’s a param, or if it’s already sitting in the item you’re using:

# app/controllers/appliances_controller.rb
def update
    ...
    if @appliance.update_attributes(params[:appliance])
      format.html { redirect_to appliance_url(@appliance.manufacturer_id, @appliance) }
    ...

Hope that helps. It’s simple, right? ;)

Thanks again to PeepCode!

Comments

[...] Continued here… [...]

Ltmon → June 19th, 2007 at 9:55 pm

Instead of:

@appliance = appliance.new(params[:appliance])
@appliance.manufacturer_id = params[:manufacturer_id]

I would do:

@appliance = @manufacturer.appliances.build(params[:appliance])

It’s a little method thrown into ActiveRecord for just this purpose. Similarly when you are doing you “edit” and “show” methods you can do:

@appliance = @manufacturer.appliances.find(params[:id])

This ensures that the appliance you are retrieving actually belongs to the parent manufacturer.

What do you think about that?

Elsewhere in the empire: Home, Blog, APOD