cardiac 0.2.0.pre2 → 0.2.0.pre3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ffcee4b9c9da2df77d5d3530d5562848b497fdab
4
- data.tar.gz: d0dc3f4297ef67ddc38f139be6b64b00b7ff4dc9
3
+ metadata.gz: faafad3cb935e5bb55d912bce4253d8a87167355
4
+ data.tar.gz: 176df035bb2ab9e4e84d78b85a7608f1d9e7f857
5
5
  SHA512:
6
- metadata.gz: d01919339fec4e2519bc8b92479383d7c259247fcae5055e1a2722baceca9f91e070398660c04103a6fb0ffdb70c55faf23a9d04973e0faf24f8fdbf45cc9715
7
- data.tar.gz: 55c38c5f11b07b8d92fc6d314d6c3a674bf8bfed80a936ab54b755042d185745d6fa76d217551d851772a00a418b664f24f24293f8df95fc0cb048df1b77f767
6
+ metadata.gz: ef4123ec8f187b9bf88850845a01d0678aabd2d5780795948a3fb02c8c03012686f3c67d48ef791b4b3f14af371606a805a6e4de9d99e31b4196b00185965651
7
+ data.tar.gz: 5ee48420e2b3053599dc737913f1c2aa43a2a2197c07e411ff884be131f4b290c379d1b7b5e1054a521d9954cf858eef49dcf42967d2d261bdef4ed41d9fc994
data/lib/cardiac.rb CHANGED
@@ -21,14 +21,15 @@ module Cardiac
21
21
  # Resources and builder DSL.
22
22
  autoload :Resource
23
23
  autoload_under 'resource' do
24
- autoload :Subresource
25
- autoload :UriMethods
26
- autoload :RequestMethods
27
24
  autoload :CodecMethods
28
- autoload :ExtensionMethods
29
25
  autoload :ConfigMethods
30
- autoload :ResourceBuilder, 'cardiac/resource/builder'
26
+ autoload :ExtensionMethods
27
+ autoload :RequestMethods
31
28
  autoload :ResourceAdapter, 'cardiac/resource/adapter'
29
+ autoload :ResourceBuilder, 'cardiac/resource/builder'
30
+ autoload :ResourceCache, 'cardiac/resource/cache'
31
+ autoload :Subresource
32
+ autoload :UriMethods
32
33
  end
33
34
  autoload :Representation
34
35
  autoload_at 'cardiac/declarations' do
@@ -38,6 +39,7 @@ module Cardiac
38
39
  end
39
40
 
40
41
  # Operations and execution.
42
+ autoload :Client
41
43
  autoload :OperationHandler
42
44
  autoload :OperationResult, 'cardiac/operation_handler'
43
45
  autoload :OperationBuilder
