methodmissing-scrooge 2.2.3 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -72,7 +72,7 @@ h4. As a Gem
72
72
 
73
73
  h2. Stability
74
74
 
75
- The whole ActiveRecord test suite passes with scrooge, except for 9 failures related to callsite augmentation (note the SQL reload snippets below).Thoughts on handling or circumventing this much appreciated.
75
+ The whole Rails 2.3.2 ActiveRecord test suite passes with scrooge, except for 13 failures related to callsite augmentation (note the SQL reload snippets below). Thoughts on handling or circumventing this much appreciated.
76
76
 
77
77
  <pre>
78
78
  <code>
@@ -203,7 +203,7 @@ on subsequent requests :
203
203
 
204
204
  h4. How it tracks
205
205
 
206
- The ActiveRecord attributes Hash is replaced with a proxy that automatically augments the callsite with any attributes referenced through the Hash lookup keys.We're also able to learn which associations is invoked from a given callsite, for preloading on subsequent requests.
206
+ The ActiveRecord attributes Hash is replaced with a proxy that automatically augments the callsite with any attributes referenced through the Hash lookup keys. We're also able to learn which associations is invoked from a given callsite, for preloading on subsequent requests.
207
207
 
208
208
  h4. Storage
209
209
 
@@ -221,12 +221,14 @@ The tracking and scoping phases is superseded by this implementation - none of t
221
221
 
222
222
  h2. Todo
223
223
 
224
- * Deeper coverage for Scrooge::AttributesProxy ( pending conversion to a subclass of Hash instead )
224
+ * Deeper coverage for Scrooge::AttributesProxy, possible handling of replace
225
225
 
226
- * Test cases for Scrooge::Callsite
226
+ * More test cases for Scrooge::Callsite
227
227
 
228
228
  * Have invoking Model#attributes not associate all columns with the callsite
229
229
 
230
230
  * Avoid possible missing attribute exceptions for destroyed objects
231
231
 
232
+ * Track rows of result set to allow more targeted loading of associations for a callsite
233
+
232
234
  (c) 2009 Lourens Naudé (methodmissing) and Stephen Sykes (sdsykes)
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # coding: UTF-8
2
+
1
3
  require 'rake'
2
4
  require 'rake/testtask'
3
5
  require 'test/helper'
@@ -24,7 +26,7 @@ begin
24
26
  Jeweler::Tasks.new do |s|
25
27
  s.name = "scrooge"
26
28
  s.summary = "Scrooge - Fetch exactly what you need"
27
- s.email = "lourens@methodmissing.com or sds@switchstep.com"
29
+ s.email = "lourens@methodmissing.com or sdsykes@gmail.com"
28
30
  s.homepage = "http://github.com/methodmissing/scrooge"
29
31
  s.description = "An ActiveRecord attribute tracker to ensure production
30
32
  Ruby applications only fetch the database content needed to minimize wire traffic
@@ -1,4 +1,4 @@
1
1
  ---
2
- :minor: 2
3
- :patch: 3
4
- :major: 2
2
+ :major: 3
3
+ :minor: 0
4
+ :patch: 0
@@ -2,10 +2,10 @@ module Scrooge
2
2
  class Callsite
3
3
 
4
4
  # Represents a Callsite and is a container for any columns and
5
- # associations ( coming soon ) referenced at the callsite.
5
+ # associations referenced at the callsite.
6
6
  #
7
7
 
8
- Mutex = Mutex.new
8
+ Mtx = Mutex.new
9
9
 
10
10
  attr_accessor :klass,
11
11
  :signature,
@@ -15,40 +15,52 @@ module Scrooge
15
15
  def initialize( klass, signature )
16
16
  @klass = klass
17
17
  @signature = signature
18
- @columns = setup_columns
19
- @associations = setup_associations
20
18
  end
21
19
 
22
20
  # Flag a column as seen
23
21
  #
