her 0.8.2 → 0.10.4

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -1
  3. data/.rubocop.yml +1291 -0
  4. data/.travis.yml +6 -1
  5. data/README.md +29 -11
  6. data/her.gemspec +3 -5
  7. data/lib/her/api.rb +16 -9
  8. data/lib/her/middleware/json_api_parser.rb +1 -1
  9. data/lib/her/model/associations/association.rb +32 -5
  10. data/lib/her/model/associations/association_proxy.rb +1 -1
  11. data/lib/her/model/associations/belongs_to_association.rb +1 -1
  12. data/lib/her/model/associations/has_many_association.rb +3 -3
  13. data/lib/her/model/attributes.rb +105 -75
  14. data/lib/her/model/http.rb +3 -3
  15. data/lib/her/model/introspection.rb +1 -1
  16. data/lib/her/model/orm.rb +96 -19
  17. data/lib/her/model/parse.rb +27 -17
  18. data/lib/her/model/relation.rb +46 -2
  19. data/lib/her/version.rb +1 -1
  20. data/spec/api_spec.rb +34 -31
  21. data/spec/collection_spec.rb +25 -10
  22. data/spec/json_api/model_spec.rb +75 -72
  23. data/spec/middleware/accept_json_spec.rb +1 -1
  24. data/spec/middleware/first_level_parse_json_spec.rb +20 -20
  25. data/spec/middleware/json_api_parser_spec.rb +26 -7
  26. data/spec/middleware/second_level_parse_json_spec.rb +8 -9
  27. data/spec/model/associations/association_proxy_spec.rb +2 -5
  28. data/spec/model/associations_spec.rb +617 -295
  29. data/spec/model/attributes_spec.rb +114 -107
  30. data/spec/model/callbacks_spec.rb +59 -27
  31. data/spec/model/dirty_spec.rb +70 -29
  32. data/spec/model/http_spec.rb +67 -35
  33. data/spec/model/introspection_spec.rb +26 -22
  34. data/spec/model/nested_attributes_spec.rb +31 -31
  35. data/spec/model/orm_spec.rb +332 -157
  36. data/spec/model/parse_spec.rb +250 -77
  37. data/spec/model/paths_spec.rb +109 -109
  38. data/spec/model/relation_spec.rb +89 -69
  39. data/spec/model/validations_spec.rb +6 -6
  40. data/spec/model_spec.rb +17 -17
  41. data/spec/spec_helper.rb +2 -3
  42. data/spec/support/macros/model_macros.rb +2 -2
  43. metadata +36 -63
@@ -4,124 +4,279 @@ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
4
4
  describe Her::Model::Associations do
5
5
  context "setting associations without details" do
6
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
7
+ subject(:associations) { Foo::User.associations }
8
+
9
+ describe "has_many associations" do
10
+ subject { associations[:has_many] }
11
+
12
+ context "single" do
13
+ let(:comments_association) do
14
+ {
15
+ name: :comments,
16
+ data_key: :comments,
17
+ default: [],
18
+ class_name: "Comment",
19
+ path: "/comments",
20
+ inverse_of: nil
21
+ }
22
+ end
23
+ before { Foo::User.has_many :comments }
13
24
 
14
- context "multiple has_many associations" do
15
- before do
16
- Foo::User.has_many :comments
17
- Foo::User.has_many :posts
25
+ it { is_expected.to eql [comments_association] }
18
26
  end
19
27
 
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
28
+ context "multiple" do
29
+ let(:comments_association) do
30
+ {
31
+ name: :comments,
32
+ data_key: :comments,
33
+ default: [],
34
+ class_name: "Comment",
35
+ path: "/comments",
36
+ inverse_of: nil
37
+ }
38
+ end
39
+ let(:posts_association) do
40
+ {
41
+ name: :posts,
42
+ data_key: :posts,
43
+ default: [],
44
+ class_name: "Post",
45
+ path: "/posts",
46
+ inverse_of: nil
47
+ }
48
+ end
49
+ before do
50
+ Foo::User.has_many :comments
51
+ Foo::User.has_many :posts
52
+ end
22
53
 
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" }] }
54
+ it { is_expected.to eql [comments_association, posts_association] }
55
+ end
26
56
  end
27
57
 
28
- context "multiple has_one associations" do
29
- before do
30
- Foo::User.has_one :category
31
- Foo::User.has_one :role
58
+ describe "has_one associations" do
59
+ subject { associations[:has_one] }
60
+
61
+ context "single" do
62
+ let(:category_association) do
63
+ {
64
+ name: :category,
65
+ data_key: :category,
66
+ default: nil,
67
+ class_name: "Category",
68
+ path: "/category"
69
+ }
70
+ end
71
+ before { Foo::User.has_one :category }
72
+
73
+ it { is_expected.to eql [category_association] }
32
74
  end
33
75
 
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
76
+ context "multiple" do
77
+ let(:category_association) do
78
+ {
79
+ name: :category,
80
+ data_key: :category,
81
+ default: nil,
82
+ class_name: "Category",
83
+ path: "/category"
84
+ }
85
+ end
86
+ let(:role_association) do
87
+ {
88
+ name: :role,
89
+ data_key: :role,
90
+ default: nil,
91
+ class_name: "Role",
92
+ path: "/role"
93
+ }
94
+ end
95
+ before do
96
+ Foo::User.has_one :category
97
+ Foo::User.has_one :role
98
+ end
36
99
 
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" }] }
100
+ it { is_expected.to eql [category_association, role_association] }
101
+ end
40
102
  end
