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