chozo 0.1.0 → 0.2.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.
data/Gemfile CHANGED
@@ -27,7 +27,7 @@ group :development do
27
27
  end rescue Errno::ENOENT
28
28
 
29
29
  elsif RbConfig::CONFIG['target_os'] =~ /linux/i
30
- gem 'libnotify', '~> 0.7.1', require: false
30
+ gem 'libnotify', '~> 0.8.0', require: false
31
31
  gem 'rb-inotify', require: false
32
32
 
33
33
  elsif RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
@@ -19,4 +19,5 @@ Gem::Specification.new do |s|
19
19
  s.add_runtime_dependency 'activesupport', '>= 3.2.0'
20
20
  s.add_runtime_dependency 'activemodel', '>= 3.2.0'
21
21
  s.add_runtime_dependency 'multi_json', '>= 1.3.0'
22
+ s.add_runtime_dependency 'hashie'
22
23
  end
@@ -4,7 +4,9 @@ module Chozo
4
4
  autoload :Errors, 'chozo/errors'
5
5
  autoload :Platform, 'chozo/platform'
6
6
  autoload :RubyEngine, 'chozo/ruby_engine'
7
+ autoload :VariaModel, 'chozo/varia_model'
7
8
  end
8
9
 
9
10
  require 'chozo/core_ext'
11
+ require 'chozo/hashie_ext'
10
12
  require 'active_support/core_ext'
@@ -1,101 +1,7 @@
1
- require 'active_model'
2
- require 'chozo/errors'
3
-
4
1
  module Chozo
5
2
  # @author Jamie Winsor <jamie@vialstudios.com>
6
3
  module Config
4
+ autoload :Abstract, 'chozo/config/abstract'
7
5
  autoload :JSON, 'chozo/config/json'
8
-
9
- extend ActiveSupport::Concern
10
-
11
- included do
12
- include ActiveModel::AttributeMethods
13
- include ActiveModel::Validations
14
- attribute_method_suffix('=')
15
- end
16
-
17
- module ClassMethods
18
- # @return [Set]
19
- def attributes
20
- @attributes ||= Set.new
21
- end
22
-
23
- # @return [Hash]
24
- def defaults
25
- @defaults ||= Hash.new
26
- end
27
-
28
- # @param [Symbol] name
29
- # @param [Hash] options
30
- #
31
- # @return [Hash]
32
- def attribute(name, options = {})
33
- if options[:default]
34
- default_for_attribute(name, options[:default])
35
- end
36
- define_attribute_method(name)
37
- attributes << name.to_sym
38
- end
39
-
40
- private
41
-
42
- def default_for_attribute(name, value)
43
- defaults[name.to_sym] = value
44
- end
45
- end
46
-
47
- attr_accessor :path
48
-
49
- # @param [String] path
50
- # @param [Hash] attributes
51
- def initialize(path = nil, attributes = {})
52
- @path = File.expand_path(path) if path
53
- self.attributes = attributes
54
- end
55
-
56
- # @param [Symbol] key
57
- #
58
- # @return [Object]
59
- def attribute(key)
60
- instance_variable_get("@#{key}") || self.class.defaults[key.to_sym]
61
- end
62
- alias_method :[], :attribute
63
-
64
- # @param [Symbol] key
65
- # @param [Object] value
66
- def attribute=(key, value)
67
- instance_variable_set("@#{key}", value)
68
- end
69
- alias_method :[]=, :attribute=
70
-
71
- # @param [Hash] new_attributes
72
- def attributes=(new_attributes)
73
- new_attributes.symbolize_keys!
74
-
75
- self.class.attributes.each do |attr_name|
76
- send(:attribute=, attr_name, new_attributes[attr_name.to_sym])
77
- end
78
- end
79
-
80
- # @param [Symbol] key
81
- #
82
- # @return [Boolean]
83
- def attribute?(key)
84
- instance_variable_get("@#{key}").present?
85
- end
86
-
87
- # @return [Hash]
88
- def attributes
89
- {}.tap do |attrs|
90
- self.class.attributes.each do |attr|
91
- attrs[attr] = attribute(attr)
92
- end
93
- end
94
- end
95
-
96
- # @return [String]
97
- def to_s
98
- "[#{self.class}] '#{self.path}': #{self.attributes}"
99
- end
100
6
  end