41
103
 
42
- context "multiple belongs_to association" do
43
- before do
44
- Foo::User.belongs_to :organization
45
- Foo::User.belongs_to :family
104
+ describe "belongs_to associations" do
105
+ subject { associations[:belongs_to] }
106
+
107
+ context "single" do
108
+ let(:organization_association) do
109
+ {
110
+ name: :organization,
111
+ data_key: :organization,
112
+ default: nil,
113
+ class_name: "Organization",
114
+ foreign_key: "organization_id",
115
+ path: "/organizations/:id"
116
+ }
117
+ end
118
+ before { Foo::User.belongs_to :organization }
119
+
120
+ it { is_expected.to eql [organization_association] }
46
121
  end
47
122
 
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" }] }
123
+ context "multiple" do
124
+ let(:organization_association) do
125
+ {
126
+ name: :organization,
127
+ data_key: :organization,
128
+ default: nil,
129
+ class_name: "Organization",
130
+ foreign_key: "organization_id",
131
+ path: "/organizations/:id"
132
+ }
133
+ end
134
+ let(:family_association) do
135
+ {
136
+ name: :family,
137
+ data_key: :family,
138
+ default: nil,
139
+ class_name: "Family",
140
+ foreign_key: "family_id",
141
+ path: "/families/:id"
142
+ }
143
+ end
144
+ before do
145
+ Foo::User.belongs_to :organization
146
+ Foo::User.belongs_to :family
147
+ end
148
+
149
+ it { is_expected.to eql [organization_association, family_association] }
150
+ end
49
151
  end
50
152
  end
51
153
 
52
154
  context "setting associations with details" do
53
155
  before { spawn_model "Foo::User" }
54
- subject { Foo::User.associations }
156
+ subject(:associations) { Foo::User.associations }
55
157
 
56
158
  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 }] }
159
+ describe "has_many associations" do
160
+ subject { associations[:has_many] }
161
+
162
+ context "single" do
163
+ let(:comments_association) do
164
+ {
165
+ name: :comments,
166
+ data_key: :user_comments,
167
+ default: {},
168
+ class_name: "Post",
169
+ path: "/comments",
170
+ inverse_of: :admin
171
+ }
172
+ end
173
+ before do
174
+ Foo::User.has_many :comments, class_name: "Post",
175
+ inverse_of: :admin,
176
+ data_key: :user_comments,
177
+ default: {}
178
+ end
179
+
180
+ it { is_expected.to eql [comments_association] }
181
+ end
60
182
  end
61
183
 
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" }] }
184
+ describe "has_one associations" do
185
+ subject { associations[:has_one] }
186
+
187
+ context "single" do
188
+ let(:category_association) do
189
+ {
190
+ name: :category,
191
+ data_key: :topic,
192
+ default: nil,
193
+ class_name: "Topic",
194
+ foreign_key: "topic_id",
195
+ path: "/category"
196
+ }
197
+ end
198
+ before do
199
+ Foo::User.has_one :category, class_name: "Topic",
200
+ foreign_key: "topic_id",
201
+ data_key: :topic, default: nil
202
+ end
203
+
204
+ it { is_expected.to eql [category_association] }
205
+ end
65
206
  end
66
207
 
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" }] }
208
+ describe "belongs_to associations" do
209
+ subject { associations[:belongs_to] }
210
+
211
+ context "single" do
212
+ let(:organization_association) do
213
+ {
214
+ name: :organization,
215
+ data_key: :org,
216
+ default: true,
217
+ class_name: "Business",
218
+ foreign_key: "org_id",
219
+ path: "/organizations/:id"
220
+ }
221
+ end
222
+ before do
223
+ Foo::User.belongs_to :organization, class_name: "Business",
224
+ foreign_key: "org_id",
225
+ data_key: :org,
226
+ default: true
227
+ end
228
+
229
+ it { is_expected.to eql [organization_association] }
230
+ end
70
231
  end
71
232
  end
72
233
 
73
234
  context "in parent class" do
74
- before { Foo::User.has_many :comments, :class_name => "Post" }
235
+ before { Foo::User.has_many :comments, class_name: "Post" }
75
236
 
76
237
  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 }] }
238
+ subject(:associations) { Class.new(Foo::User).associations }
239
+
240
+ describe "#object_id" do
241
+ subject { associations.object_id }
242
+ it { is_expected.not_to eql Foo::User.associations.object_id }
243
+ end
244
+
245
+ describe "[:has_many]" do
246
+ subject { associations[:has_many] }
247
+ let(:association) do
248
+ {
249
+ name: :comments,
250
+ data_key: :comments,
251
+ default: [],
252
+ class_name: "Post",
253
+ path: "/comments",
254
+ inverse_of: nil
255
+ }
256
+ end
257
+
258
+ it { is_expected.to eql [association] }
259
+ end
80
260
  end
81
261
  end
82
262
  end
83
263
 
84
264
  context "handling associations without details" do
85
265
  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
266
  spawn_model "Foo::User" do
114
267
  has_many :comments, class_name: "Foo::Comment"
115
- has_one :role
116
- belongs_to :organization
117
- has_many :posts, :inverse_of => :admin
268
+ has_one :role, class_name: "Foo::Role"
269
+ belongs_to :organization, class_name: "Foo::Organization"
270
+ has_many :posts, inverse_of: :admin
118
271
  end
