flexirest 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +2 -0
  4. data/.simplecov +4 -0
  5. data/.travis.yml +6 -0
  6. data/CHANGELOG.md +37 -0
  7. data/CONTRIBUTING.md +62 -0
  8. data/Gemfile +4 -0
  9. data/Guardfile +9 -0
  10. data/LICENSE.txt +22 -0
  11. data/README.md +846 -0
  12. data/Rakefile +13 -0
  13. data/doc/ActiveRestClient Internals.graffle +1236 -0
  14. data/doc/ActiveRestClient Internals.png +0 -0
  15. data/flexirest.gemspec +39 -0
  16. data/lib/flexirest.rb +25 -0
  17. data/lib/flexirest/base.rb +189 -0
  18. data/lib/flexirest/caching.rb +92 -0
  19. data/lib/flexirest/configuration.rb +209 -0
  20. data/lib/flexirest/connection.rb +103 -0
  21. data/lib/flexirest/connection_manager.rb +36 -0
  22. data/lib/flexirest/headers_list.rb +47 -0
  23. data/lib/flexirest/instrumentation.rb +62 -0
  24. data/lib/flexirest/lazy_association_loader.rb +97 -0
  25. data/lib/flexirest/lazy_loader.rb +23 -0
  26. data/lib/flexirest/logger.rb +67 -0
  27. data/lib/flexirest/mapping.rb +69 -0
  28. data/lib/flexirest/monkey_patching.rb +7 -0
  29. data/lib/flexirest/proxy_base.rb +193 -0
  30. data/lib/flexirest/recording.rb +24 -0
  31. data/lib/flexirest/request.rb +573 -0
  32. data/lib/flexirest/request_delegator.rb +44 -0
  33. data/lib/flexirest/request_filtering.rb +62 -0
  34. data/lib/flexirest/result_iterator.rb +85 -0
  35. data/lib/flexirest/validation.rb +60 -0
  36. data/lib/flexirest/version.rb +3 -0
  37. data/spec/lib/base_spec.rb +389 -0
  38. data/spec/lib/caching_spec.rb +217 -0
  39. data/spec/lib/configuration_spec.rb +234 -0
  40. data/spec/lib/connection_manager_spec.rb +43 -0
  41. data/spec/lib/connection_spec.rb +159 -0
  42. data/spec/lib/headers_list_spec.rb +61 -0
  43. data/spec/lib/instrumentation_spec.rb +58 -0
  44. data/spec/lib/lazy_association_loader_spec.rb +135 -0
  45. data/spec/lib/lazy_loader_spec.rb +25 -0
  46. data/spec/lib/logger_spec.rb +63 -0
  47. data/spec/lib/mapping_spec.rb +52 -0
  48. data/spec/lib/proxy_spec.rb +189 -0
  49. data/spec/lib/recording_spec.rb +34 -0
  50. data/spec/lib/request_filtering_spec.rb +84 -0
  51. data/spec/lib/request_spec.rb +711 -0
  52. data/spec/lib/result_iterator_spec.rb +140 -0
  53. data/spec/lib/validation_spec.rb +113 -0
  54. data/spec/lib/xml_spec.rb +74 -0
  55. data/spec/spec_helper.rb +88 -0
  56. metadata +347 -0
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'flexirest/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "flexirest"
8
+ spec.version = Flexirest::VERSION
9
+ spec.platform = Gem::Platform::RUBY
10
+ spec.authors = ["Which Ltd", "Andy Jeffries"]
11
+ spec.email = ["swlicensing@which.co.uk", "andy@andyjeffries.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://whichdigital.github.io/"
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", "~> 3"
25
+ spec.add_development_dependency "webmock"
26
+ spec.add_development_dependency "rspec_junit_formatter"
27
+ spec.add_development_dependency "simplecov"
28
+ spec.add_development_dependency "simplecov-rcov"
29
+ spec.add_development_dependency "guard-rspec"
30
+ spec.add_development_dependency 'terminal-notifier-guard'
31
+ spec.add_development_dependency 'coveralls'
32
+ spec.add_development_dependency "api-auth", ">= 1.3.1"
33
+ spec.add_development_dependency 'typhoeus'
34
+
35
+ spec.add_runtime_dependency "multi_json"
36
+ spec.add_runtime_dependency "crack"
37
+ spec.add_runtime_dependency "activesupport"
38
+ spec.add_runtime_dependency "faraday"
39
+ end
@@ -0,0 +1,25 @@
1
+ require 'active_support/all'
2
+ require "flexirest/version"
3
+ require "flexirest/mapping"
4
+ require "flexirest/caching"
5
+ require "flexirest/logger"
6
+ require "flexirest/configuration"
7
+ require "flexirest/connection"
8
+ require "flexirest/connection_manager"
9
+ require "flexirest/instrumentation"
10
+ require "flexirest/result_iterator"
11
+ require "flexirest/headers_list"
12
+ require "flexirest/lazy_loader"
13
+ require "flexirest/lazy_association_loader"
14
+ require "flexirest/request"
15
+ require "flexirest/request_delegator"
16
+ require "flexirest/validation"
17
+ require "flexirest/request_filtering"
18
+ require "flexirest/proxy_base"
19
+ require "flexirest/recording"
20
+ require "flexirest/base"
21
+ require "flexirest/monkey_patching"
22
+
23
+ module Flexirest
24
+ NAME = "Flexirest"
25
+ end
@@ -0,0 +1,189 @@
1
+ module Flexirest
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
+ attr_accessor :_etag
12
+ attr_accessor :_headers
13
+
14
+ instance_methods.each do |m|
15
+ next unless %w{display presence load require hash untrust trust freeze method enable_warnings with_warnings suppress capture silence quietly debugger breakpoint}.map(&:to_sym).include? m
16
+ undef_method m
17
+ end
18
+
19
+ def initialize(attrs={})
20
+ @attributes = {}
21
+ @dirty_attributes = Set.new
22
+
23
+ raise Exception.new("Cannot instantiate Base class") if self.class.name == "Flexirest::Base"
24
+
25
+ attrs.each do |attribute_name, attribute_value|
26
+ attribute_name = attribute_name.to_sym
27
+ if attribute_value.to_s[/^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6]))))$/]
28
+ @attributes[attribute_name] = Date.parse(attribute_value)
29
+ elsif attribute_value.to_s[/^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/]
30
+ @attributes[attribute_name] = DateTime.parse(attribute_value)
31
+ else
32
+ @attributes[attribute_name] = attribute_value
33
+ end
34
+ @dirty_attributes << attribute_name
35
+ end
36
+ end
37
+
38
+ def _clean!
39
+ @dirty_attributes = Set.new
40
+ end
41
+
42
+ def _attributes
43
+ @attributes
44
+ end
45
+
46
+ def _copy_from(result)
47
+ @attributes = result._attributes
48
+ @_status = result._status
49
+ end
50
+
51
+ def dirty?
52
+ @dirty_attributes.size > 0
53
+ end
54
+
55
+ def errors
56
+ @attributes[:errors] || (_errors != {} ? _errors : nil)
57
+ end
58
+
59
+ def self._request(request, method = :get, params = nil)
60
+ prepare_direct_request(request, method).call(params)
61
+ end
62
+
63
+ def self._plain_request(request, method = :get, params = nil)
64
+ prepare_direct_request(request, method, plain:true).call(params)
65
+ end
66
+
67
+ def self._lazy_request(request, method = :get, params = nil)
68
+ Flexirest::LazyLoader.new(prepare_direct_request(request, method), params)
69
+ end
70
+
71
+ def self.prepare_direct_request(request, method, options={})
72
+ unless request.is_a? Flexirest::Request
73
+ options[:plain] ||= false
74
+ mapped = {url:"DIRECT-CALLED-#{request}", method:method, options:{url:request, plain:options[:plain]}}
75
+
76
+ request = Request.new(mapped, self)
77
+ end
78
+ request
79
+ end
80
+
81
+ def self._request_for(method_name, *args)
82
+ if mapped = self._mapped_method(method_name)
83
+ params = (args.first.is_a?(Hash) ? args.first : nil)
84
+ request = Request.new(mapped, self, params)
85
+ request
86
+ else
87
+ nil
88
+ end
89
+ end
90
+
91
+ def [](key)
92
+ @attributes[key.to_sym]
93
+ end
94
+
95
+ def []=(key, value)
96
+ @attributes[key.to_sym] = value
97
+ @dirty_attributes << key
98
+ end
99
+
100
+ def each
101
+ @attributes.each do |key, value|
102
+ yield key, value
103
+ end
104
+ end
105
+
106
+ def inspect
107
+ inspection = if @attributes.any?
108
+ @attributes.collect { |key, value|
109
+ "#{key}: #{value_for_inspect(value)}"
110
+ }.compact.join(", ")
111
+ else
112
+ "[uninitialized]"
113
+ end
114
+ inspection += "#{"," if @attributes.any?} ETag: #{@_etag}" unless @_etag.nil?
115
+ inspection += "#{"," if @attributes.any?} Status: #{@_status}" unless @_status.nil?
116
+ inspection += " (unsaved: #{@dirty_attributes.map(&:to_s).join(", ")})" if @dirty_attributes.any?
117
+ "#<#{self.class} #{inspection}>"
118
+ end
119
+
120
+ def method_missing(name, *args)
121
+ if name.to_s[-1,1] == "="
122
+ name = name.to_s.chop.to_sym
123
+ @attributes[name] = args.first
124
+ @dirty_attributes << name
125
+ else
126
+ name_sym = name.to_sym
127
+ name = name.to_s
128
+
129
+ if @attributes.has_key? name_sym
130
+ @attributes[name_sym]
131
+ else
132
+ if name[/^lazy_/] && mapped = self.class._mapped_method(name_sym)
133
+ raise ValidationFailedException.new unless valid?
134
+ request = Request.new(mapped, self, args.first)
135
+ Flexirest::LazyLoader.new(request)
136
+ elsif mapped = self.class._mapped_method(name_sym)
137
+ raise ValidationFailedException.new unless valid?
138
+ request = Request.new(mapped, self, args.first)
139
+ request.call
140
+ elsif self.class.whiny_missing
141
+ raise NoAttributeException.new("Missing attribute #{name_sym}")
142
+ else
143
+ nil
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ def respond_to_missing?(method_name, include_private = false)
150
+ @attributes.has_key? method_name.to_sym
151
+ end
152
+
153
+ def to_hash
154
+ output = {}
155
+ @attributes.each do |key, value|
156
+ if value.is_a? Flexirest::Base
157
+ output[key.to_s] = value.to_hash
158
+ elsif value.is_a? Array
159
+ output[key.to_s] = value.map(&:to_hash)
160
+ else
161
+ output[key.to_s] = value
162
+ end
163
+ end
164
+ output
165
+ end
166
+
167
+ def to_json
168
+ output = to_hash
169
+ output.to_json
170
+ end
171
+
172
+ private
173
+
174
+ def value_for_inspect(value)
175
+ if value.is_a?(String) && value.length > 50
176
+ "#{value[0..50]}...".inspect
177
+ elsif value.is_a?(Date) || value.is_a?(Time)
178
+ %("#{value.to_s(:db)}")
179
+ else
180
+ value.inspect
181
+ end
182
+ end
183
+
184
+ end
185
+
186
+ class NoAttributeException < StandardError ; end
187
+ class ValidationFailedException < StandardError ; end
188
+ class MissingOptionalLibraryError < StandardError ; end
189
+ end
@@ -0,0 +1,92 @@
1
+ module Flexirest
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
+ @@cache_store = nil if value.nil? and return
25
+ raise InvalidCacheStoreException.new("Cache store does not implement #read") unless value.respond_to?(:read)
26
+ raise InvalidCacheStoreException.new("Cache store does not implement #write") unless value.respond_to?(:write)
27
+ raise InvalidCacheStoreException.new("Cache store does not implement #fetch") unless value.respond_to?(:fetch)
28
+ @@cache_store = value
29
+ end
30
+
31
+ def cache_store
32
+ rails_cache_store = if Object.const_defined?(:Rails)
33
+ ::Rails.cache
34
+ else
35
+ nil
36
+ end
37
+ (@@cache_store rescue nil) || rails_cache_store
38
+ end
39
+
40
+ def _reset_caching!
41
+ @@perform_caching = false
42
+ @perform_caching = false
43
+ @@cache_store = nil
44
+ end
45
+
46
+ def read_cached_response(request)
47
+ if cache_store && perform_caching
48
+ key = "#{request.class_name}:#{request.original_url}"
49
+ Flexirest::Logger.debug " \033[1;4;32m#{Flexirest::NAME}\033[0m #{key} - Trying to read from cache"
50
+ value = cache_store.read(key)
51
+ value = Marshal.load(value) rescue value
52
+ end
53
+ end
54
+
55
+ def write_cached_response(request, response, result)
56
+ return if result.is_a? Symbol
57
+ return unless perform_caching
58
+ return unless !result.respond_to?(:_status) || [200, 304].include?(result._status)
59
+ headers = response.response_headers
60
+
61
+ headers.keys.select{|h| h.is_a? String}.each do |key|
62
+ headers[key.downcase.to_sym] = headers[key]
63
+ end
64
+
65
+ if cache_store && (headers[:etag] || headers[:expires])
66
+ key = "#{request.class_name}:#{request.original_url}"
67
+ Flexirest::Logger.debug " \033[1;4;32m#{Flexirest::NAME}\033[0m #{key} - Writing to cache"
68
+ cached_response = CachedResponse.new(status:response.status, result:result)
69
+ cached_response.etag = headers[:etag] if headers[:etag]
70
+ cached_response.expires = Time.parse(headers[:expires]) rescue nil if headers[:expires]
71
+ cache_store.write(key, Marshal.dump(cached_response), {}) if cached_response.etag.present? || cached_response.expires
72
+ end
73
+ end
74
+ end
75
+
76
+ def self.included(base)
77
+ base.extend(ClassMethods)
78
+ end
79
+
80
+ end
81
+
82
+ class CachedResponse
83
+ attr_accessor :status, :result, :etag, :expires
84
+
85
+ def initialize(options)
86
+ @status = options[:status]
87
+ @result = options[:result]
88
+ @etag = options[:etag]
89
+ @expires = options[:expires]
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,209 @@
1
+ require 'cgi'
2
+
3
+ module Flexirest
4
+ module Configuration
5
+ module ClassMethods
6
+ @@base_url = nil
7
+ @@username = nil
8
+ @@password = nil
9
+ @@request_body_type = :form_encoded
10
+ @lazy_load = false
11
+ @api_auth_access_id = nil
12
+ @api_auth_secret_key = nil
13
+
14
+ def base_url(value = nil)
15
+ if value.nil?
16
+ if @base_url.nil?
17
+ @@base_url
18
+ else
19
+ @base_url
20
+ end
21
+ else
22
+ value = value.gsub(/\/$/, '')
23
+ @base_url = value
24
+ end
25
+ end
26
+
27
+ def base_url=(value)
28
+ Flexirest::Logger.info "\033[1;4;32m#{name}\033[0m Base URL set to be #{value}"
29
+ value = value.gsub(/\/+$/, '')
30
+ @@base_url = value
31
+ end
32
+
33
+ def username(value = nil)
34
+ if value.nil?
35
+ if @username.nil?
36
+ @@username
37
+ else
38
+ @username
39
+ end
40
+ else
41
+ value = CGI::escape(value) if value.present? && !value.include?("%")
42
+ @username = value
43
+ end
44
+ end
45
+
46
+ def username=(value)
47
+ Flexirest::Logger.info "\033[1;4;32m#{name}\033[0m Username set to be #{value}"
48
+ value = CGI::escape(value) if value.present? && !value.include?("%")
49
+ @@username = value
50
+ end
51
+
52
+ def password(value = nil)
53
+ if value.nil?
54
+ if @password.nil?
55
+ @@password
56
+ else
57
+ @password
58
+ end
59
+ else
60
+ value = CGI::escape(value) if value.present? && !value.include?("%")
61
+ @password = value
62
+ end
63
+ end
64
+
65
+ def password=(value)
66
+ Flexirest::Logger.info "\033[1;4;32m#{name}\033[0m Password set..."
67
+ value = CGI::escape(value) if value.present? && !value.include?("%")
68
+ @@password = value
69
+ end
70
+
71
+ def request_body_type(value = nil)
72
+ if value.nil?
73
+ if @request_body_type.nil?
74
+ @@request_body_type
75
+ else
76
+ @request_body_type
77
+ end
78
+ else
79
+ @request_body_type = value
80
+ end
81
+ end
82
+
83
+ def request_body_type=(value)
84
+ Flexirest::Logger.info "\033[1;4;32m#{name}\033[0m Request Body Type set to be #{value}"
85
+ @@request_body_type = value
86
+ end
87
+
88
+ def adapter=(adapter)
89
+ Flexirest::Logger.info "\033[1;4;32m#{name}\033[0m Adapter set to be #{adapter}"
90
+ @adapter = adapter
91
+ end
92
+
93
+ def adapter
94
+ @adapter ||= Faraday.default_adapter
95
+ end
96
+
97
+ def faraday_config(&block)
98
+ if block
99
+ @faraday_config = block
100
+ else
101
+ @faraday_config ||= default_faraday_config
102
+ end
103
+ end
104
+
105
+ def lazy_load!
106
+ @lazy_load = true
107
+ end
108
+
109
+ def lazy_load?
110
+ @lazy_load || false
111
+ end
112
+
113
+ def whiny_missing(value = nil)
114
+ value ? @whiny_missing = value : @whiny_missing || false
115
+ end
116
+
117
+ def api_auth_credentials(access_id, secret_key)
118
+ begin
119
+ require 'api-auth'
120
+ rescue LoadError
121
+ raise MissingOptionalLibraryError.new("You must include the gem 'api-auth' in your Gemfile to set api-auth credentials.")
122
+ end
123
+
124
+ @api_auth_access_id = access_id
125
+ @api_auth_secret_key = secret_key
126
+ end
127
+
128
+ def using_api_auth?
129
+ !self.api_auth_access_id.nil? && !self.api_auth_secret_key.nil?
130
+ end
131
+
132
+ def api_auth_access_id
133
+ if !@api_auth_access_id.nil?
134
+ return @api_auth_access_id
135
+ elsif self.superclass.respond_to?(:api_auth_access_id)
136
+ return self.superclass.api_auth_access_id
137
+ end
138
+
139
+ return nil
140
+ end
141
+
142
+ def api_auth_secret_key
143
+ if !@api_auth_secret_key.nil?
144
+ return @api_auth_secret_key
145
+ elsif self.superclass.respond_to?(:api_auth_secret_key)
146
+ return self.superclass.api_auth_secret_key
147
+ end
148
+
149
+ return nil
150
+ end
151
+
152
+ def verbose!
153
+ @verbose = true
154
+ end
155
+
156
+ def verbose(value = nil)
157
+ value ? @verbose = value : @verbose || false
158
+ end
159
+
160
+ def translator(value = nil)
161
+ Flexirest::Logger.warn("DEPRECATION: The translator functionality of Flexirest has been replaced with proxy functionality, see https://github.com/whichdigital/active-rest-client#proxying-apis for more information") unless value.nil?
162
+ value ? @translator = value : @translator || nil
163
+ end
164
+
165
+ def proxy(value = nil)
166
+ value ? @proxy = value : @proxy || nil
167
+ end
168
+
169
+ def _reset_configuration!
170
+ @base_url = nil
171
+ @@base_url = nil
172
+ @request_body_type = nil
173
+ @@request_body_type = :form_encoded
174
+ @whiny_missing = nil
175
+ @lazy_load = false
176
+ @faraday_config = default_faraday_config
177
+ @adapter = Faraday.default_adapter
178
+ @api_auth_access_id = nil
179
+ @api_auth_secret_key = nil
180
+ end
181
+
182
+ private
183
+
184
+ def default_faraday_config
185
+ Proc.new do |faraday|
186
+ faraday.adapter(adapter)
187
+
188
+ if faraday.options.respond_to?(:timeout=)
189
+ faraday.options.timeout = 10
190
+ faraday.options.open_timeout = 10
191
+ else
192
+ faraday.options['timeout'] = 10
193
+ faraday.options['open_timeout'] = 10
194
+ end
195
+
196
+ faraday.headers['User-Agent'] = "Flexirest/#{Flexirest::VERSION}"
197
+ faraday.headers['Connection'] = "Keep-Alive"
198
+ faraday.headers['Accept'] = "application/json"
199
+ end
200
+ end
201
+ end
202
+
203
+ def self.included(base)
204
+ base.extend(ClassMethods)
205
+ end
206
+ end
207
+
208
+ class InvalidCacheStoreException < StandardError ; end
209
+ end