methodmissing-scrooge 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/README.textile +262 -0
  2. data/lib/scrooge/core/string.rb +29 -0
  3. data/lib/scrooge/core/symbol.rb +21 -0
  4. data/lib/scrooge/core/thread.rb +26 -0
  5. data/lib/scrooge/framework/base.rb +304 -0
  6. data/lib/scrooge/framework/rails.rb +107 -0
  7. data/lib/scrooge/middleware/tracker.rb +47 -0
  8. data/lib/scrooge/orm/active_record.rb +143 -0
  9. data/lib/scrooge/orm/base.rb +102 -0
  10. data/lib/scrooge/profile.rb +204 -0
  11. data/lib/scrooge/storage/base.rb +47 -0
  12. data/lib/scrooge/storage/memory.rb +25 -0
  13. data/lib/scrooge/tracker/app.rb +101 -0
  14. data/lib/scrooge/tracker/base.rb +56 -0
  15. data/lib/scrooge/tracker/model.rb +90 -0
  16. data/lib/scrooge/tracker/resource.rb +201 -0
  17. data/lib/scrooge.rb +62 -0
  18. data/spec/fixtures/config/scrooge/scopes/1234567891/scope.yml +2 -0
  19. data/spec/fixtures/config/scrooge.yml +12 -0
  20. data/spec/helpers/framework/rails/cache.rb +25 -0
  21. data/spec/spec_helper.rb +51 -0
  22. data/spec/units/scrooge/core/string_spec.rb +21 -0
  23. data/spec/units/scrooge/core/symbol_spec.rb +13 -0
  24. data/spec/units/scrooge/core/thread_spec.rb +15 -0
  25. data/spec/units/scrooge/framework/base_spec.rb +154 -0
  26. data/spec/units/scrooge/framework/rails_spec.rb +40 -0
  27. data/spec/units/scrooge/orm/base_spec.rb +61 -0
  28. data/spec/units/scrooge/profile_spec.rb +73 -0
  29. data/spec/units/scrooge/storage/base_spec.rb +35 -0
  30. data/spec/units/scrooge/storage/memory_spec.rb +20 -0
  31. data/spec/units/scrooge/tracker/app_spec.rb +62 -0
  32. data/spec/units/scrooge/tracker/base_spec.rb +21 -0
  33. data/spec/units/scrooge/tracker/model_spec.rb +50 -0
  34. data/spec/units/scrooge/tracker/resource_spec.rb +83 -0
  35. data/spec/units/scrooge_spec.rb +13 -0
  36. metadata +110 -0
