【问题标题】:Rails 4 How to model a form with a collection of checkboxes with other text_fieldRails 4如何使用带有其他text_field的复选框集合来建模表单
【发布时间】:2023-03-27 04:20:01
【问题描述】:

首先让我说这也可能是一个建模问题,我愿意接受建模建议。

用例: 我有一个表格,我需要允许用户在他们的帖子类别上选择一个复选框。如果没有适合他们帖子的类别,则检查其他类别将显示一个文本字段供用户添加自定义类别。这应该用于创建和更新嵌套模块

数据库建模

class CreateCategories < ActiveRecord::Migration
  def change
    create_table :categories do |t|
      t.string :name, null: false
      t.timestamps null: false
    end

    reversible do |dir|
      dir.up {
        Category.create(name: 'Hats')
        Category.create(name: 'Shirts')
        Category.create(name: 'Pants')
        Category.create(name: 'Shoes')
        Category.create(name: 'Other')
      }
    end

    create_table :categorizations, id: false do |t|
      t.belongs_to :post, index: true, null: false
      t.belongs_to :category, index: true, null: false
      t.string :value
    end
  end
end

应用模型

class Post < ActiveRecord::Base
  has_many :categorizations
  accepts_nested_attributes_for :categorizations, allow_destroy: true
  has_many :categories, through: :categorizations
  accepts_nested_attributes_for :categories
end

class Category < ActiveRecord::Base
  has_many :posts
end

控制器:

  def update

    if @post.update(post_params)
      flash.now[:success] = 'success'
    else
      flash.now[:alert] = @post.errors.full_messages.to_sentence
    end

    render :edit
  end

  private

  def set_post
    @post = Post.find(params[:id])
    (Category.all - @post.categories).each do |category|
      @post.categorizations.build(category: category)
    end
    @post.categorizations.to_a.sort_by! {|x| x.category.id }
  end

  def post_params
    params.require(:post).permit(:name, :description,
                                categorizations_attributes: [ :category_id, :value, :_destroy],
                                )
  end

查看:

= f.fields_for :categorizations do |ff|
    = ff.check_box :_destroy, { checked: ff.object.persisted? }, '0', '1'
    = ff.label :_destroy, ff.object.category.name
    = ff.hidden_field :category_id
    = ff.text_field :value if ff.object.category.other?

但是,使用上述解决方案,我在保存时继续遇到重复记录错误。不知道为什么会这样?有没有更好的方法来做到这一点?

【问题讨论】:

    标签: ruby-on-rails checkbox database-design models


    【解决方案1】:

    您应该创建一个新的 Category 实例,而不是让用户选择“Other”类别,然后将文本字段存储在其他地方。使用accepts_nested_attributes_for,您走在正确的轨道上。

    下一步是:

    # app/controllers/posts_controller.rb
    
    def new
      @post = Post.new
      @post.build_category
    end
    
    private
      # don't forget strong parameters!
      def post_params
        params.require(:post).permit(
          ...
          category_attributes: [:name]
          ...
        )
      end
    

    视图(使用 simple_formnested_form gems)

    # app/views/new.html.haml
    = f.simple_nested_form_for @job do |f|
      = f.simple_fields_for :category do |g|
        = g.input :name
    

    您也可以改用表单对象来做到这一点。

    编辑: 如果您需要将 Other 类别的关注点与 Original 类别分开,您可以使用 OO 继承来做到这一点。这样做的 Rails 方式是 Single Table Inheritance

    # app/models/category.rb
    class Category < ActiveRecord::Base
    end
    
    # app/models/other_category.rb
    class OtherCategory < Category
    end
    
    # app/models/original_category.rb
    class OriginalCategory < Category
    end
    

    【讨论】:

    • 使用这个解决方案,我必须跟踪模型中的类别类型,对吗?它还会插入很多我永远不会显示的类别。
    • 我不是 100% 确定您必须跟踪类别的类型是什么意思。如果您希望每个“其他”类别的处理方式与“原始”类别不同,您可以对它们进行子类化(单表继承)。但是,是的,它会创建很多您不会显示的类别。但不确定这是否是一个真正的问题。
    【解决方案2】:

    我更喜欢这样的:

    模型

    post.rb

    class Post < ActiveRecord::Base
      has_many :categorizations
      has_many :categories, through: :categorizations
    
      accepts_nested_attributes_for :categorizations, allow_destroy: true
      accepts_nested_attributes_for :categories
    end
    

    category.rb

    class Category < ActiveRecord::Base
      has_many :categorizations
      has_many :posts, through: :categorizations
    end
    

    控制器

    ...
    def update
      if @post.update(post_params)
        flash.now[:success] = 'success'
      else
        flash.now[:alert] = @post.errors.full_messages.to_sentence
      end
      render :edit
    end
    
    private
    
    def set_post
      @post = Post.find(params[:id])
    end
    
    def post_params
      params.require(:post).permit(:name, :description, category_ids: [])
    end
    ...
    

    观看次数 我总是更喜欢简单的.erb 所以, 在simple_form 的帮助下。

    <%= simple_form_for(@post) do |f| %>
      <%= f.error_notification %>
    
      <div class="form-inputs">
        <%= f.input :content -%>
        ...
      </div>
    
      <div class="form-inputs">
        <%= f.association :categories, as: :check_boxes -%>
      </div>
    
      <div class="form-actions">
        <%= f.button :submit %>
      </div>
    <% end %>
    

    您可以通过这种方式轻松干净地设置已选中/未选中状态并进行销毁。 另外,你可以添加

    <%= f.simple_fields_for :category do |category_fields| %>
      <%= category_fields.input :name -%>
    <% end %>
    

    获取关联的嵌套字段,但不要忘记在执行此操作时将相关参数添加到 strong_params

    ...
    def post_params
      params.require(:post).permit(:name, :description, category_attributes: [:name])
    end
    ....
    

    【讨论】:

    • 分类的表单域呢?因此,当您选择其他类别时,应该会出现一个表单字段供您输入其他类别。
    • 在我的示例中,您不需要手动指定categorization。通过在Post 中使用accepts_nested_attributes_for 并将每个category 渲染为复选框,所选类别将序列化为category_ids,将在Post#category_ids= 方法上调用,因此categories 关联将被此覆盖最后一个状态,categorizations 关联将在这个场景中自动处理。这是你的问题吗?或者你想直接编辑categorization
    • “其他”类别的特殊之处在于有一个可选文本字段,允许用户向“其他”类别添加详细信息。这样信息就存储在分类中。因此,帖子有一个分类,该分类与名称为 other 的类别相关联,因此分类也包含其他详细信息的值。
    【解决方案3】:

    不要将另一个存储在您的模型中,也不要存储它的名称!如果您使用 form_for 作为您的 posts,只需添加一个不相关的字段。

    例如:f.text_field :other_nametext_field_tag :other_name

    手动将您的 Other 选项添加到下拉集合中。

    如果选择其他,可以添加JS隐藏和显示隐藏的文本字段。

    在您的 posts_controller 中执行:

    def create
      ...
      if params[:other_name]
        post.categories.create(name: param[:other_name])
      end
      ...
    end
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-11-08
      • 2013-04-26
      • 1970-01-01
      • 2011-05-12
      • 2016-10-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多