slim_scrooge 0.1.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +13 -12
- data/VERSION.yml +3 -3
- data/lib/slim_scrooge/callsite.rb +3 -1
- data/lib/slim_scrooge/monitored_hash.rb +37 -10
- data/lib/slim_scrooge/result_set.rb +2 -2
- data/lib/slim_scrooge/slim_scrooge.rb +27 -18
- data/lib/slim_scrooge.rb +0 -1
- metadata +2 -2
data/README.textile
CHANGED
@@ -6,11 +6,13 @@ It's an optimization layer to ensure your application only fetches the database
|
|
6
6
|
|
7
7
|
SlimScrooge implements both lazy loading of attributes fetched from mysql, as well as inline query optimisation, automatically restricting the columns fetched based on what was used during previous passes through the same part of your code.
|
8
8
|
|
9
|
+
SlimScrooge is similar to (and is partly derived from) "Scrooge":http://github.com/methodmissing/scrooge but has many fewer lines of code and is faster.
|
10
|
+
|
9
11
|
h2. Benchmark
|
10
12
|
|
11
13
|
SlimScrooge performs best when the database is not on the same machine as your rails app. In this case the overhead of fetching unnecessary data from the database can become more important.
|
12
14
|
|
13
|
-
I ran a benchmark that consisted of fetching 400 real urls (culled from the log file) from our complex web app. In this test I found a consistent 12% improvement in performance over plain active record. Not earth-shattering, but worthwhile. In future releases I expect further gains.
|
15
|
+
I ran a benchmark that consisted of fetching 400 real urls (culled from the log file) from our complex web app. In this test I found a consistent *12% improvement* in performance over plain active record. Not earth-shattering, but worthwhile. In future releases I expect further gains.
|
14
16
|
|
15
17
|
h2. Installation
|
16
18
|
|
@@ -22,11 +24,13 @@ h2. Installation
|
|
22
24
|
sudo gem install slim_scrooge
|
23
25
|
</pre>
|
24
26
|
|
25
|
-
|
27
|
+
Then add this to your Rails::Initializer section in environment.rb:
|
26
28
|
|
27
|
-
|
29
|
+
<pre>
|
30
|
+
config.gem 'slim_scrooge'
|
31
|
+
</pre>
|
28
32
|
|
29
|
-
|
33
|
+
h2. What it does
|
30
34
|
|
31
35
|
<pre>
|
32
36
|
# 1st request, sql is unchanged but columns accesses are recorded
|
@@ -46,21 +50,19 @@ h3. What it does
|
|
46
50
|
`brochures`.id FROM `brochures` WHERE (expires_at IS NULL)
|
47
51
|
</pre>
|
48
52
|
|
49
|
-
|
53
|
+
h2. Technical discussion
|
50
54
|
|
51
|
-
SlimScrooge hooks in at just one particular place in
|
55
|
+
SlimScrooge hooks in at just one particular place in ActiveRecord - and that place is the find_all_by_sql method. All select queries pass through this method.
|
52
56
|
|
53
57
|
SlimScrooge is able to record each call (and where it came from in your code), and to modify queries that do SELECT * FROM en-route to mysql so that they only select the rows that are actually used by that piece of code.
|
54
58
|
|
55
59
|
How does SlimScrooge know which columns are actually used?
|
56
60
|
|
57
|
-
|
58
|
-
|
59
|
-
Clearly slim-attributes knows when each column is accessed, and this information can be collected by SlimScrooge for future use.
|
61
|
+
It tracks them using a monitored hash - the hash is a subclass of Hash that has is instantiated with a proc for its default value. In this proc we handle any attributes that were not fetched from the DB - fetching the remaining columns as needed.
|
60
62
|
|
61
|
-
In fact for efficiency, column accesses are only recorded when they are for newly accessed columns. Columns we already know about are accessed completely normally, and
|
63
|
+
In fact for efficiency, column accesses are only recorded when they are for newly accessed columns. Columns we already know about are accessed completely normally, and there is no additional overhead.
|
62
64
|
|
63
|
-
|
65
|
+
h2. Caveats
|
64
66
|
|
65
67
|
It is possible to delete an object and then to try to use its attributes to access another object that perhaps must be also deleted (like the option :dependent=>:destroy in Rails associations).
|
66
68
|
|
@@ -78,7 +80,6 @@ To run the tests you need to set your database up with appropriate access for th
|
|
78
80
|
|
79
81
|
h2. References
|
80
82
|
* "Scrooge":http://github.com/methodmissing/scrooge
|
81
|
-
* "Slim-attributes":http://github.com/sdsykes/slim-attributes
|
82
83
|
|
83
84
|
h2. Authors
|
84
85
|
* Stephen Sykes (sdsykes)
|
data/VERSION.yml
CHANGED
@@ -62,7 +62,9 @@ module SlimScrooge
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def scrooge_select_sql(set)
|
65
|
-
set.collect
|
65
|
+
set.collect do |name|
|
66
|
+
"#{@quoted_table_name}.#{@model_class.connection.quote_column_name(name)}"
|
67
|
+
end.join(ScroogeComma)
|
66
68
|
end
|
67
69
|
end
|
68
70
|
end
|
@@ -2,38 +2,65 @@
|
|
2
2
|
|
3
3
|
module SlimScrooge
|
4
4
|
class MonitoredHash < Hash
|
5
|
-
attr_accessor :callsite, :result_set
|
5
|
+
attr_accessor :callsite, :result_set, :monitored_columns
|
6
6
|
|
7
|
-
def self.[](
|
8
|
-
hash =
|
7
|
+
def self.[](monitored_columns, unmonitored_columns, callsite)
|
8
|
+
hash = MonitoredHash.new {|hash, key| hash.new_column_access(key)}
|
9
|
+
hash.monitored_columns = monitored_columns
|
10
|
+
hash.merge!(unmonitored_columns)
|
9
11
|
hash.callsite = callsite
|
10
|
-
hash.result_set = result_set
|
11
12
|
hash
|
12
13
|
end
|
13
14
|
|
14
|
-
def
|
15
|
+
def new_column_access(name)
|
15
16
|
if @callsite.columns_hash.has_key?(name)
|
16
17
|
@result_set.reload! if @result_set && name != @callsite.primary_key
|
17
18
|
Callsites.add_seen_column(@callsite, name)
|
18
19
|
end
|
19
|
-
|
20
|
+
@monitored_columns[name]
|
20
21
|
end
|
21
22
|
|
22
23
|
def []=(name, value)
|
23
|
-
if
|
24
|
+
if has_key?(name)
|
25
|
+
return super
|
26
|
+
elsif @result_set && @callsite.columns_hash.has_key?(name)
|
24
27
|
@result_set.reload!
|
28
|
+
Callsites.add_seen_column(@callsite, name)
|
25
29
|
end
|
26
|
-
|
30
|
+
@monitored_columns[name] = value
|
27
31
|
end
|
28
32
|
|
29
33
|
def keys
|
30
|
-
@result_set ? @callsite.columns_hash.keys : super
|
34
|
+
@result_set ? @callsite.columns_hash.keys : super | @monitored_columns.keys
|
31
35
|
end
|
32
36
|
|
33
37
|
def has_key?(name)
|
34
|
-
@result_set ? @callsite.columns_hash.has_key?(name) : super
|
38
|
+
@result_set ? @callsite.columns_hash.has_key?(name) : super || @monitored_columns.has_key?(name)
|
35
39
|
end
|
36
40
|
|
37
41
|
alias_method :include?, :has_key?
|
42
|
+
|
43
|
+
def to_hash
|
44
|
+
@result_set.reload! if @result_set
|
45
|
+
@monitored_columns.merge(self)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Marshal
|
49
|
+
# Dump a real hash - can't dump a monitored hash due to default proc
|
50
|
+
#
|
51
|
+
def _dump(depth)
|
52
|
+
Marshal.dump(to_hash)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self._load(str)
|
56
|
+
Marshal.load(str)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Hash
|
62
|
+
alias_method :c_update, :update
|
63
|
+
def update(other_hash, &block)
|
64
|
+
c_update(other_hash.to_hash, &block)
|
38
65
|
end
|
39
66
|
end
|
@@ -22,8 +22,8 @@ module SlimScrooge
|
|
22
22
|
new_rows = model_class.connection.send(:select, sql, "#{model_class.name} Reload SlimScrooged")
|
23
23
|
new_rows.each do |row|
|
24
24
|
if old_row = rows_hash[row[callsite.primary_key]]
|
25
|
-
old_row.
|
26
|
-
row
|
25
|
+
old_row.result_set = nil
|
26
|
+
old_row.monitored_columns.merge!(row)
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
@@ -1,31 +1,40 @@
|
|
1
1
|
# Author: Stephen Sykes
|
2
2
|
|
3
3
|
module SlimScrooge
|
4
|
-
module
|
4
|
+
module FindBySql
|
5
5
|
def self.included(base)
|
6
|
-
|
6
|
+
ActiveRecord::Base.extend ClassMethods
|
7
|
+
class << base
|
8
|
+
alias_method_chain :find_by_sql, :slim_scrooge
|
9
|
+
end
|
7
10
|
end
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
module ClassMethods
|
13
|
+
def find_by_sql_with_slim_scrooge(sql)
|
14
|
+
return find_by_sql_without_slim_scrooge(sql) if sql.is_a?(Array) # don't mess with user's custom query
|
15
|
+
callsite_key = SlimScrooge::Callsites.callsite_key(callsite_hash, sql)
|
16
|
+
if SlimScrooge::Callsites.has_key?(callsite_key) # seen before
|
17
|
+
if callsite = SlimScrooge::Callsites[callsite_key] # and is scroogeable
|
18
|
+
seen_columns = callsite.seen_columns.dup # dup so cols aren't changed underneath us
|
19
|
+
rows = connection.select_all(callsite.scrooged_sql(seen_columns, sql), "#{name} Load SlimScrooged")
|
20
|
+
rows.collect! {|row| MonitoredHash[{}, row, callsite]}
|
21
|
+
result_set = SlimScrooge::ResultSet.new(rows.dup, callsite_key, seen_columns)
|
22
|
+
rows.collect! do |row|
|
23
|
+
row.result_set = result_set
|
24
|
+
instantiate(row)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
find_by_sql_without_slim_scrooge(sql)
|
28
|
+
end
|
29
|
+
elsif callsite = SlimScrooge::Callsites.create(sql, callsite_key, name) # new site that is scroogeable
|
30
|
+
rows = connection.select_all(sql, "#{name} Load SlimScrooged 1st time")
|
31
|
+
rows.collect! {|row| instantiate(MonitoredHash[row, {}, callsite])}
|
17
32
|
else
|
18
|
-
|
33
|
+
find_by_sql_without_slim_scrooge(sql)
|
19
34
|
end
|
20
|
-
elsif callsite = SlimScrooge::Callsites.create(sql, callsite_key, name)
|
21
|
-
rows = select_all_without_slim_scrooge(sql, name + " SlimScrooged 1st time")
|
22
|
-
rows.each {|row| row.real_hash = MonitoredHash[row.to_hash, callsite, nil]}
|
23
|
-
rows
|
24
|
-
else
|
25
|
-
select_all_without_slim_scrooge(sql, name)
|
26
35
|
end
|
27
36
|
end
|
28
37
|
end
|
29
38
|
end
|
30
39
|
|
31
|
-
ActiveRecord::
|
40
|
+
ActiveRecord::Base.send(:include, SlimScrooge::FindBySql)
|
data/lib/slim_scrooge.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slim_scrooge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Sykes
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-11-06 00:00:00 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|