obvious 0.0.7 → 0.0.8
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/.gitignore +1 -0
- data/lib/generators/application_generator.rb +8 -7
- data/lib/generators/descriptor.rb +22 -12
- data/lib/obvious/contract.rb +46 -12
- data/lib/obvious/entity.rb +74 -0
- data/lib/obvious/files/external/fs_plug.rb +35 -30
- data/lib/obvious/files/external/mongo_plug.rb +5 -0
- data/lib/obvious/files/external/s3_plug.rb +94 -0
- data/lib/obvious/version.rb +1 -1
- data/lib/obvious.rb +1 -0
- data/obvious.gemspec +2 -0
- data/spec/.spec_helper.rb.swp +0 -0
- data/spec/contract_spec.rb +63 -0
- data/spec/entity_spec.rb +75 -0
- data/spec/generators/descriptor_spec.rb +34 -0
- data/spec/spec_helper.rb +3 -0
- metadata +27 -4
data/.gitignore
CHANGED
@@ -30,7 +30,8 @@ module Obvious
|
|
30
30
|
|
31
31
|
puts 'Creating actions from descriptors... ' unless descriptors.length.zero?
|
32
32
|
descriptors.each do |file|
|
33
|
-
|
33
|
+
yaml = YAML.load_file(file)
|
34
|
+
descriptor = Obvious::Generators::Descriptor.new yaml
|
34
35
|
descriptor.to_file
|
35
36
|
end
|
36
37
|
|
@@ -150,22 +151,22 @@ class #{k}Contract < Contract
|
|
150
151
|
end
|
151
152
|
}
|
152
153
|
|
153
|
-
snake_name =
|
154
|
+
snake_name = k.gsub(/(.)([A-Z])/,'\1_\2').downcase
|
154
155
|
|
155
|
-
filename = "#{@app.dir}/contracts/#{snake_name}
|
156
|
+
filename = "#{@app.dir}/contracts/#{snake_name}_contract.rb"
|
156
157
|
File.open(filename, 'w') {|f| f.write(output) }
|
157
158
|
|
158
|
-
output = %Q{require_relative '../../contracts/#{snake_name}
|
159
|
+
output = %Q{require_relative '../../contracts/#{snake_name}_contract'
|
159
160
|
|
160
161
|
describe #{k}Contract do
|
161
162
|
#{method_specs}
|
162
163
|
end
|
163
164
|
}
|
164
165
|
|
165
|
-
filename = "#{@app.dir}/spec/contracts/#{snake_name}
|
166
|
+
filename = "#{@app.dir}/spec/contracts/#{snake_name}_spec.rb"
|
166
167
|
File.open(filename, 'w') {|f| f.write(output) }
|
167
168
|
|
168
|
-
output = %Q{require_relative '../../contracts/#{snake_name}
|
169
|
+
output = %Q{require_relative '../../contracts/#{snake_name}_contract'
|
169
170
|
|
170
171
|
class #{k}Double
|
171
172
|
def self.create behavior
|
@@ -187,7 +188,7 @@ class #{k}_BadOutput < #{k}Contract
|
|
187
188
|
end
|
188
189
|
}
|
189
190
|
|
190
|
-
filename = "#{@app.dir}/spec/doubles/#{snake_name}
|
191
|
+
filename = "#{@app.dir}/spec/doubles/#{snake_name}_double.rb"
|
191
192
|
File.open(filename, 'w') {|f| f.write(output) }
|
192
193
|
|
193
194
|
end
|
@@ -4,22 +4,25 @@ require_relative 'helpers/application'
|
|
4
4
|
|
5
5
|
module Obvious
|
6
6
|
module Generators
|
7
|
+
class InvalidDescriptorError < StandardError; end
|
8
|
+
|
7
9
|
class Descriptor
|
8
10
|
def initialize descriptor
|
9
11
|
@descriptor = descriptor
|
10
12
|
end
|
11
13
|
|
12
14
|
def to_file
|
13
|
-
|
15
|
+
validate_descriptor
|
16
|
+
|
14
17
|
@jacks, @entities = {}, {}
|
15
18
|
@code = ''
|
16
19
|
|
17
|
-
|
20
|
+
@descriptor['Code'].each do |entry|
|
18
21
|
write_comments_for entry
|
19
22
|
process_requirements_for entry if entry['requires']
|
20
23
|
end
|
21
24
|
|
22
|
-
write_action
|
25
|
+
write_action
|
23
26
|
end
|
24
27
|
|
25
28
|
private
|
@@ -53,12 +56,12 @@ module Obvious
|
|
53
56
|
end
|
54
57
|
end # #process_requirements_for
|
55
58
|
|
56
|
-
def write_action
|
59
|
+
def write_action
|
57
60
|
jacks_data = process_jacks
|
58
61
|
requirements = require_entities
|
59
62
|
|
60
63
|
output = %Q{#{requirements}
|
61
|
-
class #{
|
64
|
+
class #{@descriptor['Action']}
|
62
65
|
|
63
66
|
def initialize #{jacks_data[:inputs]}
|
64
67
|
#{jacks_data[:assignments]} end
|
@@ -68,24 +71,24 @@ class #{action['Action']}
|
|
68
71
|
end
|
69
72
|
}
|
70
73
|
|
71
|
-
snake_name =
|
74
|
+
snake_name = @descriptor['Action'].gsub(/(.)([A-Z])/,'\1_\2').downcase
|
72
75
|
|
73
76
|
filename = "#{Obvious::Generators::Application.instance.dir}/actions/#{snake_name}.rb"
|
74
77
|
File.open(filename, 'w') {|f| f.write(output) }
|
75
78
|
|
76
|
-
|
79
|
+
output = %Q{require_relative '../../actions/#{snake_name}'
|
77
80
|
|
78
|
-
describe #{
|
81
|
+
describe #{@descriptor['Action']} do
|
79
82
|
|
80
|
-
it '#{
|
83
|
+
it '#{@descriptor['Description']}'
|
81
84
|
|
82
85
|
it 'should raise an error with invalid input'
|
83
86
|
|
84
87
|
end
|
85
|
-
}
|
88
|
+
}
|
86
89
|
|
87
|
-
|
88
|
-
|
90
|
+
filename = "#{Obvious::Generators::Application.instance.dir}/spec/actions/#{snake_name}_spec.rb"
|
91
|
+
File.open(filename, 'w') {|f| f.write(output) }
|
89
92
|
end
|
90
93
|
|
91
94
|
def process_jacks
|
@@ -116,6 +119,13 @@ end
|
|
116
119
|
|
117
120
|
entity_requires
|
118
121
|
end
|
122
|
+
|
123
|
+
def validate_descriptor
|
124
|
+
raise InvalidDescriptorError unless @descriptor
|
125
|
+
raise InvalidDescriptorError if @descriptor['Code'].nil?
|
126
|
+
raise InvalidDescriptorError if @descriptor['Action'].nil?
|
127
|
+
raise InvalidDescriptorError if @descriptor['Description'].nil?
|
128
|
+
end
|
119
129
|
end # ::Descriptor
|
120
130
|
end
|
121
131
|
end
|
data/lib/obvious/contract.rb
CHANGED
@@ -91,8 +91,9 @@ class Contract
|
|
91
91
|
# of the output_shape.
|
92
92
|
def call_method method, input, input_shape, output_shape
|
93
93
|
if input != nil && input_shape != nil
|
94
|
-
|
95
|
-
|
94
|
+
has_shape, error_field = input.has_shape? input_shape, true
|
95
|
+
unless has_shape
|
96
|
+
raise ContractInputError, "incorrect input data format field #{error_field}"
|
96
97
|
end
|
97
98
|
|
98
99
|
result = self.send method, input
|
@@ -106,6 +107,10 @@ class Contract
|
|
106
107
|
raise ContractOutputError, 'incorrect output data format'
|
107
108
|
end
|
108
109
|
|
110
|
+
if result === {}
|
111
|
+
raise DataNotFoundError, 'data was not found'
|
112
|
+
end
|
113
|
+
|
109
114
|
# we are looking for result to be a True object
|
110
115
|
if output_shape === true
|
111
116
|
if output_shape == result
|
@@ -120,8 +125,9 @@ class Contract
|
|
120
125
|
if result.class == Array
|
121
126
|
inner_shape = output_shape[0]
|
122
127
|
result.each do |item|
|
123
|
-
|
124
|
-
|
128
|
+
has_shape, error_field = item.has_shape? inner_shape, true
|
129
|
+
unless has_shape
|
130
|
+
raise ContractOutputError, "incorrect output data format field #{error_field}"
|
125
131
|
end
|
126
132
|
end
|
127
133
|
|
@@ -140,8 +146,9 @@ class Contract
|
|
140
146
|
end
|
141
147
|
|
142
148
|
# we want result to be output_shape's shape
|
143
|
-
|
144
|
-
|
149
|
+
has_shape, error_field = result.has_shape? output_shape, true
|
150
|
+
unless has_shape
|
151
|
+
raise ContractOutputError, "incorrect output data format field #{error_field}"
|
145
152
|
end
|
146
153
|
|
147
154
|
result
|
@@ -162,18 +169,41 @@ class Hash
|
|
162
169
|
# shape = { k1: Array, k2: { k3: Module } }
|
163
170
|
# h.has_shape?(shape)
|
164
171
|
# #=> true
|
165
|
-
def has_shape?(shape)
|
172
|
+
def has_shape?(shape, return_field = false)
|
173
|
+
return_value = lambda { |r, f|
|
174
|
+
if return_field
|
175
|
+
return r, f
|
176
|
+
else
|
177
|
+
return r
|
178
|
+
end
|
179
|
+
}
|
180
|
+
|
166
181
|
# I added an empty check
|
167
182
|
if self.empty?
|
168
|
-
return shape.empty
|
169
|
-
end
|
170
|
-
|
171
|
-
|
183
|
+
return return_value.call shape.empty?, nil
|
184
|
+
end
|
185
|
+
|
186
|
+
self.each do |k, v|
|
187
|
+
return return_value.call false, k if shape[k] == nil
|
188
|
+
end
|
189
|
+
|
190
|
+
shape.each do |k, v|
|
172
191
|
# hash_value
|
173
192
|
hv = self[k]
|
174
|
-
|
193
|
+
return return_value.call false, k unless self.has_key? k
|
194
|
+
|
195
|
+
next if hv === nil
|
196
|
+
|
197
|
+
if Hash === hv
|
198
|
+
return hv.has_shape?(v, return_field)
|
199
|
+
else
|
200
|
+
return return_value.call false, k unless v === hv
|
201
|
+
end
|
175
202
|
end
|
203
|
+
|
204
|
+
return_value.call true, nil
|
176
205
|
end
|
206
|
+
|
177
207
|
end
|
178
208
|
|
179
209
|
class ContractInputError < StandardError
|
@@ -181,3 +211,7 @@ end
|
|
181
211
|
|
182
212
|
class ContractOutputError < StandardError
|
183
213
|
end
|
214
|
+
|
215
|
+
class DataNotFoundError < StandardError
|
216
|
+
end
|
217
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
module Obvious
|
3
|
+
|
4
|
+
module EntityMixin
|
5
|
+
class << self
|
6
|
+
def included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
attr_reader :shape
|
13
|
+
attr_reader :validations
|
14
|
+
|
15
|
+
def value name, type
|
16
|
+
name = name.to_sym
|
17
|
+
@shape ||= {}
|
18
|
+
@shape[name] = type
|
19
|
+
define_method(name) { @values[name] }
|
20
|
+
end
|
21
|
+
|
22
|
+
def validation name, method
|
23
|
+
name = "#{name}_validation".to_sym
|
24
|
+
@validations ||= []
|
25
|
+
@validations << name
|
26
|
+
define_method(name) { instance_exec &method }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ShapeError < StandardError; end
|
32
|
+
class TypeError < StandardError; end
|
33
|
+
class ValidationError < StandardError; end
|
34
|
+
|
35
|
+
class Entity
|
36
|
+
include EntityMixin
|
37
|
+
|
38
|
+
def initialize input
|
39
|
+
shape = self.class.shape
|
40
|
+
validations = self.class.validations || []
|
41
|
+
|
42
|
+
self.class.shape.freeze
|
43
|
+
self.class.validations.freeze
|
44
|
+
|
45
|
+
@values = {}
|
46
|
+
input.each do |k, v|
|
47
|
+
unless shape[k]
|
48
|
+
raise Obvious::ShapeError.new "Invalid input field: #{k}"
|
49
|
+
else
|
50
|
+
@values[k] = v
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
@values.freeze # this might need to be recursive?
|
55
|
+
|
56
|
+
shape.each do |k, v|
|
57
|
+
unless @values[k].class == v
|
58
|
+
msg = "Validation Error: Invalid value for #{k}, should be a #{v}"
|
59
|
+
raise Obvious::TypeError.new msg
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
validations.each { |method| send method }
|
64
|
+
|
65
|
+
freeze
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_hash
|
69
|
+
{}.tap {|h| @values.each { |k, v| h[k] = v } }
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
@@ -6,13 +6,19 @@ class FsPlug
|
|
6
6
|
@filename = filename
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
10
|
-
# open the file
|
9
|
+
def load_data
|
11
10
|
contents = File.read @filename
|
11
|
+
JSON.parse contents, :symbolize_names => true
|
12
|
+
end
|
12
13
|
|
14
|
+
def save_data input
|
15
|
+
json_data = JSON.pretty_generate input
|
16
|
+
File.open(@filename, 'w') {|f| f.write(json_data) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def save input
|
13
20
|
data = []
|
14
|
-
|
15
|
-
query = JSON.parse contents, :symbolize_names => true
|
21
|
+
query = load_data
|
16
22
|
|
17
23
|
new_element = true if input[:id] == -1 # by convention set new element flag if id == -1
|
18
24
|
|
@@ -27,51 +33,37 @@ class FsPlug
|
|
27
33
|
end
|
28
34
|
|
29
35
|
# add data to the list if it's a new element
|
30
|
-
|
31
|
-
|
36
|
+
if new_element
|
37
|
+
input[:id] = max_id + 1
|
38
|
+
data << input
|
39
|
+
end
|
32
40
|
|
33
|
-
|
34
|
-
json_data = JSON.pretty_generate data
|
35
|
-
File.open(@filename, 'w') {|f| f.write(json_data) }
|
41
|
+
save_data data
|
36
42
|
|
37
43
|
# return the transformed data
|
38
44
|
input
|
39
45
|
end
|
40
46
|
|
41
47
|
def list
|
42
|
-
|
43
|
-
contents = File.read @filename
|
44
|
-
|
45
|
-
# parse the json list
|
46
|
-
data = JSON.parse contents, :symbolize_names => true
|
47
|
-
|
48
|
-
# return the transformed data
|
49
|
-
data
|
48
|
+
load_data
|
50
49
|
end
|
51
50
|
|
52
51
|
def get input
|
53
|
-
# open the file
|
54
|
-
contents = File.read @filename
|
55
|
-
|
56
52
|
data = []
|
57
|
-
|
58
|
-
query = JSON.parse contents, :symbolize_names => true
|
53
|
+
query = load_data
|
59
54
|
|
60
55
|
# transform the data if needed
|
61
56
|
query.each do |h|
|
62
57
|
return h if h[:id] == input[:id]
|
63
58
|
end
|
64
59
|
|
65
|
-
|
60
|
+
raise Exception.new 'no object found'
|
66
61
|
end
|
67
62
|
|
68
63
|
def remove input
|
69
|
-
# open the file
|
70
|
-
contents = File.read @filename
|
71
|
-
|
72
64
|
data = []
|
73
65
|
# parse the json list
|
74
|
-
query =
|
66
|
+
query = load_data
|
75
67
|
|
76
68
|
# transform the data if needed
|
77
69
|
query.each do |h|
|
@@ -80,15 +72,28 @@ class FsPlug
|
|
80
72
|
end
|
81
73
|
end
|
82
74
|
|
83
|
-
|
84
|
-
json_data = JSON.pretty_generate data
|
85
|
-
File.open(@filename, 'w') {|f| f.write(json_data) }
|
75
|
+
save_data data
|
86
76
|
|
87
77
|
# return true on success
|
88
78
|
true
|
89
79
|
end
|
90
80
|
|
81
|
+
def find input
|
82
|
+
data = []
|
83
|
+
query = load_data
|
84
|
+
|
85
|
+
key = input.keys[0]
|
86
|
+
|
87
|
+
query.each do |h|
|
88
|
+
if input[key] == h[key]
|
89
|
+
return h
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
raise Exception.new 'no object found'
|
94
|
+
end
|
91
95
|
end
|
92
96
|
|
93
97
|
|
94
98
|
|
99
|
+
|
@@ -1,5 +1,10 @@
|
|
1
1
|
require 'moped'
|
2
2
|
|
3
|
+
# To get the mongo system to work you need to have a counters collection. The
|
4
|
+
# code here will increment the seq field which we use to set the id. If you are
|
5
|
+
# having problems, you probably need to create the collection and add entries
|
6
|
+
# for each other collection you want to have an id sequence.
|
7
|
+
|
3
8
|
class MongoPlug
|
4
9
|
def initialize collection
|
5
10
|
@collection = collection
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'aws/s3'
|
3
|
+
|
4
|
+
class S3Plug
|
5
|
+
include AWS::S3
|
6
|
+
|
7
|
+
def initialize input
|
8
|
+
@filename = input[:filename]
|
9
|
+
@bucket = input[:bucket]
|
10
|
+
|
11
|
+
# you can test locally with the fake-s3 gem: https://github.com/jubos/fake-s3
|
12
|
+
# or just swap out for the filesystem plug locally
|
13
|
+
s3_key = ENV['S3_KEY'] || '123'
|
14
|
+
s3_secret = ENV['S3_SECRET'] || 'abc'
|
15
|
+
s3_server = ENV['S3_SERVER'] || '0.0.0.0'
|
16
|
+
s3_port = ENV['S3_PORT'] || '10001'
|
17
|
+
|
18
|
+
AWS::S3::Base.establish_connection!(:access_key_id => s3_key,
|
19
|
+
:secret_access_key => s3_secret,
|
20
|
+
:server => s3_server,
|
21
|
+
:port => s3_port)
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_data
|
25
|
+
contents = S3Object.value @filename, @bucket
|
26
|
+
JSON.parse contents, :symbolize_names => true
|
27
|
+
end
|
28
|
+
|
29
|
+
def save_data data
|
30
|
+
json_data = JSON.pretty_generate data
|
31
|
+
S3Object.store @filename, json_data, @bucket
|
32
|
+
end
|
33
|
+
|
34
|
+
def save input
|
35
|
+
data = []
|
36
|
+
result = load_data
|
37
|
+
new_element = true if input[:id] == -1 # by convention set new element flag if id == -1
|
38
|
+
|
39
|
+
max_id = -1
|
40
|
+
# transform the data if needed
|
41
|
+
result.each do |h|
|
42
|
+
if input[:id] == h[:id]
|
43
|
+
h = input
|
44
|
+
end
|
45
|
+
max_id = h[:id] if h[:id] > max_id
|
46
|
+
data << h
|
47
|
+
end
|
48
|
+
|
49
|
+
# add data to the list if it's a new element
|
50
|
+
if new_element
|
51
|
+
input[:id] = max_id + 1
|
52
|
+
data << input
|
53
|
+
end
|
54
|
+
|
55
|
+
save_data data
|
56
|
+
|
57
|
+
# return the transformed data
|
58
|
+
input
|
59
|
+
end
|
60
|
+
|
61
|
+
def list
|
62
|
+
load_data
|
63
|
+
end
|
64
|
+
|
65
|
+
def get input
|
66
|
+
data = []
|
67
|
+
result = load_data
|
68
|
+
|
69
|
+
# transform the data if needed
|
70
|
+
result.each do |h|
|
71
|
+
return h if h[:id] == input[:id]
|
72
|
+
end
|
73
|
+
|
74
|
+
{}
|
75
|
+
end
|
76
|
+
|
77
|
+
def remove input
|
78
|
+
data = []
|
79
|
+
result = load_data
|
80
|
+
|
81
|
+
# transform the data if needed
|
82
|
+
result.each do |h|
|
83
|
+
unless h[:id] == input[:id]
|
84
|
+
data << h
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
save_data data
|
89
|
+
|
90
|
+
# return true on success
|
91
|
+
true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
data/lib/obvious/version.rb
CHANGED
data/lib/obvious.rb
CHANGED
data/obvious.gemspec
CHANGED
Binary file
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require_relative '../lib/obvious/contract'
|
3
|
+
|
4
|
+
|
5
|
+
describe Hash do
|
6
|
+
describe '#has_shape?' do
|
7
|
+
it 'should return true for a valid shape' do
|
8
|
+
{ id: 1 }.has_shape?(id: Fixnum).should be true
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should return false for an invalid shape' do
|
12
|
+
{ id: 1 }.has_shape?(id: String).should be false
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should retrn the invalid field if return_field flag is set' do
|
16
|
+
{ id: 1 }.has_shape?({id: String}, true).should eq [false, :id]
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should allow for nil values to be returned' do
|
20
|
+
{ id: nil }.has_shape?({id: String}).should be true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class TestContract < Contract
|
26
|
+
contract_for :test, {
|
27
|
+
input: { id: Fixnum },
|
28
|
+
output: { id: Fixnum, value: String }
|
29
|
+
}
|
30
|
+
|
31
|
+
def test input
|
32
|
+
{ id: 1, value: 'this is a test' }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe Contract do
|
37
|
+
|
38
|
+
describe "#call_method" do
|
39
|
+
it 'should return the correct output for valid input and output shapes' do
|
40
|
+
tc = TestContract.new
|
41
|
+
result = tc.test id: 1
|
42
|
+
result.should eq id: 1, value: 'this is a test'
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should raise a contract input error with bad input' do
|
46
|
+
tc = TestContract.new
|
47
|
+
expect { tc.test Hash.new }.to raise_error ContractInputError
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should raise a DataNotFound error if {} is returned' do
|
51
|
+
tc = TestContract.new
|
52
|
+
tc.should_receive(:test_alias).and_return({})
|
53
|
+
expect { tc.test id: 1 }.to raise_error DataNotFoundError
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should raise a contract output error if nil is returned' do
|
57
|
+
tc = TestContract.new
|
58
|
+
tc.should_receive(:test_alias).and_return(nil)
|
59
|
+
expect { tc.test id: 1 }.to raise_error ContractOutputError
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
data/spec/entity_spec.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require_relative '../lib/obvious/entity'
|
2
|
+
|
3
|
+
class Thing < Obvious::Entity
|
4
|
+
value :id, Fixnum
|
5
|
+
value :name, String
|
6
|
+
end
|
7
|
+
|
8
|
+
class Thing2 < Obvious::Entity
|
9
|
+
value :foo , String
|
10
|
+
|
11
|
+
validation :something, Proc.new {
|
12
|
+
if foo != "hello world"
|
13
|
+
msg = "Validation Error: Invalid value for foo, should be 'hello world'"
|
14
|
+
raise Obvious::ValidationError.new msg
|
15
|
+
end
|
16
|
+
}
|
17
|
+
|
18
|
+
def modify_foo
|
19
|
+
@values[:foo] = 100
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
class Thing3 < Obvious::Entity
|
25
|
+
value :foo , String
|
26
|
+
|
27
|
+
validation :something, Proc.new {
|
28
|
+
@values[:foo] = 12
|
29
|
+
}
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
# To test the entity, we are going to use classes that inherit from it instead
|
34
|
+
# of poking at it directly. In this case, I think that makes the most sense.
|
35
|
+
describe Thing do
|
36
|
+
it 'should create a valid object with valid input' do
|
37
|
+
input = { name: 'Thing', id: 1 }
|
38
|
+
t = Thing.new input
|
39
|
+
t.name.should eq 'Thing'
|
40
|
+
t.id.should eq 1
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should raise an error with invalid input types' do
|
44
|
+
input = { name: nil, id: nil }
|
45
|
+
expect { Thing.new input }.to raise_error Obvious::TypeError
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should raise an error with extra fields in input' do
|
49
|
+
input = { name: 'Thing', id: 1, extra: 'should explode' }
|
50
|
+
expect { Thing.new input }.to raise_error Obvious::ShapeError
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should raise an error when a method tries to modify a value' do
|
54
|
+
t = Thing2.new foo: 'hello world'
|
55
|
+
expect { t.modify_foo }.to raise_error RuntimeError
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#to_hash' do
|
59
|
+
it 'should return a hash representation of the object' do
|
60
|
+
input = { name: 'Thing', id: 1 }
|
61
|
+
t = Thing.new input
|
62
|
+
t.to_hash.should eq input
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'validation' do
|
67
|
+
it 'should raise a validation error on a failed validation' do
|
68
|
+
expect { Thing2.new foo: 'not valid I promise!' }.to raise_error Obvious::ValidationError
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should raise an error when trying to modify an object in a validation' do
|
72
|
+
expect { Thing3.new foo: 'hello world' }.to raise_error RuntimeError
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative '../../lib/generators/descriptor'
|
2
|
+
|
3
|
+
require File.expand_path('spec/spec_helper')
|
4
|
+
|
5
|
+
module Obvious
|
6
|
+
module Generators
|
7
|
+
describe Descriptor do
|
8
|
+
subject {Descriptor.new(yaml_file)}
|
9
|
+
|
10
|
+
describe "#to_file" do
|
11
|
+
|
12
|
+
context "when the descriptor is empty" do
|
13
|
+
let( :yaml_file ) { {} }
|
14
|
+
|
15
|
+
it "should raise a meaningful error" do
|
16
|
+
expect {subject.to_file}.to raise_error(InvalidDescriptorError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
["Action", "Code", "Description"].each do |section|
|
21
|
+
context "when the '#{section}' section is omitted" do
|
22
|
+
let( :yaml_file ) {
|
23
|
+
{"Action" => "Jackson", "Description" => "This is something"}.delete(section)
|
24
|
+
}
|
25
|
+
|
26
|
+
it "should raise a meaningful error" do
|
27
|
+
expect {subject.to_file}.to raise_error(InvalidDescriptorError)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: obvious
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,19 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
13
|
-
dependencies:
|
12
|
+
date: 2013-03-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70341046505020 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70341046505020
|
14
25
|
description: A set of tools to build apps using the Obvious Architecture
|
15
26
|
email:
|
16
27
|
- brianknapp@gmail.com
|
@@ -30,12 +41,19 @@ files:
|
|
30
41
|
- lib/generators/helpers/application.rb
|
31
42
|
- lib/obvious.rb
|
32
43
|
- lib/obvious/contract.rb
|
44
|
+
- lib/obvious/entity.rb
|
33
45
|
- lib/obvious/files/Rakefile
|
34
46
|
- lib/obvious/files/external/fs_plug.rb
|
35
47
|
- lib/obvious/files/external/mongo_plug.rb
|
36
48
|
- lib/obvious/files/external/mysql_plug.rb
|
49
|
+
- lib/obvious/files/external/s3_plug.rb
|
37
50
|
- lib/obvious/version.rb
|
38
51
|
- obvious.gemspec
|
52
|
+
- spec/.spec_helper.rb.swp
|
53
|
+
- spec/contract_spec.rb
|
54
|
+
- spec/entity_spec.rb
|
55
|
+
- spec/generators/descriptor_spec.rb
|
56
|
+
- spec/spec_helper.rb
|
39
57
|
homepage: http://obvious.retromocha.com/
|
40
58
|
licenses: []
|
41
59
|
post_install_message:
|
@@ -60,5 +78,10 @@ rubygems_version: 1.8.17
|
|
60
78
|
signing_key:
|
61
79
|
specification_version: 3
|
62
80
|
summary: Isn't it Obvious?
|
63
|
-
test_files:
|
81
|
+
test_files:
|
82
|
+
- spec/.spec_helper.rb.swp
|
83
|
+
- spec/contract_spec.rb
|
84
|
+
- spec/entity_spec.rb
|
85
|
+
- spec/generators/descriptor_spec.rb
|
86
|
+
- spec/spec_helper.rb
|
64
87
|
has_rdoc:
|