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?
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.
Related
Rails internally converts scopes to class methods then why can't we use class methods itself instead of going with scopes.
From the fine guide:
14 Scopes
[...]
To define a simple scope, we use the scope method inside the class, passing the query that we'd like to run when this scope is called:
class Article < ActiveRecord::Base
scope :published, -> { where(published: true) }
end
This is exactly the same as defining a class method, and which you use is a matter of personal preference:
class Article < ActiveRecord::Base
def self.published
where(published: true)
end
end
Note in particular:
This is exactly the same as defining a class method, and which you use is a matter of personal preference
And a little further (the Rails3 guide says the same thing here BTW):
14.1 Passing in arguments
[...]
Using a class method is the preferred way to accept arguments for scopes.
So which you use is a matter of preference and it is even recommended that you use class methods for scopes that take arguments.
Using scope is mostly a notational issue. If you say scope :whatever then you're explicitly saying that whatever is meant to be a query builder; if you say def self.whatever then you're not implying anything about the intent of the whatever method, you're just defining some class method that may or may not behave like a scope.
Of course, 14.1 makes a mess of this notational distinction by recommending that you not use scope when your scope takes arguments. Also keep in mind that in Rails3 you could say:
scope :published, where(published: true)
so an argumentless scope was visually "clean" and terse but adding a lambda to handle arguments would make it look messier:
scope :pancakes, ->(x) { where(things: x) }
But Rails4 wants lambdas even for argumentless scopes the distinction makes even less sense now.
I suspect that the difference is historical at this point. Scopes were probably something special back in the before times but became plain old class methods in the Rails3 era to cut down on duplication and to better mesh with the new query interface that came with Rails3.
So you can skip scope and go straight to class methods if you wish. You're even encouraged to do so when your scope takes arguments.
Scopes are just class methods.
Internally Active Record converts a scope into a class method.
"There is no difference between them" or “it is a matter of taste”. I tend to agree with both sentences, but I’d like to show some slight differences that exist between both.
This blogs explains the difference very well.
I have a model Project that appears in multiple controllers in an application I'm building as it appears on multiple pages. The where clause for this isn't complicated, per se, but I feel like it is too large to be repeated on every method requiring projects with these constraints.
My question is, where, if possible, does this common call for Projects go? In .NET, I'd have a ProjectService class with a method that would return all projects, and another that returned all projects that satisfied my conditions. I'm new to Rails so I'm struggling to see where this fits in?
You can either use a class method or Scopes.
class Project < ActiveRecord::Base
# example for a scope
scope :awkward_projects,where(awkward: true)
# example for a class method.
def self.awkward_projects
where(awkward: true)
end
end
Its very safe to do what was once given in a SO answer. Read below and choose carefully.
Quoting an answer
"Generally, I use scope entries for simple one-liners to filter down my result set. However, if I'm doing anything complicated in a "scope" which may require detailed logic, lambdas, multiple lines, etc., I prefer to use a class method. And as you caught, if I need to return counts or anything like that, I use a class method."
I'd like to catch and remove method calls in my Rails models in certain cases.
I'd like something like the remove_method, but that removes method calls.
For example, if I have a before_save callback in a model, I may want to catch it using a module that's extended into the class, and remove or prevent the before_save from firing.
Is this possible?
Thanks.
Edit:
The pervious answer I posted does not work - not sure if it ever did, but I vaguely recall using in the past. To skip callbacks, in the context of a subclass, for example, you should use skip_callback. Example of the usage:
skip_callback :save, :before, :callback_method_name
Or if you want to skip callbacks under a for a particular condition:
skip_callback :save, :before, :callback_method_name, if: :some_condition_true?
There is also a reset_callbacks method that takes the callback type as a parameter. For example, reset_callbacks :save. The callbacks can be accessed via the class methods, _save_callbacks, _create_callbacks, etc. I would use caution when using reset_callbacks since it appears that Rails itself is defining callbacks for associations, namely, :before_save_collection_association.
If you're talking about just regular Ruby objects, you can override methods in subclasses. Just define a method with the same name as the superclass. You can also override base class methods in a subclass by including a module which defines the same instance methods as the base class.
Have you tried clearing before_save like this:
before_save ->{}
Which one would be preferable to use? Named scope or class methods? The use case is that the user selects a list box in the UI and based on the selected item, the table beneath it gets sorted. This functionality is across the application and is for many models.
The most important thing is that you do what feels right and that you're consistent with it. If something seems like it would be better in a method, use a method.
As of Rails 3, named scopes and class methods can function almost identically. However, the only "rules" I tend to follow when deciding on one or the other:
Use named scopes when an argument is NOT required
Use class methods when an argument is required
I also tend to use named scopes when I plan on chaining them together, as they tend to be short with minimal logic.
In active_record/base.rb, module ActiveRecord you can see this code:
delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete,
:delete_all, :update, :update_all, :to => :scoped
Let's take first method, so i assumed that first method delegates to scoped method and then scoped should return the first record from database. But scoped is just the anonymous scope, how the current construction is doing its job?
At the same time, how dynamic methods work, like find_by_name, find_all_by_name_and_colour?
Thanks
According to the documentation, delegate:
Provides a delegate class method to
easily expose contained objects’
methods as your own. Pass one or more
methods (specified as symbols or
strings) and the name of the target
object via the :to option (also a
symbol or string)
So this delegates the methods in the list to the scoped method, which is defined in ActiveRecord::NamedScoped::ClassMethods, and which returns an anonymous scope.
As to why ActiveRecord does this, it is so we can continue to use familiar methods such as find while behind the scenes AR is actually calling the fancy new Arel methods. For instance, when you do
Post.find(37)
What actually gets executed is:
Post.where(primary_key.eq(37))
I'll answer to your second question. find_by_name and find_all_by_what_you_want rely on ruby's precious method_missing. Whenever a method doesn't exist, the object calls method_missing, which you can overwrite. For example, I may want to overwrite method_missing, catch all non-existing method calls, check with some regex if they start/end/contain some keywords, etc.
In your example, I'll overwrite method_missing, check if it starts by find, and if yes, split on the 'and' keyword to get an array of they attributes with which I want to do my find.
Here is a pretty good example : http://technicalpickles.com/posts/using-method_missing-and-respond_to-to-create-dynamic-methods/
First: "delegate" delegates to an object, not a method - so "scope" must be some object.
Without inspecting the source to verify and just going on my working knowledge of ActiveRecord, I'm going to assume that "scope" will be the current AR instance, unless it's being called on an association proxy.
Therefore:
User.first # "scope" will be User
User.posts.first # "scope" will be the Post collection proxy
#christianblais is correct on question #2, method_missing is handling these calls. Furthermore, Rails actually defines the missing method on first call, so that subsequent calls to it do not incur the overhead of method_missing