ns-options 0.4.1 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.gitignore +3 -0
  2. data/Gemfile +2 -1
  3. data/LICENSE +1 -1
  4. data/README.md +187 -301
  5. data/Rakefile +1 -1
  6. data/lib/ns-options/assert_macros.rb +9 -12
  7. data/lib/ns-options/boolean.rb +2 -0
  8. data/lib/ns-options/namespace.rb +34 -134
  9. data/lib/ns-options/namespace_advisor.rb +35 -0
  10. data/lib/ns-options/namespace_data.rb +166 -0
  11. data/lib/ns-options/namespaces.rb +23 -12
  12. data/lib/ns-options/option.rb +50 -24
  13. data/lib/ns-options/options.rb +23 -49
  14. data/lib/ns-options/proxy.rb +40 -53
  15. data/lib/ns-options/proxy_method.rb +54 -0
  16. data/lib/ns-options/root_methods.rb +77 -0
  17. data/lib/ns-options/version.rb +1 -1
  18. data/lib/ns-options.rb +18 -8
  19. data/ns-options.gemspec +3 -4
  20. data/test/helper.rb +3 -10
  21. data/test/support/app.rb +3 -1
  22. data/test/support/proxy.rb +4 -0
  23. data/test/support/type_class_proxy.rb +29 -0
  24. data/test/support/user.rb +5 -5
  25. data/test/{integration/app_test.rb → system/app_tests.rb} +8 -6
  26. data/test/{integration/proxy_test.rb → system/proxy_tests.rb} +12 -0
  27. data/test/system/type_class_proxy_tests.rb +108 -0
  28. data/test/system/user_tests.rb +146 -0
  29. data/test/unit/{ns-options/boolean_test.rb → boolean_tests.rb} +5 -4
  30. data/test/unit/namespace_advisor_tests.rb +69 -0
  31. data/test/unit/namespace_data_tests.rb +336 -0
  32. data/test/unit/namespace_tests.rb +205 -0
  33. data/test/unit/namespaces_tests.rb +99 -0
  34. data/test/unit/{ns-options/option_test.rb → option_tests.rb} +155 -93
  35. data/test/unit/options_tests.rb +152 -0
  36. data/test/unit/proxy_method_tests.rb +87 -0
  37. data/test/unit/{ns-options/proxy_test.rb → proxy_tests.rb} +52 -0
  38. data/test/unit/root_methods_tests.rb +126 -0
  39. metadata +58 -63
  40. data/lib/ns-options/errors/invalid_name.rb +0 -15
  41. data/lib/ns-options/has_options.rb +0 -53
  42. data/lib/ns-options/helper/advisor.rb +0 -88
  43. data/lib/ns-options/helper.rb +0 -87
  44. data/test/integration/user_test.rb +0 -94
  45. data/test/unit/ns-options/has_options_test.rb +0 -90
  46. data/test/unit/ns-options/helper/advisor_test.rb +0 -148
  47. data/test/unit/ns-options/helper_test.rb +0 -56
  48. data/test/unit/ns-options/namespace_test.rb +0 -432
  49. data/test/unit/ns-options/namespaces_test.rb +0 -55
  50. data/test/unit/ns-options/options_test.rb +0 -221
  51. /data/test/unit/{ns-options/assert_macros_test.rb → assert_macros_tests.rb} +0 -0
data/Rakefile CHANGED
@@ -2,4 +2,4 @@
2
2
  require "bundler/gem_tasks"
3
3
 
4
4
  require 'assert/rake_tasks'
5
- Assert::RakeTasks.for :test
5
+ Assert::RakeTasks.install
@@ -1,13 +1,12 @@
1
- module NsOptions; end
1
+ require 'ns-options'
2
+
2
3
  module NsOptions::AssertMacros
3
4
 
4
5
  # a set of Assert macros to help write namespace definition and
5
6
  # regression tests in Assert (https://github.com/teaminsight/assert)
6
7
 
7
8
  def self.included(receiver)
