herr 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +15 -0
  5. data/.yardopts +2 -0
  6. data/CONTRIBUTING.md +26 -0
  7. data/Gemfile +10 -0
  8. data/LICENSE +7 -0
  9. data/README.md +990 -0
  10. data/Rakefile +11 -0
  11. data/UPGRADE.md +81 -0
  12. data/gemfiles/Gemfile.activemodel-3.2.x +7 -0
  13. data/gemfiles/Gemfile.activemodel-4.0 +7 -0
  14. data/gemfiles/Gemfile.activemodel-4.1 +7 -0
  15. data/gemfiles/Gemfile.activemodel-4.2 +7 -0
  16. data/her.gemspec +30 -0
  17. data/lib/her.rb +16 -0
  18. data/lib/her/api.rb +115 -0
  19. data/lib/her/collection.rb +12 -0
  20. data/lib/her/errors.rb +27 -0
  21. data/lib/her/middleware.rb +10 -0
  22. data/lib/her/middleware/accept_json.rb +17 -0
  23. data/lib/her/middleware/first_level_parse_json.rb +36 -0
  24. data/lib/her/middleware/parse_json.rb +21 -0
  25. data/lib/her/middleware/second_level_parse_json.rb +36 -0
  26. data/lib/her/model.rb +72 -0
  27. data/lib/her/model/associations.rb +141 -0
  28. data/lib/her/model/associations/association.rb +103 -0
  29. data/lib/her/model/associations/association_proxy.rb +46 -0
  30. data/lib/her/model/associations/belongs_to_association.rb +96 -0
  31. data/lib/her/model/associations/has_many_association.rb +100 -0
  32. data/lib/her/model/associations/has_one_association.rb +79 -0
  33. data/lib/her/model/attributes.rb +266 -0
  34. data/lib/her/model/base.rb +33 -0
  35. data/lib/her/model/deprecated_methods.rb +61 -0
  36. data/lib/her/model/http.rb +114 -0
  37. data/lib/her/model/introspection.rb +65 -0
  38. data/lib/her/model/nested_attributes.rb +45 -0
  39. data/lib/her/model/orm.rb +205 -0
  40. data/lib/her/model/parse.rb +227 -0
  41. data/lib/her/model/paths.rb +121 -0
  42. data/lib/her/model/relation.rb +164 -0
  43. data/lib/her/version.rb +3 -0
  44. data/spec/api_spec.rb +131 -0
  45. data/spec/collection_spec.rb +26 -0
  46. data/spec/middleware/accept_json_spec.rb +10 -0
  47. data/spec/middleware/first_level_parse_json_spec.rb +62 -0
  48. data/spec/middleware/second_level_parse_json_spec.rb +35 -0
  49. data/spec/model/associations_spec.rb +416 -0
  50. data/spec/model/attributes_spec.rb +268 -0
  51. data/spec/model/callbacks_spec.rb +145 -0
  52. data/spec/model/dirty_spec.rb +86 -0
  53. data/spec/model/http_spec.rb +194 -0
  54. data/spec/model/introspection_spec.rb +76 -0
  55. data/spec/model/nested_attributes_spec.rb +134 -0
  56. data/spec/model/orm_spec.rb +479 -0
  57. data/spec/model/parse_spec.rb +373 -0
  58. data/spec/model/paths_spec.rb +341 -0
  59. data/spec/model/relation_spec.rb +226 -0
  60. data/spec/model/validations_spec.rb +42 -0
  61. data/spec/model_spec.rb +31 -0
  62. data/spec/spec_helper.rb +26 -0
  63. data/spec/support/extensions/array.rb +5 -0
  64. data/spec/support/extensions/hash.rb +5 -0
  65. data/spec/support/macros/her_macros.rb +17 -0
  66. data/spec/support/macros/model_macros.rb +29 -0
  67. data/spec/support/macros/request_macros.rb +27 -0
  68. metadata +280 -0
