Використовуйте область за замовчуванням для відносин Rails has_many


81

Скажімо, у мене є такі класи

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має сферу застосування life_supportingі SolarSystem has_many :planets. Я хотів би визначити моє відношення has_many так, щоб, коли я запитую а solar_systemдля всіх пов’язаних planets, life_supportingобласть автоматично застосовувалася. По суті, я б хотів solar_system.planets == solar_system.planets.life_supporting.

Вимоги

  • Я НЕ хочу , щоб змінити scope :life_supportingв Planetдо

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

  • Я також хотів би запобігти дублюванню, не додаючи до SolarSystem

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

Мета

Я хотів би мати щось подібне

has_many :planets, :with_scope => :life_supporting

Редагувати: Навколо роботи

Як сказав @phoet, можливо, неможливо досягти області за замовчуванням за допомогою ActiveRecord. Однак я знайшов два потенційні шляхи роботи. Обидва запобігають дублюванню. Перший, хоч і довгий, зберігає очевидну читабельність і прозорість, а другий - метод допоміжного типу, вихід якого явний.

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

Інше рішення, яке є набагато чистішим, - це просто додати наступний метод SolarSystem

def life_supporting_planets
  planets.life_supporting
end

і використовувати solar_system.life_supporting_planetsде б ви не використовували solar_system.planets.

Жоден з них не відповідає на запитання, тому я просто розміщую їх тут як робочі місця, якщо хтось інший стикається з цією ситуацією.


2
ваш обхідний шлях із використанням where_vales - це дійсно найкраще доступне рішення, на яке варто прийняти відповідь
Віктор Трон,

where_valuesможе не працювати з хеш-умовами: {:cleared => false}... він дає масив хешів, що не подобається ActiveRecord. Як хак, захоплення першого елемента в масиві працює: Planet.life_supporting.where_values[0]...
Нолан Емі

Я виявив, що мені доводилося використовувати where_astзамість того, where_valuesчи where_values_hashя використовував AREL в області застосування на іншій моделі. Працював ласощі! +1
br3nt

Відповіді:


130

У Rails 4 Associationsє необов’язковий scopeпараметр, який приймає лямбда- сигнал, який застосовується до Relation(пор. Документ для 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

У Rails 3 where_valuesобхідний шлях іноді можна покращити, використовуючи, where_values_hashщо обробляє кращі області застосування, де умови визначаються множиною whereабо хешем (не у цьому випадку).

has_many :planets, conditions: Planet.life_supporting.where_values_hash

Не могли б ви докладно розповісти про рішення rails 3? Рейки 4 одна така чиста!
Августин Рідінгер

1
Що стосується Rails 3, то, на мою думку, це має бути прочитане, has_many :planets, conditions: Planet.life_supporting.where_values_hashщоб забезпечити область дії. Це також золото для нетерплячих завантажень.
нерфолог

1
Я виявив важкий спосіб, where_values_hashякий не працює з текстом, де речення, наприклад User.where(name: 'joe').where_values_hash, повернуть хеш очікуваних умов, тоді як User.where('name = ?', 'Joe').where_values_hashні. Тому приклад з планетами, швидше за все, не вдасться.
нерфолог

1
@nerfologist Дякую, що вказали на помилку в останньому прикладі коду, я відредагував відповідь. Ваш другий коментар має сенс, я думаю, що це те, про що я мав на увазі в останньому абзаці відповіді. Не соромтеся редагувати мою відповідь, якщо ви можете знайти більш чіткий спосіб пояснити обмеження.
user1003545

2
@ GrégoireClermont, це більше не працює в Rails 5
elquimista

16

У Rails 5 наступний код чудово працює ...

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

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

1

я щойно глибоко занурився в ActiveRecord, і не схоже, що цього можна досягти за допомогою поточної реалізації has_many. Ви можете передати блок:conditions але це обмежується поверненням хешу умов, а не будь-яких матеріалів арелу.

дійсно простим і прозорим способом досягти того, що ви хочете (те, що, на мою думку, ви намагаєтесь зробити), є застосувати область дії під час виконання:

  # foo.rb
  def bars
    super.baz
  end

це далеко не те, про що ви просите, але це може просто спрацювати;)


Спасибі, фет! Це буде працювати, однак він просто буде виглядати трохи дивно , в огляді коду. Я думаю, що відгук, який я отримаю при реалізації цього, полягає в тому, щоб замість цього дублювати has_manyдекларацію, оскільки вона зрозуміліша.
Аарон,

з моєї точки зору перегляду коду, я віддав би перевагу цьому варіанту перед дублюванням умов і т. д., поки ви надаєте тест на те, що це має робити, цей підхід набагато більше СУХИЙ та SRP
phoet

Я настійно рекомендую не використовувати такий метод, коли зазвичай використовується асоціація. Натомість я б видалив умови з асоціації та сфери, яка явно викликана асоціацією. Це буде більш ремонтопридатним та зрозумілішим у майбутньому.
BM5k

На жаль, щойно зрозумів, що ця публікація справді стара. Потрапив сюди на дослідження подібної проблеми.
BM5k
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.