Rails 5:通过多态关系检索多个对象

时间:2017-05-03 16:11:45

标签: ruby-on-rails ruby activerecord

我需要通过多态关系优先在单个查询中检索多个对象。

用户模型中,我有:

class User < ApplicationRecord
  has_many :companies, through: :accounts
  has_many :permissions
  has_many :medias, class_name: 'Media::Medias', through: :permissions,
           source: :permitable, source_type: 'Media::Medias'
end

然后我的权限模型如下所示:

class Permission < ApplicationRecord
  belongs_to :permitable, polymorphic: true
  belongs_to :user, optional: true
end

媒体::媒体模型

module Media
  class Medias < ApplicationRecord
    belongs_to :companies, optional: true, inverse_of: :medias
    has_many :permissions, class_name: 'Permission', as: :permitable
    has_many :users, through: :permissions
  end
end

公司模式

class Company < ApplicationRecord
  has_many :accounts, dependent: :destroy
  has_many :users, through: :accounts
  has_many :medias, class_name: 'Media::Medias', inverse_of: :company
end

如何为ActiveRecord::Relation object和每家媒体公司的所有媒体返回current_user重要的是,我必须通过Permission使用关系模型,我不应该让公司通过Accounts。到目前为止,我已经设置了模型,因此我可以使用Current.user.medias来获取current_user的所有媒体。

谢谢!

更新

以下是我尝试过的几个版本的内容:

[4] pry(main)> user.medias.includes(:companies).map{|media| media.companies.id }
  Media::Medias Load (1.4ms)  SELECT "media_medias".* FROM "media_medias" INNER JOIN "permissions" ON "media_medias"."id" = "permissions"."permitable_id" WHERE "permissions"."user_id" = $1 AND "permissions"."permitable_type" = $2  [["user_id", 2], ["permitable_type", "Media::Medias"]]
NameError: uninitialized constant Media::Medias::Companies
from /home/ubuntu/.rvm/gems/ruby-2.3.3/gems/activerecord-5.1.0/lib/active_record/inheritance.rb:166:in `compute_type'
[5] pry(main)> user.medias.preload(:companies).select(['medias.id', 'medias.company.id'])
  Media::Medias Load (1.3ms)  SELECT medias.id, medias.company.id FROM "media_medias" INNER JOIN "permissions" ON "media_medias"."id" = "permissions"."permitable_id" WHERE "permissions"."user_id" = $1 AND "permissions"."permitable_type" = $2  [["user_id", 2], ["permitable_type", "Media::Medias"]]
=> #<Media::Medias::ActiveRecord_AssociationRelation:0x33dd094>
[6] pry(main)> user.medias.select(['medias.id', 'medias.company_id'])
  Media::Medias Load (1.1ms)  SELECT medias.id, medias.company_id FROM "media_medias" INNER JOIN "permissions" ON "media_medias"."id" = "permissions"."permitable_id" WHERE "permissions"."user_id" = $1 AND "permissions"."permitable_type" = $2  [["user_id", 2], ["permitable_type", "Media::Medias"]]
=> #<Media::Medias::ActiveRecord_AssociationRelation:0x3413234>

更新2

[4] pry(main)> puts user.medias.preload(:companies).select(['medias.id', 'medias.company.id'])
  Media::Medias Load (1.4ms)  SELECT medias.id, medias.company.id FROM "media_medias" INNER JOIN "permissions" ON "media_medias"."id" = "permissions"."permitable_id" WHERE "permissions"."user_id" = $1 AND "permissions"."permitable_type" = $2  [["user_id", 2], ["permitable_type", "Media::Medias"]]
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "medias"
LINE 1: SELECT medias.id, medias.company.id FROM "media_medias" INNE...
               ^
: SELECT medias.id, medias.company.id FROM "media_medias" INNER JOIN "permissions" ON "media_medias"."id" = "permissions"."permitable_id" WHERE "permissions"."user_id" = $1 AND "permissions"."permitable_type" = $2
from /home/ubuntu/.rvm/gems/ruby-2.3.3/gems/activerecord-5.1.0/lib/active_record/connection_adapters/postgresql_adapter.rb:620:in `async_exec'
[5] pry(main)> puts user.medias.select(['medias.id', 'medias.company_id'])
  Media::Medias Load (1.2ms)  SELECT medias.id, medias.company_id FROM "media_medias" INNER JOIN "permissions" ON "media_medias"."id" = "permissions"."permitable_id" WHERE "permissions"."user_id" = $1 AND "permissions"."permitable_type" = $2  [["user_id", 2], ["permitable_type", "Media::Medias"]]
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "medias"
LINE 1: SELECT medias.id, medias.company_id FROM "media_medias" INNE...
               ^
