methodmissing-scrooge 1.0.4 → 2.0.0

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.
Files changed (58) hide show
  1. data/README.textile +139 -449
  2. data/Rakefile +20 -19
  3. data/VERSION.yml +2 -2
  4. data/lib/attributes_proxy.rb +121 -0
  5. data/lib/scrooge.rb +206 -47
  6. data/rails/init.rb +1 -10
  7. data/test/helper.rb +88 -0
  8. data/test/models/mysql_user.rb +4 -0
  9. data/test/scrooge_test.rb +75 -0
  10. data/test/setup.rb +3 -0
  11. metadata +11 -76
  12. data/assets/config/scrooge.yml.template +0 -27
  13. data/lib/scrooge/core/string.rb +0 -29
  14. data/lib/scrooge/core/symbol.rb +0 -21
  15. data/lib/scrooge/core/thread.rb +0 -26
  16. data/lib/scrooge/framework/base.rb +0 -315
  17. data/lib/scrooge/framework/rails.rb +0 -132
  18. data/lib/scrooge/middleware/tracker.rb +0 -46
  19. data/lib/scrooge/orm/active_record.rb +0 -159
  20. data/lib/scrooge/orm/base.rb +0 -102
  21. data/lib/scrooge/profile.rb +0 -223
  22. data/lib/scrooge/storage/base.rb +0 -46
  23. data/lib/scrooge/storage/memory.rb +0 -25
  24. data/lib/scrooge/strategy/base.rb +0 -74
  25. data/lib/scrooge/strategy/controller.rb +0 -31
  26. data/lib/scrooge/strategy/scope.rb +0 -15
  27. data/lib/scrooge/strategy/stage.rb +0 -77
  28. data/lib/scrooge/strategy/track.rb +0 -19
  29. data/lib/scrooge/strategy/track_then_scope.rb +0 -41
  30. data/lib/scrooge/tracker/app.rb +0 -161
  31. data/lib/scrooge/tracker/base.rb +0 -66
  32. data/lib/scrooge/tracker/model.rb +0 -150
  33. data/lib/scrooge/tracker/resource.rb +0 -181
  34. data/spec/fixtures/config/scrooge/scopes/1234567891/scope.yml +0 -2
  35. data/spec/fixtures/config/scrooge.yml +0 -20
  36. data/spec/helpers/framework/rails/cache.rb +0 -25
  37. data/spec/spec_helper.rb +0 -55
  38. data/spec/units/scrooge/core/string_spec.rb +0 -21
  39. data/spec/units/scrooge/core/symbol_spec.rb +0 -13
  40. data/spec/units/scrooge/core/thread_spec.rb +0 -15
  41. data/spec/units/scrooge/framework/base_spec.rb +0 -160
  42. data/spec/units/scrooge/framework/rails_spec.rb +0 -40
  43. data/spec/units/scrooge/orm/base_spec.rb +0 -61
  44. data/spec/units/scrooge/profile_spec.rb +0 -79
  45. data/spec/units/scrooge/storage/base_spec.rb +0 -35
  46. data/spec/units/scrooge/storage/memory_spec.rb +0 -20
  47. data/spec/units/scrooge/strategy/base_spec.rb +0 -62
  48. data/spec/units/scrooge/strategy/controller_spec.rb +0 -26
  49. data/spec/units/scrooge/strategy/scope_spec.rb +0 -18
  50. data/spec/units/scrooge/strategy/stage_spec.rb +0 -35
  51. data/spec/units/scrooge/strategy/track_spec.rb +0 -19
  52. data/spec/units/scrooge/strategy/track_then_scope_spec.rb +0 -22
  53. data/spec/units/scrooge/tracker/app_spec.rb +0 -68
  54. data/spec/units/scrooge/tracker/base_spec.rb +0 -29
  55. data/spec/units/scrooge/tracker/model_spec.rb +0 -79
  56. data/spec/units/scrooge/tracker/resource_spec.rb +0 -115
  57. data/spec/units/scrooge_spec.rb +0 -13
  58. data/tasks/scrooge.rake +0 -43
data/README.textile CHANGED
@@ -1,353 +1,58 @@
1
1
  h1. Scrooge
2
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.
3
+ h4. This is a complete rewrite from the initial coverage at "igvita.com":http://www.igvita.com/2009/02/27/activerecord-optimization-with-scrooge/ - read on below
6
4
 
