adornable 1.0.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +66 -7
- data/.rubocop.yml +96 -0
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/README.md +342 -12
- data/Rakefile +3 -1
- data/adornable.gemspec +20 -13
- data/bin/console +1 -0
- data/lib/adornable.rb +13 -6
- data/lib/adornable/context.rb +23 -0
- data/lib/adornable/decorators.rb +29 -14
- data/lib/adornable/error.rb +2 -0
- data/lib/adornable/machinery.rb +39 -12
- data/lib/adornable/utils.rb +12 -1
- data/lib/adornable/version.rb +3 -1
- metadata +91 -13
- data/Gemfile.lock +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b916559874ffdc52baa8f1b2a75a7e550ad600c501d45adeaf4c53aa0796c97
|
4
|
+
data.tar.gz: b7fd7d3eeaaae6b706fbe9f9b9f0d24770351a5ebbbd18759bd96d014466b001
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3d49e5efb60aec78d2b416c6d36c8c02844151a6824503f9301db45a4cd392ccea9f316500723e84a07d7ecb200b86a31c71bbc571e3294adfa637a7b92956f
|
7
|
+
data.tar.gz: a97a7142ccc356e8657421e32de71f30c6a9070e73a618e5fbaa238ae2b7e3e7193ce11782a942f75b0606d14af54f79a4fd772de83526f200c2da504f353140
|
data/.gitignore
CHANGED
@@ -1,11 +1,70 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
# generic stuff
|
2
|
+
.env
|
3
|
+
*.gem
|
4
|
+
*.rbc
|
5
|
+
log/*.log
|
6
|
+
/.config
|
7
|
+
/InstalledFiles
|
6
8
|
/pkg/
|
7
|
-
/spec/reports/
|
8
9
|
/tmp/
|
9
10
|
|
10
|
-
#
|
11
|
+
# testy stuff
|
12
|
+
.rspec
|
11
13
|
.rspec_status
|
14
|
+
*.orig
|
15
|
+
/coverage/
|
16
|
+
/coverage/
|
17
|
+
/db/*.sqlite3
|
18
|
+
/db/*.sqlite3-[0-9]*
|
19
|
+
/db/*.sqlite3-journal
|
20
|
+
/public/system
|
21
|
+
/spec/examples.txt
|
22
|
+
/spec/reports/
|
23
|
+
/spec/tmp
|
24
|
+
/test/tmp/
|
25
|
+
/test/version_tmp/
|
26
|
+
capybara-*.html
|
27
|
+
pickle-email-*.html
|
28
|
+
rerun.txt
|
29
|
+
test/dummy/db/*.sqlite3
|
30
|
+
test/dummy/db/*.sqlite3-journal
|
31
|
+
test/dummy/log/*.log
|
32
|
+
test/dummy/node_modules/
|
33
|
+
test/dummy/storage/
|
34
|
+
test/dummy/tmp/
|
35
|
+
test/dummy/yarn-error.log
|
36
|
+
|
37
|
+
# debuggy stuff
|
38
|
+
.byebug_history
|
39
|
+
|
40
|
+
## doccy stuff
|
41
|
+
/.yardoc/
|
42
|
+
/_yardoc/
|
43
|
+
/doc/
|
44
|
+
/rdoc/
|
45
|
+
|
46
|
+
## bundly stuff
|
47
|
+
/.bundle/
|
48
|
+
/vendor/bundle
|
49
|
+
/lib/bundler/man/
|
50
|
+
|
51
|
+
# "for a library or gem, you might want to ignore these files since the code is
|
52
|
+
# intended to run in multiple environments"
|
53
|
+
Gemfile.lock
|
54
|
+
.ruby-version
|
55
|
+
.ruby-gemset
|
56
|
+
|
57
|
+
# rvmmy stuff
|
58
|
+
.rvmrc
|
59
|
+
|
60
|
+
# editory stuff
|
61
|
+
.idea
|
62
|
+
.vscode
|
63
|
+
*.rdb
|
64
|
+
|
65
|
+
# systemy stuff
|
66
|
+
*.swm
|
67
|
+
*.swn
|
68
|
+
*.swo
|
69
|
+
*.swp
|
70
|
+
*.DS_Store
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-performance
|
3
|
+
- rubocop-rspec
|
4
|
+
- rubocop-rake
|
5
|
+
|
6
|
+
# Globals
|
7
|
+
|
8
|
+
AllCops:
|
9
|
+
NewCops: enable
|
10
|
+
|
11
|
+
# Layout
|
12
|
+
|
13
|
+
Layout/LineLength:
|
14
|
+
Max: 120
|
15
|
+
Exclude:
|
16
|
+
- 'spec/**/*_spec.rb'
|
17
|
+
|
18
|
+
Layout/EndAlignment:
|
19
|
+
EnforcedStyleAlignWith: variable
|
20
|
+
|
21
|
+
Layout/FirstArrayElementIndentation:
|
22
|
+
EnforcedStyle: consistent
|
23
|
+
|
24
|
+
# Metrics
|
25
|
+
|
26
|
+
Metrics/AbcSize:
|
27
|
+
CountRepeatedAttributes: false
|
28
|
+
Exclude:
|
29
|
+
- 'spec/**/*_spec.rb'
|
30
|
+
|
31
|
+
Metrics/BlockLength:
|
32
|
+
Exclude:
|
33
|
+
- 'spec/**/*_spec.rb'
|
34
|
+
|
35
|
+
Metrics/ClassLength:
|
36
|
+
Max: 150
|
37
|
+
CountComments: false
|
38
|
+
CountAsOne:
|
39
|
+
- array
|
40
|
+
- hash
|
41
|
+
- heredoc
|
42
|
+
Exclude:
|
43
|
+
- 'spec/**/*_spec.rb'
|
44
|
+
|
45
|
+
Metrics/MethodLength:
|
46
|
+
Max: 20
|
47
|
+
CountComments: false
|
48
|
+
CountAsOne:
|
49
|
+
- array
|
50
|
+
- hash
|
51
|
+
- heredoc
|
52
|
+
|
53
|
+
Metrics/ModuleLength:
|
54
|
+
Max: 150
|
55
|
+
CountComments: false
|
56
|
+
CountAsOne:
|
57
|
+
- array
|
58
|
+
- hash
|
59
|
+
- heredoc
|
60
|
+
Exclude:
|
61
|
+
- 'spec/**/*_spec.rb'
|
62
|
+
|
63
|
+
# Rspec
|
64
|
+
|
65
|
+
RSpec/ExampleLength:
|
66
|
+
Max: 25
|
67
|
+
|
68
|
+
RSpec/MessageSpies:
|
69
|
+
Enabled: false
|
70
|
+
|
71
|
+
RSpec/MultipleExpectations:
|
72
|
+
Enabled: false
|
73
|
+
|
74
|
+
RSpec/NestedGroups:
|
75
|
+
Max: 10
|
76
|
+
|
77
|
+
# Style
|
78
|
+
|
79
|
+
Style/DoubleNegation:
|
80
|
+
Enabled: false
|
81
|
+
|
82
|
+
Style/ExpandPathArguments:
|
83
|
+
Exclude:
|
84
|
+
- 'adornable.gemspec'
|
85
|
+
|
86
|
+
Style/StringLiterals:
|
87
|
+
Enabled: false
|
88
|
+
|
89
|
+
Style/TrailingCommaInArguments:
|
90
|
+
EnforcedStyleForMultiline: consistent_comma
|
91
|
+
|
92
|
+
Style/TrailingCommaInArrayLiteral:
|
93
|
+
EnforcedStyleForMultiline: consistent_comma
|
94
|
+
|
95
|
+
Style/TrailingCommaInHashLiteral:
|
96
|
+
EnforcedStyleForMultiline: consistent_comma
|
data/Gemfile
CHANGED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 Keegan Leitz
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -1,35 +1,365 @@
|
|
1
1
|
# Adornable
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
Adornable provides the ability to cleanly decorate methods in Ruby. You can make and use your own decorators, and you can also use some of the built-in ones that the gem provides. _Decorating_ methods is as simple as slapping a `decorate :some_decorator` above your method definition. _Defining_ decorators can be as simple as defining a method that yields to a block, or as complex as manipulating the decorated method's receiver and arguments, and/or changing the functionality of the decorator based on custom options supplied to it when initially applying the decorator.
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
9
|
-
|
7
|
+
### Locally (to your application)
|
8
|
+
|
9
|
+
Add the gem to your application's `Gemfile`:
|
10
10
|
|
11
11
|
```ruby
|
12
12
|
gem 'adornable'
|
13
13
|
```
|
14
14
|
|
15
|
-
|
15
|
+
...and then run:
|
16
|
+
|
17
|
+
```bash
|
18
|
+
bundle install
|
19
|
+
```
|
16
20
|
|
17
|
-
|
21
|
+
### Globally (to your system)
|
18
22
|
|
19
|
-
|
23
|
+
Alternatively, install it globally:
|
20
24
|
|
21
|
-
|
25
|
+
```bash
|
26
|
+
gem install adornable
|
27
|
+
```
|
22
28
|
|
23
29
|
## Usage
|
24
30
|
|
25
|
-
|
31
|
+
### The basics
|
32
|
+
|
33
|
+
Think of a decorator as if it's just a wrapper function. You want something to happen before, around, or after a method is called, in a reusable (but dynamic) way? Maybe you want to print to a log whenever a certain method is called, or memoize its result so that additional calls don't have to re-execute the body of the method. You've tried this:
|
34
|
+
|
35
|
+
```rb
|
36
|
+
class RandomValueGenerator
|
37
|
+
def value
|
38
|
+
# logging the method call
|
39
|
+
puts "Calling method `RandomValueGenerator#value` with no arguments"
|
40
|
+
# memoizing the result
|
41
|
+
@value ||= rand
|
42
|
+
end
|
43
|
+
|
44
|
+
def values(max)
|
45
|
+
# logging the method call
|
46
|
+
puts "Calling method `RandomValueGenerator#values` with arguments `[#{max}]`"
|
47
|
+
# memoizing the result
|
48
|
+
@values ||= {}
|
49
|
+
@values[max] ||= (1..max).map { rand }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
random_value_generator = RandomValueGenerator.new
|
54
|
+
|
55
|
+
values1 = random_value_generator.values(1000)
|
56
|
+
# Calling method `RandomValueGenerator#values` with arguments `[1000]`
|
57
|
+
#=> [0.7044444114998132, 0.401953296596267, 0.3023797513191562, ...]
|
58
|
+
|
59
|
+
values1 = random_value_generator.values(1000)
|
60
|
+
# Calling method `RandomValueGenerator#values` with arguments `[1000]`
|
61
|
+
#=> [0.7044444114998132, 0.401953296596267, 0.3023797513191562, ...]
|
62
|
+
|
63
|
+
values3 = random_value_generator.values(5000)
|
64
|
+
# Calling method `RandomValueGenerator#values` with arguments `[5000]`
|
65
|
+
#=> [0.9916088057511011, 0.04466750434972333, 0.6073659341272127]
|
66
|
+
|
67
|
+
value1 = random_value_generator.value
|
68
|
+
# Calling method `RandomValueGenerator#value` with no arguments
|
69
|
+
#=> 0.4196007135344746
|
70
|
+
|
71
|
+
value2 = random_value_generator.value
|
72
|
+
# Calling method `RandomValueGenerator#value` with no arguments
|
73
|
+
#=> 0.4196007135344746
|
74
|
+
```
|
75
|
+
|
76
|
+
However, you have a million more methods to write, and if you refactor, you'll have to screw around with a slew of method definitions across your app.
|
77
|
+
|
78
|
+
What if you could do this, instead, to achieve the same result?
|
79
|
+
|
80
|
+
```rb
|
81
|
+
class RandomValueGenerator
|
82
|
+
extend Adornable
|
83
|
+
|
84
|
+
decorate :log
|
85
|
+
decorate :memoize
|
86
|
+
def value
|
87
|
+
rand
|
88
|
+
end
|
89
|
+
|
90
|
+
decorate :log
|
91
|
+
decorate :memoize
|
92
|
+
def values(max)
|
93
|
+
(1..max).map { rand }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
Nice, right?
|
99
|
+
|
100
|
+
> **Note:** in the case of multiple decorators decorating a method, each is executed from top to bottom.
|
101
|
+
|
102
|
+
### Adding decorator functionality
|
103
|
+
|
104
|
+
Add the `::decorate` macro to your classes by `extend`-ing `Adornable`:
|
105
|
+
|
106
|
+
```rb
|
107
|
+
class Foo
|
108
|
+
extend Adornable
|
109
|
+
|
110
|
+
# ...
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
### Decorating methods
|
115
|
+
|
116
|
+
Use the `decorate` macro to decorate methods.
|
117
|
+
|
118
|
+
#### Using built-in decorators
|
119
|
+
|
120
|
+
There are a couple of built-in decorators for common use-cases (these can be overridden if you so choose):
|
121
|
+
|
122
|
+
```rb
|
123
|
+
class Foo
|
124
|
+
extend Adornable
|
125
|
+
|
126
|
+
decorate :log
|
127
|
+
def some_method
|
128
|
+
# the method name (Foo#some_method) and arguments will be logged
|
129
|
+
end
|
130
|
+
|
131
|
+
decorate :memoize
|
132
|
+
def some_other_method
|
133
|
+
# the return value will be cached
|
134
|
+
end
|
135
|
+
|
136
|
+
decorate :memoize
|
137
|
+
def yet_another_method(some_arg, some_other_arg = true, key_word_arg:, key_word_arg_with_default: 123)
|
138
|
+
# the return value will be cached based on the arguments the method receives
|
139
|
+
end
|
140
|
+
|
141
|
+
decorate :log
|
142
|
+
decorate :memoize, for_any_arguments: true
|
143
|
+
def oh_boy_another_method(some_arg, some_other_arg = true, key_word_arg:, key_word_arg_with_default: 123)
|
144
|
+
# the method name (Foo#oh_boy_another_method) and arguments will be logged
|
145
|
+
# the return value will be cached regardless of the arguments received
|
146
|
+
end
|
147
|
+
|
148
|
+
decorate :log
|
149
|
+
def self.yeah_it_works_on_class_methods_too
|
150
|
+
# the method name (Foo::yeah_it_works_on_class_methods_too) and arguments
|
151
|
+
# will be logged
|
152
|
+
end
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
- `decorate :log` logs the method name and any passed arguments to the console
|
157
|
+
- `decorate :memoize` caches the result of the first call and returns that initial result (and does not execute the method again) for any additional calls. By default, it namespaces the cache by the arguments passed to the method, so it will re-compute only if the arguments change; if the arguments are the same as any previous time the method was called, it will return the cached result instead.
|
158
|
+
- pass the `for_any_arguments: true` option (e.g., `decorate :memoize, for_any_arguments: true`) to ignore the arguments in the caching process and simply memoize the result no matter what
|
159
|
+
|
160
|
+
> **Note:** in the case of multiple decorators decorating a method, each is executed from top to bottom.
|
161
|
+
|
162
|
+
#### Writing custom decorators and using them _explicitly_
|
163
|
+
|
164
|
+
You can reference any decorator method you write, like so:
|
165
|
+
|
166
|
+
```rb
|
167
|
+
class FooDecorators
|
168
|
+
# Note: this is defined as a CLASS method, but it can be applied to both class
|
169
|
+
# and instance methods. The only difference is in how you source the
|
170
|
+
# decorator when doing the decoration; see below for more info.
|
171
|
+
def self.blast_it(context)
|
172
|
+
puts "Blasting it!"
|
173
|
+
value = yield
|
174
|
+
"#{value}!"
|
175
|
+
end
|
176
|
+
|
177
|
+
# Note: this is defined as an INSTANCE method, but it can be applied to both
|
178
|
+
# class and instance methods. The only difference is in how you source
|
179
|
+
# the decorator when doing the decoration; see below for more info.
|
180
|
+
def wait_for_it(context, dot_count: 3)
|
181
|
+
ellipsis = dot_count.times.map { '.' }.join
|
182
|
+
puts "Waiting for it#{ellipsis}"
|
183
|
+
value = yield
|
184
|
+
"#{value}#{ellipsis}"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class Foo
|
189
|
+
extend Adornable
|
190
|
+
|
191
|
+
# Note: `from: FooDecorators` references a class (and will look for the
|
192
|
+
# `::blast_it` method on that class)
|
193
|
+
decorate :blast_it, from: FooDecorators
|
194
|
+
def some_method
|
195
|
+
"haha I'm a method"
|
196
|
+
end
|
197
|
+
|
198
|
+
# Note: `from: FooDecorators.new` references an instance (and will look for
|
199
|
+
# the `#wait_for_it` method on that instance)
|
200
|
+
decorate :wait_for_it, from: FooDecorators.new
|
201
|
+
def other_method
|
202
|
+
"haha I'm another method"
|
203
|
+
end
|
204
|
+
|
205
|
+
decorate :log
|
206
|
+
def yet_another_method(foo, bar:)
|
207
|
+
"haha I'm yet another method"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
foo = Foo.new
|
212
|
+
|
213
|
+
foo.some_method
|
214
|
+
#=> "haha I'm a method!" # Note the exclamation mark
|
215
|
+
|
216
|
+
foo.other_method
|
217
|
+
#=> "haha I'm another method..." # Note the ellipsis
|
218
|
+
|
219
|
+
foo.yet_another_method(123, bloop: "bleep")
|
220
|
+
# Calling method `Foo#yet_another_method` with arguments `[123, {:bloop=>"bleep"}]`
|
221
|
+
#=> "haha I'm yet another method"
|
222
|
+
```
|
223
|
+
|
224
|
+
Use the `from:` option to specify what should receive the decorator method. Keep in mind that the decorator method will be called on the thing specified by `from:`... so, if you provide a class, it better be a class method on that thing, and if you supply an instance, it better be an instance method on that thing.
|
225
|
+
|
226
|
+
Every custom decorator method that you define must take one required argument (`context`) and any number of keyword arguments. It should also `yield` (or take a block argument and invoke it) at some point in the body of the method. The point at which you `yield` will be the point at which the decorated method will execute (or, if there are multiple decorators on the method, each following decorator will be invoked until the decorators have been exhausted and the decorated method is finally executed).
|
227
|
+
|
228
|
+
##### The required argument (`context`)
|
229
|
+
|
230
|
+
The **required argument** is an instance of `Adornable::Context`, which has some useful information about the decorated method being called
|
231
|
+
|
232
|
+
- `Adornable::Context#method_name`: the name of the decorated method being called (a symbol; e.g., `:some_method` or `:other_method`)
|
233
|
+
- `Adornable::Context#method_receiver`: the actual object that the decorated method (the `#method_name`) belongs to/is being called on (an object/class; e.g., the class `Foo` if it's a decorated class method, or an instance of `Foo` if it's a decorated instance method)
|
234
|
+
- `Adornable::Context#method_arguments`: an array of arguments passed to the decorated method, including keyword arguments as a final hash (e.g., if `:yet_another_method` was called like `Foo.new.yet_another_method(123, bar: true)` then `arguments` would be `[123, {:bar=>true}]`)
|
235
|
+
|
236
|
+
##### Custom keyword arguments (optional)
|
237
|
+
|
238
|
+
The **optional keyword arguments** are any parameters you want to be able to pass to the decorator method when decorating a method with `::decorate`:
|
239
|
+
|
240
|
+
- If you define a decorator like `def self.some_decorator(context)` then it takes no options when it is used: `decorate :some_decorator`.
|
241
|
+
- If you define a decorator like `def self.some_decorator(context, some_option:)` then it takes one _required_ keyword argument when it is used: `decorate :some_decorator, some_option: 123` (so that `::some_decorator` will receive `123` as the `some_option` parameter every time the decorated method is called). You can customize functionality of the decorator this way.
|
242
|
+
- Similarly, if you define a decorator like `def self.some_decorator(context, some_option: 456)`, then it takes one _optional_ keyword argument when it is used: `decorate :some_decorator` is valid (and implies `some_option: 456` since it has a default), and `decorate :some_decorator, some_option: 789` is valid as well.
|
243
|
+
|
244
|
+
##### Yielding to the next decorator/decorated method
|
245
|
+
|
246
|
+
Every decorator method **should also probably `yield`** at some point in the method body. I say _"should"_ because, technically, you don't have to, but if you don't then the original method will never be called. That's a valid use-case, but 99% of the time you're gonna want to `yield`.
|
247
|
+
|
248
|
+
> **Note:** the return value of your decorator **will replace the return value of the decorated method,** so _also_ you should probably return whatever value `yield` returned. Again, it is a valid use case to return something _else,_ but 99% of the time you probably want to return the value returned by the wrapped method.
|
249
|
+
>
|
250
|
+
> A contrived example of when you might want to muck around with the return value:
|
251
|
+
>
|
252
|
+
> ```rb
|
253
|
+
> class FooDecorators
|
254
|
+
> def self.coerce_to_int(context)
|
255
|
+
> value = yield
|
256
|
+
> new_value = value.strip.to_i
|
257
|
+
> puts "New value: #{value.inspect} (class: #{value.class})"
|
258
|
+
> new_value
|
259
|
+
> end
|
260
|
+
> end
|
261
|
+
>
|
262
|
+
> class Foo
|
263
|
+
> extend Adornable
|
264
|
+
>
|
265
|
+
> decorate :coerce_to_int, from: FooDecorators
|
266
|
+
> def get_number_from_user
|
267
|
+
> print "Enter a number: "
|
268
|
+
> value = gets
|
269
|
+
> puts "Value: #{value.inspect} (class: #{value.class})"
|
270
|
+
> value
|
271
|
+
> end
|
272
|
+
> end
|
273
|
+
>
|
274
|
+
> foo = Foo.new
|
275
|
+
>
|
276
|
+
> foo.get_number_from_user
|
277
|
+
> # Enter a number
|
278
|
+
> # > 123
|
279
|
+
> # Value: "123" (class: String)
|
280
|
+
> # New value: 123 (class: Integer)
|
281
|
+
> #=> 123
|
282
|
+
> ```
|
283
|
+
|
284
|
+
#### Writing custom decorators and using them _implicitly_
|
285
|
+
|
286
|
+
You can also register decorator receivers so that you don't have to reference them with the `from:` option:
|
287
|
+
|
288
|
+
```rb
|
289
|
+
class FooDecorators
|
290
|
+
def self.blast_it(context)
|
291
|
+
puts "Blasting it!"
|
292
|
+
value = yield
|
293
|
+
"#{value}!"
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
class MoreFooDecorators
|
298
|
+
def wait_for_it(context, dot_count: 3)
|
299
|
+
ellipsis = dot_count.times.map { '.' }.join
|
300
|
+
puts "Waiting for it#{ellipsis}"
|
301
|
+
value = yield
|
302
|
+
"#{value}#{ellipsis}"
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
class Foo
|
307
|
+
extend Adornable
|
308
|
+
|
309
|
+
add_decorators_from FooDecorators
|
310
|
+
add_decorators_from MoreFooDecorators.new
|
311
|
+
|
312
|
+
decorate :blast_it
|
313
|
+
decorate :wait_for_it, dot_count: 9
|
314
|
+
def some_method
|
315
|
+
"haha I'm a method"
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
foo = Foo.new
|
320
|
+
|
321
|
+
foo.some_method
|
322
|
+
# Blasting it!
|
323
|
+
# Waiting for it.........
|
324
|
+
#=> "haha I'm a method!........."
|
325
|
+
```
|
326
|
+
|
327
|
+
> **Note:** All the rest of the stuff from the previous section (using decorators explicitly) also applies here (using decorators implicitly).
|
328
|
+
|
329
|
+
> **Note:** In the case of duplicate decorator methods, later receivers registered with `::add_decorators_from` will override any decorators by the same name from earlier registered receivers.
|
330
|
+
|
331
|
+
> **Note:** in the case of multiple decorators decorating a method, each is executed from top to bottom; i.e., the top wraps the next, which wraps the next, and so on, until the method itself is wrapped.
|
26
332
|
|
27
333
|
## Development
|
28
334
|
|
29
|
-
|
335
|
+
### Install dependencies
|
336
|
+
|
337
|
+
```bash
|
338
|
+
bin/setup
|
339
|
+
```
|
340
|
+
|
341
|
+
### Run the tests
|
342
|
+
|
343
|
+
```bash
|
344
|
+
rake spec
|
345
|
+
```
|
346
|
+
|
347
|
+
### Run the linter
|
348
|
+
|
349
|
+
```bash
|
350
|
+
rubocop
|
351
|
+
```
|
30
352
|
|
31
|
-
|
353
|
+
### Create release
|
354
|
+
|
355
|
+
```
|
356
|
+
rake release
|
357
|
+
```
|
32
358
|
|
33
359
|
## Contributing
|
34
360
|
|
35
|
-
Bug reports and pull requests are welcome
|
361
|
+
Bug reports and pull requests for this project are welcome at its [GitHub page](https://github.com/kjleitz/adornable). If you choose to contribute, please be nice so I don't have to run out of bubblegum, etc.
|
362
|
+
|
363
|
+
## License
|
364
|
+
|
365
|
+
This project is open source, under the terms of the [MIT license.](https://github.com/kjleitz/adornable/blob/master/LICENSE)
|
data/Rakefile
CHANGED
data/adornable.gemspec
CHANGED
@@ -1,29 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
lib = File.expand_path("../lib", __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require "adornable/version"
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name
|
8
|
-
spec.version
|
9
|
-
spec.authors
|
10
|
-
spec.email
|
8
|
+
spec.name = "adornable"
|
9
|
+
spec.version = Adornable::VERSION
|
10
|
+
spec.authors = ["Keegan Leitz"]
|
11
|
+
spec.email = ["kjleitz@gmail.com"]
|
11
12
|
|
12
|
-
spec.summary
|
13
|
-
spec.description
|
14
|
-
spec.homepage
|
15
|
-
spec.license
|
13
|
+
spec.summary = "Method decorators for Ruby"
|
14
|
+
spec.description = "Adornable provides the ability to cleanly decorate methods in Ruby. You can make and use your own decorators, and you can also use some of the built-in ones that the gem provides. _Decorating_ methods is as simple as slapping a `decorate :some_decorator` above your method definition. _Defining_ decorators can be as simple as defining a method that yields to a block, or as complex as manipulating the decorated method's receiver and arguments, and/or changing the functionality of the decorator based on custom options supplied to it when initially applying the decorator."
|
15
|
+
spec.homepage = "https://github.com/kjleitz/adornable"
|
16
|
+
spec.license = "MIT"
|
17
|
+
spec.required_ruby_version = ">= 2.4.7"
|
16
18
|
|
17
19
|
# Specify which files should be added to the gem when it is released.
|
18
20
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
19
|
-
spec.files
|
21
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
20
22
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
23
|
end
|
22
|
-
spec.bindir
|
23
|
-
spec.executables
|
24
|
+
spec.bindir = "exe"
|
25
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
26
|
spec.require_paths = ["lib"]
|
25
27
|
|
26
|
-
spec.add_development_dependency "bundler", "~>
|
27
|
-
spec.add_development_dependency "rake", "~>
|
28
|
+
spec.add_development_dependency "bundler", "~> 2.2"
|
29
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
28
30
|
spec.add_development_dependency "rspec", "~> 3.0"
|
31
|
+
spec.add_development_dependency "rubocop", "~> 1.10"
|
32
|
+
spec.add_development_dependency "rubocop-performance", "~> 1.9"
|
33
|
+
spec.add_development_dependency "rubocop-rake", "~> 0.5"
|
34
|
+
spec.add_development_dependency "rubocop-rspec", "~> 2.2"
|
35
|
+
spec.add_development_dependency "solargraph"
|
29
36
|
end
|
data/bin/console
CHANGED
data/lib/adornable.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "adornable/version"
|
2
4
|
require "adornable/utils"
|
3
5
|
require "adornable/error"
|
4
6
|
require "adornable/decorators"
|
5
7
|
require "adornable/machinery"
|
6
8
|
|
9
|
+
# Extend the `Adornable` module in your class in order to have access to the
|
10
|
+
# `decorate` and `add_decorators_from` macros.
|
7
11
|
module Adornable
|
8
12
|
def adornable_machinery
|
9
13
|
@adornable_machinery ||= Adornable::Machinery.new
|
10
14
|
end
|
11
15
|
|
12
|
-
def decorate(decorator_name, from: nil, defer_validation: false)
|
16
|
+
def decorate(decorator_name, from: nil, defer_validation: false, **decorator_options)
|
13
17
|
if Adornable::Utils.blank?(name)
|
14
18
|
raise Adornable::Error::InvalidDecoratorArguments, "Decorator name must be provided."
|
15
19
|
end
|
@@ -17,7 +21,8 @@ module Adornable
|
|
17
21
|
adornable_machinery.accumulate_decorator!(
|
18
22
|
name: decorator_name,
|
19
23
|
receiver: from,
|
20
|
-
defer_validation: !!defer_validation
|
24
|
+
defer_validation: !!defer_validation,
|
25
|
+
decorator_options: decorator_options,
|
21
26
|
)
|
22
27
|
end
|
23
28
|
|
@@ -27,9 +32,10 @@ module Adornable
|
|
27
32
|
|
28
33
|
def method_added(method_name)
|
29
34
|
machinery = adornable_machinery # for local variable
|
30
|
-
return unless machinery.
|
35
|
+
return unless machinery.accumulated_decorators?
|
36
|
+
|
31
37
|
machinery.apply_accumulated_decorators_to_instance_method!(method_name)
|
32
|
-
original_method =
|
38
|
+
original_method = instance_method(method_name)
|
33
39
|
define_method(method_name) do |*args|
|
34
40
|
bound_method = original_method.bind(self)
|
35
41
|
machinery.run_decorated_instance_method(bound_method, *args)
|
@@ -39,9 +45,10 @@ module Adornable
|
|
39
45
|
|
40
46
|
def singleton_method_added(method_name)
|
41
47
|
machinery = adornable_machinery # for local variable
|
42
|
-
return unless machinery.
|
48
|
+
return unless machinery.accumulated_decorators?
|
49
|
+
|
43
50
|
machinery.apply_accumulated_decorators_to_class_method!(method_name)
|
44
|
-
original_method =
|
51
|
+
original_method = method(method_name)
|
45
52
|
define_singleton_method(method_name) do |*args|
|
46
53
|
machinery.run_decorated_class_method(original_method, *args)
|
47
54
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Adornable
|
4
|
+
# A context object is passed to the decorator method, and contains information
|
5
|
+
# about the decorated method being called.
|
6
|
+
class Context
|
7
|
+
attr_reader(*%i[
|
8
|
+
method_receiver
|
9
|
+
method_name
|
10
|
+
method_arguments
|
11
|
+
decorator_name
|
12
|
+
decorator_options
|
13
|
+
])
|
14
|
+
|
15
|
+
def initialize(method_receiver:, method_name:, method_arguments:, decorator_name:, decorator_options:)
|
16
|
+
@method_receiver = method_receiver
|
17
|
+
@method_name = method_name
|
18
|
+
@method_arguments = method_arguments
|
19
|
+
@decorator_name = decorator_name
|
20
|
+
@decorator_options = decorator_options
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/adornable/decorators.rb
CHANGED
@@ -1,29 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'adornable/utils'
|
4
|
+
|
1
5
|
module Adornable
|
6
|
+
# `Adornable::Decorators` is used as the default namespace for decorator
|
7
|
+
# methods when a decorator method that is neither explicitly sourced (via the
|
8
|
+
# `decorate from: <receiver>` option) nor implicitly sourced (via the
|
9
|
+
# `add_decorators_from <receiver>` macro).
|
2
10
|
class Decorators
|
3
|
-
def self.log(
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
full_name = "`#{receiver_name}#{name_delimiter}#{method_name}`"
|
10
|
-
arguments_desc = arguments.empty? ? "no arguments" : "arguments `#{arguments}`"
|
11
|
+
def self.log(context)
|
12
|
+
method_receiver = context.method_receiver
|
13
|
+
method_name = context.method_name
|
14
|
+
method_args = context.method_arguments
|
15
|
+
full_name = Adornable::Utils.formal_method_name(method_receiver, method_name)
|
16
|
+
arguments_desc = method_args.empty? ? "no arguments" : "arguments `#{method_args.inspect}`"
|
11
17
|
puts "Calling method #{full_name} with #{arguments_desc}"
|
12
18
|
yield
|
13
19
|
end
|
14
20
|
|
15
|
-
def self.memoize(
|
21
|
+
def self.memoize(context, for_any_arguments: false, &block)
|
22
|
+
return memoize_for_arguments(context, &block) unless for_any_arguments
|
23
|
+
|
24
|
+
method_receiver = context.method_receiver
|
25
|
+
method_name = context.method_name
|
16
26
|
memo_var_name = :"@adornable_memoized_#{method_receiver.object_id}_#{method_name}"
|
17
27
|
existing = instance_variable_get(memo_var_name)
|
18
28
|
value = existing.nil? ? yield : existing
|
19
29
|
instance_variable_set(memo_var_name, value)
|
20
30
|
end
|
21
31
|
|
22
|
-
def self.memoize_for_arguments(
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
32
|
+
def self.memoize_for_arguments(context)
|
33
|
+
method_receiver = context.method_receiver
|
34
|
+
method_name = context.method_name
|
35
|
+
method_args = context.method_arguments
|
36
|
+
memo_var_name = :"@adornable_memoized_for_arguments_#{method_receiver.object_id}_#{method_name}"
|
37
|
+
memo = instance_variable_get(memo_var_name) || {}
|
38
|
+
instance_variable_set(memo_var_name, memo)
|
39
|
+
args_key = method_args.inspect
|
40
|
+
memo[args_key] = yield if memo[args_key].nil?
|
41
|
+
memo[args_key]
|
27
42
|
end
|
28
43
|
end
|
29
44
|
end
|
data/lib/adornable/error.rb
CHANGED
data/lib/adornable/machinery.rb
CHANGED
@@ -1,22 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'adornable/utils'
|
2
4
|
require 'adornable/error'
|
5
|
+
require 'adornable/context'
|
3
6
|
|
4
7
|
module Adornable
|
5
|
-
class Machinery
|
8
|
+
class Machinery # :nodoc:
|
6
9
|
def register_decorator_receiver!(receiver)
|
7
10
|
registered_decorator_receivers.unshift(receiver)
|
8
11
|
end
|
9
12
|
|
10
|
-
def accumulate_decorator!(name:, receiver:, defer_validation:)
|
13
|
+
def accumulate_decorator!(name:, receiver:, defer_validation:, decorator_options:)
|
11
14
|
name = name.to_sym
|
12
15
|
receiver ||= find_suitable_receiver_for(name)
|
13
16
|
validate_decorator!(name, receiver) unless defer_validation
|
14
17
|
|
15
|
-
decorator = {
|
18
|
+
decorator = {
|
19
|
+
name: name,
|
20
|
+
receiver: receiver,
|
21
|
+
options: decorator_options || {},
|
22
|
+
}
|
23
|
+
|
16
24
|
accumulated_decorators << decorator
|
17
25
|
end
|
18
26
|
|
19
|
-
def
|
27
|
+
def accumulated_decorators?
|
20
28
|
Adornable::Utils.present?(accumulated_decorators)
|
21
29
|
end
|
22
30
|
|
@@ -80,14 +88,31 @@ module Adornable
|
|
80
88
|
@class_method_decorators[name] = decorators || []
|
81
89
|
end
|
82
90
|
|
83
|
-
def run_decorators(decorators, bound_method, *
|
84
|
-
return bound_method.call(*
|
91
|
+
def run_decorators(decorators, bound_method, *method_arguments)
|
92
|
+
return bound_method.call(*method_arguments) if Adornable::Utils.blank?(decorators)
|
93
|
+
|
85
94
|
decorator, *remaining_decorators = decorators
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
95
|
+
decorator_name = decorator[:name]
|
96
|
+
decorator_receiver = decorator[:receiver]
|
97
|
+
decorator_options = decorator[:options]
|
98
|
+
validate_decorator!(decorator_name, decorator_receiver, bound_method)
|
99
|
+
|
100
|
+
context = Adornable::Context.new(
|
101
|
+
method_receiver: bound_method.receiver,
|
102
|
+
method_name: bound_method.name,
|
103
|
+
method_arguments: method_arguments,
|
104
|
+
decorator_name: decorator_name,
|
105
|
+
decorator_options: decorator_options,
|
106
|
+
)
|
107
|
+
|
108
|
+
send_parameters = if Adornable::Utils.present?(decorator_options)
|
109
|
+
[decorator_name, context, decorator_options]
|
110
|
+
else
|
111
|
+
[decorator_name, context]
|
112
|
+
end
|
113
|
+
|
114
|
+
decorator_receiver.send(*send_parameters) do
|
115
|
+
run_decorators(remaining_decorators, bound_method, *method_arguments)
|
91
116
|
end
|
92
117
|
end
|
93
118
|
|
@@ -97,6 +122,7 @@ module Adornable
|
|
97
122
|
end
|
98
123
|
end
|
99
124
|
|
125
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Layout/LineLength
|
100
126
|
def validate_decorator!(decorator_name, decorator_receiver, bound_method = nil)
|
101
127
|
return if decorator_receiver.respond_to?(decorator_name)
|
102
128
|
|
@@ -106,7 +132,7 @@ module Adornable
|
|
106
132
|
method_location = bound_method.source_location
|
107
133
|
"Cannot decorate `#{method_full_name}` (defined at `#{method_location.first}:#{method_location.second})."
|
108
134
|
end
|
109
|
-
|
135
|
+
|
110
136
|
base_message = "Decorator method `#{decorator_name.inspect}` cannot be found on `#{decorator_receiver.inspect}`."
|
111
137
|
|
112
138
|
definition_hint = if decorator_receiver.is_a?(Class) && decorator_receiver.instance_methods.include?(decorator_name)
|
@@ -120,5 +146,6 @@ module Adornable
|
|
120
146
|
message = [location_hint, base_message, definition_hint].compact.join(" ")
|
121
147
|
raise Adornable::Error::InvalidDecoratorArguments, message
|
122
148
|
end
|
149
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Layout/LineLength
|
123
150
|
end
|
124
151
|
end
|
data/lib/adornable/utils.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Adornable
|
2
|
-
class Utils
|
4
|
+
class Utils # :nodoc:
|
3
5
|
class << self
|
4
6
|
def blank?(value)
|
5
7
|
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
@@ -12,6 +14,15 @@ module Adornable
|
|
12
14
|
def presence(value)
|
13
15
|
value if present?(value)
|
14
16
|
end
|
17
|
+
|
18
|
+
def formal_method_name(method_receiver, method_name)
|
19
|
+
receiver_name, name_delimiter = if method_receiver.is_a?(Class)
|
20
|
+
[method_receiver.to_s, '::']
|
21
|
+
else
|
22
|
+
[method_receiver.class.to_s, '#']
|
23
|
+
end
|
24
|
+
"`#{receiver_name}#{name_delimiter}#{method_name}`"
|
25
|
+
end
|
15
26
|
end
|
16
27
|
end
|
17
28
|
end
|
data/lib/adornable/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: adornable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Keegan Leitz
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-02-
|
11
|
+
date: 2021-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2.2'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '13.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '13.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,7 +52,83 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '3.0'
|
55
|
-
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.10'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.10'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop-performance
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.9'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.9'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.5'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.5'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop-rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.2'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.2'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: solargraph
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Adornable provides the ability to cleanly decorate methods in Ruby. You
|
126
|
+
can make and use your own decorators, and you can also use some of the built-in
|
127
|
+
ones that the gem provides. _Decorating_ methods is as simple as slapping a `decorate
|
128
|
+
:some_decorator` above your method definition. _Defining_ decorators can be as simple
|
129
|
+
as defining a method that yields to a block, or as complex as manipulating the decorated
|
130
|
+
method's receiver and arguments, and/or changing the functionality of the decorator
|
131
|
+
based on custom options supplied to it when initially applying the decorator.
|
56
132
|
email:
|
57
133
|
- kjleitz@gmail.com
|
58
134
|
executables: []
|
@@ -61,15 +137,17 @@ extra_rdoc_files: []
|
|
61
137
|
files:
|
62
138
|
- ".gitignore"
|
63
139
|
- ".rspec"
|
140
|
+
- ".rubocop.yml"
|
64
141
|
- ".travis.yml"
|
65
142
|
- Gemfile
|
66
|
-
-
|
143
|
+
- LICENSE
|
67
144
|
- README.md
|
68
145
|
- Rakefile
|
69
146
|
- adornable.gemspec
|
70
147
|
- bin/console
|
71
148
|
- bin/setup
|
72
149
|
- lib/adornable.rb
|
150
|
+
- lib/adornable/context.rb
|
73
151
|
- lib/adornable/decorators.rb
|
74
152
|
- lib/adornable/error.rb
|
75
153
|
- lib/adornable/machinery.rb
|
@@ -79,7 +157,7 @@ homepage: https://github.com/kjleitz/adornable
|
|
79
157
|
licenses:
|
80
158
|
- MIT
|
81
159
|
metadata: {}
|
82
|
-
post_install_message:
|
160
|
+
post_install_message:
|
83
161
|
rdoc_options: []
|
84
162
|
require_paths:
|
85
163
|
- lib
|
@@ -87,15 +165,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
165
|
requirements:
|
88
166
|
- - ">="
|
89
167
|
- !ruby/object:Gem::Version
|
90
|
-
version:
|
168
|
+
version: 2.4.7
|
91
169
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
170
|
requirements:
|
93
171
|
- - ">="
|
94
172
|
- !ruby/object:Gem::Version
|
95
173
|
version: '0'
|
96
174
|
requirements: []
|
97
|
-
rubygems_version: 3.
|
98
|
-
signing_key:
|
175
|
+
rubygems_version: 3.1.4
|
176
|
+
signing_key:
|
99
177
|
specification_version: 4
|
100
178
|
summary: Method decorators for Ruby
|
101
179
|
test_files: []
|
data/Gemfile.lock
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
adornable (1.0.0)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: https://rubygems.org/
|
8
|
-
specs:
|
9
|
-
diff-lcs (1.4.4)
|
10
|
-
rake (10.5.0)
|
11
|
-
rspec (3.10.0)
|
12
|
-
rspec-core (~> 3.10.0)
|
13
|
-
rspec-expectations (~> 3.10.0)
|
14
|
-
rspec-mocks (~> 3.10.0)
|
15
|
-
rspec-core (3.10.1)
|
16
|
-
rspec-support (~> 3.10.0)
|
17
|
-
rspec-expectations (3.10.1)
|
18
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
19
|
-
rspec-support (~> 3.10.0)
|
20
|
-
rspec-mocks (3.10.2)
|
21
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
22
|
-
rspec-support (~> 3.10.0)
|
23
|
-
rspec-support (3.10.2)
|
24
|
-
|
25
|
-
PLATFORMS
|
26
|
-
ruby
|
27
|
-
|
28
|
-
DEPENDENCIES
|
29
|
-
adornable!
|
30
|
-
bundler (~> 1.17)
|
31
|
-
rake (~> 10.0)
|
32
|
-
rspec (~> 3.0)
|
33
|
-
|
34
|
-
BUNDLED WITH
|
35
|
-
1.17.3
|