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 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
- h3. Requirements
27
+ Then add this to your Rails::Initializer section in environment.rb:
26
28
 
27
- * "Slim-attributes":http://github.com/sdsykes/slim-attributes
29
+ <pre>
30
+ config.gem 'slim_scrooge'
31
+ </pre>
28
32
 
29
- h3. What it does
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
- h3. Technical discussion
53
+ h2. Technical discussion
50
54
 
51
- SlimScrooge hooks in at just one particular place in the mysql adapter - and that place is the select_all method. All select queries pass through this method.
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
- Enter Slim-attributes. Slim-attributes also hooks in at one place, the all_hashes method - and in particular it returns a proxy for the hash for each row, and doesn't actually instantiate any ruby objects for the columns in each row until they are needed.
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 slim-attributes provides its usual lazy-loading benefit without any additional overhead.
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
- h3. Caveats
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
@@ -1,5 +1,5 @@
1
1
  ---
2
- :major: 0
3
- :minor: 1
2
+ :minor: 0
3
+ :patch: 0
4
4
  :build:
5
- :patch: 1
5
+ :major: 1
@@ -62,7 +62,9 @@ module SlimScrooge
62
62
  end
63
63
 
64
64
  def scrooge_select_sql(set)
65
- set.collect{|name| "#{@quoted_table_name}.#{name}"}.join(ScroogeComma)
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.[](original_hash, callsite, result_set)
8
- hash = super(original_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 [](name)
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
- super
20
+ @monitored_columns[name]
20
21
  end
21
22
 
22
23
  def []=(name, value)
23
- if @result_set && @callsite.columns_hash.has_key?(name)
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
- super
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.real_hash.result_set = nil
26
- row.each {|col, value| old_row[col] = value}
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 SelectAll
4
+ module FindBySql
5
5
  def self.included(base)
6
- base.alias_method_chain :select_all, :slim_scrooge
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
- def select_all_with_slim_scrooge(sql, name = nil)
10
- callsite_key = SlimScrooge::Callsites.callsite_key(callsite_hash, sql)
11
- if SlimScrooge::Callsites.has_key?(callsite_key)
12
- if callsite = SlimScrooge::Callsites[callsite_key]
13
- seen_columns = callsite.seen_columns.dup
14
- rows = select_all_without_slim_scrooge(callsite.scrooged_sql(seen_columns, sql), name + " SlimScrooged")
15
- result_set = SlimScrooge::ResultSet.new(rows.dup, callsite_key, seen_columns)
16
- rows.each {|row| row.real_hash = MonitoredHash[{}, callsite, result_set]}
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
- select_all_without_slim_scrooge(sql, name)
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::ConnectionAdapters::MysqlAdapter.send(:include, SlimScrooge::SelectAll)
40
+ ActiveRecord::Base.send(:include, SlimScrooge::FindBySql)
data/lib/slim_scrooge.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # Author: Stephen Sykes
2
2
 
3
3
  require 'callsite_hash'
4
- require 'slim_attributes'
5
4
  require 'slim_scrooge/simple_set'
6
5
  require 'slim_scrooge/callsites'
7
6
  require 'slim_scrooge/callsite'
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.1.1
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-10-23 00:00:00 +03:00
12
+ date: 2009-11-06 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency