modifiers 1.0.0 → 1.1.0

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: 1362dad9adfb0a9ac28f10588f0f233f00472038
4
- data.tar.gz: 22c39749a7f5ae6c411754899ae57db54fd7e52a
3
+ metadata.gz: 1c529f23f0c4fe7d0417b298daabad35a18c98ba
4
+ data.tar.gz: 4e26087e2b3d2a386417c3ae74bef0dfac9ef131
5
5
  SHA512:
6
- metadata.gz: 3bebfb125f3104c010eb94e4ee1b9de4c40cdeec0f9f542c45d5cb074631a4b3ef93f86ba7b07d6391d16387fb6db8d479144d0ba37e5baa671cabe483b6c9f0
7
- data.tar.gz: 27007ced57e661678ceba35749a1cdad47c764375cafeab094482626677a8a547cda788209a145fc21ae12449161cf520ec78f7da2d05f9a0b76d359f08666b3
6
+ metadata.gz: 7cae095e6a838c2e06094242f883ad3bd18db3841bd931abee97dcd8b507990ad5a1e851d241b85ddfa1de3d90ab5a465aac3b5bed809a4830317405a2cab543
7
+ data.tar.gz: b1649f83e589491f7da170b057be4121be762efc592f06cb7c228c92a2d2d3e74a871859b5cc2b6b80b777976f575b16d7c688e7c901699dbf8ddfef314ef183
data/.travis.yml CHANGED
@@ -10,4 +10,3 @@ deploy:
10
10
  secure: fewAgjCXLYSkbRq57EQYRTjwPn/LfQgpvSh1HBdVG1Vjds2OCL+yl3d6x30o4q28GU7dMLKjEl2BZrrQ9TwPgit3LRPUwlsoZRvGQj+oztdNfshpWansne6coBYa5vx/tQFBSDWkTMdE2RMH1w7qP20Iqn44c4yQU1tQiqWoZMQ=
11
11
  on:
12
12
  tags: true
13
- repo: nicknovitski/modifiers
data/README.md CHANGED
@@ -5,33 +5,55 @@
5
5
 
6
6
  `Modifiers` is a collection of method modifiers, and a way to make more.
7
7
 
8
- _Method Modifiers_, obviously, are modifiers to methods. Specifically, in Ruby
8
+ _Method Modifiers_, obviously, modify methods. Specifically, in Ruby
9
9
  terms, they are class methods which:
10
10
 
11
- 1. Take a symbol argument which names an instance method of the same class, and
12
- 2. Return the same symbol, but
13
- 3. Alter the named method in some way.
11
+ 1. Take a symbol argument which names an instance method of the same class, _and_
12
+ 2. Return the same symbol, _but_
13
+ 3. Cause subsequent calls to the named method to change in some way.
14
14
 