: SELECT medias.id, medias.company_id FROM "media_medias" INNER JOIN "permissions" ON "media_medias"."id" = "permissions"."permitable_id" WHERE "permissions"."user_id" = $1 AND "permissions"."permitable_type" = $2
from /home/ubuntu/.rvm/gems/ruby-2.3.3/gems/activerecord-5.1.0/lib/active_record/connection_adapters/postgresql_adapter.rb:620:in `async_exec'

更新3

[1] pry(main)> User.find(2).medias.preload(:companies).select(['medias.id', 'medias.company.id'])
  User Load (21.5ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
  Media::Medias Load (1.7ms)  SELECT medias.id, medias.company.id FROM "media_medias" INNER JOIN "permissions" ON "media_medias"."id" = "permissions"."permitable_id" WHERE "permissions"."user_id" = $1 AND "permissions"."permitable_type" = $2  [["user_id", 2], ["permitable_type", "Media::Medias"]]
=> #<Media::Medias::ActiveRecord_AssociationRelation:0x17688f0>
[2] pry(main)> User.find(2).medias.select(['medias.id', 'medias.company_id'])
  User Load (1.7ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
  Media::Medias Load (1.8ms)  SELECT medias.id, medias.company_id FROM "media_medias" INNER JOIN "permissions" ON "media_medias"."id" = "permissions"."permitable_id" WHERE "permissions"."user_id" = $1 AND "permissions"."permitable_type" = $2  [["user_id", 2], ["permitable_type", "Media::Medias"]]
=> #<Media::Medias::ActiveRecord_AssociationRelation:0x2155d90>

2 个答案:

答案 0 :(得分:1)

为了解决这个问题,看起来问题(或者至少是一个大问题)是模型名称和关联中复数/单数元素的混淆。一般来说:

  1. 型号名称是单数。在这种情况下,&#34; medium&#34;是单数和&#34;媒体&#34;是复数。
  2. 在模型中使用关联构造时,a&#34; has_many&#34;通常会采用复数而#34; belongs_to&#34;将采取单一的。所以,期待has_many :companiesbelongs_to :user
  3. 基础表名称是复数。所以,&#34; Medium&#34;模型基于&#34;媒体&#34;表
  4. 有了这个,我创建了一个简单的Rails应用程序,作为上述代码的最小工作示例。自&#34;帐户&#34;模型没有出现在这里我从公司模型中删除了相应的元素。我还添加了反向关联。

    class User < ApplicationRecord
      has_many :permissions, inverse_of: :user
      has_many :media, class_name: 'Medium::Medium', through: :permissions, source: :permitable, source_type: 'Medium::Medium'
    end
    
    class Company < ApplicationRecord
      has_many :media, class_name: 'Medium::Medium', inverse_of: :company
    end
    
    class Medium::Medium < ApplicationRecord
      belongs_to :company, optional: true, inverse_of: :media
      has_many :permissions, class_name: 'Permission', as: :permitable, inverse_of: :permitable
      has_many :users, through: :permissions
    end
    
    class Permission < ApplicationRecord
      belongs_to :permitable, polymorphic: true, inverse_of: :permissions
      belongs_to :user, inverse_of: :permissions
    end
    

    包含测试数据的示例控制台会话:

    2.2.7 :001 > Company.create(name: 'IBM')
       (0.1ms)  begin transaction
      SQL (0.4ms)  INSERT INTO "companies" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "IBM"], ["created_at", "2017-05-05 19:11:49.017155"], ["updated_at", "2017-05-05 19:11:49.017155"]]
       (0.5ms)  commit transaction
     => #<Company id: 1, name: "IBM", created_at: "2017-05-05 19:11:49", updated_at: "2017-05-05 19:11:49"> 
    2.2.7 :002 > User.create(name: 'Joe')
       (0.0ms)  begin transaction
      SQL (0.8ms)  INSERT INTO "users" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "Joe"], ["created_at", "2017-05-05 19:11:59.401417"], ["updated_at", "2017-05-05 19:11:59.401417"]]
       (2.3ms)  commit transaction
     => #<User id: 1, name: "Joe", created_at: "2017-05-05 19:11:59", updated_at: "2017-05-05 19:11:59"> 
    2.2.7 :003 > Medium::Medium.create(name: 'TV')
       (0.0ms)  begin transaction
      SQL (0.2ms)  INSERT INTO "medium_media" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "TV"], ["created_at", "2017-05-05 19:12:11.453440"], ["updated_at", "2017-05-05 19:12:11.453440"]]
       (2.0ms)  commit transaction
     => #<Medium::Medium id: 1, company_id: nil, name: "TV", created_at: "2017-05-05 19:12:11", updated_at: "2017-05-05 19:12:11"> 
    2.2.7 :004 > Medium::Medium.create(name: 'Newspaper')
       (0.1ms)  begin transaction
      SQL (0.3ms)  INSERT INTO "medium_media" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "Newspaper"], ["created_at", "2017-05-05 19:12:18.433671"], ["updated_at", "2017-05-05 19:12:18.433671"]]
       (2.0ms)  commit transaction
     => #<Medium::Medium id: 2, company_id: nil, name: "Newspaper", created_at: "2017-05-05 19:12:18", updated_at: "2017-05-05 19:12:18"> 
    2.2.7 :005 > user = User.first
      User Load (0.3ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
     => #<User id: 1, name: "Joe", created_at: "2017-05-05 19:11:59", updated_at: "2017-05-05 19:11:59"> 
    2.2.7 :006 > user.permissions.create(permitable: Medium::Medium.first)
      Medium::Medium Load (0.2ms)  SELECT  "medium_media".* FROM "medium_media" ORDER BY "medium_media"."id" ASC LIMIT ?  [["LIMIT", 1]]
       (0.0ms)  begin transaction
      SQL (0.2ms)  INSERT INTO "permissions" ("permitable_type", "permitable_id", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["permitable_type", "Medium::Medium"], ["permitable_id", 1], ["user_id", 1], ["created_at", "2017-05-05 19:12:50.694383"], ["updated_at", "2017-05-05 19:12:50.694383"]]
       (0.5ms)  commit transaction
     => #<Permission id: 1, permitable_type: "Medium::Medium", permitable_id: 1, user_id: 1, created_at: "2017-05-05 19:12:50", updated_at: "2017-05-05 19:12:50"> 
    2.2.7 :007 > user = User.first
      User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
     => #<User id: 1, name: "Joe", created_at: "2017-05-05 19:11:59", updated_at: "2017-05-05 19:11:59"> 
    2.2.7 :008 > user.media
      Medium::Medium Load (0.2ms)  SELECT  "medium_media".* FROM "medium_media" INNER JOIN "permissions" ON "medium_media"."id" = "permissions"."permitable_id" WHERE "permissions"."user_id" = ? AND "permissions"."permitable_type" = ? LIMIT ?  [["user_id", 1], ["permitable_type", "Medium::Medium"], ["LIMIT", 11]]
     => #<ActiveRecord::Associations::CollectionProxy [#<Medium::Medium id: 1, company_id: nil, name: "TV", created_at: "2017-05-05 19:12:11", updated_at: "2017-05-05 19:12:11">]> 
    2.2.7 :009 > user.media.includes(:company)
      Medium::Medium Load (0.2ms)  SELECT  "medium_media".* FROM "medium_media" INNER JOIN "permissions" ON "medium_media"."id" = "permissions"."permitable_id" WHERE "permissions"."user_id" = ? AND "permissions"."permitable_type" = ? LIMIT ?  [["user_id", 1], ["permitable_type", "Medium::Medium"], ["LIMIT", 11]]
     => #<ActiveRecord::AssociationRelation [#<Medium::Medium id: 1, company_id: nil, name: "TV", created_at: "2017-05-05 19:12:11", updated_at: "2017-05-05 19:12:11">]> 
    

    只需确保您的模型名称正确无误即可。示例代码可以在此处下载:

    https://github.com/mdchaney/so_43765182

答案 1 :(得分:0)

尝试在User类上添加关联:

class User < ApplicationRecord
  has_many :companies, through: :accounts
  has_many :permissions
  has_many :medias, class_name: 'Media::Medias', through: :permissions,
       source: :permitable, source_type: 'Media::Medias'
  has_many :media_companies, through: :medias, source: :companies
end

然后你应该能够做到: User.find(2).media_companies

由于您在媒体与公司之间建立了belongs_to关联,因此您应该考虑在belongs_to :company课程中使用单数Media::Medias

如果您这样做,那么您可以将User类中的关联修改为:

has_many :media_companies, through: :medias, source: :company

如果你想保持复数,那么你需要添加一个关联选项才能使它工作(避免NameError: uninitialized constant Media::Medias::Companies错误):

module Media
  class Medias < ApplicationRecord
    belongs_to :companies, class_name: :Company, optional: true, inverse_of: :medias
    ...
  end
end

虽然将它切换为单数会更常规:

belongs_to :company, optional: true, inverse_of: :medias