diff options
Diffstat (limited to 'activerecord/lib/active_record')
14 files changed, 144 insertions, 135 deletions
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 62b9c94422..8027acfb83 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -82,8 +82,9 @@ module ActiveRecord constraint = table[key].eq(foreign_table[foreign_key]) if reflection.type - type = chain[i + 1].klass.base_class.name - constraint = constraint.and(table[reflection.type].eq(type)) + value = chain[i + 1].klass.base_class.name + bind_val = bind scope, table.table_name, reflection.type.to_s, value + scope = scope.where(table[reflection.type].eq(bind_val)) end scope = scope.joins(join(foreign_table, constraint)) diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index 6b6a088806..34de1a1f32 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -13,52 +13,44 @@ module ActiveRecord::Associations::Builder class Association #:nodoc: class << self - attr_accessor :valid_options attr_accessor :extensions end + self.extensions = [] - self.valid_options = [:class_name, :foreign_key, :validate] - self.extensions = [] + VALID_OPTIONS = [:class_name, :foreign_key, :validate] - attr_reader :model, :name, :scope, :options + attr_reader :name, :scope, :options - def self.build(*args, &block) - new(*args, &block).build - end - - def initialize(model, name, scope, options) + def self.build(model, name, scope, options, &block) raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol) - @model = model - @name = name - if scope.is_a?(Hash) - @scope = nil - @options = scope - else - @scope = scope - @options = options + options = scope + scope = nil end - if @scope && @scope.arity == 0 - prev_scope = @scope - @scope = proc { instance_exec(&prev_scope) } - end + builder = new(name, scope, options, &block) + reflection = builder.build(model) + builder.define_accessors model + builder.define_callbacks model, reflection + builder.define_extensions model + reflection end - def mixin - @model.generated_feature_methods - end + def initialize(name, scope, options) + @name = name + @scope = scope + @options = options - def build validate_options - define_accessors - configure_dependency if options[:dependent] - reflection = ActiveRecord::Reflection.create(macro, name, scope, options, model) - Association.extensions.each do |extension| - extension.build @model, reflection + + if scope && scope.arity == 0 + @scope = proc { instance_exec(&scope) } end - reflection + end + + def build(model) + ActiveRecord::Reflection.create(macro, name, scope, options, model) end def macro @@ -66,13 +58,23 @@ module ActiveRecord::Associations::Builder end def valid_options - Association.valid_options + Association.extensions.flat_map(&:valid_options) + VALID_OPTIONS + Association.extensions.flat_map(&:valid_options) end def validate_options options.assert_valid_keys(valid_options) end + def define_extensions(model) + end + + def define_callbacks(model, reflection) + add_before_destroy_callbacks(model, name) if options[:dependent] + Association.extensions.each do |extension| + extension.build model, reflection + end + end + # Defines the setter and getter methods for the association # class Post < ActiveRecord::Base # has_many :comments @@ -80,12 +82,13 @@ module ActiveRecord::Associations::Builder # # Post.first.comments and Post.first.comments= methods are defined by this method... - def define_accessors - define_readers - define_writers + def define_accessors(model) + mixin = model.generated_feature_methods + define_readers(mixin) + define_writers(mixin) end - def define_readers + def define_readers(mixin) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}(*args) association(:#{name}).reader(*args) @@ -93,7 +96,7 @@ module ActiveRecord::Associations::Builder CODE end - def define_writers + def define_writers(mixin) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}=(value) association(:#{name}).writer(value) @@ -101,17 +104,18 @@ module ActiveRecord::Associations::Builder CODE end - def configure_dependency + def valid_dependent_options + raise NotImplementedError + end + + private + + def add_before_destroy_callbacks(model, name) unless valid_dependent_options.include? options[:dependent] raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{options[:dependent]}" end - n = name - model.before_destroy lambda { |o| o.association(n).handle_dependency } - end - - def valid_dependent_options - raise NotImplementedError + model.before_destroy lambda { |o| o.association(name).handle_dependency } end end end diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 81293e464d..4e88b50ec5 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -12,17 +12,21 @@ module ActiveRecord::Associations::Builder !options[:polymorphic] end - def build - reflection = super - add_counter_cache_callbacks(reflection) if options[:counter_cache] - add_touch_callbacks(reflection) if options[:touch] - reflection - end - def valid_dependent_options [:destroy, :delete] end + def define_callbacks(model, reflection) + super + add_counter_cache_callbacks(model, reflection) if options[:counter_cache] + add_touch_callbacks(model, reflection) if options[:touch] + end + + def define_accessors(mixin) + super + add_counter_cache_methods mixin + end + private def add_counter_cache_methods(mixin) @@ -70,9 +74,8 @@ module ActiveRecord::Associations::Builder end end - def add_counter_cache_callbacks(reflection) + def add_counter_cache_callbacks(model, reflection) cache_column = reflection.counter_cache_column - add_counter_cache_methods mixin association = self model.after_create lambda { |record| @@ -117,7 +120,7 @@ module ActiveRecord::Associations::Builder end end - def add_touch_callbacks(reflection) + def add_touch_callbacks(model, reflection) foreign_key = reflection.foreign_key n = name touch = options[:touch] diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index 5eb11f98c8..7bd0687c0b 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -12,56 +12,50 @@ module ActiveRecord::Associations::Builder :after_add, :before_remove, :after_remove, :extend] end - attr_reader :block_extension, :extension_module + attr_reader :block_extension - def initialize(*args, &extension) - super(*args) - @block_extension = extension - end - - def build - wrap_block_extension - reflection = super - CALLBACKS.each { |callback_name| define_callback(callback_name) } - reflection + def initialize(name, scope, options) + super + @mod = nil + if block_given? + @mod = Module.new(&Proc.new) + @scope = wrap_scope @scope, @mod + end end - def writable? - true + def define_callbacks(model, reflection) + super + CALLBACKS.each { |callback_name| define_callback(model, callback_name) } end - def wrap_block_extension - if block_extension - @extension_module = mod = Module.new(&block_extension) - silence_warnings do - model.parent.const_set(extension_module_name, mod) - end - - prev_scope = @scope - - if prev_scope - @scope = proc { |owner| instance_exec(owner, &prev_scope).extending(mod) } - else - @scope = proc { extending(mod) } - end + def define_extensions(model) + if @mod + extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension" + model.parent.const_set(extension_module_name, @mod) end end - def extension_module_name - @extension_module_name ||= "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension" - end - - def define_callback(callback_name) + def define_callback(model, callback_name) full_callback_name = "#{callback_name}_for_#{name}" # TODO : why do i need method_defined? I think its because of the inheritance chain - model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name) - model.send("#{full_callback_name}=", Array(options[callback_name.to_sym])) + model.class_attribute full_callback_name unless model.method_defined?(full_callback_name) + callbacks = Array(options[callback_name.to_sym]).map do |callback| + case callback + when Symbol + ->(method, owner, record) { owner.send(callback, record) } + when Proc + ->(method, owner, record) { callback.call(owner, record) } + else + ->(method, owner, record) { callback.send(method, owner, record) } + end + end + model.send "#{full_callback_name}=", callbacks end # Defines the setter and getter methods for the collection_singular_ids. - def define_readers + def define_readers(mixin) super mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 @@ -71,7 +65,7 @@ module ActiveRecord::Associations::Builder CODE end - def define_writers + def define_writers(mixin) super mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 @@ -80,5 +74,15 @@ module ActiveRecord::Associations::Builder end CODE end + + private + + def wrap_scope(scope, mod) + if scope + proc { |owner| instance_exec(owner, &scope).extending(mod) } + else + proc { extending(mod) } + end + end end end diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb index 64ee24c7c6..55ec3bf4f1 100644 --- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -8,13 +8,8 @@ module ActiveRecord::Associations::Builder super + [:join_table, :association_foreign_key] end - def build - reflection = super - define_destroy_hook - reflection - end - - def define_destroy_hook + def define_callbacks(model, reflection) + super name = self.name model.send(:include, Module.new { class_eval <<-RUBY, __FILE__, __LINE__ + 1 diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb index 2406437a07..62d454ce55 100644 --- a/activerecord/lib/active_record/associations/builder/has_one.rb +++ b/activerecord/lib/active_record/associations/builder/has_one.rb @@ -14,12 +14,14 @@ module ActiveRecord::Associations::Builder !options[:through] end - def configure_dependency - super unless options[:through] - end - def valid_dependent_options [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception] end + + private + + def add_before_destroy_callbacks(model, name) + super unless options[:through] + end end end diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb index 7bd894d6ab..d97c0e9afd 100644 --- a/activerecord/lib/active_record/associations/builder/singular_association.rb +++ b/activerecord/lib/active_record/associations/builder/singular_association.rb @@ -10,14 +10,14 @@ module ActiveRecord::Associations::Builder true end - def define_accessors + def define_accessors(model) super - define_constructors if constructable? + define_constructors(model.generated_feature_methods) if constructable? end # Defines the (build|create)_association methods for belongs_to or has_one association - def define_constructors + def define_constructors(mixin) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def build_#{name}(*args, &block) association(:#{name}).build(*args, &block) diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index b4047b08bc..8ce02afef8 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -510,20 +510,13 @@ module ActiveRecord def callback(method, record) callbacks_for(method).each do |callback| - case callback - when Symbol - owner.send(callback, record) - when Proc - callback.call(owner, record) - else - callback.send(method, owner, record) - end + callback.call(method, owner, record) end end def callbacks_for(callback_name) full_callback_name = "#{callback_name}_for_#{reflection.name}" - owner.class.send(full_callback_name.to_sym) || [] + owner.class.send(full_callback_name) end # Should we deal with assoc.first or assoc.last by issuing an independent query to diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index bd99997e7f..ba7d2a3782 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -13,7 +13,7 @@ module ActiveRecord # 2. To get the type conditions for any STI models in the chain def target_scope scope = super - chain[1..-1].each do |reflection| + chain.drop(1).each do |reflection| scope.merge!( reflection.klass.all. except(:select, :create_with, :includes, :preload, :joins, :eager_load) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index b82d0fb872..ba1cb05d2c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -291,6 +291,14 @@ module ActiveRecord false end + # This is meant to be implemented by the adapters that support extensions + def disable_extension(name) + end + + # This is meant to be implemented by the adapters that support extensions + def enable_extension(name) + end + # A list of extensions, to be filled in by adapters that support them. At # the moment only postgresql does. def extensions diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 98126249df..342d1b1433 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -373,15 +373,11 @@ module ActiveRecord self end - def xml(options = {}) - column(args[0], :text, options) - end - private - def create_column_definition(name, type) - ColumnDefinition.new name, type - end + def create_column_definition(name, type) + ColumnDefinition.new name, type + end end class Table < ActiveRecord::ConnectionAdapters::Table diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index e088021112..2b1e997ef4 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -134,13 +134,12 @@ module ActiveRecord # Returns the Arel engine. def arel_engine - @arel_engine ||= begin + @arel_engine ||= if Base == self || connection_handler.retrieve_connection_pool(self) self else superclass.arel_engine end - end end private @@ -347,7 +346,7 @@ module ActiveRecord # Returns a hash of the given methods with their names as keys and returned values as values. def slice(*methods) - Hash[methods.map { |method| [method, public_send(method)] }].with_indifferent_access + Hash[methods.map! { |method| [method, public_send(method)] }].with_indifferent_access end def set_transaction_state(state) # :nodoc: diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 47d4f3637f..20ca4e3c91 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -592,14 +592,7 @@ module ActiveRecord row[fk_name] = ActiveRecord::FixtureSet.identify(value) end when :has_and_belongs_to_many - if (targets = row.delete(association.name.to_s)) - targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) - table_name = association.join_table - rows[table_name].concat targets.map { |target| - { association.foreign_key => row[primary_key_name], - association.association_foreign_key => ActiveRecord::FixtureSet.identify(target) } - } - end + handle_habtm(rows, row, association) end end end @@ -614,6 +607,17 @@ module ActiveRecord @primary_key_name ||= model_class && model_class.primary_key end + def handle_habtm(rows, row, association) + if (targets = row.delete(association.name.to_s)) + targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) + table_name = association.join_table + rows[table_name].concat targets.map { |target| + { association.foreign_key => row[primary_key_name], + association.association_foreign_key => ActiveRecord::FixtureSet.identify(target) } + } + end + end + def has_primary_key_column? @has_primary_key_column ||= primary_key_name && model_class.columns.any? { |c| c.name == primary_key_name } diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 2fa7b1194d..73d154e03e 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -564,7 +564,7 @@ module ActiveRecord # of scopes corresponding to the chain. def scope_chain @scope_chain ||= begin - scope_chain = source_reflection.scope_chain + scope_chain = source_reflection.scope_chain.map(&:dup) # Add to it the scope from this reflection (if any) scope_chain.first << scope if scope |