Rails 4如何使用复选框和文本框对表单进行建模?

6

首先,我要说这可能也是一个建模问题,我对模型建议持开放态度。

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

数据库建模

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?

然而,使用上述解决方案后,我在保存时仍然遇到了重复记录错误。不确定为什么会发生这种情况?是否有更好的方法来解决这个问题?

3个回答

4
我更喜欢这样的东西:
模型 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
....

分类的表单字段怎么办?因此,当您选择其他类别时,应出现一个表单字段供您输入其他类别。 - Matt
在我的例子中,您不需要手动指定“categorization”。通过在“Post”中使用“accepts_nested_attributes_for”并将每个“category”呈现为复选框,所选类别将被序列化为“category_ids”,该方法将在“Post#category_ids =”上调用,因此“categories”关联将被覆盖为最后一个状态,“categorizations”关联将在这种情况下自动处理。这是您的问题吗?还是您想直接编辑“categorization”? - Kemal Akkoyun
“其他”类别很特殊,因为它有一个可选的文本字段,允许用户向“其他”类别添加细节。因此,该信息存储在分类中。因此,帖子具有与名称为“其他”的类别相关联的分类,因此分类还保存其他详细信息的值。 - Matt

2
请勿将“其他”及其名称存储在您的模型中!如果您正在使用form_for创建posts,只需添加一个不相关的字段即可。
例如:f.text_field :other_name改为text_field_tag :other_name 手动将Other选项添加到下拉列表集合中。
如果选择了其他选项,可以添加JS以隐藏和显示隐藏的文本字段。
在您的posts_controller中执行以下操作:
def create
  ...
  if params[:other_name]
    post.categories.create(name: param[:other_name])
  end
  ...
end

0

与其让用户选择“其他”类别,然后将文本字段存储在其他地方,不如创建一个新的类别实例。你使用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宝石)

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

你也可以使用表单对象来更加简洁地完成它。

编辑: 如果你需要将“其他类别”的关注点与“原始类别”分开,你可以使用面向对象的继承来实现。Rails 的做法是使用单表继承

# 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

使用这个解决方案,我需要在模型中跟踪类别的类型,对吧?它还会插入许多我永远不会显示的类别。 - Matt
我不确定你说的“需要跟踪类别类型”是什么意思。如果你想要每个“其他”类别与“原始”类别不同处理,你可以对它们进行子类化(单表继承)。但是,是的,这将会创建很多你不需要显示的类别。不确定这是否是一个真正的问题。 - Michael Choi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接