modifiers 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 +7 -0
- data/.gitignore +23 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +224 -0
- data/Rakefile +6 -0
- data/lib/modifiers.rb +4 -0
- data/lib/modifiers/command.rb +8 -0
- data/lib/modifiers/command_query.rb +2 -0
- data/lib/modifiers/define_modifier.rb +11 -0
- data/lib/modifiers/deprecated.rb +8 -0
- data/lib/modifiers/memoized.rb +12 -0
- data/lib/modifiers/method_invocation.rb +51 -0
- data/lib/modifiers/modification.rb +42 -0
- data/lib/modifiers/query.rb +7 -0
- data/lib/modifiers/version.rb +3 -0
- data/modifiers.gemspec +25 -0
- data/spec/modifiers/command_spec.rb +22 -0
- data/spec/modifiers/define_modifier_spec.rb +23 -0
- data/spec/modifiers/deprecated_spec.rb +62 -0
- data/spec/modifiers/memoized_spec.rb +31 -0
- data/spec/modifiers/query_spec.rb +52 -0
- data/spec/shared_examples_for_modifiers.rb +48 -0
- data/spec/spec_helper.rb +12 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c3373d11c2eeb3664593d7252441c0f6df25ae45
|
4
|
+
data.tar.gz: 0eafbed86fc457e0f52c461b00631b14436116fa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 40a95f0632d5a9c9ce6e68cc4b033791ce4333d4365653a26559a371f66742e195b44ae7cb2005acb36e632145aa1ace4c89e4b51c3de29e6c1c82d0e80273a1
|
7
|
+
data.tar.gz: cd2d9a41929e713c3a84f4b4e0457f2417c890b26e1b87a757839de4ca067ee041a304211516e9ba3fb6457b4b5daf18d27a724440793a58892962fbbd4c4a97
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
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
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
.ruby-version
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 TODO: Write your name
|
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,224 @@
|
|
1
|
+
## What is/are Modifiers?
|
2
|
+
|
3
|
+
`Modifiers` is a collection of method modifiers, and a way to make more.
|
4
|
+
|
5
|
+
_Method Modifiers_, obviously, are modifiers to methods. Specifically, in Ruby
|
6
|
+
terms, they are class methods which:
|
7
|
+
|
8
|
+
1. Take a symbol argument which names an instance method of the same class, and
|
9
|
+
2. Return the same symbol, but
|
10
|
+
3. Alter the named method in some way.
|
11
|
+
|
12
|
+
Ruby has shipped with four (4) modifiers since forever: the three access
|
13
|
+
modifiers (`public`, `private`, and `protected`), and `module_function`. This
|
14
|
+
library [adds a few others, and a facility for creating even more](#usage).
|
15
|
+
|
16
|
+
## Why is/are Modifiers?
|
17
|
+
|
18
|
+
DRYing up code sometimes involves smaller fragments of shared behavior than a
|
19
|
+
method. Here's an example you've probably read and written before:
|
20
|
+
```ruby
|
21
|
+
# old and busted
|
22
|
+
def count_ducks
|
23
|
+
@count_ducks ||= DuckFlock.all.map(&size).inject(0, &:+)
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
Why are you writing the characters 'count_ducks', in the _exact same order_
|
28
|
+
__two whole times__? If you already know how to implement memoization, why let
|
29
|
+
your code challenge you to prove it every time you want it done?! Instead,
|
30
|
+
implement it one final, flawless time, and tell the interpreter firmly, "No,
|
31
|
+
_you_ type the name twice, my time is far too valuable."
|
32
|
+
|
33
|
+
## Installation
|
34
|
+
|
35
|
+
Add this line to your application's Gemfile:
|
36
|
+
|
37
|
+
gem 'modifiers', require: false
|
38
|
+
|
39
|
+
And then execute:
|
40
|
+
|
41
|
+
$ bundle
|
42
|
+
|
43
|
+
## Usage
|
44
|
+
|
45
|
+
### built-in modifiers
|
46
|
+
|
47
|
+
#### deprecated
|
48
|
+
|
49
|
+
Sometimes there's a method, and you want it to die, but not a clean, swift
|
50
|
+
death. Instead, you wish it a slow, cursed strangulation, as collaborators
|
51
|
+
gradually abandon it. Mark it with your sign, that all may know to shun it or
|
52
|
+
be punished.
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
require 'modifiers/deprecated'
|
56
|
+
|
57
|
+
class BadHacks
|
58
|
+
extend Modifiers
|
59
|
+
|
60
|
+
deprecated def awful_method
|
61
|
+
# some ugly hack, probably involving define_method and ObjectSpace
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
A method modified by `deprecated` will issue a helpful deprecation warning
|
67
|
+
every time it is called. Something like: `deprecated method
|
68
|
+
BadHacks#awful_method called from app/controllers/ducks_controller.rb:782`
|
69
|
+
|
70
|
+
(Please note that the `deprecated` method is deprecated, and you should
|
71
|
+
definitely use `Gem.deprecate` instead.)
|
72
|
+
|
73
|
+
#### memoized
|
74
|
+
|
75
|
+
Every now and then, you will come to care how long it takes for a method to
|
76
|
+
run. You may find yourself wishing it just re-used some hard-won values,
|
77
|
+
rather than throwing them away and rebuilding them anew every time you call it.
|
78
|
+
|
79
|
+
To demonstrate this, on multiple levels, I will re-use the case from a previous
|
80
|
+
section.
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
require 'modifiers/memoized'
|
84
|
+
|
85
|
+
class DuckService
|
86
|
+
extend Modifiers
|
87
|
+
|
88
|
+
memoized def count_ducks
|
89
|
+
DuckFlock.all.map(&size).inject(0, &:+)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
A method modified by `memoized` will run once normally, per unique combination
|
95
|
+
of arguments, after which it will simply return the same result for the
|
96
|
+
lifetime of the receiving object. Dazzle your friends with your terse, yet
|
97
|
+
performant, fibonnaci implementations!
|
98
|
+
|
99
|
+
(If you want all this and more, you can use
|
100
|
+
[memoist](https://github.com/matthewrudy/memoist) (formerly
|
101
|
+
`ActiveSupport::Memoizable`) instead, but I warn you: it involves `eval`.)
|
102
|
+
|
103
|
+
#### commands and queries
|
104
|
+
|
105
|
+
You may have heard of 'Command-Query Separation`, and the claim that code
|
106
|
+
quality can be improved by writing methods to either have only side-effects, or
|
107
|
+
no side-effects.
|
108
|
+
|
109
|
+
It may or may not be a good idea, but at least now it's easy to unambiguously
|
110
|
+
indicate and enforce!
|
111
|
+
|
112
|
+
First, a method modified by `command` will always return nil. It's as trivial as it
|
113
|
+
sounds.
|
114
|
+
|
115
|
+
Conversely (?), a method modified by `query` will never change the state of
|
116
|
+
anything non-global and in-process. This is also trivial, but it might seem
|
117
|
+
more impressive.
|
118
|
+
```ruby
|
119
|
+
require 'modifiers/command_query'
|
120
|
+
|
121
|
+
class DuckFarmer < Struct.new(:hutches)
|
122
|
+
extend Modifiers
|
123
|
+
|
124
|
+
query def fullest_hutch
|
125
|
+
hutches.max { |h1,h2| h1.count_eggs - h2.count_eggs
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class DuckHutch < Struct.new(:num_eggs)
|
130
|
+
def self.count_eggs
|
131
|
+
@ducks_disturbed = true
|
132
|
+
num_eggs
|
133
|
+
end
|
134
|
+
|
135
|
+
def ducks_disturbed?
|
136
|
+
@ducks_disturbed
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
john = DuckFarmer.new(Array.new(3) { DuckHutch.new(rand(20)) })
|
141
|
+
|
142
|
+
john.fullest_hutch # => #<struct DuckHutch num_eggs=11>
|
143
|
+
|
144
|
+
john.hutches.any? { |h| h.ducks_disturbed? } # => false
|
145
|
+
```
|
146
|
+
|
147
|
+
If this was an infomercial, now is when I would say something like "It's just
|
148
|
+
that easy, Michael!", and you (your name is Michael in this scenario) would say
|
149
|
+
"Now _that's_ incredible!" and the audience would applaud.
|
150
|
+
|
151
|
+
I'm mildly proud of this library.
|
152
|
+
|
153
|
+
### defining new modifiers
|
154
|
+
|
155
|
+
New modifiers can be defined in your own modules using the `define_modifier` method.
|
156
|
+
|
157
|
+
Let's start with the simplest case: the null modifier, with a name, but no behavior.
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
require 'modifiers/define_modifier'
|
161
|
+
|
162
|
+
module DuckFarmModifiers
|
163
|
+
extend Modifiers
|
164
|
+
define_modifier(:duck)
|
165
|
+
end
|
166
|
+
|
167
|
+
class DuckFarm
|
168
|
+
extend DuckFarmModifiers
|
169
|
+
|
170
|
+
duck :some_method # => unchanged
|
171
|
+
end
|
172
|
+
```
|
173
|
+
|
174
|
+
Here's an identical implementation:
|
175
|
+
```ruby
|
176
|
+
module DuckFarmModifiers
|
177
|
+
define_modifier(:duck) do |method_invocation|
|
178
|
+
method_invocation.invoke
|
179
|
+
end
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
A block passed to `define_modifier` will become the new body of the methods
|
184
|
+
modified by the modifier, kinda like with `define_method`.
|
185
|
+
|
186
|
+
The argument passed to that block will be an object representing a particular
|
187
|
+
call to the modified method. As I showed you above, all you have to do
|
188
|
+
continue that call as normal is `#invoke` it.
|
189
|
+
|
190
|
+
Or not!
|
191
|
+
```ruby
|
192
|
+
module DuckFarmModifiers
|
193
|
+
define_modifier(:x) do
|
194
|
+
# temporarily disable a method and see if anyone notices
|
195
|
+
end
|
196
|
+
end
|
197
|
+
```
|
198
|
+
|
199
|
+
You can do things before, after, or even "around" the invocation.
|
200
|
+
```ruby
|
201
|
+
module DuckFarmModifiers
|
202
|
+
define_modifier(:perf_logged) do |invocation|
|
203
|
+
start = Time.now
|
204
|
+
invocation.invoke
|
205
|
+
Rails.logger "#{invocation.method_identifier} finished in #{Time.now - start}s"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
The method invocation object can also tell you the `#arguments` in the call,
|
211
|
+
and its `#location` in the source, the `#method_name` of the method which was
|
212
|
+
modified, or even the full `#method_identifier` in
|
213
|
+
`Class.class_method`/`Class#instance_method` style. All of the modifiers
|
214
|
+
included in the library were made using it.
|
215
|
+
|
216
|
+
What awesome ones will _you_ write?
|
217
|
+
|
218
|
+
## Contributing
|
219
|
+
|
220
|
+
1. [Fork it](https://github.com/nicknovitski/modifiers/fork)
|
221
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
222
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
223
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
224
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/lib/modifiers.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'modifiers/modification'
|
2
|
+
|
3
|
+
module Modifiers
|
4
|
+
module_function def define_modifier(name, &block)
|
5
|
+
define_method(name) do |sym|
|
6
|
+
mod = Modification.new(klass: self, method: sym)
|
7
|
+
define_method(sym) { |*args| mod.call(self, *args, &block) }
|
8
|
+
send(mod.original_visibility, sym)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'modifiers/define_modifier'
|
2
|
+
|
3
|
+
module Modifiers
|
4
|
+
define_modifier(:memoized) do |invocation|
|
5
|
+
ivar = "@#{invocation.method_name}".to_sym
|
6
|
+
|
7
|
+
instance_variable_set(ivar, {}) unless instance_variable_defined?(ivar)
|
8
|
+
|
9
|
+
memoizer = instance_variable_get(ivar)
|
10
|
+
memoizer.fetch(invocation.arguments) { memoizer[invocation.arguments] = invocation.invoke }
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Modifiers
|
2
|
+
class MethodInvocation
|
3
|
+
def initialize(method:, receiver:, arguments:)
|
4
|
+
@method = method
|
5
|
+
@receiver = receiver
|
6
|
+
@arguments = arguments
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :arguments
|
10
|
+
|
11
|
+
def invoke(context = receiver)
|
12
|
+
method.bind(context).call(*arguments)
|
13
|
+
end
|
14
|
+
|
15
|
+
def location
|
16
|
+
[file, line_no]
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_name
|
20
|
+
method.name
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_identifier
|
24
|
+
target + method_name.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def target
|
30
|
+
receiver.is_a?(Module) ? "#{receiver}." : "#{receiver.class}#"
|
31
|
+
end
|
32
|
+
|
33
|
+
def file
|
34
|
+
match_caller(/(.*?):/i)
|
35
|
+
end
|
36
|
+
|
37
|
+
def line_no
|
38
|
+
match_caller(/:(\d+).*?$/i)
|
39
|
+
end
|
40
|
+
|
41
|
+
def match_caller(regex)
|
42
|
+
method_caller.match(regex).to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
def method_caller
|
46
|
+
caller[7]
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_reader :method, :receiver
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'modifiers/method_invocation'
|
2
|
+
|
3
|
+
module Modifiers
|
4
|
+
class Modification
|
5
|
+
def initialize(klass:, method:)
|
6
|
+
@klass = klass
|
7
|
+
@original_method = klass.send(:instance_method, method)
|
8
|
+
@original_visibility = visibility_on(klass)
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :original_visibility
|
12
|
+
|
13
|
+
def call(instance, *args, &block)
|
14
|
+
invocation = MethodInvocation.new(method: original_method, receiver: instance, arguments: args)
|
15
|
+
if block
|
16
|
+
instance.instance_exec(invocation, &block)
|
17
|
+
else
|
18
|
+
invocation.invoke
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :original_method, :klass
|
25
|
+
|
26
|
+
def visibility_on(klass)
|
27
|
+
if klass.private_method_defined?(original_method.name)
|
28
|
+
:private
|
29
|
+
elsif klass.protected_method_defined?(original_method.name)
|
30
|
+
:protected
|
31
|
+
else
|
32
|
+
:public
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def instance
|
37
|
+
klass.new
|
38
|
+
rescue TypeError # klass is a singleton metaclass
|
39
|
+
ObjectSpace.each_object(klass).first
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/modifiers.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 'modifiers/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'modifiers'
|
8
|
+
spec.version = Modifiers::VERSION
|
9
|
+
spec.authors = ['Nick Novitski']
|
10
|
+
spec.email = ['nicknovitski@gmail.com']
|
11
|
+
spec.summary = 'Cute and Easy method modifiers (also called decorators)'
|
12
|
+
spec.description = 'A simple and composable way to add functionality to methods.'
|
13
|
+
spec.homepage = 'https://github.com/nicknovitski/modifiers'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.test_files = spec.files.grep(/^spec\//)
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.required_ruby_version = '>= 2.1.0'
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
23
|
+
spec.add_development_dependency 'rake'
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'modifiers/command'
|
3
|
+
|
4
|
+
RSpec.describe Modifiers do
|
5
|
+
class Doer
|
6
|
+
extend Modifiers
|
7
|
+
command def take_action
|
8
|
+
# lots of side-effectful-operations
|
9
|
+
2 + 2
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
subject(:doer) { Doer.new }
|
14
|
+
|
15
|
+
describe '#command' do
|
16
|
+
it_behaves_like 'a modifier', :command, changes_return_value: true
|
17
|
+
|
18
|
+
it 'causes the method to always return nil' do
|
19
|
+
expect(doer.take_action).to be_nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'modifiers/define_modifier'
|
3
|
+
|
4
|
+
module MyModifier
|
5
|
+
extend Modifiers
|
6
|
+
define_modifier(:bro) { |i| i.invoke + ', bro' }
|
7
|
+
end
|
8
|
+
|
9
|
+
class Foo
|
10
|
+
extend MyModifier
|
11
|
+
|
12
|
+
bro def hello(name = 'world')
|
13
|
+
"hello, #{name}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
RSpec.describe Modifiers do
|
18
|
+
describe '.define_modifier' do
|
19
|
+
it 'can make new modifiers in new modules' do
|
20
|
+
expect(Foo.new.hello).to eq('hello, world, bro')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'modifiers/deprecated'
|
3
|
+
|
4
|
+
RSpec.describe Modifiers do
|
5
|
+
describe '#deprecated' do
|
6
|
+
it_behaves_like 'a modifier', :deprecated
|
7
|
+
|
8
|
+
let(:test_class) do
|
9
|
+
class Test
|
10
|
+
extend Modifiers
|
11
|
+
|
12
|
+
deprecated def method
|
13
|
+
# something you shouldn't be using
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
subject(:test_instance) { test_class.new }
|
18
|
+
|
19
|
+
around(:example) do |example|
|
20
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
21
|
+
example.call
|
22
|
+
$VERBOSE = old_verbose
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'changes the method to warn it has been deprecated' do
|
26
|
+
expect(subject).to receive(:warn).with(/deprecated/)
|
27
|
+
|
28
|
+
subject.method
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'includes the name of the method in the warning' do
|
32
|
+
expect(subject).to receive(:warn).with(/Test\#method/)
|
33
|
+
|
34
|
+
subject.method
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'includes the location of the call in the warning' do
|
38
|
+
expect(subject).to receive(:warn).with(/deprecated_spec.rb/)
|
39
|
+
|
40
|
+
subject.method
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'class methods' do
|
44
|
+
before do
|
45
|
+
class Test
|
46
|
+
class << self
|
47
|
+
extend Modifiers
|
48
|
+
deprecated def class_method
|
49
|
+
#
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'names the method correctly in the warning' do
|
56
|
+
expect(test_class).to receive(:warn).with(/Test\.class_method/)
|
57
|
+
|
58
|
+
test_class.class_method
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'modifiers/memoized'
|
3
|
+
|
4
|
+
RSpec.describe Modifiers do
|
5
|
+
class Service
|
6
|
+
def self.expensive_operation
|
7
|
+
'done'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Client
|
12
|
+
extend Modifiers
|
13
|
+
|
14
|
+
memoized def call_service
|
15
|
+
Service.expensive_operation
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
subject(:client) { Client.new }
|
20
|
+
|
21
|
+
describe '#memoized' do
|
22
|
+
it_behaves_like 'a modifier', :memoized
|
23
|
+
|
24
|
+
it 'prevents execution after the first time' do
|
25
|
+
expect(Service).to receive(:expensive_operation).once
|
26
|
+
|
27
|
+
subject.call_service
|
28
|
+
subject.call_service
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'modifiers/query'
|
3
|
+
|
4
|
+
RSpec.describe Modifiers do
|
5
|
+
class Test
|
6
|
+
extend Modifiers
|
7
|
+
|
8
|
+
def initialize(sub_test = nil)
|
9
|
+
@sub_test = sub_test
|
10
|
+
@enum = []
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :memo, :enum
|
14
|
+
|
15
|
+
query def fooer?
|
16
|
+
@memo ||= true
|
17
|
+
end
|
18
|
+
|
19
|
+
query def insertable?(val)
|
20
|
+
@enum << val
|
21
|
+
end
|
22
|
+
|
23
|
+
query def child_status
|
24
|
+
@sub_test.status
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class SubTest
|
29
|
+
def status
|
30
|
+
@status ||= :awake
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
let(:child) { SubTest.new }
|
35
|
+
subject(:instance) { Test.new(child) }
|
36
|
+
|
37
|
+
describe '#query' do
|
38
|
+
it_behaves_like 'a modifier', :query
|
39
|
+
|
40
|
+
it "prevents the method from changing the object's instance variables" do
|
41
|
+
expect { subject.fooer? }.not_to change { subject.memo }
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'prevents the method from altering arrays' do
|
45
|
+
expect { subject.insertable?(:foo) }.not_to change { subject.enum }
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'prevents the method from altering linked objects' do
|
49
|
+
expect { instance.child_status }.not_to change { child.instance_variables }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
RSpec.shared_examples 'a modifier' do |modifier, changes_return_value: false|
|
2
|
+
let(:test_class) do
|
3
|
+
klass = Class.new do
|
4
|
+
extend Modifiers
|
5
|
+
|
6
|
+
def public_method(arg = :foo)
|
7
|
+
arg
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def protected_method
|
13
|
+
#
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def private_method
|
19
|
+
#
|
20
|
+
end
|
21
|
+
end
|
22
|
+
Object.const_set(random_class_name, klass)
|
23
|
+
end
|
24
|
+
let(:random_class_name) { (0...10).map { ('A'..'Z').to_a[rand(26)] }.join }
|
25
|
+
subject(:instance) { test_class.new }
|
26
|
+
|
27
|
+
unless changes_return_value
|
28
|
+
it 'does not change the return value of the method' do
|
29
|
+
test_class.send(modifier, :public_method)
|
30
|
+
expect(instance.public_method).to be :foo
|
31
|
+
expect(instance.public_method(:bar)).to be :bar
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'does not change the visibility of a private method' do
|
36
|
+
test_class.send(modifier, :private_method)
|
37
|
+
expect(instance.private_methods).to include(:private_method)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'does not change the visibility of a protected method' do
|
41
|
+
test_class.send(modifier, :protected_method)
|
42
|
+
expect(instance.protected_methods).to include(:protected_method)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'does not increase the number of methods on the target' do
|
46
|
+
expect { test_class.send(modifier, :public_method) }.not_to change { test_class.methods }
|
47
|
+
end
|
48
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
2
|
+
$LOAD_PATH << File.join(APP_ROOT, 'lib')
|
3
|
+
|
4
|
+
require 'shared_examples_for_modifiers'
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
config.disable_monkey_patching!
|
8
|
+
|
9
|
+
config.mock_with :rspec do |mocks|
|
10
|
+
mocks.verify_partial_doubles = true
|
11
|
+
end
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: modifiers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nick Novitski
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-19 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.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
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
|
+
description: A simple and composable way to add functionality to methods.
|
56
|
+
email:
|
57
|
+
- nicknovitski@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- lib/modifiers.rb
|
68
|
+
- lib/modifiers/command.rb
|
69
|
+
- lib/modifiers/command_query.rb
|
70
|
+
- lib/modifiers/define_modifier.rb
|
71
|
+
- lib/modifiers/deprecated.rb
|
72
|
+
- lib/modifiers/memoized.rb
|
73
|
+
- lib/modifiers/method_invocation.rb
|
74
|
+
- lib/modifiers/modification.rb
|
75
|
+
- lib/modifiers/query.rb
|
76
|
+
- lib/modifiers/version.rb
|
77
|
+
- modifiers.gemspec
|
78
|
+
- spec/modifiers/command_spec.rb
|
79
|
+
- spec/modifiers/define_modifier_spec.rb
|
80
|
+
- spec/modifiers/deprecated_spec.rb
|
81
|
+
- spec/modifiers/memoized_spec.rb
|
82
|
+
- spec/modifiers/query_spec.rb
|
83
|
+
- spec/shared_examples_for_modifiers.rb
|
84
|
+
- spec/spec_helper.rb
|
85
|
+
homepage: https://github.com/nicknovitski/modifiers
|
86
|
+
licenses:
|
87
|
+
- MIT
|
88
|
+
metadata: {}
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: 2.1.0
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 2.2.2
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: Cute and Easy method modifiers (also called decorators)
|
109
|
+
test_files:
|
110
|
+
- spec/modifiers/command_spec.rb
|
111
|
+
- spec/modifiers/define_modifier_spec.rb
|
112
|
+
- spec/modifiers/deprecated_spec.rb
|
113
|
+
- spec/modifiers/memoized_spec.rb
|
114
|
+
- spec/modifiers/query_spec.rb
|
115
|
+
- spec/shared_examples_for_modifiers.rb
|
116
|
+
- spec/spec_helper.rb
|