7
- This is mostly an experiment into unobtrusive tracking, respecting development workflows
8
- and understanding Rack internals better.
5
+ Many thanks to Stephen Sykes ( "pennysmalls.com":http://pennysmalls.com ) for his time spent on shaping, implementing and troubleshooting this release.
6
+
7
+ An ActiveRecord attribute tracker to ensure production Ruby applications only fetch the database content needed to minimize wire traffic and reduce conversion overheads to native Ruby types.
9
8
 
10
9
  h2. Why bother ?
11
10
 
12
11
  * Object conversion and moving unnecessary data is both expensive and tax existing infrastructure in high load setups
13
12
  * 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
13
 
15
- h2. Suggested Use
16
-
17
- There's 3 basic modes of operation :
18
-
19
- * Track : Track attribute access to dump a representative scope profile.
20
-
21
- * Scope : Scope the process and related resources to a previously persisted scope profile.
22
-
23
- * Track then scope : A multi-stage strategy that tracks attribute access for a given warmup period, aggregates the results
24
- across multiple processes and enforce a scoping policy representative of the tracking phase.
25
-
26
- h2. Resources
27
-
28
- A resource is :
29
-
30
- * A controller and action endpoint ( inferred through framework specific routing )
31
- * A content type / format - a PDF representation may have different Model attribute requirements than a vanilla ERB view.
32
- * Request method - typically popular public facing GET requests
33
- * Public or Private as a logged in / authenticated representation likely have different attribute requirements.
34
-
35
- All Model to attribute mappings is tracked on a per Resource basis.Multiple Models per Resource is supported.
36
-
37
- h2. Strategies
38
-
39
- h4. Tracking
40
-
41
- In tracking mode Scrooge installs filters ( either through Rack middleware or framework specific hooks ) that track attribute access on a per Resource basis.
42
-
43
- A Kernel#at_exit callback dumps and timestamps this profile ( or scope ) to eg. *framework_configuration_directory/config/scopes/1234147851/scope.yml*
44
-
45
- This typically works well with functional or integration testing and can yield a substantial birds eye view of attribute
46
- use.The accuracy is directly proportional to test coverage and the quality of the test suite.
47
-
48
- Example log output :
49
-
50
- <pre>
51
- <code>
52
- Processing HotelsController#index (for 0.0.0.0 at 2009-02-09 02:55:55) [GET]
53
- Parameters: {"action"=>"index", "controller"=>"hotels"}
54
- Hotel Load (0.3ms) SELECT * FROM `hotels` LIMIT 0, 15
55
- Rendering template within layouts/application
56
- Rendering hotels/index
57
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 491) LIMIT 1
58
- [Scrooge] read attribute updated_at
59
- Rendered hotels/_hotel (2.7ms)
60
- Rendered shared/_header (0.1ms)
61
- Rendered shared/_navigation (0.3ms)
62
- Missing template hotels/_index_sidebar.erb in view path app/views
63
- Rendered shared/_sidebar (0.1ms)
64
- Rendered shared/_footer (0.1ms)
65
- Completed in 91ms (View: 90, DB: 1) | 200 OK [http://test.host/hotels]
66
- SQL (0.3ms) ROLLBACK
67
- SQL (0.1ms) BEGIN
68
- </code>
69
- </pre>
70
-
71
- An example scope / profile, saved to disk :
14
+ h2. What it does
72
15
 
73
16
  <pre>
74
17
  <code>
75
- ---
76
- - hotels_show_get:
77
- :action: show
78
- :controller: hotels
79
- :method: :get
80
- :is_public: true
81
- :format: "*/*"
82
- :models:
83
- - Address:
84
- - line1
85
- - line2
86
- - created_at
87
- - postcode
88
- - updated_at
89
- - country_id
90
- - county
91
- - location_id
92
- - town
93
- - hotel_id
94
- - Hotel:
95
- - important_notes
96
- - location_id
97
- - locations_index_get:
98
- :action: index
99
- :controller: locations
100
- :method: :get
101
- :is_public: true
102
- :format: "*/*"
103
- :models:
104
- - Location:
105
- - name
106
- - created_at
107
- - code
108
- - updated_at
109
- - level
110
- - id
111
- - countries_index_get:
112
- :action: index
113
- :controller: countries
114
- :method: :get
115
- :is_public: true
116
- :format: "*/*"
117
- :models:
118
- - Country:
119
- - name
120
- - created_at
121
- - code
122
- - updated_at
123
- - id
124
- - location_id
125
- - continent_id
126
- - hotels_index_get:
127
- :action: index
128
- :controller: hotels
129
- :method: :get
130
- :is_public: true
131
- :format: "*/*"
132
- :models:
133
- - Hotel:
134
- - from_price
135
- - narrative
136
- - star_rating
137
- - latitude
138
- - created_at
139
- - hotel_name
140
- - updated_at
141
- - important_notes
142
- - id
143
- - apt
144
- - location_id
145
- - nearest_tube
146
- - longitude
147
- - telephone
148
- - nearest_rail
149
- - location_name
150
- - distance
151
- - Image:
152
- - thumbnail_width
153
- - created_at
154
- - title
155
- - updated_at
156
- - url
157
- - thumbnail_height
158
- - height
159
- - thumbnail_url
160
- - has_thumbnail
161
- - hotel_id
162
- - width
18
+ Processing HotelsController#show (for 127.0.0.1 at 2009-03-12 14:32:45) [GET]
19
+ Parameters: {"action"=>"show", "id"=>"8699-radisson-hotel-waterfront-cape-town", "controller"=>"hotels"}
20
+ Hotel Load Scrooged (0.3ms) SELECT `hotels`.id FROM `hotels` WHERE (`hotels`.`id` = 8699)
21
+ Rendering template within layouts/application
22
+ Rendering hotels/show
23
+ Hotel Load (0.2ms) SELECT `hotels`.location_id,`hotels`.hotel_name,`hotels`.location,`hotels`.from_price,`hotels`.star_rating,`hotels`.apt,`hotels`.latitude,`hotels`.longitude,`hotels`.distance,`hotels`.narrative,`hotels`.telephone,`hotels`.important_notes,`hotels`.nearest_tube,`hotels`.nearest_rail,`hotels`.created_at,`hotels`.updated_at FROM `hotels` WHERE (`hotels`.`id` = 8699)
24
+ Image Load Scrooged (0.2ms) SELECT `images`.id FROM `images` WHERE (`images`.hotel_id = 8699) LIMIT 1
25
+ Image Load (0.2ms) SELECT `images`.hotel_id,`images`.title,`images`.url,`images`.width,`images`.height,`images`.thumbnail_url,`images`.thumbnail_width,`images`.thumbnail_height,`images`.has_thumbnail,`images`.created_at,`images`.updated_at FROM `images` WHERE (`images`.`id` = 488)
26
+ Rendered shared/_header (0.0ms)
27
+ Rendered shared/_navigation (0.2ms)
28
+ Image Load Scrooged (0.2ms) SELECT `images`.id FROM `images` WHERE (`images`.hotel_id = 8699)
29
+ CACHE (0.0ms) SELECT `images`.hotel_id,`images`.title,`images`.url,`images`.width,`images`.height,`images`.thumbnail_url,`images`.thumbnail_width,`images`.thumbnail_height,`images`.has_thumbnail,`images`.created_at,`images`.updated_at FROM `images` WHERE (`images`.`id` = 488)
30
+ Address Columns (44.8ms) SHOW FIELDS FROM `addresses`
31
+ Address Load Scrooged (0.5ms) SELECT `addresses`.id FROM `addresses` WHERE (`addresses`.hotel_id = 8699) LIMIT 1
32
+ Rendered hotels/_show_sidebar (49.4ms)
33
+ Rendered shared/_footer (0.1ms)
34
+ Completed in 56ms (View: 8, DB: 46) | 200 OK [http://localhost/hotels/8699-radisson-hotel-waterfront-cape-town]
35
+
36
+
37
+ Processing HotelsController#show (for 127.0.0.1 at 2009-03-12 14:32:48) [GET]
38
+ Parameters: {"action"=>"show", "id"=>"8699-radisson-hotel-waterfront-cape-town", "controller"=>"hotels"}
39
+ Hotel Load Scrooged (0.3ms) SELECT `hotels`.narrative,`hotels`.from_price,`hotels`.star_rating,`hotels`.hotel_name,`hotels`.id FROM `hotels` WHERE (`hotels`.`id` = 8699)
40
+ Rendering template within layouts/application
41
+ Rendering hotels/show
42
+ Image Load Scrooged (0.3ms) SELECT `images`.url,`images`.id,`images`.height,`images`.width FROM `images` WHERE (`images`.hotel_id = 8699) LIMIT 1
43
+ Rendered shared/_header (0.0ms)
44
+ Rendered shared/_navigation (0.2ms)
45
+ Image Load Scrooged (0.3ms) SELECT `images`.thumbnail_width,`images`.id,`images`.thumbnail_height,`images`.thumbnail_url FROM `images` WHERE (`images`.hotel_id = 8699)
46
+ Address Load Scrooged (0.2ms) SELECT `addresses`.id FROM `addresses` WHERE (`addresses`.hotel_id = 8699) LIMIT 1
47
+ Rendered hotels/_show_sidebar (1.3ms)
48
+ Rendered shared/_footer (0.0ms)
49
+ Completed in 7ms (View: 5, DB: 1) | 200 OK [http://localhost/hotels/8699-radisson-hotel-waterfront-cape-town]
163
50
  </code>
164
- </pre>
165
-
166
- h4. Scope
167
-
168
- 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.
169
-
170
- This is typically pushed to production where a hybrid ( track then scope strategy) mode of operation is frowned upon and adjusted for each major release or deployment.
51
+ </pre>
171
52
 
172
- Example log output :
173
-
174
- <pre>
175
- <code>
176
- Processing HotelsController#index (for 0.0.0.0 at 2009-02-09 02:59:41) [GET]
177
- Parameters: {"action"=>"index", "controller"=>"hotels"}
178
- 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
179
- Rendering template within layouts/application
180
- Rendering hotels/index
181
- 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
182
- Rendered hotels/_hotel (2.8ms)
183
- Rendered shared/_header (0.1ms)
184
- Rendered shared/_navigation (0.3ms)
185
- Missing template hotels/_index_sidebar.erb in view path app/views
186
- Rendered shared/_sidebar (0.1ms)
187
- Rendered shared/_footer (0.1ms)
188
- Completed in 90ms (View: 5, DB: 1) | 200 OK [http://test.host/hotels]
189
- SQL (0.1ms) ROLLBACK
190
- SQL (0.1ms) BEGIN
191
- </code>
192
- </pre>
193
-
194
- h4. Track then scope
195
-
196
- Multi-stage and self configuring strategy that tracks attribute access for a given warmup period, synchronize the results across n-1 processes, aggregate the results to be representative of the whole cluster ( or seamless fallback to a single process ), remove the tracking filters and install functionality that scopes database access to that of the tracking phase.
197
-
198
- Recommended for production use.
199
-
200
- Example log output :
53
+ h2. Suggested Use
201
54
 
202
- <pre>
203
- <code>
204
- Processing HotelsController#index (for 127.0.0.1 at 2009-02-16 00:00:58) [GET]
205
- Parameters: {"action"=>"index", "controller"=>"hotels"}
206
- Hotel Load (0.5ms) SELECT * FROM `hotels` LIMIT 0, 15
207
- Hotel Columns (7.7ms) SHOW FIELDS FROM `hotels`
208
- SQL (3.9ms) SELECT count(*) AS count_all FROM `hotels`
209
- Rendering template within layouts/application
210
- Rendering hotels/index
211
- Image Load (0.5ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 11381) LIMIT 1
212
- Image Columns (3.6ms) SHOW FIELDS FROM `images`
213
- Rendered hotels/_hotel (200.2ms)
214
- Image Load (0.4ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 11382) LIMIT 1
215
- Rendered hotels/_hotel (2.4ms)
216
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 11697) LIMIT 1
217
- Rendered hotels/_hotel (1.8ms)
218
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 12693) LIMIT 1
219
- Rendered hotels/_hotel (1.7ms)
220
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 12738) LIMIT 1
221
- Rendered hotels/_hotel (1.6ms)
222
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 12886) LIMIT 1
223
- Rendered hotels/_hotel (1.9ms)
224
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13007) LIMIT 1
225
- Rendered hotels/_hotel (1.8ms)
226
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13074) LIMIT 1
227
- Rendered hotels/_hotel (1.5ms)
228
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13077) LIMIT 1
229
- Rendered hotels/_hotel (1.6ms)
230
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13078) LIMIT 1
231
- Rendered hotels/_hotel (1.8ms)
232
- Image Load (0.3ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13079) LIMIT 1
233
- Rendered hotels/_hotel (2.4ms)
234
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13080) LIMIT 1
235
- Rendered hotels/_hotel (1.8ms)
236
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13082) LIMIT 1
237
- Rendered hotels/_hotel (1.5ms)
238
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13085) LIMIT 1
239
- Rendered hotels/_hotel (1.8ms)
240
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13105) LIMIT 1
241
- Rendered hotels/_hotel (1.6ms)
242
- Rendered shared/_header (0.4ms)
243
- Rendered shared/_navigation (0.8ms)
244
- Missing template hotels/_index_sidebar.erb in view path app/views
245
- Rendered shared/_sidebar (0.4ms)
246
- Rendered shared/_footer (0.3ms)
247
- Completed in 270ms (View: 243, DB: 20) | 200 OK [http://localhost/hotels]
248
- [Scrooge] Execute stage :synchronize ...
249
- [Scrooge] Uninstalling tracking middleware ...
250
- [Scrooge] Stop tracking ...
251
- [Scrooge] Synchronize results with other processes ...
252
- Cache write: 17619400_63223_756033
253
- Cache read: scrooge_tracker_aggregation
254
- Cache write: scrooge_tracker_aggregation
255
- [Scrooge] Execute stage :aggregate ...
256
- [Scrooge] Aggregate results from other processes ...
257
-
258
-
259
- Processing HotelsController#index (for 127.0.0.1 at 2009-02-16 00:01:37) [GET]
260
- Parameters: {"action"=>"index", "controller"=>"hotels"}
261
- Hotel Load (0.5ms) SELECT * FROM `hotels` LIMIT 0, 15
262
- SQL (0.2ms) SELECT count(*) AS count_all FROM `hotels`
263
- Rendering template within layouts/application
264
- Rendering hotels/index
265
- Image Load (0.3ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 11381) LIMIT 1
266
- Rendered hotels/_hotel (2.0ms)
267
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 11382) LIMIT 1
268
- Rendered hotels/_hotel (1.8ms)
269
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 11697) LIMIT 1
270
- Rendered hotels/_hotel (1.7ms)
271
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 12693) LIMIT 1
272
- Rendered hotels/_hotel (1.6ms)
273
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 12738) LIMIT 1
274
- Rendered hotels/_hotel (1.5ms)
275
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 12886) LIMIT 1
276
- Rendered hotels/_hotel (1.8ms)
277
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13007) LIMIT 1
278
- Rendered hotels/_hotel (1.8ms)
279
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13074) LIMIT 1
280
- Rendered hotels/_hotel (1.4ms)
281
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13077) LIMIT 1
282
- Rendered hotels/_hotel (1.6ms)
283
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13078) LIMIT 1
284
- Rendered hotels/_hotel (1.6ms)
285
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13079) LIMIT 1
286
- Rendered hotels/_hotel (1.8ms)
287
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13080) LIMIT 1
288
- Rendered hotels/_hotel (1.7ms)
289
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13082) LIMIT 1
290
- Rendered hotels/_hotel (1.5ms)
291
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13085) LIMIT 1
292
- Rendered hotels/_hotel (1.7ms)
293
- Image Load (0.2ms) SELECT * FROM `images` WHERE (`images`.hotel_id = 13105) LIMIT 1
294
- Rendered hotels/_hotel (81.1ms)
295
- Rendered shared/_header (0.1ms)
296
- Rendered shared/_navigation (0.3ms)
297
- Missing template hotels/_index_sidebar.erb in view path app/views
298
- Rendered shared/_sidebar (0.1ms)
299
- Rendered shared/_footer (0.1ms)
300
- Completed in 113ms (View: 107, DB: 4) | 200 OK [http://localhost/hotels]
301
- Cache read: scrooge_tracker_aggregation
302
- Cache read: 17619400_63223_756033
303
- [Scrooge] Execute stage :scope ...
304
- [Scrooge] Scope ...
305
-
306
-
307
- Processing HotelsController#index (for 127.0.0.1 at 2009-02-16 00:01:53) [GET]
308
- Parameters: {"action"=>"index", "controller"=>"hotels"}
309
- Hotel Load (0.7ms) 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
310
- SQL (0.2ms) SELECT count(*) AS count_all FROM `hotels`
311
- Rendering template within layouts/application
312
- Rendering hotels/index
313
- Image Load (0.3ms) 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 = 11381) LIMIT 1
314
- Rendered hotels/_hotel (2.0ms)
315
- 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 = 11382) LIMIT 1
316
- Rendered hotels/_hotel (1.7ms)
317
- 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 = 11697) LIMIT 1
318
- Rendered hotels/_hotel (1.7ms)
319
- 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 = 12693) LIMIT 1
320
- Rendered hotels/_hotel (1.7ms)
321
- 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 = 12738) LIMIT 1
322
- Rendered hotels/_hotel (1.5ms)
323
- 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 = 12886) LIMIT 1
324
- Rendered hotels/_hotel (1.7ms)
325
- 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 = 13007) LIMIT 1
326
- Rendered hotels/_hotel (1.8ms)
327
- 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 = 13074) LIMIT 1
328
- Rendered hotels/_hotel (1.4ms)
329
- 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 = 13077) LIMIT 1
330
- Rendered hotels/_hotel (1.5ms)
331
- 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 = 13078) LIMIT 1
332
- Rendered hotels/_hotel (1.8ms)
333
- Image Load (0.3ms) 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 = 13079) LIMIT 1
334
- Rendered hotels/_hotel (1.9ms)
335
- 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 = 13080) LIMIT 1
336
- Rendered hotels/_hotel (1.7ms)
337
- 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 = 13082) LIMIT 1
338
- Rendered hotels/_hotel (1.5ms)
339
- 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 = 13085) LIMIT 1
340
- Rendered hotels/_hotel (1.7ms)
341
- 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 = 13105) LIMIT 1
342
- Rendered hotels/_hotel (1.7ms)
343
- Rendered shared/_header (0.1ms)
344
- Rendered shared/_navigation (0.2ms)
345
- Missing template hotels/_index_sidebar.erb in view path app/views
346
- Rendered shared/_sidebar (0.0ms)
347
- Rendered shared/_footer (0.0ms)
348
- Completed in 34ms (View: 27, DB: 4) | 200 OK [http://localhost/hotels]
349
- </code>
350
- </pre>
55
+ Install, and you're off to the races!
351
56
 
352
57
  h2. Installation
353
58
 
@@ -359,177 +64,162 @@ h4. From Git
359
64
 
360
65
  git pull git://github.com/methodmissing/scrooge.git
361
66
 
362
-
363
67
  h4. As a Gem
364
68
 
365
69
  sudo gem install methodmissing-scrooge -s http://gems.github.com
366
70
 
367
- h2. Configuration
71
+ h2. Stability
368
72
 
369
- 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 ) :
73
+ The whole ActiveRecord test suite passes with scrooge, except for 9 failures related to callsite augmentation (note the SQL reload snippets below).Thoughts on handling or circumventing this much appreciated.
370
74
 
