ruby on rails - form_with - Rails has_many a través del formulario con casillas de verificación y campo adicional en el modelo de unión
rails form select (3)
¡Parece que lo descubrí! Esto es lo que tengo:
Mis modelos:
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
La forma:
<%= 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 %>
Y el controlador:
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
Desde el primer vistazo funciona bien. Espero que no se rompa de alguna manera ... :)
Gracias a todos. Un agradecimiento especial a Sandip Ransing por participar en la discusión. Espero que sea de utilidad para alguien como yo.
Estoy tratando de resolver una tarea bastante común (como pensé).
Hay tres modelos:
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
Mis problemas comienzan cuando se trata del formulario Nuevo producto / edición.
Al crear un producto, necesito verificar las categorías (a través de las casillas de verificación) a las que pertenece. Sé que se puede hacer al crear casillas de verificación con un nombre como ''producto [category_ids] []''. Pero también necesito ingresar una descripción para cada una de las relaciones marcadas que se almacenarán en el modelo de unión (Categorización).
Vi esos hermosos Railscasts en formas complejas, habtm checkboxes, etc. He estado buscando StackOverflow apenas. Pero no he tenido éxito.
Encontré un post que describe casi exactamente el mismo problema que el mío. Y la última respuesta tiene sentido para mí (parece que es el camino correcto a seguir). Pero en realidad no está funcionando bien (es decir, si falla la validación). Quiero que las categorías se muestren siempre en el mismo orden (en formularios nuevos / de edición; antes / después de la validación) y que las casillas de verificación permanezcan donde estaban si falla la validación, etc.
Cualquier cosa apreciada. Soy nuevo en Rails (cambiando de CakePHP), así que sea paciente y escriba lo más detallado posible. Por favor, apúntame de la manera correcta!
Gracias. :)
Acabo de hacer lo siguiente. Funcionó para mí ..
<%= f.label :category, "Category" %>
<%= f.select :category_ids, Category.order(''name ASC'').all.collect {|c| [c.name, c.id]}, {} %>
use accepts_nested_attributes_for
para insertar en la intermediate table
es decir, el formulario de vista de categorizations
se verá como
# 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
Vista interior
= 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