her 0.5.2 → 0.5.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.
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