开发者

ActiveRecord keeping scope encapsulated

开发者 https://www.devze.com 2023-02-05 21:15 出处:网络
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

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消