diff options
Diffstat (limited to 'activerecord/lib/active_record')
43 files changed, 411 insertions, 350 deletions
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 9d1c12ec62..d075edc159 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -223,7 +223,8 @@ module ActiveRecord reader_method(name, class_name, mapping, allow_nil, constructor) writer_method(name, class_name, mapping, allow_nil, converter) - create_reflection(:composed_of, part_id, nil, options, self) + reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self) + Reflection.add_reflection self, part_id, reflection end private diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 6fd4f3042c..5ceda933f2 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1205,7 +1205,8 @@ module ActiveRecord # has_many :reports, -> { readonly } # has_many :subscribers, through: :subscriptions, source: :user def has_many(name, scope = nil, options = {}, &extension) - Builder::HasMany.build(self, name, scope, options, &extension) + reflection = Builder::HasMany.build(self, name, scope, options, &extension) + Reflection.add_reflection self, name, reflection end # Specifies a one-to-one association with another class. This method should only be used @@ -1308,7 +1309,8 @@ module ActiveRecord # has_one :club, through: :membership # has_one :primary_address, -> { where primary: true }, through: :addressables, source: :addressable def has_one(name, scope = nil, options = {}) - Builder::HasOne.build(self, name, scope, options) + reflection = Builder::HasOne.build(self, name, scope, options) + Reflection.add_reflection self, name, reflection end # Specifies a one-to-one association with another class. This method should only be used @@ -1420,7 +1422,8 @@ module ActiveRecord # belongs_to :company, touch: true # belongs_to :company, touch: :employees_last_updated_at def belongs_to(name, scope = nil, options = {}) - Builder::BelongsTo.build(self, name, scope, options) + reflection = Builder::BelongsTo.build(self, name, scope, options) + Reflection.add_reflection self, name, reflection end # Specifies a many-to-many relationship with another class. This associates two classes via an @@ -1557,7 +1560,8 @@ module ActiveRecord # has_and_belongs_to_many :categories, join_table: "prods_cats" # has_and_belongs_to_many :categories, -> { readonly } def has_and_belongs_to_many(name, scope = nil, options = {}, &extension) - Builder::HasAndBelongsToMany.build(self, name, scope, options, &extension) + reflection = Builder::HasAndBelongsToMany.build(self, name, scope, options, &extension) + Reflection.add_reflection self, name, reflection end end end diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 338d5d2afe..67d24b35d1 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -117,7 +117,7 @@ module ActiveRecord # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the # through association's scope) def target_scope - AssociationRelation.new(klass, klass.arel_table, self).merge!(klass.all) + AssociationRelation.create(klass, klass.arel_table, self).merge!(klass.all) end # Loads the \target if needed and returns it. diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index f1bec5787a..bdfafa5066 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -119,7 +119,7 @@ module ActiveRecord # the owner klass.table_name else - reflection.table_name + super end end diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index bbc1c20f60..6b6a088806 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -2,7 +2,7 @@ # used by all associations. # # The hierarchy is defined as follows: -# Association +# Association # - SingularAssociation # - BelongsToAssociation # - HasOneAssociation @@ -14,11 +14,13 @@ module ActiveRecord::Associations::Builder class Association #:nodoc: class << self attr_accessor :valid_options + attr_accessor :extensions end self.valid_options = [:class_name, :foreign_key, :validate] + self.extensions = [] - attr_reader :model, :name, :scope, :options, :reflection + attr_reader :model, :name, :scope, :options def self.build(*args, &block) new(*args, &block).build @@ -48,15 +50,15 @@ module ActiveRecord::Associations::Builder @model.generated_feature_methods end - include Module.new { def build; end } - def build validate_options define_accessors configure_dependency if options[:dependent] - @reflection = model.create_reflection(macro, name, scope, options, model) - super # provides an extension point - @reflection + reflection = ActiveRecord::Reflection.create(macro, name, scope, options, model) + Association.extensions.each do |extension| + extension.build @model, reflection + end + reflection end def macro @@ -64,18 +66,18 @@ module ActiveRecord::Associations::Builder end def valid_options - Association.valid_options + Association.valid_options + Association.extensions.flat_map(&:valid_options) end def validate_options options.assert_valid_keys(valid_options) end - + # Defines the setter and getter methods for the association # class Post < ActiveRecord::Base # has_many :comments # end - # + # # Post.first.comments and Post.first.comments= methods are defined by this method... def define_accessors diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index d4e1a0dda1..81293e464d 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -92,7 +92,7 @@ module ActiveRecord::Associations::Builder end def self.touch_record(o, foreign_key, name, touch) # :nodoc: - old_foreign_id = o.attribute_was(foreign_key) + old_foreign_id = o.changed_attributes[foreign_key] if old_foreign_id klass = o.association(name).klass diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb index 76e48e66e5..7bd894d6ab 100644 --- a/activerecord/lib/active_record/associations/builder/singular_association.rb +++ b/activerecord/lib/active_record/associations/builder/singular_association.rb @@ -16,7 +16,7 @@ module ActiveRecord::Associations::Builder end # Defines the (build|create)_association methods for belongs_to or has_one association - + def define_constructors mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def build_#{name}(*args, &block) diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 6ddbd4955d..b4047b08bc 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -34,7 +34,7 @@ module ActiveRecord reload end - @proxy ||= CollectionProxy.new(klass, self) + @proxy ||= CollectionProxy.create(klass, self) end # Implements the writer method, e.g. foo.items= for Foo.has_many :items diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index e0715d9dd1..58fc00d811 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -25,11 +25,8 @@ module ActiveRecord attr_reader :tables delegate :options, :through_reflection, :source_reflection, :chain, :to => :reflection - delegate :table, :table_name, :to => :parent, :prefix => :parent delegate :alias_tracker, :to => :join_dependency - alias :alias_suffix :parent_table_name - def initialize(reflection, join_dependency, parent = nil) reflection.check_validity! @@ -47,6 +44,9 @@ module ActiveRecord @tables = construct_tables.reverse end + def parent_table_name; parent.table_name; end + alias :alias_suffix :parent_table_name + def ==(other) other.class == self.class && other.reflection == reflection && @@ -64,15 +64,20 @@ module ActiveRecord end end - def join_to(manager) + def join_constraints + joins = [] tables = @tables.dup - foreign_table = parent_table + + foreign_table = parent.table foreign_klass = parent.base_klass + scope_chain_iter = reflection.scope_chain.reverse_each + # The chain starts with the target table, but we want to end with it here (makes # more sense in this context), so we reverse - chain.reverse.each_with_index do |reflection, i| + chain.reverse_each do |reflection| table = tables.shift + klass = reflection.klass case reflection.source_macro when :belongs_to @@ -80,11 +85,10 @@ module ActiveRecord foreign_key = reflection.foreign_key when :has_and_belongs_to_many # Join the join table first... - manager.from(join( + joins << join( table, table[reflection.foreign_key]. - eq(foreign_table[reflection.active_record_primary_key]) - )) + eq(foreign_table[reflection.active_record_primary_key])) foreign_table, table = table, tables.shift @@ -95,38 +99,39 @@ module ActiveRecord foreign_key = reflection.active_record_primary_key end - constraint = build_constraint(reflection, table, key, foreign_table, foreign_key) + constraint = build_constraint(klass, table, key, foreign_table, foreign_key) - scope_chain_items = scope_chain[i] + scope_chain_items = scope_chain_iter.next.map do |item| + if item.is_a?(Relation) + item + else + ActiveRecord::Relation.create(klass, table).instance_exec(self, &item) + end + end if reflection.type - scope_chain_items += [ - ActiveRecord::Relation.new(reflection.klass, table) + scope_chain_items << + ActiveRecord::Relation.create(klass, table) .where(reflection.type => foreign_klass.base_class.name) - ] end - scope_chain_items += [reflection.klass.send(:build_default_scope)].compact + scope_chain_items.concat [klass.send(:build_default_scope)].compact - constraint = scope_chain_items.inject(constraint) do |chain, item| - unless item.is_a?(Relation) - item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item) - end + rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right| + left.merge right + end - if item.arel.constraints.empty? - chain - else - chain.and(item.arel.constraints) - end + if rel && !rel.arel.constraints.empty? + constraint = constraint.and rel.arel.constraints end - manager.from(join(table, constraint)) + joins << join(table, constraint) # The current table in this iteration becomes the foreign table in the next - foreign_table, foreign_klass = table, reflection.klass + foreign_table, foreign_klass = table, klass end - manager + joins end # Builds equality condition. @@ -144,13 +149,13 @@ module ActiveRecord # foreign_table #=> #<Arel::Table @name="physicians" ...> # foreign_key #=> id # - def build_constraint(reflection, table, key, foreign_table, foreign_key) + def build_constraint(klass, table, key, foreign_table, foreign_key) constraint = table[key].eq(foreign_table[foreign_key]) - if reflection.klass.finder_needs_type_condition? + if klass.finder_needs_type_condition? constraint = table.create_and([ constraint, - reflection.klass.send(:type_condition, table) + klass.send(:type_condition, table) ]) end @@ -169,11 +174,6 @@ module ActiveRecord def aliased_table_name table.table_alias || table.name end - - def scope_chain - @scope_chain ||= reflection.scope_chain.reverse - end - end end end diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb index 5a41b40c8f..27b70edf1a 100644 --- a/activerecord/lib/active_record/associations/join_helper.rb +++ b/activerecord/lib/active_record/associations/join_helper.rb @@ -19,7 +19,7 @@ module ActiveRecord if reflection.source_macro == :has_and_belongs_to_many tables << alias_tracker.aliased_table_for( - (reflection.source_reflection || reflection).join_table, + reflection.source_reflection.join_table, table_alias_for(reflection, true) ) end diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 82bf426b22..2317e34bc0 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -85,7 +85,7 @@ module ActiveRecord def initialize(records, associations, preload_scope = nil) @records = Array.wrap(records).compact.uniq @associations = Array.wrap(associations) - @preload_scope = preload_scope || Relation.new(nil, nil) + @preload_scope = preload_scope || Relation.create(nil, nil) end def run diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 00237d7caf..208da2cb77 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -8,7 +8,7 @@ module ActiveRecord include ActiveModel::AttributeMethods included do - @attribute_methods_generated = false + initialize_generated_modules include Read include Write include BeforeTypeCast @@ -19,6 +19,40 @@ module ActiveRecord include Serialization end + AttrNames = Module.new { + def self.set_name_cache(name, value) + const_name = "ATTR_#{name}" + unless const_defined? const_name + const_set const_name, value.dup.freeze + end + end + } + + class AttributeMethodCache + include Mutex_m + + def initialize + super + @module = Module.new + @method_cache = {} + end + + def [](name) + synchronize do + @method_cache.fetch(name) { + safe_name = name.unpack('h*').first + temp_method = "__temp__#{safe_name}" + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + @module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__ + @method_cache[name] = @module.instance_method temp_method + } + end + end + + private + def method_body; raise NotImplementedError; end + end + module ClassMethods def inherited(child_class) #:nodoc: child_class.initialize_generated_modules @@ -37,20 +71,19 @@ module ActiveRecord # Use a mutex; we don't want two thread simultaneously trying to define # attribute methods. generated_attribute_methods.synchronize do - return if attribute_methods_generated? + return false if @attribute_methods_generated superclass.define_attribute_methods unless self == base_class super(column_names) @attribute_methods_generated = true end - end - - def attribute_methods_generated? # :nodoc: - @attribute_methods_generated + true end def undefine_attribute_methods # :nodoc: - super if attribute_methods_generated? - @attribute_methods_generated = false + generated_attribute_methods.synchronize do + super if @attribute_methods_generated + @attribute_methods_generated = false + end end # Raises a <tt>ActiveRecord::DangerousAttributeError</tt> exception when an @@ -132,9 +165,7 @@ module ActiveRecord # If we haven't generated any methods yet, generate them, then # see if we've created the method we're looking for. def method_missing(method, *args, &block) # :nodoc: - unless self.class.attribute_methods_generated? - self.class.define_attribute_methods - + if self.class.define_attribute_methods if respond_to_without_attributes?(method) send(method, *args, &block) else @@ -145,20 +176,6 @@ module ActiveRecord end end - def attribute_missing(match, *args, &block) # :nodoc: - if self.class.columns_hash[match.attr_name] - ActiveSupport::Deprecation.warn( - "The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \ - "dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \ - "is a column of the table. If this error has happened through normal usage of Active " \ - "Record (rather than through your own code or external libraries), please report it as " \ - "a bug." - ) - end - - super - end - # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>, # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt> # which will all return +true+. It also define the attribute methods if they have @@ -177,7 +194,7 @@ module ActiveRecord # person.respond_to(:nothing) # => false def respond_to?(name, include_private = false) name = name.to_s - self.class.define_attribute_methods unless self.class.attribute_methods_generated? + self.class.define_attribute_methods result = super # If the result is false the answer is false. @@ -242,7 +259,7 @@ module ActiveRecord # person = Person.create!(name: 'David Heinemeier Hansson ' * 3) # # person.attribute_for_inspect(:name) - # # => "\"David Heinemeier Hansson David Heinemeier Hansson D...\"" + # # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\"" # # person.attribute_for_inspect(:created_at) # # => "\"2012-10-22 00:15:07\"" @@ -250,7 +267,7 @@ module ActiveRecord value = read_attribute(attr_name) if value.is_a?(String) && value.length > 50 - "#{value[0..50]}...".inspect + "#{value[0, 50]}...".inspect elsif value.is_a?(Date) || value.is_a?(Time) %("#{value.to_s(:db)}") else diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 506f5d75f9..1cf3aba41c 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -1,6 +1,38 @@ +require 'active_support/core_ext/module/method_transplanting' + module ActiveRecord module AttributeMethods module Read + ReaderMethodCache = Class.new(AttributeMethodCache) { + private + # We want to generate the methods via module_eval rather than + # define_method, because define_method is slower on dispatch. + # Evaluating many similar methods may use more memory as the instruction + # sequences are duplicated and cached (in MRI). define_method may + # be slower on dispatch, but if you're careful about the closure + # created, then define_method will consume much less memory. + # + # But sometimes the database might return columns with + # characters that are not allowed in normal method names (like + # 'my_column(omg)'. So to work around this we first define with + # the __temp__ identifier, and then use alias method to rename + # it to what we want. + # + # We are also defining a constant to hold the frozen string of + # the attribute name. Using a constant means that we do not have + # to allocate an object on each call to the attribute method. + # Making it frozen means that it doesn't get duped when used to + # key the @attributes_cache in read_attribute. + def method_body(method_name, const_name) + <<-EOMETHOD + def #{method_name} + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name} + read_attribute(name) { |n| missing_attribute(n, caller) } + end + EOMETHOD + end + }.new + extend ActiveSupport::Concern ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date] @@ -32,30 +64,30 @@ module ActiveRecord protected - # We want to generate the methods via module_eval rather than - # define_method, because define_method is slower on dispatch and - # uses more memory (because it creates a closure). - # - # But sometimes the database might return columns with - # characters that are not allowed in normal method names (like - # 'my_column(omg)'. So to work around this we first define with - # the __temp__ identifier, and then use alias method to rename - # it to what we want. - # - # We are also defining a constant to hold the frozen string of - # the attribute name. Using a constant means that we do not have - # to allocate an object on each call to the attribute method. - # Making it frozen means that it doesn't get duped when used to - # key the @attributes_cache in read_attribute. - def define_method_attribute(name) - safe_name = name.unpack('h*').first - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def __temp__#{safe_name} - read_attribute(AttrNames::ATTR_#{safe_name}) { |n| missing_attribute(n, caller) } + if Module.methods_transplantable? + def define_method_attribute(name) + method = ReaderMethodCache[name] + generated_attribute_methods.module_eval { define_method name, method } + end + else + def define_method_attribute(name) + safe_name = name.unpack('h*').first + temp_method = "__temp__#{safe_name}" + + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + + generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + def #{temp_method} + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} + read_attribute(name) { |n| missing_attribute(n, caller) } + end + STR + + generated_attribute_methods.module_eval do + alias_method name, temp_method + undef_method temp_method end - alias_method #{name.inspect}, :__temp__#{safe_name} - undef_method :__temp__#{safe_name} - STR + end end private diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index cd33494cc3..c853fc0917 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -1,6 +1,21 @@ +require 'active_support/core_ext/module/method_transplanting' + module ActiveRecord module AttributeMethods module Write + WriterMethodCache = Class.new(AttributeMethodCache) { + private + + def method_body(method_name, const_name) + <<-EOMETHOD + def #{method_name}(value) + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name} + write_attribute(name, value) + end + EOMETHOD + end + }.new + extend ActiveSupport::Concern included do @@ -10,17 +25,29 @@ module ActiveRecord module ClassMethods protected - # See define_method_attribute in read.rb for an explanation of - # this code. - def define_method_attribute=(name) - safe_name = name.unpack('h*').first - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def __temp__#{safe_name}=(value) - write_attribute(AttrNames::ATTR_#{safe_name}, value) - end - alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= - undef_method :__temp__#{safe_name}= - STR + if Module.methods_transplantable? + # See define_method_attribute in read.rb for an explanation of + # this code. + def define_method_attribute=(name) + method = WriterMethodCache[name] + generated_attribute_methods.module_eval { + define_method "#{name}=", method + } + end + else + def define_method_attribute=(name) + safe_name = name.unpack('h*').first + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + + generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + def __temp__#{safe_name}=(value) + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} + write_attribute(name, value) + end + alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= + undef_method :__temp__#{safe_name}= + STR + end end end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index a8a1847554..b30d1eb0a6 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -127,17 +127,17 @@ module ActiveRecord extend ActiveSupport::Concern module AssociationBuilderExtension #:nodoc: - def build + def self.build(model, reflection) model.send(:add_autosave_association_callbacks, reflection) - super + end + + def self.valid_options + [ :autosave ] end end included do - Associations::Builder::Association.class_eval do - self.valid_options << :autosave - include AssociationBuilderExtension - end + Associations::Builder::Association.extensions << AssociationBuilderExtension end module ClassMethods @@ -343,6 +343,7 @@ module ActiveRecord end records.each do |record| + next if record.destroyed? saved = true diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 811749c7fd..cfdcae7f63 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -624,7 +624,7 @@ module ActiveRecord end response - rescue + rescue Exception ActiveRecord::Base.clear_active_connections! unless testing raise end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index c64b542286..97e1bc5379 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -18,8 +18,7 @@ module ActiveRecord end end - # Returns an array of record hashes with the column names as keys and - # column values as values. + # Returns an ActiveRecord::Result instance. def select_all(arel, name = nil, binds = []) select(to_sql(arel, binds), name, binds) end @@ -27,8 +26,7 @@ module ActiveRecord # Returns a record hash with the column names as keys and column values # as values. def select_one(arel, name = nil, binds = []) - result = select_all(arel, name, binds) - result.first if result + select_all(arel, name, binds).first end # Returns a single value from a record @@ -355,8 +353,7 @@ module ActiveRecord subselect end - # Returns an array of record hashes with the column names as keys and - # column values as values. + # Returns an ActiveRecord::Result instance. def select(sql, name = nil, binds = []) end undef_method :select @@ -377,14 +374,14 @@ module ActiveRecord update_sql(sql, name) end - def sql_for_insert(sql, pk, id_value, sequence_name, binds) - [sql, binds] - end - - def last_inserted_id(result) - row = result.rows.first - row && row.first - end + def sql_for_insert(sql, pk, id_value, sequence_name, binds) + [sql, binds] + end + + def last_inserted_id(result) + row = result.rows.first + row && row.first + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 41e07fbda9..e6b3c8ec9f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -20,6 +20,12 @@ module ActiveRecord attr_reader :query_cache, :query_cache_enabled + def initialize(*) + super + @query_cache = Hash.new { |h,sql| h[sql] = {} } + @query_cache_enabled = false + end + # Enable the query cache within the block. def cache old, @query_cache_enabled = @query_cache_enabled, true diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 9a1923dec5..4b425494d0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -694,15 +694,6 @@ module ActiveRecord end end - # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause. - # - # distinct("posts.id", ["posts.created_at desc"]) - # - def distinct(columns, order_by) - ActiveSupport::Deprecation.warn("#distinct is deprecated and shall be removed from future releases.") - "DISTINCT #{columns_for_distinct(columns, order_by)}" - end - # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT. # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax - they # require the order columns appear in the SELECT. @@ -812,12 +803,6 @@ module ActiveRecord index_name end - def columns_for_remove(table_name, *column_names) - ActiveSupport::Deprecation.warn("columns_for_remove is deprecated and will be removed in the future") - raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.blank? - column_names.map {|column_name| quote_column_name(column_name) } - end - def rename_table_indexes(table_name, new_name) indexes(new_name).each do |index| generated_index_name = index_name(table_name, column: index.columns) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index e232cad982..b82d0fb872 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -95,8 +95,6 @@ module ActiveRecord @last_use = false @logger = logger @pool = pool - @query_cache = Hash.new { |h,sql| h[sql] = {} } - @query_cache_enabled = false @schema_cache = SchemaCache.new self @visitor = nil end @@ -307,8 +305,8 @@ module ActiveRecord # QUOTING ================================================== - # Returns a bind substitution value given a +column+ and list of current - # +binds+. + # Returns a bind substitution value given a bind +index+ and +column+ + # NOTE: The column param is currently being used by the sqlserver-adapter def substitute_at(column, index) Arel::Nodes::BindParam.new '?' end @@ -387,20 +385,6 @@ module ActiveRecord @transaction.number end - def increment_open_transactions - ActiveSupport::Deprecation.warn "#increment_open_transactions is deprecated and has no effect" - end - - def decrement_open_transactions - ActiveSupport::Deprecation.warn "#decrement_open_transactions is deprecated and has no effect" - end - - def transaction_joinable=(joinable) - message = "#transaction_joinable= is deprecated. Please pass the :joinable option to #begin_transaction instead." - ActiveSupport::Deprecation.warn message - @transaction.joinable = joinable - end - def create_savepoint end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index edeb338310..28c7cff1cc 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -1,6 +1,6 @@ require 'active_record/connection_adapters/abstract_mysql_adapter' -gem 'mysql2', '~> 0.3.10' +gem 'mysql2', '~> 0.3.13' require 'mysql2' module ActiveRecord @@ -229,8 +229,7 @@ module ActiveRecord alias exec_without_stmt exec_query - # Returns an array of record hashes with the column names as keys and - # column values as values. + # Returns an ActiveRecord::Result instance. def select(sql, name = nil, binds = []) exec_query(sql, name) end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 9b5170f657..751655e61c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -218,13 +218,6 @@ module ActiveRecord execute "ROLLBACK" end - def outside_transaction? - message = "#outside_transaction? is deprecated. This method was only really used " \ - "internally, but you can use #transaction_open? instead." - ActiveSupport::Deprecation.warn message - @connection.transaction_status == PGconn::PQTRANS_IDLE - end - def create_savepoint execute("SAVEPOINT #{current_savepoint_name}") end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index a651b6c32e..3fce8de1ba 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -384,8 +384,9 @@ module ActiveRecord def change_column(table_name, column_name, type, options = {}) clear_cache! quoted_table_name = quote_table_name(table_name) - - execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" + sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale]) + sql_type << "[]" if options[:array] + execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}" change_column_default(table_name, column_name, options[:default]) if options_include_default?(options) change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null) diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index 1d7a22e831..e5c9f6f54a 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -1,4 +1,3 @@ -require 'active_support/deprecation/reporting' module ActiveRecord module ConnectionAdapters @@ -16,13 +15,8 @@ module ActiveRecord prepare_default_proc end - def primary_keys(table_name = nil) - if table_name - @primary_keys[table_name] - else - ActiveSupport::Deprecation.warn('call primary_keys with a table name!') - @primary_keys.dup - end + def primary_keys(table_name) + @primary_keys[table_name] end # A cached lookup for table existence. @@ -41,34 +35,19 @@ module ActiveRecord end end - def tables(name = nil) - if name - @tables[name] - else - ActiveSupport::Deprecation.warn('call tables with a name!') - @tables.dup - end + def tables(name) + @tables[name] end # Get the columns for a table - def columns(table = nil) - if table - @columns[table] - else - ActiveSupport::Deprecation.warn('call columns with a table name!') - @columns.dup - end + def columns(table) + @columns[table] end # Get the columns for a table as a hash, key is the column name # value is the column object. - def columns_hash(table = nil) - if table - @columns_hash[table] - else - ActiveSupport::Deprecation.warn('call columns_hash with a table name!') - @columns_hash.dup - end + def columns_hash(table) + @columns_hash[table] end # Clears out internal caches diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 7da70cea18..e088021112 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -90,12 +90,6 @@ module ActiveRecord module ClassMethods def initialize_generated_modules super - # force attribute methods to be higher in inheritance hierarchy than other generated methods - generated_attribute_methods.const_set(:AttrNames, Module.new { - def self.const_missing(name) - const_set(name, [name.to_s.sub(/ATTR_/, '')].pack('h*').freeze) - end - }) generated_feature_methods end @@ -152,7 +146,7 @@ module ActiveRecord private def relation #:nodoc: - relation = Relation.new(self, arel_table) + relation = Relation.create(self, arel_table) if finder_needs_type_condition? relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 70eda332b3..47d4f3637f 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -379,12 +379,6 @@ module ActiveRecord @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} } - def self.find_table_name(fixture_set_name) # :nodoc: - ActiveSupport::Deprecation.warn( - "ActiveRecord::Fixtures.find_table_name is deprecated and shall be removed from future releases. Use ActiveRecord::Fixtures.default_fixture_model_name instead.") - default_fixture_model_name(fixture_set_name) - end - def self.default_fixture_model_name(fixture_set_name) # :nodoc: ActiveRecord::Base.pluralize_table_names ? fixture_set_name.singularize.camelize : diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 209de78898..626fe40103 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -82,7 +82,7 @@ module ActiveRecord stmt = relation.where( relation.table[self.class.primary_key].eq(id).and( - relation.table[lock_col].eq(self.class.quote_value(previous_lock_value)) + relation.table[lock_col].eq(self.class.quote_value(previous_lock_value, column_for_attribute(lock_col))) ) ).arel.compile_update(arel_attributes_with_values_for_update(attribute_names)) @@ -138,6 +138,7 @@ module ActiveRecord # Set the column to use for optimistic locking. Defaults to +lock_version+. def locking_column=(value) + @column_defaults = nil @locking_column = value.to_s end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index e96c347f6f..90020c3510 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -32,7 +32,7 @@ module ActiveRecord class PendingMigrationError < ActiveRecordError#:nodoc: def initialize - super("Migrations are pending; run 'rake db:migrate RAILS_ENV=#{Rails.env}' to resolve this issue.") + super("Migrations are pending; run 'bin/rake db:migrate RAILS_ENV=#{Rails.env}' to resolve this issue.") end end @@ -866,13 +866,7 @@ module ActiveRecord @direction = direction @target_version = target_version @migrated_versions = nil - - if Array(migrations).grep(String).empty? - @migrations = migrations - else - ActiveSupport::Deprecation.warn "instantiate this class with a list of migrations" - @migrations = self.class.migrations(migrations) - end + @migrations = migrations validate(@migrations) @@ -906,15 +900,7 @@ module ActiveRecord raise UnknownMigrationVersionError.new(@target_version) end - running = runnable - - if block_given? - message = "block argument to migrate is deprecated, please filter migrations before constructing the migrator" - ActiveSupport::Deprecation.warn message - running.select! { |m| yield m } - end - - running.each do |migration| + runnable.each do |migration| Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger begin diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index ac2d2f2712..23541d1d27 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -124,7 +124,7 @@ module ActiveRecord @quoted_table_name = nil @arel_table = nil @sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name - @relation = Relation.new(self, arel_table) + @relation = Relation.create(self, arel_table) end # Returns a quoted version of the table name, used to construct SQL statements. @@ -253,19 +253,6 @@ module ActiveRecord @content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column } end - # Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key - # and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute - # is available. - def column_methods_hash #:nodoc: - @dynamic_methods_hash ||= column_names.each_with_object(Hash.new(false)) do |attr, methods| - attr_name = attr.to_s - methods[attr.to_sym] = attr_name - methods["#{attr}=".to_sym] = attr_name - methods["#{attr}?".to_sym] = attr_name - methods["#{attr}_before_type_cast".to_sym] = attr_name - end - end - # Resets all the cached information about columns, which will cause them # to be reloaded on the next request. # diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 0b74553bf8..8a311039d5 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -12,7 +12,7 @@ db_namespace = namespace :db do end end - desc 'Create the database from DATABASE_URL or config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)' + desc 'Create the database from DATABASE_URL or config/database.yml for the current Rails.env (use db:create:all to create all databases in the config)' task :create => [:load_config] do if ENV['DATABASE_URL'] ActiveRecord::Tasks::DatabaseTasks.create_database_url @@ -172,7 +172,7 @@ db_namespace = namespace :db do end end - desc 'Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the db first)' + desc 'Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the database first)' task :setup => ['db:schema:load_if_ruby', 'db:structure:load_if_sql', :seed] desc 'Load the seed data from db/seeds.rb' @@ -236,7 +236,7 @@ db_namespace = namespace :db do end namespace :schema do - desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR' + desc 'Create a db/schema.rb file that is portable against any DB supported by AR' task :dump => [:environment, :load_config] do require 'active_record/schema_dumper' filename = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb') diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 8a9488656b..73d154e03e 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -10,6 +10,27 @@ module ActiveRecord self.aggregate_reflections = {} end + def self.create(macro, name, scope, options, ar) + case macro + when :has_and_belongs_to_many + klass = AssociationReflection + when :has_many, :belongs_to, :has_one + klass = options[:through] ? ThroughReflection : AssociationReflection + when :composed_of + klass = AggregateReflection + end + + klass.new(macro, name, scope, options, ar) + end + + def self.add_reflection(ar, name, reflection) + if reflection.class == AggregateReflection + ar.aggregate_reflections = ar.aggregate_reflections.merge(name => reflection) + else + ar.reflections = ar.reflections.merge(name => reflection) + end + end + # \Reflection enables to interrogate Active Record classes and objects # about their associations and aggregations. This information can, # for example, be used in a form builder that takes an Active Record object @@ -19,25 +40,6 @@ module ActiveRecord # MacroReflection class has info for AggregateReflection and AssociationReflection # classes. module ClassMethods - def create_reflection(macro, name, scope, options, active_record) - case macro - when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many - klass = options[:through] ? ThroughReflection : AssociationReflection - when :composed_of - klass = AggregateReflection - end - - reflection = klass.new(macro, name, scope, options, active_record) - - if klass == AggregateReflection - self.aggregate_reflections = self.aggregate_reflections.merge(name => reflection) - else - self.reflections = self.reflections.merge(name => reflection) - end - - reflection - end - # Returns an array of AggregateReflection objects for all the aggregations in the class. def reflect_on_all_aggregations aggregate_reflections.values @@ -191,7 +193,7 @@ module ActiveRecord attr_reader :type, :foreign_type - def initialize(*args) + def initialize(macro, name, scope, options, active_record) super @collection = [:has_many, :has_and_belongs_to_many].include?(macro) @automatic_inverse_of = nil @@ -267,7 +269,7 @@ module ActiveRecord end def source_reflection - nil + self end # A chain of reflections from this one back to the owner. For more see the explanation in @@ -369,6 +371,12 @@ module ActiveRecord VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to] INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key] + protected + + def actual_source_reflection # FIXME: this is a horrible name + self + end + private # Attempts to find the inverse association name automatically. # If it cannot find a suitable inverse association name, it returns @@ -527,7 +535,9 @@ module ActiveRecord # def chain @chain ||= begin - chain = source_reflection.chain + through_reflection.chain + a = source_reflection.chain + b = through_reflection.chain + chain = a + b chain[0] = self # Use self so we don't lose the information from :source_type chain end @@ -578,7 +588,7 @@ module ActiveRecord # A through association is nested if there would be more than one join table def nested? - chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many + chain.length > 2 || through_reflection.has_and_belongs_to_many? end # We want to use the klass from this reflection, rather than just delegate straight to @@ -587,12 +597,7 @@ module ActiveRecord def association_primary_key(klass = nil) # Get the "actual" source reflection if the immediate source reflection has a # source reflection itself - source_reflection = self.source_reflection - while source_reflection.source_reflection - source_reflection = source_reflection.source_reflection - end - - source_reflection.options[:primary_key] || primary_key(klass || self.klass) + actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass) end # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form. @@ -671,6 +676,12 @@ directive on your declaration like: check_validity_of_inverse! end + protected + + def actual_source_reflection # FIXME: this is a horrible name + source_reflection.actual_source_reflection + end + private def derive_class_name # get the class_name of the belongs_to association of the through reflection diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 18fdac2600..4e86e905ed 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -501,7 +501,22 @@ module ActiveRecord # User.where(name: 'Oscar').to_sql # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar' def to_sql - @to_sql ||= klass.connection.to_sql(arel, bind_values.dup) + @to_sql ||= begin + relation = self + connection = klass.connection + visitor = connection.visitor + + if eager_loading? + join_dependency = construct_join_dependency + relation = construct_relation_for_association_find(join_dependency) + end + + ast = relation.arel.ast + binds = relation.bind_values.dup + visitor.accept(ast) do + connection.quote(*binds.shift.reverse) + end + end end # Returns a hash of where conditions. diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 3b24ed6754..52a538e5b5 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -189,11 +189,7 @@ module ActiveRecord distinct = self.distinct_value if operation == "count" - if select_values.present? - column_name ||= select_values.join(", ") - else - column_name ||= :all - end + column_name ||= select_for_count unless arel.ast.grep(Arel::Nodes::OuterJoin).empty? distinct = true @@ -361,6 +357,15 @@ module ActiveRecord column ? column.type_cast(value) : value end + # TODO: refactor to allow non-string `select_values` (eg. Arel nodes). + def select_for_count + if select_values.present? + select_values.join(", ") + else + :all + end + end + def build_count_subquery(relation, column_name, distinct) column_alias = Arel.sql('count_column') subquery_alias = Arel.sql('subquery_for_count') diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 8d6740246c..b6f80ac5c7 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -73,16 +73,8 @@ module ActiveRecord module ClassMethods # :nodoc: @@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2) - def new(klass, *args) - relation = relation_class_for(klass).allocate - relation.__send__(:initialize, klass, *args) - relation - end - - # This doesn't have to be thread-safe. relation_class_for guarantees that this will only be - # called exactly once for a given const name. - def const_missing(name) - const_set(name, Class.new(self) { include ClassSpecificRelation }) + def create(klass, *args) + relation_class_for(klass).new(klass, *args) end private @@ -94,7 +86,13 @@ module ActiveRecord # This hash is keyed by klass.name to avoid memory leaks in development mode my_cache.compute_if_absent(klass_name) do # Cache#compute_if_absent guarantees that the block will only executed once for the given klass_name - const_get("#{name.gsub('::', '_')}_#{klass_name.gsub('::', '_')}", false) + subclass_name = "#{name.gsub('::', '_')}_#{klass_name.gsub('::', '_')}" + + if const_defined?(subclass_name) + const_get(subclass_name) + else + const_set(subclass_name, Class.new(self) { include ClassSpecificRelation }) + end end else ActiveRecord::Relation diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index bad5886cde..2d3bd563ac 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -1,5 +1,7 @@ module ActiveRecord module FinderMethods + ONE_AS_ONE = '1 AS one' + # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]). # If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key # is an integer, find by id coerces its arguments using +to_i+. @@ -202,7 +204,7 @@ module ActiveRecord relation = construct_relation_for_association_find(construct_join_dependency) return false if ActiveRecord::NullRelation === relation - relation = relation.except(:select, :order).select("1 AS one").limit(1) + relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1) case conditions when Array, Hash @@ -236,7 +238,7 @@ module ActiveRecord raise RecordNotFound, error end - protected + private def find_with_associations join_dependency = construct_join_dependency @@ -244,7 +246,7 @@ module ActiveRecord if ActiveRecord::NullRelation === relation [] else - rows = connection.select_all(relation, 'SQL', relation.bind_values.dup) + rows = connection.select_all(relation.arel, 'SQL', relation.bind_values.dup) join_dependency.instantiate(rows) end end @@ -288,6 +290,12 @@ module ActiveRecord id_rows.map {|row| row[primary_key]} end + def using_limitable_reflections?(reflections) + reflections.none? { |r| r.collection? } + end + + protected + def find_with_ids(*ids) expects_array = ids.first.kind_of?(Array) return ids.first if expects_array && ids.first.empty? @@ -377,9 +385,5 @@ module ActiveRecord end end end - - def using_limitable_reflections?(reflections) - reflections.none? { |r| r.collection? } - end end end diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 6d047e0331..c08158d38b 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -22,7 +22,7 @@ module ActiveRecord # build a relation to merge in rather than directly merging # the values. def other - other = Relation.new(relation.klass, relation.table) + other = Relation.create(relation.klass, relation.table) hash.each { |k, v| if k == :joins if Hash === v @@ -97,19 +97,35 @@ module ActiveRecord def merge_multi_values lhs_wheres = relation.where_values rhs_wheres = values[:where] || [] + lhs_binds = relation.bind_values rhs_binds = values[:bind] || [] removed, kept = partition_overwrites(lhs_wheres, rhs_wheres) - relation.where_values = kept + rhs_wheres - relation.bind_values = filter_binds(lhs_binds, removed) + rhs_binds + where_values = kept + rhs_wheres + bind_values = filter_binds(lhs_binds, removed) + rhs_binds + + conn = relation.klass.connection + bviter = bind_values.each.with_index + where_values.map! do |node| + if Arel::Nodes::Equality === node && Arel::Nodes::BindParam === node.right + (column, _), i = bviter.next + substitute = conn.substitute_at column, i + Arel::Nodes::Equality.new(node.left, substitute) + else + node + end + end + + relation.where_values = where_values + relation.bind_values = bind_values if values[:reordering] # override any order specified in the original relation relation.reorder! values[:order] elsif values[:order] - # merge in order_values from r + # merge in order_values from relation relation.order! values[:order] end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 1327cc3c34..d0c56ac3d0 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -100,6 +100,14 @@ module ActiveRecord # firing an additional query. This will often result in a # performance improvement over a simple +join+. # + # You can also specify multiple relationships, like this: + # + # users = User.includes(:address, :friends) + # + # Loading nested relationships is possible using a Hash: + # + # users = User.includes(:address, friends: [:address, :followers]) + # # === conditions # # If you want to add conditions to your included models you'll have @@ -280,16 +288,18 @@ module ActiveRecord args.flatten! validate_order_args args - references = args.reject { |arg| Arel::Node === arg } + references = args.grep(String) references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact! references!(references) if references.any? # if a symbol is given we prepend the quoted table name - args = args.map { |arg| - arg.is_a?(Symbol) ? "#{quoted_table_name}.#{arg} ASC" : arg + args.map! { |arg| + arg.is_a?(Symbol) ? + Arel::Nodes::Ascending.new(klass.arel_table[arg]) : + arg } - self.order_values = args + self.order_values + self.order_values = args.concat self.order_values self end @@ -946,10 +956,11 @@ module ActiveRecord join_dependency.graft(*stashed_association_joins) - # FIXME: refactor this to build an AST - join_dependency.join_associations.each do |association| - association.join_to(manager) - end + joins = join_dependency.join_associations.map { |association| + association.join_constraints + }.flatten + + joins.each { |join| manager.from join } manager.join_sources.concat join_list diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index c63ae9c9fb..2552cbd234 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -64,7 +64,7 @@ module ActiveRecord private def relation_with(values) # :nodoc: - result = Relation.new(klass, table, values) + result = Relation.create(klass, table, values) result.extend(*extending_values) if extending_values.any? result end diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index a7a035fe46..253368ae5b 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -3,8 +3,31 @@ module ActiveRecord # This class encapsulates a Result returned from calling +exec_query+ on any # database connection adapter. For example: # - # x = ActiveRecord::Base.connection.exec_query('SELECT * FROM foo') - # x # => #<ActiveRecord::Result:0xdeadbeef> + # result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts') + # result # => #<ActiveRecord::Result:0xdeadbeef> + # + # # Get the column names of the result: + # result.columns + # # => ["id", "title", "body"] + # + # # Get the record values of the result: + # result.rows + # # => [[1, "title_1", "body_1"], + # [2, "title_2", "body_2"], + # ... + # ] + # + # # Get an array of hashes representing the result (column => value): + # result.to_hash + # # => [{"id" => 1, "title" => "title_1", "body" => "body_1"}, + # {"id" => 2, "title" => "title_2", "body" => "body_2"}, + # ... + # ] + # + # # ActiveRecord::Result also includes Enumerable. + # result.each do |row| + # puts row['title'] + " " + row['body'] + # end class Result include Enumerable @@ -62,6 +85,7 @@ module ActiveRecord end private + def hash_rows @hash_rows ||= begin diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 31e294022f..0b87ab9926 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -3,8 +3,8 @@ module ActiveRecord extend ActiveSupport::Concern module ClassMethods - def quote_value(value, column = nil) #:nodoc: - connection.quote(value,column) + def quote_value(value, column) #:nodoc: + connection.quote(value, column) end # Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>. diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index e7d5e6ce84..7c51aa6979 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -141,19 +141,6 @@ module ActiveRecord def scope(name, body, &block) extension = Module.new(&block) if block - # Check body.is_a?(Relation) to prevent the relation actually being - # loaded by respond_to? - if body.is_a?(Relation) || !body.respond_to?(:call) - ActiveSupport::Deprecation.warn( - "Using #scope without passing a callable object is deprecated. For " \ - "example `scope :red, where(color: 'red')` should be changed to " \ - "`scope :red, -> { where(color: 'red') }`. There are numerous gotchas " \ - "in the former usage and it makes the implementation more complicated " \ - "and buggy. (If you prefer, you can just define a class method named " \ - "`self.red`.)" - ) - end - singleton_class.send(:define_method, name) do |*args| if body.respond_to?(:call) scope = all.scoping { body.call(*args) } diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 62920d936f..dcbf38a89f 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -304,6 +304,7 @@ module ActiveRecord run_callbacks :rollback ensure restore_transaction_record_state(force_restore_state) + clear_transaction_record_state end # Add the record to the current transaction so that the +after_rollback+ and +after_commit+ callbacks @@ -360,8 +361,8 @@ module ActiveRecord # Restore the new record state and id of a record that was previously saved by a call to save_record_state. def restore_transaction_record_state(force = false) #:nodoc: unless @_start_transaction_state.empty? - @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 - if @_start_transaction_state[:level] < 1 || force + transaction_level = (@_start_transaction_state[:level] || 0) - 1 + if transaction_level < 1 || force restore_state = @_start_transaction_state was_frozen = restore_state[:frozen?] @attributes = @attributes.dup if @attributes.frozen? @@ -374,7 +375,6 @@ module ActiveRecord @attributes_cache.delete(self.class.primary_key) end @attributes.freeze if was_frozen - @_start_transaction_state.clear end end end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 52e46e1ffe..b55af692d6 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -197,8 +197,8 @@ module ActiveRecord # will result in the default Rails exception page being shown), or you # can catch it and restart the transaction (e.g. by telling the user # that the title already exists, and asking him to re-enter the title). - # This technique is also known as optimistic concurrency control: - # http://en.wikipedia.org/wiki/Optimistic_concurrency_control. + # This technique is also known as + # {optimistic concurrency control}[http://en.wikipedia.org/wiki/Optimistic_concurrency_control]. # # The bundled ActiveRecord::ConnectionAdapters distinguish unique index # constraint errors from other types of database errors by throwing an |