我的应用程序有一个Job模型。系统中的每个作业都有contact
。如果您需要询问有关工作的问题,这就像您打电话的人。联系人可以是client
或客户的员工(ClientEmployee
)。
class Job < ActiveRecord::Base
belongs_to :contact, polymorphic: true
end
class Client < ActiveRecord::Base
has_many :jobs, as: :contact
has_many :employees, class_name: 'ClientEmployee'
end
class ClientEmployee < ActiveRecord::Base
belongs_to :client
has_many :jobs, as: :contact
end
客户有commissioned_jobs
的想法。客户委托的工作是那些客户是联系人的工作,或者客户的一名员工是联系人。
class Client < ActiveRecord::Base
has_many :jobs, as: :contact
has_many :employee_jobs, through: :employees, source: :jobs
def commissioned_jobs
jobs << employee_jobs
end
end
除此之外:该方法有点像黑客,因为它返回一个数组而不是ActiveRecord::Relation
。有趣的是,如果我尝试将作业连接到employee_jobs,它就会爆炸。它可能会或可能不会为我的目的。
我想向名为Client
的{{1}}添加范围。这应该返回系统中有工作或有员工的所有客户。
with_commissioned_jobs
如何实施此方法?
我正在使用Rails 3.2.9。
更新
我已经取得了一些进展,现在我有两种方法,每种方法都有我需要的一半。
class Client < ActiveRecord::Base
def self.with_commissioned_jobs
# I can get clients with jobs using: joins(:jobs). How do
# I also include clients with employees who have jobs?
end
end
现在我需要做的就是将这两个方法调用合并为一个class Client < ActiveRecord::Base
# Return all clients who have an employee with at least one job.
def self.with_employee_jobs
joins(employees: :jobs)
# SQL: SELECT "clients".* FROM "clients" INNER JOIN "client_employees" ON "client_employees"."employer_id" = "clients"."id" INNER JOIN "jobs" ON "jobs"."contact_id" = "client_employees"."id" AND "jobs"."contact_type" = 'ClientEmployee'
end
# Return all clients who have at least one job.
def self.with_jobs
joins(:jobs)
# SQL: SELECT "clients".* FROM "clients" INNER JOIN "jobs" ON "jobs"."contact_id" = "clients"."id" AND "jobs"."contact_type" = 'Client'
end
end
。我显然可以这样做:
ActiveRecord::Relation
问题是返回数组而不是 def self.with_commissioned_jobs
with_jobs + with_employee_jobs
end
的实例,我无法在其上链接更多范围。
更新2 :
使用Relation
似乎也不起作用。这是AR查询和生成的SQL。
merge
顺便说一句,这是我试图通过的测试。第一次测试失败,因为当我使用merge时没有结果。
joins(:jobs).merge(joins(employees: :jobs))
SELECT "clients".* FROM "clients" INNER JOIN "jobs"
ON "jobs"."contact_id" = "clients"."id"
AND "jobs"."contact_type" = 'Client'
INNER JOIN "client_employees"
ON "client_employees"."employer_id" = "clients"."id"
INNER JOIN "jobs" "jobs_client_employees"
ON "jobs_client_employees"."contact_id" = "client_employees"."id"
AND "jobs_client_employees"."contact_type" = 'ClientEmployee'
答案 0 :(得分:1)
试试这个:
joins( :jobs, {employees: :jobs} )
它应该加入客户的工作以及客户员工&#39;工作。有关更全面的信息,请参阅the guides。
修改强>
在您的情况下,您可以使用Relation.merge
:
joins( :jobs ).merge( joins(employees: :jobs) )
答案 1 :(得分:1)
您是否考虑过宝石meta_where?主要的是你想要返回一个ActiveRecord:Relation
对象进行进一步的链接。
更新2 :使用别名使用LEFT OUTER JOIN
个作业两次
# scope for ::Client
def self.with_commissioned_jobs
self.joins("LEFT OUTER JOIN client_employees ON clients.id =client_employees.client_id").
joins("LEFT OUTER JOIN jobs AS cjobs ON clients.id = cjobs.contact_id AND cjobs.contact_type = 'Client'").
joins("LEFT OUTER JOIN jobs AS ejobs ON client_employees.id = ejobs.contact_id AND ejobs.contact_type = 'ClientEmployee'").
where("cjobs.id IS NOT NULL OR ejobs.id IS NOT NULL")
end
看看它是否有效:
#c1 has no job
c1 = Client.create
#c2 has a job
c2 = Client.create
c2.jobs.create
#c3 has no job, but has an employee with a job
c3 = Client.create
c3.employees.create
c3.employees.first.jobs.create
puts Client.all.inspect #=> [#<Client id: 1>, #<Client id: 2>, #<Client id: 3>]
puts Client.with_commissioned_jobs #=> [#<Client id: 2>, #<Client id: 3>]
puts [c2,c3] == Client.with_commissioned_jobs.all #=> true
答案 2 :(得分:1)
你有一个重要的理由坚持多态吗?
如果ClientEmployee总是有客户端,那么您应该拥有Job.belongs_to :client
。这使你的关系变得简单。我发现添加一些冗余关联也可以是很好的性能优化,只要它不会使记录保持连贯性更加困难(即客户端/ ClientEmployee关系与Job.Client / Job.ClientEmployee分配同步时两者都在场。)
我真的很喜欢rails中的多态性,但是当你尝试加入它们时它会变得棘手,就像在这种情况下一样。即使你有单独的Client和ClientEmployee id,这在db中也会更有效(两个int对比int和string)。
答案 3 :(得分:0)
class Client < ActiveRecord::Base
has_many :jobs, as: :contact
has_many :employees, class_name: 'ClientEmployee'
scope :with_commissioned_jobs, lambda do
includes(:jobs, {:employees => :jobs}).where("jobs.contact_type IS NOT NULL AND jobs.contact_id IS NOT NULL")
end
end
好的,我从实际工作申请中做出的另一个决定。老学校帮助你。 :)
此方法只为AR创建排列条件:多态性的关系。
module ActiveRecordHelper
def self.polymorphic_sql(*args)
conditions = []
table = args.first.table_name
stack = args.extract_options!
sql_queries = stack.collect do |as_resource, hash|
resource_queries = hash.collect do |name, find_options|
resource_class = name.to_s.classify.constantize
resource_table = resource_class.table_name
conditions << resource_class.name
if find_options[:conditions].present?
conditions += find_options[:conditions][1..-1]
end
joins_clause =
Array.wrap(find_options[:join]).collect do |association|
reflection = resource_class.reflections[association]
if reflection.macro == :belongs_to && reflection.options[:polymorphic] != true
"INNER JOIN #{reflection.klass.table_name} ON #{reflection.active_record.table_name}.#{reflection.foreign_key} = #{reflection.klass.table_name}.id"
elsif reflection.macro.in?([:has_many, :has_one]) && reflection.options[:as].nil?
"INNER JOIN #{reflection.klass.table_name} ON #{reflection.klass.table_name}.#{reflection.foreign_key} = #{reflection.active_record.table_name}.id"
end
end.compact.join(" ").strip
"(#{table}.#{as_resource}_type = ? AND EXISTS(#{["SELECT 1 FROM #{resource_table}#{joins_clause.left_indent(1) if joins_clause.present?} WHERE #{resource_table}.id = #{table}.#{as_resource}_id", find_options[:conditions].first].compact.join(" AND ")}))"
end
"CASE WHEN #{table}.#{as_resource}_type IS NOT NULL AND #{table}.#{as_resource}_id IS NOT NULL THEN #{resource_queries.join(" OR ")} ELSE TRUE END"
end
conditions.insert(0, "(#{sql_queries.join(" OR ")})")
end
end
然后扩展你的多态Job:
def self.comissioned_by(client)
conditions = ActiveRecordHelper.polymorphic_sql(self, :contact => {:client => {:conditions => ["clients.id = ?", client.id]}, :client_employee => {:conditions => ["client_employees.client_id = ?", client.id]}}
where(conditions)
end
现在打电话:
Job.commissioned_by() # pass client instance
享受。如果需要任何详细信息,请键入我。
答案 4 :(得分:0)
您是否尝试过自定义加入?
def self.with_commissioned_jobs
query = <<-QUERY
INNER JOIN client_employees
ON client_employees.employer_id = clients.id
INNER JOIN jobs
ON ((jobs.contact_id = client_employees.id AND jobs.contact_type = 'ClientEmployee')
OR (jobs.contact_id = clients.id AND jobs.contact_type = 'Client'))
QUERY
joins(query)
end