web-dev-qa-db-fra.com

Comment définissez-vous les associations ActiveRecord dans Rails 3?

J'ai un projet Rails 3. Avec Rails 3 est venu Arel et la possibilité de réutiliser une portée pour en construire une autre. Je me demande s'il existe un moyen de utilisez des étendues lors de la définition d'une relation (par exemple, "has_many").

J'ai des enregistrements qui ont des colonnes d'autorisation. Je voudrais construire un default_scope qui prend en compte mes colonnes d'autorisation afin que les enregistrements (même ceux accessibles via une relation) soient filtrés.

Actuellement, dans Rails 3, default_scope (y compris les correctifs que j'ai trouvés) ne fournit pas de moyen pratique de passer un proc (dont j'ai besoin pour la liaison de variable tardive). Est-il possible de définir un has_many dans lequel une portée nommée peut être passée?

L'idée de réutiliser une portée nommée ressemblerait à:

Orders.scope :my_orders, lambda{where(:user_id => User.current_user.id)}
has_many :orders, :scope => Orders.my_orders

Ou coder implicitement cette étendue nommée dans la relation ressemblerait à:

has_many :orders, :scope => lambda{where(:user_id => User.current_user.id)}

J'essaie simplement d'appliquer default_scope avec une liaison tardive. Je préférerais utiliser une approche Arel (s'il y en a une), mais utiliserais n'importe quelle option réalisable.

Puisque je fais référence à l'utilisateur actuel, je ne peux pas compter sur des conditions qui ne sont pas évaluées au dernier moment possible, telles que:

has_many :orders, :conditions => ["user_id = ?", User.current_user.id]
59
Mario

Je vous suggère de jeter un œil à "Les étendues nommées sont mortes"

L'auteur y explique à quel point Arel est puissant :)

J'espère que ça vous aidera.

EDIT # 1 mars 2014

Comme certains commentaires l'indiquent, la différence est désormais une question de goût personnel.

Cependant, je recommande toujours personnellement d'éviter d'exposer la portée d'Arel à une couche supérieure (être un contrôleur ou tout autre élément qui accède directement aux modèles), et cela nécessiterait:

  1. Créez une étendue et exposez-la via une méthode dans votre modèle. Cette méthode serait celle que vous exposez au contrôleur;
  2. Si vous n'exposez jamais vos modèles à vos contrôleurs (vous avez donc une sorte de couche de service dessus), alors tout va bien. La couche anti-corruption est votre service et elle peut accéder à la portée de votre modèle sans trop se soucier de la façon dont les étendues sont implémentées.
21
Franck Verrot

Et les extensions d'association?

class Item < ActiveRecord::Base
  has_many :orders do
    def for_user(user_id)
      where(user_id: user_id)
    end
  end
end

Item.first.orders.for_user(current_user)

MISE À JOUR: Je voudrais souligner l'avantage des extensions d'association par rapport aux méthodes de classe ou aux étendues, c'est que vous avez accès aux internes du proxy d'association:

proxy_association.owner renvoie l'objet dont l'association fait partie. proxy_association.reflection renvoie l'objet de réflexion qui décrit l'association. proxy_association.target renvoie l'objet associé pour appartient_à ou has_one, ou la collection d'objets associés pour has_many ou has_and_belongs_to_many.

Plus de détails ici: http://guides.rubyonrails.org/association_basics.html#association-extensions

20
Peter P.

Au lieu d'étendues, je viens de définir des méthodes de classe, ce qui fonctionne très bien

def self.age0 do
  where("blah")
end
15
Kevin

J'utilise quelque chose comme:

class Invoice < ActiveRecord::Base
  scope :aged_0,  lambda{ where("created_at IS NULL OR created_at < ?", Date.today + 30.days).joins(:owner) }
end 
10
craig

Vous pouvez utiliser la méthode merge afin de fusionner les portées de différents modèles. Pour plus de détails, recherchez la fusion dans ce railscast

9
ramigg

Si vous essayez simplement d'obtenir les commandes de l'utilisateur, pourquoi n'utilisez-vous pas simplement la relation?

En supposant que l'utilisateur actuel est accessible à partir de la méthode current_user dans votre contrôleur:

@my_orders = current_user.orders

Cela garantit que seules les commandes spécifiques d'un utilisateur seront affichées. Vous pouvez également effectuer des jointures imbriquées arbitrairement pour obtenir des ressources plus approfondies en utilisant joins

current_user.orders.joins(:level1 => { :level2 => :level3 }).where('level3s.id' => X)
3
adamlamar