ruby-on-rails - form_for - form_with ruby on rails
Rails 4 Cómo modelar un formulario con una colección de casillas de verificación con otro text_field (3)
Permítanme comenzar diciendo que esto también podría ser un problema de modelado y estoy abierto a sugerencias modelo.
Caso de uso: tengo un formulario y debo permitir que un usuario seleccione una casilla de verificación en la categoría de su publicación. Si no hay ninguna categoría que se ajuste a su verificación posterior, la otra categoría mostrará un campo de texto para que el usuario agregue una categoría personalizada. Esto debería ser para crear y actualizar módulos anidados
Modelado de bases de datos
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
Modelos de aplicaciones
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
Controlador:
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
Ver:
= 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?
Sin embargo, con la solución anterior sigo corriendo para duplicar los errores de registro al guardar. ¿No estoy seguro de por qué ocurre esto? ¿Hay una mejor manera de hacer esto?
¡No guarde el otro en su modelo, ni su nombre! Si está utilizando form_for
para sus posts
, simplemente agregue un campo no relacionado.
ej .: f.text_field :other_name
to text_field_tag :other_name
Agregue manualmente su opción Other
a la colección desplegable.
Puede agregar JS para ocultar y mostrar un campo de texto oculto si se selecciona otro.
En tu posts_controller do:
def create
...
if params[:other_name]
post.categories.create(name: param[:other_name])
end
...
end
Yo preferiría algo como esto:
Modelos
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
Controlador
...
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
...
Vistas Siempre prefiero plain .erb
entonces, con la ayuda de 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 %>
Puede tener estados marcados / desmarcados y destruir de forma fácil y limpia de esta manera. Además, puedes agregar
<%= f.simple_fields_for :category do |category_fields| %>
<%= category_fields.input :name -%>
<% end %>
para obtener campos anidados para asociaciones, pero no olvides agregar params relacionados a strong_params
cuando hagas esto.
...
def post_params
params.require(:post).permit(:name, :description, category_attributes: [:name])
end
....
En lugar de hacer que el usuario seleccione la Categoría "Otros" y luego almacenar el campo de texto en otro lugar, debe crear una nueva instancia de Categoría en su lugar. Estás en el camino correcto con accepts_nested_attributes_for
.
El próximo paso sería:
# 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
Vistas (usando gemas simple_form y nested_form )
# app/views/new.html.haml
= f.simple_nested_form_for @job do |f|
= f.simple_fields_for :category do |g|
= g.input :name
También puedes hacerlo más limpio utilizando Objetos de formulario.
Editar: si necesita separar las preocupaciones de las categorías Otras de las categorías Originales, puede usar la herencia OO para hacerlo. La forma de Rails de hacer esto es la herencia de tabla única .
# 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