我有两个模型item
和user_item
。 Item
有许多user_items
,user_items
属于item
。我有一个表单,用户可以在其中创建新项目。在表单中,用户应该包括图片。名称,描述和标签将保存到新的项目对象中。图片应作为属性保存在同时创建的user_item对象上。
我一直在阅读嵌套模型表单和强参数,并使用accepts_nested_attributes_for
。我的表格似乎有效,但我不明白为什么我不需要accepts_nested_attributes_for
。虽然它似乎有效,但是我这样做有问题吗?
表格
<%= simple_form_for @item, url: items_path, method: :post do |item_builder| %>
<div class="well">
<%= item_builder.input :name, required: false, error: false, label: "Item name" %>
<%= item_builder.input :description, as: :text, required: false, error: false, label: "Describe item" %>
<%= item_builder.input :tag_list, required: false, label: "Tags" %>
<%= item_builder.simple_fields_for @item, @item.user_items.build do |user_item_builder| %>
<%= user_item_builder.input :picture, as: :file, required: false, label: "Picture of you with this item" %>
<% end %>
</div>
<div class="clearfix">
<%= item_builder.submit 'Submit new item', class: "btn btn-primary pull-right inherit-width" %>
</div>
<% end %>
Items_controller
def create
Item.transaction do
@item = Item.create(name: item_params[:name],
description: item_params[:description],
tag_list: item_params[:tag_list],
created_by: current_user.id,
status: Item::STATUS[:pending])
if item_params[:item] == nil
@item.errors.add(:picture, "is required")
end
if @item.errors.empty?
@user_item = @item.user_items.build(user_id: current_user.id, picture: item_params[:item][:picture])
if @user_item.save
flash[:notice] = "Thank you for your item submission."
redirect_to items_path
else
render :new
raise ActiveRecord::Rollback, "Useritem create failed"
end
else
render :new
raise ActiveRecord::Rollback, "no picture"
end
end
end
private
def item_params
params.require(:item).permit(:name, :description, :tag_list,
item: :picture)
end
编辑:
我在下面的理查德答案中做了修改,但我现在遇到了几个问题。这是我在items_controller中的当前创建操作
def create
@item= Item.new item_params
@item.status = Item::STATUS[:pending]
@item.created_by = current_user.id
@item.user_items.first.user_id = current_user.id
if @item.save
redirect_to items_path, notice: "Thank you for your item request!
else
render :new
end
end
验证失败后,我收到了模板错误,因此我添加了else
和render :new
。
现在验证失败时,会渲染新模板,但不显示图片的fields_for输入。
我在user_item.rb中有validates_presence_of :picture
而且我有
item.rb中的validates_associated :user_items
,但我仍然可以提交没有图片的表单。
正如您在有效提交中看到的那样,我需要将item的status属性设置为pending,并将created_by属性设置为当前用户ID。我还需要将user_item的user_id属性设置为当前用户ID。我在控制器中设置了它。我想知道如果像创建动作那样设置它是正确的方法。
答案 0 :(得分:4)
你不需要它,因为你的模式是错误的:
@user_item = @item.user_items.build(user_id: current_user.id, picture: item_params[:item][:picture])
使用上面的代码,您可以从每个user_items
创建单独的@item
。这不需要accepts_nested_attributes_for
,但是很麻烦,反对convention而且非常苛刻。
以下是如何做到的:
<强>模型强>
#app/models/item.rb
class Item < ActiveRecord::Base
has_many :user_items
has_many :users, through: :user_items
accepts_nested_attributes_for :user_items
end
#app/models/user_item.rb
class UserItem < ActiveRecord::Base
belongs_to :user
belongs_to :item
end
<强>控制器强>
#app/controllers/items_controller.rb
class ItemsController < ApplicationController
def new
@item = Item.new
@item.user_items.build
end
def create
@item = Item.new item_params
redirect_to @item, notice: "Thank you for your item submission." if @item.save
end
private
def item_params
params.require(:item).permit(:name, :description, :tag_list, user_items_attributes: [:picture])
end
end
<强>视图强>
#app/views/items/new.html.erb
<%= simple_form_for @item do |item_builder| %>
<%= item_builder.input :name, required: false, error: false, label: "Item name" %>
<%= item_builder.input :description, as: :text, required: false, error: false, label: "Describe item" %>
<%= item_builder.input :tag_list, required: false, label: "Tags" %>
<%= item_builder.simple_fields_for :user_items do |user_item_builder| %>
<%= user_item_builder.input :picture, as: :file, required: false, label: "Picture of you with this item" %>
<% end %>
<%= item_builder.submit 'Submit new item', class: "btn btn-primary pull-right inherit-width" %>
<% end %>
我做这件事的方式有问题
从技术上讲,否。
但是,如果你渴望成为一名专业人士,或者在类似的水平上,你就不会对你所编写的代码走得太远。
使用像Rails这样的框架,您可以访问前所未有的预烘焙功能。最好的代码不是你编写的代码,而是编译过的库代码。经过多年的生产使用测试。
虽然您的代码有效,但效率不高,extensible。
-
您所写的内容的最终关键是&#34;对&#34;未来你是否会为再次看待它而自豪。如果没有,你可能最好重构。
<强>更新强>
如果您想Item
一个status
,您需要查看ActiveRecord模型的enum
模块:
#app/models/item.rb
class Item < ActiveRecord::Base
enum status: [:active, :pending, :declined]
end
这是一个非常有趣的方法,因为它提供了一系列类方法(范围)和实例方法:
@item = Item.find x
@item.active? #-> true
@item.pending? #-> false
@item.declined? #-> false
Item.active #-> collection of "active" items
Item.pending #-> collection of "pending" items
Item.declined #-> collection of "declined" items
要保存项目status
,您可以使用collection_select
:
<%= form_for @item do |f| %>
<%= f.collection_select :status, Item.statuses, :first, :first %>
<%= f.submit %>
<% end %>
<强>更新强>
您的代码可以大规模改进:
def create
@item= Item.new item_params #-> this line should do ALL the heavy lifting.
if @item.save
redirect_to items_path, notice: "Thank you for your item request!"
else
render :new
end
end
private
def item_params
params.require(:item).permit(:name, :description, :tag_list, user_items_attributes: [:picture]).merge(created_by: current_user.id)
end
如果您有验证问题,则file
字段不会再次填充;它是一个操作系统问题,而不是Rails(操作系统如何知道您的文件与您第一次提交时的位置相同?)。
您需要尽可能简洁地使用create
代码;属性的显式声明通常是个坏主意。您应该尽可能多地将其放入Item
模型(enum
等)。
答案 1 :(得分:1)
虽然它似乎有效,但我的方式是否存在问题 这样做?
它的功能很棒 - 但它确实有代码味道。您的控制器可以使用属于模型层的大量业务逻辑,并且您将被迫在更新操作中使用param检查重复整个混乱。
让我们看一下 Rails方式:
class Item
enum status: [:pending, :approved, :awesome] # or whatever statuses you have
has_many :user_items
belongs_to :creator, class_name: 'User' # or author or whatever
validates_associated :user_items
accepts_nested_attributes_for :user_items, reject_if: :all_blank?
end
class UserItem
belongs_to :item
validates :picture, presence: true
end
class User
has_many :items, source: :creator, inverse_of: :creator
end
这里我们将项目设置为accepts_nested_attributes_for :user_items
并设置UserItem的验证,validates_associated
将导致项验证失败,除非所有关联的UserItem都有效。
ActiveRecord::Enum使用item.status
做我认为你正在做的事情是一种不那么愚蠢的方式。
我们的模型得到了照顾,我们可以继续使用控制器:
class ItemsController
before_action :set_item, only: [:show, :edit, :update, :destroy]
def new
@item = Item.new
@item.user_items.new # seeds the form
end
def edit
@item.user_items.new unless @item.user_items.any? # seeds the form
end
def create
@item = Item.new(item_params) do |item|
item.status = :pending
item.creator = current_user
end
if @item.save
redirect_to items_path, success: 'Oh yes'
else
render :new, error: 'Oh noes'
end
end
def update
if @item.update(item_params)
redirect_to @item, success: 'Item updated'
else
render :edit, error: 'Oh noes'
end
end
private
def set_item
@item = Item.includes(:user_items).find(params[:id])
end
def item_params
params.require(:item)
.permit(:name, :description, :tag_list, user_items_attributes: [:picture])
end
end
这是MVC Rails方式 - 模型负责验证和关联。我们使用强参数以可重用的方式将表单输入映射到模型。
最后让我们清理表格。我们将首先将表单提取为部分,以便可以重复使用。
<% # views/items/_form.html.erb %>
<%= simple_form_for @item do |item_builder| %>
<div class="well">
<%= item_builder.input :name, require: false, label: "Item name" %>
<%= item_builder.input :description, require: false, as: :text, label: "Describe item" %>
<%= item_builder.input :tag_list, label: "Tags", require: false %>
<%= item_builder.simple_fields_for :user_items do |user_item_builder| %>
<%= user_item_builder.input :picture, as: :file, label: "Picture of you with this item" %>
<% end %>
</div>
<div class="clearfix">
<%= item_builder.submit %>
</div>
<% end %>
然后,我们可以将此表单用于new
和edit
。
<h1>Create a new item</h1>
<%= render partial: '_form' %>
<h1>Edit <%= @item.name %></h1>
<%= render partial: '_form' %>