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 un domaine pour en construire un autre. Je me demande s’il existe un moyen d’utiliser les étendues lors de la définition d’une relation (par exemple, un “has_many”).

J’ai des enregistrements qui ont des colonnes d’autorisation. Je voudrais construire un default_scope qui prenne en compte mes colonnes de permission 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 un moyen pratique de passer un proc (dont j’ai besoin pour la liaison des variables tardives). Est-il possible de définir un has_many dans lequel une scope nommée peut être transmise?

L’idée de réutiliser une étendue nommée ressemblerait à ceci:

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

Ou, implicitement, le codage de l’étendue nommée dans la relation ressemblerait à ceci:

 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.

Comme 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, par exemple:

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

Je vous suggère de jeter un coup d’oeil à “Les scopes nommées sont mortes”

L’auteur explique comment Arel est puissant 🙂

J’espère que ça va aider.

EDIT # 1 Mars 2014

Comme l’indiquent certains commentaires, la différence est maintenant une question de goût personnel.

Cependant, je recommande toujours d’éviter d’exposer le domaine d’Arel à un niveau supérieur (en tant que contrôleur ou autre élément qui accède directement aux modèles), ce qui nécessiterait:

  1. Créez une étendue et exposez-la à l’aide d’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 disposez donc d’une couche de service), alors vous allez bien. La couche anti-corruption est votre service et peut accéder à la scope de votre modèle sans trop vous soucier de la manière dont les étendues sont implémentées.

Qu’en est-il des 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: J'aimerais souligner l'avantage des extensions d'association par rapport aux méthodes ou aux étendues de classe: vous avez access aux éléments internes du proxy d'association:

proxy_association.owner renvoie l'object dont l'association fait partie. proxy_association.reflection renvoie l'object de reflection qui décrit l'association. proxy_association.target retourne l'object associé pour belongs_to ou has_one, ou la collection d'objects 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

Au lieu de scopes, je viens de définir des méthodes de classe qui fonctionnent très bien

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

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 

Vous pouvez utiliser la méthode de fusion pour fusionner les étendues de différents modèles. Pour plus de détails chercher pour fusionner dans ce railcast

Si vous essayez seulement d’obtenir les commandes de l’utilisateur, pourquoi ne pas simplement utiliser la relation?

En supposant que l’utilisateur actuel est accessible depuis la méthode current_user de votre contrôleur:

 @my_orders = current_user.orders 

Cela garantit que seuls les ordres spécifiques d’un utilisateur seront affichés. Vous pouvez également faire des jointures nestedes de manière arbitraire pour obtenir des ressources plus profondes en utilisant des joins

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