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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9de7a0b0880e53f1c96603d88fa416c025a4e29d
4
- data.tar.gz: 4b65ed2fbf1926df69dc140341c677240fcc3a4d
3
+ metadata.gz: c80b59b4cfa2a683e9eaf250f975cebeff2594f0
4
+ data.tar.gz: 7004c79a99fddd803d098f4ebabe5541d8ebfeae
5
5
  SHA512:
6
- metadata.gz: 0ad77664bf164a317b348a0af8822ec3532b2b688909fb216635abf7dacba6b70a185ee8cf8b53025972969ff7e96e42f1babd4a999d143a22700d1ca62b3f32
7
- data.tar.gz: d35e820982307478a88e09ccf0b7c2c35725123f429d9795466c3b7451a51e278e395c73f0b0710e2c8d38f81797da7653503da5d090efd9796f454bb4cc0c2e
6
+ metadata.gz: ccb77ec85e8d6b98b66315c53853088308ba74a760dabe638499717f597ccdf9c2dcf44378cb1c4c1a26cf35202963fc8116c6154d785581f7a43b7dee4e96f6
7
+ data.tar.gz: b76fbe65a0a186bb5f361ab251c9d514e20b020aa0ac0949dfacfcf832e8a997392c519c23e85615227599ec0ed97feeedf5facdd1372792692fa41a2fac69e9
data/.gitignore CHANGED
@@ -7,3 +7,5 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+
11
+ Guardfile
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.2'
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 regex to match and block which
24
- takes the method name, regex matches, and arguments and does something with it:
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(MethodFound.new(/\Asay_([a-z]+)/Z/) do |method_name, matches, *arguments|
29
- "#{matches[0]}!"
30
- end)
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 :regex
10
+ attr_reader :matcher
4
11
 
5
- def initialize(regex = nil, &intercept_block)
6
- define_method_missing(regex, &intercept_block) unless regex.nil?
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
- def define_method_missing(regex, &intercept_block)
10
- @regex = regex
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 = regex.match(method_name)
13
- instance_exec(method_name, matches, *arguments, &intercept_block)
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
- (method_name =~ regex) || super(method_name, include_private)
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}: #{regex.inspect}>"
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
@@ -1,3 +1,3 @@
1
1
  module MethodFound
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
data/lib/method_found.rb CHANGED
@@ -1,8 +1,11 @@
1
+ require "securerandom"
2
+
1
3
  require "method_found/version"
4
+ require "method_found/builder"
2
5
  require "method_found/interceptor"
3
6
 
4
7
  module MethodFound
5
8
  def self.new(*args, &block)
6
- Interceptor.new(*args, &block)
9
+ Builder.new(*args, &block)
7
10
  end
8
11
  end
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.2
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-18 00:00:00.000000000 Z
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