101
7
  end
@@ -0,0 +1,36 @@
1
+ require 'forwardable'
2
+ require 'chozo/varia_model'
3
+
4
+ module Chozo
5
+ module Config
6
+ # @author Jamie Winsor <jamie@vialstudios.com>
7
+ # @api private
8
+ class Abstract
9
+ include VariaModel
10
+
11
+ attr_accessor :path
12
+
13
+ # @param [String] path
14
+ # @param [Hash] attributes
15
+ def initialize(path = nil, attributes = {})
16
+ @path = File.expand_path(path) if path
17
+
18
+ mass_assign(attributes)
19
+ end
20
+
21
+ def [](key)
22
+ self.attributes[key]
23
+ end
24
+
25
+ def []=(key, value)
26
+ self.attributes[key] = value
27
+ end
28
+
29
+ protected
30
+
31
+ def mass_assign(new_attributes = {})
32
+ attributes.deep_merge!(new_attributes)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,13 +1,11 @@
1
+ require 'chozo/errors'
1
2
  require 'multi_json'
2
3
 
3
4
  module Chozo
4
5
  module Config
5
6
  # @author Jamie Winsor <jamie@vialstudios.com>
6
- module JSON
7
- extend ActiveSupport::Concern
8
- include Chozo::Config
9
-
10
- module ClassMethods
7
+ class JSON < Config::Abstract
8
+ class << self
11
9
  # @param [String] data
12
10
  #
13
11
  # @return [~Chozo::Config::JSON]
@@ -43,16 +41,20 @@ module Chozo
43
41
  #
44
42
  # @return [~Chozo::Config::JSON]
45
43
  def from_json(json, options = {})
46
- self.attributes = MultiJson.decode(json, options)
44
+ mass_assign(MultiJson.decode(json, options))
47
45
  self
48
46
  rescue MultiJson::DecodeError => e
49
47
  raise Chozo::Errors::InvalidConfig, e
50
48
  end
51
49
 
52
50
  def save(destination = self.path)
51
+ if destination.nil?
52
+ raise Errors::ConfigSaveError, "Cannot save configuration without a destination. Provide one to save or set one on the object."
53
+ end
54
+
53
55
  FileUtils.mkdir_p(File.dirname(destination))
54
56
  File.open(destination, 'w+') do |f|
55
- f.write(self.to_json)
57
+ f.write(self.to_json(pretty: true))
56
58
  end
57
59
  end
58
60
  end
@@ -0,0 +1,4 @@
1
+ # @author Jamie Winsor <jamie@vialstudios.com>
2
+ module Boolean; end
3
+ class TrueClass; include Boolean; end
4
+ class FalseClass; include Boolean; end
@@ -4,5 +4,6 @@ module Chozo
4
4
  class ChozoError < StandardError; end
5
5
  class ConfigNotFound < ChozoError; end
6
6
  class InvalidConfig < ChozoError; end
7
+ class ConfigSaveError < ChozoError; end
7
8
  end
8
9
  end
