web-dev-qa-db-fra.com

ActiveRecord comprend. Spécifier les colonnes incluses

J'ai un modèle de profil. Profil has_one Utilisateur. Le modèle d'utilisateur a un email de champ. Quand j'appelle

Profile.some_scope.includes(:user)

il appelle

SELECT users.* FROM users WHERE users.id IN (some ids)

Mais mon modèle utilisateur comporte de nombreux champs que je n'utilise pas dans le rendu. Est-il possible de charger uniquement les emails des utilisateurs? Donc, SQL devrait ressembler à

SELECT users.email FROM users WHERE users.id IN (some ids)
25
ibylich

Les rails n'ont pas la possibilité de passer les options pour include query. Mais nous pouvons passer ces paramètres avec la déclaration d'association sous le modèle.

Pour votre scénario, vous devez créer un nouveau modèle d'association avec des utilisateurs sous le modèle de profil, comme ci-dessous.

belongs_to :user_only_fetch_email, :select => "users.id, users.email", :class_name => "User"

je viens de créer une autre association, mais elle pointe uniquement vers le modèle utilisateur. Donc, votre requête sera,

Profile.includes(:user_only_fetch_email)

ou

Profile.includes(:user_only_fetch_email).find(some_profile_ids)
31
Mohanraj

Si vous souhaitez sélectionner des attributs spécifiques, vous devez utiliser joins plutôt que includes.

À partir de cet asciicast :

l’option d’inclusion ne fonctionne pas vraiment avec l’option de sélection car nous n’avons pas le contrôle sur la façon dont la première partie de l’instruction SELECT est générée. Si vous avez besoin de contrôler les champs de la commande SELECT, vous devez utiliser des jointures sur inclure.

Utilisation de joins:

Profile.some_scope.joins(:users).select("users.email")
11
Chris Salzberg

Vous avez besoin d'appartenir au modèle.

Pour une association simple:

belongs_to :user_restricted, -> { select(:id, :email) }, class_name: 'User'

Pour une association polymorphe (par exemple, :commentable):

belongs_to :commentable_restricted, -> { select(:id, :title) }, polymorphic: true, foreign_type: :commentable_type, foreign_key: :commentable_id

Vous pouvez choisir le nom belongs_to de votre choix. Pour les exemples donnés ci-dessus, vous pouvez les utiliser comme Article.featured.includes(:user_restricted), Comment.recent.includes(:commentable_restricted) etc.

5
Musaffa

Je voulais cette fonctionnalité moi-même, alors merci de l’utiliser. Incluez cette méthode dans votre classe

#ACCEPTS args au format de chaîne "ASSOCIATION_NAME: COLUMN_NAME-COLUMN_NAME"

def self.includes_with_select(*m)
    association_arr = []
    m.each do |part|
      parts = part.split(':')
      association = parts[0].to_sym
      select_columns = parts[1].split('-')
      association_macro = (self.reflect_on_association(association).macro)
      association_arr << association.to_sym
      class_name = self.reflect_on_association(association).class_name 
      self.send(association_macro, association, -> {select *select_columns}, class_name: "#{class_name.to_sym}")
    end
    self.includes(*association_arr)
  end

Et vous pourrez appeler comme: Contract.includes_with_select('user:id-name-status', 'confirmation:confirmed-id'), et il sélectionnera les colonnes spécifiées.

2
ClassyPimp

Rails ne prend pas en charge la sélection de colonnes spécifiques lorsque includes. Vous savez, c'est juste lazy load

Il utilise le module ActiveRecord :: Associations :: Preloader pour charger les données associées avant que les données ne soient réellement utilisées. Par la méthode :

def preload(records, associations, preload_scope = nil)
    records = Array.wrap(records).compact

    if records.empty?
      []
    else
      records.uniq!
      Array.wrap(associations).flat_map { |association|
        preloaders_on association, records, preload_scope
      }
    end
 end

preload_scope, le troisième paramètre de preload, est un moyen de sélectionner des colonnes. Mais ne peut plus charger paresseux .

Aux rails 5.1.6

relation = Profile.where(id: [1,2,3])
user_columns = {:select=>[:updated_at, :id, :name]}
preloader = ActiveRecord::Associations::Preloader.new
preloader.preload(relation, :user, user_columns)

Il sélectionnera les colonnes spécifiées que vous avez passées. Mais, il ne s'agit que d'une association. Vous devez créer un correctif pour ActiveRecord::Associations::Preloader afin de prendre en charge le chargement simultané de plusieurs associations complexes. 

Voici un exemple pour patch

La façon de l'utiliser, exemple

1
EarlyZhao

En utilisant l'exemple de Mohanaj, vous pouvez faire ceci:

belongs_to :user_only_fetch_email, -> { select [:id, :email] }, :class_name => "User"
0
Yan Zhao