cushion_defaults 0.4.0 → 0.5.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: e4ae7cc718182c777f0867383128216f35841d45
4
- data.tar.gz: d7735d85bada9bb54e534fa2eb1ffc4b826711e5
3
+ metadata.gz: 725ea185c013e34737b4bf5f46e0fa08fff68428
4
+ data.tar.gz: 8414ae7cce652e15c91e654194c435ebe8aa0ed4
5
5
  SHA512:
6
- metadata.gz: 179c1e3ad13f662357a1baad18902a11ef2e10e1c8acf19d14ae5e1a18855a10f5daafbab9fdd8b840ca61f916e30b79a1a1d9a39e7f6b689a903e7423675f86
7
- data.tar.gz: ffef5ddacf6255e6dc576d9c4e0dbf618396168c9c3a35d207e54515cd886e548683eaae0f6f0514f400172d2ed826910290332bc29990d22be6600072c9e9bd
6
+ metadata.gz: eaf552d0557b4c07c083fa3db1074f4ddc058b20d8039e35d1f1c772e5f49ce3ba1e1e5a78b92973d7f4565a3ed45ebbbad2f36eee2f2e1a8c2fe3af9bce3f82
7
+ data.tar.gz: 68c8e99dd1543ae61ba627678eddaf63e962f276b5ffceb2e38eb812ec67dca281afd9f4157cbf7acae57a10d3e114bc49bb98204d7315fdf850ef9869dc7887
data/CHANGELOG.md CHANGED
@@ -52,4 +52,17 @@
52
52
 
53
53
  -0.4.0 - NEW FEATURE: Proc Cushions
54
54
  - You can now set a default to a proc that will be evaluated whenever an instance variable is absent.
55
- - For more information, see "Proc Cushions" in README.md.
55
+ - For more information, see "Proc Cushions" in README.md.
56
+
57
+ ## 0.5.x
58
+
59
+ -0.5.0
60
+ - NEW FEATURE: Bang Readers
61
+ - When called, a bang reader (e.g., +var!+) crystallizes +var+, if not set, to the default value for +var+.
62
+ - Especially useful for permanently fixing the value of a proc cushion.
63
+ - Key method: +ClassMethods#bang_the_cushion+
64
+ - NEW OPTION: bang_things_up
65
+ - If true, bang readers will automatically be set up every time a cushion_reader is created.
66
+ - Default: true
67
+ - Bugfix interaction between procs and #crystallize_defaults
68
+ - Expand README.md
data/README.md CHANGED
@@ -22,20 +22,32 @@ I try to keep all of this up-to-date, but the latest information can always be f
22
22
  3. Easily allow subclasses to inherit the default values of their ancestor classes or override them with their own default values. CushionDefaults is in this respect more flexible than using either constants or `@@defaults` variables. As an added bonus, changes to the defaults of a superclass cascade down and affect the defaults of subclasses.
23
23
  4. Optionally, if you think of your defaults as configuration rather than logic, pull them out of your code and put them in class-specific YAML files that can be automatically loaded in.
24
24
  5. Using the YAML technique, you can maintain multiple sets of defaults and load in the appropriate one depending on the environment.
25
- 6. If you follow the common pattern of setting instance variables to the default value (e.g., `@var = params[:var] || default_for_var`), you have no way of distinguishing between when `x@var` is set to the default value because that was actively selected (`params[:var]...`) and when it is set to the default because it otherwise would have been nil (`... || default_for_var`). This is especially important when defaults change occasionally (or even vary regularly!). CushionDefaults makes this situation easy to handle. Consider the following:
25
+ 6. If you follow the common pattern of setting instance variables to the default value (e.g., `@var = params[:var] || default_for_var`), you have no way of distinguishing between when `x@var` is set to the default value because that was actively selected (`params[:var]...`) and when it is set to the default because it otherwise would have been nil (`... || default_for_var`). This is especially important when defaults change occasionally (or even vary regularly!). CushionDefaults makes this situation easy to handle.
26
+
27
+ ## Give Me a Quick Example
26
28
 
