method_found 0.1.2 → 0.1.3
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 +4 -4
- data/.gitignore +2 -0
- data/Guardfile +0 -28
- data/README.md +20 -7
- data/lib/method_found/builder.rb +40 -0
- data/lib/method_found/interceptor.rb +63 -9
- data/lib/method_found/simple_model.rb +109 -0
- data/lib/method_found/version.rb +1 -1
- data/lib/method_found.rb +4 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c80b59b4cfa2a683e9eaf250f975cebeff2594f0
|
4
|
+
data.tar.gz: 7004c79a99fddd803d098f4ebabe5541d8ebfeae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ccb77ec85e8d6b98b66315c53853088308ba74a760dabe638499717f597ccdf9c2dcf44378cb1c4c1a26cf35202963fc8116c6154d785581f7a43b7dee4e96f6
|
7
|
+
data.tar.gz: b76fbe65a0a186bb5f361ab251c9d514e20b020aa0ac0949dfacfcf832e8a997392c519c23e85615227599ec0ed97feeedf5facdd1372792692fa41a2fac69e9
|
data/Guardfile
CHANGED
@@ -39,32 +39,4 @@ guard :rspec, cmd: "bundle exec rspec" do
|
|
39
39
|
# Ruby files
|
40
40
|
ruby = dsl.ruby
|
41
41
|
dsl.watch_spec_files_for(ruby.lib_files)
|
42
|
-
|
43
|
-
# Rails files
|
44
|
-
rails = dsl.rails(view_extensions: %w(erb haml slim))
|
45
|
-
dsl.watch_spec_files_for(rails.app_files)
|
46
|
-
dsl.watch_spec_files_for(rails.views)
|
47
|
-
|
48
|
-
watch(rails.controllers) do |m|
|
49
|
-
[
|
50
|
-
rspec.spec.call("routing/#{m[1]}_routing"),
|
51
|
-
rspec.spec.call("controllers/#{m[1]}_controller"),
|
52
|
-
rspec.spec.call("acceptance/#{m[1]}")
|
53
|
-
]
|
54
|
-
end
|
55
|
-
|
56
|
-
# Rails config changes
|
57
|
-
watch(rails.spec_helper) { rspec.spec_dir }
|
58
|
-
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
|
59
|
-
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
60
|
-
|
61
|
-
# Capybara features specs
|
62
|
-
watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
|
63
|
-
watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
|
64
|
-
|
65
|
-
# Turnip features and steps
|
66
|
-
watch(%r{^spec/acceptance/(.+)\.feature$})
|
67
|
-
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
68
|
-
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
|
69
|
-
end
|
70
42
|
end
|
data/README.md
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
|
6
6
|
[gem]: https://rubygems.org/gems/method_found
|
7
7
|
[travis]: https://travis-ci.org/shioyama/method_found
|
8
|
+
[docs]: http://www.rubydoc.info/gems/method_found
|
8
9
|
|
9
10
|
Intercept `method_missing` and do something useful with it.
|
10
11
|
|
@@ -13,23 +14,29 @@ Intercept `method_missing` and do something useful with it.
|
|
13
14
|
Add to your Gemfile:
|
14
15
|
|
15
16
|
```ruby
|
16
|
-
gem 'method_found', '~> 0.1.
|
17
|
+
gem 'method_found', '~> 0.1.3'
|
17
18
|
```
|
18
19
|
|
19
20
|
And bundle it.
|
20
21
|
|
21
22
|
## Usage
|
22
23
|
|
23
|
-
Include an instance of `MethodFound` with a
|
24
|
-
|
24
|
+
Include an instance of `MethodFound::Builder` with a block defining all
|
25
|
+
patterns to match. Identify a pattern with the `intercept` method, like this:
|
25
26
|
|
26
27
|
```ruby
|
27
28
|
class Foo
|
28
|
-
include
|
29
|
-
|
30
|
-
|
29
|
+
include MethodFound::Builder.new {
|
30
|
+
intercept /\Asay_([a-z]+)\Z/ do |method_name, matches, *arguments, &block|
|
31
|
+
"#{matches[1]}!"
|
32
|
+
end
|
33
|
+
}
|
31
34
|
end
|
35
|
+
```
|
36
|
+
|
37
|
+
Now you can say things:
|
32
38
|
|
39
|
+
```ruby
|
33
40
|
foo = Foo.new
|
34
41
|
foo.say_hello
|
35
42
|
#=> "hello!"
|
@@ -37,7 +44,13 @@ foo.say_bye
|
|
37
44
|
#=> "bye!"
|
38
45
|
```
|
39
46
|
|
47
|
+
That's it!
|
48
|
+
|
49
|
+
## More Information
|
50
|
+
|
51
|
+
- [Github repository](https://www.github.com/shioyama/method_found)
|
52
|
+
- [API documentation][docs]
|
53
|
+
|
40
54
|
## License
|
41
55
|
|
42
56
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
43
|
-
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module MethodFound
|
2
|
+
=begin
|
3
|
+
|
4
|
+
Creates set of interceptors to include into a class.
|
5
|
+
|
6
|
+
@example
|
7
|
+
class Post
|
8
|
+
builder = MethodFound::Builder.new {
|
9
|
+
intercept /\Asay_([a-z]+)\Z/ do |method_name, matches, *arguments|
|
10
|
+
"#{matches[1]}!"
|
11
|
+
end
|
12
|
+
|
13
|
+
intercept /\Ayell_([a-z]+)\Z/ do |method_name, matches, *arguments|
|
14
|
+
"#{matches[1]}!!!"
|
15
|
+
end
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
foo = Foo.new
|
20
|
+
foo.say_hello
|
21
|
+
#=> "hello!"
|
22
|
+
foo.yell_hello
|
23
|
+
#=> "hello!!!"
|
24
|
+
=end
|
25
|
+
class Builder < Module
|
26
|
+
attr_reader :interceptors
|
27
|
+
|
28
|
+
# @yield Yields builder as context to block, to allow calling builder
|
29
|
+
# methods to create interceptors in included class.
|
30
|
+
def initialize(&block)
|
31
|
+
@interceptors = []
|
32
|
+
instance_eval &block
|
33
|
+
end
|
34
|
+
|
35
|
+
def intercept(*args, &block)
|
36
|
+
@interceptors.push(interceptor = Interceptor.new(*args, &block))
|
37
|
+
include interceptor
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,30 +1,84 @@
|
|
1
1
|
module MethodFound
|
2
|
+
=begin
|
3
|
+
|
4
|
+
Class for intercepting method calls using method_missing. Initialized by
|
5
|
+
passing in a matcher object which can be a Regexp, a proc or lambda, or a
|
6
|
+
string/symbol.
|
7
|
+
|
8
|
+
=end
|
2
9
|
class Interceptor < Module
|
3
|
-
attr_reader :
|
10
|
+
attr_reader :matcher
|
4
11
|
|
5
|
-
|
6
|
-
|
12
|
+
# Creates an interceptor module to include into a class.
|
13
|
+
# @param (see #define_method_missing)
|
14
|
+
def initialize(matcher = nil, &intercept_block)
|
15
|
+
define_method_missing(matcher, &intercept_block) unless matcher.nil?
|
7
16
|
end
|
8
17
|
|
9
|
-
|
10
|
-
|
18
|
+
# Define method_missing and respond_to_missing? on this interceptor. Can be
|
19
|
+
# called after interceptor has been created.
|
20
|
+
# @param [Regexp,Proc,String,Symbol] matcher Matcher for intercepting
|
21
|
+
# method calls.
|
22
|
+
# @yield [method_name, matches, &block] Yiels method_name matched, set of
|
23
|
+
# matches returned from matcher, and block passed to method when called.
|
24
|
+
def define_method_missing(matcher, &intercept_block)
|
25
|
+
@matcher = matcher_ = Matcher.new(matcher)
|
26
|
+
assign_intercept_method(&intercept_block)
|
27
|
+
method_cacher = method(:cache_method)
|
28
|
+
|
11
29
|
define_method :method_missing do |method_name, *arguments, &method_block|
|
12
|
-
if matches =
|
13
|
-
|
30
|
+
if matches = matcher_.match(method_name)
|
31
|
+
method_cacher.(method_name, matches)
|
32
|
+
send(method_name, *arguments, &method_block)
|
14
33
|
else
|
15
34
|
super(method_name, *arguments, &method_block)
|
16
35
|
end
|
17
36
|
end
|
18
37
|
|
19
38
|
define_method :respond_to_missing? do |method_name, include_private = false|
|
20
|
-
|
39
|
+
if matches = matcher_.match(method_name)
|
40
|
+
method_cacher.(method_name, matches)
|
41
|
+
else
|
42
|
+
super(method_name, include_private)
|
43
|
+
end
|
21
44
|
end
|
22
45
|
end
|
23
46
|
|
24
47
|
def inspect
|
25
48
|
klass = self.class
|
26
49
|
name = klass.name || klass.inspect
|
27
|
-
"#<#{name}: #{
|
50
|
+
"#<#{name}: #{matcher.inspect}>"
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def cache_method(method_name, matches)
|
56
|
+
intercept_method = @intercept_method
|
57
|
+
define_method method_name do |*arguments, &block|
|
58
|
+
send(intercept_method, method_name, matches, *arguments, &block)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def assign_intercept_method(&intercept_block)
|
63
|
+
@intercept_method ||= "__intercept_#{SecureRandom.hex}".freeze.tap do |method_name|
|
64
|
+
define_method method_name, &intercept_block
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Matcher < Struct.new(:matcher)
|
69
|
+
def match(method_name)
|
70
|
+
if matcher.is_a?(Regexp)
|
71
|
+
matcher.match(method_name)
|
72
|
+
elsif matcher.respond_to?(:call)
|
73
|
+
matcher.call(method_name) && [method_name.to_s]
|
74
|
+
else
|
75
|
+
(matcher.to_sym == method_name) && [method_name.to_s]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def inspect
|
80
|
+
matcher.inspect
|
81
|
+
end
|
28
82
|
end
|
29
83
|
end
|
30
84
|
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require "method_found"
|
2
|
+
|
3
|
+
module MethodFound
|
4
|
+
=begin
|
5
|
+
|
6
|
+
Example class using MethodFound interceptors to implement attributes and change
|
7
|
+
tracking. Symbols passed into constructor define patterns in interceptor
|
8
|
+
modules to catch method calls and define methods on module as needed. New
|
9
|
+
methods can be dynamically defined by calling any undefined method with a bang (!).
|
10
|
+
|
11
|
+
This class is not included by default when requiring MethodFound, so you will need to explicitly require it with:
|
12
|
+
|
13
|
+
require "method_found/simple_model"
|
14
|
+
|
15
|
+
@example Setting Attributes
|
16
|
+
post = MethodFound::SimpleModel.new(:title, :content)
|
17
|
+
|
18
|
+
post.title = "Method Found"
|
19
|
+
post.content = "Once upon a time..."
|
20
|
+
|
21
|
+
post.title
|
22
|
+
#=> "Method Found"
|
23
|
+
|
24
|
+
post.content
|
25
|
+
#=> "Once upon a time..."
|
26
|
+
|
27
|
+
@example Dyanamically defining new attributes
|
28
|
+
post = MethodFound::SimpleModel.new
|
29
|
+
|
30
|
+
post.foo
|
31
|
+
#=> raises MethodNotFound error
|
32
|
+
|
33
|
+
post.foo!
|
34
|
+
#=> nil
|
35
|
+
|
36
|
+
post.foo = "my foo"
|
37
|
+
post.foo
|
38
|
+
#=> "my foo"
|
39
|
+
|
40
|
+
@example Tracking changes
|
41
|
+
post = MethodFound::SimpleModel.new(:title)
|
42
|
+
|
43
|
+
post.title = "foo"
|
44
|
+
post.title_changed?
|
45
|
+
#=> false
|
46
|
+
|
47
|
+
post.title = "bar"
|
48
|
+
post.title_changed?
|
49
|
+
#=> true
|
50
|
+
post.title_was
|
51
|
+
#=> "foo"
|
52
|
+
post.title_changes
|
53
|
+
#=> ["bar", "foo"]
|
54
|
+
|
55
|
+
=end
|
56
|
+
class SimpleModel
|
57
|
+
attr_reader :attribute_builder
|
58
|
+
|
59
|
+
# @param attribute_names [Symbol] One or more attribute names to define
|
60
|
+
# interceptors for on model.
|
61
|
+
def initialize(*attribute_names)
|
62
|
+
@attributes = Hash.new { |h, k| h[k] = [] }
|
63
|
+
|
64
|
+
@attribute_builder = builder = Builder.new do
|
65
|
+
def define_missing(attribute_name)
|
66
|
+
intercept /\A(#{attribute_name})(=|\?)?\Z/.freeze do |_, matches, *arguments|
|
67
|
+
name = matches[1]
|
68
|
+
value = @attributes[name].last
|
69
|
+
|
70
|
+
if matches[2] == "=".freeze
|
71
|
+
new_value = arguments[0]
|
72
|
+
@attributes[name].push(new_value) unless new_value == value
|
73
|
+
else
|
74
|
+
matches[2] == "?".freeze ? !!value : value
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
intercept /\A(reset_(#{attribute_name})|(#{attribute_name})_(changed\?|changes|was))\Z/.freeze do |_, matches|
|
79
|
+
if matches[1] == "reset_#{attribute_name}".freeze
|
80
|
+
!!@attributes.delete(matches[2])
|
81
|
+
else
|
82
|
+
changes = @attributes[matches[3]]
|
83
|
+
if matches[4] == "changes".freeze
|
84
|
+
changes.reverse
|
85
|
+
elsif matches[4] == "was".freeze
|
86
|
+
changes[-2]
|
87
|
+
else
|
88
|
+
changes.size > 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
intercept /\A(.+)\!\Z/ do |_, matches|
|
95
|
+
builder.define_missing matches[1]
|
96
|
+
singleton_class.include builder
|
97
|
+
@attributes[matches[1]].last
|
98
|
+
end
|
99
|
+
end
|
100
|
+
attribute_names.each { |name| builder.define_missing(name) }
|
101
|
+
|
102
|
+
singleton_class.include builder
|
103
|
+
end
|
104
|
+
|
105
|
+
def attributes
|
106
|
+
Hash[@attributes.map { |k, v| [k, v.last] }]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/method_found/version.rb
CHANGED
data/lib/method_found.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: method_found
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Salzberg
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-04-
|
11
|
+
date: 2017-04-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -71,7 +71,9 @@ files:
|
|
71
71
|
- bin/console
|
72
72
|
- bin/setup
|
73
73
|
- lib/method_found.rb
|
74
|
+
- lib/method_found/builder.rb
|
74
75
|
- lib/method_found/interceptor.rb
|
76
|
+
- lib/method_found/simple_model.rb
|
75
77
|
- lib/method_found/version.rb
|
76
78
|
- method_found.gemspec
|
77
79
|
homepage: https://github.com/shioyama/method_found
|