diff options
54 files changed, 531 insertions, 294 deletions
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc index b065be4922..6f8c79eef2 100644 --- a/RELEASING_RAILS.rdoc +++ b/RELEASING_RAILS.rdoc @@ -13,7 +13,7 @@ Today is mostly coordination tasks. Here are the things you must do today: Do not release with a Red CI. You can find the CI status here: - http://travis-ci.org/#!/rails/rails + http://travis-ci.org/rails/rails === Is Sam Ruby happy? If not, make him happy. diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index acad8a0799..4103354d13 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -2,6 +2,7 @@ require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/array/wrap' require 'active_support/rescuable' require 'action_dispatch/http/upload' +require 'stringio' module ActionController # Raised when a required parameter is missing. @@ -68,6 +69,8 @@ module ActionController # ActionController::UnpermittedParameters exception. The default value is <tt>:log</tt> # in test and development environments, +false+ otherwise. # + # Examples: + # # params = ActionController::Parameters.new # params.permitted? # => false # diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb index 1090af3060..8cba049485 100644 --- a/actionpack/test/abstract/callbacks_test.rb +++ b/actionpack/test/abstract/callbacks_test.rb @@ -259,7 +259,7 @@ module AbstractController end class TestCallbacksWithArgs < ActiveSupport::TestCase - test "callbacks still work when invoking process with multiple args" do + test "callbacks still work when invoking process with multiple arguments" do controller = CallbacksWithArgs.new controller.process(:index, " Howdy!") assert_equal "Hello world Howdy!", controller.response_body diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb index c14d24905b..5709ad0378 100644 --- a/actionpack/test/abstract/collector_test.rb +++ b/actionpack/test/abstract/collector_test.rb @@ -42,7 +42,7 @@ module AbstractController end end - test "generated methods call custom with args received" do + test "generated methods call custom with arguments received" do collector = MyCollector.new collector.html collector.text(:foo) diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 62d684fd0b..b4ada0e0b5 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -91,7 +91,7 @@ *Yves Senn* -* Use BCrypt's `MIN_COST` in the test environment for speedier tests when using `has_secure_pasword`. +* Use BCrypt's `MIN_COST` in the test environment for speedier tests when using `has_secure_password`. *Brian Cardarella + Jeremy Kemper + Trevor Turk* diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index 9324a1ad0a..474cb0aea0 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -43,8 +43,13 @@ module ActiveModel # Load bcrypt-ruby only when has_secure_password is used. # This is to avoid ActiveModel (and by extension the entire framework) # being dependent on a binary library. - gem 'bcrypt-ruby', '~> 3.0.0' - require 'bcrypt' + begin + gem 'bcrypt-ruby', '~> 3.0.0' + require 'bcrypt' + rescue LoadError + $stderr.puts "You don't have bcrypt-ruby installed in your application. Please add it to your Gemfile and run bundle install" + raise + end attr_reader :password diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 72e3cf310a..714cc95b9a 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -376,7 +376,4 @@ module ActiveModel end end -Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path| - filename = File.basename(path) - require "active_model/validations/#{filename}" -end +Dir[File.dirname(__FILE__) + "/validations/*.rb"].each { |file| require file } diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 771f1333f0..62a6b856f2 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,15 +1,30 @@ ## Rails 4.0.0 (unreleased) ## +* Add an `add_index` override in Postgresql adapter and MySQL adapter + to allow custom index type support. Fixes #6101. + + add_index(:wikis, :body, :using => 'gin') + + *Stefan Huber* and *Doabit* + +* After extraction of mass-assignment attributes (which protects [id, type] + by default) we can pass id to `update_attributes` and it will update + another record because id will be used in where statement. We never have + to change id in where statement because we try to set/replace fields for + already loaded record but we have to try to set new id for that record. + + *Dmitry Vorotilin* + * Models with multiple counter cache associations now update correctly on destroy. See #7706. *Ian Young* -* If inverse_of is true on an association, then when one calls +find()+ on - the association, ActiveRecord will first look through the in-memory objects +* If ``:inverse_of` is true on an association, then when one calls `find()` on + the association, Active Record will first look through the in-memory objects in the association for a particular id. Then, it will go to the DB if it - is not found. This is accomplished by calling +find_by_scan+ in - collection associations whenever +options[:inverse_of]+ is not nil. + is not found. This is accomplished by calling `find_by_scan` in + collection associations whenever `options[:inverse_of]` is not nil. Fixes #9470. @@ -96,11 +111,11 @@ *Yves Senn* -* Fix quoting for sqlite migrations using copy_table_contents() with binary +* Fix quoting for sqlite migrations using `copy_table_contents` with binary columns. These would fail with "SQLite3::SQLException: unrecognized token" because - the column was not being passed to quote() so the data was not quoted + the column was not being passed to `quote` so the data was not quoted correctly. *Matthew M. Boedicker* @@ -273,7 +288,7 @@ *John Wang* -* Postgresql timestamp with time zone (timestamptz) datatype now returns a +* PostgreSQL timestamp with time zone (timestamptz) datatype now returns a ActiveSupport::TimeWithZone instance instead of a string *Troy Kruthoff* @@ -503,7 +518,7 @@ *James Miller* -* Allow store accessors to be overrided like other attribute methods, e.g.: +* Allow store accessors to be overridden like other attribute methods, e.g.: class User < ActiveRecord::Base store :settings, accessors: [ :color, :homepage ], coder: JSON @@ -526,7 +541,7 @@ *Dylan Smith* * Schema dumper supports dumping the enabled database extensions to `schema.rb` - (currently only supported by postgresql). + (currently only supported by PostgreSQL). *Justin George* @@ -690,7 +705,7 @@ *Yves Senn* -* Add ability for postgresql adapter to disable user triggers in `disable_referential_integrity`. +* Add ability for PostgreSQL adapter to disable user triggers in `disable_referential_integrity`. Fixes #5523. *Gary S. Weaver* @@ -814,14 +829,14 @@ *Carlos Antonio da Silva* -* Fix postgresql adapter to handle BC timestamps correctly +* Fix PostgreSQL adapter to handle BC timestamps correctly HistoryEvent.create!(name: "something", occured_at: Date.new(0) - 5.years) *Bogdan Gusiev* -* When running migrations on Postgresql, the `:limit` option for `binary` and `text` columns is silently dropped. - Previously, these migrations caused sql exceptions, because Postgresql doesn't support limits on these types. +* When running migrations on PostgreSQL, the `:limit` option for `binary` and `text` columns is silently dropped. + Previously, these migrations caused sql exceptions, because PostgreSQL doesn't support limits on these types. *Victor Costan* @@ -1453,7 +1468,7 @@ *Egor Lynko* -* Added support for specifying the precision of a timestamp in the postgresql +* Added support for specifying the precision of a timestamp in the PostgreSQL adapter. So, instead of having to incorrectly specify the precision using the `:limit` option, you may use `:precision`, as intended. For example, in a migration: @@ -1721,7 +1736,7 @@ * Added the schema cache dump feature. - `Schema cache dump` feature was implemetend. This feature can dump/load internal state of `SchemaCache` instance + `Schema cache dump` feature was implemented. This feature can dump/load internal state of `SchemaCache` instance because we want to boot rails more quickly when we have many models. Usage notes: diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 868095f068..4c4b0f08e5 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -203,7 +203,7 @@ module ActiveRecord # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of # the kind of the class of the associated objects. Meant to be used as # a sanity check when you are about to assign an associated record. - def raise_on_type_mismatch(record) + def raise_on_type_mismatch!(record) unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize) message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})" raise ActiveRecord::AssociationTypeMismatch, message diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 54b1a69774..8eec4f56af 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -8,7 +8,7 @@ module ActiveRecord end def replace(record) - raise_on_type_mismatch(record) if record + raise_on_type_mismatch!(record) if record update_counters(record) replace_keys(record) diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb index 88ce03a3cd..eae5eed3a1 100644 --- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb @@ -22,7 +22,7 @@ module ActiveRecord reflection.polymorphic_inverse_of(record.class) end - def raise_on_type_mismatch(record) + def raise_on_type_mismatch!(record) # A polymorphic association cannot have a type mismatch, by definition end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 51bcac61c7..2385c90c1a 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -318,7 +318,7 @@ module ActiveRecord # Replace this collection with +other_array+. This will perform a diff # and delete/add only records that have changed. def replace(other_array) - other_array.each { |val| raise_on_type_mismatch(val) } + other_array.each { |val| raise_on_type_mismatch!(val) } original_target = load_target.dup if owner.new_record? @@ -465,7 +465,7 @@ module ActiveRecord def delete_or_destroy(records, method) records = records.flatten - records.each { |record| raise_on_type_mismatch(record) } + records.each { |record| raise_on_type_mismatch!(record) } existing_records = records.reject { |r| r.new_record? } if existing_records.empty? @@ -506,7 +506,7 @@ module ActiveRecord result = true records.flatten.each do |record| - raise_on_type_mismatch(record) + raise_on_type_mismatch!(record) add_to_target(record) do |rec| result &&= insert_record(rec) unless owner.new_record? end diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index d1458f30ba..a74dd1cdab 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -29,7 +29,7 @@ module ActiveRecord def concat(*records) unless owner.new_record? records.flatten.each do |record| - raise_on_type_mismatch(record) + raise_on_type_mismatch!(record) record.save! if record.new_record? end end diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index ee816d2392..98bd010f70 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -22,7 +22,7 @@ module ActiveRecord end def replace(record, save = true) - raise_on_type_mismatch(record) if record + raise_on_type_mismatch!(record) if record load_target # If target and record are nil, or target is equal to record, diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index ecfa556ab4..e536f5ebcc 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -81,7 +81,7 @@ module ActiveRecord end def extract_callstack_for_multiparameter_attributes(pairs) - attributes = { } + attributes = {} pairs.each do |(multiparameter_name, value)| attribute_name = multiparameter_name.split("(").first @@ -146,7 +146,7 @@ module ActiveRecord end else # else column is a timestamp, so if Date bits were not provided, error - validate_missing_parameters!([1,2,3]) + validate_required_parameters!([1,2,3]) # If Date bits were provided but blank, then return nil return if blank_date_parameter? @@ -172,14 +172,14 @@ module ActiveRecord def read_other(klass) max_position = extract_max_param positions = (1..max_position) - validate_missing_parameters!(positions) + validate_required_parameters!(positions) set_values = values.values_at(*positions) klass.new(*set_values) end # Checks whether some blank date parameter exists. Note that this is different - # than the validate_missing_parameters! method, since it just checks for blank + # than the validate_required_parameters! method, since it just checks for blank # positions instead of missing ones, and does not raise in case one blank position # exists. The caller is responsible to handle the case of this returning true. def blank_date_parameter? @@ -187,7 +187,7 @@ module ActiveRecord end # If some position is not provided, it errors out a missing parameter exception. - def validate_missing_parameters!(positions) + def validate_required_parameters!(positions) if missing_parameter = positions.detect { |position| !values.key?(position) } raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})") end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index aa92343f76..769e005c8f 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -348,7 +348,7 @@ module ActiveRecord # Filters the primary keys and readonly attributes from the attribute names. def attributes_for_update(attribute_names) attribute_names.select do |name| - column_for_attribute(name) && !pk_attribute?(name) && !readonly_attribute?(name) + column_for_attribute(name) && !readonly_attribute?(name) end end 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 902dbd148e..2f17e47b7c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -8,41 +8,21 @@ module ActiveRecord # Abstract representation of an index definition on a table. Instances of # this type are typically created and returned by methods in database # adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes - class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where) #:nodoc: + class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :using) #:nodoc: end # Abstract representation of a column definition. Instances of this type # are typically created by methods in TableDefinition, and added to the # +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: + class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key) #:nodoc: def string_to_binary(value) value end - def sql_type - 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? - column_options[:column] = self - add_column_options!(column_sql, column_options) unless primary_key? - column_sql + primary_key || type.to_sym == :primary_key end - - private - - def add_column_options!(sql, options) - base.add_column_options!(sql, options) - end end # Represents the schema of an SQL table in an abstract way. This class @@ -68,19 +48,25 @@ module ActiveRecord class TableDefinition # An array of ColumnDefinition objects, representing the column changes # that have been defined. - attr_accessor :columns, :indexes + attr_accessor :indexes + attr_reader :name, :temporary, :options - def initialize(base) - @columns = [] + def initialize(types, name, temporary, options) @columns_hash = {} @indexes = {} - @base = base + @native = types + @temporary = temporary + @options = options + @name = name end + def columns; @columns_hash.values; 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) - column(name, :primary_key) + def primary_key(name, type = :primary_key, options = {}) + options[:primary_key] = true + column(name, type, options) end # Returns a ColumnDefinition for the column with name +name+. @@ -233,20 +219,14 @@ module ActiveRecord raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table." end - column = self[name] || new_column_definition(@base, name, type) - - limit = options.fetch(:limit) do - native[type][:limit] if native[type].is_a?(Hash) - end - - column.limit = limit - column.precision = options[:precision] - column.scale = options[:scale] - column.default = options[:default] - column.null = options[:null] + @columns_hash[name] = new_column_definition(name, type, options) self end + def remove_column(name) + @columns_hash.delete name.to_s + end + [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type| define_method column_type do |*args| options = args.extract_options! @@ -283,23 +263,26 @@ module ActiveRecord end alias :belongs_to :references - # Returns a String whose contents are the column definitions - # 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 } * ', ' - end + def new_column_definition(name, type, options) # :nodoc: + column = create_column_definition name, type + limit = options.fetch(:limit) do + native[type][:limit] if native[type].is_a?(Hash) + end - private - def create_column_definition(base, name, type) - ColumnDefinition.new base, name, type + column.limit = limit + column.precision = options[:precision] + column.scale = options[:scale] + column.default = options[:default] + column.null = options[:null] + column.first = options[:first] + column.after = options[:after] + column.primary_key = type == :primary_key || options[:primary_key] + column end - def new_column_definition(base, name, type) - definition = create_column_definition base, name, type - @columns << definition - @columns_hash[name] = definition - definition + private + def create_column_definition(name, type) + ColumnDefinition.new name, type end def primary_key_column_name @@ -308,7 +291,24 @@ module ActiveRecord end def native - @base.native_database_types + @native + end + end + + class AlterTable # :nodoc: + attr_reader :adds + + def initialize(td) + @td = td + @adds = [] + end + + def name; @td.name; end + + def add_column(name, type, options) + name = name.to_s + type = type.to_sym + @adds << @td.new_column_definition(name, type, options) end 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 cd4409295f..26cacbde37 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -171,14 +171,14 @@ module ActiveRecord # # See also TableDefinition#column for details on how to create columns. def create_table(table_name, options = {}) - td = create_table_definition + td = create_table_definition table_name, options[:temporary], options[:options] unless options[:id] == false pk = options.fetch(:primary_key) { Base.get_primary_key table_name.to_s.singularize } - td.primary_key pk + td.primary_key pk, options.fetch(:id, :primary_key), options end yield td if block_given? @@ -187,11 +187,7 @@ module ActiveRecord drop_table(table_name, options) end - create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE " - create_sql << "#{quote_table_name(table_name)} (" - create_sql << td.to_sql - create_sql << ") #{options[:options]}" - execute create_sql + execute schema_creation.accept td td.indexes.each_pair { |c,o| add_index table_name, c, o } end @@ -359,9 +355,9 @@ module ActiveRecord # Adds a new column to the named table. # See TableDefinition#column for details of the options you can use. def add_column(table_name, column_name, type, options = {}) - add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" - add_column_options!(add_column_sql, options) - execute(add_column_sql) + at = create_alter_table table_name + at.add_column(column_name, type, options) + execute schema_creation.accept at end # Removes the given columns from the table definition. @@ -501,7 +497,14 @@ module ActiveRecord # # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active # - # Note: only supported by PostgreSQL. + # ====== Creating an index with a specific method + # add_index(:developers, :name, :using => 'btree') + # generates + # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL + # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL + # + # Note: only supported by PostgreSQL and MySQL + # 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}" @@ -749,7 +752,7 @@ module ActiveRecord index_name = index_name(table_name, column: column_names) if Hash === options # legacy support, since this param was a string - options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal) + options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using) index_type = options[:unique] ? "UNIQUE" : "" index_name = options[:name].to_s if options.key?(:name) @@ -829,8 +832,12 @@ module ActiveRecord end private - def create_table_definition - TableDefinition.new(self) + def create_table_definition(name, temporary, options) + TableDefinition.new native_database_types, name, temporary, options + end + + def create_alter_table(name) + AlterTable.new create_table_definition(name, false, {}) end def update_table_definition(table_name, base) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 7949bcb5ce..0f4ab68b61 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -18,6 +18,7 @@ module ActiveRecord autoload :ColumnDefinition autoload :TableDefinition autoload :Table + autoload :AlterTable end autoload_at 'active_record/connection_adapters/abstract/connection_pool' do @@ -100,6 +101,75 @@ module ActiveRecord @visitor = nil end + class SchemaCreation + def initialize(conn) + @conn = conn + @cache = {} + end + + def accept(o) + m = @cache[o.class] ||= "visit_#{o.class.name.split('::').last}" + send m, o + end + + private + + def visit_AlterTable(o) + sql = "ALTER TABLE #{quote_table_name(o.name)} " + sql << o.adds.map { |col| visit_AddColumn col }.join(' ') + end + + def visit_AddColumn(o) + sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale) + sql = "ADD #{quote_column_name(o.name)} #{sql_type}" + add_column_options!(sql, column_options(o)) + end + + def visit_ColumnDefinition(o) + sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale) + column_sql = "#{quote_column_name(o.name)} #{sql_type}" + add_column_options!(column_sql, column_options(o)) unless o.primary_key? + column_sql + end + + def visit_TableDefinition(o) + create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE " + create_sql << "#{quote_table_name(o.name)} (" + create_sql << o.columns.map { |c| accept c }.join(', ') + create_sql << ") #{o.options}" + create_sql + end + + def column_options(o) + column_options = {} + column_options[:null] = o.null unless o.null.nil? + column_options[:default] = o.default unless o.default.nil? + column_options[:column] = o + column_options + end + + def quote_column_name(name) + @conn.quote_column_name name + end + + def quote_table_name(name) + @conn.quote_table_name name + end + + def type_to_sql(type, limit, precision, scale) + @conn.type_to_sql type.to_sym, limit, precision, scale + end + + def add_column_options!(column_sql, column_options) + @conn.add_column_options! column_sql, column_options + column_sql + end + end + + def schema_creation + SchemaCreation.new self + end + def lease synchronize do unless in_use 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 f88f5742a8..1f111a0976 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -3,6 +3,27 @@ require 'arel/visitors/bind_visitor' module ActiveRecord module ConnectionAdapters class AbstractMysqlAdapter < AbstractAdapter + class SchemaCreation < AbstractAdapter::SchemaCreation + private + + def visit_AddColumn(o) + add_column_position!(super, o) + end + + def add_column_position!(sql, column) + if column.first + sql << " FIRST" + elsif column.after + sql << " AFTER #{quote_column_name(column.after)}" + end + sql + end + end + + def schema_creation + SchemaCreation.new self + end + class Column < ConnectionAdapters::Column # :nodoc: attr_reader :collation, :strict @@ -411,6 +432,7 @@ module ActiveRecord next if row[:Key_name] == 'PRIMARY' # skip the primary key current_index = row[:Key_name] indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], []) + indexes.last.using = row[:Index_type].downcase.to_sym end indexes.last.columns << row[:Column_name] @@ -459,10 +481,6 @@ module ActiveRecord rename_table_indexes(table_name, new_name) end - def add_column(table_name, column_name, type, options = {}) - execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}") - end - def change_column_default(table_name, column_name, default) column = column_for(table_name, column_name) change_column table_name, column_name, column.sql_type, :default => default @@ -487,6 +505,15 @@ module ActiveRecord rename_column_indexes(table_name, column_name, new_column_name) end + def add_index(table_name, column_name, options = {}) #:nodoc: + if options.is_a?(Hash) && options[:using] + 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)} USING #{options[:using]} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}" + else + super + end + end + # Maps logical Rails types to MySQL-specific data types. def type_to_sql(type, limit = nil, precision = nil, scale = nil) case type.to_s diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index 3d8f0b575c..fcbd8fd88a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -30,8 +30,8 @@ module ActiveRecord nil elsif String === string Hash[string.scan(HstorePair).map { |k,v| - v = v.upcase == 'NULL' ? nil : v.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1') - k = k.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1') + v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1') + k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1') [k,v] }] else diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 43f991b362..49b93e5626 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -18,10 +18,12 @@ module ActiveRecord def quote(value, column = nil) #:nodoc: return super unless column + sql_type = type_to_sql(column.type, column.limit, column.precision, column.scale) + case value when Range - if /range$/ =~ column.sql_type - "'#{PostgreSQLColumn.range_to_string(value)}'::#{column.sql_type}" + if /range$/ =~ sql_type + "'#{PostgreSQLColumn.range_to_string(value)}'::#{sql_type}" else super end @@ -32,13 +34,13 @@ module ActiveRecord super end when Hash - case column.sql_type + case sql_type when 'hstore' then super(PostgreSQLColumn.hstore_to_string(value), column) when 'json' then super(PostgreSQLColumn.json_to_string(value), column) else super end when IPAddr - case column.sql_type + case sql_type when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column) else super end @@ -51,14 +53,14 @@ module ActiveRecord super end when Numeric - if column.sql_type == 'money' || [:string, :text].include?(column.type) + if 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 + case sql_type when 'bytea' then "'#{escape_bytea(value)}'" when 'xml' then "xml '#{quote_string(value)}'" when /^bit/ 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 3bc61c5e0c..688bf1774b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -1,6 +1,42 @@ module ActiveRecord module ConnectionAdapters class PostgreSQLAdapter < AbstractAdapter + class SchemaCreation < AbstractAdapter::SchemaCreation + private + + def visit_AddColumn(o) + sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale) + sql = "ADD COLUMN #{quote_column_name(o.name)} #{sql_type}" + add_column_options!(sql, column_options(o)) + end + + def visit_ColumnDefinition(o) + sql = super + if o.primary_key? && o.type == :uuid + sql << " PRIMARY KEY " + add_column_options!(sql, column_options(o)) + end + sql + end + + def add_column_options!(sql, options) + if options[:array] || options[:column].try(:array) + sql << '[]' + end + + column = options.fetch(:column) { return super } + if column.type == :uuid && options[:default] =~ /\(\)/ + sql << " DEFAULT #{options[:default]}" + else + super + end + end + end + + def schema_creation + SchemaCreation.new self + end + module SchemaStatements # Drops the database specified on the +name+ attribute # and creates it again using the provided +options+. @@ -124,8 +160,9 @@ module ActiveRecord desc_order_columns = inddef.scan(/(\w+) DESC/).flatten orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {} where = inddef.scan(/WHERE (.+)$/).flatten[0] + type = inddef.scan(/USING (.+?) /).flatten[0].to_sym - column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where) + column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, type) end.compact end @@ -337,10 +374,7 @@ module ActiveRecord # See TableDefinition#column for details of the options you can use. def add_column(table_name, column_name, type, options = {}) clear_cache! - add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" - add_column_options!(add_column_sql, options) - - execute add_column_sql + super end # Changes the column of a table. @@ -375,6 +409,15 @@ module ActiveRecord rename_column_indexes(table_name, column_name, new_column_name) end + def add_index(table_name, column_name, options = {}) #:nodoc: + if options.is_a?(Hash) && options[:using] + 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)} USING #{options[:using]} (#{index_columns})#{index_options}" + else + super + end + end + def remove_index!(table_name, index_name) #:nodoc: execute "DROP INDEX #{quote_table_name(index_name)}" end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 940de7e4f6..6f1eb25990 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -330,6 +330,13 @@ module ActiveRecord class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition include ColumnMethods + def primary_key(name, type = :primary_key, options = {}) + return super unless type == :uuid + options[:default] ||= 'uuid_generate_v4()' + options[:primary_key] = true + column name, type, options + end + def column(name, type = nil, options = {}) super column = self[name] @@ -344,8 +351,8 @@ module ActiveRecord private - def create_column_definition(base, name, type) - ColumnDefinition.new base, name, type + def create_column_definition(name, type) + ColumnDefinition.new name, type end end @@ -627,19 +634,6 @@ module ActiveRecord @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i end - def add_column_options!(sql, options) - if options[:array] || options[:column].try(:array) - sql << '[]' - end - - 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 def session_auth=(user) clear_cache! @@ -900,8 +894,8 @@ module ActiveRecord $1.strip if $1 end - def create_table_definition - TableDefinition.new(self) + def create_table_definition(name, temporary, options) + TableDefinition.new native_database_types, name, temporary, options end def update_table_definition(table_name, base) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index d3ffee3a8b..50d189d27a 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -458,7 +458,7 @@ module ActiveRecord def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc: alter_table(table_name) do |definition| - definition.columns.delete(definition[column_name]) + definition.remove_column column_name end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 2958d08210..c26fc76515 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -752,7 +752,7 @@ module ActiveRecord def fixtures(*fixture_set_names) if fixture_set_names.first == :all fixture_set_names = Dir["#{fixture_path}/**/*.{yml}"] - fixture_set_names.map! { |f| f[(fixture_path.size + 1)..-5] } + fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] } else fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s } end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index b25d0601cb..f881778591 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -443,7 +443,7 @@ module ActiveRecord real_column = db_columns_with_values[i].first bind_attrs[column] = klass.connection.substitute_at(real_column, i) end - stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(bind_attrs) + stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id_was)).arel.compile_update(bind_attrs) klass.connection.update stmt, 'SQL', db_columns_with_values end end diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 5bd481082e..cde4f29d08 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -27,14 +27,6 @@ module ActiveRecord # Post.unscoped { # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10" # } - # - # It is recommended that you use the block form of unscoped because - # chaining unscoped with +scope+ does not work. Assuming that - # +published+ is a +scope+, the following two statements - # are equal: the +default_scope+ is applied on both. - # - # Post.unscoped.published - # Post.published def unscoped block_given? ? relation.scoping { yield } : relation end diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index 5d71effb1f..568fd45888 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -35,6 +35,18 @@ class ActiveSchemaTest < ActiveRecord::TestCase expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))" assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10}) + + %w(btree hash).each do |type| + expected = "CREATE INDEX `index_people_on_last_name` USING #{type} ON `people` (`last_name`)" + assert_equal expected, add_index(:people, :last_name, :using => type) + end + + expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10))" + assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree) + + expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15))" + assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree) + ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:remove_method, :index_name_exists?) end diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index b965983fec..1844a2e0dc 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -17,7 +17,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase end def test_connect_with_url - run_without_connection do |orig| + run_without_connection do ar_config = ARTest.connection_config['arunit'] skip "This test doesn't work with custom socket location" if ar_config['socket'] diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb index d94bb629a7..e6e54bf20a 100644 --- a/activerecord/test/cases/adapters/mysql/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/schema_test.rb @@ -35,6 +35,25 @@ module ActiveRecord def test_table_exists_wrong_schema assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist") end + + def test_dump_indexes + index_a_name = 'index_post_title' + index_b_name = 'index_post_body' + + table = Post.table_name + + @connection.execute "CREATE INDEX `#{index_a_name}` ON `#{table}` (`title`);" + @connection.execute "CREATE INDEX `#{index_b_name}` USING btree ON `#{table}` (`body`(10));" + + indexes = @connection.indexes(table).sort_by {|i| i.name} + assert_equal 2,indexes.size + + assert_equal :btree, indexes.select{|i| i.name == index_a_name}[0].using + assert_equal :btree, indexes.select{|i| i.name == index_b_name}[0].using + + @connection.execute "DROP INDEX `#{index_a_name}` ON `#{table}`;" + @connection.execute "DROP INDEX `#{index_b_name}` ON `#{table}`;" + end end end end diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index a83399d0cd..273ef978bf 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -35,6 +35,18 @@ class ActiveSchemaTest < ActiveRecord::TestCase expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))" assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10}) + + %w(btree hash).each do |type| + expected = "CREATE INDEX `index_people_on_last_name` USING #{type} ON `people` (`last_name`)" + assert_equal expected, add_index(:people, :last_name, :using => type) + end + + expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10))" + assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree) + + expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15))" + assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree) + ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:remove_method, :index_name_exists?) end @@ -70,8 +82,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase def test_add_timestamps with_real_execute do begin - ActiveRecord::Base.connection.create_table :delete_me do |t| - end + ActiveRecord::Base.connection.create_table :delete_me ActiveRecord::Base.connection.add_timestamps :delete_me assert column_present?('delete_me', 'updated_at', 'datetime') assert column_present?('delete_me', 'created_at', 'datetime') diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb index 94429e772f..78f754d2ce 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -44,6 +44,24 @@ module ActiveRecord assert_match(/database 'foo-bar'/, e.inspect) end + def test_dump_indexes + index_a_name = 'index_post_title' + index_b_name = 'index_post_body' + + table = Post.table_name + + @connection.execute "CREATE INDEX `#{index_a_name}` ON `#{table}` (`title`);" + @connection.execute "CREATE INDEX `#{index_b_name}` USING btree ON `#{table}` (`body`(10));" + + indexes = @connection.indexes(table).sort_by {|i| i.name} + assert_equal 2,indexes.size + + assert_equal :btree, indexes.select{|i| i.name == index_a_name}[0].using + assert_equal :btree, indexes.select{|i| i.name == index_b_name}[0].using + + @connection.execute "DROP INDEX `#{index_a_name}` ON `#{table}`;" + @connection.execute "DROP INDEX `#{index_b_name}` ON `#{table}`;" + end end end end diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index 01c3e6b49b..ac36d0e835 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -32,6 +32,17 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active') assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'") + %w(gin gist hash btree).each do |type| + expected = %(CREATE INDEX "index_people_on_last_name" ON "people" USING #{type} ("last_name")) + assert_equal expected, add_index(:people, :last_name, :using => type) + end + + expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name")) + assert_equal expected, add_index(:people, :last_name, :unique => true, :using => :gist) + + expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name") WHERE state = 'active') + assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'", :using => :gist) + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:remove_method, :index_name_exists?) end diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index c03660957e..6b726ce875 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -81,42 +81,6 @@ module ActiveRecord assert_equal 'SCHEMA', @connection.logged[0][1] end - def test_reconnection_after_simulated_disconnection_with_verify - assert @connection.active? - original_connection_pid = @connection.query('select pg_backend_pid()') - - # Fail with bad connection on next query attempt. - raw_connection = @connection.raw_connection - raw_connection_class = class << raw_connection ; self ; end - raw_connection_class.class_eval <<-CODE, __FILE__, __LINE__ + 1 - def query_fake(*args) - if !( @called ||= false ) - self.stubs(:status).returns(PGconn::CONNECTION_BAD) - @called = true - raise PGError - else - self.unstub(:status) - query_unfake(*args) - end - end - - alias query_unfake query - alias query query_fake - CODE - - begin - @connection.verify! - new_connection_pid = @connection.query('select pg_backend_pid()') - ensure - raw_connection_class.class_eval <<-CODE, __FILE__, __LINE__ + 1 - alias query query_unfake - undef query_fake - CODE - end - - assert_not_equal original_connection_pid, new_connection_pid, "Should have a new underlying connection pid" - end - # Must have with_manual_interventions set to true for this # test to run. # When prompted, restart the PostgreSQL server with the diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index ad98d7c8ce..410119adc1 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -189,6 +189,10 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase assert_cycle('ca' => 'cà', 'ac' => 'àc') end + def test_multiline + assert_cycle("a\nb" => "c\nd") + end + private def assert_cycle hash # test creation diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index cd31900d4e..e8dd188ec8 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -11,16 +11,19 @@ class SchemaTest < ActiveRecord::TestCase INDEX_B_NAME = 'b_index_things_on_different_columns_in_each_schema' INDEX_C_NAME = 'c_index_full_text_search' INDEX_D_NAME = 'd_index_things_on_description_desc' + INDEX_E_NAME = 'e_index_things_on_name_vector' INDEX_A_COLUMN = 'name' INDEX_B_COLUMN_S1 = 'email' INDEX_B_COLUMN_S2 = 'moment' INDEX_C_COLUMN = %q{(to_tsvector('english', coalesce(things.name, '')))} INDEX_D_COLUMN = 'description' + INDEX_E_COLUMN = 'name_vector' COLUMNS = [ 'id integer', 'name character varying(50)', 'email character varying(50)', 'description character varying(100)', + 'name_vector tsvector', 'moment timestamp without time zone default now()' ] PK_TABLE_NAME = 'table_with_pk' @@ -61,6 +64,8 @@ class SchemaTest < ActiveRecord::TestCase @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});" @connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);" @connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);" + @connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});" + @connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});" @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)" @connection.execute "CREATE SEQUENCE #{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}" @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))" @@ -236,15 +241,15 @@ class SchemaTest < ActiveRecord::TestCase end def test_dump_indexes_for_schema_one - do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN) + do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN) end def test_dump_indexes_for_schema_two - do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN) + do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN, INDEX_E_COLUMN) end def test_dump_indexes_for_schema_multiple_schemas_in_search_path - do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN) + do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN) end def test_with_uppercase_index_name @@ -344,15 +349,20 @@ class SchemaTest < ActiveRecord::TestCase @connection.schema_search_path = "'$user', public" end - def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name) + def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name, fourth_index_column_name) with_schema_search_path(this_schema_name) do indexes = @connection.indexes(TABLE_NAME).sort_by {|i| i.name} - assert_equal 3,indexes.size + assert_equal 4,indexes.size do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name) do_dump_index_assertions_for_one_index(indexes[1], INDEX_B_NAME, second_index_column_name) do_dump_index_assertions_for_one_index(indexes[2], INDEX_D_NAME, third_index_column_name) + do_dump_index_assertions_for_one_index(indexes[3], INDEX_E_NAME, fourth_index_column_name) + indexes.select{|i| i.name != INDEX_E_NAME}.each do |index| + assert_equal :btree, index.using + end + assert_equal :gin, indexes.select{|i| i.name == INDEX_E_NAME}[0].using assert_equal :desc, indexes.select{|i| i.name == INDEX_D_NAME}[0].orders[INDEX_D_COLUMN] end end diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index 53002c5265..c0c0e8898c 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -35,6 +35,16 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase @connection.execute 'drop table if exists pg_uuids' end + def test_id_is_uuid + assert_equal :uuid, UUID.columns_hash['id'].type + assert UUID.primary_key + end + + def test_id_has_a_default + u = UUID.create + assert_not_nil u.id + end + def test_auto_create_uuid u = UUID.create u.reload diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb index 21fb111c91..a5a22bc30b 100644 --- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb @@ -90,7 +90,7 @@ protected end def table_indexes_without_name(table) - @connection.indexes('comments_with_index').delete(:name) + @connection.indexes(table).delete(:name) end def row_count(table) diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 3a5dea6f13..d6850215b5 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1174,6 +1174,13 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_no_queries { assert_equal 5, author.posts.size, "should not cache a subset of the association" } end + test "preloading a through association twice does not reset it" do + members = Member.includes(current_membership: :club).includes(:club).to_a + assert_no_queries { + assert_equal 3, members.map(&:current_membership).map(&:club).size + } + end + test "works in combination with order(:symbol)" do author = Author.includes(:posts).references(:posts).order(:name).where('posts.title IS NOT NULL').first assert_equal authors(:bob), author diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index bd2fbaa7db..dbb2f223cd 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -8,6 +8,7 @@ module ActiveRecord def @adapter.native_database_types {:string => "varchar"} end + @viz = @adapter.schema_creation end def test_can_set_coder @@ -35,25 +36,25 @@ module ActiveRecord def test_should_not_include_default_clause_when_default_is_null column = Column.new("title", nil, "varchar(20)") column_def = ColumnDefinition.new( - @adapter, column.name, "string", + column.name, "string", column.limit, column.precision, column.scale, column.default, column.null) - assert_equal "title varchar(20)", column_def.to_sql + assert_equal "title varchar(20)", @viz.accept(column_def) end def test_should_include_default_clause_when_default_is_present column = Column.new("title", "Hello", "varchar(20)") column_def = ColumnDefinition.new( - @adapter, column.name, "string", + column.name, "string", column.limit, column.precision, column.scale, column.default, column.null) - assert_equal %Q{title varchar(20) DEFAULT 'Hello'}, column_def.to_sql + assert_equal %Q{title varchar(20) DEFAULT 'Hello'}, @viz.accept(column_def) end def test_should_specify_not_null_if_null_option_is_false column = Column.new("title", "Hello", "varchar(20)", false) column_def = ColumnDefinition.new( - @adapter, column.name, "string", + column.name, "string", column.limit, column.precision, column.scale, column.default, column.null) - assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, column_def.to_sql + assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, @viz.accept(column_def) end if current_adapter?(:MysqlAdapter) diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index 5379a70034..ac093251a5 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -117,6 +117,7 @@ class CounterCacheTest < ActiveRecord::TestCase test "update other counters on parent destroy" do david, joanna = dog_lovers(:david, :joanna) + joanna = joanna # squelch a warning assert_difference 'joanna.reload.dogs_count', -1 do david.destroy diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 8ad40ec3f4..f6cfee0cb8 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -576,6 +576,15 @@ class LoadAllFixturesTest < ActiveRecord::TestCase end end +class LoadAllFixturesWithPathnameTest < ActiveRecord::TestCase + self.fixture_path = Pathname.new(FIXTURES_ROOT).join('all') + fixtures :all + + def test_all_there + assert_equal %w(developers people tasks), fixture_table_names.sort + end +end + class FasterFixturesTest < ActiveRecord::TestCase fixtures :categories, :authors diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index b936cca875..a29189df05 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -661,6 +661,15 @@ class PersistencesTest < ActiveRecord::TestCase topic.reload assert !topic.approved? assert_equal "The First Topic", topic.title + + assert_raise(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid) do + topic.update_attributes(id: 3, title: "Hm is it possible?") + end + assert_not_equal "Hm is it possible?", Topic.find(3).title + + topic.update_attributes(id: 1234) + assert_nothing_raised { topic.reload } + assert_equal topic.title, Topic.find(1234).title end def test_update! diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 239004a223..6cd89b6227 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -634,7 +634,11 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal [DeveloperCalledJamis.find(developers(:poor_jamis).id)], DeveloperCalledJamis.poor assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis)) + + assert_equal 11, DeveloperCalledJamis.unscoped.length + assert_equal 1, DeveloperCalledJamis.poor.length assert_equal 10, DeveloperCalledJamis.unscoped.poor.length + assert_equal 10, DeveloperCalledJamis.unscoped { DeveloperCalledJamis.poor }.length end def test_default_scope_select_ignored_by_aggregations diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb index b48bdf08e8..998a59c618 100644 --- a/activesupport/lib/active_support/core_ext.rb +++ b/activesupport/lib/active_support/core_ext.rb @@ -1,4 +1,4 @@ -Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].sort.each do |path| +Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].each do |path| next if File.basename(path, '.rb') == 'logger' - require "active_support/core_ext/#{File.basename(path, '.rb')}" + require path end diff --git a/ci/travis.rb b/ci/travis.rb index b03ac4fe35..9029c3f41c 100755 --- a/ci/travis.rb +++ b/ci/travis.rb @@ -109,7 +109,7 @@ end # puts " #{`uname -a`}" # puts " #{`ruby -v`}" # puts " #{`mysql --version`}" -# # puts " #{`pg_config --version`}" +# puts " #{`pg_config --version`}" # puts " SQLite3: #{`sqlite3 -version`}" # `gem env`.each_line {|line| print " #{line}"} # puts " Bundled gems:" @@ -117,7 +117,7 @@ end # puts " Local gems:" # `gem list`.each_line {|line| print " #{line}"} -failures = results.select { |key, value| value == false } +failures = results.select { |key, value| !value } if failures.empty? puts diff --git a/guides/code/getting_started/public/404.html b/guides/code/getting_started/public/404.html index ae7b8649ae..3d287b135d 100644 --- a/guides/code/getting_started/public/404.html +++ b/guides/code/getting_started/public/404.html @@ -2,16 +2,15 @@ <html> <head> <title>The page you were looking for doesn't exist (404)</title> - <style> - body - { - background-color: #efefef; + <style> + body { + background-color: #EFEFEF; color: #2E2F30; text-align: center; - font-family: arial,sans-serif; + font-family: arial, sans-serif; } - div.dialog - { + + div.dialog { width: 25em; margin: 4em auto 0 auto; border: 1px solid #CCC; @@ -24,17 +23,18 @@ background-color: white; padding: 7px 4em 0 4em; } - h1{ + + h1 { font-size: 100%; color: #730E15; line-height: 1.5em; } - body>p - { - width: 33em; + + body > p { + width: 33em; margin: 0 auto 1em; padding: 1em 0; - background-color: #f7f7f7; + background-color: #F7F7F7; border: 1px solid #CCC; border-right-color: #999; border-bottom-color: #999; @@ -42,7 +42,7 @@ border-bottom-right-radius: 4px; border-top-color: #DADADA; color: #666; - box-shadow: 0 3px 8px rgba(50,50,50,0.17); + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); } </style> </head> diff --git a/guides/code/getting_started/public/422.html b/guides/code/getting_started/public/422.html index 0b64eb4ae9..3b946bf4a4 100644 --- a/guides/code/getting_started/public/422.html +++ b/guides/code/getting_started/public/422.html @@ -2,16 +2,15 @@ <html> <head> <title>The change you wanted was rejected (422)</title> - <style> - body - { - background-color: #efefef; + <style> + body { + background-color: #EFEFEF; color: #2E2F30; text-align: center; - font-family: arial,sans-serif; + font-family: arial, sans-serif; } - div.dialog - { + + div.dialog { width: 25em; margin: 4em auto 0 auto; border: 1px solid #CCC; @@ -24,17 +23,18 @@ background-color: white; padding: 7px 4em 0 4em; } - h1{ + + h1 { font-size: 100%; color: #730E15; line-height: 1.5em; } - body>p - { + + body > p { width: 33em; margin: 0 auto 1em; padding: 1em 0; - background-color: #f7f7f7; + background-color: #F7F7F7; border: 1px solid #CCC; border-right-color: #999; border-bottom-color: #999; @@ -42,7 +42,7 @@ border-bottom-right-radius: 4px; border-top-color: #DADADA; color: #666; - box-shadow: 0 3px 8px rgba(50,50,50,0.17); + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); } </style> </head> diff --git a/guides/code/getting_started/public/500.html b/guides/code/getting_started/public/500.html index 9641851e74..ccc4ad5656 100644 --- a/guides/code/getting_started/public/500.html +++ b/guides/code/getting_started/public/500.html @@ -2,16 +2,15 @@ <html> <head> <title>We're sorry, but something went wrong (500)</title> - <style> - body - { - background-color: #efefef; + <style> + body { + background-color: #EFEFEF; color: #2E2F30; text-align: center; - font-family: arial,sans-serif; + font-family: arial, sans-serif; } - div.dialog - { + + div.dialog { width: 25em; margin: 4em auto 0 auto; border: 1px solid #CCC; @@ -24,17 +23,18 @@ background-color: white; padding: 7px 4em 0 4em; } - h1{ + + h1 { font-size: 100%; color: #730E15; line-height: 1.5em; } - body>p - { - width: 33em; + + body > p { + width: 33em; margin: 0 auto 1em; padding: 1em 0; - background-color: #f7f7f7; + background-color: #F7F7F7; border: 1px solid #CCC; border-right-color: #999; border-bottom-color: #999; @@ -42,7 +42,7 @@ border-bottom-right-radius: 4px; border-top-color: #DADADA; color: #666; - box-shadow: 0 3px 8px rgba(50,50,50,0.17); + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); } </style> </head> diff --git a/railties/lib/rails/generators/rails/app/templates/public/404.html b/railties/lib/rails/generators/rails/app/templates/public/404.html index 0ee82d3722..a0daa0c156 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/404.html +++ b/railties/lib/rails/generators/rails/app/templates/public/404.html @@ -2,16 +2,15 @@ <html> <head> <title>The page you were looking for doesn't exist (404)</title> - <style> - body - { - background-color: #efefef; + <style> + body { + background-color: #EFEFEF; color: #2E2F30; text-align: center; - font-family: arial,sans-serif; + font-family: arial, sans-serif; } - div.dialog - { + + div.dialog { width: 25em; margin: 4em auto 0 auto; border: 1px solid #CCC; @@ -24,17 +23,18 @@ background-color: white; padding: 7px 4em 0 4em; } - h1{ + + h1 { font-size: 100%; color: #730E15; line-height: 1.5em; } - body>p - { - width: 33em; + + body > p { + width: 33em; margin: 0 auto 1em; padding: 1em 0; - background-color: #f7f7f7; + background-color: #F7F7F7; border: 1px solid #CCC; border-right-color: #999; border-bottom-color: #999; @@ -42,7 +42,7 @@ border-bottom-right-radius: 4px; border-top-color: #DADADA; color: #666; - box-shadow:0 3px 8px rgba(50,50,50,0.17); + box-shadow:0 3px 8px rgba(50, 50, 50, 0.17); } </style> </head> diff --git a/railties/lib/rails/generators/rails/app/templates/public/422.html b/railties/lib/rails/generators/rails/app/templates/public/422.html index f1f32b83ae..fbb4b84d72 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/422.html +++ b/railties/lib/rails/generators/rails/app/templates/public/422.html @@ -2,16 +2,15 @@ <html> <head> <title>The change you wanted was rejected (422)</title> - <style> - body - { - background-color: #efefef; + <style> + body { + background-color: #EFEFEF; color: #2E2F30; text-align: center; - font-family: arial,sans-serif; + font-family: arial, sans-serif; } - div.dialog - { + + div.dialog { width: 25em; margin: 4em auto 0 auto; border: 1px solid #CCC; @@ -24,17 +23,18 @@ background-color: white; padding: 7px 4em 0 4em; } - h1{ + + h1 { font-size: 100%; color: #730E15; line-height: 1.5em; } - body>p - { + + body > p { width: 33em; margin: 0 auto 1em; padding: 1em 0; - background-color: #f7f7f7; + background-color: #F7F7F7; border: 1px solid #CCC; border-right-color: #999; border-bottom-color: #999; @@ -42,7 +42,7 @@ border-bottom-right-radius: 4px; border-top-color: #DADADA; color: #666; - box-shadow:0 3px 8px rgba(50,50,50,0.17); + box-shadow:0 3px 8px rgba(50, 50, 50, 0.17); } </style> </head> diff --git a/railties/lib/rails/generators/rails/app/templates/public/500.html b/railties/lib/rails/generators/rails/app/templates/public/500.html index 9417de0cc0..e9052d35bf 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/500.html +++ b/railties/lib/rails/generators/rails/app/templates/public/500.html @@ -2,16 +2,15 @@ <html> <head> <title>We're sorry, but something went wrong (500)</title> - <style> - body - { - background-color: #efefef; + <style> + body { + background-color: #EFEFEF; color: #2E2F30; text-align: center; - font-family: arial,sans-serif; + font-family: arial, sans-serif; } - div.dialog - { + + div.dialog { width: 25em; margin: 4em auto 0 auto; border: 1px solid #CCC; @@ -24,17 +23,18 @@ background-color: white; padding: 7px 4em 0 4em; } - h1{ + + h1 { font-size: 100%; color: #730E15; line-height: 1.5em; } - body>p - { - width: 33em; + + body > p { + width: 33em; margin: 0 auto 1em; padding: 1em 0; - background-color: #f7f7f7; + background-color: #F7F7F7; border: 1px solid #CCC; border-right-color: #999; border-bottom-color: #999; @@ -42,7 +42,7 @@ border-bottom-right-radius: 4px; border-top-color: #DADADA; color: #666; - box-shadow:0 3px 8px rgba(50,50,50,0.17); + box-shadow:0 3px 8px rgba(50, 50, 50, 0.17); } </style> </head> diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb index af495bb450..80700a1d64 100644 --- a/railties/test/application/console_test.rb +++ b/railties/test/application/console_test.rb @@ -126,12 +126,6 @@ class FullStackConsoleTest < ActiveSupport::TestCase assert_output "> " end - def kill(pid) - Process.kill('QUIT', pid) - Process.wait(pid) - rescue Errno::ESRCH - end - def spawn_console pid = Process.spawn( "#{app_path}/bin/rails console --sandbox", @@ -148,15 +142,13 @@ class FullStackConsoleTest < ActiveSupport::TestCase write_prompt "Post.count", "=> 0" write_prompt "Post.create" write_prompt "Post.count", "=> 1" - - kill pid + @master.puts "quit" pid = spawn_console write_prompt "Post.count", "=> 0" write_prompt "Post.transaction { Post.create; raise }" write_prompt "Post.count", "=> 0" - ensure - kill pid if pid + @master.puts "quit" end end |