如何用多态设计用户模型解决未定义的方法"model_name"?



我使用design进行注册,使用Rails 4和Ruby 2我定义了一个用户模型,它与发言人或组织有多态关联(我还没有对这个模型进行编程)。我创建了自己的Devise RegistrationController,用于在初始化用户对象时初始化适当的对象(Speaker)。根据一条特殊的路线。我已经编辑了带有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

用户注册控制器:

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

用户注册控制器#新的路由

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"}

我已经能够解决未定义方法的问题:

undefined method `model_name' for NilClass:Class

正如成员sevenseacat所提到的,在提交表单时,resource.identifiableUserRegistrationsControllercreate方法中的nil。解决方案是确保resource.identifiablecreate方法中设置了适当的对象,因为我正在User模型中使用多态关联。例如,如果我想注册SpeakerUser.identifiable(用户是设计模型)必须返回Speaker对象。


请注意,在编写解决方案时,与我在回答中发布的代码相比,我已经重写/优化了一些代码


首先,我更改了路线。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方法中,我正在检查默认哈希中的参数是否具有值SpeakerOrganization,以确保这是唯一允许的值。在构建了通常的User对象之后,我通过调用constantizenew来构建适当的可识别(SpeakerOrganization)对象。

create方法在create方法中,design将使用其参数构建User对象。然后,我获取需要与User对象关联的对象的名称。对象的名称是通过表单传入的。然后这个名称被固定化,这样就可以对其调用new

在Rails4中,默认情况下不允许进行批量分配。您必须指定允许哪些属性。使用方法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中,我添加了两个内容。第一个是一些代码,用于呈现适当的分部,该分部包含SpeakerOrganization对象的特定字段。其次,我确保需要与User对象关联的对象的名称通过,因此控制器中的create方法已经了解了它

最新更新