embedded_associations 0.0.1

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 (59) hide show
  1. data/.gitignore +17 -0
  2. data/.travis.yml +5 -0
  3. data/Gemfile +14 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +67 -0
  6. data/Rakefile +1 -0
  7. data/embedded_associations.gemspec +21 -0
  8. data/lib/embedded_associations.rb +165 -0
  9. data/lib/embedded_associations/rails.rb +13 -0
  10. data/lib/embedded_associations/version.rb +3 -0
  11. data/spec/embedded_associations_spec.rb +324 -0
  12. data/spec/spec_helper.rb +16 -0
  13. data/spec/support/app/.gitignore +15 -0
  14. data/spec/support/app/Rakefile +7 -0
  15. data/spec/support/app/app/controllers/application_controller.rb +3 -0
  16. data/spec/support/app/app/controllers/posts_controller.rb +40 -0
  17. data/spec/support/app/app/models/account.rb +4 -0
  18. data/spec/support/app/app/models/category.rb +4 -0
  19. data/spec/support/app/app/models/comment.rb +6 -0
  20. data/spec/support/app/app/models/post.rb +8 -0
  21. data/spec/support/app/app/models/tag.rb +4 -0
  22. data/spec/support/app/app/models/user.rb +7 -0
  23. data/spec/support/app/app/serializers/account_serializer.rb +3 -0
  24. data/spec/support/app/app/serializers/category_serializer.rb +3 -0
  25. data/spec/support/app/app/serializers/comment_serializer.rb +5 -0
  26. data/spec/support/app/app/serializers/post_serializer.rb +8 -0
  27. data/spec/support/app/app/serializers/tag_serializer.rb +3 -0
  28. data/spec/support/app/app/serializers/user_serializer.rb +5 -0
  29. data/spec/support/app/config.ru +4 -0
  30. data/spec/support/app/config/application.rb +62 -0
  31. data/spec/support/app/config/boot.rb +6 -0
  32. data/spec/support/app/config/database.yml +11 -0
  33. data/spec/support/app/config/environment.rb +5 -0
  34. data/spec/support/app/config/environments/development.rb +37 -0
  35. data/spec/support/app/config/environments/production.rb +67 -0
  36. data/spec/support/app/config/environments/test.rb +37 -0
  37. data/spec/support/app/config/initializers/backtrace_silencers.rb +7 -0
  38. data/spec/support/app/config/initializers/inflections.rb +15 -0
  39. data/spec/support/app/config/initializers/mime_types.rb +5 -0
  40. data/spec/support/app/config/initializers/secret_token.rb +7 -0
  41. data/spec/support/app/config/initializers/session_store.rb +8 -0
  42. data/spec/support/app/config/initializers/wrap_parameters.rb +14 -0
  43. data/spec/support/app/config/locales/en.yml +5 -0
  44. data/spec/support/app/config/routes.rb +3 -0
  45. data/spec/support/app/db/migrate/20130225045501_create_posts.rb +10 -0
  46. data/spec/support/app/db/migrate/20130225045512_create_comments.rb +10 -0
  47. data/spec/support/app/db/migrate/20130225173707_create_users.rb +9 -0
  48. data/spec/support/app/db/migrate/20130225173717_create_accounts.rb +9 -0
  49. data/spec/support/app/db/migrate/20130226001629_create_tags.rb +9 -0
  50. data/spec/support/app/db/migrate/20130226001639_create_categories.rb +8 -0
  51. data/spec/support/app/db/schema.rb +59 -0
  52. data/spec/support/app/db/seeds.rb +7 -0
  53. data/spec/support/app/db/structure.sql +19 -0
  54. data/spec/support/app/lib/assets/.gitkeep +0 -0
  55. data/spec/support/app/lib/tasks/.gitkeep +0 -0
  56. data/spec/support/app/log/.gitkeep +0 -0
  57. data/spec/support/app/script/rails +6 -0
  58. data/spec/support/serialization_helpers.rb +18 -0
  59. metadata +168 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ rvm:
