adornable 1.0.0 → 1.0.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/Gemfile.lock +1 -1
- data/LICENSE +21 -0
- data/README.md +338 -12
- data/lib/adornable/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4bea3ddf067bcb43aee2c2c9cd380297f6f741dc358572d65903a20fd1a0e1ec
|
4
|
+
data.tar.gz: a12b2559a2a647ff58c36f072d4ad0bf71180f3382cffd69d2f43e4fd2d3a040
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 65d7e3170d90c2388a4e2a9610a6a2d2cfeb872153bb883791bf975d7f18eae4841ce3550010cfb0f17419b9bec7beeecd0d0553d9b2c46d35e6546ff0a23377
|
7
|
+
data.tar.gz: f3e79890b4d421628bbbf21680d676a83446915b34a067eb1d25fe45806ed4e78f8729be5a15f4308d1f50cfb51911c7235e05762841dd0ca264df997f3b5538
|
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/Gemfile.lock
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,361 @@
|
|
1
1
|
# Adornable
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
Adornable provides method decorators in Ruby... 'nuff said.
|
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
|
+
```
|
20
|
+
|
21
|
+
### Globally (to your system)
|
16
22
|
|
17
|
-
|
23
|
+
Alternatively, install it globally:
|
18
24
|
|
19
|
-
|
25
|
+
```bash
|
26
|
+
gem install adornable
|
27
|
+
```
|
20
28
|
|
21
|
-
|
29
|
+
...but why would you do that?
|
22
30
|
|
23
31
|
## Usage
|
24
32
|
|
25
|
-
|
33
|
+
### The basics
|
34
|
+
|
35
|
+
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:
|
36
|
+
|
37
|
+
```rb
|
38
|
+
class RandomValueGenerator
|
39
|
+
def value
|
40
|
+
# logging the method call
|
41
|
+
puts "Calling method `RandomValueGenerator#value` with no arguments"
|
42
|
+
# memoizing the result
|
43
|
+
@value ||= rand
|
44
|
+
end
|
45
|
+
|
46
|
+
def values(max)
|
47
|
+
# logging the method call
|
48
|
+
puts "Calling method `RandomValueGenerator#values` with arguments `[#{max}]`"
|
49
|
+
# memoizing the result
|
50
|
+
@values ||= {}
|
51
|
+
@values[max] ||= (1..max).map { rand }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
random_value_generator = RandomValueGenerator.new
|
56
|
+
|
57
|
+
values1 = random_value_generator.values(1000)
|
58
|
+
# Calling method `RandomValueGenerator#values` with arguments `[1000]`
|
59
|
+
#=> [0.7044444114998132, 0.401953296596267, 0.3023797513191562, ...]
|
60
|
+
|
61
|
+
values1 = random_value_generator.values(1000)
|
62
|
+
# Calling method `RandomValueGenerator#values` with arguments `[1000]`
|
63
|
+
#=> [0.7044444114998132, 0.401953296596267, 0.3023797513191562, ...]
|
64
|
+
|
65
|
+
values3 = random_value_generator.values(5000)
|
66
|
+
# Calling method `RandomValueGenerator#values` with arguments `[5000]`
|
67
|
+
#=> [0.9916088057511011, 0.04466750434972333, 0.6073659341272127]
|
68
|
+
|
69
|
+
value1 = random_value_generator.value
|
70
|
+
# Calling method `RandomValueGenerator#value` with no arguments
|
71
|
+
#=> 0.4196007135344746
|
72
|
+
|
73
|
+
value2 = random_value_generator.value
|
74
|
+
# Calling method `RandomValueGenerator#value` with no arguments
|
75
|
+
#=> 0.4196007135344746
|
76
|
+
```
|
77
|
+
|
78
|
+
...but you have a million more methods to write, and if you refactor, you'll have to screw around with a whole metric butt-load of method definitions across your app.
|
79
|
+
|
80
|
+
How about this instead?
|
81
|
+
|
82
|
+
```rb
|
83
|
+
class RandomValueGenerator
|
84
|
+
extend Adornable
|
85
|
+
|
86
|
+
decorate :log
|
87
|
+
decorate :memoize
|
88
|
+
def value
|
89
|
+
rand
|
90
|
+
end
|
91
|
+
|
92
|
+
decorate :log
|
93
|
+
decorate :memoize_for_arguments
|
94
|
+
def values(max)
|
95
|
+
(1..max).map { rand }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
random_value_generator = RandomValueGenerator.new
|
100
|
+
|
101
|
+
values1 = random_value_generator.values(1000)
|
102
|
+
# Calling method `RandomValueGenerator#values` with arguments `[1000]`
|
103
|
+
#=> [0.7044444114998132, 0.401953296596267, 0.3023797513191562, ...]
|
104
|
+
|
105
|
+
values1 = random_value_generator.values(1000)
|
106
|
+
# Calling method `RandomValueGenerator#values` with arguments `[1000]`
|
107
|
+
#=> [0.7044444114998132, 0.401953296596267, 0.3023797513191562, ...]
|
108
|
+
|
109
|
+
values3 = random_value_generator.values(5000)
|
110
|
+
# Calling method `RandomValueGenerator#values` with arguments `[5000]`
|
111
|
+
#=> [0.9916088057511011, 0.04466750434972333, 0.6073659341272127]
|
112
|
+
|
113
|
+
value1 = random_value_generator.value
|
114
|
+
# Calling method `RandomValueGenerator#value` with no arguments
|
115
|
+
#=> 0.4196007135344746
|
116
|
+
|
117
|
+
value2 = random_value_generator.value
|
118
|
+
# Calling method `RandomValueGenerator#value` with no arguments
|
119
|
+
#=> 0.4196007135344746
|
120
|
+
```
|
121
|
+
|
122
|
+
Nice, right?
|
123
|
+
|
124
|
+
> **Note:** in the case of multiple decorators decorating a method, each is executed from top to bottom.
|
125
|
+
|
126
|
+
### Adding decorator functionality
|
127
|
+
|
128
|
+
Add the `::decorate` macro to your classes by `extend`-ing `Adornable`:
|
129
|
+
|
130
|
+
```rb
|
131
|
+
class Foo
|
132
|
+
extend Adornable
|
133
|
+
|
134
|
+
# ...
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
### Decorating methods
|
139
|
+
|
140
|
+
Use the `decorate` macro to decorate methods.
|
141
|
+
|
142
|
+
#### Using built-in decorators
|
143
|
+
|
144
|
+
There are a few built-in decorators:
|
145
|
+
|
146
|
+
```rb
|
147
|
+
class Foo
|
148
|
+
extend Adornable
|
149
|
+
|
150
|
+
decorate :log
|
151
|
+
def some_method
|
152
|
+
# ...
|
153
|
+
end
|
154
|
+
|
155
|
+
decorate :memoize
|
156
|
+
def some_other_method
|
157
|
+
# ...
|
158
|
+
end
|
159
|
+
|
160
|
+
decorate :memoize_for_arguments
|
161
|
+
def yet_another_method(some_arg, some_other_arg = true, key_word_arg:, key_word_arg_with_default: 123)
|
162
|
+
# ...
|
163
|
+
end
|
164
|
+
|
165
|
+
decorate :log
|
166
|
+
decorate :memoize_for_arguments
|
167
|
+
def oh_boy_another_method(some_arg, some_other_arg = true, key_word_arg:, key_word_arg_with_default: 123)
|
168
|
+
# ...
|
169
|
+
end
|
170
|
+
|
171
|
+
decorate :log
|
172
|
+
def self.yeah_it_works_on_class_methods_too
|
173
|
+
# ...
|
174
|
+
end
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
- `decorate :log` logs the method name and any passed arguments to the console
|
179
|
+
- `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
|
180
|
+
- `decorate :memoize_for_arguments` acts like `decorate :memoize` but it namespaces that cache by the arguments passed, so it will re-compute (and cache the result) 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
|
181
|
+
|
182
|
+
> **Note:** in the case of multiple decorators decorating a method, each is executed from top to bottom.
|
183
|
+
|
184
|
+
#### Using custom decorators explicitly
|
185
|
+
|
186
|
+
You can reference any decorator method you write, like so:
|
187
|
+
|
188
|
+
```rb
|
189
|
+
class FooDecorators
|
190
|
+
# Note: this is a class method
|
191
|
+
def self.blast_it(method_receiver, method_name, arguments)
|
192
|
+
puts "Blasting it!"
|
193
|
+
value = yield
|
194
|
+
"#{value}!"
|
195
|
+
end
|
196
|
+
|
197
|
+
# Note: this is an instance method
|
198
|
+
def wait_for_it(method_receiver, method_name, arguments)
|
199
|
+
puts "Waiting..."
|
200
|
+
value = yield
|
201
|
+
"#{value}..."
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
class Foo
|
206
|
+
extend Adornable
|
207
|
+
|
208
|
+
# Note: `from: FooDecorators` references a class (and will look for the
|
209
|
+
# `::blast_it` method on that class)
|
210
|
+
decorate :blast_it, from: FooDecorators
|
211
|
+
def some_method
|
212
|
+
"haha I'm a method"
|
213
|
+
end
|
214
|
+
|
215
|
+
# Note: `from: FooDecorators.new` references an instance (and will look for
|
216
|
+
# the `#wait_for_it` method on that instance)
|
217
|
+
decorate :wait_for_it, from: FooDecorators.new
|
218
|
+
def other_method
|
219
|
+
"haha I'm another method"
|
220
|
+
end
|
221
|
+
|
222
|
+
decorate :log
|
223
|
+
def yet_another_method(foo, bar:)
|
224
|
+
"haha I'm yet another method"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
foo = Foo.new
|
229
|
+
|
230
|
+
foo.some_method
|
231
|
+
#=> "haha I'm a method!" # Note the exclamation mark
|
232
|
+
|
233
|
+
foo.other_method
|
234
|
+
#=> "haha I'm another method..." # Note the ellipsis
|
235
|
+
|
236
|
+
foo.yet_another_method(123, bloop: "bleep")
|
237
|
+
# Calling method `Foo#yet_another_method` with arguments `[123, {:bloop=>"bleep"}]`
|
238
|
+
#=> "haha I'm yet another method"
|
239
|
+
```
|
240
|
+
|
241
|
+
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, and if you supply an instance, it better be an instance method.
|
242
|
+
|
243
|
+
Every decorator method must take the following arguments:
|
244
|
+
|
245
|
+
- `method_receiver`: the actual object that the [decorated] method is being called on (an object/class); e.g., `Foo` or an instance of `Foo`
|
246
|
+
- `method_name`: the name of the [decorated] method being called on `method_receiver` (a symbol); e.g., `:some_method` or `:other_method`
|
247
|
+
- `arguments`: an array of arguments passed to the [decorated] method, including keyword arguments; e.g., if `:yet_another_method` was called like `Foo.new.yet_another_method(123, bar: true)` then `arguments` would be `[123, {:bar=>true}]`
|
248
|
+
|
249
|
+
> **Note:** 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`.
|
250
|
+
>
|
251
|
+
> **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.
|
252
|
+
|
253
|
+
Contrived example of when you might want to muck around with the return value:
|
254
|
+
|
255
|
+
```rb
|
256
|
+
class FooDecorators
|
257
|
+
def self.coerce_to_int(method_receiver, method_name, arguments)
|
258
|
+
value = yield
|
259
|
+
new_value = value.strip.to_i
|
260
|
+
puts "New value: #{value.inspect} (class: #{value.class})"
|
261
|
+
new_value
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
class Foo
|
266
|
+
extend Adornable
|
267
|
+
|
268
|
+
decorate :coerce_to_int, from: FooDecorators
|
269
|
+
def get_number_from_user
|
270
|
+
print "Enter a number: "
|
271
|
+
value = gets
|
272
|
+
puts "Value: #{value.inspect} (class: #{value.class})"
|
273
|
+
value
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
foo = Foo.new
|
278
|
+
|
279
|
+
foo.get_number_from_user
|
280
|
+
# Enter a number
|
281
|
+
# > 123
|
282
|
+
# Value: "123" (class: String)
|
283
|
+
# New value: 123 (class: Integer)
|
284
|
+
#=> 123
|
285
|
+
```
|
286
|
+
|
287
|
+
#### Using custom decorators implicitly
|
288
|
+
|
289
|
+
You can also register decorator receivers so that you don't have to reference them with the `from:` option:
|
290
|
+
|
291
|
+
```rb
|
292
|
+
class FooDecorators
|
293
|
+
# Note: this is a class method
|
294
|
+
def self.blast_it(method_receiver, method_name, arguments)
|
295
|
+
puts "Blasting it!"
|
296
|
+
value = yield
|
297
|
+
"#{value}!"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
class MoreFooDecorators
|
302
|
+
# Note: this is a class method
|
303
|
+
def self.wait_for_it(method_receiver, method_name, arguments)
|
304
|
+
puts "Waiting for it..."
|
305
|
+
value = yield
|
306
|
+
"#{value}..."
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
class Foo
|
311
|
+
extend Adornable
|
312
|
+
|
313
|
+
add_decorators_from FooDecorators
|
314
|
+
add_decorators_from MoreFooDecorators
|
315
|
+
|
316
|
+
decorate :blast_it
|
317
|
+
decorate :wait_for_it
|
318
|
+
def some_method
|
319
|
+
"haha I'm a method"
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
foo = Foo.new
|
324
|
+
|
325
|
+
foo.some_method
|
326
|
+
# Blasting it!
|
327
|
+
# Waiting for it...
|
328
|
+
#=> "haha I'm a method!..."
|
329
|
+
```
|
330
|
+
|
331
|
+
> **Note:** In the case of duplicate decorator methods, later receivers registered with `::add_decorators_from` will override any duplicate decorators from earlier registered receivers.
|
332
|
+
>
|
333
|
+
> **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
334
|
|
27
335
|
## Development
|
28
336
|
|
29
|
-
|
337
|
+
### Install dependencies
|
338
|
+
|
339
|
+
```bash
|
340
|
+
bin/setup
|
341
|
+
```
|
342
|
+
|
343
|
+
### Run testss
|
344
|
+
|
345
|
+
```bash
|
346
|
+
rake spec
|
347
|
+
```
|
30
348
|
|
31
|
-
|
349
|
+
### Create release
|
350
|
+
|
351
|
+
```
|
352
|
+
rake release
|
353
|
+
```
|
32
354
|
|
33
355
|
## Contributing
|
34
356
|
|
35
|
-
Bug reports and pull requests are welcome
|
357
|
+
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.
|
358
|
+
|
359
|
+
## License
|
360
|
+
|
361
|
+
This project is open source, under the terms of the [MIT license.](https://github.com/kjleitz/adornable/blob/master/LICENSE)
|
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.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Keegan Leitz
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-02-
|
11
|
+
date: 2021-02-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -64,6 +64,7 @@ files:
|
|
64
64
|
- ".travis.yml"
|
65
65
|
- Gemfile
|
66
66
|
- Gemfile.lock
|
67
|
+
- LICENSE
|
67
68
|
- README.md
|
68
69
|
- Rakefile
|
69
70
|
- adornable.gemspec
|