cushion_defaults 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -0
- data/README.md +331 -0
- data/lib/cushion_defaults.rb +1 -1
- data/lib/cushion_defaults/configuration.rb +176 -176
- metadata +41 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bacbeab26c1d40ab130cc3fb66b06a7e0eb26012
|
4
|
+
data.tar.gz: b056aa659f1279580225fa627482894ac8ac5e90
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d427f699e5ffb42d22c5e4cc7c2ae9817df5edeb347e984b0fff367924100defc1c8e30582f7ebcdead9b86006cd44a6bd5556357f096610335a80f25b4ed53
|
7
|
+
data.tar.gz: 4dca3355fac1a733c93d85511df0d943548774e65bc590300656b4d8cb141fa54f09282f180837924b83d8f3908488e314ff300a02272b7d83398e05260e3f07
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# CHANGELOG
|
2
|
+
|
3
|
+
## 0.0.x
|
4
|
+
|
5
|
+
- 0.0.0: Initial version.
|
6
|
+
- 0.0.1
|
7
|
+
- Include MIT License.
|
8
|
+
- Specify GitHub homepage.
|
9
|
+
- 0.0.2
|
10
|
+
- Improve `cushion_reader` speed by approx. 15%.
|
11
|
+
- Specify Ruby version >= 2.0.0.
|
12
|
+
- 0.0.3
|
13
|
+
- Fix various bugs related to YAML config loading.
|
14
|
+
- Improve examples.
|
15
|
+
|
16
|
+
## 0.1.x
|
17
|
+
|
18
|
+
- 0.1.0
|
19
|
+
- NEW FEATURE: Logging
|
20
|
+
- Add `config.record_in_log` (boolean), `config.log_lvl (int)`, and `config.logger` (Logger or similar) options.
|
21
|
+
- Add logging throughout the module at various levels.
|
22
|
+
- Logging enabled by default at info level.
|
23
|
+
- Improve performance of `cushion_reader` by approx. 115%.
|
24
|
+
- 0.1.1
|
25
|
+
- Greatly improve and expand documentation.
|
26
|
+
- Switch from rdoc to Yard
|
27
|
+
|
28
|
+
## 0.2.x
|
29
|
+
|
30
|
+
- 0.2.0
|
31
|
+
- Place Configuration within the CushionDefaults module.
|
32
|
+
- IMPROVEMENT: `cushion_defaults.gemspec`
|
33
|
+
- Clarify and expand `cushion_defaults.gemspec`. Was insufficient before.
|
34
|
+
- Now specifies development dependencies.
|
35
|
+
- Add Gemfile for bundler
|
36
|
+
- IMPROVEMENT: Testing
|
37
|
+
- `test/` renamed to `spec/`
|
38
|
+
- Add `.rspec` and `spec/spec_helper.rb`
|
39
|
+
- Improve documentation further.
|
data/README.md
ADDED
@@ -0,0 +1,331 @@
|
|
1
|
+
# Cushion Defaults
|
2
|
+
## What Does It Do?
|
3
|
+
|
4
|
+
### TL;DR
|
5
|
+
|
6
|
+
An easy, flexible, and powerful alternative to hashes of defaults. Can be used both in individual classes and in complex class hierarchies.
|
7
|
+
|
8
|
+
### The Long Version
|
9
|
+
|
10
|
+
Allows you to specify a "cushion" for various instance variables—effectively a default value—that will be returned by optional reader methods if the instance variable is undefined.
|
11
|
+
|
12
|
+
If, for example, the default value for `@wheels` in class Car is 4 and we set up a new instance called `ford` (`ford = Car.new`), then calling `ford.wheels` will return 4—even though no instance variable `@wheels` has been directly specified for `ford`. And if we later change the default value of `wheels` for Car (`Car.defaults[:wheels] = 6`), then all subsequent calls to `ford.wheels` will return 6 (unless we crystallize it beforehand—see below for more details).
|
13
|
+
|
14
|
+
## Why Should I Care?
|
15
|
+
|
16
|
+
1. Don't Repeat Yourself (DRY): Gather your defaults in one place, and specify them only once.
|
17
|
+
2. Correspondingly, minimize the amount of code you have to write and maintain. You'll be writing `x || default_value_for_x` a lot less—and if you later change the default value for x, you only have to update a single line of code.
|
18
|
+
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.
|
19
|
+
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.
|
20
|
+
5. Using the YAML technique, you can maintain multiple sets of defaults and load in the appropriate one depending on the environment.
|
21
|
+
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:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
class Person
|
25
|
+
include CushionDefaults
|
26
|
+
self.defaults[:favorite_color] = 'blue'
|
27
|
+
cushion :favorite_color
|
28
|
+
end
|
29
|
+
|
30
|
+
ryan, julia = Person.new, Person.new
|
31
|
+
ryan.favorite_color = 'blue'
|
32
|
+
|
33
|
+
ryan.favorite_color # 'blue'
|
34
|
+
ryan.has_specified?(:favorite_color) # true
|
35
|
+
julia.favorite_color # 'blue'
|
36
|
+
julia.has_specified?(:favorite_color) # false
|
37
|
+
|
38
|
+
Person.defaults[:favorite_color] = 'green'
|
39
|
+
|
40
|
+
ryan.favorite_color # 'blue'
|
41
|
+
julia.favorite_color # 'green'
|
42
|
+
```
|
43
|
+
## How Do I Get It?
|
44
|
+
`gem install 'cushion_defaults'` if you just want the gem.
|
45
|
+
|
46
|
+
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)).
|
47
|
+
## Give Me the Rundown
|
48
|
+
### The Basics
|
49
|
+
Setting up a DefaultsHash, populating it, and setting up `cushion_reader`s and `cushion_writer`s is a simple process.
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
class Plant
|
53
|
+
include CushionDefaults
|
54
|
+
self.defaults = {color: 'green', sunlight_needed: 'full'}
|
55
|
+
|
56
|
+
# cushion_defaults is here equivalent to:
|
57
|
+
# cushion_reader :color, :sunlight_needed
|
58
|
+
# cushion_writer :color, :sunlight_needed
|
59
|
+
cushion_defaults
|
60
|
+
|
61
|
+
def needs_full_sunlight?
|
62
|
+
sunlight_needed.eql?('full')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
rhododendron = Plant.new
|
67
|
+
rhododendron.color # 'green'
|
68
|
+
rhododendron.needs_full_sunlight? # true
|
69
|
+
```
|
70
|
+
|
71
|
+
Now, if we later decide to place our Plant class within Brandon Sanderson's [Mistborn](http://brandonsanderson.com/books/mistborn/the-final-empire/) world, we may want to update our defaults:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
Plant.defaults[:color] = 'brown'
|
75
|
+
```
|
76
|
+
|
77
|
+
As soon as we do this, all Plants that do not have a color explicitly assigned will return the new default value when we call their `#color` method.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
rhododendron.color # 'brown'
|
81
|
+
```
|
82
|
+
|
83
|
+
### Crystallizing Defaults
|
84
|
+
You can prevent this auto-updating, if desired, by calling `#crystallize_default` on those instances you don't want auto-updated. `#crystallize_default(sym)` effectively says "If no value for `@sym` is explicitly set, then explictly set it to the default value." Obviously, then, `#crystallize_default(sym)` affects only those instances that do not have a value explicitly specified for `sym`.
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
tulip, rose = Plant.new, Plant.new
|
88
|
+
tulip.color # 'brown'
|
89
|
+
rose.color = 'red'
|
90
|
+
tulip.has_specified?(:color) # false
|
91
|
+
rose.has_specified?(:color) # true
|
92
|
+
|
93
|
+
# crystallizes :color to 'brown'
|
94
|
+
tulip.crystallize_default(:color)
|
95
|
+
|
96
|
+
# has no effect, since :color is already set to 'red'
|
97
|
+
rose.crystallize_default(:color)
|
98
|
+
|
99
|
+
tulip.has_specified?(:color) # true
|
100
|
+
|
101
|
+
Plant.defaults[:color] = 'green'
|
102
|
+
|
103
|
+
tulip.color # 'brown'
|
104
|
+
rose.color # 'red'
|
105
|
+
Plant.new.color # 'green'
|
106
|
+
```
|
107
|
+
|
108
|
+
### Defaults and Inheritance
|
109
|
+
Classes inherit the defaults of those ancestors that respond to `#defaults` with a Hash or a descendent thereof.
|
110
|
+
|
111
|
+
This all takes place automatically. When CushionDefaults is included in a class, it automatically includes itself in all classes that subclass that class, and when a `cushion_reader` is called, it automatically moves up the class hierarchy if no value for the key is specified in the instance variable or in the current class.
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
class Klass
|
115
|
+
include CushionDefaults
|
116
|
+
self.defaults = {first: Klass, second: Klass, third: Klass}
|
117
|
+
cushion_defaults
|
118
|
+
end
|
119
|
+
class SubKlass < Klass
|
120
|
+
self.defaults += {second: SubKlass, fourth: SubKlass}
|
121
|
+
cushion :fourth
|
122
|
+
end
|
123
|
+
class SubSubKlass < SubKlass
|
124
|
+
self.defaults[:third] = SubSubKlass
|
125
|
+
end
|
126
|
+
|
127
|
+
x, y, z = Klass.new, SubKlass.new, SubSubKlass.new
|
128
|
+
z.first = 'custom'
|
129
|
+
```
|
130
|
+
|
131
|
+
Calling `#first`, `#second`, `#third`, and `#fourth`, then, would produce the following results on x, y, and z:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
puts [x.first, x.second, x.third]
|
135
|
+
# [Klass, Klass, Klass]
|
136
|
+
# x.fourth would return NoMethodError
|
137
|
+
|
138
|
+
puts [y.first, y.second, y.third, y.fourth]
|
139
|
+
# [Klass, SubKlass, Klass, SubKlass]
|
140
|
+
|
141
|
+
puts [z.first, z.second, z.third, z.fourth]
|
142
|
+
# ['custom', SubKlass, SubSubKlass, SubKlass]
|
143
|
+
```
|
144
|
+
|
145
|
+
Obviously, changing the default of a parent class changes the value returned by subclass instances, unless they have explicitly overridden the default.
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
SubKlass.defaults[:second] = 'totally new value'
|
149
|
+
z.class # SubSubKlass, which < SubKlass
|
150
|
+
z.second # 'totally new value'
|
151
|
+
```
|
152
|
+
|
153
|
+
### Adding and Removing Readers and Writers
|
154
|
+
Now, if we were to later add a new default to Plant
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
Plant.defaults[:climate] = 'temperate'
|
158
|
+
```
|
159
|
+
|
160
|
+
and ran
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
rhododendron.climate
|
164
|
+
```
|
165
|
+
|
166
|
+
we would get a `NoMethodError`.
|
167
|
+
|
168
|
+
By default, CushionDefaults does not automatically add or remove readers and writers when defaults are added and removed. To change methods, you need to manually add readers and writers for the new default:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
Plant.cushion :climate
|
172
|
+
```
|
173
|
+
|
174
|
+
If at any point you want to manually remove the `cushion_reader` or `cushion_writer` for a class (although the need for this should be rare, as you can simply overwrite it), you can run the following:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
Plant.remove_reader :climate
|
178
|
+
Plant.remove_writer :climate
|
179
|
+
```
|
180
|
+
|
181
|
+
Alternatively, CushionDefaults can automatically add and remove methods for any new defaults added and any existing defaults removed. But to do that, we need to configure CushionDefaults.
|
182
|
+
|
183
|
+
### Configuring CushionDefaults
|
184
|
+
|
185
|
+
There are two recommended techniques for configuring CushionDefaults (although a few other variations will work as well).
|
186
|
+
|
187
|
+
The simplest is to use `CushionDefaults.configure`, which yields a `CushionDefaults::Configuration` object that can be modified by a number of different methods, detailed in the docs.
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
CushionDefaults.configure do |conf|
|
191
|
+
conf.update_readers = true
|
192
|
+
conf.update_writers = true
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
196
|
+
If the above `#configure` call is placed immediately after the require statement, then no explicit calls to `cushion`, `cushion_reader`, or `cushion_writer` are needed.
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
CushionDefaults.configure do |conf|
|
200
|
+
conf.update_readers = true
|
201
|
+
conf.update_writers = true
|
202
|
+
end
|
203
|
+
|
204
|
+
class Chair
|
205
|
+
include CushionDefaults
|
206
|
+
self.defaults = {material: 'wood', comfort_factor: 5}
|
207
|
+
end
|
208
|
+
|
209
|
+
dining_room_hardback = Chair.new
|
210
|
+
dining_room_hardback.comfort_factor = 3
|
211
|
+
|
212
|
+
dining_room_hardback.material # 'wood'
|
213
|
+
dining_room_hardback.comfort_factor # 3
|
214
|
+
|
215
|
+
# automatically adds #number_accomodated and #number_accomodated=, because of above-specified options
|
216
|
+
Chair.defaults[:number_accomodated] = 1
|
217
|
+
|
218
|
+
dining_room_hardback.number_accomodated # 1
|
219
|
+
```
|
220
|
+
|
221
|
+
As an alternative to the `CushionDefaults.configure` block, you can define a cushion_defaults.yaml file. By default, CushionDefaults looks for this at `config/cushion_defaults.yaml` (relative either to the directory of the first file to require CushionDefaults or the gem's location in the file system). The YAML format is unremarkable, with the above `CushionDefaults.config do ... end` block equivalent to:
|
222
|
+
|
223
|
+
```yaml
|
224
|
+
update_readers: true
|
225
|
+
update_writers: true
|
226
|
+
```
|
227
|
+
|
228
|
+
### Storing Class Defaults in YAML Files
|
229
|
+
|
230
|
+
By default, CushionDefaults checks for YAML files for each class but does not complain if no YAML files are found. (If you want it to complain, set `config.whiny_yaml` to true.)
|
231
|
+
|
232
|
+
CushionDefaults looks for these YAML files at `config/cushion_defaults/class_name.yaml`. For class Klass, then, it would expect a config file at `config/cushion_defaults/klass.yaml`. Classes in a namespace are expected to have their YAML files in a folder named after their namespace, e.g. Modjewel::Klass in `config/cushion_defaults/modjewel/klass.yaml`.
|
233
|
+
|
234
|
+
You can specify a different YAML source folder relative to the calling directory (`config/cushion_defaults/` by default) by setting `config.yaml_source_folder`, or you can specify an absolute path to the YAML source folder by setting `config.yaml_source_full_path`.
|
235
|
+
|
236
|
+
If you ever are bug-hunting and want to see where CushionDefaults expects a YAML file to be located, you can pass the class object to `config.yaml_file_for(klass)`.
|
237
|
+
|
238
|
+
These YAML files are loaded automatically (unless `config.auto_load_from_yaml` has been set to false). But if you ever want to (wipe and) reload the defaults for a class—or load for the first time if the above option is disabled—use the class method `defaults_from_yaml`.
|
239
|
+
|
240
|
+
### Managing Multiple Class Defaults
|
241
|
+
|
242
|
+
#### Multiple Sets of Class Defaults
|
243
|
+
|
244
|
+
You can use the above techniques to maintain different sets of class defaults for all of your classes. This is especially useful if your application needs to run in different environments or regions. For a more complex (but still simple enough) example, see Example 4 in the examples folder. Following is a trivial example.
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
class Season
|
248
|
+
# Obviously this is only meteorological seasons, and only valid for the Northern hemisphere
|
249
|
+
attr_accessor :months, :short_code
|
250
|
+
def initialize(&block)
|
251
|
+
yield(self) if block_given?
|
252
|
+
end
|
253
|
+
def include?(date)
|
254
|
+
months.include?(date.month)
|
255
|
+
end
|
256
|
+
def yaml_source_path
|
257
|
+
"config/cushion_defaults/#{short_code}/"
|
258
|
+
end
|
259
|
+
end
|
260
|
+
seasons = [
|
261
|
+
Season.new {|s| s.months=[3,4,5]; s.short_code='spr'},
|
262
|
+
Season.new {|s| s.months=[6,7,8]; s.short_code='sum'},
|
263
|
+
Season.new {|s| s.months=[9,10,11]; s.short_code='fal'},
|
264
|
+
Season.new {|s| s.months=[12,1,2]; s.short_code='win'}
|
265
|
+
]
|
266
|
+
current_season = seasons.select{|s| s.include?(Date.today)}.first
|
267
|
+
CushionDefaults.configure {|conf| conf.yaml_source_path = current_season.yaml_source_path}
|
268
|
+
```
|
269
|
+
|
270
|
+
The above will set the root directory for all class defaults, depending on the current date, to one of the following: `config/cushion_defaults/spr/`, `config/cushion_defaults/sum/`, `config/cushion_defaults/fal/`, or `config/cushion_defaults/win/`.
|
271
|
+
|
272
|
+
#### Multiple Defaults for a Single Class
|
273
|
+
|
274
|
+
Alternatively, if there is only a single class whose defaults you would like to load in one of several forms, you can do something like the following:
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
# select a random language
|
278
|
+
current_lang = ['en','fr','de'].sample
|
279
|
+
class Person
|
280
|
+
include CushionDefaults
|
281
|
+
defaults_from_yaml "#{self.to_s}_#{current_lang}"
|
282
|
+
cushion_defaults
|
283
|
+
end
|
284
|
+
```
|
285
|
+
|
286
|
+
In this example, we load (randomly) either `person_en.yaml`, `person_fr.yaml`, or `person_de.yaml`.
|
287
|
+
|
288
|
+
### Pushy and Polite Defaults
|
289
|
+
|
290
|
+
Pushy and polite defaults are an experimental feature. Rough documentation can be found in the docs, and more details will be forthcoming.
|
291
|
+
|
292
|
+
### Testing and Bug Fixing
|
293
|
+
|
294
|
+
The most common testing configuration options are available by calling `config.testing!`.
|
295
|
+
|
296
|
+
You may find the following methods helpful in testing and bug fixing:
|
297
|
+
|
298
|
+
- `instance#has_specified?(sym)`: returns true if the instance has the instance variable denoted by `sym` defined
|
299
|
+
- `defaults#ish_keys`: returns the keys of both its defaults and those it inherits from its parents
|
300
|
+
- `defaults#has_ish_key?(key)`: returns true if `key` is an ish_key.
|
301
|
+
- `defaults#where_is_that_default_again(sym)`: returns the closest ancestor class in which `sym` is defined as a default. If no ancestor class has it defined as a default, returns `nil`.
|
302
|
+
|
303
|
+
## But I need...
|
304
|
+
|
305
|
+
If you need more than CushionDefaults offers right now, you've got a couple different options:
|
306
|
+
|
307
|
+
1. Suggest a feature. Please explain why you think this feature would be valuable, and offer a couple different use cases to showcase how it would help people.
|
308
|
+
2. Code a feature. Fork and pull, and I'll fold it in and implement it if it looks solid and generally useful.
|
309
|
+
3. Check out Cascading Configuration. You may want to check out [Cascading Configuration](https://github.com/RidiculousPower/cascading_configuration), which tackles a similar problem but offers a different approach and featureset.
|
310
|
+
|
311
|
+
## Feedback
|
312
|
+
|
313
|
+
Any feedback is very much appreciated!
|
314
|
+
|
315
|
+
###Bugs
|
316
|
+
|
317
|
+
Run into any bugs or issues? Please report them on the [GitHub issue tracker](https://github.com/posgarou/cushion_defaults/issues).
|
318
|
+
|
319
|
+
###Like It?
|
320
|
+
|
321
|
+
1. Tell a friend.
|
322
|
+
2. Star or [fork](https://github.com/posgarou/cushion_defaults/fork) the [GitHub repository](https://github.com/posgarou/cushion_defaults).
|
323
|
+
3. If you're feeling generous, [offer a tip](https://gratipay.com/posgarou/).
|
324
|
+
|
325
|
+
###Not Sold?
|
326
|
+
|
327
|
+
If you have the time, tell me why.
|
328
|
+
|
329
|
+
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.
|
330
|
+
|
331
|
+
If you have any specific suggestions for how to make CushionDefaults better, I'd love to hear them.
|
data/lib/cushion_defaults.rb
CHANGED
@@ -27,7 +27,7 @@ require 'cushion_defaults/defaults_hash'
|
|
27
27
|
module CushionDefaults
|
28
28
|
|
29
29
|
# Version constant
|
30
|
-
VERSION = '0.
|
30
|
+
VERSION = '0.2.0'
|
31
31
|
|
32
32
|
# The path of the first file that +includes+ CushionDefaults.
|
33
33
|
CALLING_PATH = File.expand_path(File.dirname($0)) + '/'
|
@@ -1,199 +1,199 @@
|
|
1
1
|
require 'logger'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
3
|
+
module CushionDefaults
|
4
|
+
# Effectively a singleton class, +Configuration+ keeps track of various configuration options for +CushionDefaults+.
|
5
|
+
#
|
6
|
+
# In addition, it also keeps track of certain state data for +CushionDefaults+.
|
7
|
+
#
|
8
|
+
# For configuration options, see the attribute writers.
|
9
|
+
#
|
10
|
+
class Configuration
|
11
|
+
# adapted from http://brandonhilkert.com/blog/ruby-gem-configuration-patterns/
|
12
|
+
|
13
|
+
# @return [boolean] true if readers should automatically be added, false otherwise. Def. false.
|
14
|
+
attr_accessor :update_readers
|
15
|
+
|
16
|
+
# @return [boolean] true if writers should automatially be added, false otherwise. Def. false.
|
17
|
+
attr_accessor :update_writers
|
18
|
+
|
19
|
+
# @return [boolean] true if CushionDefaults should automatically check whether a YAML file exists for each class that
|
20
|
+
# includes it, false otherwise. Def. true.
|
21
|
+
# @see ClassMethods.defaults_from_yaml defauls_from_yaml
|
22
|
+
attr_accessor :auto_load_from_yaml
|
23
|
+
|
24
|
+
# @return [string] the location, relative to the directory of the first file to include CushionDefaults, where YAML
|
25
|
+
# config files are looked for by default. Def. 'config/cushion_defaults/'
|
26
|
+
attr_accessor :yaml_source_folder
|
27
|
+
|
28
|
+
# @return [string] the full path to the directory where YAML config files are looked for. If specified, this overrides
|
29
|
+
# {#yaml_source_folder}. Def. nil.
|
30
|
+
attr_writer :yaml_source_full_path
|
31
|
+
|
32
|
+
# @return [boolean] if true, CushionDefaults makes log entries. Def. true.
|
33
|
+
attr_accessor :record_in_log
|
34
|
+
|
35
|
+
# @return [boolean] if true, CushionDefaults will complain (warning level) when it cannot find a YAML configuration
|
36
|
+
# file for a class. If false, it only posts this message at the debug level. Def. false.
|
37
|
+
attr_accessor :whiny_yaml
|
38
|
+
|
39
|
+
# @return [boolean] if true, calls to cushion_writer methods will not set the instance variable if the value passed in
|
40
|
+
# is nil (thus allowing cushion_reader to still return the default value, rather than nil). Def. true.
|
41
|
+
# @example Ignored Call to cushion_writer When ignore_attempts_to_set_nil == true
|
42
|
+
# obj.var = nil # has no effect
|
43
|
+
# obj.var # 'default value'
|
44
|
+
# @see blank_str_is_nil
|
45
|
+
attr_accessor :ignore_attempts_to_set_nil
|
46
|
+
|
47
|
+
# @return [boolean] if true, cushion_writers will also not record blank strings. Has no effect if
|
48
|
+
# {#ignore_attempts_to_set_nil} is false. Def. true.
|
49
|
+
# @example Ignored Call to cushion_writer When blank_str_is_nil == false
|
50
|
+
# obj.var = '' # has no effect
|
51
|
+
# obj.var # 'default value'
|
52
|
+
attr_accessor :blank_str_is_nil
|
53
|
+
|
54
|
+
# @return [boolean] if true, cushion_writers will issue a warning when you attempt to set an instance variable to
|
55
|
+
# nil or '' (as applicable). Has no effect if {#ignore_attempts_to_set_nil} == false. Def. false.
|
56
|
+
attr_accessor :whiny_ignores
|
57
|
+
|
58
|
+
# Has no effect.
|
59
|
+
# @deprecated
|
60
|
+
attr_accessor :cushion_child_defaults_in_parent
|
61
|
+
|
62
|
+
# @return [Logger] returns the Logger object to which CushionDefaults sends log entries.
|
63
|
+
attr_reader :logger
|
64
|
+
|
65
|
+
# Initializes with the values specified in {#defaults!}
|
66
|
+
def initialize
|
67
|
+
defaults!
|
68
|
+
@we_have_a_pushy = false
|
69
|
+
end
|
71
70
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
71
|
+
# @!attribute [r] yaml_source_full_path
|
72
|
+
# Returns or computes the folder where class-specific yaml files are expected to reside.
|
73
|
+
# @return [String] path to yaml config files.
|
74
|
+
def yaml_source_full_path
|
75
|
+
@yaml_source_full_path || CushionDefaults::CALLING_PATH + yaml_source_folder
|
76
|
+
end
|
78
77
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
78
|
+
# Update configuration options with those values contained within +loaded_config+.
|
79
|
+
#
|
80
|
+
# @param loaded_config [Hash] hash of config options and settings
|
81
|
+
def from_hash(loaded_config)
|
82
|
+
log_str = 'Loading configuration options from hash...'
|
83
|
+
loaded_config.each do |key, val|
|
84
|
+
log_str << "\n\t\t#{key}: #{val}"
|
85
|
+
# We need to be able to use some of the checks and responses in the custom setter methods
|
86
|
+
writer_sym = "#{key}=".to_sym
|
87
|
+
send writer_sym, val if respond_to? writer_sym
|
88
|
+
#instance_variable_set("@#{key.to_sym}", val) if instance_variable_defined? "@#{key.to_sym}"
|
89
|
+
end
|
90
|
+
CushionDefaults.log(log_str, :info)
|
90
91
|
end
|
91
|
-
CushionDefaults.log(log_str, :info)
|
92
|
-
end
|
93
92
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
93
|
+
# Sets or resets configuration object to default configuration settings.
|
94
|
+
def defaults!
|
95
|
+
self.update_readers = false
|
96
|
+
self.update_writers = false
|
97
|
+
self.auto_load_from_yaml = true
|
98
|
+
self.yaml_source_folder = 'config/cushion_defaults/'
|
99
|
+
self.yaml_source_full_path = nil
|
100
|
+
self.record_in_log = true
|
101
|
+
self.logger = Logger.new $stdout
|
102
|
+
self.log_lvl = Logger::INFO
|
103
|
+
self.whiny_yaml = false
|
104
|
+
self.whiny_ignores = false
|
105
|
+
self.ignore_attempts_to_set_nil = true
|
106
|
+
self.blank_str_is_nil = true
|
107
|
+
end
|
109
108
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
109
|
+
# Sets configuration object to most common testing (and development) options.
|
110
|
+
def test_settings!
|
111
|
+
defaults!
|
112
|
+
self.whiny_yaml = true
|
113
|
+
self.whiny_ignores = true
|
114
|
+
self.log_lvl = Logger::DEBUG
|
115
|
+
end
|
117
116
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
else
|
129
|
-
if new_logger.respond_to? :info
|
130
|
-
@logger = new_logger
|
131
|
-
assign_formatter_to_logger
|
117
|
+
# @!attribute [w] logger
|
118
|
+
# Sets @logger to either a different Logger or another object that implements roughly the same interface. Note
|
119
|
+
# that only minimal checking is done to ensure the interface is implemented, and passing in an object with an
|
120
|
+
# incomplete implementation of the interface will cause errors. If you want to disable logging, you should instead
|
121
|
+
# set {#record_in_log} to false.
|
122
|
+
# @see #record_in_log
|
123
|
+
def logger=(new_logger)
|
124
|
+
if new_logger.nil?
|
125
|
+
self.record_in_log = false
|
126
|
+
@logger = nil
|
132
127
|
else
|
133
|
-
|
128
|
+
if new_logger.respond_to? :info
|
129
|
+
@logger = new_logger
|
130
|
+
assign_formatter_to_logger
|
131
|
+
else
|
132
|
+
CushionDefaults.log("config.logger not set to #{new_logger}, as it does not appear to implement standard logging methods.", :error)
|
133
|
+
end
|
134
134
|
end
|
135
135
|
end
|
136
|
-
end
|
137
136
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
137
|
+
# CamelCase to underscores
|
138
|
+
# @param s [String] CamelCase
|
139
|
+
# @return [String] underscored version of s
|
140
|
+
def underscore(s)
|
141
|
+
s.gsub(/::/, '/').
|
142
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
143
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
144
|
+
tr("-", "_").
|
145
|
+
downcase
|
146
|
+
end
|
148
147
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
148
|
+
# Expected YAML location for a class.
|
149
|
+
# @param klass [Class] class in question.
|
150
|
+
# @return [String] expected path to the YAML file
|
151
|
+
def yaml_file_for(klass)
|
152
|
+
"#{CushionDefaults::configuration.yaml_source_full_path}#{underscore(klass.to_s)+'.yaml'}"
|
153
|
+
end
|
155
154
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
155
|
+
# @!attribute [w] log_lvl
|
156
|
+
# Set the level of logging. Alias of logger.level=. 0: debug, 1: info, 2: warn, 3: error, 4: fatal, 5: unknown
|
157
|
+
def log_lvl=(lvl)
|
158
|
+
if @logger
|
159
|
+
@logger.level = lvl
|
160
|
+
end
|
161
161
|
end
|
162
|
-
end
|
163
162
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
163
|
+
# @!attribute [r] we_have_a_pushy
|
164
|
+
# Denotes whether (over the life of the program) any defaults have been marked pushy.
|
165
|
+
# In the future, this may instead denote whether any defaults are *currently* marked as pushy.
|
166
|
+
# @return [boolean] true if a pushy was set at any time
|
167
|
+
# @api private
|
168
|
+
def we_have_a_pushy?
|
169
|
+
@we_have_a_pushy
|
170
|
+
end
|
172
171
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
172
|
+
# Opposite of {#we_have_a_pushy?}
|
173
|
+
# @return [boolean] true if no pushies have been set over the life of the program
|
174
|
+
# @api private
|
175
|
+
def no_pushies?
|
176
|
+
# Note that if you add a pushy, and then remove it, this will still return false. Basically, the method returns
|
177
|
+
# whether there was, at any point in time, a pushy default.
|
178
|
+
#
|
179
|
+
# The whole handling of pushies can and will be improved in the future.
|
180
|
+
!@we_have_a_pushy
|
181
|
+
end
|
183
182
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
183
|
+
# Declares that a pushy default has been set.
|
184
|
+
# @api private
|
185
|
+
def we_have_a_pushy!
|
186
|
+
@we_have_a_pushy = true
|
187
|
+
end
|
189
188
|
|
190
|
-
|
189
|
+
protected
|
191
190
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
191
|
+
# Specifies the formatter for logger. Will not be called if a logger is assigned with +should_assign_formatter+ set to
|
192
|
+
# false.
|
193
|
+
def assign_formatter_to_logger
|
194
|
+
logger.formatter = proc do |severity, time, progname, msg|
|
195
|
+
"\nCUSHIONDEFAULTS: #{severity}\n\t\##{progname ? "#{progname} at" : "At"} #{time.strftime('%d %b %Y, %H:%M:%S%p')}\n\t#{msg}\n"
|
196
|
+
end
|
197
197
|
end
|
198
198
|
end
|
199
199
|
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
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Mitchell
|
@@ -9,15 +9,47 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2014-12-09 00:00:00.000000000 Z
|
12
|
-
dependencies:
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: yard
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.8'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.8'
|
13
41
|
description: Allows you to specify a 'cushion' for various instance variables—effectively
|
14
42
|
a default value—that will be returned by optional reader methods if the instance
|
15
43
|
variable is undefined.
|
16
44
|
email: posgarou@gmail.com
|
17
45
|
executables: []
|
18
46
|
extensions: []
|
19
|
-
extra_rdoc_files:
|
47
|
+
extra_rdoc_files:
|
48
|
+
- README.md
|
49
|
+
- CHANGELOG.md
|
20
50
|
files:
|
51
|
+
- CHANGELOG.md
|
52
|
+
- README.md
|
21
53
|
- lib/cushion_defaults.rb
|
22
54
|
- lib/cushion_defaults/class_methods.rb
|
23
55
|
- lib/cushion_defaults/configuration.rb
|
@@ -26,8 +58,12 @@ homepage: https://github.com/posgarou/cushion_defaults
|
|
26
58
|
licenses:
|
27
59
|
- MIT
|
28
60
|
metadata: {}
|
29
|
-
post_install_message:
|
30
|
-
|
61
|
+
post_install_message: |-
|
62
|
+
Thanks for installing CushionDefaults!
|
63
|
+
For a quick overview, check out the README and examples/ folder.
|
64
|
+
rdoc_options:
|
65
|
+
- "--main"
|
66
|
+
- README.md
|
31
67
|
require_paths:
|
32
68
|
- lib
|
33
69
|
required_ruby_version: !ruby/object:Gem::Requirement
|