rails 中的动态表单,NoMethodError
dynamic forms in rails, NoMethodError
我是 rails 的新手,我正在根据这个 railscast 构建一些东西,
https://github.com/railscasts/403-dynamic-forms
因为它已经过时了,我无法下载它所依赖的旧 gems 之一 - 至少,使用 RVM - 所以我无法真正检查它。
然而,有人将它从 rails 3 更新到 4.2,https://github.com/asang/403-dynamic-forms,但我一直收到 undefined method 'fields' for nil:NilClass
错误。
Product.rb
class Product < ActiveRecord::Base
belongs_to :product_type
serialize :properties, Hash
validate :validate_properties
def validate_properties
product_type.fields.each do |field|
if field.required? && properties[field.name].blank?
errors.add field.name, "must not be blank"
end
end
end
end
附带问题,当从 rails 3 更改为 rails 4.2 时,product_field.rb 如何访问 :field_type
,而 :name
如果它没有控制器?
Product_field.rb - Rails 3
class ProductField < ActiveRecord::Base
belongs_to :product_type
attr_accessible :field_type, :name, :required
end
Product_field.rb - Rails 4.2
class ProductField < ActiveRecord::Base
belongs_to :product_type
end
Product_types_controller.rb
class ProductTypesController < ApplicationController
def index
@product_types = ProductType.all
respond_to do |format|
format.html
format.json { render json: @product_types }
end
end
def show
@product_type = ProductType.find(params[:id])
respond_to do |format|
format.html
format.json { render json: @product_type }
end
end
def new
@product_type = ProductType.new
respond_to do |format|
format.html
format.json { render json: @product_type }
end
end
def edit
@product_type = ProductType.find(params[:id])
end
def create
@product_type = ProductType.new(product_type_params)
respond_to do |format|
if @product_type.save
format.html { redirect_to @product_type, notice: 'Product type was successfully created.' }
format.json { render json: @product_type, status: :created, location: @product_type }
else
format.html { render action: "new" }
format.json { render json: @product_type.errors, status: :unprocessable_entity }
end
end
end
def update
@product_type = ProductType.find(params[:id])
respond_to do |format|
if @product_type.update_attributes(product_type_params)
format.html { redirect_to @product_type, notice: 'Product type was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: @product_type.errors, status: :unprocessable_entity }
end
end
end
def destroy
@product_type = ProductType.find(params[:id])
@product_type.destroy
respond_to do |format|
format.html { redirect_to product_types_url }
format.json { head :no_content }
end
end
def product_type_params
params.require(:product_type).permit(
:name, fields_attributes: [ :field_type, :name, :required ] )
end
end
Product_controller.rb
class ProductsController < ApplicationController
def index
@products = Product.all
end
def show
@product = Product.find(params[:id])
end
def new
@product = Product.new(product_type_id: params[:product_type_id])
end
def edit
@product = Product.find(params[:id])
end
def create
@product = Product.new(product_params)
if @product.save
redirect_to @product, notice: 'Product was successfully created.'
else
render action: "new"
end
end
def update
@product = Product.find(params[:id])
if @product.update_attributes(product_params)
redirect_to @product, notice: 'Product was successfully updated.'
else
render action: "edit"
end
end
def destroy
@product = Product.find(params[:id])
@product.destroy
redirect_to products_url
end
private
def product_params
params.require(:product).permit(:name, :price, :product_type_id,
:properties)
end
end
_form.html.erb
<%= form_for @product do |f| %>
<% if @product.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@product.errors.count, "error") %> prohibited this product from being saved:</h2>
<ul>
<% @product.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.hidden_field :product_type_id %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :price %><br />
<%= f.text_field :price %>
</div>
<%= f.fields_for :properties, OpenStruct.new(@product.properties) do |builder| %>
<% @product.product_type.fields.each do |field| %>
<%= render "products/fields/#{field.field_type}", field: field, f: builder %>
<% end %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
日志
Completed 500 Internal Server Error in 9ms (ActiveRecord: 0.0ms)
ActionView::Template::Error (undefined method `fields' for nil:NilClass):
22: </div>
23:
24: <%= f.fields_for :properties, OpenStruct.new(@product.properties) do |builder| %>
25: <% @product.product_type.fields.each do |field| %>
26: <%= render "products/fields/#{field.field_type}", field: field, f: builder %>
27: <% end %>
28: <% end %>
app/views/products/_form.html.erb:25:in `block (2 levels) in _app_views_products__form_html_erb__38604707748806799_70122829936940'
app/views/products/_form.html.erb:24:in `block in _app_views_products__form_html_erb__38604707748806799_70122829936940'
app/views/products/_form.html.erb:1:in `_app_views_products__form_html_erb__38604707748806799_70122829936940'
app/views/products/new.html.erb:3:in `_app_views_products_new_html_erb__487839569246893265_70122829438980'
有人在 railscast 上发表了一个评论也有同样的问题,其他人回复说这与遗漏 <%= f.hidden_field :product_type_id %>
有关,我错过了什么吗?感谢您花时间查看这个很可能是愚蠢的错误。
问题在于您如何在 ProductsController#new
中的这一行实例化您的 @product
:
def new
@product = Product.new(product_type_id: params[:product_type_id])
end
您是通过其 ID 引用 product_type,而不是作为具体引用。由于@product 未保存在您的 new
操作中,因此 @product.product_type
永远不会从数据库中加载,并且将始终为 nil
。要修复,请加载 product_type
并直接从新产品引用它:
def new
product_type = ProductType.find(params[:product_type_id])
@product = Product.new(product_type: product_type)
end
我是 rails 的新手,我正在根据这个 railscast 构建一些东西,
https://github.com/railscasts/403-dynamic-forms
因为它已经过时了,我无法下载它所依赖的旧 gems 之一 - 至少,使用 RVM - 所以我无法真正检查它。
然而,有人将它从 rails 3 更新到 4.2,https://github.com/asang/403-dynamic-forms,但我一直收到 undefined method 'fields' for nil:NilClass
错误。
Product.rb
class Product < ActiveRecord::Base
belongs_to :product_type
serialize :properties, Hash
validate :validate_properties
def validate_properties
product_type.fields.each do |field|
if field.required? && properties[field.name].blank?
errors.add field.name, "must not be blank"
end
end
end
end
附带问题,当从 rails 3 更改为 rails 4.2 时,product_field.rb 如何访问 :field_type
,而 :name
如果它没有控制器?
Product_field.rb - Rails 3
class ProductField < ActiveRecord::Base
belongs_to :product_type
attr_accessible :field_type, :name, :required
end
Product_field.rb - Rails 4.2
class ProductField < ActiveRecord::Base
belongs_to :product_type
end
Product_types_controller.rb
class ProductTypesController < ApplicationController
def index
@product_types = ProductType.all
respond_to do |format|
format.html
format.json { render json: @product_types }
end
end
def show
@product_type = ProductType.find(params[:id])
respond_to do |format|
format.html
format.json { render json: @product_type }
end
end
def new
@product_type = ProductType.new
respond_to do |format|
format.html
format.json { render json: @product_type }
end
end
def edit
@product_type = ProductType.find(params[:id])
end
def create
@product_type = ProductType.new(product_type_params)
respond_to do |format|
if @product_type.save
format.html { redirect_to @product_type, notice: 'Product type was successfully created.' }
format.json { render json: @product_type, status: :created, location: @product_type }
else
format.html { render action: "new" }
format.json { render json: @product_type.errors, status: :unprocessable_entity }
end
end
end
def update
@product_type = ProductType.find(params[:id])
respond_to do |format|
if @product_type.update_attributes(product_type_params)
format.html { redirect_to @product_type, notice: 'Product type was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: @product_type.errors, status: :unprocessable_entity }
end
end
end
def destroy
@product_type = ProductType.find(params[:id])
@product_type.destroy
respond_to do |format|
format.html { redirect_to product_types_url }
format.json { head :no_content }
end
end
def product_type_params
params.require(:product_type).permit(
:name, fields_attributes: [ :field_type, :name, :required ] )
end
end
Product_controller.rb
class ProductsController < ApplicationController
def index
@products = Product.all
end
def show
@product = Product.find(params[:id])
end
def new
@product = Product.new(product_type_id: params[:product_type_id])
end
def edit
@product = Product.find(params[:id])
end
def create
@product = Product.new(product_params)
if @product.save
redirect_to @product, notice: 'Product was successfully created.'
else
render action: "new"
end
end
def update
@product = Product.find(params[:id])
if @product.update_attributes(product_params)
redirect_to @product, notice: 'Product was successfully updated.'
else
render action: "edit"
end
end
def destroy
@product = Product.find(params[:id])
@product.destroy
redirect_to products_url
end
private
def product_params
params.require(:product).permit(:name, :price, :product_type_id,
:properties)
end
end
_form.html.erb
<%= form_for @product do |f| %>
<% if @product.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@product.errors.count, "error") %> prohibited this product from being saved:</h2>
<ul>
<% @product.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.hidden_field :product_type_id %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :price %><br />
<%= f.text_field :price %>
</div>
<%= f.fields_for :properties, OpenStruct.new(@product.properties) do |builder| %>
<% @product.product_type.fields.each do |field| %>
<%= render "products/fields/#{field.field_type}", field: field, f: builder %>
<% end %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
日志
Completed 500 Internal Server Error in 9ms (ActiveRecord: 0.0ms)
ActionView::Template::Error (undefined method `fields' for nil:NilClass):
22: </div>
23:
24: <%= f.fields_for :properties, OpenStruct.new(@product.properties) do |builder| %>
25: <% @product.product_type.fields.each do |field| %>
26: <%= render "products/fields/#{field.field_type}", field: field, f: builder %>
27: <% end %>
28: <% end %>
app/views/products/_form.html.erb:25:in `block (2 levels) in _app_views_products__form_html_erb__38604707748806799_70122829936940'
app/views/products/_form.html.erb:24:in `block in _app_views_products__form_html_erb__38604707748806799_70122829936940'
app/views/products/_form.html.erb:1:in `_app_views_products__form_html_erb__38604707748806799_70122829936940'
app/views/products/new.html.erb:3:in `_app_views_products_new_html_erb__487839569246893265_70122829438980'
有人在 railscast 上发表了一个评论也有同样的问题,其他人回复说这与遗漏 <%= f.hidden_field :product_type_id %>
有关,我错过了什么吗?感谢您花时间查看这个很可能是愚蠢的错误。
问题在于您如何在 ProductsController#new
中的这一行实例化您的 @product
:
def new
@product = Product.new(product_type_id: params[:product_type_id])
end
您是通过其 ID 引用 product_type,而不是作为具体引用。由于@product 未保存在您的 new
操作中,因此 @product.product_type
永远不会从数据库中加载,并且将始终为 nil
。要修复,请加载 product_type
并直接从新产品引用它:
def new
product_type = ProductType.find(params[:product_type_id])
@product = Product.new(product_type: product_type)
end