Skip to content

Commit

Permalink
Handle metadata and errors data
Browse files Browse the repository at this point in the history
  • Loading branch information
remi committed Jun 26, 2012
1 parent fd77546 commit 472953d
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 28 deletions.
1 change: 1 addition & 0 deletions lib/her.rb
Expand Up @@ -9,6 +9,7 @@
require "her/api"
require "her/middleware"
require "her/errors"
require "her/collection"

module Her
end
12 changes: 12 additions & 0 deletions lib/her/collection.rb
@@ -0,0 +1,12 @@
module Her
class Collection < ::Array
attr_reader :metadata, :errors

# @private
def initialize(items=[], metadata={}, errors=[]) # {{{
super(items)
@metadata = metadata || {}
@errors = errors || []
end # }}}
end
end
38 changes: 19 additions & 19 deletions lib/her/model/http.rb
Expand Up @@ -26,9 +26,9 @@ def get(path, attrs={}) # {{{
path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
get_raw(path, attrs) do |parsed_data|
if parsed_data[:data].is_a?(Array)
new_collection(parsed_data[:data])
new_collection(parsed_data)
else
new(parsed_data[:data])
new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
end
end
end # }}}
Expand All @@ -43,15 +43,15 @@ def get_raw(path, attrs={}, &block) # {{{
def get_collection(path, attrs={}) # {{{
path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
get_raw(path, attrs) do |parsed_data|
new_collection(parsed_data[:data])
new_collection(parsed_data)
end
end # }}}

# Make a GET request and return a collection of resources
def get_resource(path, attrs={}) # {{{
path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
get_raw(path, attrs) do |parsed_data|
new(parsed_data[:data])
new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
end
end # }}}

Expand All @@ -60,9 +60,9 @@ def post(path, attrs={}) # {{{
path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
post_raw(path, attrs) do |parsed_data|
if parsed_data[:data].is_a?(Array)
new_collection(parsed_data[:data])
new_collection(parsed_data)
else
new(parsed_data[:data])
new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
end
end
end # }}}
Expand All @@ -77,7 +77,7 @@ def post_raw(path, attrs={}, &block) # {{{
def post_collection(path, attrs={}) # {{{
path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
post_raw(path, attrs) do |parsed_data|
new_collection(parsed_data[:data])
new_collection(parsed_data)
end
end # }}}

Expand All @@ -94,9 +94,9 @@ def put(path, attrs={}) # {{{
path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
put_raw(path, attrs) do |parsed_data|
if parsed_data[:data].is_a?(Array)
new_collection(parsed_data[:data])
new_collection(parsed_data)
else
new(parsed_data[:data])
new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
end
end
end # }}}
Expand All @@ -111,15 +111,15 @@ def put_raw(path, attrs={}, &block) # {{{
def put_collection(path, attrs={}) # {{{
path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
put_raw(path, attrs) do |parsed_data|
new_collection(parsed_data[:data])
new_collection(parsed_data)
end
end # }}}

# Make a PUT request and return a collection of resources
def put_resource(path, attrs={}) # {{{
path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
put_raw(path, attrs) do |parsed_data|
new(parsed_data[:data])
new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
end
end # }}}

Expand All @@ -128,9 +128,9 @@ def patch(path, attrs={}) # {{{
path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
patch_raw(path, attrs) do |parsed_data|
if parsed_data[:data].is_a?(Array)
new_collection(parsed_data[:data])
new_collection(parsed_data)
else
new(parsed_data[:data])
new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
end
end
end # }}}
Expand All @@ -145,15 +145,15 @@ def patch_raw(path, attrs={}, &block) # {{{
def patch_collection(path, attrs={}) # {{{
path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
patch_raw(path, attrs) do |parsed_data|
new_collection(parsed_data[:data])
new_collection(parsed_data)
end
end # }}}

# Make a PATCH request and return a collection of resources
def patch_resource(path, attrs={}) # {{{
path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
patch_raw(path, attrs) do |parsed_data|
new(parsed_data[:data])
new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
end
end # }}}

Expand All @@ -162,9 +162,9 @@ def delete(path, attrs={}) # {{{
path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
delete_raw(path, attrs) do |parsed_data|
if parsed_data[:data].is_a?(Array)
new_collection(parsed_data[:data])
new_collection(parsed_data)
else
new(parsed_data[:data])
new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
end
end
end # }}}
Expand All @@ -179,15 +179,15 @@ def delete_raw(path, attrs={}, &block) # {{{
def delete_collection(path, attrs={}) # {{{
path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
delete_raw(path, attrs) do |parsed_data|
new_collection(parsed_data[:data])
new_collection(parsed_data)
end
end # }}}

# Make a DELETE request and return a collection of resources
def delete_resource(path, attrs={}) # {{{
path = "#{build_request_path(attrs)}/#{path}" if path.is_a?(Symbol)
delete_raw(path, attrs) do |parsed_data|
new(parsed_data[:data])
new(parsed_data[:data].merge :_metadata => parsed_data[:data], :_errors => parsed_data[:errors])
end
end # }}}

Expand Down
39 changes: 31 additions & 8 deletions lib/her/model/orm.rb
Expand Up @@ -2,11 +2,15 @@ module Her
module Model
# This module adds ORM-like capabilities to the model
module ORM
attr_reader :metadata, :errors

