extended_her 0.5

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 (66) hide show
  1. data/.gitignore +8 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +8 -0
  4. data/CONTRIBUTING.md +26 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +7 -0
  7. data/README.md +723 -0
  8. data/Rakefile +11 -0
  9. data/UPGRADE.md +32 -0
  10. data/examples/twitter-oauth/Gemfile +13 -0
  11. data/examples/twitter-oauth/app.rb +50 -0
  12. data/examples/twitter-oauth/config.ru +5 -0
  13. data/examples/twitter-oauth/views/index.haml +9 -0
  14. data/examples/twitter-search/Gemfile +12 -0
  15. data/examples/twitter-search/app.rb +55 -0
  16. data/examples/twitter-search/config.ru +5 -0
  17. data/examples/twitter-search/views/index.haml +9 -0
  18. data/extended_her.gemspec +27 -0
  19. data/lib/her.rb +23 -0
  20. data/lib/her/api.rb +108 -0
  21. data/lib/her/base.rb +17 -0
  22. data/lib/her/collection.rb +12 -0
  23. data/lib/her/errors.rb +5 -0
  24. data/lib/her/exceptions/exception.rb +4 -0
  25. data/lib/her/exceptions/record_invalid.rb +8 -0
  26. data/lib/her/exceptions/record_not_found.rb +13 -0
  27. data/lib/her/middleware.rb +9 -0
  28. data/lib/her/middleware/accept_json.rb +15 -0
  29. data/lib/her/middleware/first_level_parse_json.rb +34 -0
  30. data/lib/her/middleware/second_level_parse_json.rb +28 -0
  31. data/lib/her/model.rb +69 -0
  32. data/lib/her/model/base.rb +7 -0
  33. data/lib/her/model/hooks.rb +114 -0
  34. data/lib/her/model/http.rb +284 -0
  35. data/lib/her/model/introspection.rb +57 -0
  36. data/lib/her/model/orm.rb +191 -0
  37. data/lib/her/model/orm/comparison_methods.rb +20 -0
  38. data/lib/her/model/orm/create_methods.rb +29 -0
  39. data/lib/her/model/orm/destroy_methods.rb +53 -0
  40. data/lib/her/model/orm/error_methods.rb +19 -0
  41. data/lib/her/model/orm/fields_definition.rb +15 -0
  42. data/lib/her/model/orm/find_methods.rb +46 -0
  43. data/lib/her/model/orm/persistance_methods.rb +22 -0
  44. data/lib/her/model/orm/relation_mapper.rb +21 -0
  45. data/lib/her/model/orm/save_methods.rb +58 -0
  46. data/lib/her/model/orm/serialization_methods.rb +28 -0
  47. data/lib/her/model/orm/update_methods.rb +31 -0
  48. data/lib/her/model/paths.rb +82 -0
  49. data/lib/her/model/relationships.rb +191 -0
  50. data/lib/her/paginated_collection.rb +20 -0
  51. data/lib/her/relation.rb +94 -0
  52. data/lib/her/version.rb +3 -0
  53. data/spec/api_spec.rb +131 -0
  54. data/spec/collection_spec.rb +26 -0
  55. data/spec/middleware/accept_json_spec.rb +10 -0
  56. data/spec/middleware/first_level_parse_json_spec.rb +42 -0
  57. data/spec/middleware/second_level_parse_json_spec.rb +25 -0
  58. data/spec/model/hooks_spec.rb +406 -0
  59. data/spec/model/http_spec.rb +184 -0
  60. data/spec/model/introspection_spec.rb +59 -0
  61. data/spec/model/orm_spec.rb +552 -0
  62. data/spec/model/paths_spec.rb +286 -0
  63. data/spec/model/relationships_spec.rb +222 -0
  64. data/spec/model_spec.rb +31 -0
  65. data/spec/spec_helper.rb +46 -0
  66. metadata +222 -0
