her5 0.8.1

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 (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +17 -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 +1017 -0
  10. data/Rakefile +11 -0
  11. data/UPGRADE.md +101 -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/gemfiles/Gemfile.activemodel-5.0.x +7 -0
  17. data/her5.gemspec +30 -0
  18. data/lib/her.rb +19 -0
  19. data/lib/her/api.rb +120 -0
  20. data/lib/her/collection.rb +12 -0
  21. data/lib/her/errors.rb +104 -0
  22. data/lib/her/json_api/model.rb +57 -0
  23. data/lib/her/middleware.rb +12 -0
  24. data/lib/her/middleware/accept_json.rb +17 -0
  25. data/lib/her/middleware/first_level_parse_json.rb +36 -0
  26. data/lib/her/middleware/json_api_parser.rb +68 -0
  27. data/lib/her/middleware/parse_json.rb +28 -0
  28. data/lib/her/middleware/second_level_parse_json.rb +36 -0
  29. data/lib/her/model.rb +75 -0
  30. data/lib/her/model/associations.rb +141 -0
  31. data/lib/her/model/associations/association.rb +107 -0
  32. data/lib/her/model/associations/association_proxy.rb +45 -0
  33. data/lib/her/model/associations/belongs_to_association.rb +101 -0
  34. data/lib/her/model/associations/has_many_association.rb +101 -0
  35. data/lib/her/model/associations/has_one_association.rb +80 -0
  36. data/lib/her/model/attributes.rb +297 -0
  37. data/lib/her/model/base.rb +33 -0
  38. data/lib/her/model/deprecated_methods.rb +61 -0
  39. data/lib/her/model/http.rb +113 -0
  40. data/lib/her/model/introspection.rb +65 -0
  41. data/lib/her/model/nested_attributes.rb +84 -0
  42. data/lib/her/model/orm.rb +207 -0
  43. data/lib/her/model/parse.rb +221 -0
  44. data/lib/her/model/paths.rb +126 -0
  45. data/lib/her/model/relation.rb +164 -0
  46. data/lib/her/version.rb +3 -0
  47. data/spec/api_spec.rb +114 -0
  48. data/spec/collection_spec.rb +26 -0
  49. data/spec/json_api/model_spec.rb +305 -0
  50. data/spec/middleware/accept_json_spec.rb +10 -0
  51. data/spec/middleware/first_level_parse_json_spec.rb +62 -0
  52. data/spec/middleware/json_api_parser_spec.rb +32 -0
  53. data/spec/middleware/second_level_parse_json_spec.rb +35 -0
  54. data/spec/model/associations/association_proxy_spec.rb +31 -0
  55. data/spec/model/associations_spec.rb +504 -0
  56. data/spec/model/attributes_spec.rb +389 -0
  57. data/spec/model/callbacks_spec.rb +145 -0
  58. data/spec/model/dirty_spec.rb +91 -0
  59. data/spec/model/http_spec.rb +158 -0
  60. data/spec/model/introspection_spec.rb +76 -0
  61. data/spec/model/nested_attributes_spec.rb +134 -0
  62. data/spec/model/orm_spec.rb +506 -0
  63. data/spec/model/parse_spec.rb +345 -0
  64. data/spec/model/paths_spec.rb +347 -0
  65. data/spec/model/relation_spec.rb +226 -0
  66. data/spec/model/validations_spec.rb +42 -0
  67. data/spec/model_spec.rb +44 -0
  68. data/spec/spec_helper.rb +26 -0
  69. data/spec/support/extensions/array.rb +5 -0
  70. data/spec/support/extensions/hash.rb +5 -0
  71. data/spec/support/macros/her_macros.rb +17 -0
  72. data/spec/support/macros/model_macros.rb +36 -0
  73. data/spec/support/macros/request_macros.rb +27 -0
  74. metadata +289 -0
@@ -0,0 +1,3 @@
1
+ module Her
2
+ VERSION = "0.8.1"
3
+ end
@@ -0,0 +1,114 @@
1
+ # encoding: utf-8
2
+ require File.join(File.dirname(__FILE__), "spec_helper.rb")
3
+
4
+ describe Her::API do
5
+ subject { Her::API.new }
6
+
7
+ context "initialization" do
8
+ describe "#setup" do
9
+ context "when setting custom middleware" do
10
+ before do
11
+ class Foo; end;
12
+ class Bar; end;
13
+
14
+ subject.setup :url => "https://api.example.com" do |connection|
15
+ connection.use Foo
16
+ connection.use Bar
17
+ end
18
+ end
19
+
20
+ specify { subject.connection.builder.handlers.should == [Foo, Bar] }
21
+ end
22
+
23
+ context "when setting custom options" do
24
+ before { subject.setup :foo => { :bar => "baz" }, :url => "https://api.example.com" }
25
+ its(:options) { should == { :foo => { :bar => "baz" }, :url => "https://api.example.com" } }
26
+ end
27
+ end
28
+
29
+ describe "#request" do
30
+ before do
31
+ class SimpleParser < Faraday::Response::Middleware
32
+ def on_complete(env)
33
+ env[:body] = { :data => env[:body] }
34
+ end
35
+ end
36
+ end
37
+
38
+ context "making HTTP requests" do
39
+ let(:parsed_data) { subject.request(:_method => :get, :_path => "/foo")[:parsed_data] }
40
+ before do
41
+ subject.setup :url => "https://api.example.com" do |builder|
42
+ builder.use SimpleParser
43
+ builder.adapter(:test) { |stub| stub.get("/foo") { |env| [200, {}, "Foo, it is."] } }
44
+ end
45
+ end
46
+
47
+ specify { parsed_data[:data].should == "Foo, it is." }
48
+ end
49
+
50
+ context "making HTTP requests while specifying custom HTTP headers" do
51
+ let(:parsed_data) { subject.request(:_method => :get, :_path => "/foo", :_headers => { "X-Page" => 2 })[:parsed_data] }
52
+
53
+ before do
54
+ subject.setup :url => "https://api.example.com" do |builder|
55
+ builder.use SimpleParser
56
+ builder.adapter(:test) { |stub| stub.get("/foo") { |env| [200, {}, "Foo, it is page #{env[:request_headers]["X-Page"]}."] } }
57
+ end
58
+ end
59
+
60
+ specify { parsed_data[:data].should == "Foo, it is page 2." }
61
+ end
62
+
63
+ context "parsing a request with the default parser" do
64
+ let(:parsed_data) { subject.request(:_method => :get, :_path => "users/1")[:parsed_data] }
65
+ before do
66
+ subject.setup :url => "https://api.example.com" do |builder|
67
+ builder.use Her::Middleware::FirstLevelParseJSON
68
+ builder.adapter :test do |stub|
69
+ stub.get("/users/1") { |env| [200, {}, MultiJson.dump({ :id => 1, :name => "George Michael Bluth", :errors => ["This is a single error"], :metadata => { :page => 1, :per_page => 10 } })] }
70
+ end
71
+ end
72
+ end
73
+
74
+ specify do
75
+ parsed_data[:data].should == { :id => 1, :name => "George Michael Bluth" }
76
+ parsed_data[:errors].should == ["This is a single error"]
77
+ parsed_data[:metadata].should == { :page => 1, :per_page => 10 }
78
+ end
79
+ end
80
+
81
+ context "parsing a request with a custom parser" do
82
+ let(:parsed_data) { subject.request(:_method => :get, :_path => "users/1")[:parsed_data] }
83
+ before do
84
+ class CustomParser < Faraday::Response::Middleware
85
+ def on_complete(env)
86
+ json = MultiJson.load(env[:body], :symbolize_keys => true)
87
+ errors = json.delete(:errors) || []
88
+ metadata = json.delete(:metadata) || {}
89
+ env[:body] = {
90
+ :data => json,
91
+ :errors => errors,
92
+ :metadata => metadata,
93
+ }
94
+ end
95
+ end
96
+
97
+ subject.setup :url => "https://api.example.com" do |builder|
98
+ builder.use CustomParser
99
+ builder.use Faraday::Request::UrlEncoded
100
+ builder.adapter :test do |stub|
101
+ stub.get("/users/1") { |env| [200, {}, MultiJson.dump(:id => 1, :name => "George Michael Bluth")] }
102
+ end
103
+ end
104
+ end
105
+
106
+ specify do
107
+ parsed_data[:data].should == { :id => 1, :name => "George Michael Bluth" }
108
+ parsed_data[:errors].should == []
109
+ parsed_data[:metadata].should == {}
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Her::Collection do
4
+
5
+ let(:items) { [1, 2, 3, 4] }
6
+ let(:metadata) { { :name => 'Testname' } }
7
+ let(:errors) { { :name => ['not_present'] } }
8
+
9
+ describe "#new" do
10
+ context "without parameters" do
11
+ subject { Her::Collection.new }
12
+
13
+ it { should eq([]) }
14
+ its(:metadata) { should eq({}) }
15
+ its(:errors) { should eq({}) }
16
+ end
17
+
18
+ context "with parameters" do
19
+ subject { Her::Collection.new(items, metadata, errors) }
20
+
21
+ it { should eq([1,2,3,4]) }
22
+ its(:metadata) { should eq({ :name => 'Testname' }) }
23
+ its(:errors) { should eq({ :name => ['not_present'] }) }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,305 @@
1
+ require 'spec_helper'
2
+
3
+ describe Her::JsonApi::Model do
4
+ before do
5
+ Her::API.setup :url => "https://api.example.com" do |connection|
6
+ connection.use Her::Middleware::JsonApiParser
7
+ connection.adapter :test do |stub|
8
+ stub.get("/users/1") do |env|
9
+ [
10
+ 200,
11
+ {},
12
+ {
13
+ data: {
14
+ id: 1,
15
+ type: 'users',
16
+ attributes: {
17
+ name: "Roger Federer",
18
+ },
19
+ }
20
+
21
+ }.to_json
22
+ ]
23
+ end
24
+
25
+ stub.get("/users") do |env|
26
+ [
27
+ 200,
28
+ {},
29
+ {
30
+ data: [
31
+ {
32
+ id: 1,
33
+ type: 'users',
34
+ attributes: {
35
+ name: "Roger Federer",
36
+ },
37
+ },
38
+ {
39
+ id: 2,
40
+ type: 'users',
41
+ attributes: {
42
+ name: "Kei Nishikori",
43
+ },
44
+ }
45
+ ]
46
+ }.to_json
47
+ ]
48
+ end
49
+
50
+ stub.post("/users", data: {
51
+ type: 'users',
52
+ attributes: {
53
+ name: "Jeremy Lin",
54
+ },
55
+ }) do |env|
56
+ [
57
+ 201,
58
+ {},
59
+ {
60
+ data: {
61
+ id: 3,
62
+ type: 'users',
63
+ attributes: {
64
+ name: 'Jeremy Lin',
65
+ },
66
+ }
67
+
68
+ }.to_json
69
+ ]
70
+ end
71
+
72
+ stub.patch("/users/1", data: {
73
+ type: 'users',
74
+ id: 1,
75
+ attributes: {
76
+ name: "Fed GOAT",
77
+ },
78
+ }) do |env|
79
+ [
80
+ 200,
81
+ {},
82
+ {
83
+ data: {
84
+ id: 1,
85
+ type: 'users',
86
+ attributes: {
87
+ name: 'Fed GOAT',
88
+ },
89
+ }
90
+
91
+ }.to_json
92
+ ]
93
+ end
94
+
95
+ stub.delete("/users/1") { |env|
96
+ [ 204, {}, {}, ]
97
+ }
98
+
99
+ stub.get("/players") do |env|
100
+ [
101
+ 200,
102
+ {},
103
+ {
104
+ data: [
105
+ {
106
+ id: 1,
107
+ type: 'players',
108
+ attributes: { name: "Roger Federer", },
109
+ relationships: {
110
+ sponsors: {
111
+ data: [
112
+ {
113
+ type: 'sponsors',
114
+ id: 1,
115
+ },
116
+ {
117
+ type: 'sponsors',
118
+ id: 2,
119
+ }
120
+ ]
121
+ },
122
+ racquet: {
123
+ data: {
124
+ type: 'racquets',
125
+ id: 1,
126
+ }
127
+ }
128
+ }
129
+ },
130
+ {
131
+ id: 2,
132
+ type: 'players',
133
+ attributes: { name: "Kei Nishikori", },
134
+ relationships: {
135
+ sponsors: {
136
+ data: [
137
+ {
138
+ type: 'sponsors',
139
+ id: 2,
140
+ },
141
+ {
142
+ type: 'sponsors',
143
+ id: 3,
144
+ }
145
+ ]
146
+ },
147
+ racquet: {
148
+ data: {
149
+ type: 'racquets',
150
+ id: 2,
151
+ }
152
+ }
153
+ }
154
+ },
155
+ {
156
+ id: 3,
157
+ type: 'players',
158
+ attributes: { name: 'Hubert Huang', racquet_id: 1 },
159
+ relationships: {}
160
+ },
161
+ ],
162
+ included: [
163
+ {
164
+ type: 'sponsors',
165
+ id: 1,
166
+ attributes: {
167
+ company: 'Nike',
168
+ }
169
+ },
170
+ {
171
+ type: 'sponsors',
172
+ id: 2,
173
+ attributes: {
174
+ company: 'Wilson',
175
+ },
176
+ },
177
+ {
178
+ type: 'sponsors',
179
+ id: 3,
180
+ attributes: {
181
+ company: 'Uniqlo',
182
+ },
183
+ },
184
+ {
185
+ type: 'racquets',
186
+ id: 1,
187
+ attributes: {
188
+ name: 'Wilson Pro Staff',
189
+ },
190
+ },
191
+ {
192
+ type: 'racquets',
193
+ id: 2,
194
+ attributes: {
195
+ name: 'Wilson Steam',
196
+ }
197
+ },
198
+ ]
199
+ }.to_json
200
+ ]
201
+ end
202
+
203
+ stub.get("/players/3/sponsors") do |env|
204
+ [
205
+ 200,
206
+ {},
207
+ { data: [] }.to_json
208
+ ]
209
+ end
210
+ end
211
+ end
212
+
213
+ spawn_model("Foo::User", Her::JsonApi::Model)
214
+ end
215
+
216
+ context 'simple jsonapi document' do
217
+ it 'allows configuration of type' do
218
+ spawn_model("Foo::Bar", Her::JsonApi::Model) do
219
+ type :foobars
220
+ end
221
+
222
+ expect(Foo::Bar.instance_variable_get('@type')).to eql('foobars')
223
+ end
224
+
225
+ it 'finds models by id' do
226
+ user = Foo::User.find(1)
227
+ expect(user.attributes).to eql(
228
+ 'id' => 1,
229
+ 'name' => 'Roger Federer',
230
+ )
231
+ end
232
+
233
+ it 'finds a collection of models' do
234
+ users = Foo::User.all
235
+ expect(users.map(&:attributes)).to match_array([
236
+ {
237
+ 'id' => 1,
238
+ 'name' => 'Roger Federer',
239
+ },
240
+ {
241
+ 'id' => 2,
242
+ 'name' => 'Kei Nishikori',
243
+ }
244
+ ])
245
+ end
246
+
247
+ it 'creates a Foo::User' do
248
+ user = Foo::User.new(name: 'Jeremy Lin')
249
+ user.save
250
+ expect(user.attributes).to eql(
251
+ 'id' => 3,
252
+ 'name' => 'Jeremy Lin',
253
+ )
254
+ end
255
+
256
+ it 'updates a Foo::User' do
257
+ user = Foo::User.find(1)
258
+ user.name = 'Fed GOAT'
259
+ user.save
260
+ expect(user.attributes).to eql(
261
+ 'id' => 1,
262
+ 'name' => 'Fed GOAT',
263
+ )
264
+ end
265
+
266
+ it 'destroys a Foo::User' do
267
+ user = Foo::User.find(1)
268
+ expect(user.destroy).to be_destroyed
269
+ end
270
+
271
+ context 'undefined methods' do
272
+ it 'removes methods that are not compatible with json api' do
273
+ [:parse_root_in_json, :include_root_in_json, :root_element, :primary_key].each do |method|
274
+ expect { Foo::User.new.send(method, :foo) }.to raise_error NoMethodError, "Her::JsonApi::Model does not support the #{method} configuration option"
275
+ end
276
+ end
277
+ end
278
+ end
279
+
280
+ context 'compound document' do
281
+ before do
282
+ spawn_model("Foo::Sponsor", Her::JsonApi::Model)
283
+ spawn_model("Foo::Racquet", Her::JsonApi::Model)
284
+ spawn_model("Foo::Player", Her::JsonApi::Model) do
285
+ has_many :sponsors
286
+ has_one :racquet
287
+ end
288
+ end
289
+
290
+ it 'parses included documents into object if relationship specifieds a resource linkage' do
291
+ players = Foo::Player.all.to_a
292
+ fed = players.detect { |p| p.name == 'Roger Federer' }
293
+ expect(fed.sponsors.map(&:company)).to match_array ['Nike', 'Wilson']
294
+ expect(fed.racquet.name).to eq 'Wilson Pro Staff'
295
+
296
+ kei = players.detect { |p| p.name == 'Kei Nishikori' }
297
+ expect(kei.sponsors.map(&:company)).to match_array ['Uniqlo', 'Wilson']
298
+ expect(kei.racquet.name).to eq 'Wilson Steam'
299
+
300
+ hubert = players.detect { |p| p.name == 'Hubert Huang' }
301
+ expect(hubert.sponsors.map(&:company)).to eq []
302
+ expect(hubert.racquet.name).to be_nil
303
+ end
304
+ end
305
+ end