aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/associations.rb4
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb11
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb2
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb84
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb1
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb14
-rw-r--r--activerecord/lib/active_record/autosave_association.rb5
-rw-r--r--activerecord/lib/active_record/base.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb39
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb62
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb44
-rw-r--r--activerecord/lib/active_record/connection_adapters/statement_pool.rb40
-rw-r--r--activerecord/lib/active_record/fixtures.rb729
-rw-r--r--activerecord/lib/active_record/fixtures/file.rb12
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake12
-rw-r--r--activerecord/lib/active_record/reflection.rb28
-rw-r--r--activerecord/lib/active_record/relation.rb42
-rw-r--r--activerecord/lib/active_record/relation/batches.rb2
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb12
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb71
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb2
-rw-r--r--activerecord/lib/active_record/timestamp.rb4
30 files changed, 788 insertions, 472 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 8d755b6848..0952ea2829 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1329,7 +1329,7 @@ module ActiveRecord
#
# [:class_name]
# Specify the class name of the association. Use it only if that name can't be inferred
- # from the association name. So <tt>has_one :author</tt> will by default be linked to the Author class, but
+ # from the association name. So <tt>belongs_to :author</tt> will by default be linked to the Author class, but
# if the real class name is Person, you'll have to specify it with this option.
# [:conditions]
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
@@ -1575,7 +1575,7 @@ module ActiveRecord
# has_and_belongs_to_many :categories, :join_table => "prods_cats"
# has_and_belongs_to_many :categories, :readonly => true
# has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
- # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
+ # "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)
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 9e6d9e73c5..6cc401e6cc 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -42,10 +42,6 @@ module ActiveRecord
select_value ||= options[:uniq] && "DISTINCT #{reflection.quoted_table_name}.*"
end
- if reflection.macro == :has_and_belongs_to_many
- select_value ||= reflection.klass.arel_table[Arel.star]
- end
-
select_value
end
@@ -68,7 +64,12 @@ module ActiveRecord
end
if reflection.source_macro == :belongs_to
- key = reflection.association_primary_key
+ if reflection.options[:polymorphic]
+ key = reflection.association_primary_key(klass)
+ else
+ key = reflection.association_primary_key
+ end
+
foreign_key = reflection.foreign_key
else
key = reflection.foreign_key
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index adb6af7165..97f531d064 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -20,6 +20,10 @@ module ActiveRecord
private
+ def find_target?
+ !loaded? && foreign_key_present? && klass
+ end
+
def update_counters(record)
counter_cache_name = reflection.counter_cache_column
@@ -41,7 +45,11 @@ module ActiveRecord
end
def replace_keys(record)
- owner[reflection.foreign_key] = record && record[reflection.association_primary_key]
+ if record
+ owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
+ else
+ owner[reflection.foreign_key] = nil
+ end
end
def foreign_key_present?
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 6ba3d45aff..3181ca9a32 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -46,7 +46,7 @@ module ActiveRecord
delegate :select, :find, :first, :last,
:build, :create, :create!,
- :concat, :delete_all, :destroy_all, :delete, :destroy, :uniq,
+ :concat, :replace, :delete_all, :destroy_all, :delete, :destroy, :uniq,
:sum, :count, :size, :length, :empty?,
:any?, :many?, :include?,
:to => :@association
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index 81172179e0..b347a94978 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -16,7 +16,7 @@ module ActiveRecord
chain[1..-1].each do |reflection|
scope = scope.merge(
reflection.klass.scoped.with_default_scope.
- except(:select, :create_with, :includes)
+ except(:select, :create_with, :includes, :preload, :joins, :eager_load)
)
end
scope
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index d0687ed0b6..d7bfaa5655 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/enumerable'
+require 'active_support/deprecation'
module ActiveRecord
# = Active Record Attribute Methods
@@ -11,56 +12,83 @@ module ActiveRecord
# accessors, mutators and query methods.
def define_attribute_methods
return if attribute_methods_generated?
- super(column_names)
- @attribute_methods_generated = true
+
+ if base_class == self
+ super(column_names)
+ @attribute_methods_generated = true
+ else
+ base_class.define_attribute_methods
+ end
end
def attribute_methods_generated?
- @attribute_methods_generated ||= false
+ if base_class == self
+ @attribute_methods_generated ||= false
+ else
+ base_class.attribute_methods_generated?
+ end
end
def undefine_attribute_methods(*args)
- super
- @attribute_methods_generated = false
+ if base_class == self
+ super
+ @attribute_methods_generated = false
+ else
+ base_class.undefine_attribute_methods(*args)
+ end
end
- # Checks whether the method is defined in the model or any of its subclasses
- # that also derive from Active Record. Raises DangerousAttributeError if the
- # method is defined by Active Record though.
def instance_method_already_implemented?(method_name)
- method_name = method_name.to_s
- index = ancestors.index(ActiveRecord::Base) || ancestors.length
- @_defined_class_methods ||= ancestors.first(index).map { |m|
- m.instance_methods(false) | m.private_instance_methods(false)
- }.flatten.map {|m| m.to_s }.to_set
+ if dangerous_attribute_method?(method_name)
+ raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
+ end
- @@_defined_activerecord_methods ||= defined_activerecord_methods
- raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
- @_defined_class_methods.include?(method_name)
+ super
end
- def defined_activerecord_methods
+ # A method name is 'dangerous' if it is already defined by Active Record, but
+ # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
+ def dangerous_attribute_method?(method_name)
active_record = ActiveRecord::Base
- super_klass = ActiveRecord::Base.superclass
- methods = (active_record.instance_methods - super_klass.instance_methods) +
- (active_record.private_instance_methods - super_klass.private_instance_methods)
- methods.map {|m| m.to_s }.to_set
+ superclass = ActiveRecord::Base.superclass
+
+ (active_record.method_defined?(method_name) ||
+ active_record.private_method_defined?(method_name)) &&
+ !superclass.method_defined?(method_name) &&
+ !superclass.private_method_defined?(method_name)
end
end
- def method_missing(method_id, *args, &block)
- # If we haven't generated any methods yet, generate them, then
- # see if we've created the method we're looking for.
- if !self.class.attribute_methods_generated?
+ # 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)
+ unless self.class.attribute_methods_generated?
self.class.define_attribute_methods
- method_name = method_id.to_s
- guard_private_attribute_method!(method_name, args)
- send(method_id, *args, &block)
+
+ if respond_to_without_attributes?(method)
+ send(method, *args, &block)
+ else
+ super
+ end
else
super
end
end
+ def attribute_missing(match, *args, &block)
+ 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
+
def respond_to?(name, include_private = false)
self.class.define_attribute_methods unless self.class.attribute_methods_generated?
super
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index ed71b5e7d4..a404a5edd7 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -66,7 +66,6 @@ module ActiveRecord
@primary_key ||= ''
self.original_primary_key = @primary_key
value &&= value.to_s
- connection_pool.primary_keys[table_name] = value
self.primary_key = block_given? ? instance_eval(&block) : value
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 9a50a20fbc..4174e4da09 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -6,8 +6,6 @@ module ActiveRecord
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
included do
- attribute_method_suffix ""
-
cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index c77a3ac145..e9cdb130db 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -17,6 +17,10 @@ module ActiveRecord
write_attribute(attr_name, new_value)
end
end
+
+ if attr_name == primary_key && attr_name != "id"
+ generated_attribute_methods.module_eval("alias :id= :'#{primary_key}='")
+ end
end
end
@@ -24,12 +28,16 @@ module ActiveRecord
# for fixnum and float columns are turned into +nil+.
def write_attribute(attr_name, value)
attr_name = attr_name.to_s
- attr_name = self.class.primary_key if attr_name == 'id'
+ attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
@attributes_cache.delete(attr_name)
- if (column = column_for_attribute(attr_name)) && column.number?
+ column = column_for_attribute(attr_name)
+
+ if column && column.number?
@attributes[attr_name] = convert_number_column_value(value)
- else
+ elsif column || @attributes.has_key?(attr_name)
@attributes[attr_name] = value
+ else
+ raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
end
end
alias_method :raw_write_attribute, :write_attribute
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 085fdba639..056170d82a 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -370,7 +370,10 @@ module ActiveRecord
else
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
if autosave != false && (new_record? || record.new_record? || record[reflection.foreign_key] != key || autosave)
- record[reflection.foreign_key] = key
+ unless reflection.through_reflection
+ record[reflection.foreign_key] = key
+ end
+
saved = record.save(:validate => !autosave)
raise ActiveRecord::Rollback if !saved && autosave
saved
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 374791deb1..78159d13d4 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -442,6 +442,7 @@ module ActiveRecord #:nodoc:
class << self # Class methods
delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
+ delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :scoped
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
@@ -706,6 +707,10 @@ module ActiveRecord #:nodoc:
# Returns an array of column objects for the table associated with this class.
def columns
+ if defined?(@primary_key)
+ connection_pool.primary_keys[table_name] ||= primary_key
+ end
+
connection_pool.columns[table_name]
end
@@ -1331,7 +1336,7 @@ MSG
# Returns the class descending directly from ActiveRecord::Base or an
# abstract class, if any, in the inheritance hierarchy.
def class_of_active_record_descendant(klass)
- if klass.superclass == Base || klass.superclass.abstract_class?
+ if klass == Base || klass.superclass == Base || klass.superclass.abstract_class?
klass
elsif klass.superclass.nil?
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
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 61994d4a47..20863e73aa 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -421,7 +421,7 @@ module ActiveRecord
# can be used as an argument for establish_connection, for easily
# re-establishing the connection.
def remove_connection(klass)
- pool = @connection_pools[klass.name]
+ pool = @connection_pools.delete(klass.name)
return nil unless pool
pool.automatic_reconnect = false
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index 7312e34f01..c08c0263b9 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -126,7 +126,7 @@ module ActiveRecord
end
def connection_pool
- connection_handler.retrieve_connection_pool(self)
+ connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished
end
def retrieve_connection
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 3de850ec9e..f93c7cd74a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -102,10 +102,13 @@ module ActiveRecord
def quoted_date(value)
if value.acts_like?(:time)
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
- value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
- else
- value
- end.to_s(:db)
+
+ if value.respond_to?(zone_conversion_method)
+ value = value.send(zone_conversion_method)
+ end
+ end
+
+ value.to_s(:db)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index e9bdcc2104..a1824fe396 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -1,4 +1,5 @@
require 'active_record/connection_adapters/abstract_mysql_adapter'
+require 'active_record/connection_adapters/statement_pool'
require 'active_support/core_ext/hash/keys'
gem 'mysql', '~> 2.8.1'
@@ -90,9 +91,42 @@ module ActiveRecord
ADAPTER_NAME = 'MySQL'
+ class StatementPool < ConnectionAdapters::StatementPool
+ def initialize(connection, max = 1000)
+ super
+ @cache = Hash.new { |h,pid| h[pid] = {} }
+ end
+
+ def each(&block); cache.each(&block); end
+ def key?(key); cache.key?(key); end
+ def [](key); cache[key]; end
+ def length; cache.length; end
+ def delete(key); cache.delete(key); end
+
+ def []=(sql, key)
+ while @max <= cache.size
+ cache.shift.last[:stmt].close
+ end
+ cache[sql] = key
+ end
+
+ def clear
+ cache.values.each do |hash|
+ hash[:stmt].close
+ end
+ cache.clear
+ end
+
+ private
+ def cache
+ @cache[$$]
+ end
+ end
+
def initialize(connection, logger, connection_options, config)
super
- @statements = {}
+ @statements = StatementPool.new(@connection,
+ config.fetch(:statement_limit) { 1000 })
@client_encoding = nil
connect
end
@@ -187,9 +221,6 @@ module ActiveRecord
# Clears the prepared statements cache.
def clear_cache!
- @statements.values.each do |cache|
- cache[:stmt].close
- end
@statements.clear
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index ba4a6c7a78..5402918b1d 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1,5 +1,6 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_support/core_ext/object/blank'
+require 'active_record/connection_adapters/statement_pool'
# Make sure we're using pg high enough for PGResult#values
gem 'pg', '~> 0.11'
@@ -246,6 +247,47 @@ module ActiveRecord
true
end
+ class StatementPool < ConnectionAdapters::StatementPool
+ def initialize(connection, max)
+ super
+ @counter = 0
+ @cache = Hash.new { |h,pid| h[pid] = {} }
+ end
+
+ def each(&block); cache.each(&block); end
+ def key?(key); cache.key?(key); end
+ def [](key); cache[key]; end
+ def length; cache.length; end
+
+ def next_key
+ "a#{@counter + 1}"
+ end
+
+ def []=(sql, key)
+ while @max <= cache.size
+ dealloc(cache.shift.last)
+ end
+ @counter += 1
+ cache[sql] = key
+ end
+
+ def clear
+ cache.each_value do |stmt_key|
+ dealloc stmt_key
+ end
+ cache.clear
+ end
+
+ private
+ def cache
+ @cache[$$]
+ end
+
+ def dealloc(key)
+ @connection.query "DEALLOCATE #{key}"
+ end
+ end
+
# Initializes and connects a PostgreSQL adapter.
def initialize(connection, logger, connection_parameters, config)
super(connection, logger)
@@ -254,9 +296,10 @@ module ActiveRecord
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
@local_tz = nil
@table_alias_length = nil
- @statements = {}
connect
+ @statements = StatementPool.new @connection,
+ config.fetch(:statement_limit) { 1000 }
if postgresql_version < 80200
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
@@ -271,9 +314,6 @@ module ActiveRecord
# Clears the prepared statements cache.
def clear_cache!
- @statements.each_value do |value|
- @connection.query "DEALLOCATE #{value}"
- end
@statements.clear
end
@@ -683,12 +723,12 @@ module ActiveRecord
binds << [nil, schema] if schema
exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0
- SELECT COUNT(*)
- FROM pg_class c
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind in ('v','r')
- AND c.relname = $1
- AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'}
+ SELECT COUNT(*)
+ FROM pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+ WHERE c.relkind in ('v','r')
+ AND c.relname = $1
+ AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'}
SQL
end
@@ -996,7 +1036,7 @@ module ActiveRecord
def exec_cache(sql, binds)
unless @statements.key? sql
- nextkey = "a#{@statements.length + 1}"
+ nextkey = @statements.next_key
@connection.prepare nextkey, sql
@statements[sql] = nextkey
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 1996e49296..1932a849ee 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -1,4 +1,6 @@
require 'active_record/connection_adapters/abstract_adapter'
+require 'active_record/connection_adapters/statement_pool'
+require 'active_support/core_ext/string/encoding'
module ActiveRecord
module ConnectionAdapters #:nodoc:
@@ -47,9 +49,45 @@ module ActiveRecord
end
end
+ class StatementPool < ConnectionAdapters::StatementPool
+ def initialize(connection, max)
+ super
+ @cache = Hash.new { |h,pid| h[pid] = {} }
+ end
+
+ def each(&block); cache.each(&block); end
+ def key?(key); cache.key?(key); end
+ def [](key); cache[key]; end
+ def length; cache.length; end
+
+ def []=(sql, key)
+ while @max <= cache.size
+ dealloc(cache.shift.last[:stmt])
+ end
+ cache[sql] = key
+ end
+
+ def clear
+ cache.values.each do |hash|
+ dealloc hash[:stmt]
+ end
+ cache.clear
+ end
+
+ private
+ def cache
+ @cache[$$]
+ end
+
+ def dealloc(stmt)
+ stmt.close unless stmt.closed?
+ end
+ end
+
def initialize(connection, logger, config)
super(connection, logger)
- @statements = {}
+ @statements = StatementPool.new(@connection,
+ config.fetch(:statement_limit) { 1000 })
@config = config
end
@@ -106,10 +144,6 @@ module ActiveRecord
# Clears the prepared statements cache.
def clear_cache!
- @statements.values.map { |hash| hash[:stmt] }.each { |stmt|
- stmt.close unless stmt.closed?
- }
-
@statements.clear
end
diff --git a/activerecord/lib/active_record/connection_adapters/statement_pool.rb b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
new file mode 100644
index 0000000000..c6b1bc8b5b
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
@@ -0,0 +1,40 @@
+module ActiveRecord
+ module ConnectionAdapters
+ class StatementPool
+ include Enumerable
+
+ def initialize(connection, max = 1000)
+ @connection = connection
+ @max = max
+ end
+
+ def each
+ raise NotImplementedError
+ end
+
+ def key?(key)
+ raise NotImplementedError
+ end
+
+ def [](key)
+ raise NotImplementedError
+ end
+
+ def length
+ raise NotImplementedError
+ end
+
+ def []=(sql, key)
+ raise NotImplementedError
+ end
+
+ def clear
+ raise NotImplementedError
+ end
+
+ def delete(key)
+ raise NotImplementedError
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 3f36dcde14..6f1ec7f9b3 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -24,375 +24,368 @@ end
class FixturesFileNotFound < StandardError; end
-# Fixtures are a way of organizing data that you want to test against; in short, sample data.
-#
-# = Fixture formats
-#
-# Fixtures come in 1 flavor:
-#
-# 1. YAML fixtures
-#
-# == YAML fixtures
-#
-# This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
-# in a non-verbose, human-readable format. It ships with Ruby 1.8.1+.
-#
-# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed
-# in the directory appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is
-# automatically configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/</tt>).
-# The fixture file ends with the <tt>.yml</tt> file extension (Rails example:
-# <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>). The format of a YAML fixture file looks like this:
-#
-# rubyonrails:
-# id: 1
-# name: Ruby on Rails
-# url: http://www.rubyonrails.org
-#
-# google:
-# id: 2
-# name: Google
-# url: http://www.google.com
-#
-# This YAML fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and is followed by an
-# indented list of key/value pairs in the "key: value" format. Records are separated by a blank line for your viewing
-# pleasure.
-#
-# Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type.
-# See http://yaml.org/type/omap.html
-# for the specification. You will need ordered fixtures when you have foreign key constraints on keys in the same table.
-# This is commonly needed for tree structures. Example:
-#
-# --- !omap
-# - parent:
-# id: 1
-# parent_id: NULL
-# title: Parent
-# - child:
-# id: 2
-# parent_id: 1
-# title: Child
-#
-# = Using fixtures in testcases
-#
-# Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the
-# fixtures, but first let's take a look at a sample unit test:
-#
-# require 'test_helper'
-#
-# class WebSiteTest < ActiveSupport::TestCase
-# test "web_site_count" do
-# assert_equal 2, WebSite.count
-# end
-# end
-#
-# By default, the <tt>test_helper module</tt> will load all of your fixtures into your test database,
-# so this test will succeed.
-# The testing environment will automatically load the all fixtures into the database before each test.
-# To ensure consistent data, the environment deletes the fixtures before running the load.
-#
-# In addition to being available in the database, the fixture's data may also be accessed by
-# using a special dynamic method, which has the same name as the model, and accepts the
-# name of the fixture to instantiate:
-#
-# test "find" do
-# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
-# end
-#
-# Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the following tests:
-#
-# test "find_alt_method_1" do
-# assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name']
-# end
-#
-# test "find_alt_method_2" do
-# assert_equal "Ruby on Rails", @rubyonrails.news
-# end
-#
-# In order to use these methods to access fixtured data within your testcases, you must specify one of the
-# following in your <tt>ActiveSupport::TestCase</tt>-derived class:
-#
-# - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above)
-# self.use_instantiated_fixtures = true
-#
-# - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only)
-# self.use_instantiated_fixtures = :no_instances
-#
-# Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully
-# traversed in the database to create the fixture hash and/or instance variables. This is expensive for
-# large sets of fixtured data.
-#
-# = Dynamic fixtures with ERB
-#
-# Some times you don't care about the content of the fixtures as much as you care about the volume. In these cases, you can
-# mix ERB in with your YAML fixtures to create a bunch of fixtures for load testing, like:
-#
-# <% for i in 1..1000 %>
-# fix_<%= i %>:
-# id: <%= i %>
-# name: guy_<%= 1 %>
-# <% end %>
-#
-# This will create 1000 very simple YAML fixtures.
-#
-# Using ERB, you can also inject dynamic values into your fixtures with inserts like <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
-# This is however a feature to be used with some caution. The point of fixtures are that they're
-# stable units of predictable sample data. If you feel that you need to inject dynamic values, then
-# perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
-# in fixtures are to be considered a code smell.
-#
-# = Transactional fixtures
-#
-# TestCases can use begin+rollback to isolate their changes to the database instead of having to
-# delete+insert for every test case.
-#
-# class FooTest < ActiveSupport::TestCase
-# self.use_transactional_fixtures = true
-#
-# test "godzilla" do
-# assert !Foo.all.empty?
-# Foo.destroy_all
-# assert Foo.all.empty?
-# end
-#
-# test "godzilla aftermath" do
-# assert !Foo.all.empty?
-# end
-# end
-#
-# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
-# then you may omit all fixtures declarations in your test cases since all the data's already there
-# and every case rolls back its changes.
-#
-# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
-# access to fixture data for every table that has been loaded through fixtures (depending on the
-# value of +use_instantiated_fixtures+)
-#
-# When *not* to use transactional fixtures:
-#
-# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until
-# all parent transactions commit, particularly, the fixtures transaction which is begun in setup
-# and rolled back in teardown. Thus, you won't be able to verify
-# the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
-# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
-# Use InnoDB, MaxDB, or NDB instead.
-#
-# = Advanced YAML Fixtures
-#
-# YAML fixtures that don't specify an ID get some extra features:
-#
-# * Stable, autogenerated IDs
-# * Label references for associations (belongs_to, has_one, has_many)
-# * HABTM associations as inline lists
-# * Autofilled timestamp columns
-# * Fixture label interpolation
-# * Support for YAML defaults
-#
-# == Stable, autogenerated IDs
-#
-# Here, have a monkey fixture:
-#
-# george:
-# id: 1
-# name: George the Monkey
-#
-# reginald:
-# id: 2
-# name: Reginald the Pirate
-#
-# Each of these fixtures has two unique identifiers: one for the database
-# and one for the humans. Why don't we generate the primary key instead?
-# Hashing each fixture's label yields a consistent ID:
-#
-# george: # generated id: 503576764
-# name: George the Monkey
-#
-# reginald: # generated id: 324201669
-# name: Reginald the Pirate
-#
-# Active Record looks at the fixture's model class, discovers the correct
-# primary key, and generates it right before inserting the fixture
-# into the database.
-#
-# The generated ID for a given label is constant, so we can discover
-# any fixture's ID without loading anything, as long as we know the label.
-#
-# == Label references for associations (belongs_to, has_one, has_many)
-#
-# Specifying foreign keys in fixtures can be very fragile, not to
-# mention difficult to read. Since Active Record can figure out the ID of
-# any fixture from its label, you can specify FK's by label instead of ID.
-#
-# === belongs_to
-#
-# Let's break out some more monkeys and pirates.
-#
-# ### in pirates.yml
-#
-# reginald:
-# id: 1
-# name: Reginald the Pirate
-# monkey_id: 1
-#
-# ### in monkeys.yml
-#
-# george:
-# id: 1
-# name: George the Monkey
-# pirate_id: 1
-#
-# Add a few more monkeys and pirates and break this into multiple files,
-# and it gets pretty hard to keep track of what's going on. Let's
-# use labels instead of IDs:
-#
-# ### in pirates.yml
-#
-# reginald:
-# name: Reginald the Pirate
-# monkey: george
-#
-# ### in monkeys.yml
-#
-# george:
-# name: George the Monkey
-# pirate: reginald
-#
-# Pow! All is made clear. Active Record reflects on the fixture's model class,
-# finds all the +belongs_to+ associations, and allows you to specify
-# a target *label* for the *association* (monkey: george) rather than
-# a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
-#
-# ==== Polymorphic belongs_to
-#
-# Supporting polymorphic relationships is a little bit more complicated, since
-# Active Record needs to know what type your association is pointing at. Something
-# like this should look familiar:
-#
-# ### in fruit.rb
-#
-# belongs_to :eater, :polymorphic => true
-#
-# ### in fruits.yml
-#
-# apple:
-# id: 1
-# name: apple
-# eater_id: 1
-# eater_type: Monkey
-#
-# Can we do better? You bet!
-#
-# apple:
-# eater: george (Monkey)
-#
-# Just provide the polymorphic target type and Active Record will take care of the rest.
-#
-# === has_and_belongs_to_many
-#
-# Time to give our monkey some fruit.
-#
-# ### in monkeys.yml
-#
-# george:
-# id: 1
-# name: George the Monkey
-#
-# ### in fruits.yml
-#
-# apple:
-# id: 1
-# name: apple
-#
-# orange:
-# id: 2
-# name: orange
-#
-# grape:
-# id: 3
-# name: grape
-#
-# ### in fruits_monkeys.yml
-#
-# apple_george:
-# fruit_id: 1
-# monkey_id: 1
-#
-# orange_george:
-# fruit_id: 2
-# monkey_id: 1
-#
-# grape_george:
-# fruit_id: 3
-# monkey_id: 1
-#
-# Let's make the HABTM fixture go away.
-#
-# ### in monkeys.yml
-#
-# george:
-# id: 1
-# name: George the Monkey
-# fruits: apple, orange, grape
-#
-# ### in fruits.yml
-#
-# apple:
-# name: apple
-#
-# orange:
-# name: orange
-#
-# grape:
-# name: grape
-#
-# Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
-# on George's fixture, but we could've just as easily specified a list
-# of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
-# the fixture's model class and discovers the +has_and_belongs_to_many+
-# associations.
-#
-# == Autofilled timestamp columns
-#
-# If your table/model specifies any of Active Record's
-# standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
-# they will automatically be set to <tt>Time.now</tt>.
-#
-# If you've set specific values, they'll be left alone.
-#
-# == Fixture label interpolation
-#
-# The label of the current fixture is always available as a column value:
-#
-# geeksomnia:
-# name: Geeksomnia's Account
-# subdomain: $LABEL
-#
-# Also, sometimes (like when porting older join table fixtures) you'll need
-# to be able to get a hold of the identifier for a given label. ERB
-# to the rescue:
-#
-# george_reginald:
-# monkey_id: <%= ActiveRecord::Fixtures.identify(:reginald) %>
-# pirate_id: <%= ActiveRecord::Fixtures.identify(:george) %>
-#
-# == Support for YAML defaults
-#
-# You probably already know how to use YAML to set and reuse defaults in
-# your <tt>database.yml</tt> file. You can use the same technique in your fixtures:
-#
-# DEFAULTS: &DEFAULTS
-# created_on: <%= 3.weeks.ago.to_s(:db) %>
-#
-# first:
-# name: Smurf
-# *DEFAULTS
-#
-# second:
-# name: Fraggle
-# *DEFAULTS
-#
-# Any fixture labeled "DEFAULTS" is safely ignored.
-
module ActiveRecord
+ # \Fixtures are a way of organizing data that you want to test against; in short, sample data.
+ #
+ # They are stored in YAML files, one file per model, which are placed in the directory
+ # appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically
+ # configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/</tt>).
+ # The fixture file ends with the <tt>.yml</tt> file extension (Rails example:
+ # <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>). The format of a fixture file looks
+ # like this:
+ #
+ # rubyonrails:
+ # id: 1
+ # name: Ruby on Rails
+ # url: http://www.rubyonrails.org
+ #
+ # google:
+ # id: 2
+ # name: Google
+ # url: http://www.google.com
+ #
+ # This fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and
+ # is followed by an indented list of key/value pairs in the "key: value" format. Records are
+ # separated by a blank line for your viewing pleasure.
+ #
+ # Note that fixtures are unordered. If you want ordered fixtures, use the omap YAML type.
+ # See http://yaml.org/type/omap.html
+ # for the specification. You will need ordered fixtures when you have foreign key constraints
+ # on keys in the same table. This is commonly needed for tree structures. Example:
+ #
+ # --- !omap
+ # - parent:
+ # id: 1
+ # parent_id: NULL
+ # title: Parent
+ # - child:
+ # id: 2
+ # parent_id: 1
+ # title: Child
+ #
+ # = Using Fixtures in Test Cases
+ #
+ # Since fixtures are a testing construct, we use them in our unit and functional tests. There
+ # are two ways to use the fixtures, but first let's take a look at a sample unit test:
+ #
+ # require 'test_helper'
+ #
+ # class WebSiteTest < ActiveSupport::TestCase
+ # test "web_site_count" do
+ # assert_equal 2, WebSite.count
+ # end
+ # end
+ #
+ # By default, <tt>test_helper.rb</tt> will load all of your fixtures into your test database,
+ # so this test will succeed.
+ #
+ # The testing environment will automatically load the all fixtures into the database before each
+ # test. To ensure consistent data, the environment deletes the fixtures before running the load.
+ #
+ # In addition to being available in the database, the fixture's data may also be accessed by
+ # using a special dynamic method, which has the same name as the model, and accepts the
+ # name of the fixture to instantiate:
+ #
+ # test "find" do
+ # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
+ # end
+ #
+ # Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the
+ # following tests:
+ #
+ # test "find_alt_method_1" do
+ # assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name']
+ # end
+ #
+ # test "find_alt_method_2" do
+ # assert_equal "Ruby on Rails", @rubyonrails.news
+ # end
+ #
+ # In order to use these methods to access fixtured data within your testcases, you must specify one of the
+ # following in your <tt>ActiveSupport::TestCase</tt>-derived class:
+ #
+ # - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above)
+ # self.use_instantiated_fixtures = true
+ #
+ # - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only)
+ # self.use_instantiated_fixtures = :no_instances
+ #
+ # Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully
+ # traversed in the database to create the fixture hash and/or instance variables. This is expensive for
+ # large sets of fixtured data.
+ #
+ # = Dynamic fixtures with ERB
+ #
+ # Some times you don't care about the content of the fixtures as much as you care about the volume.
+ # In these cases, you can mix ERB in with your YAML fixtures to create a bunch of fixtures for load
+ # testing, like:
+ #
+ # <% 1.upto(1000) do |i| %>
+ # fix_<%= i %>:
+ # id: <%= i %>
+ # name: guy_<%= 1 %>
+ # <% end %>
+ #
+ # This will create 1000 very simple fixtures.
+ #
+ # Using ERB, you can also inject dynamic values into your fixtures with inserts like
+ # <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
+ # This is however a feature to be used with some caution. The point of fixtures are that they're
+ # stable units of predictable sample data. If you feel that you need to inject dynamic values, then
+ # perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
+ # in fixtures are to be considered a code smell.
+ #
+ # = Transactional Fixtures
+ #
+ # Test cases can use begin+rollback to isolate their changes to the database instead of having to
+ # delete+insert for every test case.
+ #
+ # class FooTest < ActiveSupport::TestCase
+ # self.use_transactional_fixtures = true
+ #
+ # test "godzilla" do
+ # assert !Foo.all.empty?
+ # Foo.destroy_all
+ # assert Foo.all.empty?
+ # end
+ #
+ # test "godzilla aftermath" do
+ # assert !Foo.all.empty?
+ # end
+ # end
+ #
+ # If you preload your test database with all fixture data (probably in the rake task) and use
+ # transactional fixtures, then you may omit all fixtures declarations in your test cases since
+ # all the data's already there and every case rolls back its changes.
+ #
+ # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to
+ # true. This will provide access to fixture data for every table that has been loaded through
+ # fixtures (depending on the value of +use_instantiated_fixtures+).
+ #
+ # When *not* to use transactional fixtures:
+ #
+ # 1. You're testing whether a transaction works correctly. Nested transactions don't commit until
+ # all parent transactions commit, particularly, the fixtures transaction which is begun in setup
+ # and rolled back in teardown. Thus, you won't be able to verify
+ # the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
+ # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
+ # Use InnoDB, MaxDB, or NDB instead.
+ #
+ # = Advanced Fixtures
+ #
+ # Fixtures that don't specify an ID get some extra features:
+ #
+ # * Stable, autogenerated IDs
+ # * Label references for associations (belongs_to, has_one, has_many)
+ # * HABTM associations as inline lists
+ # * Autofilled timestamp columns
+ # * Fixture label interpolation
+ # * Support for YAML defaults
+ #
+ # == Stable, Autogenerated IDs
+ #
+ # Here, have a monkey fixture:
+ #
+ # george:
+ # id: 1
+ # name: George the Monkey
+ #
+ # reginald:
+ # id: 2
+ # name: Reginald the Pirate
+ #
+ # Each of these fixtures has two unique identifiers: one for the database
+ # and one for the humans. Why don't we generate the primary key instead?
+ # Hashing each fixture's label yields a consistent ID:
+ #
+ # george: # generated id: 503576764
+ # name: George the Monkey
+ #
+ # reginald: # generated id: 324201669
+ # name: Reginald the Pirate
+ #
+ # Active Record looks at the fixture's model class, discovers the correct
+ # primary key, and generates it right before inserting the fixture
+ # into the database.
+ #
+ # The generated ID for a given label is constant, so we can discover
+ # any fixture's ID without loading anything, as long as we know the label.
+ #
+ # == Label references for associations (belongs_to, has_one, has_many)
+ #
+ # Specifying foreign keys in fixtures can be very fragile, not to
+ # mention difficult to read. Since Active Record can figure out the ID of
+ # any fixture from its label, you can specify FK's by label instead of ID.
+ #
+ # === belongs_to
+ #
+ # Let's break out some more monkeys and pirates.
+ #
+ # ### in pirates.yml
+ #
+ # reginald:
+ # id: 1
+ # name: Reginald the Pirate
+ # monkey_id: 1
+ #
+ # ### in monkeys.yml
+ #
+ # george:
+ # id: 1
+ # name: George the Monkey
+ # pirate_id: 1
+ #
+ # Add a few more monkeys and pirates and break this into multiple files,
+ # and it gets pretty hard to keep track of what's going on. Let's
+ # use labels instead of IDs:
+ #
+ # ### in pirates.yml
+ #
+ # reginald:
+ # name: Reginald the Pirate
+ # monkey: george
+ #
+ # ### in monkeys.yml
+ #
+ # george:
+ # name: George the Monkey
+ # pirate: reginald
+ #
+ # Pow! All is made clear. Active Record reflects on the fixture's model class,
+ # finds all the +belongs_to+ associations, and allows you to specify
+ # a target *label* for the *association* (monkey: george) rather than
+ # a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
+ #
+ # ==== Polymorphic belongs_to
+ #
+ # Supporting polymorphic relationships is a little bit more complicated, since
+ # Active Record needs to know what type your association is pointing at. Something
+ # like this should look familiar:
+ #
+ # ### in fruit.rb
+ #
+ # belongs_to :eater, :polymorphic => true
+ #
+ # ### in fruits.yml
+ #
+ # apple:
+ # id: 1
+ # name: apple
+ # eater_id: 1
+ # eater_type: Monkey
+ #
+ # Can we do better? You bet!
+ #
+ # apple:
+ # eater: george (Monkey)
+ #
+ # Just provide the polymorphic target type and Active Record will take care of the rest.
+ #
+ # === has_and_belongs_to_many
+ #
+ # Time to give our monkey some fruit.
+ #
+ # ### in monkeys.yml
+ #
+ # george:
+ # id: 1
+ # name: George the Monkey
+ #
+ # ### in fruits.yml
+ #
+ # apple:
+ # id: 1
+ # name: apple
+ #
+ # orange:
+ # id: 2
+ # name: orange
+ #
+ # grape:
+ # id: 3
+ # name: grape
+ #
+ # ### in fruits_monkeys.yml
+ #
+ # apple_george:
+ # fruit_id: 1
+ # monkey_id: 1
+ #
+ # orange_george:
+ # fruit_id: 2
+ # monkey_id: 1
+ #
+ # grape_george:
+ # fruit_id: 3
+ # monkey_id: 1
+ #
+ # Let's make the HABTM fixture go away.
+ #
+ # ### in monkeys.yml
+ #
+ # george:
+ # id: 1
+ # name: George the Monkey
+ # fruits: apple, orange, grape
+ #
+ # ### in fruits.yml
+ #
+ # apple:
+ # name: apple
+ #
+ # orange:
+ # name: orange
+ #
+ # grape:
+ # name: grape
+ #
+ # Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
+ # on George's fixture, but we could've just as easily specified a list
+ # of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
+ # the fixture's model class and discovers the +has_and_belongs_to_many+
+ # associations.
+ #
+ # == Autofilled Timestamp Columns
+ #
+ # If your table/model specifies any of Active Record's
+ # standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
+ # they will automatically be set to <tt>Time.now</tt>.
+ #
+ # If you've set specific values, they'll be left alone.
+ #
+ # == Fixture label interpolation
+ #
+ # The label of the current fixture is always available as a column value:
+ #
+ # geeksomnia:
+ # name: Geeksomnia's Account
+ # subdomain: $LABEL
+ #
+ # Also, sometimes (like when porting older join table fixtures) you'll need
+ # to be able to get a hold of the identifier for a given label. ERB
+ # to the rescue:
+ #
+ # george_reginald:
+ # monkey_id: <%= ActiveRecord::Fixtures.identify(:reginald) %>
+ # pirate_id: <%= ActiveRecord::Fixtures.identify(:george) %>
+ #
+ # == Support for YAML defaults
+ #
+ # You probably already know how to use YAML to set and reuse defaults in
+ # your <tt>database.yml</tt> file. You can use the same technique in your fixtures:
+ #
+ # DEFAULTS: &DEFAULTS
+ # created_on: <%= 3.weeks.ago.to_s(:db) %>
+ #
+ # first:
+ # name: Smurf
+ # *DEFAULTS
+ #
+ # second:
+ # name: Fraggle
+ # *DEFAULTS
+ #
+ # Any fixture labeled "DEFAULTS" is safely ignored.
class Fixtures
MAX_ID = 2 ** 30 - 1
diff --git a/activerecord/lib/active_record/fixtures/file.rb b/activerecord/lib/active_record/fixtures/file.rb
index 04f494db2c..6bad36abb9 100644
--- a/activerecord/lib/active_record/fixtures/file.rb
+++ b/activerecord/lib/active_record/fixtures/file.rb
@@ -29,11 +29,21 @@ module ActiveRecord
rows.each(&block)
end
+ RESCUE_ERRORS = [ ArgumentError ] # :nodoc:
+
private
+ if defined?(Psych) && defined?(Psych::SyntaxError)
+ RESCUE_ERRORS << Psych::SyntaxError
+ end
+
def rows
return @rows if @rows
- data = YAML.load(render(IO.read(@file)))
+ begin
+ data = YAML.load(render(IO.read(@file)))
+ rescue *RESCUE_ERRORS => error
+ raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace
+ end
@rows = data ? validate(data).to_a : []
end
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index 2eeff7e36f..ffee5a081a 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -71,7 +71,7 @@ module ActiveRecord
end
def invert_rename_index(args)
- [:rename_index, args.reverse]
+ [:rename_index, [args.first] + args.last(2).reverse]
end
def invert_rename_column(args)
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 2dac9ea0fb..5e65e46a7d 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -314,7 +314,7 @@ module ActiveRecord
new_id = self.class.unscoped.insert attributes_values
- self.id ||= new_id
+ self.id ||= new_id if self.class.primary_key
IdentityMap.add(self) if IdentityMap.enabled?
@new_record = false
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 13c41350fb..b3316fd1a2 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -203,11 +203,13 @@ db_namespace = namespace :db do
end
db_list = ActiveRecord::Base.connection.select_values("SELECT version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}")
file_list = []
- Dir.foreach(File.join(Rails.root, 'db', 'migrate')) do |file|
- # only files matching "20091231235959_some_name.rb" pattern
- if match_data = /^(\d{14})_(.+)\.rb$/.match(file)
- status = db_list.delete(match_data[1]) ? 'up' : 'down'
- file_list << [status, match_data[1], match_data[2].humanize]
+ ActiveRecord::Migrator.migrations_paths.each do |path|
+ Dir.foreach(path) do |file|
+ # only files matching "20091231235959_some_name.rb" pattern
+ if match_data = /^(\d{14})_(.+)\.rb$/.match(file)
+ status = db_list.delete(match_data[1]) ? 'up' : 'down'
+ file_list << [status, match_data[1], match_data[2].humanize]
+ end
end
end
db_list.map! do |version|
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index a2324039cf..120ff0cac6 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -211,11 +211,9 @@ module ActiveRecord
@association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key
end
- def association_primary_key
- @association_primary_key ||=
- options[:primary_key] ||
- !options[:polymorphic] && klass.primary_key ||
- 'id'
+ # klass option is necessary to support loading polymorphic associations
+ def association_primary_key(klass = nil)
+ options[:primary_key] || (klass || self.klass).primary_key
end
def active_record_primary_key
@@ -226,7 +224,7 @@ module ActiveRecord
if options[:counter_cache] == true
"#{active_record.name.demodulize.underscore.pluralize}_count"
elsif options[:counter_cache]
- options[:counter_cache]
+ options[:counter_cache].to_s
end
end
@@ -463,17 +461,15 @@ module ActiveRecord
# We want to use the klass from this reflection, rather than just delegate straight to
# the source_reflection, because the source_reflection may be polymorphic. We still
# need to respect the source_reflection's :primary_key option, though.
- def association_primary_key
- @association_primary_key ||= begin
- # 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] || klass.primary_key
+ def association_primary_key(klass = self.klass)
+ # 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] || klass.primary_key
end
# Gets an array of possible <tt>:through</tt> source reflection names:
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 15fd1a58c8..ecefaa633c 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -94,6 +94,48 @@ module ActiveRecord
scoping { @klass.create!(*args, &block) }
end
+ # Tries to load the first record; if it fails, then <tt>create</tt> is called with the same arguments as this method.
+ #
+ # Expects arguments in the same format as <tt>Base.create</tt>.
+ #
+ # ==== Examples
+ # # Find the first user named Penélope or create a new one.
+ # User.where(:first_name => 'Penélope').first_or_create
+ # # => <User id: 1, first_name: 'Penélope', last_name: nil>
+ #
+ # # Find the first user named Penélope or create a new one.
+ # # We already have one so the existing record will be returned.
+ # User.where(:first_name => 'Penélope').first_or_create
+ # # => <User id: 1, first_name: 'Penélope', last_name: nil>
+ #
+ # # Find the first user named Scarlett or create a new one with a particular last name.
+ # User.where(:first_name => 'Scarlett').first_or_create(:last_name => 'Johansson')
+ # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
+ #
+ # # Find the first user named Scarlett or create a new one with a different last name.
+ # # We already have one so the existing record will be returned.
+ # User.where(:first_name => 'Scarlett').first_or_create do |user|
+ # user.last_name = "O'Hara"
+ # end
+ # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
+ def first_or_create(attributes = nil, options = {}, &block)
+ first || create(attributes, options, &block)
+ end
+
+ # Like <tt>first_or_create</tt> but calls <tt>create!</tt> so an exception is raised if the created record is invalid.
+ #
+ # Expects arguments in the same format as <tt>Base.create!</tt>.
+ def first_or_create!(attributes = nil, options = {}, &block)
+ first || create!(attributes, options, &block)
+ end
+
+ # Like <tt>first_or_create</tt> but calls <tt>new</tt> instead of <tt>create</tt>.
+ #
+ # Expects arguments in the same format as <tt>Base.new</tt>.
+ def first_or_initialize(attributes = nil, options = {}, &block)
+ first || new(attributes, options, &block)
+ end
+
def respond_to?(method, include_private = false)
arel.respond_to?(method, include_private) ||
Array.method_defined?(method) ||
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index ec1176e3dd..2fd89882ff 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -62,7 +62,7 @@ module ActiveRecord
start = options.delete(:start).to_i
batch_size = options.delete(:batch_size) || 1000
- relation = relation.except(:order).order(batch_order).limit(batch_size)
+ relation = relation.reorder(batch_order).limit(batch_size)
records = relation.where(table[primary_key].gteq(start)).all
while records.any?
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 73368aed18..7eeb3dde70 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -114,7 +114,7 @@ module ActiveRecord
def first(*args)
if args.any?
if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
- to_a.first(*args)
+ limit(*args).to_a
else
apply_finder_options(args.first).first
end
@@ -134,7 +134,11 @@ module ActiveRecord
def last(*args)
if args.any?
if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
- to_a.last(*args)
+ if order_values.empty? && reorder_value.nil?
+ order("#{primary_key} DESC").limit(*args).reverse
+ else
+ to_a.last(*args)
+ end
else
apply_finder_options(args.first).last
end
@@ -180,7 +184,9 @@ module ActiveRecord
# Person.exists?(:name => "David")
# Person.exists?(['name LIKE ?', "%#{query}%"])
# Person.exists?
- def exists?(id = nil)
+ def exists?(id = false)
+ return false if id.nil?
+
id = id.id if ActiveRecord::Base === id
join_dependency = construct_join_dependency_for_association_find
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 355540782f..670ba0987d 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -37,6 +37,35 @@ module ActiveRecord
relation
end
+ # Works in two unique ways.
+ #
+ # First: takes a block so it can be used just like Array#select.
+ #
+ # Model.scoped.select { |m| m.field == value }
+ #
+ # This will build an array of objects from the database for the scope,
+ # converting them into an array and iterating through them using Array#select.
+ #
+ # Second: Modifies the SELECT statement for the query so that only certain
+ # fields are retrieved:
+ #
+ # >> Model.select(:field)
+ # => [#<Model field:value>]
+ #
+ # Although in the above example it looks as though this method returns an
+ # array, it actually returns a relation object and can have other query
+ # methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
+ #
+ # This method will also take multiple parameters:
+ #
+ # >> Model.select(:field, :other_field, :and_one_more)
+ # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
+ #
+ # Any attributes that do not have fields retrieved by a select
+ # will return `nil` when the getter method for that attribute is used:
+ #
+ # >> Model.select(:field).first.other_field
+ # => nil
def select(value = Proc.new)
if block_given?
to_a.select {|*block_args| value.call(*block_args) }
@@ -147,6 +176,42 @@ module ActiveRecord
relation
end
+ # Used to extend a scope with additional methods, either through
+ # a module or through a block provided.
+ #
+ # The object returned is a relation, which can be further extended.
+ #
+ # === Using a module
+ #
+ # module Pagination
+ # def page(number)
+ # # pagination code goes here
+ # end
+ # end
+ #
+ # scope = Model.scoped.extending(Pagination)
+ # scope.page(params[:page])
+ #
+ # You can also pass a list of modules:
+ #
+ # scope = Model.scoped.extending(Pagination, SomethingElse)
+ #
+ # === Using a block
+ #
+ # scope = Model.scoped.extending do
+ # def page(number)
+ # # pagination code goes here
+ # end
+ # end
+ # scope.page(params[:page])
+ #
+ # You can also use a block and a module list:
+ #
+ # scope = Model.scoped.extending(Pagination) do
+ # def per_page(number)
+ # # pagination code goes here
+ # end
+ # end
def extending(*modules)
modules << Module.new(&Proc.new) if block_given?
@@ -305,8 +370,10 @@ module ActiveRecord
when Arel::Nodes::Ordering
o.reverse
when String, Symbol
- s = o.to_s.gsub(/\s((desc)|(asc))\s*(,|\Z)/i) { |m| " #{$2 ? 'ASC' : 'DESC'}#{$4}" }
- s.match(/\s(de|a)sc\Z/i) ? s : s.concat(" DESC")
+ o.to_s.split(',').collect do |s|
+ s.strip!
+ s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
+ end
else
o
end
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index cbfa1ad609..0e7f57aa43 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -179,7 +179,7 @@ module ActiveRecord #:nodoc:
class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
def initialize(*args)
super
- options[:except] |= Array.wrap(@serializable.class.inheritance_column)
+ options[:except] = Array.wrap(options[:except]) | Array.wrap(@serializable.class.inheritance_column)
end
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index cccac6ffd7..4d5e469a7f 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -48,7 +48,9 @@ module ActiveRecord
current_time = current_time_from_proper_timezone
all_timestamp_attributes.each do |column|
- write_attribute(column.to_s, current_time) if respond_to?(column) && self.send(column).nil?
+ if respond_to?(column) && respond_to?("#{column}=") && self.send(column).nil?
+ write_attribute(column.to_s, current_time)
+ end
end
end