272
+
119
273
  spawn_model "Foo::Comment" do
120
274
  belongs_to :user
121
275
  parse_root_in_json true
122
276
  end
277
+
123
278
  spawn_model "Foo::Post" do
124
- belongs_to :admin, :class_name => 'Foo::User'
279
+ belongs_to :admin, class_name: "Foo::User"
125
280
  end
126
281
 
127
282
  spawn_model "Foo::Organization" do
@@ -129,143 +284,243 @@ describe Her::Model::Associations do
129
284
  end
130
285
 
131
286
  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
287
  end
137
288
 
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) }
289
+ context "with included data" do
290
+ before(:context) do
291
+ Her::API.setup url: "https://api.example.com" do |builder|
292
+ builder.use Her::Middleware::FirstLevelParseJSON
293
+ builder.use Faraday::Request::UrlEncoded
294
+ builder.adapter :test do |stub|
295
+ 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] }
296
+ stub.get("/users/1/comments") { [200, {}, [{ comment: { id: 4, body: "They're having a FIRESALE?" } }].to_json] }
297
+ stub.get("/users/1/role") { [200, {}, { id: 3, body: "User" }.to_json] }
298
+ stub.get("/users/1/posts") { [200, {}, [{ id: 1, body: "blogging stuff", admin_id: 1 }].to_json] }
299
+ stub.get("/organizations/1") { [200, {}, { organization: { id: 1, name: "Bluth Company Foo" } }.to_json] }
300
+ end
301
+ end
302
+ end
142
303
 
143
- it "maps an array of included data through has_many" do
144
- @user_with_included_data.comments.first.should be_a(Foo::Comment)
145
- @user_with_included_data.comments.length.should == 2
146
- @user_with_included_data.comments.first.id.should == 2
147
- @user_with_included_data.comments.first.body.should == "Tobias, you blow hard!"
148
- end
304
+ let(:user) { Foo::User.find(1) }
305
+ let(:user_params) { user.to_params }
149
306
 
150
- 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.should == @user_with_included_data.object_id
152
- end
307
+ it "maps an array of included data through has_many" do
308
+ expect(user.comments.first).to be_a(Foo::Comment)
309
+ expect(user.comments.length).to eq(2)
310
+ expect(user.comments.first.id).to eq(2)
311
+ expect(user.comments.first.body).to eq("Tobias, you blow hard!")
312
+ end
153
313
 
154
- it "does fetch the parent models data only once" do
155
- comment_without_included_parent_data.user.object_id.should == comment_without_included_parent_data.user.object_id
156
- end
314
+ it "does not refetch the parents models data if they have been fetched before" do
315
+ expect(user.comments.first.user.object_id).to eq(user.object_id)
316
+ end
157
317
 
158
- it "does fetch the parent models data that was cached if called with parameters" do
159
- comment_without_included_parent_data.user.object_id.should_not == comment_without_included_parent_data.user.where(:a => 2).object_id
160
- end
318
+ it "uses the given inverse_of key to set the parent model" do
319
+ expect(user.posts.first.admin.object_id).to eq(user.object_id)
320
+ end
161
321
 
162
- it "uses the given inverse_of key to set the parent model" do
163
- @user_with_included_data.posts.first.admin.object_id.should == @user_with_included_data.object_id
164
- end
322
+ it "fetches has_many data even if it was included, only if called with parameters" do
323
+ expect(user.comments.where(foo_id: 1).length).to eq(1)
324
+ end
165
325
 
166
- it "fetches data that was not included through has_many" do
167
- @user_without_included_data.comments.first.should be_a(Foo::Comment)
168
- @user_without_included_data.comments.length.should == 2
169
- @user_without_included_data.comments.first.id.should == 4
170
- @user_without_included_data.comments.first.body.should == "They're having a FIRESALE?"
171
- end
326
+ it "maps an array of included data through has_one" do
327
+ expect(user.role).to be_a(Foo::Role)
328
+ expect(user.role.object_id).to eq(user.role.object_id)
329
+ expect(user.role.id).to eq(1)
330
+ expect(user.role.body).to eq("Admin")
331
+ end
172
332
 
173
- it "fetches has_many data even if it was included, only if called with parameters" do
174
- @user_with_included_data.comments.where(:foo_id => 1).length.should == 1
175
- end
333
+ it "fetches has_one data even if it was included, only if called with parameters" do
334
+ expect(user.role.where(foo_id: 2).id).to eq(3)
335
+ end
176
336
 
177
- it "fetches data that was not included through has_many only once" do
178
- @user_without_included_data.comments.first.object_id.should == @user_without_included_data.comments.first.object_id
179
- end
337
+ it "maps an array of included data through belongs_to" do
338
+ expect(user.organization).to be_a(Foo::Organization)
339
+ expect(user.organization.id).to eq(1)
340
+ expect(user.organization.name).to eq("Bluth Company")
341
+ end
180
342
 
181
- it "fetches data that was cached through has_many if called with parameters" do
182
- @user_without_included_data.comments.first.object_id.should_not == @user_without_included_data.comments.where(:foo_id => 1).first.object_id
183
- end
343
+ it "fetches belongs_to data even if it was included, only if called with parameters" do
344
+ expect(user.organization.where(foo_id: 1).name).to eq("Bluth Company Foo")
345
+ end
184
346
 
