settingable 0.2.1 → 0.3.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 +4 -4
- data/README.md +63 -12
- data/lib/settingable/hash.rb +107 -0
- data/lib/settingable/settings.rb +32 -23
- data/lib/settingable/version.rb +1 -1
- data/lib/settingable.rb +1 -0
- data/spec/settingable/hash_spec.rb +62 -0
- data/spec/settingable/settings_spec.rb +4 -4
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 903821ee3c31b65a77729e2eb36ed6d295f00a4a
|
4
|
+
data.tar.gz: 2bdb5c3ea7c51f10c2e7b6f847859372b81508d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 972a99a3465daf5cdca3fbb336815b1351d32b2fa46f257304bbcdac6e185aaff902bfd7f601d2d6d6bed0c63cf40b23b8d3351fb9ee47e8667194863bcc03ce
|
7
|
+
data.tar.gz: fe8b359de31ce9452f6489b0970707a38539f5d5dbd1d039913477d9034f1dc5a51de8c7121fc1af70edd66de94d8b580861d4066d6bf737900a46803150da33
|
data/README.md
CHANGED
@@ -1,27 +1,78 @@
|
|
1
|
-
# settingable
|
1
|
+
# settingable [](https://travis-ci.org/medcat/settingable)
|
2
2
|
|
3
3
|
* [Homepage](https://rubygems.org/gems/settingable)
|
4
4
|
* [Documentation](http://rubydoc.info/gems/settingable/frames)
|
5
|
-
* [Email](mailto:
|
5
|
+
* [Email](mailto:me@medcat.me)
|
6
|
+
|
7
|
+
## Install
|
8
|
+
|
9
|
+
$ gem install settingable
|
6
10
|
|
7
11
|
## Description
|
8
12
|
|
9
|
-
|
13
|
+
A Settings module for your application. Its job is to make handling
|
14
|
+
user-definable settings easy for you, so you can focus on the more
|
15
|
+
important parts of your library. The main component of it is the
|
16
|
+
`Settingable::Settings` module. Just include that in a class, and
|
17
|
+
you're good to go.
|
10
18
|
|
11
|
-
|
19
|
+
```Ruby
|
20
|
+
module MyLibrary
|
21
|
+
class Settings
|
22
|
+
include Settingable::Settings
|
23
|
+
end
|
24
|
+
```
|
12
25
|
|
13
|
-
|
26
|
+
The `Settings` module provides a few methods. First, it defines the
|
27
|
+
`.instance` class method. This returns a single instance upon
|
28
|
+
repeated invocation, like the `Singleton` module. Then, it defines
|
29
|
+
the `.configure` method. This is used to (*ahem*) configure the
|
30
|
+
settings. It both yields itself and runs it in its context, so you
|
31
|
+
may configure it however you like. It also forwards the methods
|
32
|
+
`.[]`, `.[]=`, and `.fetch` on to the actual instance itself as well.
|
14
33
|
|
15
|
-
|
34
|
+
The instance forwards the `#[]`, `#[]=`, `#fetch`, and `#key?` method
|
35
|
+
on to the `Settingable::Hash` powering the settings (see the
|
36
|
+
documentation for more). The instance can also accept any other
|
37
|
+
methods. If the method ends in an equal sign (i.e. a setter method),
|
38
|
+
it is forwarded to `#[]=`; otherwise, it is forwarded to `#[]`.
|
16
39
|
|
17
|
-
|
40
|
+
Here's a few examples.
|
18
41
|
|
19
|
-
|
42
|
+
```Ruby
|
43
|
+
MyLibrary::Settings.configure do |config|
|
44
|
+
# These two are the same.
|
45
|
+
config.value = 2
|
46
|
+
config[:value] = 2
|
47
|
+
end
|
20
48
|
|
21
|
-
|
49
|
+
# These all are the same, and return the same value.
|
50
|
+
MyLibrary::Settings.value
|
51
|
+
MyLibrary::Settings[:value]
|
52
|
+
MyLibrary::Settings["value"]
|
53
|
+
MyLibrary::Settings.instance.value
|
54
|
+
MyLibrary::Settings.instance[:value]
|
55
|
+
MyLibrary::Settings.instance["value"]
|
56
|
+
```
|
57
|
+
|
58
|
+
If you attempt to access a setting value that isn't defined, even if
|
59
|
+
you use the regular accessor (`#[]`), a `KeyError` will be raised.
|
60
|
+
So, the module provides a `.default_settings` method, for you to
|
61
|
+
provide default values for the settings.
|
62
|
+
|
63
|
+
```Ruby
|
64
|
+
module MyLibrary
|
65
|
+
class Settings
|
66
|
+
include Settingable::Settings
|
22
67
|
|
23
|
-
|
68
|
+
default_settings foo: "bar"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
```
|
24
72
|
|
25
|
-
|
73
|
+
Now, check it out.
|
26
74
|
|
27
|
-
|
75
|
+
```Ruby
|
76
|
+
MyLibrary::Settings[:foo] # => "bar"
|
77
|
+
MyLibrary::Settings[:bar] # ! KeyError
|
78
|
+
```
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Settingable
|
4
|
+
# A hash that raises an error on an access that fails.
|
5
|
+
class Hash < ::Hash
|
6
|
+
# Initialize the hash. If a value is passed, the values are set
|
7
|
+
# on this hash. Does not modify the value.
|
8
|
+
#
|
9
|
+
# @param body [Hash]
|
10
|
+
# @see #convert
|
11
|
+
def initialize(body = {})
|
12
|
+
body.each do |key, value|
|
13
|
+
self[key] = value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :old_key?, :key?
|
18
|
+
|
19
|
+
# Checks to determine if the given key is set in this hash. It
|
20
|
+
# first converts the key to a Symbol, then performs the check.
|
21
|
+
#
|
22
|
+
# @param key [String, Symbol, Object]
|
23
|
+
# @return [Boolean]
|
24
|
+
def key?(key)
|
25
|
+
case key
|
26
|
+
when String then super(key.intern)
|
27
|
+
when Symbol then super(key)
|
28
|
+
else super(key.to_s.intern)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method :old_access, :[]
|
33
|
+
# Accesses the hash. It raises an error if it can't find the
|
34
|
+
# key. It attempts to convert the key into a Symbol.
|
35
|
+
#
|
36
|
+
# @param key [String, Symbol, Object] The key.
|
37
|
+
# @return [Object] The value.
|
38
|
+
# @raise KeyError If the key isn't mapped to a value.
|
39
|
+
def [](key)
|
40
|
+
case key
|
41
|
+
when String then fetch(key.intern)
|
42
|
+
when Symbol then fetch(key)
|
43
|
+
else fetch(key.to_s.intern)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
alias_method :old_set, :[]=
|
48
|
+
# Used to set a key to a value. The key is first converted to a
|
49
|
+
# Symbol, and the value is coerced into a {Hash} if it is a
|
50
|
+
# `::Hash`.
|
51
|
+
#
|
52
|
+
# @param key [String, Symbol, Object]
|
53
|
+
# @param value [Hash, Object]
|
54
|
+
# @return [void]
|
55
|
+
# @see #convert
|
56
|
+
def []=(key, value)
|
57
|
+
case key
|
58
|
+
when String then super(key.intern, convert(value))
|
59
|
+
when Symbol then super(key, convert(value))
|
60
|
+
else super(key.to_s.intern, convert(value))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# A blank object used as a canary for {#fetch}.
|
65
|
+
#
|
66
|
+
# @api private
|
67
|
+
BLANK_OBJECT = Object.new
|
68
|
+
private_constant :BLANK_OBJECT
|
69
|
+
|
70
|
+
alias_method :old_fetch, :fetch
|
71
|
+
# Performs a fetch. If the key is in the hash, it returns its
|
72
|
+
# value. If the key is not in the hash, and a value was passed,
|
73
|
+
# the value is returned. If the key is not in the hash, and a
|
74
|
+
# block was passed, it yields. Otherwise, it raises KeyError.
|
75
|
+
#
|
76
|
+
# @param key [Object] The key.
|
77
|
+
# @param value [Object] The default object.
|
78
|
+
# @yield [key]
|
79
|
+
# @return [Object]
|
80
|
+
# @raise KeyError If the key isn't in the hash and no default
|
81
|
+
# was given.
|
82
|
+
def fetch(key, value = BLANK_OBJECT)
|
83
|
+
case
|
84
|
+
when old_key?(key) then old_access(key)
|
85
|
+
when block_given? then yield(key)
|
86
|
+
when value != BLANK_OBJECT then value
|
87
|
+
else
|
88
|
+
fail KeyError, "Key not found: #{key.inspect}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
# Converts the value to a {Settingable::Hash}, if it is a regular
|
95
|
+
# ruby hash. Otherwise, it returns the value.
|
96
|
+
#
|
97
|
+
# @param value [Hash, Object]
|
98
|
+
# @return [Settingable::Hash, Object]
|
99
|
+
def convert(value)
|
100
|
+
if value.is_a?(::Hash)
|
101
|
+
Settingable::Hash.new(value)
|
102
|
+
else
|
103
|
+
value
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/settingable/settings.rb
CHANGED
@@ -9,15 +9,15 @@ module Settingable
|
|
9
9
|
module ClassMethods
|
10
10
|
extend Forwardable
|
11
11
|
|
12
|
-
def_delegators :
|
12
|
+
def_delegators :instance, :[], :[]=, :fetch,
|
13
13
|
:method_missing
|
14
|
-
def_delegator :
|
14
|
+
def_delegator :instance, :build, :configure
|
15
15
|
|
16
16
|
# Returns an instance of the included module. Repeated calls
|
17
17
|
# return the same instance.
|
18
18
|
#
|
19
19
|
# @return [Settings]
|
20
|
-
def
|
20
|
+
def instance
|
21
21
|
@_settings ||= new
|
22
22
|
end
|
23
23
|
|
@@ -46,14 +46,14 @@ module Settingable
|
|
46
46
|
end
|
47
47
|
|
48
48
|
extend Forwardable
|
49
|
-
def_delegators :@settings, :fetch
|
49
|
+
def_delegators :@settings, :fetch, :[], :[]=, :key?
|
50
50
|
|
51
51
|
# Initialize the settings. Merges the given settings to the default
|
52
52
|
# settings.
|
53
53
|
#
|
54
54
|
# @param settings [Hash] The initial settings.
|
55
55
|
def initialize(settings = {})
|
56
|
-
@settings =
|
56
|
+
@settings = Settingable::Hash.new(merged_settings(settings))
|
57
57
|
end
|
58
58
|
|
59
59
|
# Builds the settings construct. It yields itself, and then returns
|
@@ -66,23 +66,6 @@ module Settingable
|
|
66
66
|
self
|
67
67
|
end
|
68
68
|
|
69
|
-
# Sets a key to a value.
|
70
|
-
#
|
71
|
-
# @param key [Symbol, String] The key.
|
72
|
-
# @param value [Object] The value.
|
73
|
-
# @return [void]
|
74
|
-
def []=(key, value)
|
75
|
-
@settings[key.to_s.to_sym] = value
|
76
|
-
end
|
77
|
-
|
78
|
-
# Retrieves a key. If it doesn't exist, it errors.
|
79
|
-
#
|
80
|
-
# @param key [Symbol, String] The key.
|
81
|
-
# @return [Object]
|
82
|
-
def [](key)
|
83
|
-
@settings.fetch(key.to_s.to_sym)
|
84
|
-
end
|
85
|
-
|
86
69
|
# Method missing. For set methods, it maps to the `:[]=` method; for
|
87
70
|
# regular methods (i.e. not bang or ? methods), it maps to the `:[]`
|
88
71
|
# method.
|
@@ -93,9 +76,35 @@ module Settingable
|
|
93
76
|
map_method(method, args)
|
94
77
|
end
|
95
78
|
|
79
|
+
# A hook method for ruby. This should not be called directly. It
|
80
|
+
# lets ruby know that we respond to certain methods.
|
81
|
+
#
|
82
|
+
# @param method [Symbol] The method to check.
|
83
|
+
# @return [Boolean]
|
84
|
+
def responds_to_missing?(method, _include_all = false)
|
85
|
+
!(method =~ /(\?|\!)\z/)
|
86
|
+
end
|
87
|
+
|
96
88
|
private
|
97
89
|
|
98
|
-
#
|
90
|
+
# Merges the given settings with the class defaults. Performs a
|
91
|
+
# deep merge.
|
92
|
+
#
|
93
|
+
# @param provided [Hash]
|
94
|
+
# @return [Hash]
|
95
|
+
def merged_settings(provided)
|
96
|
+
DeepMerge.deep_merge(self.class.default_settings, provided)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Maps the methods. If the method is a setter, i.e. ends in `=`,
|
100
|
+
# it sets the value. If arguments are provided to a non-setter
|
101
|
+
# method, it raises a NameError.
|
102
|
+
#
|
103
|
+
# @param method [Symbol]
|
104
|
+
# @param args [Array<Object>]
|
105
|
+
# @return [Object]
|
106
|
+
# @raise NameError If more than 1 argument is provided to a
|
107
|
+
# setter, or arguments are provided to a non-setter method.
|
99
108
|
def map_method(method, args)
|
100
109
|
if method =~ /\A(.*)\=\z/ && args.length == 1
|
101
110
|
self[$+] = args[0]
|
data/lib/settingable/version.rb
CHANGED
data/lib/settingable.rb
CHANGED
@@ -0,0 +1,62 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
RSpec.describe Settingable::Hash do
|
4
|
+
let(:body) { { foo: { bar: "baz" }, hello: "world" } }
|
5
|
+
subject { Settingable::Hash.new(body) }
|
6
|
+
|
7
|
+
describe "#initialize" do
|
8
|
+
it "converts hash values" do
|
9
|
+
expect(subject[:foo]).to be_a Settingable::Hash
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#[]" do
|
14
|
+
it "raises on an invalid key" do
|
15
|
+
expect { subject[:bar] }.to raise_error(KeyError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "allows access by other objects" do
|
19
|
+
expect(subject[:hello]).to eq "world"
|
20
|
+
expect(subject["hello"]).to eq "world"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#[]=" do
|
25
|
+
it "converts hash values" do
|
26
|
+
subject[:hello] = { foo: { bar: "baz" } }
|
27
|
+
expect(subject[:hello]).to be_a(Settingable::Hash)
|
28
|
+
expect(subject[:hello][:foo]).to be_a(Settingable::Hash)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "sets the symbol" do
|
32
|
+
subject["bar"] = "baz"
|
33
|
+
expect(subject[:bar]).to eq "baz"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#key?" do
|
38
|
+
it "returns true for other objects" do
|
39
|
+
expect(subject.key?(:hello)).to be true
|
40
|
+
expect(subject.key?("hello")).to be true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#fetch" do
|
45
|
+
let(:fake) { double("fake") }
|
46
|
+
it "returns the double on a bad key" do
|
47
|
+
expect(subject.fetch(:bar, fake)).to be fake
|
48
|
+
end
|
49
|
+
|
50
|
+
it "yields on a bad key" do
|
51
|
+
expect { |y| subject.fetch(:bar, &y) }.to yield_control
|
52
|
+
end
|
53
|
+
|
54
|
+
it "yields first on a bad key" do
|
55
|
+
expect { |y| subject.fetch(:bar, :baz, &y) }.to yield_control
|
56
|
+
end
|
57
|
+
|
58
|
+
it "raises on a bad key with no default" do
|
59
|
+
expect { subject.fetch(:bar) }.to raise_error(KeyError)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -11,7 +11,7 @@ class Configuration
|
|
11
11
|
end
|
12
12
|
|
13
13
|
RSpec.describe Settingable::Settings do
|
14
|
-
subject { Configuration.
|
14
|
+
subject { Configuration.instance }
|
15
15
|
before(:each) { Configuration.reset! }
|
16
16
|
|
17
17
|
it "extends the base" do
|
@@ -20,10 +20,10 @@ RSpec.describe Settingable::Settings do
|
|
20
20
|
.to include(Settingable::Settings::ClassMethods)
|
21
21
|
end
|
22
22
|
|
23
|
-
describe ".
|
23
|
+
describe ".instance" do
|
24
24
|
it "returns the same instance" do
|
25
|
-
expect(Configuration.
|
26
|
-
expect(Configuration.
|
25
|
+
expect(Configuration.instance).to be_a(Configuration)
|
26
|
+
expect(Configuration.instance).to be(Configuration.instance)
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: settingable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Rodi
|
@@ -84,10 +84,12 @@ files:
|
|
84
84
|
- Rakefile
|
85
85
|
- lib/settingable.rb
|
86
86
|
- lib/settingable/deep_merge.rb
|
87
|
+
- lib/settingable/hash.rb
|
87
88
|
- lib/settingable/settings.rb
|
88
89
|
- lib/settingable/version.rb
|
89
90
|
- settingable.gemspec
|
90
91
|
- spec/settingable/deep_merge_spec.rb
|
92
|
+
- spec/settingable/hash_spec.rb
|
91
93
|
- spec/settingable/settings_spec.rb
|
92
94
|
- spec/settingable_spec.rb
|
93
95
|
- spec/spec_helper.rb
|
@@ -117,6 +119,7 @@ specification_version: 4
|
|
117
119
|
summary: Handles configuring your gems.
|
118
120
|
test_files:
|
119
121
|
- spec/settingable/deep_merge_spec.rb
|
122
|
+
- spec/settingable/hash_spec.rb
|
120
123
|
- spec/settingable/settings_spec.rb
|
121
124
|
- spec/settingable_spec.rb
|
122
125
|
- spec/spec_helper.rb
|