embedded_associations 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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