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/collection_proxy.rb6
-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/postgresql/schema_statements.rb5
-rw-r--r--activerecord/lib/active_record/migration.rb24
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-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.rb21
-rw-r--r--activerecord/lib/active_record/schema_migration.rb39
-rw-r--r--activerecord/lib/active_record/scoping/named.rb4
11 files changed, 194 insertions, 55 deletions
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 7f9628499c..7c43e37cf2 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -101,7 +101,7 @@ module ActiveRecord
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
#
- # person.pets.select(:name) { |pet| pet.name =~ /oo/ }
+ # person.pets.select(:name) { |pet| pet.name =~ /oo/ }
# # => [
# # #<Pet id: 2, name: "Spook">,
# # #<Pet id: 3, name: "Choo-Choo">
@@ -824,7 +824,7 @@ module ActiveRecord
#
# person.pets # => [#<Pet id: 20, name: "Snoop">]
#
- # person.pets.include?(Pet.find(20)) # => true
+ # person.pets.include?(Pet.find(20)) # => true
# person.pets.include?(Pet.find(21)) # => false
def include?(record)
@association.include?(record)
@@ -971,7 +971,7 @@ module ActiveRecord
# person.pets.reload # fetches pets from the database
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
#
- # person.pets(true)  # fetches pets from the database
+ # person.pets(true) # fetches pets from the database
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
def reload
proxy_association.reload
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 f1e42dfbbe..1586e84a25 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) VALUES ('#{sm.version}');"
- }.join "\n\n"
+ "INSERT INTO #{sm_table} (version, migrated_at, fingerprint, name) VALUES ('#{sm.version}',CURRENT_TIMESTAMP,'#{sm.fingerprint}','#{sm.name}');"
+ }.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)
- execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
+ ActiveRecord::SchemaMigration.create!(:version => version, :migrated_at => Time.now)
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
- execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
+ ActiveRecord::SchemaMigration.create!(:version => v, :migrated_at => Time.now)
inserted << v
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 9f7086cd07..18bf14d1fb 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -305,12 +305,11 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table)
row = exec_query(<<-end_sql, 'SCHEMA').rows.first
- SELECT DISTINCT(attr.attname)
+ SELECT attr.attname
FROM pg_attribute attr
- INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
WHERE cons.contype = 'p'
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
+ AND cons.conrelid = '#{quote_table_name(table)}'::regclass
end_sql
row && row.first
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 22347fcaef..4ce276d4bf 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,5 +1,6 @@
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.
@@ -554,6 +555,10 @@ module ActiveRecord
delegate :migrate, :announce, :write, :to => :migration
+ def fingerprint
+ @fingerprint ||= Digest::MD5.hexdigest(File.read(filename))
+ end
+
private
def migration
@@ -724,7 +729,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.version)
+ record_version_state_after_migrating(target)
end
end
@@ -747,7 +752,7 @@ module ActiveRecord
begin
ddl_transaction do
migration.migrate(@direction)
- record_version_state_after_migrating(migration.version)
+ record_version_state_after_migrating(migration)
end
rescue => e
canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : ""
@@ -805,13 +810,18 @@ module ActiveRecord
raise DuplicateMigrationVersionError.new(version) if version
end
- def record_version_state_after_migrating(version)
+ def record_version_state_after_migrating(target)
if down?
- migrated.delete(version)
- ActiveRecord::SchemaMigration.where(:version => version.to_s).delete_all
+ migrated.delete(target.version)
+ ActiveRecord::SchemaMigration.where(:version => target.version.to_s).delete_all
else
- migrated << version
- ActiveRecord::SchemaMigration.create!(:version => version.to_s)
+ 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+_/,'')
+ )
end
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/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 3259dbbd80..44b7eb424b 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -39,27 +39,45 @@ 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)
- unless info[:version].blank?
- initialize_schema_migrations_table
- assume_migrated_upto_version(info[:version], migrations_paths)
- end
+ # 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
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.).
- #
- # The +info+ hash is optional, and if given is used to define metadata
- # about the current schema (currently, only the schema's version):
- #
- # ActiveRecord::Schema.define(version: 20380119000001) do
- # ...
- # end
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.
+ #
+ # 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)
+ end
end
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 36bde44e7c..194a77ca16 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -24,6 +24,7 @@ module ActiveRecord
def dump(stream)
header(stream)
+ migrations(stream)
tables(stream)
trailer(stream)
stream
@@ -38,13 +39,11 @@ module ActiveRecord
end
def header(stream)
- define_params = @version ? "version: #{@version}" : ""
-
if stream.respond_to?(:external_encoding) && stream.external_encoding
stream.puts "# encoding: #{stream.external_encoding.name}"
end
- stream.puts <<HEADER
+ header_text = <<HEADER_RUBY
# 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.
@@ -57,15 +56,27 @@ module ActiveRecord
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(#{define_params}) do
+ActiveRecord::Schema.define do
-HEADER
+HEADER_RUBY
+ stream.puts header_text
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\n\n")
+ 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 9830abe7d8..6c3cd5b6ba 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -14,17 +14,38 @@ module ActiveRecord
end
def self.create_table
- unless connection.table_exists?(table_name)
+ 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
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.table_exists?(table_name)
- connection.remove_index table_name, :name => index_name
+ if connection.index_exists?(table_name, "version", :unique => true, :name => index_name)
+ connection.remove_index(table_name, :name => index_name)
+ end
connection.drop_table(table_name)
end
end
@@ -32,5 +53,17 @@ 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
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index fb5f5b5be0..8b7eda6eee 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -116,7 +116,7 @@ module ActiveRecord
# Scopes can also be used while creating/building a record.
#
# class Article < ActiveRecord::Base
- # scope :published, -> { where(published: true) }
+ # scope :published, -> { where(published: true) }
# end
#
# Article.published.new.published # => true
@@ -126,7 +126,7 @@ module ActiveRecord
# on scopes. Assuming the following setup:
#
# class Article < ActiveRecord::Base
- # scope :published, -> { where(published: true) }
+ # scope :published, -> { where(published: true) }
# scope :featured, -> { where(featured: true) }
#
# def self.latest_article