185
- it "maps an array of included data through has_one" do
186
- @user_with_included_data.role.should be_a(Foo::Role)
187
- @user_with_included_data.role.object_id.should == @user_with_included_data.role.object_id
188
- @user_with_included_data.role.id.should == 1
189
- @user_with_included_data.role.body.should == "Admin"
190
- end
347
+ it "includes has_many relationships in params by default" do
348
+ expect(user_params[:comments]).to be_kind_of(Array)
349
+ expect(user_params[:comments].length).to eq(2)
350
+ end
191
351
 
192
- it "fetches data that was not included through has_one" do
193
- @user_without_included_data.role.should be_a(Foo::Role)
194
- @user_without_included_data.role.id.should == 2
195
- @user_without_included_data.role.body.should == "User"
196
- end
352
+ it "includes has_one relationship in params by default" do
353
+ expect(user_params[:role]).to be_kind_of(Hash)
354
+ expect(user_params[:role]).not_to be_empty
355
+ end
197
356
 
198
- it "fetches has_one data even if it was included, only if called with parameters" do
199
- @user_with_included_data.role.where(:foo_id => 2).id.should == 3
357
+ it "includes belongs_to relationship in params by default" do
358
+ expect(user_params[:organization]).to be_kind_of(Hash)
359
+ expect(user_params[:organization]).not_to be_empty
360
+ end
200
361
  end
201
362
 
202
- it "maps an array of included data through belongs_to" do
203
- @user_with_included_data.organization.should be_a(Foo::Organization)
204
- @user_with_included_data.organization.id.should == 1
205
- @user_with_included_data.organization.name.should == "Bluth Company"
206
- end
363
+ context "without included data" do
364
+ before(:context) do
365
+ Her::API.setup url: "https://api.example.com" do |builder|
366
+ builder.use Her::Middleware::FirstLevelParseJSON
367
+ builder.use Faraday::Request::UrlEncoded
368
+ builder.adapter :test do |stub|
369
+ stub.get("/users/2") { [200, {}, { id: 2, name: "Lindsay Fünke", organization_id: 2 }.to_json] }
370
+ 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] }
371
+ stub.get("/users/2/comments/5") { [200, {}, { comment: { id: 5, body: "Is this the tiny town from Footloose?" } }.to_json] }
372
+ stub.get("/users/2/role") { [200, {}, { id: 2, body: "User" }.to_json] }
373
+ stub.get("/organizations/2") do |env|
374
+ if env[:params]["admin"] == "true"
375
+ [200, {}, { organization: { id: 2, name: "Bluth Company (admin)" } }.to_json]
376
+ else
377
+ [200, {}, { organization: { id: 2, name: "Bluth Company" } }.to_json]
378
+ end
379
+ end
380
+ end
381
+ end
382
+ end
207
383
 
208
- it "fetches data that was not included through belongs_to" do
209
- @user_without_included_data.organization.should be_a(Foo::Organization)
210
- @user_without_included_data.organization.id.should == 2
211
- @user_without_included_data.organization.name.should == "Bluth Company"
212
- end
384
+ let(:user) { Foo::User.find(2) }
213
385
 
214
- it "returns nil if the foreign key is nil" do
215
- @user_without_organization_and_not_persisted.organization.should be_nil
216
- end
386
+ it "fetches data that was not included through has_many" do
387
+ expect(user.comments.first).to be_a(Foo::Comment)
388
+ expect(user.comments.length).to eq(2)
389
+ expect(user.comments.first.id).to eq(4)
390
+ expect(user.comments.first.body).to eq("They're having a FIRESALE?")
391
+ end
217
392
 
218
- it "fetches belongs_to data even if it was included, only if called with parameters" do
219
- @user_with_included_data.organization.where(:foo_id => 1).name.should == "Bluth Company Foo"
220
- end
393
+ it "fetches data that was not included through has_many only once" do
394
+ expect(user.comments.first.object_id).to eq(user.comments.first.object_id)
395
+ end
221
396
 
222
- it "can tell if it has a association" do
223
- @user_without_included_data.has_association?(:unknown_association).should be false
224
- @user_without_included_data.has_association?(:organization).should be true
225
- end
397
+ it "fetches data that was cached through has_many if called with parameters" do
398
+ expect(user.comments.first.object_id).not_to eq(user.comments.where(foo_id: 1).first.object_id)
399
+ end
226
400
 
227
- it "fetches the resource corresponding to a named association" do
228
- @user_without_included_data.get_association(:unknown_association).should be_nil
229
- @user_without_included_data.get_association(:organization).name.should == "Bluth Company"
230
- end
401
+ it "fetches data again after being reloaded" do
402
+ expect { user.comments.reload }.to change { user.comments.first.object_id }
403
+ end
231
404
 
