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/Rakefile CHANGED
@@ -1,35 +1,36 @@
1
- begin
2
- require 'spec'
3
- rescue LoadError
4
- require 'rubygems'
5
- require 'spec'
6
- end
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'test/helper'
7
4
 
8
- require 'spec/rake/spectask'
5
+ task :default => [:test_with_active_record, :test_scrooge]
9
6
 
10
- $LOAD_PATH.unshift File.dirname(__FILE__) + '/../'
11
- $LOAD_PATH.unshift File.dirname(__FILE__) + '/lib'
7
+ task :test => :default
12
8
 
13
- require 'scrooge'
9
+ Rake::TestTask.new( :test_with_active_record ) { |t|
10
+ t.libs << AR_TEST_SUITE << Scrooge::Test.connection()
11
+ t.test_files = Scrooge::Test.active_record_test_files()
12
+ t.ruby_opts = ["-r #{File.join( File.dirname(__FILE__), 'test', 'setup' )}"]
13
+ t.verbose = true
14
+ }
14
15
 
15
- desc "Run the specs under spec"
16
- Spec::Rake::SpecTask.new do |t|
17
- t.spec_files = FileList['spec/**/*_spec.rb']
18
- t.spec_opts << "-c"
19
- end
16
+ Rake::TestTask.new( :test_scrooge ) { |t|
17
+ t.libs << 'lib'
18
+ t.test_files = Scrooge::Test.test_files()
19
+ t.verbose = true
20
+ }
20
21
 
21
22
  begin
22
23
  require 'jeweler'
23
24
  Jeweler::Tasks.new do |s|
24
25
  s.name = "scrooge"
25
26
  s.summary = "Scrooge - Fetch exactly what you need"
26
- s.email = "lourens@methodmissing.com"
27
+ s.email = "lourens@methodmissing.com or sds@switchstep.com"
27
28
  s.homepage = "http://github.com/methodmissing/scrooge"
28
- s.description = "A Framework and ORM agnostic Model / record attribute tracker to ensure production
29
+ s.description = "An ActiveRecord attribute tracker to ensure production
29
30
  Ruby applications only fetch the database content needed to minimize wire traffic
30
31
  and reduce conversion overheads to native Ruby types."
31
- s.authors = ["Lourens Naudé"]
32
- s.files = FileList["[A-Z]*", "{lib,spec,rails,assets,tasks}/**/*"]
32
+ s.authors = ["Lourens Naudé", "Stephen Sykes"]
33
+ s.files = FileList["[A-Z]*", "{lib,test,rails,tasks}/**/*"]
33
34
  end
34
35
  rescue LoadError
35
36
  puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 4
3
- :major: 1
4
2
  :minor: 0
