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.
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.
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
Pour MySQL essayez ceci
Model.where("MONTH(date_column) = 12")
Model.where("YEAR(date_column) = 2012")
Model.where("DAY(date_column) = 24")
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])
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.
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)
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.
Essayez ceci:
Model.where("MONTH(date_column) = ? and DAY(date_column) = ?", DateTime.now.month, DateTime.now.day)
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)
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)