Skip to content

Commit

Permalink
Further progress
Browse files Browse the repository at this point in the history
  • Loading branch information
methodmissing committed Mar 10, 2009
1 parent 27e2ed9 commit 6c8af70
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 98 deletions.
32 changes: 16 additions & 16 deletions Rakefile
@@ -1,22 +1,22 @@
begin
require 'spec'
rescue LoadError
require 'rubygems'
require 'spec'
end

require 'spec/rake/spectask'
require 'rake'
require 'rake/testtask'
require 'test/helper'

$LOAD_PATH.unshift File.dirname(__FILE__) + '/../'
$LOAD_PATH.unshift File.dirname(__FILE__) + '/lib'
task :default => [:test_with_active_record, :test_scrooge]

require 'scrooge'
Rake::TestTask.new( :test_with_active_record ) { |t|
t.libs << AR_TEST_SUITE << Scrooge::Test.connection()
#t.test_files = ["/Users/lourens/projects/rails/activerecord/test/cases/validations_test.rb"]
t.test_files = Scrooge::Test.active_record_test_files()
t.ruby_opts = ["-r #{File.join( File.dirname(__FILE__), 'test', 'setup' )}"]
t.verbose = true
}

desc "Run the specs under spec"
Spec::Rake::SpecTask.new do |t|
t.spec_files = FileList['spec/**/*_spec.rb']
t.spec_opts << "-c"
end
Rake::TestTask.new( :test_scrooge ) { |t|
t.libs << 'lib'
t.test_files = Scrooge::Test.test_files()
t.verbose = true
}

begin
require 'jeweler'
Expand Down
170 changes: 98 additions & 72 deletions lib/scrooge.rb
Expand Up @@ -21,11 +21,15 @@ class << self
#
alias :find_by_sql_without_scrooge :find_by_sql
def find_by_sql(sql)
saved_settings = Thread.current[:scrooge_settings]
Thread.current[:scrooge_settings] = nil
if scope_with_scrooge?(sql)
find_by_sql_with_scrooge(sql)
result = find_by_sql_with_scrooge(sql)
else
find_by_sql_without_scrooge(sql)
result = find_by_sql_without_scrooge(sql)
end
Thread.current[:scrooge_settings] = saved_settings
result
end

# Only scope n-1 rows by default.
Expand All @@ -37,14 +41,14 @@ def scope_with_scrooge?( sql )
# Populate the storage for a given callsite signature
#
def scrooge_callsite_set!(callsite_signature, set)
@@scrooge_callsites[table_name][callsite_signature] = set
@@scrooge_callsites[self.name][callsite_signature] = set
end

# Reference storage for a given callsite signature
#
def scrooge_callsite_set(callsite_signature)
@@scrooge_callsites[table_name] ||= {}
@@scrooge_callsites[table_name][callsite_signature]
@@scrooge_callsites[self.name] ||= {}
@@scrooge_callsites[self.name][callsite_signature]
end

# Augment a given callsite signature with a column / attribute.
Expand All @@ -68,10 +72,14 @@ def scrooge_sql( set )
#
def find_by_sql_with_scrooge( sql )
callsite_signature = (caller[ScroogeCallsiteSample] << sql.gsub(ScroogeRegexWhere, ScroogeBlankString)).hash
callsite_set = set_for_callsite( callsite_signature )
sql = sql.gsub(scrooge_select_regex, "SELECT #{scrooge_sql( callsite_set )}")
result = connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate_with_scrooge(record, callsite_signature, callsite_set) }
result
callsite_set = set_for_callsite(callsite_signature)
Thread.current[:scrooge_settings] = [callsite_signature, callsite_set]
sql = sql.gsub(scrooge_select_regex, "SELECT #{scrooge_sql(callsite_set)}")
result = connection.select_all(sanitize_sql(sql), "#{name} Load").collect! do |record|
record = instantiate(record)
record.scrooge_setup unless record.is_scrooged
record
end
end

# Return an attribute Set for a given callsite signature.
Expand All @@ -82,18 +90,26 @@ def set_for_callsite( callsite_signature )
@@scrooge_mutex.synchronize do
callsite_set = scrooge_callsite_set(callsite_signature)
unless callsite_set
callsite_set = Set.new([self.primary_key.to_s])
callsite_set = scrooge_default_callsite_set
scrooge_callsite_set!(callsite_signature, callsite_set)
end
callsite_set
end
end

def scrooge_default_callsite_set
if column_names.include?( self.inheritance_column.to_s )
Set.new([self.primary_key.to_s, self.inheritance_column.to_s])
else
Set.new([self.primary_key.to_s])
end
end

# Generate a regex that respects the table name as well to catch
# verbose SQL from JOINS etc.
#
def scrooge_select_regex
@@scrooge_select_regexes[table_name] ||= Regexp.compile( "SELECT (`?(?:#{table_name})?`?.?\\*)" )
@@scrooge_select_regexes[self.name] ||= Regexp.compile( "SELECT (`?(?:#{table_name})?`?.?\\*)" )
end

# Link the column to it's table.
Expand Down Expand Up @@ -153,58 +169,8 @@ def #{symbol}#{args}
EOV
evaluate_attribute_method attr_name, method_def
end

# TODO: override callback rather than this method
def instantiate_with_scrooge(record, callsite_signature, callsite_set)
object =
if subclass_name = record[inheritance_column]
# No type given.
if subclass_name.empty?
allocate

else
# Ignore type if no column is present since it was probably
# pulled in from a sloppy join.
unless columns_hash.include?(inheritance_column)
allocate