24
22
  def column!( column )
25
- Mutex.synchronize do
26
- @columns << column
23
+ Mtx.synchronize do
24
+ columns << column
27
25
  end
28
26
  end
29
27
 
30
- # Diff known associations with given includes
31
- #
32
- def preload( includes )
33
- # Ignore nested includes for the time being
34
- #
35
- if includes.is_a?(Hash)
36
- includes
37
- else
38
- @associations.merge( Array(includes) ).to_a
39
- end
40
- end
41
-
42
28
  # Flag an association as seen
43
29
  #
44
30
  def association!( association )
45
- Mutex.synchronize do
46
- @associations << association if preloadable_association?( association )
31
+ Mtx.synchronize do
32
+ associations << association if preloadable_association?( association )
47
33
  end
48
34
  end
49
35
 
36
+ def inspect
37
+ "<##{@klass.name} :select => '#{@klass.scrooge_select_sql( columns )}', :include => [#{associations_for_inspect}]>"
38
+ end
39
+
40
+ # Lazy init default columns
41
+ #
42
+ def default_columns
43
+ @default_columns ||= setup_columns
44
+ end
45
+
46
+ # Lazy init columns
47
+ #
48
+ def columns
49
+ @columns ||= default_columns.dup
50
+ end
51
+
52
+ # Lazy init associations
53
+ #
54
+ def associations
55
+ @associations ||= setup_associations
56
+ end
57
+
50
58
  private
51
59
 
60
+ def associations_for_inspect
61
+ associations.map{|a| ":#{a.to_s}" }.join(', ')
62
+ end
63
+
52
64
  # Only register associations that isn't polymorphic or a collection
53
65
  #
54
66
  def preloadable_association?( association )
@@ -68,11 +80,11 @@ module Scrooge
68
80
  if inheritable?
69
81
  Set.new([primary_key, inheritance_column])
70
82
  else
71
- Set.new([primary_key])
83
+ primary_key.blank? ? Set.new : Set.new([primary_key])
72
84
  end
73
85
  end
74
86
 
75
- # Stubbed for future use
87
+ # Start with no registered associations
76
88
  #
77
89
  def setup_associations
78
90
  Set.new
@@ -8,16 +8,16 @@ module Scrooge
8
8
  # Inject into ActiveRecord
9
9
  #
10
10
  def install!
11
- if scrooge_installable?
11
+ unless scrooge_installed?
12
12
  ActiveRecord::Base.send( :extend, SingletonMethods )
13
- ActiveRecord::Base.send( :include, InstanceMethods )
13
+ ActiveRecord::Associations::AssociationProxy.send( :include, InstanceMethods )
14
14
  end
15
15
  end
16
16
 
17
- private
17
+ protected
18
18
 
19
- def scrooge_installable?
20
- !ActiveRecord::Base.included_modules.include?( InstanceMethods )
19
+ def scrooge_installed?
20
+ ActiveRecord::Associations::AssociationProxy.included_modules.include?( InstanceMethods )
21
21
  end
22
22
 
23
23
  end
@@ -27,99 +27,62 @@ module Scrooge
27
27
  module SingletonMethods
28
28
 
29
29
  @@preloadable_associations = {}
30
- FindAssociatedRegex = /find_associated_records/
31
30
 
32
31
  def self.extended( base )
33
32
  eigen = class << base; self; end
34
- eigen.instance_eval do
35
- # Let :scrooge_callsite be a valid find option
36
- #
37
- remove_const(:VALID_FIND_OPTIONS)
38
- const_set( :VALID_FIND_OPTIONS, [ :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :having, :from, :lock, :scrooge_callsite ] )
39
- end
40
- eigen.alias_method_chain :find, :scrooge
41
- eigen.alias_method_chain :find_every, :scrooge
33
+ # not used at present
42
34
  end
