her5 0.8.1
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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.travis.yml +17 -0
- data/.yardopts +2 -0
- data/CONTRIBUTING.md +26 -0
- data/Gemfile +10 -0
- data/LICENSE +7 -0
- data/README.md +1017 -0
- data/Rakefile +11 -0
- data/UPGRADE.md +101 -0
- data/gemfiles/Gemfile.activemodel-3.2.x +7 -0
- data/gemfiles/Gemfile.activemodel-4.0 +7 -0
- data/gemfiles/Gemfile.activemodel-4.1 +7 -0
- data/gemfiles/Gemfile.activemodel-4.2 +7 -0
- data/gemfiles/Gemfile.activemodel-5.0.x +7 -0
- data/her5.gemspec +30 -0
- data/lib/her.rb +19 -0
- data/lib/her/api.rb +120 -0
- data/lib/her/collection.rb +12 -0
- data/lib/her/errors.rb +104 -0
- data/lib/her/json_api/model.rb +57 -0
- data/lib/her/middleware.rb +12 -0
- data/lib/her/middleware/accept_json.rb +17 -0
- data/lib/her/middleware/first_level_parse_json.rb +36 -0
- data/lib/her/middleware/json_api_parser.rb +68 -0
- data/lib/her/middleware/parse_json.rb +28 -0
- data/lib/her/middleware/second_level_parse_json.rb +36 -0
- data/lib/her/model.rb +75 -0
- data/lib/her/model/associations.rb +141 -0
- data/lib/her/model/associations/association.rb +107 -0
- data/lib/her/model/associations/association_proxy.rb +45 -0
- data/lib/her/model/associations/belongs_to_association.rb +101 -0
- data/lib/her/model/associations/has_many_association.rb +101 -0
- data/lib/her/model/associations/has_one_association.rb +80 -0
- data/lib/her/model/attributes.rb +297 -0
- data/lib/her/model/base.rb +33 -0
- data/lib/her/model/deprecated_methods.rb +61 -0
- data/lib/her/model/http.rb +113 -0
- data/lib/her/model/introspection.rb +65 -0
- data/lib/her/model/nested_attributes.rb +84 -0
- data/lib/her/model/orm.rb +207 -0
- data/lib/her/model/parse.rb +221 -0
- data/lib/her/model/paths.rb +126 -0
- data/lib/her/model/relation.rb +164 -0
- data/lib/her/version.rb +3 -0
- data/spec/api_spec.rb +114 -0
- data/spec/collection_spec.rb +26 -0
- data/spec/json_api/model_spec.rb +305 -0
- data/spec/middleware/accept_json_spec.rb +10 -0
- data/spec/middleware/first_level_parse_json_spec.rb +62 -0
- data/spec/middleware/json_api_parser_spec.rb +32 -0
- data/spec/middleware/second_level_parse_json_spec.rb +35 -0
- data/spec/model/associations/association_proxy_spec.rb +31 -0
- data/spec/model/associations_spec.rb +504 -0
- data/spec/model/attributes_spec.rb +389 -0
- data/spec/model/callbacks_spec.rb +145 -0
- data/spec/model/dirty_spec.rb +91 -0
- data/spec/model/http_spec.rb +158 -0
- data/spec/model/introspection_spec.rb +76 -0
- data/spec/model/nested_attributes_spec.rb +134 -0
- data/spec/model/orm_spec.rb +506 -0
- data/spec/model/parse_spec.rb +345 -0
- data/spec/model/paths_spec.rb +347 -0
- data/spec/model/relation_spec.rb +226 -0
- data/spec/model/validations_spec.rb +42 -0
- data/spec/model_spec.rb +44 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/extensions/array.rb +5 -0
- data/spec/support/extensions/hash.rb +5 -0
- data/spec/support/macros/her_macros.rb +17 -0
- data/spec/support/macros/model_macros.rb +36 -0
- data/spec/support/macros/request_macros.rb +27 -0
- metadata +289 -0
data/Rakefile
ADDED
data/UPGRADE.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# Upgrade Her
|
2
|
+
|
3
|
+
Here is a list of notable changes by release. Her follows the [Semantic Versioning](http://semver.org/) system.
|
4
|
+
|
5
|
+
## 0.8.1 (Note: 0.8.0 yanked)
|
6
|
+
|
7
|
+
- Initial support for JSONAPI [link](https://github.com/remiprev/her/pull/347)
|
8
|
+
- Fix for has_one association parsing [link](https://github.com/remiprev/her/pull/352)
|
9
|
+
- Fix for escaping path variables HT @marshall-lee [link](https://github.com/remiprev/her/pull/354)
|
10
|
+
- Fix syntax highlighting in README HT @tippenein [link](https://github.com/remiprev/her/pull/356)
|
11
|
+
- Fix associations with Active Model Serializers HT @minktom [link](https://github.com/remiprev/her/pull/359)
|
12
|
+
|
13
|
+
## 0.7.6
|
14
|
+
|
15
|
+
- Loosen restrictions on ActiveSupport and ActiveModel to accommodate security fixes [link](https://github.com/remiprev/her/commit/8ff641fcdaf14be7cc9b1a6ee6654f27f7dfa34c)
|
16
|
+
|
17
|
+
## 0.7.5
|
18
|
+
|
19
|
+
- Performance fix for responses with large number of objects [link](https://github.com/remiprev/her/pull/337)
|
20
|
+
- Bugfix for dirty attributes [link](https://github.com/remiprev/her/commit/70285debc6837a33a3a750c7c4a7251439464b42)
|
21
|
+
- Add ruby 2.1 and 2.2 to travis test run. We will likely be removing official 1.9.x support in the near future, and
|
22
|
+
will begin to align our support with the official ruby maintenance schedule.
|
23
|
+
- README updates
|
24
|
+
|
25
|
+
## 0.6
|
26
|
+
|
27
|
+
Associations have been refactored so that calling the association name method doesn’t immediately load or fetch the data.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class User
|
31
|
+
include Her::Model
|
32
|
+
has_many :comments
|
33
|
+
end
|
34
|
+
|
35
|
+
# This doesn’t fetch the data yet and it’s still chainable
|
36
|
+
comments = User.find(1).comments
|
37
|
+
|
38
|
+
# This actually fetches the data
|
39
|
+
puts comments.inspect
|
40
|
+
|
41
|
+
# This is no longer possible in her-0.6
|
42
|
+
comments = User.find(1).comments(:approved => 1)
|
43
|
+
|
44
|
+
# To pass additional parameters to the HTTP request, we now have to do this
|
45
|
+
comments = User.find(1).comments.where(:approved => 1)
|
46
|
+
```
|
47
|
+
|
48
|
+
## 0.5
|
49
|
+
|
50
|
+
Her is now compatible with `ActiveModel` and includes `ActiveModel::Validations`.
|
51
|
+
|
52
|
+
Before 0.5, the `errors` method on an object would return an error list received from the server (the `:errors` key defined by the parsing middleware). But now, `errors` returns the error list generated after calling the `valid?` method (or any other similar validation method from `ActiveModel::Validations`). The error list returned from the server is now accessible from the `response_errors` method.
|
53
|
+
|
54
|
+
Since 0.5.5, Her provides a `store_response_errors` method, which allows you to choose the method which will return the response errors. You can use it to revert Her back to its original behavior (ie. `errors` returning the response errors):
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
class User
|
58
|
+
include Her::Model
|
59
|
+
store_response_errors :errors
|
60
|
+
end
|
61
|
+
|
62
|
+
user = User.create(:email => "foo") # POST /users returns { :errors => ["Email is invalid"] }
|
63
|
+
user.errors # => ["Email is invalid"]
|
64
|
+
```
|
65
|
+
|
66
|
+
## 0.2.4
|
67
|
+
|
68
|
+
Her no longer includes default middleware when making HTTP requests. The user has now to define all the needed middleware. Before:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
Her::API.setup :url => "https://api.example.com" do |connection|
|
72
|
+
connection.insert(0, FaradayMiddle::OAuth)
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
Now:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
Her::API.setup :url => "https://api.example.com" do |connection|
|
80
|
+
connection.use FaradayMiddle::OAuth
|
81
|
+
connection.use Her::Middleware::FirstLevelParseJSON
|
82
|
+
connection.use Faraday::Request::UrlEncoded
|
83
|
+
connection.use Faraday::Adapter::NetHttp
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
## 0.2
|
88
|
+
|
89
|
+
The default parser middleware has been replaced to treat first-level JSON data as the resource or collection data. Before it expected this:
|
90
|
+
|
91
|
+
```json
|
92
|
+
{ "data": { "id": 1, "name": "Foo" }, "errors": [] }
|
93
|
+
```
|
94
|
+
|
95
|
+
Now it expects this (the `errors` key is not treated as resource data):
|
96
|
+
|
97
|
+
```json
|
98
|
+
{ "id": 1, "name": "Foo", "errors": [] }
|
99
|
+
```
|
100
|
+
|
101
|
+
If you still want to get the old behavior, you can use `Her::Middleware::SecondLevelParseJSON` instead of `Her::Middleware::FirstLevelParseJSON` in your middleware stack.
|
data/her5.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "her/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "her5"
|
7
|
+
s.version = Her::VERSION
|
8
|
+
s.authors = ["Rémi Prévost"]
|
9
|
+
s.email = ["remi@exomel.com"]
|
10
|
+
s.homepage = "http://her-rb.org"
|
11
|
+
s.license = "MIT"
|
12
|
+
s.summary = "A simple Representational State Transfer-based Hypertext Transfer Protocol-powered Object Relational Mapper. Her?"
|
13
|
+
s.description = "Her5 is an ORM that maps REST resources and collections to Ruby objects. THIS IS ONLY AN UPDATE TO MAKE HER (UNMAINTAINED) WORK WITH RAILS 5 I LOVE YOU"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_development_dependency "rake", "~> 10.0"
|
21
|
+
s.add_development_dependency "rspec", "~> 2.13"
|
22
|
+
s.add_development_dependency "rspec-its", "~> 1.0"
|
23
|
+
s.add_development_dependency "fivemat", "~> 1.2"
|
24
|
+
s.add_development_dependency "json", "~> 1.8"
|
25
|
+
|
26
|
+
s.add_runtime_dependency "activemodel", ">= 3.0.0", "<= 5.0.0"
|
27
|
+
s.add_runtime_dependency "activesupport", ">= 3.0.0", "<= 5.0.0"
|
28
|
+
s.add_runtime_dependency "faraday", ">= 0.8", "< 1.0"
|
29
|
+
s.add_runtime_dependency "multi_json", "~> 1.7"
|
30
|
+
end
|
data/lib/her.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "her/version"
|
2
|
+
|
3
|
+
require "multi_json"
|
4
|
+
require "faraday"
|
5
|
+
require "active_support"
|
6
|
+
require "active_support/inflector"
|
7
|
+
require "active_support/core_ext/hash"
|
8
|
+
|
9
|
+
require "her/model"
|
10
|
+
require "her/api"
|
11
|
+
require "her/middleware"
|
12
|
+
require "her/errors"
|
13
|
+
require "her/collection"
|
14
|
+
|
15
|
+
module Her
|
16
|
+
module JsonApi
|
17
|
+
autoload :Model, 'her/json_api/model'
|
18
|
+
end
|
19
|
+
end
|
data/lib/her/api.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
module Her
|
2
|
+
# This class is where all HTTP requests are made. Before using Her, you must configure it
|
3
|
+
# so it knows where to make those requests. In Rails, this is usually done in `config/initializers/her.rb`:
|
4
|
+
class API
|
5
|
+
# @private
|
6
|
+
attr_reader :connection, :options
|
7
|
+
|
8
|
+
# Constants
|
9
|
+
FARADAY_OPTIONS = [:request, :proxy, :ssl, :builder, :url, :parallel_manager, :params, :headers, :builder_class].freeze
|
10
|
+
|
11
|
+
# Setup a default API connection. Accepted arguments and options are the same as {API#setup}.
|
12
|
+
def self.setup(opts={}, &block)
|
13
|
+
@default_api = new(opts, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Create a new API object. This is useful to create multiple APIs and use them with the `uses_api` method.
|
17
|
+
# If your application uses only one API, you should use Her::API.setup to configure the default API
|
18
|
+
#
|
19
|
+
# @example Setting up a new API
|
20
|
+
# api = Her::API.new :url => "https://api.example" do |connection|
|
21
|
+
# connection.use Faraday::Request::UrlEncoded
|
22
|
+
# connection.use Her::Middleware::DefaultParseJSON
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# class User
|
26
|
+
# uses_api api
|
27
|
+
# end
|
28
|
+
def initialize(*args, &blk)
|
29
|
+
setup(*args, &blk)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Setup the API connection.
|
33
|
+
#
|
34
|
+
# @param [Hash] opts the Faraday options
|
35
|
+
# @option opts [String] :url The main HTTP API root (eg. `https://api.example.com`)
|
36
|
+
# @option opts [String] :ssl A hash containing [SSL options](https://github.com/lostisland/faraday/wiki/Setting-up-SSL-certificates)
|
37
|
+
#
|
38
|
+
# @return Faraday::Connection
|
39
|
+
#
|
40
|
+
# @example Setting up the default API connection
|
41
|
+
# Her::API.setup :url => "https://api.example"
|
42
|
+
#
|
43
|
+
# @example A custom middleware added to the default list
|
44
|
+
# class MyAuthentication < Faraday::Middleware
|
45
|
+
# def call(env)
|
46
|
+
# env[:request_headers]["X-API-Token"] = "bb2b2dd75413d32c1ac421d39e95b978d1819ff611f68fc2fdd5c8b9c7331192"
|
47
|
+
# @app.call(env)
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
# Her::API.setup :url => "https://api.example.com" do |connection|
|
51
|
+
# connection.use Faraday::Request::UrlEncoded
|
52
|
+
# connection.use Her::Middleware::DefaultParseJSON
|
53
|
+
# connection.use MyAuthentication
|
54
|
+
# connection.use Faraday::Adapter::NetHttp
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# @example A custom parse middleware
|
58
|
+
# class MyCustomParser < Faraday::Response::Middleware
|
59
|
+
# def on_complete(env)
|
60
|
+
# json = JSON.parse(env[:body], :symbolize_names => true)
|
61
|
+
# errors = json.delete(:errors) || {}
|
62
|
+
# metadata = json.delete(:metadata) || []
|
63
|
+
# env[:body] = { :data => json, :errors => errors, :metadata => metadata }
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
# Her::API.setup :url => "https://api.example.com" do |connection|
|
67
|
+
# connection.use Faraday::Request::UrlEncoded
|
68
|
+
# connection.use MyCustomParser
|
69
|
+
# connection.use Faraday::Adapter::NetHttp
|
70
|
+
# end
|
71
|
+
def setup(opts={}, &blk)
|
72
|
+
opts[:url] = opts.delete(:base_uri) if opts.include?(:base_uri) # Support legacy :base_uri option
|
73
|
+
@options = opts
|
74
|
+
@options[:suppress_response_errors] ||= []
|
75
|
+
|
76
|
+
faraday_options = @options.reject { |key, value| !FARADAY_OPTIONS.include?(key.to_sym) }
|
77
|
+
@connection = Faraday.new(faraday_options) do |connection|
|
78
|
+
yield connection if block_given?
|
79
|
+
end
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
# Define a custom parsing procedure. The procedure is passed the response object and is
|
84
|
+
# expected to return a hash with three keys: a main data Hash, an errors Hash
|
85
|
+
# and a metadata Hash.
|
86
|
+
#
|
87
|
+
# @private
|
88
|
+
def request(opts={})
|
89
|
+
method = opts.delete(:_method)
|
90
|
+
path = opts.delete(:_path)
|
91
|
+
headers = opts.delete(:_headers)
|
92
|
+
opts.delete_if { |key, value| key.to_s =~ /^_/ } # Remove all internal parameters
|
93
|
+
|
94
|
+
begin
|
95
|
+
response = @connection.send method do |request|
|
96
|
+
request.headers.merge!(headers) if headers
|
97
|
+
if method == :get
|
98
|
+
# For GET requests, treat additional parameters as querystring data
|
99
|
+
request.url path, opts
|
100
|
+
else
|
101
|
+
# For POST, PUT and DELETE requests, treat additional parameters as request body
|
102
|
+
request.url path
|
103
|
+
request.body = opts
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
{ :parsed_data => response.env[:body], :response => response }
|
108
|
+
|
109
|
+
rescue Her::Errors::ResponseError => e
|
110
|
+
raise e unless @options[:suppress_response_errors].include?(e.status)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
# @private
|
116
|
+
def self.default_api(opts={})
|
117
|
+
defined?(@default_api) ? @default_api : nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/lib/her/errors.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
module Her
|
2
|
+
module Errors
|
3
|
+
|
4
|
+
# Base class so that all Her errors can be handled generically.
|
5
|
+
class Error < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class PathError < Error
|
9
|
+
attr_reader :missing_parameter
|
10
|
+
|
11
|
+
def initialize(message, missing_parameter=nil)
|
12
|
+
super(message)
|
13
|
+
@missing_parameter = missing_parameter
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class AssociationUnknownError < Error
|
18
|
+
end
|
19
|
+
|
20
|
+
class ParseError < Error
|
21
|
+
end
|
22
|
+
|
23
|
+
class ResourceInvalid < Error
|
24
|
+
attr_reader :resource
|
25
|
+
def initialize(resource)
|
26
|
+
@resource = resource
|
27
|
+
errors = @resource.response_errors.join(", ")
|
28
|
+
super("Remote validation failed: #{errors}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Base class so that response errors can be handled generically.
|
33
|
+
class ResponseError < Error
|
34
|
+
def status
|
35
|
+
0
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Status code 401: authentication required
|
40
|
+
class Unauthorized < ResponseError
|
41
|
+
def status
|
42
|
+
401
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Status code 403: authenticated but not authorized
|
47
|
+
class Forbidden < ResponseError
|
48
|
+
def status
|
49
|
+
403
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Status code 404: resource not found
|
54
|
+
class NotFound < ResponseError
|
55
|
+
def status
|
56
|
+
404
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Status code 500: we blew up
|
61
|
+
class ServerError < ResponseError
|
62
|
+
def status
|
63
|
+
500
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Status code 502: proxy says API has gone away
|
68
|
+
class BadGateway < ResponseError
|
69
|
+
def status
|
70
|
+
502
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Status code 503: API has definitely gone away
|
75
|
+
class Unavailable < ResponseError
|
76
|
+
def status
|
77
|
+
503
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Status code 504: API has probably gone away
|
82
|
+
class TimeOut < ResponseError
|
83
|
+
def status
|
84
|
+
504
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.exception_class_for_status(status)
|
89
|
+
errors = {
|
90
|
+
401 => "Unauthorized",
|
91
|
+
403 => "Forbidden",
|
92
|
+
404 => "NotFound",
|
93
|
+
500 => "ServerError",
|
94
|
+
502 => "BadGateway",
|
95
|
+
503 => "Unavailable",
|
96
|
+
504 => "TimeOut"
|
97
|
+
}
|
98
|
+
if errors[status]
|
99
|
+
"Her::Errors::#{errors[status]}".constantize
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|