Search code examples
ruby-on-railshas-many-throughcategorization

has_many :through won't save to the database


I have an association of Item & Category through Categorization:

class Item < ActiveRecord::Base
  has_many :categorizations
  has_many :categories, :through => :categorizations, :source => :category
end


class Category < ActiveRecord::Base
  has_many :categorizations
  has_many :items, :through => :categorizations, :source => :item
  attr_accessible :name
end

class Categorization < ActiveRecord::Base
  belongs_to :item
  belongs_to :category
end

Items/new:

<div class="container">
<%= render 'shared/error_create_item_messages'%>
<br/>

<%= form_for(@item, :html => {:multipart => true} ) do |f| %>

    <div class="clearfix">      
       <label>
         <%= f.label :name %>
      </label>
      <div class="input">       
         <%= f.text_field :name %>
      </div>
    </div>


     <div class="clearfix">
        <label>
         <%= f.label :category %>
        </label>

        <%= hidden_field_tag "product[category_ids][ ]", nil %>     
        <% Category.all.each do |category| %>
          <div class="input">           
               <%= check_box_tag "item[category_ids][ ]", category.id, 
                                            @item.category_ids.include?(category.id) %>
              <%= category.name %>
          </div>    
         <% end %>
     </div>     

     <div class="action">
        <div class="btn_create_item_align">
        <%= f.submit "Create item", :class=>"btn primary" %>
        </div>
     </div>

<% end %>
</div>

Categorizations_controller

class CategorizationsController < ApplicationController
   def create
     @categories = Category.all
     Categorization.create(:item_id => item.id, :category_id => category.id)
     Categorization.save
   end

  def edit
  end

end

Items_controller

def create
    @item = @current_user.items.build(params[:item])
    @categories = Category.all
    if @item.save
      redirect_to @item
    else
      render 'new'
    end
 end

The problem is When i hit save (create Item), and i check the Categorization table and check the on console, The items saved still dont have category_id. So the new item and its attributes (name, description, price) is saved to DB properly, but NOT the category. It wont save to db.

Any ideas? (Newbie in Rails) Thanks


Solution

  • The form POSTs to ItemsController#create, and CategorizationsController#create isn't being called (you can verify this with some puts debugging).

    You can use accepts_nested_attributes_for to have the Item's create action do all the work. The trick is to only create Category associations whose boxes are checked, and you can do that with the :reject_if option (see Rails API doc for more info):

    app/models/item.rb:

    class Item < ActiveRecord::Base
      has_many :categorizations
      has_many :categories, :through => :categorizations, :source => :category
      accepts_nested_attributes_for :categories, 
                                    :reject_if => proc{|c| c[:persist].blank?}
    end
    

    Then you can create form fields for the nested objects, one checkbox per category.

    app/views/items/new.html.erb:

    <%= form_for @item do |f| %>
      <%# stuff to generate item fields... %>
    
      <%= f.fields_for :categories do |cat| %>
        <%= cat.check_box :persist %>
        <%= cat.label :name, cat.name %>
      <%- end %>
    
      <%# submit button, etc. %>
    <%- end %>
    

    Populate the set of Categories to choose from when creating a new Item by building (but not saving) Categories associated with the Item. This is effectively moving code from your view to the controller:

    app/controllers/items_controller.rb:

    def new  
      @item = Item.new  
      Category.all.each {|cat| @item.categories.build(cat.attributes) }  
    end
    

    It'll be educational to puts the params in that controller action, so you can see what the hashes being sent from the form look like.