Ruby metaprogramming: creating your own dynamic methods and ruby programming Dynamics
Method_missing is a common method of Ruby metaprogramming. The basic idea is to call a non-existent Method for callback. A typical example is the dynamic query of ActiveRecord (dynamic finder ). For example, if you have the email attribute, you can call User. find_by_email ('Joe @ example.com '). Although ActiveRecord: Base does not have a method called find_by_email.
Respond_to? Not like method_missing, it is often used when you need to confirm a feedback object, so that there is no feedback object, resulting in subsequent call errors.
The following is an example of an application:
Example
We have a Legislator class. Now, we want to add a dynamic call of find_by_first_name ('john') to it. Implement the find (: first_name => 'john') function.
Copy codeThe Code is as follows:
Class Legislator
# Assume this is a real implementation
Def find (conditions = {})
End
# This is his method after being defined
Def self. method_missing (method_sym, * arguments, & block)
# The first argument is a Symbol, so you need to_s it if you want to pattern match
If method_sym.to_s = ~ /^ Find_by _ (. *) $/
Find ($1. to_sym => arguments. first)
Else
Super
End
End
End
Call
Copy codeThe Code is as follows:
Legislator. respond_to? (: Find_by_first_name)
An error will be prompted, so proceed
Copy codeThe Code is as follows:
Class Legislator
# Omitted
# It's important to know Object defines respond_to take two parameters: the method to check, and whether to include private methods
# Http://www.ruby-doc.org/core/classes/Object.html#M000333
Def self. respond_to? (Method_sym, include_private = false)
If method_sym.to_s = ~ /^ Find_by _ (. *) $/
True
Else
Super
End
End
End
As described in the code comment, respond_to? Two parameters are required. If not provided, ArgumentError is generated.
Related reflection DRY
If we notice that there are repeated codes. We can refer to the implementation of ActiveRecord encapsulated in ActiveRecord: DynamicFinderMatch to avoid method_missing and respond_to? .
Copy codeThe Code is as follows:
Class LegislatorDynamicFinderMatch
Attr_accessor: attribute
Def initialize (method_sym)
If method_sym.to_s = ~ /^ Find_by _ (. *) $/
@ Attribute = $1. to_sym
End
End
Def match?
@ Attribute! = Nil
End
End
Class Legislator
Def self. method_missing (method_sym, * arguments, & block)
Match = LegislatorDynamicFinderMatch. new (method_sym)
If match. match?
Find (match. attribute => arguments. first)
Else
Super
End
End
Def self. respond_to? (Method_sym, include_private = false)
If LegislatorDynamicFinderMatch. new (method_sym). match?
True
Else
Super
End
End
End
Cache method_missing
You can consider caching the repeated method_missing.
Another thing we can learn from ActiveRecord is to send the now-defined method when defining method_missing. As follows:
Copy codeThe Code is as follows:
Class Legislator
Def self. method_missing (method_sym, * arguments, & block)
Match = LegislatorDynamicFinderMatch. new (method_sym)
If match. match?
Define_dynamic_finder (method_sym, match. attribute)
Send (method_sym, arguments. first)
Else
Super
End
End
Protected
Def self. define_dynamic_finder (finder, attribute)
Class_eval <-RUBY
Def self. # {finder} (# {attribute}) # def self. find_by_first_name (first_name)
Find (: # {attribute }=>#{ attribute}) # find (: first_name => first_name)
End # end
RUBY
End
End
Test
The test section is as follows:
Copy codeThe Code is as follows:
Describe LegislatorDynamicFinderMatch do
Describe 'Find _ by_first_name 'do
Before do
@ Match = LegislatorDynamicFinderMatch. new (: find_by_first_name)
End
It 'should have attribute: first_name 'do
@ Match. attribute. shocould =: first_name
End
It 'should be a Match' do
@ Match. shoshould be_a_match
End
End
Describe 'zomg 'do
Before do
@ Match = LegislatorDynamicFinderMatch (: zomg)
End
It 'should have nil attribute' do
@ Match. attribute. shoshould be_nil
End
It 'should not be a Match' do
@ Match. should_not be_a_match
End
End
End
The following is an example of RSpec:
Copy codeThe Code is as follows:
Describe Legislator, 'dynamic find_by_first_name 'do
It 'could call find (: first_name => first_name) 'do
Legislator. should_receive (: find). with (: first_name => 'john ')
Legislator. find_by_first_name ('john ')
End
End