cushion_defaults 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bca05c80623f43fc5f8d61f016ffb6ee18f832cb
4
- data.tar.gz: daa36bc9f2da343a5e4699bd42e6c415da8e80cf
3
+ metadata.gz: 2a848b1a32c350596b23bfc77dfc4203f4752f89
4
+ data.tar.gz: 5f1af47223822f820d3b6b6846534e8b5c56617c
5
5
  SHA512:
6
- metadata.gz: 46c86103b998e49862173cc1456e1c14b6615426e9c1816ac50fa9e964b8386c3b13e27fb403cd597cfd2b65df6e6fbdb7bb556e86de0473ca2a91f7ba172cb5
7
- data.tar.gz: 8b149e6f6e9d07047c9768348263ebd90acd1ad7242d2bd3368157accadb26e709ec4c5bf9e5daaf8e9c28df6a4c27a0e73f4856a3210668a45cc35890266f89
6
+ metadata.gz: 1c69bed3724da4b17b32a6acaf7974598e05eb97cf236d1e784c3ef1d9457e126f6e577e3027b98f5dfb95c0987fec56148dc6fc7b4c64fa08cdeabc5821d123
7
+ data.tar.gz: 09fec36978d01e46e1774f9f59eddf013b2e43646a41ab4138963edc9c2deb294ee92f8b20d003f8c0307a2c4304fbd74a632cd786f40909565c0e3dc949b544
@@ -1,5 +1,19 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.6.x
4
+
5
+ - 0.6.0
6
+ - PERFORMANCE:
7
+ - Performance gains in `#cushion_reader` of 20-100%. Now roughly comparable to `attr_reader`.
8
+ - In total, execution speed of `#cushion_reader` is 200-400% faster than in v. 0.0.0.
9
+ - API CHANGE:
10
+ - The following methods have been deprecated.
11
+ - `ClassMethods#remove_reader` (see `ClassMethods#remove_cushion_reader`)
12
+ - `ClassMethods#remove_bang` (see `ClassMethods#remove_cushion_bang`)
13
+ - `ClassMethods#remove_writer` (see `ClassMethods#remove_writer`)
14
+ - The now-deprecated methods, when called for `sym`, would remove any method that fit the appropriate reader/bang/writer naming convention. The new methods will only remove CushionDefaults-defined methods.
15
+ - Making a default pushy or polite now automatically declares a cushion_reader for that default in the relevant class.
16
+
3
17
  ## 0.5.x
4
18
 
5
19
  - 0.5.2: PERFORMANCE
data/README.md CHANGED
@@ -647,6 +647,6 @@ When you need or want...
647
647
 
648
648
  ### When *Shouldn't* I Use CushionDefaults?
649
649
 
650
- 1. *When speed is absolutely critical.* CushionDefaults is pretty fast (see `benchmarks/simple_benchmark.rb`), since it runs almost entirely on a series of hash lookups. But there's no way it could be as fast as `attr_accessor`: it just does more, and more computations means more time. If you have hundreds of thousands of calculations that you need performed lightning fast, you should look elsewhere.
650
+ 1. *When speed is absolutely critical.* CushionDefaults is very fast (see `benchmarks/simple_benchmark.rb`), since it runs almost entirely on a series of hash lookups and adjusts methods on the fly depending on the current defaults setting. But there's no way it could consistently be as fast as `attr_accessor`: it just does more, and more computations means more time. Current benchmarks show execution speeds of 1.1 to 1.6 times those of `attr_reader` in Ruby 2.1.5, with a smaller factor in Ruby 1.9.3—but this is a difference of 0.01s to 0.05s for 100,000 calls to the reader methods. If you have hundreds of thousands of calculations that you need performed lightning fast, you should look elsewhere; but otherwise, CushionReader should be fast enough for your purposes.
651
651
  2. *When working in a Rails environment.* CushionDefaults may eventually spawn a companion project CushionDefaults-Rails, but for now it's just not the right tool for a Rails job. There are plenty of libraries that would be better for this purpose, e.g., [default_value_for](https://github.com/FooBarWidget/default_value_for).
