aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb6
-rw-r--r--activerecord/lib/active_record/autosave_association.rb2
-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.rb6
-rw-r--r--activerecord/lib/active_record/counter_cache.rb18
-rw-r--r--activerecord/lib/active_record/integration.rb2
-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/query_cache.rb2
-rw-r--r--activerecord/lib/active_record/querying.rb7
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb9
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb6
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb90
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb10
-rw-r--r--activerecord/lib/active_record/schema.rb47
-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
19 files changed, 226 insertions, 93 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/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 907fe70522..704998301c 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -32,8 +32,6 @@ module ActiveRecord
# autosave callbacks are executed. Placing your callbacks after
# associations is usually a good practice.
#
- # == Examples
- #
# === One-to-one Example
#
# class Post
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 7c561b6f82..18bf14d1fb 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -267,7 +267,6 @@ module ActiveRecord
FROM pg_class seq,
pg_attribute attr,
pg_depend dep,
- pg_namespace name,
pg_constraint cons
WHERE seq.oid = dep.objid
AND seq.relkind = 'S'
@@ -306,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/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index c53b7b3e78..81f92db271 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -79,16 +79,17 @@ module ActiveRecord
where(primary_key => id).update_all updates.join(', ')
end
- # Increment a number field by one, usually representing a count.
+ # Increment a numeric field by one, via a direct SQL update.
#
- # This is used for caching aggregate values, so that they don't need to be computed every time.
- # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
- # shown it would have to run an SQL query to find how many posts and comments there are.
+ # This method is used primarily for maintaining counter_cache columns used to
+ # store aggregate values. For example, a DiscussionBoard may cache posts_count
+ # and comments_count to avoid running an SQL query to calculate the number of
+ # posts and comments there are each time it is displayed.
#
# ==== Parameters
#
# * +counter_name+ - The name of the field that should be incremented.
- # * +id+ - The id of the object that should be incremented.
+ # * +id+ - The id of the object that should be incremented or an Array of ids.
#
# ==== Examples
#
@@ -98,14 +99,15 @@ module ActiveRecord
update_counters(id, counter_name => 1)
end
- # Decrement a number field by one, usually representing a count.
+ # Decrement a numeric field by one, via a direct SQL update.
#
- # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
+ # This works the same as increment_counter but reduces the column value by
+ # 1 instead of increasing it.
#
# ==== Parameters
#
# * +counter_name+ - The name of the field that should be decremented.
- # * +id+ - The id of the object that should be decremented.
+ # * +id+ - The id of the object that should be decremented or an Array of ids.
#
# ==== Examples
#
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 23c272ef12..7bdc1bd4c6 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -29,8 +29,6 @@ module ActiveRecord
# Returns a cache key that can be used to identify this record.
#
- # ==== Examples
- #
# Product.new.cache_key # => "products/new"
# Product.find(5).cache_key # => "products/5" (updated_at not available)
# Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
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/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 38e18b32a4..df8654e5c1 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -4,6 +4,7 @@ module ActiveRecord
class QueryCache
module ClassMethods
# Enable the query cache within the block if Active Record is configured.
+ # If it's not, it will execute the given block.
def cache(&block)
if ActiveRecord::Base.connected?
connection.cache(&block)
@@ -13,6 +14,7 @@ module ActiveRecord
end
# Disable the query cache within the block if Active Record is configured.
+ # If it's not, it will execute the given block.
def uncached(&block)
if ActiveRecord::Base.connected?
connection.uncached(&block)
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 45f6a78428..5ddcaee6be 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -26,14 +26,13 @@ module ActiveRecord
# MySQL specific terms will lock you to using that particular database engine or require you to
# change your call if you switch engines.
#
- # ==== Examples
# # A simple SQL query spanning multiple tables
# Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
- # > [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
+ # # => [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
#
# # You can use the same string replacement techniques as you can with ActiveRecord#find
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
- # > [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
+ # # => [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
def find_by_sql(sql, binds = [])
logging_query_plan do
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
@@ -57,8 +56,6 @@ module ActiveRecord
#
# * +sql+ - An SQL statement which should return a count query from the database, see the example below.
#
- # ==== Examples
- #
# Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
def count_by_sql(sql)
logging_query_plan do
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 99e77e007a..ccc14dddeb 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -76,18 +76,17 @@ module ActiveRecord
#
# values = Person.group('last_name').maximum(:age)
# puts values["Drake"]
- # => 43
+ # # => 43
#
# drake = Family.find_by_last_name('Drake')
# values = Person.group(:family).maximum(:age) # Person belongs_to :family
# puts values[drake]
- # => 43
+ # # => 43
#
# values.each do |family, max_age|
# ...
# end
#
- # Examples:
# Person.calculate(:count, :all) # The same as Person.count
# Person.average(:age) # SELECT AVG(age) FROM people...
#
@@ -124,8 +123,6 @@ module ActiveRecord
# the plucked column names, if they can be deduced. Plucking an SQL fragment
# returns String values by default.
#
- # Examples:
- #
# Person.pluck(:id)
# # SELECT people.id FROM people
# # => [1, 2, 3]
@@ -182,8 +179,6 @@ module ActiveRecord
# Pluck all the ID's for the relation using the table's primary key
#
- # Examples:
- #
# Person.ids # SELECT people.id FROM people
# Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
def ids
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index eafe4a54c4..7ddaea1bb0 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -225,9 +225,11 @@ module ActiveRecord
orders = relation.order_values.map { |val| val.presence }.compact
values = @klass.connection.distinct("#{quoted_table_name}.#{primary_key}", orders)
- relation = relation.dup
+ relation = relation.dup.select(values)
+
+ id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
+ ids_array = id_rows.map {|row| row[primary_key]}
- ids_array = relation.select(values).collect {|row| row[primary_key]}
ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index a480ddec9e..f6d106f304 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 name <> 'Jon'
+ #
+ # User.where.not(["name = ?", "Jon"])
+ # # SELECT * FROM users WHERE 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,47 @@ module ActiveRecord
# User.joins(:posts).where({ "posts.published" => true })
# User.joins(:posts).where({ posts: { published: true } })
#
- # === empty condition
+ # === no argument or :chain
+ #
+ # If no argument or :chain is passed, #where returns a new instance of WhereChain which, when
+ # chained with either #not, #like, or #not_like, returns a new relation.
+ #
+ # User.where.not(name: "Jon")
+ # # SELECT * FROM users WHERE name <> 'Jon'
+ #
+ # Book.where.like(title: "Rails%")
+ # # SELECT * FROM books WHERE title LIKE 'Rails%'
+ #
+ # Conference.where.not_like(name: "%Kaigi")
+ # # SELECT * FROM conferences WHERE name NOT LIKE '%Kaigi'
#
- # 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)
+ # See WhereChain for more details on #not, #like, and #not_like.
+ #
+ # === 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/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 352dee3826..d417e82548 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -12,9 +12,6 @@ module ActiveRecord
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an <tt>ActiveRecord::Relation</tt>.
# Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
- #
- # ==== Examples
- #
# Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
# # Performs a single join query with both where conditions.
#
@@ -29,7 +26,6 @@ module ActiveRecord
# # => Post.where(published: true).joins(:comments)
#
# This is mainly intended for sharing common conditions between multiple associations.
- #
def merge(other)
if other.is_a?(Array)
to_a & other
@@ -51,11 +47,8 @@ module ActiveRecord
# Removes from the query the condition(s) specified in +skips+.
#
- # Example:
- #
# Post.order('id asc').except(:order) # discards the order condition
# Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
- #
def except(*skips)
result = Relation.new(klass, table, values.except(*skips))
result.default_scoped = default_scoped
@@ -65,11 +58,8 @@ module ActiveRecord
# Removes any condition from the query other than the one(s) specified in +onlies+.
#
- # Example:
- #
# Post.order('id asc').only(:where) # discards the order condition
# Post.order('id asc').only(:where, :order) # uses the specified order
- #
def only(*onlies)
result = Relation.new(klass, table, values.slice(*onlies))
result.default_scoped = default_scoped
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index eaa4aa7086..44b7eb424b 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -29,32 +29,55 @@ module ActiveRecord
# ActiveRecord::Schema is only supported by database adapters that also
# support migrations, the two features being very similar.
class Schema < Migration
+
+ # Returns the migrations paths.
+ #
+ # ActiveRecord::Schema.new.migrations_paths
+ # # => ["db/migrate"] # Rails migration path by default.
def migrations_paths
ActiveRecord::Migrator.migrations_paths
end
- def define(info, &block)
+ 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