#ruby-on-rails #postgresql #has-many-through
#ruby-on-rails #postgresql #имеет много сквозных
Вопрос:
Вопрос в следующем: «Учитывая компанию, как я должен построить запрос, который возвращает все события, созданные всеми сотрудниками в течение их соответствующих периодов занятости?»
Пример:
@company = Company.create
# Welcome John
@john = User.create
@event_a = @john.events.create date: Date.new(2013,6,1)
@event_b = @john.events.create date: Date.new(2014,1,1)
@company.employments.create user:@john, since: Date.new(2013,12,20)
# Welcome Jack
@jack = User.create
@event_c = @jack.events.create date: Date.new(2012,1,1)
@event_d = @jack.events.create date: Date.new(2013,1,1)
@company.employments.create user:@jack,
since: Date.new(2011,12,20), till: Date.new(2012,12,31)
@company.events
=> [@event_b, @event_c]
# @event_a is not returned because it was created prior to John's hiring
# @event_d is not returned because it was created after Jack's departure
Я придумал решение, но я хотел бы знать, есть ли какие-либо способы его улучшить.
class Event
belongs_to :user
# attributes
# date: datetime
# …
end
class Employment
belongs_to :user
belongs_to :event
# attributes
# since: date
# till: date
# …
end
class Company
has_many :employments
def events
Event.where employments.map do |e|
if e.since amp;amp; e.till
"(user_id = '#{e.user_id}' AND date BETWEEN '#{e.since}' AND '#{e.till}')"
elsif !e.since.nil?
"(user_id = '#{e.user_id}' AND date > '#{e.since}')"
else
"(user_id = '#{e.user_id}')"
end
end.join(' OR ')
end
end
Вы видите какой-либо другой способ сделать это?
Ответ №1:
События.объединения (user: :employements).где (company_id: id) Я полагаю, вы могли бы использовать что-то вроде этого:
def events
Event.where employments.map do |e|
user=User.find(e.user_id)
since = e.since
till = e.till || Date.parse('3100-12-31') # a day in the distant future if nil
user.events.where('(date is null) or (date > ? and date < ?)', since, till)
end
end
2-я попытка, после использования соответствующих объединений для подключения модели событий и занятости, вы можете использовать последнее where
предложение для реализации вашего фильтра.
Полный метод, после того как вы добавили правильный набор объединений и фильтров (спасибо), выглядит следующим образом.
def events
Events.joins(user: :employments).where(company_id: id). # <-This comes from the author, not me.
where('employments.since is null OR
(employments.since < events.date AND employments.till is null) OR
(employments.since < events.date AND employments.till > events.date)')
end
Комментарии:
1. Это возвращает события для данного пользователя. Я хочу, чтобы события для всех пользователей компании, пока они были сотрудниками.
2. Вы должны поместить это внутри блока карты… Я поставил для вас букву «е»… Позвольте мне перестроить ответ
3. О, извините, вам нужно что-то другое… Если это все равно, скажите мне, что я могу удалить свой ответ.
4. Спасибо, что нашли время для ответа, я ценю ваши усилия. Я вижу 4 проблемы с вашим ответом. a) это не работает 🙂 map вернет массив ActiveRecord_AssociationRelation , но он может работать с незначительными изменениями: ‘Событие. найти … с тех пор, пока). pluck(:id)’ б) он загружает все в память, в) он запрашивает базу данных несколько раз (в зависимости от количества пользователей), чего можно было бы избежать, используя employments.includes(:user) и используя e.user вместо user=User.find , г) яне люблю полагаться на случайную дату, однако маловероятно, что кодовая база проживет тысячу лет 🙂
5. @user229688, я добавил 2-ю альтернативу с двумя SQL-запросами, загружающими только события.*… Взгляните на это..
Ответ №2:
как я могу запросить все события, созданные всеми сотрудниками во время их работы в данной компании
Возможно, вы захотите использовать an ActiveRecord Association Extension
, который в основном добавляет новый метод к вашей ассоциации, позволяя вам определять фильтрацию или некоторые другие спецификации:
#app/models/company.rb
Class Company < ActiveRecord::Base
has_many :employments
has_many :events do
def by_date(since, till)
... logic here
end
end
end
Это позволит вам вызвать:
#app/controllers/events_controller.rb
Class EventsController < ApplicationController
def action
company = Company.find params[:id]
@events = company.events.by_date("01-01-2014", "04-01-2014")
end
end
Если вы дадите мне знать в комментариях, я попробую заполнить метод для вас
Комментарии:
1. Привет, спасибо за ваш ответ. Это не совсем то, что я пытаюсь сделать. Даты начала / окончания варьируются от одного сотрудника к другому, я добавлю пример.