her 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NDM1MWIwOWZmYThlZjA5YTJlMzdlZDM3MDQzMjg5ZGNlOTVhOGYxMQ==
4
+ MmIxMjIyZWU2NTIyOTdmZDdlNzU2MjBiM2NlNDU2MmFhMjJlNzVhNQ==
5
5
  data.tar.gz: !binary |-
6
- ODgwMDMwMDM3M2MyZTdjN2YwZDBhMDlhMDJlZTY5MzcwZTVhMjVjNQ==
6
+ MDJmNTM0YzI1ZmY1OTM3NDM4NTczNDYxNWZjOWI0NjU1NzNlYzY2Mg==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- OGIyY2JkM2Y5OTI4NzdmZDk5OTE2ZGM4OWU4ZTM3MTYzOWNhMGRlOWZlNTc4
10
- ZTcxODBmYzMxYjdmNDJkNGFhZDc0NGQzZGE3ODU0NTE4ODliYjQ4MTIyMGJi
11
- NjBjMTE3ZGE1ZDM5Y2QwYWVjNTgwZDQxOGRkNDc0OTRhYzA2YjQ=
9
+ M2Q3MDc2NWJmNDI1NDM3Y2M1YjNkNjgwN2JlMWY3M2M5ZjhmYWZmMTY4Njdk
10
+ MzViMjE0NDY1ZDlmOTNkNzBkNWI1MzgwNWFmYWYyM2YzOWE3YTE5YjcyZTg4
11
+ YzM5ZWZkMDFjODBjMjBlN2Q0MDBiNDI5MzYyYWNhODZlZDQzNDU=
12
12
  data.tar.gz: !binary |-
13
- M2ZlYmU3ZGE5ZjBkNDkxYjgyZWFlZmM4OWEyYjMxYzYzMmI1ODAyOTFhMmM1
14
- ZDk0ZDA1YWUxODY2NGIzZDEwMTdiODQ5MWJkN2NlYjFlMTc5YjAxZmVkYzY1
15
- NjY3YWY4NGIyNGFkNzMxNmZmY2E2YmJkOTIyNWZjMjU3MjZhNTE=
13
+ YWJmZDEwMzlhMzkxZWJlMDZjOTZlZDRkNzM4MTMyZDBlZTlkZmM1NWUzOTMz
14
+ YWFkMjBlNjgzY2U2ZWQxYTg3ZThlODZkMGVkOWU3MzVjNTU2ODc5ZjYyMGQ2
15
+ YzJkNWFhYjRiY2EzZGI1YzE1OGQ5YzJkYzVjYTY0MWJiYjBmNmQ=
data/README.md CHANGED
@@ -296,16 +296,14 @@ If there’s association data in the resource, no extra HTTP request is made whe
296
296
  ```ruby
297
297
  @user = User.find(1)
298
298
  # {
299
- # :data => {
300
- # :id => 1,
301
- # :name => "George Michael Bluth",
302
- # :comments => [
303
- # { :id => 1, :text => "Foo" },
304
- # { :id => 2, :text => "Bar" }
305
- # ],
306
- # :role => { :id => 1, :name => "Admin" },
307
- # :organization => { :id => 2, :name => "Bluth Company" }
308
- # }
299
+ # :id => 1,
300
+ # :name => "George Michael Bluth",
301
+ # :comments => [
302
+ # { :id => 1, :text => "Foo" },
303
+ # { :id => 2, :text => "Bar" }
304
+ # ],
305
+ # :role => { :id => 1, :name => "Admin" },
306
+ # :organization => { :id => 2, :name => "Bluth Company" }
309
307
  # }
310
308
  @user.comments
311
309
  # [#<Comment id=1 text="Foo">, #<Comment id=2 text="Bar">]
@@ -319,7 +317,7 @@ If there’s no association data in the resource, Her makes a HTTP request to re
319
317
 
320
318
  ```ruby
321
319
  @user = User.find(1)
322
- # { :data => { :id => 1, :name => "George Michael Bluth", :organization_id => 2 }}
320
+ # { :id => 1, :name => "George Michael Bluth", :organization_id => 2 }
323
321
 
324
322
  # has_many association:
325
323
  @user.comments
@@ -340,6 +338,28 @@ If there’s no association data in the resource, Her makes a HTTP request to re
340
338
 
341
339
  Subsequent calls to `#comments`, `#role` and `#organization` will not trigger extra HTTP requests and will return the cached objects.
342
340
 
