Permalink
Browse files

simplified loading mechanic (also fixed leak)

  • Loading branch information...
1 parent a681a0b commit 373d3a378bdc59cbc64f8d134a302a1a6470f38d @sirlantis committed Feb 14, 2011
View
@@ -20,60 +20,58 @@ module Models
extend ActiveSupport::Autoload
autoload :AttributeDiff
autoload :ModelVersion
+
+ class << self
+ def const_missing_with_generator(sym)
+ result = nil
+ result = case sym.to_s
+ when /^(.+)AttributeDiff$/
+ AttributeDiff.generate_subclass!($1.constantize)
+ # when /^(.+)ModelVersion$/
+ # ModelVersion.for_class($1.constantize)
+ end
+ result || const_missing_without_generator
+ end
+
+ alias const_missing_without_generator const_missing
+ alias const_missing const_missing_with_generator
+ end
end
IGNORED_ATTRIBUTES = [:id]
@@historical_models = []
- @@pending_models = []
@@autospawn_creation = true
@@booted = false
mattr_accessor :autospawn_creation
- def self.booted?; @@booted; end
-
def self.reset!
- @@booted = false
-
historical_models.each do |m|
Historical::Models::ModelVersion.descendants.delete(m.historical_version_class)
Historical::Models::ModelVersion::Diff.descendants.delete(m.historical_diff_class)
Historical::Models::ModelVersion::Meta.descendants.delete(m.historical_meta_class)
end
- historical_models.clear
+ @@historical_models.clear
+ @@booted = false
end
- # Generates all customized models.
+ # Eager-loads all models
def self.boot!
- return if booted?
- @@booted = true
-
- Historical::Models::AttributeDiff.generate_subclasses!
-
- pending_models.each do |klass|
- register(klass)
+ historical_models.each do |klass|
+ klass.generate_historical_models!
end
- pending_models.clear
+ @booted = true
end
- def self.register(klass, now = false)
- if now or booted?
- if klass.table_exists?
- klass.generate_historical_models!
- historical_models << klass
- else
- # puts "Warning: Table for class #{klass} does not exist."
- end
- else
- pending_models << klass
- end
+ def self.register(klass)
+ historical_models << klass
+ klass.generate_historical_models! if @booted
end
protected
mattr_reader :historical_models
- mattr_reader :pending_models
end
@@ -34,6 +34,7 @@ module ClassMethods
# Generates the customized classes ({Models::ModelVersion}, {Models::ModelVersion::Meta}, {Models::ModelVersion::Diff}) for this model.
def generate_historical_models!
return if historical_version_class
+ return unless table_exists?
builder = Historical::ClassBuilder.new(self)
builder.apply!
end
@@ -1,83 +1,85 @@
module Historical::Models
# The diff of an attribute (specialized by a type qualifier)
class AttributeDiff
-
+
# MongoMappers supported native Ruby types
SUPPORTED_NATIVE_RUBY_TYPES = %w{Date String Time Boolean Integer Float Binary}
-
+
include MongoMapper::EmbeddedDocument
plugin Historical::MongoMapper::Enhancements
-
+
# @return [String] The attribute name
key :attribute, String, :required => true
-
+
# @return [String] The attribute type (string, integer, float)
key :attribute_type, String, :required => true
-
+
# @return The old value
# @private
key :_old_value, Object
-
+
# @return The new value
# @private
key :_new_value, Object
-
+
# @return [ModelVersion::Diff] The parent diff model.
attr_accessor :parent
-
+
alias_method :old_value, :_old_value
- alias_method :new_value, :_new_value
+ alias_method :new_value, :_new_value
protected :_old_value=, :_new_value=, :_old_value, :_new_value
-
+
# @return [Class] The specialized {AttributeDiff} class for a attribute type
# @private
def self.specialized_for(parent, attribute)
type = detect_attribute_type(parent, attribute)
Historical::Models.const_get(specialized_class_name(type))
end
-
+
def old_value=(value)
self._old_value = value
end
-
+
def new_value=(value)
self._new_value = value
end
-
+
# Get attribute type for an attribute
def self.detect_attribute_type(parent, attribute)
model_class = parent.record.class
-
+
column = model_class.columns.select do |c|
c.name.to_s == attribute.to_s
end.first
-
+
column ? column.type.to_s : nil
end
-
- # Generates subclasses of {AttributeDiff} for each type in {SUPPORTED_NATIVE_RUBY_TYPES}
+
+ def self.namespace
+ Historical::Models
+ end
+
+ # Generates subclasses of {AttributeDiff} for types in {SUPPORTED_NATIVE_RUBY_TYPES}
# @private
- def self.generate_subclasses!
- SUPPORTED_NATIVE_RUBY_TYPES.each do |type|
- diff_class = Class.new(self)
- type_class = type.constantize
+ def self.generate_subclass!(type_class)
+ raise "not supported: #{type_class}" unless SUPPORTED_NATIVE_RUBY_TYPES.include?(type_class.name)
+ const_name = specialized_class_name(type_class)
+
+ namespace.send(:remove_const, const_name) if namespace.const_defined?(const_name)
+
+ Class.new(self).tap do |diff_class|
diff_class.send :key, :_old_value, type_class
diff_class.send :key, :_new_value, type_class
- const_name = specialized_class_name(type_class)
- namespace = Historical::Models
-
- namespace.send(:remove_const, const_name) if namespace.const_defined?(const_name)
namespace.const_set(const_name, diff_class)
-
diff_class.unloadable
end
end
-
+
protected
-
+
# A unique subclass name for the type diffs.
# @private
def self.specialized_class_name(type)
@@ -89,7 +91,7 @@ def self.specialized_class_name(type)
end
"#{ruby_type}#{simple_name}"
end
-
+
# @return [String] Pure classname without modules
# @private
def self.simple_name
@@ -2,94 +2,94 @@ module Historical::Models
# A complete snapshot of a model.
class ModelVersion
extend ActiveSupport::Autoload
-
+
autoload :Diff
autoload :Meta
-
+
include MongoMapper::Document
plugin Historical::MongoMapper::SciAntidote
plugin Historical::MongoMapper::Enhancements
-
+
validate :validate_diff
validate :validate_meta
-
+
validates_presence_of :meta
-
+
belongs_to_active_record :_record, :polymorphic => true, :required => true, :index => true
# The diff between the current and the previous version (if exists)
# @return [Diff]
attr_reader :diff
-
+
# The meta-data associated with the diff (i.e. this version)
# @return [Meta]
attr_reader :meta
-
+
alias_method :record, :_record
-
+
delegate :creation?, :update?, :to => :meta
-
+
# @return [Array<ModelVersion>] All other versions of the associated record
def siblings
self.class.for_record(_record_id, _record_type)
end
-
+
# @return [ModelVersion] All the previous versions (immediate predecessor first)
def previous_versions
(new? ? siblings : siblings.where(:"meta.created_at".lte => meta.created_at, :_id.lt => _id)).sort(:"meta.created_at".desc, :_id.desc)
end
-
+
# @return [ModelVersion] The immediate predecessor version
def previous
previous_versions.first
end
-
+
# @return [Array<ModelVersion>] All the next versions (immediate successor first)
def next_versions
siblings.where(:"meta.created_at".gte => meta.created_at, :_id.gt => _id).sort(:"meta.created_at".asc, :_id.asc)
end
-
+
# @return [ModelVersion] The immediate successor version
def next
next_versions.first
end
-
+
# @return [Integer] The current version index (zero-based)
def version_index
previous_versions.count
end
-
+
# Restores the current version.
# @param base A reference to the record (to reduce DB queries)
# @return [Object] The restored version.
def restore(base = nil)
base ||= record
-
+
base.clone.tap do |r|
r.class.columns.each do |c|
attr = c.name.to_sym
next if Historical::IGNORED_ATTRIBUTES.include? attr
-
+
r[attr] = send(attr)
end
-
+
r.id = base.id
r.historical_version = version_index
r.clear_association_cache
r.invalidate_history!
end
end
-
+
# Prevents the restored version from being saved or modifed
# @return [Object] A frozen instance of {#restore}
def restore_with_protection(*args)
restore_without_protection(*args).tap do |r|
r.readonly!
end
end
-
+
alias_method_chain :restore, :protection
-
+
# @return [Array<ModelVersion>] All versions for the provided record
# @overload for_record(id, type)
# @param id [Integer] The id of the record
@@ -105,7 +105,11 @@ def self.for_record(record_or_id, type = nil)
ModelVersion.where(:_record_id => record_or_id.id, :_record_type => record_or_id.class.name)
end
end
-
+
+ def try_generate_subclass!(class_name)
+
+ end
+
# @return [Class] Customized class definition for a record class (e.g. TopicVersion, MessageVersion).
def self.for_class(source_class)
Class.new(self).tap do |cls|
@@ -114,33 +118,33 @@ def self.for_class(source_class)
type = Historical::ActiveRecord.sql_to_type(col.type)
cls.send :key, col.name, type.constantize
end
-
+
cls.send :one, :diff, :class => source_class.historical_diff_class
cls.send :one, :meta, :class => source_class.historical_meta_class
-
+
cls.ensure_embedded_indexes
end
end
-
+
def self.load(attrs)
return nil if attrs.nil?
-
+
if (record_type = attrs['_record_type']).present?
record_type.constantize.historical_version_class
else
self
end.allocate.initialize_from_database(attrs)
end
-
+
protected
-
+
# Cascading validation for `diff`
def validate_diff
if diff.present? and !diff.valid?
errors.add(:diff, "not valid")
end
end
-
+
# Cascading validation for `meta`
def validate_meta
if meta.present? and !meta.valid?
Oops, something went wrong.

0 comments on commit 373d3a3

Please sign in to comment.