Skip to content

Commit

Permalink
Improve support for custom methods
Browse files Browse the repository at this point in the history
  • Loading branch information
remi committed Apr 9, 2012
1 parent 9038a23 commit 17d968c
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 42 deletions.
21 changes: 18 additions & 3 deletions README.md
Expand Up @@ -55,8 +55,23 @@ user.comments # => [#<Comment id=1>, #<Comment id=2>]

## Custom requests

TBD.
You can easily add custom methods for your models. You can either use `get_collection` (which maps the returned data to a collection of resources), `get_resource` (which maps the returned data to a single resource) or `get_raw` (which yields the parsed data return from the HTTP request).

## Hooks
```ruby
class User
include Her::Model

def self.popular
get_collection("/users/popular")
end

TBD.
def self.total
get_raw("/users/stats") do |parsed_data|
parsed_data[:data][:total_users]
end
end
end

User.popular # => [#<User id=1>, #<User id=2>]
User.total # => 42
```
4 changes: 2 additions & 2 deletions lib/her/api.rb
Expand Up @@ -26,7 +26,7 @@ def setup(attrs={}) # {{{
@parse_with = lambda do |response|
json = JSON.parse(response.body, :symbolize_names => true)
{
:resource => json[:data],
:data => json[:data],
:errors => json[:errors],
:metadata => json[:metadata],
}
Expand All @@ -38,7 +38,7 @@ def setup(attrs={}) # {{{
end # }}}

# Define a custom parsing procedure. The procedure is passed the response object and is
# expected to return a hash with three keys: a main resource Hash, an errors Array
# expected to return a hash with three keys: a main data Hash, an errors Array
# and a metadata Hash.
#
# @example
Expand Down
95 changes: 79 additions & 16 deletions lib/her/model/http.rb
Expand Up @@ -28,36 +28,99 @@ def request(attrs={}, &block) # {{{
end # }}}

# Make a GET request and return the parsed JSON response (not mapped to objects)
#
# @example
# User.get "/users/1"
def get(path, attrs={}, &block) # {{{
def get_raw(path, attrs={}, &block) # {{{
request(attrs.merge(:_method => :get, :_path => path), &block)
end # }}}

# Make a GET request and return a collection of resources
def get_collection(path, attrs={}) # {{{
get_raw(path, attrs) do |parsed_data|
new_collection(parsed_data)
end
end # }}}

# Make a GET request and return a collection of resources
def get_resource(path, attrs={}) # {{{
get_raw(path, attrs) do |parsed_data|
new(parsed_data[:resource])
end
end # }}}

# Make a POST request and return the parsed JSON response (not mapped to objects)
#
# @example
# User.post "/users", :fullname => "G.O.B. Bluth"
def post(path, attrs={}, &block) # {{{
def post_raw(path, attrs={}, &block) # {{{
request(attrs.merge(:_method => :post, :_path => path), &block)
end # }}}

# Make a POST request and return a collection of resources
def post_collection(path, attrs={}) # {{{
post_raw(path, attrs) do |parsed_data|
new_collection(parsed_data)
end
end # }}}

# Make a POST request and return a collection of resources
def post_resource(path, attrs={}) # {{{
post_raw(path, attrs) do |parsed_data|
new(parsed_data[:resource])
end
end # }}}

# Make a PUT request and return the parsed JSON response (not mapped to objects)
#
# @example
# User.put "/users/1", :email => "gob@bluthcompany.com"
def put(path, attrs={}, &block) # {{{
def put_raw(path, attrs={}, &block) # {{{
request(attrs.merge(:_method => :put, :_path => path), &block)
end # }}}

# Make a PUT request and return a collection of resources
def put_collection(path, attrs={}) # {{{
put_raw(path, attrs) do |parsed_data|
new_collection(parsed_data)
end
end # }}}

# Make a PUT request and return a collection of resources
def put_resource(path, attrs={}) # {{{
put_raw(path, attrs) do |parsed_data|
new(parsed_data[:resource])
end
end # }}}

