Rails 3 Nested Attributes?

701 views Asked by At

I have an application that tracks services (servers) with IP addresses. I'm trying to set it up so that when I create a new service the following happens:

  • All IP addresses are looked up, if they do not have a service_id then they're classed as available and returned to a select box on the new service page.
  • A user selects one or more IP addresses from the select box, fills in the rest of the required service data then when they hit submit, each IP address is updated with the service_id of the service that was just created. (so that the IP is marked as being taken).

As I understand, I think this should be possible with either nested attributes or virtual attributes... But I am not sure.

I have models like so:

class Service < ActiveRecord::Base
  has_many :ips
  attr_accessor :service_ips
end

class Ip < ActiveRecord::Base
  belongs_to :service
end

Controller like so:

 class ServicesController < ApplicationController
 def new
   @available_ips = Ip.where(:service_id == nil)
 end

And a view like so:

<%= form_for(@service) do |f| %>
  <%= f.label :service_ips %>
  <%= f.collection_select(:service_ips, @available_ips, :id, :address, { }, {:multiple => true, :size => 5}) %>

  <%= f.label :hostname %><br />
  <%= f.text_field :hostname, :size => 40 %>

  <%= f.submit :id => "submit"%>
<% end %>

How do I make it so each selected IP is updated with the newly created service_id?

1

There are 1 answers

2
sj26 On BEST ANSWER

This isn't really a nested attributes thing, nor do you need virtual attributes. You're just editing a has-many relationship.

Firstly, you probably want to be using the edit/update actions to be RESTful. Read the routing guide.

In your routes.rb:

resources :services

Then:

class ServicesController
  def edit
    @service = Service.find(params[:id])
    @available_ips = Ip.where(:service_id => nil)
  end

  def update
    @service = Service.find(params[:id])
    if @service.update_attributes params[:service]
      redirect_to @service
    else
      render :edit
    end
  end
end

You don't need the accessor in your model, the collection is an accessor:

class Service < ActiveRecord::Base
  has_many :ips
end

class Ip < ActiveRecord::Base
  belongs_to :service
end

Then in your views/services/edit.html.erb:

<%= form_for(@service) do |f| %>
  <%= f.label :ips %>
  <%= f.collection_select(:ip_ids, @available_ips + @service.ips, :id, :address, { }, {:multiple => true, :size => 5}) %>

  <%= f.label :hostname %><br />
  <%= f.text_field :hostname, :size => 40 %>

  <%= f.submit :id => "submit" %>
<% end %>