【问题标题】:Rails has_many through form with checkboxes and extra field in the join modelRails has_many 通过表单在连接模型中带有复选框和额外字段
【发布时间】:2012-02-28 18:40:24
【问题描述】:

我正在尝试解决一个非常常见的(如我所想的)任务。

共有三种型号:

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

  has_many :categorizations
  has_many :categories, :through => :categorizations

  accepts_nested_attributes_for :categorizations
end

class Categorization < ActiveRecord::Base
  belongs_to :product
  belongs_to :category

  validates :description, presence: true # note the additional field here
end

class Category < ActiveRecord::Base
  validates :name, presence: true
end

我的问题始于产品新/编辑表单。

创建产品时,我需要检查它所属的类别(通过复选框)。我知道可以通过创建名称为“product[category_ids][]”的复选框来完成。但我还需要为每个检查的关系输入一个描述,这些关系将存储在连接模型(分类)中。

我在复杂的表单、habtm 复选框等上看到了那些漂亮的 Railscast。我几乎没有搜索 StackOverflow。但我没有成功。

我找到了一个post,它描述的问题与我的几乎完全相同。最后一个答案对我来说很有意义(看起来这是正确的方法)。但它实际上并不能很好地工作(即如果验证失败)。我希望类别始终以相同的顺序显示(在新/编辑表单中;在验证之前/之后),并且如果验证失败等,复选框保持在原来的位置。

感谢任何想法。 我是 Rails 新手(从 CakePHP 切换),所以请耐心等待并尽可能详细地编写。请以正确的方式指出我!

谢谢。 :)

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 forms nested-forms has-many-through


    【解决方案1】:

    使用accepts_nested_attributes_for 插入intermediate tablecategorizations 视图表单看起来像 -

    # 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
    

    内部视图

    = 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
    

    【讨论】:

    • 谢谢!但是您的解决方案似乎与验证和其他内容不兼容。复选框总是被选中,即使我取消选中它们也会被忽略(在验证失败或验证通过关系后它们会返回)。此外,我想始终以相同的顺序列出类别(在您的示例中,您在已与产品关联的类别之后附加未分配的类别)。有什么想法吗?
    • 您可以在收货时订购@product.categorizations
    • 你能粘贴通过的参数吗?
    • 好吧.. 现在我已经尝试了你的整个代码(复制粘贴它)。而且我不得不说它根本不起作用。:) 你在发帖之前自己尝试过吗?即使我通过修复它的一些错误使它工作,仍然存在一些大问题(即在数据库中创建具有 NULL category_id 的分类等)。您能否尝试使其正常工作(如我在最初的问题中所述)?哦,顺便说一句,我在 Rails 3.2 上。
    • 好吧。上面的代码是给你正确的方向..不要指望一切都会像你预期的那样顺利..你必须迎合代码没有执行的事情。
    【解决方案2】:

    看来我想通了!这是我得到的:

    我的模型:

    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
    

    形式:

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

    还有控制器:

    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
    

    乍一看,它工作得很好。我希望它不会以某种方式破裂.. :)

    谢谢大家。特别感谢 Sandip Ransing 参与讨论。我希望它对像我这样的人有用。

    【讨论】:

    • 做得很好。不过,我觉得可能有更简单的方法。
    • 非常感谢分享,我还必须补充stackoverflow.com/a/15920542/148421,因为我的值没有被保存并且我错过了如何允许嵌套属性
    • 使用强参数需要添加 categorizations_attributes: [:id, :category_id, :description, :_destroy]
    【解决方案3】:

    我刚刚做了以下事情。它对我有用..

    <%= f.label :category, "Category" %>
    <%= f.select :category_ids, Category.order('name ASC').all.collect {|c| [c.name, c.id]}, {} %>
    

    【讨论】:

    • 这不允许操作要求的额外连接模型字段
    猜你喜欢
    • 2012-05-16
    • 2011-06-30
    • 2010-09-29
    • 1970-01-01
    • 1970-01-01
    • 2016-07-23
    • 1970-01-01
    • 2016-02-18
    相关资源
    最近更新 更多