14

Tôi đang cố gắng giải quyết một nhiệm vụ khá phổ biến (như tôi nghĩ).Rails has_many thông qua biểu mẫu với hộp kiểm và trường bổ sung trong mô hình tham gia

Như vậy là ba mô hình:

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 

vấn đề của tôi bắt đầu khi nói đến sản phẩm mới hình thức/chỉnh sửa.

Khi tạo một sản phẩm, tôi cần phải kiểm tra các loại (thông qua hộp kiểm) mà nó thuộc về. Tôi biết nó có thể được thực hiện bằng cách tạo các hộp kiểm với tên như 'product [category_ids] []'. Nhưng tôi cũng cần phải nhập một mô tả cho mỗi quan hệ được kiểm tra sẽ được lưu trữ trong mô hình kết nối (Phân loại).

Tôi thấy những Railscasts đẹp trên các biểu mẫu phức tạp, hộp kiểm tra, vv .. Tôi đã tìm kiếm StackOverflow hầu như không. Nhưng tôi đã không thành công.

Tôi đã tìm thấy một post mô tả gần như chính xác cùng một vấn đề với tôi. Và câu trả lời cuối cùng có ý nghĩa với tôi (có vẻ như đó là cách đi đúng). Nhưng nó không thực sự hoạt động tốt (tức là nếu xác thực không thành công). Tôi muốn các danh mục được hiển thị luôn theo cùng thứ tự (trong biểu mẫu mới/chỉnh sửa; trước/sau khi xác thực) và các hộp kiểm để giữ nguyên vị trí nếu xác thực không thành công, v.v.

Bất kỳ lỗi nào được đánh giá cao. Tôi mới sử dụng Rails (chuyển đổi từ CakePHP) vì vậy hãy kiên nhẫn và viết chi tiết nhất có thể. Xin hãy chỉ cho tôi đúng cách!

Cảm ơn bạn. :)

Trả lời

26

Hình như tôi figured it out! Đây là những gì tôi nhận:

mô hình của tôi:

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 

Hình thức:

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

Và bộ điều khiển:

class ProductsController < ApplicationController 

    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 

Từ cái nhìn đầu tiên nó hoạt động tốt. Tôi hy vọng nó sẽ không phá vỡ bằng cách nào đó .. :)

Cảm ơn tất cả. Đặc biệt cảm ơn Sandip Ransing đã tham gia thảo luận. Tôi hy vọng nó sẽ hữu ích cho một người như tôi.

+1

được thực hiện tốt. tôi có cảm giác có thể có một cách dễ dàng hơn. – courtsimas

+0

Cảm ơn rất nhiều vì đã chia sẻ, tôi cũng đã bổ sung cho http://stackoverflow.com/a/15920542/148421 vì giá trị của tôi không được lưu và tôi đã bỏ lỡ cách cho phép các thuộc tính lồng nhau – Andrea

1

sử dụng accepts_nested_attributes_for để chèn vào intermediate table ví dụ categorizations dạng xem sẽ trông giống như -

# 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 

Bên trong cái nhìn

= 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 
+0

Cảm ơn bạn! Nhưng có vẻ như giải pháp của bạn không hoạt động tốt với xác thực và các nội dung khác. Hộp kiểm luôn được kiểm tra và thậm chí nếu tôi bỏ chọn chúng, nó sẽ bị bỏ qua (chúng quay lại sau khi xác thực không thành công hoặc nếu xác thực qua các quan hệ không bị loại bỏ). Ngoài ra, tôi muốn liệt kê các danh mục luôn theo thứ tự giống nhau (trong ví dụ của bạn, bạn đang thêm các danh mục chưa được chỉ định sau các danh mục đã được liên kết với sản phẩm). Bất kỳ ý tưởng? – ok32

+0

bạn có thể đặt hàng trên bộ sưu tập '@ product.categorizations' –

+0

bạn có thể dán tham số nhận được thông qua không? –

1

Tôi vừa làm như sau. Nó làm việc cho tôi ..

<%= f.label :category, "Category" %> 
<%= f.select :category_ids, Category.order('name ASC').all.collect {|c| [c.name, c.id]}, {} %> 
+0

Điều này không cho phép tham gia bổ sung trường mô hình như op được yêu cầu – dft