Polymorphic belongs_to与动态类的关联(取自其他关系)

时间:2017-02-05 16:10:07

标签: ruby-on-rails ruby

我有一个Rails应用程序,用户可以跟踪播出节目和剧集。

为了简化跟踪(尚未)观看节目的过程,用户可以将其帐户与其他服务同步。他们可以在用户设置页面中选择要与之同步的服务。

要进行同步,我从其他服务加载其配置文件,然后通过检测上次同步更改的算法运行它,并相应地更新DB。为了存储最后的同步状态,对于每个Show ID,我创建一个" UsersSyncIdStatus"存储show ID的对象,以及同步服务中该节目的当前状态。

请注意,这些服务不会使用与我的网站相同的显示ID,这意味着我有一个表,我可以使用它来转换"从他们的节目ID到我的节目ID。由于每个服务提供的信息不同,因此必须将它们存储在不同的表中。

现在,这是(简化版本)如何设置数据库架构:

create_table "service1_ids", force: :cascade do |t|
  t.integer "service_id", null: false
  t.integer "show_id",    null: false
  [...]
end

create_table "service2_ids", force: :cascade do |t|
  t.integer "service_id", null: false
  t.integer "show_id",    null: false
  [...]
end

create_table "users_sync_id_statuses", force: :cascade do |t|
  t.integer  "user_id"
  t.integer  "service_id",                 null: false
  t.integer  "sync_status", default: 0, null: false
  t.datetime "sync_date",               null: false
  [...]
end

create_table "users", force: :cascade do |t|
  [...]
  t.datetime "synced_at"
  t.boolean  "sync_enabled",               default: false, null: false
  t.integer  "sync_method",                default: 0,     null: false
  [...]
end

特别是,users.sync_method是一个枚举,用于存储用户为同步选择的服务:

SYNC_METHODS = {
    0 => {
        symbol: :service1,
        name: 'Service1',
        model_name: 'Service1Id',
        show_scope: :service1_ids
    }
    1 => {
        symbol: :service2,
        name: 'Service2',
        model_name: 'Service2Id',
        show_scope: :service2_ids
    }
}

这意味着只需执行SyncHelper::SYNC_METHODS[current_user.sync_method][:model_name]即可轻松了解特定用户的ID的型号名称。

现在,问题是,如何在" users_sync_id_statuses"之间建立关系?和" serviceN_ids"?为了知道" service_id"列对应,我必须要问'用户模型。

我目前已将其作为一种方法实施:

class User
    def sync_method_hash
        SyncHelper::SYNC_METHODS[self.sync_method]
    end

    def sync_method_model
        self.sync_method_hash[:model_name].constantize
    end 
end

class UsersSyncIdStatus
    def service_id_obj
        self.user.sync_method_model.where(service_id: self.service_id).first
    end
end

但是,UsersSyncIdStatus.service_id_obj是一种方法,而不是一种关系,这意味着我无法完成关系所允许的所有花哨的东西。例如,我无法轻松获取特定用户的UsersSyncIdStatus并显示ID:

current_user.sync_id_statuses.where(service_id_obj: {show_id: 123}).first

我可以把它变成一个多态关系,但我真的不想添加一个文本列来包含类名,当它是一个"常量"从每个用户的角度来看(对于用户切换同步服务,该用户的所有UsersSyncIdStatuses都被销毁,因此用户的UsersSyncIdStatuses中的服务类型永远不会超过1个。)

有什么想法吗?提前谢谢!

1 个答案:

答案 0 :(得分:2)

我认为vanilla Rails 5不支持我想做的事情,如果我错了,请有人纠正我。

然而,在对Rails如何实现多态关系进行一些研究后,我能够相对容易地修补Rails 5以添加此功能:

config/initializers/belongs_to_polymorphic_type_send.rb

# Modified from:  rails/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
module ActiveRecord
    # = Active Record Belongs To Polymorphic Association
    module Associations
        class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
            def klass
                type = owner.send(reflection.foreign_type)
                type.presence && (type.respond_to?(:constantize) ? type.constantize : type)
            end
        end
    end
end

app/models/users_sync_id_status.rb

class UsersSyncIdStatus
    belongs_to :service_id_obj, polymorphic: true, foreign_key: :service_id, primary_key: :service_id
    def service_id_obj_type
        self.user.sync_method_model
    end
end

使用这个monkey-patch,belongs_to多态关联不会假设type字段是varchar列,而是将其称为对象上的方法。这意味着您可以非常轻松地添加自己的动态行为,而不会破坏任何旧行为(AFAIK,没有对此进行密集测试)。

对于我的特定用例,我使用sync_id_obj_type方法查询应该在多态关联中使用的类的用户对象。