232
- it "pass query string parameters when additional arguments are passed" do
233
- @user_without_included_data.organization.where(:admin => true).name.should == "Bluth Company (admin)"
234
- @user_without_included_data.organization.name.should == "Bluth Company"
235
- end
405
+ it "fetches data that was not included through has_one" do
406
+ expect(user.role).to be_a(Foo::Role)
407
+ expect(user.role.id).to eq(2)
408
+ expect(user.role.body).to eq("User")
409
+ end
410
+
411
+ it "fetches data that was not included through belongs_to" do
412
+ expect(user.organization).to be_a(Foo::Organization)
413
+ expect(user.organization.id).to eq(2)
414
+ expect(user.organization.name).to eq("Bluth Company")
415
+ end
416
+
417
+ it "can tell if it has a association" do
418
+ expect(user.has_association?(:unknown_association)).to be false
419
+ expect(user.has_association?(:organization)).to be true
420
+ end
421
+
422
+ it "fetches the resource corresponding to a named association" do
423
+ expect(user.get_association(:unknown_association)).to be_nil
424
+ expect(user.get_association(:organization).name).to eq("Bluth Company")
425
+ end
426
+
427
+ it "pass query string parameters when additional arguments are passed" do
428
+ expect(user.organization.where(admin: true).name).to eq("Bluth Company (admin)")
429
+ expect(user.organization.name).to eq("Bluth Company")
430
+ end
431
+
432
+ it "fetches data with the specified id when calling find" do
433
+ comment = user.comments.find(5)
434
+ expect(comment).to be_a(Foo::Comment)
435
+ expect(comment.id).to eq(5)
436
+ end
236
437
 
237
- it "fetches data with the specified id when calling find" do
238
- comment = @user_without_included_data.comments.find(5)
239
- comment.should be_a(Foo::Comment)
240
- comment.id.should eq(5)
438
+ it "'s associations responds to #empty?" do
439
+ expect(user.organization.respond_to?(:empty?)).to be_truthy
440
+ expect(user.organization).not_to be_empty
441
+ end
241
442
  end
242
443
 
243
- it "'s associations responds to #empty?" do
244
- @user_without_included_data.organization.respond_to?(:empty?).should be_truthy
245
- @user_without_included_data.organization.should_not be_empty
444
+ context "without included parent data" do
445
+ before(:context) do
446
+ Her::API.setup url: "https://api.example.com" do |builder|
447
+ builder.use Her::Middleware::FirstLevelParseJSON
448
+ builder.use Faraday::Request::UrlEncoded
449
+ end
450
+ end
451
+
452
+ let(:comment) { Foo::Comment.new(id: 7, user_id: 1) }
453
+
454
+ it "does fetch the parent models data only once" do
455
+ expect(comment.user.object_id).to eq(comment.user.object_id)
456
+ end
457
+
458
+ it "does fetch the parent models data that was cached if called with parameters" do
459
+ expect(comment.user.object_id).not_to eq(comment.user.where(a: 2).object_id)
460
+ end
246
461
  end
247
462
 
248
- it 'includes has_many relationships in params by default' do
249
- params = @user_with_included_data.to_params
250
- params[:comments].should be_kind_of(Array)
251
- params[:comments].length.should eq(2)
463
+ context "when resource is new" do
464
+ let(:new_user) { Foo::User.new }
465
+
466
+ it "doesn't attempt to fetch association data" do
467
+ expect(new_user.comments).to eq([])
468
+ expect(new_user.role).to be_nil
469
+ expect(new_user.organization).to be_nil
470
+ end
252
471
  end
253
472
 
254
- [:create, :save_existing, :destroy].each do |type|
255
- context "after #{type}" do
256
- let(:subject) { self.send("user_with_included_data_after_#{type}")}
473
+ context "when foreign_key is nil" do
474
+ before do
475
+ spawn_model "Foo::User" do
476
+ belongs_to :organization, class_name: "Foo::Organization"
477
+ end
478
+
479
+ spawn_model "Foo::Organization" do
480
+ parse_root_in_json true
481
+ end
482
+ end
483
+
484
+ let(:user) { Foo::User.new(organization_id: nil, name: "Katlin Fünke") }
485
+
486
+ it "returns nil" do
487
+ expect(user.organization).to be_nil
488
+ end
489
+ end
257
490
 
258
- it "maps an array of included data through has_many" do
259
- subject.comments.first.should be_a(Foo::Comment)
260
- subject.comments.length.should == 1
261
- subject.comments.first.id.should == 99
262
- subject.comments.first.body.should == "Rodríguez, nasibisibusi?"
491
+ context "after" do
492
+ before(:context) do
493
+ Her::API.setup url: "https://api.example.com" do |builder|
494
+ builder.use Her::Middleware::FirstLevelParseJSON
495
+ builder.use Faraday::Request::UrlEncoded
496
+ builder.adapter :test do |stub|
497
+ 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] }
498
+ 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] }
499
+ 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] }
500
+ end
263
501
  end
502
+ end
503
+
504
+ let(:user_after_create) { Foo::User.create }
505
+ let(:user_after_save_existing) { Foo::User.save_existing(5, name: "Clancy Brown") }
506
+ let(:user_after_destroy) { Foo::User.new(id: 5).destroy }
507
+
508
+ [:create, :save_existing, :destroy].each do |type|
509
+ context "after #{type}" do
510
+ let(:subject) { send("user_after_#{type}") }
264
511
 
265
- it "maps an array of included data through has_one" do
266
- subject.role.should be_a(Foo::Role)
267
- subject.role.id.should == 1
268
- subject.role.body.should == "Admin"
512
+ it "maps an array of included data through has_many" do
513
+ expect(subject.comments.first).to be_a(Foo::Comment)
514
+ expect(subject.comments.length).to eq(1)
515
+ expect(subject.comments.first.id).to eq(99)
516
+ expect(subject.comments.first.body).to eq("Rodríguez, nasibisibusi?")
517
+ end
518
+
519
+ it "maps an array of included data through has_one" do
520
+ expect(subject.role).to be_a(Foo::Role)
521
+ expect(subject.role.id).to eq(1)
522
+ expect(subject.role.body).to eq("Admin")
523
+ end
269
524
  end
270
525
  end
271
526
  end
@@ -273,130 +528,188 @@ describe Her::Model::Associations do
273
528
 
274
529
  context "handling associations with details in active_model_serializers format" do
275
530
  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
531
  spawn_model "Foo::User" do
289
- parse_root_in_json true, :format => :active_model_serializers
532
+ parse_root_in_json true, format: :active_model_serializers
290
533
  has_many :comments, class_name: "Foo::Comment"
291
- belongs_to :organization
534
+ has_one :role, class_name: "Foo::Role"
535
+ belongs_to :organization, class_name: "Foo::Organization"
536
+ end
537
+
538
+ spawn_model "Foo::Role" do
539
+ belongs_to :user
540
+ parse_root_in_json true, format: :active_model_serializers
292
541
  end
542
+
293
543
  spawn_model "Foo::Comment" do
294
544
  belongs_to :user
295
- parse_root_in_json true, :format => :active_model_serializers
545
+ parse_root_in_json true, format: :active_model_serializers
296
546
  end
547
+
297
548
  spawn_model "Foo::Organization" do
298
- parse_root_in_json true, :format => :active_model_serializers
549
+ parse_root_in_json true, format: :active_model_serializers
299
550
  end
300
-
301
- @user_with_included_data = Foo::User.find(1)
302
- @user_without_included_data = Foo::User.find(2)
303
551
  end
304
552
 
305
- it "maps an array of included data through has_many" do
306
- @user_with_included_data.comments.first.should be_a(Foo::Comment)
307
- @user_with_included_data.comments.length.should == 2
308
- @user_with_included_data.comments.first.id.should == 2
309
- @user_with_included_data.comments.first.body.should == "Tobias, you blow hard!"
310
- end
553
+ context "with included data" do
554
+ before(:context) do
555
+ Her::API.setup url: "https://api.example.com" do |builder|
556
+ builder.use Her::Middleware::FirstLevelParseJSON
557
+ builder.use Faraday::Request::UrlEncoded
558
+ builder.adapter :test do |stub|
559
+ 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] }
560
+ stub.get("/users/1/comments") { [200, {}, { comments: [{ id: 4, body: "They're having a FIRESALE?" }] }.to_json] }
561
+ stub.get("/organizations/1") { [200, {}, { organization: { id: 1, name: "Bluth Company Foo" } }.to_json] }
562
+ end
563
+ end
564
+ end
311
565
 
312
- 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.should == @user_with_included_data.object_id
314
- end
566
+ let(:user) { Foo::User.find(1) }
567
+ let(:user_params) { user.to_params }
315
568
 
316
- it "fetches data that was not included through has_many" do
317
- @user_without_included_data.comments.first.should be_a(Foo::Comment)
318
- @user_without_included_data.comments.length.should == 2
319
- @user_without_included_data.comments.first.id.should == 4
320
- @user_without_included_data.comments.first.body.should == "They're having a FIRESALE?"
321
- end
569
+ it "maps an array of included data through has_many" do
570
+ expect(user.comments.first).to be_a(Foo::Comment)
571
+ expect(user.comments.length).to eq(2)
572
+ expect(user.comments.first.id).to eq(2)
573
+ expect(user.comments.first.body).to eq("Tobias, you blow hard!")
574
+ end
322
575
 
323
- it "fetches has_many data even if it was included, only if called with parameters" do
324
- @user_with_included_data.comments.where(:foo_id => 1).length.should == 1
325
- end
576
+ it "does not refetch the parents models data if they have been fetched before" do
577
+ expect(user.comments.first.user.object_id).to eq(user.object_id)
578
+ end
326
579
 
327
- it "maps an array of included data through belongs_to" do
328
- @user_with_included_data.organization.should be_a(Foo::Organization)
329
- @user_with_included_data.organization.id.should == 1
330
- @user_with_included_data.organization.name.should == "Bluth Company"
331
- end
580
+ it "fetches has_many data even if it was included, only if called with parameters" do
581
+ expect(user.comments.where(foo_id: 1).length).to eq(1)
582
+ end
332
583
 
333
- it "fetches data that was not included through belongs_to" do
334
- @user_without_included_data.organization.should be_a(Foo::Organization)
335
- @user_without_included_data.organization.id.should == 1
336
- @user_without_included_data.organization.name.should == "Bluth Company Foo"
337
- end
584
+ it "maps an array of included data through belongs_to" do
585
+ expect(user.organization).to be_a(Foo::Organization)
586
+ expect(user.organization.id).to eq(1)
587
+ expect(user.organization.name).to eq("Bluth Company")
588
+ end
338
589
 
339
- it "fetches belongs_to data even if it was included, only if called with parameters" do
340
- @user_with_included_data.organization.where(:foo_id => 1).name.should == "Bluth Company Foo"
341
- end
590
+ it "fetches belongs_to data even if it was included, only if called with parameters" do
591
+ expect(user.organization.where(foo_id: 1).name).to eq("Bluth Company Foo")
592
+ end
593
+
594
+ it "includes has_many relationships in params by default" do
595
+ expect(user_params[:comments]).to be_kind_of(Array)
596
+ expect(user_params[:comments].length).to eq(2)
597
+ end
342
598
 
