Rails has_many through form with checkboxes and extra field in the join model

后端 未结 3 1911
长情又很酷
长情又很酷 2020-12-01 03:08

I\'m trying to solve a pretty common (as I thought) task.

There\'re three models:

class Product < ActiveRecord::Base  
  validates :name, presence         


        
相关标签:
3条回答
  • 2020-12-01 03:37

    Looks like I figured it out! Here's what I got:

    My models:

    class Product < ActiveRecord::Base
      has_many :categorizations, dependent: :destroy
      has_many :categories, through: :categorizations
    
      accepts_nested_attributes_for :categorizations, allow_destroy: true
    
      validates :name, presence: true
    
      def initialized_categorizations # this is the key method
        [].tap do |o|
          Category.all.each do |category|
            if c = categorizations.find { |c| c.category_id == category.id }
              o << c.tap { |c| c.enable ||= true }
            else
              o << Categorization.new(category: category)
            end
          end
        end
      end
    
    end
    
    class Category < ActiveRecord::Base
      has_many :categorizations, dependent: :destroy
      has_many :products, through: :categorizations
    
      validates :name, presence: true
    end
    
    class Categorization < ActiveRecord::Base
      belongs_to :product
      belongs_to :category
    
      validates :description, presence: true
    
      attr_accessor :enable # nice little thingy here
    end
    

    The form:

    <%= form_for(@product) do |f| %>
      ...
      <div class="field">
        <%= f.label :name %><br />
        <%= f.text_field :name %>
      </div>
    
      <%= f.fields_for :categorizations, @product.initialized_categorizations do |builder| %>
        <% category = builder.object.category %>
        <%= builder.hidden_field :category_id %>
    
        <div class="field">
          <%= builder.label :enable, category.name %>
          <%= builder.check_box :enable %>
        </div>
    
        <div class="field">
          <%= builder.label :description %><br />
          <%= builder.text_field :description %>
        </div>
      <% end %>
    
      <div class="actions">
        <%= f.submit %>
      </div>
    <% end %>
    

    And the controller:

    class ProductsController < ApplicationController
      # use `before_action` instead of `before_filter` if you are using rails 5+ and above, because `before_filter` has been deprecated/removed in those versions of rails.
      before_filter :process_categorizations_attrs, only: [:create, :update]
    
      def process_categorizations_attrs
        params[:product][:categorizations_attributes].values.each do |cat_attr|
          cat_attr[:_destroy] = true if cat_attr[:enable] != '1'
        end
      end
    
      ...
    
      # all the rest is a standard scaffolded code
    
    end
    

    From the first glance it works just fine. I hope it won't break somehow.. :)

    Thanks all. Special thanks to Sandip Ransing for participating in the discussion. I hope it will be useful for somebody like me.

    0 讨论(0)
  • 2020-12-01 03:39

    I just did the following. It worked for me..

    <%= f.label :category, "Category" %>
    <%= f.select :category_ids, Category.order('name ASC').all.collect {|c| [c.name, c.id]}, {} %>
    
    0 讨论(0)
  • 2020-12-01 03:59

    use accepts_nested_attributes_for to insert into intermediate table i.e. categorizations view form will look like -

    # make sure to build product categorizations at controller level if not already
    class ProductsController < ApplicationController
      before_filter :build_product, :only => [:new]
      before_filter :load_product, :only => [:edit]
      before_filter :build_or_load_categorization, :only => [:new, :edit]
    
      def create
        @product.attributes = params[:product]
        if @product.save
          flash[:success] = I18n.t('product.create.success')
          redirect_to :action => :index
        else
          render_with_categorization(:new)
        end
      end 
    
      def update
        @product.attributes = params[:product]
        if @product.save
          flash[:success] = I18n.t('product.update.success')
          redirect_to :action => :index
        else
          render_with_categorization(:edit)
        end
      end
    
      private
      def build_product
        @product = Product.new
      end
    
      def load_product
        @product = Product.find_by_id(params[:id])
        @product || invalid_url
      end
    
      def build_or_load_categorization
        Category.where('id not in (?)', @product.categories).each do |c|
          @product.categorizations.new(:category => c)
        end
      end
    
      def render_with_categorization(template)
        build_or_load_categorization
        render :action => template
      end
    end
    

    Inside view

    = form_for @product do |f|
      = f.fields_for :categorizations do |c|
       %label= c.object.category.name
       = c.check_box :category_id, {}, c.object.category_id, nil
       %label Description
       = c.text_field :description
    
    0 讨论(0)
提交回复
热议问题