Lin*_*der 2 ruby-on-rails ruby-on-rails-4
我试图在同一关系中关联不同的模型,就像这样;AUser可以有许多不同的朋友,但每个朋友模型在验证和数据方面都是不同的。
User.first.friends # => [BestFriend, RegularFriend, CloseFriend, ...]
Run Code Online (Sandbox Code Playgroud)
每个朋友类都有不同的列,但它们都响应相同的方法#to_s。
class User < ActiveRecord::Base
has_many :friends # What more to write here?
end
class BestFriend < ActiveRecord::Base
belongs_to :user
validates_presence_of :shared_interests # Shared between all friend models
validates_presence_of :favourite_colour # Does only exists in this model
def to_s
"This is my best friend whos favourite colour is #{favourite_colour} ..."
end
end
class RegularFriend < ActiveRecord::Base
belongs_to :user
validates_presence_of :shared_interests # Shared between all friend models
validates_presence_of :address # Does only exists in this model
def to_s
"This is a regular friend who lives at #{address} ..."
end
end
class CloseFriend < ActiveRecord::Base
belongs_to :user
validates_presence_of :shared_interests # Shared between all friend models
validates_presence_of :car_type # Does only exists in this model
def to_s
"This is a close friend who drives a #{car_type} ..."
end
end
Run Code Online (Sandbox Code Playgroud)
这是我正在努力实现的愿望。
user.friends.page(4).per(10)。user_id以确保记录是唯一的。朋友的属性user_id不能是唯一的,因为值可能会在朋友之间共享。这是一个如何使用它的示例。
user = User.create!
CloseFriend.create!({ car_type: "Volvo", shared_interests: "Diving", user: user })
RegularFriend.create!({ country: "Norway", shared_interests: "Swim", user: user })
BestFriend.create!({ favourite_colour: "Blue", shared_interests: "Driving", user: user })
BestFriend.create({ shared_interests: "Driving", user: user }) # => Invalid record
user.friends.page(1).per(3).order("created_at ASC").each do |friend|
puts friend.to_s
# This is a close friend who drives a Volvo ...
# This is a regular friend who lives in Norway ...
# This is my best friend whos favourite colour is Blue ...
end
Run Code Online (Sandbox Code Playgroud)
怎么可能解决这个问题?
我喜欢这个问题。我的回答会很长:)
首先,您会遇到问题,因为您的 Ruby OOP 架构与关系数据库原则相冲突。想想你存储数据的表。通常,您无法将数据存储在不同的表中并通过一个查询获取它。因为 SQL 以相同类型的记录响应您,但表的行具有不同的类型。
让我们从另一边看。你有用户模型。你有几个 ?Friend 模型,他们实际上都是朋友。因此,即使它们不共享行为,您也必须从一个基类(我猜是 Friend)继承它们,因为您希望在分页中将它们用作具有相同行为的对象(ruby 不需要它,但经典的 OOP 设计需要它) )。现在你在 RoR 中有类似的东西:
class User < AR::Base
has_many :friends
end
class Friend < AR::Base
belongs_to :user
end
class BestFriend < Friend
end
class OldFriend < Friend
end
Run Code Online (Sandbox Code Playgroud)
幸运的是,您不是第一个 :) 有一些模式将 OOP 继承投射到 RDBMS 上。他们由 Martin Fowler 在他的企业应用程序架构模式(又名 PoEAA)中描述。
alias属性,而 OldFriend 没有,但无论如何 ActiveRecord 将为您创建alias和alias=方法。从 OOP 的角度来看,它并不干净。您所能做的就是取消定义这些方法或使用raise 'Not Available for %s' % self.class.name.您可以使用 NoSQL 解决方案轻松实现目标。您只需在 MongoDB 之类的东西中创建朋友集合,并将所有数据存储在集合的文档中。但不要使用 Mongo :)
所以我建议你使用 STI,但我有一些不错的技巧。如果您使用 PostgreSQL,您可以将所有特定于模型的数据存储在 JSON 或 hstore 列中,并将 NoSQL 的灵活性与 RDBMS 模式严格性和 ActiveRecord 功能相结合。只需为特定于模型的列创建 setter/getter。像这样:
class Friend < AR::Base
def self.json_accessor(*names)
names.each do |name|
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
def #{name}
friend_details['#{name}']
end
def #{name}=(value)
# mark attribute as dirty
friend_details_will_change!
friend_details['#{name}'] = value
end
RUBY
end
end
end
class BestFriend < Friend
json_accessor :alias
end
Run Code Online (Sandbox Code Playgroud)
这不是唯一的方法,但我希望所有这些东西都能帮助您创建一个可靠的解决方案。
PS对不起。我认为这几乎是博文,而不仅仅是回答。但我对所有这些与数据库相关的事情非常热情:)
PPS 再次抱歉。就是停不下来 具体表继承的奖励。您仍然可以使用数据库视图创建具有单独表的多合一表。例子:
D B:
CREATE VIEW FRIENDS
AS
SELECT ID,
USER_ID,
NAME,
'best' TYPE
FROM BEST_FRIENDS
UNION ALL
SELECT ID,
USER_ID,
NAME,
'old' TYPE
FROM OLD_FRIENDS
UNION ALL
...
Run Code Online (Sandbox Code Playgroud)
导轨:
class User < AR::Base
has_many :friends
end
class Friend < AR::Base
belongs_to :user
def to_concrete
('%sFriend' % type.camelize).find(id)
end
end
class BestFriend < Friend
end
class OldFriend < Friend
end
Run Code Online (Sandbox Code Playgroud)
用法:
user = User.create!
BestFriend.create!(user_id: user.id, alias: 'Foo', name: 'Bar')
OldFriend.create!(user_id: user.id, name: 'Baz')
user.friends
# One SQL query to FRIENDS view
# [#Friend(id: 1, type: 'best', name: 'Bar'), #Friend(id: 2, type: 'old', name: 'Baz')]
user.friends.map(&:name)
# ['Bar', 'Baz']
user.first.alias
# NoMethodError
user.first.to_concrete
# #BestFriend(id: 1, name: 'Bar', alias: 'Foo')
user.first.to_concrete.alias
# 'Foo'
user.second.to_concrete
# #OldFriend(id: 2, name: 'Baz')
user.second.to_concrete.alias
# NoMethodError
Run Code Online (Sandbox Code Playgroud)