15
- Ruby has shipped with four (4) modifiers since forever: the three access
16
- modifiers (`public`, `private`, and `protected`), and `module_function`. This
17
- library [adds a few others, and a facility for creating even more](#usage).
15
+ This library [includes a few, as well as ways to make more](#usage).
18
16
 
19
17
  ## Why is/are Modifiers?
20
18
 
21
- DRYing up code sometimes involves smaller fragments of shared behavior than a
22
- method. Here's an example you've probably read and written before:
19
+ The pursuit of DRY code can involve fragments of shared behavior smaller than a
20
+ method.
21
+
22
+ Here's an example that might feel familiar:
23
23
  ```ruby
24
- # old and busted
25
24
  def count_ducks
26
25
  @count_ducks ||= DuckFlock.all.map(&size).inject(0, &:+)
27
26
  end
28
27
  ```
29
28
 
30
- Why are you writing the characters 'count_ducks', in the _exact same order_
31
- __two whole times__? If you already know how to implement memoization, why let
32
- your code challenge you to prove it every time you want it done?! Instead,
33
- implement it one final, flawless time, and tell the interpreter firmly, "No,
34
- _you_ type the name twice, my time is far too valuable."
29
+ This method is quite small, but it still complects the concerns of counting
30
+ ducks and of saving and reusing the result of a calculation, and that latter
31
+ concern might be repeated any number of times in your codebase.
32
+
33
+ With modifiers, we can encapsulate the implementation of the memoization, and
34
+ keep the intent:
35
+ ```ruby
36
+ def count_ducks
37
+ DuckFlock.all.map(&size).inject(0, &:+)
38
+ end
39
+ memoized :count_ducks
40
+ ```
41
+
42
+ ## Requirements
43
+
44
+ Behind the scenes, `modifiers` uses `Module#prepend`, so it requires Ruby
45
+ version 2.0.0 or higher.
46
+
47
+ If you have at least version 2.1.0, you can call them in-line with your method
48
+ definitions, which looks completely baller imo:
49
+ ```ruby
50
+ # requires Ruby 2.1.0 or higher, cool kids only
51
+ memoized query def fetch_from_api(params)
52
+ ApiFetcher.new(params).call
53
+ end
54
+ ```
55
+
56
+ (All other code examples in this document work on 2.0.)
35
57
 
36
58
  ## Installation
37
59
 
@@ -47,6 +69,36 @@ And then execute:
47
69
 
48
70
  ### built-in modifiers
49
71
 
72
+ #### memoized
73
+
74
+ Every now and then, you start to care how long it takes for a method to run.
75
+ You may find yourself wishing it just re-used some hard-won values, rather than
76
+ throwing them away and rebuilding them anew every time you call it.
77
+
78
+ (You may recognize the example from earlier, but this one is more complete.)
79
+
80
+ ```ruby
81
+ require 'modifiers/memoized'
82
+
83
+ class DuckService
84
+ extend Modifiers
85
+
86
+ def count_ducks
87
+ DuckFlock.all.map(&size).inject(0, &:+)
88
+ end
89
+ memoized :count_ducks
90
+ end
91
+ ```
92
+
93
+ A method modified by `memoized` will run once normally, per unique combination
94
+ of arguments, after which it will simply return the same result for the
95
+ lifetime of the receiving object. Dazzle your friends with your terse, yet
96
+ performant, fibonnaci implementations!
97
+
98
+ (If you want all this and more, you can use
99
+ [memoist](https://github.com/matthewrudy/memoist) (formerly
100
+ `ActiveSupport::Memoizable`) instead, but I warn you: it involves `eval`.)
101
+
50
102
  #### deprecated
51
103
 
52
104
  Sometimes there's a method, and you want it to die, but not a clean, swift
@@ -60,9 +112,10 @@ require 'modifiers/deprecated'
60
112
  class BadHacks
61
113
  extend Modifiers
62
114
 
63
- deprecated def awful_method
115
+ def awful_method
64
116
  # some ugly hack, probably involving define_method and ObjectSpace
65
117
  end
118
+ deprecated :awful_method
66
119
  end
67
120
  ```
68
121
 
@@ -73,36 +126,6 @@ BadHacks#awful_method called from app/controllers/ducks_controller.rb:782`
73
126
  (Please note that the `deprecated` method is deprecated, and you should
74
127
  definitely use `Gem.deprecate` instead.)
75
128
 
76
- #### memoized
77
-
78
- Every now and then, you will come to care how long it takes for a method to
79
- run. You may find yourself wishing it just re-used some hard-won values,
80
- rather than throwing them away and rebuilding them anew every time you call it.
81
-
82
- To demonstrate this, on multiple levels, I will re-use the case from a previous
83
- section.
84
-
85
- ```ruby
86
- require 'modifiers/memoized'
87
-
88
- class DuckService
89
- extend Modifiers
90
-
91
- memoized def count_ducks
92
- DuckFlock.all.map(&size).inject(0, &:+)
93
- end
94
- end
95
- ```
96
-
97
- A method modified by `memoized` will run once normally, per unique combination
98
- of arguments, after which it will simply return the same result for the
99
- lifetime of the receiving object. Dazzle your friends with your terse, yet
100
- performant, fibonnaci implementations!
101
-
102
- (If you want all this and more, you can use
103
- [memoist](https://github.com/matthewrudy/memoist) (formerly
104
- `ActiveSupport::Memoizable`) instead, but I warn you: it involves `eval`.)
105
-
106
129
  #### commands and queries
107
130
 
108
131
  You may have heard of 'Command-Query Separation`, and the claim that code
@@ -118,15 +141,17 @@ sounds.
118
141
  Conversely (?), a method modified by `query` will never change the state of
119
142
  anything non-global and in-process. This is also trivial, but it might seem
120
143
  more impressive.
144
+
121
145
  ```ruby
122
146
  require 'modifiers/command_query'
123
147
 
124
148
  class DuckFarmer < Struct.new(:hutches)
125
149
  extend Modifiers
126
150
 
127
- query def fullest_hutch
151
+ def fullest_hutch
128
152
  hutches.max { |h1,h2| h1.count_eggs - h2.count_eggs }
129
153
  end
154
+ query :fullest_hutch
130
155
  end
131
156
 
132
157
  class DuckHutch < Struct.new(:num_eggs)
@@ -151,8 +176,6 @@ If this was an infomercial, now is when I would say something like "It's just
151
176
  that easy, Michael!", and you (your name is Michael in this scenario) would say
152
177
  "Now _that's_ incredible!" and the audience would applaud.
153
178
 
154
- I'm mildly proud of this library.
155
-
156
179
  ### defining new modifiers
157
180
 
158
181
  New modifiers can be defined in your own modules using the `define_modifier` method.
@@ -164,59 +187,90 @@ require 'modifiers/define_modifier'
164
187
 
165
188
  module DuckFarmModifiers
166
189
  extend Modifiers
167
- define_modifier(:duck)
190
+ define_modifier(:duck) do |*args, &block|
191
+ super(*args, &block)
192
+ end
168
193
  end
169
194
 
170
195
  class DuckFarm
171
196
  extend DuckFarmModifiers
197
+ def farm
198
+ # raise, tend, cultivate
199
+ end
172
200
 
173
- duck :some_method # => unchanged
201
+ duck :farm # => unchanged
174
202
  end
175
203
  ```
176
204
 
177
- Here's an identical implementation:
205
+ Much as with `define_method`, the first argument to `define_modifier` gives us
206
+ the name of the new modifier, and the block gives us the implementation of a
207
+ given *modification*: a method which intercepts calls to the original method
208
+ (in this case, `DuckFarm#farm`), does whatever it likes, then invokes the
209
+ original method using `super`.
210
+
211
+ (Sadly, just as with `define_method`, you have to use explicit arguments when
212
+ calling `super`. Sorry, doing otherwise involved too much oddity.)
213
+
214
+ But maybe you don't want to call the original method at all!
178
215
  ```ruby
179
216
  module DuckFarmModifiers
180
- define_modifier(:duck) do |method_invocation|
181
- method_invocation.invoke
182
- end
217
+ define_modifier(:x) { }
183
218
  end
184
219
  ```
185
220
 
186
- A block passed to `define_modifier` will become the new body of the methods
187
- modified by the modifier, kinda like with `define_method`.
188
-
189
- The argument passed to that block will be an object representing a particular
190
- call to the modified method. As I showed you above, all you have to do
191
- continue that call as normal is `#invoke` it.
192
-
193
- Or not!
221
+ Or maybe you don't want to call it with the same arguments!
194
222
  ```ruby
195
223
  module DuckFarmModifiers
196
- define_modifier(:x) do
197
- # temporarily disable a method and see if anyone notices
224
+ define_modifier(:int) do |*args, &block|
225
+ super(*args.map(&:to_i), &block)
198
226
  end
199
227
  end
200
- ```
201
228
 
202
229
  You can do things before, after, or even "around" the invocation.
203
230
  ```ruby
204
231
  module DuckFarmModifiers
205
- define_modifier(:perf_logged) do |invocation|
232
+ define_modifier(:perf_logged) do |*args, &block|
206
233
  start = Time.now
207
- invocation.invoke
208
- Rails.logger "#{invocation.method_identifier} finished in #{Time.now - start}s"
234
+ super(*args, &block)
235
+ Rails.logger "#{self.class.name}##{__method__} finished in #{Time.now - start}s"
209
236
  end
210
237
  end
211
238
  ```
212
239
 
213
- The method invocation object can also tell you the `#arguments` in the call,
214
- and its `#location` in the source, the `#method_name` of the method which was
215
- modified, or even the full `#method_identifier` in
216
- `Class.class_method`/`Class#instance_method` style. All of the modifiers
217
- included in the library were made using it.
240
+ ### Extending modifiers
241
+
242
+ The body of a modifier will be evaluated in the context of the receiver
243
+ instance of the modified method, so you can refer to any other instance
244
+ methods. However, given that you probably wrote your modifier to be used from
245
+ _any_ object, there isn't much you can rely on, so you may find yourself
246
+ writing everything you need right there in the method, resulting in a long and
247
+ ugly method body.
218
248
 
219
- What awesome ones will _you_ write?
249
+ Luckily, there's a way out: if a given modification requires additional
250
+ behavior, simply pass a module with all the other methods you need as the
251
+ second argument to `define_modifier`.
252
+
253
+ ```ruby
254
+ module DuckFarmModifiers
255
+ module Gracefully
256
+ private
257
+
258
+ def logger
259
+ Rails.logger
260
+ end
261
+
262
+ def log_exception(method_name, exception)
263
+ logger.warn("#{method_name} raised #{exception}")
264
+ end
265
+ end
266
+
267
+ define_modifier(:gracefully, Gracefully) do |*args, &block|
268
+ super(*args, &block)
269
+ rescue => e
270
+ log_exception(__method__, e)
271
+ end
272
+ end
273
+ ```
220
274
 
221
275
  ## Contributing
222
276
 
@@ -6,6 +6,7 @@ module Modifiers
6
6
  mod = Modification.new(modifier, self, modified, method_body)
7
7
  mod.send(:include, helper) if helper
8
8
  prepend mod
9
+ modified
9
10
  end
10
11
  end
11
12
  module_function :define_modifier
@@ -1,3 +1,3 @@
1
1
  module Modifiers
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.0'
3
3
  end
@@ -24,6 +24,10 @@ RSpec.shared_examples 'a modifier' do |modifier, changes_return_value: false|
24
24
  let(:random_class_name) { (0...10).map { ('A'..'Z').to_a[rand(26)] }.join }
25
25
  subject(:instance) { test_class.new }
26
26
 
27
+ it 'returns the symbol it is passed' do
28
+ expect(test_class.send(modifier, :public_method)).to be :public_method
29
+ end
30
+
27
31
  unless changes_return_value
28
32
  it 'does not change the return value of the method' do
29
33
  test_class.send(modifier, :public_method)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modifiers
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Novitski