web-dev-qa-db-fra.com

Utiliser une portée par défaut sur une relation Rails has_many

Disons que j'ai les classes suivantes

class SolarSystem < ActiveRecord::Base
  has_many :planets
end

class Planet < ActiveRecord::Base
  scope :life_supporting, where('distance_from_Sun > ?', 5).order('diameter ASC')
end

Planet a une portée life_supporting et SolarSystemhas_many :planets. Je voudrais définir ma relation has_many afin que lorsque je demande un solar_system Pour tous les planets associés, la portée life_supporting Soit automatiquement appliquée. Essentiellement, je voudrais solar_system.planets == solar_system.planets.life_supporting.

Exigences

  • --- pas je veux changer scope :life_supporting Dans Planet en

    default_scope where('distance_from_Sun > ?', 5).order('diameter ASC')

  • Je voudrais également éviter la duplication en évitant d'avoir à ajouter à SolarSystem

    has_many :planets, :conditions => ['distance_from_Sun > ?', 5], :order => 'diameter ASC'

Objectif

J'aimerais avoir quelque chose comme

has_many :planets, :with_scope => :life_supporting

Modifier: Contours de travail

Comme l'a dit @phoet, il peut ne pas être possible d'atteindre une portée par défaut en utilisant ActiveRecord. Cependant, j'ai trouvé deux solutions possibles. Les deux empêchent la duplication. La première, bien que longue, conserve une lisibilité et une transparence évidentes, et la seconde est une méthode de type assistant dont la sortie est explicite.

class SolarSystem < ActiveRecord::Base
  has_many :planets, :conditions => Planet.life_supporting.where_values,
    :order => Planet.life_supporting.order_values
end

class Planet < ActiveRecord::Base
  scope :life_supporting, where('distance_from_Sun > ?', 5).order('diameter ASC')
end

Une autre solution beaucoup plus propre consiste simplement à ajouter la méthode suivante à SolarSystem

def life_supporting_planets
  planets.life_supporting
end

et d'utiliser solar_system.life_supporting_planets partout où vous utiliseriez solar_system.planets.

Aucun des deux ne répond à la question, je les mets simplement ici comme solution de rechange si quelqu'un d'autre devait rencontrer cette situation.

73
Aaron

Dans Rails 4, Associations ont un paramètre optionnel scope qui accepte un lambda qui est appliqué au Relation (cf. la doc pour ActiveRecord :: Associations :: ClassMethods )

class SolarSystem < ActiveRecord::Base
  has_many :planets, -> { life_supporting }
end

class Planet < ActiveRecord::Base
  scope :life_supporting, -> { where('distance_from_Sun > ?', 5).order('diameter ASC') }
end

Dans Rails 3, le where_values la solution de contournement peut parfois être améliorée en utilisant where_values_hash qui gère de meilleures étendues où les conditions sont définies par plusieurs where ou par un hachage (ce n'est pas le cas ici).

has_many :planets, conditions: Planet.life_supporting.where_values_hash
114
gregclermont

Dans Rails 5, le code suivant fonctionne très bien ...

  class Order 
    scope :paid, -> { where status: %w[paid refunded] }
  end 

  class Store 
    has_many :paid_orders, -> { paid }, class_name: 'Order'
  end 
3
Martin Streicher

je viens de plonger profondément dans ActiveRecord et il ne semble pas que cela puisse être réalisé avec l'implémentation actuelle de has_many. vous pouvez passer un bloc à :conditions mais cela se limite à renvoyer un hachage de conditions, pas n'importe quel type d'arel.

une manière vraiment simple et transparente de réaliser ce que vous voulez (ce que je pense que vous essayez de faire) est d'appliquer la portée au moment de l'exécution:

  # foo.rb
  def bars
    super.baz
  end

c'est loin de ce que vous demandez, mais ça pourrait bien marcher;)

1
phoet