2
+ - 1.9.3
3
+ before_install:
4
+ - gem install bundler --version '>= 1.2.2'
5
+ script: "bundle exec rspec"
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'rails', '3.2.12'
7
+ gem 'sqlite3'
8
+ gem 'active_model_serializers'
9
+
10
+ gem 'rspec'
11
+ gem 'rspec-rails'
12
+
13
+ gem 'pry'
14
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Gordon L. Hempton
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # EmbeddedAssociations
2
+
3
+ Provides ActionController-level support for embedded associations in Rails. Use cases include:
4
+
5
+ * Being able to easily consume embedded records serialized from [Active Model Serializers](https://github.com/rails-api/active_model_serializers).
6
+ * Simplifying REST API implementations.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'embedded_associations'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install embedded_associations
21
+
22
+ ## Usage
23
+
24
+ Embedded Associations provides an `embedded_association` macro to ActionController:
25
+
26
+ ```ruby
27
+ class PostsController < ApplicationController
28
+ embedded_association :tags
29
+ embedded_association :comments => :user
30
+ end
31
+ ```
32
+
33
+ Behind the scenes, this will make the controller parse out sub-params passed in and perform the necessary ActiveRecord model manipulation. E.g., consider the params hash below:
34
+
35
+ ```ruby
36
+ {
37
+ post: {
38
+ tags: ['rest', 'ember-data'],
39
+ comments: [
40
+ {user: {name: 'Gordon'}}
41
+ ]
42
+ }
43
+ }
44
+ ```
45
+
46
+ Based on the declared `embedded_association`s, the controller will manipulate the `tags` and `comments` association to reflect the passed in params (including deleting or creating child records).
47
+
48
+ ### Controller and Model Pre-Requisites
49
+
50
+ EmbeddedAssociations depends on your controller implementation to have two methods available, `resource` and `resource_name`. These methods will be familiar and already provided to people who use [CanCan's](https://github.com/ryanb/cancan) `load_resource` macro.
51
+
52
+ When defining the relationships in the model, it is also important to set the `autosave` and `dependent` options on the association:
53
+
54
+ ```ruby
55
+ class Post < ActiveRecord::Base
56
+ has_many :comments, autosave: true, dependent: :destroy
57
+ has_many :tags, autosave: true, dependent: :destroy
58
+ end
59
+ ```
60
+
61
+ ## Contributing
62
+
63
+ 1. Fork it
64
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
65
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
66
+ 4. Push to the branch (`git push origin my-new-feature`)
67
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'embedded_associations/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "embedded_associations"
8
+ gem.version = EmbeddedAssociations::VERSION
9
+ gem.authors = ["Gordon L. Hempton"]
10
+ gem.email = ["ghempton@gmail.com"]
11
+ gem.description = %q{ActiveRecord controller-level support for embedded associations}
12
+ gem.summary = %q{ActiveRecord controller-level support for embedded associations}
13
+ gem.homepage = "https://github.com/GroupTalent/embedded_associations"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency "railties", "> 3.0.0"
21
+ end
@@ -0,0 +1,165 @@
1
+ require "embedded_associations/rails"
2
+ require "embedded_associations/version"
3
+
4
+ module EmbeddedAssociations
5
+
6
+ def self.included(base)
7
+ base.instance_eval do
8
+
9
+ class_attribute :embedded_associations
10
+
11
+ def self.embedded_association(definition)
12
+ unless embedded_associations
13
+ self.embedded_associations = Definitions.new
14
+ before_filter :handle_embedded_associations, only: [:update, :create, :destroy]
15
+ end
16
+ self.embedded_associations = embedded_associations.add_definition(definition)
17
+ end
18
+ end
19
+ end
20
+
21
+ def handle_embedded_associations
22
+ Processor.new(embedded_associations, self).run
23
+ end
24
+
25
+ def root_resource
26
+ resource
27
+ end
28
+
29
+ def root_resource_name
30
+ resource_name
31
+ end
32
+
33
+ # Simple callbacks for now, eventually should use a filter system
34
+ def before_embedded_update(record); end
35
+ def before_embedded_create(record); end
36
+ def before_embedded_destroy(record); end
37
+
38
+ class Definitions
39
+ include Enumerable
40
+
41
+ attr_accessor :definitions
42
+
43
+ def initialize
44
+ @definitions = []
45
+ end
46
+
47
+ # Keep immutable to prevent all controllers
48
+ # from sharing the same copy
49
+ def add_definition(definition)
50
+ result = self.dup
51
+ result.definitions << definition
52
+ result
53
+ end
54
+
55
+ def initialize_copy(source)
56
+ self.definitions = source.definitions.dup
57
+ end
58
+
59
+ def each(&block)
60
+ self.definitions.each &block
61
+ end
62
+ end
63
+
64
+ class Processor
65
+
66
+ attr_reader :definitions
67
+ attr_reader :controller
68
+
69
+ def initialize(definitions, controller)
70
+ @definitions = definitions
71
+ @controller = controller
72
+ end
73
+
74
+ def run
75
+ definitions.each do |definition|
76
+ handle_resource(definition, controller.root_resource, controller.params[controller.root_resource_name])
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ # Definition can be either a name, array, or hash.
83
+ def handle_resource(definition, parent, parent_params)
84
+ if definition.is_a? Array
85
+ return definition.each{|d| handle_resource(d, parent, parent_params)}
86
+ end
87
+ # normalize to a hash
88
+ unless definition.is_a? Hash
89
+ definition = {definition => nil}
90
+ end
91
+
92
+ definition.each do |name, child_definition|
93
+ binding.pry unless parent
94
+ reflection = parent.class.reflect_on_association(name)
95
+ attrs = parent_params && parent_params.delete(name.to_s)
96
+
97
+ if reflection.collection?
98
+ attrs ||= []
99
+ handle_plural_resource parent, name, attrs, child_definition
100
+ else
101
+ handle_singular_resource parent, name, attrs, child_definition
102
+ end
103
+ end
104
+ end
105
+
106
+ def handle_plural_resource(parent, name, attr_array, child_definition)
107
+ current_assoc = parent.send(name)
108
+
109
+ # Mark non-existant records as deleted
110
+ current_assoc.select{|r| attr_array.none?{|attrs| attrs['id'] && attrs['id'].to_i == r.id}}.each do |r|
111
+ handle_resource(child_definition, r, nil) if child_definition
112
+ run_before_destroy_callbacks(r)
113
+ r.mark_for_destruction
114
+ end
115
+
116
+ attr_array.each do |attrs|
117
+ if id = attrs['id']
118
+ # can't use current_assoc.find(id), see http://stackoverflow.com/questions/11605120/autosave-ignored-on-has-many-relation-what-am-i-missing
119
+ r = current_assoc.find{|r| r.id == id.to_i}
120
+ handle_resource(child_definition, r, attrs) if child_definition
121
+ r.assign_attributes(attrs)
122
+ run_before_update_callbacks(r)
123
+ else
124
+ r = current_assoc.build()
125
+ handle_resource(child_definition, r, attrs) if child_definition
126
+ r.assign_attributes(attrs)
127
+ run_before_create_callbacks(r)
128
+ end
129
+ end
130
+ end
131
+
132
+ def handle_singular_resource(parent, name, attrs, child_definition)
133
+ current_assoc = parent.send(name)
134
+ if r = current_assoc
135
+ if attrs
136
+ handle_resource(child_definition, r, attrs) if child_definition
137
+ r.assign_attributes(attrs)
138
+ run_before_update_callbacks(r)
139
+ else
140
+ handle_resource(child_definition, r, attrs) if child_definition
141
+ run_before_destroy_callbacks(r)
142
+ r.mark_for_destruction
143
+ end
144
+ elsif attrs
145
+ r = parent.send("build_#{name}")
146
+ handle_resource(child_definition, r, attrs) if child_definition
147
+ r.assign_attributes(attrs)
148
+ run_before_create_callbacks(r)
149
+ end
150
+ end
151
+
152
+ def run_before_create_callbacks(record)
153
+ controller.send(:before_embedded_create, record)
154
+ end
155
+
156
+ def run_before_update_callbacks(record)
157
+ controller.send(:before_embedded_update, record)
158
+ end
159
+
160
+ def run_before_destroy_callbacks(record)
161
+ controller.send(:before_embedded_destroy, record)
162
+ end
163
+ end
164
+
165
+ end
@@ -0,0 +1,13 @@
1
+ require 'rails'
2
+
3
+ module EmbeddedAssociations
4
+
5
+ class Engine < ::Rails::Engine
6
+ initializer "embedded_associations" do
7
+ ActiveSupport.on_load(:action_controller) do
8
+ include EmbeddedAssociations
9
+ end
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,3 @@
1
+ module EmbeddedAssociations
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,324 @@
1
+ require 'spec_helper'
2
+
3
+ describe PostsController, type: :controller do
4
+ include SerializationHelpers
5
+
6
+ describe "embedded has_many" do
7
+
8
+ context "creating" do
9
+
10
+ it "should create child records" do
11
+ json = post :create, post: {
12
+ tags: [{},{}]
13
+ }
14
+
15
+ expect(Post.count).to eq(1)
16
+ expect(Tag.count).to eq(2)
17
+
18
+ Tag.all.each{ |t| expect(t.post).to_not be_nil }
19
+ end
20
+
21
+ end
22
+
23
+ context "updating" do
24
+
25
+ let(:tags) {[Tag.create, Tag.create]}
26
+ let(:resource) { Post.create({tags: tags}, without_protection: true) }
27
+ let(:hash) { serialize(resource) }
28
+
29
+ it "should create new child records" do
30
+ hash[:tags] += [{},{}]
31
+ json = post :update, :id => resource.id, post: hash
32
+
33
+ expect(Post.count).to eq(1)
34
+ expect(Tag.count).to eq(4)
35
+
36
+ Tag.all.each{ |t| expect(t.post).to_not be_nil }
37
+ end
38
+
39
+ it "should destroy missing child records" do
40
+ hash[:tags] = hash[:tags].take(1)
41
+ json = post :update, :id => resource.id, post: hash
42
+
43
+ expect(Post.count).to eq(1)
44
+ expect(Tag.count).to eq(1)
45
+
46
+ Tag.all.each{ |t| expect(t.post).to_not be_nil }
47
+ end
48
+
49
+ it "should update modified child records" do
50
+ hash[:tags].first[:name] = 'modified'
51
+ json = post :update, :id => resource.id, post: hash
52
+
53
+ expect(Post.count).to eq(1)
54
+ expect(Tag.count).to eq(2)
55
+
56
+ expect(Tag.first.name).to eq('modified')
57
+
58
+ Tag.all.each{ |t| expect(t.post).to_not be_nil }
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+
65
+ describe "embedded belongs_to" do
66
+
67
+ context "creating" do
68
+
69
+ it "should create child record" do
70
+ json = post :create, post: {
71
+ category: {name: 'ember-data'}
72
+ }
73
+
74
+ expect(Post.count).to eq(1)
75
+ expect(Category.count).to eq(1)
76
+
77
+ resource = Post.first
78
+
79
+ expect(resource.category).to_not be_nil
80
+ end
81
+
82
+ end
83
+
84
+ context "updating" do
85
+
86
+ let(:resource) { Post.create }
87
+ let(:hash) { serialize(resource) }
88
+
89
+ it "should create new child record" do
90
+ hash[:category] = {name: 'ember'}
91
+ json = post :update, :id => resource.id, post: hash
92
+
93
+ expect(Post.count).to eq(1)
94
+ expect(Category.count).to eq(1)
95
+
96
+ resource.reload
97
+
98
+ expect(resource.category).to_not be_nil
99
+ end
100
+
101
+ context do
102
+
103
+ let(:resource) { Post.create({category: Category.create(name: 'ember')}, without_protection: true) }
104
+
105
+ it "should destroy nil child record" do
106
+ hash[:category] = nil
107
+ json = post :update, :id => resource.id, post: hash
108
+
109
+ expect(Post.count).to eq(1)
110
+ expect(Category.count).to eq(0)
111
+
112
+ resource.reload
113
+
114
+ expect(resource.category).to be_nil
115
+ end
116
+
117
+ it "should update modified child records" do
118
+ hash[:category][:name] = 'ember-data'
119
+ json = post :update, :id => resource.id, post: hash
120
+
121
+ resource.reload
122
+
123
+ expect(resource.category.name).to eq('ember-data')
124
+ end
125
+
126
+ end
127
+ end
128
+
129
+ end
130
+
131
+ describe "embedded belongs_to -> has_one" do
132
+
133
+ context "creating" do
134
+
135
+ it "should create hierarchy" do
136
+ json = post :create, post: {
137
+ user: {name: 'G$', account: {}}
138
+ }
139
+
140
+ expect(Post.count).to eq(1)
141
+ expect(User.count).to eq(1)
142
+ expect(Account.count).to eq(1)
143
+
144
+ resource = Post.first
145
+
146
+ expect(resource.user).to_not be_nil
147
+ expect(resource.user.account).to_not be_nil
148
+ end
149
+
150
+ end
151
+
152
+ context "updating" do
153
+
154
+ let(:resource) { Post.create }
155
+ let(:hash) { serialize(resource) }
156
+
157
+ it "should create new hierarchy" do
158
+ hash[:user] = {name: 'G$', account: {}}
159
+ json = post :update, :id => resource.id, post: hash
160
+
161
+ expect(User.count).to eq(1)
162
+ expect(Account.count).to eq(1)
163
+
164
+ resource.reload
165
+
166
+ expect(resource.user).to_not be_nil
167
+ expect(resource.user.account)
168
+ end
169
+
170
+ context do
171
+
172
+ let(:resource) { Post.create({user: User.create({name: 'G$', account: Account.create}, without_protection: true)}, without_protection: true) }
173
+
174
+ it "should destroy nil child hierarchy" do
175
+ hash[:user] = nil
176
+ json = post :update, :id => resource.id, post: hash
177
+
178
+ expect(Post.count).to eq(1)
179
+ expect(User.count).to eq(0)
180
+ expect(Account.count).to eq(0)
181
+
182
+ resource.reload
183
+
184
+ expect(resource.user).to be_nil
185
+ end
186
+
187
+ it "should destroy nil grand-child" do
188
+ hash[:user] = {name: 'G$'}
189
+ json = post :update, :id => resource.id, post: hash
190
+
191
+ expect(Post.count).to eq(1)
192
+ expect(User.count).to eq(1)
193
+ expect(Account.count).to eq(0)
194
+
195
+ resource.reload
196
+
197
+ expect(resource.user.account).to be_nil
198
+ end
199
+
200
+ it "should update modified child records" do
201
+ hash[:user][:name] = 'wes'
202
+ hash[:user][:account][:note] = 'test'
203
+ json = post :update, :id => resource.id, post: hash
204
+
205
+ resource.reload
206
+
207
+ expect(resource.user.name).to eq('wes')
208
+ expect(resource.user.account.note).to eq('test')
209
+ end
210
+
211
+ it "should update modified grand-child" do
212
+ hash[:user][:account][:note] = 'test'
213
+ json = post :update, :id => resource.id, post: hash
214
+
215
+ resource.reload
216
+
217
+ expect(resource.user.account.note).to eq('test')
218
+ end
219
+
220
+ end
221
+
222
+ end
223
+
224
+ end
225
+
226
+ describe "embedded has_many -> belongs_to -> has_one" do
227
+
228
+ context "creating" do
229
+
230
+ it "should create hierarchy" do
231
+ json = post :create, post: {
232
+ comments: [{user: {name: 'G$', account: {}}}]
233
+ }
234
+
235
+ expect(Post.count).to eq(1)
236
+ expect(Comment.count).to eq(1)
237
+ expect(User.count).to eq(1)
238
+ expect(Account.count).to eq(1)
239
+
240
+ resource = Post.first
241
+
242
+ expect(resource.comments).to_not be_empty
243
+ expect(resource.comments.first.user).to_not be_nil
244
+ expect(resource.comments.first.user.account).to_not be_nil
245
+ end
246
+
247
+ end
248
+
249
+ context "updating" do
250
+
251
+ let(:resource) { Post.create }
252
+ let(:hash) { serialize(resource) }
253
+
254
+ it "should create new hierarchy" do
255
+ hash[:comments] = [{user: {name: 'G$', account: {}}}]
256
+ json = post :update, :id => resource.id, post: hash
257
+
258
+ expect(Comment.count).to eq(1)
259
+ expect(User.count).to eq(1)
260
+ expect(Account.count).to eq(1)
261
+
262
+ resource.reload
263
+
264
+ expect(resource.comments).to_not be_empty
265
+ expect(resource.comments.first.user).to_not be_nil
266
+ expect(resource.comments.first.user.account).to_not be_nil
267
+ end
268
+
269
+ context do
270
+
271
+ let(:resource) {
272
+ p = Post.create
273
+ c = p.comments.create
274
+ u = c.create_user({account: Account.create}, without_protection: true)
275
+ c.save
276
+ p
277
+ }
278
+
279
+ it "should destroy nil child hierarchy" do
280
+ hash[:comments] = nil
281
+ json = post :update, :id => resource.id, post: hash
282
+
283
+ expect(Post.count).to eq(1)
284
+ expect(Comment.count).to eq(0)
285
+ expect(User.count).to eq(0)
286
+ expect(Account.count).to eq(0)
287
+
288
+ resource.reload
289
+
290
+ expect(resource.comments).to be_empty
291
+ end
292
+
293
+ it "should destroy nil grand-child hierarchy" do
294
+ hash[:comments].first[:user] = nil
295
+ json = post :update, :id => resource.id, post: hash
296
+
297
+ expect(Post.count).to eq(1)
298
+ expect(Comment.count).to eq(1)
299
+ expect(User.count).to eq(0)
300
+ expect(Account.count).to eq(0)
301
+
302
+ resource.reload
303
+
304
+ expect(resource.comments.first.user).to be_nil
305
+ end
306
+
307
+ it "should update modified child records" do
308
+ hash[:comments].first[:user][:name] = 'wes'
309
+ hash[:comments].first[:user][:account][:note] = 'test'
310
+ json = post :update, :id => resource.id, post: hash
311
+
312
+ resource.reload
313
+
314
+ expect(resource.comments.first.user.name).to eq('wes')
315
+ expect(resource.comments.first.user.account.note).to eq('test')
316
+ end
317
+
318
+ end
319
+
320
+ end
321
+
322
+ end
323
+
324
+ end