43
-
44
- # Let .find setup callsite information and preloading.
45
- #
46
- def find_with_scrooge(*args)
47
- options = args.extract_options!
48
- validate_find_options(options)
49
- set_readonly_option!(options)
50
35
 
51
- if (_caller = caller).grep( FindAssociatedRegex ).empty?
52
- cs_signature = callsite_signature( _caller, options.except(:conditions, :limit, :offset) )
53
- options[:scrooge_callsite], options[:include] = cs_signature, scrooge_callsite(cs_signature).preload( options[:include] )
54
- end
55
-
56
- case args.first
57
- when :first then find_initial(options)
58
- when :last then find_last(options)
59
- when :all then find_every(options)
60
- else find_from_ids(args, options)
36
+ def preload_scrooge_associations(result_set, callsite_sig)
37
+ scrooge_preloading_exclude do
38
+ callsite_associations = scrooge_callsite(callsite_sig).associations.to_a
39
+ preload_associations(result_set, callsite_associations) unless callsite_associations.empty?
61
40
  end
62
41
  end
63
-
64
- # Override find_ever to pass along the callsite signature
65
- #
66
- def find_every_with_scrooge(options)
67
- include_associations = merge_includes(scope(:find, :include), options[:include])
68
42
 
69
- if include_associations.any? && references_eager_loaded_tables?(options)
70
- records = find_with_associations(options)
71
- else
72
- records = find_by_sql(construct_finder_sql(options), options[:scrooge_callsite])
73
- if include_associations.any?
74
- preload_associations(records, include_associations)
75
- end
43
+ def scrooge_preloading_exclude
44
+ unless Thread.current[:scrooge_preloading]
45
+ Thread.current[:scrooge_preloading] = true
46
+ yield
47
+ Thread.current[:scrooge_preloading] = false
76
48
  end
77
-
78
- records.each { |record| record.readonly! } if options[:readonly]
79
-
80
- records
81
- end
49
+ end
82
50
 
83
51
  # Let's not preload polymorphic associations or collections
84
52
  #
85
53
  def preloadable_associations
86
- @@preloadable_associations[self.name] ||= reflect_on_all_associations.reject{|a| a.options[:polymorphic] || a.macro == :has_many }.map{|a| a.name }
87
- end
88
-
54
+ @@preloadable_associations[self.name] ||=
55
+ reflect_on_all_associations.reject{|a| a.options[:polymorphic] || a.macro == :has_many}.map(&:name)
56
+ end
57
+
89
58
  end
90
59
 
91
60
  module InstanceMethods
92
-
93
- # Association getter with Scrooge support
94
- #
95
- def association_instance_get(name)
96
- association = instance_variable_get("@#{name}")
97
- if association.respond_to?(:loaded?)
98
- scrooge_seen_association!( name )
99
- association
100
- end
61
+
62
+ def self.included( base )
63
+ base.alias_method_chain :load_target, :scrooge
101
64
  end
102
65
 
103
- # Association setter with Scrooge support
66
+ # note AssociationCollection has its own version of load_target, but we don't
67
+ # do collections at the moment anyway
104
68
  #
105
- def association_instance_set(name, association)
106
- scrooge_seen_association!( name )
107
- instance_variable_set("@#{name}", association)
108
- end
109
-
110
- # Register an association with Scrooge
111
- #
112
- def scrooge_seen_association!( association )
113
- if scrooged? && !scrooge_seen_association?( association )
114
- @attributes.scrooge_associations << association
115
- self.class.scrooge_callsite( @attributes.callsite_signature ).association!( association )
116
- end
69
+ def load_target_with_scrooge
70
+ scrooge_seen_association!(@reflection.name)
71
+ load_target_without_scrooge
117
72
  end
118
-
73
+
119
74
  private
120
75
 