@@ -0,0 +1,268 @@
1
+ # encoding: utf-8
2
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
3
+
4
+ describe Her::Model::Attributes do
5
+ context "mapping data to Ruby objects" do
6
+ before { spawn_model "Foo::User" }
7
+
8
+ it "handles new resource" do
9
+ @new_user = Foo::User.new(:fullname => "Tobias Fünke")
10
+ @new_user.new?.should be_truthy
11
+ @new_user.fullname.should == "Tobias Fünke"
12
+ end
13
+
14
+ it "accepts new resource with strings as hash keys" do
15
+ @new_user = Foo::User.new('fullname' => "Tobias Fünke")
16
+ @new_user.fullname.should == "Tobias Fünke"
17
+ end
18
+
19
+ it "handles method missing for getter" do
20
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
21
+ expect { @new_user.unknown_method_for_a_user }.to raise_error(NoMethodError)
22
+ expect { @new_user.fullname }.not_to raise_error()
23
+ end
24
+
25
+ it "handles method missing for setter" do
26
+ @new_user = Foo::User.new
27
+ expect { @new_user.fullname = "Tobias Fünke" }.not_to raise_error()
28
+ end
29
+
30
+ it "handles method missing for query" do
31
+ @new_user = Foo::User.new
32
+ expect { @new_user.fullname? }.not_to raise_error()
33
+ end
34
+
35
+ it "handles respond_to for getter" do
36
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
37
+ @new_user.should_not respond_to(:unknown_method_for_a_user)
38
+ @new_user.should respond_to(:fullname)
39
+ end
40
+
41
+ it "handles respond_to for setter" do
42
+ @new_user = Foo::User.new
43
+ @new_user.should respond_to(:fullname=)
44
+ end
45
+
46
+ it "handles respond_to for query" do
47
+ @new_user = Foo::User.new
48
+ @new_user.should respond_to(:fullname?)
49
+ end
50
+
51
+ it "handles has_attribute? for getter" do
52
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
53
+ @new_user.should_not have_attribute(:unknown_method_for_a_user)
54
+ @new_user.should have_attribute(:fullname)
55
+ end
56
+
57
+ it "handles get_attribute for getter" do
58
+ @new_user = Foo::User.new(:fullname => 'Mayonegg')
59
+ @new_user.get_attribute(:unknown_method_for_a_user).should be_nil
60
+ @new_user.get_attribute(:fullname).should == 'Mayonegg'
61
+ end
62
+
63
+ it "handles get_attribute for getter with dash" do
64
+ @new_user = Foo::User.new(:'life-span' => '3 years')
65
+ @new_user.get_attribute(:unknown_method_for_a_user).should be_nil
66
+ @new_user.get_attribute(:'life-span').should == '3 years'
67
+ end
68
+ end
69
+
70
+
71
+ context "assigning new resource data" do
72
+ before do
73
+ spawn_model "Foo::User"
74
+ @user = Foo::User.new(:active => false)
75
+ end
76
+
77
+ it "handles data update through #assign_attributes" do
78
+ @user.assign_attributes :active => true
79
+ @user.should be_active
80
+ end
81
+ end
82
+
83
+ context "checking resource equality" do
84
+ before do
85
+ Her::API.setup :url => "https://api.example.com" do |builder|
86
+ builder.use Her::Middleware::FirstLevelParseJSON
87
+ builder.use Faraday::Request::UrlEncoded
88
+ builder.adapter :test do |stub|
89
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke" }.to_json] }
90
+ stub.get("/users/2") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke" }.to_json] }
91
+ stub.get("/admins/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke" }.to_json] }
92
+ end
93
+ end
94
+
95
+ spawn_model "Foo::User"
96
+ spawn_model "Foo::Admin"
97
+ end
98
+
99
+ let(:user) { Foo::User.find(1) }
100
+
101
+ it "returns true for the exact same object" do
102
+ user.should == user
103
+ end
104
+
105
+ it "returns true for the same resource via find" do
106
+ user.should == Foo::User.find(1)
107
+ end
108
+
109
+ it "returns true for the same class with identical data" do
110
+ user.should == Foo::User.new(:id => 1, :fullname => "Lindsay Fünke")
111
+ end
112
+
113
+ it "returns true for a different resource with the same data" do
114
+ user.should == Foo::Admin.find(1)
115
+ end
116
+
117
+ it "returns false for the same class with different data" do
118
+ user.should_not == Foo::User.new(:id => 2, :fullname => "Tobias Fünke")
119
+ end
120
+
121
+ it "returns false for a non-resource with the same data" do
122
+ fake_user = double(:data => { :id => 1, :fullname => "Lindsay Fünke" })
123
+ user.should_not == fake_user
124
+ end
125
+
126
+ it "delegates eql? to ==" do
127
+ other = Object.new
128
+ user.should_receive(:==).with(other).and_return(true)
129
+ user.eql?(other).should be_truthy
130
+ end
131
+
132
+ it "treats equal resources as equal for Array#uniq" do
133
+ user2 = Foo::User.find(1)
134
+ [user, user2].uniq.should == [user]
135
+ end
136
+
137
+ it "treats equal resources as equal for hash keys" do
138
+ Foo::User.find(1)
139
+ hash = { user => true }
140
+ hash[Foo::User.find(1)] = false
141
+ hash.size.should == 1
142
+ hash.should == { user => false }
143
+ end
144
+ end
145
+
146
+ context "handling metadata and errors" do
147
+ before do
148
+ Her::API.setup :url => "https://api.example.com" do |builder|
149
+ builder.use Her::Middleware::FirstLevelParseJSON
150
+ builder.adapter :test do |stub|
151
+ stub.post("/users") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke" }.to_json] }
152
+ end
153
+ end
154
+
155
+ spawn_model 'Foo::User' do
156
+ store_response_errors :errors
157
+ store_metadata :my_data
158
+ end
159
+
160
+ @user = Foo::User.new(:_errors => ["Foo", "Bar"], :_metadata => { :secret => true })
161
+ end
162
+
163
+ it "should return response_errors stored in the method provided by `store_response_errors`" do
164
+ @user.errors.should == ["Foo", "Bar"]
165
+ end
166
+
167
+ it "should remove the default method for errors" do
168
+ expect { @user.response_errors }.to raise_error(NoMethodError)
169
+ end
170
+
171
+ it "should return metadata stored in the method provided by `store_metadata`" do
172
+ @user.my_data.should == { :secret => true }
173
+ end
174
+
175
+ it "should remove the default method for metadata" do
176
+ expect { @user.metadata }.to raise_error(NoMethodError)
177
+ end
178
+
179
+ it "should work with #save" do
180
+ @user.assign_attributes(:fullname => "Tobias Fünke")
181
+ @user.save
182
+ expect { @user.metadata }.to raise_error(NoMethodError)
183
+ @user.my_data.should be_empty
184
+ @user.errors.should be_empty
185
+ end
186
+ end
187
+
188
+ context "overwriting default attribute methods" do
189
+ context "for getter method" do
190
+ before do
191
+ Her::API.setup :url => "https://api.example.com" do |builder|
192
+ builder.use Her::Middleware::FirstLevelParseJSON
193
+ builder.adapter :test do |stub|
194
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke", :document => { :url => "http://example.com" } }.to_json] }
195
+ end
196
+ end
197
+
198
+ spawn_model 'Foo::User' do
199
+ def document
200
+ @attributes[:document][:url]
201
+ end
202
+ end
203
+ end
204
+
205
+ it "bypasses Her's method" do
206
+ @user = Foo::User.find(1)
207
+ @user.document.should == "http://example.com"
208
+
209
+ @user = Foo::User.find(1)
210
+ @user.document.should == "http://example.com"
211
+ end
212
+ end
213
+
214
+ context "for setter method" do
215
+ before do
216
+ Her::API.setup :url => "https://api.example.com" do |builder|
217
+ builder.use Her::Middleware::FirstLevelParseJSON
218
+ builder.adapter :test do |stub|
219
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke", :document => { :url => "http://example.com" } }.to_json] }
220
+ end
221
+ end
222
+
223
+ spawn_model 'Foo::User' do
224
+ def document=(document)
225
+ @attributes[:document] = document[:url]
226
+ end
227
+ end
228
+ end
229
+
230
+ it "bypasses Her's method" do
231
+ @user = Foo::User.find(1)
232
+ @user.document.should == "http://example.com"
233
+
234
+ @user = Foo::User.find(1)
235
+ @user.document.should == "http://example.com"
236
+ end
237
+ end
238
+
239
+ context "for predicate method" do
240
+ before do
241
+ Her::API.setup :url => "https://api.example.com" do |builder|
242
+ builder.use Her::Middleware::FirstLevelParseJSON
243
+ builder.adapter :test do |stub|
244
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke", :document => { :url => nil } }.to_json] }
245
+ stub.get("/users/2") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke", :document => { :url => "http://example.com" } }.to_json] }
246
+ end
247
+ end
248
+
249
+ spawn_model 'Foo::User' do
250
+ def document?
251
+ document[:url].present?
252
+ end
253
+ end
254
+ end
255
+
256
+ it "byoasses Her's method" do
257
+ @user = Foo::User.find(1)
258
+ @user.document?.should be_falsey
259
+
260
+ @user = Foo::User.find(1)
261
+ @user.document?.should be_falsey
262
+
263
+ @user = Foo::User.find(2)
264
+ @user.document?.should be_truthy
265
+ end
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,145 @@
1
+ # encoding: utf-8
2
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
3
+
4
+ describe "Her::Model and ActiveModel::Callbacks" do
5
+ before do
6
+ Her::API.setup :url => "https://api.example.com" do |builder|
7
+ builder.use Her::Middleware::FirstLevelParseJSON
8
+ end
9
+
10
+ spawn_model "Foo::User"
11
+ end
12
+
13
+ context :before_save do
14
+ subject { Foo::User.create(:name => "Tobias Funke") }
15
+ before do
16
+ Her::API.default_api.connection.adapter :test do |stub|
17
+ stub.post("/users") { |env| [200, {}, { :id => 1, :name => env[:body][:name] }.to_json] }
18
+ stub.put("/users/1") { |env| [200, {}, { :id => 1, :name => env[:body][:name] }.to_json] }
19
+ end
20
+ end
21
+
22
+ context "when using a symbol callback" do
23
+ before do
24
+ class Foo::User
25
+ before_save :alter_name
26
+ def alter_name; self.name.upcase!; end
27
+ end
28
+ end
29
+
30
+ its(:name) { should == "TOBIAS FUNKE" }
31
+ end
32
+
33
+ context "when using a block callback" do
34
+ before do
35
+ class Foo::User
36
+ before_save lambda { self.name.upcase! }
37
+ end
38
+ end
39
+
40
+ its(:name) { should == "TOBIAS FUNKE" }
41
+ end
42
+
43
+ context "when changing a value of an existing resource in a callback" do
44
+ before do
45
+ class Foo::User
46
+ before_save :alter_name
47
+ def alter_name
48
+ self.name = "Lumberjack" if persisted?
49
+ end
50
+ end
51
+ end
52
+
53
+ it "should call the server with the canged value" do
54
+ subject.name.should == "Tobias Funke"
55
+ subject.save
56
+ subject.name.should == "Lumberjack"
57
+ end
58
+ end
59
+ end
60
+
61
+ context :before_create do
62
+ subject { Foo::User.create(:name => "Tobias Funke") }
63
+ before do
64
+ Her::API.default_api.connection.adapter :test do |stub|
65
+ stub.post("/users") { |env| [200, {}, { :id => 1, :name => env[:body][:name] }.to_json] }
66
+ end
67
+ end
68
+
69
+ context "when using a symbol callback" do
70
+ before do
71
+ class Foo::User
72
+ before_create :alter_name
73
+ def alter_name; self.name.upcase!; end
74
+ end
75
+ end
76
+
77
+ its(:name) { should == "TOBIAS FUNKE" }
78
+ end
79
+
80
+ context "when using a block callback" do
81
+ before do
82
+ class Foo::User
83
+ before_create lambda { self.name.upcase! }
84
+ end
85
+ end
86
+
87
+ its(:name) { should == "TOBIAS FUNKE" }
88
+ end
89
+ end
90
+
91
+ context :after_find do
92
+ subject { Foo::User.find(1) }
93
+ before do
94
+ Her::API.default_api.connection.adapter :test do |stub|
95
+ stub.get("/users/1") { |env| [200, {}, { :id => 1, :name => "Tobias Funke" }.to_json] }
96
+ end
97
+ end
98
+
99
+ context "when using a symbol callback" do
100
+ before do
101
+ class Foo::User
102
+ after_find :alter_name
103
+ def alter_name; self.name.upcase!; end
104
+ end
105
+ end
106
+
107
+ its(:name) { should == "TOBIAS FUNKE" }
108
+ end
109
+
110
+ context "when using a block callback" do
111
+ before do
112
+ class Foo::User
113
+ after_find lambda { self.name.upcase! }
114
+ end
115
+ end
116
+
117
+ its(:name) { should == "TOBIAS FUNKE" }
118
+ end
119
+ end
120
+
121
+ context :after_initialize do
122
+ subject { Foo::User.new(:name => "Tobias Funke") }
123
+
124
+ context "when using a symbol callback" do
125
+ before do
126
+ class Foo::User
127
+ after_initialize :alter_name
128
+ def alter_name; self.name.upcase!; end
129
+ end
130
+ end
131
+
132
+ its(:name) { should == "TOBIAS FUNKE" }
133
+ end
134
+
135
+ context "when using a block callback" do
136
+ before do
137
+ class Foo::User
138
+ after_initialize lambda { self.name.upcase! }
139
+ end
140
+ end
141
+
142
+ its(:name) { should == "TOBIAS FUNKE" }
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
3
+
4
+ describe "Her::Model and ActiveModel::Dirty" do
5
+ context "checking dirty attributes" 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.get("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Lindsay Fünke" }.to_json] }
12
+ stub.get("/users/2") { |env| [200, {}, { :id => 2, :fullname => "Maeby Fünke" }.to_json] }
13
+ stub.get("/users/3") { |env| [200, {}, { :user_id => 3, :fullname => "Maeby Fünke" }.to_json] }
14
+ stub.put("/users/1") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke" }.to_json] }
15
+ stub.put("/users/2") { |env| [400, {}, { :errors => ["Email cannot be blank"] }.to_json] }
16
+ stub.post("/users") { |env| [200, {}, { :id => 1, :fullname => "Tobias Fünke" }.to_json] }
17
+ end
18
+ end
19
+
20
+ spawn_model "Foo::User" do
21
+ attributes :fullname, :email
22
+ end
23
+ spawn_model "Dynamic::User" do
24
+ primary_key :user_id
25
+ end
26
+ end
27
+
28
+ context "for existing resource" do
29
+ let(:user) { Foo::User.find(1) }
30
+ it "has no changes" do
31
+ user.changes.should be_empty
32
+ user.should_not be_changed
33
+ end
34
+ context "with successful save" do
35
+ it "tracks dirty attributes" do
36
+ user.fullname = "Tobias Fünke"
37
+ user.fullname_changed?.should be_truthy
38
+ user.email_changed?.should be_falsey
39
+ user.should be_changed
40
+ user.save
41
+ user.should_not be_changed
42
+ end
43
+
44
+ it "tracks previous changes" do
45
+ user.fullname = "Tobias Fünke"
46
+ user.save
47
+ user.previous_changes.should eq({"fullname"=>"Lindsay Fünke"})
48
+ end
49
+
50
+ it 'tracks dirty attribute for mass assign for dynamic created attributes' do
51
+ user = Dynamic::User.find(3)
52
+ user.assign_attributes(:fullname => 'New Fullname')
53
+ user.fullname_changed?.should be_truthy
54
+ user.should be_changed
55
+ user.changes.length.should eq(1)
56
+ end
57
+ end
58
+
59
+ context "with erroneous save" do
60
+ it "tracks dirty attributes" do
61
+ user = Foo::User.find(2)
62
+ user.fullname = "Tobias Fünke"
63
+ user.fullname_changed?.should be_truthy
64
+ user.email_changed?.should be_falsey
65
+ user.should be_changed
66
+ user.save
67
+ user.should be_changed
68
+ end
69
+ end
70
+ end
71
+
72
+ context "for new resource" do
73
+ let(:user) { Foo::User.new(:fullname => "Lindsay Fünke") }
74
+ it "has changes" do
75
+ user.should be_changed
76
+ end
77
+ it "tracks dirty attributes" do
78
+ user.fullname = "Tobias Fünke"
79
+ user.fullname_changed?.should be_truthy
80
+ user.should be_changed
81
+ user.save
82
+ user.should_not be_changed
83
+ end
84
+ end
85
+ end
86
+ end