652
652
  3. *When you want to keep your dependencies down.* Some people end up with 150 apps on their phones; others end up with 150 gems in their projects. CushionDefaults itself doesn't depend on any other gems (in production), but that still doesn't mean it's worth the extra overhead to use it in every project.
@@ -31,7 +31,7 @@ require 'cushion_defaults/errors'
31
31
  module CushionDefaults
32
32
 
33
33
  # Version constant
34
- VERSION = '0.5.2'
34
+ VERSION = '0.6.0'
35
35
 
36
36
  # The path of the first file that includes CushionDefaults.
37
37
  CALLING_PATH = File.expand_path(File.dirname($0)) + '/'
@@ -135,6 +135,8 @@ module CushionDefaults
135
135
 
136
136
  base.extend(ClassMethods)
137
137
 
138
+ base.send :_set_up_cushion_method_sets
139
+
138
140
  if @conf.auto_load_from_yaml
139
141
  base.defaults_from_yaml
140
142
  else
@@ -1,9 +1,11 @@
1
+ require 'set'
2
+
1
3
  module CushionDefaults
2
4
  # A series of class methods to be plopped into any class that includes CushionDefaults.
3
5
  module ClassMethods
4
6
 
5
7
  # Reader for the @defaults@ @DefaultsHash@.
6
- attr_reader :defaults
8
+ attr_reader :defaults, :descendents
7
9
 
8
10
  # @!attribute [w] defaults
9
11
  # Wipe @defaults and replace it with the keys/vals of @replacement_hash@.
@@ -73,6 +75,15 @@ module CushionDefaults
73
75
  end
74
76
  end
75
77
 
78
+ def _set_up_cushion_method_sets
79
+ @have_cushion_readers = Set.new
80
+ @have_bang_readers = Set.new
81
+ @have_cushion_writers = Set.new
82
+ @descendents = Set.new
83
+ end
84
+
85
+ private :_set_up_cushion_method_sets
86
+
76
87
  # @!group Reader/Writer
77
88
 
78
89
  # Sets up a cushion_reader for each :sym in @syms@.
@@ -93,30 +104,80 @@ module CushionDefaults
93
104
  # @param syms [*#to_sym] instance variables that should have @cushion_readers@
94
105
  # @see Configuration#update_readers
95
106
  def cushion_reader(*syms)
96
- syms.each do |sym|
97
- sym = sym.to_sym
98
- if self_or_parent_instance_method?(sym)
99
- CushionDefaults.log("#{self} or a parent class already has what looks like a getter method for #{sym_str}", :warn)
100
- end
101
- instance_variable_sym = "@#{sym}".to_sym
107
+ # send it with force==true
108
+ syms.each { |sym| _update_cushion_reader(sym.to_sym, true) }
109
+ bang_reader *syms if CushionDefaults.conf.bang_things_up
110
+ end
102
111
 
103
- # significant performance gains from using this rather than #defaults in method below
104
- defaults_cache = @defaults
112
+ # @api private
113
+ def _define_cushion_reader_pushy_dynamic(sym)
114
+ defaults_cache = @defaults
115
+ define_method(sym) do
116
+ defaults_cache[sym].call(self, sym)
117
+ da_default.respond_to?(:call) ? da_default.call(self, sym) : da_default
118
+ end
119
+ end
105
120
 
106
- define_method(sym) do
107
- if instance_variable_defined?(instance_variable_sym) && (CushionDefaults.conf.no_pushies? || defaults_cache.not_pushy?(sym))
108
- instance_variable_get(instance_variable_sym)
109
- else
110
- # much faster to save as var
111
- da_default = defaults_cache[sym]
112
- da_default.respond_to?(:call) ? da_default.call(self, sym) : da_default
113
- end
114
- end
115
- CushionDefaults.log("cushion_reader #{sym} established for #{self}")
121
+ # @api private
122
+ def _define_cushion_reader_pushy_static(sym)
123
+ defaults_cache = @defaults
124
+ define_method(sym) do
125
+ defaults_cache[sym]
116
126
  end
