active_data 0.3.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rspec +0 -1
  4. data/.rvmrc +1 -1
  5. data/.travis.yml +13 -6
  6. data/Appraisals +7 -0
  7. data/Gemfile +1 -5
  8. data/Guardfile +68 -15
  9. data/README.md +144 -2
  10. data/active_data.gemspec +19 -11
  11. data/gemfiles/rails.4.0.gemfile +14 -0
  12. data/gemfiles/rails.4.1.gemfile +14 -0
  13. data/gemfiles/rails.4.2.gemfile +14 -0
  14. data/gemfiles/rails.5.0.gemfile +14 -0
  15. data/lib/active_data.rb +120 -3
  16. data/lib/active_data/active_record/associations.rb +50 -0
  17. data/lib/active_data/active_record/nested_attributes.rb +24 -0
  18. data/lib/active_data/config.rb +40 -0
  19. data/lib/active_data/errors.rb +93 -0
  20. data/lib/active_data/extensions.rb +33 -0
  21. data/lib/active_data/model.rb +16 -74
  22. data/lib/active_data/model/associations.rb +84 -15
  23. data/lib/active_data/model/associations/base.rb +79 -0
  24. data/lib/active_data/model/associations/collection/embedded.rb +12 -0
  25. data/lib/active_data/model/associations/collection/proxy.rb +32 -0
  26. data/lib/active_data/model/associations/collection/referenced.rb +26 -0
  27. data/lib/active_data/model/associations/embeds_many.rb +124 -18
  28. data/lib/active_data/model/associations/embeds_one.rb +90 -15
  29. data/lib/active_data/model/associations/nested_attributes.rb +180 -0
  30. data/lib/active_data/model/associations/references_many.rb +96 -0
  31. data/lib/active_data/model/associations/references_one.rb +83 -0
  32. data/lib/active_data/model/associations/reflections/base.rb +100 -0
  33. data/lib/active_data/model/associations/reflections/embeds_many.rb +25 -0
  34. data/lib/active_data/model/associations/reflections/embeds_one.rb +49 -0
  35. data/lib/active_data/model/associations/reflections/reference_reflection.rb +45 -0
  36. data/lib/active_data/model/associations/reflections/references_many.rb +28 -0
  37. data/lib/active_data/model/associations/reflections/references_one.rb +28 -0
  38. data/lib/active_data/model/associations/validations.rb +63 -0
  39. data/lib/active_data/model/attributes.rb +247 -0
  40. data/lib/active_data/model/attributes/attribute.rb +73 -0
  41. data/lib/active_data/model/attributes/base.rb +116 -0
  42. data/lib/active_data/model/attributes/collection.rb +17 -0
  43. data/lib/active_data/model/attributes/dictionary.rb +26 -0
  44. data/lib/active_data/model/attributes/localized.rb +42 -0
  45. data/lib/active_data/model/attributes/reference_many.rb +21 -0
  46. data/lib/active_data/model/attributes/reference_one.rb +42 -0
  47. data/lib/active_data/model/attributes/reflections/attribute.rb +55 -0
  48. data/lib/active_data/model/attributes/reflections/base.rb +62 -0
  49. data/lib/active_data/model/attributes/reflections/collection.rb +10 -0
  50. data/lib/active_data/model/attributes/reflections/dictionary.rb +13 -0
  51. data/lib/active_data/model/attributes/reflections/localized.rb +43 -0
  52. data/lib/active_data/model/attributes/reflections/reference_many.rb +10 -0
  53. data/lib/active_data/model/attributes/reflections/reference_one.rb +58 -0
  54. data/lib/active_data/model/attributes/reflections/represents.rb +55 -0
  55. data/lib/active_data/model/attributes/represents.rb +64 -0
  56. data/lib/active_data/model/callbacks.rb +71 -0
  57. data/lib/active_data/model/conventions.rb +35 -0
  58. data/lib/active_data/model/dirty.rb +77 -0
  59. data/lib/active_data/model/lifecycle.rb +307 -0
  60. data/lib/active_data/model/localization.rb +21 -0
  61. data/lib/active_data/model/persistence.rb +57 -0
  62. data/lib/active_data/model/primary.rb +51 -0
  63. data/lib/active_data/model/scopes.rb +77 -0
  64. data/lib/active_data/model/validations.rb +27 -0
  65. data/lib/active_data/model/validations/associated.rb +19 -0
  66. data/lib/active_data/model/validations/nested.rb +39 -0
  67. data/lib/active_data/railtie.rb +7 -0
  68. data/lib/active_data/version.rb +1 -1
  69. data/spec/lib/active_data/active_record/associations_spec.rb +149 -0
  70. data/spec/lib/active_data/active_record/nested_attributes_spec.rb +16 -0
  71. data/spec/lib/active_data/config_spec.rb +44 -0
  72. data/spec/lib/active_data/model/associations/embeds_many_spec.rb +362 -52
  73. data/spec/lib/active_data/model/associations/embeds_one_spec.rb +250 -31
  74. data/spec/lib/active_data/model/associations/nested_attributes_spec.rb +23 -0
  75. data/spec/lib/active_data/model/associations/references_many_spec.rb +196 -0
  76. data/spec/lib/active_data/model/associations/references_one_spec.rb +134 -0
  77. data/spec/lib/active_data/model/associations/reflections/embeds_many_spec.rb +144 -0
  78. data/spec/lib/active_data/model/associations/reflections/embeds_one_spec.rb +116 -0
  79. data/spec/lib/active_data/model/associations/reflections/references_many_spec.rb +255 -0
  80. data/spec/lib/active_data/model/associations/reflections/references_one_spec.rb +208 -0
  81. data/spec/lib/active_data/model/associations/validations_spec.rb +153 -0
  82. data/spec/lib/active_data/model/associations_spec.rb +189 -0
  83. data/spec/lib/active_data/model/attributes/attribute_spec.rb +144 -0
  84. data/spec/lib/active_data/model/attributes/base_spec.rb +82 -0
  85. data/spec/lib/active_data/model/attributes/collection_spec.rb +73 -0
  86. data/spec/lib/active_data/model/attributes/dictionary_spec.rb +93 -0
  87. data/spec/lib/active_data/model/attributes/localized_spec.rb +88 -33
  88. data/spec/lib/active_data/model/attributes/reflections/attribute_spec.rb +72 -0
  89. data/spec/lib/active_data/model/attributes/reflections/base_spec.rb +56 -0
  90. data/spec/lib/active_data/model/attributes/reflections/collection_spec.rb +37 -0
  91. data/spec/lib/active_data/model/attributes/reflections/dictionary_spec.rb +43 -0
  92. data/spec/lib/active_data/model/attributes/reflections/localized_spec.rb +37 -0
  93. data/spec/lib/active_data/model/attributes/reflections/represents_spec.rb +70 -0
  94. data/spec/lib/active_data/model/attributes/represents_spec.rb +153 -0
  95. data/spec/lib/active_data/model/attributes_spec.rb +243 -0
  96. data/spec/lib/active_data/model/callbacks_spec.rb +338 -0
  97. data/spec/lib/active_data/model/conventions_spec.rb +12 -0
  98. data/spec/lib/active_data/model/dirty_spec.rb +75 -0
  99. data/spec/lib/active_data/model/lifecycle_spec.rb +330 -0
  100. data/spec/lib/active_data/model/nested_attributes.rb +202 -0
  101. data/spec/lib/active_data/model/persistence_spec.rb +47 -0
  102. data/spec/lib/active_data/model/primary_spec.rb +84 -0
  103. data/spec/lib/active_data/model/scopes_spec.rb +88 -0
  104. data/spec/lib/active_data/model/typecasting_spec.rb +192 -0
  105. data/spec/lib/active_data/model/validations/associated_spec.rb +94 -0
  106. data/spec/lib/active_data/model/validations/nested_spec.rb +93 -0
  107. data/spec/lib/active_data/model/validations_spec.rb +31 -0
  108. data/spec/lib/active_data/model_spec.rb +1 -32
  109. data/spec/lib/active_data_spec.rb +12 -0
  110. data/spec/spec_helper.rb +39 -0
  111. data/spec/support/model_helpers.rb +10 -0
  112. metadata +246 -54
  113. data/gemfiles/Gemfile.rails-3 +0 -14
  114. data/lib/active_data/attributes/base.rb +0 -69
  115. data/lib/active_data/attributes/localized.rb +0 -42
  116. data/lib/active_data/model/associations/association.rb +0 -30
  117. data/lib/active_data/model/attributable.rb +0 -122
  118. data/lib/active_data/model/collectionizable.rb +0 -55
  119. data/lib/active_data/model/collectionizable/proxy.rb +0 -42
  120. data/lib/active_data/model/extensions.rb +0 -9
  121. data/lib/active_data/model/extensions/array.rb +0 -24
  122. data/lib/active_data/model/extensions/big_decimal.rb +0 -17
  123. data/lib/active_data/model/extensions/boolean.rb +0 -38
  124. data/lib/active_data/model/extensions/date.rb +0 -17
  125. data/lib/active_data/model/extensions/date_time.rb +0 -17
  126. data/lib/active_data/model/extensions/float.rb +0 -17
  127. data/lib/active_data/model/extensions/hash.rb +0 -22
  128. data/lib/active_data/model/extensions/integer.rb +0 -17
  129. data/lib/active_data/model/extensions/localized.rb +0 -22
  130. data/lib/active_data/model/extensions/object.rb +0 -17
  131. data/lib/active_data/model/extensions/string.rb +0 -17
  132. data/lib/active_data/model/extensions/time.rb +0 -17
  133. data/lib/active_data/model/localizable.rb +0 -31
  134. data/lib/active_data/model/nested_attributes.rb +0 -58
  135. data/lib/active_data/model/parameterizable.rb +0 -29
  136. data/lib/active_data/validations.rb +0 -7
  137. data/lib/active_data/validations/associated.rb +0 -17
  138. data/spec/lib/active_data/model/attributable_spec.rb +0 -191
  139. data/spec/lib/active_data/model/collectionizable_spec.rb +0 -60
  140. data/spec/lib/active_data/model/nested_attributes_spec.rb +0 -67
  141. data/spec/lib/active_data/model/type_cast_spec.rb +0 -116
  142. data/spec/lib/active_data/validations/associated_spec.rb +0 -88
