J'ai un projet RoR dans les travaux. Voici les sections applicables de mes modèles.
Accueil
has_many :communities, :through => :availabilities
has_many :availabilities, :order => "price ASC"
Communauté
has_many :homes, :through => :availabilities
has_many :availabilities
Disponibilité
belongs_to :home
belongs_to :community
La table "disponibilités" de la base de données contient la colonne de données supplémentaire "price"
Alors maintenant je peux appeler
@home.availabilities.each do |a|
a.community.name
a.price
et récupérer les données de disponibilités classées par prix comme je le souhaite. Ma question est la suivante:
Existe-t-il un moyen de commander automatiquement Homes par avaliabilities.first.price
(premier = plus bas)? Peut-être que quelque chose avec default_scope :order
?
Je suggérerais d'éviter d'utiliser default_scope
, en particulier sur quelque chose comme le prix sur une autre table. Chaque fois que vous utiliserez cette table, des jointures et des commandes auront lieu, ce qui donnera peut-être des résultats étranges dans les requêtes complexes et, de toute façon, ralentira votre requête.
Il n'y a rien de mal à avoir une portée propre, c'est plus simple et c'est encore plus clair, vous pouvez le rendre aussi simple que:
scope :ordered, -> { includes(:availabilities).order('availabilities.price') }
PS: N'oubliez pas d'ajouter un index sur price
Compris avec l'aide de cet article lié .
J'ai déplacé les commandes du modèle Home vers le modèle de disponibilité:
Disponibilité
default_scope :order => "price ASC"
Ensuite, je recherche les disponibilités dans le modèle Home et les trie par prix:
Accueil
default_scope :include => :availabilities, :order => "availabilities.price ASC"
@ecoologic answer :
scope :ordered, -> { includes(:availabilities).order('availabilities.price') }
est formidable, mais il convient de mentionner que includes
pourrait, et dans certains cas devrait être remplacé par joins
. Ils ont tous deux leur cas d'utilisation optimale .
Du point de vue pratique, il existe deux différences principales:
includes
charge le (s) enregistrement (s) associé (s); dans ce cas, Availability
enregistre. joins
ne chargez aucun enregistrement associé. Vous devez donc utiliser includes
lorsque vous souhaitez utiliser les données du modèle de jointure, par exemple. affichez price
quelque part. De plus, joins
doit être utilisé si vous souhaitez utiliser les données du modèle de jointure uniquement dans une requête, par exemple. dans les clauses ORDER BY
ou WHERE
.
includes
charge tous les enregistrements, alors que joins
charge uniquement les enregistrements auxquels est associé un modèle de jointure. Donc, dans le cas d'OP, Home.includes(:availabilities)
chargerait toutes les maisons, alors que Home.joins(:availabilities)
ne chargerait que les maisons qui ont au moins une disponibilité associée.
Voir aussi cette question .
Dans Rails 5.2+, vous pouvez recevoir un avertissement de dépréciation lorsque vous transmettez un paramètre string à la méthode order:
AVERTISSEMENT DE DEPRECATION: Méthode de requête dangereuse (méthode dont les arguments sont utilisés en tant que SQL brut) appelée avec un ou plusieurs arguments non-attributaires: "table.column". Les arguments non-attributaires ne seront pas autorisés dans Rails 6.0. Cette méthode ne doit pas être appelée avec des valeurs fournies par l'utilisateur, telles que des paramètres de requête ou des attributs de modèle.
Pour résoudre ce problème, vous pouvez utiliser Arel.sql()
:
scope :ordered, -> {
includes(:availabilities).order(Arel.sql('availabilities.price'))
}
Une autre façon d'y parvenir:
scope :ordered, -> { includes(:availabilities).order(Availability.arel_table[:price]) }
Vous pouvez également spécifier la direction ASC
avec
scope :ordered, -> { includes(:availabilities).order(Availability.arel_table[:price].asc) }
DESC
:
scope :ordered, -> { includes(:availabilities).order(Availability.arel_table[:price].desc) }
L'utilisation de arel_table
sur le modèle ActiveRecord
vous permet de sauvegarder le scénario lorsque le nom de la table est modifié (mais cela se produit très rarement).
Notez qu'il est agréable d'ajouter main_table#id
pour un tri déterminé.
Donc, la version finale serait:
scope :ordered, -> {
includes(:availabilities).
order(Availability.arel_table[:price].asc, order(Home.arel_table[:id].asc)
}
Vous pouvez également trier les tables liées comme ceci (par exemple):
class User
has_many :posts
end
class Post
belongs_to :user
scope :sorted_by_user_and_title, -> {
joins(:user).merge(
User.order(first_name: :asc, last_name: :asc)
)
.order(title: :desc)
# SELECT * FROM `posts`
# INNER JOIN `users` ON `posts`.`user_id` = `users`.`id`
# ORDER BY
# `users`.`first_name` ASC, `users`.`last_name` ASC, `posts`.`title` DESC;
}
scope :sorted_by_title_and_user, -> {
order(title: :desc)
.joins(:user).merge(
User.order(first_name: :asc, last_name: :asc)
)
# SELECT * FROM `posts`
# INNER JOIN `users` ON `posts`.`user_id` = `users`.`id`
# ORDER BY
# `posts`.`title` DESC, `users`.`first_name` ASC, `users`.`last_name` ASC;
}
end
Cordialement