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,134 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe ActiveData::Model::Associations::ReferencesOne do
5
+ before do
6
+ stub_class(:author, ActiveRecord::Base) { }
7
+
8
+ stub_model(:book) do
9
+ include ActiveData::Model::Persistence
10
+ include ActiveData::Model::Associations
11
+
12
+ attribute :title, String
13
+ references_one :author
14
+ end
15
+ end
16
+
17
+ let(:author) { Author.create!(name: 'Johny') }
18
+ let(:other) { Author.create!(name: 'Other') }
19
+ let(:book) { Book.new }
20
+ let(:association) { book.association(:author) }
21
+
22
+ let(:existing_book) { Book.instantiate title: 'My Life', author_id: author.id }
23
+ let(:existing_association) { existing_book.association(:author) }
24
+
25
+ describe 'book#association' do
26
+ specify { expect(association).to be_a described_class }
27
+ specify { expect(association).to eq(book.association(:author)) }
28
+ end
29
+
30
+ describe '#target' do
31
+ specify { expect(association.target).to be_nil }
32
+ specify { expect(existing_association.target).to eq(existing_book.author) }
33
+ end
34
+
35
+ describe '#loaded?' do
36
+ let(:new_author) { Author.create(name: 'Morty') }
37
+
38
+ specify { expect(association.loaded?).to eq(false) }
39
+ specify { expect { association.target }.to change { association.loaded? }.to(true) }
40
+ specify { expect { association.replace(new_author) }.to change { association.loaded? }.to(true) }
41
+ specify { expect { association.replace(nil) }.to change { association.loaded? }.to(true) }
42
+ specify { expect { existing_association.replace(new_author) }.to change { existing_association.loaded? }.to(true) }
43
+ specify { expect { existing_association.replace(nil) }.to change { existing_association.loaded? }.to(true) }
44
+ end
45
+
46
+ describe '#reload' do
47
+ specify { expect(association.reload).to be_nil }
48
+
49
+ specify { expect(existing_association.reload).to be_a Author }
50
+ specify { expect(existing_association.reload).to be_persisted }
51
+
52
+ context do
53
+ before { existing_association.reader.name = "New" }
54
+ specify { expect { existing_association.reload }
55
+ .to change { existing_association.reader.name }
56
+ .from('New').to('Johny') }
57
+ end
58
+ end
59
+
60
+ describe '#reader' do
61
+ specify { expect(association.reader).to be_nil }
62
+
63
+ specify { expect(existing_association.reader).to be_a Author }
64
+ specify { expect(existing_association.reader).to be_persisted }
65
+ end
66
+
67
+ describe '#default' do
68
+ before { Book.references_one :author, default: ->(book) { author.id } }
69
+ let(:existing_book) { Book.instantiate title: 'My Life' }
70
+
71
+ specify { expect(association.target).to eq(author) }
72
+ specify { expect { association.replace(other) }.to change { association.target }.to(other) }
73
+ specify { expect { association.replace(nil) }.to change { association.target }.to be_nil }
74
+
75
+ specify { expect(existing_association.target).to be_nil }
76
+ specify { expect { existing_association.replace(other) }.to change { existing_association.target }.to(other) }
77
+ specify { expect { existing_association.replace(nil) }.not_to change { existing_association.target } }
78
+ end
79
+
80
+
81
+ describe '#writer' do
82
+ context 'new owner' do
83
+ let(:new_author) { Author.new(name: 'Morty') }
84
+
85
+ let(:book) do
86
+ Book.new.tap do |book|
87
+ book.send(:mark_persisted!)
88
+ end
89
+ end
90
+
91
+ specify { expect { association.writer(nil) }
92
+ .not_to change { book.author_id } }
93
+ specify { expect { association.writer(new_author) }
94
+ .to change { association.reader.name rescue nil }.from(nil).to('Morty') }
95
+ specify { expect { association.writer(new_author) }
96
+ .not_to change { book.author_id }.from(nil) }
97
+
98
+ end
99
+
100
+ context 'persisted owner' do
101
+ let(:new_author) { Author.create(name: 'Morty') }
102
+
103
+ specify { expect { association.writer(stub_model(:dummy).new) }
104
+ .to raise_error ActiveData::AssociationTypeMismatch }
105
+
106
+ specify { expect(association.writer(nil)).to be_nil }
107
+ specify { expect(association.writer(new_author)).to eq(new_author) }
108
+ specify { expect { association.writer(nil) }
109
+ .not_to change { book.read_attribute(:author_id) } }
110
+ specify { expect { association.writer(new_author) }
111
+ .to change { association.reader.try(:attributes) }.from(nil).to('id' => 1, 'name' => 'Morty') }
112
+ specify { expect { association.writer(new_author) }
113
+ .to change { book.read_attribute(:author_id) } }
114
+
115
+
116
+ specify { expect { existing_association.writer(stub_class(:dummy, ActiveRecord::Base).new) rescue nil }
117
+ .not_to change { existing_book.read_attribute(:author_id) } }
118
+ specify { expect { existing_association.writer(stub_class(:dummy, ActiveRecord::Base).new) rescue nil }
119
+ .not_to change { existing_association.reader } }
120
+
121
+ specify { expect(existing_association.writer(nil)).to be_nil }
122
+ specify { expect(existing_association.writer(new_author)).to eq(new_author) }
123
+ specify { expect { existing_association.writer(nil) }
124
+ .to change { existing_book.read_attribute(:author_id) }.from(author.id).to(nil) }
125
+ specify { expect { existing_association.writer(new_author) }
126
+ .to change { existing_association.reader.try(:attributes) }
127
+ .from('id' => 1, 'name' => 'Johny').to('id' => 2, 'name' => 'Morty') }
128
+ specify { expect { existing_association.writer(new_author) }
129
+ .to change { existing_book.read_attribute(:author_id) }
130
+ .from(author.id).to(new_author.id) }
131
+
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,144 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe ActiveData::Model::Associations::Reflections::EmbedsMany do
5
+ before do
6
+ stub_model(:project) do
7
+ include ActiveData::Model::Lifecycle
8
+ attribute :title, String
9
+ end
10
+ stub_model(:user) do
11
+ include ActiveData::Model::Associations
12
+
13
+ attribute :name, String
14
+ embeds_many :projects
15
+ end
16
+ end
17
+ let(:user) { User.new }
18
+
19
+ context ':read, :write' do
20
+ before do
21
+ stub_model(:user) do
22
+ include ActiveData::Model::Persistence
23
+ include ActiveData::Model::Associations
24
+
25
+ attribute :name
26
+ embeds_many :projects,
27
+ read: ->(reflection, object) {
28
+ value = object.read_attribute(reflection.name)
29
+ JSON.parse(value) if value.present?
30
+ },
31
+ write: ->(reflection, object, value) {
32
+ object.write_attribute(reflection.name, value.to_json)
33
+ }
34
+ end
35
+ end
36
+
37
+ let(:user) { User.instantiate name: 'Rick', projects: [{title: 'Genesis'}].to_json }
38
+ let(:new_project1) { Project.new(title: 'Project 1') }
39
+ let(:new_project2) { Project.new(title: 'Project 2') }
40
+
41
+ specify { expect { user.projects.concat([new_project1, new_project2]) }
42
+ .to change { user.read_attribute(:projects) }
43
+ .from([{title: 'Genesis'}].to_json)
44
+ .to([{title: 'Genesis'}, {title: 'Project 1'}, {title: 'Project 2'}].to_json) }
45
+ end
46
+
47
+ describe '#projects' do
48
+ specify { expect(user.projects).to eq([]) }
49
+
50
+ describe '#build' do
51
+ let(:project) { Project.new title: 'Project' }
52
+ specify { expect(user.projects.build(title: 'Project')).to eq(project) }
53
+ specify { expect { user.projects.build(title: 'Project') }.to change { user.projects }.from([]).to([project]) }
54
+ end
55
+
56
+ describe '#create' do
57
+ let(:project) { Project.new title: 'Project' }
58
+ specify { expect(user.projects.create(title: 'Project')).to eq(project) }
59
+ specify { expect { user.projects.create(title: 'Project') }.to change { user.projects }.from([]).to([project]) }
60
+ end
61
+
62
+ describe '#create!' do
63
+ let(:project) { Project.new title: 'Project' }
64
+ specify { expect(user.projects.create!(title: 'Project')).to eq(project) }
65
+ specify { expect { user.projects.create!(title: 'Project') }.to change { user.projects }.from([]).to([project]) }
66
+ end
67
+
68
+ describe '#reload' do
69
+ let(:project) { Project.new title: 'Project' }
70
+ before do
71
+ user.update(projects: [project])
72
+ user.apply_association_changes!
73
+ end
74
+ before { user.projects.build }
75
+
76
+ specify { expect(user.projects.count).to eq(2) }
77
+ specify { expect(user.projects.reload).to eq([project]) }
78
+ end
79
+
80
+ describe '#concat' do
81
+ let(:project) { Project.new title: 'Project' }
82
+ specify { expect { user.projects.concat project }.to change { user.projects }.from([]).to([project]) }
83
+ specify { expect { user.projects.concat project, 'string' }.to raise_error ActiveData::AssociationTypeMismatch }
84
+
85
+ context do
86
+ let(:other) { Project.new title: 'Other' }
87
+ before { user.projects = [other] }
88
+ specify { expect { user.projects.concat project }.to change { user.projects }.from([other]).to([other, project]) }
89
+ end
90
+ end
91
+ end
92
+
93
+ describe '#projects=' do
94
+ let(:project) { Project.new title: 'Project' }
95
+ specify { expect { user.projects = [] }.not_to change { user.projects }.from([]) }
96
+ specify { expect { user.projects = [project] }.to change { user.projects }.from([]).to([project]) }
97
+ specify { expect { user.projects = [project, 'string'] }.to raise_error ActiveData::AssociationTypeMismatch }
98
+
99
+ context do
100
+ let(:other) { Project.new title: 'Other' }
101
+ before { user.projects = [other] }
102
+ specify { expect { user.projects = [project] }.to change { user.projects }.from([other]).to([project]) }
103
+ specify { expect { user.projects = [] }.to change { user.projects }.from([other]).to([]) }
104
+ end
105
+ end
106
+
107
+ context 'on the fly' do
108
+ context do
109
+ before do
110
+ stub_model(:user) do
111
+ include ActiveData::Model::Associations
112
+
113
+ attribute :title, String
114
+ embeds_many :projects do
115
+ attribute :title, String
116
+ end
117
+ end
118
+ end
119
+
120
+ specify { expect(User.reflect_on_association(:projects).klass).to eq(User::Project) }
121
+ specify { expect(User.new.projects).to eq([]) }
122
+ specify { expect(User.new.tap { |u| u.projects.create(title: 'Project') }.projects).to be_a(ActiveData::Model::Associations::Collection::Embedded) }
123
+ specify { expect(User.new.tap { |u| u.projects.create(title: 'Project') }.read_attribute(:projects)).to eq([{'title' => 'Project'}]) }
124
+ end
125
+
126
+ context do
127
+ before do
128
+ stub_model(:user) do
129
+ include ActiveData::Model::Associations
130
+
131
+ attribute :title, String
132
+ embeds_many :projects, class_name: 'Project' do
133
+ attribute :value, String
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', 'value' => nil}]) }
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,116 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe ActiveData::Model::Associations::Reflections::EmbedsOne do
5
+ before do
6
+ stub_model(:author) do
7
+ include ActiveData::Model::Lifecycle
8
+ attribute :name, String
9
+ end
10
+
11
+ stub_model(:book) do
12
+ include ActiveData::Model::Associations
13
+
14
+ attribute :title, String
15
+ embeds_one :author
16
+ end
17
+ end
18
+ let(:book) { Book.new }
19
+
20
+ specify { expect(book.author).to be_nil }
21
+
22
+ context ':read, :write' do
23
+ before do
24
+ stub_model(:book) do
25
+ include ActiveData::Model::Persistence
26
+ include ActiveData::Model::Associations
27
+
28
+ attribute :title
29
+ embeds_one :author,
30
+ read: ->(reflection, object) {
31
+ value = object.read_attribute(reflection.name)
32
+ JSON.parse(value) if value.present?
33
+ },
34
+ write: ->(reflection, object, value) {
35
+ object.write_attribute(reflection.name, value ? value.to_json : nil)
36
+ }
37
+ end
38
+ end
39
+
40
+ let(:book) { Book.instantiate author: {name: 'Duke'}.to_json }
41
+ let(:author) { Author.new(name: 'Rick') }
42
+
43
+ specify { expect { book.author = author }
44
+ .to change { book.read_attribute(:author) }
45
+ .from({name: 'Duke'}.to_json).to({name: 'Rick'}.to_json) }
46
+ end
47
+
48
+ describe '#author=' do
49
+ let(:author) { Author.new name: 'Author' }
50
+ specify { expect { book.author = author }.to change { book.author }.from(nil).to(author) }
51
+ specify { expect { book.author = 'string' }.to raise_error ActiveData::AssociationTypeMismatch }
52
+
53
+ context do
54
+ let(:other) { Author.new name: 'Other' }
55
+ before { book.author = other }
56
+ specify { expect { book.author = author }.to change { book.author }.from(other).to(author) }
57
+ specify { expect { book.author = nil }.to change { book.author }.from(other).to(nil) }
58
+ end
59
+ end
60
+
61
+ describe '#build_author' do
62
+ let(:author) { Author.new name: 'Author' }
63
+ specify { expect(book.build_author(name: 'Author')).to eq(author) }
64
+ specify { expect { book.build_author(name: 'Author') }.to change { book.author }.from(nil).to(author) }
65
+ end
66
+
67
+ describe '#create_author' do
68
+ let(:author) { Author.new name: 'Author' }
69
+ specify { expect(book.create_author(name: 'Author')).to eq(author) }
70
+ specify { expect { book.create_author(name: 'Author') }.to change { book.author }.from(nil).to(author) }
71
+ end
72
+
73
+ describe '#create_author!' do
74
+ let(:author) { Author.new name: 'Author' }
75
+ specify { expect(book.create_author!(name: 'Author')).to eq(author) }
76
+ specify { expect { book.create_author!(name: 'Author') }.to change { book.author }.from(nil).to(author) }
77
+ end
78
+
79
+ context 'on the fly' do
80
+ context do
81
+ before do
82
+ stub_model(:book) do
83
+ include ActiveData::Model::Associations
84
+
85
+ attribute :title, String
86
+ embeds_one :author do
87
+ attribute :name, String
88
+ end
89
+ end
90
+ end
91
+
92
+ specify { expect(Book.reflect_on_association(:author).klass).to eq(Book::Author) }
93
+ specify { expect(Book.new.author).to be_nil }
94
+ specify { expect(Book.new.tap { |b| b.create_author(name: 'Author') }.author).to be_a(Book::Author) }
95
+ specify { expect(Book.new.tap { |b| b.create_author(name: 'Author') }.read_attribute(:author)).to eq('name' => 'Author') }
96
+ end
97
+
98
+ context do
99
+ before do
100
+ stub_model(:book) do
101
+ include ActiveData::Model::Associations
102
+
103
+ attribute :title, String
104
+ embeds_one :author, class_name: 'Author' do
105
+ attribute :age, Integer
106
+ end
107
+ end
108
+ end
109
+
110
+ specify { expect(Book.reflect_on_association(:author).klass).to eq(Book::Author) }
111
+ specify { expect(Book.new.author).to be_nil }
112
+ specify { expect(Book.new.tap { |b| b.create_author(name: 'Author') }.author).to be_a(Book::Author) }
113
+ specify { expect(Book.new.tap { |b| b.create_author(name: 'Author') }.read_attribute(:author)).to eq('name' => 'Author', 'age' => nil) }
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,255 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe ActiveData::Model::Associations::Reflections::ReferencesMany do
5
+ before do
6
+ stub_class(:author, ActiveRecord::Base) do
7
+ scope :name_starts_with_a, -> { where('name LIKE "a%"') }
8
+ end
9
+
10
+ stub_model(:book) do
11
+ include ActiveData::Model::Associations
12
+
13
+ attribute :title
14
+ references_many :authors
15
+ end
16
+ end
17
+
18
+ let(:author) { Author.create!(name: 'Rick') }
19
+ let(:other) { Author.create!(name: 'John') }
20
+ let(:book) { Book.new }
21
+ let(:book_with_author) { Book.new(authors: [author]) }
22
+
23
+ specify { expect(book.authors).to be_empty }
24
+
25
+ context ':class_name' do
26
+ before do
27
+ stub_model(:book) do
28
+ include ActiveData::Model::Associations
29
+
30
+ attribute :title
31
+ references_many :creators, class_name: 'Author'
32
+ end
33
+ end
34
+
35
+ let(:book) { Book.new }
36
+
37
+ specify { expect { book.creators << author }
38
+ .to change { book.creators }.from([]).to([author]) }
39
+ specify { expect { book.creators << author }
40
+ .to change { book.creator_ids }.from([]).to([author.id]) }
41
+ end
42
+
43
+ describe ':primary_key' do
44
+ before do
45
+ stub_model(:book) do
46
+ include ActiveData::Model::Associations
47
+ collection :author_names, String
48
+ references_many :authors, primary_key: 'name'
49
+ end
50
+ end
51
+
52
+ let(:author) { Author.create!(name: 'Rick') }
53
+
54
+ specify { expect { book.author_names = [author.name] }
55
+ .to change { book.authors }.from([]).to([author]) }
56
+ specify { expect { book.authors = [author] }
57
+ .to change { book.author_names }.from([]).to([author.name]) }
58
+ end
59
+
60
+ describe ':reference_key' do
61
+ before do
62
+ stub_model(:book) do
63
+ include ActiveData::Model::Associations
64
+ references_many :authors, reference_key: 'identify'
65
+ end
66
+ end
67
+
68
+ let(:author) { Author.create!(name: 'Rick') }
69
+
70
+ specify { expect { book.identify = [author.id] }
71
+ .to change { book.authors }.from([]).to([author]) }
72
+ specify { expect { book.authors = [author] }
73
+ .to change { book.identify }.from([]).to([author.id]) }
74
+ end
75
+
76
+ describe ':default' do
77
+ shared_examples_for :persisted_default do |default|
78
+ before do
79
+ stub_model(:book) do
80
+ include ActiveData::Model::Associations
81
+ references_many :authors
82
+ references_many :owners, class_name: 'Author', default: default
83
+ end
84
+ end
85
+
86
+ let(:author) { Author.create! }
87
+ let(:other) { Author.create! }
88
+ let(:book) { Book.new(authors: [author]) }
89
+
90
+ specify { expect(book.owner_ids).to eq([author.id]) }
91
+ specify { expect(book.owners).to eq([author]) }
92
+ specify { expect { book.owners = [other] }.to change { book.owner_ids }.from([author.id]).to([other.id]) }
93
+ specify { expect { book.owners = [other] }.to change { book.owners }.from([author]).to([other]) }
94
+ specify { expect { book.owner_ids = [other.id] }.to change { book.owner_ids }.from([author.id]).to([other.id]) }
95
+ specify { expect { book.owner_ids = [other.id] }.to change { book.owners }.from([author]).to([other]) }
96
+ specify { expect { book.owners = [] }.to change { book.owner_ids }.from([author.id]).to([]) }
97
+ specify { expect { book.owners = [] }.to change { book.owners }.from([author]).to([]) }
98
+ specify { expect { book.owner_ids = [] }.not_to change { book.owner_ids }.from([author.id]) }
99
+ specify { expect { book.owner_ids = [] }.not_to change { book.owners }.from([author]) }
100
+ specify { expect { book.owner_ids = [nil] }.to change { book.owner_ids }.from([author.id]).to([]) }
101
+ specify { expect { book.owner_ids = [nil] }.to change { book.owners }.from([author]).to([]) }
102
+ specify { expect { book.owner_ids = [''] }.to change { book.owner_ids }.from([author.id]).to([]) }
103
+ specify { expect { book.owner_ids = [''] }.to change { book.owners }.from([author]).to([]) }
104
+ specify { expect { book.owner_ids = nil }.not_to change { book.owner_ids }.from([author.id]) }
105
+ specify { expect { book.owner_ids = nil }.not_to change { book.owners }.from([author]) }
106
+ specify { expect { book.owner_ids = '' }.to change { book.owner_ids }.from([author.id]).to([]) }
107
+ specify { expect { book.owner_ids = '' }.to change { book.owners }.from([author]).to([]) }
108
+ end
109
+
110
+ it_behaves_like :persisted_default, -> { authors.map(&:id) }
111
+ it_behaves_like :persisted_default, -> { authors }
112
+
113
+ shared_examples_for :new_record_default do |default|
114
+ before do
115
+ stub_model(:book) do
116
+ include ActiveData::Model::Associations
117
+ references_many :authors
118
+ references_many :owners, class_name: 'Author', default: default
119
+ end
120
+ end
121
+
122
+ let(:author) { Author.create! }
123
+ let(:book) { Book.new }
124
+
125
+ specify { expect(book.owner_ids).to eq([nil]) }
126
+ specify { expect(book.owners).to match([an_instance_of(Author).and(have_attributes(name: 'Author'))]) }
127
+ specify { expect { book.owners = [other] }.to change { book.owner_ids }.from([nil]).to([other.id]) }
128
+ specify { expect { book.owners = [other] }.to change { book.owners }.from([an_instance_of(Author)]).to([other]) }
129
+ specify { expect { book.owner_ids = [other.id] }.to change { book.owner_ids }.from([nil]).to([other.id]) }
130
+ specify { expect { book.owner_ids = [other.id] }.to change { book.owners }.from([an_instance_of(Author)]).to([other]) }
131
+ specify { expect { book.owners = [] }.to change { book.owner_ids }.from([nil]).to([]) }
132
+ specify { expect { book.owners = [] }.to change { book.owners }.from([an_instance_of(Author)]).to([]) }
133
+ specify { expect { book.owner_ids = [] }.not_to change { book.owner_ids }.from([nil]) }
134
+ specify { expect { book.owner_ids = [] }.not_to change { book.owners }.from([an_instance_of(Author)]) }
135
+ specify { expect { book.owner_ids = [nil] }.to change { book.owner_ids }.from([nil]).to([]) }
136
+ specify { expect { book.owner_ids = [nil] }.to change { book.owners }.from([an_instance_of(Author)]).to([]) }
137
+ specify { expect { book.owner_ids = [''] }.to change { book.owner_ids }.from([nil]).to([]) }
138
+ specify { expect { book.owner_ids = [''] }.to change { book.owners }.from([an_instance_of(Author)]).to([]) }
139
+ specify { expect { book.owner_ids = nil }.not_to change { book.owner_ids }.from([nil]) }
140
+ specify { expect { book.owner_ids = nil }.not_to change { book.owners }.from([an_instance_of(Author)]) }
141
+ specify { expect { book.owner_ids = '' }.to change { book.owner_ids }.from([nil]).to([]) }
142
+ specify { expect { book.owner_ids = '' }.to change { book.owners }.from([an_instance_of(Author)]).to([]) }
143
+ end
144
+
145
+ it_behaves_like :new_record_default, name: 'Author'
146
+ it_behaves_like :new_record_default, -> { Author.new(name: 'Author') }
147
+ end
148
+
149
+ describe '#scope' do
150
+ before do
151
+ stub_model(:book) do
152
+ include ActiveData::Model::Associations
153
+ references_many :authors, -> { name_starts_with_a }
154
+ end
155
+ end
156
+
157
+ let!(:author1) { Author.create!(name: 'Rick') }
158
+ let!(:author2) { Author.create!(name: 'Aaron') }
159
+ specify { expect { book.authors = [author1, author2] }
160
+ .to change { book.authors }.from([]).to([author1, author2]) }
161
+ specify { expect { book.authors = [author1, author2] }
162
+ .to change { book.author_ids }.from([]).to([author1.id, author2.id]) }
163
+
164
+ specify { expect { book.author_ids = [author1.id, author2.id] }
165
+ .to change { book.authors }.from([]).to([author2]) }
166
+ specify { expect { book.author_ids = [author1.id, author2.id] }
167
+ .to change { book.author_ids }.from([]).to([author2.id]) }
168
+
169
+ specify { expect { book.authors = [author1, author2] }
170
+ .to change { book.authors.reload }.from([]).to([author2]) }
171
+ specify { expect { book.authors = [author1, author2] }
172
+ .to change { book.authors.reload; book.author_ids }.from([]).to([author2.id]) }
173
+ end
174
+
175
+ describe '#author' do
176
+ it { expect(book.authors).not_to respond_to(:build) }
177
+ it { expect(book.authors).not_to respond_to(:create) }
178
+ it { expect(book.authors).not_to respond_to(:create!) }
179
+
180
+ describe '#clear' do
181
+ it { expect { book_with_author.authors.clear }.to change { book_with_author.authors }.from([author]).to([]) }
182
+ end
183
+
184
+ describe '#reload' do
185
+ before { book.authors << author.tap { |a| a.name = 'Don Juan' } }
186
+ it { expect { book.authors.reload }.to change { book.authors.map(&:name) }.from(['Don Juan']).to(['Rick']) }
187
+ end
188
+
189
+ describe '#concat' do
190
+ it { expect { book.authors.concat author }.to change { book.authors }.from([]).to([author]) }
191
+ it { expect { book.authors << author << other }.to change { book.authors }.from([]).to([author, other]) }
192
+ context 'no duplication' do
193
+ before { book.authors << author }
194
+ it { expect { book.authors.concat author }.not_to change { book.authors }.from([author]) }
195
+ end
196
+ end
197
+
198
+ context 'scope missing method delegation' do
199
+ it { expect(book_with_author.authors.scope).to be_a ActiveRecord::Relation }
200
+ it { expect(book_with_author.authors.where(name: 'John')).to be_a ActiveRecord::Relation }
201
+ it { expect(book_with_author.authors.name_starts_with_a).to be_a ActiveRecord::Relation }
202
+ end
203
+ end
204
+
205
+ describe '#author_ids' do
206
+ it { expect(book_with_author.author_ids).to eq([author.id]) }
207
+ xit { expect { book_with_author.author_ids << other.id }.to change { book_with_author.authors }.from([author]).to([author, other]) }
208
+ it { expect { book_with_author.author_ids = [other.id] }.to change { book_with_author.authors }.from([author]).to([other]) }
209
+ end
210
+
211
+ describe '#authors=' do
212
+ specify { expect { book.authors = [author] }.to change { book.authors }.from([]).to([author]) }
213
+ specify { expect { book.authors = ['string'] }.to raise_error ActiveData::AssociationTypeMismatch }
214
+
215
+ context do
216
+ before { book.authors = [other] }
217
+ specify { expect { book.authors = [author] }.to change { book.authors }.from([other]).to([author]) }
218
+ specify { expect { book.authors = [author] }.to change { book.author_ids }.from([other.id]).to([author.id]) }
219
+ specify { expect { book.authors = [] }.to change { book.authors }.from([other]).to([]) }
220
+ specify { expect { book.authors = [] }.to change { book.author_ids }.from([other.id]).to([]) }
221
+ end
222
+
223
+ context 'model not persisted' do
224
+ let(:author) { Author.new }
225
+ specify { expect { book.authors = [author, other] }.to change { book.authors }.from([]).to([author, other]) }
226
+ specify { expect { book.authors = [author, other] }.to change { book.author_ids }.from([]).to([nil, other.id]) }
227
+
228
+ context do
229
+ before { book.authors = [author, other] }
230
+ specify { expect { author.save! }.to change { book.author_ids }.from([nil, other.id])
231
+ .to(match([an_instance_of(Fixnum), other.id])) }
232
+ specify { expect { author.save! }.not_to change { book.authors } }
233
+ end
234
+ end
235
+ end
236
+
237
+ describe '#author_ids=' do
238
+ specify { expect { book.author_ids = [author.id] }.to change { book.author_ids }.from([]).to([author.id]) }
239
+ specify { expect { book.author_ids = [author.id] }.to change { book.authors }.from([]).to([author]) }
240
+
241
+ specify { expect { book.author_ids = [author.id.next.to_s] }.not_to change { book.author_ids }.from([]) }
242
+ specify { expect { book.author_ids = [author.id.next.to_s] }.not_to change { book.authors }.from([]) }
243
+
244
+ specify { expect { book.author_ids = [author.id.next.to_s, author.id] }.to change { book.author_ids }.from([]).to([author.id]) }
245
+ specify { expect { book.author_ids = [author.id.next.to_s, author.id] }.to change { book.authors }.from([]).to([author]) }
246
+
247
+ context do
248
+ before { book.authors = [other] }
249
+ specify { expect { book.author_ids = [author.id] }.to change { book.author_ids }.from([other.id]).to([author.id]) }
250
+ specify { expect { book.author_ids = [author.id] }.to change { book.authors }.from([other]).to([author]) }
251
+ specify { expect { book.author_ids = [] }.to change { book.author_ids }.from([other.id]).to([]) }
252
+ specify { expect { book.author_ids = [] }.to change { book.authors }.from([other]).to([]) }
253
+ end
254
+ end
255
+ end