@@ -0,0 +1,21 @@
1
+ module ActiveData
2
+ module Model
3
+ module Localization
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def localized *args, &block
8
+ add_attribute(ActiveData::Model::Attributes::Reflections::Localized, *args, &block)
9
+ end
10
+
11
+ def fallbacks locale
12
+ ::I18n.respond_to?(:fallbacks) ? ::I18n.fallbacks[locale] : [locale]
13
+ end
14
+
15
+ def locale
16
+ I18n.locale
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,57 @@
1
+ module ActiveData
2
+ module Model
3
+ module Persistence
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def instantiate data
8
+ data = data.stringify_keys
9
+ instance = allocate
10
+
11
+ instance.instance_variable_set(:@initial_attributes, data.slice(*attribute_names))
12
+ instance.send(:mark_persisted!)
13
+
14
+ instance
15
+ end
16
+
17
+ def instantiate_collection data
18
+ collection = Array.wrap(data).map { |attrs| instantiate attrs }
19
+ collection = scope(collection, true) if respond_to?(:scope)
20
+ collection
21
+ end
22
+ end
23
+
24
+ def persisted?
25
+ !!@persisted
26
+ end
27
+
28
+ def destroyed?
29
+ !!@destroyed
30
+ end
31
+
32
+ def marked_for_destruction?
33
+ @marked_for_destruction
34
+ end
35
+
36
+ def mark_for_destruction
37
+ @marked_for_destruction = true
38
+ end
39
+
40
+ def _destroy
41
+ marked_for_destruction?
42
+ end
43
+
44
+ private
45
+
46
+ def mark_persisted!
47
+ @persisted = true
48
+ @destroyed = false
49
+ end
50
+
51
+ def mark_destroyed!
52
+ @persisted = false
53
+ @destroyed = true
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,51 @@
1
+ module ActiveData
2
+ module Model
3
+ module Primary
4
+ extend ActiveSupport::Concern
5
+ DEFAULT_PRIMARY_ATTRIBUTE_OPTIONS = -> { {
6
+ type: ActiveData::UUID,
7
+ default: -> { ActiveData::UUID.random_create }
8
+ } }
9
+
10
+ included do
11
+ class_attribute :_primary_name, instance_writer: false
12
+ delegate :has_primary_attribute?, to: 'self.class'
13
+
14
+ prepend PrependMethods
15
+ alias_method :eql?, :==
16
+ end
17
+
18
+ module ClassMethods
19
+ def primary *args
20
+ options = args.extract_options!
21
+ self._primary_name = (args.first.presence || ActiveData.primary_attribute).to_s
22
+ unless has_attribute?(_primary_name)
23
+ options.merge!(type: args.second) if args.second
24
+ attribute _primary_name, options.presence || DEFAULT_PRIMARY_ATTRIBUTE_OPTIONS.call
25
+ end
26
+ alias_attribute :primary_attribute, _primary_name
27
+ end
28
+ alias_method :primary_attribute, :primary
29
+
30
+ def has_primary_attribute?
31
+ has_attribute? _primary_name
32
+ end
33
+
34
+ def primary_name
35
+ _primary_name
36
+ end
37
+ end
38
+
39
+ module PrependMethods
40
+ def == other
41
+ other.instance_of?(self.class) &&
42
+ has_primary_attribute? ?
43
+ primary_attribute ?
44
+ primary_attribute == other.primary_attribute :
45
+ object_id == other.object_id :
46
+ super(other)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,77 @@
1
+ module ActiveData
2
+ module Model
3
+ module Scopes
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :_scope_base
8
+ scopify
9
+ end
10
+
11
+ module ScopeProxy
12
+ extend ActiveSupport::Concern
13
+
14
+ def self.for(model)
15
+ klass = Class.new(model._scope_base) do
16
+ include ActiveData::Model::Scopes::ScopeProxy
17
+ end
18
+ klass.define_singleton_method(:_scope_model) { model }
19
+ model.const_set('ScopeProxy', klass)
20
+ end
21
+
22
+ included do
23
+ def initialize source = nil, trust = false
24
+ source ||= self.class.superclass.new
25
+
26
+ source.each do |entity|
27
+ raise AssociationTypeMismatch.new(self.class._scope_model, entity.class) unless entity.is_a?(self.class._scope_model)
28
+ end unless trust && source.is_a?(self.class)
29
+
30
+ super source
31
+ end
32
+ end
33
+
34
+ def respond_to_missing? method, _
35
+ super || self.class._scope_model.respond_to?(method)
36
+ end
37
+
38
+ def method_missing method, *args, &block
39
+ with_scope { self.class._scope_model.public_send(method, *args, &block) }
40
+ end
41
+
42
+ def with_scope
43
+ previous_scope, self.class._scope_model.current_scope = self.class._scope_model.current_scope, self
44
+ result = yield
45
+ self.class._scope_model.current_scope = previous_scope
46
+ result
47
+ end
48
+ end
49
+
50
+ module ClassMethods
51
+ def scopify scope_base = Array
52
+ self._scope_base = scope_base
53
+ end
54
+
55
+ def scope_class
56
+ @scope_class ||= ActiveData::Model::Scopes::ScopeProxy.for(self)
57
+ end
58
+
59
+ def scope *args
60
+ if args.empty?
61
+ current_scope
62
+ else
63
+ scope_class.new *args
64
+ end
65
+ end
66
+
67
+ def current_scope= value
68
+ @current_scope = value
69
+ end
70
+
71
+ def current_scope
72
+ @current_scope ||= scope_class.new
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,27 @@
1
+ module ActiveData
2
+ module Model
3
+ module Validations
4
+ extend ActiveSupport::Concern
5
+ include ActiveModel::Validations
6
+
7
+ included do
8
+ extend HelperMethods
9
+ include HelperMethods
10
+
11
+ alias_method :validate, :valid?
12
+ end
13
+
14
+ def validate! context = nil
15
+ valid?(context) || raise_validation_error
16
+ end
17
+
18
+ protected
19
+
20
+ def raise_validation_error
21
+ raise ActiveData::ValidationError.new(self)
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ Dir[File.dirname(__FILE__) + "/validations/*.rb"].each { |file| require file }
@@ -0,0 +1,19 @@
1
+ module ActiveData
2
+ module Model
3
+ module Validations
4
+ class AssociatedValidator < ActiveModel::EachValidator
5
+ def validate_each(record, attribute, value)
6
+ if Array.wrap(value).reject { |r| r.respond_to?(:valid?) && r.valid?(record.validation_context) }.any?
7
+ record.errors.add(attribute, :invalid, options.merge(value: value))
8
+ end
9
+ end
10
+ end
11
+
12
+ module HelperMethods
13
+ def validates_associated(*attr_names)
14
+ validates_with AssociatedValidator, _merge_attributes(attr_names)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ module ActiveData
2
+ module Model
3
+ module Validations
4
+ class NestedValidator < ActiveModel::EachValidator
5
+ def self.validate_nested(record, name, value)
6
+ if value.is_a?(Enumerable)
7
+ value.each.with_index do |object, i|
8
+ if yield(object)
9
+ object.errors.each do |key, message|
10
+ key = "#{name}.#{i}.#{key}"
11
+ record.errors[key] << message
12
+ record.errors[key].uniq!
13
+ end
14
+ end
15
+ end
16
+ elsif value && yield(value)
17
+ value.errors.each do |key, message|
18
+ key = "#{name}.#{key}"
19
+ record.errors[key] << message
20
+ record.errors[key].uniq!
21
+ end
22
+ end
23
+ end
24
+
25
+ def validate_each(record, attribute, value)
26
+ self.class.validate_nested(record, attribute, value) do |object|
27
+ object.invalid?
28
+ end
29
+ end
30
+ end
31
+
32
+ module HelperMethods
33
+ def validates_nested(*attr_names)
34
+ validates_with NestedValidator, _merge_attributes(attr_names)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ module ActiveData
2
+ class Railtie < Rails::Railtie
3
+ initializer 'active_data.logger', after: 'active_record.logger' do
4
+ ActiveSupport.on_load(:active_record) { ActiveData.logger ||= ActiveRecord::Base.logger }
5
+ end
6
+ end
7
+ end
@@ -1,3 +1,3 @@
1
1
  module ActiveData
2
- VERSION = "0.3.0"
2
+ VERSION = '1.0.0'
3
3
  end
@@ -0,0 +1,149 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveData::ActiveRecord::Associations do
4
+ before do
5
+ stub_model(:project) do
6
+ include ActiveData::Model::Lifecycle
7
+ include ActiveData::Model::Associations
8
+
9
+ attribute :title, String
10
+
11
+ validates :title, presence: true
12
+
13
+ embeds_one :author do
14
+ attribute :name, String
15
+
16
+ validates :name, presence: true
17
+ end
18
+ end
19
+
20
+ stub_model(:profile) do
21
+ include ActiveData::Model::Lifecycle
22
+
23
+ attribute :first_name, String
24
+ attribute :last_name, String
25
+ end
26
+
27
+ stub_class(:user, ActiveRecord::Base) do
28
+ embeds_many :projects
29
+ embeds_one :profile
30
+
31
+ validates :projects, associated: true
32
+ end
33
+ end
34
+
35
+ its(:projects) { should = [] }
36
+ its(:profile) { should = nil }
37
+
38
+ context 'new owner' do
39
+ subject(:user) { User.new }
40
+
41
+ describe '#projects' do
42
+ specify { expect { user.projects << Project.new }
43
+ .not_to change { user.read_attribute(:projects) } }
44
+ specify { expect { user.projects << Project.new(title: 'First') }
45
+ .not_to change { user.read_attribute(:projects) } }
46
+ specify { expect { user.projects << Project.new(title: 'First') }
47
+ .not_to change { user.projects.reload.count } }
48
+ specify do
49
+ user.projects << Project.new(title: 'First')
50
+ user.save
51
+ expect(user.reload.projects.first.title).to eq('First')
52
+ end
53
+ end
54
+
55
+ describe '#profile' do
56
+ specify { expect { user.profile = Profile.new(first_name: 'google.com') }
57
+ .not_to change { user.read_attribute(:profile) } }
58
+ specify { expect { user.profile = Profile.new(first_name: 'google.com') }
59
+ .to change { user.profile }.from(nil).to(an_instance_of(Profile)) }
60
+ specify do
61
+ user.profile = Profile.new(first_name: 'google.com')
62
+ user.save
63
+ expect(user.reload.profile.first_name).to eq('google.com')
64
+ end
65
+ end
66
+ end
67
+
68
+ context 'persisted owner' do
69
+ subject(:user) { User.create }
70
+
71
+ describe '#projects' do
72
+ specify { expect { user.projects << Project.new }
73
+ .not_to change { user.read_attribute(:projects) } }
74
+ specify { expect { user.projects << Project.new(title: 'First') }
75
+ .to change { user.projects.reload.count }.from(0).to(1) }
76
+ specify {
77
+ user.projects << Project.new(title: 'First')
78
+ user.save
79
+ expect(user.reload.projects.first.title).to eq('First') }
80
+
81
+ context do
82
+ let(:project) { Project.new(title: 'First') }
83
+ before { project.build_author(name: 'Author') }
84
+
85
+ specify { expect { user.projects << project }
86
+ .to change { user.attributes['projects'] }.from(nil)
87
+ .to([{title: 'First', author: {name: 'Author'}}].to_json) }
88
+ specify { expect { user.projects << project; user.save }
89
+ .to change { user.reload.attributes['projects'] }.from(nil)
90
+ .to([{title: 'First', author: {name: 'Author'}}].to_json) }
91
+ end
92
+ end
93
+
94
+ describe '#profile' do
95
+ specify { expect { user.profile = Profile.new(first_name: 'google.com') }
96
+ .to change { user.profile }.from(nil).to(an_instance_of(Profile)) }
97
+ specify {
98
+ user.profile = Profile.new(first_name: 'google.com')
99
+ user.save
100
+ expect(user.reload.profile.first_name).to eq('google.com') }
101
+ specify { expect { user.profile = Profile.new(first_name: 'google.com') }
102
+ .to change { user.attributes['profile'] }.from(nil)
103
+ .to({first_name: 'google.com', last_name: nil}.to_json) }
104
+ specify { expect { user.profile = Profile.new(first_name: 'google.com'); user.save }
105
+ .to change { user.reload.attributes['profile'] }.from(nil)
106
+ .to({first_name: 'google.com', last_name: nil}.to_json) }
107
+ end
108
+ end
109
+
110
+ context 'class determine errors' do
111
+ specify do
112
+ expect { stub_class(:book, ActiveRecord::Base) do
113
+ embeds_one :author, class_name: 'Borogoves'
114
+ end.reflect_on_association(:author).klass }.to raise_error NameError
115
+ end
116
+
117
+ specify do
118
+ expect { stub_class(:user, ActiveRecord::Base) do
119
+ embeds_many :projects, class_name: 'Borogoves' do
120
+ attribute :title
121
+ end
122
+ end.reflect_on_association(:projects).klass }.to raise_error NameError
123
+ end
124
+ end
125
+
126
+ context 'on the fly' do
127
+ before do
128
+ stub_class(:user, ActiveRecord::Base) do
129
+ embeds_many :projects do
130
+ attribute :title, String
131
+ end
132
+ embeds_one :profile, class_name: 'Profile' do
133
+ attribute :age, Integer
134
+ end
135
+ end
136
+ end
137
+
138
+ specify { expect(User.reflect_on_association(:projects).klass).to eq(User::Project) }
139
+ specify { expect(User.new.projects).to eq([]) }
140
+ specify { expect(User.new.tap { |u| u.projects.create(title: 'Project') }.projects).to be_a(ActiveData::Model::Associations::Collection::Embedded) }
141
+ specify { expect(User.new.tap { |u| u.projects.create(title: 'Project') }.read_attribute(:projects)).to eq([{title: 'Project'}].to_json) }
142
+
143
+ specify { expect(User.reflect_on_association(:profile).klass).to eq(User::Profile) }
144
+ specify { expect(User.reflect_on_association(:profile).klass).to be < Profile }
145
+ specify { expect(User.new.profile).to be_nil }
146
+ specify { expect(User.new.tap { |u| u.create_profile(first_name: 'Profile') }.profile).to be_a(User::Profile) }
147
+ specify { expect(User.new.tap { |u| u.create_profile(first_name: 'Profile') }.read_attribute(:profile)).to eq({first_name: 'Profile', last_name: nil, age: nil}.to_json) }
148
+ end
149
+ end