371
75
  <pre>
372
76
  <code>
373
- production:
374
- orm: :active_record
375
- storage: :memory
376
- strategy: :track_then_scope
377
- warmup: 600 # warmup / track for 10 minutes
378
- scope:
379
- on_missing_attribute: :reload # or :raise
380
- logged_in_session: :user_id # session key that represents the logged in user
381
- enabled: true
382
- development:
383
- orm: :active_record
384
- storage: :memory
385
- strategy: :track
386
- warmup: 600 # warmup / track for 10 minutes
387
- scope:
388
- on_missing_attribute: :reload # or :raise
389
- logged_in_session: :user_id # session key that represents the logged in user
390
- enabled: true
391
- test:
392
- orm: :active_record
393
- storage: :memory
394
- strategy: :track
395
- warmup: 600 # warmup / track for 10 minutes
396
- scope:
397
- on_missing_attribute: :reload # or :raise
398
- logged_in_session: :user_id # session key that represents the logged in user
399
- enabled: true
77
+ 2) Failure:
78
+ test_finding_with_includes_on_belongs_to_association_with_same_include_includes_only_once(EagerAssociationTest)
79
+ [/opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.1/test/cases/../../lib/active_record/test_case.rb:31:in `assert_queries'
80
+ /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.3.1/test/cases/associations/eager_test.rb:139:in `test_finding_with_includes_on_belongs_to_association_with_same_include_includes_only_once'
81
+ /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.3.1/lib/active_support/testing/setup_and_teardown.rb:57:in `__send__'
82
+ /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.3.1/lib/active_support/testing/setup_and_teardown.rb:57:in `run']:
83
+ 5 instead of 3 queries were executed.
84
+ Queries:
85
+ SELECT `posts`.id,`posts`.type FROM `posts` WHERE (`posts`.`id` = 1)
86
+ SELECT `posts`.author_id,`posts`.title,`posts`.body,`posts`.comments_count,`posts`.taggings_count FROM `posts` WHERE (`posts`.`id` = 1)
87
+ SELECT `authors`.name,`authors`.id FROM `authors` WHERE (`authors`.`id` = 1)
88
+ SELECT `authors`.author_address_id,`authors`.author_address_extra_id FROM `authors` WHERE (`authors`.`id` = 1)
89
+ SELECT `author_addresses`.id FROM `author_addresses` WHERE (`author_addresses`.`id` = 1) .
90
+ <3> expected but was
91
+ <5>.
400
92
  </code>
401
93
  </pre>
402
94
 
403
- h4. ORM
95
+ To run tests in your environment :
404
96
 
405
- Scrooge is ORM agnostic and ships with an ActiveRecord layer.
97
+ * Configure to run the ActiveRecord test suite as per the "docs":http://github.com/rails/rails/blob/8a17fd1a65ab8e2fa6b36d79603fde0e6ffd083f/activerecord/RUNNING_UNIT_TESTS
98
+ * 'rake test' from within the scrooge root directory
99
+ * It'll attempt to find the path to the ActiveRecord test cases through rubygems
100
+ * Known to work with both 2.2.2, 2.3.0 and the upcoming 2.3.1
101
+
102
+ h2. Initial Benchmarks
103
+
104
+ Passenger, Rails 2.2.2, remote DB :
406
105
 
407
106
  <pre>
408
107
  <code>
409
- orm: :active_record
410
- </pre>
108
+ Without scrooge:
109
+
110
+ Concurrency Level: 1
111
+ Time taken for tests: 68.279156 seconds
112
+ Complete requests: 150
113
+ Failed requests: 0
114
+ Write errors: 0
115
+ Total transferred: 13741201 bytes
116
+ HTML transferred: 13679100 bytes
117
+ Requests per second: 2.20 [#/sec] (mean)
118
+ Time per request: 455.194 [ms] (mean)
119
+ Time per request: 455.194 [ms] (mean, across all concurrent requests)
120
+ Transfer rate: 196.53 [Kbytes/sec] received
121
+
122
+ With scrooge:
123
+
124
+ Concurrency Level: 1
125
+ Time taken for tests: 58.162039 seconds
126
+ Complete requests: 150
127
+ Failed requests: 0
128
+ Write errors: 0
129
+ Total transferred: 13747200 bytes
130
+ HTML transferred: 13685100 bytes
131
+ Requests per second: 2.58 [#/sec] (mean)
132
+ Time per request: 387.747 [ms] (mean)
133
+ Time per request: 387.747 [ms] (mean, across all concurrent requests)
134
+ Transfer rate: 230.82 [Kbytes/sec] received
411
135
  </code>
136
+ </pre>
137
+
138
+ h2. How it works
412
139
 
413
- h4. Storage backend
140
+ h4. Callsites
414
141
 
415
- 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.A stub for future functionality.
142
+ Ruby allows introspection of the call tree through
416
143
 
417
144
  <pre>
418
145
  <code>
419
- storage: :memory
420
- </pre>
421
- </code>
422
-
423
- h4. Verbose
146
+ Kernel#caller
147
+ </code>
148
+ </pre>
424
149
 
425
- Log all tracking interactions to the framework logger when enabled.Disabled for production.
150
+ Scrooge analyzes the last 10 calltree elements that triggered
426
151
 
427
152
  <pre>
428
153
  <code>
429
- verbose: false
154
+ ActiveRecord::Base.find_by_sql
155
+ </code>
430
156
  </pre>
431
- </code>
432
157
 
433
- h4. Strategy
158
+ Lets refer to that as a callsite, or signature.
434
159
 
435
- One of :track, :scope or :track_then_scope .Only the :track_then_scope strategy respects the :warmup configuration option.
436
-
437
- h4. Warmup
438
-
439
- The designated warmup period for the :track_then_scope strategy, given in seconds.Typically 600 to 3600.
160
+ Thus given SQL such as
440
161
 
441
162
  <pre>
442
163
  <code>
443
- warmup: 600
164
+ "SELECT * FROM `images` WHERE (`images`.hotel_id = 11697) LIMIT 1"
165
+ </code>
444
166
  </pre>
445
- </code>
446
167
 
447
- h4. Scope
168
+ Called from our application helper
448
169
 
449
- A scope is a reference to a timestamped Scrooge run where access to Model attributes is tracked on a per Resource basis.
170
+ <pre>
171
+ <code>
172
+ ["/Users/lourens/projects/superbreak_app/vendor/plugins/scrooge/rails/../lib/scrooge.rb:27:in `find_by_sql'", "/Users/lourens/projects/superbreak_app/vendor/rails/activerecord/lib/active_record/base.rb:1557:in `find_every'", "/Users/lourens/projects/superbreak_app/vendor/rails/activerecord/lib/active_record/base.rb:1514:in `find_initial'", "/Users/lourens/projects/superbreak_app/vendor/rails/activerecord/lib/active_record/base.rb:613:in `find'", "/Users/lourens/projects/superbreak_app/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb:60:in `find'", "/Users/lourens/projects/superbreak_app/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb:67:in `first'", "/Users/lourens/projects/superbreak_app/app/helpers/application_helper.rb:60:in `hotel_image'", "/Users/lourens/projects/superbreak_app/app/views/hotels/_hotel.html.erb:4:in `_run_erb_app47views47hotels47_hotel46html46erb_locals_hotel_hotel_counter_object'", "/Users/lourens/projects/superbreak_app/vendor/rails/actionpack/lib/action_view/renderable.rb:36:in `send'", "/Users/lourens/projects/superbreak_app/vendor/rails/actionpack/lib/action_view/renderable.rb:36:in `render'", "/Users/lourens/projects/superbreak_app/vendor/rails/actionpack/lib/action_view/renderable_partial.rb:20:in `render'"]
173
+ </code>
174
+ </pre>
450
175
 
176
+ We can generate a unique callsite identifier with the following calculation :
451
177
  <pre>
452
178
  <code>
453
- scope: 1234567891
179
+ (The above calltree << "SELECT * FROM `images` ).hash " # cut off conditions etc.
180
+ </code>
454
181
  </pre>
455
- </code>
456
182
 
457
- If not scope is given in the configuration, ENV['scope'] would also be considered to facilitate configuration through Capistrano etc.
183
+ Callsites are tracked on a per model ( table name ) basis.
458
184
 
459
- h4. Handling Missing Attributes
185
+ h4. Scope
460
186
 
461
- 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.
187
+ Only SQL statements that meet the following criteria is considered for optimization :
462
188
 
463
- <pre>
464
- <code>
465
- on_missing_attribute: :reload # or :raise
466
- </pre>
467
- </code>
189
+ * A SELECT statement
468
190
 
469
- h4. Private and Public Resources
191
+ * Not an INNER JOIN
470
192
 
471
- An authenticated or logged in resource likely has different model requirements than it's public counterpart.
193
+ * The Model has a primary key defined
472
194
 
473
- <pre>
474
- <code>
475
- logged_in_session: :user_id # session key that represents the logged in user
476
- </pre>
477
- </code>
195
+ h4. How it tracks
196
+
197
+ The ActiveRecord attributes Hash is replaced with a proxy that automatically augments the callsite with any attributes referenced through the Hash lookup keys.
478
198
 
479
- h4. Status
199
+ h4. Storage
480
200
 
481
- Scrooge can be disabled with :
201
+ There's a slight memory hit for each model as the callsites is stored as a class level Hash, which is relatively lightweight and looks like this :
482
202
 
483
203
  <pre>
484
204
  <code>
485
- enabled: false
486
- </pre>
205
+ {-113952497=>#<Set: {"User", "Password"}>}
487
206
  </code>
207
+ </pre>
488
208
 
489
- h2. Rails specific rake tasks.
209
+ h4. Tracking and scoping ?
490
210
 
491
- Ships with tasks to assist in inspecting results.
211
+ The tracking and scoping phases is superseded by this implementation - none of those hindrances anymore.
492
212
 
493
- <pre>
494
- <code>
495
- methodmissing:superbreak_app lourens$ rake scrooge:list
496
- (in /Users/lourens/projects/superbreak_app)
497
- - 1234735663
498
- - 1234735722
499
- - 1234735744
500
- - 1234735790
501
- - 1234738880
502
- methodmissing:superbreak_app lourens$ rake scope=1234735790 scrooge:inspect
503
- (in /Users/lourens/projects/superbreak_app)
504
- #<GET :hotels/show (*/*)
505
- - #<Hotel :important_notes, :location_id>
506
- - #<Address :line1, :created_at, :line2, :postcode, :updated_at, :country_id, :county, :location_id, :town, :hotel_id>
507
-
508
- #<GET :countries/index (*/*)
509
- - #<Country :name, :created_at, :code, :updated_at, :id, :location_id, :continent_id>
510
-
511
- #<GET :locations/index (*/*)
512
- - #<Location :name, :created_at, :code, :updated_at, :level, :id>
513
-
514
- #<GET :hotels/index (*/*)
515
- - #<Image :created_at, :thumbnail_width, :title, :updated_at, :url, :thumbnail_height, :height, :thumbnail_url, :has_thumbnail, :width, :hotel_id>
516
- - #<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>
517
- </code>
518
- </pre>
213
+ h2. Todo
519
214
 
520
- h2. Notes
215
+ * Deeper coverage for Scrooge::AttributesProxy ( pending conversion to a subclass of Hash instead )
521
216
 
522
- This is an initial release, has not yet been battle tested in production and is pending Ruby 1.9.1 compatibility.
217
+ * Extract Scrooge::Callsite
523
218
 
524
- Developed on and for Rails 2.3, known to work with Rails 2.2.2
219
+ * Track both columns AND association invocations off Scrooge::Callsite
525
220
 
526
- h2. Known pending issues
221
+ * Have invoking Model#attributes not associate all columns with the callsite
527
222
 
528
- * Do not track columns that isn't explicitly defined ( AR counter caching checks etc. ) :
529
- <code>
530
- <pre>
531
- ActiveRecord::StatementInvalid (Mysql::Error: Unknown column 'orders.brochures_count' in 'field list': SELECT orders.brochures_count, orders.id FROM `orders` WHERE (`orders`.`id` = 1688139) LIMIT 1)
532
- </code>
533
- </pre>
223
+ * Avoid possible missing attribute exceptions for destroyed objects
534
224
 
535
- * alias_attribute compatibility
225
+ (c) 2009 Lourens Naudé (methodmissing) and Stephen Sykes (sdsykes)