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.
- data/README.textile +139 -449
- data/Rakefile +20 -19
- data/VERSION.yml +2 -2
- data/lib/attributes_proxy.rb +121 -0
- data/lib/scrooge.rb +206 -47
- data/rails/init.rb +1 -10
- data/test/helper.rb +88 -0
- data/test/models/mysql_user.rb +4 -0
- data/test/scrooge_test.rb +75 -0
- data/test/setup.rb +3 -0
- metadata +11 -76
- data/assets/config/scrooge.yml.template +0 -27
- data/lib/scrooge/core/string.rb +0 -29
- data/lib/scrooge/core/symbol.rb +0 -21
- data/lib/scrooge/core/thread.rb +0 -26
- data/lib/scrooge/framework/base.rb +0 -315
- data/lib/scrooge/framework/rails.rb +0 -132
- data/lib/scrooge/middleware/tracker.rb +0 -46
- data/lib/scrooge/orm/active_record.rb +0 -159
- data/lib/scrooge/orm/base.rb +0 -102
- data/lib/scrooge/profile.rb +0 -223
- data/lib/scrooge/storage/base.rb +0 -46
- data/lib/scrooge/storage/memory.rb +0 -25
- data/lib/scrooge/strategy/base.rb +0 -74
- data/lib/scrooge/strategy/controller.rb +0 -31
- data/lib/scrooge/strategy/scope.rb +0 -15
- data/lib/scrooge/strategy/stage.rb +0 -77
- data/lib/scrooge/strategy/track.rb +0 -19
- data/lib/scrooge/strategy/track_then_scope.rb +0 -41
- data/lib/scrooge/tracker/app.rb +0 -161
- data/lib/scrooge/tracker/base.rb +0 -66
- data/lib/scrooge/tracker/model.rb +0 -150
- data/lib/scrooge/tracker/resource.rb +0 -181
- data/spec/fixtures/config/scrooge/scopes/1234567891/scope.yml +0 -2
- data/spec/fixtures/config/scrooge.yml +0 -20
- data/spec/helpers/framework/rails/cache.rb +0 -25
- data/spec/spec_helper.rb +0 -55
- data/spec/units/scrooge/core/string_spec.rb +0 -21
- data/spec/units/scrooge/core/symbol_spec.rb +0 -13
- data/spec/units/scrooge/core/thread_spec.rb +0 -15
- data/spec/units/scrooge/framework/base_spec.rb +0 -160
- data/spec/units/scrooge/framework/rails_spec.rb +0 -40
- data/spec/units/scrooge/orm/base_spec.rb +0 -61
- data/spec/units/scrooge/profile_spec.rb +0 -79
- data/spec/units/scrooge/storage/base_spec.rb +0 -35
- data/spec/units/scrooge/storage/memory_spec.rb +0 -20
- data/spec/units/scrooge/strategy/base_spec.rb +0 -62
- data/spec/units/scrooge/strategy/controller_spec.rb +0 -26
- data/spec/units/scrooge/strategy/scope_spec.rb +0 -18
- data/spec/units/scrooge/strategy/stage_spec.rb +0 -35
- data/spec/units/scrooge/strategy/track_spec.rb +0 -19
- data/spec/units/scrooge/strategy/track_then_scope_spec.rb +0 -22
- data/spec/units/scrooge/tracker/app_spec.rb +0 -68
- data/spec/units/scrooge/tracker/base_spec.rb +0 -29
- data/spec/units/scrooge/tracker/model_spec.rb +0 -79
- data/spec/units/scrooge/tracker/resource_spec.rb +0 -115
- data/spec/units/scrooge_spec.rb +0 -13
- data/tasks/scrooge.rake +0 -43
data/Rakefile
CHANGED
@@ -1,35 +1,36 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require 'rubygems'
|
5
|
-
require 'spec'
|
6
|
-
end
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'test/helper'
|
7
4
|
|
8
|
-
|
5
|
+
task :default => [:test_with_active_record, :test_scrooge]
|
9
6
|
|
10
|
-
|
11
|
-
$LOAD_PATH.unshift File.dirname(__FILE__) + '/lib'
|
7
|
+
task :test => :default
|
12
8
|
|
13
|
-
|
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
|
-
|
16
|
-
|
17
|
-
t.
|
18
|
-
t.
|
19
|
-
|
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 = "
|
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,
|
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
@@ -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 '
|
4
|
-
require '
|
5
|
-
|
6
|
-
|
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
|
-
|
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
|
-
#
|
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
|
20
|
-
|
41
|
+
def scrooge_callsite_set(callsite_signature)
|
42
|
+
scrooge_callsites
|
43
|
+
@@scrooge_callsites[self.table_name][callsite_signature]
|
21
44
|
end
|
22
45
|
|
23
|
-
#
|
24
|
-
#
|
25
|
-
def
|
26
|
-
@@
|
46
|
+
# Expose known callsites for this model
|
47
|
+
#
|
48
|
+
def scrooge_callsites
|
49
|
+
@@scrooge_callsites[self.table_name] ||= {}
|
27
50
|
end
|
28
51
|
|
29
|
-
#
|
30
|
-
#
|
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
|
33
|
-
|
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
|
-
|
41
|
-
|
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
|
-
|
45
|
-
|
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
|
-
|
51
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
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
|
-
|
63
|
-
|
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,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