@@ -0,0 +1,286 @@
1
+ # encoding: utf-8
2
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
3
+
4
+ describe Her::Model::Paths do
5
+ context "building request paths" do
6
+ context "simple model" do
7
+ before do
8
+ spawn_model "Foo::User"
9
+ end
10
+
11
+ describe "#build_request_path" do
12
+ it "builds paths with defaults" do
13
+ Foo::User.build_request_path(:id => "foo").should == "users/foo"
14
+ Foo::User.build_request_path(:id => nil).should == "users"
15
+ Foo::User.build_request_path.should == "users"
16
+ end
17
+
18
+ it "builds paths with custom collection path" do
19
+ Foo::User.collection_path "/utilisateurs"
20
+ Foo::User.build_request_path(:id => "foo").should == "/utilisateurs/foo"
21
+ Foo::User.build_request_path.should == "/utilisateurs"
22
+ end
23
+
24
+ it "builds paths with custom relative collection path" do
25
+ Foo::User.collection_path "utilisateurs"
26
+ Foo::User.build_request_path(:id => "foo").should == "utilisateurs/foo"
27
+ Foo::User.build_request_path.should == "utilisateurs"
28
+ end
29
+
30
+ it "builds paths with custom collection path with multiple variables" do
31
+ Foo::User.collection_path "/organizations/:organization_id/utilisateurs"
32
+
33
+ Foo::User.build_request_path(:id => "foo", :_organization_id => "acme").should == "/organizations/acme/utilisateurs/foo"
34
+ Foo::User.build_request_path(:_organization_id => "acme").should == "/organizations/acme/utilisateurs"
35
+
36
+ Foo::User.build_request_path(:id => "foo", :organization_id => "acme").should == "/organizations/acme/utilisateurs/foo"
37
+ Foo::User.build_request_path(:organization_id => "acme").should == "/organizations/acme/utilisateurs"
38
+ end
39
+
40
+ it "builds paths with custom relative collection path with multiple variables" do
41
+ Foo::User.collection_path "organizations/:organization_id/utilisateurs"
42
+
43
+ Foo::User.build_request_path(:id => "foo", :_organization_id => "acme").should == "organizations/acme/utilisateurs/foo"
44
+ Foo::User.build_request_path(:_organization_id => "acme").should == "organizations/acme/utilisateurs"
45
+
46
+ Foo::User.build_request_path(:id => "foo", :organization_id => "acme").should == "organizations/acme/utilisateurs/foo"
47
+ Foo::User.build_request_path(:organization_id => "acme").should == "organizations/acme/utilisateurs"
48
+ end
49
+
50
+ it "builds paths with custom item path" do
51
+ Foo::User.resource_path "/utilisateurs/:id"
52
+ Foo::User.build_request_path(:id => "foo").should == "/utilisateurs/foo"
53
+ Foo::User.build_request_path.should == "users"
54
+ end
55
+
56
+ it "builds paths with custom relative item path" do
57
+ Foo::User.resource_path "utilisateurs/:id"
58
+ Foo::User.build_request_path(:id => "foo").should == "utilisateurs/foo"
59
+ Foo::User.build_request_path.should == "users"
60
+ end
61
+
62
+ it "raises exceptions when building a path without required custom variables" do
63
+ Foo::User.collection_path "/organizations/:organization_id/utilisateurs"
64
+ expect { Foo::User.build_request_path(:id => "foo") }.to raise_error(Her::Errors::PathError)
65
+ end
66
+ end
67
+ end
68
+
69
+ context "simple model with multiple words" do
70
+ before do
71
+ spawn_model "Foo::AdminUser"
72
+ end
73
+
74
+ describe "#build_request_path" do
75
+ it "builds paths with defaults" do
76
+ Foo::AdminUser.build_request_path(:id => "foo").should == "admin_users/foo"
77
+ Foo::AdminUser.build_request_path.should == "admin_users"
78
+ end
79
+
80
+ it "builds paths with custom collection path" do
81
+ Foo::AdminUser.collection_path "/users"
82
+ Foo::AdminUser.build_request_path(:id => "foo").should == "/users/foo"
83
+ Foo::AdminUser.build_request_path.should == "/users"
84
+ end
85
+
86
+ it "builds paths with custom relative collection path" do
87
+ Foo::AdminUser.collection_path "users"
88
+ Foo::AdminUser.build_request_path(:id => "foo").should == "users/foo"
89
+ Foo::AdminUser.build_request_path.should == "users"
90
+ end
91
+
92
+ it "builds paths with custom collection path with multiple variables" do
93
+ Foo::AdminUser.collection_path "/organizations/:organization_id/users"
94
+ Foo::AdminUser.build_request_path(:id => "foo", :_organization_id => "acme").should == "/organizations/acme/users/foo"
95
+ Foo::AdminUser.build_request_path(:_organization_id => "acme").should == "/organizations/acme/users"
96
+ end
97
+
98
+ it "builds paths with custom relative collection path with multiple variables" do
99
+ Foo::AdminUser.collection_path "organizations/:organization_id/users"
100
+ Foo::AdminUser.build_request_path(:id => "foo", :_organization_id => "acme").should == "organizations/acme/users/foo"
101
+ Foo::AdminUser.build_request_path(:_organization_id => "acme").should == "organizations/acme/users"
102
+ end
103
+
104
+ it "builds paths with custom item path" do
105
+ Foo::AdminUser.resource_path "/users/:id"
106
+ Foo::AdminUser.build_request_path(:id => "foo").should == "/users/foo"
107
+ Foo::AdminUser.build_request_path.should == "admin_users"
108
+ end
109
+
110
+ it "builds paths with custom relative item path" do
111
+ Foo::AdminUser.resource_path "users/:id"
112
+ Foo::AdminUser.build_request_path(:id => "foo").should == "users/foo"
113
+ Foo::AdminUser.build_request_path.should == "admin_users"
114
+ end
115
+
116
+ it "raises exceptions when building a path without required custom variables" do
117
+ Foo::AdminUser.collection_path "/organizations/:organization_id/users"
118
+ expect { Foo::AdminUser.build_request_path(:id => "foo") }.to raise_error(Her::Errors::PathError)
119
+ end
120
+
121
+ it "raises exceptions when building a relative path without required custom variables" do
122
+ Foo::AdminUser.collection_path "organizations/:organization_id/users"
123
+ expect { Foo::AdminUser.build_request_path(:id => "foo") }.to raise_error(Her::Errors::PathError)
124
+ end
125
+ end
126
+ end
127
+
128
+ context "nested model" do
129
+ before do
130
+ spawn_model "Foo::User"
131
+ end
132
+
133
+ describe "#build_request_path" do
134
+ it "builds paths with defaults" do
135
+ Foo::User.build_request_path(:id => "foo").should == "users/foo"
136
+ Foo::User.build_request_path.should == "users"
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ context "making subdomain HTTP requests" do
143
+ before do
144
+ Her::API.setup :url => "https://api.example.com/" do |builder|
145
+ builder.use Her::Middleware::FirstLevelParseJSON
146
+ builder.use Faraday::Request::UrlEncoded
147
+ builder.adapter :test do |stub|
148
+ stub.get("organizations/2/users") { |env| [200, {}, [{ :id => 1, :fullname => "Tobias Fünke", :organization_id => 2 }, { :id => 2, :fullname => "Lindsay Fünke", :organization_id => 2 }].to_json] }
149
+ stub.post("organizations/2/users") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke", :organization_id => 2 }.to_json] }
150
+ stub.put("organizations/2/users/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke", :organization_id => 2 }.to_json] }
151
+ stub.get("organizations/2/users/1") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke", :organization_id => 2, :active => true }.to_json] }
152
+ stub.delete("organizations/2/users/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke", :organization_id => 2, :active => false }.to_json] }
153
+ end
154
+ end
155
+
156
+ spawn_model "Foo::User" do
157
+ collection_path "organizations/:organization_id/users"
158
+ end
159
+ end
160
+
161
+ describe "fetching a resource" do
162
+ it "maps a single resource to a Ruby object" do
163
+ @user = Foo::User.find(1, :_organization_id => 2)
164
+ @user.id.should == 1
165
+ @user.fullname.should == "Tobias Fünke"
166
+ end
167
+ end
168
+
169
+ describe "fetching a collection" do
170
+ it "maps a collection of resources to an array of Ruby objects" do
171
+ @users = Foo::User.all(:_organization_id => 2)
172
+ @users.length.should == 2
173
+ @users.first.fullname.should == "Tobias Fünke"
174
+ end
175
+ end
176
+
177
+ describe "handling new resource" do
178
+ it "handles new resource" do
179
+ @new_user = Foo::User.new(:fullname => "Tobias Fünke", :organization_id => 2)
180
+ @new_user.new?.should be_true
181
+
182
+ @existing_user = Foo::User.find(1, :_organization_id => 2)
183
+ @existing_user.new?.should be_false
184
+ end
185
+ end
186
+
187
+ describe "creating resources" do
188
+ it "handle one-line resource creation" do
189
+ @user = Foo::User.create(:fullname => "Tobias Fünke", :organization_id => 2)
190
+ @user.id.should == 1
191
+ @user.fullname.should == "Tobias Fünke"
192
+ end
193
+
194
+ it "handle resource creation through Model.new + #save" do
195
+ @user = Foo::User.new(:fullname => "Tobias Fünke", :organization_id => 2)
196
+ @user.save
197
+ @user.fullname.should == "Tobias Fünke"
198
+ end
199
+ end
200
+
201
+ context "updating resources" do
202
+ it "handle resource data update without saving it" do
203
+ @user = Foo::User.find(1, :_organization_id => 2)
204
+ @user.fullname.should == "Tobias Fünke"
205
+ @user.fullname = "Kittie Sanchez"
206
+ @user.fullname.should == "Kittie Sanchez"
207
+ end
208
+
209
+ it "handle resource update through the .update class method" do
210
+ @user = Foo::User.save_existing(1, { :fullname => "Lindsay Fünke", :organization_id => 2 })
211
+ @user.fullname.should == "Lindsay Fünke"
212
+ end
213
+
214
+ it "handle resource update through #save on an existing resource" do
215
+ @user = Foo::User.find(1, :_organization_id => 2)
216
+ @user.fullname = "Lindsay Fünke"
217
+ @user.save
218
+ @user.fullname.should == "Lindsay Fünke"
219
+ end
220
+ end
221
+
222
+ context "deleting resources" do
223
+ it "handle resource deletion through the .destroy class method" do
224
+ @user = Foo::User.destroy_existing(1, :_organization_id => 2)
225
+ @user.active.should be_false
226
+ end
227
+
228
+ it "handle resource deletion through #destroy on an existing resource" do
229
+ @user = Foo::User.find(1, :_organization_id => 2)
230
+ @user.destroy
231
+ @user.active.should be_false
232
+ end
233
+ end
234
+ end
235
+
236
+ context "making path HTTP requests" do
237
+ before do
238
+ Her::API.setup :url => "https://example.com/api/" do |builder|
239
+ builder.use Her::Middleware::FirstLevelParseJSON
240
+ builder.use Faraday::Request::UrlEncoded
241
+ builder.adapter :test do |stub|
242
+ stub.get("/api/organizations/2/users") { |env| [200, {}, [{ :id => 1, :fullname => "Tobias Fünke", :organization_id => 2 }, { :id => 2, :fullname => "Lindsay Fünke", :organization_id => 2 }].to_json] }
243
+ stub.get("/api/organizations/2/users/1") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke", :organization_id => 2, :active => true }.to_json] }
244
+ end
245
+ end
246
+
247
+ spawn_model "Foo::User" do
248
+ collection_path "organizations/:organization_id/users"
249
+ end
250
+ end
251
+
252
+ describe "fetching a resource" do
253
+ it "maps a single resource to a Ruby object" do
254
+ @user = Foo::User.find(1, :_organization_id => 2)
255
+ @user.id.should == 1
256
+ @user.fullname.should == "Tobias Fünke"
257
+ end
258
+ end
259
+
260
+ describe "fetching a collection" do
261
+ it "maps a collection of resources to an array of Ruby objects" do
262
+ @users = Foo::User.all(:_organization_id => 2)
263
+ @users.length.should == 2
264
+ @users.first.fullname.should == "Tobias Fünke"
265
+ end
266
+ end
267
+
268
+ describe "fetching a resource with absolute path" do
269
+ it "maps a single resource to a Ruby object" do
270
+ Foo::User.resource_path '/api/' + Foo::User.resource_path
271
+ @user = Foo::User.find(1, :_organization_id => 2)
272
+ @user.id.should == 1
273
+ @user.fullname.should == "Tobias Fünke"
274
+ end
275
+ end
276
+
277
+ describe "fetching a collection with absolute path" do
278
+ it "maps a collection of resources to an array of Ruby objects" do
279
+ Foo::User.collection_path '/api/' + Foo::User.collection_path
280
+ @users = Foo::User.all(:_organization_id => 2)
281
+ @users.length.should == 2
282
+ @users.first.fullname.should == "Tobias Fünke"
283
+ end
284
+ end
285
+ end
286
+ end
@@ -0,0 +1,222 @@
1
+ # encoding: utf-8
2
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
3
+
4
+ describe Her::Model::Relationships do
5
+ context "setting relationships without details" do
6
+ before do
7
+ spawn_model "Foo::User"
8
+ end
9
+
10
+ it "handles a single 'has_many' relationship" do
11
+ Foo::User.has_many :comments
12
+ Foo::User.relationships[:has_many].should == [{ :name => :comments, :class_name => "Comment", :path => "/comments", :inverse_of => nil }]
13
+ end
14
+
15
+ it "handles multiples 'has_many' relationship" do
16
+ Foo::User.has_many :comments
17
+ Foo::User.has_many :posts
18
+ Foo::User.relationships[:has_many].should == [{ :name => :comments, :class_name => "Comment", :path => "/comments", :inverse_of => nil }, { :name => :posts, :class_name => "Post", :path => "/posts", :inverse_of => nil }]
19
+ end
20
+
21
+ it "handles a single 'has_one' relationship" do
22
+ Foo::User.has_one :category
23
+ Foo::User.relationships[:has_one].should == [{ :name => :category, :class_name => "Category", :path => "/category" }]
24
+ end
25
+
26
+ it "handles multiples 'has_one' relationship" do
27
+ Foo::User.has_one :category
28
+ Foo::User.has_one :role
29
+ Foo::User.relationships[:has_one].should == [{ :name => :category, :class_name => "Category", :path => "/category" }, { :name => :role, :class_name => "Role", :path => "/role" }]
30
+ end
31
+
32
+ it "handles a single belongs_to relationship" do
33
+ Foo::User.belongs_to :organization
34
+ Foo::User.relationships[:belongs_to].should == [{ :name => :organization, :class_name => "Organization", :foreign_key => "organization_id", :path => "/organizations/:id" }]
35
+ end
36
+
37
+ it "handles multiples 'belongs_to' relationship" do
38
+ Foo::User.belongs_to :organization
39
+ Foo::User.belongs_to :family
40
+ Foo::User.relationships[:belongs_to].should == [{ :name => :organization, :class_name => "Organization", :foreign_key => "organization_id", :path => "/organizations/:id" }, { :name => :family, :class_name => "Family", :foreign_key => "family_id", :path => "/families/:id" }]
41
+ end
42
+ end
43
+
44
+ context "setting relationships with details" do
45
+ before do
46
+ spawn_model "Foo::User"
47
+ end
48
+
49
+ it "handles a single 'has_many' relationship" do
50
+ Foo::User.has_many :comments, :class_name => "Post", :inverse_of => :admin
51
+ Foo::User.relationships[:has_many].should == [{ :name => :comments, :class_name => "Post", :path => "/comments", :inverse_of => :admin }]
52
+ end
53
+
54
+ it "handles a single 'has_one' relationship" do
55
+ Foo::User.has_one :category, :class_name => "Topic", :foreign_key => "topic_id"
56
+ Foo::User.relationships[:has_one].should == [{ :name => :category, :class_name => "Topic", :foreign_key => "topic_id", :path => "/category" }]
57
+ end
58
+
59
+ it "handles a single belongs_to relationship" do
60
+ Foo::User.belongs_to :organization, :class_name => "Business", :foreign_key => "org_id"
61
+ Foo::User.relationships[:belongs_to].should == [{ :name => :organization, :class_name => "Business", :foreign_key => "org_id", :path => "/organizations/:id" }]
62
+ end
63
+
64
+ context "inheriting relationships from a superclass" do
65
+ it "copies relationships to the subclass" do
66
+ Foo::User.has_many :comments, :class_name => "Post"
67
+ subclass = Class.new(Foo::User)
68
+ subclass.relationships.object_id.should_not == Foo::User.relationships.object_id
69
+ subclass.relationships[:has_many].length.should == 1
70
+ subclass.relationships[:has_many].first[:class_name].should == "Post"
71
+ end
72
+ end
73
+ end
74
+
75
+ context "handling relationships without details" do
76
+ before do
77
+ Her::API.setup :url => "https://api.example.com" do |builder|
78
+ builder.use Her::Middleware::FirstLevelParseJSON
79
+ builder.use Faraday::Request::UrlEncoded
80
+ builder.adapter :test do |stub|
81
+ stub.get("/users/1") { |env| [200, {}, { :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] }
82
+ stub.get("/users/2") { |env| [200, {}, { :id => 2, :name => "Lindsay Fünke", :organization_id => 2 }.to_json] }
83
+ stub.get("/users/1/comments") { |env| [200, {}, [{ :id => 4, :body => "They're having a FIRESALE?" }].to_json] }
84
+ stub.get("/users/2/comments") { |env| [200, {}, [{ :id => 4, :body => "They're having a FIRESALE?" }, { :id => 5, :body => "Is this the tiny town from Footloose?" }].to_json] }
85
+ stub.get("/users/2/role") { |env| [200, {}, { :id => 2, :body => "User" }.to_json] }
86
+ stub.get("/users/1/role") { |env| [200, {}, { :id => 3, :body => "User" }.to_json] }
87
+ stub.get("/users/1/posts") { |env| [200, {}, {:id => 1, :body => 'blogging stuff', :admin_id => 1 }.to_json] }
88
+ stub.get("/organizations/1") { |env| [200, {}, { :id => 1, :name => "Bluth Company Foo" }.to_json] }
89
+ stub.get("/organizations/2") { |env| [200, {}, { :id => 2, :name => "Bluth Company" }.to_json] }
90
+ end
91
+ end
92
+
93
+ spawn_model "Foo::User" do
94
+ has_many :comments
95
+ has_one :role
96
+ belongs_to :organization
97
+ has_many :posts, :inverse_of => :admin
98
+ end
99
+ spawn_model "Foo::Comment" do
100
+ belongs_to :user
101
+ end
102
+ spawn_model "Foo::Post" do
103
+ belongs_to :admin, :class_name => 'Foo::User'
104
+ end
105
+
106
+ spawn_model "Foo::Organization"
107
+ spawn_model "Foo::Role"
108
+
109
+ @user_with_included_data = Foo::User.find(1)
110
+ @user_without_included_data = Foo::User.find(2)
111
+ end
112
+
113
+ it "maps an array of included data through has_many" do
114
+ @user_with_included_data.comments.first.should be_a(Foo::Comment)
115
+ @user_with_included_data.comments.length.should == 2
116
+ @user_with_included_data.comments.first.id.should == 2
117
+ @user_with_included_data.comments.first.body.should == "Tobias, you blow hard!"
118
+ end
119
+
120
+ it "does not refetch the parents models data if they have been fetched before" do
121
+ @user_with_included_data.comments.first.user.object_id.should == @user_with_included_data.object_id
122
+ end
123
+
124
+ it "uses the given inverse_of key to set the parent model" do
125
+ @user_with_included_data.posts.first.admin.object_id.should == @user_with_included_data.object_id
126
+ end
127
+
128
+ it "fetches data that was not included through has_many" do
129
+ @user_without_included_data.comments.first.should be_a(Foo::Comment)
130
+ @user_without_included_data.comments.length.should == 2
131
+ @user_without_included_data.comments.first.id.should == 4
132
+ @user_without_included_data.comments.first.body.should == "They're having a FIRESALE?"
133
+ end
134
+
135
+ it "fetches has_many data even if it was included, only if called with parameters" do
136
+ @user_with_included_data.comments(:foo_id => 1).length.should == 1
137
+ end
138
+
139
+ it "maps an array of included data through has_one" do
140
+ @user_with_included_data.role.should be_a(Foo::Role)
141
+ @user_with_included_data.role.id.should == 1
142
+ @user_with_included_data.role.body.should == "Admin"
143
+ end
144
+
145
+ it "fetches data that was not included through has_one" do
146
+ @user_without_included_data.role.should be_a(Foo::Role)
147
+ @user_without_included_data.role.id.should == 2
148
+ @user_without_included_data.role.body.should == "User"
149
+ end
150
+
151
+ it "fetches has_one data even if it was included, only if called with parameters" do
152
+ @user_with_included_data.role(:foo_id => 2).id.should == 3
153
+ end
154
+
155
+ it "maps an array of included data through belongs_to" do
156
+ @user_with_included_data.organization.should be_a(Foo::Organization)
157
+ @user_with_included_data.organization.id.should == 1
158
+ @user_with_included_data.organization.name.should == "Bluth Company"
159
+ end
160
+
161
+ it "fetches data that was not included through belongs_to" do
162
+ @user_without_included_data.organization.should be_a(Foo::Organization)
163
+ @user_without_included_data.organization.id.should == 2
164
+ @user_without_included_data.organization.name.should == "Bluth Company"
165
+ end
166
+
167
+ it "fetches belongs_to data even if it was included, only if called with parameters" do
168
+ @user_with_included_data.organization(:foo_id => 1).name.should == "Bluth Company Foo"
169
+ end
170
+
171
+ it "can tell if it has a relationship" do
172
+ @user_without_included_data.has_relationship?(:unknown_relationship).should be_false
173
+ @user_without_included_data.has_relationship?(:organization).should be_true
174
+ end
175
+
176
+ it "fetches the resource corresponding to a named relationship" do
177
+ @user_without_included_data.get_relationship(:unknown_relationship).should be_nil
178
+ @user_without_included_data.get_relationship(:organization).name.should == "Bluth Company"
179
+ end
180
+ end
181
+
182
+ context "handling relationships with details" do
183
+ before do
184
+ Her::API.setup :url => "https://api.example.com" do |builder|
185
+ builder.use Her::Middleware::FirstLevelParseJSON
186
+ builder.use Faraday::Request::UrlEncoded
187
+ builder.adapter :test do |stub|
188
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :name => "Tobias Fünke", :organization => { :id => 1, :name => "Bluth Company" }, :organization_id => 1 }.to_json] }
189
+ stub.get("/users/2") { |env| [200, {}, { :id => 2, :name => "Lindsay Fünke", :organization_id => 1 }.to_json] }
190
+ stub.get("/users/3") { |env| [200, {}, { :id => 2, :name => "Lindsay Fünke", :organization => nil }.to_json] }
191
+ stub.get("/companies/1") { |env| [200, {}, { :id => 1, :name => "Bluth Company" }.to_json] }
192
+ end
193
+ end
194
+
195
+ spawn_model "Foo::User" do
196
+ belongs_to :company, :path => "/organizations/:id", :foreign_key => :organization_id
197
+ end
198
+
199
+ spawn_model "Foo::Company"
200
+
201
+ @user_with_included_data = Foo::User.find(1)
202
+ @user_without_included_data = Foo::User.find(2)
203
+ @user_with_included_nil_data = Foo::User.find(3)
204
+ end
205
+
206
+ it "maps an array of included data through belongs_to" do
207
+ @user_with_included_data.company.should be_a(Foo::Company)
208
+ @user_with_included_data.company.id.should == 1
209
+ @user_with_included_data.company.name.should == "Bluth Company"
210
+ end
211
+
212
+ it "does not map included data if it’s nil" do
213
+ @user_with_included_nil_data.organization.should be_nil
214
+ end
215
+
216
+ it "fetches data that was not included through belongs_to" do
217
+ @user_without_included_data.company.should be_a(Foo::Company)
218
+ @user_without_included_data.company.id.should == 1
219
+ @user_without_included_data.company.name.should == "Bluth Company"
220
+ end
221
+ end
222
+ end