diff options
Diffstat (limited to 'activerecord/lib/active_record')
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 |