处理 Rails 模型中重复属性组的最佳方法
Best way to handle repeated attribute groups in Rails models
在 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
可能还有更多带有“位置”的模型。
现在,假设在整个应用程序中使用位置的任何地方,都会计算一个近似值 latitude/longitude。那么我怎样才能以这样的方式写这个,这样我就不会 1) 在迁移中重复属性和 2) 不重复相关逻辑(即 latitude/longitude 计算)?
一个选项
我想到的一个可能的解决方案是创建一个名为 Location
的单独模型并在 Person
和 Building
中引用它。例如:
# 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
。
然而,这似乎有点矫枉过正。难道我每次想访问 Person
或 Building
的位置时都必须查询数据库,即使我已经加载了它们吗?什么是最好的解决方案?
不,这并不过分。 Rails 允许您根据不同情况使用 eager_load
or preload
。如果您担心数据库性能,可以使用这些方法来限制数据库查询次数。
但是,您的位置在第一个开发周期中可能看起来相同,但它们可能会变得更加复杂并具有不同的行为,因此您可以使用 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, ..>
在 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
可能还有更多带有“位置”的模型。
现在,假设在整个应用程序中使用位置的任何地方,都会计算一个近似值 latitude/longitude。那么我怎样才能以这样的方式写这个,这样我就不会 1) 在迁移中重复属性和 2) 不重复相关逻辑(即 latitude/longitude 计算)?
一个选项
我想到的一个可能的解决方案是创建一个名为 Location
的单独模型并在 Person
和 Building
中引用它。例如:
# 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
。
然而,这似乎有点矫枉过正。难道我每次想访问 Person
或 Building
的位置时都必须查询数据库,即使我已经加载了它们吗?什么是最好的解决方案?
不,这并不过分。 Rails 允许您根据不同情况使用 eager_load
or preload
。如果您担心数据库性能,可以使用这些方法来限制数据库查询次数。
但是,您的位置在第一个开发周期中可能看起来相同,但它们可能会变得更加复杂并具有不同的行为,因此您可以使用 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, ..>