@@ -0,0 +1,5 @@
1
+ require 'hashie'
2
+
3
+ Dir["#{File.dirname(__FILE__)}/hashie_ext/*.rb"].sort.each do |path|
4
+ require "chozo/hashie_ext/#{File.basename(path, '.rb')}"
5
+ end
@@ -0,0 +1,49 @@
1
+ module Hashie
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
3
+ class Mash < Hashie::Hash
4
+ alias_method :old_setter, :[]=
5
+ alias_method :old_dup, :dup
6
+
7
+ attr_writer :coercions
8
+
9
+ # @return [Hash]
10
+ def coercions
11
+ @coercions ||= HashWithIndifferentAccess.new
12
+ end
13
+
14
+ def coercion(key)
15
+ self.coercions[key]
16
+ end
17
+
18
+ def set_coercion(key, fun)
19
+ self.coercions[key] = fun
20
+ end
21
+
22
+ # Override setter to coerce the given value if a coercion is defined
23
+ def []=(key, value)
24
+ coerced_value = coercion(key).present? ? coercion(key).call(value) : value
25
+ old_setter(key, coerced_value)
26
+ end
27
+
28
+ # Return the containing Hashie::Mash of the given dotted path
29
+ #
30
+ # @param [String] path
31
+ #
32
+ # @return [Hashie::Mash, nil]
33
+ def container(path)
34
+ parts = path.split('.', 2)
35
+ match = (self[parts[0].to_s] || self[parts[0].to_sym])
36
+ if !parts[1] or match.nil?
37
+ self
38
+ else
39
+ match.container(parts[1])
40
+ end
41
+ end
42
+
43
+ def dup
44
+ mash = old_dup
45
+ mash.coercions = self.coercions
46
+ mash
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,180 @@
1
+ require 'chozo/core_ext'
2
+ require 'chozo/hashie_ext'
3
+
4
+ module Chozo
5
+ # @author Jamie Winsor <jamie@vialstudios.com>
6
+ module VariaModel
7
+ module ClassMethods
8
+ # @return [HashWithIndifferentAccess]
9
+ def attributes
10
+ @attributes ||= Hashie::Mash.new
11
+ end
12
+
13
+ # @return [HashWithIndifferentAccess]
14
+ def validations
15
+ @validations ||= HashWithIndifferentAccess.new
16
+ end
17
+
18
+ # @param [#to_s] name
19
+ # @option options [Symbol, Array<Symbol>] :type
20
+ # @option options [Boolean] :required
21
+ # @option options [Object] :default
22
+ # @option options [Proc] :coerce
23
+ def attribute(name, options = {})
24
+ name = name.to_s
25
+ options[:type] = Array(options[:type])
26
+ options[:required] ||= false
27
+
28
+ register_attribute(name, options)
29
+ define_mimic_methods(name, options)
30
+ end
31
+
32
+ # @param [String] name
33
+ #
34
+ # @return [Array]
35
+ def validations_for(name)
36
+ self.validations[name] ||= Array.new
37
+ end
38
+
39
+ # @param [Constant, Array<Constant>] types
40
+ # @param [VariaModel] model
41
+ # @param [String] key
42
+ #
43
+ # @return [Array]
44
+ def validate_kind_of(types, model, key)
45
+ errors = Array.new
46
+ types = types.uniq
47
+ matches = false
48
+
49
+ types.each do |type|
50
+ if model.attributes.dig(key).is_a?(type)
51
+ matches = true
52
+ break
53
+ end
54
+ end
55
+
56
+ if matches
57
+ [ :ok, "" ]
58
+ else
59
+ types_msg = types.collect { |type| "'#{type}'" }
60
+ [ :error, "Expected attribute: '#{key}' to be a type of: #{types_msg.join(', ')}" ]
61
+ end
62
+ end
63
+
64
+ # @param [VariaModel] model
65
+ # @param [String] key
66
+ #
67
+ # @return [Array]
68
+ def validate_required(model, key)
69
+ if model.attributes.dig(key).present?
70
+ [ :ok, "" ]
71
+ else
72
+ [ :error, "A value is required for attribute: '#{key}'" ]
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def register_attribute(name, options = {})
79
+ if options[:type] && options[:type].any?
80
+ unless options[:required]
81
+ options[:type] << NilClass
82
+ end
83
+ register_validation(name, lambda { |object, key| validate_kind_of(options[:type], object, key) })
84
+ end
85
+
86
+ if options[:required]
87
+ register_validation(name, lambda { |object, key| validate_required(object, key) })
88
+ end
89
+
90
+ class_eval do
91
+ new_attributes = Hashie::Mash.from_dotted_path(name, options[:default])
92
+ self.attributes.merge!(new_attributes)
93
+
94
+ if options[:coerce].is_a?(Proc)
95
+ register_coercion(name, options[:coerce])
96
+ end
97
+ end
98
+ end
99
+
100
+ def register_validation(name, fun)
101
+ self.validations[name] = (self.validations_for(name) << fun)
102
+ end
103
+
104
+ def register_coercion(name, fun)
105
+ self.attributes.container(name).set_coercion(name.split('.').last, fun)
106
+ end
107
+
108
+ def define_mimic_methods(name, options = {})
109
+ fun_name = name.split('.').first
110
+
111
+ class_eval do
112
+ define_method fun_name do
113
+ self.attributes[fun_name]
114
+ end
115
+
116
+ define_method "#{fun_name}=" do |value|
117
+ value = if options[:coerce].is_a?(Proc)
118
+ options[:coerce].call(value)
119
+ else
120
+ value
121
+ end
122
+
123
+ self.attributes[fun_name] = value
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ class << self
130
+ def included(base)
131
+ base.extend(ClassMethods)
132
+ end
133
+ end
134
+
135
+ # @return [HashWithIndifferentAccess]
136
+ def attributes
137
+ @attributes ||= self.class.attributes.dup
138
+ end
139
+
140
+ # @return [HashWithIndifferentAccess]
141
+ def validate
142
+ self.class.validations.each do |attr_path, validations|
143
+ validations.each do |validation|
144
+ status, messages = validation.call(self, attr_path)
145
+
146
+ if status == :error
147
+ if messages.is_a?(Array)
148
+ messages.each do |message|
149
+ self.add_error(attr_path, message)
150
+ end
151
+ else
152
+ self.add_error(attr_path, messages)
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ self.errors
159
+ end
160
+
161
+ # @return [Boolean]
162
+ def valid?
163
+ validate.empty?
164
+ end
165
+
166
+ # @return [HashWithIndifferentAccess]
167
+ def errors
168
+ @errors ||= HashWithIndifferentAccess.new
169
+ end
170
+
171
+ protected
172
+
173
+ # @param [String] attribute
174
+ # @param [String] message
175
+ def add_error(attribute, message)
176
+ self.errors[attribute] ||= Array.new
177
+ self.errors[attribute] << message
178
+ end
179
+ end
180
+ end
@@ -1,3 +1,3 @@
1
1
  module Chozo
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,12 @@
1
+ RSpec::Matchers.define :each do |check|
2
+ match do |actual|
3
+ actual.each_with_index do |index, o|
4
+ @object = o
5
+ index.should check
6
+ end
7
+ end
8
+
9
+ failure_message_for_should do |actual|
10
+ "at[#{@object}] #{check.failure_message_for_should}"
11
+ end
12
+ end
@@ -1,18 +1,6 @@
1
1
  require 'spec_helper'
