ActiveRecord Поиск по году, дню или месяцу в поле даты - PullRequest
64 голосов
/ 09 марта 2012

У меня есть модель ActiveRecord, которая имеет атрибут даты.Можно ли использовать этот атрибут даты для поиска по году, дню и месяцу:

Model.find_by_year(2012)
Model.find_by_month(12)
Model.find_by_day(1)

или просто можно найти find_by_date (2012-12-1)?Я мог бы избежать создания атрибутов Год, Месяц и День.

Ответы [ 11 ]

155 голосов
/ 09 марта 2012

Если ваш «атрибут даты» является датой (а не полной отметкой времени), тогда простой where даст вам «найти по дате»:

Model.where(:date_column => date)

Вы не хотитеfind_by_date_column, поскольку это даст не более одного результата.

Для запросов на год, месяц и день вы хотите использовать функцию extract SQL:

Model.where('extract(year  from date_column) = ?', desired_year)
Model.where('extract(month from date_column) = ?', desired_month)
Model.where('extract(day   from date_column) = ?', desired_day_of_month)

Однако, если вы используете SQLite, вам придется возиться с strftime, поскольку он не знает, что такое extract:

Model.where("cast(strftime('%Y', date_column) as int) = ?", desired_year)
Model.where("cast(strftime('%m', date_column) as int) = ?", desired_month)
Model.where("cast(strftime('%d', date_column) as int) = ?", desired_day_of_month)

Спецификаторы формата %m и %dв некоторых случаях добавляются начальные нули, и это может привести к путанице в тестах на равенство, поэтому cast(... as int) для принудительного преобразования форматированных строк в числа.

ActiveRecord не защитит вас от всех различий между базами данных, поэтому, как тольковы делаете что-то нетривиальное, вам нужно либо создать собственный слой переносимости (некрасиво, но иногда необходимо), привязать себя к ограниченному набору баз данных (реалистично, если вы не выпускаете что-то, что должно работать в какой-либо базе данных), или сделатьвся ваша логика в Ruby (безумно для любого нетривиального объема данных).

В больших таблицах запросы на год, месяц и день месяца будут довольно медленными.Некоторые базы данных позволяют вам добавлять индексы к результатам функций, но ActiveRecord слишком глуп, чтобы понимать их, поэтому он будет сильно мешать, если вы попытаетесь их использовать;поэтому, если вы обнаружите, что эти запросы слишком медленные, вам придется добавить три лишних столбца, которых вы пытаетесь избежать.

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

Использование метода класса является предпочтительным способом получения аргументовдля областей применения.Эти методы по-прежнему будут доступны для объектов ассоциации ...

Таким образом, у вас будут методы класса, которые выглядят так:

def self.by_year(year)
    where('extract(year from date_column) = ?', year)
end
# etc.
29 голосов
/ 27 марта 2013

Независимая от базы данных версия ...

def self.by_year(year)
  dt = DateTime.new(year)
  boy = dt.beginning_of_year
  eoy = dt.end_of_year
  where("published_at >= ? and published_at <= ?", boy, eoy)
end
11 голосов
/ 26 октября 2012

Для MySQL попробуйте это

Model.where("MONTH(date_column) = 12")
Model.where("YEAR(date_column) = 2012")
Model.where("DAY(date_column) = 24")
10 голосов
/ 30 марта 2013

В вашей модели:

scope :by_year, lambda { |year| where('extract(year from created_at) = ?', year) }

В вашем контроллере:

@courses = Course.by_year(params[:year])
5 голосов
/ 09 марта 2012

Я думаю, что самый простой способ сделать это - это область действия:

scope :date, lambda {|date| where('date_field > ? AND date_field < ?', DateTime.parse(date).beginning_of_day, DateTime.parse(date).end_of_day}

Используйте это так:

Model.date('2012-12-1').all

Это вернет все модели с полем date_ между началом и концом дня для отправленной вами даты.

3 голосов
/ 02 мая 2014

Попробуйте это:

Model.where("MONTH(date_column) = ? and DAY(date_column) = ?", DateTime.now.month, DateTime.now.day)
2 голосов
/ 12 декабря 2014

Простая реализация для Just The Year

приложение / модели / your_model.rb

scope :created_in, ->(year) { where( "YEAR( created_at ) = ?", year ) }

Использование

Model.created_in(1984)
1 голос
/ 20 сентября 2013

Это будет еще один:

def self.by_year(year)
  where("published_at >= ? and published_at <= ?", "#{year}-01-01", "#{year}-12-31")
end

У меня довольно хорошо работает в SQLite.

1 голос
/ 11 мая 2013

Мне нравится ответ BSB, но как область действия

scope :by_year, (lambda do |year| 
  dt = DateTime.new(year.to_i, 1, 1)
  boy = dt.beginning_of_year
  eoy = dt.end_of_year
  where("quoted_on >= ? and quoted_on <= ?", boy, eoy)
end)
0 голосов
/ 13 ноября 2018

Другая короткая область, как показано ниже:

  class Leave
    scope :by_year, -> (year) {
      where(date:
                Date.new(year).beginning_of_year..Date.new(year).end_of_year)
    }
  end

Вызовите область:

Leave.by_year(2020)
...