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,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