her 0.2.5 → 0.2.6

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/.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