8
- receiver.class_eval do
9
- extend MacroMethods
10
- end
9
+ receiver.class_eval { extend MacroMethods }
11
10
  end
12
11
 
13
12
  module MacroMethods
@@ -19,8 +18,7 @@ module NsOptions::AssertMacros
19
18
  Assert::Macro.new(macro_name) do
20
19
  namespaces.each do |ns|
21
20
  should "have a namespace named '#{ns}'", called_from do
22
- assert_respond_to ns, subject
23
- assert_kind_of NsOptions::Namespace, subject.send(ns)
21
+ assert subject.has_namespace?(ns)
24
22
  end
25
23
  end
26
24
  end
@@ -34,8 +32,7 @@ module NsOptions::AssertMacros
34
32
  Assert::Macro.new(macro_name) do
35
33
  options.each do |opt|
36
34
  should "have an option named '#{opt}'", called_from do
37
- assert_respond_to opt, subject
38
- assert_kind_of NsOptions::Option, subject.options[opt]
35
+ assert subject.has_option?(opt)
39
36
  end
40
37
  end
41
38
  end
@@ -43,7 +40,7 @@ module NsOptions::AssertMacros
43
40
 
44
41
  def have_option(*args)
45
42
  called_from = caller.first
46
- rules, type_class, opt_name = NsOptions::Option.args(*args)
43
+ opt_name, type_class, rules = NsOptions::Option.args(args)
47
44
  test_name = [
48
45
  "have an option: '#{opt_name}'",
49
46
  "of type '#{type_class}'",
@@ -53,10 +50,10 @@ module NsOptions::AssertMacros
53
50
  Assert::Macro.new(test_name) do
54
51
 
55
52
  should test_name do
56
- # name assertions
57
- assert_respond_to opt_name, subject
58
53
 
59
- opt = subject.options[opt_name]
54
+ # have assertions
55
+ assert subject.has_option?(opt_name)
56
+ opt = subject.__data__.child_options[opt_name]
60
57
  assert_kind_of NsOptions::Option, opt
61
58
 
62
59
  # type_class assertions
@@ -1,3 +1,5 @@
1
+ require 'ns-options'
2
+
1
3
  module NsOptions
2
4
  class Boolean
3
5
 
@@ -1,119 +1,54 @@
1
+ require 'ns-options/namespace_data'
2
+ require 'ns-options/namespace_advisor'
3
+
1
4
  module NsOptions
2
5
 
3
6
  class Namespace
4
- attr_accessor :options, :metaclass
5
7
 
6
- # Every namespace tracks a metaclass to allow for individual reader/writers for their options,
7
- # without any collisions. Since every namespace is of the same class, defining option reader and
8
- # writer methods directly on the class would make multiple namespaces with different options
9
- # impossible.
10
- def initialize(key, parent = nil, &block)
11
- self.metaclass = (class << self; self; end)
12
- self.options = NsOptions::Options.new(key, parent)
13
- self.define(&block)
14
- end
8
+ attr_reader :__name__, :__data__
15
9
 
16
- # This is a helper to check if options that were defined as :required have been set.
17
- def required_set?
18
- self.options.required_set?
10
+ def initialize(name, option_type_class=nil, &block)
11
+ @__name__ = name
12
+ @__data__ = NamespaceData.new(self, option_type_class || Object)
13
+ @__data__.define(&block)
19
14
  end
20
- alias :valid? :required_set?
21
15
 
22
- # Define an option for this namespace. Add the option to the namespace's options collection
23
- # and then define accessors for the option. With the following:
24
- #
25
- # namespace.option(:root, String, { :some_option => true })
26
- #
27
- # you will get accessors for root:
28
- #
29
- # namespace.root = "something" # set's the root option to 'something'
30
- # namespace.root # => "something"
31
- # namespace.root("something else") # set's the root option to `something-else`
32
- #
33
- # The defined option is returned as well.
34
- def option(*args)
35
- NsOptions::Helper.advisor(self).is_this_option_ok?(args[0], caller)
36
- option = self.options.add(*args)
37
- NsOptions::Helper.define_option_methods(self, option)
38
- option
16
+ def option(name, *args)
17
+ NamespaceAdvisor.new(@__data__, name, 'an option').run($stdout, caller)
18
+ @__data__.add_option(name, *args)
39
19
  end
40
20
  alias_method :opt, :option
41
21
 
42
- # Define a namespace under this namespace. Firstly, a new key is constructured from this current
43
- # namespace's key and the name for the new namespace. The namespace is then added to the
44
- # options collection. Finally a reader method is defined for accessing the namespace. With the
45
- # following:
46
- #
47
- # parent_namespace.namespace(:specific) do
48
- # option :root
49
- # end
50
- #
51
- # you will get a reader for the namespace:
52
- #
53
- # parent_namespace.specific # => returns the namespace
54
- # parent_namespace.specific.root = "something" # => options are accessed in the same way
55
- #
56
- # The defined namespaces is returned as well.
57
- def namespace(name, key = nil, &block)
58
- key = "#{self.options.key}:#{(key || name)}"
59
- NsOptions::Helper.advisor(self).is_this_sub_namespace_ok?(name, caller)
60
- namespace = self.options.add_namespace(name, key, self, &block)
61
- NsOptions::Helper.define_namespace_methods(self, name)
62
- namespace
22
+ def namespace(name, *args, &block)
23
+ NamespaceAdvisor.new(@__data__, name, 'a namespace').run($stdout, caller)
24
+ @__data__.add_namespace(name, *args, &block)
63
25
  end
64
26
  alias_method :ns, :namespace
65
27
 
66
- # The opposite of #to_hash. Takes a hash representation of options and namespaces and mass
67
- # assigns option values.
68
- def apply(option_values = {})
69
- option_values.each do |name, value|
70
- namespace = self.options.namespaces[name]
71
- if self.options[name] || !namespace
72
- self.send("#{name}=", value)
73
- elsif namespace && value.kind_of?(Hash)
74
- namespace.apply(value)
75
- end
76
- end
28
+ def option_type_class(*args)
29
+ return @__data__.option_type_class if args.empty?
30
+ @__data__.set_option_type_class(*args)
77
31
  end
32
+ alias_method :opt_type_class, :option_type_class
78
33
 
79
- # return a hash representation of the namespace
80
- # use symbols for the hash
81
- def to_hash
82
- Hash.new.tap do |out|
83
- self.options.each do |name, opt|
84
- out[name.to_sym] = opt.value
85
- end
86
- self.options.namespaces.each do |name, value|
87
- out[name.to_sym] = value.to_hash
88
- end
89
- end
90
- end
34
+ def has_option?(name); @__data__.has_option?(name); end
35
+ def has_namespace?(name); @__data__.has_namespace?(name); end
36
+ def required_set?; @__data__.required_set?; end
37
+ alias_method :valid?, :required_set?
38
+
39
+ def define(*args, &block); @__data__.define(*args, &block); end
40
+ def build_from(other_ns); @__data__.build_from(other_ns.__data__); end
41
+ def reset(*args, &block); @__data__.reset(*args, &block); end
42
+ def apply(*args, &block); @__data__.apply(*args, &block); end
43
+ def to_hash(*args, &block); @__data__.to_hash(*args, &block); end
44
+ def each(*args, &block); @__data__.each(*args, &block); end
91
45
 
92
- # allow for iterating over the key/values of a namespace
93
- # this uses #to_hash so you won't get option/namespace objs for the values
94
- def each
95
- self.to_hash.each { |k,v| yield k,v if block_given? }
46
+ def respond_to?(meth)
47
+ @__data__.ns_respond_to?(meth) || super
96
48
  end
97
49
 
98
- # The define method is provided for convenience and commonization. The internal system
99
- # uses it to commonly use a block with a namespace. The method can be used externally when
100
- # a namespace is created separately from where options are added/set on it. For example:
101
- #
102
- # parent_namespace.namespace(:specific)
103
- #
104
- # parent_namespace.specific.define do
105
- # option :root
106
- # end
107
- #
108
- # Will define a new namespace under the parent namespace and then will later on add options to
109
- # it.
110
- def define(&block)
111
- if block && block.arity > 0
112
- yield self
113
- elsif block
114
- self.instance_eval(&block)
115
- end
116
- self
50
+ def method_missing(meth, *args, &block)
51
+ @__data__.ns_method_missing(caller, meth, *args, &block)
117
52
  end
118
53
 
119
54
  def ==(other_ns)
@@ -124,43 +59,8 @@ module NsOptions
124
59
  end
125
60
  end
126
61
 
127
- # There are a number of cases we want to watch for:
128
- # 1. A reader of a 'known' option. This case is for an option that's been defined for an
129
- # ancestor of this namespace but not directly for this namespace. In this case we fetch
130
- # the options definition and use it to define the option directly for this namespace.
131
- # 2. TODO
132
- # 3. A writer of a 'known' option. This case is similar to the above, but instead we are
133
- # wanting to write a value. We need to fetch the option definition, define it and then
134
- # we write the option as we normally would.
135
- # 4. A dynamic writer. The option is not 'known' to the namespace, so we use the value and it's
136
- # class to define the option for this namespace. Then we just use the writer as we normally
137
- # would.
138
- def method_missing(method, *args, &block)
139
- option_name = method.to_s.gsub("=", "")
140
- value = args.size == 1 ? args[0] : args
141
- if args.empty? && self.respond_to?(option_name)
142
- option = NsOptions::Helper.find_and_define_option(self, option_name)
143
- self.send(option.name)
144
- elsif args.empty? && (namespace = self.options.get_namespace(option_name))
145
- NsOptions::Helper.find_and_define_namespace(self, option_name)
146
- self.send(option_name)
147
- elsif !args.empty? && self.respond_to?(option_name)
148
- option = NsOptions::Helper.find_and_define_option(self, option_name)
149
- self.send("#{option.name}=", value)
150
- elsif !args.empty?
151
- option = self.option(option_name)
152
- self.send("#{option.name}=", value)
153
- else
154
- super
155
- end
156
- end
157
-
158
- def respond_to?(method)
159
- super || self.options.is_defined?(method.to_s.gsub("=", ""))
160
- end
161
-
162
62
  def inspect(*args)
163
- "#<#{self.class}:#{'0x%x' % (self.object_id << 1)}:#{self.options.key} #{self.to_hash.inspect}>"
63
+ "#<#{self.class}:#{'0x%x' % (self.object_id << 1)}:#{@__name__} #{to_hash.inspect}>"
164
64
  end
165
65
 
166
66
  end
@@ -0,0 +1,35 @@
1
+ module NsOptions
2
+
3
+ class NamespaceAdvisor
4
+
5
+ def initialize(ns_data, name, kind)
6
+ @ns_data = ns_data
7
+ @name = name
8
+
9
+ @msg = if not_recommended?
10
+ "WARNING: Defining #{kind} with the name `#{@name}' overwrites a method"\
11
+ " NsOptions depends on. It may cause NsOptions to behave oddly and"\
12
+ " is not recommended."
13
+ elsif duplicate?
14
+ "WARNING: `#{@name}' has already been defined and is being overwritten."
15
+ end
16
+ end
17
+
18
+ def run(io, from_caller)
19
+ return true if @msg.nil?
20
+
21
+ io.puts @msg
22
+ io.puts from_caller.first
23
+ false
24
+ end
25
+
26
+ def not_recommended?; not_recommended_names.include?(@name.to_sym); end
27
+ def duplicate?; @ns_data.has_option?(@name) || @ns_data.has_namespace?(@name); end
28
+
29
+ def not_recommended_names
30
+ NsOptions::Namespace.instance_methods(false).map(&:to_sym)
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,166 @@
1
+ require 'ns-options/options'
2
+ require 'ns-options/namespaces'
3
+
4
+ module NsOptions
5
+
6
+ class NamespaceData
7
+
8
+ attr_reader :ns, :option_type_class, :child_options, :child_namespaces
9
+
10
+ def initialize(ns, option_type_class)
11
+ @ns, @option_type_class = ns, option_type_class
12
+ @child_namespaces = NsOptions::Namespaces.new
13
+ @child_options = NsOptions::Options.new
14
+ end
15
+
16
+ # Recursively check if options that were defined as :required have been set.
17
+ def required_set?
18
+ @child_options.required_set? && @child_namespaces.required_set?
19
+ end
20
+
21
+ def set_option_type_class(value)
22
+ @option_type_class = value
23
+ end
24
+
25
+ def has_option?(name); !!@child_options[name]; end
26
+ def get_option(name); @child_options.get(name); end
27
+ def set_option(name, val); @child_options.set(name, val); end
28
+ def add_option(*args)
29
+ name = args.first
30
+ opt = NsOptions::Option.new(*NsOptions::Option.args(args, @option_type_class))
31
+
32
+ @child_namespaces.rm(name)
33
+ @child_options.add(name, opt)
34
+ end
35
+
36
+ def has_namespace?(name); !!@child_namespaces[name]; end
37
+ def get_namespace(name); @child_namespaces.get(name); end
38
+ def add_namespace(name, option_type_class=nil, &block)
39
+ opt_type_class = option_type_class || @option_type_class
40
+ ns = NsOptions::Namespace.new(name, opt_type_class, &block)
41
+
42
+ @child_options.rm(name)
43
+ @child_namespaces.add(name, ns)
44
+ end
45
+
46
+ # recursively build a hash representation of the namespace, using symbols
47
+ # for the option/namespace name-keys
48
+ def to_hash
49
+ Hash.new.tap do |hash|
50
+ @child_options.each{|name, opt| hash[name.to_sym] = opt.value}
51
+ @child_namespaces.each{|name, value| hash[name.to_sym] = value.to_hash}
52
+ end
53
+ end
54
+
55
+ # The opposite of #to_hash. Takes a hash representation of options and
56
+ # namespaces and mass assigns option values.
57
+ def apply(values=nil)
58
+ (values || {}).each do |name, value|
59
+ if has_namespace?(name)
60
+ # recursively apply namespace values if hash given; ignore otherwise.
61
+ get_namespace(name).apply(value) if value.kind_of?(Hash)
62
+ else
63
+ # be sure to use the namespace's writer to write the option value
64
+ @ns.send("#{name}=", value)
65
+ end
66
+ end
67
+ end
68
+
69
+ # allow for iterating over the key/values of a namespace
70
+ # this uses #to_hash so you won't get option/namespace objs for the values
71
+ def each
72
+ to_hash.each{|k,v| yield k,v if block_given? }
73
+ end
74
+
75
+ # define the parent ns using the given block
76
+ def define(&block)
77
+ if block && block.arity > 0
78
+ block.call @ns
79
+ elsif block
80
+ @ns.instance_eval(&block)
81
+ end
82
+ @ns
83
+ end
84
+
85
+ def build_from(other_ns_data)
86
+ set_option_type_class(other_ns_data.option_type_class)
87
+
88
+ other_ns_data.child_options.each do |name, opt|
89
+ add_option(name, opt.type_class, opt.rules)
90
+ end
91
+
92
+ other_ns_data.child_namespaces.each do |name, ns|
93
+ new_ns = add_namespace(name)
94
+ new_ns.build_from(ns)
95
+ end
96
+ end
97
+
98
+ def reset
99
+ child_options.each {|name, opt| opt.reset}
100
+ child_namespaces.each {|name, ns| ns.reset}
101
+ end
102
+
103
+ class DslMethod
104
+ attr_reader :name, :data
105
+
106
+ def initialize(meth, *args, &block)
107
+ @method_string, @args, @block = meth.to_s, args, block
108
+ @name = @method_string.gsub("=", "")
109
+ @data = args.size == 1 ? args[0] : args
110
+ end
111
+
112
+ def writer?; !!(@method_string =~ /=\Z/); end
113
+ def reader?; !self.writer?; end
114
+ def has_args?; !@args.empty?; end
115
+ end
116
+
117
+ def ns_respond_to?(meth)
118
+ dslm = DslMethod.new(meth)
119
+
120
+ has_namespace?(dslm.name) || # namespace reader
121
+ has_option?(dslm.name) || # option reader
122
+ dslm.writer? || # dynamic option writer
123
+ false
124
+ end
125
+
126
+ def ns_method_missing(bt, meth, *args, &block)
127
+ dslm = DslMethod.new(meth, *args, &block)
128
+
129
+ if is_namespace_reader?(dslm)
130
+ get_namespace(dslm.name).define(&block)
131
+ elsif is_option_reader?(dslm)
132
+ get_option(dslm.name)
133
+ elsif is_option_writer?(dslm)
134
+ # TODO: remove same-named opt/ns when adding the other with same name
135
+ add_option(dslm.name) unless has_option?(dslm.name)
136
+ begin
137
+ set_option(dslm.name, dslm.data)
138
+ rescue NsOptions::Option::CoerceError
139
+ error! bt, err # reraise this exception with a sane backtrace
140
+ end
141
+ else # namespace writer or unknown
142
+ # raise a no meth err with a sane backtrace
143
+ error! bt, NoMethodError.new("undefined method `#{meth}' for #{@ns.inspect}")
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def is_namespace_reader?(dsl_method)
150
+ has_namespace?(dsl_method.name) && !dsl_method.has_args?
151
+ end
152
+
153
+ def is_option_reader?(dsl_method)
154
+ has_option?(dsl_method.name) && !dsl_method.has_args?
155
+ end
156
+
157
+ def is_option_writer?(dsl_method)
158
+ !has_namespace?(dsl_method.name) && dsl_method.has_args?
159
+ end
160
+
161
+ def error!(backtrace, exception)
162
+ exception.set_backtrace(backtrace); raise exception
163
+ end
164
+
165
+ end
166
+ end
@@ -1,22 +1,33 @@
1
- module NsOptions
1
+ require 'ns-options/namespace'
2
2
 
3
- class Namespaces < Hash
3
+ module NsOptions
4
+ class Namespaces
4
5
 
5
- def [](name)
6
- super(name.to_sym)
7
- end
8
- def []=(name, value)
9
- super(name.to_sym, value)
6
+ def initialize
7
+ @hash = Hash.new
10
8
  end
11
9
 
12
- def add(name, key, parent = nil, &block)
13
- self[name] = NsOptions::Namespace.new(key, parent, &block)
10
+ # for hash with indifferent access behavior
11
+ def [](name); @hash[name.to_s]; end
12
+ def []=(name, value); @hash[name.to_s] = value; end
13
+
14
+ def keys(*args, &block); @hash.keys(*args, &block); end
15
+ def each(*args, &block); @hash.each(*args, &block); end
16
+ def empty?(*args, &block); @hash.empty?(*args, &block); end
17
+
18
+ def add(name, ns); self[name] = ns; end
19
+ def rm(name); @hash.delete(name.to_s); end
20
+ def get(name); self[name]; end
21
+
22
+ def required_set?
23
+ are_all_these_namespaces_set? @hash.values
14
24
  end
15
25
 
16
- def get(name)
17
- self[name]
26
+ private
27
+
28
+ def are_all_these_namespaces_set?(namespaces)
29
+ namespaces.inject(true) {|bool, ns| bool && ns.required_set?}
18
30
  end
19
31
 
20
32
  end
21
-
22
33
  end
@@ -1,26 +1,38 @@
1
1
  module NsOptions
2
2
 
3
3
  class Option
4
- attr_accessor :name, :value, :type_class, :rules
4
+
5
+ class CoerceError < ::ArgumentError
6
+ def initialize(type_class, value, err)
7
+ super("can't coerce `#{value.inspect}' to `#{type_class}': #{err.message}")
8
+ set_backtrace(err.backtrace)
9
+ end
10
+ end
5
11
 
6
12
  def self.rules(rules)
13
+ # make sure any given `:args` rule is an array
7
14
  (rules || {}).tap do |r|
8
15
  r[:args] = (r[:args] ? [*r[:args]] : [])
9
16
  end
10
17
  end
11
18
 
12
- def self.args(*args)
19
+ def self.args(args, default_type_class=nil)
13
20
  [ self.rules(args.last.kind_of?(::Hash) ? args.pop : {}),
14
21
  # if a nil type_class is given, just use Object
15
22
  # this makes the option accept any value with no type coercion
16
- (args[1] || Object),
23
+ (args[1] || default_type_class || Object),
17
24
  args[0].to_s
18
- ]
25
+ ].reverse
19
26
  end
