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
@@ -1,159 +0,0 @@
1
- module Scrooge
2
- module Orm
3
- class ActiveRecord < Base
4
- module ScroogeAttributes
5
-
6
- module SingletonMethods
7
-
8
- private
9
-
10
- # Define an attribute reader method. Cope with nil column.
11
- def define_read_method(symbol, attr_name, column)
12
- cast_code = column.type_cast_code('v') if column
13
- access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
14
-
15
- unless attr_name.to_s == self.primary_key.to_s
16
- access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
17
- end
18
-
19
- if cache_attribute?(attr_name)
20
- access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
21
- end
22
- evaluate_attribute_method attr_name, "def #{symbol}; register_with_scrooge!(:#{attr_name}, 'define read method' ); #{access_code}; end"
23
- end
24
-
25
- # Define read method for serialized attribute.
26
- def define_read_method_for_serialized_attribute(attr_name)
27
- evaluate_attribute_method attr_name, "def #{attr_name}; register_with_scrooge!(:#{attr_name}, 'define read method for serialized attribute' ); unserialize_attribute('#{attr_name}'); end"
28
- end
29
-
30
- # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
31
- # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone.
32
- def define_read_method_for_time_zone_conversion(attr_name)
33
- method_body = <<-EOV
34
- def #{attr_name}(reload = false)
35
- register_with_scrooge!(:#{attr_name}, 'define read method for time zone conversion' )
36
- cached = @attributes_cache['#{attr_name}']
37
- return cached if cached && !reload
38
- time = read_attribute('#{attr_name}')
39
- @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
40
- end
41
- EOV
42
- evaluate_attribute_method attr_name, method_body
43
- end
44
-
45
- end
46
-
47
- module InstanceMethods
48
-
49
- # Attach to AR::Base#read_attribute.
50
- #
51
- def read_attribute(attr_name)
52
- register_with_scrooge!( attr_name, 'read attribute' )
53
- super( attr_name )
54
- end
55
-
56
- # Attach to AR::Base#read_attribute_before_typecast.
57
- #
58
- def read_attribute_before_type_cast(attr_name)
59
- register_with_scrooge!( attr_name, 'read attribute before type cast' )
60
- super(attr_name)
61
- end
62
-
63
- private
64
-
65
- def register_with_scrooge!( attr_name, caller ) #:nodoc:
66
- if ::Scrooge::Base.profile.orm.track?
67
- # Avoid string interpolation if not required
68
- ::Scrooge::Base.profile.log( "Register attribute #{attr_name.inspect} from #{caller}" ) if ::Scrooge::Base.profile.verbose?
69
- Thread.scrooge_resource << [self.class.base_class, attr_name]
70
- end
71
- end
72
-
73
- def missing_attribute(attr_name, stack) #:nodoc:
74
- if Scrooge::Base.profile.raise_on_missing_attribute?
75
- super(attr_name, stack)
76
- else
77
- logger.info "[Scrooge] missing attribute #{attr_name.to_s}"
78
- reload( :select => '*' )
79
- end
80
- end
81
-
82
- end
83
-
84
- end
85
-
86
- class << self
87
-
88
- # Inject Scrooge ActiveRecord attribute tracking.
89
- #
90
- def install!
91
- ::ActiveRecord::Base.send( :extend, Scrooge::Orm::ActiveRecord::ScroogeAttributes::SingletonMethods )
92
- ::ActiveRecord::Base.send( :include, Scrooge::Orm::ActiveRecord::ScroogeAttributes::InstanceMethods )
93
- end
94
-
95
- # Determine if the ActiveRecord attribute tracker has already been installed.
96
- #
97
- def installed?
98
- begin
99
- ::ActiveRecord::Base.included_modules.include?( Scrooge::Orm::ActiveRecord::ScroogeAttributes )
100
- rescue => exception
101
- end
102
- end
103
-
104
- end
105
-
106
- # Generate scope helpers for a given resource.
107
- #
108
- def scope_to( resource )
109
- resource.models.each do |model|
110
- scope_resource_to_model( resource, model )
111
- end
112
- end
113
-
114
- # Generate scope helpers for a given model and resource.
115
- #
116
- def scope_resource_to_model( resource, model )
117
- method_name = resource_scope_method( resource )
118
- klass = klass_for_model( model )
119
- unless resource_scope_method?( resource, klass )
120
- klass.instance_eval(<<-EOS, __FILE__, __LINE__)
121
- def #{method_name}
122
- with_scope( { :find => { :select => '#{model.to_sql}' } }) do
123
- yield
124
- end
125
- end
126
- EOS
127
- end
128
- end
129
-
130
- # Returns a lookup key from a given String or AR klass
131
- #
132
- def name( model )
133
- model = model.to_const!(false) if model.is_a?(String)
134
- model.respond_to?(:base_class) ? model.base_class.to_s : model.to_s
135
- end
136
-
137
- # Returns a table name from a given String or AR klass
138
- #
139
- def table_name( model )
140
- model = model.to_const!(false) if model.is_a?(String)
141
- model.table_name
142
- end
143
-
144
- # Returns a primary key from a given String or AR klass
145
- #
146
- def primary_key( model )
147
- model = model.to_const!(false) if model.is_a?(String)
148
- model.primary_key
149
- end
150
-
151
- private
152
-
153
- def klass_for_model( model ) #:nodoc:
154
- model.model.is_a?(String) ? model.model.to_const!(false) : model.model
155
- end
156
-
157
- end
158
- end
159
- end
@@ -1,102 +0,0 @@
1
- module Scrooge
2
- module Orm
3
-
4
- autoload :ActiveRecord, 'scrooge/orm/active_record'
5
-
6
- class Base < Scrooge::Base
7
-
8
- # ORM agnostic base class.
9
-
10
- class NotImplemented < StandardError
11
- end
12
-
13
- class << self
14
-
15
- # Instantiate and optionally install from a given ORM signature.
16
- #
17
- def instantiate( orm_signature )
18
- orm_instance = "scrooge/orm/#{orm_signature.to_s}".to_const!
19
- orm_instance.class.install! unless orm_instance.class.installed?
20
- orm_instance
21
- end
22
-
23
- # Subclasses is required to implemented an installation hook.
24
- #
25
- def install!
26
- raise NotImplemented
27
- end
28
-
29
- # Subclasses should be able to determine if it's already been injected into
30
- # the underlying ORM package.
31
- #
32
- def installed?
33
- raise NotImplemented
34
- end
35
-
36
- end
37
-
38
- # Generate scope helpers for a Resource and it's related Model trackers.
39
- #
40
- def scope_to( resource )
41
- raise NotImplemented
42
- end
43
-
44
- # Generate scope helpers for a given Resource and Model tracker.
45
- #
46
- def scope_resource_to_model( resource, model )
47
- raise NotImplemented
48
- end
49
-
50
- # Returns a lookup key from a given String or class
51
- #
52
- def name( model )
53
- raise NotImplemented
54
- end
55
-
56
- # Returns a table name from a given String or class
57
- #
58
- def table_name( model )
59
- raise NotImplemented
60
- end
61
-
62
- # Returns a primary key from a given String or class
63
- #
64
- def primary_key( model )
65
- raise NotImplemented
66
- end
67
-
68
- # Generates a sanitized method name for a given resource.
69
- #
70
- def resource_scope_method( resource )
71
- "scope_to_#{resource.signature}".to_sym
72
- end
73
-
74
- # Determine if a scope method has already been generated for a given
75
- # Resource and klass.
76
- #
77
- def resource_scope_method?( resource, klass )
78
- klass.respond_to?( resource_scope_method( resource ) )
79
- end
80
-
81
- # Only track if the current profile is configured for tracking and a tracker
82
- # resource is active, iow. we're in the scope of a request.
83
- #
84
- def track?
85
- profile.track? && resource?
86
- end
87
-
88
- # Do we have access to Resource Tracker instance ?
89
- #
90
- def resource?
91
- !resource().nil?
92
- end
93
-
94
- # Delegate to Thread.current
95
- #
96
- def resource
97
- Thread.current[:scrooge_resource]
98
- end
99
-
100
- end
101
- end
102
- end
@@ -1,223 +0,0 @@
1
- require 'yaml'
2
-
3
- module Scrooge
4
- class Profile
5
-
6
- # A Profile for Scrooge that holds configuration, determines if we're tracking or
7
- # scoping and provides access to a Tracker, ORM, Storage and Framework instance.
8
-
9
- GUARD = Mutex.new
10
-
11
- class << self
12
-
13
- # Setup a new instance from the path to a YAML configuration file and a
14
- # given environment.
15
- #
16
- def setup( path, environment )
17
- begin
18
- new( read_config( path, environment ) )
19
- rescue ::Errno::ENOENT
20
- puts "Scrooge Configuration file not available."
21
- end
22
- end
23
-
24
- # Pairs profile setup with the host framework.
25
- #
26
- def setup!
27
- setup( framework.configuration_file, framework.environment )
28
- end
29
-
30
- # Yields an instance to a wrapper of the host framework.
31
- #
32
- def framework
33
- @@framework ||= Scrooge::Framework::Base.instantiate
34
- end
35
-
36
- private
37
-
38
- def read_config( path, environment ) #:nodoc:
39
- YAML.load( IO.read( path ) )[environment.to_s]
40
- end
41
-
42
- end
43
-
44
- attr_reader :options
45
-
46
- def initialize( options = {} )
47
- self.options = options
48
- end
49
-
50
- # options writer that attempts to reconfigure for any configuration changes.
51
- #
52
- def options=( options )
53
- @options = options
54
- configure!
55
- end
56
-
57
- # Delegates to the underlying ORM framework.
58
- #
59
- def orm
60
- @orm_instance ||= Scrooge::Orm::Base.instantiate( @orm )
61
- end
62
-
63
- # Delegates to the current storage backend.
64
- #
65
- def storage
66
- @storage_instance ||= Scrooge::Storage::Base.instantiate( @storage )
67
- end
68
-
69
- # Delegates to the Application Tracker.
70
- #
71
- def tracker
72
- @tracker_instance ||= Scrooge::Tracker::App.new
73
- end
74
-
75
- # Yields a strategt instance.
76
- #
77
- def strategy
78
- @strategy_instance ||= "scrooge/strategy/#{@strategy.to_s}".to_const!
79
- end
80
-
81
- # Delegates to the current framework.
82
- #
83
- def framework
84
- self.class.framework
85
- end
86
-
87
- # Log a message to the framework's logger.
88
- #
89
- def log( message, flush = false )
90
- framework.log( message, flush ) rescue ''
91
- end
92
-
93
- # Log a message to the framework's logger.
94
- #
95
- def verbose_log( message, flush = false )
96
- log( message, flush ) if verbose?
97
- end
98
-
99
- # Are we tracking ?
100
- #
101
- def track?
102
- GUARD.synchronize do
103
- @track == true
104
- end
105
- end
106
-
107
- # Enable the tracking phase
108
- #
109
- def start_tracking!
110
- GUARD.synchronize do
111
- @track = true
112
- end
113
- end
114
-
115
- # Disable the tracking phase
116
- #
117
- def stop_tracking!
118
- GUARD.synchronize do
119
- @track = false
120
- end
121
- end
122
-
123
- # The active scope signature
124
- #
125
- def scope
126
- @scope
127
- end
128
-
129
- # Assign the current scope from a given signature or tracker
130
- #
131
- def scope=( signature_or_tracker )
132
- if signature_or_tracker.kind_of?( Scrooge::Tracker::Base )
133
- scope_to_tracker!( signature_or_tracker )
134
- else
135
- scope_to_signature!( signature_or_tracker )
136
- end
137
- end
138
-
139
- # Should Scrooge inject itself ?
140
- #
141
- def enabled?
142
- @enabled
143
- end
144
-
145
- # Should Scrooge be chatty ?
146
- #
147
- def verbose?
148
- @verbose
149
- end
150
-
151
- # Should we raise on missing attributes ?
152
- #
153
- def raise_on_missing_attribute?
154
- @on_missing_attribute == :raise
155
- end
156
-
157
- # Expose the warmup phase during which tracking occur.
158
- #
159
- def warmup
160
- @warmup
161
- end
162
-
163
- # A session key that indicates a logged in / private session.
164
- #
165
- def logged_in_session
166
- @logged_in_session
167
- end
168
-
169
- private
170
-
171
- def configure! #:nodoc:
172
- @orm = configure_with( @options['orm'], [:active_record], :active_record )
173
- @storage = configure_with( @options['storage'], [:memory], :memory )
174
- @strategy = configure_with( @options['strategy'], [:track, :scope, :track_then_scope], :track )
175
- @scope = configure_with( @options['scope'].to_s, framework_scopes, ENV['scope'] )
176
- @warmup = configure_with( @options['warmup'], 0..14400, 0 )
177
- @enabled = configure_with( @options['enabled'], [true, false], false )
178
- @on_missing_attribute = configure_with( @options['on_missing_attribute'], [:reload, :raise], :reload )
179
- @verbose = configure_with( @options['verbose'], [true, false], false )
180
- @logged_in_session = configure_with( @options['logged_in_session'], nil, :user_id )
181
- reset_backends!
182
- memoize_backends!
183
- end
184
-
185
- def configure_with( given, valid, default ) #:nodoc:
186
- if given
187
- valid && valid.include?( given ) ? given : default
188
- else
189
- default
190
- end
191
- end
192
-
193
- def framework_scopes #:nodoc:
194
- framework.scopes rescue []
195
- end
196
-
197
- def reset_backends! #:nodoc:
198
- @orm_instance = nil
199
- @tracker_instance = nil
200
- @storage_instance = nil
201
- end
202
-
203
- # Force constant lookups as autoload is not threadsafe.
204
- #
205
- def memoize_backends! #:nodoc:
206
- framework() rescue nil
207
- orm()
208
- storage()
209
- tracker()
210
- end
211
-
212
- def scope_to_signature!( scope_signature ) #:nodoc:
213
- log "Scope to signature #{scope_signature} ..."
214
- @tracker_instance = framework.from_scope!( scope_signature )
215
- end
216
-
217
- def scope_to_tracker!( tracker ) #:nodoc:
218
- log "Scope to tracker #{tracker.inspect} ..."
219
- @tracker_instance = tracker
220
- end
221
-
222
- end
223
- end
@@ -1,46 +0,0 @@
1
- module Scrooge
2
- module Storage
3
-
4
- autoload :Memory, 'scrooge/storage/memory'
5
-
6
- class Base < Scrooge::Base
7
- class NotImplemented < StandardError
8
- end
9
-
10
- # A Single Mutex for all Storage subclasses as their's only one storage instance.
11
- #
12
- GUARD = Mutex.new
13
-
14
- NAMESPACE = 'scrooge_storage'.freeze
15
-
16
- class << self
17
-
18
- # Yields a storage instance from a given signature.
19
- #
20
- def instantiate( storage_signature )
21
- "scrooge/storage/#{storage_signature.to_s}".to_const!
22
- end
23
-
24
- end
25
-
26
- # Retrieve a given tracker from storage.
27
- #
28
- def read( tracker )
29
- raise NotImplemented
30
- end
31
-
32
- # Persist a given tracker to storage.
33
- #
34
- def write( tracker, buffered = true )
35
- raise NotImplemented
36
- end
37
-
38
- # Namespace lookup keys.
39
- #
40
- def expand_key( key )
41
- "#{NAMESPACE}/#{key}"
42
- end
43
-
44
- end
45
- end
46
- end
@@ -1,25 +0,0 @@
1
- module Scrooge
2
- module Storage
3
- class Memory < Base
4
-
5
- attr_reader :storage
6
-
7
- def initialize
8
- @storage = {}
9
- end
10
-
11
- def read( tracker )
12
- GUARD.synchronize do
13
- @storage[tracker.signature]
14
- end
15
- end
16
-
17
- def write( tracker, buffered = true )
18
- GUARD.synchronize do
19
- @storage[tracker.signature] = tracker
20
- end
21
- end
22
-
23
- end
24
- end
25
- end
@@ -1,74 +0,0 @@
1
- module Scrooge
2
- module Strategy
3
-
4
- autoload :Controller, 'scrooge/strategy/controller'
5
- autoload :Stage, 'scrooge/strategy/stage'
6
- autoload :Scope, 'scrooge/strategy/scope'
7
- autoload :Track, 'scrooge/strategy/track'
8
- autoload :TrackThenScope, 'scrooge/strategy/track_then_scope'
9
-
10
- class Base
11
-
12
- class NoStages < StandardError
13
- end
14
-
15
- @@stages = Hash.new( [] )
16
- @@stages[self.name] = []
17
-
18
- attr_reader :thread
19
-
20
- class << self
21
-
22
- # Stage definition macro.
23
- #
24
- # stage :track, :for => 10.minutes do
25
- # ....
26
- # end
27
- #
28
- def stage( signature, options = {}, &block )
29
- @@stages[self.name] << Scrooge::Strategy::Stage.new( signature, options, &block )
30
- end
31
-
32
- # List all defined stages for this klass.
33
- #
34
- def stages
35
- @@stages[self.name]
36
- end
37
-
38
- # Are there any stages defined ?
39
- #
40
- def stages?
41
- !stages.empty?
42
- end
43
-
44
- # Test teardown helper.
45
- #
46
- def flush!
47
- @@stages[self.name] = []
48
- end
49
-
50
- end
51
-
52
- # Requires at least one stage definition.
53
- #
54
- def initialize
55
- raise NoStages unless self.class.stages?
56
- end
57
-
58
- # Piggy back on stages defined for this klass.
59
- #
60
- def stages
61
- self.class.stages
62
- end
63
-
64
- # Enforce this strategy
65
- #
66
- def execute!
67
- if Scrooge::Base.profile.enabled?
68
- @thread = Scrooge::Strategy::Controller.new( self ).run!
69
- end
70
- end
71
-
72
- end
73
- end
74
- end
@@ -1,31 +0,0 @@
1
- module Scrooge
2
- module Strategy
3
- class Controller
4
-
5
- attr_accessor :strategy
6
- attr_reader :thread
7
-
8
- def initialize( strategy )
9
- @strategy = strategy
10
- end
11
-
12
- # Execute a given strategy
13
- #
14
- def run!
15
- @thread = Thread.new do
16
- Thread.current.abort_on_exception = true
17
- stages.map do |stage|
18
- stage.execute!
19
- end
20
- end
21
- end
22
-
23
- private
24
-
25
- def stages #:nodoc:
26
- @strategy.stages
27
- end
28
-
29
- end
30
- end
31
- end
@@ -1,15 +0,0 @@
1
- module Scrooge
2
- module Strategy
3
- class Scope < Base
4
-
5
- stage :scope do
6
-
7
- log( "Scope ...", true )
8
- self.scope = scope if framework.scope?( scope )
9
- framework.install_scope_middleware( tracker )
10
-
11
- end
12
-
13
- end
14
- end
15
- end