117
- bang_reader *syms if CushionDefaults.conf.bang_things_up
118
127
  end
119
128
 
129
+ # @api private
130
+ def _define_cushion_reader_no_pushy_dynamic(sym)
131
+ defaults_cache = @defaults
132
+ instance_variable_sym = "@#{sym}".to_sym
133
+ define_method(sym) do
134
+ instance_variable_defined?(instance_variable_sym) ? instance_variable_get(instance_variable_sym) : defaults_cache[sym].call(self, sym)
135
+ end
136
+ end
137
+
138
+ # @api private
139
+ def _define_cushion_reader_no_pushy_static(sym)
140
+ defaults_cache = @defaults
141
+ instance_variable_sym = "@#{sym}".to_sym
142
+ define_method(sym) do
143
+ instance_variable_defined?(instance_variable_sym) ? instance_variable_get(instance_variable_sym) : defaults_cache[sym]
144
+ end
145
+ end
146
+
147
+ private :_define_cushion_reader_no_pushy_dynamic, :_define_cushion_reader_no_pushy_static, :_define_cushion_reader_pushy_dynamic, :_define_cushion_reader_pushy_static
148
+
149
+ # @param sym [Symbol] the attribute to update the cushion_reader for
150
+ # @param force [boolean] if true, it will always update the method
151
+ # @api private
152
+ def _update_cushion_reader(sym, force=false)
153
+ # This will redefine a method each time the default is changed, but at this point that does not seem like a big
154
+ # enough performance hit to warrant the checks required to avoid it.
155
+
156
+ # force: should we force it to continue? (Basically should only be forced by {#cushion_reader})
157
+ # include?: if the reader already exists (i.e., it was previously manually created) then continue
158
+ # update_readers: if we're updating readers automatically, then continue
159
+ return unless force || @have_cushion_readers.include?(sym) || CushionDefaults.conf.update_readers
160
+
161
+ # include?: so we only show this warning once
162
+ if !@have_cushion_readers.include?(sym) && self_or_parent_instance_method?(sym)
163
+ CushionDefaults.log("#{self} or a parent class already has what looks like an attr_reader for #{sym}", :warn)
164
+ end
165
+
166
+ # Create one of four cushion_reader methods depending on the state and type of the default value
167
+ if @defaults.pushy? sym
168
+ @defaults[sym].respond_to?(:call) ? _define_cushion_reader_pushy_dynamic(sym) : _define_cushion_reader_pushy_static(sym)
169
+ else
170
+ default = @defaults[sym]
171
+ default.respond_to?(:call) ? _define_cushion_reader_no_pushy_dynamic(sym) : _define_cushion_reader_no_pushy_static(sym)
172
+ end
173
+ @have_cushion_readers.add sym
174
+ CushionDefaults.log("cushion_reader #{sym} updated for #{self}")
175
+ end
176
+
177
+ private :_update_cushion_reader
178
+
179
+
180
+
120
181
  # Identical to {#cushion_reader} with one important exception: after determining the default value, @bang_reader@
121
182
  # goes on to crystallize this default value for the instance.
122
183
  #
@@ -224,39 +285,95 @@ module CushionDefaults
224
285
  end
225
286
  end
226
287
 
227
- # Undefines the reader for sym in this class (if present).
288
+ # Undefines the reader for sym in this class (if present). Does not affect ancestor classes.
228
289
  # @note This method will delete any method of the form @sym@, not just cushion_readers.
229
290
  # @param sym [#to_sym] instance variable to delete reader for
291
+ # @deprecated
230
292
  def remove_reader(sym)
231
293
  sym = sym.to_sym
