简体   繁体   English

Rails 4如何使用其他text_field的复选框集合为表单建模

[英]Rails 4 How to model a form with a collection of checkboxes with other text_field

let me first start by saying this could also be a modeling problem and I am open to model suggestions. 首先让我先说这也可能是一个建模问题,我对模型建议持开放态度。

Use Case: I have a form and I need to allow a user to select a checkbox on the category of their post. 使用案例:我有一个表单,我需要允许用户选择其帖子类别的复选框。 If there is no category that fits their post checking the other category will show a text field for the user to add a custom category. 如果没有适合其帖子检查的类别,则其他类别将显示用户添加自定义类别的文本字段。 This should would for creating and updating nested modules 这应该用于创建和更新嵌套模块

DB Modeling 数据库建模

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

App Models 应用模型

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

Controller: 控制器:

  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

View: 视图:

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

However with the above solution i continue to run in to duplicate record errors when saving. 但是,通过上述解决方案,我在保存时继续运行以复制记录错误。 Not sure why this is happening? 不知道为什么会这样? Is there a better way to do this? 有一个更好的方法吗?

I would prefer something like this: 我更喜欢这样的东西:

Models 楷模

post.rb 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 category.rb

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

Controller 调节器

...
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
...

Views I always preferer plain .erb so, with help of simple_form . 查看我总是preferer平原.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 %>

You can have checked/unchecked states and destroy easily and cleanly by this way. 您可以通过这种方式检查/取消选中状态并轻松干净地销毁。 In addition, you can add 另外,你可以添加

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

to get nested fields for associations, but don't forget to add related params to strong_params when you do this. 获取关联的嵌套字段,但不要忘记在执行此strong_params时将相关的参数添加到strong_params

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

Don't store the other in your model, nor it's name! 不要将另一个存放在您的模型中,也不要将其名称存储起来! If you're using form_for for your posts , simply add an unrelated field. 如果您在posts使用form_for ,只需添加一个不相关的字段即可。

ex: f.text_field :other_name to text_field_tag :other_name 例如: f.text_field :other_nametext_field_tag :other_name

Manually add your Other option to the dropdown collection. 手动将“ Other选项添加到下拉列表集合中。

You can add JS to hide and display a hidden text field if other is selected. 如果选择了其他,则可以添加JS以隐藏和显示隐藏文本字段。

In your posts_controller do: 在你的posts_controller中:

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

Instead of having the user select the "Other" Category and then storing the text field somewhere else, you should create a new Category instance instead. 您应该创建一个新的Category实例,而不是让用户选择“其他”类别,然后将文本字段存储在其他位置。 You are on the right track with the accepts_nested_attributes_for . 您使用accepts_nested_attributes_for在正确的轨道上。

The next step would be: 下一步将是:

# 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

Views (using simple_form and nested_form gems) 视图(使用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

You can also do it cleaner using Form Objects instead. 您也可以使用表单对象来更干净。

Edit: If you need to separate concerns of the Other categories from the Original categories, you can use OO inheritance to do so. 编辑:如果您需要将其他类别的关注点与原始类别分开,则可以使用OO继承来执行此操作。 The Rails Way of doing this is Single Table Inheritance . 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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM