id 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,18 +1,23 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- id (0.0.5)
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.11)
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.1)
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.6'
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
@@ -1,3 +1,6 @@
1
1
  require 'active_support/inflector'
2
2
  require 'active_support/core_ext/string/inflections'
3
+ require 'active_support/core_ext/hash/except'
4
+ require 'optional'
5
+ require 'money'
3
6
  require_relative 'id/all'
@@ -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
 
@@ -3,11 +3,15 @@ module Id
3
3
  attr_reader :data
4
4
 
5
5
  def initialize(data)
6
- @data = Hashifier.new(data).hashify
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
- builder_class.field f
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
@@ -1,5 +1,7 @@
1
+ require_relative 'type_casts'
1
2
  require_relative 'field'
2
3
  require_relative 'association'
3
4
  require_relative 'has_one'
4
5
  require_relative 'has_many'
5
6
  require_relative 'builder'
7
+ require_relative 'compound_field'
@@ -32,7 +32,7 @@ module Id
32
32
 
33
33
  module FieldBuilder
34
34
 
35
- def field(f)
35
+ def define_setter(f)
36
36
  define_method f do |value|
37
37
  set(f, value)
38
38
  end
@@ -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
@@ -8,23 +8,75 @@ module Id
8
8
  @options = options
9
9
  end
10
10
 
11
- def define
11
+ def define_getter
12
12
  field = self
13
13
  model.send :define_method, name do
14
- data.fetch(field.key) { field.default or raise MissingAttributeError }
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, nil)
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
@@ -2,7 +2,7 @@ module Id
2
2
  module Model
3
3
  class HasMany < Association
4
4
 
5
- def define
5
+ def define_getter
6
6
  field = self
7
7
  model.send :define_method, name do
8
8
  memoize field.name do
@@ -2,11 +2,26 @@ module Id
2
2
  module Model
3
3
  class HasOne < Association
4
4
 
5
- def define
5
+ def define_getter
6
6
  field = self
7
7
  model.send :define_method, name do
8
8
  memoize field.name do
9
- field.type.new(data.fetch(field.key) { raise MissingAttributeError })
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
@@ -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.6
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:
@@ -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
-