【问题标题】:Best way to handle repeated attribute groups in Rails models [closed]在 Rails 模型中处理重复属性组的最佳方法 [关闭]
【发布时间】:2021-02-11 15:11:37
【问题描述】:

在 Ruby on Rails 6 中处理具有相同名称、类型和用途的一组属性在整个应用程序中的多个模型上重复的场景的最佳方法是什么?

例如,(请注意,这只是一个示例,而不是我的实际设置),假设我们有一个 Person 模型,具有以下数据库迁移:

def change
  create_table :persons do |t|
    # The usual attributes...
    t.string :first_name
    t.string :last_name
    t.string :email
    # ...

    # Location attributes:
    t.string :country
    t.string :address
    t.string :city
    t.string :zip_code
  end
end

现在假设我们有另一个完全不同的模型,Building,它也有一个位置,就像这样:

def change
  create_table :buildings do |t|
    # Some attributes...
    t.string :name
    t.decimal :height
    t.references :type, foreign_key: { to_table: :building_types }
    # ...

    # Location attributes (the exact same ones as for Person):
    t.string :country
    t.string :address
    t.string :city
    t.string :zip_code
  end
end

可能还有更多带有“位置”的模型。

现在,假设在整个应用程序中使用位置的任何位置,都会计算出大致的纬度/经度。那么我怎样才能以这样的方式编写它,以便我不是 1)在迁移中重复属性和 2)不重复相关逻辑(即纬度/经度计算)?

一个选项

我想到的一个潜在解决方案是创建一个名为Location 的单独模型,并在PersonBuilding 中引用它。例如:

# xxx_create_persons.rb

class CreatePersons < ActiveRecord::Migration[6.1]
  def change
    create_table :persons do |t|
      # The usual attributes...
      t.string :first_name
      t.string :last_name
      t.string :email
      # ...

      # Just a single location reference:
      t.references :location
    end
  end
end
# xxx_create_buildings.rb

class CreateBuildings < ActiveRecord::Migration[6.1]
  def change
    create_table :buildings do |t|
      # Some attributes...
      t.string :name
      t.decimal :height
      t.references :type, foreign_key: { to_table: :building_types }
      # ...

      # Just a single location reference:
      t.references :location
    end
  end
end
# xxx_create_locations.rb

class CreateLocations < ActiveRecord::Migration[6.1]
  def change
    create_table :locations do |t|
      t.string :country
      t.string :address
      t.string :city
      t.string :zip_code
    end
  end
end

在模型类中:

# person.rb

class Person < ApplicationRecord
  # ...
  belongs_to :location
end
# building.rb

class Building < ApplicationRecord
  # ...
  belongs_to :location
end
# location.rb

class Location < ApplicationRecord
  # ...
  # With a little work, a polymorphic `has_one` could be added here.

  def calc_latitude
    # ...
  end

  def calc_longitude
    # ...
  end
end

那么,当然,我可以这样做:@building.location.calc_longitude

但是,这似乎有点矫枉过正。是不是每次我想访问PersonBuilding 的位置时都必须查询数据库,即使我已经加载了它们?最好的解决方案是什么?

【问题讨论】:

  • 你所说的“矫枉过正”是一个规范的解决方案,但不是唯一的解决方案。它是否最适合取决于许多其他背景和意见。请注意,您可以进行各种连接,以便在单个数据库调用中检索数据。
  • @DaveNewton 我明白了。还有其他不涉及单独数据库表的可行解决方案吗? (对我来说)似乎我不需要将数据分成多个表,因为我只使用一次位置数据的实例。但是,如果不是,您能否向我指出一个描述如何进行您提到的连接的资源?谢谢!
  • Rails 文档和许多教程一样涵盖了所有这些内容。
  • 如果您担心所有这些连接和表的性能,您可以查看物化视图,Mysql 和 Postgres 对它们有很好的支持。

标签: ruby-on-rails ruby model ruby-on-rails-6.1


【解决方案1】:

不,这不是矫枉过正。 Rails 允许您根据具体情况使用eager_loadpreload。如果您担心数据库性能,可以使用这些方法来限制数据库查询的数量。

但是,您的位置在第一个开发周期中可能看起来相同,但它们可能会变得更加复杂并具有不同的行为,因此您可以使用 STI 重构它们:

class Location < ActiveRecord; end
class BuildingLocation < Location; end
class PersonLocation < Location; end

将重复数据分隔在不同的表中是一种好方法。 如果您的每个用户和每个建筑物的位置共享相同的记录,您可以查看has_and_belongs_to_many 关系。示例:

class Location < ActiveRecord
  has_and_belongs_to_many :persons
  has_and_belongs_to_many :buildings
end

# then you can have the same location for both users and buildings
Person.first.location # => <#Location id: 1, ..>
Building.first.location # => <#Location id: 1, ..>

【讨论】:

  • 感谢您的及时答复!我完全忘记了使用eager_loadpreloadincludes 等的能力。这应该很好用。除非很快发布更好的答案,否则我很可能会接受你的。
  • 是的,我也想知道还有什么其他选择,但这个被认为是 rails-way
猜你喜欢
  • 2010-11-13
  • 1970-01-01
  • 1970-01-01
  • 2013-09-27
  • 2020-09-12
  • 1970-01-01
  • 1970-01-01
  • 2012-09-12
  • 1970-01-01
相关资源
最近更新 更多