121
- def scrooge_seen_association?( association )
122
- @attributes.scrooge_associations.include?( association )
76
+ # Register an association with Scrooge
77
+ #
78
+ def scrooge_seen_association!( association )
79
+ if @owner.scrooged? && !@loaded
80
+ @owner.class.scrooge_callsite(callsite_signature).association!(association)
81
+ end
82
+ end
83
+
84
+ def callsite_signature
85
+ @owner.instance_variable_get(:@attributes).callsite_signature
123
86
  end
124
87
 
125
88
  end
@@ -31,12 +31,11 @@ module Scrooge
31
31
  # Hash container for attributes with scrooge monitoring of attribute access
32
32
  #
33
33
 
34
- attr_accessor :callsite_signature, :scrooge_columns, :scrooge_associations, :fully_fetched, :klass, :updateable_result_set
34
+ attr_accessor :callsite_signature, :scrooge_columns, :fully_fetched, :klass, :updateable_result_set
35
35
 
36
- def self.setup(record, scrooge_columns, scrooge_associations, klass, callsite_signature, updateable_result_set)
36
+ def self.setup(record, scrooge_columns, klass, callsite_signature, updateable_result_set)
37
37
  hash = new.replace(record)
38
38
  hash.scrooge_columns = scrooge_columns.dup
39
- hash.scrooge_associations = scrooge_associations.dup
40
39
  hash.fully_fetched = false
41
40
  hash.klass = klass
42
41
  hash.callsite_signature = callsite_signature
@@ -141,7 +140,6 @@ module Scrooge
141
140
 
142
141
  def dup_self
143
142
  @scrooge_columns = @scrooge_columns.dup
144
- @scrooge_associations = @scrooge_associations.dup
145
143
  self
146
144
  end
147
145
  end
@@ -56,36 +56,41 @@ module Scrooge
56
56
  #
57
57
  def scrooge_reload( p_keys, missing_columns )
58
58
  sql_keys = p_keys.collect{|pk| "'#{pk}'"}.join(ScroogeComma)
59
- connection.send( :select, "SELECT #{scrooge_select_sql(missing_columns)} FROM #{quoted_table_name} WHERE #{quoted_table_name}.#{primary_key} IN (#{sql_keys})" )
59
+ connection.send( :select, "SELECT #{scrooge_select_sql(missing_columns)} FROM #{quoted_table_name} WHERE #{quoted_table_name}.#{primary_key} IN (#{sql_keys})", "#{name} Scrooge Reload" )
60
60
  end
61
61
 
62
+ # Only scope n-1 rows by default.
63
+ #
64
+ def scope_with_scrooge?( sql )
65
+ sql =~ scrooge_select_regex &&
66
+ column_names.include?(self.primary_key.to_s) &&
67
+ sql !~ ScroogeRegexJoin
68
+ end
69
+
62
70
  private
63
-
64
- # Only scope n-1 rows by default.
65
- # Stephen: Temp. relaxed the LIMIT constraint - please advise.
66
- def scope_with_scrooge?( sql )
67
- sql =~ scrooge_select_regex &&
68
- column_names.include?(self.primary_key.to_s) &&
69
- sql !~ ScroogeRegexJoin
70
- end
71
71
 
72
72
  # Find through callsites.
73
73
  #
74
- def find_by_sql_with_scrooge( sql, callsite_sig = nil )
75
- callsite_sig ||= callsite_signature( caller, callsite_sql( sql ) )
74
+ def find_by_sql_with_scrooge(sql)
75
+ callsite_sig = callsite_signature( caller, callsite_sql( sql ) )
76
76
  callsite_columns = scrooge_callsite(callsite_sig).columns
77
- callsite_associations = scrooge_callsite(callsite_sig).associations
78
77
  sql = sql.gsub(scrooge_select_regex, "SELECT #{scrooge_select_sql(callsite_columns)} FROM")
79
78
  results = connection.select_all(sanitize_sql(sql), "#{name} Load Scrooged")
79
+
80
80
  result_set = ResultSets::ResultArray.new