3
+ :patch: 0
4
+ :major: 2
@@ -0,0 +1,121 @@
1
+ module Scrooge
2
+ class AttributesProxy
3
+ attr_reader :callsite_signature
4
+
5
+ def initialize(record, scrooge_columns, klass, callsite_signature)
6
+ @attributes = record
7
+ @scrooge_columns = scrooge_columns.dup
8
+ @fully_fetched = false
9
+ @klass = klass
10
+ @callsite_signature = callsite_signature
11
+ end
12
+
13
+ # Delegate Hash keys to all defined columns
14
+ #
15
+ def keys
16
+ @klass.column_names
17
+ end
18
+
19
+ # Let #has_key? consider defined columns
20
+ #
21
+ def has_key?(attr_name)
22
+ keys.include?(attr_name.to_s)
23
+ end
24
+
25
+ alias_method :include?, :has_key?
26
+ alias_method :key?, :has_key?
27
+
28
+ # Lazily augment and load missing attributes
29
+ #
30
+ def [](attr_name)
31
+ attr_s = attr_name.to_s
32
+ if interesting_for_scrooge?( attr_s )
33
+ augment_callsite!( attr_s )
34
+ fetch_remaining
35
+ @scrooge_columns << attr_s
36
+ end
37
+ @attributes[attr_s]
38
+ end
39
+
40
+ def fetch(*args, &block)
41
+ self[args[0]]
42
+ @attributes.fetch(*args, &block)
43
+ end
44
+
45
+ def []=(attr_name, value)
46
+ attr_s = attr_name.to_s
47
+ @attributes[attr_s] = value
48
+ @scrooge_columns << attr_s
49
+ end
50
+
51
+ def dup
52
+ super.dup_self
53
+ end
54
+
55
+ def to_a
56
+ fetch_remaining
57
+ @attributes.to_a
58
+ end
59
+
60
+ def delete(attr_name)
61
+ self[attr_name]
62
+ @attributes.delete(attr_name)
63
+ end
64
+
65
+ def update(hash)
66
+ hash.to_hash.each do |k, v|
67
+ self[k] = v
68
+ end
69
+ end
70
+
71
+ def to_hash
72
+ fetch_remaining
73
+ @attributes
74
+ end
75
+
76
+ def freeze
77
+ @attributes.freeze
78
+ end
79
+
80
+ def frozen?
81
+ @attributes.frozen?
82
+ end
83
+
84
+ def fetch_remaining
85
+ unless @fully_fetched
86
+ columns_to_fetch = @klass.column_names - @scrooge_columns.to_a
87
+ unless columns_to_fetch.empty?
88
+ begin
89
+ new_object = fetch_record_with_remaining_columns( columns_to_fetch )
90
+ rescue ActiveRecord::RecordNotFound
91
+ raise ActiveRecord::MissingAttributeError, "scrooge cannot fetch missing attribute(s) because record went away"
92
+ end
93
+ @attributes = new_object.instance_variable_get(:@attributes).merge(@attributes)
94
+ end
95
+ @fully_fetched = true
96
+ end
97
+ end
98
+
99
+ protected
100
+
101
+ def fetch_record_with_remaining_columns( columns_to_fetch )
102
+ @klass.send(:with_exclusive_scope) do
103
+ @klass.find(@attributes[@klass.primary_key], :select=>@klass.scrooge_sql(columns_to_fetch))
104
+ end
105
+ end
106
+
107
+ def interesting_for_scrooge?( attr_s )
108
+ has_key?(attr_s) && !@scrooge_columns.include?(attr_s)
109
+ end
110
+
111
+ def augment_callsite!( attr_s )
112
+ @klass.augment_scrooge_callsite!(callsite_signature, attr_s)
113
+ end
114
+
115
+ def dup_self
116
+ @attributes = @attributes.dup
117
+ @scrooge_columns = @scrooge_columns.dup
118
+ self
119
+ end
120
+ end
121
+ end
data/lib/scrooge.rb CHANGED
@@ -1,67 +1,226 @@
1
1
  $:.unshift(File.dirname(__FILE__))
2
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
3
+ require 'set'
4
+ require 'attributes_proxy'
5
+
6
+ module ActiveRecord
11
7
  class Base
12
-
13
- GUARD = ::Mutex.new
14
-
8
+
9
+ @@scrooge_mutex = Mutex.new
10
+ @@scrooge_callsites = {}
11
+ @@scrooge_select_regexes = {}
12
+
13
+ ScroogeBlankString = "".freeze
14
+ ScroogeComma = ",".freeze
15
+ ScroogeRegexWhere = /WHERE.*/
16
+ ScroogeRegexJoin = /INNER JOIN/
17
+ ScroogeCallsiteSample = 0..10
18
+
15
19
  class << self
16
-
17
- # Active Profile reader
20
+
21
+ # Determine if a given SQL string is a candidate for callsite <=> columns
22
+ # optimization.
23
+ #
24
+ alias :find_by_sql_without_scrooge :find_by_sql
25
+ def find_by_sql(sql)
26
+ if scope_with_scrooge?(sql)
27
+ find_by_sql_with_scrooge(sql)
28
+ else
29
+ find_by_sql_without_scrooge(sql)
30
+ end
31
+ end
32
+
33
+ # Populate the storage for a given callsite signature
34
+ #
35
+ def scrooge_callsite_set!(callsite_signature, set)
36
+ @@scrooge_callsites[self.table_name][callsite_signature] = set
37
+ end
38
+
39
+ # Reference storage for a given callsite signature
18
40
  #
