Metaprogramming in Rails
Meta-programming means your programming language is writing code for you. And for sure Ruby is a great language having all cool features.Rails being a great ruby framework encashes all features of ruby. Rails itself uses meta-programming every where.
For example in ActiveRecord, we have dynamic method on attributes name like
1 2 3 | find_by_something(some_possible_values) find_all_by_something(spv) find_by_something_and_otherthing(some_value, other_value) |
In this topic we will see how we can meta-program in Rails. Using Meta Programming one can reduce LOC to dramatic level and it also reduce headache in maintaining code base.
Here I am not going into details for class_eval and instanse_eval because there are many blog post out there. But I do give some examples.
Consider following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Testt end Testt.class_eval do def foo "I'll be available as instance method" end def self.foo "I'll be available as class method" end end Testt.new().foo => I'll be available as instance method Testt.foo =>I be available as class method |
Let me explain that class_eval evaluates a string or code block in context of class or module it is called upon.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Testt.instance_eval do def foo "I'll be available as class method" end def self.bar "I'll be available as class method" end end Testt.foo =>I'll be available as class method Testt.bar =>I'll be available as class method |
instance_eval can evaluate a string or a code block in the context of the receiver.
The third way to define a method in class or module is define_method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Testt define_method :foo do "welcome" end end Testt.new().foo => welcome module Hello define_method :foo do |name| "welcome #{name}" end end include Hello foo "Dave" => welcome Dave |
Now we can spice our project with metaprogramming. See how?
Suppose in our project we had a model to maintain users
1 2 3 4 | class User < ActiveRecord::Base #user has one role so association is has_one :role end |
We also have a Role model that belongs to user. Schema for role is suppose
1 | Role(id: integer, name: string, user_id: integer) |
At the initiation of project the role known were admin and user. So I have defined two method in User model named as is_admin? and is_user?. But after some time we need to add some other roles as we need to define same function for them as well.
So to get rid of this we flavoured our project with bits of metaprogramming. See here how
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class User < ActiveRecord::Base #user has one role so association is has_one :role #First time when there is no method found; this method will catch that def self.method_missing(method_sym, *params) #Here we match the pattern of method and see if we can process it or not if method_sym.to_s =~ /^is_(.*)\?$/ function = $1 class_eval <<-END def method_sym.to_s "#{role.name == function.to_s.upcase}" end END #Let's register this method with user class when it is invoked first time. send(method_sym) else super end end #Let this class respond for these methods def self.respond_to?(method_sym, *args, &block) method_sym.to_s =~ /^is_(.*)\?$/ ? true : super end end |