81
81
  updateable = ResultSets::UpdateableResultSet.new(result_set, self)
82
82
  results.inject(result_set) do |memo, record|
83
- memo << instantiate(ScroogedAttributes.setup(record, callsite_columns, callsite_associations, self, callsite_sig, updateable))
83
+ memo << instantiate(ScroogedAttributes.setup(record, callsite_columns, self, callsite_sig, updateable))
84
84
  end
85
+
86
+ if Associations::Macro.scrooge_installed?
87
+ preload_scrooge_associations(result_set, callsite_sig)
88
+ end
89
+
90
+ result_set
85
91
  end
86
92
 
87
- def find_by_sql_without_scrooge( sql, callsite = nil )
88
- scrooge_unlink_callsite!( callsite ) if callsite
93
+ def find_by_sql_without_scrooge( sql)
89
94
  connection.select_all(sanitize_sql(sql), "#{name} Load").collect! do |record|
90
95
  instantiate( UnscroogedAttributes.setup(record) )
91
96
  end
@@ -140,8 +145,8 @@ module Scrooge
140
145
  #
141
146
  def becomes_with_scrooge(klass)
142
147
  if scrooged?
143
- self.class.scrooge_callsite(@attributes.callsite_signature).columns.each do |attrib|
144
- klass.scrooge_seen_column!(@attributes.callsite_signature, attrib)
148
+ self.class.scrooge_callsite(callsite_signature).columns.each do |attrib|
149
+ klass.scrooge_seen_column!(callsite_signature, attrib)
145
150
  end
146
151
  end
147
152
  becomes_without_scrooge(klass)
@@ -168,6 +173,12 @@ module Scrooge
168
173
  respond_to_without_scrooge?(symbol, include_private)
169
174
  end
170
175
  end
176
+
177
+ # Expose this record's callsite signature
178
+ #
179
+ def callsite_signature
180
+ @attributes.callsite_signature
181
+ end
171
182
 
172
183
  private
173
184
 
@@ -16,6 +16,9 @@ module Scrooge
16
16
  @klass = klass # expected class of items in the array
17
17
  end
18
18
 
19
+ # Called by a ScroogedAttributes hash when it is asked for a column
20
+ # that was not fetched from the DB
21
+ #
19
22
  def reload_columns!(columns_to_fetch)
20
23
  reloaded_columns = hash_by_primary_key(reload_columns_for_ids(columns_to_fetch, result_set_ids))
21
24
  if !reloaded_columns.has_key?(@updaters_attributes[primary_key_name])
@@ -40,6 +43,10 @@ module Scrooge
40
43
  end.uniq
41
44
  end
42
45
 
46
+ # The result set is weak referenced by its object_id
47
+ # So we check that a unique id matches what we have remembered to make sure
48
+ # that we got the right object (object ids are recycled by ruby)
49
+ #
43
50
  def result_set
44
51
  return nil unless @result_set_object_id
45
52
  result_set = ObjectSpace._id2ref(@result_set_object_id)
@@ -48,10 +55,17 @@ module Scrooge
48
55
  nil
49
56
  end
50
57
 
58
+ # When called from after_find and after_initialize, the object
59
+ # being accessed (and causing the reload) is not in the result set yet.
60
+ # We make sure that we add its attributes to result_set_attributes so it
61
+ # gets reloaded too
62
+ #
51
63
  def default_attributes
52
64
  [@updaters_attributes]
53
65
  end
54
66
 
67
+ # Ids of the items to be reloaded
68
+ #
55
69
  def result_set_ids
56
70
  result_set_attributes.inject([]) do |memo, attributes|
57
71
  unless attributes.fully_fetched
@@ -61,16 +75,22 @@ module Scrooge
61
75
  end
62
76
  end
63
77
 
78
+ # Update the result set with attributes provided, as returned by
79
+ # the sql server
80
+ #
64
81
  def update_with(remaining_attributes)
