简体   繁体   中英

Dry up Rails Active Record query conditions

In my Ruby on Rails project, I have a mailer that basically prepares a daily digest of things that happened in the system for a given user. In the mailer controller, I am gathering all the relevant records from the various models according to some common pattern (within a certain date, not authored by this user, not flagged, etc) and with minor differences from model to model.

There are half a dozen of models involved here (and counting), and most of them have unified column names for certain things (like date of publishing, or whether an item is flagged by admin or not). Hence, the ' where 's that go into query are mostly the same. There are minor differences in conditions, but at least 2 or 3 conditions are exactly the same. I easily assume there may be even more similar conditions between models, since we are just starting the feature and haven't figured out the eventual shape of the data yet.

I basically chain the ' where ' calls upon each model. It irritates me to have 6 lines of code so close to each other, spanning so far to the right of my code editor, and yet so similar. I am dreaded by the idea that at some point we will have to change one of the 'core' conditions, munging with that many lines of code all at once.

What I'd love to do is to move a core set of conditions that goes into each query into some sort of Proc or whatever, then simply call it upon each model like a scope, and after that continue the ' where ' chain with model-specific conditions. Much like a scope on each model.

What I am struggling with is how exactly to do that, while keeping the code inside mailer. I certainly know that I can declare a complex scope inside a concern, then mix it into my models and start each of queries with that scope. However, this way the logic will go away from the mailer into an uncharted territory of model concerns, and also it will complicate each model with a scope that is currently only needed for one little mailer in a huge system. Also, for some queries, a set of details from User model is required for a query, and I don't want each of my models to handle User.

I like the way scopes are defined in the Active Record models via lambdas (like scope :pending, -> { where(approved: [nil, false]) } ), and was looking for a way to use similar syntax outside model class and inside my mailer method (possibly with a tap or something like that), but I haven't found any good examples of such an approach.

So, is it possible to achieve? Can I collect the core ' where ' calls inside some variable in my mailer method and apply them to many models, while still being able to continue the where chain after that?

You could do something like:

@report_a = default_scope(ModelA)
@report_b = default_scope(ModelB)

private
def default_scope(model)
  model.
    where(approved: [nil, false]).
    order(:created_at)
    # ...
end

The beauty of Arel, the technology behind ActiveRecord query-building, is it's all completely composable, using ordinary ruby.

Do I understand your question right that this is what you want to do?

def add_on_something(arel_scope)
   arel_scope.where("magic = true").where("something = 1")
end


add_on_something(User).where("more").order("whatever").limit(10)

add_on_something( Project.where("whatever") ).order("something")

Just ordinary ruby method will do it, you don't need a special AR feature. Because AR scopes are already composable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM