castle-her 1.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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/.travis.yml +17 -0
- data/.yardopts +2 -0
- data/CONTRIBUTING.md +26 -0
- data/Gemfile +10 -0
- data/LICENSE +7 -0
- data/README.md +1017 -0
- data/Rakefile +11 -0
- data/UPGRADE.md +110 -0
- data/castle-her.gemspec +30 -0
- data/gemfiles/Gemfile.activemodel-3.2.x +7 -0
- data/gemfiles/Gemfile.activemodel-4.0 +7 -0
- data/gemfiles/Gemfile.activemodel-4.1 +7 -0
- data/gemfiles/Gemfile.activemodel-4.2 +7 -0
- data/gemfiles/Gemfile.activemodel-5.0.x +7 -0
- data/lib/castle-her.rb +20 -0
- data/lib/castle-her/api.rb +113 -0
- data/lib/castle-her/collection.rb +12 -0
- data/lib/castle-her/errors.rb +27 -0
- data/lib/castle-her/json_api/model.rb +46 -0
- data/lib/castle-her/middleware.rb +12 -0
- data/lib/castle-her/middleware/accept_json.rb +17 -0
- data/lib/castle-her/middleware/first_level_parse_json.rb +36 -0
- data/lib/castle-her/middleware/json_api_parser.rb +36 -0
- data/lib/castle-her/middleware/parse_json.rb +21 -0
- data/lib/castle-her/middleware/second_level_parse_json.rb +36 -0
- data/lib/castle-her/model.rb +75 -0
- data/lib/castle-her/model/associations.rb +141 -0
- data/lib/castle-her/model/associations/association.rb +103 -0
- data/lib/castle-her/model/associations/association_proxy.rb +45 -0
- data/lib/castle-her/model/associations/belongs_to_association.rb +96 -0
- data/lib/castle-her/model/associations/has_many_association.rb +100 -0
- data/lib/castle-her/model/associations/has_one_association.rb +79 -0
- data/lib/castle-her/model/attributes.rb +284 -0
- data/lib/castle-her/model/base.rb +33 -0
- data/lib/castle-her/model/deprecated_methods.rb +61 -0
- data/lib/castle-her/model/http.rb +114 -0
- data/lib/castle-her/model/introspection.rb +65 -0
- data/lib/castle-her/model/nested_attributes.rb +45 -0
- data/lib/castle-her/model/orm.rb +207 -0
- data/lib/castle-her/model/parse.rb +216 -0
- data/lib/castle-her/model/paths.rb +126 -0
- data/lib/castle-her/model/relation.rb +164 -0
- data/lib/castle-her/version.rb +3 -0
- data/spec/api_spec.rb +114 -0
- data/spec/collection_spec.rb +26 -0
- data/spec/json_api/model_spec.rb +166 -0
- data/spec/middleware/accept_json_spec.rb +10 -0
- data/spec/middleware/first_level_parse_json_spec.rb +62 -0
- data/spec/middleware/json_api_parser_spec.rb +32 -0
- data/spec/middleware/second_level_parse_json_spec.rb +35 -0
- data/spec/model/associations/association_proxy_spec.rb +31 -0
- data/spec/model/associations_spec.rb +504 -0
- data/spec/model/attributes_spec.rb +389 -0
- data/spec/model/callbacks_spec.rb +145 -0
- data/spec/model/dirty_spec.rb +91 -0
- data/spec/model/http_spec.rb +158 -0
- data/spec/model/introspection_spec.rb +76 -0
- data/spec/model/nested_attributes_spec.rb +134 -0
- data/spec/model/orm_spec.rb +506 -0
- data/spec/model/parse_spec.rb +345 -0
- data/spec/model/paths_spec.rb +347 -0
- data/spec/model/relation_spec.rb +226 -0
- data/spec/model/validations_spec.rb +42 -0
- data/spec/model_spec.rb +44 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/extensions/array.rb +5 -0
- data/spec/support/extensions/hash.rb +5 -0
- data/spec/support/macros/her_macros.rb +17 -0
- data/spec/support/macros/model_macros.rb +36 -0
- data/spec/support/macros/request_macros.rb +27 -0
- metadata +290 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe Her::Model::Associations::AssociationProxy do
|
5
|
+
describe "proxy assignment methods" do
|
6
|
+
before do
|
7
|
+
Her::API.setup url: "https://api.example.com" do |builder|
|
8
|
+
builder.use Her::Middleware::FirstLevelParseJSON
|
9
|
+
builder.use Faraday::Request::UrlEncoded
|
10
|
+
builder.adapter :test do |stub|
|
11
|
+
stub.get("/users/1") { |env| [200, {}, { :id => 1, :name => "Tobias Fünke" }.to_json ] }
|
12
|
+
stub.get("/users/1/fish") { |env| [200, {}, { :id => 1, :name => "Tobias's Fish" }.to_json ] }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
spawn_model "User" do
|
16
|
+
has_one :fish
|
17
|
+
end
|
18
|
+
spawn_model "Fish"
|
19
|
+
end
|
20
|
+
|
21
|
+
subject { User.find(1) }
|
22
|
+
|
23
|
+
it "should assign value" do
|
24
|
+
subject.fish.name = "Fishy"
|
25
|
+
expect(subject.fish.name).to eq "Fishy"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
|
@@ -0,0 +1,504 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.join(File.dirname(__FILE__), "../spec_helper.rb")
|
3
|
+
|
4
|
+
describe Her::Model::Associations do
|
5
|
+
context "setting associations without details" do
|
6
|
+
before { spawn_model "Foo::User" }
|
7
|
+
subject { Foo::User.associations }
|
8
|
+
|
9
|
+
context "single has_many association" do
|
10
|
+
before { Foo::User.has_many :comments }
|
11
|
+
its([:has_many]) { should eql [{ :name => :comments, :data_key => :comments, :default => [], :class_name => "Comment", :path => "/comments", :inverse_of => nil }] }
|
12
|
+
end
|
13
|
+
|
14
|
+
context "multiple has_many associations" do
|
15
|
+
before do
|
16
|
+
Foo::User.has_many :comments
|
17
|
+
Foo::User.has_many :posts
|
18
|
+
end
|
19
|
+
|
20
|
+
its([:has_many]) { should eql [{ :name => :comments, :data_key => :comments, :default => [], :class_name => "Comment", :path => "/comments", :inverse_of => nil }, { :name => :posts, :data_key => :posts, :default => [], :class_name => "Post", :path => "/posts", :inverse_of => nil }] }
|
21
|
+
end
|
22
|
+
|
23
|
+
context "single has_one association" do
|
24
|
+
before { Foo::User.has_one :category }
|
25
|
+
its([:has_one]) { should eql [{ :name => :category, :data_key => :category, :default => nil, :class_name => "Category", :path => "/category" }] }
|
26
|
+
end
|
27
|
+
|
28
|
+
context "multiple has_one associations" do
|
29
|
+
before do
|
30
|
+
Foo::User.has_one :category
|
31
|
+
Foo::User.has_one :role
|
32
|
+
end
|
33
|
+
|
34
|
+
its([:has_one]) { should eql [{ :name => :category, :data_key => :category, :default => nil, :class_name => "Category", :path => "/category" }, { :name => :role, :data_key => :role, :default => nil, :class_name => "Role", :path => "/role" }] }
|
35
|
+
end
|
36
|
+
|
37
|
+
context "single belongs_to association" do
|
38
|
+
before { Foo::User.belongs_to :organization }
|
39
|
+
its([:belongs_to]) { should eql [{ :name => :organization, :data_key => :organization, :default => nil, :class_name => "Organization", :foreign_key => "organization_id", :path => "/organizations/:id" }] }
|
40
|
+
end
|
41
|
+
|
42
|
+
context "multiple belongs_to association" do
|
43
|
+
before do
|
44
|
+
Foo::User.belongs_to :organization
|
45
|
+
Foo::User.belongs_to :family
|
46
|
+
end
|
47
|
+
|
48
|
+
its([:belongs_to]) { should eql [{ :name => :organization, :data_key => :organization, :default => nil, :class_name => "Organization", :foreign_key => "organization_id", :path => "/organizations/:id" }, { :name => :family, :data_key => :family, :default => nil, :class_name => "Family", :foreign_key => "family_id", :path => "/families/:id" }] }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "setting associations with details" do
|
53
|
+
before { spawn_model "Foo::User" }
|
54
|
+
subject { Foo::User.associations }
|
55
|
+
|
56
|
+
context "in base class" do
|
57
|
+
context "single has_many association" do
|
58
|
+
before { Foo::User.has_many :comments, :class_name => "Post", :inverse_of => :admin, :data_key => :user_comments, :default => {} }
|
59
|
+
its([:has_many]) { should eql [{ :name => :comments, :data_key => :user_comments, :default => {}, :class_name => "Post", :path => "/comments", :inverse_of => :admin }] }
|
60
|
+
end
|
61
|
+
|
62
|
+
context "single has_one association" do
|
63
|
+
before { Foo::User.has_one :category, :class_name => "Topic", :foreign_key => "topic_id", :data_key => :topic, :default => nil }
|
64
|
+
its([:has_one]) { should eql [{ :name => :category, :data_key => :topic, :default => nil, :class_name => "Topic", :foreign_key => "topic_id", :path => "/category" }] }
|
65
|
+
end
|
66
|
+
|
67
|
+
context "single belongs_to association" do
|
68
|
+
before { Foo::User.belongs_to :organization, :class_name => "Business", :foreign_key => "org_id", :data_key => :org, :default => true }
|
69
|
+
its([:belongs_to]) { should eql [{ :name => :organization, :data_key => :org, :default => true, :class_name => "Business", :foreign_key => "org_id", :path => "/organizations/:id" }] }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "in parent class" do
|
74
|
+
before { Foo::User.has_many :comments, :class_name => "Post" }
|
75
|
+
|
76
|
+
describe "associations accessor" do
|
77
|
+
subject { Class.new(Foo::User).associations }
|
78
|
+
its(:object_id) { should_not eql Foo::User.associations.object_id }
|
79
|
+
its([:has_many]) { should eql [{ :name => :comments, :data_key => :comments, :default => [], :class_name => "Post", :path => "/comments", :inverse_of => nil }] }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "handling associations without details" do
|
85
|
+
before do
|
86
|
+
Her::API.setup :url => "https://api.example.com" do |builder|
|
87
|
+
builder.use Her::Middleware::FirstLevelParseJSON
|
88
|
+
builder.use Faraday::Request::UrlEncoded
|
89
|
+
builder.adapter :test do |stub|
|
90
|
+
stub.get("/users/1") { |env| [200, {}, { :id => 1, :name => "Tobias Fünke", :comments => [{ :comment => { :id => 2, :body => "Tobias, you blow hard!", :user_id => 1 } }, { :comment => { :id => 3, :body => "I wouldn't mind kissing that man between the cheeks, so to speak", :user_id => 1 } }], :role => { :id => 1, :body => "Admin" }, :organization => { :id => 1, :name => "Bluth Company" }, :organization_id => 1 }.to_json] }
|
91
|
+
stub.get("/users/2") { |env| [200, {}, { :id => 2, :name => "Lindsay Fünke", :organization_id => 2 }.to_json] }
|
92
|
+
stub.get("/users/1/comments") { |env| [200, {}, [{ :comment => { :id => 4, :body => "They're having a FIRESALE?" } }].to_json] }
|
93
|
+
stub.get("/users/2/comments") { |env| [200, {}, [{ :comment => { :id => 4, :body => "They're having a FIRESALE?" } }, { :comment => { :id => 5, :body => "Is this the tiny town from Footloose?" } }].to_json] }
|
94
|
+
stub.get("/users/2/comments/5") { |env| [200, {}, { :comment => { :id => 5, :body => "Is this the tiny town from Footloose?" } }.to_json] }
|
95
|
+
stub.get("/users/2/role") { |env| [200, {}, { :id => 2, :body => "User" }.to_json] }
|
96
|
+
stub.get("/users/1/role") { |env| [200, {}, { :id => 3, :body => "User" }.to_json] }
|
97
|
+
stub.get("/users/1/posts") { |env| [200, {}, [{:id => 1, :body => 'blogging stuff', :admin_id => 1 }].to_json] }
|
98
|
+
stub.get("/organizations/1") { |env| [200, {}, { :organization => { :id => 1, :name => "Bluth Company Foo" } }.to_json] }
|
99
|
+
stub.post("/users") { |env| [200, {}, { :id => 5, :name => "Mr. Krabs", :comments => [{ :comment => { :id => 99, :body => "Rodríguez, nasibisibusi?", :user_id => 5 } }], :role => { :id => 1, :body => "Admin" }, :organization => { :id => 3, :name => "Krusty Krab" }, :organization_id => 3 }.to_json] }
|
100
|
+
stub.put("/users/5") { |env| [200, {}, { :id => 5, :name => "Clancy Brown", :comments => [{ :comment => { :id => 99, :body => "Rodríguez, nasibisibusi?", :user_id => 5 } }], :role => { :id => 1, :body => "Admin" }, :organization => { :id => 3, :name => "Krusty Krab" }, :organization_id => 3 }.to_json] }
|
101
|
+
stub.delete("/users/5") { |env| [200, {}, { :id => 5, :name => "Clancy Brown", :comments => [{ :comment => { :id => 99, :body => "Rodríguez, nasibisibusi?", :user_id => 5 } }], :role => { :id => 1, :body => "Admin" }, :organization => { :id => 3, :name => "Krusty Krab" }, :organization_id => 3 }.to_json] }
|
102
|
+
|
103
|
+
stub.get("/organizations/2") do |env|
|
104
|
+
if env[:params]["admin"] == "true"
|
105
|
+
[200, {}, { :organization => { :id => 2, :name => "Bluth Company (admin)" } }.to_json]
|
106
|
+
else
|
107
|
+
[200, {}, { :organization => { :id => 2, :name => "Bluth Company" } }.to_json]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
spawn_model "Foo::User" do
|
114
|
+
has_many :comments, class_name: "Foo::Comment"
|
115
|
+
has_one :role
|
116
|
+
belongs_to :organization
|
117
|
+
has_many :posts, :inverse_of => :admin
|
118
|
+
end
|
119
|
+
spawn_model "Foo::Comment" do
|
120
|
+
belongs_to :user
|
121
|
+
parse_root_in_json true
|
122
|
+
end
|
123
|
+
spawn_model "Foo::Post" do
|
124
|
+
belongs_to :admin, :class_name => 'Foo::User'
|
125
|
+
end
|
126
|
+
|
127
|
+
spawn_model "Foo::Organization" do
|
128
|
+
parse_root_in_json true
|
129
|
+
end
|
130
|
+
|
131
|
+
spawn_model "Foo::Role"
|
132
|
+
|
133
|
+
@user_with_included_data = Foo::User.find(1)
|
134
|
+
@user_without_included_data = Foo::User.find(2)
|
135
|
+
@user_without_organization_and_not_persisted = Foo::User.new(organization_id: nil, name: "Katlin Fünke")
|
136
|
+
end
|
137
|
+
|
138
|
+
let(:user_with_included_data_after_create) { Foo::User.create }
|
139
|
+
let(:user_with_included_data_after_save_existing) { Foo::User.save_existing(5, :name => "Clancy Brown") }
|
140
|
+
let(:user_with_included_data_after_destroy) { Foo::User.new(:id => 5).destroy }
|
141
|
+
let(:comment_without_included_parent_data) { Foo::Comment.new(:id => 7, :user_id => 1) }
|
142
|
+
|
143
|
+
it "maps an array of included data through has_many" do
|
144
|
+
expect(@user_with_included_data.comments.first).to be_a(Foo::Comment)
|
145
|
+
expect(@user_with_included_data.comments.length).to eq(2)
|
146
|
+
expect(@user_with_included_data.comments.first.id).to eq(2)
|
147
|
+
expect(@user_with_included_data.comments.first.body).to eq("Tobias, you blow hard!")
|
148
|
+
end
|
149
|
+
|
150
|
+
it "does not refetch the parents models data if they have been fetched before" do
|
151
|
+
expect(@user_with_included_data.comments.first.user.object_id).to eq(@user_with_included_data.object_id)
|
152
|
+
end
|
153
|
+
|
154
|
+
it "does fetch the parent models data only once" do
|
155
|
+
expect(comment_without_included_parent_data.user.object_id).to eq(comment_without_included_parent_data.user.object_id)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "does fetch the parent models data that was cached if called with parameters" do
|
159
|
+
expect(comment_without_included_parent_data.user.object_id).not_to eq(comment_without_included_parent_data.user.where(:a => 2).object_id)
|
160
|
+
end
|
161
|
+
|
162
|
+
it "uses the given inverse_of key to set the parent model" do
|
163
|
+
expect(@user_with_included_data.posts.first.admin.object_id).to eq(@user_with_included_data.object_id)
|
164
|
+
end
|
165
|
+
|
166
|
+
it "fetches data that was not included through has_many" do
|
167
|
+
expect(@user_without_included_data.comments.first).to be_a(Foo::Comment)
|
168
|
+
expect(@user_without_included_data.comments.length).to eq(2)
|
169
|
+
expect(@user_without_included_data.comments.first.id).to eq(4)
|
170
|
+
expect(@user_without_included_data.comments.first.body).to eq("They're having a FIRESALE?")
|
171
|
+
end
|
172
|
+
|
173
|
+
it "fetches has_many data even if it was included, only if called with parameters" do
|
174
|
+
expect(@user_with_included_data.comments.where(:foo_id => 1).length).to eq(1)
|
175
|
+
end
|
176
|
+
|
177
|
+
it "fetches data that was not included through has_many only once" do
|
178
|
+
expect(@user_without_included_data.comments.first.object_id).to eq(@user_without_included_data.comments.first.object_id)
|
179
|
+
end
|
180
|
+
|
181
|
+
it "fetches data that was cached through has_many if called with parameters" do
|
182
|
+
expect(@user_without_included_data.comments.first.object_id).not_to eq(@user_without_included_data.comments.where(:foo_id => 1).first.object_id)
|
183
|
+
end
|
184
|
+
|
185
|
+
it "maps an array of included data through has_one" do
|
186
|
+
expect(@user_with_included_data.role).to be_a(Foo::Role)
|
187
|
+
expect(@user_with_included_data.role.object_id).to eq(@user_with_included_data.role.object_id)
|
188
|
+
expect(@user_with_included_data.role.id).to eq(1)
|
189
|
+
expect(@user_with_included_data.role.body).to eq("Admin")
|
190
|
+
end
|
191
|
+
|
192
|
+
it "fetches data that was not included through has_one" do
|
193
|
+
expect(@user_without_included_data.role).to be_a(Foo::Role)
|
194
|
+
expect(@user_without_included_data.role.id).to eq(2)
|
195
|
+
expect(@user_without_included_data.role.body).to eq("User")
|
196
|
+
end
|
197
|
+
|
198
|
+
it "fetches has_one data even if it was included, only if called with parameters" do
|
199
|
+
expect(@user_with_included_data.role.where(:foo_id => 2).id).to eq(3)
|
200
|
+
end
|
201
|
+
|
202
|
+
it "maps an array of included data through belongs_to" do
|
203
|
+
expect(@user_with_included_data.organization).to be_a(Foo::Organization)
|
204
|
+
expect(@user_with_included_data.organization.id).to eq(1)
|
205
|
+
expect(@user_with_included_data.organization.name).to eq("Bluth Company")
|
206
|
+
end
|
207
|
+
|
208
|
+
it "fetches data that was not included through belongs_to" do
|
209
|
+
expect(@user_without_included_data.organization).to be_a(Foo::Organization)
|
210
|
+
expect(@user_without_included_data.organization.id).to eq(2)
|
211
|
+
expect(@user_without_included_data.organization.name).to eq("Bluth Company")
|
212
|
+
end
|
213
|
+
|
214
|
+
it "returns nil if the foreign key is nil" do
|
215
|
+
expect(@user_without_organization_and_not_persisted.organization).to eq(nil)
|
216
|
+
end
|
217
|
+
|
218
|
+
it "fetches belongs_to data even if it was included, only if called with parameters" do
|
219
|
+
expect(@user_with_included_data.organization.where(:foo_id => 1).name).to eq("Bluth Company Foo")
|
220
|
+
end
|
221
|
+
|
222
|
+
it "can tell if it has a association" do
|
223
|
+
expect(@user_without_included_data.has_association?(:unknown_association)).to eq(false)
|
224
|
+
expect(@user_without_included_data.has_association?(:organization)).to eq(true)
|
225
|
+
end
|
226
|
+
|
227
|
+
it "fetches the resource corresponding to a named association" do
|
228
|
+
expect(@user_without_included_data.get_association(:unknown_association)).to eq(nil)
|
229
|
+
expect(@user_without_included_data.get_association(:organization).name).to eq("Bluth Company")
|
230
|
+
end
|
231
|
+
|
232
|
+
it "pass query string parameters when additional arguments are passed" do
|
233
|
+
expect(@user_without_included_data.organization.where(:admin => true).name).to eq("Bluth Company (admin)")
|
234
|
+
expect(@user_without_included_data.organization.name).to eq("Bluth Company")
|
235
|
+
end
|
236
|
+
|
237
|
+
it "fetches data with the specified id when calling find" do
|
238
|
+
comment = @user_without_included_data.comments.find(5)
|
239
|
+
expect(comment).to be_a(Foo::Comment)
|
240
|
+
expect(comment.id).to eq(5)
|
241
|
+
end
|
242
|
+
|
243
|
+
it "'s associations responds to #empty?" do
|
244
|
+
expect(@user_without_included_data.organization.respond_to?(:empty?)).to be_truthy
|
245
|
+
expect(@user_without_included_data.organization).not_to be_empty
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'includes has_many relationships in params by default' do
|
249
|
+
params = @user_with_included_data.to_params
|
250
|
+
expect(params[:comments]).to be_kind_of(Array)
|
251
|
+
expect(params[:comments].length).to eq(2)
|
252
|
+
end
|
253
|
+
|
254
|
+
[:create, :save_existing, :destroy].each do |type|
|
255
|
+
context "after #{type}" do
|
256
|
+
let(:subject) { self.send("user_with_included_data_after_#{type}")}
|
257
|
+
|
258
|
+
it "maps an array of included data through has_many" do
|
259
|
+
expect(subject.comments.first).to be_a(Foo::Comment)
|
260
|
+
expect(subject.comments.length).to eq(1)
|
261
|
+
expect(subject.comments.first.id).to eq(99)
|
262
|
+
expect(subject.comments.first.body).to eq("Rodríguez, nasibisibusi?")
|
263
|
+
end
|
264
|
+
|
265
|
+
it "maps an array of included data through has_one" do
|
266
|
+
expect(subject.role).to be_a(Foo::Role)
|
267
|
+
expect(subject.role.id).to eq(1)
|
268
|
+
expect(subject.role.body).to eq("Admin")
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
context "handling associations with details in active_model_serializers format" do
|
275
|
+
before do
|
276
|
+
Her::API.setup :url => "https://api.example.com" do |builder|
|
277
|
+
builder.use Her::Middleware::FirstLevelParseJSON
|
278
|
+
builder.use Faraday::Request::UrlEncoded
|
279
|
+
builder.adapter :test do |stub|
|
280
|
+
stub.get("/users/1") { |env| [200, {}, { :user => { :id => 1, :name => "Tobias Fünke", :comments => [{ :id => 2, :body => "Tobias, you blow hard!", :user_id => 1 }, { :id => 3, :body => "I wouldn't mind kissing that man between the cheeks, so to speak", :user_id => 1 }], :role => { :id => 1, :body => "Admin" }, :organization => { :id => 1, :name => "Bluth Company" }, :organization_id => 1 } }.to_json] }
|
281
|
+
stub.get("/users/2") { |env| [200, {}, { :user => { :id => 2, :name => "Lindsay Fünke", :organization_id => 1 } }.to_json] }
|
282
|
+
stub.get("/users/1/comments") { |env| [200, {}, { :comments => [{ :id => 4, :body => "They're having a FIRESALE?" }] }.to_json] }
|
283
|
+
stub.get("/users/2/comments") { |env| [200, {}, { :comments => [{ :id => 4, :body => "They're having a FIRESALE?" }, { :id => 5, :body => "Is this the tiny town from Footloose?" }] }.to_json] }
|
284
|
+
stub.get("/users/2/comments/5") { |env| [200, {}, { :comment => { :id => 5, :body => "Is this the tiny town from Footloose?" } }.to_json] }
|
285
|
+
stub.get("/organizations/1") { |env| [200, {}, { :organization => { :id => 1, :name => "Bluth Company Foo" } }.to_json] }
|
286
|
+
end
|
287
|
+
end
|
288
|
+
spawn_model "Foo::User" do
|
289
|
+
parse_root_in_json true, :format => :active_model_serializers
|
290
|
+
has_many :comments, class_name: "Foo::Comment"
|
291
|
+
belongs_to :organization
|
292
|
+
end
|
293
|
+
spawn_model "Foo::Comment" do
|
294
|
+
belongs_to :user
|
295
|
+
parse_root_in_json true, :format => :active_model_serializers
|
296
|
+
end
|
297
|
+
spawn_model "Foo::Organization" do
|
298
|
+
parse_root_in_json true, :format => :active_model_serializers
|
299
|
+
end
|
300
|
+
|
301
|
+
@user_with_included_data = Foo::User.find(1)
|
302
|
+
@user_without_included_data = Foo::User.find(2)
|
303
|
+
end
|
304
|
+
|
305
|
+
it "maps an array of included data through has_many" do
|
306
|
+
expect(@user_with_included_data.comments.first).to be_a(Foo::Comment)
|
307
|
+
expect(@user_with_included_data.comments.length).to eq(2)
|
308
|
+
expect(@user_with_included_data.comments.first.id).to eq(2)
|
309
|
+
expect(@user_with_included_data.comments.first.body).to eq("Tobias, you blow hard!")
|
310
|
+
end
|
311
|
+
|
312
|
+
it "does not refetch the parents models data if they have been fetched before" do
|
313
|
+
expect(@user_with_included_data.comments.first.user.object_id).to eq(@user_with_included_data.object_id)
|
314
|
+
end
|
315
|
+
|
316
|
+
it "fetches data that was not included through has_many" do
|
317
|
+
expect(@user_without_included_data.comments.first).to be_a(Foo::Comment)
|
318
|
+
expect(@user_without_included_data.comments.length).to eq(2)
|
319
|
+
expect(@user_without_included_data.comments.first.id).to eq(4)
|
320
|
+
expect(@user_without_included_data.comments.first.body).to eq("They're having a FIRESALE?")
|
321
|
+
end
|
322
|
+
|
323
|
+
it "fetches has_many data even if it was included, only if called with parameters" do
|
324
|
+
expect(@user_with_included_data.comments.where(:foo_id => 1).length).to eq(1)
|
325
|
+
end
|
326
|
+
|
327
|
+
it "maps an array of included data through belongs_to" do
|
328
|
+
expect(@user_with_included_data.organization).to be_a(Foo::Organization)
|
329
|
+
expect(@user_with_included_data.organization.id).to eq(1)
|
330
|
+
expect(@user_with_included_data.organization.name).to eq("Bluth Company")
|
331
|
+
end
|
332
|
+
|
333
|
+
it "fetches data that was not included through belongs_to" do
|
334
|
+
expect(@user_without_included_data.organization).to be_a(Foo::Organization)
|
335
|
+
expect(@user_without_included_data.organization.id).to eq(1)
|
336
|
+
expect(@user_without_included_data.organization.name).to eq("Bluth Company Foo")
|
337
|
+
end
|
338
|
+
|
339
|
+
it "fetches belongs_to data even if it was included, only if called with parameters" do
|
340
|
+
expect(@user_with_included_data.organization.where(:foo_id => 1).name).to eq("Bluth Company Foo")
|
341
|
+
end
|
342
|
+
|
343
|
+
it "fetches data with the specified id when calling find" do
|
344
|
+
comment = @user_without_included_data.comments.find(5)
|
345
|
+
expect(comment).to be_a(Foo::Comment)
|
346
|
+
expect(comment.id).to eq(5)
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'includes has_many relationships in params by default' do
|
350
|
+
params = @user_with_included_data.to_params
|
351
|
+
expect(params[:comments]).to be_kind_of(Array)
|
352
|
+
expect(params[:comments].length).to eq(2)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
context "handling associations with details" do
|
357
|
+
before do
|
358
|
+
Her::API.setup :url => "https://api.example.com" do |builder|
|
359
|
+
builder.use Her::Middleware::FirstLevelParseJSON
|
360
|
+
builder.use Faraday::Request::UrlEncoded
|
361
|
+
builder.adapter :test do |stub|
|
362
|
+
stub.get("/users/1") { |env| [200, {}, { :id => 1, :name => "Tobias Fünke", :organization => { :id => 1, :name => "Bluth Company Inc." }, :organization_id => 1 }.to_json] }
|
363
|
+
stub.get("/users/4") { |env| [200, {}, { :id => 1, :name => "Tobias Fünke", :organization => { :id => 1, :name => "Bluth Company Inc." } }.to_json] }
|
364
|
+
stub.get("/users/2") { |env| [200, {}, { :id => 2, :name => "Lindsay Fünke", :organization_id => 1 }.to_json] }
|
365
|
+
stub.get("/users/3") { |env| [200, {}, { :id => 2, :name => "Lindsay Fünke", :company => nil }.to_json] }
|
366
|
+
stub.get("/companies/1") { |env| [200, {}, { :id => 1, :name => "Bluth Company" }.to_json] }
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
spawn_model "Foo::User" do
|
371
|
+
belongs_to :company, :path => "/organizations/:id", :foreign_key => :organization_id, :data_key => :organization
|
372
|
+
end
|
373
|
+
|
374
|
+
spawn_model "Foo::Company"
|
375
|
+
|
376
|
+
@user_with_included_data = Foo::User.find(1)
|
377
|
+
@user_without_included_data = Foo::User.find(2)
|
378
|
+
@user_with_included_nil_data = Foo::User.find(3)
|
379
|
+
@user_with_included_data_but_no_fk = Foo::User.find(4)
|
380
|
+
end
|
381
|
+
|
382
|
+
it "maps an array of included data through belongs_to" do
|
383
|
+
expect(@user_with_included_data.company).to be_a(Foo::Company)
|
384
|
+
expect(@user_with_included_data.company.id).to eq(1)
|
385
|
+
expect(@user_with_included_data.company.name).to eq("Bluth Company Inc.")
|
386
|
+
end
|
387
|
+
|
388
|
+
it "does not map included data if it’s nil" do
|
389
|
+
expect(@user_with_included_nil_data.company).to eq(nil)
|
390
|
+
end
|
391
|
+
|
392
|
+
it "fetches data that was not included through belongs_to" do
|
393
|
+
expect(@user_without_included_data.company).to be_a(Foo::Company)
|
394
|
+
expect(@user_without_included_data.company.id).to eq(1)
|
395
|
+
expect(@user_without_included_data.company.name).to eq("Bluth Company")
|
396
|
+
end
|
397
|
+
|
398
|
+
it "does not require foreugn key to have nested object" do
|
399
|
+
expect(@user_with_included_data_but_no_fk.company.name).to eq("Bluth Company Inc.")
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
context "object returned by the association method" do
|
404
|
+
before do
|
405
|
+
spawn_model "Foo::Role" do
|
406
|
+
def present?
|
407
|
+
"of_course"
|
408
|
+
end
|
409
|
+
end
|
410
|
+
spawn_model "Foo::User" do
|
411
|
+
has_one :role
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
let(:associated_value) { Foo::Role.new }
|
416
|
+
let(:user_with_role) do
|
417
|
+
Foo::User.new.tap { |user| user.role = associated_value }
|
418
|
+
end
|
419
|
+
|
420
|
+
subject { user_with_role.role }
|
421
|
+
|
422
|
+
it "doesnt mask the object's basic methods" do
|
423
|
+
expect(subject.class).to eq(Foo::Role)
|
424
|
+
end
|
425
|
+
|
426
|
+
it "doesnt mask core methods like extend" do
|
427
|
+
committer = Module.new
|
428
|
+
subject.extend committer
|
429
|
+
expect(associated_value).to be_kind_of committer
|
430
|
+
end
|
431
|
+
|
432
|
+
it "can return the association object" do
|
433
|
+
expect(subject.association).to be_kind_of(Her::Model::Associations::Association)
|
434
|
+
end
|
435
|
+
|
436
|
+
it "still can call fetch via the association" do
|
437
|
+
expect(subject.association.fetch).to eq(associated_value)
|
438
|
+
end
|
439
|
+
|
440
|
+
it "calls missing methods on associated value" do
|
441
|
+
expect(subject.present?).to eq("of_course")
|
442
|
+
end
|
443
|
+
|
444
|
+
it "can use association methods like where" do
|
445
|
+
expect(subject.where(role: 'committer').association.
|
446
|
+
params).to include(:role)
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
context "building and creating association data" do
|
451
|
+
before do
|
452
|
+
spawn_model "Foo::Comment"
|
453
|
+
spawn_model "Foo::User" do
|
454
|
+
has_many :comments
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
context "with #build" do
|
459
|
+
it "takes the parent primary key" do
|
460
|
+
@comment = Foo::User.new(:id => 10).comments.build(:body => "Hello!")
|
461
|
+
expect(@comment.body).to eq("Hello!")
|
462
|
+
expect(@comment.user_id).to eq(10)
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
context "with #create" do
|
467
|
+
before do
|
468
|
+
Her::API.setup :url => "https://api.example.com" do |builder|
|
469
|
+
builder.use Her::Middleware::FirstLevelParseJSON
|
470
|
+
builder.use Faraday::Request::UrlEncoded
|
471
|
+
builder.adapter :test do |stub|
|
472
|
+
stub.get("/users/10") { |env| [200, {}, { :id => 10 }.to_json] }
|
473
|
+
stub.post("/comments") { |env| [200, {}, { :id => 1, :body => Faraday::Utils.parse_query(env[:body])['body'], :user_id => Faraday::Utils.parse_query(env[:body])['user_id'].to_i }.to_json] }
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
Foo::User.use_api Her::API.default_api
|
478
|
+
Foo::Comment.use_api Her::API.default_api
|
479
|
+
end
|
480
|
+
|
481
|
+
it "takes the parent primary key and saves the resource" do
|
482
|
+
@user = Foo::User.find(10)
|
483
|
+
@comment = @user.comments.create(:body => "Hello!")
|
484
|
+
expect(@comment.id).to eq(1)
|
485
|
+
expect(@comment.body).to eq("Hello!")
|
486
|
+
expect(@comment.user_id).to eq(10)
|
487
|
+
expect(@user.comments).to eq([@comment])
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
context "with #new" do
|
492
|
+
it "creates nested models from hash attibutes" do
|
493
|
+
user = Foo::User.new(:name => "vic", :comments => [{:text => "hello"}])
|
494
|
+
expect(user.comments.first.text).to eq("hello")
|
495
|
+
end
|
496
|
+
|
497
|
+
it "assigns nested models if given as already constructed objects" do
|
498
|
+
bye = Foo::Comment.new(:text => "goodbye")
|
499
|
+
user = Foo::User.new(:name => 'vic', :comments => [bye])
|
500
|
+
expect(user.comments.first.text).to eq('goodbye')
|
501
|
+
end
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|