65
82
  current_attributes = hash_by_primary_key(result_set_attributes)
66
83
  remaining_attributes.each do |r_id, r_att|
67
84
  old_attributes = current_attributes[r_id]
68
85
  if old_attributes
69
- old_attributes.update(r_att.merge(old_attributes))
86
+ old_attributes.update(r_att.merge(old_attributes)) # must call update, do not use reverse_update
70
87
  end
71
88
  end
72
89
  end
73
90
 
91
+ # Make an efficient data structure to access items when the
92
+ # primary key is known
93
+ #
74
94
  def hash_by_primary_key(rows)
75
95
  rows.inject({}) {|memo, row| memo[row[primary_key_name]] = row; memo}
76
96
  end
@@ -19,11 +19,11 @@ module ActiveRecord
19
19
  # Determine if a given SQL string is a candidate for callsite <=> columns
20
20
  # optimization.
21
21
  #
22
- def find_by_sql(sql, callsite_signature = nil)
22
+ def find_by_sql(sql)
23
23
  if scope_with_scrooge?(sql)
24
- find_by_sql_with_scrooge(sql, callsite_signature)
24
+ find_by_sql_with_scrooge(sql)
25
25
  else
26
- find_by_sql_without_scrooge(sql, callsite_signature)
26
+ find_by_sql_without_scrooge(sql)
27
27
  end
28
28
  end
29
29
 
@@ -79,4 +79,4 @@ module ActiveRecord
79
79
  end
80
80
 
81
81
  Scrooge::Optimizations::Columns::Macro.install!
82
- Scrooge::Optimizations::Associations::Macro.install!
82
+ Scrooge::Optimizations::Associations::Macro.install!
@@ -0,0 +1,41 @@
1
+ require "#{File.dirname(__FILE__)}/helper"
2
+
3
+ Scrooge::Test.prepare!
4
+
5
+ class CallsiteTest < ActiveSupport::TestCase
6
+
7
+ def setup
8
+ @callsite = Scrooge::Callsite.new( MysqlTablePrivilege, 123456 )
9
+ end
10
+
11
+ test "should initialize with a default set of columns" do
12
+ assert @callsite.columns.empty?
13
+ assert_equal Scrooge::Callsite.new( MysqlUser, 123456 ).columns, Set["User"]
14
+ Scrooge::Callsite.any_instance.stubs(:inheritable?).returns(true)
15
+ Scrooge::Callsite.any_instance.stubs(:inheritance_column).returns("inheritance")
16
+ assert_equal Scrooge::Callsite.new( MysqlUser, 123456 ).columns, Set["User","inheritance"]
17
+ end
18
+
19
+ test "should be inspectable" do
20
+ @callsite.association! :mysql_user
21
+ @callsite.column! :db
22
+ assert_equal @callsite.inspect, "<#MysqlTablePrivilege :select => '`tables_priv`.db', :include => [:mysql_user]>"
23
+ end
24
+
25
+ test "should flag a column as seen" do
26
+ assert_difference '@callsite.columns.size' do
27
+ @callsite.column! :Db
28
+ end
29
+ end
30
+
31
+ test "should flag only preloadable associations as seen" do
32
+ assert_no_difference '@callsite.associations.size' do
33
+ @callsite.association! :undefined
34
+ end
35
+ assert_difference '@callsite.associations.size', 2 do
36
+ @callsite.association! :column_privilege
37
+ @callsite.association! :mysql_user
38
+ end
39
+ end
40
+
41
+ end
@@ -2,6 +2,7 @@ require File.join( File.dirname(__FILE__), 'setup' )
2
2
  require 'rubygems'
3
3
  require 'mocha'
4
4
  require 'active_support/test_case'
5
+ ENV["BACKTRACE"] = "1"
5
6
 
6
7
  module Scrooge
7
8
  class Test
