Rails 3 объединяет области с соединениями

Настраивать

Для этого вопроса я буду использовать следующие три класса:

class SolarSystem < ActiveRecord::Base
  has_many :planets

  scope :has_earthlike_planet, joins(:planets).merge(Planet.like_earth)
end

class Planet < ActiveRecord::Base
  belongs_to :solar_system
  belongs_to :planet_type

  scope :like_earth, joins(:planet_type).where(:planet_types => {:life => true, :gravity => 9.8})
end

class PlanetType < ActiveRecord::Base
  has_many :planets

  attr_accessible :gravity, :life
end

Проблема

Прицел has_earthlike_planet не работает. Это дает мне следующую ошибку:

ActiveRecord::ConfigurationError: Ассоциация с именем «тип_планеты» не найдена; может вы неправильно написали?

Вопрос

Я узнал, что это потому, что это эквивалентно следующему:

joins(:planets, :planet_type)...

и SolarSystem не имеет ассоциации planet_type. Я бы хотел использовать область like_earth для Planet, has_earthlike_planet для SolarSystem и хотел бы избежать дублирования кода и условий. Есть ли способ объединить эти области, как я пытаюсь сделать, но что-то упустил? Если нет, то какие другие методы я могу использовать для достижения этих целей?


person Aaron    schedule 23.10.2012    source источник


Ответы (3)


По-видимому, в настоящее время вы можете объединять только простые конструкции, которые не включают соединения. Вот возможный обходной путь, если вы измените свои модели, чтобы они выглядели следующим образом:

class SolarSystem < ActiveRecord::Base
  has_many :planets
  has_many :planet_types, :through => :planets

  scope :has_earthlike_planet, joins(:planet_types).merge(PlanetType.like_earth)
end

class Planet < ActiveRecord::Base
  belongs_to :solar_system
  belongs_to :planet_type

  scope :like_earth, joins(:planet_type).merge(PlanetType.like_earth)
end

class PlanetType < ActiveRecord::Base
   has_many :planets

   attr_accessible :gravity, :life

   scope :like_earth, where(:life => true, :gravity => 9.8)
end

** ОБНОВИТЬ **

К сведению, об этом поведении была зарегистрирована ошибка — надеюсь, она скоро будет исправлена. .

person PinnyM    schedule 23.10.2012
comment
Мне нравится идея перенести условия в модель, над которой они работают, чтобы сохранить логику и просто упростить слияния вверх по цепочке. Единственная проблема с этим простым примером заключается в том, что, учитывая доступную структуру и информацию, невероятно маловероятно, что будет более одной записи PlanetType, имеющей значение like_earth. Это делает базовую область видимости неуместной, однако это может быть лучшим местом для нее, пока эта ошибка не будет исправлена. - person Aaron; 23.10.2012

Вы повторно используете условия из области Planet.like_earth, которая присоединяется к planet_type. Когда эти условия объединяются, ассоциация planet_type вызывается для SolarSystem, которого не существует.

SolarSystem имеет много от planet_types до planets, но это все же не правильное название ассоциации, так как оно во множественном числе. Вы можете добавить следующее в класс SolarSystem, чтобы настроить ассоциацию planet_type, которая является просто псевдонимом для planet_types. Однако вы не можете использовать Ruby alias, поскольку AREL отражает макросы ассоциации и не запрашивает, отвечает ли модель на метод с таким именем:

class SolarSystem < ActiveRecord::Base
  has_many :planets
  has_many :planet_types, :through => :planets
  has_many :planet_type, :through => :planets, :class_name => 'PlanetType'

  scope :has_earthlike_planet, joins(:planets).merge(Planet.like_earth)
end

SolarSystem.has_earthlike_planet.to_sql # => SELECT "solar_systems".* FROM "solar_systems" INNER JOIN "planets" ON "planets"."solar_system_id" = "solar_systems"."id" INNER JOIN "planets" "planet_types_solar_systems_join" ON "solar_systems"."id" = "planet_types_solar_systems_join"."solar_system_id" INNER JOIN "planet_types" ON "planet_types"."id" = "planet_types_solar_systems_join"."planet_type_id" WHERE "planet_types"."life" = 't' AND "planet_types"."gravity" = 9.8
person Ben Simpson    schedule 23.10.2012
comment
К счастью, для жителей Татуина нет такого понятия, как «Энтерпрайз». Я думаю, вы имеете в виду Империю. Во-вторых, на Татуине нет специй — все на Кесселе. Наконец, я думаю, вы имели в виду планету, а не растение. :П - person Ben Simpson; 23.10.2012
comment
Похоже, что это создает дополнительную временную связь под названием planet_types_solar_systems_join. Если я изменю область действия на scope :has_earthlike_planet, joins(:planet_type).merge(Planet.like_earth) (обратите внимание на объединение planet_type вместо planets), запрос будет выглядеть правильно. Я знаю, что за кулисами он делает SolarSystem.joins(:planet_type, :planet_type), но поскольку у него нет побочных эффектов, я думаю, что со мной все в порядке. Это определенно похоже на хак, чтобы обойти Rails, не поддерживая, куда идут соединения, используя модифицированные ассоциации. - person Aaron; 23.10.2012

Простое решение, которое я нашел, заключается в том, что вы можете изменить свои соединения в своем классе Planet на

joins(Planet.joins(:planet_type).join_sql)

Это создаст строку SQL для соединений, которая всегда будет включать правильные имена таблиц и, следовательно, всегда должна работать, независимо от того, вызываете ли вы область напрямую или используете ее в слиянии. Это не так красиво выглядит и может быть немного хакерским, но это всего лишь немного больше кода, и вам не нужно менять свои ассоциации.

person Alexander Weber    schedule 28.09.2016