I'm pondering a way to wrap method calls on models in RoR. (The reason for this being a custom model caching system that would allow me to do a lot of work behind the scenes on data being created by some large models that my clients are using without having to constantly load those models and parse ALL the very fat data to get the output. I could just send the output already calculated and stored in a different place and a delayed job behind the scenes that does the calculation and stores it isn't really an option due to how the data is collected or the egregious volume in question.. Its hard to explain.)
So given that I have an instance of this extremely simple class;
class Person < ApplicationRecord
attr_accessor: :name
has_many :books
has_many :friends
end
How would I go about writing something that allows me to programmatically "intercept" ALL (or all except those I specifically say not to) method calls to a given class.
So if someone does this;
bob = Person.find 1
puts bob.name
puts bob.friends.count
I could have this system know to do something before .name
is called or before .friends
is called. Something like checking to see if we already have an answer to this question somewhere else..
I was looking at using a before_hook
and override the method_added
hook to prepend my code per this question: call before methods in model on ruby but the issue was it works for .name
but not for .friends
. I assume this is due to some rails magic as friends just gives you the active record call per normal without actually executing the hook..
I'd love to be able to do something like;
class Person < ApplicationRecord
include CustomCachableThingy
before_hook :friends, :name, except: :books
attr_accessor: :name
has_many :books
has_many :friends
end
Again this is a very simple example. The models in question are way too big and have way too much data. So trying to find something I can make work for my client.
Thoughts?
If you really want to wrap every method call (or even the ones defined by the target class itself), you can define a module that does 2 things,
method_added
method call and wrap every new method that is added with the same custom codeA good example using this strategy is answered here. You can customize this to take additional except
configuration and exclude those methods.
The meta-programming going on here is to rewrite each method as it is injected into the instance.
Another option is to wrap only specific method calls with your custom code, if you know which ones you'll need to cache. It will be less overhead and cleaner implementation.
For implementation, you can use the same meta-programming approach as described in the first example. One more approach can be to add an alias to the original method and rewrite the method to include your hook. Something like,
def add_before_hook(method_name)
class_eval do
without = :"#{method_name}_without_before_each_method"
alias_method without, method_name
define_method method_name do |*args, &block|
puts 'called before'
send without, *args, &block
end
end
end
The problem with the second approach is that it pollutes your method namespace by adding a duplicate method for each of your instance methods.