method_found 0.1.1 → 0.1.6
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 +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: []
|