Question

Say I have a few activerecord models in my rails 3.1 project that look like this:

class Component < ActiveRecord::Base
    has_many :bugs
end

class Bug < ActiveRecord::Base
    belongs_to :component
    belongs_to :project

    scope :open, where(:open => true)
    scope :closed, where(:open => false)
end

class Project < ActiveRecord::Base
    has_many :bugs
    has_many :components_with_bugs, :through => :bugs, :conditions => ["bugs.open = ?", true]
end

In Short: I have a has_many through association (components_with_bugs) where I want to scope the "through" model. At present I'm doing this by duplicating the code for the scope.

Is there any way to define this has many through association (components_with_bugs) such that I can reuse the Bug.open scope on the through model, while still loading the components in a single database query? (I'm imagining something like :conditions => Bug.open)

No correct solution

OTHER TIPS

Rails 4 answer

Given you have:

class Component < ActiveRecord::Base
  has_many :bugs
end

class Bug < ActiveRecord::Base
  belongs_to :component
  belongs_to :project

  scope :open,   ->{ where( open: true) }
  scope :closed, ->{ where( open: false) }
end

You have two possibilities:

class Project < ActiveRecord::Base
  has_many :bugs

  # you can use an explicitly named scope
  has_many :components_with_bugs, -> { merge( Bug.open ) }, through: :bugs, source: 'component'

  # or you can define an association extension method
  has_many :components, through: :bugs do
    def with_open_bugs
      merge( Bug.open )
    end
  end
end

Calling projet.components_with_bugs or project.components.with_open_bugs will fire the same sql query:

SELECT "components".* FROM "components"
INNER JOIN "bugs" ON "components"."id" = "bugs"."component_id"
WHERE "bugs"."project_id" = ? AND "bugs"."open" = 't'  [["project_id", 1]]

Which one is better to use depends on your application. But if you need to use many scopes on the same association, I guess association extensions could be clearer.

The real magic is done with merge which allows you to, as the name says, merge conditions of another ActiveRecord::Relation. In this case, it is responsible for adding AND "bugs"."open" = 't' in the sql query.

Apart from your scopes , write the default scope as:
default_scope where(:open => true) in your "through" model Bug.

class Bug < ActiveRecord::Base
  belongs_to :component
  belongs_to :project
  default_scope where(:open => true)
  scope :open, where(:open => true)
  scope :closed, where(:open => false)
end

And in the Project model remove :conditions => ["bugs.open = ?", true]

class Project < ActiveRecord::Base
  has_many :bugs
  has_many :components_with_bugs, :through => :bugs
end

I think the above will work for you.

Try using the following.

has_many :components_with_bugs, :through => :bugs do
   Bug.open
end

Can't you use something like this ?

has_many :components_with_bugs, :through => :bugs, :conditions => Bug.open.where_values

I haven't tested it, just proposing an path for investigation

The http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html specifies

:conditions Specify the conditions that the associated object must meet in order to be included as a WHERE SQL fragment, such as authorized = 1.

Hence you can do it as:

class Project < ActiveRecord::Base
  has_many :bugs
  has_many :components_with_bugs, :through => :bugs do
    def open
      where("bugs.open = ?", true)
    end
  end
end

EDIT:

You can't specify another model's scope as a condition. In your case, they way you have it implemented is right. You can implement it another way as

has_many :components_with_bugs, :through => :bugs # in this case, no need to use the relation.

def open_bugs
  self.bugs.openn # openn is the scope in bug. Don't use name 'open'. It's a private method of Array.
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top