active_rest_client 0.9.58

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.simplecov +4 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +9 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +585 -0
  9. data/Rakefile +3 -0
  10. data/active_rest_client.gemspec +34 -0
  11. data/lib/active_rest_client.rb +23 -0
  12. data/lib/active_rest_client/base.rb +128 -0
  13. data/lib/active_rest_client/caching.rb +84 -0
  14. data/lib/active_rest_client/configuration.rb +69 -0
  15. data/lib/active_rest_client/connection.rb +76 -0
  16. data/lib/active_rest_client/connection_manager.rb +21 -0
  17. data/lib/active_rest_client/headers_list.rb +47 -0
  18. data/lib/active_rest_client/instrumentation.rb +62 -0
  19. data/lib/active_rest_client/lazy_association_loader.rb +95 -0
  20. data/lib/active_rest_client/lazy_loader.rb +23 -0
  21. data/lib/active_rest_client/logger.rb +67 -0
  22. data/lib/active_rest_client/mapping.rb +65 -0
  23. data/lib/active_rest_client/proxy_base.rb +143 -0
  24. data/lib/active_rest_client/recording.rb +24 -0
  25. data/lib/active_rest_client/request.rb +412 -0
  26. data/lib/active_rest_client/request_filtering.rb +52 -0
  27. data/lib/active_rest_client/result_iterator.rb +66 -0
  28. data/lib/active_rest_client/validation.rb +60 -0
  29. data/lib/active_rest_client/version.rb +3 -0
  30. data/spec/lib/base_spec.rb +245 -0
  31. data/spec/lib/caching_spec.rb +179 -0
  32. data/spec/lib/configuration_spec.rb +105 -0
  33. data/spec/lib/connection_manager_spec.rb +36 -0
  34. data/spec/lib/connection_spec.rb +73 -0
  35. data/spec/lib/headers_list_spec.rb +61 -0
  36. data/spec/lib/instrumentation_spec.rb +59 -0
  37. data/spec/lib/lazy_association_loader_spec.rb +118 -0
  38. data/spec/lib/lazy_loader_spec.rb +25 -0
  39. data/spec/lib/logger_spec.rb +63 -0
  40. data/spec/lib/mapping_spec.rb +48 -0
  41. data/spec/lib/proxy_spec.rb +154 -0
  42. data/spec/lib/recording_spec.rb +34 -0
  43. data/spec/lib/request_filtering_spec.rb +72 -0
  44. data/spec/lib/request_spec.rb +471 -0
  45. data/spec/lib/result_iterator_spec.rb +104 -0
  46. data/spec/lib/validation_spec.rb +113 -0
  47. data/spec/spec_helper.rb +22 -0
  48. metadata +265 -0
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+ RSpec::Core::RakeTask.new('spec')
@@ -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