Rails5、Polymorphic Associations和多重has_ones

Rails 5, Polymorphic Associations and multiple has_ones

我有一个 rails 应用如下: 一个存储一些地理信息(基本上是一个位置)的位置模型,一个 post 模型和一个用户模型。 post 模型可以有一个位置。用户模型可以将一个位置作为家庭位置,将另一个位置作为远程位置:

class Location < ApplicationRecord
  belongs_to :locationable, polymorphic: true
end

class Post < ApplicationRecord
  has_one :location, as: :locationable
  accepts_nested_attributes_for :location
end

class User < ApplicationRecord
  has_one :homelocation, as: :locationable, class_name: 'Location'
  has_one :remotelocation, as: :locationable, class_name: 'Location'
  accepts_nested_attributes_for :homelocation, :remotelocation
end

post 和位置信息非常有用。如果我从用户模型中删除其中一个“has_one”行并将 homelocation 重命名为 location,一切也都很好。如果我希望用户有两个不同的位置,我会在尝试保存更改时收到 'Unpermitted parameters: homelocation, remotelocation' 错误。

我的users_controller有一个

def user_params
  params.require(:user).permit(:admin, :name, :motto, homelocation_attributes: [:id, :address], remotelocation_attributes: [:id, :address])
end

正如 posts_controller 有一个

def post_params
  params.require(:post).permit(:title, :content, location_attributes: [:id, :address])
end

我的表单如下所示:

.form-group.string.required.user_homelocation_address
  label.control-label.string.required for="user_homelocation_address"
    abbr title="required"
    | Home Location
  input#user_homelocation_address.form-control.string.required name="user[homelocation][address]" type="text"

.form-group.string.required.user_remotelocation_address
  label.control-label.string.required for="user_remotelocation_address"
    abbr title="required"
    | Remote Location
  input#user_remotelocation_address.form-control.string.required name="user[remotelocation][address]" type="text"

那么,为什么这对一个 'has_one' 有效,但对两个无效?

问题确实是 Location 不知道它是用户的 homelocation 还是 remotelocation。解决办法是让 User 属于 Location

class Location < ApplicationRecord
end

class Post < ApplicationRecord
  belongs_to :location
  accepts_nested_attributes_for :location
end

class User < ApplicationRecord
  belongs_to :homelocation,   class_name: 'Location'
  belongs_to :remotelocation, class_name: 'Location'
  accepts_nested_attributes_for :homelocation, :remotelocation
end

显然,更改 table 以匹配。

没有简单的方法可以从 Location 返回到它的所有者。这是要求吗?

更新 1

我最初没有说的是,我认为多态 belongs_to 是那些 'considered evil' 主题之一,而且显然对要求的理解很模糊。它几乎总是一种糟糕的代码味道,这意味着你不能实现外键,我认为这是一种基本的做法,还有其他方法可以解决它试图解决的问题。我的直觉是创建 2 个模型和 2 个 tables,UserLocationPostLocation.

正如我最初所说,问题仍然存在,您如何知道 Location 是家庭位置还是远程位置,或者换句话说,location.locationable = some_user 设置了什么? Rails没办法知道,这才是你真正需要解决的问题。

鉴于上面的模型,有一些方法可以从 Location 导航到它的所有者,但要使其正常运行,我建议您将 type 字段添加到 Location table 以便您知道它是 posthome 还是 remote 位置。然后你可以写:

class Location < ApplicationRecord
  def owner
    case type
    when 'post'   Post.where(location_id: self).first
    when 'home'   User.where(homelocation_id: self).first
    when 'remote' User.where(remotelocation_id: self).first
  end
  # or # 
  def owner
    case type
    when 'post'   Post.where(location_id: self).first
    else          User.where('homelocation_id = ? OR remotelocation_id = ?', self, self).first
  end
end

理论上,您可以使用 Location class 和 UserLocationPostLocation subclasses 来做同样的事情。

选项 2

考虑了一下,我可能会这样实现:

class Location < ApplicationRecord
  belongs_to :locationable, polymorphic: true
  ## Again add a `type` field ##
end

class Post < ApplicationRecord
  has_one :location, as: :locationable
  accepts_nested_attributes_for :location
end

class User < ApplicationRecord
  has_many :locations, as: :locationable, class_name: 'Location'
  has_one  :homelocation,  class_name: 'Location', foreign_key: 'locationable_id', -> { where(type: 'home') }
  has_one  :remotelocation,  class_name: 'Location', foreign_key: 'locationable_id', -> { where(type: 'remote') }
  accepts_nested_attributes_for :homelocation, :remotelocation
end

虽然我对 belongs_to polymorphic 的想法仍然有效 :)

在所有情况下,您可能还需要编写代码来创建 homelocationremotelocation 实例,而不是使用 accepts_nested_attributes。此外,如果您要检索许多记录并尝试解决 n+1 问题,这些选项也不会很好地执行。