@@ -13,7 +14,8 @@ module Scrooge
13
14
  def prepare!
14
15
  require 'test/unit'
15
16
  connect!
16
- require_models()
17
+ require File.join( File.dirname(__FILE__), 'scrooge_helper' ) unless defined?(ActiveRecord::Base.connection.class::IGNORED_SQL)
18
+ require_models()
17
19
  end
18
20
 
19
21
  def setup!
@@ -31,7 +33,7 @@ module Scrooge
31
33
  end
32
34
 
33
35
  def test_files
34
- glob( "#{File.dirname(__FILE__)}/*_test.rb" )
36
+ glob( "#{File.dirname(__FILE__)}/**/*_test.rb" )
35
37
  end
36
38
 
37
39
  private
@@ -85,4 +87,4 @@ module Scrooge
85
87
  end
86
88
  end
87
89
 
88
- Scrooge::Test.setup!
90
+ Scrooge::Test.setup!
@@ -0,0 +1,7 @@
1
+ class MysqlColumnPrivilege < ActiveRecord::Base
2
+ set_table_name 'columns_priv'
3
+ set_primary_key nil
4
+
5
+ belongs_to :mysql_user, :class_name => 'MysqlUser', :foreign_key => 'User'
6
+
7
+ end
@@ -0,0 +1,5 @@
1
+ class MysqlHost < ActiveRecord::Base
2
+ set_table_name 'host'
3
+ set_primary_key 'Host'
4
+
5
+ end
@@ -0,0 +1,8 @@
1
+ class MysqlTablePrivilege < ActiveRecord::Base
2
+ set_table_name 'tables_priv'
3
+ set_primary_key nil
4
+
5
+ belongs_to :mysql_user, :class_name => 'MysqlUser', :foreign_key => 'User'
6
+ belongs_to :column_privilege, :class_name => 'MysqlColumnPrivilege', :foreign_key => 'Column_priv'
7
+
8
+ end
@@ -2,7 +2,12 @@ class MysqlUser < ActiveRecord::Base
2
2
  set_table_name 'user'
3
3
  set_primary_key 'User'
4
4
 
5
+ has_many :table_privileges, :class_name => 'MysqlTablePrivilege', :foreign_key => 'User'
6
+ has_many :column_privileges, :class_name => 'MysqlColumnPrivilege', :foreign_key => 'User'
7
+ belongs_to :host, :class_name => 'MysqlHost', :foreign_key => 'Host'
8
+
5
9
  def after_initialize
6
10
  max_connections if @attributes.has_key?("max_user_connections")
7
11
  end
12
+
8
13
  end
@@ -0,0 +1,27 @@
1
+ require "#{File.dirname(__FILE__)}/../../helper"
2
+
3
+ Scrooge::Test.prepare!
4
+
5
+ class OptimizationsAssociationsMacroTest < ActiveSupport::TestCase
6
+
7
+ test "should be able to flag any associations instantiated from a record" do
8
+ @user = MysqlUser.find(:first)
9
+ @user.host
10
+ assert_equal MysqlUser.scrooge_callsite( @user.callsite_signature ).associations, Set[:host]
11
+ end
12
+
13
+ test "should only flag preloadable associations" do
14
+ Scrooge::Callsite.any_instance.expects(:association!).once
15
+ @user = MysqlUser.find(:first)
16
+ @user.host
17
+ assert_equal MysqlUser.scrooge_callsite( @user.callsite_signature ).associations, Set.new
18
+ end
19
+
20
+ test "should be able to identify all preloadable associations for a given Model" do
21
+ assert_equal MysqlUser.preloadable_associations, [:host]
22
+ assert_equal MysqlHost.preloadable_associations, []
23
+ assert_equal MysqlColumnPrivilege.preloadable_associations, [:mysql_user]
24
+ assert_equal MysqlTablePrivilege.preloadable_associations, [:mysql_user, :column_privilege]
25
+ end
26
+
27
+ end
@@ -0,0 +1,12 @@
1
+ # Borrowed from active record's helper
2
+ ActiveRecord::Base.connection.class.class_eval do
3
+ IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /SHOW FIELDS/]
4
+
5
+ def execute_with_query_record(sql, name = nil, &block)
6
+ $queries_executed ||= []
7
+ $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
8
+ execute_without_query_record(sql, name, &block)
9
+ end
10
+
11
+ alias_method_chain :execute, :query_record
12
+ end
@@ -1,9 +1,9 @@
1
1
  require "#{File.dirname(__FILE__)}/helper"