27
29
  ```ruby
30
+ require 'color'
31
+
28
32
  class Person
29
33
  include CushionDefaults
30
34
  self.defaults[:favorite_color] = 'blue'
31
- cushion :favorite_color
35
+ self.defaults[:favorite_shade_of_gray] = do |instance|
36
+ Color::RGB.by_name(instance.favorite_color).to_grayscale.to_rgb
37
+ end
38
+ cushion :favorite_color, :favorite_shade_of_gray
32
39
  end
33
40
 
34
41
  ryan, julia = Person.new, Person.new
35
42
  ryan.favorite_color = 'blue'
36
43
 
44
+ ryan.favorite_shade_of_gray # RGB [#808080], computed from favorite_color == 'blue'
45
+ ryan.favorite_shade_of_gray = Color::RGB.by_name('silver')
46
+ ryan.favorite_shade_of_gray # RGB [#cccccc]
47
+
37
48
  ryan.favorite_color # 'blue'
38
49
  ryan.has_specified?(:favorite_color) # true
50
+
39
51
  julia.favorite_color # 'blue'
40
52
  julia.has_specified?(:favorite_color) # false
41
53
 
@@ -44,7 +56,9 @@ Person.defaults[:favorite_color] = 'green'
44
56
  ryan.favorite_color # 'blue'
45
57
  julia.favorite_color # 'green'
46
58
  ```
59
+
47
60
  ## How Do I Get It?
61
+
48
62
  `gem install 'cushion_defaults'` if you just want the gem.
49
63
 
