Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update a relation in a scope?

How can I have something like an ActiveRecord scope that modifies the relation it's acting on?

Specifically, I want to be able to do this:

Model.some_scope.fake_destroy_all

Where fake_destroy_all is the functionality I'm trying to create. It'll be pretty much equivalent to .update_all(deleted: true).

A weak alternative would be:

def fake_destroy_all(relation = Model)
    relation.update_all(deleted: true)
end

#...

Model.fake_destroy_all(Model.some_scope)

But that's not ideal. What I'd like to do is something like:

scope :fake_destroy_all, update_all(deleted: true)

But that doesn't work.

Is it possible to do something like what I'm describing?

like image 295
Fishtoaster Avatar asked Sep 30 '22 23:09

Fishtoaster


1 Answers

Try this:

def self.fake_destroy_all
  self.update_all(deleted: true)
end

It turns out that relations retain all the class methods. Scopes are class methods as well, and it's the exact mechanism that allows scopes to be "chained".

Therefore, relations with this model will also have this method with self being an instance of a relation.

The scope syntax can probably be fixed like so (cannot test right now, but note the lambda instead of simple method call):

scope :fake_destroy_all, -> { update_all(deleted: true) }

I suppose, without updating inside a lambda it effectively calls this method on invoking scope, on class definition. It does work with simple where-type scopes because where has no side-effect, it only returns an applicable filter. It's evaluated once and saved in a method for use in the future as-is.

With a lambda, however, the execution is held until the scope is actually applied, not until defined. It's necessary, because update_all does have a side-effect.

The same trick is useful if you are specifying a scope with a constantly changing condition, the simplest case being dependency on Time.now, that changes over time and needs to be re-evaluated each time.

However, I suggest you use a class method: a scope is more like a filter, whereas this is more like a "filter enforcement". While this might work too, scopes are semantically for different use cases.

like image 123
D-side Avatar answered Oct 03 '22 10:10

D-side