我正在使用Rails 4和Ruby 2进行注册 我已经定义了一个User模型,它与Speaker或Organization有多态关系(我还没有编写这个模型)。我已经创建了自己的Devise RegistrationController,用于在初始化User对象时初始化相应的对象(Speaker)。根据特殊路线。我已经使用Speaker对象的字段编辑了新的注册表单。 我想要实现的是当用户填写所有字段(来自User的字段和来自Speaker的字段)时,会创建适当的对象。因此,User对象具有指向Speaker对象的链接。但是,当我提交表单时出现以下错误,但我得到了以下错误,我当前的实现:
undefined method `model_name' for NilClass:Class
提取的来源(第35行):
<%=
fields_for resource.identifiable do | identifiable_fields |
render "#{resource.identifiable.class.name.downcase}_fields", f: identifiable_fields
end
%>
模型如下:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :identifiable, :polymorphic => true
end
class Speaker < ActiveRecord::Base
has_one :user, as: :identifiable
validates :first_name, :presence => true, length: { in: 2..20 }
validates :last_name, :presence => true, length: { in: 2..20 }
def full_name
[first_name, last_name].join(' ')
end
def full_name_reverse
[last_name, first_name].join(', ')
end
end
UserRegistrationsController:
class UserRegistrationsController < Devise::RegistrationsController
# Devise RegistrationsController override.
# version 3.2.3
def new
head :not_implemented and return unless is_identifiable_name?(params[:identifiable_name])
build_resource_with_identity({},params[:identifiable_name])
respond_with self.resource
end
def create
build_resource(sign_up_params)
puts sign_up_params
if resource.save
yield resource if block_given?
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
respond_with resource, :location => after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
respond_with resource, :location => after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
respond_with resource
end
end
private
def build_resource_with_identity(hash = nil, identifiable_name)
self.resource = resource_class.new_with_session(hash || {}, session)
self.resource.identifiable = identifiable_name.downcase.camelize.constantize.new
end
def is_identifiable_name?(identifiable_name)
['speaker'].include? identifiable_name.downcase
end
end
UserRegistrationsController的路由#new
get 'speakers/register', to: 'user_registrations#new', defaults: { identifiable_name: 'speaker' }, as: :new_speaker_registration
新用户注册表单:
<h2>Registreren</h2>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div class="row">
<div class="small-3 columns">
<%= f.label :email, 'Email', class: "right inline" %>
</div>
<div class="small-9 columns">
<%= f.email_field :email, :autofocus => true %>
</div>
</div>
<div class="row">
<div class="small-3 columns">
<%= f.label :password, 'Wachtwoord', class: "right inline" %>
</div>
<div class="small-9 columns">
<%= f.password_field :password, :autofocus => true %>
</div>
</div>
<div class="row">
<div class="small-3 columns">
<%= f.label :password_confirmation, 'Bevestig wachtwoord', class: "right inline" %>
</div>
<div class="small-9 columns">
<%= f.password_field :password_confirmation, :autofocus => true %>
</div>
</div>
<%=
fields_for resource.identifiable do | identifiable_fields |
render "#{resource.identifiable.class.name.downcase}_fields", f: identifiable_fields
end
%>
<div class="row">
<div class="small-9 small-offset-3">
<%= f.submit "Registreren", class: "small button radius" %>
</div>
</div>
<% end %>
<%= render "devise/shared/links" %>
扬声器字段部分:
<div class="row">
<div class="small-3 columns">
<%= f.label :first_name, 'Voornaam', class: "right inline" %>
</div>
<div class="small-9 columns">
<%= f.text_field :first_name, :autofocus => true %>
</div>
</div>
<div class="row">
<div class="small-3 columns">
<%= f.label :last_name, 'Achternaam', class: "right inline" %>
</div>
<div class="small-9 columns">
<%= f.text_field :last_name, :autofocus => true %>
</div>
</div>
日志:
应用程序跟踪:
app/views/user_registrations/new.html.erb:35:in `block in _app_views_user_registrations_new_html_erb___3888261175776937705_70060949603300'
app/views/user_registrations/new.html.erb:3:in `_app_views_user_registrations_new_html_erb___3888261175776937705_70060949603300'
app/controllers/user_registrations_controller.rb:28:in `create'
发送的参数:
{"utf8"=>"✓",
"authenticity_token"=>"vDLeKE+CJvzZa/GDcE9KqvV8jszWhc5uPBvBgBkTjO0=",
"user"=>{"email"=>"",
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]"},
"speaker"=>{"first_name"=>"",
"last_name"=>""},
"commit"=>"Registreren"}
答案 0 :(得分:0)
我已经能够解决未定义方法的问题:
undefined method `model_name' for NilClass:Class
正如成员sevenseacat所述,在提交表单时,resource.identifiable
在 UserRegistrationsController 的 create 方法中为nil
。解决方案是确保在resource.identifiable
方法中使用适当的对象设置create
,因为我在 User 模型中使用了多态关联。例如,如果我想注册扬声器,User.identifiable
(用户是设计模型)必须返回扬声器对象。
请注意,在编写解决方案时,我已经重写/优化了一些代码,而不是我在答案中发布的代码。
首先,我更改了 routes.rb 。 我已经在默认哈希中给出了它应该与User模型关联的模型的名称。
get 'speakers/register', to: 'user_registrations#new', defaults: { identifiable_type: 'Speaker' }, as: :new_speaker_registration
get 'organizations/register', to: 'user_registrations#new', defaults: { identifiable_type: 'Organization' }, as: :new_organization_registration
在 user_registrations_controller.rb 中,我改变了一些事情。
新方法
在new
方法中,我正在检查默认哈希中的参数是否具有值 Speaker 或 Organization ,以确保它们是允许的唯一值。
在构建通常的 User 对象之后,我通过调用constantize
构建适当的可识别( Speaker 或 Organization )对象,然后new
。
创建方法
在create方法中,devise将使用其参数构建User对象。
然后,我将获取需要与 User 对象关联的对象的名称。对象的名称通过表单传递。然后对此名称进行了公式化,以便可以在其上调用new
。
默认情况下不允许进行质量分配。您必须指定允许的属性。使用方法identifiable_resource_params
,我将根据对象的名称检查要传入的参数。
class UserRegistrationsController < Devise::RegistrationsController
# Devise RegistrationsController override.
# version 3.2.3
def new
head :not_implemented and return unless is_identifiable_type?(params[:identifiable_type])
build_resource
self.resource.identifiable = params[:identifiable_type].constantize.new
respond_with self.resource
end
def create
build_resource(sign_up_params)
identifiable_type = params[:user][:identifiable_type].to_s
resource.identifiable = identifiable_type.constantize.new(identifiable_resource_params(identifiable_type))
if resource.save
yield resource if block_given?
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
respond_with resource, :location => after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
respond_with resource, :location => after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
respond_with resource
end
end
private
def is_identifiable_type? identifiable_type
['Speaker','Organization'].include? identifiable_type
end
def is_speaker_type? identifiable_type
'Speaker'.eql?(identifiable_type)
end
def is_organization_type? identifiable_type
'Organization'.eql?(identifiable_type)
end
def identifiable_resource_params identifiable_type
if is_speaker_type? identifiable_type
params.require(identifiable_type.underscore.to_sym).permit(:first_name, :last_name)
elsif is_organization_type? identifiable_type
params.require(identifiable_type.underscore.to_sym).permit(:name, :description, :category, :contact_first_name, :contact_last_name, :contact_email)
end
end
end
在 new.html.erb 中,我添加了两件事。
第一个是一些代码,用于呈现包含 Speaker 或 Organization 对象的特定字段的相应部分。其次,我确保需要与 User 对象关联的对象的名称被传递,因此控制器中的create
方法知道它。