19
- def profile
20
- @@profile ||= Scrooge::Profile.new
41
+ def scrooge_callsite_set(callsite_signature)
42
+ scrooge_callsites
43
+ @@scrooge_callsites[self.table_name][callsite_signature]
21
44
  end
22
45
 
23
- # Active Profile writer.
24
- #
25
- def profile=( profile )
26
- @@profile = profile
46
+ # Expose known callsites for this model
47
+ #
48
+ def scrooge_callsites
49
+ @@scrooge_callsites[self.table_name] ||= {}
27
50
  end
28
51
 
29
- # Installs a YAML configuration template in the host framework's config
30
- # directory.
52
+ # Flush all known callsites.Mostly a test helper.
53
+ #
54
+ def scrooge_flush_callsites!
55
+ @@scrooge_callsites[self.table_name] = {}
56
+ end
57
+
58
+ # Augment a given callsite signature with a column / attribute.
59
+ #
60
+ def augment_scrooge_callsite!( callsite_signature, attr_name )
61
+ set = set_for_callsite( callsite_signature ) # make set if needed - eg unserialized models after restart
62
+ @@scrooge_mutex.synchronize do
63
+ set << attr_name
64
+ end
65
+ end
66
+
67
+ # Generates a SELECT snippet for this Model from a given Set of columns
31
68
  #
32
- def setup!
33
- unless configuration_file_exists?
34
- FileUtils.cp( configuration_template(), profile.framework.configuration_file )
35
- end
69
+ def scrooge_sql( set )
70
+ set.map{|a| attribute_with_table( a ) }.join( ScroogeComma )
36
71
  end
37
-
72
+
38
73
  private
39
-
40
- def configuration_file_exists? #:nodoc:
41
- File.exist?( profile.framework.configuration_file )
74
+
75
+ # Only scope n-1 rows by default.
76
+ # Stephen: Temp. relaxed the LIMIT constraint - please advise.
77
+ def scope_with_scrooge?( sql )
78
+ sql =~ scrooge_select_regex &&
79
+ column_names.include?(self.primary_key.to_s) &&
80
+ sql !~ ScroogeRegexJoin
81
+ end
82
+
83
+ # Find through callsites.
84
+ #
85
+ def find_by_sql_with_scrooge( sql )
86
+ callsite_signature = (caller[ScroogeCallsiteSample] << sql.gsub(ScroogeRegexWhere, ScroogeBlankString)).hash
87
+ callsite_set = set_for_callsite(callsite_signature)
88
+ sql = sql.gsub(scrooge_select_regex, "SELECT #{scrooge_sql(callsite_set)} FROM")
89
+ result = connection.select_all(sanitize_sql(sql), "#{name} Load Scrooged").collect! do |record|
90
+ instantiate(Scrooge::AttributesProxy.new(record, callsite_set, self, callsite_signature))
42
91
  end
43
-
44
- def configuration_template #:nodoc:
45
- File.join( File.dirname(__FILE__), '..', 'assets', 'config', 'scrooge.yml.template' )
92
+ end
93
+
94
+ # Return an attribute Set for a given callsite signature.
95
+ # Respects already tracked columns and ensures at least the primary key
96
+ # if this is a fresh callsite.
97
+ #
98
+ def set_for_callsite( callsite_signature )
99
+ @@scrooge_mutex.synchronize do
100
+ callsite_set = scrooge_callsite_set(callsite_signature)
101
+ unless callsite_set
102
+ callsite_set = scrooge_default_callsite_set
103
+ scrooge_callsite_set!(callsite_signature, callsite_set)
104
+ end
105
+ callsite_set
46
106
  end
