her 0.3.7 → 0.3.8
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.
- data/README.md +565 -10
- data/Rakefile +0 -16
- data/{CONTRIBUTING.md → docs/CONTRIBUTING.md} +0 -0
- data/{UPGRADE.md → docs/UPGRADE.md} +2 -0
- data/her.gemspec +3 -9
- data/lib/her/api.rb +1 -1
- data/lib/her/model/hooks.rb +8 -1
- data/lib/her/model/introspection.rb +1 -1
- data/lib/her/model/orm.rb +9 -2
- data/lib/her/version.rb +1 -1
- data/spec/api_spec.rb +6 -0
- data/spec/model/hooks_spec.rb +131 -0
- data/spec/model/introspection_spec.rb +10 -2
- data/spec/spec_helper.rb +1 -1
- metadata +12 -113
- data/FEATURES.md +0 -296
- data/Guardfile +0 -7
- data/MIDDLEWARE.md +0 -183
- data/TESTING.md +0 -88
data/Guardfile
DELETED
data/MIDDLEWARE.md
DELETED
@@ -1,183 +0,0 @@
|
|
1
|
-
# Middleware
|
2
|
-
|
3
|
-
Since Her relies on [Faraday](https://github.com/technoweenie/faraday) to send HTTP requests, you can choose the middleware used to handle requests and responses. Using the block in the `setup` call, you have access to Faraday’s `connection` object and are able to customize the middleware stack used on each request and response.
|
4
|
-
|
5
|
-
## Authentication
|
6
|
-
|
7
|
-
Her doesn’t support authentication by default. However, it’s easy to implement one with request middleware. Using the `connection` block, we can add it to the middleware stack.
|
8
|
-
|
9
|
-
For example, to add a API token header to your requests in a Rails application, you would do something like this:
|
10
|
-
|
11
|
-
```ruby
|
12
|
-
# app/controllers/application_controller.rb
|
13
|
-
class ApplicationController < ActionController::Base
|
14
|
-
around_filter :do_with_authenticated_user
|
15
|
-
|
16
|
-
def do_with_authenticated_user
|
17
|
-
Thread.current[:my_api_token] = session[:my_api_token]
|
18
|
-
begin
|
19
|
-
yield
|
20
|
-
ensure
|
21
|
-
Thread.current[:my_access_token] = nil
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# lib/my_token_authentication.rb
|
27
|
-
class MyTokenAuthentication < Faraday::Middleware
|
28
|
-
def initialize(app, options={})
|
29
|
-
@app = app
|
30
|
-
end
|
31
|
-
|
32
|
-
def call(env)
|
33
|
-
env[:request_headers]["X-API-Token"] = Thread.current[:my_api_token] if Thread.current[:my_api_token].present?
|
34
|
-
@app.call(env)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# config/initializers/her.rb
|
39
|
-
require "lib/my_token_authentication"
|
40
|
-
|
41
|
-
Her::API.setup :url => "https://api.example.com" do |connection|
|
42
|
-
connection.use MyTokenAuthentication
|
43
|
-
connection.use Faraday::Request::UrlEncoded
|
44
|
-
connection.use Her::Middleware::DefaultParseJSON
|
45
|
-
connection.use Faraday::Adapter::NetHttp
|
46
|
-
end
|
47
|
-
```
|
48
|
-
|
49
|
-
Now, each HTTP request made by Her will have the `X-API-Token` header.
|
50
|
-
|
51
|
-
## Parsing JSON data
|
52
|
-
|
53
|
-
By default, Her handles JSON data. It expects the resource/collection data to be returned at the first level.
|
54
|
-
|
55
|
-
```javascript
|
56
|
-
// The response of GET /users/1
|
57
|
-
{ "id" : 1, "name" : "Tobias Fünke" }
|
58
|
-
|
59
|
-
// The response of GET /users
|
60
|
-
[{ "id" : 1, "name" : "Tobias Fünke" }]
|
61
|
-
```
|
62
|
-
|
63
|
-
However, you can define your own parsing method using a response middleware. The middleware should set `env[:body]` to a hash with three keys: `data`, `errors` and `metadata`. The following code uses a custom middleware to parse the JSON data:
|
64
|
-
|
65
|
-
```ruby
|
66
|
-
# Expects responses like:
|
67
|
-
#
|
68
|
-
# {
|
69
|
-
# "result": {
|
70
|
-
# "id": 1,
|
71
|
-
# "name": "Tobias Fünke"
|
72
|
-
# },
|
73
|
-
# "errors" => []
|
74
|
-
# }
|
75
|
-
#
|
76
|
-
class MyCustomParser < Faraday::Response::Middleware
|
77
|
-
def on_complete(env)
|
78
|
-
json = MultiJson.load(env[:body], :symbolize_keys => true)
|
79
|
-
env[:body] = {
|
80
|
-
:data => json[:result],
|
81
|
-
:errors => json[:errors],
|
82
|
-
:metadata => json[:metadata]
|
83
|
-
}
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
Her::API.setup :url => "https://api.example.com" do |connection|
|
88
|
-
connection.use Faraday::Request::UrlEncoded
|
89
|
-
connection.use MyCustomParser
|
90
|
-
connection.use Faraday::Adapter::NetHttp
|
91
|
-
end
|
92
|
-
```
|
93
|
-
|
94
|
-
## OAuth
|
95
|
-
|
96
|
-
Using the `faraday_middleware` and `simple_oauth` gems, it’s fairly easy to use OAuth authentication with Her.
|
97
|
-
|
98
|
-
In your Gemfile:
|
99
|
-
|
100
|
-
```ruby
|
101
|
-
gem "her"
|
102
|
-
gem "faraday_middleware"
|
103
|
-
gem "simple_oauth"
|
104
|
-
```
|
105
|
-
|
106
|
-
In your Ruby code:
|
107
|
-
|
108
|
-
```ruby
|
109
|
-
# Create an application on `https://dev.twitter.com/apps` to set these values
|
110
|
-
TWITTER_CREDENTIALS = {
|
111
|
-
:consumer_key => "",
|
112
|
-
:consumer_secret => "",
|
113
|
-
:token => "",
|
114
|
-
:token_secret => ""
|
115
|
-
}
|
116
|
-
|
117
|
-
Her::API.setup :url => "https://api.twitter.com/1/" do |connection|
|
118
|
-
connection.use FaradayMiddleware::OAuth, TWITTER_CREDENTIALS
|
119
|
-
connection.use Faraday::Request::UrlEncoded
|
120
|
-
connection.use Her::Middleware::DefaultParseJSON
|
121
|
-
connection.use Faraday::Adapter::NetHttp
|
122
|
-
end
|
123
|
-
|
124
|
-
class Tweet
|
125
|
-
include Her::Model
|
126
|
-
end
|
127
|
-
|
128
|
-
@tweets = Tweet.get("/statuses/home_timeline.json")
|
129
|
-
```
|
130
|
-
|
131
|
-
See the *Authentication* middleware section for an example of how to pass different credentials based on the current user.
|
132
|
-
|
133
|
-
## Caching
|
134
|
-
|
135
|
-
Again, using the `faraday_middleware` makes it very easy to cache requests and responses:
|
136
|
-
|
137
|
-
In your Gemfile:
|
138
|
-
|
139
|
-
```ruby
|
140
|
-
gem "her"
|
141
|
-
gem "faraday_middleware"
|
142
|
-
```
|
143
|
-
|
144
|
-
In your Ruby code:
|
145
|
-
|
146
|
-
```ruby
|
147
|
-
class MyCache < Hash
|
148
|
-
def read(key)
|
149
|
-
if cached = self[key]
|
150
|
-
Marshal.load(cached)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
def write(key, data)
|
155
|
-
self[key] = Marshal.dump(data)
|
156
|
-
end
|
157
|
-
|
158
|
-
def fetch(key)
|
159
|
-
read(key) || yield.tap { |data| write(key, data) }
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
# A cache system must respond to `#write`, `#read` and `#fetch`.
|
164
|
-
# We should be probably using something like Memcached here, not a global object
|
165
|
-
$cache = MyCache.new
|
166
|
-
|
167
|
-
Her::API.setup :url => "https://api.example.com" do |connection|
|
168
|
-
connection.use Faraday::Request::UrlEncoded
|
169
|
-
connection.use FaradayMiddleware::Caching, $cache
|
170
|
-
connection.use Her::Middleware::DefaultParseJSON
|
171
|
-
connection.use Faraday::Adapter::NetHttp
|
172
|
-
end
|
173
|
-
|
174
|
-
class User
|
175
|
-
include Her::Model
|
176
|
-
end
|
177
|
-
|
178
|
-
@user = User.find(1)
|
179
|
-
# GET /users/1
|
180
|
-
|
181
|
-
@user = User.find(1)
|
182
|
-
# This request will be fetched from the cache
|
183
|
-
```
|
data/TESTING.md
DELETED
@@ -1,88 +0,0 @@
|
|
1
|
-
# Testing Her models
|
2
|
-
|
3
|
-
Suppose we have these two models bound to your API:
|
4
|
-
|
5
|
-
```ruby
|
6
|
-
# app/models/user.rb
|
7
|
-
class User
|
8
|
-
include Her::Model
|
9
|
-
custom_get :popular
|
10
|
-
end
|
11
|
-
|
12
|
-
# app/models/post.rb
|
13
|
-
class Post
|
14
|
-
include Her::Model
|
15
|
-
custom_get :recent, :archived
|
16
|
-
end
|
17
|
-
```
|
18
|
-
|
19
|
-
In order to test them, we’ll have to stub the remote API requests. With [RSpec](https://github.com/rspec/rspec-core), we can do this like so:
|
20
|
-
|
21
|
-
```ruby
|
22
|
-
# spec/spec_helper.rb
|
23
|
-
RSpec.configure do |config|
|
24
|
-
config.include(Module.new do
|
25
|
-
def stub_api_for(klass)
|
26
|
-
klass.uses_api (api = Her::API.new)
|
27
|
-
|
28
|
-
# Here, you would customize this for your own API (URL, middleware, etc)
|
29
|
-
# like you have done in your application’s initializer
|
30
|
-
api.setup :url => "http://api.example.com" do |connection|
|
31
|
-
connection.use Her::Middleware::FirstLevelParseJSON
|
32
|
-
connection.use Faraday::Request::UrlEncoded
|
33
|
-
connection.adapter(:test) { |s| yield(s) }
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end)
|
37
|
-
end
|
38
|
-
```
|
39
|
-
|
40
|
-
Then, in your tests, we can specify what (fake) HTTP requests will return:
|
41
|
-
|
42
|
-
```ruby
|
43
|
-
# spec/models/user.rb
|
44
|
-
describe User do
|
45
|
-
before do
|
46
|
-
stub_api_for(User) do |stub|
|
47
|
-
stub.get("/users/popular") { |env| [200, {}, [{ :id => 1, :name => "Tobias Fünke" }, { :id => 2, :name => "Lindsay Fünke" }].to_json] }
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
describe :popular do
|
52
|
-
subject { User.popular }
|
53
|
-
its(:length) { should == 2 }
|
54
|
-
its(:errors) { should be_empty }
|
55
|
-
end
|
56
|
-
end
|
57
|
-
```
|
58
|
-
|
59
|
-
We can redefine the API for a model as many times as we want, like for more complex tests:
|
60
|
-
|
61
|
-
```ruby
|
62
|
-
# spec/models/user.rb
|
63
|
-
describe Post do
|
64
|
-
describe :recent do
|
65
|
-
before do
|
66
|
-
stub_api_for(Post) do |stub|
|
67
|
-
stub.get("/posts/recent") { |env| [200, {}, [{ :id => 1 }, { :id => 2 }].to_json] }
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
subject { Post.recent }
|
72
|
-
its(:length) { should == 2 }
|
73
|
-
its(:errors) { should be_empty }
|
74
|
-
end
|
75
|
-
|
76
|
-
describe :archived do
|
77
|
-
before do
|
78
|
-
stub_api_for(Post) do |stub|
|
79
|
-
stub.get("/posts/archived") { |env| [200, {}, [{ :id => 1 }, { :id => 2 }].to_json] }
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
subject { Post.archived }
|
84
|
-
its(:length) { should == 2 }
|
85
|
-
its(:errors) { should be_empty }
|
86
|
-
end
|
87
|
-
end
|
88
|
-
```
|