# Make a PATCH request and return the parsed JSON response (not mapped to objects)
def patch_raw(path, attrs={}, &block) # {{{
request(attrs.merge(:_method => :patch, :_path => path), &block)
end # }}}

# Make a PATCH request and return a collection of resources
def patch_collection(path, attrs={}) # {{{
patch_raw(path, attrs) do |parsed_data|
new_collection(parsed_data)
end
end # }}}

# Make a PATCH request and return a collection of resources
def patch_resource(path, attrs={}) # {{{
patch_raw(path, attrs) do |parsed_data|
new(parsed_data[:resource])
end
end # }}}

# Make a DELETE request and return the parsed JSON response (not mapped to objects)
#
# @example
# User.delete "/users/1"
def delete(path, attrs={}, &block) # {{{
def delete_raw(path, attrs={}, &block) # {{{
request(attrs.merge(:_method => :delete, :_path => path), &block)
end # }}}

# Make a DELETE request and return a collection of resources
def delete_collection(path, attrs={}) # {{{
delete_raw(path, attrs) do |parsed_data|
new_collection(parsed_data)
end
end # }}}

# Make a DELETE request and return a collection of resources
def delete_resource(path, attrs={}) # {{{
delete_raw(path, attrs) do |parsed_data|
new(parsed_data[:resource])
end
end # }}}
end
end
end
9 changes: 9 additions & 0 deletions lib/her/model/orm.rb
Expand Up @@ -9,6 +9,15 @@ def initialize(single_data) # {{{
@data = self.class.parse_relationships(@data)
end # }}}

# Initialize a collection of resources with raw data from an HTTP request
#
# @example
# User.get("/users/popular") { |data| User.new_collection(data) }
def new_collection(parsed_data) # {{{
collection_data = parsed_data[:resource]
Her::Model::ORM.initialize_collection(self.to_s.downcase.to_sym, collection_data)
end # }}}

# Initialize a collection of resources
# @private
def self.initialize_collection(name, collection_data) # {{{
Expand Down
6 changes: 3 additions & 3 deletions lib/her/model/relationships.rb
Expand Up @@ -34,9 +34,7 @@ def has_many(name, attrs={}) # {{{

define_method(name) do
return @data[name] if @data.include?(name) # Do not fetch from API again if we have it in @data
self.class.get("#{collection_path}/#{id}/#{Object.const_get(name.to_s.classify).collection_path}") do |parsed_data|
@data[name] = Her::Model::ORM.initialize_collection(name, parsed_data[:resource])
end
self.class.get_collection("#{collection_path}/#{id}/#{Object.const_get(name.to_s.classify).collection_path}")
end
end # }}}

Expand All @@ -50,6 +48,8 @@ def has_many(name, attrs={}) # {{{
def belongs_to(name, attrs={}) # {{{
@her_relationships ||= {}
(@her_relationships[:belongs_to] ||= []) << attrs.merge(:name => name)

# TODO Write some code here
end # }}}
end
end
Expand Down
55 changes: 37 additions & 18 deletions spec/model_spec.rb
Expand Up @@ -3,7 +3,7 @@

describe Her::Model do
describe Her::Model::HTTP do
context "binding a model with an API" do # {{{
context "binding a model with an API" do
it "binds a model to an instance of Her::API" do # {{{
@api = Her::API.new
@api.setup :base_uri => "https://api.example.com"
Expand Down Expand Up @@ -78,16 +78,19 @@ class Comment; include Her::Model; end
@her_api.base_uri.should == "https://api2.example.com"
end
end # }}}
end # }}}
end

context "making HTTP requests" do
before do # {{{
@api = Her::API.new
@api.setup :base_uri => "https://api.example.com"
FakeWeb.register_uri(:get, "https://api.example.com/users", :body => { :data => [{ :id => 1 }] }.to_json)
FakeWeb.register_uri(:get, "https://api.example.com/users?page=2", :body => { :data => [{ :id => 2 }] }.to_json)
FakeWeb.register_uri(:get, "https://api.example.com/users/popular", :body => { :data => [{ :id => 1 }, { :id => 2 }] }.to_json)
FakeWeb.register_uri(:get, "https://api.example.com/users/1", :body => { :data => { :id => 1 } }.to_json)
FakeWeb.register_uri(:post, "https://api.example.com/users", :body => { :data => [{ :id => 3 }] }.to_json)
FakeWeb.register_uri(:put, "https://api.example.com/users/4", :body => { :data => [{ :id => 4 }] }.to_json)
FakeWeb.register_uri(:patch, "https://api.example.com/users/6", :body => { :data => [{ :id => 6 }] }.to_json)
FakeWeb.register_uri(:delete, "https://api.example.com/users/5", :body => { :data => [{ :id => 5 }] }.to_json)