47
-
107
+ end
108
+
109
+ # Ensure that the inheritance column is defined for the callsite if
110
+ # this is an STI klass tree.
111
+ #
112
+ def scrooge_default_callsite_set
113
+ if column_names.include?( self.inheritance_column.to_s )
114
+ Set.new([self.primary_key.to_s, self.inheritance_column.to_s])
115
+ else
116
+ Set.new([self.primary_key.to_s])
117
+ end
118
+ end
119
+
120
+ # Generate a regex that respects the table name as well to catch
121
+ # verbose SQL from JOINS etc.
122
+ #
123
+ def scrooge_select_regex
124
+ @@scrooge_select_regexes[self.table_name] ||= Regexp.compile( "SELECT (`?(?:#{table_name})?`?.?\\*) FROM" )
125
+ end
126
+
127
+ # Link the column to its table
128
+ #
129
+ def attribute_with_table( attr_name )
130
+ "#{quoted_table_name}.#{attr_name.to_s}"
131
+ end
132
+
133
+ end # class << self
134
+
135
+ # Is this instance being handled by scrooge?
136
+ #
137
+ def scrooged?
138
+ @attributes.is_a?(Scrooge::AttributesProxy)
48
139
  end
49
140
 
50
- def profile
51
- self.class.profile
141
+ # Delete should fully load all the attributes before the @attributes hash is frozen
142
+ #
143
+ alias_method :delete_without_scrooge, :delete
144
+ def delete
145
+ scrooge_fetch_remaining
146
+ delete_without_scrooge
147
+ end
148
+
149
+ # Destroy should fully load all the attributes before the @attributes hash is frozen
150
+ #
151
+ alias_method :destroy_without_scrooge, :destroy
152
+ def destroy
153
+ scrooge_fetch_remaining
154
+ destroy_without_scrooge
52
155
  end
53
-
54
- end
55
156
 
56
- module Middleware
57
- autoload :Tracker, 'scrooge/middleware/tracker'
58
- end
157
+ # Augment callsite info for new model class when using STI
158
+ #
159
+ def becomes(klass)
160
+ returning klass.new do |became|
161
+ became.instance_variable_set("@attributes", @attributes)
162
+ became.instance_variable_set("@attributes_cache", @attributes_cache)
163
+ became.instance_variable_set("@new_record", new_record?)
164
+ if scrooged?
165
+ self.class.scrooge_callsite_set(@attributes.callsite_signature).each do |attrib|
166
+ became.class.augment_scrooge_callsite!(@attributes.callsite_signature, attrib)
167
+ end
168
+ end
169
+ end
170
+ end
59
171
 
60
- end
172
+ # Marshal
173
+ # force a full load if needed, and remove any possibility for missing attr flagging
174
+ #
175
+ def _dump(depth)
176
+ scrooge_fetch_remaining
177
+ scrooge_dump_flag_this
178
+ str = Marshal.dump(self)
179
+ scrooge_dump_unflag_this
180
+ str
181
+ end
182
+
183
+ # Marshal.load
184
+ #
185
+ def self._load(str)
186
+ Marshal.load(str)
187
+ end
188
+
189
+ # Enables us to use Marshal.dump inside our _dump method without an infinite loop
190
+ #
191
+ alias_method :respond_to_without_scrooge, :respond_to?
192
+ def respond_to?(symbol, include_private=false)
193
+ if symbol == :_dump && scrooge_dump_flagged?
194
+ false
195
+ else
196
+ respond_to_without_scrooge(symbol, include_private)
197
+ end
198
+ end
199
+
200
+ private
201
+
202
+ # Flag Marshal dump in progress
203
+ #
204
+ def scrooge_dump_flag_this
205
+ Thread.current[:scrooge_dumping_objects] ||= []
206
+ Thread.current[:scrooge_dumping_objects] << object_id
207
+ end
208
+
209
+ # Flag Marhsal dump not in progress
210
+ #
211
+ def scrooge_dump_unflag_this
212
+ Thread.current[:scrooge_dumping_objects].delete(object_id)
213
+ end
214
+
215
+ # Flag scrooge as dumping ( excuse my French )
216
+ #
217
+ def scrooge_dump_flagged?
218
+ Thread.current[:scrooge_dumping_objects] && Thread.current[:scrooge_dumping_objects].include?(object_id)
219
+ end
220
+
221
+ def scrooge_fetch_remaining
222
+ @attributes.fetch_remaining if scrooged?
223
+ end
61
224
 
