settingable 0.2.1 → 0.3.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: 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