How to order Rails Collection with a scope?

Viewed 308

Given a scope that determines if a model is active or not..

def active?
    return false unless published_at && expires_at && id

    DateTime.now.between? published_at, expires_at
  end

How do you use this scope, with other scopes, to sort a collection?

This doens't seem to work.

@posts = current_user.posts
        .includes(:orgs)
        .order(:active?)
        .order(created_at: :desc)
1 Answers

Your code doesn't work because you're returning a boolean in both cases. And a boolean can't be chained.

Instead, you need to return an ActiveRecord_Relation that can be chained. So, perhaps something more like:

def active?
  return none unless published_at && expires_at && id
  where("published_at <= ? AND expires_at >= ?", DateTime.now, DateTime.now)
end

TBH, I'm not sure the published_at && expires_at && id bit belongs in there (because published_at, expires_at, and id are all instance methods and you said you want a scope and a scope is in the class context). So, perhaps it's more like:

def active?
  where("published_at <= ? AND expires_at >= ?", DateTime.now, DateTime.now)
end

And, you said you want it to be a scope. So, I guess

scope :active?, -> { where("published_at <= ? AND expires_at >= ?", DateTime.now, DateTime.now) }

A scope is really just a class method. So, that could also be like:

class << self

  def active?
    where("published_at <= ? AND expires_at >= ?", DateTime.now, DateTime.now)
  end

end

...or:

def self.active?
  where("published_at <= ? AND expires_at >= ?", DateTime.now, DateTime.now)
end

...depending on what floats your boat.

Now, like I said, that's going to return an ActiveRecord_Relation, but doesn't do anything about sorting. I guess that would look like:

@posts =  current_user.
            posts.
            includes(:orgs).
            active?.
            order(created_at: :desc)

Personally, I think I would break that scope up to be more like:

class << self

  def active?
    published?.not_expired?
  end

  def published?
    where("published_at <= ?", DateTime.now)
  end

  def not_expired?
    where.not("expires_at <= ?", DateTime.now)
  end

end

'cause somewhere else you might want to use published? and not_expired?.

Related