62
- require 'scrooge/profile'
63
- require 'scrooge/storage/base'
64
- require 'scrooge/orm/base'
65
- require 'scrooge/framework/base'
66
- require 'scrooge/tracker/base'
67
- require 'scrooge/strategy/base'
225
+ end
226
+ end
data/rails/init.rb CHANGED
@@ -1,10 +1 @@
1
- require File.join(File.dirname(__FILE__), '..', 'lib', 'scrooge' )
2
-
3
- # Hook to register through Scrooge::Framework::Base.inherited
4
- Scrooge::Framework::Rails
5
-
6
- Scrooge::Base.profile = Scrooge::Profile.setup!
7
- Scrooge::Base.profile.framework.initialized do
8
- Scrooge::Base.profile.log "Initialized"
9
- Scrooge::Base.profile.strategy.execute!
10
- end
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'scrooge' )
data/test/helper.rb ADDED
@@ -0,0 +1,88 @@
1
+ require File.join( File.dirname(__FILE__), 'setup' )
2
+ require 'rubygems'
3
+ require 'mocha'
4
+ require 'active_support/test_case'
5
+
6
+ module Scrooge
7
+ class Test
8
+
9
+ MODELS_DIR = "#{File.dirname(__FILE__)}/models".freeze
10
+
11
+ class << self
12
+
13
+ def prepare!
14
+ require 'test/unit'
15
+ connect!
16
+ require_models()
17
+ end
18
+
19
+ def setup!
20
+ setup_constants!
21
+ setup_config!
22
+ end
23
+
24
+ def connection
25
+ File.join( AR_TEST_SUITE, 'connections', 'native_mysql' )
26
+ end
27
+
28
+ def active_record_test_files
29
+ (files ||= [] ) << glob( "#{AR_TEST_SUITE}/cases/**/*_test.rb" )
30
+ files.sort
31
+ end
32
+
33
+ def test_files
34
+ glob( "#{File.dirname(__FILE__)}/*_test.rb" )
35
+ end
36
+
37
+ private
38
+
39
+ def connect!
40
+ ::ActiveRecord::Base.establish_connection( :adapter => 'mysql',
41
+ :username => 'root',
42
+ :database => 'mysql',
43
+ :pool => 1 )
44
+ end
45
+
46
+ def require_models
47
+ Dir.entries( MODELS_DIR ).grep(/.rb/).each do |model|
48
+ require_model( model )
49
+ end
50
+ end
51
+
52
+ def require_model( model )
53
+ require "#{MODELS_DIR}/#{model}"
54
+ end
55
+
56
+ def setup_constants!
57
+ set_constant( 'MYSQL_DB_USER' ){ 'rails' }
58
+ set_constant( 'AR_TEST_SUITE' ) do
59
+ find_active_record_test_suite()
60
+ end
61
+ end
62
+
63
+ def setup_config!
64
+ unless Object.const_defined?( 'MIGRATIONS_ROOT' )
65
+ require "#{::AR_TEST_SUITE}/config"
66
+ end
67
+ end
68
+
69
+ def set_constant( constant )
70
+ Object.const_set(constant, yield ) unless Object.const_defined?( constant )
71
+ end
72
+
73
+ def find_active_record_test_suite
74
+ ts = ($:).grep( /activerecord/ ).last.split('/')
75
+ ts.pop
76
+ ts << 'test'
77
+ ts.join('/')
78
+ end
79
+
80
+ def glob( pattern )
81
+ Dir.glob( pattern )
82
+ end
83
+
84
+ end
85
+ end
86
+ end
87
+
88
+ Scrooge::Test.setup!
@@ -0,0 +1,4 @@
1
+ class MysqlUser < ActiveRecord::Base
2
+ set_table_name 'user'
3
+ set_primary_key 'User'
4
+ end
@@ -0,0 +1,75 @@
1
+ require "#{File.dirname(__FILE__)}/helper"
2
+
3
+ Scrooge::Test.prepare!
4
+
5
+ class ScroogeTest < ActiveSupport::TestCase
6
+
7
+ teardown do
8
+ MysqlUser.scrooge_flush_callsites!
9
+ end
10
+
11
+ test "should not attempt to optimize models without a defined primary key" do
12
+ MysqlUser.stubs(:primary_key).returns('undefined')
13
+ MysqlUser.expects(:find_by_sql_with_scrooge).never
14
+ MysqlUser.find(:first)
15
+ end
16
+
17
+ test "should not optimize any SQL other than result retrieval" do
18
+ MysqlUser.expects(:find_by_sql_with_scrooge).never
19
+ MysqlUser.find_by_sql("SHOW fields from mysql.user")
20
+ end
21
+
22
+ test "should not optimize inner joins" do
23
+ MysqlUser.expects(:find_by_sql_with_scrooge).never
24
+ MysqlUser.find_by_sql("SELECT * FROM columns_priv INNER JOIN user ON columns_priv.User = user.User")
25
+ end
26
+
27
+ test "should be able to flag applicable records as being scrooged" do
28
+ assert MysqlUser.find(:first).scrooged?
29
+ assert MysqlUser.find_by_sql( "SELECT * FROM mysql.user WHERE User = 'root'" ).first.scrooged?
30
+ end
31
+
32
+ test "should be able to track callsites" do
33
+ assert_difference 'MysqlUser.scrooge_callsites.size' do
34
+ MysqlUser.find(:first)
35
+ end
36
+ end
37
+
38
+ test "should be able to retrieve a callsite form a given signature" do
39
+ assert MysqlUser.find(:first).scrooged?
40
+ assert_instance_of Set, MysqlUser.scrooge_callsite_set( first_callsite )
41
+ end
42
+
43
+ test "should be able to populate the callsite for a given signature" do
44
+ MysqlUser.scrooge_callsite_set!(123456, Set[1,2,3])
45
+ assert_equal MysqlUser.scrooge_callsite_set(123456), Set[1,2,3]
46
+ end
47
+
48
+ test "should be able to augment an existing callsite with attributes" do
49
+ MysqlUser.find(:first)
50
+ MysqlUser.augment_scrooge_callsite!( first_callsite, 'Password' )
51
+ assert MysqlUser.scrooge_callsite_set( first_callsite ).include?( 'Password' )
52
+ end
53
+
54
+ test "should be able to generate a SQL select snippet from a given set" do
55
+ assert_equal MysqlUser.scrooge_sql( Set['Password','User','Host'] ), "`user`.User,`user`.Password,`user`.Host"
56
+ end
57
+
58
+ test "should be able to augment an existing callsite when attributes is referenced that we haven't seen yet" do
59
+ user = MysqlUser.find(:first)
60
+ MysqlUser.expects(:augment_scrooge_callsite!).times(2)
61
+ user.Password
62
+ user.Host
63
+ end
64
+
65
+ test "should not augment the callsite with known columns" do
66
+ user = MysqlUser.find(:first)
67
+ MysqlUser.expects(:augment_scrooge_callsite!).never
68
+ user.User
69
+ end
70
+
71
+ def first_callsite
72
+ MysqlUser.scrooge_callsites.to_a.flatten.first
73
+ end
74
+
75
+ end
data/test/setup.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require 'activerecord'
3
+ require File.join( File.dirname(__FILE__), '..', 'lib', 'scrooge' )