aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record')
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb5
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb39
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb17
-rw-r--r--activerecord/lib/active_record/integration.rb13
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb14
-rw-r--r--activerecord/lib/active_record/migration.rb26
-rw-r--r--activerecord/lib/active_record/model_schema.rb7
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake2
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb38
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb84
-rw-r--r--activerecord/lib/active_record/schema.rb40
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb17
-rw-r--r--activerecord/lib/active_record/schema_migration.rb39
21 files changed, 241 insertions, 194 deletions
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index c7d8a84a7e..c3266f2bb4 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -153,6 +153,11 @@ module ActiveRecord
delete_through_records(records)
+ if source_reflection.options[:counter_cache]
+ counter = source_reflection.counter_cache_column
+ klass.decrement_counter counter, records.map(&:id)
+ end
+
if through_reflection.macro == :has_many && update_through_counter?(method)
update_counter(-count, through_reflection)
end
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index 6c5e2ac05d..ecfa556ab4 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -132,7 +132,7 @@ module ActiveRecord
if object.class.send(:create_time_zone_conversion_attribute?, name, column)
Time.zone.local(*set_values)
else
- Time.time_with_datetime_fallback(object.class.default_timezone, *set_values)
+ Time.send(object.class.default_timezone, *set_values)
end
end
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 b5a8011ca4..82d0cf7e2e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1,4 +1,5 @@
require 'thread'
+require 'thread_safe'
require 'monitor'
require 'set'
require 'active_support/deprecation'
@@ -236,9 +237,6 @@ module ActiveRecord
@spec = spec
- # The cache of reserved connections mapped to threads
- @reserved_connections = {}
-
@checkout_timeout = spec.config[:checkout_timeout] || 5
@dead_connection_timeout = spec.config[:dead_connection_timeout]
@reaper = Reaper.new self, spec.config[:reaping_frequency]
@@ -247,6 +245,9 @@ module ActiveRecord
# default max pool size to 5
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
+ # The cache of reserved connections mapped to threads
+ @reserved_connections = ThreadSafe::Cache.new(:initial_capacity => @size)
+
@connections = []
@automatic_reconnect = true
@@ -267,7 +268,9 @@ module ActiveRecord
# #connection can be called any number of times; the connection is
# held in a hash keyed by the thread id.
def connection
- synchronize do
+ # this is correctly done double-checked locking
+ # (ThreadSafe::Cache's lookups have volatile semantics)
+ @reserved_connections[current_connection_id] || synchronize do
@reserved_connections[current_connection_id] ||= checkout
end
end
@@ -310,7 +313,7 @@ module ActiveRecord
# Disconnects all connections in the pool, and clears the pool.
def disconnect!
synchronize do
- @reserved_connections = {}
+ @reserved_connections.clear
@connections.each do |conn|
checkin conn
conn.disconnect!
@@ -323,7 +326,7 @@ module ActiveRecord
# Clears the cache which maps classes.
def clear_reloadable_connections!
synchronize do
- @reserved_connections = {}
+ @reserved_connections.clear
@connections.each do |conn|
checkin conn
conn.disconnect! if conn.requires_reloading?
@@ -490,11 +493,15 @@ module ActiveRecord
# determine the connection pool that they should use.
class ConnectionHandler
def initialize
- # These hashes are keyed by klass.name, NOT klass. Keying them by klass
+ # These caches are keyed by klass.name, NOT klass. Keying them by klass
# alone would lead to memory leaks in development mode as all previous
# instances of the class would stay in memory.
- @owner_to_pool = Hash.new { |h,k| h[k] = {} }
- @class_to_pool = Hash.new { |h,k| h[k] = {} }
+ @owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
+ h[k] = ThreadSafe::Cache.new(:initial_capacity => 2)
+ end
+ @class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
+ h[k] = ThreadSafe::Cache.new
+ end
end
def connection_pool_list
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 7ec6abbc45..73012834c9 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -190,16 +190,16 @@ module ActiveRecord
#
# What can be written like this with the regular calls to column:
#
- # create_table "products", force: true do |t|
- # t.column "shop_id", :integer
- # t.column "creator_id", :integer
- # t.column "name", :string, default: "Untitled"
- # t.column "value", :string, default: "Untitled"
- # t.column "created_at", :datetime
- # t.column "updated_at", :datetime
+ # create_table :products do |t|
+ # t.column :shop_id, :integer
+ # t.column :creator_id, :integer
+ # t.column :name, :string, default: "Untitled"
+ # t.column :value, :string, default: "Untitled"
+ # t.column :created_at, :datetime
+ # t.column :updated_at, :datetime
# end
#
- # Can also be written as follows using the short-hand:
+ # can also be written as follows using the short-hand:
#
# create_table :products do |t|
# t.integer :shop_id, :creator_id
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index a470e8de07..f1e42dfbbe 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -490,8 +490,8 @@ module ActiveRecord
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
ActiveRecord::SchemaMigration.order('version').map { |sm|
- "INSERT INTO #{sm_table} (version, migrated_at, fingerprint, name) VALUES ('#{sm.version}',LOCALTIMESTAMP,'#{sm.fingerprint}','#{sm.name}');"
- }.join("\n\n")
+ "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');"
+ }.join "\n\n"
end
# Should not be called normally, but this operation is non-destructive.
@@ -512,7 +512,7 @@ module ActiveRecord
end
unless migrated.include?(version)
- ActiveRecord::SchemaMigration.create!(:version => version, :migrated_at => Time.now)
+ execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
end
inserted = Set.new
@@ -520,7 +520,7 @@ module ActiveRecord
if inserted.include?(v)
raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict."
elsif v < version
- ActiveRecord::SchemaMigration.create!(:version => v, :migrated_at => Time.now)
+ execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
inserted << v
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 84e73e6f0f..d37e489f5c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -704,6 +704,45 @@ module ActiveRecord
end
column
end
+
+ def configure_connection
+ variables = @config[:variables] || {}
+
+ # By default, MySQL 'where id is null' selects the last inserted id.
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
+ variables[:sql_auto_is_null] = 0
+
+ # Increase timeout so the server doesn't disconnect us.
+ wait_timeout = @config[:wait_timeout]
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
+ variables[:wait_timeout] = wait_timeout
+
+ # Make MySQL reject illegal values rather than truncating or blanking them, see
+ # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
+ # If the user has provided another value for sql_mode, don't replace it.
+ if strict_mode? && !variables.has_key?(:sql_mode)
+ variables[:sql_mode] = 'STRICT_ALL_TABLES'
+ end
+
+ # NAMES does not have an equals sign, see
+ # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
+ # (trailing comma because variable_assignments will always have content)
+ encoding = "NAMES #{@config[:encoding]}, " if @config[:encoding]
+
+ # Gather up all of the SET variables...
+ variable_assignments = variables.map do |k, v|
+ if v == ':default' || v == :default
+ "@@SESSION.#{k.to_s} = DEFAULT" # Sets the value to the global or compile default
+ elsif !v.nil?
+ "@@SESSION.#{k.to_s} = #{quote(v)}"
+ end
+ # or else nil; compact to clear nils out
+ end.compact.join(', ')
+
+ # ...and send them all in one query
+ execute("SET #{encoding} #{variable_assignments}", :skip_logging)
+ end
+
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 80984f39c9..df23dbfb60 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -240,7 +240,7 @@ module ActiveRecord
# Treat 0000-00-00 00:00:00 as nil.
return nil if year.nil? || (year == 0 && mon == 0 && mday == 0)
- Time.time_with_datetime_fallback(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
+ Time.send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
end
def fast_string_to_date(string)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index f55d19393c..a6013f754a 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -251,27 +251,7 @@ module ActiveRecord
def configure_connection
@connection.query_options.merge!(:as => :array)
-
- # By default, MySQL 'where id is null' selects the last inserted id.
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
- variable_assignments = ['SQL_AUTO_IS_NULL=0']
-
- # Make MySQL reject illegal values rather than truncating or
- # blanking them. See
- # http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
- variable_assignments << "SQL_MODE='STRICT_ALL_TABLES'" if strict_mode?
-
- encoding = @config[:encoding]
-
- # make sure we set the encoding
- variable_assignments << "NAMES '#{encoding}'" if encoding
-
- # increase timeout so mysql server doesn't disconnect us
- wait_timeout = @config[:wait_timeout]
- wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
- variable_assignments << "@@wait_timeout = #{wait_timeout}"
-
- execute("SET #{variable_assignments.join(', ')}", :skip_logging)
+ super
end
def version
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index e9677415cc..631f646f58 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -51,7 +51,8 @@ module ActiveRecord
# * <tt>:database</tt> - The name of the database. No default, must be provided.
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
# * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
- # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html)
+ # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html)
+ # * <tt>:variables</tt> - (Optional) A hash session variables to send as `SET @@SESSION.key = value` on each database connection. Use the value `:default` to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/set-statement.html).
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
@@ -535,18 +536,10 @@ module ActiveRecord
configure_connection
end
+ # Many Rails applications monkey-patch a replacement of the configure_connection method
+ # and don't call 'super', so leave this here even though it looks superfluous.
def configure_connection
- encoding = @config[:encoding]
- execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
-
- # By default, MySQL 'where id is null' selects the last inserted id.
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
- execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
-
- # Make MySQL reject illegal values rather than truncating or
- # blanking them. See
- # http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
- execute("SET SQL_MODE='STRICT_ALL_TABLES'", :skip_logging) if strict_mode?
+ super
end
def select(sql, name = nil, binds = [])
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index e18464fa35..e24ee1efdd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -24,7 +24,7 @@ module ActiveRecord
# Forward any unused config params to PGconn.connect.
[:statement_limit, :encoding, :min_messages, :schema_search_path,
:schema_order, :adapter, :pool, :checkout_timeout, :template,
- :reaping_frequency, :insert_returning].each do |key|
+ :reaping_frequency, :insert_returning, :variables].each do |key|
conn_params.delete key
end
conn_params.delete_if { |k,v| v.nil? }
@@ -238,6 +238,8 @@ module ActiveRecord
# <encoding></tt> call on the connection.
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
+ # * <tt>:variables</tt> - An optional hash of additional parameters that
+ # will be used in <tt>SET SESSION key = val</tt> calls on the connection.
# * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT</tt> statements
# defaults to true.
#
@@ -706,11 +708,24 @@ module ActiveRecord
# If using Active Record's time zone support configure the connection to return
# TIMESTAMP WITH ZONE types in UTC.
+ # (SET TIME ZONE does not use an equals sign like other SET variables)
if ActiveRecord::Base.default_timezone == :utc
execute("SET time zone 'UTC'", 'SCHEMA')
elsif @local_tz
execute("SET time zone '#{@local_tz}'", 'SCHEMA')
end
+
+ # SET statements from :variables config hash
+ # http://www.postgresql.org/docs/8.3/static/sql-set.html
+ variables = @config[:variables] || {}
+ variables.map do |k, v|
+ if v == ':default' || v == :default
+ # Sets the value to the global or compile default
+ execute("SET SESSION #{k.to_s} TO DEFAULT", 'SCHEMA')
+ elsif !v.nil?
+ execute("SET SESSION #{k.to_s} TO #{quote(v)}", 'SCHEMA')
+ end
+ end
end
# Returns the current ID of a table's sequence.
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 7bdc1bd4c6..7f877a6471 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -1,5 +1,16 @@
module ActiveRecord
module Integration
+ extend ActiveSupport::Concern
+
+ included do
+ ##
+ # :singleton-method:
+ # Indicates the format used to generate the timestamp format in the cache key.
+ # This is +:number+, by default.
+ class_attribute :cache_timestamp_format, :instance_writer => false
+ self.cache_timestamp_format = :nsec
+ end
+
# Returns a String, which Action Pack uses for constructing an URL to this
# object. The default implementation returns this record's id as a String,
# or nil if this record's unsaved.
@@ -37,7 +48,7 @@ module ActiveRecord
when new_record?
"#{self.class.model_name.cache_key}/new"
when timestamp = self[:updated_at]
- timestamp = timestamp.utc.to_s(:nsec)
+ timestamp = timestamp.utc.to_s(cache_timestamp_format)
"#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
else
"#{self.class.model_name.cache_key}/#{id}"
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index ca79950049..2366a91bb5 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -1,7 +1,7 @@
module ActiveRecord
class LogSubscriber < ActiveSupport::LogSubscriber
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
-
+
def self.runtime=(value)
Thread.current[:active_record_sql_runtime] = value
end
@@ -20,6 +20,16 @@ module ActiveRecord
@odd_or_even = false
end
+ def render_bind(column, value)
+ if column.type == :binary
+ rendered_value = "<#{value.bytesize} bytes of binary data>"
+ else
+ rendered_value = value
+ end
+
+ [column.name, rendered_value]
+ end
+
def sql(event)
self.class.runtime += event.duration
return unless logger.debug?
@@ -34,7 +44,7 @@ module ActiveRecord
unless (payload[:binds] || []).empty?
binds = " " + payload[:binds].map { |col,v|
- [col.name, v]
+ render_bind(col, v)
}.inspect
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 4ce276d4bf..ef2107ad24 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,6 +1,5 @@
require "active_support/core_ext/class/attribute_accessors"
require 'set'
-require 'digest/md5'
module ActiveRecord
# Exception that can be raised to stop migrations from going backwards.
@@ -555,10 +554,6 @@ module ActiveRecord
delegate :migrate, :announce, :write, :to => :migration
- def fingerprint
- @fingerprint ||= Digest::MD5.hexdigest(File.read(filename))
- end
-
private
def migration
@@ -670,7 +665,7 @@ module ActiveRecord
files = Dir[*paths.map { |p| "#{p}/**/[0-9]*_*.rb" }]
migrations = files.map do |file|
- version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?.rb/).first
+ version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/).first
raise IllegalMigrationNameError.new(file) unless version
version = version.to_i
@@ -729,7 +724,7 @@ module ActiveRecord
raise UnknownMigrationVersionError.new(@target_version) if target.nil?
unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
target.migrate(@direction)
- record_version_state_after_migrating(target)
+ record_version_state_after_migrating(target.version)
end
end
@@ -752,7 +747,7 @@ module ActiveRecord
begin
ddl_transaction do
migration.migrate(@direction)
- record_version_state_after_migrating(migration)
+ record_version_state_after_migrating(migration.version)
end
rescue => e
canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : ""
@@ -810,18 +805,13 @@ module ActiveRecord
raise DuplicateMigrationVersionError.new(version) if version
end
- def record_version_state_after_migrating(target)
+ def record_version_state_after_migrating(version)
if down?
- migrated.delete(target.version)
- ActiveRecord::SchemaMigration.where(:version => target.version.to_s).delete_all
+ migrated.delete(version)
+ ActiveRecord::SchemaMigration.where(:version => version.to_s).delete_all
else
- migrated << target.version
- ActiveRecord::SchemaMigration.create!(
- :version => target.version.to_s,
- :migrated_at => Time.now,
- :fingerprint => target.fingerprint,
- :name => File.basename(target.filename,'.rb').gsub(/^\d+_/,'')
- )
+ migrated << version
+ ActiveRecord::SchemaMigration.create!(:version => version.to_s)
end
end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 628ab0f566..85fb4be992 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -224,11 +224,10 @@ module ActiveRecord
def decorate_columns(columns_hash) # :nodoc:
return if columns_hash.empty?
- serialized_attributes.each_key do |key|
- columns_hash[key] = AttributeMethods::Serialization::Type.new(columns_hash[key])
- end
-
columns_hash.each do |name, col|
+ if serialized_attributes.key?(name)
+ columns_hash[name] = AttributeMethods::Serialization::Type.new(col)
+ end
if create_time_zone_conversion_attribute?(name, col)
columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(col)
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 94c109e72b..4d1a9c94b7 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -259,7 +259,7 @@ module ActiveRecord
verify_readonly_attribute(key.to_s)
end
- updated_count = self.class.where(self.class.primary_key => id).update_all(attributes)
+ updated_count = self.class.unscoped.where(self.class.primary_key => id).update_all(attributes)
attributes.each do |k, v|
raw_write_attribute(k, v)
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 0a9caa25b2..b25c0270c2 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -167,7 +167,7 @@ db_namespace = namespace :db do
# desc "Raises an error if there are pending migrations"
task :abort_if_pending_migrations => [:environment, :load_config] do
- pending_migrations = ActiveRecord::Migrator.new(:up, ActiveRecord::Migrator.migrations_paths).pending_migrations
+ pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Migrator.migrations_paths).pending_migrations
if pending_migrations.any?
puts "You have #{pending_migrations.size} pending migrations:"
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 2184625e22..431d083f21 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -1,5 +1,6 @@
require 'active_support/concern'
-require 'mutex_m'
+require 'thread'
+require 'thread_safe'
module ActiveRecord
module Delegation # :nodoc:
@@ -73,8 +74,7 @@ module ActiveRecord
end
module ClassMethods
- # This hash is keyed by klass.name to avoid memory leaks in development mode
- @@subclasses = Hash.new { |h, k| h[k] = {} }.extend(Mutex_m)
+ @@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2)
def new(klass, *args)
relation = relation_class_for(klass).allocate
@@ -82,33 +82,27 @@ module ActiveRecord
relation
end
+ # This doesn't have to be thread-safe. relation_class_for guarantees that this will only be
+ # called exactly once for a given const name.
+ def const_missing(name)
+ const_set(name, Class.new(self) { include ClassSpecificRelation })
+ end
+
+ private
# Cache the constants in @@subclasses because looking them up via const_get
# make instantiation significantly slower.
def relation_class_for(klass)
- if klass && klass.name
- if subclass = @@subclasses.synchronize { @@subclasses[self][klass.name] }
- subclass
- else
- subclass = const_get("#{name.gsub('::', '_')}_#{klass.name.gsub('::', '_')}", false)
- @@subclasses.synchronize { @@subclasses[self][klass.name] = subclass }
- subclass
+ if klass && (klass_name = klass.name)
+ my_cache = @@subclasses.compute_if_absent(self) { ThreadSafe::Cache.new }
+ # This hash is keyed by klass.name to avoid memory leaks in development mode
+ my_cache.compute_if_absent(klass_name) do
+ # Cache#compute_if_absent guarantees that the block will only executed once for the given klass_name
+ const_get("#{name.gsub('::', '_')}_#{klass_name.gsub('::', '_')}", false)
end
else
ActiveRecord::Relation
end
end
-
- # Check const_defined? in case another thread has already defined the constant.
- # I am not sure whether this is strictly necessary.
- def const_missing(name)
- @@subclasses.synchronize {
- if const_defined?(name)
- const_get(name)
- else
- const_set(name, Class.new(self) { include ClassSpecificRelation })
- end
- }
- end
end
def respond_to?(method, include_private = false)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index a480ddec9e..46c0d6206f 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -4,6 +4,51 @@ module ActiveRecord
module QueryMethods
extend ActiveSupport::Concern
+ # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
+ # In this case, #where must be chained with either #not, #like, or #not_like to return a new relation.
+ class WhereChain
+ def initialize(scope)
+ @scope = scope
+ end
+
+ # Returns a new relation expressing WHERE + NOT condition
+ # according to the conditions in the arguments.
+ #
+ # #not accepts conditions in one of these formats: String, Array, Hash.
+ # See #where for more details on each format.
+ #
+ # User.where.not("name = 'Jon'")
+ # # SELECT * FROM users WHERE NOT (name = 'Jon')
+ #
+ # User.where.not(["name = ?", "Jon"])
+ # # SELECT * FROM users WHERE NOT (name = 'Jon')
+ #
+ # User.where.not(name: "Jon")
+ # # SELECT * FROM users WHERE name != 'Jon'
+ #
+ # User.where.not(name: nil)
+ # # SELECT * FROM users WHERE name IS NOT NULL
+ #
+ # User.where.not(name: %w(Ko1 Nobu))
+ # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
+ def not(opts, *rest)
+ where_value = @scope.send(:build_where, opts, rest).map do |rel|
+ case rel
+ when Arel::Nodes::In
+ Arel::Nodes::NotIn.new(rel.left, rel.right)
+ when Arel::Nodes::Equality
+ Arel::Nodes::NotEqual.new(rel.left, rel.right)
+ when String
+ Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel))
+ else
+ Arel::Nodes::Not.new(rel)
+ end
+ end
+ @scope.where_values += where_value
+ @scope
+ end
+ end
+
Relation::MULTI_VALUE_METHODS.each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}_values # def select_values
@@ -370,18 +415,41 @@ module ActiveRecord
# User.joins(:posts).where({ "posts.published" => true })
# User.joins(:posts).where({ posts: { published: true } })
#
- # === empty condition
+ # === no argument
+ #
+ # If no argument is passed, #where returns a new instance of WhereChain, that
+ # can be chained with #not to return a new relation that negates the where clause.
+ #
+ # User.where.not(name: "Jon")
+ # # SELECT * FROM users WHERE name != 'Jon'
+ #
+ # See WhereChain for more details on #not.
#
- # If the condition returns true for blank?, then where is a no-op and returns the current relation.
- def where(opts, *rest)
- opts.blank? ? self : spawn.where!(opts, *rest)
+ # === blank condition
+ #
+ # If the condition is any blank-ish object, then #where is a no-op and returns
+ # the current relation.
+ def where(opts = :chain, *rest)
+ if opts == :chain
+ WhereChain.new(spawn)
+ elsif opts.blank?
+ self
+ else
+ spawn.where!(opts, *rest)
+ end
end
- def where!(opts, *rest) # :nodoc:
- references!(PredicateBuilder.references(opts)) if Hash === opts
+ # #where! is identical to #where, except that instead of returning a new relation, it adds
+ # the condition to the existing relation.
+ def where!(opts = :chain, *rest) # :nodoc:
+ if opts == :chain
+ WhereChain.new(self)
+ else
+ references!(PredicateBuilder.references(opts)) if Hash === opts
- self.where_values += build_where(opts, rest)
- self
+ self.where_values += build_where(opts, rest)
+ self
+ end
end
# Allows to specify a HAVING clause. Note that you can't use HAVING
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 44b7eb424b..3259dbbd80 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -39,45 +39,27 @@ module ActiveRecord
end
def define(info, &block) # :nodoc:
- @using_deprecated_version_setting = info[:version].present?
- SchemaMigration.drop_table
- initialize_schema_migrations_table
-
instance_eval(&block)
- # handle files from pre-4.0 that used :version option instead of dumping migration table
- assume_migrated_upto_version(info[:version], migrations_paths) if @using_deprecated_version_setting
+ unless info[:version].blank?
+ initialize_schema_migrations_table
+ assume_migrated_upto_version(info[:version], migrations_paths)
+ end
end
# Eval the given block. All methods available to the current connection
# adapter are available within the block, so you can easily use the
# database definition DSL to build up your schema (+create_table+,
# +add_index+, etc.).
- def self.define(info={}, &block)
- new.define(info, &block)
- end
-
- # Create schema migration history. Include migration statements in a block to this method.
- #
- # migrations do
- # migration 20121128235959, "44f1397e3b92442ca7488a029068a5ad", "add_horn_color_to_unicorns"
- # migration 20121129235959, "4a1eb3965d94406b00002b370854eae8", "add_magic_power_to_unicorns"
- # end
- def migrations
- raise(ArgumentError, "Can't set migrations while using :version option") if @using_deprecated_version_setting
- yield
- end
-
- # Add a migration to the ActiveRecord::SchemaMigration table.
#
- # The +version+ argument is an integer.
- # The +fingerprint+ and +name+ arguments are required but may be empty strings.
- # The migration's +migrated_at+ attribute is set to the current time,
- # instead of being set explicitly as an argument to the method.
+ # The +info+ hash is optional, and if given is used to define metadata
+ # about the current schema (currently, only the schema's version):
#
- # migration 20121129235959, "4a1eb3965d94406b00002b370854eae8", "add_magic_power_to_unicorns"
- def migration(version, fingerprint, name)
- SchemaMigration.create!(version: version, migrated_at: Time.now, fingerprint: fingerprint, name: name)
+ # ActiveRecord::Schema.define(version: 20380119000001) do
+ # ...
+ # end
+ def self.define(info={}, &block)
+ new.define(info, &block)
end
end
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 73c0f5b9eb..36bde44e7c 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -24,7 +24,6 @@ module ActiveRecord
def dump(stream)
header(stream)
- migrations(stream)
tables(stream)
trailer(stream)
stream
@@ -45,7 +44,7 @@ module ActiveRecord
stream.puts "# encoding: #{stream.external_encoding.name}"
end
- header_text = <<HEADER_RUBY
+ stream.puts <<HEADER
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
@@ -60,25 +59,13 @@ module ActiveRecord
ActiveRecord::Schema.define(#{define_params}) do
-HEADER_RUBY
- stream.puts header_text
+HEADER
end
def trailer(stream)
stream.puts "end"
end
- def migrations(stream)
- all_migrations = ActiveRecord::SchemaMigration.all.to_a
- if all_migrations.any?
- stream.puts(" migrations do")
- all_migrations.each do |migration|
- stream.puts(migration.schema_line(" "))
- end
- stream.puts(" end")
- end
- end
-
def tables(stream)
@connection.tables.sort.each do |tbl|
next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index b2ae369eb6..9830abe7d8 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -14,38 +14,17 @@ module ActiveRecord
end
def self.create_table
- if connection.table_exists?(table_name)
- cols = connection.columns(table_name).collect { |col| col.name }
- unless cols.include?("migrated_at")
- connection.add_column(table_name, "migrated_at", :datetime)
- q_table_name = connection.quote_table_name(table_name)
- q_timestamp = connection.quoted_date(Time.now)
- connection.update("UPDATE #{q_table_name} SET migrated_at = '#{q_timestamp}' WHERE migrated_at IS NULL")
- connection.change_column(table_name, "migrated_at", :datetime, :null => false)
- end
- unless cols.include?("fingerprint")
- connection.add_column(table_name, "fingerprint", :string, :limit => 32)
- end
- unless cols.include?("name")
- connection.add_column(table_name, "name", :string)
- end
- else
+ unless connection.table_exists?(table_name)
connection.create_table(table_name, :id => false) do |t|
t.column :version, :string, :null => false
- t.column :migrated_at, :datetime, :null => false
- t.column :fingerprint, :string, :limit => 32
- t.column :name, :string
end
- connection.add_index(table_name, "version", :unique => true, :name => index_name)
+ connection.add_index table_name, :version, :unique => true, :name => index_name
end
- reset_column_information
end
def self.drop_table
- if connection.index_exists?(table_name, "version", :unique => true, :name => index_name)
- connection.remove_index(table_name, :name => index_name)
- end
if connection.table_exists?(table_name)
+ connection.remove_index table_name, :name => index_name
connection.drop_table(table_name)
end
end
@@ -53,17 +32,5 @@ module ActiveRecord
def version
super.to_i
end
-
- # Construct ruby source to include in schema.rb dump for this migration.
- # Pass a string of spaces as +indent+ to allow calling code to control how deeply indented the line is.
- # The generated line includes the migration version, fingerprint, and name. Either fingerprint or name
- # can be an empty string.
- #
- # Example output:
- #
- # migration 20121129235959, "ee4be703f9e6e2fc0f4baddebe6eb8f7", "add_magic_power_to_unicorns"
- def schema_line(indent)
- %Q(#{indent}migration %s, "%s", "%s") % [version, fingerprint, name]
- end
end
end