method_missing_router 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm ruby-1.9.2@method_missing_router --create
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in method_missing_router.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2 do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
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,3 @@
1
+ module MethodMissingRouter
2
+ VERSION = "0.0.1"
3
+ 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
@@ -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