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
Related
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.
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
This is essentially a snippet from Ruby Metaprogramming 2. In the section they gloss over this example but there isn't really an explanation.
module MyRefinement
refine MyClass do
def my_method
"refined"
end
end
end
class MyClass
def my_method
"original"
end
def another_method
my_method
end
end
using MyRefinement
obj = MyClass.new
puts obj.my_method #=> "refined"
puts obj.another_method #=> "original"
Why doesn't the refinement apply when you call my_method from another method?
It avoids "leaky" refinements, e.g., the refinement applies specifically to the method you refine.
http://yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice/
Very near the bottom this functionality is explained; nutshell:
[the] refinement should not leak [...]. If it did, it would mean that any call into any method could leak a refinement into that method, which is the opposite of the purpose of the feature.
refine keyword use to Refinements of the class locally. It's mean we can monkey patch any method by refinement of the class.
In your case, the process of refine/redfined/monkey patch only active when the method get call directly. Also Refinements are lexical in scope. When control is transferred outside the scope the refinement is deactivated.
To get better insight, read the scope part of refinements from here: Refinements
In the code below, method roar is not defined in class Lion, but still can be called using method_missing.
class Lion
def method_missing(name, *args)
puts "Lion will #{name}: #{args[0]}"
end
end
lion = Lion.new
lion.roar("ROAR!!!") # => Lion will roar: ROAR!!!
In which situations and how should I use this method_missing? And is it safe to use?
It's entirely safe to use provided you use it in expected ways and don't get carried away. Not everything you can do is worth doing, after all.
The advantage of method_missing is you can respond to all kinds of things in unique ways.
The disadvantage is you don't advertise your capabilities. Other objects that expect you to respond_to? something will not get confirmation and might treat your custom object in ways you don't intend.
For building Domain Specific Languages and providing very loose glue between components, this sort of thing is invaluable.
A great example of where this is a good fit is the Ruby OpenStruct class.
Summary: When to use? When it will make your life easier and not complicate others' lives.
Here's one example that pops to mind. It's from redis_failover gem.
# Dispatches redis operations to master/slaves.
def method_missing(method, *args, &block)
if redis_operation?(method)
dispatch(method, *args, &block)
else
super
end
end
Here we check if the method called is actually a command of redis connection. If so, we delegate it to underlying connection(s). If not, relay to super.
Another famous example of method_missing application is ActiveRecord finders.
User.find_by_email_and_age('me#example.com', 20)
There's not, of course, a method find_by_email_and_age. Instead, the method_missing breaks the name, analyzes the parts and invokes find with proper parameters.
Here's a favorite of mine
class Hash
def method_missing(sym,*args)
fetch(sym){fetch(sym.to_s){super}}
end
end
Which lets me access values of a hash as if they were attributes. This is particular handy when working with JSON data.
So for example, rather than having to write tweets.collect{|each|each['text']} I can just write tweets.collect(&:text) which is much shorter. Or also, rather than tweets.first['author'] I can just write tweets.first.author which feels much more natural. Actually, it gives you Javascript-style access to values of a hash.
NB, I'm expecting the monkey patching police at my door any minuteā¦
First and foremost, stick to Sergio Tulentsev's summary.
Apart from that, I think looking at examples is the best way to get a feeling for right and wrong situations for method_missing; so here is another simple example:
I recently made use of method_missing in a Null Object.
The Null Object was a replacement for a Order model.
The Order stores different prices for different currencies.
Without method_missing it looks like this:
class NullOrder
def price_euro
0.0
end
def price_usd
0.0
end
# ...
# repeat for all other currencies
end
With method_missing, I can shorten it to:
class NullOrder
def method_missing(m, *args, &block)
m.to_s =~ /price_/ ? 0.0 : super
end
end
With the added benefit of not having to (remember to) update the NullOrder when I add new price_xxx attributes to Order.
I also found a blog post from (Paolo Perrotta) where it demonstrated when to use method_missing:
class InformationDesk
def emergency
# Call emergency...
"emergency() called"
end
def flights
# Provide flight information...
"flights() called"
end
# ...even more methods
end
Check if a service has been asked during lunch time.
class DoNotDisturb
def initialize
#desk = InformationDesk.new
end
def method_missing(name, *args)
unless name.to_s == "emergency"
hour = Time.now.hour
raise "Out for lunch" if hour >= 12 && hour < 14
end
#desk.send(name, *args)
end
end
# At 12:30...
DoNotDisturb.new.emergency # => "emergency() called"
DoNotDisturb.new.flights # ~> -:37:in `method_missing': Out for lunch (RuntimeError)
When it comes to run time introspection and dynamic code generation I don't think ruby has any rivals except possibly for some lisp dialects. The other day I was doing some code exercise to explore ruby's dynamic facilities and I started to wonder about ways of adding methods to existing objects. Here are 3 ways I could think of:
obj = Object.new
# add a method directly
def obj.new_method
...
end
# add a method indirectly with the singleton class
class << obj
def new_method
...
end
end
# add a method by opening up the class
obj.class.class_eval do
def new_method
...
end
end
This is just the tip of the iceberg because I still haven't explored various combinations of instance_eval, module_eval and define_method. Is there an online/offline resource where I can find out more about such dynamic tricks?
Ruby Metaprogramming seems to be a good resource. (And, linked from there, The Book of Ruby.)
If obj has a superclass, you can add methods to obj from the superclass using define_method (API) as you mentioned. If you ever look at the Rails source code, you'll notice that they do this quite a bit.
Also while this isn't exactly what you're asking for, you can easily give the impression of creating an almost infinite number of methods dynamically by using method_missing:
def method_missing(name, *args)
string_name = name.to_s
return super unless string_name =~ /^expected_\w+/
# otherwise do something as if you have a method called expected_name
end
Adding that to your class will allow it to respond to any method call which looks like
#instance.expected_something
I like the book Metaprogramming Ruby which is published by the publishers of the pickaxe book.