343
- it "fetches data with the specified id when calling find" do
344
- comment = @user_without_included_data.comments.find(5)
345
- comment.should be_a(Foo::Comment)
346
- comment.id.should eq(5)
599
+ it "includes has_one relationships in params by default" do
600
+ expect(user_params[:role]).to be_kind_of(Hash)
601
+ expect(user_params[:role]).not_to be_empty
602
+ end
603
+
604
+ it "includes belongs_to relationship in params by default" do
605
+ expect(user_params[:organization]).to be_kind_of(Hash)
606
+ expect(user_params[:organization]).not_to be_empty
607
+ end
347
608
  end
348
609
 
349
- it 'includes has_many relationships in params by default' do
350
- params = @user_with_included_data.to_params
351
- params[:comments].should be_kind_of(Array)
352
- params[:comments].length.should eq(2)
610
+ context "without included data" do
611
+ before(:context) do
612
+ Her::API.setup url: "https://api.example.com" do |builder|
613
+ builder.use Her::Middleware::FirstLevelParseJSON
614
+ builder.use Faraday::Request::UrlEncoded
615
+ builder.adapter :test do |stub|
616
+ stub.get("/users/2") { [200, {}, { user: { id: 2, name: "Lindsay Fünke", organization_id: 1 } }.to_json] }
617
+ 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] }
618
+ stub.get("/users/2/comments/5") { [200, {}, { comment: { id: 5, body: "Is this the tiny town from Footloose?" } }.to_json] }
619
+ stub.get("/organizations/1") { [200, {}, { organization: { id: 1, name: "Bluth Company Foo" } }.to_json] }
620
+ end
621
+ end
622
+ end
623
+
624
+ let(:user) { Foo::User.find(2) }
625
+
626
+ it "fetches data that was not included through has_many" do
627
+ expect(user.comments.first).to be_a(Foo::Comment)
628
+ expect(user.comments.length).to eq(2)
629
+ expect(user.comments.first.id).to eq(4)
630
+ expect(user.comments.first.body).to eq("They're having a FIRESALE?")
631
+ end
632
+
633
+ it "fetches data that was not included through belongs_to" do
634
+ expect(user.organization).to be_a(Foo::Organization)
635
+ expect(user.organization.id).to eq(1)
636
+ expect(user.organization.name).to eq("Bluth Company Foo")
637
+ end
638
+
639
+ it "fetches data with the specified id when calling find" do
640
+ comment = user.comments.find(5)
641
+ expect(comment).to be_a(Foo::Comment)
642
+ expect(comment.id).to eq(5)
643
+ end
353
644
  end
354
645
  end
355
646
 
356
647
  context "handling associations with details" do
357
648
  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] }
649
+ spawn_model "Foo::User" do
650
+ belongs_to :company, path: "/organizations/:id", foreign_key: :organization_id, data_key: :organization
651
+ end
652
+
653
+ spawn_model "Foo::Company"
654
+ end
655
+
656
+ context "with included data" do
657
+ before(:context) do
658
+ Her::API.setup url: "https://api.example.com" do |builder|
659
+ builder.use Her::Middleware::FirstLevelParseJSON
660
+ builder.use Faraday::Request::UrlEncoded
661
+ builder.adapter :test do |stub|
662
+ stub.get("/users/1") { [200, {}, { id: 1, name: "Tobias Fünke", organization: { id: 1, name: "Bluth Company Inc." }, organization_id: 1 }.to_json] }
663
+ stub.get("/users/4") { [200, {}, { id: 1, name: "Tobias Fünke", organization: { id: 1, name: "Bluth Company Inc." } }.to_json] }
664
+ stub.get("/users/3") { [200, {}, { id: 2, name: "Lindsay Fünke", organization: nil }.to_json] }
665
+ end
367
666
  end
368
667
  end
369
668
 
370
- spawn_model "Foo::User" do
371
- belongs_to :company, :path => "/organizations/:id", :foreign_key => :organization_id, :data_key => :organization
669
+ let(:user) { Foo::User.find(1) }
670
+
671
+ it "maps an array of included data through belongs_to" do
672
+ expect(user.company).to be_a(Foo::Company)
673
+ expect(user.company.id).to eq(1)
674
+ expect(user.company.name).to eq("Bluth Company Inc.")
372
675
  end
373
676
 
374
- spawn_model "Foo::Company"
677
+ context "when included data is nil" do
678
+ let(:user) { Foo::User.find(3) }
375
679
 
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
680
+ it "does not map included data" do
681
+ expect(user.company).to be_nil
682
+ end
683
+ end
381
684
 
382
- it "maps an array of included data through belongs_to" do
383
- @user_with_included_data.company.should be_a(Foo::Company)
384
- @user_with_included_data.company.id.should == 1
385
- @user_with_included_data.company.name.should == "Bluth Company Inc."
386
- end
685
+ context "when included data has no foreign_key" do
686
+ let(:user) { Foo::User.find(4) }
387
687
 
388
- it "does not map included data if it’s nil" do
389
- @user_with_included_nil_data.company.should be_nil
688
+ it "maps included data anyway" do
689
+ expect(user.company.name).to eq("Bluth Company Inc.")
690
+ end
691
+ end
390
692
  end
391
693
 