@@ -0,0 +1,101 @@
1
+ module Scrooge
2
+ module Tracker
3
+ class App < Scrooge::Tracker::Base
4
+
5
+ # Application container for various Resources.
6
+
7
+ GUARD = Monitor.new
8
+
9
+ attr_accessor :resources
10
+
11
+ def initialize
12
+ super()
13
+ @resources = Set.new
14
+ end
15
+
16
+ # Has any Resources been tracked ?
17
+ #
18
+ def any?
19
+ GUARD.synchronize do
20
+ !@resources.empty?
21
+ end
22
+ end
23
+
24
+ # Add a Resource instance to this tracker.
25
+ #
26
+ def <<( resource )
27
+ GUARD.synchronize do
28
+ @resources << setup_resource( resource )
29
+ end
30
+ end
31
+
32
+ def marshal_dump #:nodoc:
33
+ GUARD.synchronize do
34
+ dumped_resources()
35
+ end
36
+ end
37
+
38
+ def marshal_load( data ) #:nodoc:
39
+ GUARD.synchronize do
40
+ @resources = Set.new( restored_resources( data ) )
41
+ end
42
+ self
43
+ end
44
+
45
+ # Track a given Resource.
46
+ #
47
+ def track( resource )
48
+ profile.log "Track with resource #{resource.inspect}"
49
+ begin
50
+ yield
51
+ ensure
52
+ self << resource if resource.any?
53
+ end
54
+ end
55
+
56
+ def inspect #:nodoc:
57
+ if any?
58
+ @resources.map{|r| r.inspect }.join( "\n\n" )
59
+ else
60
+ super
61
+ end
62
+ end
63
+
64
+ # If we've seen this resource before, return the original, else, returns
65
+ # the given resource.
66
+ #
67
+ def resource_for( resource )
68
+ @resources.detect{|r| r.signature == resource.signature } || resource
69
+ end
70
+
71
+ private
72
+
73
+ def setup_resource( resource ) #:nodoc:
74
+ GUARD.synchronize do
75
+ resource_for( resource )
76
+ end
77
+ end
78
+
79
+ def environment #:nodoc:
80
+ profile.framework.environment
81
+ end
82
+
83
+ def restored_resources( data ) #:nodoc:
84
+ GUARD.synchronize do
85
+ data.map do |resource|
86
+ Resource.new.marshal_load( resource )
87
+ end
88
+ end
89
+ end
90
+
91
+ def dumped_resources #:nodoc:
92
+ GUARD.synchronize do
93
+ @resources.to_a.map do |resource|
94
+ resource.marshal_dump
95
+ end
96
+ end
97
+ end
98
+
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,56 @@
1
+ require 'set'
2
+
3
+ module Scrooge
4
+ module Tracker
5
+
6
+ autoload :App, 'scrooge/tracker/app'
7
+ autoload :Resource, 'scrooge/tracker/resource'
8
+ autoload :Model, 'scrooge/tracker/model'
9
+
10
+ class Base < Scrooge::Base
11
+ include Comparable
12
+
13
+ # Scrooge Tracker base class.
14
+
15
+ class NotImplemented < StandardError
16
+ end
17
+
18
+ attr_accessor :counter
19
+
20
+ def initialize
21
+ @counter = 0
22
+ end
23
+
24
+ def to_i
25
+ @counter
26
+ end
27
+
28
+ # Requires subclasses to implement a custom marshal_dump
29
+ #
30
+ def marshal_dump
31
+ raise NotImplemented
32
+ end
33
+
34
+ # Requires subclasses to implement a custom marshal_load
35
+ #
36
+ def marshal_load( data )
37
+ raise NotImplemented
38
+ end
39
+
40
+ # Compare trackers through their Marshal representations.
41
+ #
42
+ def ==( tracker )
43
+ compare_to( tracker )
44
+ end
45
+ alias :eql? :==
46
+ alias :<=> :==
47
+
48
+ private
49
+
50
+ def compare_to( tracker ) #:nodoc:
51
+ marshal_dump == tracker.marshal_dump
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,90 @@
1
+ module Scrooge
2
+ module Tracker
3
+ class Model < Base
4
+
5
+ GUARD = Monitor.new
6
+
7
+ attr_accessor :model,
8
+ :attributes
9
+
10
+ def initialize( model )
11
+ super()
12
+ @model = model
13
+ @attributes = Set.new
14
+ end
15
+
16
+ # Has any Attributes been tracked ?
17
+ #
18
+ def any?
19
+ GUARD.synchronize do
20
+ !@attributes.empty?
21
+ end
22
+ end
23
+
24
+ # Add a Model attribute to this tracker.
25
+ #
26
+ def <<( attribute )
27
+ GUARD.synchronize do
28
+ Array( attribute ).each do |attr|
29
+ attributes << attr
30
+ end
31
+ end
32
+ end
33
+
34
+ def marshal_dump #:nodoc:
35
+ GUARD.synchronize do
36
+ { name() => @attributes.to_a }
37
+ end
38
+ end
39
+
40
+ def marshal_load( data ) #:nodoc:
41
+ GUARD.synchronize do
42
+ @model = data.keys.first
43
+ @attributes = Set.new( data[@model] )
44
+ self
45
+ end
46
+ end
47
+
48
+ # Memoize the name lookup.
49
+ #
50
+ def name
51
+ @name ||= profile.orm.name( @model )
52
+ end
53
+
54
+ # Memoize the table name lookup.
55
+ #
56
+ def table_name
57
+ @table_name ||= profile.orm.table_name( @model )
58
+ end
59
+
60
+ # Memoize the primary key lookup.
61
+ #
62
+ def primary_key
63
+ @primary_key ||= profile.orm.primary_key( @model )
64
+ end
65
+
66
+ # Dump to a SQL SELECT snippet.
67
+ #
68
+ def to_sql
69
+ GUARD.synchronize do
70
+ attributes_with_primary_key().map{|a| "#{table_name}.#{a.to_s}" }.join(', ')
71
+ end
72
+ end
73
+
74
+ def inspect #:nodoc:
75
+ "#<#{name()} #{attributes_for_inspect}>"
76
+ end
77
+
78
+ private
79
+
80
+ def attributes_for_inspect #:nodoc:
81
+ @attributes.map{|a| ":#{a}" }.join(', ')
82
+ end
83
+
84
+ def attributes_with_primary_key #:nodoc:
85
+ @attributes << primary_key
86
+ end
87
+
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,201 @@
1
+ module Scrooge
2
+ module Tracker
3
+ class Resource < Base
4
+
5
+ # A Resource tracker is scoped to a
6
+ #
7
+ # * controller
8
+ # * action
9
+ # * request method
10
+ # * content type
11
+ #
12
+ # and is a container for any Models referenced during the active
13
+ # request / response cycle.
14
+
15
+ GET = /get/i
16
+
17
+ GUARD = Monitor.new
18
+
19
+ attr_accessor :controller,
20
+ :action,
21
+ :method,
22
+ :format,
23
+ :models
24
+
25
+ def initialize
26
+ super()
27
+ @models = Set.new
28
+ yield self if block_given?
29
+ end
30
+
31
+ # Has any Models been tracked ?
32
+ #
33
+ def any?
34
+ GUARD.synchronize do
35
+ !@models.empty?
36
+ end
37
+ end
38
+
39
+ # Generates a signature / lookup key.
40
+ #
41
+ def signature
42
+ @signature ||= "#{controller.to_s}_#{action.to_s}_#{method.to_s}"
43
+ end
44
+
45
+ # Only track GET requests
46
+ #
47
+ def trackable?
48
+ !( method || '' ).to_s.match( GET ).nil?
49
+ end
50
+
51
+ # Add a Model to this resource.
52
+ #
53
+ def <<( model )
54
+ GUARD.synchronize do
55
+ @models << track_model_from( model )
56
+ end
57
+ end
58
+
59
+ def marshal_dump #:nodoc:
60
+ GUARD.synchronize do
61
+ { signature => { :controller => @controller,
62
+ :action => @action,
63
+ :method => @method,
64
+ :format => @format,
65
+ :models => dumped_models() } }
66
+ end
67
+ end
68
+
69
+ def marshal_load( data ) #:nodoc:
70
+ GUARD.synchronize do
71
+ data = data.to_a.flatten.last
72
+ @controller = data[:controller]
73
+ @action = data[:action]
74
+ @method = data[:method]
75
+ @format = data[:format]
76
+ @models = restored_models( data[:models] )
77
+ self
78
+ end
79
+ end
80
+
81
+ # Yields a collection of Rack middleware to scope Model attributes to the
82
+ # tracked dataset.
83
+ #
84
+ def middleware
85
+ @middleware ||= begin
86
+ GUARD.synchronize do
87
+ models.map do |model|
88
+ middleware_for_model( model )
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ # Return a valid Rack middleware instance for a given model.
95
+ #
96
+ def middleware_for_model( model )
97
+ resource = self
98
+ profile.orm.scope_resource_to_model( resource, model )
99
+ klass = Class.new
100
+ klass.class_eval(<<-EOS, __FILE__, __LINE__)
101
+
102
+ class << self
103
+
104
+ def inspect
105
+ "#<Scrooge::Middleware #{model.inspect}>"
106
+ end
107
+
108
+ # Around Filter compatible implementation for Rails as Dispatcher is
109
+ # the root Rack application and as such don't provide access to the Rails
110
+ # Routing internals from other middleware.
111
+ #
112
+ def filter( controller, &block )
113
+ #{model.model.to_s}.#{profile.orm.resource_scope_method( resource ).to_s} do
114
+ Scrooge::Base.profile.log "Scope for Model #{model.inspect}"
115
+ block.call
116
+ end
117
+ end
118
+
119
+ end
120
+
121
+ def initialize(app)
122
+ @app = app
123
+ end
124
+
125
+ def call(env)
126
+ if scope?( env )
127
+ #{model.model.to_s}.#{profile.orm.resource_scope_method( resource ).to_s} do
128
+ @app.call(env)
129
+ end
130
+ else
131
+ @app.call(env)
132
+ end
133
+ end
134
+
135
+ private
136
+
137
+ def scope?( env )
138
+ Scrooge::Base.profile.orm.resource_scope_method( resource( env ) ) == :#{profile.orm.resource_scope_method( resource ).to_s}
139
+ end
140
+
141
+ def resource( env )
142
+ Scrooge::Base.profile.framework.resource( env )
143
+ end
144
+
145
+ EOS
146
+ klass
147
+ end
148
+
149
+ def inspect #:nodoc:
150
+ "#<#{@method.to_s.upcase} :#{@controller}/#{@action} (#{@format})\n#{models_for_inspect()}"
151
+ end
152
+
153
+ private
154
+
155
+ def track_model_from( model ) #:nodoc:
156
+ model.is_a?( Array ) ? model_from_enumerable( model ) : setup_model( model )
157
+ end
158
+
159
+ def model_from_enumerable( model ) #:nodoc:
160
+ model, attribute = model
161
+ model = setup_model( model )
162
+ model << attribute
163
+ model
164
+ end
165
+
166
+ def models_for_inspect #:nodoc:
167
+ models.map{|m| " - #{m.inspect}" }.join( "\n" )
168
+ end
169
+
170
+ def dumped_models #:nodoc:
171
+ GUARD.synchronize do
172
+ @models.to_a.map{|m| m.marshal_dump }
173
+ end
174
+ end
175
+
176
+ def restored_models( models ) #:nodoc:
177
+ GUARD.synchronize do
178
+ models.map do |model|
179
+ m = model.keys.first # TODO: cleanup
180
+ Model.new( m ).marshal_load( model )
181
+ end
182
+ end
183
+ end
184
+
185
+ def setup_model( model ) #:nodoc:
186
+ GUARD.synchronize do
187
+ if model.is_a?( Scrooge::Tracker::Model )
188
+ model
189
+ else
190
+ model_for( model ) || Scrooge::Tracker::Model.new( model )
191
+ end
192
+ end
193
+ end
194
+
195
+ def model_for( model ) #:nodoc:
196
+ @models.detect{|m| m.model.name == model.name }
197
+ end
198
+
199
+ end
200
+ end
201
+ end
data/lib/scrooge.rb ADDED
@@ -0,0 +1,62 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ require 'yaml'
4
+ require 'fileutils'
5
+ require 'scrooge/core/string'
6
+ require 'scrooge/core/symbol'
7
+ require 'scrooge/core/thread'
8
+ require 'thread'
9
+
10
+ module Scrooge
11
+ class Base
12
+
13
+ GUARD = ::Mutex.new
14
+
15
+ class << self
16
+
17
+ # Active Profile reader
18
+ #
19
+ def profile
20
+ @@profile ||= Scrooge::Profile.new
21
+ end
22
+
23
+ # Active Profile writer.
24
+ #
25
+ def profile=( profile )
26
+ @@profile = profile
27
+ end
28
+
29
+ # Installs a YAML configuration template in the host framework's config
30
+ # directory.
31
+ #
32
+ def setup!
33
+ unless File.exist?( profile.framework.configuration_file )
34
+ FileUtils.cp( configuration_template(), profile.framework.configuration_file )
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def configuration_template #:nodoc:
41
+ File.join( File.dirname(__FILE__), '..', 'assets', 'config', 'scrooge.yml.template' )
42
+ end
43
+
44
+ end
45
+
46
+ def profile
47
+ self.class.profile
48
+ end
49
+
50
+ end
51
+
52
+ module Middleware
53
+ autoload :Tracker, 'scrooge/middleware/tracker'
54
+ end
55
+
56
+ end
57
+
58
+ require 'scrooge/profile'
59
+ require 'scrooge/storage/base'
60
+ require 'scrooge/orm/base'
61
+ require 'scrooge/framework/base'
62
+ require 'scrooge/tracker/base'
@@ -0,0 +1,2 @@
1
+ ---
2
+ test: []
@@ -0,0 +1,12 @@
1
+ production:
2
+ orm: :active_record
3
+ storage: :memory
4
+ scope: 1234567891
5
+ on_missing_attribute: :reload
6
+ enabled: true
7
+ test:
8
+ orm: :active_record
9
+ storage: :memory
10
+ scope:
11
+ on_missing_attribute: :reload
12
+ enabled: true
@@ -0,0 +1,25 @@
1
+ module Spec
2
+ module Helpers
3
+ module Framework
4
+ module Rails
5
+ class Cache
6
+
7
+ attr_reader :storage
8
+
9
+ def initialize
10
+ @storage = {}
11
+ end
12
+
13
+ def read( key )
14
+ @storage[key]
15
+ end
16
+
17
+ def write( key, value )
18
+ @storage[key] = value
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,51 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+
3
+ require 'rubygems'
4
+ require 'fileutils'
5
+ require 'logger'
6
+ require 'stringio'
7
+ require 'lib/scrooge'
8
+ require 'spec/helpers/framework/rails/cache'
9
+
10
+ #ActiveRecord::Base.logger = Logger.new(StringIO.new)
11
+
12
+ Spec::Runner.configure do |config|
13
+
14
+ Kernel.const_set :FIXTURES, "#{Dir.pwd}/spec/fixtures" unless defined?(FIXTURES)
15
+ Kernel.const_set :TMP, "#{Dir.pwd}/spec/tmp" unless defined?(TMP)
16
+ Kernel.const_set :CONFIG, "#{Dir.pwd}/spec/config" unless defined?(CONFIG)
17
+
18
+ config.before :all do
19
+ [TMP, CONFIG].each do |dir|
20
+ FileUtils.mkdir_p dir
21
+ end
22
+ end
23
+
24
+ config.before :each do
25
+ end
26
+
27
+ config.after :each do
28
+ end
29
+
30
+ config.after :all do
31
+ [TMP, CONFIG].each do |dir|
32
+ FileUtils.rm_r( dir ) rescue nil
33
+ end
34
+ end
35
+
36
+ def with_rails
37
+ begin
38
+ Kernel.const_set :RAILS_ROOT, "#{Dir.pwd}/spec" unless defined?(RAILS_ROOT)
39
+ Kernel.const_set :Rails, Class.new unless defined?(Rails)
40
+ Kernel.const_set :RAILS_ENV, "test" unless defined?(RAILS_ENV)
41
+ ::Rails.stub!(:cache).and_return( Spec::Helpers::Framework::Rails::Cache.new )
42
+ ::Rails.stub!(:root).and_return( RAILS_ROOT )
43
+ yield
44
+ ensure
45
+ [:RAILS_ROOT, :RAILS_ENV, :Rails].each do |const|
46
+ Kernel.send( :remove_const, const )
47
+ end
48
+ end
49
+ end
50
+
51
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe Scrooge::Core::String do
4
+
5
+ before(:each) do
6
+ @string = 'scrooge/base'
7
+ end
8
+
9
+ it "should be able to convert itself to a constant" do
10
+ @string.to_const().should == 'Scrooge::Base'
11
+ end
12
+
13
+ it "should be able to convert itself to a class" do
14
+ @string.to_const!( false ).should == Scrooge::Base
15
+ end
16
+
17
+ it "should return self if it's not able to constantize" do
18
+ 'not/defined'.to_const!( false ).should == 'not/defined'
19
+ end
20
+
21
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe Scrooge::Core::Symbol do
4
+
5
+ before(:each) do
6
+ @symbol = :active_record
7
+ end
8
+
9
+ it "should be able to convert itself to a constant" do
10
+ @symbol.to_const().should == 'ActiveRecord'
11
+ end
12
+
13
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "Scrooge::Core::Thread singleton" do
4
+
5
+ it "should be able to yield the current scrooge resource" do
6
+ Thread.scrooge_resource.class.should equal( Scrooge::Tracker::Resource )
7
+ end
8
+
9
+ it "should be able to reset the current scrooge resource" do
10
+ @resource = Thread.scrooge_resource
11
+ Thread.reset_scrooge_resource!
12
+ @resource.object_id.should_not equal( Thread.scrooge_resource.object_id )
13
+ end
14
+
15
+ end