method_found 0.1.1 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +2 -0
- data/Guardfile +0 -28
- data/README.md +20 -7
- data/lib/method_found.rb +5 -1
- data/lib/method_found/attribute_interceptor.rb +91 -0
- data/lib/method_found/attribute_methods.rb +79 -0
- data/lib/method_found/builder.rb +40 -0
- data/lib/method_found/interceptor.rb +77 -8
- data/lib/method_found/version.rb +1 -1
- data/method_found.gemspec +1 -2
- metadata +12 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 95397b45051b6b4b33b65a371f9115455f7006398d726678bd045e6a3bb32f5b
|
4
|
+
data.tar.gz: b4cd923223329c88bd1c3e3ce2281f14975a44ecd2c99711251626cc62c3b0e4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b0d3e0960d80193f87d0d690cd18081caae5a10fdbcd85a9cd85c914a2b65c7192ec4a6ada6cb5f1f57bb0bb8c489dddf9edc233bb1fc63d2e3fb078684fdd4d
|
7
|
+
data.tar.gz: 8a7acaa4dffeb0a638dfb7e47449c5e8820530d3958149ac69abbb2186c6d9d907529e47b78e3d3b40ae15a77d8603b3b5445d67a3b6eec21aecee24252997ef
|
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.6'
|
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
|
-
|
data/lib/method_found.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
|
1
3
|
require "method_found/version"
|
4
|
+
require "method_found/builder"
|
2
5
|
require "method_found/interceptor"
|
6
|
+
require "method_found/attribute_methods"
|
3
7
|
|
4
8
|
module MethodFound
|
5
9
|
def self.new(*args, &block)
|
6
|
-
|
10
|
+
Builder.new(*args, &block)
|
7
11
|
end
|
8
12
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require "method_found"
|
2
|
+
|
3
|
+
module MethodFound
|
4
|
+
=begin
|
5
|
+
|
6
|
+
Class defining prefix, suffix and affix methods to a class for a given
|
7
|
+
attribute name or set of attribute names.
|
8
|
+
|
9
|
+
@example
|
10
|
+
class Person < Struct.new(:attributes)
|
11
|
+
include MethodFound::AttributeInterceptor.new
|
12
|
+
include MethodFound::AttributeInterceptor.new(suffix: '=')
|
13
|
+
include MethodFound::AttributeInterceptor.new(suffix: '_contrived?')
|
14
|
+
include MethodFound::AttributeInterceptor.new(prefix: 'clear_')
|
15
|
+
include MethodFound::AttributeInterceptor.new(prefix: 'reset_', suffix: '_to_default!')
|
16
|
+
|
17
|
+
def initialize(attributes = {})
|
18
|
+
super(attributes)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def attribute_contrived?(attr)
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear_attribute(attr)
|
28
|
+
send("#{attr}=", nil)
|
29
|
+
end
|
30
|
+
|
31
|
+
def reset_attribute_to_default!(attr)
|
32
|
+
send("#{attr}=", 'Default Name')
|
33
|
+
end
|
34
|
+
|
35
|
+
def attribute(attr)
|
36
|
+
attributes[attr]
|
37
|
+
end
|
38
|
+
|
39
|
+
def attribute=(attr, value)
|
40
|
+
attributes[attr] = value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
=end
|
44
|
+
class AttributeInterceptor < Interceptor
|
45
|
+
def initialize(prefix: '', suffix: '')
|
46
|
+
@regex = /\A(?:#{Regexp.escape(prefix)})(.*)(?:#{Regexp.escape(suffix)})\z/
|
47
|
+
@method_missing_target = method_missing_target = "#{prefix}attribute#{suffix}"
|
48
|
+
@method_name = "#{prefix}%s#{suffix}"
|
49
|
+
|
50
|
+
super to_proc do |_, attr_name, *args, &block|
|
51
|
+
send(method_missing_target, attr_name, *args, &block)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def define_attribute_methods(*attr_names)
|
56
|
+
handler = @method_missing_target
|
57
|
+
attr_names.each do |attr_name|
|
58
|
+
define_method method_name(attr_name) do |*arguments, &block|
|
59
|
+
send(handler, attr_name, *arguments, &block)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def alias_attribute(new_name, old_name)
|
65
|
+
handler = method_name(old_name)
|
66
|
+
define_method method_name(new_name) do |*arguments, &block|
|
67
|
+
send(handler, *arguments, &block)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_proc
|
72
|
+
regex = @regex
|
73
|
+
proc do |method_name|
|
74
|
+
(matches = regex.match(method_name)) &&
|
75
|
+
(method_name != :attributes) &&
|
76
|
+
respond_to?(:attributes) &&
|
77
|
+
(attributes.keys & matches.captures).first
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def inspect
|
82
|
+
"<#{self.class.name}: #{@regex.inspect}>"
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def method_name(attr_name)
|
88
|
+
@method_name % attr_name
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require "method_found/attribute_interceptor"
|
2
|
+
|
3
|
+
module MethodFound
|
4
|
+
=begin
|
5
|
+
|
6
|
+
Re-implementation of ActiveModel::AttributeMethods from Rails using
|
7
|
+
{MethodFound::Interceptor} modules instead of class variables.
|
8
|
+
|
9
|
+
@example
|
10
|
+
class Person < Struct.new(:attributes)
|
11
|
+
include MethodFound::AttributeMethods
|
12
|
+
|
13
|
+
attribute_method_suffix ''
|
14
|
+
attribute_method_suffix '='
|
15
|
+
attribute_method_suffix '_contrived?'
|
16
|
+
attribute_method_prefix 'clear_'
|
17
|
+
attribute_method_affix prefix: 'reset_', suffix: '_to_default!'
|
18
|
+
|
19
|
+
define_attribute_methods :name
|
20
|
+
|
21
|
+
def initialize(attributes = {})
|
22
|
+
super(attributes)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def attribute_contrived?(attr)
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def clear_attribute(attr)
|
32
|
+
send("#{attr}=", nil)
|
33
|
+
end
|
34
|
+
|
35
|
+
def reset_attribute_to_default!(attr)
|
36
|
+
send("#{attr}=", 'Default Name')
|
37
|
+
end
|
38
|
+
|
39
|
+
def attribute(attr)
|
40
|
+
attributes[attr]
|
41
|
+
end
|
42
|
+
|
43
|
+
def attribute=(attr, value)
|
44
|
+
attributes[attr] = value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
=end
|
49
|
+
module AttributeMethods
|
50
|
+
def self.included(base)
|
51
|
+
base.include(AttributeInterceptor.new)
|
52
|
+
base.instance_eval do
|
53
|
+
def attribute_method_prefix(*prefixes)
|
54
|
+
prefixes.each { |prefix| include AttributeInterceptor.new(prefix: prefix) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def attribute_method_suffix(*suffixes)
|
58
|
+
suffixes.each { |suffix| include AttributeInterceptor.new(suffix: suffix) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def attribute_method_affix(*affixes)
|
62
|
+
affixes.each { |affix| include AttributeInterceptor.new(prefix: affix[:prefix], suffix: affix[:suffix]) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def define_attribute_methods(*attr_names)
|
66
|
+
ancestors.each do |ancestor|
|
67
|
+
ancestor.define_attribute_methods(*attr_names) if ancestor.is_a?(AttributeInterceptor)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def alias_attribute(new_name, old_name)
|
72
|
+
ancestors.each do |ancestor|
|
73
|
+
ancestor.alias_attribute(new_name, old_name) if ancestor.is_a?(AttributeInterceptor)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -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
|
31
|
+
@interceptors = []
|
32
|
+
super
|
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,22 +1,91 @@
|
|
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
|
-
|
10
|
+
attr_reader :matcher
|
11
|
+
|
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?
|
16
|
+
end
|
17
|
+
|
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
|
+
|
4
29
|
define_method :method_missing do |method_name, *arguments, &method_block|
|
5
|
-
if matches =
|
6
|
-
|
30
|
+
if matches = matcher_.match(method_name, self)
|
31
|
+
method_cacher.(method_name, matches)
|
32
|
+
send(method_name, *arguments, &method_block)
|
7
33
|
else
|
8
34
|
super(method_name, *arguments, &method_block)
|
9
35
|
end
|
10
36
|
end
|
11
37
|
|
12
38
|
define_method :respond_to_missing? do |method_name, include_private = false|
|
13
|
-
|
39
|
+
if matches = matcher_.match(method_name, self)
|
40
|
+
method_cacher.(method_name, matches)
|
41
|
+
else
|
42
|
+
super(method_name, include_private)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def inspect
|
48
|
+
klass = self.class
|
49
|
+
name = klass.name || klass.inspect
|
50
|
+
"#<#{name}: #{matcher.inspect}>"
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def cache_method(method_name, matches)
|
56
|
+
intercept_method, matcher = @intercept_method, @matcher
|
57
|
+
define_method method_name do |*arguments, &block|
|
58
|
+
if matcher.proc? && !(matches = matcher.match(method_name, self))
|
59
|
+
return super(*arguments, &block)
|
60
|
+
end
|
61
|
+
arguments = [matches, *arguments] unless method(intercept_method).arity == 1
|
62
|
+
send(intercept_method, method_name, *arguments, &block)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def assign_intercept_method(&intercept_block)
|
67
|
+
@intercept_method ||= "__intercept_#{SecureRandom.hex}".freeze.tap do |method_name|
|
68
|
+
define_method method_name, &intercept_block
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Matcher < Struct.new(:matcher)
|
73
|
+
def match(method_name, context)
|
74
|
+
if matcher.is_a?(Regexp)
|
75
|
+
matcher.match(method_name)
|
76
|
+
elsif matcher.respond_to?(:call)
|
77
|
+
context.instance_exec(method_name, &matcher)
|
78
|
+
else
|
79
|
+
(matcher.to_sym == method_name)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def proc?
|
84
|
+
matcher.is_a?(Proc)
|
14
85
|
end
|
15
86
|
|
16
|
-
|
17
|
-
|
18
|
-
name = klass.name || klass.inspect
|
19
|
-
"#<#{name}: #{regex.inspect}>"
|
87
|
+
def inspect
|
88
|
+
matcher.inspect
|
20
89
|
end
|
21
90
|
end
|
22
91
|
end
|
data/lib/method_found/version.rb
CHANGED
data/method_found.gemspec
CHANGED
@@ -27,7 +27,6 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
-
spec.add_development_dependency "
|
31
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
30
|
+
spec.add_development_dependency "rake", "~> 12.3"
|
32
31
|
spec.add_development_dependency "rspec", "~> 3.0"
|
33
32
|
end
|
metadata
CHANGED
@@ -1,43 +1,29 @@
|
|
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.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Salzberg
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-29 00:00:00.000000000 Z
|
12
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.12'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '1.12'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: rake
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
30
16
|
requirements:
|
31
17
|
- - "~>"
|
32
18
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
19
|
+
version: '12.3'
|
34
20
|
type: :development
|
35
21
|
prerelease: false
|
36
22
|
version_requirements: !ruby/object:Gem::Requirement
|
37
23
|
requirements:
|
38
24
|
- - "~>"
|
39
25
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
26
|
+
version: '12.3'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: rspec
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,7 +38,7 @@ dependencies:
|
|
52
38
|
- - "~>"
|
53
39
|
- !ruby/object:Gem::Version
|
54
40
|
version: '3.0'
|
55
|
-
description:
|
41
|
+
description:
|
56
42
|
email:
|
57
43
|
- chris@dejimata.com
|
58
44
|
executables: []
|
@@ -71,6 +57,9 @@ files:
|
|
71
57
|
- bin/console
|
72
58
|
- bin/setup
|
73
59
|
- lib/method_found.rb
|
60
|
+
- lib/method_found/attribute_interceptor.rb
|
61
|
+
- lib/method_found/attribute_methods.rb
|
62
|
+
- lib/method_found/builder.rb
|
74
63
|
- lib/method_found/interceptor.rb
|
75
64
|
- lib/method_found/version.rb
|
76
65
|
- method_found.gemspec
|
@@ -78,7 +67,7 @@ homepage: https://github.com/shioyama/method_found
|
|
78
67
|
licenses:
|
79
68
|
- MIT
|
80
69
|
metadata: {}
|
81
|
-
post_install_message:
|
70
|
+
post_install_message:
|
82
71
|
rdoc_options: []
|
83
72
|
require_paths:
|
84
73
|
- lib
|
@@ -93,9 +82,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
82
|
- !ruby/object:Gem::Version
|
94
83
|
version: '0'
|
95
84
|
requirements: []
|
96
|
-
|
97
|
-
|
98
|
-
signing_key:
|
85
|
+
rubygems_version: 3.0.6
|
86
|
+
signing_key:
|
99
87
|
specification_version: 4
|
100
88
|
summary: Gem to intercept method_missing calls and do something useful.
|
101
89
|
test_files: []
|