从 has_many 更改为 has_one 关系轨

Posted

技术标签:

【中文标题】从 has_many 更改为 has_one 关系轨【英文标题】:Changing from has_many to has_one relation rails 【发布时间】:2021-03-10 01:20:26 【问题描述】:

这里personaldetails belongs_to user,给定的关系是has_many,这是错误的。我想将has_many关系转换为has_one关系,即用户has_one个人详细信息。当我直接更改关系时,我收到错误“未初始化的常量 User::Personaldetails。请指导我如何转换关系。

个人详情.rb

class Personaldetail < ApplicationRecord
    belongs_to :user
end

用户.rb

class User < ApplicationRecord
 has_many :personaldetails, dependent: :destroy
accepts_nested_attributes_for :personaldetails, reject_if: :all_blank, allow_destroy: true
end

routes.rb

 resources :users, except: [:new] do
  resources :personaldetails
 end

user_steps_controller.rb

class UserStepsController < ApplicationController
    include Wicked::Wizard
    steps : :personaldetails
    def show
        @user = current_user
        @personaldetails = @user.personaldetails.build
        render_wizard
    end

def update
    @user = current_user
    @user.update!(user_params)
    render_wizard @user
end
   
private

def user_params
    params.require(:user).permit(:name, :password, :password_confirmation, :user_id,
      personaldetails_attributes: [:id,:first_name, :last_name, :gmail, :mobile_no, :city, :state, :pin_code, :_destroy])
end
end

personaldetails.html.erb

<%= form_with(model: @user, url: wizard_path, local: true) do |form| %> 
  <%= form.fields_for :personaldetail,Personaldetail.new do |info| %>
    <%= render 'personaldetails_field', form: info %>
  <% end %>
  <%= form.submit %>
<% end %>

_personaldetails_field.html.erb

<div class="field">
<%= form.label :First_name %><br />
<%= form.text_field :first_name %>
</div>
<div class="field">
<%= form.label :Last_name %><br />
<%= form.text_field :last_name %>
</div>
<div class="field">
<%= form.label :email %><br />
<%= form.text_field :gmail %>
</div>
<div class="field">
<%= form.label :Mobile_number %><br />
<%= form.text_field :mobile_no %>
</div>
<div class="field">
<%= form.label :City %><br />
<%= form.text_field :city %>
</div>
<div class="field">
<%= form.label :State %><br />
<%= form.text_field :state %>
</div>
<div class="field">
<%= form.label :Pincode %><br />
<%= form.text_field :pin_code %>
</div>

所以解决办法是:

个人详情.rb

class Personaldetail < ApplicationRecord
    belongs_to :user
end

用户.rb

class User < ApplicationRecord
 has_one :personaldetails, dependent: :destroy
accepts_nested_attributes_for :personaldetails, reject_if: :all_blank, allow_destroy: true
end

routes.rb

 resources :users, except: [:new] do
  resources :personaldetail
 end

user_steps_controller.rb

class UserStepsController < ApplicationController
    include Wicked::Wizard
    steps : :personaldetails
    def show
        @user = current_user
        render_wizard
    end

def update
    @user = current_user
    @user.update!(user_params)
    render_wizard @user
end
   
private

def user_params
    params.require(:user).permit(:name, :password, :password_confirmation, :user_id,
      personaldetails_attributes: [:id,:first_name, :last_name, :gmail, :mobile_no, :city, :state, :pin_code, :_destroy])
end
end

personaldetail.html.erb

<%= form_with(model: @user, url: wizard_path, local: true) do |form| %> 
  <%= form.fields_for :personaldetail,@user.personaldetail || @user.build_personaldetail do |info| %>
    <%= render 'personaldetail_field', form: info %>
  <% end %>
  <%= form.submit %>
<% end %>

_personaldetail_field.html.erb

<div class="field">
<%= form.label :First_name %><br />
<%= form.text_field :first_name %>
</div>
<div class="field">
<%= form.label :Last_name %><br />
<%= form.text_field :last_name %>
</div>
<div class="field">
<%= form.label :email %><br />
<%= form.text_field :gmail %>
</div>
<div class="field">
<%= form.label :Mobile_number %><br />
<%= form.text_field :mobile_no %>
</div>
<div class="field">
<%= form.label :City %><br />
<%= form.text_field :city %>
</div>
<div class="field">
<%= form.label :State %><br />
<%= form.text_field :state %>
</div>
<div class="field">
<%= form.label :Pincode %><br />
<%= form.text_field :pin_code %>
</div>

【问题讨论】:

Rails 命名约定要求 has_one 是单数形式。这意味着在代码中引用personal_details 数组的每个地方,您现在都需要直接引用personal_detail 实例。 那么现在是@user.personaldetail 吗? 【参考方案1】:

尝试:has_one :personaldetail, dependent: :destroy Rails 从名称和关联类型中猜测类名,因此使用 has_many 他们会尝试单数化关联名称 (personaldetails => Personaldetail) 但使用 has_one 他们会尝试按原样访问它 (personaldetails => Personaldetails)

【讨论】:

恐怕我没有完全理解。你能详细说明一下吗【参考方案2】:

正如 spickermann 的评论,has_many 关系需要复数形式,has_one 需要单数形式。 也就是说,您应该已经能够从以下方面推断出关系:

@user.personaldetails # user has many personal details
@user.personaldetail  # user has one personal detail

只是一个考虑:当对象/模型没有正确命名时,会出现许多奇怪的情况。根据经验,您应该为需要命名的对象使用最合适和最精确的英文名词。在这种情况下,这将对您有很大帮助。在正常的英语中,说“用户有个人详细信息”有点奇怪,但你当然会说“有个人详细信息”。特别是在涉及 ActiveRecord 关联时,Rails 语法应该尽可能接近英语,以避免以后的误解。例如,如果模型不是“PersonalDetail”,而是称为“Account”或“Profile”,我想就不会出现这种混淆。

【讨论】:

非常感谢您的回复,我其实有一个疑问,如果我使用has_one并创建一个personaldetail对象,然后创建另一个personaldetail的新对象,新对象会取代旧对象吗? 在 Rails 控制台中尝试一下,看看会发生什么。错误消息将是您最好的朋友,可帮助您了解 ActiveRecord 关系的工作原理:) 是的,我试过了,但没有出现错误。我其实以为我会得到一个错误 感谢您的建议,是的,您是对的,我们应该将其命名为个人资料或帐户。 不客气 - 假设personaldetail 对象是针对同一用户的 - 只需检查 user.personaldetail 并查看您创建的两个中的哪一个:)【参考方案3】:

一些建议/cmets

    将模型名称保留为 CamelCase,例如 PersonalDetail 而不是 Personaldetail 和关联名称 has_one :personal_detail 使用has_one 关系,您可以使用user.build_personal_detail.save 创建对象 当您再次运行第二步时,它将在personal_details 表中创建另一条记录,并在该事务中返回新记录。但是,之后当您尝试查询时,它将返回第一个创建的personal_details 记录而不是新记录。这是因为ActiveRecord 默认按 id 排序,has_one 关系限制为 1

【讨论】:

以上是关于从 has_many 更改为 has_one 关系轨的主要内容,如果未能解决你的问题,请参考以下文章

Yii中的相关表格

使用 has_many 关系保存记录

Rails ActiveRecord 关联“has_many,每个都有 has_one”

处理一个用户模型的 has_one 和 has_many 关联

如何在同一模型中进行 has_many 和 has_one 关联?

ThinkPHP模型中的HAS_ONE,BELONG_TO,HAS_MANY实践