Search code examples
ruby-on-railsnested-attributesvirtual-attribute

Rails 3 Nested Attributes?


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?


Solution

  • 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 %>