ActiveRecord recherche par année, jour ou mois sur un champ de Date

J'ai un modèle ActiveRecord qui a un attribut date. Est-il possible d'utiliser cet attribut de date pour trouver par année, Jour et mois:

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

Ou est-il simplement possible de find_by_date(2012-12-1).

J'espérais éviter de créer des attributs Année, Mois et jour.

58
demandé sur mu is too short 2012-03-09 00:40:30

10 réponses

En supposant que votre "attribut date" est une date (plutôt qu'un horodatage complet), un simple where vous donnera votre "find by date":

Model.where(:date_column => date)

Vous ne voulez pas find_by_date_column car cela donnera au plus un résultat.

Pour les requêtes année, mois et jour, vous souhaitez utiliser la fonction SQL extract:

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)

Cependant, si vous utilisez SQLite, vous devrez jouer avec strftime car il ne sait pas ce que extract est:

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)

Les spécificateurs de format %m et %d ajoutera des zéros en tête dans certains cas et cela peut confondre les tests d'égalité, d'où le cast(... as int) pour forcer les chaînes formatées à des nombres.

ActiveRecord ne vous protégera pas de toutes les différences entre les bases de données, donc dès que vous faites quelque chose de non trivial, vous devez soit construire votre propre couche de portabilité (laide mais parfois nécessaire), vous attacher à un ensemble limité de bases de données (réaliste sauf si vous libérez quelque chose qui doit fonctionner sur n'importe quelle base de Ruby (fou pour toute quantité de données non triviale).

Les requêtes année, mois et jour du mois seront assez lentes sur les grandes tables. Certaines bases de données vous permettent d'ajouter des index sur les résultats de la fonction mais ActiveRecord est trop stupide pour comprendre ce qui va faire de gros dégâts si vous essayez de les utiliser; donc, si vous trouvez que ces requêtes sont trop lent, alors vous aurez à ajouter les trois colonnes que vous essayez d'éviter.

Si vous utilisez beaucoup ces requêtes, vous pourriez ajoutez des étendues pour eux, mais la méthode recommandée par pour ajouter une portée avec un argument consiste simplement à ajouter une méthode de classe:

L'utilisation d'une méthode de classe est le moyen préféré d'accepter les arguments pour les étendues. Ces méthodes seront toujours accessibles sur les objets d'association...

Donc, vous auriez des méthodes de classe qui ressemblent à ceci:

def self.by_year(year)
    where('extract(year from date_column) = ?', year)
end
# etc.
142
répondu mu is too short 2016-04-08 00:37:12

Version indépendante de la base de données ...

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
25
répondu BSB 2013-03-27 05:24:04

Pour MySQL essayez ceci

Model.where("MONTH(date_column) = 12")
Model.where("YEAR(date_column) = 2012")
Model.where("DAY(date_column) = 24")
10
répondu Beena Shetty 2018-07-10 13:10:35

Dans votre Modèle:

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

Dans votre Contrôleur:

@courses = Course.by_year(params[:year])
9
répondu 张健健 2013-03-30 03:16:48

Je pense que la façon la plus simple de le faire est une portée:

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

Utilisez-le comme ceci:

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

Qui retournera tous les modèles avec un date_field entre le début et la fin de la journée pour la date que vous envoyez.

4
répondu Veraticus 2012-03-08 20:53:26

J'aime la réponse de BSB mais comme une portée

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)
1
répondu idrinkpabst 2013-05-11 05:06:42

Ce serait un autre:

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

Fonctionne assez bien pour moi dans SQLite.

1
répondu Tintin81 2013-09-20 17:12:05

Essayez ceci:

Model.where("MONTH(date_column) = ? and DAY(date_column) = ?", DateTime.now.month, DateTime.now.day)
1
répondu monteirobrena 2014-05-02 12:55:01

Mise en œuvre Simple pour seulement l'année

App / modèles / your_model.rb

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

L'Utilisation de

Model.created_in(1984)
1
répondu Joshua Pinter 2015-03-24 17:56:37

Pas besoin de extract méthode, cela fonctionne comme un charme dans postgresql:

text_value = "1997" # or text_value = '1997-05-19' or '1997-05' or '05', etc
sql_string = "TO_CHAR(date_of_birth::timestamp, 'YYYY-MM-DD') ILIKE '%#{text_value.strip}%'"
YourModel.where(sql_string)
0
répondu heriberto perez 2017-06-07 17:13:10