diff options
Diffstat (limited to 'activerecord')
74 files changed, 1852 insertions, 1410 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 0497f6326e..b549f594bb 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,8 +1,71 @@ +* Exit with non-zero status for failed database rake tasks. + + *Jay Hayes* + +* Added `ActiveRecord::Base.to_param` for convenient "pretty" URLs derived from a model's attribute or method. + + Example: + + class User < ActiveRecord::Base + to_param :name + end + + user = User.find_by(name: 'Fancy Pants') + user.id # => 123 + user.to_param # => "123-fancy-pants" + + *Javan Makhmali* + +* Added `ActiveRecord::Base.no_touching`, which allows ignoring touch on models. + + Example: + + Post.no_touching do + Post.first.touch + end + + *Sam Stephenson*, *Damien Mathieu* + +* Prevent the counter cache from being decremented twice when destroying + a record on a `has_many :through` association. + + Fixes #11079. + + *Dmitry Dedov* + +* Unify boolean type casting for `MysqlAdapter` and `Mysql2Adapter`. + `type_cast` will return `1` for `true` and `0` for `false`. + + Fixes #11119. + + *Adam Williams*, *Yves Senn* + +* Fix bug where `has_one` associaton record update result in crash, when replaced with itself. + + Fixes #12834. + + *Denis Redozubov*, *Sergio Cambra* + +* Log bind variables after they are type casted. This makes it more + transparent what values are actually sent to the database. + + irb(main):002:0> Event.find("im-no-integer") + # Before: ... WHERE "events"."id" = $1 LIMIT 1 [["id", "im-no-integer"]] + # After: ... WHERE "events"."id" = $1 LIMIT 1 [["id", 0]] + + *Yves Senn* + +* Fix uninitialized constant `TransactionState` error when `Marshall.load` is used on an Active Record result. + + Fixes #12790. + + *Jason Ayre* + * `.unscope` now removes conditions specified in `default_scope`. *Jon Leighton* -* Added ActiveRecord::QueryMethods#rewhere which will overwrite an existing, named where condition. +* Added `ActiveRecord::QueryMethods#rewhere` which will overwrite an existing, named where condition. Examples: @@ -12,7 +75,7 @@ *DHH* -* Extend ActiveRecord::Base#cache_key to take an optional list of timestamp attributes of which the highest will be used. +* Extend `ActiveRecord::Base#cache_key` to take an optional list of timestamp attributes of which the highest will be used. Example: @@ -21,7 +84,7 @@ *DHH* -* Added ActiveRecord::Base#enum for declaring enum attributes where the values map to integers in the database, but can be queried by name. +* Added `ActiveRecord::Base#enum` for declaring enum attributes where the values map to integers in the database, but can be queried by name. Example: @@ -34,23 +97,23 @@ # conversation.update! status: 0 conversation.active! conversation.active? # => true - conversation.status # => :active + conversation.status # => "active" # conversation.update! status: 1 conversation.archived! conversation.archived? # => true - conversation.status # => :archived + conversation.status # => "archived" # conversation.update! status: 1 conversation.status = :archived *DHH* -* ActiveRecord::Base#attribute_for_inspect now truncates long arrays (more than 10 elements) +* `ActiveRecord::Base#attribute_for_inspect` now truncates long arrays (more than 10 elements). *Jan Bernacki* -* Allow for the name of the schema_migrations table to be configured. +* Allow for the name of the `schema_migrations` table to be configured. *Jerad Phelps* @@ -94,6 +157,7 @@ *Severin Schoepke* * `ActiveRecord::Store` works together with PG `hstore` columns. + Fixes #12452. *Yves Senn* diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 7a2c5c8bf2..cbac2ef3c6 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -45,6 +45,7 @@ module ActiveRecord autoload :Migrator, 'active_record/migration' autoload :ModelSchema autoload :NestedAttributes + autoload :NoTouching autoload :Persistence autoload :QueryCache autoload :Querying 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 56331bbb0b..31b8d27892 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -163,7 +163,7 @@ module ActiveRecord delete_through_records(records) - if source_reflection.options[:counter_cache] + if source_reflection.options[:counter_cache] && method != :destroy counter = source_reflection.counter_cache_column klass.decrement_counter counter, records.map(&:id) end diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 0008600418..944caacab6 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -26,11 +26,13 @@ module ActiveRecord load_target return self.target if !(target || record) - if (target != record) || record.changed? + + assigning_another_record = target != record + if assigning_another_record || record.changed? save &&= owner.persisted? transaction_if(save) do - remove_target!(options[:dependent]) if target && !target.destroyed? + remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record if record set_owner_attributes(record) diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 5701804168..d484659190 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -24,6 +24,9 @@ module ActiveRecord # serialized object must be of that class on retrieval or # <tt>SerializationTypeMismatch</tt> will be raised. # + # A notable side effect of serialized attributes is that the model will + # be updated on every save, even if it is not dirty. + # # ==== Parameters # # * +attr_name+ - The field name that should be serialized. diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 69a9eabefb..e05e22ebb0 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -295,6 +295,7 @@ module ActiveRecord #:nodoc: extend Delegation::DelegateCache include Persistence + include NoTouching include ReadonlyAttributes include ModelSchema include Inheritance diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb new file mode 100644 index 0000000000..7c330a2f25 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -0,0 +1,83 @@ +module ActiveRecord + module ConnectionAdapters + class AbstractAdapter + class SchemaCreation # :nodoc: + def initialize(conn) + @conn = conn + @cache = {} + end + + def accept(o) + m = @cache[o.class] ||= "visit_#{o.class.name.split('::').last}" + send m, o + 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 + + 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_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[:first] = o.first + column_options[:after] = o.after + 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!(sql, options) + sql << " DEFAULT #{@conn.quote(options[:default], options[:column])}" if options_include_default?(options) + # must explicitly check for :null to allow change_column to work on migrations + if options[:null] == false + sql << " NOT NULL" + end + if options[:auto_increment] == true + sql << " AUTO_INCREMENT" + end + sql + end + + def options_include_default?(options) + options.include?(:default) && !(options[:null] == false && options[:default].nil?) + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index cbe563676b..8aa1ce5c04 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -4,6 +4,7 @@ require 'bigdecimal/util' require 'active_support/core_ext/benchmark' require 'active_record/connection_adapters/schema_cache' require 'active_record/connection_adapters/abstract/schema_dumper' +require 'active_record/connection_adapters/abstract/schema_creation' require 'monitor' module ActiveRecord @@ -16,6 +17,7 @@ module ActiveRecord autoload_at 'active_record/connection_adapters/abstract/schema_definitions' do autoload :IndexDefinition autoload :ColumnDefinition + autoload :ChangeColumnDefinition autoload :TableDefinition autoload :Table autoload :AlterTable @@ -40,6 +42,7 @@ module ActiveRecord autoload :ClosedTransaction autoload :RealTransaction autoload :SavepointTransaction + autoload :TransactionState end # Active Record supports multiple database systems. AbstractAdapter and @@ -105,84 +108,6 @@ module ActiveRecord true 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 - - 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 - - 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_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[:first] = o.first - column_options[:after] = o.after - 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!(sql, options) - sql << " DEFAULT #{@conn.quote(options[:default], options[:column])}" if options_include_default?(options) - # must explicitly check for :null to allow change_column to work on migrations - if options[:null] == false - sql << " NOT NULL" - end - if options[:auto_increment] == true - sql << " AUTO_INCREMENT" - end - sql - end - - def options_include_default?(options) - options.include?(:default) && !(options[:null] == false && options[:default].nil?) - end - end - def schema_creation SchemaCreation.new self end 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 9a88be5219..dcbc3466b2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -206,6 +206,12 @@ module ActiveRecord true end + def type_cast(value, column) + return super unless value == true || value == false + + value ? 1 : 0 + end + # MySQL 4 technically support transaction isolation, but it is affected by a bug # where the transaction level gets persisted for the whole session: # diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index d61847506d..c4dcba0501 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -160,12 +160,6 @@ module ActiveRecord # QUOTING ================================================== - def type_cast(value, column) - return super unless value == true || value == false - - value ? 1 : 0 - end - def quote_string(string) #:nodoc: @connection.quote(string) end @@ -474,7 +468,11 @@ module ActiveRecord def exec_stmt(sql, name, binds) cache = {} - log(sql, name, binds) do + type_casted_binds = binds.map { |col, val| + [col, type_cast(val, col)] + } + + log(sql, name, type_casted_binds) do if binds.empty? stmt = @connection.prepare(sql) else @@ -485,7 +483,7 @@ module ActiveRecord end begin - stmt.execute(*binds.map { |col, val| type_cast(val, col) }) + stmt.execute(*type_casted_binds.map { |_, val| val }) rescue Mysql::Error => e # Older versions of MySQL leave the prepared statement in a bad # place when an error occurs. To support older mysql versions, we diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index e9daa5d7ff..c1f978a081 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -86,8 +86,11 @@ module ActiveRecord case value when Range - return super(value, column) unless /range$/ =~ column.sql_type - PostgreSQLColumn.range_to_string(value) + if /range$/ =~ column.sql_type + PostgreSQLColumn.range_to_string(value) + else + super(value, column) + end when NilClass if column.array && array_member 'NULL' @@ -101,12 +104,21 @@ module ActiveRecord when 'point' then PostgreSQLColumn.point_to_string(value) when 'json' then PostgreSQLColumn.json_to_string(value) else - return super(value, column) unless column.array - PostgreSQLColumn.array_to_string(value, column, self) + if column.array + PostgreSQLColumn.array_to_string(value, column, self) + else + super(value, column) + end end when String - return super(value, column) unless 'bytea' == column.sql_type - { :value => value, :format => 1 } + if 'bytea' == column.sql_type + # Return a bind param hash with format as binary. + # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc + # for more information + { value: value, format: 1 } + else + super(value, column) + end when Hash case column.sql_type when 'hstore' then PostgreSQLColumn.hstore_to_string(value) @@ -114,8 +126,11 @@ module ActiveRecord else super(value, column) end when IPAddr - return super(value, column) unless ['inet','cidr'].include? column.sql_type - PostgreSQLColumn.cidr_to_string(value) + if %w(inet cidr).include? column.sql_type + PostgreSQLColumn.cidr_to_string(value) + else + super(value, column) + end else super(value, column) end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 3668aecd4b..adeb57d913 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -631,7 +631,6 @@ module ActiveRecord true end - # Returns true. def supports_explain? true end @@ -783,11 +782,12 @@ module ActiveRecord def exec_cache(sql, name, binds) stmt_key = prepare_statement(sql) + type_casted_binds = binds.map { |col, val| + [col, type_cast(val, col)] + } - log(sql, name, binds, stmt_key) do - @connection.send_query_prepared(stmt_key, binds.map { |col, val| - type_cast(val, col) - }) + log(sql, name, type_casted_binds, stmt_key) do + @connection.send_query_prepared(stmt_key, type_casted_binds.map { |_, val| val }) @connection.block @connection.get_last_result end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index e5ad08b6b0..2cf1015f2c 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -141,14 +141,12 @@ module ActiveRecord 'SQLite' end - # Returns true def supports_ddl_transactions? true end - # Returns true if SQLite version is '3.6.8' or greater, false otherwise. def supports_savepoints? - sqlite_version >= '3.6.8' + true end # Returns true, since this connection adapter supports prepared statement @@ -162,7 +160,6 @@ module ActiveRecord true end - # Returns true. def supports_primary_key? #:nodoc: true end @@ -171,7 +168,6 @@ module ActiveRecord true end - # Returns true def supports_add_column? true end @@ -193,11 +189,6 @@ module ActiveRecord @statements.clear end - # Returns true - def supports_count_distinct? #:nodoc: - true - end - def supports_index_sort_order? true end @@ -218,7 +209,6 @@ module ActiveRecord @connection.encoding.to_s end - # Returns true. def supports_explain? true end @@ -291,8 +281,11 @@ module ActiveRecord end def exec_query(sql, name = nil, binds = []) - log(sql, name, binds) do + type_casted_binds = binds.map { |col, val| + [col, type_cast(val, col)] + } + log(sql, name, type_casted_binds) do # Don't cache statements if they are not prepared if without_prepared_statement?(binds) stmt = @connection.prepare(sql) @@ -307,9 +300,7 @@ module ActiveRecord stmt = cache[:stmt] cols = cache[:cols] ||= stmt.columns stmt.reset! - stmt.bind_params binds.map { |col, val| - type_cast(val, col) - } + stmt.bind_params type_casted_binds.map { |_, val| val } end ActiveRecord::Result.new(cols, stmt.to_a) diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index d93c26c130..96b5686ae0 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -109,7 +109,7 @@ module ActiveRecord elsif abstract_class? "#{super}(abstract)" elsif !connected? - "#{super}(no database connection)" + "#{super} (call '#{super}.connection' to establish a connection)" elsif table_exists? attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', ' "#{super}(#{attr_list})" diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index a70638f3df..5fcc0382d8 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -5,20 +5,18 @@ module ActiveRecord # enum status: [ :active, :archived ] # end # - # Conversation::STATUS # => { active: 0, archived: 1 } - # # # conversation.update! status: 0 # conversation.active! # conversation.active? # => true - # conversation.status # => :active + # conversation.status # => "active" # # # conversation.update! status: 1 # conversation.archived! # conversation.archived? # => true - # conversation.status # => :archived + # conversation.status # => "archived" # # # conversation.update! status: 1 - # conversation.status = :archived + # conversation.status = "archived" # # You can set the default value from the database declaration, like: # @@ -33,17 +31,26 @@ module ActiveRecord # class Conversation < ActiveRecord::Base # enum status: { active: 0, archived: 1 } # end + # + # In rare circumstances you might need to access the mapping directly. + # The mappings are exposed through a constant with the attributes name: + # + # Conversation::STATUS # => { "active" => 0, "archived" => 1 } + # + # Use that constant when you need to know the ordinal value of an enum: + # + # Conversation.where("status <> ?", Conversation::STATUS[:archived]) module Enum def enum(definitions) klass = self definitions.each do |name, values| - enum_values = {} + # DIRECTION = { } + enum_values = _enum_methods_module.const_set name.to_s.upcase, ActiveSupport::HashWithIndifferentAccess.new name = name.to_sym _enum_methods_module.module_eval do # def direction=(value) self[:direction] = DIRECTION[value] end define_method("#{name}=") { |value| - value = value.to_s unless enum_values.has_key?(value) raise ArgumentError, "'#{value}' is not a valid #{name}" end @@ -55,7 +62,7 @@ module ActiveRecord pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index pairs.each do |value, i| - enum_values[value.to_s] = i + enum_values[value] = i # scope :incoming, -> { where direction: 0 } klass.scope value, -> { klass.where name => i } @@ -64,7 +71,7 @@ module ActiveRecord define_method("#{value}?") { self[name] == i } # def incoming! update! direction: :incoming end - define_method("#{value}!") { update! name => value.to_sym } + define_method("#{value}!") { update! name => value } end end end diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb index f88c8e30e6..27576b1e61 100644 --- a/activerecord/lib/active_record/integration.rb +++ b/activerecord/lib/active_record/integration.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/string/filters' + module ActiveRecord module Integration extend ActiveSupport::Concern @@ -65,5 +67,45 @@ module ActiveRecord "#{self.class.model_name.cache_key}/#{id}" end end + + module ClassMethods + # Defines your model's +to_param+ method to generate "pretty" URLs + # using +method_name+, which can be any attribute or method that + # responds to +to_s+. + # + # class User < ActiveRecord::Base + # to_param :name + # end + # + # user = User.find_by(name: 'Fancy Pants') + # user.id # => 123 + # user_path(user) # => "/users/123-fancy-pants" + # + # Values longer than 20 characters will be truncated. The value + # is truncated word by word. + # + # user = User.find_by(name: 'David HeinemeierHansson') + # user.id # => 125 + # user_path(user) # => "/users/125-david" + # + # Because the generated param begins with the record's +id+, it is + # suitable for passing to +find+. In a controller, for example: + # + # params[:id] # => "123-fancy-pants" + # User.find(params[:id]).id # => 123 + def to_param(method_name = nil) + if method_name.nil? + super() + else + define_method :to_param do + if (default = super()) && (result = send(method_name).to_s).present? + "#{default}-#{result.squish.truncate(20, separator: /\s/, omission: nil).parameterize}" + else + default + end + end + end + end + end end end diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index 927fbab8f0..654ef21b07 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -23,6 +23,8 @@ module ActiveRecord def render_bind(column, value) if column if column.binary? + # This specifically deals with the PG adapter that casts bytea columns into a Hash. + value = value[:value] if value.is_a?(Hash) value = "<#{value.bytesize} bytes of binary data>" end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 5224a6b67c..d010f23517 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -32,7 +32,11 @@ module ActiveRecord class PendingMigrationError < ActiveRecordError#:nodoc: def initialize - super("Migrations are pending; run 'bin/rake db:migrate RAILS_ENV=#{::Rails.env}' to resolve this issue.") + if defined?(Rails) + super("Migrations are pending; run 'bin/rake db:migrate RAILS_ENV=#{::Rails.env}' to resolve this issue.") + else + super("Migrations are pending; run 'bin/rake db:migrate' to resolve this issue.") + end end end diff --git a/activerecord/lib/active_record/no_touching.rb b/activerecord/lib/active_record/no_touching.rb new file mode 100644 index 0000000000..dbf4564ae5 --- /dev/null +++ b/activerecord/lib/active_record/no_touching.rb @@ -0,0 +1,52 @@ +module ActiveRecord + # = Active Record No Touching + module NoTouching + extend ActiveSupport::Concern + + module ClassMethods + # Lets you selectively disable calls to `touch` for the + # duration of a block. + # + # ==== Examples + # ActiveRecord::Base.no_touching do + # Project.first.touch # does nothing + # Message.first.touch # does nothing + # end + # + # Project.no_touching do + # Project.first.touch # does nothing + # Message.first.touch # works, but does not touch the associated project + # end + # + def no_touching(&block) + NoTouching.apply_to(self, &block) + end + end + + class << self + def apply_to(klass) #:nodoc: + klasses.push(klass) + yield + ensure + klasses.pop + end + + def applied_to?(klass) #:nodoc: + klasses.any? { |k| k >= klass } + end + + private + def klasses + Thread.current[:no_touching_classes] ||= [] + end + end + + def no_touching? + NoTouching.applied_to?(self.class) + end + + def touch(*) + super unless no_touching? + end + end +end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 52b3d3e5e6..3ca66223b6 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -8,31 +8,47 @@ db_namespace = namespace :db do namespace :create do task :all => :load_config do - ActiveRecord::Tasks::DatabaseTasks.create_all + begin + ActiveRecord::Tasks::DatabaseTasks.create_all + rescue + exit(1) + end end end desc 'Create the database from DATABASE_URL or config/database.yml for the current Rails.env (use db:create:all to create all databases in the config)' task :create => [:load_config] do - if ENV['DATABASE_URL'] - ActiveRecord::Tasks::DatabaseTasks.create_database_url - else - ActiveRecord::Tasks::DatabaseTasks.create_current + begin + if ENV['DATABASE_URL'] + ActiveRecord::Tasks::DatabaseTasks.create_database_url + else + ActiveRecord::Tasks::DatabaseTasks.create_current + end + rescue + exit(1) end end namespace :drop do task :all => :load_config do - ActiveRecord::Tasks::DatabaseTasks.drop_all + begin + ActiveRecord::Tasks::DatabaseTasks.drop_all + rescue + exit(1) + end end end desc 'Drops the database using DATABASE_URL or the current Rails.env (use db:drop:all to drop all databases)' task :drop => [:load_config] do - if ENV['DATABASE_URL'] - ActiveRecord::Tasks::DatabaseTasks.drop_database_url - else - ActiveRecord::Tasks::DatabaseTasks.drop_current + begin + if ENV['DATABASE_URL'] + ActiveRecord::Tasks::DatabaseTasks.drop_database_url + else + ActiveRecord::Tasks::DatabaseTasks.drop_current + end + rescue + exit(1) end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 60f2726a6e..ff088818ee 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -244,7 +244,13 @@ module ActiveRecord def empty? return @records.empty? if loaded? - limit_value == 0 ? true : !exists? + if limit_value == 0 + true + else + # FIXME: This count is not compatible with #select('authors.*') or other select narrows + c = count + c.respond_to?(:zero?) ? c.zero? : c.empty? + end end # Returns true if there are any records. diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 3a02bf90e9..d91d6367a3 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -6,11 +6,12 @@ module ActiveRecord # If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key # is an integer, find by id coerces its arguments using +to_i+. # - # Person.find(1) # returns the object for ID = 1 - # Person.find("1") # returns the object for ID = 1 - # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6) - # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17) - # Person.find([1]) # returns an array for the object with ID = 1 + # Person.find(1) # returns the object for ID = 1 + # Person.find("1") # returns the object for ID = 1 + # Person.find("31-sarah") # returns the object for ID = 31 + # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6) + # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17) + # Person.find([1]) # returns an array for the object with ID = 1 # Person.where("administrator = 1").order("created_on DESC").find(1) # # <tt>ActiveRecord::RecordNotFound</tt> will be raised if one or more ids are not found. diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index 1dc3bf3f12..469451e2f4 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -83,9 +83,10 @@ module ActiveRecord end def initialize_copy(other) - @columns = columns.dup - @rows = rows.dup - @hash_rows = nil + @columns = columns.dup + @rows = rows.dup + @column_types = column_types.dup + @hash_rows = nil end private diff --git a/activerecord/lib/active_record/runtime_registry.rb b/activerecord/lib/active_record/runtime_registry.rb index 63e6738622..9d605b826a 100644 --- a/activerecord/lib/active_record/runtime_registry.rb +++ b/activerecord/lib/active_record/runtime_registry.rb @@ -13,5 +13,10 @@ module ActiveRecord extend ActiveSupport::PerThreadRegistry attr_accessor :connection_handler, :sql_runtime, :connection_id + + [:connection_handler, :sql_runtime, :connection_id].each do |val| + class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__ + class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__ + end end end diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index b841b977fc..186a737561 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -15,6 +15,11 @@ module ActiveRecord # You can set custom coder to encode/decode your serialized attributes to/from different formats. # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+. # + # NOTE - If you are using PostgreSQL specific columns like +hstore+ or +json+ there is no need for + # the serialization provided by +store+. Simply use +store_accessor+ instead to generate + # the accessor methods. Be aware that these columns use a string keyed hash and do not allow access + # using a symbol. + # # Examples: # # class User < ActiveRecord::Base diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index be7d496d15..0b6a49ac85 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -69,9 +69,11 @@ module ActiveRecord class_for_adapter(configuration['adapter']).new(*arguments).create rescue DatabaseAlreadyExists $stderr.puts "#{configuration['database']} already exists" + raise rescue Exception => error $stderr.puts error, *(error.backtrace) $stderr.puts "Couldn't create database for #{configuration.inspect}" + raise end def create_all @@ -95,6 +97,7 @@ module ActiveRecord rescue Exception => error $stderr.puts error, *(error.backtrace) $stderr.puts "Couldn't drop #{configuration['database']}" + raise end def drop_all diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index dd355e8d0c..595edc6263 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -195,22 +195,20 @@ module ActiveRecord Klass.remove_connection end - test "transaction state is reset after a reconnect" do - skip "in-memory db doesn't allow reconnect" if in_memory_db? - - @connection.begin_transaction - assert @connection.transaction_open? - @connection.reconnect! - assert !@connection.transaction_open? - end - - test "transaction state is reset after a disconnect" do - skip "in-memory db doesn't allow disconnect" if in_memory_db? + unless in_memory_db? + test "transaction state is reset after a reconnect" do + @connection.begin_transaction + assert @connection.transaction_open? + @connection.reconnect! + assert !@connection.transaction_open? + end - @connection.begin_transaction - assert @connection.transaction_open? - @connection.disconnect! - assert !@connection.transaction_open? + test "transaction state is reset after a disconnect" do + @connection.begin_transaction + assert @connection.transaction_open? + @connection.disconnect! + assert !@connection.transaction_open? + end end end end diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index 7cc71c8929..a1b41b6991 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -16,15 +16,15 @@ class MysqlConnectionTest < ActiveRecord::TestCase end end - def test_connect_with_url - run_without_connection do - ar_config = ARTest.connection_config['arunit'] - - skip "This test doesn't work with custom socket location" if ar_config['socket'] - - url = "mysql://#{ar_config["username"]}@localhost/#{ar_config["database"]}" - Klass.establish_connection(url) - assert_equal ar_config['database'], Klass.connection.current_database + unless ARTest.connection_config['arunit']['socket'] + def test_connect_with_url + run_without_connection do + ar_config = ARTest.connection_config['arunit'] + + url = "mysql://#{ar_config["username"]}@localhost/#{ar_config["database"]}" + Klass.establish_connection(url) + assert_equal ar_config['database'], Klass.connection.current_database + end end end diff --git a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb index 83de90f179..209a0cf464 100644 --- a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb @@ -3,20 +3,20 @@ require 'cases/helper' module ActiveRecord::ConnectionAdapters class MysqlAdapter class StatementPoolTest < ActiveRecord::TestCase - def test_cache_is_per_pid - return skip('must support fork') unless Process.respond_to?(:fork) + if Process.respond_to?(:fork) + def test_cache_is_per_pid + cache = StatementPool.new nil, 10 + cache['foo'] = 'bar' + assert_equal 'bar', cache['foo'] - cache = StatementPool.new nil, 10 - cache['foo'] = 'bar' - assert_equal 'bar', cache['foo'] + pid = fork { + lookup = cache['foo']; + exit!(!lookup) + } - pid = fork { - lookup = cache['foo']; - exit!(!lookup) - } - - Process.waitpid pid - assert $?.success?, 'process should exit successfully' + Process.waitpid pid + assert $?.success?, 'process should exit successfully' + end end end end diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb new file mode 100644 index 0000000000..267aa232d9 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb @@ -0,0 +1,91 @@ +require "cases/helper" + +class Mysql2BooleanTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + class BooleanType < ActiveRecord::Base + self.table_name = "mysql_booleans" + end + + setup do + @connection = ActiveRecord::Base.connection + @connection.create_table("mysql_booleans") do |t| + t.boolean "archived" + t.string "published", limit: 1 + end + BooleanType.reset_column_information + + @emulate_booleans = ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans + end + + teardown do + emulate_booleans @emulate_booleans + @connection.drop_table "mysql_booleans" + end + + test "column type with emulated booleans" do + emulate_booleans true + + assert_equal :boolean, boolean_column.type + assert_equal :string, string_column.type + end + + test "column type without emulated booleans" do + emulate_booleans false + + assert_equal :integer, boolean_column.type + assert_equal :string, string_column.type + end + + test "test type casting with emulated booleans" do + emulate_booleans true + + boolean = BooleanType.create!(archived: true, published: true) + attributes = boolean.reload.attributes_before_type_cast + + assert_equal 1, attributes["archived"] + assert_equal "1", attributes["published"] + + assert_equal 1, @connection.type_cast(true, boolean_column) + assert_equal 1, @connection.type_cast(true, string_column) + end + + test "test type casting without emulated booleans" do + emulate_booleans false + + boolean = BooleanType.create!(archived: true, published: true) + attributes = boolean.reload.attributes_before_type_cast + + assert_equal 1, attributes["archived"] + assert_equal "1", attributes["published"] + + assert_equal 1, @connection.type_cast(true, boolean_column) + assert_equal 1, @connection.type_cast(true, string_column) + end + + test "with booleans stored as 1 and 0" do + @connection.execute "INSERT INTO mysql_booleans(archived, published) VALUES(1, '1')" + boolean = BooleanType.first + assert_equal true, boolean.archived + assert_equal "1", boolean.published + end + + test "with booleans stored as t" do + @connection.execute "INSERT INTO mysql_booleans(published) VALUES('t')" + boolean = BooleanType.first + assert_equal "t", boolean.published + end + + def boolean_column + BooleanType.columns.find { |c| c.name == 'archived' } + end + + def string_column + BooleanType.columns.find { |c| c.name == 'published' } + end + + def emulate_booleans(value) + ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = value + BooleanType.reset_column_information + end +end diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 81aa977c59..90cca7d3e6 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -98,33 +98,33 @@ module ActiveRecord # you know the incantation to do that. # To restart PostgreSQL 9.1 on OS X, installed via MacPorts, ... # sudo su postgres -c "pg_ctl restart -D /opt/local/var/db/postgresql91/defaultdb/ -m fast" - def test_reconnection_after_actual_disconnection_with_verify - skip "with_manual_interventions is false in configuration" unless ARTest.config['with_manual_interventions'] + if ARTest.config['with_manual_interventions'] + def test_reconnection_after_actual_disconnection_with_verify + original_connection_pid = @connection.query('select pg_backend_pid()') - original_connection_pid = @connection.query('select pg_backend_pid()') + # Sanity check. + assert @connection.active? - # Sanity check. - assert @connection.active? + puts 'Kill the connection now (e.g. by restarting the PostgreSQL ' + + 'server with the "-m fast" option) and then press enter.' + $stdin.gets - puts 'Kill the connection now (e.g. by restarting the PostgreSQL ' + - 'server with the "-m fast" option) and then press enter.' - $stdin.gets + @connection.verify! - @connection.verify! + assert @connection.active? - assert @connection.active? + # If we get no exception here, then either we re-connected successfully, or + # we never actually got disconnected. + new_connection_pid = @connection.query('select pg_backend_pid()') - # If we get no exception here, then either we re-connected successfully, or - # we never actually got disconnected. - new_connection_pid = @connection.query('select pg_backend_pid()') + assert_not_equal original_connection_pid, new_connection_pid, + "umm -- looks like you didn't break the connection, because we're still " + + "successfully querying with the same connection pid." - assert_not_equal original_connection_pid, new_connection_pid, - "umm -- looks like you didn't break the connection, because we're still " + - "successfully querying with the same connection pid." - - # Repair all fixture connections so other tests won't break. - @fixture_connections.each do |c| - c.verify! + # Repair all fixture connections so other tests won't break. + @fixture_connections.each do |c| + c.verify! + end end end diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index c5ff8cb609..01de202d11 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -184,16 +184,6 @@ _SQL assert_equal :text, @first_array.column_for_attribute(:nicknames).type end - def test_data_type_of_range_types - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - assert_equal :daterange, @first_range.column_for_attribute(:date_range).type - assert_equal :numrange, @first_range.column_for_attribute(:num_range).type - assert_equal :tsrange, @first_range.column_for_attribute(:ts_range).type - assert_equal :tstzrange, @first_range.column_for_attribute(:tstz_range).type - assert_equal :int4range, @first_range.column_for_attribute(:int4_range).type - assert_equal :int8range, @first_range.column_for_attribute(:int8_range).type - end - def test_data_type_of_tsvector_types assert_equal :tsvector, @first_tsvector.column_for_attribute(:text_vector).type end @@ -240,57 +230,185 @@ _SQL assert_equal "'text' 'vector'", @first_tsvector.text_vector end - def test_int4range_values - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - assert_equal 1...11, @first_range.int4_range - assert_equal 2...10, @second_range.int4_range - assert_equal 2...Float::INFINITY, @third_range.int4_range - assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int4_range) - assert_nil @empty_range.int4_range - end + if ActiveRecord::Base.connection.supports_ranges? + def test_data_type_of_range_types + assert_equal :daterange, @first_range.column_for_attribute(:date_range).type + assert_equal :numrange, @first_range.column_for_attribute(:num_range).type + assert_equal :tsrange, @first_range.column_for_attribute(:ts_range).type + assert_equal :tstzrange, @first_range.column_for_attribute(:tstz_range).type + assert_equal :int4range, @first_range.column_for_attribute(:int4_range).type + assert_equal :int8range, @first_range.column_for_attribute(:int8_range).type + end - def test_int8range_values - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - assert_equal 10...101, @first_range.int8_range - assert_equal 11...100, @second_range.int8_range - assert_equal 11...Float::INFINITY, @third_range.int8_range - assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int8_range) - assert_nil @empty_range.int8_range - end + def test_int4range_values + assert_equal 1...11, @first_range.int4_range + assert_equal 2...10, @second_range.int4_range + assert_equal 2...Float::INFINITY, @third_range.int4_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int4_range) + assert_nil @empty_range.int4_range + end - def test_daterange_values - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 5), @first_range.date_range - assert_equal Date.new(2012, 1, 3)...Date.new(2012, 1, 4), @second_range.date_range - assert_equal Date.new(2012, 1, 3)...Float::INFINITY, @third_range.date_range - assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range) - assert_nil @empty_range.date_range - end + def test_int8range_values + assert_equal 10...101, @first_range.int8_range + assert_equal 11...100, @second_range.int8_range + assert_equal 11...Float::INFINITY, @third_range.int8_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int8_range) + assert_nil @empty_range.int8_range + end - def test_numrange_values - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - assert_equal BigDecimal.new('0.1')..BigDecimal.new('0.2'), @first_range.num_range - assert_equal BigDecimal.new('0.1')...BigDecimal.new('0.2'), @second_range.num_range - assert_equal BigDecimal.new('0.1')...BigDecimal.new('Infinity'), @third_range.num_range - assert_equal BigDecimal.new('-Infinity')...BigDecimal.new('Infinity'), @fourth_range.num_range - assert_nil @empty_range.num_range - end + def test_daterange_values + assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 5), @first_range.date_range + assert_equal Date.new(2012, 1, 3)...Date.new(2012, 1, 4), @second_range.date_range + assert_equal Date.new(2012, 1, 3)...Float::INFINITY, @third_range.date_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range) + assert_nil @empty_range.date_range + end - def test_tsrange_values - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - tz = ::ActiveRecord::Base.default_timezone - assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)..Time.send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range - assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range - assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range) - assert_nil @empty_range.ts_range - end + def test_numrange_values + assert_equal BigDecimal.new('0.1')..BigDecimal.new('0.2'), @first_range.num_range + assert_equal BigDecimal.new('0.1')...BigDecimal.new('0.2'), @second_range.num_range + assert_equal BigDecimal.new('0.1')...BigDecimal.new('Infinity'), @third_range.num_range + assert_equal BigDecimal.new('-Infinity')...BigDecimal.new('Infinity'), @fourth_range.num_range + assert_nil @empty_range.num_range + end + + def test_tsrange_values + tz = ::ActiveRecord::Base.default_timezone + assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)..Time.send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range + assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range) + assert_nil @empty_range.ts_range + end + + def test_tstzrange_values + assert_equal Time.parse('2010-01-01 09:30:00 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), @first_range.tstz_range + assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Time.parse('2011-01-01 17:30:00 UTC'), @second_range.tstz_range + assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range) + assert_nil @empty_range.tstz_range + end - def test_tstzrange_values - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - assert_equal Time.parse('2010-01-01 09:30:00 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), @first_range.tstz_range - assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Time.parse('2011-01-01 17:30:00 UTC'), @second_range.tstz_range - assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range) - assert_nil @empty_range.tstz_range + def test_create_tstzrange + tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT') + range = PostgresqlRange.new(:tstz_range => tstzrange) + assert range.save + assert range.reload + assert_equal range.tstz_range, tstzrange + assert_equal range.tstz_range, Time.parse('2010-01-01 13:30:00 UTC')...Time.parse('2011-02-02 19:30:00 UTC') + end + + def test_update_tstzrange + new_tstzrange = Time.parse('2010-01-01 14:30:00 CDT')...Time.parse('2011-02-02 14:30:00 CET') + @first_range.tstz_range = new_tstzrange + assert @first_range.save + assert @first_range.reload + assert_equal new_tstzrange, @first_range.tstz_range + @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000') + assert @first_range.save + assert @first_range.reload + assert_nil @first_range.tstz_range + end + + def test_create_tsrange + tz = ::ActiveRecord::Base.default_timezone + tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0) + range = PostgresqlRange.new(:ts_range => tsrange) + assert range.save + assert range.reload + assert_equal range.ts_range, tsrange + end + + def test_update_tsrange + tz = ::ActiveRecord::Base.default_timezone + new_tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0) + @first_range.ts_range = new_tsrange + assert @first_range.save + assert @first_range.reload + assert_equal new_tsrange, @first_range.ts_range + @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0) + assert @first_range.save + assert @first_range.reload + assert_nil @first_range.ts_range + end + + def test_create_numrange + numrange = BigDecimal.new('0.5')...BigDecimal.new('1') + range = PostgresqlRange.new(:num_range => numrange) + assert range.save + assert range.reload + assert_equal range.num_range, numrange + end + + def test_update_numrange + new_numrange = BigDecimal.new('0.5')...BigDecimal.new('1') + @first_range.num_range = new_numrange + assert @first_range.save + assert @first_range.reload + assert_equal new_numrange, @first_range.num_range + @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5') + assert @first_range.save + assert @first_range.reload + assert_nil @first_range.num_range + end + + def test_create_daterange + daterange = Range.new(Date.new(2012, 1, 1), Date.new(2013, 1, 1), true) + range = PostgresqlRange.new(:date_range => daterange) + assert range.save + assert range.reload + assert_equal range.date_range, daterange + end + + def test_update_daterange + new_daterange = Date.new(2012, 2, 3)...Date.new(2012, 2, 10) + @first_range.date_range = new_daterange + assert @first_range.save + assert @first_range.reload + assert_equal new_daterange, @first_range.date_range + @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3) + assert @first_range.save + assert @first_range.reload + assert_nil @first_range.date_range + end + + def test_create_int4range + int4range = Range.new(3, 50, true) + range = PostgresqlRange.new(:int4_range => int4range) + assert range.save + assert range.reload + assert_equal range.int4_range, int4range + end + + def test_update_int4range + new_int4range = 6...10 + @first_range.int4_range = new_int4range + assert @first_range.save + assert @first_range.reload + assert_equal new_int4range, @first_range.int4_range + @first_range.int4_range = 3...3 + assert @first_range.save + assert @first_range.reload + assert_nil @first_range.int4_range + end + + def test_create_int8range + int8range = Range.new(30, 50, true) + range = PostgresqlRange.new(:int8_range => int8range) + assert range.save + assert range.reload + assert_equal range.int8_range, int8range + end + + def test_update_int8range + new_int8range = 60000...10000000 + @first_range.int8_range = new_int8range + assert @first_range.save + assert @first_range.reload + assert_equal new_int8range, @first_range.int8_range + @first_range.int8_range = 39999...39999 + assert @first_range.save + assert @first_range.reload + assert_nil @first_range.int8_range + end end def test_money_values @@ -306,141 +424,6 @@ _SQL assert_equal(-2.25, column.type_cast("($2.25)")) end - def test_create_tstzrange - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT') - range = PostgresqlRange.new(:tstz_range => tstzrange) - assert range.save - assert range.reload - assert_equal range.tstz_range, tstzrange - assert_equal range.tstz_range, Time.parse('2010-01-01 13:30:00 UTC')...Time.parse('2011-02-02 19:30:00 UTC') - end - - def test_update_tstzrange - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - new_tstzrange = Time.parse('2010-01-01 14:30:00 CDT')...Time.parse('2011-02-02 14:30:00 CET') - @first_range.tstz_range = new_tstzrange - assert @first_range.save - assert @first_range.reload - assert_equal new_tstzrange, @first_range.tstz_range - @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000') - assert @first_range.save - assert @first_range.reload - assert_nil @first_range.tstz_range - end - - def test_create_tsrange - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - tz = ::ActiveRecord::Base.default_timezone - tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0) - range = PostgresqlRange.new(:ts_range => tsrange) - assert range.save - assert range.reload - assert_equal range.ts_range, tsrange - end - - def test_update_tsrange - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - tz = ::ActiveRecord::Base.default_timezone - new_tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0) - @first_range.ts_range = new_tsrange - assert @first_range.save - assert @first_range.reload - assert_equal new_tsrange, @first_range.ts_range - @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0) - assert @first_range.save - assert @first_range.reload - assert_nil @first_range.ts_range - end - - def test_create_numrange - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - numrange = BigDecimal.new('0.5')...BigDecimal.new('1') - range = PostgresqlRange.new(:num_range => numrange) - assert range.save - assert range.reload - assert_equal range.num_range, numrange - end - - def test_update_numrange - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - new_numrange = BigDecimal.new('0.5')...BigDecimal.new('1') - @first_range.num_range = new_numrange - assert @first_range.save - assert @first_range.reload - assert_equal new_numrange, @first_range.num_range - @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5') - assert @first_range.save - assert @first_range.reload - assert_nil @first_range.num_range - end - - def test_create_daterange - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - daterange = Range.new(Date.new(2012, 1, 1), Date.new(2013, 1, 1), true) - range = PostgresqlRange.new(:date_range => daterange) - assert range.save - assert range.reload - assert_equal range.date_range, daterange - end - - def test_update_daterange - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - new_daterange = Date.new(2012, 2, 3)...Date.new(2012, 2, 10) - @first_range.date_range = new_daterange - assert @first_range.save - assert @first_range.reload - assert_equal new_daterange, @first_range.date_range - @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3) - assert @first_range.save - assert @first_range.reload - assert_nil @first_range.date_range - end - - def test_create_int4range - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - int4range = Range.new(3, 50, true) - range = PostgresqlRange.new(:int4_range => int4range) - assert range.save - assert range.reload - assert_equal range.int4_range, int4range - end - - def test_update_int4range - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - new_int4range = 6...10 - @first_range.int4_range = new_int4range - assert @first_range.save - assert @first_range.reload - assert_equal new_int4range, @first_range.int4_range - @first_range.int4_range = 3...3 - assert @first_range.save - assert @first_range.reload - assert_nil @first_range.int4_range - end - - def test_create_int8range - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - int8range = Range.new(30, 50, true) - range = PostgresqlRange.new(:int8_range => int8range) - assert range.save - assert range.reload - assert_equal range.int8_range, int8range - end - - def test_update_int8range - skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? - new_int8range = 60000...10000000 - @first_range.int8_range = new_int8range - assert @first_range.save - assert @first_range.reload - assert_equal new_int8range, @first_range.int8_range - @first_range.int8_range = 39999...39999 - assert @first_range.save - assert @first_range.reload - assert_nil @first_range.int8_range - end - def test_update_tsvector new_text_vector = "'new' 'text' 'vector'" @first_tsvector.text_vector = new_text_vector diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index de724486c2..2845413575 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -14,10 +14,6 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection - unless @connection.supports_extensions? - return skip "do not test on PG without hstore" - end - unless @connection.extension_enabled?('hstore') @connection.enable_extension 'hstore' @connection.commit_db_transaction @@ -38,191 +34,193 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase @connection.execute 'drop table if exists hstores' end - def test_hstore_included_in_extensions - assert @connection.respond_to?(:extensions), "connection should have a list of extensions" - assert @connection.extensions.include?('hstore'), "extension list should include hstore" - end + if ActiveRecord::Base.connection.supports_extensions? + def test_hstore_included_in_extensions + assert @connection.respond_to?(:extensions), "connection should have a list of extensions" + assert @connection.extensions.include?('hstore'), "extension list should include hstore" + end - def test_disable_enable_hstore - assert @connection.extension_enabled?('hstore') - @connection.disable_extension 'hstore' - assert_not @connection.extension_enabled?('hstore') - @connection.enable_extension 'hstore' - assert @connection.extension_enabled?('hstore') - ensure - # Restore column(s) dropped by `drop extension hstore cascade;` - load_schema - end + def test_disable_enable_hstore + assert @connection.extension_enabled?('hstore') + @connection.disable_extension 'hstore' + assert_not @connection.extension_enabled?('hstore') + @connection.enable_extension 'hstore' + assert @connection.extension_enabled?('hstore') + ensure + # Restore column(s) dropped by `drop extension hstore cascade;` + load_schema + end - def test_column - assert_equal :hstore, @column.type - end + def test_column + assert_equal :hstore, @column.type + end - def test_change_table_supports_hstore - @connection.transaction do - @connection.change_table('hstores') do |t| - t.hstore 'users', default: '' + def test_change_table_supports_hstore + @connection.transaction do + @connection.change_table('hstores') do |t| + t.hstore 'users', default: '' + end + Hstore.reset_column_information + column = Hstore.columns.find { |c| c.name == 'users' } + assert_equal :hstore, column.type + + raise ActiveRecord::Rollback # reset the schema change end + ensure Hstore.reset_column_information - column = Hstore.columns.find { |c| c.name == 'users' } - assert_equal :hstore, column.type - - raise ActiveRecord::Rollback # reset the schema change end - ensure - Hstore.reset_column_information - end - def test_cast_value_on_write - x = Hstore.new tags: {"bool" => true, "number" => 5} - assert_equal({"bool" => "true", "number" => "5"}, x.tags) - x.save - assert_equal({"bool" => "true", "number" => "5"}, x.reload.tags) - end - - def test_type_cast_hstore - assert @column + def test_cast_value_on_write + x = Hstore.new tags: {"bool" => true, "number" => 5} + assert_equal({"bool" => "true", "number" => "5"}, x.tags) + x.save + assert_equal({"bool" => "true", "number" => "5"}, x.reload.tags) + end - data = "\"1\"=>\"2\"" - hash = @column.class.string_to_hstore data - assert_equal({'1' => '2'}, hash) - assert_equal({'1' => '2'}, @column.type_cast(data)) + def test_type_cast_hstore + assert @column - assert_equal({}, @column.type_cast("")) - assert_equal({'key'=>nil}, @column.type_cast('key => NULL')) - assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast(%q(c=>"}", "\"a\""=>"b \"a b"))) - end + data = "\"1\"=>\"2\"" + hash = @column.class.string_to_hstore data + assert_equal({'1' => '2'}, hash) + assert_equal({'1' => '2'}, @column.type_cast(data)) - def test_with_store_accessors - x = Hstore.new(language: "fr", timezone: "GMT") - assert_equal "fr", x.language - assert_equal "GMT", x.timezone + assert_equal({}, @column.type_cast("")) + assert_equal({'key'=>nil}, @column.type_cast('key => NULL')) + assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast(%q(c=>"}", "\"a\""=>"b \"a b"))) + end - x.save! - x = Hstore.first - assert_equal "fr", x.language - assert_equal "GMT", x.timezone + def test_with_store_accessors + x = Hstore.new(language: "fr", timezone: "GMT") + assert_equal "fr", x.language + assert_equal "GMT", x.timezone - x.language = "de" - x.save! + x.save! + x = Hstore.first + assert_equal "fr", x.language + assert_equal "GMT", x.timezone - x = Hstore.first - assert_equal "de", x.language - assert_equal "GMT", x.timezone - end + x.language = "de" + x.save! - def test_gen1 - assert_equal(%q(" "=>""), @column.class.hstore_to_string({' '=>''})) - end + x = Hstore.first + assert_equal "de", x.language + assert_equal "GMT", x.timezone + end - def test_gen2 - assert_equal(%q(","=>""), @column.class.hstore_to_string({','=>''})) - end + def test_gen1 + assert_equal(%q(" "=>""), @column.class.hstore_to_string({' '=>''})) + end - def test_gen3 - assert_equal(%q("="=>""), @column.class.hstore_to_string({'='=>''})) - end + def test_gen2 + assert_equal(%q(","=>""), @column.class.hstore_to_string({','=>''})) + end - def test_gen4 - assert_equal(%q(">"=>""), @column.class.hstore_to_string({'>'=>''})) - end + def test_gen3 + assert_equal(%q("="=>""), @column.class.hstore_to_string({'='=>''})) + end - def test_parse1 - assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast('a=>null,b=>NuLl,c=>"NuLl",null=>c')) - end + def test_gen4 + assert_equal(%q(">"=>""), @column.class.hstore_to_string({'>'=>''})) + end - def test_parse2 - assert_equal({" " => " "}, @column.type_cast("\\ =>\\ ")) - end + def test_parse1 + assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast('a=>null,b=>NuLl,c=>"NuLl",null=>c')) + end - def test_parse3 - assert_equal({"=" => ">"}, @column.type_cast("==>>")) - end + def test_parse2 + assert_equal({" " => " "}, @column.type_cast("\\ =>\\ ")) + end - def test_parse4 - assert_equal({"=a"=>"q=w"}, @column.type_cast('\=a=>q=w')) - end + def test_parse3 + assert_equal({"=" => ">"}, @column.type_cast("==>>")) + end - def test_parse5 - assert_equal({"=a"=>"q=w"}, @column.type_cast('"=a"=>q\=w')) - end + def test_parse4 + assert_equal({"=a"=>"q=w"}, @column.type_cast('\=a=>q=w')) + end - def test_parse6 - assert_equal({"\"a"=>"q>w"}, @column.type_cast('"\"a"=>q>w')) - end + def test_parse5 + assert_equal({"=a"=>"q=w"}, @column.type_cast('"=a"=>q\=w')) + end - def test_parse7 - assert_equal({"\"a"=>"q\"w"}, @column.type_cast('\"a=>q"w')) - end + def test_parse6 + assert_equal({"\"a"=>"q>w"}, @column.type_cast('"\"a"=>q>w')) + end - def test_rewrite - @connection.execute "insert into hstores (tags) VALUES ('1=>2')" - x = Hstore.first - x.tags = { '"a\'' => 'b' } - assert x.save! - end + def test_parse7 + assert_equal({"\"a"=>"q\"w"}, @column.type_cast('\"a=>q"w')) + end + def test_rewrite + @connection.execute "insert into hstores (tags) VALUES ('1=>2')" + x = Hstore.first + x.tags = { '"a\'' => 'b' } + assert x.save! + end - def test_select - @connection.execute "insert into hstores (tags) VALUES ('1=>2')" - x = Hstore.first - assert_equal({'1' => '2'}, x.tags) - end + def test_select + @connection.execute "insert into hstores (tags) VALUES ('1=>2')" + x = Hstore.first + assert_equal({'1' => '2'}, x.tags) + end - def test_select_multikey - @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')" - x = Hstore.first - assert_equal({'1' => '2', '2' => '3'}, x.tags) - end + def test_select_multikey + @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')" + x = Hstore.first + assert_equal({'1' => '2', '2' => '3'}, x.tags) + end - def test_create - assert_cycle('a' => 'b', '1' => '2') - end + def test_create + assert_cycle('a' => 'b', '1' => '2') + end - def test_nil - assert_cycle('a' => nil) - end + def test_nil + assert_cycle('a' => nil) + end - def test_quotes - assert_cycle('a' => 'b"ar', '1"foo' => '2') - end + def test_quotes + assert_cycle('a' => 'b"ar', '1"foo' => '2') + end - def test_whitespace - assert_cycle('a b' => 'b ar', '1"foo' => '2') - end + def test_whitespace + assert_cycle('a b' => 'b ar', '1"foo' => '2') + end - def test_backslash - assert_cycle('a\\b' => 'b\\ar', '1"foo' => '2') - end + def test_backslash + assert_cycle('a\\b' => 'b\\ar', '1"foo' => '2') + end - def test_comma - assert_cycle('a, b' => 'bar', '1"foo' => '2') - end + def test_comma + assert_cycle('a, b' => 'bar', '1"foo' => '2') + end - def test_arrow - assert_cycle('a=>b' => 'bar', '1"foo' => '2') - end + def test_arrow + assert_cycle('a=>b' => 'bar', '1"foo' => '2') + end - def test_quoting_special_characters - assert_cycle('ca' => 'cà', 'ac' => 'àc') - end + def test_quoting_special_characters + assert_cycle('ca' => 'cà', 'ac' => 'àc') + end - def test_multiline - assert_cycle("a\nb" => "c\nd") + def test_multiline + assert_cycle("a\nb" => "c\nd") + end end private - def assert_cycle hash - # test creation - x = Hstore.create!(:tags => hash) - x.reload - assert_equal(hash, x.tags) - - # test updating - x = Hstore.create!(:tags => {}) - x.tags = hash - x.save! - x.reload - assert_equal(hash, x.tags) - end + + def assert_cycle(hash) + # test creation + x = Hstore.create!(:tags => hash) + x.reload + assert_equal(hash, x.tags) + + # test updating + x = Hstore.create!(:tags => {}) + x.tags = hash + x.save! + x.reload + assert_equal(hash, x.tags) + end end diff --git a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb index c5fd40accc..1497b0abc7 100644 --- a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb @@ -14,20 +14,20 @@ module ActiveRecord end class StatementPoolTest < ActiveRecord::TestCase - def test_cache_is_per_pid - return skip('must support fork') unless Process.respond_to?(:fork) + if Process.respond_to?(:fork) + def test_cache_is_per_pid + cache = StatementPool.new nil, 10 + cache['foo'] = 'bar' + assert_equal 'bar', cache['foo'] - cache = StatementPool.new nil, 10 - cache['foo'] = 'bar' - assert_equal 'bar', cache['foo'] - - pid = fork { - lookup = cache['foo']; - exit!(!lookup) - } + pid = fork { + lookup = cache['foo']; + exit!(!lookup) + } - Process.waitpid pid - assert $?.success?, 'process should exit successfully' + Process.waitpid pid + assert $?.success?, 'process should exit successfully' + end end def test_dealloc_does_not_raise_on_inactive_connection diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb index dbc69a529c..89210866f0 100644 --- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb +++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb @@ -12,10 +12,6 @@ class TimestampTest < ActiveRecord::TestCase end def test_load_infinity_and_beyond - unless current_adapter?(:PostgreSQLAdapter) - return skip("only tested on postgresql") - end - d = Developer.find_by_sql("select 'infinity'::timestamp as updated_at") assert d.first.updated_at.infinite?, 'timestamp should be infinite' @@ -26,10 +22,6 @@ class TimestampTest < ActiveRecord::TestCase end def test_save_infinity_and_beyond - unless current_adapter?(:PostgreSQLAdapter) - return skip("only tested on postgresql") - end - d = Developer.create!(:name => 'aaron', :updated_at => 1.0 / 0.0) assert_equal(1.0 / 0.0, d.updated_at) @@ -85,9 +77,6 @@ class TimestampTest < ActiveRecord::TestCase end def test_bc_timestamp - unless current_adapter?(:PostgreSQLAdapter) - return skip("only tested on postgresql") - end date = Date.new(0) - 1.second Developer.create!(:name => "aaron", :updated_at => date) assert_equal date, Developer.find_by_name("aaron").updated_at @@ -109,5 +98,4 @@ class TimestampTest < ActiveRecord::TestCase end result && result.send(option) end - end diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index a753a23c09..3f5d981444 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -12,10 +12,6 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection - unless @connection.supports_extensions? - return skip "do not test on PG without uuid-ossp" - end - unless @connection.extension_enabled?('uuid-ossp') @connection.enable_extension 'uuid-ossp' @connection.commit_db_transaction @@ -35,33 +31,35 @@ 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 + if ActiveRecord::Base.connection.supports_extensions? + 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_id_has_a_default + u = UUID.create + assert_not_nil u.id + end - def test_auto_create_uuid - u = UUID.create - u.reload - assert_not_nil u.other_uuid - end + def test_auto_create_uuid + u = UUID.create + u.reload + assert_not_nil u.other_uuid + end - def test_pk_and_sequence_for_uuid_primary_key - pk, seq = @connection.pk_and_sequence_for('pg_uuids') - assert_equal 'id', pk - assert_equal nil, seq - end + def test_pk_and_sequence_for_uuid_primary_key + pk, seq = @connection.pk_and_sequence_for('pg_uuids') + assert_equal 'id', pk + assert_equal nil, seq + end - def test_schema_dumper_for_uuid_primary_key - schema = StringIO.new - ActiveRecord::SchemaDumper.dump(@connection, schema) - assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema.string) - assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema.string) + def test_schema_dumper_for_uuid_primary_key + schema = StringIO.new + ActiveRecord::SchemaDumper.dump(@connection, schema) + assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema.string) + assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema.string) + end end end @@ -74,6 +72,11 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase @connection = ActiveRecord::Base.connection @connection.reconnect! + unless @connection.extension_enabled?('uuid-ossp') + @connection.enable_extension 'uuid-ossp' + @connection.commit_db_transaction + end + @connection.transaction do @connection.create_table('pg_uuids', id: false) do |t| t.primary_key :id, :uuid, default: nil @@ -86,12 +89,14 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase @connection.execute 'drop table if exists pg_uuids' end - def test_id_allows_default_override_via_nil - col_desc = @connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default + if ActiveRecord::Base.connection.supports_extensions? + def test_id_allows_default_override_via_nil + col_desc = @connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default FROM pg_attribute a LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first - assert_nil col_desc["default"] + assert_nil col_desc["default"] + end end end @@ -110,6 +115,11 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase @connection = ActiveRecord::Base.connection @connection.reconnect! + unless @connection.extension_enabled?('uuid-ossp') + @connection.enable_extension 'uuid-ossp' + @connection.commit_db_transaction + end + @connection.transaction do @connection.create_table('pg_uuid_posts', id: :uuid) do |t| t.string 'title' @@ -128,9 +138,11 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase end end - def test_collection_association_with_uuid - post = UuidPost.create! - comment = post.uuid_comments.create! - assert post.uuid_comments.find(comment.id) + if ActiveRecord::Base.connection.supports_extensions? + def test_collection_association_with_uuid + post = UuidPost.create! + comment = post.uuid_comments.create! + assert post.uuid_comments.find(comment.id) + end end end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb index 5a4fe63580..f545fc2011 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb @@ -12,7 +12,7 @@ module ActiveRecord :adapter => 'sqlite3', :timeout => 100 - assert Dir.exists? dir.join('db') + assert Dir.exist? dir.join('db') assert File.exist? dir.join('db/foo.sqlite3') end end diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb index 2f04c60a9a..fd0044ac05 100644 --- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb @@ -3,20 +3,21 @@ require 'cases/helper' module ActiveRecord::ConnectionAdapters class SQLite3Adapter class StatementPoolTest < ActiveRecord::TestCase - def test_cache_is_per_pid - return skip('must support fork') unless Process.respond_to?(:fork) + if Process.respond_to?(:fork) + def test_cache_is_per_pid - cache = StatementPool.new nil, 10 - cache['foo'] = 'bar' - assert_equal 'bar', cache['foo'] + cache = StatementPool.new nil, 10 + cache['foo'] = 'bar' + assert_equal 'bar', cache['foo'] - pid = fork { - lookup = cache['foo']; - exit!(!lookup) - } + pid = fork { + lookup = cache['foo']; + exit!(!lookup) + } - Process.waitpid pid - assert $?.success?, 'process should exit successfully' + Process.waitpid pid + assert $?.success?, 'process should exit successfully' + end end end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index c450b1beb5..47592f312e 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -514,6 +514,15 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal(post.taggings.count, post.taggings_count) end + def test_update_counter_caches_on_destroy + post = posts(:welcome) + tag = post.tags.create!(name: 'doomed') + + assert_difference 'post.reload.taggings_count', -1 do + tag.tagged_posts.destroy(post) + end + end + def test_replace_association assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people(true)} diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index cdd386187b..1f78c73f71 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -509,16 +509,30 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_no_queries { Firm.new.account = account } end - def test_has_one_assignment_triggers_save_on_change + def test_has_one_assignment_dont_trigger_save_on_change_of_same_object pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") ship = pirate.build_ship(name: 'old name') ship.save! ship.name = 'new name' assert ship.changed? + assert_queries(1) do + # One query for updating name, not triggering query for updating pirate_id + pirate.ship = ship + end + + assert_equal 'new name', pirate.ship.reload.name + end + + def test_has_one_assignment_triggers_save_on_change_on_replacing_object + pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") + ship = pirate.build_ship(name: 'old name') + ship.save! + + new_ship = Ship.create(name: 'new name') assert_queries(2) do # One query for updating name and second query for updating pirate_id - pirate.ship = ship + pirate.ship = new_ship end assert_equal 'new name', pirate.ship.reload.name diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index 9fe5ff50d9..dffee42e7d 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -70,7 +70,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase end def test_find_with_implicit_inner_joins_does_not_set_associations - authors = Author.joins(:posts).select('authors.*') + authors = Author.joins(:posts).select('authors.*').to_a assert !authors.empty?, "expected authors to be non-empty" assert authors.all? { |a| !a.instance_variable_defined?(:@posts) }, "expected no authors to have the @posts association loaded" end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 2d6979238e..cde188f6c3 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -612,18 +612,17 @@ class BasicsTest < ActiveRecord::TestCase assert_equal 'たこ焼き仮面', weird.なまえ end - def test_respect_internal_encoding - if current_adapter?(:PostgreSQLAdapter) - skip 'pg does not respect internal encoding and always returns utf8' - end - old_default_internal = Encoding.default_internal - silence_warnings { Encoding.default_internal = "EUC-JP" } + unless current_adapter?(:PostgreSQLAdapter) + def test_respect_internal_encoding + old_default_internal = Encoding.default_internal + silence_warnings { Encoding.default_internal = "EUC-JP" } - Weird.reset_column_information + Weird.reset_column_information - assert_equal ["EUC-JP"], Weird.columns.map {|c| c.name.encoding.name }.uniq - ensure - silence_warnings { Encoding.default_internal = old_default_internal } + assert_equal ["EUC-JP"], Weird.columns.map {|c| c.name.encoding.name }.uniq + ensure + silence_warnings { Encoding.default_internal = old_default_internal } + end end def test_non_valid_identifier_column_name @@ -1362,34 +1361,33 @@ class BasicsTest < ActiveRecord::TestCase assert_equal 1, post.comments.length end - def test_marshal_between_processes - skip "can't marshal between processes when using an in-memory db" if in_memory_db? - skip "fork isn't supported" unless Process.respond_to?(:fork) + if Process.respond_to?(:fork) && !in_memory_db? + def test_marshal_between_processes + # Define a new model to ensure there are no caches + if self.class.const_defined?("Post", false) + flunk "there should be no post constant" + end - # Define a new model to ensure there are no caches - if self.class.const_defined?("Post", false) - flunk "there should be no post constant" - end + self.class.const_set("Post", Class.new(ActiveRecord::Base) { + has_many :comments + }) - self.class.const_set("Post", Class.new(ActiveRecord::Base) { - has_many :comments - }) + rd, wr = IO.pipe - rd, wr = IO.pipe + ActiveRecord::Base.connection_handler.clear_all_connections! - ActiveRecord::Base.connection_handler.clear_all_connections! + fork do + rd.close + post = Post.new + post.comments.build + wr.write Marshal.dump(post) + wr.close + end - fork do - rd.close - post = Post.new - post.comments.build - wr.write Marshal.dump(post) wr.close + assert Marshal.load rd.read + rd.close end - - wr.close - assert Marshal.load rd.read - rd.close end def test_marshalling_new_record_round_trip_with_associations diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb index 03aa9fdb27..291751c435 100644 --- a/activerecord/test/cases/bind_parameter_test.rb +++ b/activerecord/test/cases/bind_parameter_test.rb @@ -23,46 +23,56 @@ module ActiveRecord @listener = LogListener.new @pk = Topic.columns.find { |c| c.primary } ActiveSupport::Notifications.subscribe('sql.active_record', @listener) - - skip_if_prepared_statement_caching_is_not_supported end def teardown ActiveSupport::Notifications.unsubscribe(@listener) end - def test_binds_are_logged - sub = @connection.substitute_at(@pk, 0) - binds = [[@pk, 1]] - sql = "select * from topics where id = #{sub}" + if ActiveRecord::Base.connection.supports_statement_cache? + def test_binds_are_logged + sub = @connection.substitute_at(@pk, 0) + binds = [[@pk, 1]] + sql = "select * from topics where id = #{sub}" - @connection.exec_query(sql, 'SQL', binds) + @connection.exec_query(sql, 'SQL', binds) - message = @listener.calls.find { |args| args[4][:sql] == sql } - assert_equal binds, message[4][:binds] - end + message = @listener.calls.find { |args| args[4][:sql] == sql } + assert_equal binds, message[4][:binds] + end - def test_find_one_uses_binds - Topic.find(1) - binds = [[@pk, 1]] - message = @listener.calls.find { |args| args[4][:binds] == binds } - assert message, 'expected a message with binds' - end + def test_binds_are_logged_after_type_cast + sub = @connection.substitute_at(@pk, 0) + binds = [[@pk, "3"]] + sql = "select * from topics where id = #{sub}" - def test_logs_bind_vars - pk = Topic.columns.find { |x| x.primary } - - payload = { - :name => 'SQL', - :sql => 'select * from topics where id = ?', - :binds => [[pk, 10]] - } - event = ActiveSupport::Notifications::Event.new( - 'foo', - Time.now, - Time.now, - 123, - payload) + @connection.exec_query(sql, 'SQL', binds) + + message = @listener.calls.find { |args| args[4][:sql] == sql } + assert_equal [[@pk, 3]], message[4][:binds] + end + + def test_find_one_uses_binds + Topic.find(1) + binds = [[@pk, 1]] + message = @listener.calls.find { |args| args[4][:binds] == binds } + assert message, 'expected a message with binds' + end + + def test_logs_bind_vars + pk = Topic.columns.find { |x| x.primary } + + payload = { + :name => 'SQL', + :sql => 'select * from topics where id = ?', + :binds => [[pk, 10]] + } + event = ActiveSupport::Notifications::Event.new( + 'foo', + Time.now, + Time.now, + 123, + payload) logger = Class.new(ActiveRecord::LogSubscriber) { attr_reader :debugs @@ -78,12 +88,7 @@ module ActiveRecord logger.sql event assert_match([[pk.name, 10]].inspect, logger.debugs.first) - end - - private - - def skip_if_prepared_statement_caching_is_not_supported - skip('prepared statement caching is not supported') unless @connection.supports_statement_cache? + end end end end diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb index df17732fff..00667cc52e 100644 --- a/activerecord/test/cases/connection_management_test.rb +++ b/activerecord/test/cases/connection_management_test.rb @@ -26,25 +26,25 @@ module ActiveRecord assert ActiveRecord::Base.connection_handler.active_connections? end - def test_connection_pool_per_pid - return skip('must support fork') unless Process.respond_to?(:fork) + if Process.respond_to?(:fork) + def test_connection_pool_per_pid + object_id = ActiveRecord::Base.connection.object_id - object_id = ActiveRecord::Base.connection.object_id + rd, wr = IO.pipe - rd, wr = IO.pipe + pid = fork { + rd.close + wr.write Marshal.dump ActiveRecord::Base.connection.object_id + wr.close + exit! + } - pid = fork { - rd.close - wr.write Marshal.dump ActiveRecord::Base.connection.object_id wr.close - exit! - } - - wr.close - Process.waitpid pid - assert_not_equal object_id, Marshal.load(rd.read) - rd.close + Process.waitpid pid + assert_not_equal object_id, Marshal.load(rd.read) + rd.close + end end def test_app_delegation diff --git a/activerecord/test/cases/disconnected_test.rb b/activerecord/test/cases/disconnected_test.rb index 1fecfd077e..9e268dad74 100644 --- a/activerecord/test/cases/disconnected_test.rb +++ b/activerecord/test/cases/disconnected_test.rb @@ -7,7 +7,6 @@ class TestDisconnectedAdapter < ActiveRecord::TestCase self.use_transactional_fixtures = false def setup - skip "in-memory database mustn't disconnect" if in_memory_db? @connection = ActiveRecord::Base.connection end @@ -17,11 +16,13 @@ class TestDisconnectedAdapter < ActiveRecord::TestCase ActiveRecord::Base.establish_connection(spec) end - test "can't execute statements while disconnected" do - @connection.execute "SELECT count(*) from products" - @connection.disconnect! - assert_raises(ActiveRecord::StatementInvalid) do + unless in_memory_db? + test "can't execute statements while disconnected" do @connection.execute "SELECT count(*) from products" + @connection.disconnect! + assert_raises(ActiveRecord::StatementInvalid) do + @connection.execute "SELECT count(*) from products" + end end end end diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index 54cc60913a..35e8a98156 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -57,4 +57,10 @@ class EnumTest < ActiveRecord::TestCase end assert_equal "'unknown' is not a valid status", e.message end + + test "constant to access the mapping" do + assert_equal 0, Book::STATUS[:proposed] + assert_equal 1, Book::STATUS["written"] + assert_equal 2, Book::STATUS[:published] + end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 4188b32731..735f804184 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -150,14 +150,14 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_ids_with_limit_and_offset - assert_equal 2, Entrant.all.merge!(:limit => 2).find([1,3,2]).size - assert_equal 1, Entrant.all.merge!(:limit => 3, :offset => 2).find([1,3,2]).size + assert_equal 2, Entrant.limit(2).find([1,3,2]).size + assert_equal 1, Entrant.limit(3).offset(2).find([1,3,2]).size # Also test an edge case: If you have 11 results, and you set a # limit of 3 and offset of 9, then you should find that there # will be only 2 results, regardless of the limit. devs = Developer.all - last_devs = Developer.all.merge!(:limit => 3, :offset => 9).find devs.map(&:id) + last_devs = Developer.limit(3).offset(9).find devs.map(&:id) assert_equal 2, last_devs.size end @@ -310,7 +310,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_only_some_columns - topic = Topic.all.merge!(:select => "author_name").find(1) + topic = Topic.select("author_name").find(1) assert_raise(ActiveModel::MissingAttributeError) {topic.title} assert_raise(ActiveModel::MissingAttributeError) {topic.title?} assert_nil topic.read_attribute("title") @@ -322,23 +322,23 @@ class FinderTest < ActiveRecord::TestCase end def test_find_on_array_conditions - assert Topic.all.merge!(:where => ["approved = ?", false]).find(1) - assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => ["approved = ?", true]).find(1) } + assert Topic.where(["approved = ?", false]).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.where(["approved = ?", true]).find(1) } end def test_find_on_hash_conditions - assert Topic.all.merge!(:where => { :approved => false }).find(1) - assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { :approved => true }).find(1) } + assert Topic.where(approved: false).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.where(approved: true).find(1) } end def test_find_on_hash_conditions_with_explicit_table_name - assert Topic.all.merge!(:where => { 'topics.approved' => false }).find(1) - assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { 'topics.approved' => true }).find(1) } + assert Topic.where('topics.approved' => false).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => true).find(1) } end def test_find_on_hash_conditions_with_hashed_table_name - assert Topic.all.merge!(:where => {:topics => { :approved => false }}).find(1) - assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => {:topics => { :approved => true }}).find(1) } + assert Topic.where(topics: { approved: false }).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.where(topics: { approved: true }).find(1) } end def test_find_with_hash_conditions_on_joined_table @@ -348,7 +348,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_with_hash_conditions_on_joined_table_and_with_range - firms = DependentFirm.all.merge!(:joins => :account, :where => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}) + firms = DependentFirm.joins(:account).where(name: 'RailsCore', accounts: { credit_limit: 55..60 }) assert_equal 1, firms.size assert_equal companies(:rails_core), firms.first end @@ -366,71 +366,71 @@ class FinderTest < ActiveRecord::TestCase end def test_find_on_hash_conditions_with_range - assert_equal [1,2], Topic.all.merge!(:where => { :id => 1..2 }).to_a.map(&:id).sort - assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { :id => 2..3 }).find(1) } + assert_equal [1,2], Topic.where(id: 1..2).to_a.map(&:id).sort + assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2..3).find(1) } end def test_find_on_hash_conditions_with_end_exclusive_range - assert_equal [1,2,3], Topic.all.merge!(:where => { :id => 1..3 }).to_a.map(&:id).sort - assert_equal [1,2], Topic.all.merge!(:where => { :id => 1...3 }).to_a.map(&:id).sort - assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { :id => 2...3 }).find(3) } + assert_equal [1,2,3], Topic.where(id: 1..3).to_a.map(&:id).sort + assert_equal [1,2], Topic.where(id: 1...3).to_a.map(&:id).sort + assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2...3).find(3) } end def test_find_on_hash_conditions_with_multiple_ranges - assert_equal [1,2,3], Comment.all.merge!(:where => { :id => 1..3, :post_id => 1..2 }).to_a.map(&:id).sort - assert_equal [1], Comment.all.merge!(:where => { :id => 1..1, :post_id => 1..10 }).to_a.map(&:id).sort + assert_equal [1,2,3], Comment.where(id: 1..3, post_id: 1..2).to_a.map(&:id).sort + assert_equal [1], Comment.where(id: 1..1, post_id: 1..10).to_a.map(&:id).sort end def test_find_on_hash_conditions_with_array_of_integers_and_ranges - assert_equal [1,2,3,5,6,7,8,9], Comment.all.merge!(:where => {:id => [1..2, 3, 5, 6..8, 9]}).to_a.map(&:id).sort + assert_equal [1,2,3,5,6,7,8,9], Comment.where(id: [1..2, 3, 5, 6..8, 9]).to_a.map(&:id).sort end def test_find_on_multiple_hash_conditions - assert Topic.all.merge!(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false }).find(1) - assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }).find(1) } - assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }).find(1) } - assert_raise(ActiveRecord::RecordNotFound) { Topic.all.merge!(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }).find(1) } + assert Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: false).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) } + assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "HHC", replies_count: 1, approved: false).find(1) } + assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) } end def test_condition_interpolation assert_kind_of Firm, Company.where("name = '%s'", "37signals").first - assert_nil Company.all.merge!(:where => ["name = '%s'", "37signals!"]).first - assert_nil Company.all.merge!(:where => ["name = '%s'", "37signals!' OR 1=1"]).first - assert_kind_of Time, Topic.all.merge!(:where => ["id = %d", 1]).first.written_on + assert_nil Company.where(["name = '%s'", "37signals!"]).first + assert_nil Company.where(["name = '%s'", "37signals!' OR 1=1"]).first + assert_kind_of Time, Topic.where(["id = %d", 1]).first.written_on end def test_condition_array_interpolation - assert_kind_of Firm, Company.all.merge!(:where => ["name = '%s'", "37signals"]).first - assert_nil Company.all.merge!(:where => ["name = '%s'", "37signals!"]).first - assert_nil Company.all.merge!(:where => ["name = '%s'", "37signals!' OR 1=1"]).first - assert_kind_of Time, Topic.all.merge!(:where => ["id = %d", 1]).first.written_on + assert_kind_of Firm, Company.where(["name = '%s'", "37signals"]).first + assert_nil Company.where(["name = '%s'", "37signals!"]).first + assert_nil Company.where(["name = '%s'", "37signals!' OR 1=1"]).first + assert_kind_of Time, Topic.where(["id = %d", 1]).first.written_on end def test_condition_hash_interpolation - assert_kind_of Firm, Company.all.merge!(:where => { :name => "37signals"}).first - assert_nil Company.all.merge!(:where => { :name => "37signals!"}).first - assert_kind_of Time, Topic.all.merge!(:where => {:id => 1}).first.written_on + assert_kind_of Firm, Company.where(name: "37signals").first + assert_nil Company.where(name: "37signals!").first + assert_kind_of Time, Topic.where(id: 1).first.written_on end def test_hash_condition_find_malformed assert_raise(ActiveRecord::StatementInvalid) { - Company.all.merge!(:where => { :id => 2, :dhh => true }).first + Company.where(id: 2, dhh: true).first } end def test_hash_condition_find_with_escaped_characters Company.create("name" => "Ain't noth'n like' \#stuff") - assert Company.all.merge!(:where => { :name => "Ain't noth'n like' \#stuff" }).first + assert Company.where(name: "Ain't noth'n like' \#stuff").first end def test_hash_condition_find_with_array - p1, p2 = Post.all.merge!(:limit => 2, :order => 'id asc').to_a - assert_equal [p1, p2], Post.all.merge!(:where => { :id => [p1, p2] }, :order => 'id asc').to_a - assert_equal [p1, p2], Post.all.merge!(:where => { :id => [p1, p2.id] }, :order => 'id asc').to_a + p1, p2 = Post.limit(2).order('id asc').to_a + assert_equal [p1, p2], Post.where(id: [p1, p2]).order('id asc').to_a + assert_equal [p1, p2], Post.where(id: [p1, p2.id]).order('id asc').to_a end def test_hash_condition_find_with_nil - topic = Topic.all.merge!(:where => { :last_read => nil } ).first + topic = Topic.where(last_read: nil).first assert_not_nil topic assert_nil topic.last_read end @@ -481,7 +481,7 @@ class FinderTest < ActiveRecord::TestCase with_env_tz 'America/New_York' do with_timezone_config default: :local do topic = Topic.first - assert_equal topic, Topic.all.merge!(:where => ['written_on = ?', topic.written_on.getutc]).first + assert_equal topic, Topic.where(['written_on = ?', topic.written_on.getutc]).first end end end @@ -490,7 +490,7 @@ class FinderTest < ActiveRecord::TestCase with_env_tz 'America/New_York' do with_timezone_config default: :local do topic = Topic.first - assert_equal topic, Topic.all.merge!(:where => {:written_on => topic.written_on.getutc}).first + assert_equal topic, Topic.where(written_on: topic.written_on.getutc).first end end end @@ -499,7 +499,7 @@ class FinderTest < ActiveRecord::TestCase with_env_tz 'America/New_York' do with_timezone_config default: :utc do topic = Topic.first - assert_equal topic, Topic.all.merge!(:where => ['written_on = ?', topic.written_on.getlocal]).first + assert_equal topic, Topic.where(['written_on = ?', topic.written_on.getlocal]).first end end end @@ -508,32 +508,32 @@ class FinderTest < ActiveRecord::TestCase with_env_tz 'America/New_York' do with_timezone_config default: :utc do topic = Topic.first - assert_equal topic, Topic.all.merge!(:where => {:written_on => topic.written_on.getlocal}).first + assert_equal topic, Topic.where(written_on: topic.written_on.getlocal).first end end end def test_bind_variables - assert_kind_of Firm, Company.all.merge!(:where => ["name = ?", "37signals"]).first - assert_nil Company.all.merge!(:where => ["name = ?", "37signals!"]).first - assert_nil Company.all.merge!(:where => ["name = ?", "37signals!' OR 1=1"]).first - assert_kind_of Time, Topic.all.merge!(:where => ["id = ?", 1]).first.written_on + assert_kind_of Firm, Company.where(["name = ?", "37signals"]).first + assert_nil Company.where(["name = ?", "37signals!"]).first + assert_nil Company.where(["name = ?", "37signals!' OR 1=1"]).first + assert_kind_of Time, Topic.where(["id = ?", 1]).first.written_on assert_raise(ActiveRecord::PreparedStatementInvalid) { - Company.all.merge!(:where => ["id=? AND name = ?", 2]).first + Company.where(["id=? AND name = ?", 2]).first } assert_raise(ActiveRecord::PreparedStatementInvalid) { - Company.all.merge!(:where => ["id=?", 2, 3, 4]).first + Company.where(["id=?", 2, 3, 4]).first } end def test_bind_variables_with_quotes Company.create("name" => "37signals' go'es agains") - assert Company.all.merge!(:where => ["name = ?", "37signals' go'es agains"]).first + assert Company.where(["name = ?", "37signals' go'es agains"]).first end def test_named_bind_variables_with_quotes Company.create("name" => "37signals' go'es agains") - assert Company.all.merge!(:where => ["name = :name", {:name => "37signals' go'es agains"}]).first + assert Company.where(["name = :name", {name: "37signals' go'es agains"}]).first end def test_bind_arity @@ -551,10 +551,10 @@ class FinderTest < ActiveRecord::TestCase assert_nothing_raised { bind("'+00:00'", :foo => "bar") } - assert_kind_of Firm, Company.all.merge!(:where => ["name = :name", { :name => "37signals" }]).first - assert_nil Company.all.merge!(:where => ["name = :name", { :name => "37signals!" }]).first - assert_nil Company.all.merge!(:where => ["name = :name", { :name => "37signals!' OR 1=1" }]).first - assert_kind_of Time, Topic.all.merge!(:where => ["id = :id", { :id => 1 }]).first.written_on + assert_kind_of Firm, Company.where(["name = :name", { name: "37signals" }]).first + assert_nil Company.where(["name = :name", { name: "37signals!" }]).first + assert_nil Company.where(["name = :name", { name: "37signals!' OR 1=1" }]).first + assert_kind_of Time, Topic.where(["id = :id", { id: 1 }]).first.written_on end class SimpleEnumerable @@ -736,10 +736,9 @@ class FinderTest < ActiveRecord::TestCase end def test_find_all_with_join - developers_on_project_one = Developer.all.merge!( - :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id', - :where => 'project_id=1' - ).to_a + developers_on_project_one = Developer. + joins('LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id'). + where('project_id=1').to_a assert_equal 3, developers_on_project_one.length developer_names = developers_on_project_one.map { |d| d.name } assert developer_names.include?('David') @@ -747,20 +746,17 @@ class FinderTest < ActiveRecord::TestCase end def test_joins_dont_clobber_id - first = Firm.all.merge!( - :joins => 'INNER JOIN companies clients ON clients.firm_id = companies.id', - :where => 'companies.id = 1' - ).first + first = Firm. + joins('INNER JOIN companies clients ON clients.firm_id = companies.id'). + where('companies.id = 1').first assert_equal 1, first.id end def test_joins_with_string_array - person_with_reader_and_post = Post.all.merge!( - :joins => [ - "INNER JOIN categorizations ON categorizations.post_id = posts.id", - "INNER JOIN categories ON categories.id = categorizations.category_id AND categories.type = 'SpecialCategory'" - ] - ) + person_with_reader_and_post = Post. + joins(["INNER JOIN categorizations ON categorizations.post_id = posts.id", + "INNER JOIN categories ON categories.id = categorizations.category_id AND categories.type = 'SpecialCategory'" + ]) assert_equal 1, person_with_reader_and_post.size end @@ -785,9 +781,9 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_records - p1, p2 = Post.all.merge!(:limit => 2, :order => 'id asc').to_a - assert_equal [p1, p2], Post.all.merge!(:where => ['id in (?)', [p1, p2]], :order => 'id asc') - assert_equal [p1, p2], Post.all.merge!(:where => ['id in (?)', [p1, p2.id]], :order => 'id asc') + p1, p2 = Post.limit(2).order('id asc').to_a + assert_equal [p1, p2], Post.where(['id in (?)', [p1, p2]]).order('id asc') + assert_equal [p1, p2], Post.where(['id in (?)', [p1, p2.id]]).order('id asc') end def test_select_value @@ -814,34 +810,35 @@ class FinderTest < ActiveRecord::TestCase end def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct - assert_equal 2, Post.all.merge!(:includes => { :authors => :author_address }, :order => 'author_addresses.id DESC ', :limit => 2).to_a.size + assert_equal 2, Post.includes(authors: :author_address).order('author_addresses.id DESC ').limit(2).to_a.size - assert_equal 3, Post.all.merge!(:includes => { :author => :author_address, :authors => :author_address}, - :order => 'author_addresses_authors.id DESC ', :limit => 3).to_a.size + assert_equal 3, Post.includes(author: :author_address, authors: :author_address). + order('author_addresses_authors.id DESC ').limit(3).to_a.size end def test_find_with_nil_inside_set_passed_for_one_attribute - client_of = Company.all.merge!( - :where => { - :client_of => [2, 1, nil], - :name => ['37signals', 'Summit', 'Microsoft'] }, - :order => 'client_of DESC' - ).map { |x| x.client_of } + client_of = Company. + where(client_of: [2, 1, nil], + name: ['37signals', 'Summit', 'Microsoft']). + order('client_of DESC'). + map { |x| x.client_of } assert client_of.include?(nil) assert_equal [2, 1].sort, client_of.compact.sort end def test_find_with_nil_inside_set_passed_for_attribute - client_of = Company.all.merge!( - :where => { :client_of => [nil] }, - :order => 'client_of DESC' - ).map { |x| x.client_of } + client_of = Company. + where(client_of: [nil]). + order('client_of DESC'). + map { |x| x.client_of } assert_equal [], client_of.compact end def test_with_limiting_with_custom_select + skip 'broken test' if current_adapter?(:MysqlAdapter) + posts = Post.references(:authors).merge( :includes => :author, :select => ' posts.*, authors.id as "author_id"', :limit => 3, :order => 'posts.id' @@ -868,7 +865,7 @@ class FinderTest < ActiveRecord::TestCase end def test_finder_with_offset_string - assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.all.merge!(:offset => "3").to_a } + assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.offset("3").to_a } end protected diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb index 840865c4cf..07ffcef875 100644 --- a/activerecord/test/cases/integration_test.rb +++ b/activerecord/test/cases/integration_test.rb @@ -23,6 +23,46 @@ class IntegrationTest < ActiveRecord::TestCase assert_equal '1', client.to_param end + def test_to_param_class_method + firm = Firm.find(4) + assert_equal '4-flamboyant-software', firm.to_param + end + + def test_to_param_class_method_truncates + firm = Firm.find(4) + firm.name = 'a ' * 100 + assert_equal '4-a-a-a-a-a-a-a-a-a', firm.to_param + end + + def test_to_param_class_method_truncates_edge_case + firm = Firm.find(4) + firm.name = 'David HeinemeierHansson' + assert_equal '4-david', firm.to_param + end + + def test_to_param_class_method_squishes + firm = Firm.find(4) + firm.name = "ab \n" * 100 + assert_equal '4-ab-ab-ab-ab-ab-ab', firm.to_param + end + + def test_to_param_class_method_uses_default_if_blank + firm = Firm.find(4) + firm.name = nil + assert_equal '4', firm.to_param + firm.name = ' ' + assert_equal '4', firm.to_param + end + + def test_to_param_class_method_uses_default_if_not_persisted + firm = Firm.new(name: 'Fancy Shirts') + assert_equal nil, firm.to_param + end + + def test_to_param_with_no_arguments + assert_equal 'Firm', Firm.to_param + end + def test_cache_key_for_existing_record_is_not_timezone_dependent utc_key = Developer.first.cache_key diff --git a/activerecord/test/cases/invalid_connection_test.rb b/activerecord/test/cases/invalid_connection_test.rb index f2d8f18ec7..f6774d7ef4 100644 --- a/activerecord/test/cases/invalid_connection_test.rb +++ b/activerecord/test/cases/invalid_connection_test.rb @@ -17,6 +17,6 @@ class TestAdapterWithInvalidConnection < ActiveRecord::TestCase end test "inspect on Model class does not raise" do - assert_equal "#{Bird.name}(no database connection)", Bird.inspect + assert_equal "#{Bird.name} (call '#{Bird.name}.connection' to establish a connection)", Bird.inspect end end diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 3bdc5a1302..97c0350911 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -119,11 +119,11 @@ class LogSubscriberTest < ActiveRecord::TestCase Thread.new { assert_equal 0, ActiveRecord::LogSubscriber.runtime }.join end - def test_binary_data_is_not_logged - skip if current_adapter?(:Mysql2Adapter) - - Binary.create(data: 'some binary data') - wait - assert_match(/<16 bytes of binary data>/, @logger.logged(:debug).join) + unless current_adapter?(:Mysql2Adapter) + def test_binary_data_is_not_logged + Binary.create(data: 'some binary data') + wait + assert_match(/<16 bytes of binary data>/, @logger.logged(:debug).join) + end end end diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index e37dca856d..e43e256d24 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -74,8 +74,8 @@ module ActiveRecord assert_equal "hello", five.default unless mysql end - def test_add_column_with_array - if current_adapter?(:PostgreSQLAdapter) + if current_adapter?(:PostgreSQLAdapter) + def test_add_column_with_array connection.create_table :testings connection.add_column :testings, :foo, :string, :array => true @@ -83,13 +83,9 @@ module ActiveRecord array_column = columns.detect { |c| c.name == "foo" } assert array_column.array - else - skip "array option only supported in PostgreSQLAdapter" end - end - def test_create_table_with_array_column - if current_adapter?(:PostgreSQLAdapter) + def test_create_table_with_array_column connection.create_table :testings do |t| t.string :foo, :array => true end @@ -98,8 +94,6 @@ module ActiveRecord array_column = columns.detect { |c| c.name == "foo" } assert array_column.array - else - skip "array option only supported in PostgreSQLAdapter" end end @@ -211,20 +205,18 @@ module ActiveRecord connection.create_table table_name end - def test_add_column_not_null_without_default - # Sybase, and SQLite3 will not allow you to add a NOT NULL - # column to a table without a default value. - if current_adapter?(:SybaseAdapter, :SQLite3Adapter) - skip "not supported on #{connection.class}" - end - - connection.create_table :testings do |t| - t.column :foo, :string - end - connection.add_column :testings, :bar, :string, :null => false + # Sybase, and SQLite3 will not allow you to add a NOT NULL + # column to a table without a default value. + unless current_adapter?(:SybaseAdapter, :SQLite3Adapter) + def test_add_column_not_null_without_default + connection.create_table :testings do |t| + t.column :foo, :string + end + connection.add_column :testings, :bar, :string, :null => false - assert_raise(ActiveRecord::StatementInvalid) do - connection.execute "insert into testings (foo, bar) values ('hello', NULL)" + assert_raise(ActiveRecord::StatementInvalid) do + connection.execute "insert into testings (foo, bar) values ('hello', NULL)" + end end end diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb index aa606ac8bb..ccf19fb4d0 100644 --- a/activerecord/test/cases/migration/column_attributes_test.rb +++ b/activerecord/test/cases/migration/column_attributes_test.rb @@ -35,12 +35,12 @@ module ActiveRecord assert_no_column TestModel, :last_name end - def test_unabstracted_database_dependent_types - skip "not supported" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) - - add_column :test_models, :intelligence_quotient, :tinyint - TestModel.reset_column_information - assert_match(/tinyint/, TestModel.columns_hash['intelligence_quotient'].sql_type) + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) + def test_unabstracted_database_dependent_types + add_column :test_models, :intelligence_quotient, :tinyint + TestModel.reset_column_information + assert_match(/tinyint/, TestModel.columns_hash['intelligence_quotient'].sql_type) + end end # We specifically do a manual INSERT here, and then test only the SELECT @@ -95,22 +95,22 @@ module ActiveRecord assert_equal 7, wealth_column.scale end - def test_change_column_preserve_other_column_precision_and_scale - skip "only on sqlite3" unless current_adapter?(:SQLite3Adapter) + if current_adapter?(:SQLite3Adapter) + def test_change_column_preserve_other_column_precision_and_scale + connection.add_column 'test_models', 'last_name', :string + connection.add_column 'test_models', 'wealth', :decimal, :precision => 9, :scale => 7 - connection.add_column 'test_models', 'last_name', :string - connection.add_column 'test_models', 'wealth', :decimal, :precision => 9, :scale => 7 - - wealth_column = TestModel.columns_hash['wealth'] - assert_equal 9, wealth_column.precision - assert_equal 7, wealth_column.scale + wealth_column = TestModel.columns_hash['wealth'] + assert_equal 9, wealth_column.precision + assert_equal 7, wealth_column.scale - connection.change_column 'test_models', 'last_name', :string, :null => false - TestModel.reset_column_information + connection.change_column 'test_models', 'last_name', :string, :null => false + TestModel.reset_column_information - wealth_column = TestModel.columns_hash['wealth'] - assert_equal 9, wealth_column.precision - assert_equal 7, wealth_column.scale + wealth_column = TestModel.columns_hash['wealth'] + assert_equal 9, wealth_column.precision + assert_equal 7, wealth_column.scale + end end def test_native_types @@ -163,13 +163,13 @@ module ActiveRecord assert_kind_of BigDecimal, bob.wealth end - def test_out_of_range_limit_should_raise - skip("MySQL and PostgreSQL only") unless current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) - - assert_raise(ActiveRecordError) { add_column :test_models, :integer_too_big, :integer, :limit => 10 } + if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) + def test_out_of_range_limit_should_raise + assert_raise(ActiveRecordError) { add_column :test_models, :integer_too_big, :integer, :limit => 10 } - unless current_adapter?(:PostgreSQLAdapter) - assert_raise(ActiveRecordError) { add_column :test_models, :text_too_big, :integer, :limit => 0xfffffffff } + unless current_adapter?(:PostgreSQLAdapter) + assert_raise(ActiveRecordError) { add_column :test_models, :text_too_big, :integer, :limit => 0xfffffffff } + end end end end diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb index 913d935f7c..87e29e41ba 100644 --- a/activerecord/test/cases/migration/column_positioning_test.rb +++ b/activerecord/test/cases/migration/column_positioning_test.rb @@ -9,10 +9,6 @@ module ActiveRecord def setup super - unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) - skip "not supported on #{connection.class}" - end - @connection = ActiveRecord::Base.connection connection.create_table :testings, :id => false do |t| @@ -28,33 +24,34 @@ module ActiveRecord ActiveRecord::Base.primary_key_prefix_type = nil end - def test_column_positioning - assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name } - end + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) + def test_column_positioning + assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name } + end - def test_add_column_with_positioning - conn.add_column :testings, :new_col, :integer - assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name } - end + def test_add_column_with_positioning + conn.add_column :testings, :new_col, :integer + assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name } + end - def test_add_column_with_positioning_first - conn.add_column :testings, :new_col, :integer, :first => true - assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name } - end + def test_add_column_with_positioning_first + conn.add_column :testings, :new_col, :integer, :first => true + assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name } + end - def test_add_column_with_positioning_after - conn.add_column :testings, :new_col, :integer, :after => :first - assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name } - end + def test_add_column_with_positioning_after + conn.add_column :testings, :new_col, :integer, :after => :first + assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name } + end - def test_change_column_with_positioning - conn.change_column :testings, :second, :integer, :first => true - assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name } + def test_change_column_with_positioning + conn.change_column :testings, :second, :integer, :first => true + assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name } - conn.change_column :testings, :second, :integer, :after => :third - assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name } + conn.change_column :testings, :second, :integer, :after => :third + assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name } + end end - end end end diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb index 04521a5f5a..8d1daa0a04 100644 --- a/activerecord/test/cases/migration/index_test.rb +++ b/activerecord/test/cases/migration/index_test.rb @@ -27,32 +27,28 @@ module ActiveRecord ActiveRecord::Base.primary_key_prefix_type = nil end - def test_rename_index - skip "not supported on openbase" if current_adapter?(:OpenBaseAdapter) - - # keep the names short to make Oracle and similar behave - connection.add_index(table_name, [:foo], :name => 'old_idx') - connection.rename_index(table_name, 'old_idx', 'new_idx') - - # if the adapter doesn't support the indexes call, pick defaults that let the test pass - assert_not connection.index_name_exists?(table_name, 'old_idx', false) - assert connection.index_name_exists?(table_name, 'new_idx', true) - end - - def test_double_add_index - skip "not supported on openbase" if current_adapter?(:OpenBaseAdapter) + unless current_adapter?(:OpenBaseAdapter) + def test_rename_index + # keep the names short to make Oracle and similar behave + connection.add_index(table_name, [:foo], :name => 'old_idx') + connection.rename_index(table_name, 'old_idx', 'new_idx') + + # if the adapter doesn't support the indexes call, pick defaults that let the test pass + assert_not connection.index_name_exists?(table_name, 'old_idx', false) + assert connection.index_name_exists?(table_name, 'new_idx', true) + end - connection.add_index(table_name, [:foo], :name => 'some_idx') - assert_raises(ArgumentError) { + def test_double_add_index connection.add_index(table_name, [:foo], :name => 'some_idx') - } - end - - def test_remove_nonexistent_index - skip "not supported on openbase" if current_adapter?(:OpenBaseAdapter) + assert_raises(ArgumentError) { + connection.add_index(table_name, [:foo], :name => 'some_idx') + } + end - # we do this by name, so OpenBase is a wash as noted above - assert_raise(ArgumentError) { connection.remove_index(table_name, "no_such_index") } + def test_remove_nonexistent_index + # we do this by name, so OpenBase is a wash as noted above + assert_raise(ArgumentError) { connection.remove_index(table_name, "no_such_index") } + end end def test_add_index_works_with_long_index_names @@ -189,14 +185,14 @@ module ActiveRecord end end - def test_add_partial_index - skip 'only on pg' unless current_adapter?(:PostgreSQLAdapter) - - connection.add_index("testings", "last_name", :where => "first_name = 'john doe'") - assert connection.index_exists?("testings", "last_name") + if current_adapter?(:PostgreSQLAdapter) + def test_add_partial_index + connection.add_index("testings", "last_name", :where => "first_name = 'john doe'") + assert connection.index_exists?("testings", "last_name") - connection.remove_index("testings", "last_name") - assert !connection.index_exists?("testings", "last_name") + connection.remove_index("testings", "last_name") + assert !connection.index_exists?("testings", "last_name") + end end private diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb index 3ff89524fe..19eb7d3c9e 100644 --- a/activerecord/test/cases/migration/references_index_test.rb +++ b/activerecord/test/cases/migration/references_index_test.rb @@ -50,14 +50,14 @@ module ActiveRecord assert connection.index_exists?(table_name, :bar_id, :name => :index_testings_on_bar_id, :unique => true) end - def test_creates_polymorphic_index - return skip "Oracle Adapter does not support foreign keys if :polymorphic => true is used" if current_adapter? :OracleAdapter + unless current_adapter? :OracleAdapter + def test_creates_polymorphic_index + connection.create_table table_name do |t| + t.references :foo, :polymorphic => true, :index => true + end - connection.create_table table_name do |t| - t.references :foo, :polymorphic => true, :index => true + assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type) end - - assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type) end def test_creates_index_for_existing_table @@ -87,16 +87,16 @@ module ActiveRecord assert_not connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) end - def test_creates_polymorphic_index_for_existing_table - return skip "Oracle Adapter does not support foreign keys if :polymorphic => true is used" if current_adapter? :OracleAdapter - connection.create_table table_name - connection.change_table table_name do |t| - t.references :foo, :polymorphic => true, :index => true - end + unless current_adapter? :OracleAdapter + def test_creates_polymorphic_index_for_existing_table + connection.create_table table_name + connection.change_table table_name do |t| + t.references :foo, :polymorphic => true, :index => true + end - assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type) + assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type) + end end - end end end diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb index 22dbd7c38b..2a7fafc559 100644 --- a/activerecord/test/cases/migration/rename_table_test.rb +++ b/activerecord/test/cases/migration/rename_table_test.rb @@ -19,24 +19,24 @@ module ActiveRecord super end - def test_rename_table_for_sqlite_should_work_with_reserved_words - renamed = false - - skip "not supported" unless current_adapter?(:SQLite3Adapter) - - add_column :test_models, :url, :string - connection.rename_table :references, :old_references - connection.rename_table :test_models, :references - - renamed = true - - # Using explicit id in insert for compatibility across all databases - connection.execute "INSERT INTO 'references' (url, created_at, updated_at) VALUES ('http://rubyonrails.com', 0, 0)" - assert_equal 'http://rubyonrails.com', connection.select_value("SELECT url FROM 'references' WHERE id=1") - ensure - return unless renamed - connection.rename_table :references, :test_models - connection.rename_table :old_references, :references + if current_adapter?(:SQLite3Adapter) + def test_rename_table_for_sqlite_should_work_with_reserved_words + renamed = false + + add_column :test_models, :url, :string + connection.rename_table :references, :old_references + connection.rename_table :test_models, :references + + renamed = true + + # Using explicit id in insert for compatibility across all databases + connection.execute "INSERT INTO 'references' (url, created_at, updated_at) VALUES ('http://rubyonrails.com', 0, 0)" + assert_equal 'http://rubyonrails.com', connection.select_value("SELECT url FROM 'references' WHERE id=1") + ensure + return unless renamed + connection.rename_table :references, :test_models + connection.rename_table :old_references, :references + end end def test_rename_table @@ -76,14 +76,14 @@ module ActiveRecord assert_equal ['special_url_idx'], connection.indexes(:octopi).map(&:name) end - def test_rename_table_for_postgresql_should_also_rename_default_sequence - skip 'not supported' unless current_adapter?(:PostgreSQLAdapter) - - rename_table :test_models, :octopi + if current_adapter?(:PostgreSQLAdapter) + def test_rename_table_for_postgresql_should_also_rename_default_sequence + rename_table :test_models, :octopi - pk, seq = connection.pk_and_sequence_for('octopi') + pk, seq = connection.pk_and_sequence_for('octopi') - assert_equal "octopi_#{pk}_seq", seq + assert_equal "octopi_#{pk}_seq", seq + end end end end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index acfde2a27a..f74e903348 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -230,83 +230,73 @@ class MigrationTest < ActiveRecord::TestCase assert migration.went_down, 'have not gone down' end - def test_migrator_one_up_with_exception_and_rollback - unless ActiveRecord::Base.connection.supports_ddl_transactions? - skip "not supported on #{ActiveRecord::Base.connection.class}" - end - - assert_no_column Person, :last_name - - migration = Class.new(ActiveRecord::Migration) { - def version; 100 end - def migrate(x) - add_column "people", "last_name", :string - raise 'Something broke' - end - }.new + if ActiveRecord::Base.connection.supports_ddl_transactions? + def test_migrator_one_up_with_exception_and_rollback + assert_no_column Person, :last_name + + migration = Class.new(ActiveRecord::Migration) { + def version; 100 end + def migrate(x) + add_column "people", "last_name", :string + raise 'Something broke' + end + }.new - migrator = ActiveRecord::Migrator.new(:up, [migration], 100) + migrator = ActiveRecord::Migrator.new(:up, [migration], 100) - e = assert_raise(StandardError) { migrator.migrate } + e = assert_raise(StandardError) { migrator.migrate } - assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message + assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message - assert_no_column Person, :last_name, - "On error, the Migrator should revert schema changes but it did not." - end - - def test_migrator_one_up_with_exception_and_rollback_using_run - unless ActiveRecord::Base.connection.supports_ddl_transactions? - skip "not supported on #{ActiveRecord::Base.connection.class}" + assert_no_column Person, :last_name, + "On error, the Migrator should revert schema changes but it did not." end - assert_no_column Person, :last_name + def test_migrator_one_up_with_exception_and_rollback_using_run + assert_no_column Person, :last_name - migration = Class.new(ActiveRecord::Migration) { - def version; 100 end - def migrate(x) - add_column "people", "last_name", :string - raise 'Something broke' - end - }.new - - migrator = ActiveRecord::Migrator.new(:up, [migration], 100) + migration = Class.new(ActiveRecord::Migration) { + def version; 100 end + def migrate(x) + add_column "people", "last_name", :string + raise 'Something broke' + end + }.new - e = assert_raise(StandardError) { migrator.run } + migrator = ActiveRecord::Migrator.new(:up, [migration], 100) - assert_equal "An error has occurred, this migration was canceled:\n\nSomething broke", e.message + e = assert_raise(StandardError) { migrator.run } - assert_no_column Person, :last_name, - "On error, the Migrator should revert schema changes but it did not." - end + assert_equal "An error has occurred, this migration was canceled:\n\nSomething broke", e.message - def test_migration_without_transaction - unless ActiveRecord::Base.connection.supports_ddl_transactions? - skip "not supported on #{ActiveRecord::Base.connection.class}" + assert_no_column Person, :last_name, + "On error, the Migrator should revert schema changes but it did not." end - assert_no_column Person, :last_name + def test_migration_without_transaction + assert_no_column Person, :last_name - migration = Class.new(ActiveRecord::Migration) { - self.disable_ddl_transaction! + migration = Class.new(ActiveRecord::Migration) { + self.disable_ddl_transaction! - def version; 101 end - def migrate(x) - add_column "people", "last_name", :string - raise 'Something broke' - end - }.new + def version; 101 end + def migrate(x) + add_column "people", "last_name", :string + raise 'Something broke' + end + }.new - migrator = ActiveRecord::Migrator.new(:up, [migration], 101) - e = assert_raise(StandardError) { migrator.migrate } - assert_equal "An error has occurred, all later migrations canceled:\n\nSomething broke", e.message + migrator = ActiveRecord::Migrator.new(:up, [migration], 101) + e = assert_raise(StandardError) { migrator.migrate } + assert_equal "An error has occurred, all later migrations canceled:\n\nSomething broke", e.message - assert_column Person, :last_name, - "without ddl transactions, the Migrator should not rollback on error but it did." - ensure - Person.reset_column_information - if Person.column_names.include?('last_name') - Person.connection.remove_column('people', 'last_name') + assert_column Person, :last_name, + "without ddl transactions, the Migrator should not rollback on error but it did." + ensure + Person.reset_column_information + if Person.column_names.include?('last_name') + Person.connection.remove_column('people', 'last_name') + end end end @@ -450,62 +440,62 @@ class MigrationTest < ActiveRecord::TestCase Person.connection.drop_table :binary_testings rescue nil end - def test_create_table_with_custom_sequence_name - skip "not supported" unless current_adapter? :OracleAdapter - - # table name is 29 chars, the standard sequence name will - # be 33 chars and should be shortened - assert_nothing_raised do - begin - Person.connection.create_table :table_with_name_thats_just_ok do |t| - t.column :foo, :string, :null => false + if current_adapter? :OracleAdapter + def test_create_table_with_custom_sequence_name + # table name is 29 chars, the standard sequence name will + # be 33 chars and should be shortened + assert_nothing_raised do + begin + Person.connection.create_table :table_with_name_thats_just_ok do |t| + t.column :foo, :string, :null => false + end + ensure + Person.connection.drop_table :table_with_name_thats_just_ok rescue nil end - ensure - Person.connection.drop_table :table_with_name_thats_just_ok rescue nil end - end - # should be all good w/ a custom sequence name - assert_nothing_raised do - begin - Person.connection.create_table :table_with_name_thats_just_ok, - :sequence_name => 'suitably_short_seq' do |t| - t.column :foo, :string, :null => false - end + # should be all good w/ a custom sequence name + assert_nothing_raised do + begin + Person.connection.create_table :table_with_name_thats_just_ok, + :sequence_name => 'suitably_short_seq' do |t| + t.column :foo, :string, :null => false + end - Person.connection.execute("select suitably_short_seq.nextval from dual") + Person.connection.execute("select suitably_short_seq.nextval from dual") - ensure - Person.connection.drop_table :table_with_name_thats_just_ok, - :sequence_name => 'suitably_short_seq' rescue nil + ensure + Person.connection.drop_table :table_with_name_thats_just_ok, + :sequence_name => 'suitably_short_seq' rescue nil + end end - end - # confirm the custom sequence got dropped - assert_raise(ActiveRecord::StatementInvalid) do - Person.connection.execute("select suitably_short_seq.nextval from dual") + # confirm the custom sequence got dropped + assert_raise(ActiveRecord::StatementInvalid) do + Person.connection.execute("select suitably_short_seq.nextval from dual") + end end end - def test_out_of_range_limit_should_raise - skip("MySQL and PostgreSQL only") unless current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) - - Person.connection.drop_table :test_limits rescue nil - assert_raise(ActiveRecord::ActiveRecordError, "integer limit didn't raise") do - Person.connection.create_table :test_integer_limits, :force => true do |t| - t.column :bigone, :integer, :limit => 10 + if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) + def test_out_of_range_limit_should_raise + Person.connection.drop_table :test_limits rescue nil + assert_raise(ActiveRecord::ActiveRecordError, "integer limit didn't raise") do + Person.connection.create_table :test_integer_limits, :force => true do |t| + t.column :bigone, :integer, :limit => 10 + end end - end - unless current_adapter?(:PostgreSQLAdapter) - assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do - Person.connection.create_table :test_text_limits, :force => true do |t| - t.column :bigtext, :text, :limit => 0xfffffffff + unless current_adapter?(:PostgreSQLAdapter) + assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do + Person.connection.create_table :test_text_limits, :force => true do |t| + t.column :bigtext, :text, :limit => 0xfffffffff + end end end - end - Person.connection.drop_table :test_limits rescue nil + Person.connection.drop_table :test_limits rescue nil + end end protected diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb index b82409bfbe..c70a8f296f 100644 --- a/activerecord/test/cases/multiparameter_attributes_test.rb +++ b/activerecord/test/cases/multiparameter_attributes_test.rb @@ -268,12 +268,12 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase end end - def test_multiparameter_attributes_setting_time_attribute - return skip "Oracle does not have TIME data type" if current_adapter? :OracleAdapter - - topic = Topic.new( "bonus_time(4i)"=> "01", "bonus_time(5i)" => "05" ) - assert_equal 1, topic.bonus_time.hour - assert_equal 5, topic.bonus_time.min + unless current_adapter? :OracleAdapter + def test_multiparameter_attributes_setting_time_attribute + topic = Topic.new( "bonus_time(4i)"=> "01", "bonus_time(5i)" => "05" ) + assert_equal 1, topic.bonus_time.hour + assert_equal 5, topic.bonus_time.min + end end def test_multiparameter_attributes_setting_date_attribute diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index aa125c70c5..1b915387be 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -185,19 +185,19 @@ end class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase self.use_transactional_fixtures = false - def test_set_primary_key_with_no_connection - return skip("disconnect wipes in-memory db") if in_memory_db? + unless in_memory_db? + def test_set_primary_key_with_no_connection + connection = ActiveRecord::Base.remove_connection - connection = ActiveRecord::Base.remove_connection + model = Class.new(ActiveRecord::Base) + model.primary_key = 'foo' - model = Class.new(ActiveRecord::Base) - model.primary_key = 'foo' + assert_equal 'foo', model.primary_key - assert_equal 'foo', model.primary_key + ActiveRecord::Base.establish_connection(connection) - ActiveRecord::Base.establish_connection(connection) - - assert_equal 'foo', model.primary_key + assert_equal 'foo', model.primary_key + end end end @@ -212,7 +212,6 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter) ensure con.reconnect! end - end end diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb index c171c5e14e..7f99b6841f 100644 --- a/activerecord/test/cases/relation/delegation_test.rb +++ b/activerecord/test/cases/relation/delegation_test.rb @@ -61,8 +61,10 @@ module ActiveRecord end class DelegationRelationTest < DelegationTest + fixtures :comments + def target - Comment.where.not(body: nil) + Comment.where(body: 'Normal type') end [:map, :collect].each do |method| @@ -88,7 +90,7 @@ module ActiveRecord end [:select!, :uniq!].each do |method| - test "##{method} is triggers an immutable error" do + test "##{method} triggers an immutable error" do assert_raises ActiveRecord::ImmutableRelation do assert_responds(target, method) end diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb new file mode 100644 index 0000000000..782c8bc1fd --- /dev/null +++ b/activerecord/test/cases/relation/merging_test.rb @@ -0,0 +1,150 @@ +require 'cases/helper' +require 'models/author' +require 'models/comment' +require 'models/developer' +require 'models/post' +require 'models/project' + +class RelationMergingTest < ActiveRecord::TestCase + fixtures :developers, :comments, :authors, :posts + + def test_relation_merging + devs = Developer.where("salary >= 80000").merge(Developer.limit(2)).merge(Developer.order('id ASC').where("id < 3")) + assert_equal [developers(:david), developers(:jamis)], devs.to_a + + dev_with_count = Developer.limit(1).merge(Developer.order('id DESC')).merge(Developer.select('developers.*')) + assert_equal [developers(:poor_jamis)], dev_with_count.to_a + end + + def test_relation_to_sql + sql = Post.connection.unprepared_statement do + Post.first.comments.to_sql + end + assert_no_match(/\?/, sql) + end + + def test_relation_merging_with_arel_equalities_keeps_last_equality + devs = Developer.where(Developer.arel_table[:salary].eq(80000)).merge( + Developer.where(Developer.arel_table[:salary].eq(9000)) + ) + assert_equal [developers(:poor_jamis)], devs.to_a + end + + def test_relation_merging_with_arel_equalities_keeps_last_equality_with_non_attribute_left_hand + salary_attr = Developer.arel_table[:salary] + devs = Developer.where( + Arel::Nodes::NamedFunction.new('abs', [salary_attr]).eq(80000) + ).merge( + Developer.where( + Arel::Nodes::NamedFunction.new('abs', [salary_attr]).eq(9000) + ) + ) + assert_equal [developers(:poor_jamis)], devs.to_a + end + + def test_relation_merging_with_eager_load + relations = [] + relations << Post.order('comments.id DESC').merge(Post.eager_load(:last_comment)).merge(Post.all) + relations << Post.eager_load(:last_comment).merge(Post.order('comments.id DESC')).merge(Post.all) + + relations.each do |posts| + post = posts.find { |p| p.id == 1 } + assert_equal Post.find(1).last_comment, post.last_comment + end + end + + def test_relation_merging_with_locks + devs = Developer.lock.where("salary >= 80000").order("id DESC").merge(Developer.limit(2)) + assert devs.locked.present? + end + + def test_relation_merging_with_preload + [Post.all.merge(Post.preload(:author)), Post.preload(:author).merge(Post.all)].each do |posts| + assert_queries(2) { assert posts.first.author } + end + end + + def test_relation_merging_with_joins + comments = Comment.joins(:post).where(:body => 'Thank you for the welcome').merge(Post.where(:body => 'Such a lovely day')) + assert_equal 1, comments.count + end + + def test_relation_merging_with_association + assert_queries(2) do # one for loading post, and another one merged query + post = Post.where(:body => 'Such a lovely day').first + comments = Comment.where(:body => 'Thank you for the welcome').merge(post.comments) + assert_equal 1, comments.count + end + end + + test "merge collapses wheres from the LHS only" do + left = Post.where(title: "omg").where(comments_count: 1) + right = Post.where(title: "wtf").where(title: "bbq") + + expected = [left.where_values[1]] + right.where_values + merged = left.merge(right) + + assert_equal expected, merged.where_values + assert !merged.to_sql.include?("omg") + assert merged.to_sql.include?("wtf") + assert merged.to_sql.include?("bbq") + end + + def test_merging_removes_rhs_bind_parameters + left = Post.where(id: Arel::Nodes::BindParam.new('?')) + column = Post.columns_hash['id'] + left.bind_values += [[column, 20]] + right = Post.where(id: 10) + + merged = left.merge(right) + assert_equal [], merged.bind_values + end + + def test_merging_keeps_lhs_bind_parameters + column = Post.columns_hash['id'] + binds = [[column, 20]] + + right = Post.where(id: Arel::Nodes::BindParam.new('?')) + right.bind_values += binds + left = Post.where(id: 10) + + merged = left.merge(right) + assert_equal binds, merged.bind_values + end + + def test_merging_reorders_bind_params + post = Post.first + id_column = Post.columns_hash['id'] + title_column = Post.columns_hash['title'] + + bv = Post.connection.substitute_at id_column, 0 + + right = Post.where(id: bv) + right.bind_values += [[id_column, post.id]] + + left = Post.where(title: bv) + left.bind_values += [[title_column, post.title]] + + merged = left.merge(right) + assert_equal post, merged.first + end +end + +class MergingDifferentRelationsTest < ActiveRecord::TestCase + fixtures :posts, :authors + + test "merging where relations" do + hello_by_bob = Post.where(body: "hello").joins(:author). + merge(Author.where(name: "Bob")).pluck("posts.id") + + assert_equal [posts(:misc_by_bob).id, + posts(:other_by_bob).id], hello_by_bob + end + + test "merging order relations" do + posts_by_author_name = Post.limit(3).joins(:author). + merge(Author.order(:name)).pluck("authors.name") + + assert_equal ["Bob", "Bob", "David"], posts_by_author_name + end +end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index c9c7ac04b3..baa3acf3fb 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -774,75 +774,6 @@ class RelationTest < ActiveRecord::TestCase assert_raises(ArgumentError) { Developer.select } end - def test_relation_merging - devs = Developer.where("salary >= 80000").merge(Developer.limit(2)).merge(Developer.order('id ASC').where("id < 3")) - assert_equal [developers(:david), developers(:jamis)], devs.to_a - - dev_with_count = Developer.limit(1).merge(Developer.order('id DESC')).merge(Developer.select('developers.*')) - assert_equal [developers(:poor_jamis)], dev_with_count.to_a - end - - def test_relation_to_sql - sql = Post.connection.unprepared_statement do - Post.first.comments.to_sql - end - assert_no_match(/\?/, sql) - end - - def test_relation_merging_with_arel_equalities_keeps_last_equality - devs = Developer.where(Developer.arel_table[:salary].eq(80000)).merge( - Developer.where(Developer.arel_table[:salary].eq(9000)) - ) - assert_equal [developers(:poor_jamis)], devs.to_a - end - - def test_relation_merging_with_arel_equalities_keeps_last_equality_with_non_attribute_left_hand - salary_attr = Developer.arel_table[:salary] - devs = Developer.where( - Arel::Nodes::NamedFunction.new('abs', [salary_attr]).eq(80000) - ).merge( - Developer.where( - Arel::Nodes::NamedFunction.new('abs', [salary_attr]).eq(9000) - ) - ) - assert_equal [developers(:poor_jamis)], devs.to_a - end - - def test_relation_merging_with_eager_load - relations = [] - relations << Post.order('comments.id DESC').merge(Post.eager_load(:last_comment)).merge(Post.all) - relations << Post.eager_load(:last_comment).merge(Post.order('comments.id DESC')).merge(Post.all) - - relations.each do |posts| - post = posts.find { |p| p.id == 1 } - assert_equal Post.find(1).last_comment, post.last_comment - end - end - - def test_relation_merging_with_locks - devs = Developer.lock.where("salary >= 80000").order("id DESC").merge(Developer.limit(2)) - assert devs.locked.present? - end - - def test_relation_merging_with_preload - [Post.all.merge(Post.preload(:author)), Post.preload(:author).merge(Post.all)].each do |posts| - assert_queries(2) { assert posts.first.author } - end - end - - def test_relation_merging_with_joins - comments = Comment.joins(:post).where(:body => 'Thank you for the welcome').merge(Post.where(:body => 'Such a lovely day')) - assert_equal 1, comments.count - end - - def test_relation_merging_with_association - assert_queries(2) do # one for loading post, and another one merged query - post = Post.where(:body => 'Such a lovely day').first - comments = Comment.where(:body => 'Thank you for the welcome').merge(post.comments) - assert_equal 1, comments.count - end - end - def test_count posts = Post.all @@ -1597,56 +1528,4 @@ class RelationTest < ActiveRecord::TestCase Array.send(:remove_method, :__omg__) end end - - test "merge collapses wheres from the LHS only" do - left = Post.where(title: "omg").where(comments_count: 1) - right = Post.where(title: "wtf").where(title: "bbq") - - expected = [left.where_values[1]] + right.where_values - merged = left.merge(right) - - assert_equal expected, merged.where_values - assert !merged.to_sql.include?("omg") - assert merged.to_sql.include?("wtf") - assert merged.to_sql.include?("bbq") - end - - def test_merging_removes_rhs_bind_parameters - left = Post.where(id: Arel::Nodes::BindParam.new('?')) - column = Post.columns_hash['id'] - left.bind_values += [[column, 20]] - right = Post.where(id: 10) - - merged = left.merge(right) - assert_equal [], merged.bind_values - end - - def test_merging_keeps_lhs_bind_parameters - column = Post.columns_hash['id'] - binds = [[column, 20]] - - right = Post.where(id: Arel::Nodes::BindParam.new('?')) - right.bind_values += binds - left = Post.where(id: 10) - - merged = left.merge(right) - assert_equal binds, merged.bind_values - end - - def test_merging_reorders_bind_params - post = Post.first - id_column = Post.columns_hash['id'] - title_column = Post.columns_hash['title'] - - bv = Post.connection.substitute_at id_column, 0 - - right = Post.where(id: bv) - right.bind_values += [[id_column, post.id]] - - left = Post.where(title: bv) - left.bind_values += [[title_column, post.title]] - - merged = left.merge(right) - assert_equal post, merged.first - end end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 1ee8e60924..741827446d 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -252,19 +252,20 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{t.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output end - def test_schema_dump_includes_extensions - connection = ActiveRecord::Base.connection - skip unless connection.supports_extensions? - - connection.stubs(:extensions).returns(['hstore']) - output = standard_dump - assert_match "# These are extensions that must be enabled", output - assert_match %r{enable_extension "hstore"}, output - - connection.stubs(:extensions).returns([]) - output = standard_dump - assert_no_match "# These are extensions that must be enabled", output - assert_no_match %r{enable_extension}, output + if ActiveRecord::Base.connection.supports_extensions? + def test_schema_dump_includes_extensions + connection = ActiveRecord::Base.connection + + connection.stubs(:extensions).returns(['hstore']) + output = standard_dump + assert_match "# These are extensions that must be enabled", output + assert_match %r{enable_extension "hstore"}, output + + connection.stubs(:extensions).returns([]) + output = standard_dump + assert_no_match "# These are extensions that must be enabled", output + assert_no_match %r{enable_extension}, output + end end def test_schema_dump_includes_xml_shorthand_definition diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 4fd9d6f52a..2fbe19ab06 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -54,14 +54,14 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal 'Jamis', DeveloperCalledJamis.create!.name end - def test_default_scoping_with_threads - skip "in-memory database mustn't disconnect" if in_memory_db? - - 2.times do - Thread.new { - assert DeveloperOrderedBySalary.all.to_sql.include?('salary DESC') - DeveloperOrderedBySalary.connection.close - }.join + unless in_memory_db? + def test_default_scoping_with_threads + 2.times do + Thread.new { + assert DeveloperOrderedBySalary.all.to_sql.include?('salary DESC') + DeveloperOrderedBySalary.connection.close + }.join + end end end @@ -362,23 +362,21 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal 1, DeveloperWithIncludes.where(:audit_logs => { :message => 'foo' }).count end - def test_default_scope_is_threadsafe - if in_memory_db? - skip "in memory db can't share a db between threads" + unless in_memory_db? + def test_default_scope_is_threadsafe + threads = [] + assert_not_equal 1, ThreadsafeDeveloper.unscoped.count + + threads << Thread.new do + Thread.current[:long_default_scope] = true + assert_equal 1, ThreadsafeDeveloper.all.to_a.count + ThreadsafeDeveloper.connection.close + end + threads << Thread.new do + assert_equal 1, ThreadsafeDeveloper.all.to_a.count + ThreadsafeDeveloper.connection.close + end + threads.each(&:join) end - - threads = [] - assert_not_equal 1, ThreadsafeDeveloper.unscoped.count - - threads << Thread.new do - Thread.current[:long_default_scope] = true - assert_equal 1, ThreadsafeDeveloper.all.to_a.count - ThreadsafeDeveloper.connection.close - end - threads << Thread.new do - assert_equal 1, ThreadsafeDeveloper.all.to_a.count - ThreadsafeDeveloper.connection.close - end - threads.each(&:join) end end diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb index bdcf31043a..d16cf40555 100644 --- a/activerecord/test/cases/tasks/mysql_rake_test.rb +++ b/activerecord/test/cases/tasks/mysql_rake_test.rb @@ -61,103 +61,104 @@ module ActiveRecord ActiveRecord::StatementInvalid.new("Can't create database 'dev'; database exists:") ) - ActiveRecord::Tasks::DatabaseTasks.create @configuration + assert_raises(ActiveRecord::Tasks::DatabaseAlreadyExists) do + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end end end - class MysqlDBCreateAsRootTest < ActiveRecord::TestCase - def setup - unless current_adapter?(:MysqlAdapter) - return skip("only tested on mysql") + if current_adapter?(:MysqlAdapter) + class MysqlDBCreateAsRootTest < ActiveRecord::TestCase + def setup + @connection = stub("Connection", create_database: true) + @error = Mysql::Error.new "Invalid permissions" + @configuration = { + 'adapter' => 'mysql', + 'database' => 'my-app-db', + 'username' => 'pat', + 'password' => 'wossname' + } + + $stdin.stubs(:gets).returns("secret\n") + $stdout.stubs(:print).returns(nil) + @error.stubs(:errno).returns(1045) + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection). + raises(@error). + then.returns(true) end - @connection = stub("Connection", create_database: true) - @error = Mysql::Error.new "Invalid permissions" - @configuration = { - 'adapter' => 'mysql', - 'database' => 'my-app-db', - 'username' => 'pat', - 'password' => 'wossname' - } - - $stdin.stubs(:gets).returns("secret\n") - $stdout.stubs(:print).returns(nil) - @error.stubs(:errno).returns(1045) - ActiveRecord::Base.stubs(:connection).returns(@connection) - ActiveRecord::Base.stubs(:establish_connection). - raises(@error). - then.returns(true) - end + if defined?(::Mysql) + def test_root_password_is_requested + assert_permissions_granted_for "pat" + $stdin.expects(:gets).returns("secret\n") - def test_root_password_is_requested - assert_permissions_granted_for "pat" - skip "only if mysql is available" unless defined?(::Mysql) - $stdin.expects(:gets).returns("secret\n") + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end + end - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + def test_connection_established_as_root + assert_permissions_granted_for "pat" + ActiveRecord::Base.expects(:establish_connection).with( + 'adapter' => 'mysql', + 'database' => nil, + 'username' => 'root', + 'password' => 'secret' + ) - def test_connection_established_as_root - assert_permissions_granted_for "pat" - ActiveRecord::Base.expects(:establish_connection).with( - 'adapter' => 'mysql', - 'database' => nil, - 'username' => 'root', - 'password' => 'secret' - ) + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + def test_database_created_by_root + assert_permissions_granted_for "pat" + @connection.expects(:create_database). + with('my-app-db', :charset => 'utf8', :collation => 'utf8_unicode_ci') - def test_database_created_by_root - assert_permissions_granted_for "pat" - @connection.expects(:create_database). - with('my-app-db', :charset => 'utf8', :collation => 'utf8_unicode_ci') + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + def test_grant_privileges_for_normal_user + assert_permissions_granted_for "pat" + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - def test_grant_privileges_for_normal_user - assert_permissions_granted_for "pat" - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + def test_do_not_grant_privileges_for_root_user + @configuration['username'] = 'root' + @configuration['password'] = '' + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - def test_do_not_grant_privileges_for_root_user - @configuration['username'] = 'root' - @configuration['password'] = '' - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + def test_connection_established_as_normal_user + assert_permissions_granted_for "pat" + ActiveRecord::Base.expects(:establish_connection).returns do + ActiveRecord::Base.expects(:establish_connection).with( + 'adapter' => 'mysql', + 'database' => 'my-app-db', + 'username' => 'pat', + 'password' => 'secret' + ) - def test_connection_established_as_normal_user - assert_permissions_granted_for "pat" - ActiveRecord::Base.expects(:establish_connection).returns do - ActiveRecord::Base.expects(:establish_connection).with( - 'adapter' => 'mysql', - 'database' => 'my-app-db', - 'username' => 'pat', - 'password' => 'secret' - ) + raise @error + end - raise @error + ActiveRecord::Tasks::DatabaseTasks.create @configuration end - ActiveRecord::Tasks::DatabaseTasks.create @configuration - end + def test_sends_output_to_stderr_when_other_errors + @error.stubs(:errno).returns(42) - def test_sends_output_to_stderr_when_other_errors - @error.stubs(:errno).returns(42) + $stderr.expects(:puts).at_least_once.returns(nil) - $stderr.expects(:puts).at_least_once.returns(nil) + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end - ActiveRecord::Tasks::DatabaseTasks.create @configuration + private + def assert_permissions_granted_for(db_user) + db_name = @configuration['database'] + db_password = @configuration['password'] + @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON #{db_name}.* TO '#{db_user}'@'localhost' IDENTIFIED BY '#{db_password}' WITH GRANT OPTION;") + end end - - private - def assert_permissions_granted_for(db_user) - db_name = @configuration['database'] - db_password = @configuration['password'] - @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON #{db_name}.* TO '#{db_user}'@'localhost' IDENTIFIED BY '#{db_password}' WITH GRANT OPTION;") - end end class MySQLDBDropTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb index 6ea225178f..88ce666619 100644 --- a/activerecord/test/cases/tasks/postgresql_rake_test.rb +++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb @@ -59,7 +59,9 @@ module ActiveRecord $stderr.expects(:puts). with("Couldn't create database for #{@configuration.inspect}") - ActiveRecord::Tasks::DatabaseTasks.create @configuration + assert_raises(Exception) do + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end end def test_create_when_database_exists_outputs_info_to_stderr @@ -69,7 +71,9 @@ module ActiveRecord ActiveRecord::StatementInvalid.new('database "my-app-db" already exists') ) - ActiveRecord::Tasks::DatabaseTasks.create @configuration + assert_raises(ActiveRecord::Tasks::DatabaseAlreadyExists) do + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end end end diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb index da3471adf9..eecc3527d2 100644 --- a/activerecord/test/cases/tasks/sqlite_rake_test.rb +++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb @@ -27,7 +27,9 @@ module ActiveRecord $stderr.expects(:puts).with("#{@database} already exists") - ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' + assert_raises(ActiveRecord::Tasks::DatabaseAlreadyExists) do + ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' + end end def test_db_create_with_file_does_nothing @@ -36,7 +38,9 @@ module ActiveRecord ActiveRecord::Base.expects(:establish_connection).never - ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' + assert_raises(ActiveRecord::Tasks::DatabaseAlreadyExists) do + ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' + end end def test_db_create_establishes_a_connection @@ -52,7 +56,9 @@ module ActiveRecord $stderr.expects(:puts). with("Couldn't create database for #{@configuration.inspect}") - ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' + assert_raises(Exception) do + ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root' + end end end diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index 8c6d189b0c..910f46d693 100644 --- a/activerecord/test/cases/test_case.rb +++ b/activerecord/test/cases/test_case.rb @@ -78,7 +78,7 @@ module ActiveRecord # ignored SQL, or better yet, use a different notification for the queries # instead examining the SQL content. oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im] - mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/] + mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i] postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i] sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im] diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index ff1b01556d..8c45f2a3f8 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -11,6 +11,7 @@ class TimestampTest < ActiveRecord::TestCase def setup @developer = Developer.first + @owner = Owner.first @developer.update_columns(updated_at: Time.now.prev_month) @previously_updated_at = @developer.updated_at end @@ -92,6 +93,53 @@ class TimestampTest < ActiveRecord::TestCase assert_nothing_raised { Car.first.touch } end + def test_touching_a_no_touching_object + Developer.no_touching do + assert @developer.no_touching? + assert !@owner.no_touching? + @developer.touch + end + + assert !@developer.no_touching? + assert !@owner.no_touching? + assert_equal @previously_updated_at, @developer.updated_at + end + + def test_touching_related_objects + @owner = Owner.first + @previously_updated_at = @owner.updated_at + + Owner.no_touching do + @owner.pets.first.touch + end + + assert_equal @previously_updated_at, @owner.updated_at + end + + def test_global_no_touching + ActiveRecord::Base.no_touching do + assert @developer.no_touching? + assert @owner.no_touching? + @developer.touch + end + + assert !@developer.no_touching? + assert !@owner.no_touching? + assert_equal @previously_updated_at, @developer.updated_at + end + + def test_no_touching_threadsafe + Thread.new do + Developer.no_touching do + assert @developer.no_touching? + + sleep(1) + end + end + + assert !@developer.no_touching? + end + def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_update_the_parent_updated_at pet = Pet.first owner = pet.owner diff --git a/activerecord/test/cases/transaction_isolation_test.rb b/activerecord/test/cases/transaction_isolation_test.rb index 4f1cb99b68..84c16fb109 100644 --- a/activerecord/test/cases/transaction_isolation_test.rb +++ b/activerecord/test/cases/transaction_isolation_test.rb @@ -1,113 +1,105 @@ require 'cases/helper' -class TransactionIsolationUnsupportedTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false +unless ActiveRecord::Base.connection.supports_transaction_isolation? + class TransactionIsolationUnsupportedTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false - class Tag < ActiveRecord::Base - end - - setup do - if ActiveRecord::Base.connection.supports_transaction_isolation? - skip "database supports transaction isolation; test is irrelevant" + class Tag < ActiveRecord::Base end - end - test "setting the isolation level raises an error" do - assert_raises(ActiveRecord::TransactionIsolationError) do - Tag.transaction(isolation: :serializable) { } + test "setting the isolation level raises an error" do + assert_raises(ActiveRecord::TransactionIsolationError) do + Tag.transaction(isolation: :serializable) { } + end end end end -class TransactionIsolationTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false - - class Tag < ActiveRecord::Base - self.table_name = 'tags' - end - - class Tag2 < ActiveRecord::Base - self.table_name = 'tags' - end +if ActiveRecord::Base.connection.supports_transaction_isolation? + class TransactionIsolationTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false - setup do - unless ActiveRecord::Base.connection.supports_transaction_isolation? - skip "database does not support setting transaction isolation" + class Tag < ActiveRecord::Base + self.table_name = 'tags' end - Tag.establish_connection 'arunit' - Tag2.establish_connection 'arunit' - Tag.destroy_all - end - - # It is impossible to properly test read uncommitted. The SQL standard only - # specifies what must not happen at a certain level, not what must happen. At - # the read uncommitted level, there is nothing that must not happen. - test "read uncommitted" do - unless ActiveRecord::Base.connection.transaction_isolation_levels.include?(:read_uncommitted) - skip "database does not support read uncommitted isolation level" + class Tag2 < ActiveRecord::Base + self.table_name = 'tags' end - Tag.transaction(isolation: :read_uncommitted) do - assert_equal 0, Tag.count - Tag2.create - assert_equal 1, Tag.count + + setup do + Tag.establish_connection 'arunit' + Tag2.establish_connection 'arunit' + Tag.destroy_all end - end - # We are testing that a dirty read does not happen - test "read committed" do - Tag.transaction(isolation: :read_committed) do - assert_equal 0, Tag.count + # It is impossible to properly test read uncommitted. The SQL standard only + # specifies what must not happen at a certain level, not what must happen. At + # the read uncommitted level, there is nothing that must not happen. + if ActiveRecord::Base.connection.transaction_isolation_levels.include?(:read_uncommitted) + test "read uncommitted" do + Tag.transaction(isolation: :read_uncommitted) do + assert_equal 0, Tag.count + Tag2.create + assert_equal 1, Tag.count + end + end + end - Tag2.transaction do - Tag2.create + # We are testing that a dirty read does not happen + test "read committed" do + Tag.transaction(isolation: :read_committed) do assert_equal 0, Tag.count + + Tag2.transaction do + Tag2.create + assert_equal 0, Tag.count + end end + + assert_equal 1, Tag.count end - assert_equal 1, Tag.count - end + # We are testing that a nonrepeatable read does not happen + if ActiveRecord::Base.connection.transaction_isolation_levels.include?(:repeatable_read) + test "repeatable read" do + tag = Tag.create(name: 'jon') - # We are testing that a nonrepeatable read does not happen - test "repeatable read" do - unless ActiveRecord::Base.connection.transaction_isolation_levels.include?(:repeatable_read) - skip "database does not support repeatable read isolation level" - end - tag = Tag.create(name: 'jon') + Tag.transaction(isolation: :repeatable_read) do + tag.reload + Tag2.find(tag.id).update(name: 'emily') - Tag.transaction(isolation: :repeatable_read) do - tag.reload - Tag2.find(tag.id).update(name: 'emily') + tag.reload + assert_equal 'jon', tag.name + end - tag.reload - assert_equal 'jon', tag.name + tag.reload + assert_equal 'emily', tag.name + end end - tag.reload - assert_equal 'emily', tag.name - end - - # We are only testing that there are no errors because it's too hard to - # test serializable. Databases behave differently to enforce the serializability - # constraint. - test "serializable" do - Tag.transaction(isolation: :serializable) do - Tag.create + # We are only testing that there are no errors because it's too hard to + # test serializable. Databases behave differently to enforce the serializability + # constraint. + test "serializable" do + Tag.transaction(isolation: :serializable) do + Tag.create + end end - end - test "setting isolation when joining a transaction raises an error" do - Tag.transaction do - assert_raises(ActiveRecord::TransactionIsolationError) do - Tag.transaction(isolation: :serializable) { } + test "setting isolation when joining a transaction raises an error" do + Tag.transaction do + assert_raises(ActiveRecord::TransactionIsolationError) do + Tag.transaction(isolation: :serializable) { } + end end end - end - test "setting isolation when starting a nested transaction raises error" do - Tag.transaction do - assert_raises(ActiveRecord::TransactionIsolationError) do - Tag.transaction(requires_new: true, isolation: :serializable) { } + test "setting isolation when starting a nested transaction raises error" do + Tag.transaction do + assert_raises(ActiveRecord::TransactionIsolationError) do + Tag.transaction(requires_new: true, isolation: :serializable) { } + end end end end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 980981903a..89dab16975 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -485,6 +485,13 @@ class TransactionTest < ActiveRecord::TestCase raise ActiveRecord::Rollback end end + ensure + begin + Topic.connection.remove_column('topics', 'stuff') + rescue + ensure + Topic.reset_column_information + end end def test_transactions_state_from_rollback @@ -571,23 +578,23 @@ if current_adapter?(:PostgreSQLAdapter) class ConcurrentTransactionTest < TransactionTest # This will cause transactions to overlap and fail unless they are performed on # separate database connections. - def test_transaction_per_thread - skip "in memory db can't share a db between threads" if in_memory_db? - - threads = 3.times.map do - Thread.new do - Topic.transaction do - topic = Topic.find(1) - topic.approved = !topic.approved? - assert topic.save! - topic.approved = !topic.approved? - assert topic.save! + unless in_memory_db? + def test_transaction_per_thread + threads = 3.times.map do + Thread.new do + Topic.transaction do + topic = Topic.find(1) + topic.approved = !topic.approved? + assert topic.save! + topic.approved = !topic.approved? + assert topic.save! + end + Topic.connection.close end - Topic.connection.close end - end - threads.each { |t| t.join } + threads.each { |t| t.join } + end end # Test for dirty reads among simultaneous transactions. diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 2b33f01783..56d8db0be9 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -365,15 +365,15 @@ class UniquenessValidationTest < ActiveRecord::TestCase } end - def test_validate_uniqueness_with_array_column - return skip "Uniqueness on arrays has only been tested in PostgreSQL so far." if !current_adapter? :PostgreSQLAdapter - - e1 = Employee.create("nicknames" => ["john", "johnny"], "commission_by_quarter" => [1000, 1200]) - assert e1.persisted?, "Saving e1" - - e2 = Employee.create("nicknames" => ["john", "johnny"], "commission_by_quarter" => [2200]) - assert !e2.persisted?, "e2 shouldn't be valid" - assert e2.errors[:nicknames].any?, "Should have errors for nicknames" - assert_equal ["has already been taken"], e2.errors[:nicknames], "Should have uniqueness message for nicknames" + if current_adapter? :PostgreSQLAdapter + def test_validate_uniqueness_with_array_column + e1 = Employee.create("nicknames" => ["john", "johnny"], "commission_by_quarter" => [1000, 1200]) + assert e1.persisted?, "Saving e1" + + e2 = Employee.create("nicknames" => ["john", "johnny"], "commission_by_quarter" => [2200]) + assert !e2.persisted?, "e2 shouldn't be valid" + assert e2.errors[:nicknames].any?, "Should have errors for nicknames" + assert_equal ["has already been taken"], e2.errors[:nicknames], "Should have uniqueness message for nicknames" + end end end diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb index 83a710b1b7..15815d56e4 100644 --- a/activerecord/test/cases/yaml_serialization_test.rb +++ b/activerecord/test/cases/yaml_serialization_test.rb @@ -43,4 +43,8 @@ class YamlSerializationTest < ActiveRecord::TestCase t = Psych.load Psych.dump topic assert_equal topic.attributes, t.attributes end + + def test_active_record_relation_serialization + [Topic.all].to_yaml + end end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 0b0b304121..76411ecb37 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -40,6 +40,8 @@ module Namespaced end class Firm < Company + to_param :name + has_many :clients, -> { order "id" }, :dependent => :destroy, :before_remove => :log_before_remove, :after_remove => :log_after_remove has_many :unsorted_clients, :class_name => "Client" has_many :unsorted_clients_with_symbol, :class_name => :Client |