byroot/frozen_record

Advanced filtering

sandstrom opened this issue · 4 comments

I'm in the process of migrating a bunch of models that are backed by YAML files. We had some homegrown code that basically did what frozen record does, but not as elegant as this library.

One thing that has cropped up though, is some filtering limitations.

We have one model with scopes that does greater-than/less-than comparisons of time.

More specifically, we're doing something like this with our homegrown code:

scope :in_use, lambda {
  current_time = Time.now
  records.find_all { |c| !c.withdrawn_at || c.withdrawn_at > current_time }
}

I haven't figured out a way of moving this over to FrozenRecord scopes, since the query interface doesn't support a gt operator in where.

So one idea I wanted to throw out there:

How about adding support for .find_by_lambda(lambda { |r| … }) or .find_all { |r| … }, for those edge cases where the regular querying interface isn't enough?

Is this something you'd be open to exploring? In that case, I can submit a more fullfledged example of what the API could look like.

byroot commented

I'd rather not add extra complexity, even less so if it's not interfaces supported by Active Record.

For use cases like this I simple recommend to drop down to Array methods

Alright, makes sense to avoid interfaces that aren't supported by Active Record.

I've gotten past the first hurdle using range queries, so some progress! 😄

This scope will exclude both times outside the range and nil values.

scope :withdrawn, lambda {
  where(withdrawn_at: ..Time.now)
}

Using Or

Next challenge is the inverse though. Include items where withdrawn_at is nil, or the time is outside the range (not yet withdrawn). Since nil is always considered outside the range, using only the logic from the scope above won't work.

There is or in Active Record. However, I'm not sure it's possible to use within scopes in Active Record.

scope :except_withdrawn, lambda {
  where.not(withdrawn_at: nil).or(Currency.where(withdrawn_at: ..Time.now))

Source:
https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-or

Regular method

I can likely make this work decently by simply using a regular method:

def self.except_withdrawn
  all.find_all { |r| r.withdrawn_at == nil || r.withdrawn_at > Time.now })
end

Summary

Overall this isn't a major problem though. I think there are decent workarounds to using a scope here. Just wanted to mention .or since we're discussing the topic.

Feel free to close this, unless you want to explore this area further.

I'll close as filtering is already not very performant, so I'd rather not slow it down. If you feel strongly about .or, please open a PR, but I'll only accept it if the perf impact is minimal.