341
+ #### Notes about paths
342
+
343
+ Resources must always have all the required attributes to build their complete path. For example, if you have these models:
344
+
345
+ ```ruby
346
+ class User
347
+ include Her::Model
348
+ collection_path "organizations/:organization_id/users"
349
+ end
350
+
351
+ class Organization
352
+ include Her::Model
353
+ has_many :users
354
+ end
355
+ ```
356
+
357
+ Her expects all `User` resources to have an `:organization_id` (or `:_organization_id`) attribute. Otherwise, calling mostly all methods, like `User.all`, will thrown an exception like this one:
358
+
359
+ ```ruby
360
+ Her::Errors::PathError: Missing :_organization_id parameter to build the request path. Path is `organizations/:organization_id/users`. Parameters are `{ … }`.
361
+ ```
362
+
343
363
  ### Validations
344
364
 
345
365
  Her includes `ActiveModel::Validations` so you can declare validations the same way you do in Rails.
@@ -2,5 +2,6 @@ module Her
2
2
  module Errors
3
3
  class PathError < StandardError; end;
4
4
  class AssociationUnknownError < StandardError; end;
5
+ class ParseError < StandardError; end;
5
6
  end
6
7
  end
@@ -1,3 +1,4 @@
1
+ require "her/middleware/parse_json"
1
2
  require "her/middleware/first_level_parse_json"
2
3
  require "her/middleware/second_level_parse_json"
3
4
  require "her/middleware/accept_json"
@@ -1,15 +1,15 @@
1
1
  module Her
2
2
  module Middleware
3
3
  # This middleware treat the received first-level JSON structure as the resource data.
4
- class FirstLevelParseJSON < Faraday::Response::Middleware
4
+ class FirstLevelParseJSON < ParseJSON
5
5
  # Parse the response body
6
6
  #
7
7
  # @param [String] body The response body
8
8
  # @return [Mixed] the parsed response
9
9
  def parse(body)
10
- json = MultiJson.load(body, :symbolize_keys => true)
10
+ json = parse_json(body)
11
11
  errors = json.delete(:errors) || {}
