aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/associations.rb16
-rw-r--r--activerecord/lib/active_record/associations/association.rb15
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb55
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb49
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb8
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb33
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb8
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb8
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb19
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb33
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb19
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb49
-rw-r--r--activerecord/lib/active_record/associations/preloader/collection_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_many_through.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_one.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb27
-rw-r--r--activerecord/lib/active_record/autosave_association.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb235
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb7
-rw-r--r--activerecord/lib/active_record/core.rb2
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/model.rb2
-rw-r--r--activerecord/lib/active_record/observer.rb6
-rw-r--r--activerecord/lib/active_record/reflection.rb45
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb45
-rw-r--r--activerecord/lib/active_record/sanitization.rb11
-rw-r--r--activerecord/lib/active_record/schema_migration.rb11
-rw-r--r--activerecord/lib/active_record/session_store.rb4
-rw-r--r--activerecord/lib/active_record/store.rb22
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
34 files changed, 477 insertions, 294 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index a62fce4756..a90c5a2a9a 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1192,8 +1192,8 @@ module ActiveRecord
# ORDER BY p.first_name
# }
# }
- def has_many(name, options = {}, &extension)
- Builder::HasMany.build(self, name, options, &extension)
+ def has_many(name, scope = {}, options = nil, &extension)
+ Builder::HasMany.build(self, name, scope, options, &extension)
end
# Specifies a one-to-one association with another class. This method should only be used
@@ -1308,8 +1308,8 @@ module ActiveRecord
# has_one :boss, :readonly => :true
# has_one :club, :through => :membership
# has_one :primary_address, :through => :addressables, :conditions => ["addressable.primary = ?", true], :source => :addressable
- def has_one(name, options = {})
- Builder::HasOne.build(self, name, options)
+ def has_one(name, scope = {}, options = nil)
+ Builder::HasOne.build(self, name, scope, options)
end
# Specifies a one-to-one association with another class. This method should only be used
@@ -1431,8 +1431,8 @@ module ActiveRecord
# belongs_to :post, :counter_cache => true
# belongs_to :company, :touch => true
# belongs_to :company, :touch => :employees_last_updated_at
- def belongs_to(name, options = {})
- Builder::BelongsTo.build(self, name, options)
+ def belongs_to(name, scope = {}, options = nil)
+ Builder::BelongsTo.build(self, name, scope, options)
end
# Specifies a many-to-many relationship with another class. This associates two classes via an
@@ -1605,8 +1605,8 @@ module ActiveRecord
# has_and_belongs_to_many :categories, :readonly => true
# has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
# proc { |record| "DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}" }
- def has_and_belongs_to_many(name, options = {}, &extension)
- Builder::HasAndBelongsToMany.build(self, name, options, &extension)
+ def has_and_belongs_to_many(name, scope = {}, options = nil, &extension)
+ Builder::HasAndBelongsToMany.build(self, name, scope, options, &extension)
end
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index e75003f261..4b989793da 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -148,6 +148,21 @@ module ActiveRecord
end
end
+ # We can't dump @reflection since it contains the scope proc
+ def marshal_dump
+ reflection = @reflection
+ @reflection = nil
+
+ ivars = instance_variables.map { |name| [name, instance_variable_get(name)] }
+ [reflection.name, ivars]
+ end
+
+ def marshal_load(data)
+ reflection_name, ivars = data
+ ivars.each { |name, val| instance_variable_set(name, val) }
+ @reflection = @owner.class.reflect_on_association(reflection_name)
+ end
+
private
def find_target?
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 89a626693d..1303822868 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -6,7 +6,7 @@ module ActiveRecord
attr_reader :association, :alias_tracker
delegate :klass, :owner, :reflection, :interpolate, :to => :association
- delegate :chain, :conditions, :options, :source_options, :active_record, :to => :reflection
+ delegate :chain, :scope_chain, :options, :source_options, :active_record, :to => :reflection
def initialize(association)
@association = association
@@ -15,20 +15,7 @@ module ActiveRecord
def scope
scope = klass.unscoped
-
- scope.extending!(*Array(options[:extend]))
-
- # It's okay to just apply all these like this. The options will only be present if the
- # association supports that option; this is enforced by the association builder.
- scope.merge!(options.slice(
- :readonly, :references, :order, :limit, :joins, :group, :having, :offset, :select, :uniq))
-
- if options[:include]
- scope.includes! options[:include]
- elsif options[:through]
- scope.includes! source_options[:include]
- end
-
+ scope.merge! eval_scope(klass, reflection.scope) if reflection.scope
add_constraints(scope)
end
@@ -82,8 +69,6 @@ module ActiveRecord
foreign_key = reflection.active_record_primary_key
end
- conditions = self.conditions[i]
-
if reflection == chain.last
bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key]
scope = scope.where(table[key].eq(bind_val))
@@ -93,14 +78,6 @@ module ActiveRecord
bind_val = bind scope, table.table_name, reflection.type.to_s, value
scope = scope.where(table[reflection.type].eq(bind_val))
end
-
- conditions.each do |condition|
- if options[:through] && condition.is_a?(Hash)
- condition = disambiguate_condition(table, condition)
- end
-
- scope = scope.where(interpolate(condition))
- end
else
constraint = table[key].eq(foreign_table[foreign_key])
@@ -110,13 +87,15 @@ module ActiveRecord
end
scope = scope.joins(join(foreign_table, constraint))
+ end
- conditions.each do |condition|
- condition = interpolate(condition)
- condition = disambiguate_condition(table, condition) unless i == 0
+ # Exclude the scope of the association itself, because that
+ # was already merged in the #scope method.
+ (scope_chain[i] - [self.reflection.scope]).each do |scope_chain_item|
+ item = eval_scope(reflection.klass, scope_chain_item)
- scope = scope.where(condition)
- end
+ scope.includes! item.includes_values
+ scope.where_values += item.where_values
end
end
@@ -138,19 +117,11 @@ module ActiveRecord
end
end
- def disambiguate_condition(table, condition)
- if condition.is_a?(Hash)
- Hash[
- condition.map do |k, v|
- if v.is_a?(Hash)
- [k, v]
- else
- [table.table_alias || table.name, { k => v }]
- end
- end
- ]
+ def eval_scope(klass, scope)
+ if scope.is_a?(Relation)
+ scope
else
- condition
+ klass.unscoped.instance_exec(owner, &scope)
end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 9a6896dd55..4a78a9c076 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -1,36 +1,61 @@
module ActiveRecord::Associations::Builder
class Association #:nodoc:
- class_attribute :valid_options
- self.valid_options = [:class_name, :foreign_key, :select, :conditions, :include, :extend, :readonly, :validate, :references]
+ class << self
+ attr_accessor :valid_options
+ end
- # Set by subclasses
- class_attribute :macro
+ self.valid_options = [:class_name, :foreign_key, :validate]
- attr_reader :model, :name, :options, :reflection
+ attr_reader :model, :name, :scope, :options, :reflection
- def self.build(model, name, options)
- new(model, name, options).build
+ def self.build(*args, &block)
+ new(*args, &block).build
end
- def initialize(model, name, options)
- @model, @name, @options = model, name, options
+ def initialize(model, name, scope, options)
+ @model = model
+ @name = name
+
+ if options
+ @scope = scope
+ @options = options
+ else
+ @scope = nil
+ @options = scope
+ end
+
+ if @scope && @scope.arity == 0
+ prev_scope = @scope
+ @scope = proc { instance_exec(&prev_scope) }
+ end
end
def mixin
@model.generated_feature_methods
end
+ include Module.new { def build; end }
+
def build
validate_options
- reflection = model.create_reflection(self.class.macro, name, options, model)
define_accessors
- reflection
+ @reflection = model.create_reflection(macro, name, scope, options, model)
+ super # provides an extension point
+ @reflection
+ end
+
+ def macro
+ raise NotImplementedError
+ end
+
+ def valid_options
+ Association.valid_options
end
private
def validate_options
- options.assert_valid_keys(self.class.valid_options)
+ options.assert_valid_keys(valid_options)
end
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 4183c222de..4bef996297 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -2,9 +2,13 @@ require 'active_support/core_ext/object/inclusion'
module ActiveRecord::Associations::Builder
class BelongsTo < SingularAssociation #:nodoc:
- self.macro = :belongs_to
+ def macro
+ :belongs_to
+ end
- self.valid_options += [:foreign_type, :polymorphic, :touch]
+ def valid_options
+ super + [:foreign_type, :polymorphic, :touch]
+ end
def constructable?
!options[:polymorphic]
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index 768f70b6c9..af81af4ad2 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -2,19 +2,14 @@ module ActiveRecord::Associations::Builder
class CollectionAssociation < Association #:nodoc:
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
- self.valid_options += [
- :table_name, :order, :group, :having, :limit, :offset, :uniq, :finder_sql,
- :counter_sql, :before_add, :after_add, :before_remove, :after_remove
- ]
-
- attr_reader :block_extension
-
- def self.build(model, name, options, &extension)
- new(model, name, options, &extension).build
+ def valid_options
+ super + [:table_name, :finder_sql, :counter_sql, :before_add, :after_add, :before_remove, :after_remove]
end
- def initialize(model, name, options, &extension)
- super(model, name, options)
+ attr_reader :block_extension, :extension_module
+
+ def initialize(*args, &extension)
+ super(*args)
@block_extension = extension
end
@@ -32,18 +27,24 @@ module ActiveRecord::Associations::Builder
private
def wrap_block_extension
- options[:extend] = Array(options[:extend])
-
if block_extension
+ @extension_module = mod = Module.new(&block_extension)
silence_warnings do
- model.parent.const_set(extension_module_name, Module.new(&block_extension))
+ 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
- options[:extend].push("#{model.parent}::#{extension_module_name}".constantize)
end
end
def extension_module_name
- @extension_module_name ||= "#{model.to_s.demodulize}#{name.to_s.camelize}AssociationExtension"
+ @extension_module_name ||= "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
end
def define_callback(callback_name)
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 f7656ecd47..7f79ef25f2 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
@@ -1,8 +1,12 @@
module ActiveRecord::Associations::Builder
class HasAndBelongsToMany < CollectionAssociation #:nodoc:
- self.macro = :has_and_belongs_to_many
+ def macro
+ :has_and_belongs_to_many
+ end
- self.valid_options += [:join_table, :association_foreign_key, :delete_sql, :insert_sql]
+ def valid_options
+ super + [:join_table, :association_foreign_key, :delete_sql, :insert_sql]
+ end
def build
reflection = super
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index d37d4e9d33..81df1fb135 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -2,9 +2,13 @@ require 'active_support/core_ext/object/inclusion'
module ActiveRecord::Associations::Builder
class HasMany < CollectionAssociation #:nodoc:
- self.macro = :has_many
+ def macro
+ :has_many
+ end
- self.valid_options += [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of]
+ def valid_options
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of]
+ end
def build
reflection = super
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index bc8a212bee..cdb45e8e58 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -2,12 +2,15 @@ require 'active_support/core_ext/object/inclusion'
module ActiveRecord::Associations::Builder
class HasOne < SingularAssociation #:nodoc:
- self.macro = :has_one
-
- self.valid_options += [:order, :as]
+ def macro
+ :has_one
+ end
- class_attribute :through_options
- self.through_options = [:through, :source, :source_type]
+ def valid_options
+ valid = super + [:order, :as]
+ valid += [:through, :source, :source_type] if options[:through]
+ valid
+ end
def constructable?
!options[:through]
@@ -21,12 +24,6 @@ module ActiveRecord::Associations::Builder
private
- def validate_options
- valid_options = self.class.valid_options
- valid_options += self.class.through_options if options[:through]
- options.assert_valid_keys(valid_options)
- end
-
def configure_dependency
if options[:dependent]
unless options[:dependent].in?([:destroy, :delete, :nullify, :restrict])
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index 436b6c1524..90a4b7c2ef 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -1,6 +1,8 @@
module ActiveRecord::Associations::Builder
class SingularAssociation < Association #:nodoc:
- self.valid_options += [:remote, :dependent, :counter_cache, :primary_key, :inverse_of]
+ def valid_options
+ super + [:remote, :dependent, :counter_cache, :primary_key, :inverse_of]
+ end
def constructable?
true
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 2f6ddfeeb3..55628c81bb 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -183,7 +183,7 @@ module ActiveRecord
reflection.klass.count_by_sql(custom_counter_sql)
else
- if options[:uniq]
+ if association_scope.uniq_value
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
column_name ||= reflection.klass.primary_key
count_options[:distinct] = true
@@ -246,14 +246,14 @@ module ActiveRecord
# +count_records+, which is a method descendants have to provide.
def size
if !find_target? || loaded?
- if options[:uniq]
+ if association_scope.uniq_value
target.uniq.size
else
target.size
end
- elsif !loaded? && options[:group]
+ elsif !loaded? && !association_scope.group_values.empty?
load_target.size
- elsif !loaded? && !options[:uniq] && target.is_a?(Array)
+ elsif !loaded? && !association_scope.uniq_value && target.is_a?(Array)
unsaved_records = target.select { |r| r.new_record? }
unsaved_records.size + count_records
else
@@ -344,7 +344,7 @@ module ActiveRecord
callback(:before_add, record)
yield(record) if block_given?
- if options[:uniq] && index = @target.index(record)
+ if association_scope.uniq_value && index = @target.index(record)
@target[index] = record
else
@target << record
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index e631579087..669b7e03c2 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -46,7 +46,7 @@ module ActiveRecord
# documented side-effect of the method that may avoid an extra SELECT.
@target ||= [] and loaded! if count == 0
- [options[:limit], count].compact.min
+ [association_scope.limit_value, count].compact.min
end
def has_cached_counter?(reflection = reflection)
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 0d7d28e458..0d3b4dbab1 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -92,14 +92,21 @@ module ActiveRecord
constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
- conditions = self.conditions[i].dup
- conditions << { reflection.type => foreign_klass.base_class.name } if reflection.type
+ scope_chain_items = scope_chain[i]
- conditions.each do |condition|
- condition = active_record.send(:sanitize_sql, interpolate(condition), table.table_alias || table.name)
- condition = Arel.sql(condition) unless condition.is_a?(Arel::Node)
+ if reflection.type
+ scope_chain_items += [
+ ActiveRecord::Relation.new(reflection.klass, table)
+ .where(reflection.type => foreign_klass.base_class.name)
+ ]
+ end
+
+ scope_chain_items.each do |item|
+ unless item.is_a?(Relation)
+ item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item)
+ end
- constraint = constraint.and(condition)
+ constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty?
end
relation.from(join(table, constraint))
@@ -137,18 +144,8 @@ module ActiveRecord
table.table_alias || table.name
end
- def conditions
- @conditions ||= reflection.conditions.reverse
- end
-
- private
-
- def interpolate(conditions)
- if conditions.respond_to?(:to_proc)
- instance_eval(&conditions)
- else
- conditions
- end
+ def scope_chain
+ @scope_chain ||= reflection.scope_chain.reverse
end
end
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 54705e4950..ce5bf15f10 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -42,7 +42,7 @@ module ActiveRecord
autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many'
autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
- attr_reader :records, :associations, :options, :model
+ attr_reader :records, :associations, :preload_scope, :model
# Eager loads the named associations for the given Active Record record(s).
#
@@ -78,15 +78,10 @@ module ActiveRecord
# [ :books, :author ]
# { :author => :avatar }
# [ :books, { :author => :avatar } ]
- #
- # +options+ contains options that will be passed to ActiveRecord::Base#find
- # (which is called under the hood for preloading records). But it is passed
- # only one level deep in the +associations+ argument, i.e. it's not passed
- # to the child associations when +associations+ is a Hash.
- def initialize(records, associations, options = {})
- @records = Array.wrap(records).compact.uniq
- @associations = Array.wrap(associations)
- @options = options
+ 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)
end
def run
@@ -110,7 +105,7 @@ module ActiveRecord
def preload_hash(association)
association.each do |parent, child|
- Preloader.new(records, parent, options).run
+ Preloader.new(records, parent, preload_scope).run
Preloader.new(records.map { |record| record.send(parent) }.flatten, child).run
end
end
@@ -125,7 +120,7 @@ module ActiveRecord
def preload_one(association)
grouped_records(association).each do |reflection, klasses|
klasses.each do |klass, records|
- preloader_for(reflection).new(klass, records, reflection, options).run
+ preloader_for(reflection).new(klass, records, reflection, preload_scope).run
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index b4c3908b10..e7fd72994f 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -2,16 +2,16 @@ module ActiveRecord
module Associations
class Preloader
class Association #:nodoc:
- attr_reader :owners, :reflection, :preload_options, :model, :klass
-
- def initialize(klass, owners, reflection, preload_options)
- @klass = klass
- @owners = owners
- @reflection = reflection
- @preload_options = preload_options || {}
- @model = owners.first && owners.first.class
- @scoped = nil
- @owners_by_key = nil
+ attr_reader :owners, :reflection, :preload_scope, :model, :klass
+
+ def initialize(klass, owners, reflection, preload_scope)
+ @klass = klass
+ @owners = owners
+ @reflection = reflection
+ @preload_scope = preload_scope
+ @model = owners.first && owners.first.class
+ @scoped = nil
+ @owners_by_key = nil
end
def run
@@ -92,34 +92,29 @@ module ActiveRecord
records_by_owner
end
+ def reflection_scope
+ @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(&reflection.scope) : klass.unscoped
+ end
+
def build_scope
scope = klass.unscoped
scope.default_scoped = true
- scope = scope.where(interpolate(options[:conditions]))
- scope = scope.where(interpolate(preload_options[:conditions]))
+ values = reflection_scope.values
+ preload_values = preload_scope.values
- scope = scope.select(preload_options[:select] || options[:select] || table[Arel.star])
- scope = scope.includes(preload_options[:include] || options[:include])
+ scope.where_values = Array(values[:where]) + Array(preload_values[:where])
+ scope.references_values = Array(values[:references]) + Array(preload_values[:references])
+
+ scope.select! preload_values[:select] || values[:select] || table[Arel.star]
+ scope.includes! preload_values[:includes] || values[:includes]
if options[:as]
- scope = scope.where(
- klass.table_name => {
- reflection.type => model.base_class.sti_name
- }
- )
+ scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
end
scope
end
-
- def interpolate(conditions)
- if conditions.respond_to?(:to_proc)
- klass.send(:instance_eval, &conditions)
- else
- conditions
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb
index c248aeaaf6..e6cd35e7a1 100644
--- a/activerecord/lib/active_record/associations/preloader/collection_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/collection_association.rb
@@ -6,7 +6,7 @@ module ActiveRecord
private
def build_scope
- super.order(preload_options[:order] || options[:order])
+ super.order(preload_scope.values[:order] || reflection_scope.values[:order])
end
def preload
diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
index c6e9ede356..9a662d3f53 100644
--- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
@@ -6,7 +6,7 @@ module ActiveRecord
def associated_records_by_owner
super.each do |owner, records|
- records.uniq! if options[:uniq]
+ records.uniq! if reflection_scope.uniq_value
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/has_one.rb b/activerecord/lib/active_record/associations/preloader/has_one.rb
index 848448bb48..24728e9f01 100644
--- a/activerecord/lib/active_record/associations/preloader/has_one.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_one.rb
@@ -14,7 +14,7 @@ module ActiveRecord
private
def build_scope
- super.order(preload_options[:order] || options[:order])
+ super.order(preload_scope.values[:order] || reflection_scope.values[:order])
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index ad6374d09a..1c1ba11c44 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -14,10 +14,7 @@ module ActiveRecord
def associated_records_by_owner
through_records = through_records_by_owner
- ActiveRecord::Associations::Preloader.new(
- through_records.values.flatten,
- source_reflection.name, options
- ).run
+ Preloader.new(through_records.values.flatten, source_reflection.name, reflection_scope).run
through_records.each do |owner, records|
records.map! { |r| r.send(source_reflection.name) }.flatten!
@@ -28,10 +25,7 @@ module ActiveRecord
private
def through_records_by_owner
- ActiveRecord::Associations::Preloader.new(
- owners, through_reflection.name,
- through_options
- ).run
+ Preloader.new(owners, through_reflection.name, through_scope).run
Hash[owners.map do |owner|
through_records = Array.wrap(owner.send(through_reflection.name))
@@ -45,21 +39,22 @@ module ActiveRecord
end]
end
- def through_options
- through_options = {}
+ def through_scope
+ through_scope = through_reflection.klass.unscoped
if options[:source_type]
- through_options[:conditions] = { reflection.foreign_type => options[:source_type] }
+ through_scope.where! reflection.foreign_type => options[:source_type]
else
- if options[:conditions]
- through_options[:include] = options[:include] || options[:source]
- through_options[:conditions] = options[:conditions]
+ unless reflection_scope.where_values.empty?
+ through_scope.includes_values = reflection_scope.values[:includes] || options[:source]
+ through_scope.where_values = reflection_scope.values[:where]
end
- through_options[:order] = options[:order]
+ through_scope.order! reflection_scope.values[:order]
+ through_scope.references! reflection_scope.values[:references]
end
- through_options
+ through_scope
end
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index d545e7799d..290f57659d 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -127,23 +127,17 @@ module ActiveRecord
module AutosaveAssociation
extend ActiveSupport::Concern
- ASSOCIATION_TYPES = %w{ HasOne HasMany BelongsTo HasAndBelongsToMany }
-
module AssociationBuilderExtension #:nodoc:
- def self.included(base)
- base.valid_options << :autosave
- end
-
def build
- reflection = super
model.send(:add_autosave_association_callbacks, reflection)
- reflection
+ super
end
end
included do
- ASSOCIATION_TYPES.each do |type|
- Associations::Builder.const_get(type).send(:include, AssociationBuilderExtension)
+ Associations::Builder::Association.class_eval do
+ self.valid_options << :autosave
+ include AssociationBuilderExtension
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index df4a9d5afc..2b0bc3f497 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -318,7 +318,7 @@ module ActiveRecord
select_all(sql, 'SCHEMA').map { |table|
table.delete('Table_type')
sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
- exec_without_stmt(sql, 'SCHEMA').first['Create Table'] + ";\n\n"
+ exec_query(sql, 'SCHEMA').first['Create Table'] + ";\n\n"
}.join
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 0b6734b010..3b0353358a 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -215,7 +215,7 @@ module ActiveRecord
def select_rows(sql, name = nil)
@connection.query_with_result = true
- rows = exec_without_stmt(sql, name).rows
+ rows = exec_query(sql, name).rows
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
rows
end
@@ -279,31 +279,171 @@ module ActiveRecord
end
def exec_query(sql, name = 'SQL', binds = [])
- log(sql, name, binds) do
- exec_stmt(sql, name, binds) do |cols, stmt|
- ActiveRecord::Result.new(cols, stmt.to_a) if cols
- end
+ # If the configuration sets prepared_statements:false, binds will
+ # always be empty, since the bind variables will have been already
+ # substituted and removed from binds by BindVisitor, so this will
+ # effectively disable prepared statement usage completely.
+ if binds.empty?
+ result_set, affected_rows = exec_without_stmt(sql, name)
+ else
+ result_set, affected_rows = exec_stmt(sql, name, binds)
end
+
+ yield affected_rows if block_given?
+
+ result_set
end
def last_inserted_id(result)
@connection.insert_id
end
+ class Result < ActiveRecord::Result
+ def initialize(columns, rows, column_types)
+ super(columns, rows)
+ @column_types = column_types
+ end
+ end
+
+ module Fields
+ class Type
+ def type; end
+
+ def type_cast_for_write(value)
+ value
+ end
+ end
+
+ class Identity < Type
+ def type_cast(value); value; end
+ end
+
+ class Integer < Type
+ def type_cast(value)
+ return if value.nil?
+
+ value.to_i rescue value ? 1 : 0
+ end
+ end
+
+ class Date < Type
+ def type; :date; end
+
+ def type_cast(value)
+ return if value.nil?
+
+ # FIXME: probably we can improve this since we know it is mysql
+ # specific
+ ConnectionAdapters::Column.value_to_date value
+ end
+ end
+
+ class DateTime < Type
+ def type; :datetime; end
+
+ def type_cast(value)
+ return if value.nil?
+
+ # FIXME: probably we can improve this since we know it is mysql
+ # specific
+ ConnectionAdapters::Column.string_to_time value
+ end
+ end
+
+ class Time < Type
+ def type; :time; end
+
+ def type_cast(value)
+ return if value.nil?
+
+ # FIXME: probably we can improve this since we know it is mysql
+ # specific
+ ConnectionAdapters::Column.string_to_dummy_time value
+ end
+ end
+
+ class Float < Type
+ def type; :float; end
+
+ def type_cast(value)
+ return if value.nil?
+
+ value.to_f
+ end
+ end
+
+ class Decimal < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::Column.value_to_decimal value
+ end
+ end
+
+ class Boolean < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::Column.value_to_boolean value
+ end
+ end
+
+ TYPES = {}
+
+ # Register an MySQL +type_id+ with a typcasting object in
+ # +type+.
+ def self.register_type(type_id, type)
+ TYPES[type_id] = type
+ end
+
+ def self.alias_type(new, old)
+ TYPES[new] = TYPES[old]
+ end
+
+ register_type Mysql::Field::TYPE_TINY, Fields::Boolean.new
+ register_type Mysql::Field::TYPE_LONG, Fields::Integer.new
+ alias_type Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_LONG
+ alias_type Mysql::Field::TYPE_NEWDECIMAL, Mysql::Field::TYPE_LONG
+
+ register_type Mysql::Field::TYPE_VAR_STRING, Fields::Identity.new
+ register_type Mysql::Field::TYPE_BLOB, Fields::Identity.new
+ register_type Mysql::Field::TYPE_DATE, Fields::Date.new
+ register_type Mysql::Field::TYPE_DATETIME, Fields::DateTime.new
+ register_type Mysql::Field::TYPE_TIME, Fields::Time.new
+ register_type Mysql::Field::TYPE_FLOAT, Fields::Float.new
+
+ Mysql::Field.constants.grep(/TYPE/).map { |class_name|
+ Mysql::Field.const_get class_name
+ }.reject { |const| TYPES.key? const }.each do |const|
+ register_type const, Fields::Identity.new
+ end
+ end
+
def exec_without_stmt(sql, name = 'SQL') # :nodoc:
# Some queries, like SHOW CREATE TABLE don't work through the prepared
# statement API. For those queries, we need to use this method. :'(
log(sql, name) do
result = @connection.query(sql)
- cols = []
- rows = []
+ affected_rows = @connection.affected_rows
if result
- cols = result.fetch_fields.map { |field| field.name }
- rows = result.to_a
+ types = {}
+ result.fetch_fields.each { |field|
+ if field.decimals > 0
+ types[field.name] = Fields::Decimal.new
+ else
+ types[field.name] = Fields::TYPES.fetch(field.type) {
+ Fields::Identity.new
+ }
+ end
+ }
+ result_set = Result.new(types.keys, result.to_a, types)
result.free
+ else
+ result_set = ActiveRecord::Result.new([], [])
end
- ActiveRecord::Result.new(cols, rows)
+
+ [result_set, affected_rows]
end
end
@@ -321,16 +461,18 @@ module ActiveRecord
alias :create :insert_sql
def exec_delete(sql, name, binds)
- log(sql, name, binds) do
- exec_stmt(sql, name, binds) do |cols, stmt|
- stmt.affected_rows
- end
+ affected_rows = 0
+
+ exec_query(sql, name, binds) do |n|
+ affected_rows = n
end
+
+ affected_rows
end
alias :exec_update :exec_delete
def begin_db_transaction #:nodoc:
- exec_without_stmt "BEGIN"
+ exec_query "BEGIN"
rescue Mysql::Error
# Transactions aren't supported
end
@@ -339,41 +481,44 @@ module ActiveRecord
def exec_stmt(sql, name, binds)
cache = {}
- if binds.empty?
- stmt = @connection.prepare(sql)
- else
- cache = @statements[sql] ||= {
- :stmt => @connection.prepare(sql)
- }
- stmt = cache[:stmt]
- end
+ log(sql, name, binds) do
+ if binds.empty?
+ stmt = @connection.prepare(sql)
+ else
+ cache = @statements[sql] ||= {
+ :stmt => @connection.prepare(sql)
+ }
+ stmt = cache[:stmt]
+ end
- begin
- stmt.execute(*binds.map { |col, val| type_cast(val, col) })
- rescue Mysql::Error => e
- # Older versions of MySQL leave the prepared statement in a bad
- # place when an error occurs. To support older mysql versions, we
- # need to close the statement and delete the statement from the
- # cache.
- stmt.close
- @statements.delete sql
- raise e
- end
+ begin
+ stmt.execute(*binds.map { |col, val| type_cast(val, col) })
+ rescue Mysql::Error => e
+ # Older versions of MySQL leave the prepared statement in a bad
+ # place when an error occurs. To support older mysql versions, we
+ # need to close the statement and delete the statement from the
+ # cache.
+ stmt.close
+ @statements.delete sql
+ raise e
+ end
- cols = nil
- if metadata = stmt.result_metadata
- cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
- field.name
- }
- end
+ cols = nil
+ if metadata = stmt.result_metadata
+ cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
+ field.name
+ }
+ end
- result = yield [cols, stmt]
+ result_set = ActiveRecord::Result.new(cols, stmt.to_a) if cols
+ affected_rows = stmt.affected_rows
- stmt.result_metadata.free if cols
- stmt.free_result
- stmt.close if binds.empty?
+ stmt.result_metadata.free if cols
+ stmt.free_result
+ stmt.close if binds.empty?
- result
+ [result_set, affected_rows]
+ end
end
def connect
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 7b263fd62d..a57a532ba2 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1227,12 +1227,19 @@ module ActiveRecord
end
# Renames a table.
+ # Also renames a table's primary key sequence if the sequence name matches the
+ # Active Record default.
#
# Example:
# rename_table('octopuses', 'octopi')
def rename_table(name, new_name)
clear_cache!
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
+ pk, seq = pk_and_sequence_for(new_name)
+ if seq == "#{name}_#{pk}_seq"
+ new_seq = "#{new_name}_#{pk}_seq"
+ execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
+ end
end
# Adds a new column to the named table.
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 90f156456e..803d68187c 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -380,7 +380,7 @@ module ActiveRecord
#
# So we can avoid the method_missing hit by explicitly defining #to_ary as nil here.
#
- # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/
+ # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
def to_ary # :nodoc:
nil
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 4ce42feb74..e0344f3c56 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -86,7 +86,7 @@ module ActiveRecord
stmt = relation.where(
relation.table[self.class.primary_key].eq(id).and(
- relation.table[lock_col].eq(quote_value(previous_lock_value))
+ relation.table[lock_col].eq(self.class.quote_value(previous_lock_value))
)
).arel.compile_update(arel_attributes_with_values_for_update(attribute_names))
diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb
index 0015e3a567..fb3fb643ff 100644
--- a/activerecord/lib/active_record/model.rb
+++ b/activerecord/lib/active_record/model.rb
@@ -74,9 +74,9 @@ module ActiveRecord
include Inheritance
include Scoping
include Sanitization
- include Integration
include AttributeAssignment
include ActiveModel::Conversion
+ include Integration
include Validations
include CounterCache
include Locking::Optimistic
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index fdf17c003c..e940d357da 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -74,6 +74,12 @@ module ActiveRecord
#
# Observers will not be invoked unless you define these in your application configuration.
#
+ # If you are using Active Record outside Rails, activate the observers explicitly in a configuration or
+ # environment file:
+ #
+ # ActiveRecord::Base.add_observer CommentObserver.instance
+ # ActiveRecord::Base.add_observer SignupObserver.instance
+ #
# == Loading
#
# Observers register themselves in the model class they observe, since it is the class that
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 0d9534acd6..3e8d9718f5 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -20,9 +20,9 @@ module ActiveRecord
# MacroReflection class has info for the AssociationReflection
# class.
module ClassMethods
- def create_reflection(macro, name, options, active_record)
+ def create_reflection(macro, name, scope, options, active_record)
klass = options[:through] ? ThroughReflection : AssociationReflection
- reflection = klass.new(macro, name, options, active_record)
+ reflection = klass.new(macro, name, scope, options, active_record)
self.reflections = self.reflections.merge(name => reflection)
reflection
@@ -71,6 +71,8 @@ module ActiveRecord
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
attr_reader :macro
+ attr_reader :scope
+
# Returns the hash of options used for the macro.
#
# <tt>has_many :clients</tt> returns +{}+
@@ -80,9 +82,10 @@ module ActiveRecord
attr_reader :plural_name # :nodoc:
- def initialize(macro, name, options, active_record)
+ def initialize(macro, name, scope, options, active_record)
@macro = macro
@name = name
+ @scope = scope
@options = options
@active_record = active_record
@plural_name = active_record.pluralize_table_names ?
@@ -142,7 +145,7 @@ module ActiveRecord
@klass ||= active_record.send(:compute_type, class_name)
end
- def initialize(macro, name, options, active_record)
+ def initialize(*args)
super
@collection = [:has_many, :has_and_belongs_to_many].include?(macro)
end
@@ -244,11 +247,10 @@ module ActiveRecord
false
end
- # An array of arrays of conditions. Each item in the outside array corresponds to a reflection
- # in the #chain. The inside arrays are simply conditions (and each condition may itself be
- # a hash, array, arel predicate, etc...)
- def conditions
- [[options[:conditions]].compact]
+ # An array of arrays of scopes. Each item in the outside array corresponds to a reflection
+ # in the #chain.
+ def scope_chain
+ scope ? [[scope]] : [[]]
end
alias :source_macro :macro
@@ -416,28 +418,25 @@ module ActiveRecord
# has_many :tags
# end
#
- # There may be conditions on Person.comment_tags, Article.comment_tags and/or Comment.tags,
+ # There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
# but only Comment.tags will be represented in the #chain. So this method creates an array
- # of conditions corresponding to the chain. Each item in the #conditions array corresponds
- # to an item in the #chain, and is itself an array of conditions from an arbitrary number
- # of relevant reflections, plus any :source_type or polymorphic :as constraints.
- def conditions
- @conditions ||= begin
- conditions = source_reflection.conditions.map { |c| c.dup }
+ # of scopes corresponding to the chain.
+ def scope_chain
+ @scope_chain ||= begin
+ scope_chain = source_reflection.scope_chain.map(&:dup)
- # Add to it the conditions from this reflection if necessary.
- conditions.first << options[:conditions] if options[:conditions]
+ # Add to it the scope from this reflection (if any)
+ scope_chain.first << scope if scope
- through_conditions = through_reflection.conditions
+ through_scope_chain = through_reflection.scope_chain
if options[:source_type]
- through_conditions.first << { foreign_type => options[:source_type] }
+ through_scope_chain.first <<
+ through_reflection.klass.where(foreign_type => options[:source_type])
end
# Recursively fill out the rest of the array from the through reflection
- conditions += through_conditions
-
- conditions
+ scope_chain + through_scope_chain
end
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index e401ed37b0..1271b74ead 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -51,7 +51,18 @@ module ActiveRecord
#
# allows you to access the +address+ attribute of the +User+ model without
# firing an additional query. This will often result in a
- # performance improvement over a simple +join+
+ # performance improvement over a simple +join+.
+ #
+ # === conditions
+ #
+ # If you want to add conditions to your included models you'll have
+ # to explicitly reference them. For example:
+ #
+ # User.includes(:posts).where('posts.name = ?', 'example')
+ #
+ # Will throw an error, but this will work:
+ #
+ # User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
def includes(*args)
args.empty? ? self : spawn.includes!(*args)
end
@@ -63,6 +74,12 @@ module ActiveRecord
self
end
+ # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
+ #
+ # User.eager_load(:posts)
+ # => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
+ # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
+ # "users"."id"
def eager_load(*args)
args.blank? ? self : spawn.eager_load!(*args)
end
@@ -72,6 +89,10 @@ module ActiveRecord
self
end
+ # Allows preloading of +args+, in the same way that +includes+ does:
+ #
+ # User.preload(:posts)
+ # => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
def preload(*args)
args.blank? ? self : spawn.preload!(*args)
end
@@ -147,7 +168,7 @@ module ActiveRecord
# User.group(:name)
# => SELECT "users".* FROM "users" GROUP BY name
#
- # Returns an array with distinct records based on the `group` attribute:
+ # Returns an array with distinct records based on the +group+ attribute:
#
# User.select([:id, :name])
# => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
@@ -211,6 +232,10 @@ module ActiveRecord
self
end
+ # Performs a joins on +args+:
+ #
+ # User.joins(:posts)
+ # => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
def joins(*args)
args.compact.blank? ? self : spawn.joins!(*args)
end
@@ -334,6 +359,10 @@ module ActiveRecord
self
end
+ # Allows to specify a HAVING clause. Note that you can't use HAVING
+ # without also specifying a GROUP clause.
+ #
+ # Order.having('SUM(price) > 30').group('user_id')
def having(opts, *rest)
opts.blank? ? self : spawn.having!(opts, *rest)
end
@@ -375,6 +404,8 @@ module ActiveRecord
self
end
+ # Specifies locking settings (default to +true+). For more information
+ # on locking, please see +ActiveRecord::Locking+.
def lock(locks = true)
spawn.lock!(locks)
end
@@ -423,6 +454,12 @@ module ActiveRecord
scoped.extending(NullRelation)
end
+ # Sets readonly attributes for the returned relation. If value is
+ # true (default), attempting to update a record will result in an error.
+ #
+ # users = User.readonly
+ # users.first.save
+ # => ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
def readonly(value = true)
spawn.readonly!(value)
end
@@ -529,7 +566,7 @@ module ActiveRecord
def extending!(*modules, &block)
modules << Module.new(&block) if block_given?
- self.extending_values = modules.flatten
+ self.extending_values += modules.flatten
extend(*extending_values) if extending_values.any?
self
@@ -552,7 +589,7 @@ module ActiveRecord
end
def build_arel
- arel = table.from table
+ arel = Arel::SelectManager.new(table.engine, table)
build_joins(arel, joins_values) unless joins_values.empty?
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 46f6c283e3..ca767cc704 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -148,15 +148,8 @@ module ActiveRecord
end
# TODO: Deprecate this
- def quoted_id #:nodoc:
- quote_value(id, column_for_attribute(self.class.primary_key))
- end
-
- private
-
- # Quote strings appropriately for SQL statements.
- def quote_value(value, column = nil)
- self.class.connection.quote(value, column)
+ def quoted_id
+ self.class.quote_value(id, column_for_attribute(self.class.primary_key))
end
end
end
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index 236ec563d2..ca22154c84 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -7,7 +7,11 @@ module ActiveRecord
attr_accessible :version
def self.table_name
- Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
+ "#{Base.table_name_prefix}schema_migrations#{Base.table_name_suffix}"
+ end
+
+ def self.index_name
+ "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
end
def self.create_table
@@ -15,14 +19,13 @@ module ActiveRecord
connection.create_table(table_name, :id => false) do |t|
t.column :version, :string, :null => false
end
- connection.add_index table_name, :version, :unique => true,
- :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
+ connection.add_index table_name, :version, :unique => true, :name => index_name
end
end
def self.drop_table
if connection.table_exists?(table_name)
- connection.remove_index table_name, :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
+ connection.remove_index table_name, :name => index_name
connection.drop_table(table_name)
end
end
diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index 5a256b040b..d2d3106721 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -7,7 +7,7 @@ module ActiveRecord
#
# The default assumes a +sessions+ tables with columns:
# +id+ (numeric primary key),
- # +session_id+ (text, or longtext if your session data exceeds 65K), and
+ # +session_id+ (string, usually varchar; maximum length is 255), and
# +data+ (text or longtext; careful if your session data exceeds 65KB).
#
# The +session_id+ column should always be indexed for speedy lookups.
@@ -218,7 +218,7 @@ module ActiveRecord
# Look up a session by id and unmarshal its data if found.
def find_by_session_id(session_id)
- if record = connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{connection.quote(session_id.to_s)}")
+ if record = connection.select_one("SELECT #{connection.quote_column_name(data_column)} AS data FROM #{@@table_name} WHERE #{connection.quote_column_name(@@session_id_column)}=#{connection.quote(session_id.to_s)}")
new(:session_id => session_id, :marshaled_data => record['data'])
end
end
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index d836acf18f..81576e7cd3 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -57,8 +57,7 @@ module ActiveRecord
keys = keys.flatten
keys.each do |key|
define_method("#{key}=") do |value|
- initialize_store_attribute(store_attribute)
- attribute = send(store_attribute)
+ attribute = initialize_store_attribute(store_attribute)
if value != attribute[key]
attribute[key] = value
send :"#{store_attribute}_will_change!"
@@ -66,8 +65,7 @@ module ActiveRecord
end
define_method(key) do
- initialize_store_attribute(store_attribute)
- send(store_attribute)[key]
+ initialize_store_attribute(store_attribute)[key]
end
end
@@ -77,16 +75,12 @@ module ActiveRecord
private
def initialize_store_attribute(store_attribute)
- case attribute = send(store_attribute)
- when ActiveSupport::HashWithIndifferentAccess
- # Already initialized. Do nothing.
- when Hash
- # Initialized as a Hash. Convert to indifferent access.
- send :"#{store_attribute}=", attribute.with_indifferent_access
- else
- # Uninitialized. Set to an indifferent hash.
- send :"#{store_attribute}=", ActiveSupport::HashWithIndifferentAccess.new
+ attribute = send(store_attribute)
+ unless attribute.is_a?(HashWithIndifferentAccess)
+ attribute = IndifferentCoder.as_indifferent_hash(attribute)
+ send :"#{store_attribute}=", attribute
end
+ attribute
end
class IndifferentCoder
@@ -109,7 +103,7 @@ module ActiveRecord
def self.as_indifferent_hash(obj)
case obj
- when ActiveSupport::HashWithIndifferentAccess
+ when HashWithIndifferentAccess
obj
when Hash
obj.with_indifferent_access
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 9e4b588ac2..5a24135f8e 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -26,7 +26,7 @@ module ActiveRecord
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.send(:id))) if record.persisted?
Array(options[:scope]).each do |scope_item|
- scope_value = record.send(scope_item)
+ scope_value = record.read_attribute(scope_item)
reflection = record.class.reflect_on_association(scope_item)
if reflection
scope_value = record.send(reflection.foreign_key)