392
- it "fetches data that was not included through belongs_to" do
393
- @user_without_included_data.company.should be_a(Foo::Company)
394
- @user_without_included_data.company.id.should == 1
395
- @user_without_included_data.company.name.should == "Bluth Company"
396
- end
694
+ context "without included data" do
695
+ before(:context) do
696
+ Her::API.setup url: "https://api.example.com" do |builder|
697
+ builder.use Her::Middleware::FirstLevelParseJSON
698
+ builder.use Faraday::Request::UrlEncoded
699
+ builder.adapter :test do |stub|
700
+ stub.get("/users/2") { [200, {}, { id: 2, name: "Lindsay Fünke", organization_id: 1 }.to_json] }
701
+ stub.get("/organizations/1") { [200, {}, { id: 1, name: "Bluth Company" }.to_json] }
702
+ end
703
+ end
704
+ end
397
705
 
398
- it "does not require foreugn key to have nested object" do
399
- @user_with_included_data_but_no_fk.company.name.should == "Bluth Company Inc."
706
+ let(:user) { Foo::User.find(2) }
707
+
708
+ it "fetches data that was not included through belongs_to" do
709
+ expect(user.company).to be_a(Foo::Company)
710
+ expect(user.company.id).to eq(1)
711
+ expect(user.company.name).to eq("Bluth Company")
712
+ end
400
713
  end
401
714
  end
402
715
 
@@ -420,30 +733,30 @@ describe Her::Model::Associations do
420
733
  subject { user_with_role.role }
421
734
 
422
735
  it "doesnt mask the object's basic methods" do
423
- subject.class.should == Foo::Role
736
+ expect(subject.class).to eq(Foo::Role)
424
737
  end
425
738
 
426
739
  it "doesnt mask core methods like extend" do
427
740
  committer = Module.new
428
- subject.extend committer
429
- associated_value.should be_kind_of committer
741
+ subject.extend committer
742
+ expect(associated_value).to be_kind_of committer
430
743
  end
431
744
 
432
745
  it "can return the association object" do
433
- subject.association.should be_kind_of Her::Model::Associations::Association
746
+ expect(subject.association).to be_kind_of Her::Model::Associations::Association
434
747
  end
435
748
 
436
749
  it "still can call fetch via the association" do
437
- subject.association.fetch.should eq associated_value
750
+ expect(subject.association.fetch).to eq associated_value
438
751
  end
439
752
 
440
753
  it "calls missing methods on associated value" do
441
- subject.present?.should == "of_course"
754
+ expect(subject.present?).to eq("of_course")
442
755
  end
443
756
 
444
757
  it "can use association methods like where" do
445
- subject.where(role: 'committer').association.
446
- params.should include :role
758
+ expect(subject.where(role: "committer").association
759
+ .params).to include :role
447
760
  end
448
761
  end
449
762
 
@@ -456,21 +769,25 @@ describe Her::Model::Associations do
456
769
  end
457
770
 
458
771
  context "with #build" do
772
+ let(:comment) { Foo::User.new(id: 10).comments.build(body: "Hello!") }
773
+
459
774
  it "takes the parent primary key" do
460
- @comment = Foo::User.new(:id => 10).comments.build(:body => "Hello!")
461
- @comment.body.should == "Hello!"
462
- @comment.user_id.should == 10
775
+ expect(comment.body).to eq("Hello!")
776
+ expect(comment.user_id).to eq(10)
463
777
  end
464
778
  end
465
779
 
466
780
  context "with #create" do
781
+ let(:user) { Foo::User.find(10) }
782
+ let(:comment) { user.comments.create(body: "Hello!") }
783
+
467
784
  before do
468
- Her::API.setup :url => "https://api.example.com" do |builder|
785
+ Her::API.setup url: "https://api.example.com" do |builder|
469
786
  builder.use Her::Middleware::FirstLevelParseJSON
470
787
  builder.use Faraday::Request::UrlEncoded
471
788
  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] }
789
+ stub.get("/users/10") { [200, {}, { id: 10 }.to_json] }
790
+ 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
791
  end
475
792
  end
476
793
 
@@ -479,25 +796,30 @@ describe Her::Model::Associations do
479
796
  end
480
797
 
481
798
  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
- @comment.id.should == 1
485
- @comment.body.should == "Hello!"
486
- @comment.user_id.should == 10
487
- @user.comments.should == [@comment]
799
+ expect(comment.id).to eq(1)
800
+ expect(comment.body).to eq("Hello!")
801
+ expect(comment.user_id).to eq(10)
802
+ expect(user.comments).to eq([comment])
488
803
  end
489
804
  end
490
805
 
491
806
  context "with #new" do
492
- it "creates nested models from hash attibutes" do
493
- user = Foo::User.new(:name => "vic", :comments => [{:text => "hello"}])
494
- user.comments.first.text.should == "hello"
807
+ let(:user) { Foo::User.new(name: "vic", comments: [comment]) }
808
+
809
+ context "using hash attributes" do
810
+ let(:comment) { { text: "hello" } }
811
+
812
+ it "assigns nested models" do
813
+ expect(user.comments.first.text).to eq("hello")
814
+ end
495
815
  end
496
816
 
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
- user.comments.first.text.should == 'goodbye'
817
+ context "using constructed objects" do
818
+ let(:comment) { Foo::Comment.new(text: "goodbye") }
819
+
820
+ it "assigns nested models" do
821
+ expect(user.comments.first.text).to eq("goodbye")
822
+ end
501
823
  end
502
824
  end
503
825
  end