232
294
  if self_has_method?(sym)
295
+ undef_method(sym)
296
+ @have_cushion_readers.delete(sym)
297
+ CushionDefaults.log("cushion_reader #{sym} removed from #{self}", :info)
298
+ end
299
+ end
300
+
301
+ # Undefines the cushion_reader for sym in this class (if present). Does not affect ancestor classes.
302
+ # @param sym [#to_sym] instance variable to delete cushion_reader for
303
+ def remove_cushion_reader(sym)
304
+ sym = sym.to_sym
305
+ if @have_cushion_readers.delete?(sym) && self_has_method?(sym)
233
306
  undef_method(sym)
234
307
  CushionDefaults.log("cushion_reader #{sym} removed from #{self}", :info)
235
308
  end
236
309
  end
237
310
 
238
- # Undefines any bang method for sym in this class (if present).
311
+ # Undefines any bang method for sym in this class (if present). Does not affect ancestor classes.
239
312
  # @note This method will delete any method of the form @sym!@, not just bang readers.
240
313
  # @param sym [#to_sym] instance variable to delete bang method for
314
+ # @deprecated
241
315
  def remove_bang(sym)
242
- sym = "sym!".to_sym
316
+ sym = "#{sym}!".to_sym
243
317
  if self_has_method?(sym)
318
+ undef_method(sym)
319
+ @have_bang_readers.delete(sym)
320
+ CushionDefaults.log("bang reader #{sym} removed from #{self}", :info)
321
+ end
322
+ end
323
+
324
+ # Undefines any bang method for sym in this class (if present). Does not affect ancestor classes.
325
+ # @note This method will delete any method of the form @sym!@, not just bang readers.
326
+ # @param sym [#to_sym] instance variable to delete bang method for
327
+ def remove_cushion_bang(sym)
328
+ sym = "#{sym}!".to_sym
329
+ if @have_bang_readers.delete?(sym) && self_has_method?(sym)
244
330
  undef_method(sym)
245
331
  CushionDefaults.log("bang reader #{sym} removed from #{self}", :info)
246
332
  end
247
333
  end
248
334
 
249
- # Undefines any writer for sym in this class (if present).
335
+ # Undefines any writer for sym in this class (if present). Does not affect ancestor classes.
250
336
  # @note This method will delete any method of the format 'sym=', not just cushion_writers.
251
337
  # @param sym [#to_sym] instance variable to delete writer for
338
+ # @deprecated
252
339
  def remove_writer(sym)
253
340
  write_sym = "#{sym}=".to_sym
254
341
  if self_has_method?(write_sym)
342
+ undef_method(write_sym)
343
+ @have_cushion_writers.delete(write_sym)
344
+ CushionDefaults.log("cushion_writer #{sym}= removed from #{self}", :info)
345
+ end
346
+ end
347
+
348
+ # Undefines any writer for sym in this class (if present). Does not affect ancestor classes.
349
+ # @note This method will delete any method of the format 'sym=', not just cushion_writers.
350
+ # @param sym [#to_sym] instance variable to delete writer for
351
+ def remove_cushion_writer(sym)
352
+ write_sym = "#{sym}=".to_sym
353
+ if @have_cushion_writers.delete?(write_sym) && self_has_method?(write_sym)
255
354
  undef_method(write_sym)
256
355
  CushionDefaults.log("cushion_writer #{sym}= removed from #{self}", :info)
257
356
  end
258
357
  end
259
358
 
