Could someone help me understand what alias_method does in this code - ruby

I'm having trouble understanding the purpose of alias_method in this code
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
alias_method :configure_connection_without_interval, :configure_connection
define_method :configure_connection do
configure_connection_without_interval
execute('SET intervalstyle = iso_8601', 'SCHEMA')
end
end
What is the purpose of line 4 where they call configure_connection_without_interval -- doesn't that just call itself?
The code below works for me but I don't fully know what I'm doing and I'm worried it'll create bugs later
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
define_method :configure_connection do
execute('SET intervalstyle = iso_8601', 'SCHEMA')
end
end

alias_method operates immediately on that line before the method is redefined, preserving the old method under that given name.
That's a common Ruby technique to capture a version of a particular method and wrap it in another. When you're patching a class directly instead of subclassing you'll often be forced to do this.

Related

ActiveRecord: override attribute writers by using a class method

I don't know how to correctly phrase the title, I think the best way to explain this issue is just with code samples.
My goal
I want to define a meta method like this (in Rails 5):
class Post < ApplicationRecord
override_this_attribute_writer :some_attribute
end
The override_this_attribute_writer follows a common pattern, it overrides the original writer by doing some filtering on top of it. I find this way of overriding very convenient and clear.
First approach
module MyCommonModule
extend ActiveSupport::Concern
module ClassMethods
def override_this_attribute_writer(attribute_name)
alias_method :"#{attribute_name}_old=", :"#{attribute_name}="
define_method :"#{attribute_name}=" do |a_value|
# Do my stuff
send(:"#{attribute_name}_old=", a_value)
end
end
end
When doing this, I was getting an exception at the call of alias_method, because, apparently, the method I was trying to copy didn't exist (yet).
Second approach
module MyCommonModule
extend ActiveSupport::Concern
module ClassMethods
def override_this_attribute_writer(attribute_name)
define_method :"#{attribute_name}=" do |a_value|
# Do my stuff
send(:write_attribute, attribute_name, a_value)
end
end
end
I was expecting this not to work: if, when running the meta method, ActiveRecord hasn't created the attribute writer yet, this means that it will do it later and override the method that I just defined.
But surprisingly it worked! So I put my hands inside ActiveRecord (5.1.5) to find out more.
Dig into ActiveRecord 5.1.5
I wanted to ensure that what I did was safe and it wasn't just working by accident: I looked into the definition of method writer, and put binding.pry around the method.
This is the result of the experiment:
For attributes that I did not override,
This line is called
Then the method is defined inside this module eval call
Finally, the newly created writer method is correctly called when performing object.attribute=
For attributes that I DID override,
My own method is defined before anything else (when the ActiveRecord writers aren't there yet
Then ActiveRecord calls the same line that handles writer creation, as in the previous example
The method gets (apparently) correctly created by ActiveRecord, since it passes again by this point
But now, surprisingly, when calling object.attribute= my own method is still called in place of the ActiveRecord one
So, this is what I don't understand: if ActiveRecord seems to be overriding my method but it doesn't, what prevents it from doing it?
My questions
What in the end I need to know is whether the fix I have done is actually a good practice (and robust) or it's at risk and it might break if in the future we do upgrades.
If you think that my fix is dangerous, would you be able to suggest a different way to achieve the same goal?
Calling super is even more idiomatic:
module MyCommonModule
extend ActiveSupport::Concern
module ClassMethods
def override_this_attribute_writer(attribute_name)
define_method :"#{attribute_name}=" do |value|
# do some stuff
super value
end
end
end
end

Behavior of `super`

I have this code:
class B
def self.definer(name, *args, &block)
define_method(name) { self.instance_exec(*args, &block) }
end
end
and when I try to use it, I get this error:
B.definer(:tst) { super }
# => :tst
B.new.tst
# => TypeError: self has wrong type to call super in this context: B (expected #<Class:#<Object:0x007fd3008123f8>>)
I understand that super has a special meaning, and works little different from calling a method. Can someone explain why and what is happening? It would also be great if someone suggests a solution for this.
I don't get the same error message as you did, but get an error anyway. super must be used within a method definition. But you are not using it in a method definition. That raises an error.
Regarding the solution, I cannot give you one since it is not clear at all what you are trying to do.
You definitely don't want instance_exec there.
If you didn't have the *args involved, I'd say you just wanted this:
def self.definer(name, &block)
define_method(name, &block)
end
But then your new definer method would do the exact same thing that define_method does in the first place, so there's be no reason to create it, instead of just using define_method in the first place.
What are you actually trying to do? Explain what you want to do, and maybe someone can help you.
But I think the instance_exec in your existing implementation isn't what you want -- it is immediately executing the block upon definer call, when calling define_method -- I think you want the block executed when the method you are defining is being called instead? But I'm not really sure, it depends on what you're trying to do, which is unclear. super doesn't really make any sense within an instance_exec -- super to what method did you think you'd be calling?

refinement using a previously defined refinement in the same file: possible?

Sadly, the below does not work. Time is not able to respond_to(:format_to_my_datetime). So my assumption is that refinements in the same file can't use each other. Instead where I define my ActiveSupport refinement, I would've needed within that file to use using TimeFormatter to refine Time in the context of that method.
But am I missing anything? Is there any way I can do this all in one file, besides just reimplementing format_to_my_datetime(timezone) on ActiveSupport?
module TimeFormatter
include MyFormats
refine String do
def format_to_my_name
self.gsub(MY_NAME_FORMAT,"")
end
end
refine Time do
def format_to_my_datetime(timezone)
self.getlocal(timezone).strftime(MY_DATETIME_FORMAT)
end
end
refine ActiveSupport::TimeWithZone do
def method_missing(method, *args)
if Time.respond_to?(method)
self.to_time.send(:method, *args)
end
end
end
end
Found my answer here: Why does `send` fail with Ruby 2.0 refinement?
Turns out that indirect method access is specifically revoked for Refinements, which is a huge bummer to me:
== Indirect method accesses
Any indirect method access such as Kernel#send, Kernel#method, and
Kernel#respond_to? shall not honor refinements in the caller context
during method lookup.
NOTE: This behavior will be changed in the future.
=end

Is it possible to access instance values within a methods missing function?

I was trying to write my first method_missing override when I kept running into (edited) stack level too deep errors. The main culprit seemed to be trying to utilize an instance attribute. For instance if 'self' was a instance of the User class then checking for something like:
def method_missing(name)
if self.name
# do stuff
end
end
Would seg fault. I spent a long time on this but ended up giving up. There must be something I'm not understanding about accessing it.
Edit
My apologies, Andrew is correct, I am getting Stack Level too deep errors. With this in mind, what is the appropriate (if any) way to access the instances attribute values?
You can potentially rectify this problem by ensuring that self.name actually exists:
def method_missing(name)
if self.respond_to?(:name) && self.name
# do stuff
end
end
Note this may not work if your class inherits from anything Railsy (e.g. ActiveRecord::Base), since it overrides respond_to?.
If you are in a Railsy class, your method missing should call super, lest you lose a lot of the "magic" ActiveRecord methods (including, probably, self.name itself):
def method_missing(name, *args, &block)
if name_is_something_i_should_handle_here
# do your stuff
else
super(name, *args, block) # call parent's method_missing
end
end
Obviously you should replace name_is_something_i_should_handle_here with the appropriate logic.
You may also wish to consider using dynamic method creation instead of method_missing.

When do you use method aliasing?

Do you use the alias method in order to add more ways to call methods (like length and size) or is there another use for it?
The alias_method call is also useful for re-implementing something but preserving the original version. There's also alias_method_chain from Rails which makes that kind of thing even easier.
alias_method also comes in handy when you have a number of behaviors that are initially identical but might diverge in the future, where you can at least rough them in to start.
def handle_default_situation
nil
end
%w[ poll push foo ].each do |type|
alias_method :"handle_#{type}_situation", :handle_default_situation
end
Yes.
It is often used to preserve a handle to existing methods before overriding them. (contrived example)
Given a class like this:
class Foo
def do_something
puts "something"
end
end
You could see code that adds new behaviour like so:
class Foo
def do_something_with_logging
puts "started doing something"
do_something_without_logging # call original implementation
puts "stopped doing something"
end
alias_method :do_something_without_logging, :do_something
alias_method :do_something, :do_something_with_logging
end
(this is exactly how alias_method_chain works)
However, for this use case it't often more appropriate to use inheritance and modules to your advantage.
Still, alias_method is a useful tool to have, if you absolutely need to redefine behaviour in an existing class (or if you wanted to implement something like alias_method_chain)

Resources