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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 322c4138d887529b845f3027badc7b35a6031693
4
- data.tar.gz: 9f24b8585d437a0b06be1e81c4e2816930d196af
3
+ metadata.gz: 903821ee3c31b65a77729e2eb36ed6d295f00a4a
4
+ data.tar.gz: 2bdb5c3ea7c51f10c2e7b6f847859372b81508d8
5
5
  SHA512:
6
- metadata.gz: 8cec1a8f17952b59f03a0c552aa36efbf571eb40479575c843b015e2ec3d15f6c001f39b533409f204c3d8c20f441568e8aa703aa09b54c0bf0f1e525e441a4a
7
- data.tar.gz: 58be6a3f657922814dcb7f0525f5a78e05beb0c07394eb0ed83e333809bef2fcdf0bd17a5f6cb8af87073d7e056cbcad8180fba756cae97b5325aa8eb2d6e465
6
+ metadata.gz: 972a99a3465daf5cdca3fbb336815b1351d32b2fa46f257304bbcdac6e185aaff902bfd7f601d2d6d6bed0c63cf40b23b8d3351fb9ee47e8667194863bcc03ce
7
+ data.tar.gz: fe8b359de31ce9452f6489b0970707a38539f5d5dbd1d039913477d9034f1dc5a51de8c7121fc1af70edd66de94d8b580861d4066d6bf737900a46803150da33
data/README.md CHANGED
@@ -1,27 +1,78 @@
1
- # settingable
1
+ # settingable [![Build Status](https://travis-ci.org/medcat/settingable.svg)](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:redjazz96@gmail.com)
5
+ * [Email](mailto:me@medcat.me)
6
+
7
+ ## Install
8
+
9
+ $ gem install settingable
6
10
 
7
11
  ## Description
8
12
 
9
- TODO: Description
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
- ## Features
19
+ ```Ruby
20
+ module MyLibrary
21
+ class Settings
22
+ include Settingable::Settings
23
+ end
24
+ ```
12
25
 
13
- ## Examples
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
- require 'settingable'
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
- ## Requirements
40
+ Here's a few examples.
18
41
 
19
- ## Install
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
- $ gem install settingable
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
- ## Copyright
68
+ default_settings foo: "bar"
69
+ end
70
+ end
71
+ ```
24
72
 
25
- Copyright (c) 2015 Jeremy Rodi
73
+ Now, check it out.
26
74
 
27
- See {file:LICENSE.txt} for details.
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
@@ -9,15 +9,15 @@ module Settingable
9
9
  module ClassMethods
10
10
  extend Forwardable
11
11
 
12
- def_delegators :settings, :[], :[]=, :fetch,
12
+ def_delegators :instance, :[], :[]=, :fetch,
13
13
  :method_missing
14
- def_delegator :settings, :build, :configure
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 settings
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 = DeepMerge.deep_merge(self.class.default_settings, 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
- # Maps the methods.
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]
@@ -3,5 +3,5 @@
3
3
  # :nodoc:
4
4
  module Settingable
5
5
  # settingable version
6
- VERSION = "0.2.1"
6
+ VERSION = "0.3.0"
7
7
  end
data/lib/settingable.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "settingable/version"
4
4
  require "settingable/deep_merge"
5
+ require "settingable/hash"
5
6
  require "settingable/settings"
6
7
 
7
8
  # Contains the Setting module.
@@ -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.settings }
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 ".settings" do
23
+ describe ".instance" do
24
24
  it "returns the same instance" do
25
- expect(Configuration.settings).to be_a(Configuration)
26
- expect(Configuration.settings).to be(Configuration.settings)
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.2.1
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