Используйте атрибут или атрибут ассоциации в запросе - PullRequest
0 голосов
/ 05 марта 2019

У меня есть Rails Concern ExtendedEvent, который я использую для инкапсуляции логики, относящейся к событиям, которые имеют время начала и время окончания, которых у меня несколько в моем приложении. В настоящее время это выглядит так:

Использование в классе обычно выглядит так:

class InAndOutEvent < ApplicationRecord

  include ExtendedEvent

  def configure_extended_event
    @ee_start_date_time = enter_date_time
    @ee_end_date_time = exit_date_time
  end
end

Где enter_date_time и exit_date_time - атрибуты модели. Однако альтернативное использование будет примерно таким:

class InAndOutEvent < ApplicationRecord
  belongs_to: entry
  belongs_to: exit

  include ExtendedEvent

  def configure_extended_event
    @ee_start_date_time = entry.date_time
    @ee_end_date_time = exit.date_time
  end
end

Да, и возможно, что мои entry и exit ассоциации на самом деле принадлежат к одному классу, что будет означать использование псевдонимов в запросах:

  belongs_to :entry, class_name: "ThingWithDateTime"
  belongs_to :exit, class_name: "ThingWithDateTime"

Я хочу определить область действия класса или метод поиска, который позволит мне находить модели, включающие ExtendedEvent, основываясь на том, перекрываются ли они с заданным временным диапазоном start_time..end_time. В явно неработающем псевдокоде:

require 'active_support/concern'
module ExtendedEvent
  extend ActiveSupport::Concern

  class_methods do
    def overlaps_with(time_range)
      where(ee_start_date_time: time_range)
        .or(self.where(ee_end_date_time: time_range))
    end
  end

  # ... instance methods as above omitted

end

Я знаю, что невозможно использовать общий метод, подобный этому, но мое время начала / окончания задается атрибутами модели, поэтому такое ощущение, что это должно быть возможно. Если бы мой атрибут всегда был в модели, я мог бы заменить определение переменных экземпляра на alias_attribute и использовать это, возможно, как:

alias_attribute :ee_start_time, :enter_date_time

но я не думаю, что это сработало бы с атрибутом ассоциации.

Есть ли способ достичь этого? Я использую Rails 5.1 на Ruby 2.4.

Ответы [ 2 ]

0 голосов
/ 05 марта 2019

Не похоже, что вставленные вами модели имеют отношение к запросам в БД, поэтому я буду использовать свои собственные примеры:

time_range = [1.hour.ago, 1.hour.since]

overlap_records = Model.where('? > models.end_time OR models.start_time < ?', *time_range)

associated_overlap_recs = Assoc.joins(:model).where('? > models.end_time OR models.start_time < ?', *time_range)

Убедитесь, что два временных столбца являются составными индексированными.


Предполагая, что есть:

class InAndOutEvent < ActiveRecord::Base
  belongs_to :entry, class_name: "ThingWithDateTime"
  belongs_to :exit, class_name: "ThingWithDateTime"
end

class ThingWithDateTime < ActiveRecord::Base
end

Вы должны быть в состоянии сделать что-то вроде этого:

# data "from the association"
entry_time = thing_with_date_time_foo.date_time
exit_time = thing_with_date_time_bar.date_time

# to adhere to your api that accepts a `time_range` parameter
time_range = [entry_time, exit_time]
InAndOutEvent.joins(:entry, :exit).where(
  '? > thing_with_date_times.date_time OR thing_with_date_times_in_and_out_events.start_time < ?',
  *time_range
)

Конкретный запрос зависит от псевдонимы таблиц ActiveRecord "выбирает" для объединения.

Все становится намного сложнее, если одна или обе ассоциации являются полиморфными.

0 голосов
/ 05 марта 2019

Можно использовать where только если вы ищете в полях из базы данных, и поэтому, в этом случае, вы должны использовать обычный ruby ​​(который будет медленным):

def self.overlaps_with(time_range)
  select do |instance|
    time_range.include?(instance.ee_start_date_time)
    || time_range.include?(instance.ee_end_date_time)
  end
end

Также, если вам нужен ActiveRecord_Relation вместо массива (чтобы вы могли использовать методы типа where позже), вы можете сделать это следующим образом:

def self.overlaps_with(time_range)
  ids = select do |instance|
    time_range.include?(instance.ee_start_date_time)
    || time_range.include?(instance.ee_end_date_time)
  end.map(&:id)

  where(id: ids)
end
...