2
-
2
+
3
3
  Scrooge::Test.prepare!
4
-
5
- class ScroogeTest < ActiveSupport::TestCase
6
-
4
+
5
+ class ScroogeTest < ActiveRecord::TestCase
6
+
7
7
  teardown do
8
8
  MysqlUser.scrooge_flush_callsites!
9
9
  end
@@ -39,6 +39,10 @@ class ScroogeTest < ActiveSupport::TestCase
39
39
  assert MysqlUser.find(:first).scrooged?
40
40
  assert_instance_of Scrooge::Callsite, MysqlUser.scrooge_callsite( first_callsite )
41
41
  end
42
+
43
+ test "should not flag records via Model.find with a custom :select requirement as scrooged" do
44
+ assert !MysqlUser.find(:first, :select => 'user.Password' ).scrooged?
45
+ end
42
46
 
43
47
  test "should be able to augment an existing callsite with attributes" do
44
48
  MysqlUser.find(:first)
@@ -68,6 +72,10 @@ class ScroogeTest < ActiveSupport::TestCase
68
72
  [:max_connections, :max_user_connections].each {|f| MysqlUser.find(:first).read_attribute(f)}
69
73
  end
70
74
 
75
+ test "should make 3 queries to fetch same item twice due to reload and remembering" do
76
+ assert_queries(3) {2.times {MysqlUser.find(:first).max_connections}}
77
+ end
78
+
71
79
  def first_callsite
72
80
  MysqlUser.scrooge_callsites.to_a.flatten.first
73
81
  end
@@ -1,3 +1,3 @@
1
1
  require 'rubygems'
2
2
  require 'activerecord'
3
- require File.join( File.dirname(__FILE__), '..', 'lib', 'scrooge' )
3
+ require File.join( File.dirname(__FILE__), '..', 'lib', 'scrooge' )
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: methodmissing-scrooge
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.3
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - "Lourens Naud\xC3\xA9"
@@ -10,12 +10,12 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-03-18 00:00:00 -07:00
13
+ date: 2009-03-22 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies: []
16
16
 
17
17
  description: An ActiveRecord attribute tracker to ensure production Ruby applications only fetch the database content needed to minimize wire traffic and reduce conversion overheads to native Ruby types.
18
- email: lourens@methodmissing.com or sds@switchstep.com
18
+ email: lourens@methodmissing.com or sdsykes@gmail.com
19
19
  executables: []
20
20
 
21
21
  extensions: []
@@ -38,9 +38,17 @@ files:
38
38
  - lib/optimizations/result_sets/result_array.rb
39
39
  - lib/optimizations/result_sets/updateable_result_set.rb
40
40
  - lib/scrooge.rb
41
+ - test/callsite_test.rb
41
42
  - test/helper.rb
42
43
  - test/models
44
+ - test/models/mysql_column_privilege.rb
45
+ - test/models/mysql_host.rb
46
+ - test/models/mysql_table_privilege.rb
43
47
  - test/models/mysql_user.rb
48
+ - test/optimizations
49
+ - test/optimizations/associations
50
+ - test/optimizations/associations/macro_test.rb
51
+ - test/scrooge_helper.rb
44
52
  - test/scrooge_test.rb
45
53
  - test/setup.rb
46
54
  - rails/init.rb