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