method_missing_router 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/Guardfile +9 -0
- data/README.md +64 -0
- data/Rakefile +1 -0
- data/examples/example_spec.rb +49 -0
- data/lib/method_missing_router/version.rb +3 -0
- data/lib/method_missing_router.rb +110 -0
- data/method_missing_router.gemspec +26 -0
- data/spec/lib/method_missing_router_spec.rb +118 -0
- data/spec/spec_helper.rb +1 -0
- metadata +98 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm ruby-1.9.2@method_missing_router --create
|
data/Gemfile
ADDED
data/Guardfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# MethodMissingRouter
|
2
|
+
MethodMissingRouter provides a DSL for dynamic methods backed by method\_missing. You define a regular expression => method route and MethodMissingRouter spits out the tedious boilerplate code for implementing method_missing and respond_to_missing?
|
3
|
+
|
4
|
+
## Basic usage
|
5
|
+
|
6
|
+
To route class methods use
|
7
|
+
class_route_missing /SOME_REGEX/, options
|
8
|
+
|
9
|
+
To route instance methods use
|
10
|
+
route_missing /SOME_REGEX/, options
|
11
|
+
|
12
|
+
## Working With Regex Matches
|
13
|
+
|
14
|
+
You can configure the router to pass your regex matches to the target method by setting ":pass_match" or ":pass_matches" in the options hash
|
15
|
+
|
16
|
+
## Examples (from /examples/example_spec.rb)
|
17
|
+
|
18
|
+
describe "an example of instance routing with multiple matches" do
|
19
|
+
class SpaceShip
|
20
|
+
include MethodMissingRouter
|
21
|
+
route_missing /^fire\_the\_(.+)_and_(.+)/, :fire_weapon, :pass_matches => true
|
22
|
+
|
23
|
+
def fire_weapon(weapon_names, count)
|
24
|
+
"firing #{count} #{weapon_names.first} and #{weapon_names.last}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'passes along the name of the weapon since we set pass matches' do
|
29
|
+
ship = SpaceShip.new
|
30
|
+
ship.fire_the_missiles_and_bacon(5).should == "firing 5 missiles and bacon"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
describe "an example of class routing with a single match" do
|
36
|
+
class LetterCounter
|
37
|
+
include MethodMissingRouter
|
38
|
+
class_route_missing /([a-z])s/, :count_letter, :pass_match => true
|
39
|
+
|
40
|
+
def self.count_letter(letter, string)
|
41
|
+
string.count(letter)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'passes along the letter since we set pass match' do
|
46
|
+
LetterCounter.as('a string and another string').should == 3
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
describe "an example of instance routing that doesn't override the message" do
|
52
|
+
class Turtle
|
53
|
+
include MethodMissingRouter
|
54
|
+
route_missing /some_message/, :announce
|
55
|
+
|
56
|
+
def announce(message)
|
57
|
+
"message was #{message}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'passes along the original method name since we did not ask to pass the match(es)' do
|
62
|
+
Turtle.new.some_message_fred.should == "message was some_message_fred"
|
63
|
+
end
|
64
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "an example of instance routing with multiple matches" do
|
4
|
+
class SpaceShip
|
5
|
+
include MethodMissingRouter
|
6
|
+
route_missing /^fire\_the\_(.+)_and_(.+)/, :fire_weapon, :pass_matches => true
|
7
|
+
|
8
|
+
def fire_weapon(weapon_names, count)
|
9
|
+
"firing #{count} #{weapon_names.first} and #{weapon_names.last}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'passes along the name of the weapon since we set pass matches' do
|
14
|
+
ship = SpaceShip.new
|
15
|
+
ship.fire_the_missiles_and_bacon(5).should == "firing 5 missiles and bacon"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
describe "an example of class routing with a single match" do
|
21
|
+
class LetterCounter
|
22
|
+
include MethodMissingRouter
|
23
|
+
class_route_missing /([a-z])s/, :count_letter, :pass_match => true
|
24
|
+
|
25
|
+
def self.count_letter(letter, string)
|
26
|
+
string.count(letter)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'passes along the letter since we set pass match' do
|
31
|
+
LetterCounter.as('a string and another string').should == 3
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
describe "an example of instance routing that doesn't override the message" do
|
37
|
+
class Turtle
|
38
|
+
include MethodMissingRouter
|
39
|
+
route_missing /some_message/, :announce
|
40
|
+
|
41
|
+
def announce(message)
|
42
|
+
"message was #{message}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'passes along the original method name since we did not ask to pass the match(es)' do
|
47
|
+
Turtle.new.some_message_fred.should == "message was some_message_fred"
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require "method_missing_router/version"
|
2
|
+
|
3
|
+
module MethodMissingRouter
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(RoutedMethodMissing)
|
6
|
+
base.send(:include, RoutedMethodMissing)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
base.reset_method_missing_routes!
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
module RoutedMethodMissing
|
13
|
+
def respond_to_missing?(method_name, *)
|
14
|
+
method_missing_routes.route_for(method_name) || super
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def method_missing(method_name, *args, &block)
|
20
|
+
if route = method_missing_routes.route_for(method_name)
|
21
|
+
call_info = CallInfo.new(self, route.target, *args, &block)
|
22
|
+
call_info.prepend_argument(route.message_for(method_name))
|
23
|
+
return call_info.call
|
24
|
+
else
|
25
|
+
super(method_name, *args, &block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_missing_routes
|
30
|
+
if self.kind_of?(Class)
|
31
|
+
self.instance_variable_get("@class_method_missing_routes")
|
32
|
+
else
|
33
|
+
self.class.instance_variable_get("@method_missing_routes")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
module ClassMethods
|
40
|
+
def class_route_missing(matcher, target, options={})
|
41
|
+
@class_method_missing_routes << Route.new(matcher, target, options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def route_missing(matcher, target, options={})
|
45
|
+
@method_missing_routes << Route.new(matcher, target, options)
|
46
|
+
end
|
47
|
+
|
48
|
+
def reset_method_missing_routes!
|
49
|
+
@method_missing_routes = RouteCollection.new
|
50
|
+
@class_method_missing_routes = RouteCollection.new
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class RouteCollection
|
55
|
+
def initialize
|
56
|
+
@routes = []
|
57
|
+
end
|
58
|
+
|
59
|
+
def <<(item)
|
60
|
+
@routes << item
|
61
|
+
end
|
62
|
+
|
63
|
+
def route_for(method_name)
|
64
|
+
@routes.find{ |route | route.applies_to?(method_name) }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Route
|
69
|
+
attr_reader :target
|
70
|
+
|
71
|
+
def initialize(regex, target, options)
|
72
|
+
@regex = regex
|
73
|
+
@target = target
|
74
|
+
@options = options
|
75
|
+
end
|
76
|
+
|
77
|
+
def applies_to?(method_name)
|
78
|
+
@regex =~ method_name
|
79
|
+
end
|
80
|
+
|
81
|
+
def message_for(method_name)
|
82
|
+
captures = @regex.match(method_name).captures
|
83
|
+
if @options[:pass_matches]
|
84
|
+
return captures
|
85
|
+
elsif @options[:pass_match]
|
86
|
+
return captures.first
|
87
|
+
else
|
88
|
+
return method_name.to_s
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class CallInfo
|
94
|
+
attr_accessor :object, :method_name, :args, :block
|
95
|
+
def initialize(object, method_name, *args, &block)
|
96
|
+
@object = object
|
97
|
+
@method_name = method_name
|
98
|
+
@args = args
|
99
|
+
@block = block
|
100
|
+
end
|
101
|
+
|
102
|
+
def prepend_argument(new_arg)
|
103
|
+
@args.unshift(new_arg)
|
104
|
+
end
|
105
|
+
|
106
|
+
def call
|
107
|
+
@object.send(method_name, *args, &block)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "method_missing_router/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "method_missing_router"
|
7
|
+
s.version = MethodMissingRouter::VERSION
|
8
|
+
s.authors = ["Matt Margolis"]
|
9
|
+
s.email = ["matt@mattmargolis.net"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Declarative method routing for method_missing}
|
12
|
+
s.description = %q{Use regular expressions to route missing methods}
|
13
|
+
|
14
|
+
s.rubyforge_project = "method_missing_router"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rake"
|
23
|
+
s.add_development_dependency "rspec"
|
24
|
+
s.add_development_dependency "guard-rspec"
|
25
|
+
# s.add_runtime_dependency "rest-client"
|
26
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestMissingMethodRouter
|
4
|
+
include MethodMissingRouter
|
5
|
+
end
|
6
|
+
|
7
|
+
describe MethodMissingRouter do
|
8
|
+
let(:test_class) { TestMissingMethodRouter }
|
9
|
+
|
10
|
+
after(:each) do
|
11
|
+
test_class.reset_method_missing_routes!
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'class method routing' do
|
15
|
+
it 'can route all missing messages to the same target' do
|
16
|
+
test_class.class_route_missing(/.+/, :every_message)
|
17
|
+
test_class.should_receive(:every_message).twice
|
18
|
+
test_class.blammo
|
19
|
+
test_class.dragon_soda
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'can route two different formats of message to different targets' do
|
23
|
+
test_class.class_route_missing(/^fish/, :fish_message)
|
24
|
+
test_class.class_route_missing(/[a-z]+/, :lowercase_message)
|
25
|
+
|
26
|
+
test_class.should_receive(:fish_message)
|
27
|
+
test_class.should_receive(:lowercase_message)
|
28
|
+
|
29
|
+
test_class.fish_powers_activate
|
30
|
+
test_class.somelowercaseletters
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'passes the original message name as the first argument' do
|
34
|
+
test_class.class_route_missing(/^fire\_the/, :fire_weapon)
|
35
|
+
test_class.should_receive(:fire_weapon).with('fire_the_lazer', {:power => 11})
|
36
|
+
test_class.fire_the_lazer(:power => 11)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'passes the regex match as the first argument when pass_match option is set' do
|
40
|
+
test_class.class_route_missing(/^fire\_the\_(.+)/, :fire_weapon, :pass_match => true)
|
41
|
+
test_class.should_receive(:fire_weapon).with('lazer', {:power => 11})
|
42
|
+
test_class.fire_the_lazer(:power => 11)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'passes all regex matches as the first argument when pass_matches option is set' do
|
46
|
+
test_class.class_route_missing(/^fire\_the\_(.+)\_and\_(.+)/, :fire_weapon, :pass_matches => true)
|
47
|
+
test_class.should_receive(:fire_weapon).with(['lazer', 'canon'], {:power => 11})
|
48
|
+
test_class.fire_the_lazer_and_canon(:power => 11)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'passes undeclared messages to super' do
|
52
|
+
expect{test_class.something_unexpected}.to raise_error(NoMethodError)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'properly handles queries about responding to a routed method' do
|
56
|
+
test_class.class_route_missing(/^has_a_method_called/, :foobar)
|
57
|
+
test_class.should respond_to(:has_a_method_called_frank)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'properly handles queries about responding to a non-routed unknown method' do
|
61
|
+
test_class.should_not respond_to(:some_method_i_do_not_have)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'instance method routing' do
|
66
|
+
let(:test_instance) { test_class.new }
|
67
|
+
|
68
|
+
it 'can route all missing messages to the same target' do
|
69
|
+
test_class.route_missing(/.*/, :every_message)
|
70
|
+
test_instance.should_receive(:every_message).twice
|
71
|
+
|
72
|
+
test_instance.blammo
|
73
|
+
test_instance.dragon_soda
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'can route two different formats of message to different targets' do
|
77
|
+
test_class.route_missing(/^cow/, :cow_message)
|
78
|
+
test_class.route_missing(/in_a_bag$/, :in_a_bag)
|
79
|
+
|
80
|
+
test_instance.should_receive(:cow_message)
|
81
|
+
test_instance.should_receive(:in_a_bag)
|
82
|
+
|
83
|
+
test_instance.cowsplode
|
84
|
+
test_instance.pudding_in_a_bag
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'passes the original message name as the first argument' do
|
88
|
+
test_class.route_missing(/^fire\_the\_/, :fire_weapon)
|
89
|
+
test_instance.should_receive(:fire_weapon).with('fire_the_lazer', {:power => 11})
|
90
|
+
test_instance.fire_the_lazer(:power => 11)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'passes the regex match as the first argument when pass_match option is set' do
|
94
|
+
test_class.route_missing(/^fire\_the\_(.+)/, :fire_weapon, :pass_match => true)
|
95
|
+
test_instance.should_receive(:fire_weapon).with('lazer', {:power => 11})
|
96
|
+
test_instance.fire_the_lazer(:power => 11)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'passes all regex matches as the first argument when pass_matches option is set' do
|
100
|
+
test_class.route_missing(/^fire\_the\_(.+)\_and\_(.+)/, :fire_weapon, :pass_matches => true)
|
101
|
+
test_instance.should_receive(:fire_weapon).with(['lazer', 'canon'], {:power => 11})
|
102
|
+
test_instance.fire_the_lazer_and_canon(:power => 11)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'passes undeclared messages to super' do
|
106
|
+
expect{test_instance.something_unexpected}.to raise_error(NoMethodError)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'properly handles queries about responding to a routed method' do
|
110
|
+
test_class.class_route_missing(/^has_a_method_called/, :foobar)
|
111
|
+
test_class.should respond_to(:has_a_method_called_frank)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'properly handles queries about responding to a non-routed unknown method' do
|
115
|
+
test_class.should_not respond_to(:some_method_i_do_not_have)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "method_missing_router.rb"
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: method_missing_router
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matt Margolis
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-12 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70167569910440 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70167569910440
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70167569909020 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70167569909020
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: guard-rspec
|
38
|
+
requirement: &70167569907480 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70167569907480
|
47
|
+
description: Use regular expressions to route missing methods
|
48
|
+
email:
|
49
|
+
- matt@mattmargolis.net
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- .rvmrc
|
56
|
+
- Gemfile
|
57
|
+
- Guardfile
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- examples/example_spec.rb
|
61
|
+
- lib/method_missing_router.rb
|
62
|
+
- lib/method_missing_router/version.rb
|
63
|
+
- method_missing_router.gemspec
|
64
|
+
- spec/lib/method_missing_router_spec.rb
|
65
|
+
- spec/spec_helper.rb
|
66
|
+
homepage: ''
|
67
|
+
licenses: []
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
hash: 743983748671300555
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
segments:
|
88
|
+
- 0
|
89
|
+
hash: 743983748671300555
|
90
|
+
requirements: []
|
91
|
+
rubyforge_project: method_missing_router
|
92
|
+
rubygems_version: 1.8.10
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: Declarative method routing for method_missing
|
96
|
+
test_files:
|
97
|
+
- spec/lib/method_missing_router_spec.rb
|
98
|
+
- spec/spec_helper.rb
|