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 +1 -1
- data/chozo.gemspec +1 -0
- data/lib/chozo.rb +2 -0
- data/lib/chozo/config.rb +1 -95
- data/lib/chozo/config/abstract.rb +36 -0
- data/lib/chozo/config/json.rb +9 -7
- data/lib/chozo/core_ext/boolean.rb +4 -0
- data/lib/chozo/errors.rb +1 -0
- data/lib/chozo/hashie_ext.rb +5 -0
- data/lib/chozo/hashie_ext/mash.rb +49 -0
- data/lib/chozo/varia_model.rb +180 -0
- data/lib/chozo/version.rb +1 -1
- data/spec/support/matchers/each.rb +12 -0
- data/spec/unit/chozo/config/json_spec.rb +25 -29
- data/spec/unit/chozo/varia_model_spec.rb +395 -0
- metadata +29 -5
- data/spec/unit/chozo/config_spec.rb +0 -33
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.
|
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
|
data/chozo.gemspec
CHANGED
data/lib/chozo.rb
CHANGED
@@ -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'
|
data/lib/chozo/config.rb
CHANGED
@@ -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
|
data/lib/chozo/config/json.rb
CHANGED
@@ -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
|
-
|
7
|
-
|
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
|
-
|
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
|
data/lib/chozo/errors.rb
CHANGED
@@ -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
|
data/lib/chozo/version.rb
CHANGED
@@ -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
|
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
|
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
|
56
|
-
subject.from_file(file).should be_a(
|
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
|
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(
|
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
|
100
|
-
config
|
91
|
+
config.name.should eql("reset")
|
92
|
+
config.job.should eql("programmer")
|
101
93
|
end
|
94
|
+
end
|
102
95
|
|
103
|
-
|
104
|
-
|
96
|
+
describe "#save" do
|
97
|
+
it "raises a ConfigSaveError if no path is set or given" do
|
98
|
+
subject.path = nil
|
105
99
|
|
106
|
-
|
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.
|
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-
|
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:
|
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
|