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:

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