Skip to content

Commit

Permalink
Improve the way we handle middleware; expose Faraday’s connection bui…
Browse files Browse the repository at this point in the history
…lder
  • Loading branch information
remi committed Apr 30, 2012
1 parent b93a382 commit b73abec
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 71 deletions.
55 changes: 47 additions & 8 deletions README.md
Expand Up @@ -60,7 +60,7 @@ Since Her relies on [Faraday](https://github.com/technoweenie/faraday) to send H

### Authentication

Her doesn’t support any kind of authentication. However, it’s very easy to implement one with a request middleware. Using the `add_middleware` key, we add it to the default list of middleware.
Her doesn’t support any kind of authentication. However, it’s very easy to implement one with a request middleware. Using the builder block, we add it to the default list of middleware.

```ruby
class MyAuthentication < Faraday::Middleware
Expand All @@ -70,7 +70,9 @@ class MyAuthentication < Faraday::Middleware
end
end

Her::API.setup :base_uri => "https://api.example.com", :add_middleware => [MyAuthentication]
Her::API.setup :base_uri => "https://api.example.com" do |builder|
builder.use MyAuthentication
end
```

Now, each HTTP request made by Her will have the `X-API-Token` header.
Expand All @@ -89,7 +91,7 @@ By default, Her handles JSON data. It expects the resource/collection data to be
[{ "id" : 1, "name" : "Tobias Fünke" }]
```

However, you can define your own parsing method, using a response middleware. The middleware is expected to set `env[:body]` to a hash with three keys: `data`, `errors` and `metadata`. The following code enables parsing JSON data and treating the result as first-level properties. Using the `parse_middleware` key, we then replace the default parser.
However, you can define your own parsing method, using a response middleware. The middleware is expected to set `env[:body]` to a hash with three keys: `data`, `errors` and `metadata`. The following code enables parsing JSON data and treating the result as first-level properties. Using the builder block, we then replace the default parser.

```ruby
class MyCustomParser < Faraday::Response::Middleware
Expand All @@ -103,7 +105,10 @@ class MyCustomParser < Faraday::Response::Middleware
end
end

Her::API.setup :base_uri => "https://api.example.com", :parse_middleware => MyCustomParser
Her::API.setup :base_uri => "https://api.example.com" do |builder|
builder.delete Her::Middleware::DefaultParseJSON
builder.use MyCustomParser
end
# User.find(1) will now expect "https://api.example.com/users/1" to return something like '{ "data" => { "id": 1, "name": "Tobias Fünke" }, "errors" => [] }'
```

Expand All @@ -130,10 +135,9 @@ TWITTER_CREDENTIALS = {
:token_secret => ""
}

Her::API.setup({
:base_uri => "https://api.twitter.com/1/",
:add_middleware => [FaradayMiddleware::OAuth => TWITTER_CREDENTIALS]
})
Her::API.setup :base_uri => "https://api.twitter.com/1/" do |builder|
builder.use FaradayMiddleware::OAuth, TWITTER_CREDENTIALS
end

class Tweet
include Her::Model
Expand All @@ -142,6 +146,41 @@ end
@tweets = Tweet.get("/statuses/home_timeline.json")
```

### Caching

Again, using the `faraday_middleware` makes it very easy to cache requests and responses:

In your Gemfile:

```ruby
gem "her"
gem "faraday_middleware"
```

In your Ruby code:

```ruby
class MyCache
def write(key, value); end
def read(key); end
def fetch(key, &block); end
end

# A cache system must respond to `#write`, `#read` and `#fetch`.
$cache = MyCache.new

Her::API.setup :base_uri => "https://api.example.com" do |builder|
builder.use FaradayMiddleware::Caching, $cache
end

class User
include Her::Model
end

@user = User.find(1)
@user = User.find(1) # This request will be fetched from the cache
```
## Relationships
You can define `has_many`, `has_one` and `belongs_to` relationships in your models. The relationship data is handled in two different ways. When parsing a resource from JSON data, if there’s a relationship data included, it will be used to create new Ruby objects.
Expand Down
23 changes: 17 additions & 6 deletions lib/her/api.rb
Expand Up @@ -8,16 +8,20 @@ class API
# Setup a default API connection. Accepted arguments and options are the same as {API#setup}.
def self.setup(attrs={}) # {{{
@@default_api = new
@@default_api.setup(attrs)
connection = @@default_api.setup(attrs)
yield connection.builder if block_given?
connection
end # }}}

# Setup the API connection.
#
# @param [Hash] attrs the options to create a message with
# @option attrs [String] :base_uri The main HTTP API root (eg. `https://api.example.com`)
# @option attrs [Array, Class] :middleware A list of the only middleware Her will use
# @option attrs [Array, Class] :add_middleware A list of middleware to add to Her’s default middleware
# @option attrs [Class] :parse_middleware A middleware that will replace {Her::Middleware::FirstLevelParseJSON} to parse the received JSON
# @option attrs [Array, Class] :middleware **Deprecated** A list of the only middleware Her will use
# @option attrs [Array, Class] :add_middleware **Deprecated** A list of middleware to add to Her’s default middleware
# @option attrs [Class] :parse_middleware **Deprecated** A middleware that will replace {Her::Middleware::FirstLevelParseJSON} to parse the received JSON
#
# @return Faraday::Connection
#
# @example Setting up the default API connection
# Her::API.setup :base_uri => "https://api.example"
Expand All @@ -29,7 +33,9 @@ def self.setup(attrs={}) # {{{
# @all.call(env)
# end
# end
# Her::API.setup :base_uri => "https://api.example.com", :add_middleware => [MyAuthentication]
# Her::API.setup :base_uri => "https://api.example.com" do |builder|
# builder.use MyAuthentication
# end
#
# @example A custom parse middleware
# class MyCustomParser < Faraday::Response::Middleware
Expand All @@ -40,7 +46,10 @@ def self.setup(attrs={}) # {{{
# env[:body] = { :data => json, :errors => errors, :metadata => metadata }
# end
# end
# Her::API.setup :base_uri => "https://api.example.com", :parse_middleware => MyCustomParser
# Her::API.setup :base_uri => "https://api.example.com" do |builder|
# builder.delete Her::Middleware::DefaultParseJSON
# builder.use MyCustomParser
# end
def setup(attrs={}) # {{{
@base_uri = attrs[:base_uri]
@middleware = Her::API.default_middleware
Expand All @@ -62,6 +71,8 @@ def setup(attrs={}) # {{{
end
end
end
yield @connection.builder if block_given?
@connection
end # }}}

# Define a custom parsing procedure. The procedure is passed the response object and is
Expand Down
2 changes: 2 additions & 0 deletions lib/her/middleware.rb
Expand Up @@ -2,5 +2,7 @@ module Her
module Middleware
autoload :FirstLevelParseJSON, "her/middleware/first_level_parse_json"
autoload :SecondLevelParseJSON, "her/middleware/second_level_parse_json"

DefaultParseJSON = FirstLevelParseJSON
end
end
2 changes: 1 addition & 1 deletion lib/her/version.rb
@@ -1,3 +1,3 @@
module Her
VERSION = "0.2"
VERSION = "0.2.1"
end
60 changes: 4 additions & 56 deletions spec/api_spec.rb
Expand Up @@ -16,41 +16,6 @@
@api.setup :base_uri => "https://api.example.com"
@api.base_uri.should == "https://api.example.com"
end # }}}

it "sets additional middleware" do # {{{
class Foo < Faraday::Response::Middleware; end;
class Bar < Faraday::Response::Middleware; end;
class Baz < Faraday::Response::Middleware; end;

@api = Her::API.new
@api.setup :base_uri => "https://api.example.com", :add_middleware => [Foo, Bar, Baz => { :hello => :world }]
@api.middleware.should == [Foo, Bar, { Baz => { :hello => :world } }, Her::Middleware::FirstLevelParseJSON, Faraday::Request::UrlEncoded, Faraday::Adapter::NetHttp]

@api = Her::API.new
@api.setup :base_uri => "https://api.example.com", :add_middleware => Foo
@api.middleware.should == [Foo, Her::Middleware::FirstLevelParseJSON, Faraday::Request::UrlEncoded, Faraday::Adapter::NetHttp]

@api = Her::API.new
@api.setup :base_uri => "https://api.example.com", :add_middleware => [{Baz => { :hello => :world }}]
@api.middleware.should == [{ Baz => { :hello => :world } }, Her::Middleware::FirstLevelParseJSON, Faraday::Request::UrlEncoded, Faraday::Adapter::NetHttp]
end # }}}

it "overrides middleware" do # {{{
class Foo < Faraday::Response::Middleware; end;
class Bar < Faraday::Response::Middleware; end;

@api = Her::API.new
@api.setup :base_uri => "https://api.example.com", :middleware => [Foo, Bar]
@api.middleware.should == [Foo, Bar]
end # }}}

it "sets a parse middleware" do # {{{
class Foo < Faraday::Response::Middleware; end;

@api = Her::API.new
@api.setup :base_uri => "https://api.example.com", :parse_middleware => Foo
@api.middleware.should == [Foo, Faraday::Request::UrlEncoded, Faraday::Adapter::NetHttp]
end # }}}
end

describe "#request" do
Expand Down Expand Up @@ -97,32 +62,15 @@ def on_complete(env)
end

@api = Her::API.new
@api.setup :base_uri => "https://api.example.com", :parse_middleware => CustomParser
@api.setup :base_uri => "https://api.example.com" do |connection|
connection.delete Her::Middleware::DefaultParseJSON
connection.use CustomParser
end
parsed_data = @api.request(:_method => :get, :_path => "users/1")
parsed_data[:data].should == { :id => 1, :name => "George Michael Bluth" }
parsed_data[:errors].should == []
parsed_data[:metadata].should == {}
end # }}}

it "uses middleware with custom arguments" do # {{{
FakeWeb.register_uri(:get, "https://api.example.com/users/1", :body => MultiJson.dump(:id => 1, :name => "George Michael Bluth"))

class FakeOAuth < Faraday::Middleware
def initialize(app, options={})
super(app)
@options = options
end

def call(env)
env[:request_headers]["X-Hello"] = @options[:foo]
@app.call(env)
end
end

@api = Her::API.new
@api.setup :base_uri => "https://api.example.com", :add_middleware => [FakeOAuth => { :foo => "World" }]
parsed_data = @api.request(:_method => :get, :_path => "users/1")
end # }}}
end
end
end

0 comments on commit b73abec

Please sign in to comment.