359
+ def _hide_cushion_reader(sym)
360
+ # Called (1) when a cushion reader exists and (2) the reader needs to be hidden
361
+ alias_method "_#{sym}_hidden".to_sym, sym
362
+ remove_method sym
363
+ end
364
+
365
+ def _reveal_cushion_reader(sym)
366
+ # Called (1) when a previous cushion reader exists but (2) it was hidden due to a parent's sym becoming pushy
367
+ alias_method sym, "_#{sym}_hidden".to_sym
368
+ remove_method "_#{sym}_hidden".to_sym
369
+ # Things might have changed, so we should update the method
370
+ @have_cushion_readers.add sym
371
+ _update_cushion_reader sym
372
+ end
373
+
374
+ private :_hide_cushion_reader, :_reveal_cushion_reader
375
+
376
+
260
377
  # Sets up both {#cushion_reader}s and {#cushion_writer}s for @syms@. Will set up {#bang_reader}s if
261
378
  # {CushionDefaults::Configuration#bang_things_up Configuration#bang_things_up} is true.
262
379
  # @param syms [*#to_sym] Those instance variables that should have {#cushion_reader}s and {#cushion_writer}s.
@@ -377,16 +494,59 @@ module CushionDefaults
377
494
 
378
495
  # Declare each sym to be pushy. Pushy defaults will be returned by {#cushion_reader}s regardless of what the
379
496
  # instance variables are set to.
497
+ #
498
+ # This will automatically create {#cushion_reader}'s for all variables made pushy.
380
499
  def make_pushy(*syms)
381
- syms.each { |sym| @defaults.pushy!(sym) }
500
+ syms.each do |sym|
501
+ @defaults.pushy!(sym)
502
+ _update_cushion_reader(sym, true)
503
+ end
382
504
  end
383
505
 
384
506
  # Declare each sym to be polite. A polite default operates as normal. This is useful to override an ancestor class'
385
507
  # pushy declaration.
508
+ #
509
+ # This will automatically create {#cushion_reader}'s for all variables made polite.
386
510
  def make_polite(*syms)
387
- syms.each { |sym| @defaults.not_pushy!(sym) }
511
+ syms.each do |sym|
512
+ @defaults.not_pushy!(sym)
513
+ _update_cushion_reader(sym, true)
514
+ end
388
515
  end
389
516
 
517
+ # Called when a variable either becomes pushy or _may_ have become pushy.
518
+ def _cascade_pushy_symbol(sym)
519
+ # if we get down far enough, it may no longer be pushy (we read a polite)
520
+ # at that point, we can assume accurate state
521
+ return unless @defaults.pushy? sym
522
+ if self_has_method? sym
523
+ if @defaults.pushy_in_parent? sym
524
+ # if it's pushy in a parent now, then hide the reader
525
+ # we hide it rather than delete it because we want to be able to restore a manually-created cushion_reader if
526
+ # this is later made not-polite
527
+ _hide_cushion_reader sym
528
+ @have_cushion_readers.delete sym
529
+ # cascade it down to descendents
530
+ @descendents.each { |desc| desc._cascade_pushy_symbol sym if respond_to? :_cascade_pushy_symbol }
531
+ else
532
+ # if it's pushy in us (has to be, if not pushy in parent) then just update the reader
533
+ _update_cushion_reader sym
534
+ # also, if it's pushy in us, we don't need to look at descendents any more, so no need to call
535
+ end
536
+ end
537
+ end
538
+
539
+ # Called when a variable either becomes polite or _may_ have become polite.
540
+ def _cascade_polite_symbol(sym)
541
+ # if we get down far enough, it may become pushy again
542
+ return unless @defaults.not_pushy? sym
543
+ _reveal_cushion_reader(sym) if self_has_method? "_#{sym}_hidden".to_sym
544
+ # cascade it down to descendents
545
+ @descendents.each { |desc| desc._cascade_polite_symbol sym if respond_to? :_cascade_polite_symbol }
546
+ end
547
+
548
+ private :_cascade_pushy_symbol, :_cascade_polite_symbol
549
+
390
550
  # @!endgroup
391
551
 
392
552
  # Ensure that if class @Klass@ includes @CushionDefaults@, then any class that subclasses @Klass@ will include it as
@@ -396,6 +556,8 @@ module CushionDefaults
396
556
  # @api private
397
557
  def inherited(inheritor)
