her 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format=documentation
data/README.md CHANGED
@@ -1,9 +1,7 @@
1
- # Her
1
+ # Her [![Build Status](https://secure.travis-ci.org/remiprev/her.png)](http://travis-ci.org/remiprev/her) [![Gem dependency status](https://gemnasium.com/remiprev/her.png?travis)](https://gemnasium.com/remiprev/her)
2
2
 
3
3
  Her is an ORM (Object Relational Mapper) that maps REST resources to Ruby objects. It is designed to build applications that are powered by a RESTful API instead of a database.
4
4
 
5
- [![Build Status](https://secure.travis-ci.org/remiprev/her.png)](http://travis-ci.org/remiprev/her)
6
-
7
5
  ## Installation
8
6
 
9
7
  In your Gemfile, add:
@@ -24,10 +22,10 @@ First, you have to define which API your models will be bound to. For example, w
24
22
 
25
23
  ```ruby
26
24
  # config/initializers/her.rb
27
- Her::API.setup :base_uri => "https://api.example.com" do |builder|
28
- builder.use Faraday::Request::UrlEncoded
29
- builder.use Her::Middleware::DefaultParseJSON
30
- builder.use Faraday::Adapter::NetHttp
25
+ Her::API.setup :url => "https://api.example.com" do |connection|
26
+ connection.use Faraday::Request::UrlEncoded
27
+ connection.use Her::Middleware::DefaultParseJSON
28
+ connection.use Faraday::Adapter::NetHttp
31
29
  end
32
30
  ```
33
31
 
@@ -62,13 +60,15 @@ User.find(1)
62
60
  # PUT https://api.example.com/users/1 with the data and return+update the User object
63
61
  ```
64
62
 
63
+ You can look into the `examples` directory for sample applications using Her.
64
+
65
65
  ## Middleware
66
66
 
67
- Since Her relies on [Faraday](https://github.com/technoweenie/faraday) to send HTTP requests, you can add additional middleware to handle requests and responses. Using the block in the `setup` call, you have access to Faraday’s `builder` object and are able to customize the middleware stack used on each request and response.
67
+ Since Her relies on [Faraday](https://github.com/technoweenie/faraday) to send HTTP requests, you can add additional middleware 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.
68
68
 
69
69
  ### Authentication
70
70
 
71
- 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.
71
+ Her doesn’t support any kind of authentication. However, it’s very easy to implement one with a request middleware. Using the connection block, we add it to the default list of middleware.
72
72
 
73
73
  ```ruby
74
74
  class MyAuthentication < Faraday::Middleware
@@ -82,13 +82,13 @@ class MyAuthentication < Faraday::Middleware
82
82
  end
83
83
  end
84
84
 
85
- Her::API.setup :base_uri => "https://api.example.com" do |builder|
85
+ Her::API.setup :url => "https://api.example.com" do |connection|
86
86
  # This token could be stored in the client session
87
- builder.use MyAuthentication, :token => "bb2b2dd75413d32c1ac421d39e95b978d1819ff611f68fc2fdd5c8b9c7331192"
87
+ connection.use MyAuthentication, :token => "bb2b2dd75413d32c1ac421d39e95b978d1819ff611f68fc2fdd5c8b9c7331192"
88
88
 
89
- builder.use Faraday::Request::UrlEncoded
90
- builder.use Her::Middleware::DefaultParseJSON
91
- builder.use Faraday::Adapter::NetHttp
89
+ connection.use Faraday::Request::UrlEncoded
90
+ connection.use Her::Middleware::DefaultParseJSON
91
+ connection.use Faraday::Adapter::NetHttp
92
92
  end
93
93
  ```
94
94
 
@@ -106,7 +106,7 @@ By default, Her handles JSON data. It expects the resource/collection data to be
106
106
  [{ "id" : 1, "name" : "Tobias Fünke" }]
107
107
  ```
108
108
 
109
- 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 with our custom parser.
109
+ 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 connection block, we then replace the default parser with our custom parser.
110
110
 
111
111
  ```ruby
112
112
  class MyCustomParser < Faraday::Response::Middleware
@@ -120,10 +120,10 @@ class MyCustomParser < Faraday::Response::Middleware
120
120
  end
121
121
  end
122
122
 
123
- Her::API.setup :base_uri => "https://api.example.com" do |builder|
124
- builder.use Faraday::Request::UrlEncoded
125
- builder.use MyCustomParser
126
- builder.use Faraday::Adapter::NetHttp
123
+ Her::API.setup :url => "https://api.example.com" do |connection|
124
+ connection.use Faraday::Request::UrlEncoded
125
+ connection.use MyCustomParser
126
+ connection.use Faraday::Adapter::NetHttp
127
127
  end
128
128
  # User.find(1) will now expect "https://api.example.com/users/1" to return something like '{ "result" => { "id": 1, "name": "Tobias Fünke" }, "errors" => [] }'
129
129
  ```
@@ -151,11 +151,11 @@ TWITTER_CREDENTIALS = {
151
151
  :token_secret => ""
152
152
  }
153
153
 
154
- Her::API.setup :base_uri => "https://api.twitter.com/1/" do |builder|
155
- builder.use FaradayMiddleware::OAuth, TWITTER_CREDENTIALS
156
- builder.use Faraday::Request::UrlEncoded
157
- builder.use Her::Middleware::DefaultParseJSON
158
- builder.use Faraday::Adapter::NetHttp
154
+ Her::API.setup :url => "https://api.twitter.com/1/" do |connection|
155
+ connection.use FaradayMiddleware::OAuth, TWITTER_CREDENTIALS
156
+ connection.use Faraday::Request::UrlEncoded
157
+ connection.use Her::Middleware::DefaultParseJSON
158
+ connection.use Faraday::Adapter::NetHttp
159
159
  end
160
160
 
161
161
  class Tweet
@@ -199,11 +199,11 @@ end
199
199
  # We should be probably using something like Memcached here, not a global object
200
200
  $cache = MyCache.new
201
201
 
202
- Her::API.setup :base_uri => "https://api.example.com" do |builder|
203
- builder.use Faraday::Request::UrlEncoded
204
- builder.use FaradayMiddleware::Caching, $cache
205
- builder.use Her::Middleware::DefaultParseJSON
206
- builder.use Faraday::Adapter::NetHttp
202
+ Her::API.setup :url => "https://api.example.com" do |connection|
203
+ connection.use Faraday::Request::UrlEncoded
204
+ connection.use FaradayMiddleware::Caching, $cache
205
+ connection.use Her::Middleware::DefaultParseJSON
206
+ connection.use Faraday::Adapter::NetHttp
207
207
  end
208
208
 
209
209
  class User
@@ -403,17 +403,17 @@ It is possible to use different APIs for different models. Instead of calling `H
403
403
  ```ruby
404
404
  # config/initializers/her.rb
405
405
  $my_api = Her::API.new
406
- $my_api.setup :base_uri => "https://my_api.example.com" do |builder|
407
- builder.use Faraday::Request::UrlEncoded
408
- builder.use Her::Middleware::DefaultParseJSON
409
- builder.use Faraday::Adapter::NetHttp
406
+ $my_api.setup :url => "https://my_api.example.com" do |connection|
407
+ connection.use Faraday::Request::UrlEncoded
408
+ connection.use Her::Middleware::DefaultParseJSON
409
+ connection.use Faraday::Adapter::NetHttp
410
410
  end
411
411
 
412
412
  $other_api = Her::API.new
413
- $other_api.setup :base_uri => "https://other_api.example.com" do |builder|
414
- builder.use Faraday::Request::UrlEncoded
415
- builder.use Her::Middleware::DefaultParseJSON
416
- builder.use Faraday::Adapter::NetHttp
413
+ $other_api.setup :url => "https://other_api.example.com" do |connection|
414
+ connection.use Faraday::Request::UrlEncoded
415
+ connection.use Her::Middleware::DefaultParseJSON
416
+ connection.use Faraday::Adapter::NetHttp
417
417
  end
418
418
  ```
419
419
 
@@ -437,6 +437,51 @@ Category.all
437
437
  # GET https://other_api.example.com/categories
438
438
  ```
439
439
 
440
+ ## SSL
441
+
442
+ When initializing `Her::API`, you can pass any parameter supported by `Faraday.new`. So [to use HTTPS](https://github.com/technoweenie/faraday/wiki/Setting-up-SSL-certificates), you can use Faraday’s `:ssl` option.
443
+
444
+ ```ruby
445
+ ssl_options = { :ca_path => "/usr/lib/ssl/certs" }
446
+ Her::API.setup :url => "https://api.example.com", :ssl => ssl_options do |connection|
447
+ connection.use Faraday::Request::UrlEncoded
448
+ connection.use Her::Middleware::DefaultParseJSON
449
+ connection.use Faraday::Adapter::NetHttp
450
+ end
451
+ ```
452
+
453
+ ## Testing
454
+
455
+ Using Faraday stubbing feature, it’s very easy to write tests for our models. For example, using [RSpec](https://github.com/rspec/rspec-core):
456
+
457
+ ```ruby
458
+ # app/models/post.rb
459
+ class Post
460
+ include Her::Model
461
+ custom_get :popular
462
+ end
463
+
464
+ # spec/models/post.rb
465
+ describe Post do
466
+ before do
467
+ Her::API.setup :url => "http://api.example.com" do |connection|
468
+ connection.use Her::Middleware::FirstLevelParseJSON
469
+ connection.use Faraday::Request::UrlEncoded
470
+ connection.adapter :test do |stub|
471
+ stub.get("/users/popular") { |env| [200, {}, [{ :id => 1, :name => "Tobias Fünke" }, { :id => 2, :name => "Lindsay Fünke" }].to_json] }
472
+ end
473
+ end
474
+ end
475
+
476
+ describe ".popular" do
477
+ it "should fetch all popular posts" do
478
+ @posts = Post.popular
479
+ @posts.length.should == 2
480
+ end
481
+ end
482
+ end
483
+ ```
484
+
440
485
  ## Things to be done
441
486
 
442
487
  * Better error handling
@@ -464,6 +509,7 @@ These fine folks helped with Her:
464
509
  * [@rafaelss](https://github.com/rafaelss)
465
510
  * [@tysontate](https://github.com/tysontate)
466
511
  * [@nfo](https://github.com/nfo)
512
+ * [@simonprevost](https://github.com/simonprevost)
467
513
 
468
514
  ## License
469
515
 
data/Rakefile CHANGED
@@ -9,7 +9,6 @@ task :default => :spec
9
9
  desc "Run all specs"
10
10
  RSpec::Core::RakeTask.new(:spec) do |task| # {{{
11
11
  task.pattern = "spec/**/*_spec.rb"
12
- task.rspec_opts = "--colour --format=documentation"
13
12
  end # }}}
14
13
 
15
14
  desc "Generate YARD Documentation"
data/UPGRADE.md CHANGED
@@ -1,20 +1,20 @@
1
- Here is a list of backward-incompatible changes that were introduced while Her is still \<1.0. After reaching 1.0, it will follow the [Semantic Versioning](http://semver.org/) system.
1
+ Here is a list of backward-incompatible changes that were introduced while Her is pre-1.0. After reaching 1.0, it will follow the [Semantic Versioning](http://semver.org/) system.
2
2
 
3
3
  ## 0.2.4
4
4
 
5
5
  * Her no longer includes default middleware when making HTTP requests. The user has now to define all the needed middleware. Before:
6
6
 
7
- Her::API.setup :base_uri => "https://api.example.com" do |builder|
8
- builder.insert(0, FaradayMiddle::OAuth)
7
+ Her::API.setup :url => "https://api.example.com" do |connection|
8
+ connection.insert(0, FaradayMiddle::OAuth)
9
9
  end
10
10
 
11
11
  Now:
12
-
13
- Her::API.setup :base_uri => "https://api.example.com" do |builder|
14
- builder.use FaradayMiddle::OAuth
15
- builder.use Her::Middleware::FirstLevelParseJSON
16
- builder.use Faraday::Request::UrlEncoded
17
- builder.use Faraday::Adapter::NetHttp
12
+
13
+ Her::API.setup :url => "https://api.example.com" do |connection|
14
+ connection.use FaradayMiddle::OAuth
15
+ connection.use Her::Middleware::FirstLevelParseJSON
16
+ connection.use Faraday::Request::UrlEncoded
17
+ connection.use Faraday::Adapter::NetHttp
18
18
  end
19
19
 
20
20
  ## 0.2
@@ -22,9 +22,9 @@ Here is a list of backward-incompatible changes that were introduced while Her i
22
22
  * The default parser middleware has been replaced to treat first-level JSON data as the resource or collection data. Before it expected this:
23
23
 
24
24
  { "data": { "id": 1, "name": "Foo" }, "errors": [] }
25
-
25
+
26
26
  Now it expects this (the `errors` key is not treated as resource data):
27
-
27
+
28
28
  { "id": 1, "name": "Foo", "errors": [] }
29
-
30
- If you still want to get the old behavior, you can use `Her::Middleware::SecondLevelParseJSON` instead of `Her::Middleware::FirstLevelParseJSON` in your middleware stack.
29
+
30
+ If you still want to get the old behavior, you can use `Her::Middleware::SecondLevelParseJSON` instead of `Her::Middleware::FirstLevelParseJSON` in your middleware stack.
@@ -1,7 +1,5 @@
1
1
  # Create custom parser
2
2
  class TwitterParser < Faraday::Response::Middleware
3
- METADATA_KEYS = [:completed_in, :max_id, :max_id_str, :next_page, :page, :query, :refresh_url, :results_per_page, :since_id, :since_id_str]
4
-
5
3
  def on_complete(env)
6
4
  json = MultiJson.load(env[:body], :symbolize_keys => true)
7
5
  errors = [json.delete(:error)]
@@ -13,6 +11,7 @@ class TwitterParser < Faraday::Response::Middleware
13
11
  end
14
12
  end
15
13
 
14
+ # See https://dev.twitter.com/apps
16
15
  TWITTER_CREDENTIALS = {
17
16
  :consumer_key => "",
18
17
  :consumer_secret => "",
@@ -21,9 +20,11 @@ TWITTER_CREDENTIALS = {
21
20
  }
22
21
 
23
22
  # Initialize API
24
- Her::API.setup :base_uri => "https://api.twitter.com/1/" do |builder|
25
- builder.insert 0, FaradayMiddleware::OAuth, TWITTER_CREDENTIALS
26
- builder.swap Her::Middleware::DefaultParseJSON, TwitterParser
23
+ Her::API.setup :url => "https://api.twitter.com/1/" do |builder|
24
+ builder.use FaradayMiddleware::OAuth, TWITTER_CREDENTIALS
25
+ builder.use Faraday::Request::UrlEncoded
26
+ builder.use TwitterParser
27
+ builder.use Faraday::Adapter::NetHttp
27
28
  end
28
29
 
29
30
  # Define classes
@@ -4,10 +4,12 @@ class TwitterSearchParser < Faraday::Response::Middleware
4
4
 
5
5
  def on_complete(env)
6
6
  json = MultiJson.load(env[:body], :symbolize_keys => true)
7
+ data = json.delete(:results)
8
+ errors = [json.delete(:error)].compact
7
9
  env[:body] = {
8
- :data => json[:results],
9
- :errors => [json[:error]],
10
- :metadata => json.select { |key, value| METADATA_KEYS.include?(key) }
10
+ :data => data,
11
+ :errors => errors,
12
+ :metadata => json
11
13
  }
12
14
  end
13
15
  end
@@ -31,11 +33,11 @@ end
31
33
  $cache = MyCache.new
32
34
 
33
35
  # Initialize API
34
- Her::API.setup :base_uri => "http://search.twitter.com" do |builder|
35
- builder.use Faraday::Request::UrlEncoded
36
- builder.use FaradayMiddleware::Caching, $cache
37
- builder.use TwitterSearchParser
38
- builder.use Faraday::Adapter::NetHttp
36
+ Her::API.setup :url => "http://search.twitter.com" do |connection|
37
+ connection.use Faraday::Request::UrlEncoded
38
+ connection.use FaradayMiddleware::Caching, $cache
39
+ connection.use TwitterSearchParser
40
+ connection.use Faraday::Adapter::NetHttp
39
41
  end
40
42
 
41
43
  # Define classes
@@ -8,6 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.authors = ["Rémi Prévost"]
9
9
  s.email = ["remi@exomel.com"]
10
10
  s.homepage = "http://remiprev.github.com/her"
11
+ s.license = "MIT"
11
12
  s.summary = "A simple Representational State Transfer-based Hypertext Transfer Protocol-powered Object Relational Mapper. Her?"
12
13
  s.description = "Her is an ORM that maps REST resources and collections to Ruby objects"
13
14
 
@@ -16,18 +17,17 @@ Gem::Specification.new do |s|
16
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
18
  s.require_paths = ["lib"]
18
19
 
19
- s.add_development_dependency "rake", "0.9.2.2"
20
- s.add_development_dependency "rspec", "2.9.0"
21
- s.add_development_dependency "yard", "0.7.5"
22
- s.add_development_dependency "redcarpet", "1.17.2"
23
- s.add_development_dependency "mocha", "0.11.3"
24
- s.add_development_dependency "fakeweb", "1.3.0"
25
- s.add_development_dependency "guard", "1.0.1"
26
- s.add_development_dependency "guard-rspec", "0.7.0"
27
- s.add_development_dependency "rb-fsevent", "0.9.1"
28
- s.add_development_dependency "growl", "1.0.3"
20
+ s.add_development_dependency "rake", "~> 0.9.2"
21
+ s.add_development_dependency "rspec", "~> 2.10"
22
+ s.add_development_dependency "yard", "~> 0.8"
23
+ s.add_development_dependency "redcarpet", "~> 2.1"
24
+ s.add_development_dependency "mocha", "~> 0.11"
25
+ s.add_development_dependency "guard", "~> 1.0"
26
+ s.add_development_dependency "guard-rspec", "~> 0.7"
27
+ s.add_development_dependency "rb-fsevent", "~> 0.9"
28
+ s.add_development_dependency "growl", "~> 1.0"
29
29
 
30
- s.add_runtime_dependency "activesupport", "3.2.3"
31
- s.add_runtime_dependency "faraday", "0.8.0"
32
- s.add_runtime_dependency "multi_json", "1.3.4"
30
+ s.add_runtime_dependency "activesupport", "~> 3.2"
31
+ s.add_runtime_dependency "faraday", "~> 0.8"
32
+ s.add_runtime_dependency "multi_json", "~> 1.3"
33
33
  end
data/lib/her.rb CHANGED
@@ -1,12 +1,15 @@
1
1
  require "her/version"
2
+
2
3
  require "multi_json"
3
4
  require "faraday"
4
5
  require "active_support"
5
6
  require "active_support/inflector"
6
7
 
8
+ require "her/model"
9
+ require "her/api"
10
+ require "her/middleware"
11
+ require "her/errors"
12
+ require "her/collection"
13
+
7
14
  module Her
8
- autoload :Model, "her/model"
9
- autoload :API, "her/api"
10
- autoload :Middleware, "her/middleware"
11
- autoload :Errors, "her/errors"
12
15
  end
@@ -3,7 +3,7 @@ module Her
3
3
  # so it knows where to make those requests. In Rails, this is usually done in `config/initializers/her.rb`:
4
4
  class API
5
5
  # @private
6
- attr_reader :base_uri, :connection
6
+ attr_reader :base_uri, :connection, :options
7
7
 
8
8
  # Setup a default API connection. Accepted arguments and options are the same as {API#setup}.
9
9
  def self.setup(attrs={}, &block) # {{{
@@ -13,13 +13,14 @@ module Her
13
13
 
14
14
  # Setup the API connection.
15
15
  #
16
- # @param [Hash] attrs the options to create a message with
17
- # @option attrs [String] :base_uri The main HTTP API root (eg. `https://api.example.com`)
16
+ # @param [Hash] attrs the Faraday options
17
+ # @option attrs [String] :url The main HTTP API root (eg. `https://api.example.com`)
18
+ # @option attrs [String] :ssl A hash containing [SSL options](https://github.com/technoweenie/faraday/wiki/Setting-up-SSL-certificates)
18
19
  #
19
20
  # @return Faraday::Connection
20
21
  #
21
22
  # @example Setting up the default API connection
22
- # Her::API.setup :base_uri => "https://api.example"
23
+ # Her::API.setup :url => "https://api.example"
23
24
  #
24
25
  # @example A custom middleware added to the default list
25
26
  # class MyAuthentication < Faraday::Middleware
@@ -28,8 +29,10 @@ module Her
28
29
  # @all.call(env)
29
30
  # end
30
31
  # end
31
- # Her::API.setup :base_uri => "https://api.example.com" do |builder|
32
- # builder.use MyAuthentication
32
+ # Her::API.setup :url => "https://api.example.com" do |connection|
33
+ # connection.use Faraday::Request::UrlEncoded
34
+ # connection.use Her::Middleware::DefaultParseJSON
35
+ # connection.use Faraday::Adapter::NetHttp
33
36
  # end
34
37
  #
35
38
  # @example A custom parse middleware
@@ -41,14 +44,17 @@ module Her
41
44
  # env[:body] = { :data => json, :errors => errors, :metadata => metadata }
42
45
  # end
43
46
  # end
44
- # Her::API.setup :base_uri => "https://api.example.com" do |builder|
45
- # builder.delete Her::Middleware::DefaultParseJSON
46
- # builder.use MyCustomParser
47
+ # Her::API.setup :url => "https://api.example.com" do |connection|
48
+ # connection.use Faraday::Request::UrlEncoded
49
+ # connection.use MyCustomParser
50
+ # connection.use Faraday::Adapter::NetHttp
47
51
  # end
48
52
  def setup(attrs={}) # {{{
49
- @base_uri = attrs[:base_uri]
50
- @connection = Faraday.new(:url => @base_uri) do |connection|
51
- yield connection.builder if block_given?
53
+ attrs[:url] = attrs.delete(:base_uri) if attrs.include?(:base_uri) # Support legacy :base_uri option
54
+ @base_uri = attrs[:url]
55
+ @options = attrs
56
+ @connection = Faraday.new(attrs) do |connection|
57
+ yield connection if block_given?
52
58
  end
53
59
  end # }}}
54
60
 
@@ -60,8 +66,10 @@ module Her
60
66
  def request(attrs={}) # {{{
61
67
  method = attrs.delete(:_method)
62
68
  path = attrs.delete(:_path)
69
+ headers = attrs.delete(:_headers)
63
70
  attrs.delete_if { |key, value| key.to_s =~ /^_/ } # Remove all internal parameters
64
71
  response = @connection.send method do |request|
72
+ request.headers.merge!(headers) if headers
65
73
  if method == :get
66
74
  # For GET requests, treat additional parameters as querystring data
67
75
  request.url path, attrs