12
- metadata = json.delete(:metadata) || []
12
+ metadata = json.delete(:metadata) || {}
13
13
  {
14
14
  :data => json,
15
15
  :errors => errors,
@@ -22,11 +22,11 @@ module Her
22
22
  #
23
23
  # @param [Hash] env The response environment
24
24
  def on_complete(env)
25
- case env[:status]
25
+ env[:body] = case env[:status]
26
26
  when 204
27
- env[:body] = parse('{}')
27
+ parse('{}')
28
28
  else
29
- env[:body] = parse(env[:body])
29
+ parse(env[:body])
30
30
  end
31
31
  end
32
32
  end
@@ -0,0 +1,20 @@
1
+ module Her
2
+ module Middleware
3
+ class ParseJSON < Faraday::Response::Middleware
4
+ def parse_json(body = nil)
5
+ body ||= '{}'
6
+ message = "Response from the API must behave like a Hash or an Array (last JSON response was #{body.inspect})"
7
+
8
+ json = begin
9
+ MultiJson.load(body, :symbolize_keys => true)
10
+ rescue MultiJson::LoadError
11
+ raise Her::Errors::ParseError, message
12
+ end
13
+
14
+ raise Her::Errors::ParseError, message unless json.is_a?(Hash) or json.is_a?(Array)
15
+
16
+ json
17
+ end
18
+ end
19
+ end
20
+ end
@@ -2,13 +2,14 @@ module Her
2
2
  module Middleware
3
3
  # This middleware expects the resource/collection data to be contained in the `data`
4
4
  # key of the JSON object
5
- class SecondLevelParseJSON < Faraday::Response::Middleware
5
+ class SecondLevelParseJSON < ParseJSON
6
6
  # Parse the response body
7
7
  #
8
8
  # @param [String] body The response body
9
9
  # @return [Mixed] the parsed response
10
10
  def parse(body)
11
- json = MultiJson.load(body, :symbolize_keys => true)
11
+ json = parse_json(body)
12
+
12
13
  {
13
14
  :data => json[:data],
14
15
  :errors => json[:errors],
@@ -21,7 +22,12 @@ module Her
21
22
  #
22
23
  # @param [Hash] env The response environment
23
24
  def on_complete(env)
24
- env[:body] = parse(env[:body])
25
+ env[:body] = case env[:status]
26
+ when 204
27
+ parse('{}')
28
+ else
29
+ parse(env[:body])
30
+ end
25
31
  end
26
32
  end
27
33
  end
@@ -265,7 +265,7 @@ module Her
265
265
  resource = nil
266
266
  request(params.merge(:_method => :get, :_path => "#{build_request_path(params.merge(:id => id))}")) do |parsed_data, response|
267
267
  if response.success?
268
- resource = new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
268
+ resource = new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
269
269
  resource.run_callbacks :find
270
270
  else
271
271
  return nil
@@ -1,3 +1,3 @@
1
1
  module Her
2
- VERSION = "0.5.2"
2
+ VERSION = "0.5.3"
3
3
  end
@@ -5,6 +5,9 @@ describe Her::Middleware::FirstLevelParseJSON do
5
5
  subject { described_class.new }
6
6
  let(:body_without_errors) { "{\"id\": 1, \"name\": \"Tobias Fünke\", \"metadata\": 3}" }
7
7
  let(:body_with_errors) { "{\"id\": 1, \"name\": \"Tobias Fünke\", \"errors\": { \"name\": [ \"not_valid\", \"should_be_present\" ] }, \"metadata\": 3}" }
8
+ let(:body_with_malformed_json) { "wut." }
9
+ let(:body_with_invalid_json) { "true" }
10
+ let(:nil_body) { nil }
8
11
 
9
12
  it "parses body as json" do
10
13
  subject.parse(body_without_errors).tap do |json|
@@ -30,6 +33,18 @@ describe Her::Middleware::FirstLevelParseJSON do
30
33
  subject.parse(body_with_errors)[:errors].should eq({:name => [ 'not_valid', 'should_be_present']})
31
34
  end
32
35
 
36
+ it 'ensures that malformed JSON throws an exception' do
37
+ expect { subject.parse(body_with_malformed_json) }.to raise_error(Her::Errors::ParseError, 'Response from the API must behave like a Hash or an Array (last JSON response was "wut.")')
38
+ end
39
+
40
+ it 'ensures that invalid JSON throws an exception' do
41
+ expect { subject.parse(body_with_invalid_json) }.to raise_error(Her::Errors::ParseError, 'Response from the API must behave like a Hash or an Array (last JSON response was "true")')
42
+ end
43
+
44
+ it 'ensures that a nil response returns an empty hash' do
45
+ subject.parse(nil_body)[:data].should eq({})
46
+ end
47
+
33
48
  context 'with status code 204' do
34
49
  it 'returns an empty body' do
35
50
  env = { :status => 204 }
@@ -3,23 +3,33 @@ require "spec_helper"
3
3
 
4
4
  describe Her::Middleware::SecondLevelParseJSON do
5
5
  subject { described_class.new }
6
- let(:body) { "{\"data\": 1, \"errors\": 2, \"metadata\": 3}" }
7
6
 
8
- it "parses body as json" do
9
- subject.parse(body).tap do |json|
10
- json[:data].should == 1
11
- json[:errors].should == 2
12
- json[:metadata].should == 3
7
+ context "with valid JSON body" do
8
+ let(:body) { "{\"data\": 1, \"errors\": 2, \"metadata\": 3}" }
9
+ it "parses body as json" do
10
+ subject.parse(body).tap do |json|
11
+ json[:data].should == 1
12
+ json[:errors].should == 2
13
+ json[:metadata].should == 3
14
+ end
15
+ end
16
+
17
+ it "parses :body key as json in the env hash" do
18
+ env = { :body => body }
19
+ subject.on_complete(env)
20
+ env[:body].tap do |json|
21
+ json[:data].should == 1
22
+ json[:errors].should == 2
23
+ json[:metadata].should == 3
24
+ end
13
25
  end
14
26
  end
15
27
 
16
- it "parses :body key as json in the env hash" do
17
- env = { :body => body }
18
- subject.on_complete(env)
19
- env[:body].tap do |json|
20
- json[:data].should == 1
21
- json[:errors].should == 2
22
- json[:metadata].should == 3
28
+ context "with invalid JSON body" do
29
+ let(:body) { '"foo"' }
30
+ it 'ensures that invalid JSON throws an exception' do
31
+ expect { subject.parse(body) }.to raise_error(Her::Errors::ParseError, 'Response from the API must behave like a Hash or an Array (last JSON response was "\"foo\"")')
23
32
  end
24
33
  end
34
+
25
35
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: her
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rémi Prévost
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-03-30 00:00:00.000000000 Z
11
+ date: 2013-04-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -140,6 +140,7 @@ files:
140
140
  - lib/her/middleware.rb
141
141
  - lib/her/middleware/accept_json.rb
142
142
  - lib/her/middleware/first_level_parse_json.rb
143
+ - lib/her/middleware/parse_json.rb
143
144
  - lib/her/middleware/second_level_parse_json.rb
144
145
  - lib/her/model.rb
145
146
  - lib/her/model/associations.rb