【问题标题】:Rails 5, Polymorphic Associations and multiple has_onesRails 5,多态关联和多个 has_ones
【发布时间】:2017-03-20 20:35:36
【问题描述】:

我有一个 Rails 应用程序,如下所示: 一个位置模型,它存储一些地理信息(基本上是一个位置)、一个帖子模型和一个用户模型。后模型可以有一个位置。用户模型可以将一个位置作为家庭位置,将另一个位置作为远程位置:

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

帖子和位置的东西效果很好。如果我从用户模型中删除“has_one”行并将 homelocation 重命名为 location,一切都很好。但是,如果我希望用户拥有两个不同的位置,则在尝试保存更改时会收到“未经许可的参数: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”,而不适用于两个?

【问题讨论】:

  • 对所有这些东西的很好的解释都在这里,顺便说一句:stackoverflow.com/questions/23814903/…@sirramongabriel - 只是遗憾的是,他似乎也无法弄清楚我的问题;阅读最后一段。

标签: ruby-on-rails activerecord polymorphism polymorphic-associations has-one


【解决方案1】:

问题实际上是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

而且,显然,更改表格以匹配。

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

更新 1

我最初没有说的,显然对需求的模糊理解,我认为多态 belongs_to 是那些“被认为是邪恶的”主题之一。这几乎总是一种不好的代码气味,这意味着您无法实现我认为必不可少的外键,并且还有其他方法可以解决它试图解决的问题。我的直觉是创建 2 个模型和 2 个表,UserLocationPostLocation

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

鉴于上面的模型,有一些方法可以从 Location 导航到它的所有者,但为了使其表现得体,我建议您在 Location 表中添加一个 type 字段,以便您知道它是否是posthomeremote 位置。然后你可以写:

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 类和UserLocationPostLocation 子类的STI 做同样的事情。

选项 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 问题,这些选项也不会很好地执行。

【讨论】:

  • 这不是我最初想要的,因为我需要从一个位置导航回一个用户......但也许你的解决方案仍然比我想要的更好:我可以使用其中一个SQL 查询中的地理函数,用于获取某个位置周围某个半径内的所有帖子或用户。
猜你喜欢
  • 2021-03-23
  • 1970-01-01
  • 2019-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多