20
27
 
21
- def initialize(*args)
22
- self.rules, self.type_class, self.name = self.class.args(*args)
23
- self.value = self.rules[:default]
28
+ attr_accessor :name, :value, :type_class, :rules
29
+
30
+ def initialize(name, type_class=nil, rules=nil)
31
+ @name, @rules = name, (rules || {})
32
+ @type_class = type_class || Object
33
+ @rules[:args] ||= []
34
+
35
+ self.reset
24
36
  end
25
37
 
26
38
  # if reading a lazy_proc, call the proc and return its coerced return val
@@ -35,10 +47,12 @@ module NsOptions
35
47
  end
36
48
  end
37
49
 
38
- # if setting a lazy_proc, just store the proc off to be called when read
39
- # otherwise, coerce and store the value being set
40
50
  def value=(new_value)
41
- @value = self.lazy_proc?(new_value) ? new_value : self.coerce(new_value)
51
+ save_value(new_value)
52
+ end
53
+
54
+ def reset
55
+ save_value(self.rules[:default])
42
56
  end
43
57
 
44
58
  def is_set?
@@ -46,7 +60,7 @@ module NsOptions
46
60
  end
47
61
 
48
62
  def required?
49
- !!self.rules[:required] || !!self.rules[:require]
63
+ !!self.rules[:required]
50
64
  end