@@ -0,0 +1,58 @@
1
+ require 'rack'
2
+ require 'rack/client'
3
+ require 'rack/cache'
4
+ require 'stringio'
5
+
6
+ module Cardiac
7
+
8
+ class Client < Rack::Client::Simple
9
+
10
+ # :nodoc:
11
+ class ErrorLogger
12
+ def initialize(app)
13
+ @app = app
14
+ end
15
+
16
+ def call(env)
17
+ @app.call(env)
18
+ ensure
19
+ if message = env['rack.errors'].to_s.presence
20
+ Cardiac::Model::Base.logger.error message
21
+ end
22
+ end
23
+ end
24
+
25
+ # :nodoc:
26
+ class SwitchHeaders
27
+ def initialize(app,match,switch)
28
+ @app, @match, @switch = app, match, switch
29
+ end
30
+
31
+ def call(env)
32
+ status, headers, body = @app.call(env)
33
+ [status, Hash[ headers.to_a.map{|k,v| [@match===k ? @switch+$' : k, v] }], body]
34
+ end
35
+ end
36
+
37
+ def build_env(*)
38
+ env = super
39
+ end
40
+
41
+ def http_user_agent
42
+ "cardiac #{Cardiac::VERSION} (rack-client #{Rack::Client::VERSION})"
43
+ end
44
+
45
+ def self.new
46
+ super Rack::Client::Handler::NetHTTP
47
+ end
48
+
49
+ def self.request(*args)
50
+ @instance ||= new
51
+ @instance.request(*args)
52
+ end
53
+
54
+ def self.build_mock_response body, code, headers={}
55
+ Rack::Client::Simple::CollapsedResponse.new code, headers, StringIO.new(body)
56
+ end
57
+ end
58
+ end
@@ -12,12 +12,12 @@ module Cardiac
12
12
  def operation(event)
13
13
  return unless logger.debug?
14
14
 
15
- payload = event.payload
16
-
17
- url = payload[:url]
15
+ payload = event.payload
16
+ name, url = payload[:name], payload[:url]
17
+
18
18
  stats = "#{event.duration.round(1)}ms"
19
- stats = "CACHED #{stats}" if /fresh/ === payload[:response_headers].try(:[],'X-Rack-Client-Cache')
20
- name = "#{payload[:name]} #{payload[:verb]} (#{stats})"
19
+ stats = "CACHED #{stats}" if name!='CACHE' && /fresh/ === payload[:response_headers].try(:[],'X-Rack-Client-Cache')
20
+ name = "#{name} #{payload[:verb]} (#{stats})"
21
21
 
22
22
  if extra = payload.except(:name, :verb, :url, :response_headers).presence
23
23
  extra = " " + extra.map{|key,value|
@@ -4,6 +4,8 @@ require 'active_attr'
4
4
  module Cardiac
5
5
  module Model
6
6
  class Base
7
+ extend Cardiac::ResourceCache::ClassMethods
8
+
7
9
  include ActiveAttr::Model
8
10
  include Cardiac::Model::Attributes
9
11
  include Cardiac::Model::Querying
@@ -155,7 +157,7 @@ module Cardiac
155
157
  end
156
158
  end
157
159
 
158
- ActiveSupport.run_load_hooks(:cardiac, Base)
160
+ ActiveSupport.run_load_hooks(:cardiac_model, Base)
159
161
  end
160
162
 
161
163
  end
@@ -19,8 +19,6 @@ module Cardiac
19
19
  if status = super
20
20
  @previously_changed = changes
21
21
  @changed_attributes.clear
22
- #elsif IdentityMap.enabled?
23
- # IdentityMap.remove(self)
24
22
  end
25
23
  status
26
24
  end
@@ -33,9 +31,6 @@ module Cardiac
33
31
  @previously_changed = changes
34
32
  @changed_attributes.clear
35
33
  end
36
- #rescue
37
- # IdentityMap.remove(self) if IdentityMap.enabled?
38
- # raise
39
34
  end
40
35
 
41
36
  # <tt>reload</tt> the record and clears changed attributes.
@@ -70,10 +70,8 @@ module Cardiac
70
70
  # Reloads the attributes of this object from the remote.
71
71
  # Any (optional) arguments are passed to find when reloading.
72
72
  def reload(*args)
73
- #IdentityMap.without do
74
- fresh_object = self.class.find(self.id, *args)
75
- @attributes.update(fresh_object.instance_variable_get('@attributes'))
76
- #end
73
+ fresh_object = self.class.find(self.id, *args)
74
+ @attributes.update(fresh_object.instance_variable_get('@attributes'))
77
75
  self
78
76
  end
79
77
 
@@ -1,10 +1,6 @@
1
1
  require 'active_support/configurable'
2
2
  require 'active_support/rescuable'
3
3
  require 'active_support/callbacks'
4
- require 'rack'
5
- require 'rack/client'
6
- require 'rack/cache'
7
- require 'stringio'
8
4
 
9
5
  module Cardiac
10
6
 
@@ -44,40 +40,6 @@ module Cardiac
44
40
  response
45
41
  end
46
42
 
47
- class Client < Rack::Client::Simple
48
- class SwitchHeaders
49
- def initialize(app,match,switch)
50
- @app, @match, @switch = app, match, switch
51
- end
52
- def call(env)
53
- status, headers, body = @app.call(env)
54
- [status, Hash[ headers.to_a.map{|k,v| [@match===k ? @switch+$' : k, v] }], body]
55
- end
56
- end
57
-
58
- use SwitchHeaders, /^X-HideRack-/, 'X-Rack-'
59
- use SwitchHeaders, /^X-Rack-/, 'X-Rack-Client-'
60
- use Rack::Cache,
61
- 'rack-cache.ignore_headers' => ['Set-Cookie','X-Content-Digest']
62
- use Rack::Head
63
- use Rack::ConditionalGet
64
- use Rack::ETag
65
- use SwitchHeaders, /^X-Rack-/, 'X-HideRack-'
66
-
67
- def self.new
68
- super Rack::Client::Handler::NetHTTP
69
- end
70
-
71
- def self.request(*args)
72
- @instance ||= new
73
- @instance.request(*args)
74
- end
75
-
76
- def http_user_agent
77
- "cardiac #{Cardiac::VERSION} (rack-client #{Rack::Client::VERSION})"
78
- end
79
- end
80
-
81
43
  attr_accessor :verb, :url, :headers, :payload, :options, :response_handler, :result
82
44
 
83
45
  def initialize client_options, payload=nil, &response_handler
@@ -204,12 +166,7 @@ module Cardiac
204
166
  # Optionally, these conditions could also be made to provide a mock response on a connection error.
205
167
  # This would prevent the operation from aborting but still show that the response was not transmitted.
206
168
  def service_unavailable e
207
- self.result = build_mock_response e, '503' if mock_response_on_connection_error
208
- end
209
-
210
- # Internal method.
211
- def build_mock_response body, code, version='1.0', headers={}
212
- Rack::Client::Simple::CollapsedResponse.new code, headers, StringIO.new(body)
169
+ self.result = Client.build_mock_response e, '503' if mock_response_on_connection_error
213
170
  end
214
171
  end
215
172
  end
@@ -1,14 +1,10 @@
1
1
  require 'active_attr/railtie'
2
- require "cardiac/model/base"
2
+ require 'cardiac/model'
3
3
  require 'cardiac/log_subscriber'
4
4
 
5
5
  module Cardiac
6
6
  class Railtie < Rails::Railtie
7
7
 
8
- initializer "cardiac.logger" do
9
- ActiveSupport.on_load(:cardiac) { self.logger ||= ::Rails.logger }
10
- end
11
-
12
8
  # Make the console output logging to STDERR (unless AR already did it).
13
9
  console do |app|
14
10
  unless defined? ::ActiveRecord::Base
@@ -16,5 +12,74 @@ module Cardiac
16
12
  Rails.logger.extend ActiveSupport::Logger.broadcast(console)
17
13
  end
18
14
  end
15
+
16
+ # Rails 4.0+
17
+ if config.respond_to? :eager_load_namespaces
18
+ config.eager_load_namespaces << Cardiac << Cardiac::Model
19
+ end
20
+
21
+ # Cardiac
22
+ # ---------------------------------------------------------------------------
23
+
24
+ config.cardiac = ActiveSupport::OrderedOptions.new
25
+ config.cardiac.client_cache = {}
26
+ config.cardiac.verbose = false
27
+
28
+ config.app_middleware.insert_after "::ActionDispatch::Callbacks",
29
+ "Cardiac::ResourceCache::Middleware"
30
+
31
+ initializer 'cardiac.build_client_middleware' do |app|
32
+ ActiveSupport.on_load(:cardiac) do
33
+ Cardiac::Client.tap do |client|
34
+
35
+ # Optionally print out the rack errors after the request
36
+ client.use Cardiac::Client::ErrorLogger if app.config.cardiac.verbose
37
+
38
+ # Restore any headers set by the remote server's Rack.
39
+ client.use Cardiac::Client::SwitchHeaders, /^X-HideRack-/, 'X-Rack-'
40
+
41
+ # Rename any headers set by the local client's Rack.
42
+ client.use Cardiac::Client::SwitchHeaders, /^X-Rack-/, 'X-Rack-Client-'
43
+
44
+ # Unless disabled, configure the client's Rack cache.
45
+ # NOTE: Certain headers should ALWAYS be ignored by the client.
46
+ if client_cache = app.config.cardiac.client_cache
47
+ client_cache = {} unless Hash===client_cache
48
+
49
+ client_cache[:verbose] = app.config.cardiac.verbose unless client_cache.key? :verbose
50
+
51
+ client_cache[:ignore_headers] = Array(client_cache[:ignore_headers]) + ['Set-Cookie','X-Content-Digest']
52
+
53
+ client.use Rack::Cache, Hash[ client_cache.map{|k,v| ["rack-cache.#{k}", v] } ]
54
+ end
55
+
56
+ # This is the "meat" of our basic middleware.
57
+ client.use Rack::Head
58
+ client.use Rack::ConditionalGet
59
+ client.use Rack::ETag
60
+
61
+ # Hide any headers set by the remote server's Rack.
62
+ client.use Cardiac::Client::SwitchHeaders, /^X-Rack-/, 'X-HideRack-'
63
+
64
+ end
65
+ end
66
+ end
67
+
68
+
69
+ # Cardiac::Model
70
+ # ---------------------------------------------------------------------------
71
+
72
+ config.cardiac_model = ActiveSupport::OrderedOptions.new
73
+
74
+ initializer "cardiac_model.logger" do
75
+ ActiveSupport.on_load(:cardiac_model) do
76
+ self.logger ||= ::Rails.logger
77
+ end
78
+ end
79
+
80
+ # Make sure that the model layer is already available when running a script.
81
+ runner do
82
+ require 'cardiac/model/base'
83
+ end
19
84
  end
20
85
  end
@@ -74,4 +74,6 @@ module Cardiac
74
74
  base.userinfo = base.query = nil
75
75
  end
76
76
  end
77
+
78
+ ActiveSupport.run_load_hooks(:cardiac, Resource)
77
79
  end
@@ -8,13 +8,14 @@ module Cardiac
8
8
  # An adapter for performing operations on a resource.
9
9
  class ResourceAdapter
10
10
  include ::ActiveSupport::Callbacks
11
- include ::Cardiac::Representation::LookupMethods
11
+ include Representation::LookupMethods
12
+ include ResourceCache::InstanceMethods
12
13
 
13
14
  define_callbacks :resolve, :prepare, :encode, :execute, :decode
14
15
 
15
16
  attr_accessor :klass, :resource, :payload, :result
16
17
 
17
- delegate :request_has_body?, :response_has_body?, to: :resource
18
+ delegate :request_has_body?, :response_has_body?, :request_is_safe?, :request_is_idempotent?, to: :resource
18
19
  delegate :encoder_reflection, :decoder_reflections, to: '@reflection'
19
20
  delegate :transmitted?, :aborted?, :completed?, :response, to: :result, allow_nil: true
20
21
 
@@ -24,12 +25,9 @@ module Cardiac
24
25
  delegate :logger, to: '::Cardiac::Model::Base'
25
26
 
26
27
  def initialize(klass,base,payload=nil)
27
- @klass = klass
28
- @reflection = base.to_reflection if base.respond_to?(:to_reflection)
29
- run_callbacks :resolve do
30
- @resource = base.to_resource if base.respond_to?(:to_resource)
31
- end
32
- @reflection ||= @resource.to_reflection if resolved?
28
+ @klass = klass
29
+ @reflection = base.to_reflection if base.respond_to? :to_reflection
30
+ resolve! base
33
31
  end
34
32
 
35
33
  def __client_options__
@@ -58,9 +56,7 @@ module Cardiac
58
56
 
59
57
  # Convenience method to return the current HTTP verb
60
58
  def http_verb
61
- if defined? @__client_options__
62
- @__client_options__[:method].to_s.upcase
63
- end
59
+ @http_verb ||= (defined? @__client_options__ and @__client_options__[:method].to_s.upcase)
64
60
  end
65
61
 
66
62
  # Performs a remote call by performing the remaining phases in the lifecycle of this adapter.
@@ -80,15 +76,26 @@ module Cardiac
80
76
  end
81
77
 
82
78
  def prepared?
83
- __client_options__.present?
79
+ @__client_options__.present?
84
80
  end
85
81
 
86
82
  protected
87
83
 
84
+ def resolve! base
85
+ run_callbacks :resolve do
86
+ @resource = base.to_resource if base.respond_to?(:to_resource)
87
+ end
88
+ @reflection ||= @resource.to_reflection if resolved?
89
+ end
90
+
88
91
  def prepare! verb=nil
89
92
  run_callbacks :prepare do
90
- self.resource = resource.http_method(verb) if verb
93
+ if verb
94
+ self.resource = resource.http_method(verb)
95
+ @__client_options__ = nil
96
+ end
91
97
  end
98
+
92
99
  __client_options__.symbolize_keys!
93
100
  prepared?
94
101
  end
@@ -114,14 +121,20 @@ module Cardiac
114
121
  end
115
122
 
116
123
  def execute!
117
- event = event_attributes
118
- instrumenter.instrument "operation.cardiac", event do
119
- run_callbacks :execute do
120
- handler = __handler__.new(__client_options__, payload, &__client_handler__)
121
- self.result = handler.transmit!
124
+ run_callbacks :execute do
125
+ clear_resource_cache unless request_is_safe?
126
+
127
+ instrumenter.instrument "operation.cardiac", event=event_attributes do
128
+ if resource_cache_enabled? http_verb
129
+ url, headers = __client_options__.slice(:url, :headers)
130
+ self.result = cache_resource(url.to_s, headers, event) { transmit! }
131
+ else
132
+ self.result = transmit!
133
+ end
122
134
  event[:response_headers] = result.response.headers if result && result.response
123
- completed?
124
135
  end
136
+
137
+ completed?
125
138
  end
126
139
  rescue => e
127
140
  message = "#{e.class.name}: #{e.message}: #{resource.to_url}"
@@ -147,12 +160,16 @@ module Cardiac
147
160
 
148
161
  private
149
162
 
163
+ def transmit!
164
+ __handler__.new(__client_options__, payload, &__client_handler__).transmit!
165
+ end
166
+
150
167
  def model_name
151
168
  __klass_get(:model_name).try(:to_s)
152
169
  end
153
170
 
154
- def event_attributes
155
- h = { name: model_name, verb: http_verb, url: resource.to_url, payload: payload }
171
+ def event_attributes(name=model_name)
172
+ h = { name: name, verb: http_verb, url: resource.to_url, payload: payload }
156
173
  ctx = __klass_get :operation_context
157
174
  ctx = { context: ctx } unless ctx.present? && Hash===ctx
158
175
  h.reverse_merge! ctx if ctx