# Initialize a new object with data received from an HTTP request
# @private
def initialize(single_data={}) # {{{
def initialize(data={}) # {{{
@data = {}
cleaned_data = single_data.inject({}) do |memo, item|
@metadata = data.delete(:_metadata) || {}
@errors = data.delete(:_errors) || {}
cleaned_data = data.inject({}) do |memo, item|
key, value = item
send "#{key}=".to_sym, value unless value.nil?
respond_to?("#{key}=") ? memo : memo.merge({ key => value })
Expand All @@ -16,8 +20,11 @@ def initialize(single_data={}) # {{{

# Initialize a collection of resources
# @private
def self.initialize_collection(name, collection_data) # {{{
collection_data.map { |item_data| Object.const_get(name.to_s.classify).new(item_data) }
def self.initialize_collection(name, parsed_data={}) # {{{
collection_data = parsed_data[:data].map do |item_data|
Object.const_get(name.to_s.classify).new(item_data)
end
Her::Collection.new(collection_data, parsed_data[:metadata], parsed_data[:errors])
end # }}}

# Handles missing methods by routing them through @data
Expand Down Expand Up @@ -45,16 +52,26 @@ def id # {{{

# Initialize a collection of resources with raw data from an HTTP request
#
# @param [Array] collection_data An array of model hashes
def new_collection(collection_data) # {{{
Her::Model::ORM.initialize_collection(self.to_s.underscore, collection_data)
# @param [Array] parsed_data
def new_collection(parsed_data) # {{{
Her::Model::ORM.initialize_collection(self.to_s.underscore, parsed_data)
end # }}}

# Return `true` if a resource was not saved yet
def new? # {{{
!@data.include?(:id)
end # }}}

# Return `true` if a resource does not contain errors
def valid? # {{{
@errors.empty?
end # }}}

# Return `true` if a resource contains errors
def invalid? # {{{
@errors.any?
end # }}}

# Fetch a specific resource based on an ID
#
# @example
Expand All @@ -73,7 +90,7 @@ def find(id, params={}) # {{{
# # Fetched via GET "/users"
def all(params={}) # {{{
request(params.merge(:_method => :get, :_path => "#{build_request_path(params)}")) do |parsed_data|
new_collection(parsed_data[:data])
new_collection(parsed_data)
end
end # }}}

Expand All @@ -89,6 +106,8 @@ def create(params={}) # {{{
request(params.merge(:_method => :post, :_path => "#{build_request_path(params)}")) do |parsed_data|
resource.instance_eval do
@data = parsed_data[:data]
@metadata = parsed_data[:metadata]
@errors = parsed_data[:errors]
end
end
end
Expand Down Expand Up @@ -133,6 +152,8 @@ def save # {{{
self.class.wrap_in_hooks(resource, *hooks) do |resource, klass|
klass.request(params.merge(:_method => method, :_path => "#{request_path}")) do |parsed_data|
@data = parsed_data[:data]
@metadata = parsed_data[:metadata]
@errors = parsed_data[:errors]
end
end
self
Expand All @@ -150,6 +171,8 @@ def destroy # {{{
self.class.wrap_in_hooks(resource, :destroy) do |resource, klass|
klass.request(params.merge(:_method => :delete, :_path => "#{request_path}")) do |parsed_data|
@data = parsed_data[:data]
@metadata = parsed_data[:metadata]
@errors = parsed_data[:errors]
end
end
self
Expand Down
2 changes: 1 addition & 1 deletion lib/her/model/relationships.rb
Expand Up @@ -19,7 +19,7 @@ def parse_relationships(data) # {{{
next if !data.include?(name) or data[name].nil?
data[name] = case type
when :has_many
Her::Model::ORM.initialize_collection(class_name, data[name])
Her::Model::ORM.initialize_collection(class_name, :data => data[name])
when :has_one, :belongs_to
Object.const_get(class_name).new(data[name])
else
Expand Down
39 changes: 39 additions & 0 deletions spec/model/orm_spec.rb
Expand Up @@ -62,6 +62,45 @@
end# }}}
end

context "mapping data, metadata and error data to Ruby objects" do
before do # {{{
api = Her::API.new
api.setup :url => "https://api.example.com" do |builder|
builder.use Her::Middleware::SecondLevelParseJSON
builder.use Faraday::Request::UrlEncoded
builder.adapter :test do |stub|
stub.get("/users") { |env| [200, {}, { :data => [{ :id => 1, :name => "Tobias Fünke" }, { :id => 2, :name => "Lindsay Fünke" }], :metadata => { :total_pages => 10, :next_page => 2 }, :errors => ["Oh", "My", "God"] }.to_json] }
stub.post("/users") { |env| [200, {}, { :data => { :name => "George Michael Bluth" }, :metadata => { :foo => :bar }, :errors => ["Yes", "Sir"] }.to_json] }
end
end

spawn_model :User do
uses_api api
end
end # }}}

it "handles metadata on a collection" do # {{{
@users = User.all
@users.metadata[:total_pages].should == 10
end # }}}

it "handles error data on a collection" do # {{{
@users = User.all
@users.errors.length.should == 3
end # }}}

it "handles metadata on a resource" do # {{{
@user = User.create(:name => "George Michael Bluth")
@user.metadata[:foo].should == "bar"
end # }}}

it "handles error data on a resource" do # {{{
@user = User.create(:name => "George Michael Bluth")
@user.errors.should == ["Yes", "Sir"]
@user.should be_invalid
end # }}}
end

context "defining custom getters and setters" do
before do # {{{
api = Her::API.new
Expand Down

0 comments on commit 472953d

Please sign in to comment.