id 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +8 -3
- data/README.md +55 -1
- data/id.gemspec +3 -1
- data/lib/id.rb +3 -0
- data/lib/id/hashifier.rb +5 -0
- data/lib/id/model.rb +13 -7
- data/lib/id/model/all.rb +2 -0
- data/lib/id/model/builder.rb +1 -1
- data/lib/id/model/compound_field.rb +23 -0
- data/lib/id/model/field.rb +55 -3
- data/lib/id/model/has_many.rb +1 -1
- data/lib/id/model/has_one.rb +17 -2
- data/lib/id/model/type_casts.rb +96 -0
- data/spec/lib/id/model/has_one_spec.rb +29 -0
- data/spec/lib/id/model/type_casts_spec.rb +44 -0
- data/spec/lib/id/model_spec.rb +160 -0
- metadata +39 -3
- data/spec/lib/id_spec.rb +0 -92
data/Gemfile.lock
CHANGED
@@ -1,18 +1,23 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
id (0.0.
|
4
|
+
id (0.0.6)
|
5
5
|
activesupport
|
6
|
+
money
|
7
|
+
optional
|
6
8
|
|
7
9
|
GEM
|
8
10
|
remote: https://rubygems.org/
|
9
11
|
specs:
|
10
|
-
activesupport (3.2.
|
12
|
+
activesupport (3.2.12)
|
11
13
|
i18n (~> 0.6)
|
12
14
|
multi_json (~> 1.0)
|
13
15
|
diff-lcs (1.2.1)
|
14
|
-
i18n (0.6.
|
16
|
+
i18n (0.6.4)
|
17
|
+
money (5.1.1)
|
18
|
+
i18n (~> 0.6.0)
|
15
19
|
multi_json (1.7.2)
|
20
|
+
optional (0.0.3)
|
16
21
|
rspec (2.13.0)
|
17
22
|
rspec-core (~> 2.13.0)
|
18
23
|
rspec-expectations (~> 2.13.0)
|
data/README.md
CHANGED
@@ -3,4 +3,58 @@
|
|
3
3
|
|
4
4
|
JSON is a great way to transfer data between systems, and it's easy to parse into a Ruby hash. But sometimes it's nice to have actual methods to call when you want to get attributes from your data, rather than coupling your entire codebase to the hash representation by littering it with calls to `fetch` or `[]`. The same goes for BSON documents stored in Mongo.
|
5
5
|
|
6
|
-
That's where `id` (as in Freud) comes in. You define your model classes using syntax that should look pretty familiar if you've used any popular Ruby ORMs - but `id` is not an ORM. Model objects defined with `id` have a constructor that accepts a hash, and you define the values of this hash that are made readable as fields - but that hash can come from any source.
|
6
|
+
That's where `id` (as in Freud) comes in. You define your model classes using syntax that should look pretty familiar if you've used any popular Ruby ORMs - but `id` is not an ORM. Model objects defined with `id` have a constructor that accepts a hash, and you define the values of this hash that are made readable as fields - but that hash can come from any source.
|
7
|
+
|
8
|
+
#### Defining a model
|
9
|
+
|
10
|
+
Defining a model looks like this:
|
11
|
+
|
12
|
+
class MyModel
|
13
|
+
include Id::Model
|
14
|
+
|
15
|
+
field :foo
|
16
|
+
field :bar, default: 42
|
17
|
+
field :baz, key: 'barry'
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
my_model = MyModel.new(foo: 7, barry: 'hello')
|
22
|
+
my_model.foo # => 7
|
23
|
+
my_model.bar # => 42
|
24
|
+
my_model.baz # => 'hello'
|
25
|
+
|
26
|
+
As you can see, you can specify default values as well as key aliases.
|
27
|
+
|
28
|
+
#### Associations
|
29
|
+
|
30
|
+
You can also specify has_one or has_many "associations" - what would be nested subdocuments in MongoDB for example - like this:
|
31
|
+
|
32
|
+
class Zoo
|
33
|
+
include Id::Model
|
34
|
+
|
35
|
+
has_many :lions
|
36
|
+
has_many :zebras
|
37
|
+
has_one :zookeeper, type: Person
|
38
|
+
end
|
39
|
+
|
40
|
+
zoo = Zoo.new(lions: [{name: 'Hetty'}],
|
41
|
+
zebras: [{name: 'Lisa'}],
|
42
|
+
zookeeper: {name: 'Russell' d})
|
43
|
+
|
44
|
+
zoo.lions.first.class # => Lion
|
45
|
+
zoo.lions.first.name # => "Hetty"
|
46
|
+
zoo.zookeeper.class # => Person
|
47
|
+
zoo.zookeeper.name # => "Russell"
|
48
|
+
|
49
|
+
Types are inferred from the association name unless one is specified.
|
50
|
+
|
51
|
+
#### Designed for immutability
|
52
|
+
|
53
|
+
`id` models provide accessor methods, but no mutator methods, because they are designed for immutability. How do immutable models work? When you need to change some field of a model object, a new copy of the object is created with the field changed as required. This is handled for you by `id`'s `set` method:
|
54
|
+
|
55
|
+
person = Person.new(name: 'Russell', job: 'programmer')
|
56
|
+
person.set(name: 'Radek') # => returns a new Person whose name is Radek and whose job is 'programmer'
|
57
|
+
|
58
|
+
You can even set fields on nested models in this way:
|
59
|
+
|
60
|
+
person.hat.set(color: 'red') # => returns a new person object with a new hat object with its color set to red
|
data/id.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'id'
|
3
|
-
s.version = '0.0.
|
3
|
+
s.version = '0.0.7'
|
4
4
|
s.date = '2013-03-28'
|
5
5
|
s.summary = "Simple models based on hashes"
|
6
6
|
s.description = "Developed at On The Beach Ltd. Contact russell.dunphy@onthebeach.co.uk"
|
@@ -11,6 +11,8 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.require_paths = ["lib"]
|
12
12
|
s.homepage = 'http://github.com/onthebeach/id'
|
13
13
|
|
14
|
+
s.add_dependency "optional"
|
15
|
+
s.add_dependency "money"
|
14
16
|
s.add_dependency "activesupport"
|
15
17
|
|
16
18
|
s.add_development_dependency "rspec"
|
data/lib/id.rb
CHANGED
data/lib/id/hashifier.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
module Id
|
2
2
|
class Hashifier
|
3
3
|
|
4
|
+
def self.hashify(data)
|
5
|
+
new(data).hashify
|
6
|
+
end
|
7
|
+
|
4
8
|
def initialize(data)
|
5
9
|
@data = data
|
6
10
|
end
|
@@ -15,6 +19,7 @@ module Id
|
|
15
19
|
case v
|
16
20
|
when Id::Model then v.data
|
17
21
|
when Array then v.first.is_a?(Id::Model) ? v.map(&:data) : v
|
22
|
+
when Hash then Hashifier.hashify(v)
|
18
23
|
else v end
|
19
24
|
end
|
20
25
|
|
data/lib/id/model.rb
CHANGED
@@ -3,11 +3,15 @@ module Id
|
|
3
3
|
attr_reader :data
|
4
4
|
|
5
5
|
def initialize(data)
|
6
|
-
@data = Hashifier.
|
6
|
+
@data = Hashifier.hashify(data)
|
7
7
|
end
|
8
8
|
|
9
9
|
def set(values)
|
10
|
-
self.class.new(data.merge(values))
|
10
|
+
self.class.new(data.merge(Hashifier.hashify(values)))
|
11
|
+
end
|
12
|
+
|
13
|
+
def unset(*keys)
|
14
|
+
self.class.new(data.except(*keys.map(&:to_s)))
|
11
15
|
end
|
12
16
|
|
13
17
|
private
|
@@ -21,19 +25,21 @@ module Id
|
|
21
25
|
end
|
22
26
|
|
23
27
|
module Descriptor
|
28
|
+
|
24
29
|
def field(f, options={})
|
25
|
-
Field.new(self, f, options).define
|
26
|
-
builder_class.field f
|
30
|
+
(options[:optional] ? FieldOption : Field).new(self, f, options).define
|
27
31
|
end
|
28
32
|
|
29
33
|
def has_one(f, options={})
|
30
|
-
HasOne.new(self, f, options).define
|
31
|
-
builder_class.field f
|
34
|
+
(options[:optional] ? HasOneOption : HasOne).new(self, f, options).define
|
32
35
|
end
|
33
36
|
|
34
37
|
def has_many(f, options={})
|
35
38
|
HasMany.new(self, f, options).define
|
36
|
-
|
39
|
+
end
|
40
|
+
|
41
|
+
def compound_field(f, fields, options={})
|
42
|
+
CompoundField.new(self, f, fields, options).define
|
37
43
|
end
|
38
44
|
|
39
45
|
def builder
|
data/lib/id/model/all.rb
CHANGED
data/lib/id/model/builder.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Id
|
2
|
+
module Model
|
3
|
+
class CompoundField < Association
|
4
|
+
|
5
|
+
def initialize(model, name, fields, options)
|
6
|
+
@fields = fields
|
7
|
+
super(model, name, options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def define_getter
|
11
|
+
field = self
|
12
|
+
model.send :define_method, name do
|
13
|
+
memoize field.name do
|
14
|
+
compound = Hash[field.fields.map { |k,v| [k.to_s, send(v) { raise MissingAttributeError, k.to_s }]}]
|
15
|
+
field.type.new(compound)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :fields
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/id/model/field.rb
CHANGED
@@ -8,23 +8,75 @@ module Id
|
|
8
8
|
@options = options
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
11
|
+
def define_getter
|
12
12
|
field = self
|
13
13
|
model.send :define_method, name do
|
14
|
-
|
14
|
+
memoize field.name do
|
15
|
+
field.cast data.fetch(field.key, &field.default_value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def define_setter
|
21
|
+
model.send(:builder_class).define_setter name
|
22
|
+
end
|
23
|
+
|
24
|
+
def define_is_present
|
25
|
+
field = self
|
26
|
+
model.send :define_method, "#{name}?" do
|
27
|
+
data.has_key?(field.key) && !data.fetch(field.key).nil?
|
15
28
|
end
|
16
29
|
end
|
17
30
|
|
31
|
+
def define
|
32
|
+
define_getter
|
33
|
+
define_setter
|
34
|
+
define_is_present
|
35
|
+
end
|
36
|
+
|
37
|
+
def cast(value)
|
38
|
+
TypeCasts.cast(options.fetch(:type, false), value)
|
39
|
+
end
|
40
|
+
|
18
41
|
def key
|
19
42
|
options.fetch(:key, name).to_s
|
20
43
|
end
|
21
44
|
|
45
|
+
def default_value
|
46
|
+
proc do
|
47
|
+
if default? then default
|
48
|
+
elsif !optional? then raise MissingAttributeError, key end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def default?
|
53
|
+
options.has_key?(:default)
|
54
|
+
end
|
55
|
+
|
22
56
|
def default
|
23
|
-
options.fetch(:default
|
57
|
+
options.fetch(:default)
|
58
|
+
end
|
59
|
+
|
60
|
+
def optional?
|
61
|
+
options.fetch(:optional, false)
|
24
62
|
end
|
25
63
|
|
26
64
|
attr_reader :model, :name, :options
|
27
65
|
end
|
28
66
|
|
67
|
+
class FieldOption < Field
|
68
|
+
def define_getter
|
69
|
+
field = self
|
70
|
+
model.send :define_method, name do
|
71
|
+
memoize field.name do
|
72
|
+
if d = data.fetch(field.key, &field.default_value)
|
73
|
+
Some[field.cast d]
|
74
|
+
else
|
75
|
+
None
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
29
81
|
end
|
30
82
|
end
|
data/lib/id/model/has_many.rb
CHANGED
data/lib/id/model/has_one.rb
CHANGED
@@ -2,11 +2,26 @@ module Id
|
|
2
2
|
module Model
|
3
3
|
class HasOne < Association
|
4
4
|
|
5
|
-
def
|
5
|
+
def define_getter
|
6
6
|
field = self
|
7
7
|
model.send :define_method, name do
|
8
8
|
memoize field.name do
|
9
|
-
|
9
|
+
child = data.fetch(field.key) { raise MissingAttributeError, field.key }
|
10
|
+
field.type.new(child) unless child.nil?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class HasOneOption < Association
|
18
|
+
|
19
|
+
def define_getter
|
20
|
+
field = self
|
21
|
+
model.send :define_method, name do
|
22
|
+
memoize field.name do
|
23
|
+
child = data.fetch(field.key, nil)
|
24
|
+
child.nil? ? None : Some[field.type.new(child)]
|
10
25
|
end
|
11
26
|
end
|
12
27
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Id
|
2
|
+
module Model
|
3
|
+
module TypeCasts
|
4
|
+
|
5
|
+
def self.cast(type, value)
|
6
|
+
casts.fetch(type, Identity).new(value).cast
|
7
|
+
end
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
def self.register(cast)
|
12
|
+
casts[cast.type] = cast
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def self.casts
|
18
|
+
@casts ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
class Cast
|
22
|
+
|
23
|
+
def initialize(value)
|
24
|
+
@value = value
|
25
|
+
end
|
26
|
+
|
27
|
+
def cast
|
28
|
+
value.is_a?(type) ? value : conversion
|
29
|
+
end
|
30
|
+
|
31
|
+
def type
|
32
|
+
self.class.type
|
33
|
+
end
|
34
|
+
|
35
|
+
def conversion
|
36
|
+
raise NotImplementedError
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def self.type
|
42
|
+
raise NotImplementedError
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :value
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
class Identity < Cast
|
50
|
+
def cast
|
51
|
+
value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Date < Cast
|
56
|
+
|
57
|
+
def self.type
|
58
|
+
::Date
|
59
|
+
end
|
60
|
+
|
61
|
+
def conversion
|
62
|
+
::Date.parse value
|
63
|
+
end
|
64
|
+
|
65
|
+
TypeCasts.register(self)
|
66
|
+
end
|
67
|
+
|
68
|
+
class Time < Cast
|
69
|
+
|
70
|
+
def self.type
|
71
|
+
::Time
|
72
|
+
end
|
73
|
+
|
74
|
+
def conversion
|
75
|
+
::Time.parse value
|
76
|
+
end
|
77
|
+
|
78
|
+
TypeCasts.register(self)
|
79
|
+
end
|
80
|
+
|
81
|
+
class Money < Cast
|
82
|
+
|
83
|
+
def self.type
|
84
|
+
::Money
|
85
|
+
end
|
86
|
+
|
87
|
+
def conversion
|
88
|
+
::Money.new value.to_i
|
89
|
+
end
|
90
|
+
|
91
|
+
TypeCasts.register(self)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Cat
|
4
|
+
include Id::Model
|
5
|
+
end
|
6
|
+
|
7
|
+
class OptionalModel
|
8
|
+
include Id::Model
|
9
|
+
|
10
|
+
has_one :cat, optional: true
|
11
|
+
end
|
12
|
+
|
13
|
+
module Id
|
14
|
+
module Model
|
15
|
+
|
16
|
+
describe HasOneOption do
|
17
|
+
|
18
|
+
it 'returns a none if the key is missing' do
|
19
|
+
OptionalModel.new({}).cat.should be_none
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'returns a some if the key is there' do
|
23
|
+
OptionalModel.new(cat: {}).cat.should be_some(Cat)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Id
|
4
|
+
module Model
|
5
|
+
module TypeCasts
|
6
|
+
|
7
|
+
describe Cast do
|
8
|
+
it 'requires a type' do
|
9
|
+
expect { Cast.new(1).type }.to raise_error NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'requires a conversion' do
|
13
|
+
expect { Cast.new(1).conversion }.to raise_error NotImplementedError
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
describe Date do
|
19
|
+
it 'casts strings to dates' do
|
20
|
+
Date.new("06-06-1983").cast.should eq ::Date.new(1983, 6, 6)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe Time do
|
25
|
+
it 'casts strings to Times' do
|
26
|
+
Time.new("06-06-1983").cast.should eq ::Time.parse("06-06-1983")
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'does nothing if the value is already of the correct type' do
|
30
|
+
time = ::Time.now
|
31
|
+
Time.new(time).cast.should eq time
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe Money do
|
36
|
+
it 'ints or strings to Money' do
|
37
|
+
Money.new("500").cast.should eq ::Money.new(5_00)
|
38
|
+
Money.new(500).cast.should eq ::Money.new(5_00)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/spec/lib/id/model_spec.rb
CHANGED
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class NestedModel
|
4
|
+
include Id::Model
|
5
|
+
field :yak
|
6
|
+
end
|
7
|
+
|
8
|
+
class CompboundElementModel
|
9
|
+
include Id::Model
|
10
|
+
field :plugh
|
11
|
+
field :thud
|
12
|
+
end
|
13
|
+
|
14
|
+
class TestModel
|
15
|
+
include Id::Model
|
16
|
+
|
17
|
+
field :foo
|
18
|
+
field :bar, key: 'baz'
|
19
|
+
field :qux, optional: true
|
20
|
+
field :quux, default: false
|
21
|
+
field :date_of_birth, optional: true, type: Date
|
22
|
+
field :empty_date, optional: true, type: Date
|
23
|
+
field :christmas, default: Date.new(2014,12,25), type: Date
|
24
|
+
field :quxx, optional: true
|
25
|
+
|
26
|
+
compound_field :corge, {plugh: 'foo', thud: 'quux'}, type: CompboundElementModel
|
27
|
+
|
28
|
+
has_one :aliased_model, type: NestedModel
|
29
|
+
has_one :nested_model, key: 'aliased_model'
|
30
|
+
has_one :extra_nested_model
|
31
|
+
has_one :test_model
|
32
|
+
has_many :nested_models
|
33
|
+
|
34
|
+
class ExtraNestedModel
|
35
|
+
include Id::Model
|
36
|
+
field :cats
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe Id::Model do
|
41
|
+
let (:model) { TestModel.new(foo: 3,
|
42
|
+
baz: 6,
|
43
|
+
quxx: 8,
|
44
|
+
test_model: {},
|
45
|
+
date_of_birth: '06-06-1983',
|
46
|
+
aliased_model: { 'yak' => 11},
|
47
|
+
nested_models: [{ 'yak' => 11}, { yak: 14 }],
|
48
|
+
extra_nested_model: { cats: "MIAOW" }) }
|
49
|
+
|
50
|
+
|
51
|
+
describe ".new" do
|
52
|
+
it 'converts any passed id models to their hash representations' do
|
53
|
+
new_model = TestModel.new(test_model: model)
|
54
|
+
new_model.test_model.data.should eq model.data
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe ".field" do
|
59
|
+
it 'defines an accessor on the model' do
|
60
|
+
model.foo.should eq 3
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'allows key aliases' do
|
64
|
+
model.bar.should eq 6
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'allows default values' do
|
68
|
+
model.quux.should be_false
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "optional flag" do
|
72
|
+
context 'when field is not in hash' do
|
73
|
+
it 'is None' do
|
74
|
+
expect(model.qux).to eq None
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
context 'when field is in hash' do
|
79
|
+
it 'is Some' do
|
80
|
+
expect(model.quxx).to eq Some[8]
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "typecast option" do
|
87
|
+
it 'typecasts to the provided type if a cast exists' do
|
88
|
+
model.date_of_birth.should be_some Date
|
89
|
+
model.christmas.should be_a Date
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should work for empty optional fields' do
|
93
|
+
model.empty_date.should be_none
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
describe ".compound_field" do
|
100
|
+
it 'defines an accessor on the model' do
|
101
|
+
model.corge.should be_a CompboundElementModel
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'deals with default values' do
|
105
|
+
model.corge.thud.should be_false
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe ".has_one" do
|
110
|
+
it "allows nested models" do
|
111
|
+
model.aliased_model.should be_a NestedModel
|
112
|
+
end
|
113
|
+
it "allows nested models" do
|
114
|
+
model.nested_model.should be_a NestedModel
|
115
|
+
model.nested_model.yak.should eq 11
|
116
|
+
end
|
117
|
+
it "allows associations to be nested within the class" do
|
118
|
+
model.extra_nested_model.should be_a TestModel::ExtraNestedModel
|
119
|
+
model.extra_nested_model.cats.should eq 'MIAOW'
|
120
|
+
end
|
121
|
+
it "allows recursively defined models" do
|
122
|
+
model.test_model.should be_a TestModel
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe ".has_many" do
|
127
|
+
it 'creates an array of nested models' do
|
128
|
+
model.nested_models.should be_a Array
|
129
|
+
model.nested_models.first.should be_a NestedModel
|
130
|
+
model.nested_models.first.yak.should eq 11
|
131
|
+
model.nested_models.last.yak.should eq 14
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#set" do
|
136
|
+
it "creates a new model with the provided values changed" do
|
137
|
+
model.set(foo: 999).should be_a TestModel
|
138
|
+
model.set(foo: 999).foo.should eq 999
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "#unset" do
|
143
|
+
it 'returns a new basket minus the passed key' do
|
144
|
+
expect { model.set(foo: 999, bar: 555).unset(:foo, :bar).foo }.to raise_error Id::MissingAttributeError, "foo"
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'does not error if the key to be removed does not exist' do
|
148
|
+
expect { model.unset(:not_in_hash) }.to_not raise_error
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe "#fields are present methods" do
|
153
|
+
it 'allows you to check if fields are present' do
|
154
|
+
model = TestModel.new(foo: 1)
|
155
|
+
model.foo?.should be_true
|
156
|
+
model.bar?.should be_false
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: id
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -12,6 +12,38 @@ bindir: bin
|
|
12
12
|
cert_chain: []
|
13
13
|
date: 2013-03-28 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: optional
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: money
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
15
47
|
- !ruby/object:Gem::Dependency
|
16
48
|
name: activesupport
|
17
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -82,13 +114,16 @@ files:
|
|
82
114
|
- lib/id/model/all.rb
|
83
115
|
- lib/id/model/association.rb
|
84
116
|
- lib/id/model/builder.rb
|
117
|
+
- lib/id/model/compound_field.rb
|
85
118
|
- lib/id/model/field.rb
|
86
119
|
- lib/id/model/has_many.rb
|
87
120
|
- lib/id/model/has_one.rb
|
121
|
+
- lib/id/model/type_casts.rb
|
88
122
|
- spec/lib/id/model/association_spec.rb
|
89
123
|
- spec/lib/id/model/builder_spec.rb
|
124
|
+
- spec/lib/id/model/has_one_spec.rb
|
125
|
+
- spec/lib/id/model/type_casts_spec.rb
|
90
126
|
- spec/lib/id/model_spec.rb
|
91
|
-
- spec/lib/id_spec.rb
|
92
127
|
- spec/spec_helper.rb
|
93
128
|
homepage: http://github.com/onthebeach/id
|
94
129
|
licenses: []
|
@@ -117,7 +152,8 @@ summary: Simple models based on hashes
|
|
117
152
|
test_files:
|
118
153
|
- spec/lib/id/model/association_spec.rb
|
119
154
|
- spec/lib/id/model/builder_spec.rb
|
155
|
+
- spec/lib/id/model/has_one_spec.rb
|
156
|
+
- spec/lib/id/model/type_casts_spec.rb
|
120
157
|
- spec/lib/id/model_spec.rb
|
121
|
-
- spec/lib/id_spec.rb
|
122
158
|
- spec/spec_helper.rb
|
123
159
|
has_rdoc:
|
data/spec/lib/id_spec.rb
DELETED
@@ -1,92 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
class NestedModel
|
4
|
-
include Id::Model
|
5
|
-
field :yak
|
6
|
-
end
|
7
|
-
|
8
|
-
class TestModel
|
9
|
-
include Id::Model
|
10
|
-
|
11
|
-
field :foo
|
12
|
-
field :bar, key: 'baz'
|
13
|
-
field :quux, default: 'kwak'
|
14
|
-
|
15
|
-
has_one :aliased_model, type: NestedModel
|
16
|
-
has_one :nested_model, key: 'aliased_model'
|
17
|
-
has_one :extra_nested_model
|
18
|
-
has_one :test_model
|
19
|
-
has_many :nested_models
|
20
|
-
|
21
|
-
class ExtraNestedModel
|
22
|
-
include Id::Model
|
23
|
-
field :cats!
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
describe Id::Model do
|
28
|
-
let (:model) { TestModel.new(foo: 3,
|
29
|
-
baz: 6,
|
30
|
-
test_model: {},
|
31
|
-
aliased_model: { 'yak' => 11},
|
32
|
-
nested_models: [{ 'yak' => 11}, { yak: 14 }],
|
33
|
-
extra_nested_model: { cats!: "MIAOW" }) }
|
34
|
-
|
35
|
-
|
36
|
-
describe ".new" do
|
37
|
-
it 'converts any passed id models to their hash representations' do
|
38
|
-
new_model = TestModel.new(test_model: model)
|
39
|
-
new_model.test_model.data.should eq model.data
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
describe ".field" do
|
44
|
-
it 'defines an accessor on the model' do
|
45
|
-
model.foo.should eq 3
|
46
|
-
end
|
47
|
-
|
48
|
-
it 'allows key aliases' do
|
49
|
-
model.bar.should eq 6
|
50
|
-
end
|
51
|
-
|
52
|
-
it 'allows default values' do
|
53
|
-
model.quux.should eq 'kwak'
|
54
|
-
end
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
describe ".has_one" do
|
59
|
-
it "allows nested models" do
|
60
|
-
model.aliased_model.should be_a NestedModel
|
61
|
-
end
|
62
|
-
it "allows nested models" do
|
63
|
-
model.nested_model.should be_a NestedModel
|
64
|
-
model.nested_model.yak.should eq 11
|
65
|
-
end
|
66
|
-
it "allows associations to be nested within the class" do
|
67
|
-
model.extra_nested_model.should be_a TestModel::ExtraNestedModel
|
68
|
-
model.extra_nested_model.cats!.should eq 'MIAOW'
|
69
|
-
end
|
70
|
-
it "allows recursively defined models" do
|
71
|
-
model.test_model.should be_a TestModel
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
describe ".has_many" do
|
76
|
-
it 'creates an array of nested models' do
|
77
|
-
model.nested_models.should be_a Array
|
78
|
-
model.nested_models.first.should be_a NestedModel
|
79
|
-
model.nested_models.first.yak.should eq 11
|
80
|
-
model.nested_models.last.yak.should eq 14
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
describe "#set" do
|
85
|
-
it "creates a new model with the provided values changed" do
|
86
|
-
model.set(foo: 999).should be_a TestModel
|
87
|
-
model.set(foo: 999).foo.should eq 999
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
end
|
92
|
-
|