最佳实践方法 - 在 1 form_for 中创建 2 条记录

Best practice method - creating 2 records in 1 form_for

我的模特:

brand.rb

  has_many :products
  has_many :votes
  belongs_to :user
  accepts_nested_attributes_for :products, :allow_destroy => true

product.rb

  belongs_to :user
  belongs_to :brand

vote.rb

  belongs_to :brand
  belongs_to :user

routes.rb

  resources :brands do
    resources :products
  end

我的目标:Brand 的现有 Brand 记录上创建 2 条记录(产品和投票),brand/show页。

我的解决方案:

brand/show.html.erb

<% form_for([@brand, @brand.send(:product).klass.new]) do |f| %>
  <%= f.label :title %>
  <%= f.text_field :title %>

  <%= f.label :price %>
  <%= f.text_field :price %>

  <%= fields_for :votes, @brand.votes.new do |builder| %>
    <%= builder.label :rating %>
    <%= builder.text_field :rating %>
  <% end %>

  <%= f.submit %>
<% end %>

products_controller.rb

def create
  if Brand.exists?(:id => params[:brand_id])
    @review          = Review.new(review_params)
    @vote            = Vote.new(votes_params)
    @review.user_id  = @vote.user_id = current_user.id
    @review.brand_id = @vote.brands_id = params[:brand_id]

    if @vote.valid? && @review.valid?
      @vote.save
      @review.save
      redirect_to brands_path
    else
      flash[:errors][:vote]   = @vote.errors
      flash[:errors][:review] = @review.errors
      redirect_to brands_path
    end
  end
end    

private
def product_params
  params.require(:review).permit(:title, :price)
end    

def votes_params
  params.require(:votes).permit(:rating)
end

这是解决我的任务的正确方法吗?我可以这样使用吗?

我将更改以下逻辑:

@review.user_id = @vote.user_id = current_user.id
@review.server_id = @vote.server_id = params[:server_id]

只需将 current_user.id 和参数 [:server_id] 分别添加到 product_params 和 votes_params。

此外,没有必要为 vote/review 使用实例变量。

除此之外,保存两个模型对我来说似乎还可以。

这就是我重构您的创建方法的方式:

def create
  brand = Brand.find(params[:brand_id]) # no test to know if Brand exists, if it does not it means the user gave a wrong Brand id, then a 404 error should be rendered
  @product = brand.products.create(products_params.merge({user_id: current_user.id}) # we can directly create this instance
  @vote    = brand.votes.create(votes_params) # we can directly create this instance
  # we add the errors in the flash if they exist
  flash[:errors][:vote]   = @vote.errors if @vote.errors.present?
  flash[:errors][:product] = @product.errors if @product.errors.present?

  redirect_to brands_path # since we want to redirect to brands_path if the creation succeeded or failed, we don't need to use it twice in the code
end 

另外,小改进:

@brand.send(:product).klass.new
# can become
@brand.products.new # produces a initialized Product instance

fields_for :votes, @brand.votes.new
# can become
f.fields_for @brand.votes.new
# notice the usage of `f` builder to generate the fields_for
# it will nest the params in `params[:brand][:votes_attributes][0]`

# brand.rb
accepts_nested_attributes_for :products, :allow_destroy => true
# add the following:
accepts_nested_attributes_for :votes, :allow_destroy => true

您显然必须相应地更新强参数,但这是简单的部分 ;-)