【问题标题】:One relationship with multiple models [interesting rails relations concept]一个与多个模型的关系[有趣的轨道关系概念]
【发布时间】:2018-02-10 13:47:27
【问题描述】:

我有这个应用程序,其中模型User 可以有多个“通道”。应用程序的通道部分必须易于扩展,每个通道都有自己的模型

我开始创建一个Channel 模型,该模型具有belongs_to UserUser has_many Channel 的反向关系。

我在考虑有一个类似这样的 API:

user = User.create(name: 'John')

user.channels.create(name: 'google', account_id: '1234') # should create a GoogleChannel::Google Model
user.channels.create(name: 'facebook', account_name: 'qwerty') # should create a FacebookChannel::Facebook Model

我正在尝试拥有一个 Channel 模型,并且每个频道模型都有一个专用模型,用于每个频道(谷歌、Facebook 等),并与该模型具有 has_one 关系。

更新:

我正在使用 mongoid 和 rails

【问题讨论】:

  • 好像你在谈论 STI。
  • 还是多态?

标签: ruby-on-rails activerecord orm model relationship


【解决方案1】:

我不确定这是否有效。它使用 STI。

第一种方法:单表继承

class User << ApplicationRecord
  has_many :channels
  delegate :google_channels, :facebook_channels, :twitter_channels, to: :channels
end

class Channel << ApplicationRecord
  belongs_to :user
  self.inheritance_column = :brand

  scope :google_channels,   -> { where(brand: 'Google')   } 
  scope :facebook_channels, -> { where(brand: 'Facebook') } 
  scope :twitter_channels,  -> { where(brand: 'Twitter') }

  def self.brands
    %w(Google Facebook Twitter)
  end 
end

class GoogleChannel << Channel; end
class FacebookChannel << Channel; end
class TwitterChannel << Channel; end

我认为你可以:

current_user.channels << GoogleChannel.new(name: "First Google Channel") 
current_user.channels << Facebook.new(name: "Facebook") 
current_user.channels << Twitter.new(name: "Tweety")
current_user.channels << GoogleChannel.new(name: "Second Google Channel") 

googs = current_user.google_channels
all = current_user.channels
# etc.

所有频道共享同一张表。如果您需要为每个不同的品牌提供不同的属性,这将不是最佳选择。

第二种方法:多态模型

如果每个模型(品牌)需要不同的表,可以使用多态方法(未测试):

class User << ApplicationRecord
  has_many :channels
  has_many :google_channels, through: :channels, source: :branded_channel, source_type: 'GoogleChannel'
  has_many :facebook_channels, through: :channels, source: :branded_channel, source_type: 'FacebookChannel'
end

class Channel << ApplicationRecord
  belongs_to :user
  belongs_to :branded_channel, polymorphic: true
end

#This channel has its own table, and can have more attributes than Channel
class GoogleChannel << ApplicationRecord
  has_one :channel, as: :branded_channel
end

#This channel has its own table, and can have more attributes than Channel
class FacebookChannel << ApplicationRecord
  has_one :channel, as: :branded_channel
end

goog = GoogleChannel.create(all_google_channel_params)
face = GoogleChannel.create(all_facebook_channel_params)
current_user.channels << Channel.new(branded_channel: goog)
current_user.channels << Channel.new(branded_channel: face) 

【讨论】:

  • 嗯...很有趣。我今天试试看
  • 是的,就是这样。每个品牌的模型(不同的表)对我正在构建的应用程序最有意义。我希望我能得到一个类似user.channel.model.visit 的API,并在通道模型上运行visit 方法(例如:GoogleChannel::Google),而不指定我想要的通道类型(user.channels.google_channels)。
  • 我发布了使用多态模型的第二种方法,但我不确定它是否有效(应该进行一些更改)
  • 谢谢Pablo,但我认为我想要的是一种“魔法”。不知道能不能按照我的方式去做。
【解决方案2】:

我假设您想创建 STI,其中 User 有很多频道,所以在 Rails 5 中您可以尝试以下操作:

class User < ApplicationRecord
  has_many :user_channels
  has_many :channels, through: :user_channels
  has_many :facebook_channels, class_name: 'FacebookChannel', through: :user_channels
  has_many :google_channels, class_name: 'GoogleChannel', through: :user_channels
end

class UserChannel < ApplicationRecord
  # user_id
  # channel_id
  belongs_to :user
  belongs_to :channel
  belongs_to :facebook_channel, class_name: 'FacebookChannel', foreign_key: :channel_id, optional: true
  belongs_to :google_channel, class_name: 'GoogleChannel', foreign_key: :channel_id, optional: true
end

class Channel < ApplicationRecord
  # You need to have "type" column to be created for storing different chanels
  has_many :user_channels
  has_many :users, through: :user_channels
end

class FacebookChannel < Channel
  has_many :user_channels
  has_many :users, through: :user_channels
end

class GoogleChannel < Channel
  has_many :user_channels
  has_many :users, through: :user_channels
end

现在您可以通过current_user.facebook_channels.create(name: "My Facebook Channel") 获取所有FacebookChannelcurrent_user.facebook_channels

基本上,它与普通的 has_many through 关系一样工作,具有 STI 的额外功能 - 您将子模型名称存储在 Channel 模型的 type 列中。

更新

对不起,我不知道我的建议不适用于 MongoDB。也许你可以简单地在你的channels 表中创建channel_type 列,有简单的channel belongs_to user 关系然后这样做:

  1. current_user.channels.create(name: "My Channel Name", channel_type: "facebook")
  2. current_user.channels.where(channel_type: "facebook")

您可以简单地执行current_user.channels,它为您提供用户的所有渠道,然后如果您需要,您可以根据channel_type 值对每条记录执行您需要的任何操作。 你甚至可以在 Channel 模型中创建一些作用域:

class Channel < ApplicationRecord
  scope :facebook_channels, -> { where(channel_type: "facebook") }
end

然后你就可以做你想要的current_user.channels.facebook_channels

channel_type 列可以是字符串或整数,如果你使用枚举的话。

此外,如果您创建visit 列(例如“布尔”),您可以执行current_user.channels.where(visit: true) 或创建scope :visit_true, -&gt; { where(visit: true) } 并执行current_user.channels.visit_true 之类的操作

你说什么?

【讨论】:

  • 嗯,它更多的是多表关系。我希望拥有更多 current_user.channel.model 的 api,但不知道那是什么模型。所以我不必猜测/找出它是谷歌还是 Facebook 频道。
  • 顺便说一句,我使用的是 mongoid,所以我没有 has_many_through 关系:|
  • @adrianthedev 请看我上面的更新。
  • 是的@matiss。这就是我所做的。这不是 rails/AR/relationships 方式,但它会为我做。现在我有了这个桥接模型Channel,每个通道都有不同的模型(所以我不会超载Channel 模型)。我后悔没有的是关系查询。除非我将url(或我可能需要用于查询的任何其他属性)属性存储到Channel 模型(从长远来看,这可能会使它超载) )。非常感谢您的回答!
猜你喜欢
  • 2016-03-14
  • 2021-06-14
  • 2016-03-15
  • 1970-01-01
  • 1970-01-01
  • 2014-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多