Rails 3将范围与连接合并

时间:2012-10-23 18:12:08

标签: ruby-on-rails rails-activerecord

设置

对于这个问题,我将使用以下三个类:

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:名为'planet_type'的关联是   未找到;也许你拼错了?

问题

我发现这是因为它等同于以下内容:

joins(:planets, :planet_type)...

和SolarSystem没有planet_type关联。我想使用like_earth上的Planet范围,has_earthlike_planet上的SolarSystem,并希望避免重复代码和条件。有没有办法合并这些范围,就像我试图做但却缺少一块?如果没有,我可以用什么其他技术来实现这些目标?

3 个答案:

答案 0 :(得分:8)

显然,此时您只能合并不涉及连接的简单构造。如果您将模型修改为如下所示,则可以采用以下方法:

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

**更新**

对于记录,bug was filed关于此行为 - 希望很快就会修复......

答案 1 :(得分:1)

您正在重复使用加入Planet.like_earth的范围planet_type中的条件。合并这些条件后,将在planet_type上调用SolarSystem关联,该关联不存在。

SolarSystem有许多planet_typesplanets,但这仍然不是正确的关联名称,因为它是多元化的。您可以将以下内容添加到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

答案 2 :(得分:1)

我发现一个简单的解决方案是您可以将Planet类中的联接更改为

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

这将为连接创建一个SQL字符串,它始终包含正确的表名,因此无论您是直接调用范围还是在合并中使用它,都应该始终有效。它不是那么漂亮,可能有点像黑客,但它只是一点点代码,没有必要改变你的关联。