Last active
December 28, 2015 22:19
-
-
Save mvoto/7571212 to your computer and use it in GitHub Desktop.
This is an initial draft for a blog post
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Metaprogramming is not a monster | |
- Intro: | |
Every ruby developer should have heard about this topic and 8 from 10 just get scared, probably. | |
We know that "procs", "lambdas" and "yield" are metaprogramming magic, but there is much more about this 'magic self generating code' land. | |
I don't want to dive deep into this topic here, just going to talk a little about a daily experience that I've faced and could learn a lot and | |
be satisfied with metaprogramming, and yes, I figured out that metaprogrammin is not a monster. | |
- The problem: | |
We are currently keeping registers on database instead of destroying it, but not all the models and the project also has some dependent destroy associations that behaves this way. | |
So the problem was: after objects deletion, we needed to display on a few places those deleted information, as associated data of an existing object. | |
To make things clear, let's talk about project modelling: | |
Order has one delivery_location | |
DeliveryLocation is a non-deletable model | |
Here is the situation: | |
1 - Someone deletes a delivery location from an order | |
2 - We need to display info just like this: order.delivery_location.address.street | |
3 - The order is an existing object and its delivery location is hidden. | |
- The solution: | |
We should use a simple scope method to fetch deleted(hidden) data, but we need to get the object associated hidden data. | |
Then I heard from Leo(the ruby expert guy): why don't we use metaprogramming to solve that ? And he sent me a good draft about a possible solution. | |
His idea was: to create a concern called DeletableAssociations which we wanted to use on this elegant way inside models: include_deleted :delivery_location, :other_associated_deleted. | |
So we created this concern with include_deleted as a ClassMethod, receiving 'associated' as params, i.e.: :delivery_location, :other_associated_deleted. | |
As first step we had to figure out which kind of assocation we are handling, we used the AR method: reflect_on_association(http://apidock.com/rails/ActiveRecord/Reflection/ClassMethods/reflect_on_association) for that. | |
Right after we wanted to add an instance method on the receiver, passing a block to the ruby define_method(http://apidock.com/ruby/Module/define_method). On our case we were using | |
this concern for the Order model. | |
Then we needed to make the block that fetches the deleted data to be self executed on the receiver, for that we used the instance_exec(http://apidock.com/ruby/Object/instance_exec) | |
Show me the code: | |
def include_deleted(*args) | |
args.each do |method_name| | |
association = reflect_on_association(method_name).macro | |
define_method method_name do | |
instance_exec method_name do | |
if association == :has_many | |
self.new_record? ? super() : super().with_deleted | |
else | |
klass = method_name.to_s.camelize.constantize | |
self.new_record? ? super() : klass.unscoped { super() } | |
end | |
end | |
end | |
end | |
end | |
These referenced methods(except relfect_on_association) are pure metaprogramming, it is beautiful when you realize that you wrote code that make things simple and you used | |
a little portion of ruby language power. So we avoid using lots of repeated code on various models, now we just add a include_deleted and send associations as params. | |
The combo define_method and instance_exec gives us the power to execute some block of code everytime that an object instance receives a method, that is fantastic. | |
Hope that you loose your fears and be curious to use and study more about metaprogramming, I will keep on it. | |
- Conclusion: | |
1 - Understand your problem / try to imagine the beautiful and DRYiest solution | |
2 - Be brave and study ruby language, use the documentation | |
3 - Listen to ruby experts(better if you have some of them in your team, I am lucky with that) | |
4 - Don't give up until you get done with metaprogramming | |
Special thanks to Leo, who contributed a lot on this solution. | |
https://docs.google.com/document/d/1CO8m71wiMr7Ny8MCZu_VMK4FZPCgEb8kxhNuFRObCtw/edit?pli=1 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment