diff options
Diffstat (limited to 'activerecord/lib')
44 files changed, 619 insertions, 291 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 35e4eb19a4..0c670bdaa1 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -241,6 +241,7 @@ module ActiveRecord # others.destroy_all | X | X | X # others.find(*args) | X | X | X # others.exists? | X | X | X + # others.distinct | X | X | X # others.uniq | X | X | X # others.reset | X | X | X # @@ -1024,7 +1025,7 @@ module ActiveRecord # [collection<<(object, ...)] # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. # Note that this operation instantly fires update sql without waiting for the save or update call on the - # parent object. + # parent object, unless the parent object is a new record. # [collection.delete(object, ...)] # Removes one or more objects from the collection by setting their foreign keys to +NULL+. # Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>, @@ -1231,7 +1232,7 @@ module ActiveRecord # its owner is destroyed: # # * <tt>:destroy</tt> causes the associated object to also be destroyed - # * <tt>:delete</tt> causes the asssociated object to be deleted directly from the database (so callbacks will not execute) + # * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute) # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed. # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object @@ -1407,6 +1408,8 @@ module ActiveRecord # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the # custom <tt>:join_table</tt> option if you need to. + # If your tables share a common prefix, it will only appear once at the beginning. For example, + # the tables "catalog_categories" and "catalog_products" generate a join table name of "catalog_categories_products". # # The join table should not have a primary key or a model associated with it. You must manually generate the # join table with a migration such as this: @@ -1433,7 +1436,7 @@ module ActiveRecord # Adds one or more objects to the collection by creating associations in the join table # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method). # Note that this operation instantly fires update sql without waiting for the save or update call on the - # parent object. + # parent object, unless the parent object is a new record. # [collection.delete(object, ...)] # Removes one or more objects from the collection by removing their associations from the join table. # This does not destroy the objects. diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index c5fb1fe2c7..a9525436fb 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -22,7 +22,7 @@ module ActiveRecord private def column_for(table_name, column_name) - columns = alias_tracker.connection.schema_cache.columns_hash[table_name] + columns = alias_tracker.connection.schema_cache.columns_hash(table_name) columns[column_name] end diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 97b1ff18e2..fbcb21118d 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -21,11 +21,13 @@ module ActiveRecord::Associations::Builder def add_counter_cache_callbacks(reflection) cache_column = reflection.counter_cache_column + foreign_key = reflection.foreign_key mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def belongs_to_counter_cache_after_create_for_#{name} record = #{name} record.class.increment_counter(:#{cache_column}, record.id) unless record.nil? + @_after_create_counter_called = true end def belongs_to_counter_cache_before_destroy_for_#{name} @@ -34,10 +36,28 @@ module ActiveRecord::Associations::Builder record.class.decrement_counter(:#{cache_column}, record.id) unless record.nil? end end + + def belongs_to_counter_cache_after_update_for_#{name} + if (@_after_create_counter_called ||= false) + @_after_create_counter_called = false + elsif self.#{foreign_key}_changed? && !new_record? && defined?(#{name.to_s.camelize}) + model = #{name.to_s.camelize} + foreign_key_was = self.#{foreign_key}_was + foreign_key = self.#{foreign_key} + + if foreign_key && model.respond_to?(:increment_counter) + model.increment_counter(:#{cache_column}, foreign_key) + end + if foreign_key_was && model.respond_to?(:decrement_counter) + model.decrement_counter(:#{cache_column}, foreign_key_was) + end + end + end CODE model.after_create "belongs_to_counter_cache_after_create_for_#{name}" model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}" + model.after_update "belongs_to_counter_cache_after_update_for_#{name}" klass = reflection.class_name.safe_constantize klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly) diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 5feb149946..906560bd44 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -34,7 +34,7 @@ module ActiveRecord reload end - CollectionProxy.new(klass, self) + @proxy ||= CollectionProxy.new(klass, self) end # Implements the writer method, e.g. foo.items= for Foo.has_many :items @@ -174,13 +174,14 @@ module ActiveRecord reflection.klass.count_by_sql(custom_counter_sql) else - if association_scope.uniq_value + relation = scope + if association_scope.distinct_value # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL. column_name ||= reflection.klass.primary_key - count_options[:distinct] = true + relation = relation.distinct end - value = scope.count(column_name, count_options) + value = relation.count(column_name) limit = options[:limit] offset = options[:offset] @@ -204,6 +205,15 @@ module ActiveRecord dependent = options[:dependent] if records.first == :all + + if dependent && dependent == :destroy + message = 'In Rails 4.1 delete_all on associations would not fire callbacks. ' \ + 'It means if the :dependent option is :destroy then the associated ' \ + 'records would be deleted without loading and invoking callbacks.' + + ActiveRecord::Base.logger ? ActiveRecord::Base.logger.warn(message) : $stderr.puts(message) + end + if loaded? || dependent == :destroy delete_or_destroy(load_target, dependent) else @@ -237,14 +247,14 @@ module ActiveRecord # +count_records+, which is a method descendants have to provide. def size if !find_target? || loaded? - if association_scope.uniq_value + if association_scope.distinct_value target.uniq.size else target.size end elsif !loaded? && !association_scope.group_values.empty? load_target.size - elsif !loaded? && !association_scope.uniq_value && target.is_a?(Array) + elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array) unsaved_records = target.select { |r| r.new_record? } unsaved_records.size + count_records else @@ -297,12 +307,13 @@ module ActiveRecord end end - def uniq + def distinct seen = {} load_target.find_all do |record| seen[record.id] = true unless seen.key?(record.id) end end + alias uniq distinct # Replace this collection with +other_array+. This will perform a diff # and delete/add only records that have changed. @@ -343,7 +354,7 @@ module ActiveRecord callback(:before_add, record) yield(record) if block_given? - if association_scope.uniq_value && index = @target.index(record) + if association_scope.distinct_value && index = @target.index(record) @target[index] = record else @target << record diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 543204abac..c2add32aa6 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -649,11 +649,12 @@ module ActiveRecord # # #<Pet name: "Fancy-Fancy"> # # ] # - # person.pets.select(:name).uniq + # person.pets.select(:name).distinct # # => [#<Pet name: "Fancy-Fancy">] - def uniq - @association.uniq + def distinct + @association.distinct end + alias uniq distinct # Count all records using SQL. # diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index 93618721bb..bb3e3db379 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -26,7 +26,7 @@ module ActiveRecord join_table[reflection.association_foreign_key] => record.id ) - owner.connection.insert stmt + owner.class.connection.insert stmt end record @@ -41,7 +41,7 @@ module ActiveRecord def delete_records(records, method) if sql = options[:delete_sql] records = load_target if records == :all - records.each { |record| owner.connection.delete(interpolate(sql, record)) } + records.each { |record| owner.class.connection.delete(interpolate(sql, record)) } else relation = join_table condition = relation[reflection.foreign_key].eq(owner.id) @@ -53,7 +53,7 @@ module ActiveRecord ) end - owner.connection.delete(relation.where(condition).compile_delete) + owner.class.connection.delete(relation.where(condition).compile_delete) end end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index f59565ae77..b7b4d7e3ae 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -23,9 +23,10 @@ module ActiveRecord if options[:dependent] == :destroy # No point in executing the counter update since we're going to destroy the parent anyway load_target.each(&:mark_for_destruction) + destroy_all + else + delete_all end - - delete_all end end diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb index 9a662d3f53..38bc7ce7da 100644 --- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb +++ b/activerecord/lib/active_record/associations/preloader/has_many_through.rb @@ -6,7 +6,7 @@ module ActiveRecord def associated_records_by_owner super.each do |owner, records| - records.uniq! if reflection_scope.uniq_value + records.uniq! if reflection_scope.distinct_value end end end diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 3e454b713a..931209b07b 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -90,7 +90,7 @@ module ActiveRecord base_name.foreign_key else if ActiveRecord::Base != self && table_exists? - connection.schema_cache.primary_keys[table_name] + connection.schema_cache.primary_keys(table_name) else 'id' 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 1754e424b8..9137504d15 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -238,7 +238,7 @@ module ActiveRecord @checkout_timeout = spec.config[:checkout_timeout] || 5 @dead_connection_timeout = spec.config[:dead_connection_timeout] || 5 - @reaper = Reaper.new self, spec.config[:reaping_frequency] + @reaper = Reaper.new(self, spec.config[:reaping_frequency] || 10) @reaper.run # default max pool size to 5 diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index c3d15ca929..c64b542286 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -125,7 +125,8 @@ module ActiveRecord # In order to get around this problem, #transaction will emulate the effect # of nested transactions, by using savepoints: # http://dev.mysql.com/doc/refman/5.0/en/savepoint.html - # Savepoints are supported by MySQL and PostgreSQL, but not SQLite3. + # Savepoints are supported by MySQL and PostgreSQL. SQLite3 version >= '3.6.8' + # supports savepoints. # # It is safe to call this method if a database transaction is already open, # i.e. if #transaction is called within another #transaction block. In case 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 42206de8fc..902dbd148e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -16,7 +16,6 @@ module ActiveRecord # +columns+ attribute of said TableDefinition object, in order to be used # for generating a number of table creation or table changing SQL statements. class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :precision, :scale, :default, :null) #:nodoc: - def string_to_binary(value) value end @@ -25,19 +24,24 @@ module ActiveRecord base.type_to_sql(type.to_sym, limit, precision, scale) end + def primary_key? + type.to_sym == :primary_key + end + def to_sql column_sql = "#{base.quote_column_name(name)} #{sql_type}" column_options = {} column_options[:null] = null unless null.nil? column_options[:default] = default unless default.nil? - add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key + column_options[:column] = self + add_column_options!(column_sql, column_options) unless primary_key? column_sql end private def add_column_options!(sql, options) - base.add_column_options!(sql, options.merge(:column => self)) + base.add_column_options!(sql, options) end end @@ -73,15 +77,6 @@ module ActiveRecord @base = base end - def xml(*args) - raise NotImplementedError unless %w{ - sqlite mysql mysql2 - }.include? @base.adapter_name.downcase - - options = args.extract_options! - column(args[0], :text, options) - end - # Appends a primary key definition to the table definition. # Can be called multiple times, but this is probably not a good idea. def primary_key(name) @@ -292,19 +287,23 @@ module ActiveRecord # concatenated together. This string can then be prepended and appended to # to generate the final SQL to create the table. def to_sql - @columns.map { |c| c.to_sql } * ', ' + columns.map { |c| c.to_sql } * ', ' end private + def create_column_definition(base, name, type) + ColumnDefinition.new base, name, type + end + def new_column_definition(base, name, type) - definition = ColumnDefinition.new base, name, type + definition = create_column_definition base, name, type @columns << definition @columns_hash[name] = definition definition end def primary_key_column_name - primary_key_column = columns.detect { |c| c.type == :primary_key } + primary_key_column = columns.detect { |c| c.primary_key? } primary_key_column && primary_key_column.name end 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 0cce8c7596..cd4409295f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -5,7 +5,7 @@ module ActiveRecord module SchemaStatements include ActiveRecord::Migration::JoinTable - # Returns a Hash of mappings from the abstract data types to the native + # Returns a hash of mappings from the abstract data types to the native # database types. See TableDefinition#column for details on the recognized # abstract data types. def native_database_types @@ -20,6 +20,7 @@ module ActiveRecord # Checks to see if the table +table_name+ exists on the database. # # table_exists?(:developers) + # def table_exists?(table_name) tables.include?(table_name.to_s) end @@ -29,17 +30,18 @@ module ActiveRecord # Checks to see if an index exists on a table for a given index definition. # - # # Check an index exists - # index_exists?(:suppliers, :company_id) + # # Check an index exists + # index_exists?(:suppliers, :company_id) + # + # # Check an index on multiple columns exists + # index_exists?(:suppliers, [:company_id, :company_type]) # - # # Check an index on multiple columns exists - # index_exists?(:suppliers, [:company_id, :company_type]) + # # Check a unique index exists + # index_exists?(:suppliers, :company_id, unique: true) # - # # Check a unique index exists - # index_exists?(:suppliers, :company_id, unique: true) + # # Check an index with a custom name exists + # index_exists?(:suppliers, :company_id, name: "idx_company_id" # - # # Check an index with a custom name exists - # index_exists?(:suppliers, :company_id, name: "idx_company_id" def index_exists?(table_name, column_name, options = {}) column_names = Array(column_name) index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names) @@ -56,17 +58,18 @@ module ActiveRecord # Checks to see if a column exists in a given table. # - # # Check a column exists - # column_exists?(:suppliers, :name) + # # Check a column exists + # column_exists?(:suppliers, :name) # - # # Check a column exists of a particular type - # column_exists?(:suppliers, :name, :string) + # # Check a column exists of a particular type + # column_exists?(:suppliers, :name, :string) + # + # # Check a column exists with a specific definition + # column_exists?(:suppliers, :name, :string, limit: 100) + # column_exists?(:suppliers, :name, :string, default: 'default') + # column_exists?(:suppliers, :name, :string, null: false) + # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2) # - # # Check a column exists with a specific definition - # column_exists?(:suppliers, :name, :string, limit: 100) - # column_exists?(:suppliers, :name, :string, default: 'default') - # column_exists?(:suppliers, :name, :string, null: false) - # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2) def column_exists?(table_name, column_name, type = nil, options = {}) columns(table_name).any?{ |c| c.name == column_name.to_s && (!type || c.type == type) && @@ -84,27 +87,30 @@ module ActiveRecord # form or the regular form, like this: # # === Block form - # # create_table() passes a TableDefinition object to the block. - # # This form will not only create the table, but also columns for the - # # table. # - # create_table(:suppliers) do |t| - # t.column :name, :string, limit: 60 - # # Other fields here - # end + # # create_table() passes a TableDefinition object to the block. + # # This form will not only create the table, but also columns for the + # # table. + # + # create_table(:suppliers) do |t| + # t.column :name, :string, limit: 60 + # # Other fields here + # end # # === Block form, with shorthand - # # You can also use the column types as method calls, rather than calling the column method. - # create_table(:suppliers) do |t| - # t.string :name, limit: 60 - # # Other fields here - # end + # + # # You can also use the column types as method calls, rather than calling the column method. + # create_table(:suppliers) do |t| + # t.string :name, limit: 60 + # # Other fields here + # end # # === Regular form - # # Creates a table called 'suppliers' with no columns. - # create_table(:suppliers) - # # Add a column to 'suppliers'. - # add_column(:suppliers, :name, :string, {limit: 60}) + # + # # Creates a table called 'suppliers' with no columns. + # create_table(:suppliers) + # # Add a column to 'suppliers'. + # add_column(:suppliers, :name, :string, {limit: 60}) # # The +options+ hash can include the following keys: # [<tt>:id</tt>] @@ -127,37 +133,53 @@ module ActiveRecord # Defaults to false. # # ====== Add a backend specific option to the generated SQL (MySQL) - # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8') + # + # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8') + # # generates: - # CREATE TABLE suppliers ( - # id int(11) DEFAULT NULL auto_increment PRIMARY KEY - # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 + # + # CREATE TABLE suppliers ( + # id int(11) DEFAULT NULL auto_increment PRIMARY KEY + # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 # # ====== Rename the primary key column - # create_table(:objects, primary_key: 'guid') do |t| - # t.column :name, :string, limit: 80 - # end + # + # create_table(:objects, primary_key: 'guid') do |t| + # t.column :name, :string, limit: 80 + # end + # # generates: - # CREATE TABLE objects ( - # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY, - # name varchar(80) - # ) + # + # CREATE TABLE objects ( + # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY, + # name varchar(80) + # ) # # ====== Do not add a primary key column - # create_table(:categories_suppliers, id: false) do |t| - # t.column :category_id, :integer - # t.column :supplier_id, :integer - # end + # + # create_table(:categories_suppliers, id: false) do |t| + # t.column :category_id, :integer + # t.column :supplier_id, :integer + # end + # # generates: - # CREATE TABLE categories_suppliers ( - # category_id int, - # supplier_id int - # ) + # + # CREATE TABLE categories_suppliers ( + # category_id int, + # supplier_id int + # ) # # See also TableDefinition#column for details on how to create columns. def create_table(table_name, options = {}) td = create_table_definition - td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false + + unless options[:id] == false + pk = options.fetch(:primary_key) { + Base.get_primary_key table_name.to_s.singularize + } + + td.primary_key pk + end yield td if block_given? @@ -176,8 +198,8 @@ module ActiveRecord # Creates a new join table with the name created using the lexical order of the first two # arguments. These arguments can be a String or a Symbol. # - # # Creates a table called 'assemblies_parts' with no id. - # create_join_table(:assemblies, :parts) + # # Creates a table called 'assemblies_parts' with no id. + # create_join_table(:assemblies, :parts) # # You can pass a +options+ hash can include the following keys: # [<tt>:table_name</tt>] @@ -201,12 +223,16 @@ module ActiveRecord # end # # ====== Add a backend specific option to the generated SQL (MySQL) - # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8') + # + # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8') + # # generates: - # CREATE TABLE assemblies_parts ( - # assembly_id int NOT NULL, - # part_id int NOT NULL, - # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 + # + # CREATE TABLE assemblies_parts ( + # assembly_id int NOT NULL, + # part_id int NOT NULL, + # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 + # def create_join_table(table_1, table_2, options = {}) join_table_name = find_join_table_name(table_1, table_2, options) @@ -223,7 +249,7 @@ module ActiveRecord end # Drops the join table specified by the given arguments. - # See create_join_table for details. + # See +create_join_table+ for details. # # Although this command ignores the block if one is given, it can be helpful # to provide one in a migration's +change+ method so it can be reverted. @@ -235,66 +261,74 @@ module ActiveRecord # A block for changing columns in +table+. # - # # change_table() yields a Table instance - # change_table(:suppliers) do |t| - # t.column :name, :string, limit: 60 - # # Other column alterations here - # end + # # change_table() yields a Table instance + # change_table(:suppliers) do |t| + # t.column :name, :string, limit: 60 + # # Other column alterations here + # end # # The +options+ hash can include the following keys: # [<tt>:bulk</tt>] # Set this to true to make this a bulk alter query, such as - # ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ... + # + # ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ... # # Defaults to false. # # ====== Add a column - # change_table(:suppliers) do |t| - # t.column :name, :string, limit: 60 - # end + # + # change_table(:suppliers) do |t| + # t.column :name, :string, limit: 60 + # end # # ====== Add 2 integer columns - # change_table(:suppliers) do |t| - # t.integer :width, :height, null: false, default: 0 - # end + # + # change_table(:suppliers) do |t| + # t.integer :width, :height, null: false, default: 0 + # end # # ====== Add created_at/updated_at columns - # change_table(:suppliers) do |t| - # t.timestamps - # end + # + # change_table(:suppliers) do |t| + # t.timestamps + # end # # ====== Add a foreign key column - # change_table(:suppliers) do |t| - # t.references :company - # end # - # Creates a <tt>company_id(integer)</tt> column + # change_table(:suppliers) do |t| + # t.references :company + # end + # + # Creates a <tt>company_id(integer)</tt> column. # # ====== Add a polymorphic foreign key column + # # change_table(:suppliers) do |t| # t.belongs_to :company, polymorphic: true # end # - # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns + # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns. # # ====== Remove a column + # # change_table(:suppliers) do |t| # t.remove :company # end # # ====== Remove several columns + # # change_table(:suppliers) do |t| # t.remove :company_id # t.remove :width, :height # end # # ====== Remove an index + # # change_table(:suppliers) do |t| # t.remove_index :company_id # end # - # See also Table for details on - # all of the various column transformation + # See also Table for details on all of the various column transformation. def change_table(table_name, options = {}) if supports_bulk_alter? && options[:bulk] recorder = ActiveRecord::Migration::CommandRecorder.new(self) @@ -307,7 +341,8 @@ module ActiveRecord # Renames a table. # - # rename_table('octopuses', 'octopi') + # rename_table('octopuses', 'octopi') + # def rename_table(table_name, new_name) raise NotImplementedError, "rename_table is not implemented" end @@ -331,7 +366,8 @@ module ActiveRecord # Removes the given columns from the table definition. # - # remove_columns(:suppliers, :qualification, :experience) + # remove_columns(:suppliers, :qualification, :experience) + # def remove_columns(table_name, *column_names) raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty? column_names.each do |column_name| @@ -341,7 +377,7 @@ module ActiveRecord # Removes the column from the table definition. # - # remove_column(:suppliers, :qualification) + # remove_column(:suppliers, :qualification) # # The +type+ and +options+ parameters will be ignored if present. It can be helpful # to provide these in a migration's +change+ method so it can be reverted. @@ -353,24 +389,50 @@ module ActiveRecord # Changes the column's definition according to the new options. # See TableDefinition#column for details of the options you can use. # - # change_column(:suppliers, :name, :string, limit: 80) - # change_column(:accounts, :description, :text) + # change_column(:suppliers, :name, :string, limit: 80) + # change_column(:accounts, :description, :text) + # def change_column(table_name, column_name, type, options = {}) raise NotImplementedError, "change_column is not implemented" end - # Sets a new default value for a column. + # Sets a new default value for a column: + # + # change_column_default(:suppliers, :qualification, 'new') + # change_column_default(:accounts, :authorized, 1) + # + # Setting the default to +nil+ effectively drops the default: + # + # change_column_default(:users, :email, nil) # - # change_column_default(:suppliers, :qualification, 'new') - # change_column_default(:accounts, :authorized, 1) - # change_column_default(:users, :email, nil) def change_column_default(table_name, column_name, default) raise NotImplementedError, "change_column_default is not implemented" end + # Sets or removes a +NOT NULL+ constraint on a column. The +null+ flag + # indicates whether the value can be +NULL+. For example + # + # change_column_null(:users, :nickname, false) + # + # says nicknames cannot be +NULL+ (adds the constraint), whereas + # + # change_column_null(:users, :nickname, true) + # + # allows them to be +NULL+ (drops the constraint). + # + # The method accepts an optional fourth argument to replace existing + # +NULL+s with some other value. Use that one when enabling the + # constraint if needed, since otherwise those rows would not be valid. + # + # Please note the fourth argument does not set a column's default. + def change_column_null(table_name, column_name, null, default = nil) + raise NotImplementedError, "change_column_null is not implemented" + end + # Renames a column. # - # rename_column(:suppliers, :description, :name) + # rename_column(:suppliers, :description, :name) + # def rename_column(table_name, column_name, new_column_name) raise NotImplementedError, "rename_column is not implemented" end @@ -382,60 +444,87 @@ module ActiveRecord # you pass <tt>:name</tt> as an option. # # ====== Creating a simple index - # add_index(:suppliers, :name) + # + # add_index(:suppliers, :name) + # # generates - # CREATE INDEX suppliers_name_index ON suppliers(name) + # + # CREATE INDEX suppliers_name_index ON suppliers(name) # # ====== Creating a unique index - # add_index(:accounts, [:branch_id, :party_id], unique: true) + # + # add_index(:accounts, [:branch_id, :party_id], unique: true) + # # generates - # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id) + # + # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id) # # ====== Creating a named index - # add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party') + # + # add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party') + # # generates + # # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id) # # ====== Creating an index with specific key length - # add_index(:accounts, :name, name: 'by_name', length: 10) + # + # add_index(:accounts, :name, name: 'by_name', length: 10) + # # generates - # CREATE INDEX by_name ON accounts(name(10)) # - # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15}) + # CREATE INDEX by_name ON accounts(name(10)) + # + # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15}) + # # generates - # CREATE INDEX by_name_surname ON accounts(name(10), surname(15)) # - # Note: SQLite doesn't support index length + # CREATE INDEX by_name_surname ON accounts(name(10), surname(15)) + # + # Note: SQLite doesn't support index length. # # ====== Creating an index with a sort order (desc or asc, asc is the default) - # add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc}) + # + # add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc}) + # # generates - # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname) # - # Note: mysql doesn't yet support index order (it accepts the syntax but ignores it) + # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname) + # + # Note: MySQL doesn't yet support index order (it accepts the syntax but ignores it). # # ====== Creating a partial index - # add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active") + # + # add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active") + # # generates - # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active # - # Note: only supported by PostgreSQL + # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active # + # Note: only supported by PostgreSQL. def add_index(table_name, column_name, options = {}) index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options) execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}" end - # Remove the given index from the table. + # Removes the given index from the table. + # + # Removes the +index_accounts_on_column+ in the +accounts+ table. # - # Remove the index_accounts_on_column in the accounts table. # remove_index :accounts, :column - # Remove the index named index_accounts_on_branch_id in the accounts table. + # + # Removes the index named +index_accounts_on_branch_id+ in the +accounts+ table. + # # remove_index :accounts, column: :branch_id - # Remove the index named index_accounts_on_branch_id_and_party_id in the accounts table. + # + # Removes the index named +index_accounts_on_branch_id_and_party_id+ in the +accounts+ table. + # # remove_index :accounts, column: [:branch_id, :party_id] - # Remove the index named by_branch_party in the accounts table. + # + # Removes the index named +by_branch_party+ in the +accounts+ table. + # # remove_index :accounts, name: :by_branch_party + # def remove_index(table_name, options = {}) remove_index!(table_name, index_name_for_remove(table_name, options)) end @@ -444,10 +533,12 @@ module ActiveRecord execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}" end - # Rename an index. + # Renames an index. + # + # Rename the +index_people_on_last_name+ index to +index_users_on_last_name+: # - # Rename the index_people_on_last_name index to index_users_on_last_name # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name' + # def rename_index(table_name, old_name, new_name) # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance) old_index_def = indexes(table_name).detect { |i| i.name == old_name } @@ -470,7 +561,7 @@ module ActiveRecord end end - # Verify the existence of an index with a given name. + # Verifies the existence of an index with a given name. # # The default argument is returned if the underlying implementation does not define the indexes method, # as there's no way to determine the correct answer in that case. @@ -484,13 +575,16 @@ module ActiveRecord # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable. # # ====== Create a user_id column - # add_reference(:products, :user) + # + # add_reference(:products, :user) # # ====== Create a supplier_id and supplier_type columns - # add_belongs_to(:products, :supplier, polymorphic: true) + # + # add_belongs_to(:products, :supplier, polymorphic: true) # # ====== Create a supplier_id, supplier_type columns and appropriate index - # add_reference(:products, :supplier, polymorphic: true, index: true) + # + # add_reference(:products, :supplier, polymorphic: true, index: true) # def add_reference(table_name, ref_name, options = {}) polymorphic = options.delete(:polymorphic) @@ -505,10 +599,12 @@ module ActiveRecord # <tt>remove_reference</tt>, <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable. # # ====== Remove the reference - # remove_reference(:products, :user, index: true) + # + # remove_reference(:products, :user, index: true) # # ====== Remove polymorphic reference - # remove_reference(:products, :supplier, polymorphic: true) + # + # remove_reference(:products, :supplier, polymorphic: true) # def remove_reference(table_name, ref_name, options = {}) remove_column(table_name, "#{ref_name}_id") @@ -516,11 +612,6 @@ module ActiveRecord end alias :remove_belongs_to :remove_reference - # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the - # entire structure of the database. - def structure_dump - end - def dump_schema_information #:nodoc: sm_table = ActiveRecord::Migrator.schema_migrations_table_name @@ -600,21 +691,24 @@ module ActiveRecord # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax. # # distinct("posts.id", "posts.created_at desc") + # def distinct(columns, order_by) "DISTINCT #{columns}" end - # Adds timestamps (created_at and updated_at) columns to the named table. + # Adds timestamps (+created_at+ and +updated_at+) columns to the named table. + # + # add_timestamps(:suppliers) # - # add_timestamps(:suppliers) def add_timestamps(table_name) add_column table_name, :created_at, :datetime add_column table_name, :updated_at, :datetime end - # Removes the timestamp columns (created_at and updated_at) from the table definition. + # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition. # # remove_timestamps(:suppliers) + # def remove_timestamps(table_name) remove_column table_name, :updated_at remove_column table_name, :created_at @@ -692,6 +786,14 @@ module ActiveRecord index_name = index_name(table_name, options) unless index_name_exists?(table_name, index_name, true) + if options.is_a?(Hash) && options.has_key?(:name) + options_without_column = options.dup + options_without_column.delete :column + index_name_without_column = index_name(table_name, options_without_column) + + return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false) + end + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist" end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index ff9de712bc..7949bcb5ce 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -118,6 +118,17 @@ module ActiveRecord @in_use = false end + def unprepared_visitor + self.class::BindSubstitution.new self + end + + def unprepared_statement + old, @visitor = @visitor, unprepared_visitor + yield + ensure + @visitor = old + end + # Returns the human-readable name of the adapter. Use mixed case - one # can always use downcase if needed. def adapter_name 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 9826b18053..f88f5742a8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -143,7 +143,7 @@ module ActiveRecord if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @visitor = Arel::Visitors::MySQL.new self else - @visitor = BindSubstitution.new self + @visitor = unprepared_visitor end end @@ -332,20 +332,6 @@ module ActiveRecord # SCHEMA STATEMENTS ======================================== - def structure_dump #:nodoc: - if supports_views? - sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'" - else - sql = "SHOW TABLES" - end - - select_all(sql, 'SCHEMA').map { |table| - table.delete('Table_type') - sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}" - exec_query(sql, 'SCHEMA').first['Create Table'] + ";\n\n" - }.join - end - # Drops the database specified on the +name+ attribute # and creates it again using the provided +options+. def recreate_database(name, options = {}) diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 20a5ca2baa..25b8aef617 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -38,6 +38,15 @@ module ActiveRecord configure_connection end + MAX_INDEX_LENGTH_FOR_UTF8MB4 = 191 + def initialize_schema_migrations_table + if @config[:encoding] == 'utf8mb4' + ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_UTF8MB4) + else + ActiveRecord::SchemaMigration.create_table + end + end + def supports_explain? true end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 47e2e3928f..43f991b362 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -51,9 +51,12 @@ module ActiveRecord super end when Numeric - return super unless column.sql_type == 'money' - # Not truly string input, so doesn't require (or allow) escape string syntax. - "'#{value}'" + if column.sql_type == 'money' || [:string, :text].include?(column.type) + # Not truly string input, so doesn't require (or allow) escape string syntax. + "'#{value}'" + else + super + end when String case column.sql_type when 'bytea' then "'#{escape_bytea(value)}'" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index c91e1b3fb9..dfa4c3967a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -338,13 +338,14 @@ module ActiveRecord self end + def xml(options = {}) + column(args[0], :text, options) + end + private - def new_column_definition(base, name, type) - definition = ColumnDefinition.new base, name, type - @columns << definition - @columns_hash[name] = definition - definition + def create_column_definition(base, name, type) + ColumnDefinition.new base, name, type end end @@ -489,7 +490,7 @@ module ActiveRecord if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @visitor = Arel::Visitors::PostgreSQL.new self else - @visitor = BindSubstitution.new self + @visitor = unprepared_visitor end @connection_parameters, @config = connection_parameters, config @@ -594,13 +595,13 @@ module ActiveRecord end def enable_extension(name) - exec_query("CREATE EXTENSION IF NOT EXISTS #{name}").tap { + exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap { reload_type_map } end def disable_extension(name) - exec_query("DROP EXTENSION IF EXISTS #{name} CASCADE").tap { + exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap { reload_type_map } end @@ -631,7 +632,13 @@ module ActiveRecord if options[:array] || options[:column].try(:array) sql << '[]' end - super + + column = options.fetch(:column) { return super } + if column.type == :uuid && options[:default] =~ /\(\)/ + sql << " DEFAULT #{options[:default]}" + else + super + end end # Set the authorized user for this session diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index 5839d1d3b4..1d7a22e831 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -1,7 +1,9 @@ +require 'active_support/deprecation/reporting' + module ActiveRecord module ConnectionAdapters class SchemaCache - attr_reader :primary_keys, :tables, :version + attr_reader :version attr_accessor :connection def initialize(conn) @@ -14,6 +16,15 @@ module ActiveRecord prepare_default_proc end + def primary_keys(table_name = nil) + if table_name + @primary_keys[table_name] + else + ActiveSupport::Deprecation.warn('call primary_keys with a table name!') + @primary_keys.dup + end + end + # A cached lookup for table existence. def table_exists?(name) return @tables[name] if @tables.key? name @@ -30,12 +41,22 @@ module ActiveRecord end end + def tables(name = nil) + if name + @tables[name] + else + ActiveSupport::Deprecation.warn('call tables with a name!') + @tables.dup + end + end + # Get the columns for a table def columns(table = nil) if table @columns[table] else - @columns + ActiveSupport::Deprecation.warn('call columns with a table name!') + @columns.dup end end @@ -45,7 +66,8 @@ module ActiveRecord if table @columns_hash[table] else - @columns_hash + ActiveSupport::Deprecation.warn('call columns_hash with a table name!') + @columns_hash.dup end end @@ -58,6 +80,12 @@ module ActiveRecord @version = nil end + def size + [@columns, @columns_hash, @primary_keys, @tables].map { |x| + x.size + }.inject :+ + end + # Clear out internal caches for table with +table_name+. def clear_table_cache!(table_name) @columns.delete table_name @@ -69,9 +97,9 @@ module ActiveRecord def marshal_dump # if we get current version during initialization, it happens stack over flow. @version = ActiveRecord::Migrator.current_version - [@version] + [:@columns, :@columns_hash, :@primary_keys, :@tables].map do |val| - self.instance_variable_get(val).inject({}) { |h, v| h[v[0]] = v[1]; h } - end + [@version] + [@columns, @columns_hash, @primary_keys, @tables].map { |val| + Hash[val] + } end def marshal_load(array) @@ -87,7 +115,7 @@ module ActiveRecord end @columns_hash.default_proc = Proc.new do |h, table_name| - h[table_name] = Hash[columns[table_name].map { |col| + h[table_name] = Hash[columns(table_name).map { |col| [col.name, col] }] end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 981c4c96a0..d3ffee3a8b 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -113,7 +113,7 @@ module ActiveRecord if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @visitor = Arel::Visitors::SQLite.new self else - @visitor = BindSubstitution.new self + @visitor = unprepared_visitor end end @@ -583,9 +583,17 @@ module ActiveRecord quoted_columns = columns.map { |col| quote_column_name(col) } * ',' quoted_to = quote_table_name(to) + + raw_column_mappings = Hash[columns(from).map { |c| [c.name, c] }] + exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row| sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES (" - sql << columns.map {|col| quote row[column_mappings[col]]} * ', ' + + column_values = columns.map do |col| + quote(row[column_mappings[col]], raw_column_mappings[col]) + end + + sql << column_values * ', ' sql << ')' exec_query sql end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 899fe7d7c7..aa56219755 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -69,6 +69,14 @@ module ActiveRecord mattr_accessor :timestamped_migrations, instance_writer: false self.timestamped_migrations = true + ## + # :singleton-method: + # Disable implicit join references. This feature was deprecated with Rails 4. + # If you don't make use of implicit references but still see deprecation warnings + # you can disable the feature entirely. This will be the default with Rails 4.1. + mattr_accessor :disable_implicit_join_references, instance_writer: false + self.disable_implicit_join_references = false + class_attribute :connection_handler, instance_writer: false self.connection_handler = ConnectionAdapters::ConnectionHandler.new end @@ -324,6 +332,7 @@ module ActiveRecord # also be used to "borrow" the connection to do database work that isn't # easily done without going straight to SQL. def connection + ActiveSupport::Deprecation.warn("#connection is deprecated in favour of accessing it via the class") self.class.connection end diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 81f92db271..81cca37939 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -11,7 +11,7 @@ module ActiveRecord # ==== Parameters # # * +id+ - The id of the object you wish to reset a counter on. - # * +counters+ - One or more counter names to reset + # * +counters+ - One or more association counters to reset # # ==== Examples # @@ -21,6 +21,7 @@ module ActiveRecord object = find(id) counters.each do |association| has_many_association = reflect_on_association(association.to_sym) + raise ArgumentError, "'#{self.name}' has no association called '#{association}'" unless has_many_association if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection has_many_association = has_many_association.through_reflection diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index b2a9a54af1..3135465dfe 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -6,7 +6,8 @@ module ActiveRecord def collecting_queries_for_explain # :nodoc: current = Thread.current original, current[:available_queries_for_explain] = current[:available_queries_for_explain], [] - return yield, current[:available_queries_for_explain] + yield + return current[:available_queries_for_explain] ensure # Note that the return value above does not depend on this assigment. current[:available_queries_for_explain] = original diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index e630897a4b..f54865c86e 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -15,6 +15,9 @@ module ActiveRecord # and if the inheritance column is attr accessible, it initializes an # instance of the given subclass instead of the base class def new(*args, &block) + if abstract_class? || self == Base + raise NotImplementedError, "#{self} is an abstract class and can not be instantiated." + end if (attrs = args.first).is_a?(Hash) if subclass = subclass_from_attrs(attrs) return subclass.new(*args, &block) @@ -167,11 +170,16 @@ module ActiveRecord # this will ignore the inheritance column and return nil def subclass_from_attrs(attrs) subclass_name = attrs.with_indifferent_access[inheritance_column] - return nil if subclass_name.blank? || subclass_name == self.name - unless subclass = subclasses.detect { |sub| sub.name == subclass_name } - raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}") + + if subclass_name.present? && subclass_name != self.name + subclass = subclass_name.safe_constantize + + unless subclasses.include?(subclass) + raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}") + end + + subclass end - subclass end end diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb index 32d35f0ec1..48c73d7781 100644 --- a/activerecord/lib/active_record/integration.rb +++ b/activerecord/lib/active_record/integration.rb @@ -49,7 +49,7 @@ module ActiveRecord case when new_record? "#{self.class.model_name.cache_key}/new" - when timestamp = self[:updated_at] + when timestamp = max_updated_column_timestamp timestamp = timestamp.utc.to_s(cache_timestamp_format) "#{self.class.model_name.cache_key}/#{id}-#{timestamp}" else diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 701949e57b..209de78898 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -86,7 +86,7 @@ module ActiveRecord ) ).arel.compile_update(arel_attributes_with_values_for_update(attribute_names)) - affected_rows = connection.update stmt + affected_rows = self.class.connection.update stmt unless affected_rows == 1 raise ActiveRecord::StaleObjectError.new(self, "update") @@ -117,7 +117,7 @@ module ActiveRecord if locking_enabled? column_name = self.class.locking_column column = self.class.columns_hash[column_name] - substitute = connection.substitute_at(column, relation.bind_values.length) + substitute = self.class.connection.substitute_at(column, relation.bind_values.length) relation = relation.where(self.class.arel_table[column_name].eq(substitute)) relation.bind_values << [column, self[column_name].to_i] diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 823595a128..5d7762ec3a 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -330,6 +330,24 @@ module ActiveRecord # # For a list of commands that are reversible, please see # <tt>ActiveRecord::Migration::CommandRecorder</tt>. + # + # == Transactional Migrations + # + # If the database adapter supports DDL transactions, all migrations will + # automatically be wrapped in a transaction. There are queries that you + # can't execute inside a transaction though, and for these situations + # you can turn the automatic transactions off. + # + # class ChangeEnum < ActiveRecord::Migration + # disable_ddl_transaction! + # + # def up + # execute "ALTER TYPE model_size ADD VALUE 'new_value'" + # end + # end + # + # Remember that you can still open your own transactions, even if you + # are in a Migration with <tt>self.disable_ddl_transaction!</tt>. class Migration autoload :CommandRecorder, 'active_record/migration/command_recorder' @@ -351,6 +369,7 @@ module ActiveRecord class << self attr_accessor :delegate # :nodoc: + attr_accessor :disable_ddl_transaction # :nodoc: end def self.check_pending! @@ -365,8 +384,16 @@ module ActiveRecord new.migrate direction end - cattr_accessor :verbose + # Disable DDL transactions for this migration. + def self.disable_ddl_transaction! + @disable_ddl_transaction = true + end + + def disable_ddl_transaction # :nodoc: + self.class.disable_ddl_transaction + end + cattr_accessor :verbose attr_accessor :name, :version def initialize(name = self.class.name, version = nil) @@ -375,8 +402,8 @@ module ActiveRecord @connection = nil end + self.verbose = true # instantiate the delegate object after initialize is defined - self.verbose = true self.delegate = new # Reverses the migration commands for the given block and @@ -607,8 +634,17 @@ module ActiveRecord source_migrations = ActiveRecord::Migrator.migrations(path) source_migrations.each do |migration| - source = File.read(migration.filename) - source = "# This migration comes from #{scope} (originally #{migration.version})\n#{source}" + source = File.binread(migration.filename) + inserted_comment = "# This migration comes from #{scope} (originally #{migration.version})\n" + if /\A#.*\b(?:en)?coding:\s*\S+/ =~ source + # If we have a magic comment in the original migration, + # insert our comment after the first newline(end of the magic comment line) + # so the magic keep working. + # Note that magic comments must be at the first line(except sh-bang). + source[/\n/] = "\n#{inserted_comment}" + else + source = "#{inserted_comment}#{source}" + end if duplicate = destination_migrations.detect { |m| m.name == migration.name } if options[:on_skip] && duplicate.scope != scope.to_s @@ -622,7 +658,7 @@ module ActiveRecord old_path, migration.filename = migration.filename, new_path last = migration - File.open(migration.filename, "w") { |f| f.write source } + File.binwrite(migration.filename, source) copied << migration options[:on_copy].call(scope, migration, old_path) if options[:on_copy] destination_migrations << migration @@ -663,7 +699,7 @@ module ActiveRecord File.basename(filename) end - delegate :migrate, :announce, :write, :to => :migration + delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration private @@ -856,12 +892,12 @@ module ActiveRecord Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger begin - ddl_transaction do + ddl_transaction(migration) do migration.migrate(@direction) record_version_state_after_migrating(migration.version) end rescue => e - canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : "" + canceled_msg = use_transaction?(migration) ? "this and " : "" raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace end end @@ -935,12 +971,16 @@ module ActiveRecord end # Wrap the migration in a transaction only if supported by the adapter. - def ddl_transaction - if Base.connection.supports_ddl_transactions? + def ddl_transaction(migration) + if use_transaction?(migration) Base.transaction { yield } else yield end end + + def use_transaction?(migration) + !migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions? + end end end diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 85fb4be992..ac2d2f2712 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -205,7 +205,7 @@ module ActiveRecord # Returns an array of column objects for the table associated with this class. def columns - @columns ||= connection.schema_cache.columns[table_name].map do |col| + @columns ||= connection.schema_cache.columns(table_name).map do |col| col = col.dup col.primary = (col.name == primary_key) col diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index c5bd11edbf..602ab9e2f4 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -269,23 +269,36 @@ module ActiveRecord self.nested_attributes_options = nested_attributes_options type = (reflection.collection? ? :collection : :one_to_one) - - # def pirate_attributes=(attributes) - # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options) - # end - generated_feature_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1 - if method_defined?(:#{association_name}_attributes=) - remove_method(:#{association_name}_attributes=) - end - def #{association_name}_attributes=(attributes) - assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes) - end - eoruby + generate_association_writer(association_name, type) else raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?" end end end + + private + + # Generates a writer method for this association. Serves as a point for + # accessing the objects in the association. For example, this method + # could generate the following: + # + # def pirate_attributes=(attributes) + # assign_nested_attributes_for_one_to_one_association(:pirate, attributes) + # end + # + # This redirects the attempts to write objects in an association through + # the helper methods defined below. Makes it seem like the nested + # associations are just regular associations. + def generate_association_writer(association_name, type) + generated_feature_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1 + if method_defined?(:#{association_name}_attributes=) + remove_method(:#{association_name}_attributes=) + end + def #{association_name}_attributes=(attributes) + assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes) + end + eoruby + end end # Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's @@ -371,20 +384,7 @@ module ActiveRecord raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" end - if limit = options[:limit] - limit = case limit - when Symbol - send(limit) - when Proc - limit.call - else - limit - end - - if limit && attributes_collection.size > limit - raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead." - end - end + check_record_limit!(options[:limit], attributes_collection) if attributes_collection.is_a? Hash keys = attributes_collection.keys @@ -433,6 +433,29 @@ module ActiveRecord end end + # Takes in a limit and checks if the attributes_collection has too many + # records. The method will take limits in the form of symbols, procs, and + # number-like objects (anything that can be compared with an integer). + # + # Will raise an TooManyRecords error if the attributes_collection is + # larger than the limit. + def check_record_limit!(limit, attributes_collection) + if limit + limit = case limit + when Symbol + send(limit) + when Proc + limit.call + else + limit + end + + if limit && attributes_collection.size > limit + raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead." + end + end + end + # Updates a record with the +attributes+ or marks it for destruction if # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+. def assign_to_or_mark_for_destruction(record, attributes, allow_destroy) @@ -452,6 +475,11 @@ module ActiveRecord has_destroy_flag?(attributes) || call_reject_if(association_name, attributes) end + # Determines if a record with the particular +attributes+ should be + # rejected by calling the reject_if Symbol or Proc (if defined). + # The reject_if option is defined by +accepts_nested_attributes_for+. + # + # Returns false if there is a +destroy_flag+ on the attributes. def call_reject_if(association_name, attributes) return false if has_destroy_flag?(attributes) case callback = self.nested_attributes_options[association_name][:reject_if] diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 347f023793..b25d0601cb 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -410,7 +410,7 @@ module ActiveRecord def relation_for_destroy pk = self.class.primary_key column = self.class.columns_hash[pk] - substitute = connection.substitute_at(column, 0) + substitute = self.class.connection.substitute_at(column, 0) relation = self.class.unscoped.where( self.class.arel_table[pk].eq(substitute)) diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index e04a3d0976..902fd90c54 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -8,7 +8,7 @@ module ActiveRecord delegate :find_each, :find_in_batches, :to => :all delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, - :having, :create_with, :uniq, :references, :none, :to => :all + :having, :create_with, :uniq, :distinct, :references, :none, :to => :all delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :all # Executes a custom SQL query against your database and returns all the results. The results will diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 13f3bf7085..99117b74c5 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -49,7 +49,7 @@ module ActiveRecord Rails.logger.extend ActiveSupport::Logger.broadcast console end - runner do |app| + runner do require "active_record/base" end @@ -64,7 +64,7 @@ module ActiveRecord ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger } end - initializer "active_record.migration_error" do |app| + initializer "active_record.migration_error" do if config.active_record.delete(:migration_error) == :page_load config.app_middleware.insert_after "::ActionDispatch::Callbacks", "ActiveRecord::Migration::CheckPending" @@ -158,7 +158,7 @@ module ActiveRecord end # Expose database runtime to controller for logging. - initializer "active_record.log_runtime" do |app| + initializer "active_record.log_runtime" do require "active_record/railties/controller_runtime" ActiveSupport.on_load(:action_controller) do include ActiveRecord::Railties::ControllerRuntime diff --git a/activerecord/lib/active_record/railties/console_sandbox.rb b/activerecord/lib/active_record/railties/console_sandbox.rb index 90b462fad6..604a220303 100644 --- a/activerecord/lib/active_record/railties/console_sandbox.rb +++ b/activerecord/lib/active_record/railties/console_sandbox.rb @@ -1,4 +1,5 @@ -ActiveRecord::Base.connection.begin_db_transaction +ActiveRecord::Base.connection.begin_transaction(joinable: false) + at_exit do - ActiveRecord::Base.connection.rollback_db_transaction + ActiveRecord::Base.connection.rollback_transaction end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index efbae108b9..037097d2dd 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -10,7 +10,7 @@ module ActiveRecord :extending] SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, - :reverse_order, :uniq, :create_with] + :reverse_order, :distinct, :create_with] VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS @@ -188,8 +188,7 @@ module ActiveRecord # Please see further details in the # {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain]. def explain - _, queries = collecting_queries_for_explain { exec_queries } - exec_explain(queries) + exec_explain(collecting_queries_for_explain { exec_queries }) end # Converts relation objects to Array. @@ -507,6 +506,12 @@ module ActiveRecord includes_values & joins_values end + # +uniq+ and +uniq!+ are silently deprecated. +uniq_value+ delegates to +distinct_value+ + # to maintain backwards compatibility. Use +distinct_value+ instead. + def uniq_value + distinct_value + end + # Compares two relations for equality. def ==(other) case other @@ -590,7 +595,8 @@ module ActiveRecord if (references_values - joined_tables).any? true - elsif (string_tables - joined_tables).any? + elsif !ActiveRecord::Base.disable_implicit_join_references && + (string_tables - joined_tables).any? ActiveSupport::Deprecation.warn( "It looks like you are eager loading table(s) (one of: #{string_tables.join(', ')}) " \ "that are referenced in a string SQL snippet. For example: \n" \ @@ -604,7 +610,10 @@ module ActiveRecord "From now on, you must explicitly tell Active Record when you are referencing a table " \ "from a string:\n" \ "\n" \ - " Post.includes(:comments).where(\"comments.title = 'foo'\").references(:comments)\n\n" + " Post.includes(:comments).where(\"comments.title = 'foo'\").references(:comments)\n" \ + "\n" \ + "If you don't rely on implicit join references you can disable the feature entirely" \ + "by setting `config.active_record.disable_implicit_join_references = true`." ) true else diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 7f95181c67..be011b22af 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -11,7 +11,7 @@ module ActiveRecord # Person.count(:all) # # => performs a COUNT(*) (:all is an alias for '*') # - # Person.count(:age, distinct: true) + # Person.distinct.count(:age) # # => counts the number of different age values # # If +count+ is used with +group+, it returns a Hash whose keys represent the aggregated column, @@ -198,8 +198,13 @@ module ActiveRecord def perform_calculation(operation, column_name, options = {}) operation = operation.to_s.downcase - # If #count is used in conjuction with #uniq it is considered distinct. (eg. relation.uniq.count) - distinct = options[:distinct] || self.uniq_value + # If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count) + distinct = self.distinct_value + if options.has_key?(:distinct) + ActiveSupport::Deprecation.warn "The :distinct option for `Relation#count` is deprecated. " \ + "Please use `Relation#distinct` instead. (eg. `relation.distinct.count`)" + distinct = options[:distinct] + end if operation == "count" column_name ||= (select_for_count || :all) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index b7960936cf..10a31109d5 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -710,20 +710,22 @@ module ActiveRecord # User.select(:name) # # => Might return two records with the same name # - # User.select(:name).uniq - # # => Returns 1 record per unique name + # User.select(:name).distinct + # # => Returns 1 record per distinct name # - # User.select(:name).uniq.uniq(false) + # User.select(:name).distinct.distinct(false) # # => You can also remove the uniqueness - def uniq(value = true) - spawn.uniq!(value) + def distinct(value = true) + spawn.distinct!(value) end + alias uniq distinct - # Like #uniq, but modifies relation in place. - def uniq!(value = true) # :nodoc: - self.uniq_value = value + # Like #distinct, but modifies relation in place. + def distinct!(value = true) # :nodoc: + self.distinct_value = value self end + alias uniq! distinct! # Used to extend a scope with additional methods, either through # a module or through a block provided. @@ -814,7 +816,7 @@ module ActiveRecord build_select(arel, select_values.uniq) - arel.distinct(uniq_value) + arel.distinct(distinct_value) arel.from(build_from) if from_value arel.lock(lock_value) if lock_value diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index 9830abe7d8..6077144265 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -13,18 +13,21 @@ module ActiveRecord "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}" end - def self.create_table + def self.create_table(limit=nil) unless connection.table_exists?(table_name) - connection.create_table(table_name, :id => false) do |t| - t.column :version, :string, :null => false + version_options = {null: false} + version_options[:limit] = limit if limit + + connection.create_table(table_name, id: false) do |t| + t.column :version, :string, version_options end - connection.add_index table_name, :version, :unique => true, :name => index_name + connection.add_index table_name, :version, unique: true, name: index_name end end def self.drop_table if connection.table_exists?(table_name) - connection.remove_index table_name, :name => index_name + connection.remove_index table_name, name: index_name connection.drop_table(table_name) end end diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 01fbb96b8e..12317601b6 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -159,10 +159,19 @@ module ActiveRecord end singleton_class.send(:define_method, name) do |*args| - options = body.respond_to?(:call) ? unscoped { body.call(*args) } : body - relation = all.merge(options) + if body.respond_to?(:call) + scope = extension ? body.call(*args).extending(extension) : body.call(*args) - extension ? relation.extending(extension) : relation + if scope + default_scoped = scope.default_scoped + scope = relation.merge(scope) + scope.default_scoped = default_scoped + end + else + scope = body + end + + scope || all end end end diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index 17378969a5..50569d2462 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -26,7 +26,9 @@ module ActiveRecord $stdout.print error.error establish_connection root_configuration_without_database connection.create_database configuration['database'], creation_options - connection.execute grant_statement.gsub(/\s+/, ' ').strip + if configuration['username'] != 'root' + connection.execute grant_statement.gsub(/\s+/, ' ').strip + end establish_connection configuration else $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}" @@ -57,7 +59,10 @@ module ActiveRecord args.concat(["--result-file", "#{filename}"]) args.concat(["--no-data"]) args.concat(["#{configuration['database']}"]) - Kernel.system(*args) + unless Kernel.system(*args) + $stderr.puts "Could not dump the database structure. "\ + "Make sure `mysqldump` is in your PATH and check the command output for warnings." + end end def structure_load(filename) diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 8ded6d4a86..ae99cff35e 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -98,6 +98,12 @@ module ActiveRecord timestamp_attributes_for_create + timestamp_attributes_for_update end + def max_updated_column_timestamp + if (timestamps = timestamp_attributes_for_update.map { |attr| self[attr] }.compact).present? + timestamps.map { |ts| ts.to_time }.max + end + end + def current_time_from_proper_timezone self.class.default_timezone == :utc ? Time.now.utc : Time.now end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 33718ef0e9..4dbd668fcf 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -160,7 +160,7 @@ module ActiveRecord # end # end # - # only "Kotori" is created. (This works on MySQL and PostgreSQL, but not on SQLite3.) + # only "Kotori" is created. This works on MySQL and PostgreSQL. SQLite3 version >= '3.6.8' also supports it. # # Most databases don't support true nested transactions. At the time of # writing, the only database that we're aware of that supports true nested diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 1427189851..a705d8c2c4 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -2,6 +2,10 @@ module ActiveRecord module Validations class UniquenessValidator < ActiveModel::EachValidator # :nodoc: def initialize(options) + if options[:conditions] && !options[:conditions].respond_to?(:call) + raise ArgumentError, "#{options[:conditions]} was passed as :conditions but is not callable. " \ + "Pass a callable instead: `conditions: -> { where(approved: true) }`" + end super({ case_sensitive: true }.merge!(options)) end @@ -19,7 +23,7 @@ module ActiveRecord relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted? relation = scope_relation(record, table, relation) relation = finder_class.unscoped.where(relation) - relation.merge!(options[:conditions]) if options[:conditions] + relation = relation.merge(options[:conditions]) if options[:conditions] if relation.exists? error_options = options.except(:case_sensitive, :scope, :conditions) @@ -116,7 +120,7 @@ module ActiveRecord # of the title attribute: # # class Article < ActiveRecord::Base - # validates_uniqueness_of :title, conditions: where('status != ?', 'archived') + # validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') } # end # # When the record is created, a check is performed to make sure that no @@ -132,7 +136,7 @@ module ActiveRecord # the uniqueness constraint. # * <tt>:conditions</tt> - Specify the conditions to be included as a # <tt>WHERE</tt> SQL fragment to limit the uniqueness constraint lookup - # (e.g. <tt>conditions: where('status = ?', 'active')</tt>). + # (e.g. <tt>conditions: -> { where(status: 'active') }</tt>). # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by # non-text columns (+true+ by default). # * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb index 3a3cf86d73..fd94a2d038 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb @@ -2,8 +2,12 @@ class <%= migration_class_name %> < ActiveRecord::Migration def change create_table :<%= table_name %> do |t| <% attributes.each do |attribute| -%> +<% if attribute.password_digest? -%> + t.string :password_digest<%= attribute.inject_options %> +<% else -%> t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %> <% end -%> +<% end -%> <% if options[:timestamps] %> t.timestamps <% end -%> diff --git a/activerecord/lib/rails/generators/active_record/model/templates/model.rb b/activerecord/lib/rails/generators/active_record/model/templates/model.rb index 056f55470c..808598699b 100644 --- a/activerecord/lib/rails/generators/active_record/model/templates/model.rb +++ b/activerecord/lib/rails/generators/active_record/model/templates/model.rb @@ -1,7 +1,10 @@ <% module_namespacing do -%> class <%= class_name %> < <%= parent_class_name.classify %> -<% attributes.select {|attr| attr.reference? }.each do |attribute| -%> +<% attributes.select(&:reference?).each do |attribute| -%> belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %> <% end -%> +<% if attributes.any?(&:password_digest?) -%> + has_secure_password +<% end -%> end <% end -%> |