ActiveRecord keeping scope encapsulated
I have two models, foo
and bar
, foo
has many bars
.
Bar
is an event that happens for a given period of time, so I'd like a method or scope that returns an ActiveRecord::Relation
representing the foos that have currently active bars.
This is easy enough in the Foo
class with a scope:
class Foo < ActiveRecord::Base
has_many :bars
scope :has_current_bars, joins(:bars).where('bar.foo_id IS NOT NULL').where('bar.starts_at <= ?', DateTime.now).wh开发者_开发百科ere('bar.ends_at >= ?', DateTime.now)
What I don't like about this, is that foo
needs to know an awful lot about the internals of bar
.
Can this be rewritten, possibly by adding a scope on bar
, so foo
doesn't need to know about bar
attributes?
Absolutely. You can, and should, move the scope to Bar
.
class Bar < ActiveRecord::Base
belongs_to :foo
scope :current, where('starts_at <= ? AND ends_at >= ?', DateTime.now, DateTime.now)
end
foo = Foo.first
foo.bars.current # Will return all of foo's bars which match the scope
# EDIT:
bars.current.map(&:foo) # Will return all foos that have current bars
class Foo < ActiveRecord::Base
has_many :bars
def self.has_current_bars
joins(:bars).merge(Bar.current)
end
# or
scope :has_current_bars, joins(:bars).merge(Bar.current)
end
class Bar < ActiveRecord::Base
scope :current, where('bar.starts_at <= ?', DateTime.now).where('bar.ends_at >= ?', DateTime.now)
end
foos = Foo.has_current_bars
If you want to encapsulate your query objects, I have written a micro library that makes it really simple to move complex query logic outside of your models and controllers.
https://github.com/ElMassimo/queryable
It takes care of making your scopes chainable, and delegating methods like each and map to the actual query.
For this case, you could have two query objects, FooQuery and BarQuery, and make this objects collaborate so that each query object takes care of encapsulating the logic related to its corresponding model.
精彩评论