web-dev-qa-db-fra.com

Rails - Trier par données de table de jointure

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?

23
Steve Davis

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

27
ecoologic

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"
21
Steve Davis

@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:

  1. 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.

  2. 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 .

9
TeWu

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'))
}
4
Lucas Caton

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)
}
2

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

0
mld.oscar