Rails has_many через форму с флажками и дополнительным полем в модели соединения - PullRequest
16 голосов
/ 07 февраля 2012

Я пытаюсь решить довольно распространенную (как я думал) задачу.

Существует три модели:

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

Мои проблемы начинаются, когда дело доходит до нового продукта /Форма редактирования.

При создании товара мне нужно проверить категории (с помощью флажков), к которым он принадлежит.Я знаю, что это можно сделать, создав флажки с такими именами, как «product [category_ids] []».Но мне также нужно ввести описание для каждого из проверенных отношений, которое будет сохранено в модели соединения (Категоризация).

Я видел эти прекрасные Railscasts на сложных формах, флажки habtm и т. Д. Я искалStackOverflow вряд ли.Но мне это не удалось.

Я нашел один пост , в котором описана почти та же проблема, что и у меня.И последний ответ имеет какой-то смысл для меня (похоже, это правильный путь).Но это на самом деле не работает хорошо (то есть, если проверка не проходит).Я хочу, чтобы категории отображались всегда в одном и том же порядке (в новых / редактируемых формах; до / после проверки) и чтобы флажки оставались там, где они были, если проверка не пройдена и т. Д.

Любые мысли приветствуются.Я новичок в Rails (переход с CakePHP), поэтому, пожалуйста, наберитесь терпения и напишите как можно более подробно.Пожалуйста, укажите мне правильный путь!

Спасибо.:)

Ответы [ 3 ]

29 голосов
/ 08 февраля 2012

Похоже, я понял это!Вот что я получил:

Мои модели:

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

Форма:

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

И контроллер:

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

ИзНа первый взгляд все работает просто отлично.Я надеюсь, что это не сломается как-то ..:)

Спасибо всем.Особая благодарность Sandip Ransing за участие в обсуждении.Я надеюсь, что это будет полезно для таких, как я.

1 голос
/ 13 июля 2013

Я просто сделал следующее.У меня это сработало ..

<%= f.label :category, "Category" %>
<%= f.select :category_ids, Category.order('name ASC').all.collect {|c| [c.name, c.id]}, {} %>
1 голос
/ 07 февраля 2012

используйте accepts_nested_attributes_for для вставки в intermediate table, т.е. categorizations форма просмотра будет выглядеть как -

# 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

Вид изнутри

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