51
65
 
52
66
  def ==(other)
@@ -57,6 +71,12 @@ module NsOptions
57
71
 
58
72
  protected
59
73
 
74
+ # if setting a lazy_proc, just store the proc off to be called when read
75
+ # otherwise, coerce and store the value being set
76
+ def save_value(new_value)
77
+ @value = self.lazy_proc?(new_value) ? new_value : self.coerce(new_value)
78
+ end
79
+
60
80
  # a value is considered to by a lazy eval proc if it some kind of a of
61
81
  # Proc and the option it is being set on is not explicitly defined as some
62
82
  # kind of Proc
@@ -67,24 +87,30 @@ module NsOptions
67
87
  end
68
88
 
69
89
  def coerce(value)
70
- return value if (value.kind_of?(self.type_class)) || value.nil?
71
-
72
- if [ Integer, Float, String ].include?(self.type_class)
73
- # ruby type conversion, i.e. String(1)
74
- Object.send(self.type_class.to_s.to_sym, value)
75
- elsif self.type_class == Symbol
76
- value.to_sym
77
- elsif self.type_class == Hash
78
- {}.merge(value)
79
- else
80
- begin
90
+ return value if no_coercing_needed?(value)
91
+
92
+ begin
93
+ if [ Integer, Float, String ].include?(self.type_class)
94
+ # ruby type conversion, i.e. String(1)
95
+ Object.send(self.type_class.to_s, value)
96
+ elsif self.type_class == Symbol
97
+ value.to_sym
98
+ elsif self.type_class == Hash
99
+ {}.merge(value)
100
+ else
81
101
  self.type_class.new(value, *self.rules[:args])
82
- rescue ArgumentError => err
83
- raise ArgumentError, "#{self.type_class} `initialize': #{err.message}"
84
102
  end
103
+ rescue Exception => err
104
+ raise CoerceError.new(self.type_class, value, err)
85
105
  end
86
106
  end
87
107
 
108
+ private
109
+
110
+ def no_coercing_needed?(value)
111
+ value.kind_of?(self.type_class) || value.nil?
112
+ end
113
+
88
114
  end
89
115
 
90
116
  end