2
- require 'shared_examples/chozo/config'
3
-
4
- TestJSONConfig = Class.new do
5
- include Chozo::Config::JSON
6
-
7
- attribute :name
8
- validates_presence_of :name
9
-
10
- attribute :job
11
- end
12
2
 
13
3
  describe Chozo::Config::JSON do
14
- it_behaves_like "Chozo::Config", TestJSONConfig
15
-
16
4
  let(:json) do
17
5
  %(
18
6
  {
@@ -24,10 +12,15 @@ describe Chozo::Config::JSON do
24
12
  end
25
13
 
26
14
  describe "ClassMethods" do
27
- subject { TestJSONConfig }
15
+ subject do
16
+ Class.new(Chozo::Config::JSON) do
17
+ attribute :name, required: true
18
+ attribute :job
19
+ end
20
+ end
28
21
 
29
22
  describe "::from_json" do
30
- it "returns an instance of the including class" do
23
+ it "returns an instance of the inheriting class" do
31
24
  subject.from_json(json).should be_a(subject)
32
25
  end
33
26
 
@@ -37,12 +30,6 @@ describe Chozo::Config::JSON do
37
30
  config[:name].should eql("reset")
38
31
  config[:job].should eql("programmer")
39
32
  end
40
-
41
- it "does not set an attribute value for undefined attributes" do
42
- config = subject.from_json(json)
43
-
44
- config[:status].should be_nil
45
- end
46
33
  end
47
34
 
48
35
  describe "::from_file" do
@@ -52,8 +39,8 @@ describe Chozo::Config::JSON do
52
39
  File.open(file, "w") { |f| f.write(json) }
53
40
  end
54
41
 
55
- it "returns an instance of the including class" do
56
- subject.from_file(file).should be_a(TestJSONConfig)
42
+ it "returns an instance of the inheriting class" do
43
+ subject.from_file(file).should be_a(subject)
57
44
  end
58
45
 
59
46
  it "sets the object's filepath to the path of the loaded file" do
@@ -70,7 +57,12 @@ describe Chozo::Config::JSON do
70
57
  end
71
58
  end
72
59
 
73
- subject { TestJSONConfig.new }
60
+ subject do
61
+ Class.new(Chozo::Config::JSON) do
62
+ attribute :name, required: true
63
+ attribute :job
64
+ end.new
65
+ end
74
66
 
75
67
  describe "#to_json" do
76
68
  before(:each) do
@@ -90,20 +82,24 @@ describe Chozo::Config::JSON do
90
82
 
91
83
  describe "#from_json" do
92
84
  it "returns an instance of the updated class" do
93
- subject.from_json(json).should be_a(subject.class)
85
+ subject.from_json(json).should be_a(Chozo::Config::JSON)
94
86
  end
95
87
 
96
88
  it "assigns values for each defined attribute" do
97
89
  config = subject.from_json(json)
98
90
 
99
- config[:name].should eql("reset")
100
- config[:job].should eql("programmer")
91
+ config.name.should eql("reset")
92
+ config.job.should eql("programmer")
101
93
  end
94
+ end
102
95
 
103
- it "does not set an attribute value for undefined attributes" do
104
- config = subject.from_json(json)
96
+ describe "#save" do
97
+ it "raises a ConfigSaveError if no path is set or given" do
98
+ subject.path = nil
105
99
 
106
- config[:status].should be_nil
100
+ lambda {
101
+ subject.save
102
+ }.should raise_error(Chozo::Errors::ConfigSaveError)
107
103
  end
108
104
  end
109
105
  end
@@ -0,0 +1,395 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chozo::VariaModel do
4
+ describe "ClassMethods" do
5
+ subject do
6
+ Class.new do
7
+ include Chozo::VariaModel
8
+ end
9
+ end
10
+
11
+ describe "::attributes" do
12
+ it "returns a Hashie::Mash" do
13
+ subject.attributes.should be_a(Hashie::Mash)
14
+ end
15
+
16
+ it "is empty by default" do
17
+ subject.attributes.should be_empty
18
+ end
19
+ end
20
+
21
+ describe "::attribute" do
22
+ it "adds an attribute to the attributes hash for each attribute function call" do
23
+ subject.attribute 'jamie.winsor'
24
+ subject.attribute 'brooke.winsor'
25
+
26
+ subject.attributes.should have(2).items
27
+ end
28
+
29
+ it "adds a validation if :required option is true" do
30
+ subject.attribute 'brooke.winsor', required: true
31
+
32
+ subject.validations.should have(1).item
33
+ end
34
+
35
+ it "adds a validation if the :type option is provided" do
36
+ subject.attribute 'brooke.winsor', type: :string
37
+
38
+ subject.validations.should have(1).item
39
+ end
40
+
41
+ it "sets a default value if :default option is provided" do
42
+ subject.attribute 'brooke.winsor', default: 'rhode island'
43
+
44
+ subject.attributes.dig('brooke.winsor').should eql('rhode island')
45
+ end
46
+ end
47
+
48
+ describe "::validations" do
49
+ it "returns a HashWithIndifferentAccess" do
50
+ subject.validations.should be_a(HashWithIndifferentAccess)
51
+ end
52
+
53
+ it "is empty by default" do
54
+ subject.validations.should be_empty
55
+ end
56
+ end
57
+
58
+ describe "::validations_for" do
59
+ context "when an attribute is registered and has validations" do
60
+ before(:each) do
61
+ subject.attribute("nested.attribute", required: true, type: String)
62
+ end
63
+
64
+ it "returns an array of procs" do
65
+ validations = subject.validations_for("nested.attribute")
66
+
67
+ validations.should be_a(Array)
68
+ validations.should each be_a(Proc)
69
+ end
70
+ end
71
+
72
+ context "when an attribute is registered but has no validations" do
73
+ before(:each) do
74
+ subject.attribute("nested.attribute")
75
+ end
76
+
77
+ it "returns an empty array" do
78
+ validations = subject.validations_for("nested.attribute")
79
+
80
+ validations.should be_a(Array)
81
+ validations.should be_empty
82
+ end
83
+ end
84
+
85
+ context "when an attribute is not registered" do
86
+ it "returns an empty array" do
87
+ validations = subject.validations_for("not_existing.attribute")
88
+
89
+ validations.should be_a(Array)
90
+ validations.should be_empty
91
+ end
92
+ end
93
+ end
94
+
95
+ describe "::validate_kind_of" do
96
+ let(:types) do
97
+ [
98
+ String,
99
+ Boolean
100
+ ]
101
+ end
102
+
103
+ let(:key) do
104
+ 'nested.one'
105
+ end
106
+
107
+ subject do
108
+ Class.new do
109
+ include Chozo::VariaModel
110
+
111
+ attribute 'nested.one', types: [String, Boolean]
112
+ end
113
+ end
114
+
115
+ let(:model) do
116
+ subject.new
117
+ end
118
+
119
+ it "returns an array" do
120
+ subject.validate_kind_of(types, model, key).should be_a(Array)
121
+ end
122
+
123
+ context "failure" do
124
+ before(:each) do
125
+ model.nested.one = nil
126
+ end
127
+
128
+ it "returns an array where the first element is ':error'" do
129
+ subject.validate_kind_of(types, model, key).first.should eql(:error)
130
+ end
131
+
132
+ it "returns an array where the second element is an error message containing the attribute and types" do
133
+ types.each do |type|
134
+ subject.validate_kind_of(types, model, key)[1].should =~ /#{type}/
135
+ end
136
+ subject.validate_kind_of(types, model, key)[1].should =~ /#{key}/
137
+ end
138
+ end
139
+
140
+ context "success" do
141
+ before(:each) do
142
+ model.nested.one = true
143
+ end
144
+
145
+ it "returns an array where the first element is ':ok'" do
146
+ subject.validate_kind_of(types, model, key).first.should eql(:ok)
147
+ end
148
+
149
+ it "returns an array where the second element is a blank string" do
150
+ subject.validate_kind_of(types, model, key)[1].should be_blank
151
+ end
152
+ end
153
+
154
+ context "when given two types of the same kind" do
155
+ let(:types) do
156
+ [
157
+ String,
158
+ String
159
+ ]
160
+ end
161
+
162
+ let(:key) do
163
+ 'nested.one'
164
+ end
165
+
166
+ subject do
167
+ Class.new do
168
+ include Chozo::VariaModel
169
+
170
+ attribute 'nested.one', types: [String, Boolean]
171
+ end
172
+ end
173
+
174
+ let(:model) do
175
+ subject.new
176
+ end
177
+
178
+ before(:each) do
179
+ model.nested.one = nil
180
+ end
181
+
182
+ it "returns a error message that contains the type error only once" do
183
+ subject.validate_kind_of(types, model, key)[1].should eql("Expected attribute: 'nested.one' to be a type of: 'String'")
184
+ end
185
+ end
186
+ end
187
+
188
+ describe "::validate_required" do
189
+ let(:key) do
190
+ 'nested.one'
191
+ end
192
+
193
+ subject do
194
+ Class.new do
195
+ include Chozo::VariaModel
196
+
197
+ attribute 'nested.one', required: true
198
+ end
199
+ end
200
+
201
+ let(:model) do
202
+ subject.new
203
+ end
204
+
205
+ it "returns an array" do
206
+ subject.validate_required(model, key).should be_a(Array)
207
+ end
208
+
209
+ context "failure" do
210
+ before(:each) do
211
+ model.nested.one = nil
212
+ end
213
+
214
+ it "returns an array where the first element is ':error'" do
215
+ subject.validate_required(model, key).first.should eql(:error)
216
+ end
217
+
218
+ it "returns an array where the second element is an error message containing the attribute name" do
219
+ subject.validate_required(model, key)[1].should =~ /#{key}/
220
+ end
221
+ end
222
+
223
+ context "success" do
224
+ before(:each) do
225
+ model.nested.one = "hello"
226
+ end
227
+
228
+ it "returns an array where the first element is ':ok'" do
229
+ subject.validate_required(model, key).first.should eql(:ok)
230
+ end
231
+
232
+ it "returns an array where the second element is a blank string" do
233
+ subject.validate_required(model, key)[1].should be_blank
234
+ end
235
+ end
236
+ end
237
+ end
238
+
239
+ subject do
240
+ Class.new do
241
+ include Chozo::VariaModel
242
+
243
+ attribute 'nested.not_coerced', default: 'hello'
244
+ attribute 'nested.no_default'
245
+ attribute 'nested.coerced', coerce: lambda { |m| m.to_s }
246
+ attribute 'toplevel', default: 'hello'
247
+ attribute 'no_default'
248
+ attribute 'coerced', coerce: lambda { |m| m.to_s }
249
+ end.new
250
+ end
251
+
252
+ describe "GeneratedAccessors" do
253
+ describe "nested getter" do
254
+ it "returns the default value" do
255
+ subject.nested.not_coerced.should eql('hello')
256
+ end
257
+
258
+ it "returns nil if there is no default value" do
259
+ subject.nested.no_default.should be_nil
260
+ end
261
+ end
262
+
263
+ describe "toplevel getter" do
264
+ it "returns the default value" do
265
+ subject.toplevel.should eql('hello')
266
+ end
267
+
268
+ it "returns nil if there is no default value" do
269
+ subject.no_default.should be_nil
270
+ end
271
+ end
272
+
273
+ describe "nested setter" do
274
+ it "sets the value of the nested attribute" do
275
+ subject.nested.not_coerced = 'world'
276
+
277
+ subject.nested.not_coerced.should eql('world')
278
+ end
279
+ end
280
+
281
+ describe "toplevel setter" do
282
+ it "sets the value of the top level attribute" do
283
+ subject.toplevel = 'world'
284
+
285
+ subject.toplevel.should eql('world')
286
+ end
287
+ end
288
+
289
+ describe "nested coerced setter" do
290
+ it "sets the value of the nested coerced attribute" do
291
+ subject.nested.coerced = 1
292
+
293
+ subject.nested.coerced.should eql("1")
294
+ end
295
+ end
296
+
297
+ describe "toplevel coerced setter" do
298
+ it "sets the value of the top level coerced attribute" do
299
+ subject.coerced = 1
300
+
301
+ subject.coerced.should eql('1')
302
+ end
303
+ end
304
+
305
+ context "given two nested attributes with a common parent and default values" do
306
+ subject do
307
+ Class.new do
308
+ include Chozo::VariaModel
309
+
310
+ attribute 'nested.one', default: 'val_one'
311
+ attribute 'nested.two', default: 'val_two'
312
+ end.new
313
+ end
314
+
315
+ it "sets a default value for each nested attribute" do
316
+ subject.nested.one.should eql('val_one')
317
+ subject.nested.two.should eql('val_two')
318
+ end
319
+ end
320
+
321
+ context "given two nested attributes with a common parent and coercions" do
322
+ subject do
323
+ Class.new do
324
+ include Chozo::VariaModel
325
+
326
+ attribute 'nested.one', coerce: lambda { |m| m.to_s }
327
+ attribute 'nested.two', coerce: lambda { |m| m.to_s }
328
+ end.new
329
+ end
330
+
331
+ it "coerces each value if both have a coercion" do
332
+ subject.nested.one = 1
333
+ subject.nested.two = 2
334
+
335
+ subject.nested.one.should eql("1")
336
+ subject.nested.two.should eql("2")
337
+ end
338
+ end
339
+ end
340
+
341
+ describe "Validations" do
342
+ describe "validate required" do
343
+ subject do
344
+ Class.new do
345
+ include Chozo::VariaModel
346
+
347
+ attribute 'brooke.winsor', required: true
348
+ end.new
349
+ end
350
+
351
+ it "is not valid if it fails validation" do
352
+ subject.should_not be_valid
353
+ end
354
+
355
+ it "adds an error for each attribute that fails validations" do
356
+ subject.validate
357
+
358
+ subject.errors.should have(1).item
359
+ end
360
+
361
+ it "adds a message for each failed validation" do
362
+ subject.validate
363
+
364
+ subject.errors['brooke.winsor'].should have(1).item
365
+ subject.errors['brooke.winsor'][0].should eql("A value is required for attribute: 'brooke.winsor'")
366
+ end
367
+ end
368
+
369
+ describe "validate type" do
370
+ subject do
371
+ Class.new do
372
+ include Chozo::VariaModel
373
+
374
+ attribute 'brooke.winsor', type: String
375
+ end.new
376
+ end
377
+
378
+ before(:each) do
379
+ subject.brooke.winsor = false
380
+ end
381
+
382
+ it "returns false if it fails validation" do
383
+ subject.should_not be_valid
384
+ end
385
+
386
+ it "adds an error if it fails validation" do
387
+ subject.validate
388
+
389
+ subject.errors.should have(1).item
390
+ subject.errors['brooke.winsor'].should have(1).item
391
+ subject.errors['brooke.winsor'][0].should eql("Expected attribute: 'brooke.winsor' to be a type of: 'String', 'NilClass'")
392
+ end
393
+ end
394
+ end
395
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chozo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-29 00:00:00.000000000 Z
12
+ date: 2012-10-31 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -59,6 +59,22 @@ dependencies:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: 1.3.0
62
+ - !ruby/object:Gem::Dependency
63
+ name: hashie
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
62
78
  description: A collection of supporting libraries and Ruby core extensions
63
79
  email:
64
80
  - jamie@vialstudios.com
@@ -77,22 +93,28 @@ files:
77
93
  - chozo.gemspec
78
94
  - lib/chozo.rb
79
95
  - lib/chozo/config.rb
96
+ - lib/chozo/config/abstract.rb
80
97
  - lib/chozo/config/json.rb
81
98
  - lib/chozo/core_ext.rb
99
+ - lib/chozo/core_ext/boolean.rb
82
100
  - lib/chozo/core_ext/hash.rb
83
101
  - lib/chozo/core_ext/kernel.rb
84
102
  - lib/chozo/errors.rb
103
+ - lib/chozo/hashie_ext.rb
104
+ - lib/chozo/hashie_ext/mash.rb
85
105
  - lib/chozo/platform.rb
86
106
  - lib/chozo/ruby_engine.rb
107
+ - lib/chozo/varia_model.rb
87
108
  - lib/chozo/version.rb
88
109
  - spec/shared_examples/chozo/config.rb
89
110
  - spec/spec_helper.rb
90
111
  - spec/support/helpers.rb
112
+ - spec/support/matchers/each.rb
91
113
  - spec/unit/chozo/config/json_spec.rb
92
- - spec/unit/chozo/config_spec.rb
93
114
  - spec/unit/chozo/core_ext/hash_spec.rb
94
115
  - spec/unit/chozo/platform_spec.rb
95
116
  - spec/unit/chozo/ruby_engine_spec.rb
117
+ - spec/unit/chozo/varia_model_spec.rb
96
118
  homepage: https://github.com/reset/chozo
97
119
  licenses: []
98
120
  post_install_message:
@@ -113,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
135
  version: '0'
114
136
  segments:
115
137
  - 0
116
- hash: 3399825819985301824
138
+ hash: 3079662827452475923
117
139
  requirements: []
118
140
  rubyforge_project:
119
141
  rubygems_version: 1.8.23
@@ -124,8 +146,10 @@ test_files:
124
146
  - spec/shared_examples/chozo/config.rb
125
147
  - spec/spec_helper.rb
126
148
  - spec/support/helpers.rb
149
+ - spec/support/matchers/each.rb
127
150
  - spec/unit/chozo/config/json_spec.rb
128
- - spec/unit/chozo/config_spec.rb
129
151
  - spec/unit/chozo/core_ext/hash_spec.rb
130
152
  - spec/unit/chozo/platform_spec.rb
131
153
  - spec/unit/chozo/ruby_engine_spec.rb
154
+ - spec/unit/chozo/varia_model_spec.rb
155
+ has_rdoc:
@@ -1,33 +0,0 @@
1
- require 'spec_helper'
2
- require 'shared_examples/chozo/config'
3
-
4
- TestConfig = Class.new do
5
- include Chozo::Config
6
-
7
- attribute :name
8
- validates_presence_of :name
9
-
10
- attribute :job
11
- end
12
-
13
- describe Chozo::Config do
14
- it_behaves_like "Chozo::Config", TestConfig
15
-
16
- subject do
17
- TestConfig.new
18
- end
19
-
20
- describe "Validations" do
21
- it "is valid if all required attributes are specified" do
22
- subject.name = "reset"
23
-
24
- subject.should be_valid
25
- end
26
-
27
- it "is not valid if a required attribute is not specified" do
28
- subject.name = nil
29
-
30
- subject.should_not be_valid
31
- end
32
- end
33
- end