Object.instance_eval { remove_const :User } if Object.const_defined?(:User)
Expand All @@ -97,40 +100,57 @@ class User
User.uses_api @api
end # }}}

it "handle GET" do # {{{
User.get("/users") do |parsed_data|
it "handle raw GET" do # {{{
User.get_raw("/users") do |parsed_data|
parsed_data[:resource].should == [{ :id => 1 }]
end
end # }}}

it "handle POST" do # {{{
User.post("/users") do |parsed_data|
it "handle raw POST" do # {{{
User.post_raw("/users") do |parsed_data|
parsed_data[:resource].should == [{ :id => 3 }]
end
end # }}}

it "handle PUT" do # {{{
User.put("/users/4") do |parsed_data|
it "handle raw PUT" do # {{{
User.put_raw("/users/4") do |parsed_data|
parsed_data[:resource].should == [{ :id => 4 }]
end
end # }}}

it "handle DELETE" do # {{{
User.delete("/users/5") do |parsed_data|
it "handle raw PATCH" do # {{{
User.patch_raw("/users/6") do |parsed_data|
parsed_data[:resource].should == [{ :id => 6 }]
end
end # }}}

it "handle raw DELETE" do # {{{
User.delete_raw("/users/5") do |parsed_data|
parsed_data[:resource].should == [{ :id => 5 }]
end
end # }}}

it "handle querystring parameters" do # {{{
User.get("/users", :page => 2) do |parsed_data|
User.get_raw("/users", :page => 2) do |parsed_data|
parsed_data[:resource].should == [{ :id => 2 }]
end
end # }}}

it "handle GET collection" do # {{{
@users = User.get_collection("/users/popular")
@users.length.should == 2
@users.first.id.should == 1
end # }}}

it "handle GET resource" do # {{{
@user = User.get_resource("/users/1")
@user.id.should == 1
end # }}}
end
end

describe Her::Model::ORM do
context "mapping data to Ruby objects" do # {{{
context "mapping data to Ruby objects" do
before do # {{{
@api = Her::API.new
@api.setup :base_uri => "https://api.example.com"
Expand All @@ -155,7 +175,7 @@ class User
@users.length.should == 2
@users.first.name.should == "Tobias Fünke"
end # }}}
end # }}}
end

context "creating resources" do
before do # {{{
Expand All @@ -173,7 +193,7 @@ class User
end

describe Her::Model::Relationships do
context "setting relationships" do # {{{
context "setting relationships" do
before do # {{{
Object.instance_eval { remove_const :User } if Object.const_defined?(:User)
class User
Expand Down Expand Up @@ -202,9 +222,9 @@ class User
User.belongs_to :family
User.relationships[:belongs_to].should == [{ :name => :organization }, { :name => :family }]
end # }}}
end # }}}
end

context "handling relationships" do # {{{
context "handling relationships" do
before do # {{{
Her::API.setup :base_uri => "https://api.example.com"
FakeWeb.register_uri(:get, "https://api.example.com/users/1", :body => { :data => { :id => 1, :name => "Tobias Fünke", :comments => [{ :id => 2, :body => "Tobias, you blow hard!" }, { :id => 3, :body => "I wouldn't mind kissing that man between the cheeks, so to speak" }] } }.to_json)
Expand Down Expand Up @@ -236,7 +256,6 @@ class Comment
@user.comments.first.id.should == 4
@user.comments.first.body.should == "They're having a FIRESALE?"
end # }}}

end # }}}
end
end
end

0 comments on commit 17d968c

Please sign in to comment.