domain_model 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in model.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # DomainModel
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'model'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install model
18
+
19
+ ## Tests
20
+
21
+ Run the tests:
22
+
23
+ rake test
24
+
25
+ ## Usage
26
+
27
+ TODO: Write usage instructions here
28
+
29
+ ## Contributing
30
+
31
+ 1. Fork it
32
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
33
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
34
+ 4. Push to the branch (`git push origin my-new-feature`)
35
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task :default => :test
4
+ task :spec => :test
5
+
6
+ task :test do
7
+ sh("bundle exec rspec spec")
8
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'domain_model/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "domain_model"
8
+ spec.version = DomainModel::VERSION
9
+ spec.authors = ["Rafer Hazen"]
10
+ spec.email = ["rafer@ralua.com"]
11
+ spec.summary = %q{Minimal framework for definition of type-aware domain models}
12
+ spec.description = spec.summary
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec", "~> 2.14"
24
+ end
@@ -0,0 +1,322 @@
1
+ module DomainModel
2
+ def self.included(base)
3
+ base.extend(ClassMethods)
4
+ end
5
+
6
+ def initialize(attributes={})
7
+ self.class.fields.select(&:collection?).each do |field|
8
+ send("#{field.name}=", [])
9
+ end
10
+
11
+ attributes.each { |k,v | send("#{k}=", v) }
12
+ end
13
+
14
+ def errors
15
+ errors = ModelErrors.new
16
+
17
+ self.class.fields.each do |field|
18
+ errors.add(field.name, field.errors(self.send(field.name)))
19
+ end
20
+
21
+ self.class.validations.each { |v| v.execute(self, errors) }
22
+
23
+ errors
24
+ end
25
+
26
+ def valid?
27
+ errors.empty?
28
+ end
29
+
30
+ def ==(other)
31
+ other.is_a?(self.class) && attributes == other.attributes
32
+ end
33
+
34
+ def inspect
35
+ "#<#{self.class} " + attributes.map { |n, v| "#{n}: #{v.inspect}" }.join(", ") + ">"
36
+ end
37
+
38
+ def attributes
39
+ attributes = {}
40
+ self.class.fields.map(&:name).each do |name|
41
+ attributes[name] = send(name)
42
+ end
43
+ attributes
44
+ end
45
+
46
+ def to_primitive
47
+ Serializer.serialize(self)
48
+ end
49
+
50
+ module ClassMethods
51
+ def validate(*args, &block)
52
+ @validations ||= []
53
+ validations << Validation.new(*args, &block)
54
+ end
55
+
56
+ def field(*args)
57
+ fields << (field = Field.new(*args))
58
+ attr_accessor(field.name)
59
+ end
60
+
61
+ def fields
62
+ @fields ||= []
63
+ end
64
+
65
+ def validations
66
+ @validations ||= []
67
+ end
68
+
69
+ def from_primitive(primitive)
70
+ Deserializer.deserialize(self, primitive)
71
+ end
72
+ end
73
+
74
+ class Serializer
75
+ def self.serialize(object)
76
+ new.serialize(object)
77
+ end
78
+
79
+ def serialize(object)
80
+ case object
81
+ when DomainModel
82
+ serialize(object.attributes)
83
+ when Hash
84
+ object.each {|k,v| object[k] = serialize(v) }
85
+ when Array
86
+ object.map { |o| serialize(o) }
87
+ else
88
+ object
89
+ end
90
+ end
91
+ end
92
+
93
+ class Deserializer
94
+ def self.deserialize(type, primitive)
95
+ new.deserialize(type, primitive)
96
+ end
97
+
98
+ def deserialize(type, primitive)
99
+ case
100
+ when type <= DomainModel
101
+ primitive.each do |k, v|
102
+ field = type.fields.find { |f| f.name.to_s == k.to_s }
103
+
104
+ next unless field && field.monotype
105
+
106
+ if field.collection?
107
+ primitive[k] = v.map { |e| deserialize(field.monotype, e) }
108
+ else
109
+ primitive[k] = deserialize(field.monotype, v)
110
+ end
111
+ end
112
+
113
+ type.new(primitive)
114
+ else
115
+ primitive
116
+ end
117
+ end
118
+ end
119
+
120
+ class Field
121
+ attr_reader :name, :types
122
+
123
+ def initialize(name, options = {})
124
+ @name = name
125
+ @required = options.fetch(:required, false)
126
+ @collection = options.fetch(:collection, false)
127
+ @validate = options.fetch(:validate, false)
128
+
129
+ raw_type = options.fetch(:type, BasicObject)
130
+ @types = raw_type.is_a?(Module) ? [raw_type] : raw_type
131
+
132
+ if required? and collection?
133
+ raise ArgumentError, "fields cannot be both :collection and :required"
134
+ end
135
+ end
136
+
137
+ def errors(value)
138
+ Validator.errors(self, value)
139
+ end
140
+
141
+ def monotype
142
+ types.first if types.count == 1
143
+ end
144
+
145
+ def required?
146
+ !!@required
147
+ end
148
+
149
+ def collection?
150
+ !!@collection
151
+ end
152
+
153
+ def validate?
154
+ !!@validate
155
+ end
156
+ end
157
+
158
+ class ModelErrors
159
+ def initialize
160
+ @hash = Hash.new
161
+ end
162
+
163
+ def add(field_name, error)
164
+ @hash[field_name] ||= []
165
+ @hash[field_name] += Array(error)
166
+ end
167
+
168
+ def [](field_name)
169
+ @hash[field_name] || []
170
+
171
+ end
172
+
173
+ def empty?
174
+ @hash.values.flatten.empty?
175
+ end
176
+ end
177
+
178
+ class FieldErrors
179
+ def initialize(model_errors, field)
180
+ @model_errors, @field = model_errors, field
181
+ end
182
+
183
+ def add(error)
184
+ @model_errors.add(@field.name, error)
185
+ end
186
+
187
+ def empty?
188
+ @model_errors[@field.name].empty?
189
+ end
190
+ end
191
+
192
+ class Validation
193
+ def initialize(*args, &block)
194
+ @field_name = args[0] if args[0].is_a?(Symbol)
195
+ @options = args[0] if args[0].is_a?(Hash)
196
+ @options = args[1] if args[1].is_a?(Hash)
197
+ @options = {} if @options.nil?
198
+
199
+ @block = block
200
+ end
201
+
202
+ def execute(model, errors)
203
+ if global?
204
+ if always? or errors.empty?
205
+ model.instance_exec(errors, &@block)
206
+ end
207
+ else
208
+ field = model.class.fields.find { |f| f.name == @field_name}
209
+ raise("No field called #{@field_name}") if field.nil?
210
+
211
+ field_errors = FieldErrors.new(errors, field)
212
+
213
+ if always? or field_errors.empty?
214
+ model.instance_exec(field_errors, &@block)
215
+ end
216
+ end
217
+ end
218
+
219
+ def global?
220
+ @field_name.nil?
221
+ end
222
+
223
+ def always?
224
+ @options.fetch(:always, global?)
225
+ end
226
+ end
227
+
228
+ class Validator
229
+ def self.errors(field, value)
230
+ validator = field.collection? ? Collection : Scalar
231
+ validator.new(field, value).errors
232
+ end
233
+
234
+ private
235
+
236
+ attr_reader :field
237
+
238
+ def types
239
+ field.types
240
+ end
241
+
242
+ class Collection < Validator
243
+ def initialize(field, values)
244
+ @field, @values = field, values
245
+ end
246
+
247
+ def errors
248
+ case
249
+ when (not enumerable?)
250
+ ["was declared as a collection and is not enumerable"]
251
+ when type_mismatch?
252
+ ["contains a value that is not an instance of #{types.map(&:inspect).join(' or ')}"]
253
+ when transitively_invalid?
254
+ ["is invalid"]
255
+ else
256
+ []
257
+ end
258
+ end
259
+
260
+ private
261
+
262
+ attr_reader :values
263
+
264
+ def enumerable?
265
+ values.is_a?(Enumerable)
266
+ end
267
+
268
+ def type_mismatch?
269
+ values.any? do |value|
270
+ field.types.none? { |t| value.is_a?(t) }
271
+ end
272
+ end
273
+
274
+ def transitively_invalid?
275
+ field.validate? and values.any? { |v| not v.valid? }
276
+ end
277
+ end
278
+
279
+ class Scalar < Validator
280
+ def initialize(field, value)
281
+ @field, @value = field, value
282
+ end
283
+
284
+ def errors
285
+ case
286
+ when legitimately_empty?
287
+ []
288
+ when (value.nil? and field.required?)
289
+ ["cannot be nil"]
290
+ when type_mismatch?
291
+ ["is not an instance of #{type_string} (was #{value.class.inspect})"]
292
+ when transitively_invalid?
293
+ ["is invalid"]
294
+ else
295
+ []
296
+ end
297
+ end
298
+
299
+ private
300
+
301
+ attr_reader :value
302
+
303
+ def type_mismatch?
304
+ types.none? { |t| value.is_a?(t) }
305
+ end
306
+
307
+ def type_string
308
+ types.map(&:inspect).join(' or ')
309
+ end
310
+
311
+ def legitimately_empty?
312
+ value.nil? and not field.required?
313
+ end
314
+
315
+ def transitively_invalid?
316
+ field.validate? and not value.valid?
317
+ end
318
+ end
319
+ end
320
+ end
321
+
322
+ require "domain_model/version"
@@ -0,0 +1,3 @@
1
+ module DomainModel
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,453 @@
1
+ require "spec_helper"
2
+
3
+ describe DomainModel do
4
+ describe ".field" do
5
+ it "creates a getter and setter for the field" do
6
+ define { field :name }
7
+
8
+ client = Client.new
9
+ client.name = "Rafer"
10
+
11
+ expect(client.name).to eq("Rafer")
12
+ end
13
+
14
+ it "defaults :collection fields to an empty array" do
15
+ define { field :name, :collection => true }
16
+
17
+ client = Client.new
18
+ expect(client.name).to eq([])
19
+ end
20
+
21
+ it "does not allow the :required attribute with :collection" do
22
+ required_collection = lambda do
23
+ define { field :name, :collection => true, :required => true }
24
+ end
25
+
26
+ expect(&required_collection).to raise_error(ArgumentError, /fields cannot be both :collection and :required/ )
27
+ end
28
+ end
29
+
30
+ describe ".validate" do
31
+ it "runs added validations each time #errors is called" do
32
+ run_count = 0
33
+
34
+ define do
35
+ validate { run_count += 1 }
36
+ end
37
+
38
+ client = Client.new
39
+
40
+ expect { client.errors }.to change { run_count }.to(1)
41
+ expect { client.errors }.to change { run_count }.to(2)
42
+ end
43
+
44
+ describe "with no field name" do
45
+ it "is passed the errors object" do
46
+ define do
47
+ validate { |e| e.add(:field, "ERROR") }
48
+ end
49
+
50
+ client = Client.new
51
+ expect(client.errors[:field] ).to include("ERROR")
52
+ end
53
+
54
+ it "is executed after the built in field validations" do
55
+ define do
56
+ field :field, :required => true
57
+ validate { |e| e.add(:field, "There were #{e[:field].size} errors") }
58
+ end
59
+
60
+ client = Client.new
61
+ expect(client.errors[:field]).to include("There were 1 errors")
62
+ end
63
+
64
+ it "is not executed if there are any errors on the model with :always => false" do
65
+ define do
66
+ field :field, :required => true
67
+ validate(:always => false) { |e| e.add(:field, "never happen") }
68
+ end
69
+
70
+ client = Client.new
71
+ expect(client.errors[:field]).to eq(["cannot be nil"])
72
+ end
73
+
74
+ it "is executed irrespetive of other errors with :always => true" do
75
+ define do
76
+ field :field, :required => true
77
+ validate(:always => true) { |e| e.add(:field, "should happen") }
78
+ end
79
+
80
+ client = Client.new
81
+ expect(client.errors[:field]).to eq(["cannot be nil", "should happen"])
82
+ end
83
+
84
+ it "defaults :always to true" do
85
+ define do
86
+ field :field, :required => true
87
+ validate { |e| e.add(:field, "should happen") }
88
+ end
89
+
90
+ client = Client.new
91
+ expect(client.errors[:field]).to eq(["cannot be nil", "should happen"])
92
+ end
93
+ end
94
+
95
+ describe "with a field name" do
96
+ it "is passed an errors object for that field" do
97
+ define do
98
+ field :field
99
+ validate(:field) { |e| e.add("is not great")}
100
+ end
101
+
102
+ client = Client.new
103
+ expect(client.errors[:field]).to include("is not great")
104
+ end
105
+
106
+ it "is not executed if there are already errors on the specified field with :always => false" do
107
+ define do
108
+ field :field, :required => true
109
+ validate(:field, :always => false) { |e| e.add("never happen") }
110
+ end
111
+
112
+ client = Client.new
113
+ expect(client.errors[:field]).to eq(["cannot be nil"])
114
+ end
115
+
116
+ it "is executed irrespective of field errors with :always => true" do
117
+ define do
118
+ field :field, :required => true
119
+ validate(:field, :always => true) { |e| e.add("should happen") }
120
+ end
121
+
122
+ client = Client.new
123
+ expect(client.errors[:field]).to eq(["cannot be nil", "should happen"])
124
+ end
125
+
126
+ it "defaults :always to false" do
127
+ define do
128
+ field :field, :required => true
129
+ validate(:field) { |e| e.add("never happen") }
130
+ end
131
+
132
+ client = Client.new
133
+ expect(client.errors[:field]).to eq(["cannot be nil"])
134
+ end
135
+ end
136
+ end
137
+
138
+ describe ".new" do
139
+ before do
140
+ define do
141
+ field :name
142
+ field :children, :collection => true
143
+ end
144
+ end
145
+
146
+ it "accepts fields" do
147
+ client = Client.new(:name => "Rafer")
148
+ expect(client.name).to eq("Rafer")
149
+ end
150
+
151
+ it "accepts fields with string keys" do
152
+ client = Client.new("name" => "Rafer")
153
+ expect(client.name).to eq("Rafer")
154
+ end
155
+
156
+ it "accepts collection fields" do
157
+ client = Client.new(:children => ["child"])
158
+ expect(client.children).to eq(["child"])
159
+ end
160
+
161
+ it "accepts collection fields with string keys" do
162
+ client = Client.new("children" => ["child"])
163
+ expect(client.children).to eq(["child"])
164
+ end
165
+
166
+ it "defaults collection fields to an empty array" do
167
+ client = Client.new
168
+ expect(client.children).to eq([])
169
+ end
170
+
171
+ it "raises an exception for unrecognized parmeters" do
172
+ expect { Client.new(:wrong => "") }.to raise_error(NoMethodError)
173
+ end
174
+ end
175
+
176
+ describe ".errors" do
177
+ it "is empty when no fields or validations have been defined" do
178
+ define { }
179
+ expect(Client.new.errors).to be_empty
180
+ end
181
+
182
+ it "includes errors for incorrect types" do
183
+ define { field :field, :type => String }
184
+ client = Client.new(:field => :wrong)
185
+
186
+ expect(client.errors[:field]).to include("is not an instance of String (was Symbol)")
187
+ end
188
+
189
+ it "doesn't include errors for correct types" do
190
+ define { field :field, :type => String }
191
+ client = Client.new(:field => "right")
192
+
193
+ expect(client.errors[:field]).to eq([])
194
+ end
195
+
196
+ it "doesn't include errors for correct subtypes" do
197
+ define { field :field, :type => Numeric }
198
+ client = Client.new(:field => 1)
199
+
200
+ expect(client.errors[:field]).to eq([])
201
+ end
202
+
203
+ it "includes an error for required fields that are nil" do
204
+ define { field :field, :required => true }
205
+ client = Client.new(:field => nil)
206
+
207
+ expect(client.errors[:field]).to include("cannot be nil")
208
+ end
209
+
210
+ it "doesn't include errors for non-required, typed fields" do
211
+ define { field :field, :type => String }
212
+
213
+ expect(Client.new.errors[:field]).to eq([])
214
+ end
215
+
216
+ it "includes errors for incorrect types (when multiple are specified)" do
217
+ define { field :field, :type => [String, Symbol] }
218
+ client = Client.new(:field => 1)
219
+
220
+ expect(client.errors[:field]).to include("is not an instance of String or Symbol (was Fixnum)")
221
+ end
222
+
223
+ it "includes no errors for correct types (when multiple types are specified)" do
224
+ define { field :field, :type => [String, Symbol] }
225
+
226
+ expect(Client.new(:field => "right").errors[:field]).to eq([])
227
+ expect(Client.new(:field => :right).errors[:field]).to eq([])
228
+ end
229
+
230
+ it "includes only the 'empty' error for fields that are required and typed, with a nil value" do
231
+ define { field :field, :type => String, :required => true }
232
+ client = Client.new(:field => nil)
233
+
234
+ expect(client.errors[:field]).to include("cannot be nil")
235
+ end
236
+
237
+ it "includes errors for invalid collaborators (when :validate is specified)" do
238
+ define { field :field, :validate => true }
239
+ client = Client.new(:field => double(:valid? => false))
240
+
241
+ expect(client.errors[:field]).to include("is invalid")
242
+ end
243
+
244
+ it "doesn't includes errors for valid collaborators (when :validate is specified)" do
245
+ define { field :field, :validate => true }
246
+ client = Client.new(:field => double(:valid? => true))
247
+
248
+ expect(client.errors[:field]).to eq([])
249
+ end
250
+
251
+ it "doesn't validate collaborators if the type is incorrect" do
252
+ define { field :field, :type => String, :validate => true }
253
+
254
+ collaborator = double
255
+ client = Client.new(:field => collaborator)
256
+
257
+ expect(collaborator).not_to receive(:valid?)
258
+
259
+ client.errors
260
+ end
261
+
262
+ describe "collections" do
263
+ it "includes an error if the value is not enumerable" do
264
+ define { field :field, :type => String, :collection => true }
265
+ client = Client.new(:field => 1)
266
+
267
+ expect(client.errors[:field]).to include("was declared as a collection and is not enumerable")
268
+ end
269
+
270
+ it "doesn't include errors if there are no values" do
271
+ define { field :field, :type => String, :collection => true }
272
+ client = Client.new
273
+
274
+ expect(client.errors[:field]).to eq([])
275
+ end
276
+
277
+ it "includes errors for incorrect types" do
278
+ define { field :field, :type => String, :collection => true }
279
+ client = Client.new(:field => [:wrong])
280
+
281
+ expect(client.errors[:field]).to include("contains a value that is not an instance of String")
282
+ end
283
+
284
+ it "includes errors for incorrect types (multiple types)" do
285
+ define { field :field, :type => [String, Symbol], :collection => true }
286
+ client = Client.new(:field => [1])
287
+
288
+ expect(client.errors[:field]).to include("contains a value that is not an instance of String or Symbol")
289
+ end
290
+
291
+ it "doesn't include errors for correctly typed values" do
292
+ define { field :field, :type => String, :collection => true }
293
+ client = Client.new(:field => ["right", "right"])
294
+
295
+ expect(client.errors[:field]).to eq([])
296
+ end
297
+
298
+ it "doesn't include errors for correctly typed values (multiple types)" do
299
+ define { field :field, :type => [String, Symbol], :collection => true }
300
+ client = Client.new(:field => ["right", :right])
301
+
302
+ expect(client.errors[:field]).to eq([])
303
+ end
304
+
305
+ it "includes errors for invalid collaborators (when :validate is specified)" do
306
+ define { field :field, :validate => true, :collection => true }
307
+ client = Client.new(:field => [double(:valid? => false)])
308
+
309
+ expect(client.errors[:field]).to include("is invalid")
310
+ end
311
+
312
+ it "doesn't include errors for valid collaborators (when :validate is specified)" do
313
+ define { field :field, :validate => true, :collection => true }
314
+ client = Client.new(:field => [double(:valid? => true)])
315
+
316
+ expect(client.errors[:field]).to eq([])
317
+ end
318
+
319
+ it "doesn't validate collaborators if the type is incorrect" do
320
+ define { field :field, :type => String, :validate => true, :collection => true }
321
+
322
+ collaborator = double
323
+ client = Client.new(:field => [collaborator])
324
+
325
+ expect(collaborator).not_to receive(:valid?)
326
+
327
+ client.errors
328
+ end
329
+ end
330
+ end
331
+
332
+ describe "#==" do
333
+ before { define { field :field } }
334
+
335
+ it "is true if all fields are equal" do
336
+ client_1 = Client.new(:field => "A")
337
+ client_2 = Client.new(:field => "A")
338
+
339
+ expect(client_1).to eq(client_2)
340
+ end
341
+
342
+ it "is false if any field is different" do
343
+ client_1 = Client.new(:field => "A")
344
+ client_2 = Client.new(:field => "B")
345
+
346
+ expect(client_1).not_to eq(client_2)
347
+ end
348
+
349
+ it "is false if the object is of another type" do
350
+ expect(Client.new).not_to eq(double)
351
+ end
352
+ end
353
+
354
+ describe "#inspect" do
355
+ before { define { field :field } }
356
+
357
+ it "shows the name and value of all fields" do
358
+ client = Client.new(:field => "VALUE")
359
+ expect(client.inspect).to match(/field: "VALUE"/)
360
+ end
361
+ end
362
+
363
+ describe "#valid?" do
364
+ it "is true if there are no errors" do
365
+ define { field :field }
366
+ expect(Client.new.valid?).to be(true)
367
+ end
368
+
369
+ it "is false if there are errors" do
370
+ define { field :field, :required => true }
371
+ expect(Client.new.valid?).to be(false)
372
+ end
373
+ end
374
+
375
+ describe "#attributes" do
376
+ it "returns the models as a hash" do
377
+ define { field :field }
378
+ client = Client.new(:field => "VALUE")
379
+
380
+ expect(client.attributes).to eq({:field => "VALUE"})
381
+ end
382
+ end
383
+
384
+ describe "#to_primitive" do
385
+ class Child
386
+ include DomainModel
387
+ field :field
388
+ end
389
+
390
+ it "returns a hash of the field's attribtues" do
391
+ define { field :field }
392
+ client = Client.new(:field => "VALUE")
393
+
394
+ expect(client.to_primitive).to eq({:field => "VALUE"})
395
+ end
396
+
397
+ it "converts referenced models" do
398
+ define do
399
+ field :child, :type => Child
400
+ end
401
+ client = Client.new(:child => Child.new(:field => "VALUE"))
402
+
403
+ expect(client.to_primitive).to eq(:child => {:field => "VALUE"})
404
+ end
405
+
406
+ it "converts collection models" do
407
+ define { field :children, :collection => true }
408
+ client = Client.new(:children => [Child.new(:field => "VALUE")])
409
+
410
+ expect(client.to_primitive).to eq(:children => [ :field => "VALUE" ])
411
+ end
412
+ end
413
+
414
+ describe ".from_primitive" do
415
+ class Child
416
+ include DomainModel
417
+ field :field
418
+ end
419
+
420
+ it "parses simple fields " do
421
+ define { field :field }
422
+
423
+ client = Client.from_primitive({:field => "VALUE"})
424
+ expect(client.field).to eq("VALUE")
425
+ end
426
+
427
+ it "parses referenced DomainModels" do
428
+ define { field :child, :type => Child }
429
+
430
+ client = Client.from_primitive(:child => {:field => "VALUE"})
431
+ child = client.child
432
+
433
+ expect(child).to eq(Child.new(:field => "VALUE"))
434
+ end
435
+
436
+ it "parses collection DomainModels" do
437
+ define { field :children, :type => Child, :collection => true }
438
+
439
+ client = Client.from_primitive(:children => [{:field => "VALUE"}] )
440
+ children = client.children
441
+
442
+ expect(children).to eq([Child.new(:field => "VALUE")])
443
+ end
444
+ end
445
+
446
+ def define(&block)
447
+ client = Class.new do
448
+ include DomainModel
449
+ instance_eval(&block)
450
+ end
451
+ stub_const("Client", client)
452
+ end
453
+ end
@@ -0,0 +1,2 @@
1
+ require "bundler/setup"
2
+ require "domain_model"
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: domain_model
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rafer Hazen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-02-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.14'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.14'
62
+ description: Minimal framework for definition of type-aware domain models
63
+ email:
64
+ - rafer@ralua.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - LICENSE.txt
72
+ - README.md
73
+ - Rakefile
74
+ - domain_model.gemspec
75
+ - lib/domain_model.rb
76
+ - lib/domain_model/version.rb
77
+ - spec/model_spec.rb
78
+ - spec/spec_helper.rb
79
+ homepage: ''
80
+ licenses:
81
+ - MIT
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 1.8.23
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: Minimal framework for definition of type-aware domain models
104
+ test_files:
105
+ - spec/model_spec.rb
106
+ - spec/spec_helper.rb