50
64
  If you want to help out the project or edit the source code, clone the repository (hosted at [GitHub](https://github.com/posgarou/cushion_defaults)), fork it, make changes, and make a pull request.
@@ -99,10 +113,12 @@ class Klass
99
113
  self.defaults = {first: Klass, second: Klass, third: Klass}
100
114
  cushion_defaults
101
115
  end
116
+
102
117
  class SubKlass < Klass
103
118
  self.defaults += {second: SubKlass, fourth: SubKlass}
104
119
  cushion :fourth
105
120
  end
121
+
106
122
  class SubSubKlass < SubKlass
107
123
  self.defaults[:third] = SubSubKlass
108
124
  end
@@ -114,14 +130,14 @@ z.first = 'custom'
114
130
  Calling `#first`, `#second`, `#third`, and `#fourth`, then, would produce the following results on x, y, and z:
115
131
 
116
132
  ```ruby
117
- puts [x.first, x.second, x.third]
133
+ [x.first, x.second, x.third]
118
134
  # [Klass, Klass, Klass]
119
135
  # x.fourth would return NoMethodError
120
136
 
121
- puts [y.first, y.second, y.third, y.fourth]
137
+ [y.first, y.second, y.third, y.fourth]
122
138
  # [Klass, SubKlass, Klass, SubKlass]
123
139
 
124
- puts [z.first, z.second, z.third, z.fourth]
140
+ [z.first, z.second, z.third, z.fourth]
125
141
  # ['custom', SubKlass, SubSubKlass, SubKlass]
126
142
  ```
127
143
 
@@ -135,7 +151,7 @@ z.second # 'totally new value'
135
151
 
136
152
  ### Adding and Removing Readers and Writers
137
153
 
138
- Now, if we were to later add a new default to Plant
154
+ Now, if we were to later add a new default to our Plant class from up above
139
155
 
140
156
  ```ruby
141
157
  Plant.defaults[:climate] = 'temperate'
@@ -144,6 +160,7 @@ Plant.defaults[:climate] = 'temperate'
144
160
  and ran
145
161
 
146
162
  ```ruby
163
+ rhododendron = Plant.new
147
164
  rhododendron.climate
148
165
  ```
149
166
 
@@ -209,6 +226,8 @@ update_readers: true
209
226
  update_writers: true
210
227
  ```
211
228
 
229
+ For a complete list of options available (along with explanations), see the docs for CushionDefaults::Configuration, especially the method group "Option Reader/Writers."
230
+
212
231
  ### Proc Cushions
213
232
 
214
233
  CushionDefaults now supports proc cushions, which offer a powerful new level of flexibility in getting and setting defaults.
@@ -219,38 +238,44 @@ Take the following example:
219
238
 
220
239
  ```ruby
221
240
  class Language
222
- attr_accessor :greeting
241
+ attr_accessor :say_hello
223
242
  def initialize(&block)
224
243
  yield self if block_given?
225
244
  end
226
245
  end
227
246
 
228
247
  $languages = {
229
- en: Language.new { |l| l.greeting = 'Hello' },
230
- fr: Language.new { |l| l.greeting = 'Bonjour' }
248
+ en: Language.new { |l| l.say_hello = 'Hello' },
249
+ fr: Language.new { |l| l.say_hello = 'Bonjour' }
231
250
  }
232
251
 
233
252
  class Person
234
253
  include CushionDefaults
235
254
 
255
+ attr_accessor :name
256
+
257
+ def initialize(&block)
258
+ yield self if block_given?
259
+ end
260
+
236
261
  self.defaults[:language] = $languages[:en]
237
262
 
238
- # By default, return the greeting for the instance's language
263
+ # By default, return the greeting for the person's language and the person's name
239
264
  self.defaults[:greeting] = proc do |instance|
240
- instance.language.greeting
265
+ "#{instance.language.say_hello}, #{instance.name}"
241
266
  end
242
267
 
243
268
  cushion_defaults
244
269
  end
245
270
 
246
- john = Person.new
247
- john.greeting # 'Hello'
271
+ peter = Person.new { |p| p.name = 'Peter' }
272
+ peter.greeting # 'Hello, Peter'
248
273
 
249
- pierre = Person.new
250
- pierre.greeting # 'Hello', since languages[:en] is the default language
274
+ pierre = Person.new { |p| p.name = 'Pierre' }
275
+ pierre.greeting # 'Hello, Pierre', since languages[:en] is the default language
251
276
 
252
277
  pierre.language = $languages[:fr]
253
- pierre.greeting # 'Bonjour', since languages[:fr] is now pierre's greeting
278
+ pierre.greeting # 'Bonjour, Pierre', since languages[:fr] is now pierre's language, and #greeting gets its #say_hello
254
279
 
255
280
  pierre.greeting = 'Salut!'
256
281
  pierre.greeting # 'Salut!', since pierre has a custom greeting
@@ -279,9 +304,32 @@ passerby.when_i_noticed_you # Time.now
279
304
  sleep(1.0)
280
305
 
281
306
  passerby.when_i_noticed_you == Time.now # false—1 sec later
307
+
282
308
  ```
283
309
 
284
- These two techniques can provide sophisticated means of both setting cushions or defaults while allowing customizable values for particular instances.
310
+ Alternatively, you can write a normal proc and call the variable's +bang_reader+ if you're worried the variable may not be set.
311
+
312
+ ```ruby
313
+ class Person
314
+ include CushionDefaults
315
+
316
+ self.defaults[:when_i_noticed_you] = proc { Time.now }
317
+
318
+ cushion :when_i_noticed_you
319
+ end
320
+
321
+ passerby = Person.new
322
+
323
+ # since @when_i_noticed_you is undefined the bang_reader sets it to Time.now
324
+ passerby.when_i_noticed_you! # Time.now
325
+
326
+ # wait a sec
327
+ sleep(1.0)
328
+
329
+ passerby.when_i_noticed_you == Time.now # false—1 sec later
330
+ ```
331
+
332
+ These techniques can provide sophisticated means of both setting cushions or defaults while allowing customizable values for particular instances.
285
333
 
286
334
  ### Freezing and Thawing Defaults
287
335
 
@@ -475,6 +523,48 @@ Plant.new.color # 'green'
475
523
 
476
524
  (Crystallizing defaults should be carefully distinguished from freezing defaults: crystallizing defaults applies to specific instances, whereas freezing defaults applies to the class itself.)
477
525
 
526
+ ### What About Persistence?
527
+
528
+ The key rule when using CushionDefaults with any sort of persistence is this: use instance variables, and not `cushion_reader`s, when preparing objects for storage.
529
+
530
+ If you use `cushion_reader`s when storing your objects, you run the risk of accidentally crystallizing your defaults. Take, for instance, the following (incorrect) code:
531
+
532
+ ```ruby
533
+ class AccidentallyInLove
534
+ include CushionDefaults
535
+ self.defaults[:girl_for_me] = proc { %w(Sally Jane Dora Annabel).sample }
536
+ cushion :girl_for_me
537
+
538
+ def marshal_dump
539
+ # This is the problem: it returns the default for marshaling!
540
+ [girl_for_me]
541
+ end
542
+ def marshal_load array
543
+ self.girl_for_me = array.first
544
+ end
545
+ end
546
+
547
+ marco = AccidentallyInLove.new
548
+
549
+ # only 0.4% chance these are the same!
550
+ 5.times { puts marco.girl_for_me }
551
+
552
+ marco = Marshal.load(Marshal.dump(marco))
553
+
554
+ # Marco settled down without realizing it
555
+ 5.times { puts marco.girl_for_me }
556
+ ```
557
+
558
+ Assuming your intent in marshaling isn't to crystallize the default and force him to settle down, you can either leave in place the default methods (which work) or overload `marshal_dump` as follows:
559
+
560
+ ```ruby
561
+ def marshal_dump
562
+ [@girl_for_me]
563
+ end
564
+ ```
565
+
566
+ (Note, however, that any sort of reconstruction of objects is incompatible with setting `Configuration.ignore_attempts_to_set_nil` to false.)
567
+
478
568
  ### Pushy and Polite Defaults
479
569
 
480
570
  Pushy and polite defaults are an experimental feature. Rough documentation can be found in the docs, and more details will be forthcoming.
@@ -518,4 +608,27 @@ If you have the time, tell me why.
518
608
 
519
609
  Not a useful concept? Don't like the implementation? Think the default configuration options should be different? Edit [the wiki](https://github.com/posgarou/cushion_defaults/wiki) and let me know.
520
610
 
521
- If you have any specific suggestions for how to make CushionDefaults better, I'd love to hear them.
611
+ If you have any specific suggestions for how to make CushionDefaults better, I'd love to hear them.
612
+
613
+ ## Should I Stay or Should I Go Now?
614
+
615
+ Ultimately that's your decision. Try it, and see if it works for your use case. That said, here are some general guidelines from my perspective.
616
+
617
+ ### When *Should* I Use CushionDefaults?
618
+
619
+ When you need or want...
620
+
621
+ 1. DRY defaults handling.
622
+ 2. Flexibility.
623
+ 3. A powerful feature set for handling (and overriding) inheritance of defaults.
624
+ 4. To know when an instance variable is set to the default and when it is simply not specified.
625
+ 5. The ability to manage and maintain multiple sets of defaults (while remaining DRY).
626
+ 6. To separate your defaults (configuration) off from your codebase (logic).
627
+ 7. To gracefully handle changing defaults.
628
+ 8. To ensure that some or all of your defaults *don't* change.
629
+
630
+ ### When *Shouldn't* I Use CushionDefaults?
631
+
632
+ 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.
633
+ 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).
634
+ 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.
@@ -86,6 +86,9 @@ module CushionDefaults
86
86
  # Note that if the default responds to :call (a proc, e.g.), then the default is instead called with the instance
87
87
  # variable and the symbolic representation of the default requested. This allows for proc cushions.
88
88
  #
89
+ # Will set up {#bang_reader}s if
90
+ # {CushionDefaults::Configuration#bang_things_up Configuration#bang_things_up} is true.
91
+ #
89
92
  # The readers are named according to the same format as +attr_reader+.
90
93
  # @param syms [*#to_sym] instance variables that should have +cushion_readers+
91
94
  # @see Configuration#update_readers
@@ -106,7 +109,68 @@ module CushionDefaults
106
109
  defaults[sym]
107
110
  end
108
111
  end
109
- CushionDefaults.log("cushion_reader #{sym} established for #{self}")
112
+ CushionDefaults.log("cushion_reader #{sym_str} established for #{self}")
113
+ end
114
+ bang_reader *syms if CushionDefaults.conf.bang_things_up
115
+ end
116
+
117
+ # Identical to {#cushion_reader} with one important exception: after determining the default value, +bang_reader+
118
+ # goes on to crystallize this default value for the instance.
119
+ #
120
+ # This is especially useful with proc cushions, as the below examples make clear.
121
+ #
122
+ # Note that this method is equivalent to calling +instance.crystallize_default(:sym)+.
123
+ #
124
+ # Note also, finally, that bang_readers can exist and function even if no {#cushion_writer}s are defined.
125
+ #
126
+ # @param syms [*#to_sym] instance variables that should have +bang_readers+
127
+ # @see CushionDefaults::Configuration#bang_things_up Configuration#bang_things_up
128
+ # @see CushionDefaults::CushionDefaults#crystalize_default CushionDefaults#crystalize_default
129
+ #
130
+ # @example Simple Example #1: Flowers for cushion_reader
131
+ # class Flower
132
+ # include CushionDefaults
133
+ # self.defaults[:he_loves_me] = proc { (Time.now.sec % 2).zero? }
134
+ # cushion :he_loves_me
135
+ # end
136
+ #
137
+ # daisy = Flower.new
138
+ #
139
+ # daisy.he_loves_me # true
140
+ #
141
+ # # slight delay...
142
+ # daisy.he_loves_me # false
143
+ #
144
+ # # slight delay...
145
+ # daisy.he_loves_me! # true
146
+ #
147
+ # 5.times { daisy.he_loves_me } # [true, true, true, true, true]
148
+ #
149
+ # @example Simple Example #2: Wake Up Little Susie
150
+ # class Dreamer
151
+ # include CushionDefaults
152
+ # self.defaults[:time_awoken] = proc { Time.now }
153
+ # cushion :time_awoken
154
+ # end
155
+ #
156
+ # little_susie = Dreamer.new
157
+ #
158
+ # little_susie.time_awoken # returns Time.now, but doesn't set @time_awoken
159
+ # little_susie.time_awoken! # sets @time_awoken to Time.now
160
+ #
161
+ # little_susie.time_awoken # returns the @time_awoken set in the line above
162
+ # little_susie.time_awoken! # returns @time_awoken and does not set it to a new value
163
+ def bang_reader(*syms)
164
+ syms.each do |sym|
165
+ sym = sym.to_sym
166
+ sym_str = "#{sym}!"
167
+ if self_or_parent_instance_method?(sym)
168
+ CushionDefaults.log("#{self} or a parent class already has a bang method #{sym_str}", :warn)
169
+ end
170
+ define_method(sym_str.to_sym) do
171
+ crystallize_default(sym)
172
+ end
173
+ CushionDefaults.log("bang_reader #{sym_str} established for #{self}")
110
174
  end
111
175
  end
112
176
 
@@ -156,7 +220,7 @@ module CushionDefaults
156
220
  end
157
221
  end
158
222
 
159
- # Undefines any reader for sym.
223
+ # Undefines the reader for sym in this class (if present).
160
224
  # @note This method will delete any method of the form +sym+, not just cushion_readers.
161
225
  # @param sym [#to_sym] instance variable to delete reader for
162
226
  def remove_reader(sym)
@@ -167,7 +231,18 @@ module CushionDefaults
167
231
  end
168
232
  end
169
233
 
170
- # Undefines any writer for sym.
234
+ # Undefines any bang method for sym in this class (if present).
235
+ # @note This method will delete any method of the form +sym!+, not just bang readers.
236
+ # @param sym [#to_sym] instance variable to delete bang method for
237
+ def remove_bang(sym)
238
+ sym = "sym!".to_sym
239
+ if self_has_method?(sym)
240
+ undef_method(sym)
241
+ CushionDefaults.log("bang reader #{sym} removed from #{self}", :info)
242
+ end
243
+ end
244
+
245
+ # Undefines any writer for sym in this class (if present).
171
246
  # @note This method will delete any method of the format 'sym=', not just cushion_writers.
172
247
  # @param sym [#to_sym] instance variable to delete writer for
173
248
  def remove_writer(sym)
@@ -178,7 +253,8 @@ module CushionDefaults
178
253
  end
179
254
  end
180
255
 
181
- # Sets up both {#cushion_reader}s and {#cushion_writer}s for +syms+.
256
+ # Sets up both {#cushion_reader}s and {#cushion_writer}s for +syms+. Will set up {#bang_reader}s if
257
+ # {CushionDefaults::Configuration#bang_things_up Configuration#bang_things_up} is true.
182
258
  # @param syms [*#to_sym] Those instance variables that should have {#cushion_reader}s and {#cushion_writer}s.
183
259
  def cushion(*syms)
184
260
  cushion_reader(*syms)
@@ -31,13 +31,17 @@ module CushionDefaults
31
31
  # {#yaml_source_folder}. Def. nil.
32
32
  attr_writer :yaml_source_full_path
33
33
 
34
- # @return [boolean] if true, CushionDefaults makes log entries. Def. true.
35
- attr_accessor :record_in_log
36
-
37
34
  # @return [boolean] if true, CushionDefaults will complain (warning level) when it cannot find a YAML configuration
38
35
  # file for a class. If false, it only posts this message at the debug level. Def. false.
39
36
  attr_accessor :whiny_yaml
40
37
 
38
+ # @return [boolean] if true, all calls to {ClassMethods#cushion_reader}, implicit or explicit, will also call
39
+ # {ClassMethods#bang_reader}.
40
+ attr_accessor :bang_things_up
41
+
42
+ # @return [boolean] if true, CushionDefaults makes log entries. Def. true.
43
+ attr_accessor :record_in_log
44
+
41
45
  # @return [boolean] if true, calls to cushion_writer methods will not set the instance variable if the value passed in
42
46
  # is nil (thus allowing cushion_reader to still return the default value, rather than nil). Def. true.
43
47
  # @example Ignored Call to cushion_writer When ignore_attempts_to_set_nil == true
@@ -127,6 +131,7 @@ module CushionDefaults
127
131
  self.auto_load_from_yaml = true
128
132
  self.yaml_source_folder = 'config/cushion_defaults/'
129
133
  self.yaml_source_full_path = nil
134
+ self.bang_things_up = true
130
135
  self.record_in_log = true
131
136
  self.logger = Logger.new $stdout
132
137
  self.log_lvl = Logger::INFO
@@ -232,6 +232,7 @@ module CushionDefaults
232
232
  # @see ClassMethods#remove_writer
233
233
  def remove_methods_as_needed(key)
234
234
  @owner.remove_reader key.to_sym if CushionDefaults.conf.update_readers
235
+ @owner.remove_bang key.to_sym if CushionDefaults.conf.update_readers
235
236
  @owner.remove_writer key.to_sym if CushionDefaults.conf.update_writers
236
237
  end
237
238
 
@@ -28,7 +28,7 @@ require 'cushion_defaults/errors'
28
28
  module CushionDefaults
29
29
 
30
30
  # Version constant
31
- VERSION = '0.4.0'
31
+ VERSION = '0.5.0'
32
32
 
33
33
  # The path of the first file that +includes+ CushionDefaults.
34
34
  CALLING_PATH = File.expand_path(File.dirname($0)) + '/'
@@ -175,7 +175,7 @@ module CushionDefaults
175
175
  # acting if a nilish value is specified and the value for :default_key is nilish.
176
176
  if !has_specified? default_key || (act_if_nilish && CushionDefaults.nilish?(instance_variable_get("@#{default_key}")))
177
177
  default_value = default(default_key)
178
- instance_variable_set("@#{default_key}", default_value)
178
+ instance_variable_set("@#{default_key}", default_value.respond_to?(:call) ? default_value.call(self, default_key) : default_value)
179
179
  else
180
180
  log("Did not update #{default_key}")
181
181
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cushion_defaults
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Mitchell
@@ -38,9 +38,8 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.8'
41
- description: Allows you to specify a 'cushion' for various instance variables—effectively
42
- a default value—that will be returned by optional reader methods if the instance
43
- variable is undefined.
41
+ description: Cushion your instance variables. Get a default if a variable isn’t defined.
42
+ DRY off your code.
44
43
  email: posgarou@gmail.com
45
44
  executables: []
46
45
  extensions: []