398
558
  inheritor.send :include, CushionDefaults
559
+ @descendents << inheritor
560
+ super inheritor
399
561
  end
400
562
 
401
563
  protected
@@ -77,8 +77,9 @@ module CushionDefaults
77
77
  key = key.to_sym
78
78
  return unless has_key? key
79
79
  remove_methods_as_needed(key)
80
- @pushy_defaults.delete(key)
81
- @polite_defaults.delete(key)
80
+ #TODO Need some better way to handle when @pushy_defaults is emptied or cleared. Need to talk to children.
81
+ @owner.send :_cascade_polite_symbol, key if @pushy_defaults.delete? key
82
+ @owner.send :_cascade_pushy_symbol, key if @polite_defaults.delete? key
82
83
  @frozen_defaults.delete(key)
83
84
  super(key)
84
85
  CushionDefaults.log("Default for #{key} in #{@owner} deleted.")
@@ -89,9 +90,9 @@ module CushionDefaults
89
90
  def clear
90
91
  keys.each do |key|
91
92
  remove_methods_as_needed(key.to_sym)
93
+ @owner.send :_cascade_polite_symbol, key if @pushy_defaults.delete? key
94
+ @owner.send :_cascade_pushy_symbol, key if @pushy_defaults.delete? key
92
95
  end
93
- @pushy_defaults.clear
94
- @polite_defaults.clear
95
96
  @frozen_defaults.clear
96
97
  super
97
98
  CushionDefaults.log("All defaults cleared for #{@owner}.", :info)
@@ -163,6 +164,9 @@ module CushionDefaults
163
164
  CushionDefaults.conf.we_have_a_pushy!
164
165
  @pushy_defaults.add(sym)
165
166
  @polite_defaults.delete(sym)
167
+
168
+ @owner.send :_cascade_pushy_symbol, sym
169
+
166
170
  CushionDefaults.log(":#{sym} in #{@owner} is now pushy.")
167
171
  end
168
172
 
@@ -170,6 +174,11 @@ module CushionDefaults
170
174
  def not_pushy!(sym)
171
175
  @pushy_defaults.delete(sym)
172
176
  @polite_defaults.add(sym)
177
+
178
+ @owner.send :_cascade_polite_symbol, sym
179
+
180
+ # we may need now to define a reader for it
181
+ @owner.send :_update_cushion_reader, sym
173
182
  CushionDefaults.log("#{sym} in #{@owner} is now polite.")
174
183
  end
175
184
 
@@ -220,7 +229,7 @@ module CushionDefaults
220
229
  # @see Configuration#update_writers
221
230
  # @see ClassMethods#cushion
222
231
  def add_methods_as_needed(key)
223
- @owner.cushion_reader key.to_sym if CushionDefaults.conf.update_readers
232
+ @owner.send :_update_cushion_reader, key
224
233
  @owner.cushion_writer key.to_sym if CushionDefaults.conf.update_writers
225
234
  end
226
235
 
@@ -231,9 +240,9 @@ module CushionDefaults
231
240
  # @see ClassMethods#remove_reader
232
241
  # @see ClassMethods#remove_writer
233
242
  def remove_methods_as_needed(key)
234
- @owner.remove_reader key.to_sym if CushionDefaults.conf.update_readers
235
- @owner.remove_bang key.to_sym if CushionDefaults.conf.update_readers
236
- @owner.remove_writer key.to_sym if CushionDefaults.conf.update_writers
243
+ @owner.remove_cushion_reader key.to_sym if CushionDefaults.conf.update_readers
244
+ @owner.remove_bang_reader key.to_sym if CushionDefaults.conf.update_readers
245
+ @owner.remove_cushion_writer key.to_sym if CushionDefaults.conf.update_writers
237
246
  end
238
247
 
239
248
  protected
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cushion_defaults
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Mitchell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-12 00:00:00.000000000 Z
11
+ date: 2014-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec