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 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