ご挨拶、
Companies
とUsers
がCompanyMembership
モデルを介して相互に属する必要があるアプリケーションがあります。このモデルには、メンバーシップに関する追加情報(具体的には、ユーザーがブール値admin
を介した会社の管理者)。コードの単純なバージョン:
class CompanyMembership < ActiveRecord::Base
belongs_to :company
belongs_to :user
end
class Company < ActiveRecord::Base
has_many :company_memberships
has_many :users, :through => :company_memberships
end
class User < ActiveRecord::Base
has_many :company_memberships
has_many :companies, :through => :company_memberships
end
もちろん、これにより、company.users.all
などを介して会社のすべてのメンバーを簡単に取得できます。ただし、その会社の管理者である会社のすべてのユーザーのリストを取得しようとしています(また、ユーザーが特定の会社の管理者であるかどうかをテストしようとしています)。私の最初の解決策は、company.rb
で次のとおりでした。
def admins
company_memberships.where(:admin => true).collect do |membership|
membership.user
end
end
def is_admin?(user)
admins.include? user
end
これは機能しますが、何かが非効率に感じられ(各メンバーシップを繰り返し、毎回SQLを実行しますか?それともRelationはそれよりも賢いですか?)、これを実行するためのより良い方法があるかどうかはわかりません(おそらくRails 3が使用する?)スコープまたは新しいRelation
オブジェクト。
続行するための最良の方法(できればRails 3つのベストプラクティスを使用))に関するアドバイスをいただければ幸いです。
私はこれを間違った方法で行っていたと思います。実際に必要だったusers
の代わりに_company_memberships
_の条件を指定しました(Users
のリストではなくCompanyMemberships
のリスト)。私が探していたと思う解決策は次のとおりです。
_users.where(:company_memberships => {:admin => true})
_
これにより、次のSQLが生成されます(IDが1の会社の場合)。
_SELECT "users".* FROM "users"
INNER JOIN "company_memberships"
ON "users".id = "company_memberships".user_id
WHERE (("company_memberships".company_id = 1))
AND ("company_memberships"."admin" = 't')
_
必要かどうかはまだわかりませんが、includes()
メソッドは、必要に応じてSQLクエリの数を抑えるために積極的な読み込みを実行します。
Active Recordを使用すると、ロードされるすべての関連付けを事前に指定できます。これは、_
Model.find
_呼び出しのincludes
メソッドを指定することで可能になります。インクルードを使用すると、Active Recordは、指定されたすべての関連付けが、可能な限り最小限のquerys.queriesを使用してロードされるようにします。 RoRガイド:ActiveRecordクエリ
(これが最善/最も効果的/正しい方法ではないと考える人からの提案はまだ受け付けています。)
さらにクリーンな方法は、次のような関連付けを会社モデルに追加することです。
has_many :admins, :through => :company_memberships, :class_name => :user, :conditions => {:admin => true}
正確な構文を正しく取得するには、 Rails doc を掘り下げる必要があるかもしれません。
ビューで参照する可能性のある:userに関連付けられた他のクラスがない限り、:includeは必要ありません。
このようなものはどうですか:
Company.find(:id).company_memberships.where(:admin => true).joins(:user)
私はこの答えに出くわし、今日ではhas_many association scopes
を使用するより良い方法があると信じています( has_manyドキュメント ):
has_many :admins, -> { where(admin: true) }, through: :company_memberships, class_name: :user
Has_manyアソシエーションの2番目のパラメーターは、フィルターを含むprocまたはlambdaにすることができます。
これを行うために activerecord_where_assoc gemを作成しました。
これにより、結合やインクルードは不要です。積極的な読み込みを行うことは、義務ではなく、あなたの選択のままです。
users.where_assoc_exists(:company_memberships, admin: true, company_id: @company.id)
会社をどのようにフィルタリングするかについての質問は不明確なので、私はそれを自分で追加しました。これがないと、ユーザーが複数の会社に所属できる場合、ある会社の管理者であるということは、別の会社の管理者として扱われることを意味する可能性があります。
このgemはRails 3では機能しませんが、9年後、Rails 4.1以降がサポートされています。