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,UserLocation
和 PostLocation
.
正如我最初所说,问题仍然存在,您如何知道 Location 是家庭位置还是远程位置,或者换句话说,location.locationable = some_user
设置了什么? Rails没办法知道,这才是你真正需要解决的问题。
鉴于上面的模型,有一些方法可以从 Location
导航到它的所有者,但要使其正常运行,我建议您将 type
字段添加到 Location
table 以便您知道它是 post
、home
还是 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 和 UserLocation
和 PostLocation
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
的想法仍然有效 :)
在所有情况下,您可能还需要编写代码来创建 homelocation
和 remotelocation
实例,而不是使用 accepts_nested_attributes
。此外,如果您要检索许多记录并尝试解决 n+1
问题,这些选项也不会很好地执行。
我有一个 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,UserLocation
和 PostLocation
.
正如我最初所说,问题仍然存在,您如何知道 Location 是家庭位置还是远程位置,或者换句话说,location.locationable = some_user
设置了什么? Rails没办法知道,这才是你真正需要解决的问题。
鉴于上面的模型,有一些方法可以从 Location
导航到它的所有者,但要使其正常运行,我建议您将 type
字段添加到 Location
table 以便您知道它是 post
、home
还是 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 和 UserLocation
和 PostLocation
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
的想法仍然有效 :)
在所有情况下,您可能还需要编写代码来创建 homelocation
和 remotelocation
实例,而不是使用 accepts_nested_attributes
。此外,如果您要检索许多记录并尝试解决 n+1
问题,这些选项也不会很好地执行。