methodmissing-scrooge 1.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 (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