herr 0.7.3

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