J'ai un modèle ActiveRecord qui a un attribut de date. Est-il possible d'utiliser cet attribut de date pour rechercher 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 trouver_par_date (2012-12-1).
J'espérais pouvoir éviter de créer des attributs Année, Mois et Jour.
En supposant que votre "attribut de date" est une date (plutôt qu'un horodatage complet), un simple where
vous donnera votre "recherche par 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 sur l'année, le mois et le 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 qu'est 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)
Les spécificateurs de format %m
Et %d
Ajouteront des zéros non significatifs dans certains cas et cela peut confondre les tests d'égalité, d'où la fonction cast(... as int)
pour forcer les chaînes formatées en 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 créer votre propre couche de portabilité (laide mais parfois nécessaire), vous lier à un ensemble limité de bases de données (réaliste sauf si vous publiez quelque chose qui doit s'exécuter sur n'importe quelle base de données), ou faites toute votre logique dans Ruby (fou pour toute quantité de données non triviale).
Les requêtes sur l'année, le mois et le 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 des fonctions, mais ActiveRecord est trop stupide pour les comprendre, donc cela fera un gros gâchis si vous essayez de les utiliser; donc, si vous trouvez que ces requêtes sont trop lentes, vous devrez ajouter les trois colonnes supplémentaires que vous essayez d'éviter.
Si vous utilisez beaucoup ces requêtes, vous pouvez leur ajouter des étendues, mais la méthode recommandée pour ajouter une étendue 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 des arguments pour les étendues. Ces méthodes seront toujours accessibles sur les objets d'association ...
Vous auriez donc 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
Cela renverra tous les modèles avec un champ date_ entre le début et la fin de la journée pour la date que vous envoyez.
Essaye ça:
Model.where("MONTH(date_column) = ? and DAY(date_column) = ?", DateTime.now.month, DateTime.now.day)
app/models/your_model.rb
scope :created_in, ->(year) { where( "YEAR( created_at ) = ?", year ) }
tilisation
Model.created_in(1984)
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.
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)
Une autre portée courte comme ci-dessous:
class Leave
scope :by_year, -> (year) {
where(date:
Date.new(year).beginning_of_year..Date.new(year).end_of_year)
}
end
Appelez la portée:
Leave.by_year(2020)
Pas besoin de la méthode extract
, 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)