ghost_dog 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.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YWJkODQ1MWVhYTc1NzYyZjdlODNjZjc3YjFjYTQyODJlMjYzNDExOA==
5
+ data.tar.gz: !binary |-
6
+ YWJhMzMxMDdkMDZhMjAwZDEwYjRlZjcxNzFhMWNiNzk1Mjk1YTYxMA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ YTBiMGUzOGEzZWVlMGM2ZDhhZWU4MTg0NTNjMjcyYjE1NWIwYjVlY2RmMDYx
10
+ MTgwYjg2NTgzNjQyNGI3MWY2NmYwOWIyOGE0ZGZjZjg1M2M1NDU2MjA1NzJm
11
+ NzQ3ZDZkYjk2NDQ0ODk5ZDIwNTQ1YjI5ZDg3ODU1ZmQwMTI1MTI=
12
+ data.tar.gz: !binary |-
13
+ MmYzYTlmNGY0YTljYTliODcxNDM2YWI4YzZlYzFiNjdkMmIzZDBiYzYxMzc3
14
+ ZDcyYTc5ZDQyMWQzM2RjMDY4ZTc3OTRjOWM4NDU5M2YzYTViZjA4ZTBhMjFk
15
+ OTU1ZTUwYTIzMjM0MTQwYjNmMjRiM2Y4MzJjOWVjNTZhYzc0NjA=
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ ghost_dog
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-1.9.3-p194
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.2"
4
+ - "1.9.3"
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ghost-dog.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Andrew Warner
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # Ghost Dog
2
+
3
+ ![](https://travis-ci.org/a-warner/ghost_dog.png)
4
+
5
+ Making method_missing easier to deal with since 2013...
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'ghost_dog'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install ghost_dog
20
+
21
+ ## Motivation
22
+
23
+ I've written similar looking `method_missing` and `repond_to?` code frequently enough
24
+ that I finally decided it was time for an abstraction. This might not be the best
25
+ abstraction, but it was the simplest way of thinking about it for me. Feel free to fork
26
+ the project and add your own interfaces for it.
27
+
28
+ Ghost Dog is built around the simple type `Responder`, which implements `matches?` and `call`. `matches?` is used
29
+ to determine whether a `Responder` can be used for a missing method (and therefore used in both `respond_to?`
30
+ and `method_missing`) and `call` is used to actually respond to a method invocation. Most of the time you shouldn't
31
+ be dealing directly with the `Responder` class; instead, you'll define methods either via the shorthand or with
32
+ the DSL.
33
+
34
+ In order to define a `ghost_method`, you need to implement a Matcher, which can either be a simple regex with
35
+ capture groups or a proc. When using a regex, any capture groups will be passed to your responding block. When using
36
+ a proc, anything returned by the proc will be passed to your responding block. The matcher block should return
37
+ something truthy if the method is available, otherwise it should return something falsy. See below for additional
38
+ examples.
39
+
40
+ ## Usage
41
+
42
+ Check out the specs for full usage information. Here are some quick examples:
43
+
44
+ Simple syntax:
45
+
46
+ ```ruby
47
+ class Minimal
48
+ include GhostDog
49
+
50
+ ghost_method /^tell_me_(.+)$/ do |what_to_tell|
51
+ what_to_tell.gsub('_', ' ')
52
+ end
53
+ end
54
+ ```
55
+
56
+ ```sh
57
+ >> Minimal.new.tell_me_hello_world # "hello world"
58
+ ```
59
+
60
+ More complex example, using the DSL version:
61
+
62
+ ```ruby
63
+ class ComplexExample
64
+ include GhostDog
65
+
66
+ def names
67
+ ['ishmael', 'dave']
68
+ end
69
+
70
+ ghost_method do
71
+ match_with do |method_name|
72
+ if match = method_name.match(/^call_me_(#{names.join('|')})$/)
73
+ match.to_a.drop(1)
74
+ end
75
+ end
76
+
77
+ respond_with do |name|
78
+ "what's going on #{name}?"
79
+ end
80
+ end
81
+ end
82
+ ```
83
+
84
+ ```sh
85
+ >> ComplexExample.new.call_me_ishmael # "what's going on ishmael?"
86
+ >> ComplexExample.new.call_me_samuel # NoMethodError
87
+ ```
88
+
89
+ Implementing a (fairly) complex example (using `detect_by` instead of `find_by`) as a duplicate of ActiveRecord's dynamic finders:
90
+
91
+ ```ruby
92
+ class MyModel < ActiveRecord::Base
93
+ class << self
94
+ include GhostDog
95
+
96
+ ghost_method do
97
+ create_method false
98
+
99
+ match_with do |method_name|
100
+ cols = column_names.join('|')
101
+ if match = method_name.match(/\Adetect_by_(#{cols})((?:_and_(?:#{cols}))*)\z/)
102
+ [[match[1]] + match[2].split('_and_').select(&:present?)]
103
+ end
104
+ end
105
+
106
+ respond_with do |*args|
107
+ columns_to_find_by = args.shift
108
+ unless args.length == columns_to_find_by.length
109
+ raise ArgumentError, "Wrong number of arguments (#{args.length} for #{columns_to_find_by.length})"
110
+ end
111
+
112
+ where(Hash[columns_to_find_by.zip(args)])
113
+ end
114
+ end
115
+ end
116
+ end
117
+ ```
118
+
119
+ ## Contributing
120
+
121
+ 1. Fork it
122
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
123
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
124
+ 4. Push to the branch (`git push origin my-new-feature`)
125
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/ghost_dog.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ghost_dog/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ghost_dog"
8
+ spec.version = GhostDog::VERSION
9
+ spec.authors = ["Andrew Warner"]
10
+ spec.email = ["wwarner.andrew@gmail.com"]
11
+ spec.description = %q{Making method_missing easier since 2013}
12
+ spec.summary = %q{Making method_missing easier since 2013}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "pry"
25
+ end
@@ -0,0 +1,38 @@
1
+ module GhostDog
2
+ class Responder
3
+ class DSL
4
+ def initialize
5
+ @options = { :create_method => true }
6
+ end
7
+
8
+ def to_responder
9
+ raise "Incomplete ghost method - must specify matcher and responding_block" unless [@matcher, @responder].all?
10
+
11
+ Responder.new(@matcher, @options, @responder)
12
+ end
13
+
14
+ private
15
+
16
+ def create_method(value)
17
+ @options[:create_method] = value
18
+ end
19
+
20
+ def respond_with(&block)
21
+ @responder = block
22
+ end
23
+
24
+ def match_with(matcher_as_arg = nil, &matcher_as_block)
25
+ raise "Must specify either exactly one of a matcher instance or a matcher block" unless !!matcher_as_arg ^ block_given?
26
+
27
+ matcher = matcher_as_block || matcher_as_arg
28
+
29
+ @matcher =
30
+ if matcher.respond_to? :matches
31
+ matcher
32
+ else
33
+ Responder.const_get("#{matcher.class}Matcher").new(matcher)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ module GhostDog
2
+ class Responder
3
+ class ProcMatcher < Struct.new(:block)
4
+ def matches(receiver, method_name)
5
+ receiver.instance_exec(method_name, &block)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module GhostDog
2
+ class Responder
3
+ class RegexpMatcher < Struct.new(:regexp)
4
+ def matches(receiver, method_name)
5
+ if match = regexp.match(method_name)
6
+ match.to_a.drop(1)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,66 @@
1
+ require 'ghost_dog/responder/proc_matcher'
2
+ require 'ghost_dog/responder/regexp_matcher'
3
+ require 'ghost_dog/responder/dsl'
4
+
5
+ module GhostDog
6
+ class Responder
7
+ attr_reader :matcher, :responding_block, :options
8
+ def initialize(matcher, options, responding_block)
9
+ @matcher = matcher
10
+ @options = options
11
+ @responding_block = responding_block
12
+ end
13
+
14
+ def matches(receiver, method)
15
+ matcher.matches(receiver, method)
16
+ end
17
+ alias_method :matches?, :matches
18
+
19
+ def call(instance, klass, method, passed_args, passed_block)
20
+ match_result = Array[matches(instance, method)].flatten(1)
21
+
22
+ if create_method?
23
+ klass.class_exec(responding_block) do |respond_with|
24
+ define_method(method) do |*args, &block|
25
+ instance_exec(*(match_result + args), &respond_with)
26
+ end
27
+ end
28
+
29
+ instance.send(method, *passed_args, &passed_block)
30
+ else
31
+ instance.instance_exec(*(match_result + passed_args), &responding_block)
32
+ end
33
+ end
34
+
35
+ def self.from(matcher, options, block)
36
+ raise ArgumentError, "Must specify a block to create a ghost_method" unless block
37
+
38
+ if matcher.nil?
39
+ unless options.empty?
40
+ raise ArgumentError, "You cannot specify creation options if you're \
41
+ using the shorthand...specify those options in the DSL instead"
42
+ end
43
+
44
+ using_dsl(&block)
45
+ else
46
+ using_dsl do
47
+ options.each {|k, v| send(k, v) }
48
+ match_with(matcher)
49
+ respond_with(&block)
50
+ end
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def create_method?
57
+ options[:create_method]
58
+ end
59
+
60
+ def self.using_dsl(&block)
61
+ Responder::DSL.new.tap do |dsl|
62
+ dsl.instance_eval(&block)
63
+ end.to_responder
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,3 @@
1
+ module GhostDog
2
+ VERSION = "0.0.1"
3
+ end
data/lib/ghost_dog.rb ADDED
@@ -0,0 +1,85 @@
1
+ require 'ghost_dog/version'
2
+ require 'ghost_dog/responder'
3
+ require 'delegate'
4
+
5
+ module GhostDog
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ base.send(:include, InstanceMethods)
9
+ end
10
+
11
+ module InstanceMethods
12
+ def respond_to?(method, include_private = false)
13
+ super || !!responding_ghost_method(method.to_s)
14
+ end
15
+
16
+ private
17
+
18
+ def _ghost_methods
19
+ _klass_where_ghost_method_definitions_are.send(:_ghost_method_definitions)
20
+ end
21
+
22
+ def _klass_where_ghost_method_definitions_are
23
+ if self.class == Class
24
+ self.singleton_class
25
+ else
26
+ self.class
27
+ end
28
+ end
29
+
30
+ def responding_ghost_method(method)
31
+ @_considered_methods ||= []
32
+ return if @_considered_methods.include?(method)
33
+
34
+ @_considered_methods << method
35
+
36
+ _ghost_methods.detect do |matcher|
37
+ matcher.matches?(self, method)
38
+ end
39
+ ensure
40
+ @_considered_methods = []
41
+ end
42
+
43
+ def method_missing(method, *args, &block)
44
+ if matcher = responding_ghost_method(method.to_s)
45
+ matcher.call(self, _klass_where_ghost_method_definitions_are, method.to_s, args, block)
46
+ else
47
+ super
48
+ end
49
+ end
50
+
51
+ def inherited(child)
52
+ _setup_ghost_dog_singleton_class_inheritance(child)
53
+ super
54
+ end
55
+
56
+ def _setup_ghost_dog_singleton_class_inheritance(child)
57
+ if singleton_class.respond_to?(:_setup_ghost_dog_inheritance, :include_private)
58
+ singleton_class.send(:_setup_ghost_dog_inheritance, child.singleton_class)
59
+ end
60
+ end
61
+ end
62
+
63
+ module ClassMethods
64
+ protected
65
+
66
+ def ghost_method(matcher = nil, options = {}, &block)
67
+ _ghost_method_definitions << Responder.from(matcher, options, block)
68
+ end
69
+
70
+ private
71
+
72
+ def _ghost_method_definitions
73
+ @_ghost_methods ||= []
74
+ end
75
+
76
+ def inherited(child)
77
+ _setup_ghost_dog_inheritance(child)
78
+ super
79
+ end
80
+
81
+ def _setup_ghost_dog_inheritance(child)
82
+ child.instance_variable_set('@_ghost_methods', _ghost_method_definitions)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,371 @@
1
+ require 'spec_helper'
2
+
3
+ describe GhostDog do
4
+ subject { described_class }
5
+ it { should_not be_nil }
6
+
7
+ context 'simple responder' do
8
+ class Minimal
9
+ include GhostDog
10
+
11
+ ghost_method /^tell_me_(.+)$/ do |what_to_tell|
12
+ what_to_tell.gsub('_', ' ')
13
+ end
14
+ end
15
+
16
+ let(:obj) { Minimal.new }
17
+ subject { obj }
18
+ its(:tell_me_hello_world) { should == "hello world" }
19
+ it { should respond_to(:tell_me_hello_world) }
20
+
21
+ context 'defines method' do
22
+ let(:obj) { Minimal.new }
23
+ before { obj.tell_me_whatup }
24
+
25
+ its(:public_methods) { should include :tell_me_whatup }
26
+ its(:tell_me_whatup) { should == "whatup" }
27
+ end
28
+ end
29
+
30
+ context 'class level responder' do
31
+ class ClassLevel
32
+ class << self
33
+ include GhostDog
34
+
35
+ ghost_method /^say_([^_]+)_to_(.+)$/ do |what, name|
36
+ "#{what}, #{name}!"
37
+ end
38
+ end
39
+ end
40
+
41
+ let(:obj) { ClassLevel }
42
+ subject { obj }
43
+
44
+ its(:say_hello_to_andrew) { should == "hello, andrew!" }
45
+ it { should respond_to(:say_goodbye_to_max) }
46
+ it { should_not respond_to(:tell_max_hello) }
47
+ its(:public_methods) { should include :say_hello_to_andrew }
48
+
49
+ context 'another class' do
50
+ class AnotherClass; end
51
+ let(:obj) { AnotherClass }
52
+ it { should_not respond_to(:say_hello_to_andrew) }
53
+ end
54
+ end
55
+
56
+ context 'inheritable method_missing' do
57
+ class SuperKlass
58
+ class << self
59
+ include GhostDog
60
+
61
+ ghost_method /^call_an_([^_]+)_overridable_method$/ do |text|
62
+ "#{text} #{overridable_method}"
63
+ end
64
+
65
+ def overridable_method
66
+ "SuperKlass"
67
+ end
68
+ end
69
+
70
+ include GhostDog
71
+
72
+ ghost_method /^instance_methods_say_(.+)$/ do |what_to_say|
73
+ "#{greeting} #{what_to_say}"
74
+ end
75
+
76
+ def greeting
77
+ "Super greeting"
78
+ end
79
+ end
80
+
81
+ class SubKlass < SuperKlass
82
+ def self.overridable_method
83
+ "SubKlass"
84
+ end
85
+
86
+ def greeting
87
+ "Sub greeting"
88
+ end
89
+ end
90
+
91
+ subject { obj }
92
+
93
+ context SuperKlass do
94
+ let(:obj) { SuperKlass }
95
+ its(:call_an_awesome_overridable_method) { should == 'awesome SuperKlass' }
96
+
97
+ context 'instance' do
98
+ let(:obj) { SuperKlass.new }
99
+ its(:instance_methods_say_salutations) { should == "Super greeting salutations" }
100
+ end
101
+ end
102
+
103
+ context SubKlass do
104
+ let(:obj) { SubKlass }
105
+ its(:call_an_awesome_overridable_method) { should == 'awesome SubKlass' }
106
+
107
+ context 'instance' do
108
+ let(:obj) { SubKlass.new }
109
+ its(:instance_methods_say_salutations) { should == "Sub greeting salutations" }
110
+ end
111
+ end
112
+ end
113
+
114
+ context 'complex matchers' do
115
+ class ComplexExample
116
+ include GhostDog
117
+
118
+ def names
119
+ ['ishmael', 'dave']
120
+ end
121
+
122
+ ghost_method do
123
+ match_with do |method_name|
124
+ if match = method_name.match(/^call_me_(#{names.join('|')})$/)
125
+ match.to_a.drop(1)
126
+ end
127
+ end
128
+
129
+ respond_with do |name|
130
+ "what's going on #{name}?"
131
+ end
132
+ end
133
+ end
134
+
135
+ subject { obj }
136
+
137
+ let(:obj) { ComplexExample.new }
138
+
139
+ it { should_not respond_to(:call_me_john) }
140
+ ['ishmael', 'dave'].each do |name|
141
+ it { should respond_to(:"call_me_#{name}") }
142
+ its(:"call_me_#{name}") { should == "what's going on #{name}?" }
143
+ end
144
+
145
+ context 'custom matcher class' do
146
+ class CustomMatcher
147
+ def matches(receiver, method_name)
148
+ method_name == 'andrew' && method_name
149
+ end
150
+ end
151
+
152
+ class CustomMatcher2
153
+ def matches(receiver, method_name)
154
+ method_name == 'john' && method_name
155
+ end
156
+ end
157
+
158
+ class CustomMatcherExample
159
+ include GhostDog
160
+
161
+ ghost_method do
162
+ match_with CustomMatcher.new
163
+
164
+ respond_with do |name|
165
+ "that's a cool matcher #{name}"
166
+ end
167
+ end
168
+
169
+ ghost_method CustomMatcher2.new do |name|
170
+ "more concise, #{name}!"
171
+ end
172
+ end
173
+
174
+ let(:obj) { CustomMatcherExample.new }
175
+
176
+ it { should respond_to(:andrew) }
177
+ its(:andrew) { should == "that's a cool matcher andrew" }
178
+ it { should respond_to(:john) }
179
+ its(:john) { should == "more concise, john!" }
180
+
181
+ end
182
+
183
+ context 'superclass and subclass' do
184
+ def self.setup_superclass(super_class)
185
+
186
+ super_class.class_eval do
187
+ include GhostDog
188
+
189
+ ghost_method do
190
+ match_with do |method_name|
191
+ if match = method_name.match(/^(#{names.join('|')})$/)
192
+ match.to_a.drop(1)
193
+ end
194
+ end
195
+
196
+ respond_with do |name|
197
+ "hello mr #{name}"
198
+ end
199
+ end
200
+ end
201
+
202
+ end
203
+
204
+ def self.setup_subclasses(sub_class_1, sub_class_2)
205
+ sub_class_1.class_eval do
206
+ def names; ['james', 'john']; end
207
+ end
208
+
209
+ sub_class_2.class_eval do
210
+ def names; ['anna', 'margaret']; end
211
+ end
212
+ end
213
+
214
+ def self.name_examples_for(names)
215
+ names.each do |name|
216
+ it { should respond_to(name) }
217
+ its(name) { should == "hello mr #{name}" }
218
+ end
219
+ end
220
+
221
+ def self.non_responding_examples_for(names)
222
+ names.map(&:to_sym).each do |name|
223
+ it { should_not respond_to(name) }
224
+ it 'should raise NoMethodError' do
225
+ expect { send(name) }.to raise_exception NoMethodError
226
+ end
227
+ end
228
+ end
229
+
230
+ def self.name_examples(ctx, obj, options)
231
+ context ctx do
232
+ let(:obj) { obj }
233
+ name_examples_for(options.fetch(:responds_to))
234
+ non_responding_examples_for(options.fetch(:non_responses))
235
+ end
236
+ end
237
+
238
+ class SuperClass; end
239
+ setup_superclass(SuperClass)
240
+ class SubClassA < SuperClass; end
241
+ class SubClassB < SuperClass; end
242
+ setup_subclasses(SubClassA, SubClassB)
243
+
244
+ subject { obj }
245
+
246
+ name_examples(SubClassA, SubClassA.new,
247
+ :responds_to => ['james', 'john'],
248
+ :non_responses => ['anna', 'margaret'])
249
+ name_examples(SubClassB, SubClassB.new,
250
+ :responds_to => ['anna', 'margaret'],
251
+ :non_responses => ['james', 'john'])
252
+
253
+ class SuperEigenClass; end
254
+ setup_superclass(SuperEigenClass.singleton_class)
255
+ class SubEigenclassA < SuperEigenClass; end
256
+ class SubEigenclassB < SuperEigenClass; end
257
+ setup_subclasses(SubEigenclassA.singleton_class, SubEigenclassB.singleton_class)
258
+
259
+ name_examples(SubEigenclassA.singleton_class, SubEigenclassA,
260
+ :responds_to => ['james', 'john'],
261
+ :non_responses => ['anna', 'margaret'])
262
+ name_examples(SubEigenclassB.singleton_class, SubEigenclassB,
263
+ :responds_to => ['anna', 'margaret'],
264
+ :non_responses => ['james', 'john'])
265
+ end
266
+ end
267
+
268
+ context 'missing method' do
269
+ class Broken
270
+ include GhostDog
271
+
272
+ ghost_method /\Ahello_(.+)\Z/ do |name|
273
+ method_that_doesnt_exist(name)
274
+ end
275
+
276
+ ghost_method do
277
+ match_with do |method_name|
278
+ nothing_to_see_here(method_name)
279
+ end
280
+
281
+ respond_with do |something|
282
+ raise "I shouldn't be called..."
283
+ end
284
+ end
285
+ end
286
+
287
+ subject { Broken.new }
288
+
289
+ context 'in method definition' do
290
+ it 'should raise error' do
291
+ expect { subject.hello_world }.to raise_error NoMethodError
292
+ end
293
+ end
294
+
295
+ context 'in method matcher' do
296
+ it 'should raise error' do
297
+ expect { subject.foo_bar }.to raise_error NoMethodError
298
+ end
299
+ end
300
+ end
301
+
302
+ context 'calling ghost method from a ghost method' do
303
+ class DoubleGhost
304
+ include GhostDog
305
+
306
+ ghost_method /\Ahello_(.+)\Z/ do |name|
307
+ "hello to you, #{name}"
308
+ end
309
+
310
+ ghost_method /\Afoo_(.+)\Z/ do |after_foo|
311
+ send(:"hello_#{after_foo}")
312
+ end
313
+ end
314
+
315
+ subject { DoubleGhost.new }
316
+
317
+ its(:foo_bar) { should == "hello to you, bar" }
318
+ end
319
+
320
+ context "doesn't define method" do
321
+ class DontDefineMethod; end
322
+
323
+ def self.dont_create_method_examples_for(klass, &block)
324
+ klass.class_eval do
325
+ include GhostDog
326
+
327
+ ghost_method /\Aforest_whitaker_is_(\w+)\z/, :create_method => false do |what_he_is|
328
+ "#{prefix} forest is #{what_he_is}?"
329
+ end
330
+
331
+ ghost_method do
332
+ create_method false
333
+
334
+ match_with do |method_name|
335
+ method_name.to_s =~ /\A(#{name})_is_a_fan_of_forest\z/ && $1
336
+ end
337
+
338
+ respond_with { |name| "#{name} sure is!" }
339
+ end
340
+
341
+ def name
342
+ 'andrew'
343
+ end
344
+
345
+ def prefix
346
+ "did you know that"
347
+ end
348
+ end
349
+
350
+ context klass do
351
+ subject { block.call }
352
+
353
+ before do
354
+ subject.forest_whitaker_is_awesome
355
+ subject.andrew_is_a_fan_of_forest
356
+ end
357
+
358
+ its(:forest_whitaker_is_awesome) { should == "did you know that forest is awesome?" }
359
+ its(:public_methods) { should_not include :forest_whitaker_is_awesome }
360
+ it { should respond_to(:forest_whitaker_is_awesome) }
361
+
362
+ its(:andrew_is_a_fan_of_forest) { should == "andrew sure is!" }
363
+ its(:public_methods) { should_not include :andrew_is_a_fan_of_forest }
364
+ it { should respond_to(:andrew_is_a_fan_of_forest) }
365
+ end
366
+ end
367
+
368
+ dont_create_method_examples_for(DontDefineMethod) { DontDefineMethod.new }
369
+ dont_create_method_examples_for(DontDefineMethod.singleton_class) { DontDefineMethod }
370
+ end
371
+ end
@@ -0,0 +1,9 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'ghost_dog'
5
+ require 'pry'
6
+
7
+ RSpec.configure do |config|
8
+ config.color_enabled = true
9
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ghost_dog
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Warner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-05-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Making method_missing easier since 2013
70
+ email:
71
+ - wwarner.andrew@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .ruby-gemset
78
+ - .ruby-version
79
+ - .travis.yml
80
+ - Gemfile
81
+ - LICENSE.txt
82
+ - README.md
83
+ - Rakefile
84
+ - ghost_dog.gemspec
85
+ - lib/ghost_dog.rb
86
+ - lib/ghost_dog/responder.rb
87
+ - lib/ghost_dog/responder/dsl.rb
88
+ - lib/ghost_dog/responder/proc_matcher.rb
89
+ - lib/ghost_dog/responder/regexp_matcher.rb
90
+ - lib/ghost_dog/version.rb
91
+ - spec/lib/ghost_dog_spec.rb
92
+ - spec/spec_helper.rb
93
+ homepage: ''
94
+ licenses:
95
+ - MIT
96
+ metadata: {}
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 2.0.3
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: Making method_missing easier since 2013
117
+ test_files:
118
+ - spec/lib/ghost_dog_spec.rb
119
+ - spec/spec_helper.rb