extended_her 0.5

Sign up to get free protection for your applications and to get access to all the features.
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,184 @@
1
+ # encoding: utf-8
2
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
3
+
4
+ describe Her::Model::HTTP do
5
+ context "binding a model with an API" do
6
+ let(:api1) { Her::API.new :url => "https://api1.example.com" }
7
+ let(:api2) { Her::API.new :url => "https://api2.example.com" }
8
+
9
+ before do
10
+ spawn_model("Foo::User")
11
+ spawn_model("Foo::Comment")
12
+ Her::API.setup :url => "https://api.example.com"
13
+ end
14
+
15
+ context "when binding a model to an instance of Her::API" do
16
+ before { Foo::User.uses_api api1 }
17
+ subject { Foo::User.her_api }
18
+ its(:base_uri) { should == "https://api1.example.com" }
19
+ end
20
+
21
+ context "when binding a model directly to Her::API" do
22
+ before { spawn_model "Foo::User" }
23
+ subject { Foo::User.her_api }
24
+ its(:base_uri) { should == "https://api.example.com" }
25
+ end
26
+
27
+ context "when binding two models to two different instances of Her::API" do
28
+ before do
29
+ Foo::User.uses_api api1
30
+ Foo::Comment.uses_api api2
31
+ end
32
+
33
+ specify { Foo::User.her_api.base_uri.should == "https://api1.example.com" }
34
+ specify { Foo::Comment.her_api.base_uri.should == "https://api2.example.com" }
35
+ end
36
+
37
+ context "binding one model to Her::API and another one to an instance of Her::API" do
38
+ before { Foo::Comment.uses_api api2 }
39
+ specify { Foo::User.her_api.base_uri.should == "https://api.example.com" }
40
+ specify { Foo::Comment.her_api.base_uri.should == "https://api2.example.com" }
41
+ end
42
+
43
+ context "when binding a model to its superclass' her_api" do
44
+ before do
45
+ spawn_model "Foo::Superclass"
46
+ Foo::Superclass.uses_api api1
47
+ Foo::Subclass = Class.new(Foo::Superclass)
48
+ end
49
+
50
+ specify { Foo::Subclass.her_api.should == Foo::Superclass.her_api }
51
+ end
52
+
53
+ context "when changing her_api without changing the parent class' her_api" do
54
+ before do
55
+ spawn_model "Foo::Superclass"
56
+ Foo::Subclass = Class.new(Foo::Superclass)
57
+ Foo::Superclass.uses_api api1
58
+ Foo::Subclass.uses_api api2
59
+ end
60
+
61
+ specify { Foo::Subclass.her_api.should_not == Foo::Superclass.her_api }
62
+ end
63
+ end
64
+
65
+ context "making HTTP requests" do
66
+ before do
67
+ Her::API.setup :url => "https://api.example.com" do |builder|
68
+ builder.use Her::Middleware::FirstLevelParseJSON
69
+ builder.use Faraday::Request::UrlEncoded
70
+ builder.adapter :test do |stub|
71
+ stub.get("/users") { |env| [200, {}, [{ :id => 1 }].to_json] }
72
+ stub.get("/users/1") { |env| [200, {}, { :id => 1 }.to_json] }
73
+ stub.get("/users/popular") do |env|
74
+ if env[:params]["page"] == "2"
75
+ [200, {}, [{ :id => 3 }, { :id => 4 }].to_json]
76
+ else
77
+ [200, {}, [{ :id => 1 }, { :id => 2 }].to_json]
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ spawn_model "Foo::User"
84
+ end
85
+
86
+ describe :get do
87
+ subject { Foo::User.get(:popular) }
88
+ its(:length) { should == 2 }
89
+ specify { subject.first.id.should == 1 }
90
+ end
91
+
92
+ describe :get_raw do
93
+ context "with a block" do
94
+ specify do
95
+ Foo::User.get_raw("/users") do |parsed_data|
96
+ parsed_data[:data].should == [{ :id => 1 }]
97
+ end
98
+ end
99
+ end
100
+
101
+ context "with a return value" do
102
+ subject { Foo::User.get_raw("/users") }
103
+ specify { subject[:data].should == [{ :id => 1 }] }
104
+ end
105
+ end
106
+
107
+ describe :get_collection do
108
+ context "with a String path" do
109
+ subject { Foo::User.get_collection("/users/popular") }
110
+ its(:length) { should == 2 }
111
+ specify { subject.first.id.should == 1 }
112
+ end
113
+
114
+ context "with a Symbol" do
115
+ subject { Foo::User.get_collection(:popular) }
116
+ its(:length) { should == 2 }
117
+ specify { subject.first.id.should == 1 }
118
+ end
119
+
120
+ context "with extra parameters" do
121
+ subject { Foo::User.get_collection(:popular, :page => 2) }
122
+ its(:length) { should == 2 }
123
+ specify { subject.first.id.should == 3 }
124
+ end
125
+ end
126
+
127
+ describe :get_resource do
128
+ context "with a String path" do
129
+ subject { Foo::User.get_resource("/users/1") }
130
+ its(:id) { should == 1 }
131
+ end
132
+
133
+ context "with a Symbol" do
134
+ subject { Foo::User.get_resource(:"1") }
135
+ its(:id) { should == 1 }
136
+ end
137
+ end
138
+
139
+ describe :get_raw do
140
+ specify do
141
+ Foo::User.get_raw(:popular) do |parsed_data|
142
+ parsed_data[:data].should == [{ :id => 1 }, { :id => 2 }]
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ context "setting custom HTTP requests" do
149
+ before do
150
+ Her::API.setup :url => "https://api.example.com" do |connection|
151
+ connection.use Her::Middleware::FirstLevelParseJSON
152
+ connection.adapter :test do |stub|
153
+ stub.get("/users/popular") { |env| [200, {}, [{ :id => 1 }, { :id => 2 }].to_json] }
154
+ stub.post("/users/from_default") { |env| [200, {}, { :id => 4 }.to_json] }
155
+ end
156
+ end
157
+
158
+ spawn_model "Foo::User"
159
+ end
160
+
161
+ subject { Foo::User }
162
+
163
+ describe :custom_get do
164
+ before { Foo::User.custom_get :popular, :recent }
165
+ it { should respond_to(:popular) }
166
+ it { should respond_to(:recent) }
167
+
168
+ context "making the HTTP request" do
169
+ subject { Foo::User.popular }
170
+ its(:length) { should == 2 }
171
+ end
172
+ end
173
+
174
+ describe :custom_post do
175
+ before { Foo::User.custom_post :from_default }
176
+ it { should respond_to(:from_default) }
177
+
178
+ context "making the HTTP request" do
179
+ subject { Foo::User.from_default(:name => "Tobias Fünke") }
180
+ its(:id) { should == 4 }
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
3
+
4
+ describe Her::Model::Introspection do
5
+ context "introspecting a resource" do
6
+ before do
7
+ Her::API.setup :url => "https://api.example.com" do |builder|
8
+ builder.use Her::Middleware::FirstLevelParseJSON
9
+ builder.use Faraday::Request::UrlEncoded
10
+ builder.adapter :test do |stub|
11
+ stub.post("/users") { |env| [200, {}, { :id => 1, :name => "Tobias Funke" }.to_json] }
12
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :name => "Tobias Funke" }.to_json] }
13
+ stub.put("/users/1") { |env| [200, {}, { :id => 1, :name => "Tobias Funke" }.to_json] }
14
+ stub.delete("/users/1") { |env| [200, {}, { :id => 1, :name => "Tobias Funke" }.to_json] }
15
+ end
16
+ end
17
+
18
+ spawn_model "Foo::User"
19
+ end
20
+
21
+ describe "#inspect" do
22
+ it "outputs resource attributes for an existing resource" do
23
+ @user = Foo::User.find(1)
24
+ ["#<Foo::User name: \"Tobias Funke\", id: 1>", "#<Foo::User id: 1, name: \"Tobias Funke\">"].should include(@user.inspect)
25
+ end
26
+
27
+ it "outputs resource attributes for an not-saved-yet resource" do
28
+ @user = Foo::User.new(:name => "Tobias Funke")
29
+ @user.inspect.should == "#<Foo::User name: \"Tobias Funke\">"
30
+ end
31
+
32
+ it "outputs resource attributes using getters" do
33
+ @user = Foo::User.new(:name => "Tobias Funke", :password => "Funke")
34
+ @user.instance_eval {def password; 'filtered'; end}
35
+ @user.inspect.should include("name: \"Tobias Funke\"")
36
+ @user.inspect.should include("password: \"filtered\"")
37
+ @user.inspect.should_not include("password: \"Funke\"")
38
+ end
39
+ end
40
+ end
41
+
42
+ describe "#nearby_class" do
43
+ context "for a class inside of a module" do
44
+ before do
45
+ spawn_model "Foo::User"
46
+ spawn_model "Foo::AccessRecord"
47
+ spawn_model "AccessRecord"
48
+ spawn_model "Log"
49
+ end
50
+
51
+ it "returns a sibling class, if found" do
52
+ Foo::User.nearby_class("AccessRecord").should == Foo::AccessRecord
53
+ AccessRecord.nearby_class("Log").should == Log
54
+ Foo::User.nearby_class("Log").should == Log
55
+ Foo::User.nearby_class("X").should be_nil
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,552 @@
1
+ # encoding: utf-8
2
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
3
+
4
+ describe Her::Model::ORM do
5
+ context "mapping data to Ruby objects" do
6
+ before do
7
+ api = Her::API.new
8
+ api.setup :url => "https://api.example.com" do |builder|
9
+ builder.use Her::Middleware::FirstLevelParseJSON
10
+ builder.use Faraday::Request::UrlEncoded
11
+ builder.adapter :test do |stub|
12
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :name => "Tobias Fünke" }.to_json] }
13
+ stub.get("/users") { |env| [200, {}, [{ :id => 1, :name => "Tobias Fünke" }, { :id => 2, :name => "Lindsay Fünke" }].to_json] }
14
+ stub.get("/admin_users") { |env| [200, {}, [{ :id => 1, :name => "Tobias Fünke" }, { :id => 2, :name => "Lindsay Fünke" }].to_json] }
15
+ end
16
+ end
17
+
18
+ spawn_model "Foo::User" do
19
+ uses_api api
20
+ end
21
+
22
+ spawn_model "Foo::AdminUser" do
23
+ uses_api api
24
+ end
25
+ end
26
+
27
+ it "maps a single resource to a Ruby object" do
28
+ @user = Foo::User.find(1)
29
+ @user.id.should == 1
30
+ @user.name.should == "Tobias Fünke"
31
+ end
32
+
33
+ it "maps a collection of resources to an array of Ruby objects" do
34
+ @users = Foo::User.all
35
+ @users.length.should == 2
36
+ @users.first.name.should == "Tobias Fünke"
37
+
38
+ @users = Foo::AdminUser.all
39
+ @users.length.should == 2
40
+ @users.first.name.should == "Tobias Fünke"
41
+ end
42
+
43
+ it "handles new resource" do
44
+ @new_user = Foo::User.new(:fullname => "Tobias Fünke")
45
+ @new_user.new?.should be_true
46
+ @new_user.fullname.should == "Tobias Fünke"
47
+
48
+ @existing_user = Foo::User.find(1)
49
+ @existing_user.new?.should be_false
50
+ end
51
+
52
+ it "accepts new resource with strings as hash keys" do
53
+ @new_user = Foo::User.new('fullname' => "Tobias Fünke")
54
+ @new_user.fullname.should == "Tobias Fünke"
55
+ end
56
+
57
+ it "handles method missing for getter" do
58
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
59
+ lambda { @new_user.unknown_method_for_a_user }.should raise_error(NoMethodError)
60
+ expect { @new_user.fullname }.to_not raise_error(NoMethodError)
61
+ end
62
+
63
+ it "handles method missing for setter" do
64
+ @new_user = Foo::User.new
65
+ expect { @new_user.fullname = "Tobias Fünke" }.to_not raise_error(NoMethodError)
66
+ end
67
+
68
+ it "handles method missing for query" do
69
+ @new_user = Foo::User.new
70
+ expect { @new_user.fullname? }.to_not raise_error(NoMethodError)
71
+ end
72
+
73
+ it "handles respond_to for getter" do
74
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
75
+ @new_user.should_not respond_to(:unknown_method_for_a_user)
76
+ @new_user.should respond_to(:fullname)
77
+ end
78
+
79
+ it "handles respond_to for setter" do
80
+ @new_user = Foo::User.new
81
+ @new_user.should respond_to(:fullname=)
82
+ end
83
+
84
+ it "handles respond_to for query" do
85
+ @new_user = Foo::User.new
86
+ @new_user.should respond_to(:fullname?)
87
+ end
88
+
89
+ it "handles has_data? for getter" do
90
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
91
+ @new_user.should_not have_data(:unknown_method_for_a_user)
92
+ @new_user.should have_data(:fullname)
93
+ end
94
+
95
+ it "handles get_data for getter" do
96
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
97
+ @new_user.get_data(:unknown_method_for_a_user).should be_nil
98
+ @new_user.get_data(:fullname).should == 'Mayonegg'
99
+ end
100
+ end
101
+
102
+ context "mapping data, metadata and error data to Ruby objects" do
103
+ before do
104
+ api = Her::API.new
105
+ api.setup :url => "https://api.example.com" do |builder|
106
+ builder.use Her::Middleware::SecondLevelParseJSON
107
+ builder.use Faraday::Request::UrlEncoded
108
+ builder.adapter :test do |stub|
109
+ stub.get("/users") { |env| [200, {}, { :data => [{ :id => 1, :name => "Tobias Fünke" }, { :id => 2, :name => "Lindsay Fünke" }], :metadata => { :total_pages => 10, :next_page => 2 }, :errors => ["Oh", "My", "God"] }.to_json] }
110
+ stub.post("/users") { |env| [200, {}, { :data => { :name => "George Michael Bluth" }, :metadata => { :foo => "bar" }, :errors => ["Yes", "Sir"] }.to_json] }
111
+ end
112
+ end
113
+
114
+ spawn_model :User do
115
+ uses_api api
116
+ end
117
+ end
118
+
119
+ it "handles metadata on a collection" do
120
+ @users = User.all
121
+ @users.metadata[:total_pages].should == 10
122
+ end
123
+
124
+ it "handles error data on a collection" do
125
+ @users = User.all
126
+ @users.errors.length.should == 3
127
+ end
128
+
129
+ it "handles metadata on a resource" do
130
+ @user = User.create(:name => "George Michael Bluth")
131
+ @user.metadata[:foo].should == "bar"
132
+ end
133
+
134
+ it "handles error data on a resource" do
135
+ @user = User.create(:name => "George Michael Bluth")
136
+ @user.errors.should == ["Yes", "Sir"]
137
+ @user.should be_invalid
138
+ end
139
+ end
140
+
141
+ context "defining custom getters and setters" do
142
+ before do
143
+ api = Her::API.new
144
+ 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("/users/1") { |env| [200, {}, { :id => 1, :friends => ["Maeby", "GOB", "Anne"] }.to_json] }
149
+ stub.get("/users/2") { |env| [200, {}, { :id => 1, :organization => true }.to_json] }
150
+ end
151
+ end
152
+
153
+ spawn_model :User do
154
+ uses_api api
155
+ belongs_to :organization
156
+
157
+ def friends=(val)
158
+ val = val.gsub("\r", "").split("\n").map { |friend| friend.gsub(/^\s*\*\s*/, "") } if val and val.is_a?(String)
159
+ @data[:friends] = val
160
+ end
161
+
162
+ def friends
163
+ @data[:friends].map { |friend| "* #{friend}" }.join("\n")
164
+ end
165
+
166
+ # Why would anybody want to do this? I don’t know.
167
+ def organization=(organization)
168
+ @data[:organization] = { :foo => :bar }
169
+ end
170
+ end
171
+ end
172
+
173
+ it "handles custom setters" do
174
+ @user = User.find(1)
175
+ @user.friends.should == "* Maeby\n* GOB\n* Anne"
176
+ @user.instance_eval do
177
+ @data[:friends] = ["Maeby", "GOB", "Anne"]
178
+ end
179
+ end
180
+
181
+ it "handles custom setters with relationships" do
182
+ @user = User.find(2)
183
+ @user.organization.should == { :foo => :bar }
184
+ end
185
+
186
+ it "handles custom getters" do
187
+ @user = User.new
188
+ @user.friends = "* George\n* Oscar\n* Lucille"
189
+ @user.friends.should == "* George\n* Oscar\n* Lucille"
190
+ @user.instance_eval do
191
+ @data[:friends] = ["George", "Oscar", "Lucille"]
192
+ end
193
+ end
194
+ end
195
+
196
+ context "finding resources" do
197
+ before do
198
+ api = Her::API.new
199
+ api.setup :url => "https://api.example.com" do |builder|
200
+ builder.use Her::Middleware::FirstLevelParseJSON
201
+ builder.use Faraday::Request::UrlEncoded
202
+ builder.adapter :test do |stub|
203
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :age => 42 }.to_json] }
204
+ stub.get("/users/2") { |env| [200, {}, { :id => 2, :age => 34 }.to_json] }
205
+ stub.get("/users?age=42") { |env| [200, {}, [{ :id => 1, :age => 42 }].to_json] }
206
+ end
207
+ end
208
+
209
+ spawn_model :User do
210
+ uses_api api
211
+ end
212
+ end
213
+
214
+ it "handles finding by a single id" do
215
+ @user = User.find(1)
216
+ @user.id.should == 1
217
+ end
218
+
219
+ it "handles finding by multiple ids" do
220
+ @users = User.find(1, 2)
221
+ @users.should be_kind_of(Array)
222
+ @users.length.should == 2
223
+ @users[0].id.should == 1
224
+ @users[1].id.should == 2
225
+ end
226
+
227
+ it "handles finding by an array of ids" do
228
+ @users = User.find([1, 2])
229
+ @users.should be_kind_of(Array)
230
+ @users.length.should == 2
231
+ @users[0].id.should == 1
232
+ @users[1].id.should == 2
233
+ end
234
+
235
+ it "handles finding by an array of ids of length 1" do
236
+ @users = User.find([1])
237
+ @users.should be_kind_of(Array)
238
+ @users.length.should == 1
239
+ @users[0].id.should == 1
240
+ end
241
+
242
+ it "handles finding with other parameters" do
243
+ @users = User.all(:age => 42)
244
+ @users.should be_kind_of(Array)
245
+ @users.should be_all { |u| u.age == 42 }
246
+ end
247
+ end
248
+
249
+ context "creating resources" do
250
+ before do
251
+ Her::API.setup :url => "https://api.example.com" do |builder|
252
+ builder.use Her::Middleware::FirstLevelParseJSON
253
+ builder.use Faraday::Request::UrlEncoded
254
+ builder.adapter :test do |stub|
255
+ stub.post("/users") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke" }.to_json] }
256
+ stub.post("/companies") { |env| [200, {}, { :errors => ["name is required"] }.to_json] }
257
+ end
258
+ end
259
+
260
+ spawn_model "Foo::User"
261
+ spawn_model "Foo::Company"
262
+ end
263
+
264
+ it "handle one-line resource creation" do
265
+ @user = Foo::User.create(:fullname => "Tobias Fünke")
266
+ @user.id.should == 1
267
+ @user.fullname.should == "Tobias Fünke"
268
+ end
269
+
270
+ it "handle resource creation through Model.new + #save" do
271
+ @user = Foo::User.new(:fullname => "Tobias Fünke")
272
+ @user.save.should be_true
273
+ @user.fullname.should == "Tobias Fünke"
274
+ end
275
+
276
+ it "returns false when #save gets errors" do
277
+ @company = Foo::Company.new
278
+ @company.save.should be_false
279
+ end
280
+
281
+ it "don't overwrite data if response is empty" do
282
+ @company = Foo::Company.new(:name => 'Company Inc.')
283
+ @company.save.should be_false
284
+ @company.name.should == "Company Inc."
285
+ end
286
+ end
287
+
288
+ context "updating resources" do
289
+ before do
290
+ Her::API.setup :url => "https://api.example.com" do |builder|
291
+ builder.use Her::Middleware::FirstLevelParseJSON
292
+ builder.use Faraday::Request::UrlEncoded
293
+ builder.adapter :test do |stub|
294
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke" }.to_json] }
295
+ stub.put("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke" }.to_json] }
296
+ end
297
+ end
298
+
299
+ spawn_model "Foo::User"
300
+ end
301
+
302
+ it "handle resource data update without saving it" do
303
+ @user = Foo::User.find(1)
304
+ @user.fullname.should == "Tobias Fünke"
305
+ @user.fullname = "Kittie Sanchez"
306
+ @user.fullname.should == "Kittie Sanchez"
307
+ end
308
+
309
+ it "handle resource update through the .update class method" do
310
+ @user = Foo::User.save_existing(1, { :fullname => "Lindsay Fünke" })
311
+ @user.fullname.should == "Lindsay Fünke"
312
+ end
313
+
314
+ it "handle resource update through #save on an existing resource" do
315
+ @user = Foo::User.find(1)
316
+ @user.fullname = "Lindsay Fünke"
317
+ @user.save
318
+ @user.fullname.should == "Lindsay Fünke"
319
+ end
320
+ end
321
+
322
+ context "assigning new resource data" do
323
+ before do
324
+ Her::API.setup :url => "https://api.example.com" do |builder|
325
+ builder.use Her::Middleware::FirstLevelParseJSON
326
+ builder.use Faraday::Request::UrlEncoded
327
+ builder.adapter :test do |stub|
328
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke", :active => true }.to_json] }
329
+ end
330
+ end
331
+
332
+ spawn_model "Foo::User"
333
+ @user = Foo::User.find(1)
334
+ end
335
+
336
+ it "handles data update through #assign_attributes" do
337
+ @user.assign_attributes :active => true
338
+ @user.should be_active
339
+ end
340
+ end
341
+
342
+ context "deleting resources" do
343
+ before do
344
+ Her::API.setup :url => "https://api.example.com" do |builder|
345
+ builder.use Her::Middleware::FirstLevelParseJSON
346
+ builder.use Faraday::Request::UrlEncoded
347
+ builder.adapter :test do |stub|
348
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke", :active => true }.to_json] }
349
+ stub.delete("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke", :active => false }.to_json] }
350
+ end
351
+ end
352
+
353
+ spawn_model "Foo::User"
354
+ end
355
+
356
+ it "handle resource deletion through the .destroy class method" do
357
+ @user = Foo::User.destroy_existing(1)
358
+ @user.active.should be_false
359
+ end
360
+
361
+ it "handle resource deletion through #destroy on an existing resource" do
362
+ @user = Foo::User.find(1)
363
+ @user.destroy
364
+ @user.active.should be_false
365
+ end
366
+ end
367
+
368
+ context "saving resources with overridden to_params" do
369
+ before do
370
+ Her::API.setup :url => "https://api.example.com" do |builder|
371
+ builder.use Her::Middleware::FirstLevelParseJSON
372
+ builder.use Faraday::Request::UrlEncoded
373
+ builder.adapter :test do |stub|
374
+ stub.post("/users") do |env|
375
+ body = {
376
+ :id => 1,
377
+ :fullname => Faraday::Utils.parse_query(env[:body])['fullname']
378
+ }.to_json
379
+ [200, {}, body]
380
+ end
381
+ end
382
+ end
383
+
384
+ spawn_model "Foo::User" do
385
+ def to_params
386
+ { :fullname => "Lindsay Fünke" }
387
+ end
388
+ end
389
+ end
390
+
391
+ it "changes the request parameters for one-line resource creation" do
392
+ @user = Foo::User.create(:fullname => "Tobias Fünke")
393
+ @user.fullname.should == "Lindsay Fünke"
394
+ end
395
+
396
+ it "changes the request parameters for Model.new + #save" do
397
+ @user = Foo::User.new(:fullname => "Tobias Fünke")
398
+ @user.save
399
+ @user.fullname.should == "Lindsay Fünke"
400
+ end
401
+ end
402
+
403
+ context "checking resource equality" do
404
+ before do
405
+ Her::API.setup :url => "https://api.example.com" do |builder|
406
+ builder.use Her::Middleware::FirstLevelParseJSON
407
+ builder.use Faraday::Request::UrlEncoded
408
+ builder.adapter :test do |stub|
409
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke" }.to_json] }
410
+ stub.get("/users/2") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke" }.to_json] }
411
+ stub.get("/admins/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke" }.to_json] }
412
+ end
413
+ end
414
+
415
+ spawn_model "Foo::User"
416
+ spawn_model "Foo::Admin"
417
+ end
418
+
419
+ let(:user) { Foo::User.find(1) }
420
+
421
+ it "returns true for the exact same object" do
422
+ user.should == user
423
+ end
424
+
425
+ it "returns true for the same resource via find" do
426
+ user.should == Foo::User.find(1)
427
+ end
428
+
429
+ it "returns true for the same class with identical data" do
430
+ user.should == Foo::User.new(:id => 1, :fullname => "Lindsay Fünke")
431
+ end
432
+
433
+ it "returns true for a different resource with the same data" do
434
+ user.should == Foo::Admin.find(1)
435
+ end
436
+
437
+ it "returns false for the same class with different data" do
438
+ user.should_not == Foo::User.new(:id => 2, :fullname => "Tobias Fünke")
439
+ end
440
+
441
+ it "returns false for a non-resource with the same data" do
442
+ fake_user = stub(:data => { :id => 1, :fullname => "Lindsay Fünke" })
443
+ user.should_not == fake_user
444
+ end
445
+
446
+ it "delegates eql? to ==" do
447
+ other = Object.new
448
+ user.expects(:==).with(other).returns(true)
449
+ user.eql?(other).should be_true
450
+ end
451
+
452
+ it "treats equal resources as equal for Array#uniq" do
453
+ user2 = Foo::User.find(1)
454
+ [user, user2].uniq.should == [user]
455
+ end
456
+
457
+ it "treats equal resources as equal for hash keys" do
458
+ Foo::User.find(1)
459
+ hash = { user => true }
460
+ hash[Foo::User.find(1)] = false
461
+ hash.size.should == 1
462
+ hash.should == { user => false }
463
+ end
464
+ end
465
+
466
+ context "when include_root_in_json is true" do
467
+ context "when include_root_in_json is true" do
468
+ before do
469
+ spawn_model "Foo::User" do
470
+ include_root_in_json true
471
+ end
472
+ end
473
+
474
+ it "wraps params in the element name" do
475
+ @new_user = Foo::User.new(:fullname => "Tobias Fünke")
476
+ @new_user.to_params.should == { 'user' => { :fullname => "Tobias Fünke" } }
477
+ end
478
+ end
479
+
480
+ context "when include_root_in_json is set to another value" do
481
+ before do
482
+ spawn_model "Foo::User" do
483
+ include_root_in_json :person
484
+ end
485
+ end
486
+
487
+ it "wraps params in the specified value" do
488
+ @new_user = Foo::User.new(:fullname => "Tobias Fünke")
489
+ @new_user.to_params.should == { :person => { :fullname => "Tobias Fünke" } }
490
+ end
491
+ end
492
+ end
493
+
494
+ context "when parse_root_in_json is set" do
495
+ before do
496
+ Her::API.setup :url => "https://api.example.com" do |builder|
497
+ builder.use Her::Middleware::FirstLevelParseJSON
498
+ builder.use Faraday::Request::UrlEncoded
499
+ end
500
+ end
501
+
502
+ context "when parse_root_in_json is true" do
503
+ before do
504
+ Her::API.default_api.connection.adapter :test do |stub|
505
+ stub.post("/users") { |env| [200, {}, { :user => { :id => 1, :fullname => "Lindsay Fünke" } }.to_json] }
506
+ stub.get("/users") { |env| [200, {}, [{ :user => { :id => 1, :fullname => "Lindsay Fünke" } }].to_json] }
507
+ stub.get("/users/1") { |env| [200, {}, { :user => { :id => 1, :fullname => "Lindsay Fünke" } }.to_json] }
508
+ stub.put("/users/1") { |env| [200, {}, { :user => { :id => 1, :fullname => "Tobias Fünke Jr." } }.to_json] }
509
+ end
510
+
511
+ spawn_model("Foo::User") { parse_root_in_json true }
512
+ end
513
+
514
+ it "parse the data from the JSON root element after .create" do
515
+ @new_user = Foo::User.create(:fullname => "Lindsay Fünke")
516
+ @new_user.fullname.should == "Lindsay Fünke"
517
+ end
518
+
519
+ it "parse the data from the JSON root element after .all" do
520
+ @users = Foo::User.all
521
+ @users.first.fullname.should == "Lindsay Fünke"
522
+ end
523
+
524
+ it "parse the data from the JSON root element after .find" do
525
+ @user = Foo::User.find(1)
526
+ @user.fullname.should == "Lindsay Fünke"
527
+ end
528
+
529
+ it "parse the data from the JSON root element after .save" do
530
+ @user = Foo::User.find(1)
531
+ @user.fullname = "Tobias Fünke"
532
+ @user.save
533
+ @user.fullname.should == "Tobias Fünke Jr."
534
+ end
535
+ end
536
+
537
+ context "when parse_root_in_json is set to a symbol" do
538
+ before do
539
+ Her::API.default_api.connection.adapter :test do |stub|
540
+ stub.post("/users") { |env| [200, {}, { :person => { :id => 1, :fullname => "Lindsay Fünke" } }.to_json] }
541
+ end
542
+
543
+ spawn_model("Foo::User") { parse_root_in_json :person }
544
+ end
545
+
546
+ it "parse the data with the symbol" do
547
+ @new_user = Foo::User.create(:fullname => "Lindsay Fünke")
548
+ @new_user.fullname.should == "Lindsay Fünke"
549
+ end
550
+ end
551
+ end
552
+ end