options_hash 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 080f15a9148fb2d6250e85f0779d4bf84813e34d
4
+ data.tar.gz: 7d41b4bc07b434c3c52df25b73c1d6dc2f38bbfc
5
+ SHA512:
6
+ metadata.gz: b7f4cbdfbb969bdee95b86fb23c61b2c31f858f6d3f62e245894691feb91746bef5617c25474971eec0c7b8336f0d6a48c1ce65ad06ecc7d4ddd4f64101e1850
7
+ data.tar.gz: 8c14f9cd0777ac19bd8a43e33ef562b109a0af748ecb648a9e8a18b2cdc39bb5036d37d2889a0fd93055f0504478d4a0bde4a55b8cfed1bf24f70f27966fd597
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ - 1.9.2
6
+ - jruby-18mode
7
+ - jruby-19mode
8
+ - rbx-2.1.1
9
+ - ruby-head
10
+ - jruby-head
11
+ - 1.8.7
12
+ - ree
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in options_hash.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jared Grippe
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,172 @@
1
+ # OptionsHash
2
+
3
+ An OptionsHash is a definition of required and optional keys in an options hash
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'options_hash'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install options_hash
18
+
19
+ ## Usage
20
+
21
+ A simple use case goes something like this:
22
+
23
+ ```ruby
24
+ require 'net/http'
25
+
26
+ def http_get options
27
+ options = OptionsHash.parse(options) do
28
+ required :host, :path
29
+ optional :port, default: 80
30
+ end
31
+ Net::HTTP.get(options.host, options.path, options.port)
32
+ end
33
+
34
+ http_get
35
+ # => ArgumentError: wrong number of arguments (0 for 1)
36
+ http_get({})
37
+ # => ArgumentError: required options: :host, :path
38
+ http_get(host: 'google.com')
39
+ # => ArgumentError: required options: :path
40
+ http_get(host: 'google.com', path: '/')
41
+ # => "<HTML><HEAD><meta ht..."
42
+ http_get(host: 'google.com', path: '/', port: 81)
43
+ # => Net::HTTPRequestTimeOut
44
+ ```
45
+
46
+ A possibly more common use case:
47
+
48
+ ```ruby
49
+ class Person
50
+ class Options < OptionsHash
51
+ required :name
52
+ optional :age, default: 31
53
+ end
54
+
55
+ def initialize options
56
+ @options = Options.parse(options)
57
+ end
58
+ attr_reader :options
59
+ end
60
+
61
+ me = Person.new({}) # => ArgumentError: required options: :name
62
+
63
+ me = Person.new(name: 'Jared Grippe')
64
+ me.options.name # => "Jared Grippe"
65
+ me.options.age # => 31
66
+ me.options.to_hash # => {:name=>"Jared Grippe", :age=>31}
67
+
68
+ me = Person.new(name: 'Jared Grippe', age: 25)
69
+ me.options.name # => "Jared Grippe"
70
+ me.options.age # => 25
71
+ ```
72
+
73
+ Advanced usage:
74
+
75
+ ```ruby
76
+ class PersonOptions < OptionsHash
77
+ required :name
78
+ optional :favorite_color
79
+ optional :species, default: :human
80
+ end
81
+
82
+ PersonOptions.options # => {
83
+ # :name=>required without default,
84
+ # :favorite_color=>optional without default,
85
+ # :species=>optional with default}
86
+
87
+ PersonOptions[:name] # => required without default
88
+ PersonOptions[:name].required? # => true
89
+ PersonOptions[:name].has_default? # => false
90
+ PersonOptions[:name].has_default_proc? # => false
91
+ PersonOptions[:name].default # => nil
92
+
93
+ PersonOptions.parse({}) # ArgumentError: required options: :name
94
+
95
+ PersonOptions.parse(name: 'Steve')
96
+ # => #<PersonOptions {:name=>"Steve", :favorite_color=>nil, :species=>:human}>
97
+
98
+ PersonOptions.parse(name: 'Steve', favorite_color: 'blue')
99
+ # => #<PersonOptions {:name=>"Steve", :favorite_color=>"blue", :species=>:human}>
100
+
101
+ PersonOptions.parse(name: 'Steve', species: :robot)
102
+ # => #<PersonOptions {:name=>"Steve", :favorite_color=>"blue", :species=>:robot}>
103
+
104
+ options = PersonOptions.parse(name: 'Steve', species: :robot)
105
+
106
+ options.name # => "Steve"
107
+ options.favorite_color # => nil
108
+ options.species # => :robot
109
+
110
+ options.given? :name # => true
111
+ options.given? :favorite_color # => false
112
+ options.given? :species # => true
113
+
114
+ options.to_hash # => {:name=>"Steve", :favorite_color=>nil, :species=>:robot}
115
+
116
+ ```
117
+
118
+
119
+
120
+
121
+ All options can take a block to determine their default value:
122
+
123
+ ```ruby
124
+ class PersonOptions < OptionsHash
125
+ required(:first_name, :last_name) { |name| name.to_sym }
126
+ optional :full_name, default: ->{ "#{first_name} #{last_name}" }
127
+ end
128
+
129
+ PersonOptions
130
+ # => PersonOptions(required: [:first_name, :last_name], optional: [:full_name])
131
+
132
+ options = PersonOptions.parse(first_name: 'Jared', last_name: 'Grippe')
133
+ # => #<PersonOptions {:first_name=>:Jared, :last_name=>:Grippe, :full_name=>"Jared Grippe"}>
134
+
135
+ options.given_options
136
+ # => {:first_name=>"Jared", :last_name=>"Grippe"}
137
+
138
+ options[:first_name] # => :Jared
139
+ options[:last_name] # => :Grippe
140
+ options[:full_name] # => "Jared Grippe"
141
+ ```
142
+
143
+ Option hashes can inherit from other option hashes
144
+
145
+ ```ruby
146
+ class AnimalOptions < OptionsHash
147
+ required :species
148
+ optional :alive, default: true
149
+ end
150
+ class DogOptions < AnimalOptions
151
+ required :name, :breed
152
+ end
153
+
154
+ AnimalOptions
155
+ # => AnimalOptions(required: [:species], optional: [:alive])
156
+
157
+ DogOptions
158
+ # => DogOptions(required: [:breed, :name, :species], optional: [:alive])
159
+
160
+ DogOptions[:alive].default # => true
161
+ ```
162
+
163
+
164
+
165
+
166
+ ## Contributing
167
+
168
+ 1. Fork it
169
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
170
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
171
+ 4. Push to the branch (`git push origin my-new-feature`)
172
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,204 @@
1
+ require "options_hash/version"
2
+
3
+ class OptionsHash
4
+
5
+ class Option
6
+ def initialize required, default=nil
7
+ @required, @default = !!required, default
8
+ end
9
+ def required?; @required; end
10
+ attr_reader :default
11
+ def has_default?
12
+ !!default
13
+ end
14
+ def has_default_proc?
15
+ default.is_a? Proc
16
+ end
17
+ def inspect
18
+ %(#{required? ? 'required' : 'optional'} #{default ? 'with' : 'without'} default)
19
+ end
20
+ alias_method :to_s, :inspect
21
+ end
22
+
23
+ def self.inherited(subclass)
24
+ subclass.send :extend, ClassMethods
25
+ subclass.send :include, InstanceMethods
26
+ subclass
27
+ end
28
+
29
+ class << self
30
+ alias_method :_new, :new
31
+ undef_method :new
32
+ private :_new
33
+ def parse options, &block
34
+ block_given? or raise ArgumentError, 'block required', caller(2)
35
+ Class.new(self, &block).parse(options)
36
+ rescue ArgumentError => error
37
+ raise ArgumentError, error.message, caller(2)
38
+ end
39
+ end
40
+
41
+ module ClassMethods
42
+
43
+ def parse options
44
+ _new options
45
+ rescue ArgumentError => error
46
+ raise ArgumentError, error.message, caller(2)
47
+ end
48
+
49
+ def options
50
+ @options ||= {}
51
+ (superclass.respond_to?(:options) ? superclass.options : {}).merge @options
52
+ end
53
+
54
+ def option? key
55
+ keys.include? key
56
+ end
57
+
58
+ def [] key
59
+ option? key or raise KeyError, "#{key} is not an option", caller(1)
60
+ options[key]
61
+ end
62
+
63
+ def keys
64
+ options.keys.to_set
65
+ end
66
+
67
+ def required_keys
68
+ options.select{|key, option| option.required? }.keys.to_set
69
+ end
70
+
71
+ def optional_keys
72
+ options.reject{|key, option| option.required? }.keys.to_set
73
+ end
74
+
75
+ def required *options, &block
76
+ default = extract_default options, &block
77
+ set_options Option.new(true, default), *options
78
+ end
79
+
80
+ def optional *options, &block
81
+ default = extract_default options, &block
82
+ set_options Option.new(false, default), *options
83
+ end
84
+
85
+ def define_attr_readers object, instance_variable_name=:@options
86
+ self.freeze
87
+ instance_variable_name = "@#{instance_variable_name.to_s.sub(/@/,'')}"
88
+ keys.each do |key|
89
+ object.send(:define_method, "#{key}" ) do
90
+ instance_variable_get(instance_variable_name)[key]
91
+ end
92
+ end
93
+ end
94
+
95
+ alias_method :_inspect, :inspect
96
+ private :_inspect
97
+
98
+ def class_name
99
+ name || "OptionsHash:#{_inspect[/^#<Class:(\w+)/,1]}"
100
+ end
101
+
102
+ def inspect
103
+ inspect = super
104
+ required_keys = self.required_keys.to_a.sort
105
+ optional_keys = self.optional_keys.to_a.sort
106
+ "#{class_name}(required: #{required_keys.inspect}, optional: #{optional_keys.inspect})"
107
+ end
108
+ alias_method :to_s, :inspect
109
+
110
+ private
111
+
112
+ def extract_default options, &block
113
+ return block if block_given?
114
+ (options.last.is_a?(Hash) ? options.pop : {})[:default]
115
+ end
116
+
117
+ def set_options definition, *options
118
+ @options ||= {}
119
+ options.each do |key|
120
+ key = key.to_sym
121
+ @options[key] = definition.dup
122
+ define_method("#{key}" ){ fetch key }
123
+ define_method("#{key}?"){ given? key }
124
+ end
125
+ end
126
+
127
+ end
128
+
129
+ module InstanceMethods
130
+ def initialize given_options
131
+ @keys = self.class.keys.freeze
132
+ @options = self.class.options.freeze
133
+ @given_options = (given_options || {}).freeze
134
+
135
+ unknown_options = @given_options.keys - keys.to_a
136
+ unknown_options.empty? or raise ArgumentError, "unknown options: #{unknown_options.sort.map(&:inspect).join(', ')}"
137
+
138
+ missing_required_options = required_keys.to_a - @given_options.keys
139
+ missing_required_options.empty? or raise ArgumentError, "required options: #{missing_required_options.sort.map(&:inspect).join(', ')}"
140
+
141
+
142
+ @values = {}
143
+ keys.to_a.sort.each{|key| send(key) }
144
+ end
145
+ attr_reader :keys, :options, :given_options
146
+
147
+ def required_keys
148
+ @options.select do |key, option|
149
+ option.required?
150
+ end.keys.to_set
151
+ end
152
+
153
+ def given? key
154
+ @given_options.key? key
155
+ end
156
+
157
+ def fetch key
158
+ key = key.to_sym
159
+ keys.include?(key) or raise KeyError, "#{key} is not an option", caller(1)
160
+ return @values[key] if @values.key? key
161
+
162
+ option = @options[key]
163
+ default_proc = option.default if option.default && option.default.is_a?(Proc)
164
+
165
+ if option.required?
166
+ value = @given_options[key]
167
+ value = instance_exec(value, &default_proc) if option.required? && default_proc
168
+ return @values[key] = value
169
+ end
170
+
171
+ return @values[key] = @given_options[key] if @given_options.key? key
172
+ return @values[key] = default_proc ? instance_exec(&default_proc) : option.default
173
+ end
174
+ alias_method :[], :fetch
175
+
176
+ def to_hash
177
+ keys.each_with_object Hash.new do |key, hash|
178
+ hash[key] = send(key)
179
+ end
180
+ end
181
+
182
+
183
+ def inspect
184
+ %(#<#{self.class.class_name} #{to_hash.inspect}>)
185
+ end
186
+ alias_method :to_s, :inspect
187
+ end
188
+
189
+ end
190
+
191
+
192
+ # class Foo
193
+
194
+ # def whatever options={}
195
+ # OptionsHash.parse(options) do
196
+ # required :name
197
+ # optional :size, default: 42
198
+ # end
199
+ # end
200
+
201
+ # end
202
+
203
+
204
+
@@ -0,0 +1,3 @@
1
+ class OptionsHash
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'options_hash/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "options_hash"
8
+ spec.version = OptionsHash::VERSION
9
+ spec.authors = ["Jared Grippe"]
10
+ spec.email = ["jared@deadlyicon.com"]
11
+ spec.description = %q{A configurable options hash definition}
12
+ spec.summary = %q{A configurable options hash definition}
13
+ spec.homepage = "https://github.com/deadlyicon/options_hash"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "pry-debugger"
25
+ end
@@ -0,0 +1,295 @@
1
+ require 'spec_helper'
2
+
3
+ describe OptionsHash do
4
+ subject{ described_class }
5
+
6
+ it { should_not respond_to :new }
7
+ it { should_not respond_to :options }
8
+ it { should_not respond_to :keys }
9
+ it { should_not respond_to :required_keys }
10
+ it { should_not respond_to :optional_keys }
11
+ it { should_not respond_to :required }
12
+ it { should_not respond_to :optional }
13
+
14
+ its(:name ){ should == 'OptionsHash' }
15
+ its(:inspect){ should == 'OptionsHash' }
16
+ its(:to_s ){ should == 'OptionsHash' }
17
+
18
+ shared_examples 'an options hash' do
19
+ it { should_not respond_to :new }
20
+ it { should respond_to :options }
21
+ it { should respond_to :keys }
22
+ it { should respond_to :required_keys }
23
+ it { should respond_to :optional_keys }
24
+ it { should respond_to :required }
25
+ it { should respond_to :optional }
26
+ end
27
+
28
+ shared_examples 'an options hash instance' do
29
+ its(:inspect){ should =~ %r[#<#{subject.class.class_name} #{subject.to_hash.inspect}>] }
30
+ its(:to_s ){ should =~ %r[#<#{subject.class.class_name} #{subject.to_hash.inspect}>] }
31
+ it 'should respond to each key' do
32
+ subject.keys.each do |key|
33
+ expect(subject).to respond_to "#{key}"
34
+ expect(subject).to respond_to "#{key}?"
35
+ end
36
+ end
37
+ end
38
+
39
+
40
+ describe '.parse' do
41
+ context 'when given no arguments' do
42
+ subject{ ->{ OptionsHash.parse } }
43
+ it { should raise_error ArgumentError, 'wrong number of arguments (0 for 1)' }
44
+ end
45
+
46
+ context 'when given no block' do
47
+ subject{ ->{ OptionsHash.parse({}) } }
48
+ it { should raise_error ArgumentError, 'block required' }
49
+ end
50
+
51
+ context 'when given insufficient options' do
52
+ subject{ ->{ OptionsHash.parse({}){ required :name } } }
53
+ it { should raise_error ArgumentError, 'required options: :name' }
54
+ end
55
+
56
+ context 'when given unknown options' do
57
+ subject{ ->{ OptionsHash.parse(foo:1){ } } }
58
+ it { should raise_error ArgumentError, 'unknown options: :foo' }
59
+ end
60
+
61
+ context 'when given valid options' do
62
+ subject do
63
+ OptionsHash.parse(name: 'steve') do
64
+ required :name
65
+ optional :size, default: 42
66
+ end
67
+ end
68
+ it_behaves_like 'an options hash instance'
69
+ its(:name){ should eq 'steve' }
70
+ its(:size){ should eq 42 }
71
+ its(:to_hash){ should eq(:name=>"steve", :size=>42) }
72
+ end
73
+ end
74
+
75
+
76
+ describe 'OptionsHash(required: [:a, :b], optional: [:c, :d])' do
77
+ let :options_hash do
78
+ Class.new(OptionsHash) do
79
+ required :a
80
+ required :b do |b|
81
+ b.inspect
82
+ end
83
+ optional :c
84
+ optional :d, default: ->{ 42 }
85
+ end
86
+ end
87
+ describe '.parse' do
88
+ context '(nil)' do
89
+ subject{ ->{ options_hash.parse(nil) } }
90
+ it{ should raise_error ArgumentError, 'required options: :a, :b' }
91
+ end
92
+ context '({})' do
93
+ subject{ ->{ options_hash.parse({}) } }
94
+ it{ should raise_error ArgumentError, 'required options: :a, :b' }
95
+ end
96
+ context '(a:1)' do
97
+ subject{ ->{ options_hash.parse(a:1) } }
98
+ it{ should raise_error ArgumentError, 'required options: :b' }
99
+ end
100
+ context '(b:1)' do
101
+ subject{ ->{ options_hash.parse(b:1) } }
102
+ it{ should raise_error ArgumentError, 'required options: :a' }
103
+ end
104
+ context '(a:1, b:2)' do
105
+ subject{ options_hash.parse(a:1, b:2) }
106
+ its(:a){ should eq 1 }
107
+ its(:b){ should eq '2' }
108
+ its(:c){ should be_nil }
109
+ its(:d){ should eq 42 }
110
+ end
111
+ context '(a:1, b:2, c:3)' do
112
+ subject{ options_hash.parse(a:1, b:2, c:3) }
113
+ its(:a){ should eq 1 }
114
+ its(:b){ should eq '2' }
115
+ its(:c){ should eq 3 }
116
+ its(:d){ should eq 42 }
117
+ end
118
+ context '(a:1, b:2, c:3, d:4)' do
119
+ subject{ options_hash.parse(a:1, b:2, c:3, d:4) }
120
+ its(:a){ should eq 1 }
121
+ its(:b){ should eq '2' }
122
+ its(:c){ should eq 3 }
123
+ its(:d){ should eq 4 }
124
+ end
125
+ context '(a: nil, b: nil, c: nil, d: nil)' do
126
+ subject{ options_hash.parse(a: nil, b: nil, c: nil, d: nil) }
127
+ its(:a){ should be_nil }
128
+ its(:b){ should eq 'nil' }
129
+ its(:c){ should be_nil }
130
+ its(:d){ should be_nil }
131
+ end
132
+ end
133
+
134
+ describe '#define_attr_readers' do
135
+ it 'should freeze the options hash' do
136
+ expect(options_hash).to_not be_frozen
137
+ options_hash.define_attr_readers(Class.new)
138
+ expect(options_hash).to be_frozen
139
+ end
140
+ it "should define attr readers for each option" do
141
+ options_hash = self.options_hash
142
+ _class = Class.new
143
+ _class.send(:define_method, :initialize) do |options|
144
+ @options = options_hash.parse(options)
145
+ end
146
+ options_hash.define_attr_readers(_class)
147
+
148
+ expect(_class.new(a:1,b:2,c:3,d:4).a).to eq 1
149
+ expect(_class.new(a:1,b:2,c:3,d:4).b).to eq '2'
150
+ expect(_class.new(a:1,b:2,c:3,d:4).c).to eq 3
151
+ expect(_class.new(a:1,b:2,c:3,d:4).d).to eq 4
152
+
153
+
154
+ options_hash = self.options_hash
155
+ _class = Class.new
156
+ _class.send(:define_method, :initialize) do |options|
157
+ @swordfish = options_hash.parse(options)
158
+ end
159
+ options_hash.define_attr_readers(_class, :@swordfish)
160
+
161
+ expect(_class.new(a:1,b:2,c:3,d:4).a).to eq 1
162
+ expect(_class.new(a:1,b:2,c:3,d:4).b).to eq '2'
163
+ expect(_class.new(a:1,b:2,c:3,d:4).c).to eq 3
164
+ expect(_class.new(a:1,b:2,c:3,d:4).d).to eq 4
165
+ end
166
+ end
167
+ end
168
+
169
+
170
+ describe 'Class.new(OptionsHash)' do
171
+ subject{ Class.new(OptionsHash) }
172
+
173
+ it_behaves_like 'an options hash'
174
+
175
+ its(:name ){ should be_nil }
176
+ its(:inspect){ should =~ %r{OptionsHash:(\w+)\(required: \[\], optional: \[\]\)} }
177
+ its(:to_s ){ should =~ %r{OptionsHash:(\w+)\(required: \[\], optional: \[\]\)} }
178
+
179
+ describe '.parse({})' do
180
+ subject{ Class.new(OptionsHash).parse({}) }
181
+ it_behaves_like 'an options hash instance'
182
+ end
183
+ describe '.parse(nil)' do
184
+ subject{ Class.new(OptionsHash).parse(nil) }
185
+ it_behaves_like 'an options hash instance'
186
+ end
187
+ end
188
+
189
+
190
+
191
+ describe EmptyOptions do
192
+ subject{ EmptyOptions }
193
+
194
+ it_behaves_like 'an options hash'
195
+
196
+ its(:name ){ should == 'EmptyOptions' }
197
+ its(:inspect){ should == %{EmptyOptions(required: [], optional: [])} }
198
+ its(:to_s ){ should == %{EmptyOptions(required: [], optional: [])} }
199
+ end
200
+
201
+ describe PersonOptions do
202
+ subject{ PersonOptions }
203
+
204
+ it_behaves_like 'an options hash'
205
+
206
+ its(:keys){ should eq Set[:name, :level_of_schooling, :height, :weight, :size, :iq, :intelegence] }
207
+
208
+ describe %(PersonOptions.parse(name: 'jared', level_of_schooling: 100, iq: 240, intelegence: 2)) do
209
+ subject{ PersonOptions.parse(name: 'jared', level_of_schooling: 100, iq: 240, intelegence: 2) }
210
+
211
+ it_behaves_like 'an options hash instance'
212
+
213
+ its(:name? ){ should be_true }
214
+ its(:level_of_schooling? ){ should be_true }
215
+ its(:height? ){ should be_false }
216
+ its(:weight? ){ should be_false }
217
+ its(:size? ){ should be_false }
218
+ its(:iq? ){ should be_true }
219
+ its(:intelegence? ){ should be_true }
220
+ its(:name ){ should eq 'jared'}
221
+ its(:level_of_schooling ){ should eq 100 }
222
+ its(:height ){ should eq 2 }
223
+ its(:weight ){ should eq 2 }
224
+ its(:size ){ should eq 400 }
225
+ its(:iq ){ should eq 120.0 }
226
+ its(:intelegence ){ should eq 1.0 }
227
+
228
+ its(:keys){ should eq Set[:name, :level_of_schooling, :height, :weight, :size, :iq, :intelegence] }
229
+ its(:to_hash){ should eq(:name=>"jared", :level_of_schooling=>100, :height=>2, :weight=>2, :size=>400, :iq=>120.0, :intelegence=>1.0) }
230
+ end
231
+
232
+ describe 'argument errors' do
233
+ it "should be raised" do
234
+ expect{ subject.parse }.to raise_error ArgumentError, 'wrong number of arguments (0 for 1)'
235
+ expect{ subject.parse({}) }.to raise_error ArgumentError, 'required options: :intelegence, :iq, :level_of_schooling, :name'
236
+ expect{ subject.parse(intelegence: 1) }.to raise_error ArgumentError, 'required options: :iq, :level_of_schooling, :name'
237
+ expect{ subject.parse(intelegence: 1, iq: 1) }.to raise_error ArgumentError, 'required options: :level_of_schooling, :name'
238
+ expect{ subject.parse(intelegence: 1, iq: 1, level_of_schooling: 1) }.to raise_error ArgumentError, 'required options: :name'
239
+ expect{ subject.parse(intelegence: 1, iq: 1, level_of_schooling: 1, name: 1) }.to_not raise_error
240
+ expect{ subject.parse(b:1, a:2, name:'steve') }.to raise_error ArgumentError, 'unknown options: :a, :b'
241
+ end
242
+ end
243
+ end
244
+
245
+ describe 'Person.new' do
246
+ let(:proc){ ->{ Person.new } }
247
+ subject{ proc }
248
+ it { should raise_error ArgumentError, 'required options: :intelegence, :iq, :level_of_schooling, :name' }
249
+
250
+ describe 'ArgumentError' do
251
+ let(:error){ begin; proc.call; rescue => error; error end }
252
+ subject{ error }
253
+
254
+ describe 'backtrace[0]' do
255
+ subject{ error.backtrace[0] }
256
+ it { should eq "#{Bundler.root+'spec/support/person.rb'}:6:in `initialize'" }
257
+ end
258
+ end
259
+ end
260
+
261
+ describe 'Person.new(father:1)' do
262
+ let(:proc){ ->{ Person.new(father:1) } }
263
+ subject{ proc }
264
+ it { should raise_error ArgumentError, 'unknown options: :father' }
265
+
266
+ describe 'ArgumentError' do
267
+ let(:error){ begin; proc.call; rescue => error; error end }
268
+ subject{ error }
269
+
270
+ describe 'backtrace[0]' do
271
+ subject{ error.backtrace[0] }
272
+ it { should eq "#{Bundler.root+'spec/support/person.rb'}:6:in `initialize'" }
273
+ end
274
+ end
275
+ end
276
+
277
+ describe %[Person.new(name: 'jared', level_of_schooling: 100, iq: 240, intelegence: 2)] do
278
+ subject{ Person.new(name: 'jared', level_of_schooling: 100, iq: 240, intelegence: 2) }
279
+ it{ should respond_to :name }
280
+ it{ should respond_to :level_of_schooling }
281
+ it{ should respond_to :height }
282
+ it{ should respond_to :weight }
283
+ it{ should respond_to :size }
284
+ it{ should respond_to :iq }
285
+ it{ should respond_to :intelegence }
286
+ its(:name ){ should eq 'jared'}
287
+ its(:level_of_schooling ){ should eq 100 }
288
+ its(:height ){ should eq 2 }
289
+ its(:weight ){ should eq 2 }
290
+ its(:size ){ should eq 400 }
291
+ its(:iq ){ should eq 120.0 }
292
+ its(:intelegence ){ should eq 1.0 }
293
+ end
294
+
295
+ end
@@ -0,0 +1,24 @@
1
+ require 'options_hash'
2
+ require 'pry-debugger'
3
+
4
+ require Bundler.root + 'spec/support/empty_options.rb'
5
+ require Bundler.root + 'spec/support/person_options.rb'
6
+ require Bundler.root + 'spec/support/person.rb'
7
+
8
+ # This file was generated by the `rspec --init` command. Conventionally, all
9
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
10
+ # Require this file using `require "spec_helper"` to ensure that it is only
11
+ # loaded once.
12
+ #
13
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
14
+ RSpec.configure do |config|
15
+ config.treat_symbols_as_metadata_keys_with_true_values = true
16
+ config.run_all_when_everything_filtered = true
17
+ config.filter_run :focus
18
+
19
+ # Run specs in random order to surface order dependencies. If you find an
20
+ # order dependency and want to debug it, you can fix the order by providing
21
+ # the seed, which is printed after each run.
22
+ # --seed 1234
23
+ config.order = 'random'
24
+ end
@@ -0,0 +1,3 @@
1
+ class EmptyOptions < OptionsHash
2
+
3
+ end
@@ -0,0 +1,9 @@
1
+ class Person
2
+
3
+ PersonOptions.define_attr_readers(self)
4
+
5
+ def initialize options=nil
6
+ @options = PersonOptions.parse(options)
7
+ end
8
+
9
+ end
@@ -0,0 +1,10 @@
1
+ class PersonOptions < OptionsHash
2
+ required :name, :level_of_schooling
3
+
4
+ optional :height, :weight, default: ->{ 2 }
5
+ optional :size, &->{ (weight * height) * 100 }
6
+
7
+ required :iq, :intelegence do |given_value|
8
+ given_value.to_f * 0.50
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: options_hash
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jared Grippe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-debugger
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: A configurable options hash definition
70
+ email:
71
+ - jared@deadlyicon.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .rspec
78
+ - .travis.yml
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - lib/options_hash.rb
84
+ - lib/options_hash/version.rb
85
+ - options_hash.gemspec
86
+ - spec/options_hash_spec.rb
87
+ - spec/spec_helper.rb
88
+ - spec/support/empty_options.rb
89
+ - spec/support/person.rb
90
+ - spec/support/person_options.rb
91
+ homepage: https://github.com/deadlyicon/options_hash
92
+ licenses:
93
+ - MIT
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 2.1.9
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: A configurable options hash definition
115
+ test_files:
116
+ - spec/options_hash_spec.rb
117
+ - spec/spec_helper.rb
118
+ - spec/support/empty_options.rb
119
+ - spec/support/person.rb
120
+ - spec/support/person_options.rb