else
begin
compute_type(subclass_name).allocate
rescue NameError
raise SubclassNotFound,
"The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
"This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
"Please rename this column if you didn't intend it to be used for storing the inheritance class " +
"or overwrite #{self.to_s}.inheritance_column to use another column for that information."
end
end
end
else
allocate
end

object.instance_variable_set("@attributes", record)
object.instance_variable_set("@attributes_cache", Hash.new)

object.scrooge_callsite_signature = callsite_signature
# maintain separate record of columns that are loaded for just this record
# could be different from the class level columns
object.scrooge_own_callsite_set = callsite_set.dup
object.is_scrooged = true

if object.respond_to_without_attributes?(:after_find)
object.send(:callback, :after_find)
end

if object.respond_to_without_attributes?(:after_initialize)
object.send(:callback, :after_initialize)
end

object
end

end
end # class << self

# Make reload load the attributes that this model thinks it needs
# needed because reloading * will be defeated by scrooge
Expand All @@ -219,6 +185,26 @@ def reload(options = nil)
reload_without_scrooge(options)
end

# Setup scrooge settings on an AR object
# Maintain separate record of columns that have been loaded for just this record
# could be different from the class level columns
#
def scrooge_setup
callsite_signature, callsite_set = Thread.current[:scrooge_settings]
@scrooge_own_callsite_set ||= callsite_set.dup
@scrooge_callsite_signature = callsite_signature
@is_scrooged = true
end

# Callbacks after_find and after_initialize can happen before instantiate returns
# so make sure that this record is marked as scrooged first
#
alias_method :callback_without_scrooge, :callback
def callback(method)
scrooge_setup if Thread.current[:scrooge_settings] && !new_record?
callback_without_scrooge(method)
end

# Augment the callsite with a fresh column reference.
#
def augment_scrooge_attribute!(attr_name)
Expand All @@ -230,16 +216,30 @@ def augment_scrooge_attribute!(attr_name)
# but continue record missing columns after this
#
def scrooge_missing_attribute(attr_name)
Rails.logger.info "********** added #{attr_name} for #{self.class.table_name}"
logger.info "********** added #{attr_name} for #{self.class.table_name}"
scrooge_full_reload if !@scrooge_fully_loaded
augment_scrooge_attribute!(attr_name)
end

# Load the rest of the columns from the DB
# Take care not to reload the ones we already have, they
# might have been assigned to
#
def scrooge_full_reload
@scrooge_fully_loaded = true
reload(:select => self.class.scrooge_sql(all_column_names - @scrooge_own_callsite_set.to_a))
end

# Complete the object - load it and record all attribute names
# Used by delete / destroy and marshal
#
def scrooge_complete_object
if @is_scrooged
scrooge_full_reload unless @scrooge_fully_loaded
@scrooge_own_callsite_set.merge(all_column_names)
end
end

def all_column_names
@all_column_names ||= self.class.columns.collect(&:name)
end
Expand Down Expand Up @@ -268,6 +268,22 @@ def read_attribute_before_type_cast(attr_name)
end
end

# Delete should fully load all the attributes before the @attributes hash is frozen
#
alias_method :delete_without_scrooge, :delete
def delete
scrooge_complete_object
delete_without_scrooge
end

# Destroy should fully load all the attributes before the @attributes hash is frozen
#
alias_method :destroy_without_scrooge, :destroy
def destroy
scrooge_complete_object
destroy_without_scrooge
end

# Is the given column known to Scrooge ?
#
def scrooge_attr_present?(attr_name)
Expand All @@ -276,23 +292,33 @@ def scrooge_attr_present?(attr_name)

# Marshal
# force a full load if needed, and remove any possibility for missing attr flagging
# TODO: fix for nested objects?
#
def _dump(depth)
if @is_scrooged
scrooge_full_reload unless @scrooge_fully_loaded
@scrooge_own_callsite_set.merge(all_column_names)
end
Thread.current[:scrooge_dumping] = true
scrooge_complete_object
scrooge_dump_flag_this
str = Marshal.dump(self)
Thread.current[:scrooge_dumping] = false
scrooge_dump_unflag_this
str
end

def scrooge_dump_flag_this
Thread.current[:scrooge_dumping_objects] ||= []
Thread.current[:scrooge_dumping_objects] << object_id
end

def scrooge_dump_unflag_this
Thread.current[:scrooge_dumping_objects].delete(object_id)
end

def scrooge_dump_flagged?
Thread.current[:scrooge_dumping_objects] && Thread.current[:scrooge_dumping_objects].include?(object_id)
end

# Enables us to use Marshal.dump inside our _dump method without an infinite loop
#
alias_method :respond_to_without_scrooge, :respond_to?
def respond_to?(symbol, include_private=false)
if symbol == :_dump && Thread.current[:scrooge_dumping]
if symbol == :_dump && scrooge_dump_flagged?
false
else
respond_to_without_scrooge(symbol, include_private)
Expand All @@ -303,4 +329,4 @@ def self._load(str)
Marshal.load(str)
end
end
end
end
11 changes: 1 addition & 10 deletions rails/init.rb
@@ -1,10 +1 @@
require File.join(File.dirname(__FILE__), '..', 'lib', 'scrooge' )

# Hook to register through Scrooge::Framework::Base.inherited
Scrooge::Framework::Rails

Scrooge::Base.profile = Scrooge::Profile.setup!
Scrooge::Base.profile.framework.initialized do
Scrooge::Base.profile.log "Initialized"
Scrooge::Base.profile.strategy.execute!
end
require File.join(File.dirname(__FILE__), '..', 'lib', 'scrooge' )

0 comments on commit 6c8af70

Please sign in to comment.