Understanding delegate and scoped methods in Rails - ruby-on-rails

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

Related

Rails params "method" isn't a method, is it?

In the Rails documentation, the following example is given as a way to display what the server receives from a POST request:
def create
render plain: params[:article].inspect
end
In the subsequent description, the text states
The params method is the object which represents the parameters (or fields) coming in from the form. The params method returns an ActiveSupport::HashWithIndifferentAccess object.
While I understand that all methods are objects, I don't understand how it's correct to refer to the params object as a method. Specifically, the phrase "returns an ActiveSupport::HashWithIndifferentAccess object" suggests to me that there are two calls going on--what in python might look like:
params().__getitem__('article')
but I don't think that's what's actually going on.
The conversation around those lines also refers to params as a method, so I'm starting to think I must be missing something.
I'm new to Ruby, and while I understand that all methods are objects,
No, they aren't. Methods belong to objects (more precisely: methods are defined in modules, and executed in the context of objects), but they are not, by themselves, objects. (It is, however, possible to obtain a reflective proxy which represents the concept of a method by calling the method method, which returns a Method object.)
I don't understand how it's correct to refer to the params object as a method.
Because it is a method. Not an object.
What else would it be? Syntactically, it's obvious that it can only be one of three things: a keyword, a variable, or a method call.
It can't be a keyword, because Rails is just a Ruby library, and Ruby libraries can't change the syntax of the language (e.g. add or remove keywords). It can't be a variable, because in order for it to be parsed as a variable, the parser would need to have seen an assignment to it within the same block.
Ergo, the only thing it can possibly be, is a method call. You don't even need to know anything about Rails to know this. It's just basic Ruby syntax 101.
Specifically, the phrase "returns an ActiveSupport::HashWithIndifferentAccess object" suggests to me that there are two calls going on--what in python might look like:
params().__getitem__('article')
but I don't think that's what's actually going on.
That is exactly what is going on. You call the method params and then you call the method [] on the object that is returned by calling the method params.
This is in no way different from foo.bar: you call foo, then call bar on the return value of foo.
The params method is a method, returns a hash (which holds some details about parameters send to the app). Simplified it looks like this:
def fake_params
{ :controller => 'foo', :action => 'bar' }
end
You can call another method directly on the returned hash like this:
fake_params[:action] #=> 'bar'
params is a method defined in ActionController::Metal which returns the request.parameters object.
https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal.rb#L140

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?
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.

Passing arguments to nested to_json call

Consider a controller action that returns the following:
Post.includes(:comments).to_json(:include => [:comments])
Is it somehow possible to pass arguments to the to_json comments method call so that I could modify comments json representation in this place rather than doing it in Comment model?
to_json only accepts a list of options, per the documentation... If you absolutely had to, you could monkey-patch to_json to do whatever work you needed to do, then call super to execute the default to_json, but I wouldn't recommend this.
In your situation, based on what I understand you're describing, it's probably best to simply do it either on the Comment model, or as a private method on the controller.

Is there a way to remove method calls in Rails?

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 ->{}

What type of language construct is Rails' "validates"?

I'm just starting to grok Ruby. My background is in .NET and PHP. In Rails, and I'm sure in other frameworks as well, I see stuff like this on classes:
class Person < ActiveRecord::Base
validates :terms_of_service, :acceptance => true
end
What exactly is "validates"? Is it a function? If it's a function, how does the validation actually work since you don't tell the validates function which model you are validating?
Where can I read more about how this actually works behind the scenes?
It's... a little complicated - but the short answer is that validates is a class method of Person, inherited from ActiveRecord::Base. That line could equally have been written validates(:terms_of_service, :acceptance => true).
Ruby, like a lot of interpreted languages, effectively "executes" class definitions, so when it encounters the validates line, it sees it as a method call where the current self object is the instance of the Class class representing the class Person, inheriting from ActiveRecord::Base. It calls the method, which has the effect of hooking a validator into the Person class.
You can read about the method here - but do note that that adds more confusion, since it lists the method as an instance method of ActiveModel::Validations::ClassMethods. Huh? Well, Ruby has two ways of taking functionality from another Module and putting it into your class - you can either include the module (in which case its instance methods become instance methods of your class), or extend the module (in which case its instance methods become class methods of your class).
So, to summarise: validates is declared as an instance method of ActiveModel::Validations::ClassMethods, which is extended into ActiveRecord::Base. Therefore, validates is a class method of ActiveRecord::Base and, by inheritance, Person. The line in your code snippet is just a method call.
However, having said all that, most Rubyists and Railsists will largely ignore those facts; validates is what's called a "decorator", and most people will simply read it as a statement about Person.
It's a special callback method that gets called to validate data before it's saved to the database.
A callback, more generally, is a method that "hooks into" an object or code module/library in such a way that the method gets called automatically when certain events occur. In this case, the event that calls the set of validation methods is the act of trying to save something to the database. This is useful because, before you write new or updated information to your database, you want to make sure it's valid, in order to prevent bad data from leaking into your application.
The following methods trigger validations, and will save the object to the database only if all validations pass (in the literal sense, this means all the validation methods must return a value that is not false, or nil, etc., otherwise validation is considered to have failed):
create
create!
save
save!
update
update_attributes
update_attributes!
"Methods" in Ruby are very similar to the concept of functions in other languages. In many cases you can think of them interchangeably - they declare some functionality, they take parameters as input, and they return a result as output.
More detail on validations: http://guides.rubyonrails.org/active_record_validations_callbacks.html
Ruby (and Rails) use "callbacks" in many different situations - pretty much anytime you want some method to be called as a result of some event occuring.
More reading on callbacks: http://www.khelll.com/blog/ruby/ruby-callbacks/

Resources