active_rest_client 0.9.58
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.simplecov +4 -0
- data/Gemfile +4 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +585 -0
- data/Rakefile +3 -0
- data/active_rest_client.gemspec +34 -0
- data/lib/active_rest_client.rb +23 -0
- data/lib/active_rest_client/base.rb +128 -0
- data/lib/active_rest_client/caching.rb +84 -0
- data/lib/active_rest_client/configuration.rb +69 -0
- data/lib/active_rest_client/connection.rb +76 -0
- data/lib/active_rest_client/connection_manager.rb +21 -0
- data/lib/active_rest_client/headers_list.rb +47 -0
- data/lib/active_rest_client/instrumentation.rb +62 -0
- data/lib/active_rest_client/lazy_association_loader.rb +95 -0
- data/lib/active_rest_client/lazy_loader.rb +23 -0
- data/lib/active_rest_client/logger.rb +67 -0
- data/lib/active_rest_client/mapping.rb +65 -0
- data/lib/active_rest_client/proxy_base.rb +143 -0
- data/lib/active_rest_client/recording.rb +24 -0
- data/lib/active_rest_client/request.rb +412 -0
- data/lib/active_rest_client/request_filtering.rb +52 -0
- data/lib/active_rest_client/result_iterator.rb +66 -0
- data/lib/active_rest_client/validation.rb +60 -0
- data/lib/active_rest_client/version.rb +3 -0
- data/spec/lib/base_spec.rb +245 -0
- data/spec/lib/caching_spec.rb +179 -0
- data/spec/lib/configuration_spec.rb +105 -0
- data/spec/lib/connection_manager_spec.rb +36 -0
- data/spec/lib/connection_spec.rb +73 -0
- data/spec/lib/headers_list_spec.rb +61 -0
- data/spec/lib/instrumentation_spec.rb +59 -0
- data/spec/lib/lazy_association_loader_spec.rb +118 -0
- data/spec/lib/lazy_loader_spec.rb +25 -0
- data/spec/lib/logger_spec.rb +63 -0
- data/spec/lib/mapping_spec.rb +48 -0
- data/spec/lib/proxy_spec.rb +154 -0
- data/spec/lib/recording_spec.rb +34 -0
- data/spec/lib/request_filtering_spec.rb +72 -0
- data/spec/lib/request_spec.rb +471 -0
- data/spec/lib/result_iterator_spec.rb +104 -0
- data/spec/lib/validation_spec.rb +113 -0
- data/spec/spec_helper.rb +22 -0
- metadata +265 -0
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'active_rest_client/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "active_rest_client"
|
8
|
+
spec.version = ActiveRestClient::VERSION
|
9
|
+
spec.platform = Gem::Platform::RUBY
|
10
|
+
spec.authors = ["Andy Jeffries"]
|
11
|
+
spec.email = ["andy.jeffries@which.co.uk"]
|
12
|
+
spec.description = %q{Accessing REST services in an ActiveRecord style}
|
13
|
+
spec.summary = %q{This gem is for accessing REST services in an ActiveRecord style. ActiveResource already exists for this, but it doesn't work where the resource naming doesn't follow Rails conventions, it doesn't have in-built caching and it's not as flexible in general.}
|
14
|
+
spec.homepage = "http://www.which.co.uk/"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split($/)
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "rspec_junit_formatter"
|
26
|
+
spec.add_development_dependency "simplecov"
|
27
|
+
spec.add_development_dependency "simplecov-rcov"
|
28
|
+
spec.add_development_dependency "guard-rspec"
|
29
|
+
spec.add_development_dependency 'terminal-notifier-guard'
|
30
|
+
|
31
|
+
spec.add_runtime_dependency "oj", "=2.1.4" # 2.1.7 breaks under linux
|
32
|
+
spec.add_runtime_dependency "activesupport"
|
33
|
+
spec.add_runtime_dependency "patron", '=0.4.9' # 0.4.18 breaks against Curl v0.7.15
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require "active_rest_client/version"
|
3
|
+
require "active_rest_client/mapping"
|
4
|
+
require "active_rest_client/caching"
|
5
|
+
require "active_rest_client/logger"
|
6
|
+
require "active_rest_client/configuration"
|
7
|
+
require "active_rest_client/connection"
|
8
|
+
require "active_rest_client/connection_manager"
|
9
|
+
require "active_rest_client/instrumentation"
|
10
|
+
require "active_rest_client/result_iterator"
|
11
|
+
require "active_rest_client/headers_list"
|
12
|
+
require "active_rest_client/lazy_loader"
|
13
|
+
require "active_rest_client/lazy_association_loader"
|
14
|
+
require "active_rest_client/request"
|
15
|
+
require "active_rest_client/validation"
|
16
|
+
require "active_rest_client/request_filtering"
|
17
|
+
require "active_rest_client/proxy_base"
|
18
|
+
require "active_rest_client/recording"
|
19
|
+
require "active_rest_client/base"
|
20
|
+
|
21
|
+
module ActiveRestClient
|
22
|
+
NAME = "ActiveRestClient"
|
23
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module ActiveRestClient
|
2
|
+
class Base
|
3
|
+
include Mapping
|
4
|
+
include Configuration
|
5
|
+
include RequestFiltering
|
6
|
+
include Validation
|
7
|
+
include Caching
|
8
|
+
include Recording
|
9
|
+
|
10
|
+
attr_accessor :_status
|
11
|
+
|
12
|
+
instance_methods.each do |m|
|
13
|
+
next unless %i{display errors presence load require hash untrust trust freeze method enable_warnings with_warnings suppress capture silence quietly debugger breakpoint}.include? m
|
14
|
+
undef_method m
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(attrs={})
|
18
|
+
@attributes = {}
|
19
|
+
@dirty_attributes = Set.new
|
20
|
+
|
21
|
+
raise Exception.new("Cannot instantiate Base class") if self.class.name == "ActiveRestClient::Base"
|
22
|
+
|
23
|
+
attrs.each do |attribute_name, attribute_value|
|
24
|
+
attribute_name = attribute_name.to_sym
|
25
|
+
if attribute_value.to_s[/\d{4}\-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2})/]
|
26
|
+
@attributes[attribute_name] = DateTime.parse(attribute_value)
|
27
|
+
else
|
28
|
+
@attributes[attribute_name] = attribute_value
|
29
|
+
end
|
30
|
+
@dirty_attributes << attribute_name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def _clean!
|
35
|
+
@dirty_attributes = Set.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def _attributes
|
39
|
+
@attributes
|
40
|
+
end
|
41
|
+
|
42
|
+
def _copy_from(result)
|
43
|
+
@attributes = result._attributes
|
44
|
+
@_status = result._status
|
45
|
+
end
|
46
|
+
|
47
|
+
def dirty?
|
48
|
+
@dirty_attributes.size > 0
|
49
|
+
end
|
50
|
+
|
51
|
+
def self._request(request, method = :get, params = nil)
|
52
|
+
prepare_direct_request(request, method).call(params)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self._lazy_request(request, method = :get, params = nil)
|
56
|
+
ActiveRestClient::LazyLoader.new(prepare_direct_request(request, method), params)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.prepare_direct_request(request, method)
|
60
|
+
unless request.is_a? ActiveRestClient::Request
|
61
|
+
mapped = {url:"DIRECT-CALLED-URL", method:method, options:{url:request}}
|
62
|
+
request = Request.new(mapped, self)
|
63
|
+
end
|
64
|
+
request
|
65
|
+
end
|
66
|
+
|
67
|
+
def self._request_for(method_name, *args)
|
68
|
+
if mapped = self._mapped_method(method_name)
|
69
|
+
params = (args.first.is_a?(Hash) ? args.first : nil)
|
70
|
+
request = Request.new(mapped, self, params)
|
71
|
+
request
|
72
|
+
else
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def [](key)
|
78
|
+
@attributes[key.to_sym]
|
79
|
+
end
|
80
|
+
|
81
|
+
def []=(key, value)
|
82
|
+
@attributes[key.to_sym] = value
|
83
|
+
@dirty_attributes << key
|
84
|
+
end
|
85
|
+
|
86
|
+
def each
|
87
|
+
@attributes.each do |key, value|
|
88
|
+
yield key, value
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def method_missing(name, *args)
|
93
|
+
if name.to_s[-1,1] == "="
|
94
|
+
name = name.to_s.chop.to_sym
|
95
|
+
@attributes[name] = args.first
|
96
|
+
@dirty_attributes << name
|
97
|
+
else
|
98
|
+
name_sym = name.to_sym
|
99
|
+
name = name.to_s
|
100
|
+
|
101
|
+
if @attributes.has_key? name_sym
|
102
|
+
@attributes[name_sym]
|
103
|
+
else
|
104
|
+
if name[/^lazy_/] && mapped = self.class._mapped_method(name_sym)
|
105
|
+
raise ValidationFailedException.new unless valid?
|
106
|
+
request = Request.new(mapped, self, args.first)
|
107
|
+
ActiveRestClient::LazyLoader.new(request)
|
108
|
+
elsif mapped = self.class._mapped_method(name_sym)
|
109
|
+
raise ValidationFailedException.new unless valid?
|
110
|
+
request = Request.new(mapped, self, args.first)
|
111
|
+
request.call
|
112
|
+
elsif self.class.whiny_missing
|
113
|
+
raise NoAttributeException.new("Missing attribute #{name_sym}")
|
114
|
+
else
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def respond_to_missing?(method_name, include_private = false)
|
122
|
+
@attributes.has_key? method_name.to_sym
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class NoAttributeException < StandardError ; end
|
127
|
+
class ValidationFailedException < StandardError ; end
|
128
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module ActiveRestClient
|
2
|
+
module Caching
|
3
|
+
module ClassMethods
|
4
|
+
@@perform_caching = true
|
5
|
+
|
6
|
+
def perform_caching(value = nil)
|
7
|
+
if value.nil?
|
8
|
+
if @perform_caching.nil?
|
9
|
+
@@perform_caching
|
10
|
+
else
|
11
|
+
@perform_caching
|
12
|
+
end
|
13
|
+
else
|
14
|
+
@perform_caching = value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def perform_caching=(value)
|
19
|
+
@@perform_caching = value
|
20
|
+
@perform_caching = value
|
21
|
+
end
|
22
|
+
|
23
|
+
def cache_store=(value)
|
24
|
+
raise InvalidCacheStoreException.new("Cache store does not implement #read") unless value.respond_to?(:read)
|
25
|
+
raise InvalidCacheStoreException.new("Cache store does not implement #write") unless value.respond_to?(:write)
|
26
|
+
raise InvalidCacheStoreException.new("Cache store does not implement #fetch") unless value.respond_to?(:fetch)
|
27
|
+
@@cache_store = value
|
28
|
+
end
|
29
|
+
|
30
|
+
def cache_store
|
31
|
+
rails_cache_store = if Object.const_defined?(:Rails)
|
32
|
+
::Rails.cache
|
33
|
+
else
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
(@@cache_store rescue nil) || rails_cache_store
|
37
|
+
end
|
38
|
+
|
39
|
+
def _reset_caching!
|
40
|
+
@@perform_caching = false
|
41
|
+
@perform_caching = false
|
42
|
+
@@cache_store = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def read_cached_response(request)
|
46
|
+
if cache_store
|
47
|
+
key = "#{request.class_name}:#{request.original_url}"
|
48
|
+
cache_store.read(key)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def write_cached_response(request, response, result)
|
53
|
+
response.headers.keys.select{|h| h.is_a? String}.each do |key|
|
54
|
+
response.headers[key.downcase.to_sym] = response.headers[key]
|
55
|
+
end
|
56
|
+
|
57
|
+
if cache_store && (response.headers[:etag] || response.headers[:expires])
|
58
|
+
key = "#{request.class_name}:#{request.original_url}"
|
59
|
+
ActiveRestClient::Logger.debug " \033[1;4;32m#{ActiveRestClient::NAME}\033[0m #{key} - Writing to cache"
|
60
|
+
cached_response = CachedResponse.new(status:response.status, result:result)
|
61
|
+
cached_response.etag = response.headers[:etag] if response.headers[:etag]
|
62
|
+
cached_response.expires = Time.parse(response.headers[:expires]) if response.headers[:expires]
|
63
|
+
cache_store.write(key, cached_response, {})
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.included(base)
|
69
|
+
base.extend(ClassMethods)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
class CachedResponse
|
75
|
+
attr_accessor :status, :result, :etag, :expires
|
76
|
+
|
77
|
+
def initialize(options)
|
78
|
+
@status = options[:status]
|
79
|
+
@result = options[:result]
|
80
|
+
@etag = options[:etag]
|
81
|
+
@expires = options[:expires]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ActiveRestClient
|
2
|
+
module Configuration
|
3
|
+
module ClassMethods
|
4
|
+
@@base_url = nil
|
5
|
+
@lazy_load = false
|
6
|
+
|
7
|
+
def base_url(value = nil)
|
8
|
+
if value.nil?
|
9
|
+
if @base_url.nil?
|
10
|
+
@@base_url
|
11
|
+
else
|
12
|
+
@base_url
|
13
|
+
end
|
14
|
+
else
|
15
|
+
value = value.gsub(/\/$/, '')
|
16
|
+
@base_url = value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def base_url=(value)
|
21
|
+
ActiveRestClient::Logger.info "\033[1;4;32m#{name}\033[0m Base URL set to be #{value}"
|
22
|
+
value = value.gsub(/\/+$/, '')
|
23
|
+
@@base_url = value
|
24
|
+
end
|
25
|
+
|
26
|
+
def lazy_load!
|
27
|
+
@lazy_load = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def lazy_load?
|
31
|
+
@lazy_load || false
|
32
|
+
end
|
33
|
+
|
34
|
+
def whiny_missing(value = nil)
|
35
|
+
value ? @whiny_missing = value : @whiny_missing || false
|
36
|
+
end
|
37
|
+
|
38
|
+
def verbose!
|
39
|
+
@verbose = true
|
40
|
+
end
|
41
|
+
|
42
|
+
def verbose(value = nil)
|
43
|
+
value ? @verbose = value : @verbose || false
|
44
|
+
end
|
45
|
+
|
46
|
+
def translator(value = nil)
|
47
|
+
ActiveRestClient::Logger.warn("DEPRECATION: The translator functionality of ActiveRestClient has been replaced with proxy functionality, see https://github.com/whichdigital/active-rest-client#proxying-apis for more information") unless value.nil?
|
48
|
+
value ? @translator = value : @translator || nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def proxy(value = nil)
|
52
|
+
value ? @proxy = value : @proxy || nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def _reset_configuration!
|
56
|
+
@base_url = nil
|
57
|
+
@@base_url = nil
|
58
|
+
@whiny_missing = nil
|
59
|
+
@lazy_load = false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.included(base)
|
64
|
+
base.extend(ClassMethods)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class InvalidCacheStoreException < StandardError ; end
|
69
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'patron'
|
2
|
+
|
3
|
+
module ActiveRestClient
|
4
|
+
|
5
|
+
class TimeoutException < StandardError ; end
|
6
|
+
class ConnectionFailedException < StandardError ; end
|
7
|
+
|
8
|
+
class Connection
|
9
|
+
attr_accessor :session, :base_url
|
10
|
+
|
11
|
+
def initialize(base_url)
|
12
|
+
@base_url = base_url
|
13
|
+
@session = Patron::Session.new
|
14
|
+
@session.timeout = 10
|
15
|
+
@session.connect_timeout = 10
|
16
|
+
@session.base_url = base_url
|
17
|
+
@session.insecure = true
|
18
|
+
@session.headers['User-Agent'] = "ActiveRestClient/#{ActiveRestClient::VERSION}"
|
19
|
+
@session.headers['Connection'] = "Keep-Alive"
|
20
|
+
@session.headers['Accept'] = "application/json"
|
21
|
+
end
|
22
|
+
|
23
|
+
def reconnect
|
24
|
+
session = Patron::Session.new
|
25
|
+
session.timeout = @session.timeout
|
26
|
+
session.base_url = @session.base_url
|
27
|
+
session.insecure = true
|
28
|
+
@session.headers.each do |k,v|
|
29
|
+
session.headers[k] = v
|
30
|
+
end
|
31
|
+
@session = session
|
32
|
+
end
|
33
|
+
|
34
|
+
def headers
|
35
|
+
@session.headers
|
36
|
+
end
|
37
|
+
|
38
|
+
def make_safe_request(path, &block)
|
39
|
+
block.call
|
40
|
+
rescue Patron::TimeoutError
|
41
|
+
raise ActiveRestClient::TimeoutException.new("Timed out getting #{@base_url}#{path}")
|
42
|
+
rescue Patron::ConnectionFailed
|
43
|
+
begin
|
44
|
+
reconnect
|
45
|
+
block.call
|
46
|
+
rescue Patron::ConnectionFailed
|
47
|
+
raise ActiveRestClient::ConnectionFailedException.new("Unable to connect to #{@base_url}#{path}")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def get(path, headers={})
|
52
|
+
make_safe_request(path) do
|
53
|
+
@session.get(path, headers)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def put(path, data, headers={})
|
58
|
+
make_safe_request(path) do
|
59
|
+
@session.put(path, data, headers)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def post(path, data, headers={})
|
64
|
+
make_safe_request(path) do
|
65
|
+
@session.post(path, data, headers)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def delete(path, headers={})
|
70
|
+
make_safe_request(path) do
|
71
|
+
@session.delete(path, headers)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActiveRestClient
|
2
|
+
class ConnectionManager
|
3
|
+
def self.reset!
|
4
|
+
@_connections = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.get_connection(base_url)
|
8
|
+
raise Exception.new("Nil base URL passed to ConnectionManager.get_connection") if base_url.nil?
|
9
|
+
@_connections ||= {}
|
10
|
+
@_connections[base_url] ||= Connection.new(base_url)
|
11
|
+
@_connections[base_url]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.find_connection_for_url(url)
|
15
|
+
@_connections ||= {}
|
16
|
+
found = @_connections.keys.detect {|key| url[0,key.length] == key}
|
17
|
+
@_connections[found] if found
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|