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.
- data/.gitignore +17 -0
- data/.travis.yml +5 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +22 -0
- data/README.md +67 -0
- data/Rakefile +1 -0
- data/embedded_associations.gemspec +21 -0
- data/lib/embedded_associations.rb +165 -0
- data/lib/embedded_associations/rails.rb +13 -0
- data/lib/embedded_associations/version.rb +3 -0
- data/spec/embedded_associations_spec.rb +324 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/app/.gitignore +15 -0
- data/spec/support/app/Rakefile +7 -0
- data/spec/support/app/app/controllers/application_controller.rb +3 -0
- data/spec/support/app/app/controllers/posts_controller.rb +40 -0
- data/spec/support/app/app/models/account.rb +4 -0
- data/spec/support/app/app/models/category.rb +4 -0
- data/spec/support/app/app/models/comment.rb +6 -0
- data/spec/support/app/app/models/post.rb +8 -0
- data/spec/support/app/app/models/tag.rb +4 -0
- data/spec/support/app/app/models/user.rb +7 -0
- data/spec/support/app/app/serializers/account_serializer.rb +3 -0
- data/spec/support/app/app/serializers/category_serializer.rb +3 -0
- data/spec/support/app/app/serializers/comment_serializer.rb +5 -0
- data/spec/support/app/app/serializers/post_serializer.rb +8 -0
- data/spec/support/app/app/serializers/tag_serializer.rb +3 -0
- data/spec/support/app/app/serializers/user_serializer.rb +5 -0
- data/spec/support/app/config.ru +4 -0
- data/spec/support/app/config/application.rb +62 -0
- data/spec/support/app/config/boot.rb +6 -0
- data/spec/support/app/config/database.yml +11 -0
- data/spec/support/app/config/environment.rb +5 -0
- data/spec/support/app/config/environments/development.rb +37 -0
- data/spec/support/app/config/environments/production.rb +67 -0
- data/spec/support/app/config/environments/test.rb +37 -0
- data/spec/support/app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/support/app/config/initializers/inflections.rb +15 -0
- data/spec/support/app/config/initializers/mime_types.rb +5 -0
- data/spec/support/app/config/initializers/secret_token.rb +7 -0
- data/spec/support/app/config/initializers/session_store.rb +8 -0
- data/spec/support/app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/support/app/config/locales/en.yml +5 -0
- data/spec/support/app/config/routes.rb +3 -0
- data/spec/support/app/db/migrate/20130225045501_create_posts.rb +10 -0
- data/spec/support/app/db/migrate/20130225045512_create_comments.rb +10 -0
- data/spec/support/app/db/migrate/20130225173707_create_users.rb +9 -0
- data/spec/support/app/db/migrate/20130225173717_create_accounts.rb +9 -0
- data/spec/support/app/db/migrate/20130226001629_create_tags.rb +9 -0
- data/spec/support/app/db/migrate/20130226001639_create_categories.rb +8 -0
- data/spec/support/app/db/schema.rb +59 -0
- data/spec/support/app/db/seeds.rb +7 -0
- data/spec/support/app/db/structure.sql +19 -0
- data/spec/support/app/lib/assets/.gitkeep +0 -0
- data/spec/support/app/lib/tasks/.gitkeep +0 -0
- data/spec/support/app/log/.gitkeep +0 -0
- data/spec/support/app/script/rails +6 -0
- data/spec/support/serialization_helpers.rb +18 -0
- metadata +168 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
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,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
|