chozo 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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