methodmissing-scrooge 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +262 -0
- data/lib/scrooge/core/string.rb +29 -0
- data/lib/scrooge/core/symbol.rb +21 -0
- data/lib/scrooge/core/thread.rb +26 -0
- data/lib/scrooge/framework/base.rb +304 -0
- data/lib/scrooge/framework/rails.rb +107 -0
- data/lib/scrooge/middleware/tracker.rb +47 -0
- data/lib/scrooge/orm/active_record.rb +143 -0
- data/lib/scrooge/orm/base.rb +102 -0
- data/lib/scrooge/profile.rb +204 -0
- data/lib/scrooge/storage/base.rb +47 -0
- data/lib/scrooge/storage/memory.rb +25 -0
- data/lib/scrooge/tracker/app.rb +101 -0
- data/lib/scrooge/tracker/base.rb +56 -0
- data/lib/scrooge/tracker/model.rb +90 -0
- data/lib/scrooge/tracker/resource.rb +201 -0
- data/lib/scrooge.rb +62 -0
- data/spec/fixtures/config/scrooge/scopes/1234567891/scope.yml +2 -0
- data/spec/fixtures/config/scrooge.yml +12 -0
- data/spec/helpers/framework/rails/cache.rb +25 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/units/scrooge/core/string_spec.rb +21 -0
- data/spec/units/scrooge/core/symbol_spec.rb +13 -0
- data/spec/units/scrooge/core/thread_spec.rb +15 -0
- data/spec/units/scrooge/framework/base_spec.rb +154 -0
- data/spec/units/scrooge/framework/rails_spec.rb +40 -0
- data/spec/units/scrooge/orm/base_spec.rb +61 -0
- data/spec/units/scrooge/profile_spec.rb +73 -0
- data/spec/units/scrooge/storage/base_spec.rb +35 -0
- data/spec/units/scrooge/storage/memory_spec.rb +20 -0
- data/spec/units/scrooge/tracker/app_spec.rb +62 -0
- data/spec/units/scrooge/tracker/base_spec.rb +21 -0
- data/spec/units/scrooge/tracker/model_spec.rb +50 -0
- data/spec/units/scrooge/tracker/resource_spec.rb +83 -0
- data/spec/units/scrooge_spec.rb +13 -0
- metadata +110 -0
data/README.textile
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
h1. Scrooge
|
2
|
+
|
3
|
+
A Framework and ORM agnostic Model / record attribute tracker to ensure production
|
4
|
+
Ruby applications only fetch the database content needed to minimize wire traffic
|
5
|
+
and reduce conversion overheads to native Ruby types.
|
6
|
+
|
7
|
+
This is mostly an experiment into unobtrusive tracking, respecting development workflows
|
8
|
+
and understanding Rack internals better.
|
9
|
+
|
10
|
+
h2. Why bother ?
|
11
|
+
|
12
|
+
* Object conversion and moving unnecessary data is both expensive and tax existing infrastructure in high load setups
|
13
|
+
* Manually extracting and scoping SELECT clauses is not sustainable in a clean and painless manner with iterative development, even less so in large projects.
|
14
|
+
|
15
|
+
h2. Suggested Use
|
16
|
+
|
17
|
+
Scrooge *requires* a comprehensive existing functional or integration test suite for best results.If test coverage is flaky or non-existent, then a) you shouldn't worry about performance tuning and b) shouldn't be reading this - go spec.
|
18
|
+
|
19
|
+
There's 2 basic modes of operation, a tracking or a scope phase.
|
20
|
+
|
21
|
+
h4. Resources
|
22
|
+
|
23
|
+
A resource is :
|
24
|
+
|
25
|
+
* A controller and action endpoint ( inferred through framework specific routing )
|
26
|
+
* A content type / format - a PDF representation may have different Model attribute requirements than a vanilla ERB view.
|
27
|
+
* Request method - typically popular public facing GET requests
|
28
|
+
|
29
|
+
All Model to attribute mappings is tracked on a per Resource basis.Multiple Models per Resource is supported.
|
30
|
+
|
31
|
+
h4. Tracking
|
32
|
+
|
33
|
+
In tracking mode, which is the default when no scope is given and Scrooge is enabled, Scrooge installs filters ( either through Rack middleware or framework specific hooks ) that track attribute access on a per Resource basis.
|
34
|
+
|
35
|
+
A Kernel#at_exit callback dumps and timestamps this profile ( or scope ) to eg. *framework_configuration_directory/config/scopes/1234147851/scope.yml*
|
36
|
+
|
37
|
+
This typically happens during functional or integration testing.
|
38
|
+
|
39
|
+
The test log may look like :
|
40
|
+
|
41
|
+
<pre>
|
42
|
+
<code>
|
43
|
+
Processing HotelsController#index (for 0.0.0.0 at 2009-02-09 02:55:55) [GET]
|
44
|
+
Parameters: {"action"=>"index", "controller"=>"hotels"}
|
45
|
+
[Scrooge] Track with resource #< :/ ()
|
46
|
+
[Scrooge] Track for Resource #<GET :hotels/index (*/*)
|
47
|
+
- #<Hotel :from_price, :narrative, :star_rating, :latitude, :created_at, :hotel_name, :updated_at, :important_notes, :id, :apt, :location_id, :nearest_tube, :longitude, :telephone, :nearest_rail, :location_name, :distance>
|
48
|
+
- #<Image :thumbnail_width, :created_at, :title, :updated_at, :url, :thumbnail_height, :height, :thumbnail_url, :has_thumbnail, :hotel_id, :width>
|
49
|
+
Hotel Load (0.3ms) SELECT * FROM `hotels` LIMIT 0, 15
|
50
|
+
Rendering template within layouts/application
|
51
|
+
Rendering hotels/index
|
52
|
+
Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 491) LIMIT 1
|
53
|
+
[Scrooge] read attribute updated_at
|
54
|
+
Rendered hotels/_hotel (2.7ms)
|
55
|
+
Rendered shared/_header (0.1ms)
|
56
|
+
Rendered shared/_navigation (0.3ms)
|
57
|
+
Missing template hotels/_index_sidebar.erb in view path app/views
|
58
|
+
Rendered shared/_sidebar (0.1ms)
|
59
|
+
Rendered shared/_footer (0.1ms)
|
60
|
+
Completed in 91ms (View: 90, DB: 1) | 200 OK [http://test.host/hotels]
|
61
|
+
SQL (0.3ms) ROLLBACK
|
62
|
+
SQL (0.1ms) BEGIN
|
63
|
+
</code>
|
64
|
+
</pre>
|
65
|
+
|
66
|
+
An example scope / profile, saved to disk :
|
67
|
+
|
68
|
+
<pre>
|
69
|
+
<code>
|
70
|
+
---
|
71
|
+
- hotels_show_get:
|
72
|
+
:action: show
|
73
|
+
:controller: hotels
|
74
|
+
:method: :get
|
75
|
+
:format: "*/*"
|
76
|
+
:models:
|
77
|
+
- Address:
|
78
|
+
- line1
|
79
|
+
- line2
|
80
|
+
- created_at
|
81
|
+
- postcode
|
82
|
+
- updated_at
|
83
|
+
- country_id
|
84
|
+
- county
|
85
|
+
- location_id
|
86
|
+
- town
|
87
|
+
- hotel_id
|
88
|
+
- Hotel:
|
89
|
+
- important_notes
|
90
|
+
- location_id
|
91
|
+
- locations_index_get:
|
92
|
+
:action: index
|
93
|
+
:controller: locations
|
94
|
+
:method: :get
|
95
|
+
:format: "*/*"
|
96
|
+
:models:
|
97
|
+
- Location:
|
98
|
+
- name
|
99
|
+
- created_at
|
100
|
+
- code
|
101
|
+
- updated_at
|
102
|
+
- level
|
103
|
+
- id
|
104
|
+
- countries_index_get:
|
105
|
+
:action: index
|
106
|
+
:controller: countries
|
107
|
+
:method: :get
|
108
|
+
:format: "*/*"
|
109
|
+
:models:
|
110
|
+
- Country:
|
111
|
+
- name
|
112
|
+
- created_at
|
113
|
+
- code
|
114
|
+
- updated_at
|
115
|
+
- id
|
116
|
+
- location_id
|
117
|
+
- continent_id
|
118
|
+
- hotels_index_get:
|
119
|
+
:action: index
|
120
|
+
:controller: hotels
|
121
|
+
:method: :get
|
122
|
+
:format: "*/*"
|
123
|
+
:models:
|
124
|
+
- Hotel:
|
125
|
+
- from_price
|
126
|
+
- narrative
|
127
|
+
- star_rating
|
128
|
+
- latitude
|
129
|
+
- created_at
|
130
|
+
- hotel_name
|
131
|
+
- updated_at
|
132
|
+
- important_notes
|
133
|
+
- id
|
134
|
+
- apt
|
135
|
+
- location_id
|
136
|
+
- nearest_tube
|
137
|
+
- longitude
|
138
|
+
- telephone
|
139
|
+
- nearest_rail
|
140
|
+
- location_name
|
141
|
+
- distance
|
142
|
+
- Image:
|
143
|
+
- thumbnail_width
|
144
|
+
- created_at
|
145
|
+
- title
|
146
|
+
- updated_at
|
147
|
+
- url
|
148
|
+
- thumbnail_height
|
149
|
+
- height
|
150
|
+
- thumbnail_url
|
151
|
+
- has_thumbnail
|
152
|
+
- hotel_id
|
153
|
+
- width
|
154
|
+
</code>
|
155
|
+
</pre>
|
156
|
+
|
157
|
+
h4. Scoping
|
158
|
+
|
159
|
+
A previously persisted scope / profile can be restored from disk and injected to the applicable Resources.Database content retrieved will match that of the given scope timestamp.
|
160
|
+
|
161
|
+
This is typically pushed to production and adjusted for each major release or deployment.
|
162
|
+
|
163
|
+
Log output may look like :
|
164
|
+
|
165
|
+
<pre>
|
166
|
+
<code>
|
167
|
+
Processing HotelsController#index (for 0.0.0.0 at 2009-02-09 02:59:41) [GET]
|
168
|
+
Parameters: {"action"=>"index", "controller"=>"hotels"}
|
169
|
+
[Scrooge] Scope for Model #<Image :created_at, :thumbnail_width, :title, :updated_at, :url, :id, :thumbnail_height, :height, :thumbnail_url, :has_thumbnail, :width, :hotel_id>
|
170
|
+
[Scrooge] Scope for Model #<Hotel :narrative, :from_price, :created_at, :latitude, :star_rating, :hotel_name, :updated_at, :important_notes, :apt, :id, :nearest_tube, :location_id, :nearest_rail, :telephone, :longitude, :distance, :location_name>
|
171
|
+
Hotel Load (0.4ms) SELECT hotels.narrative, hotels.from_price, hotels.created_at, hotels.latitude, hotels.star_rating, hotels.hotel_name, hotels.updated_at, hotels.important_notes, hotels.apt, hotels.id, hotels.nearest_tube, hotels.location_id, hotels.nearest_rail, hotels.telephone, hotels.longitude, hotels.distance, hotels.location_name FROM `hotels` LIMIT 0, 15
|
172
|
+
Rendering template within layouts/application
|
173
|
+
Rendering hotels/index
|
174
|
+
Image Load (0.2ms) SELECT images.created_at, images.thumbnail_width, images.title, images.updated_at, images.url, images.id, images.thumbnail_height, images.height, images.thumbnail_url, images.has_thumbnail, images.width, images.hotel_id FROM `images` WHERE (`images`.hotel_id = 491) LIMIT 1
|
175
|
+
Rendered hotels/_hotel (2.8ms)
|
176
|
+
Rendered shared/_header (0.1ms)
|
177
|
+
Rendered shared/_navigation (0.3ms)
|
178
|
+
Missing template hotels/_index_sidebar.erb in view path app/views
|
179
|
+
Rendered shared/_sidebar (0.1ms)
|
180
|
+
Rendered shared/_footer (0.1ms)
|
181
|
+
Completed in 90ms (View: 5, DB: 1) | 200 OK [http://test.host/hotels]
|
182
|
+
SQL (0.1ms) ROLLBACK
|
183
|
+
SQL (0.1ms) BEGIN
|
184
|
+
</code>
|
185
|
+
</pre>
|
186
|
+
|
187
|
+
h2. Installation
|
188
|
+
|
189
|
+
h4. As a Rails plugin ( Recommended )
|
190
|
+
|
191
|
+
./script/plugin install git://github.com/methodmissing/scrooge.git
|
192
|
+
|
193
|
+
h4. From Git
|
194
|
+
|
195
|
+
git pull git://github.com/methodmissing/scrooge.git
|
196
|
+
|
197
|
+
|
198
|
+
h4. As a Gem
|
199
|
+
|
200
|
+
sudo gem install methodmissing-scrooge -s http://gems.github.com
|
201
|
+
|
202
|
+
h2. Configuration
|
203
|
+
|
204
|
+
Scrooge installs ( see recommended installation above ) a configuration file with the following format within *framework_configuration_directory/scrooge.yml ( RAILS_ROOT/config/scrooge.yml for a Rails setup ) :
|
205
|
+
|
206
|
+
production:
|
207
|
+
orm: :active_record
|
208
|
+
storage: :memory
|
209
|
+
scope:
|
210
|
+
on_missing_attribute: :reload # or :raise
|
211
|
+
enabled: true
|
212
|
+
development:
|
213
|
+
orm: :active_record
|
214
|
+
storage: :memory
|
215
|
+
scope:
|
216
|
+
on_missing_attribute: :reload # or :raise
|
217
|
+
enabled: true
|
218
|
+
test:
|
219
|
+
orm: :active_record
|
220
|
+
storage: :memory
|
221
|
+
scope:
|
222
|
+
on_missing_attribute: :reload # or :raise
|
223
|
+
enabled: true
|
224
|
+
|
225
|
+
h4. ORM
|
226
|
+
|
227
|
+
Scrooge is ORM agnostic and ships with an ActiveRecord layer.
|
228
|
+
|
229
|
+
orm: :active_record
|
230
|
+
|
231
|
+
h4. Storage backend
|
232
|
+
|
233
|
+
Tracking results can be persisted to a given backend or storage option.Ships with a memory store, but can be extended to file system, memcached etc. as all Tracker components is designed to be Marshal friendly.
|
234
|
+
|
235
|
+
storage: :memory
|
236
|
+
|
237
|
+
h4. Scope
|
238
|
+
|
239
|
+
A scope is a reference to a timestamped Scrooge run where access to Model attributes is tracked on a per Resource basis.
|
240
|
+
|
241
|
+
scope: 1234567891
|
242
|
+
|
243
|
+
If not scope is given in the configuration, ENV['scope'] would also be considered to facilitate configuration through Capistrano etc.
|
244
|
+
|
245
|
+
h4. Handling Missing Attributes
|
246
|
+
|
247
|
+
When the contents for a given Model attribute has not been retrieved from the database, most ORM frameworks raise an error by default.This is configurable to reloading the model with all it's columns or raise instead.
|
248
|
+
|
249
|
+
on_missing_attribute: :reload # or :raise
|
250
|
+
|
251
|
+
h4. Status
|
252
|
+
|
253
|
+
Scrooge can be disabled with :
|
254
|
+
|
255
|
+
enabled: false
|
256
|
+
|
257
|
+
h2. Notes
|
258
|
+
|
259
|
+
This is an initial release, has not yet been battle tested in production and is pending Ruby 1.9.1 compatibility.
|
260
|
+
|
261
|
+
Initially evaluated a centralized tracker concept for multi-server environments with minimal configuration overhead
|
262
|
+
and on the fly scope injection after a given warmup threshold but found that to be overkill for a first release.
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Scrooge
|
2
|
+
module Core
|
3
|
+
module String
|
4
|
+
|
5
|
+
# Framework agnostic String <=> Constant helpers.
|
6
|
+
# Perhaps not the cleanest abstraction, but also not good practice to piggy
|
7
|
+
# back on or use a naming convention that may clash with and uproot the API
|
8
|
+
# any given framework ships with.
|
9
|
+
|
10
|
+
def to_const
|
11
|
+
self.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_const!( instantiate = true )
|
15
|
+
begin
|
16
|
+
const = Object.module_eval(to_const, __FILE__, __LINE__)
|
17
|
+
instantiate ? const.new : const
|
18
|
+
rescue => exception
|
19
|
+
exception.to_s.match( /uninitialized constant/ ) ? self : raise
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class String
|
28
|
+
include Scrooge::Core::String
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Scrooge
|
2
|
+
module Core
|
3
|
+
module Symbol
|
4
|
+
|
5
|
+
# See Scrooge::Core::Symbol
|
6
|
+
|
7
|
+
def to_const
|
8
|
+
to_s.to_const
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_const!
|
12
|
+
to_s.to_const!
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Symbol
|
20
|
+
include Scrooge::Core::Symbol
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Scrooge
|
2
|
+
module Core
|
3
|
+
module Thread
|
4
|
+
|
5
|
+
# Scrooge Resource tracker scoped to the current Thread for threadsafety in
|
6
|
+
# multi-threaded environments.
|
7
|
+
|
8
|
+
def scrooge_resource
|
9
|
+
current[:scrooge_resource] ||= Scrooge::Tracker::Resource.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def scrooge_resource=( resource )
|
13
|
+
current[:scrooge_resource] = resource
|
14
|
+
end
|
15
|
+
|
16
|
+
def reset_scrooge_resource!
|
17
|
+
current[:scrooge_resource] = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Thread
|
25
|
+
extend Scrooge::Core::Thread
|
26
|
+
end
|
@@ -0,0 +1,304 @@
|
|
1
|
+
module Scrooge
|
2
|
+
module Framework
|
3
|
+
|
4
|
+
# Scrooge is framework agnostic and attempts to abstract the following :
|
5
|
+
#
|
6
|
+
# * current environment
|
7
|
+
# * app root dir
|
8
|
+
# * app tmp dir
|
9
|
+
# * app config dir
|
10
|
+
# * logging
|
11
|
+
# * resource endpoints
|
12
|
+
# * caching
|
13
|
+
# * injecting Rack MiddleWare
|
14
|
+
#
|
15
|
+
# Framework Signatures
|
16
|
+
#
|
17
|
+
# Scrooge will attempt to determine the current active framework it's deployed with
|
18
|
+
# through various framework specific hooks.
|
19
|
+
#
|
20
|
+
# module Scrooge
|
21
|
+
# module Framework
|
22
|
+
# module YetAnother < Base
|
23
|
+
# ...
|
24
|
+
# signature do
|
25
|
+
# Object.const_defined?( "UnlikeAnyOther" )
|
26
|
+
# end
|
27
|
+
# ...
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
|
32
|
+
autoload :Rails, 'scrooge/framework/rails'
|
33
|
+
|
34
|
+
class Base < Scrooge::Base
|
35
|
+
|
36
|
+
GUARD = Mutex.new
|
37
|
+
|
38
|
+
SCOPE_REGEX = /\d{10}/.freeze
|
39
|
+
|
40
|
+
CONFIGURATION_FILE = 'scrooge.yml'.freeze
|
41
|
+
|
42
|
+
SCOPE_FILE = 'scope.yml'.freeze
|
43
|
+
|
44
|
+
class NotImplemented < StandardError
|
45
|
+
end
|
46
|
+
|
47
|
+
class NoSupportedFrameworks < StandardError
|
48
|
+
end
|
49
|
+
|
50
|
+
class InvalidScopeSignature < StandardError
|
51
|
+
end
|
52
|
+
|
53
|
+
class << self
|
54
|
+
|
55
|
+
# Per framework signature lookup.
|
56
|
+
#
|
57
|
+
@@signatures = {}
|
58
|
+
@@signatures[self.name] = Hash.new( [] )
|
59
|
+
|
60
|
+
# Support none by default.
|
61
|
+
#
|
62
|
+
@@frameworks = []
|
63
|
+
|
64
|
+
# Registers a framework signature.
|
65
|
+
#
|
66
|
+
def signature( &block )
|
67
|
+
@@signatures[self.name] = signatures << block
|
68
|
+
end
|
69
|
+
|
70
|
+
# All signatures for the current klass.
|
71
|
+
#
|
72
|
+
def signatures
|
73
|
+
@@signatures[self.name] || []
|
74
|
+
end
|
75
|
+
|
76
|
+
# All supported frameworks.
|
77
|
+
#
|
78
|
+
def frameworks
|
79
|
+
@@frameworks
|
80
|
+
end
|
81
|
+
|
82
|
+
# Infer the framework Scrooge attaches to in a first yield manner.
|
83
|
+
# A match of all defined signatures is required.
|
84
|
+
#
|
85
|
+
def which_framework?
|
86
|
+
iterate_frameworks() || raise( NoSupportedFrameworks )
|
87
|
+
end
|
88
|
+
|
89
|
+
# Yield an instance of the current framework.
|
90
|
+
#
|
91
|
+
def instantiate
|
92
|
+
which_framework?().new
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def inherited( subclass ) #:nodoc:
|
98
|
+
@@frameworks << subclass
|
99
|
+
end
|
100
|
+
|
101
|
+
def iterate_frameworks #:nodoc:
|
102
|
+
frameworks.detect do |framework|
|
103
|
+
framework.signatures.all?{|sig| sig.call }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
# The framework environment eg. test, development etc.
|
110
|
+
#
|
111
|
+
def environment
|
112
|
+
raise NotImplemented
|
113
|
+
end
|
114
|
+
|
115
|
+
# Application root directory
|
116
|
+
#
|
117
|
+
def root
|
118
|
+
raise NotImplemented
|
119
|
+
end
|
120
|
+
|
121
|
+
# Application temp. directory
|
122
|
+
#
|
123
|
+
def tmp
|
124
|
+
raise NotImplemented
|
125
|
+
end
|
126
|
+
|
127
|
+
# Application configuration directory
|
128
|
+
#
|
129
|
+
def config
|
130
|
+
raise NotImplemented
|
131
|
+
end
|
132
|
+
|
133
|
+
# Application logger instance.
|
134
|
+
# API compat with stdlib Logger assumed.
|
135
|
+
#
|
136
|
+
def logger
|
137
|
+
raise NotImplemented
|
138
|
+
end
|
139
|
+
|
140
|
+
# Supplement the current Resource tracker with additional environment context.
|
141
|
+
#
|
142
|
+
def resource( env, request = nil )
|
143
|
+
raise NotImplemented
|
144
|
+
end
|
145
|
+
|
146
|
+
# Write to the framework cache.
|
147
|
+
#
|
148
|
+
def write_cache( key, value )
|
149
|
+
raise NotImplemented
|
150
|
+
end
|
151
|
+
|
152
|
+
# Read from the framework cache.
|
153
|
+
#
|
154
|
+
def read_cache( key )
|
155
|
+
raise NotImplemented
|
156
|
+
end
|
157
|
+
|
158
|
+
# Access to the framework's Rack middleware stack.
|
159
|
+
#
|
160
|
+
def middleware
|
161
|
+
raise NotImplemented
|
162
|
+
end
|
163
|
+
|
164
|
+
# Inject scoping middleware.
|
165
|
+
#
|
166
|
+
def install_scope_middleware( tracker )
|
167
|
+
raise NotImplemented
|
168
|
+
end
|
169
|
+
|
170
|
+
# Inject tracking middleware.
|
171
|
+
#
|
172
|
+
def install_tracking_middleware
|
173
|
+
raise NotImplemented
|
174
|
+
end
|
175
|
+
|
176
|
+
# Register a code block to run when the host framework is fully initialized.
|
177
|
+
#
|
178
|
+
def initialized( &block )
|
179
|
+
raise NotImplemented
|
180
|
+
end
|
181
|
+
|
182
|
+
# Yields a controller constant from a given Resource Tracker
|
183
|
+
#
|
184
|
+
def controller( resource )
|
185
|
+
raise NotImplemented
|
186
|
+
end
|
187
|
+
|
188
|
+
# Retrieve all previously persisted scopes tracked with Scrooge.
|
189
|
+
#
|
190
|
+
def scopes
|
191
|
+
ensure_scopes_path do
|
192
|
+
Dir.entries( scopes_path ).grep( SCOPE_REGEX )
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Determine if there's any previously persisted scopes.
|
197
|
+
#
|
198
|
+
def scopes?
|
199
|
+
!scopes().empty?
|
200
|
+
end
|
201
|
+
|
202
|
+
# Return the scopes storage path for the current framework.
|
203
|
+
#
|
204
|
+
def scopes_path
|
205
|
+
@profiles_path ||= File.join( config, 'scrooge', 'scopes' )
|
206
|
+
end
|
207
|
+
|
208
|
+
# Return the scopes storage path for a given scope and optional filename.
|
209
|
+
#
|
210
|
+
def scope_path( scope, filename = nil )
|
211
|
+
path = File.join( scopes_path, scope.to_s )
|
212
|
+
filename ? File.join( path, filename ) : path
|
213
|
+
end
|
214
|
+
|
215
|
+
# Log a message to the logger.
|
216
|
+
#
|
217
|
+
def log( message )
|
218
|
+
logger.info "[Scrooge] #{message}"
|
219
|
+
end
|
220
|
+
|
221
|
+
# Persist the current tracker as scope or restore a previously persisted scope
|
222
|
+
# from a given signature.
|
223
|
+
#
|
224
|
+
def scope!( scope = nil )
|
225
|
+
scope ? from_scope!( scope ) : to_scope!()
|
226
|
+
end
|
227
|
+
|
228
|
+
# Do we have a valid scope signature ?
|
229
|
+
#
|
230
|
+
def scope?( scope )
|
231
|
+
scopes.include?( scope.to_s )
|
232
|
+
end
|
233
|
+
|
234
|
+
# Restore a previously persisted scope to the current tracker from a given
|
235
|
+
# signature.Raises Scrooge::Framework::InvalidScopeSignature if the signature
|
236
|
+
# could not be found.
|
237
|
+
#
|
238
|
+
def from_scope!( scope )
|
239
|
+
GUARD.synchronize do
|
240
|
+
if scope?( scope )
|
241
|
+
restore_scope!( scope )
|
242
|
+
else
|
243
|
+
raise InvalidScopeSignature
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Dump the current tracker to the filesystem.
|
249
|
+
#
|
250
|
+
def to_scope!
|
251
|
+
GUARD.synchronize do
|
252
|
+
scope = Time.now.to_i
|
253
|
+
dump_scope!( scope )
|
254
|
+
scope
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Full path the scrooge configuration file.
|
259
|
+
#
|
260
|
+
def configuration_file
|
261
|
+
@configuration_file ||= File.join( config, CONFIGURATION_FILE )
|
262
|
+
end
|
263
|
+
|
264
|
+
private
|
265
|
+
|
266
|
+
def restore_scope!( scope ) #:nodoc:
|
267
|
+
tracker = Scrooge::Tracker::App.new
|
268
|
+
tracker.marshal_load( scope_from_yaml( scope ) )
|
269
|
+
tracker
|
270
|
+
end
|
271
|
+
|
272
|
+
def dump_scope!( scope ) #:nodoc:
|
273
|
+
ensure_scope_path( scope ) do
|
274
|
+
File.open( scope_path( scope, SCOPE_FILE ), 'w' ) do |io|
|
275
|
+
scope_to_yaml( io )
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def scope_from_yaml( scope ) #:nodoc:
|
281
|
+
YAML.load( IO.read( scope_path( scope.to_s, SCOPE_FILE ) ) )
|
282
|
+
end
|
283
|
+
|
284
|
+
def scope_to_yaml( io ) #:nodoc:
|
285
|
+
YAML.dump( Scrooge::Base.profile.tracker.marshal_dump, io )
|
286
|
+
end
|
287
|
+
|
288
|
+
def ensure_scope_path( scope ) #:nodoc:
|
289
|
+
makedir_unless_exist( scope_path( scope ) )
|
290
|
+
yield if block_given?
|
291
|
+
end
|
292
|
+
|
293
|
+
def ensure_scopes_path #:nodoc:
|
294
|
+
makedir_unless_exist( scopes_path )
|
295
|
+
yield if block_given?
|
296
|
+
end
|
297
|
+
|
298
|
+
def makedir_unless_exist( path ) #:nodoc:
|
299
|
+
FileUtils.makedirs( path ) unless File.exist?( path )
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|