Moja aplikacja ma model Job. Każde zadanie w systemie ma contact
. To jest jak osoba, do której chcesz zadzwonić, jeśli chcesz zadać pytanie dotyczące pracy. Kontaktem może być client
lub pracownik klienta (ClientEmployee
).Łączenie ActiveRecord :: Relacje zapytane przez skojarzenie polimorficzne
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
Klienci mają pomysł na commissioned_jobs
. Klienci, którym zlecono zadania, to te, dla których klient jest kontaktem LUB jednym z pracowników klienta jest kontakt.
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
bok: Ta metoda jest nieco hack, ponieważ zwraca tablicę raczej niż ActiveRecord::Relation
. Interesujące jest również to, że wysadza się, gdy próbuję konkatentować pracę w employee_jobs. To może, ale nie musi, zrobić dla moich celów.
Chciałbym dodać zakres do Client
o nazwie with_commissioned_jobs
. Powinno to zwrócić wszystkich klientów w systemie, którzy mają pracę lub mają pracowników, którzy mają pracę.
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
Jak zastosować tę metodę?
Używam Rails 3.2.9.
Aktualizacja:
Zrobiłem pewne postępy i teraz mam dwa sposoby, z których każdy robi połowę tego, co mi potrzeba.
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
Teraz wszystko, co muszę zrobić, to połączyć te dwie wywołania metod w jeden ActiveRecord::Relation
. Mogę oczywiście to zrobić:
def self.with_commissioned_jobs
with_jobs + with_employee_jobs
end
Problem polega na tym, że zwraca tablicę zamiast instancję Relation
i nie mogę łańcuchu zasięgów na nim.
Aktualizacja 2:
Korzystanie merge
wydaje się nie działa. Oto zapytanie AR i wynikowy kod SQL.
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'
Przy okazji, oto testy, które próbuję przekazać. Pierwszy test kończy się niepowodzeniem, ponieważ przy scalaniu uzyskuję zerowe wyniki.
describe "with_commissioned_jobs" do
# A client with a job.
let!(:client_with) { create :client }
let!(:job) { create :job, contact: client_with }
# A client who does not himself have a job, but who has an employee
# with a job.
let!(:client_with_emp) { create :client }
let!(:employee) { create :client_employee, employer: client_with_emp }
let!(:emp_job) { create :job, contact: employee }
# A client with nothing. Should not show up.
let!(:client_without) { create :client }
it "should return clients with jobs and clients with employee jobs" do
Client.with_commissioned_jobs.should == [client_with, client_with_emp]
end
it "should return a relation" do
Client.with_commissioned_jobs.should be_instance_of(ActiveRecord::Relation)
end
end
Czy obejrzałeś arel? https://github.com/rails/arel Zawiera warunek dla zapytań i dobrze obsługuje połączenia złożone. – John
Z pewnością przyjmuję również rozwiązanie oparte na Arel. Spędziłem kilka godzin próbując wymyślić jeden i wydaje mi się, że nie jestem w stanie nim zarządzać. –