diff options
36 files changed, 309 insertions, 83 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 421e7088cb..699b6fd2d1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ Ruby on Rails is a volunteer effort. We encourage you to pitch in. [Join the tea *We only accept bug reports and pull requests on GitHub*. -* If you have a question about how to use Ruby on Rails, please [ask the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk). +* If you have a question about how to use Ruby on Rails, please [ask it on the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk). * If you have a change or new feature in mind, please [suggest it on the rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code. diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 2b851cc28d..3170389b36 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -16,7 +16,7 @@ module ActionDispatch # env["action_dispatch.parameter_filter"] = [:foo, "bar"] # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]" # - # env["action_dispatch.parameter_filter"] = lambda do |k,v| + # env["action_dispatch.parameter_filter"] = -> (k, v) do # v.reverse! if k =~ /secret/i # end # => reverses the value to all keys matching /secret/i diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb index 15b5a48535..6c7fba00cb 100644 --- a/actionpack/lib/action_dispatch/middleware/reloader.rb +++ b/actionpack/lib/action_dispatch/middleware/reloader.rb @@ -11,9 +11,9 @@ module ActionDispatch # the response body. This is important for streaming responses such as the # following: # - # self.response_body = lambda { |response, output| + # self.response_body = -> (response, output) do # # code here which refers to application models - # } + # end # # Cleanup callbacks will not be called until after the response_body lambda # is evaluated, ensuring that it can refer to application models and other diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 49009a45cc..0a444ddffc 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -418,7 +418,7 @@ module ActionDispatch # A pattern can also point to a +Rack+ endpoint i.e. anything that # responds to +call+: # - # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via: :get + # match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get # match 'photos/:id', to: PhotoRackApp, via: :get # # Yes, controller actions are just rack endpoints # match 'photos/:id', to: PhotosController.action(:show), via: :get @@ -470,7 +470,7 @@ module ActionDispatch # +call+ or a string representing a controller's action. # # match 'path', to: 'controller#action', via: :get - # match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via: :get + # match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get # match 'path', to: RackApp, via: :get # # [:on] @@ -899,7 +899,7 @@ module ActionDispatch # # Requests to routes can be constrained based on specific criteria: # - # constraints(lambda { |req| req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do + # constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do # resources :iphones # end # diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index 60fc9ee1a2..e32f8e219e 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -207,6 +207,7 @@ module ActionView # # => <img alt="Icon" class="menu_icon" src="/icons/icon.gif" /> def image_tag(source, options={}) options = options.symbolize_keys + check_for_image_tag_errors(options) src = options[:src] = path_to_image(source) @@ -325,6 +326,12 @@ module ActionView [size, size] end end + + def check_for_image_tag_errors(options) + if options[:size] && (options[:height] || options[:width]) + raise ArgumentError, "Cannot pass a :size option with a :height or :width option" + end + end end end end diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb index 8a5928477f..38fee3b314 100644 --- a/actionview/lib/action_view/helpers/form_options_helper.rb +++ b/actionview/lib/action_view/helpers/form_options_helper.rb @@ -80,7 +80,7 @@ module ActionView # # When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled. # - # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }}) + # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: -> (category) { category.archived? }}) # # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return: # <select name="post[category_id]" id="post_category_id"> diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 02dce71496..6e6ce20924 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -464,6 +464,14 @@ class AssetTagHelperTest < ActionView::TestCase assert_equal({:size => '16x10'}, options) end + def test_image_tag_raises_an_error_for_competing_size_arguments + exception = assert_raise(ArgumentError) do + image_tag("gold.png", :height => "100", :width => "200", :size => "45x70") + end + + assert_equal("Cannot pass a :size option with a :height or :width option", exception.message) + end + def test_favicon_link_tag FaviconLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end diff --git a/activejob/Rakefile b/activejob/Rakefile index 6c7e6aae6e..0e36bb81b3 100644 --- a/activejob/Rakefile +++ b/activejob/Rakefile @@ -35,7 +35,7 @@ namespace :test do t.libs << 'test' t.test_files = FileList['test/cases/**/*_test.rb'] t.verbose = true - t.warning = true + t.warning = false t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end @@ -54,7 +54,7 @@ namespace :test do t.libs << 'test' t.test_files = FileList['test/integration/**/*_test.rb'] t.verbose = true - t.warning = true + t.warning = false t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end end diff --git a/activejob/lib/active_job/queue_adapters/que_adapter.rb b/activejob/lib/active_job/queue_adapters/que_adapter.rb index 84cc2845b0..a1a41ccc32 100644 --- a/activejob/lib/active_job/queue_adapters/que_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/que_adapter.rb @@ -16,11 +16,11 @@ module ActiveJob # Rails.application.config.active_job.queue_adapter = :que class QueAdapter def enqueue(job) #:nodoc: - JobWrapper.enqueue job.serialize, queue: job.queue_name + JobWrapper.enqueue job.serialize end def enqueue_at(job, timestamp) #:nodoc: - JobWrapper.enqueue job.serialize, queue: job.queue_name, run_at: Time.at(timestamp) + JobWrapper.enqueue job.serialize, run_at: Time.at(timestamp) end class JobWrapper < Que::Job #:nodoc: diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb index 09f5c329cc..96794ffef3 100644 --- a/activejob/test/integration/queuing_test.rb +++ b/activejob/test/integration/queuing_test.rb @@ -11,7 +11,7 @@ class QueuingTest < ActiveSupport::TestCase end test 'should not run jobs queued on a non-listening queue' do - skip if adapter_is?(:inline) || adapter_is?(:sucker_punch) + skip if adapter_is?(:inline) || adapter_is?(:sucker_punch) || adapter_is?(:que) old_queue = TestJob.queue_name begin diff --git a/activejob/test/support/integration/adapters/que.rb b/activejob/test/support/integration/adapters/que.rb index ff5235bdc8..0cd8952a28 100644 --- a/activejob/test/support/integration/adapters/que.rb +++ b/activejob/test/support/integration/adapters/que.rb @@ -23,7 +23,7 @@ module QueJobsManager @thread = Thread.new do loop do - Que::Job.work("integration_tests") + Que::Job.work sleep 0.5 end end diff --git a/activejob/test/support/que/inline.rb b/activejob/test/support/que/inline.rb index 2e210acb6b..0232da1370 100644 --- a/activejob/test/support/que/inline.rb +++ b/activejob/test/support/que/inline.rb @@ -3,7 +3,11 @@ require 'que' Que::Job.class_eval do class << self; alias_method :original_enqueue, :enqueue; end def self.enqueue(*args) - args.pop if args.last.is_a?(Hash) + if args.last.is_a?(Hash) + options = args.pop + options.delete(:run_at) + args << options unless options.empty? + end self.run(*args) end end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 0e799eb40e..09045087d9 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -5,6 +5,21 @@ *Ryuta Kamizono* +* Correctly dump `:options` on `create_table` for MySQL. + + *Ryuta Kamizono* + +* PostgreSQL: `:collation` support for string and text columns. + + Example: + + create_table :foos do |t| + t.string :string_en, collation: 'en_US.UTF-8' + t.text :text_ja, collation: 'ja_JP.UTF-8' + end + + *Ryuta Kamizono* + * Make `unscope` aware of "less than" and "greater than" conditions. *TAKAHASHI Kazuaki* diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 91c7298983..2c7409b2dc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -123,6 +123,8 @@ module ActiveRecord 'f' end + # Quote date/time values for use in SQL input. Includes microseconds + # if the value is a Time responding to usec. def quoted_date(value) if value.acts_like?(:time) zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index f754df93b6..18d943f452 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -14,10 +14,6 @@ module ActiveRecord send m, o end - def visit_AddColumn(o) - "ADD #{accept(o)}" - end - delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql, to: :@conn private :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql @@ -25,7 +21,7 @@ module ActiveRecord def visit_AlterTable(o) sql = "ALTER TABLE #{quote_table_name(o.name)} " - sql << o.adds.map { |col| visit_AddColumn col }.join(' ') + sql << o.adds.map { |col| accept col }.join(' ') sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(' ') sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(' ') end @@ -37,6 +33,10 @@ module ActiveRecord column_sql end + def visit_AddColumnDefinition(o) + "ADD #{accept(o.column)}" + end + def visit_TableDefinition(o) create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE " create_sql << "#{quote_table_name(o.name)} " @@ -70,6 +70,7 @@ module ActiveRecord column_options[:after] = o.after column_options[:auto_increment] = o.auto_increment column_options[:primary_key] = o.primary_key + column_options[:collation] = o.collation column_options end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 4761024ad0..0ccf0c498b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -15,13 +15,16 @@ module ActiveRecord # are typically created by methods in TableDefinition, and added to the # +columns+ attribute of said TableDefinition object, in order to be used # for generating a number of table creation or table changing SQL statements. - class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :sql_type) #:nodoc: + class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type) #:nodoc: def primary_key? primary_key || type.to_sym == :primary_key end end + class AddColumnDefinition < Struct.new(:column) # :nodoc: + end + class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc: end @@ -227,7 +230,7 @@ module ActiveRecord # The +type+ parameter is normally one of the migrations native types, # which is one of the following: # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>, - # <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>, + # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>, # <tt>:binary</tt>, <tt>:boolean</tt>. # @@ -434,6 +437,7 @@ module ActiveRecord column.after = options[:after] column.auto_increment = options[:auto_increment] column.primary_key = type == :primary_key || options[:primary_key] + column.collation = options[:collation] column end @@ -476,7 +480,7 @@ module ActiveRecord def add_column(name, type, options) name = name.to_s type = type.to_sym - @adds << @td.new_column_definition(name, type, options) + @adds << AddColumnDefinition.new(@td.new_column_definition(name, type, options)) end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index 999cb0ec5a..deb014ad46 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -35,12 +35,16 @@ module ActiveRecord default = schema_default(column) if column.has_default? spec[:default] = default unless default.nil? + if collation = schema_collation(column) + spec[:collation] = collation + end + spec end # Lists the valid migration options def migration_keys - [:name, :limit, :precision, :scale, :default, :null] + [:name, :limit, :precision, :scale, :default, :null, :collation] end private @@ -56,6 +60,10 @@ module ActiveRecord type.type_cast_for_schema(default) end end + + def schema_collation(column) + column.collation.inspect if column.collation + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index c4a37f8538..654ed0250e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -14,6 +14,10 @@ module ActiveRecord {} end + def table_options(table_name) + nil + end + # Truncates a table alias according to the limits of the current adapter. def table_alias_for(table_name) table_name[0...table_alias_length].tr('.', '_') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index ae42e8ef8d..0705c22a8c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -387,8 +387,8 @@ module ActiveRecord end end - def new_column(name, default, sql_type_metadata = nil, null = true) - Column.new(name, default, sql_type_metadata, null) + def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) + Column.new(name, default, sql_type_metadata, null, default_function, collation) end def lookup_cast_type(sql_type) # :nodoc: 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 f4f52e85e3..cd51b60616 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -14,7 +14,7 @@ module ActiveRecord end class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition - attr_accessor :charset, :collation + attr_accessor :charset end class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition @@ -28,7 +28,6 @@ module ActiveRecord column.auto_increment = true end column.charset = options[:charset] - column.collation = options[:collation] column end @@ -44,10 +43,6 @@ module ActiveRecord end class SchemaCreation < AbstractAdapter::SchemaCreation - def visit_AddColumn(o) - add_column_position!(super, column_options(o)) - end - private def visit_DropForeignKey(name) @@ -67,6 +62,10 @@ module ActiveRecord create_sql end + def visit_AddColumnDefinition(o) + add_column_position!(super, column_options(o.column)) + end + def visit_ChangeColumnDefinition(o) change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}" add_column_position!(change_column_sql, column_options(o.column)) @@ -75,7 +74,6 @@ module ActiveRecord def column_options(o) column_options = super column_options[:charset] = o.charset - column_options[:collation] = o.collation column_options end @@ -128,20 +126,20 @@ module ActiveRecord spec = super spec.delete(:precision) if /time/ === column.sql_type && column.precision == 0 spec.delete(:limit) if :boolean === column.type + spec + end + + def schema_collation(column) if column.collation && table_name = column.instance_variable_get(:@table_name) @collation_cache ||= {} @collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"] - spec[:collation] = column.collation.inspect if column.collation != @collation_cache[table_name] + column.collation.inspect if column.collation != @collation_cache[table_name] end - spec - end - - def migration_keys - super + [:collation] end + private :schema_collation class Column < ConnectionAdapters::Column # :nodoc: - delegate :strict, :collation, :extra, to: :sql_type_metadata, allow_nil: true + delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true def initialize(*) super @@ -195,12 +193,11 @@ module ActiveRecord end class MysqlTypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc: - attr_reader :collation, :extra, :strict + attr_reader :extra, :strict - def initialize(type_metadata, collation: "", extra: "", strict: false) + def initialize(type_metadata, extra: "", strict: false) super(type_metadata) @type_metadata = type_metadata - @collation = collation @extra = extra @strict = strict end @@ -218,7 +215,7 @@ module ActiveRecord protected def attributes_for_hash - [self.class, @type_metadata, collation, extra, strict] + [self.class, @type_metadata, extra, strict] end end @@ -342,8 +339,8 @@ module ActiveRecord raise NotImplementedError end - def new_column(field, default, sql_type_metadata = nil, null = true) # :nodoc: - Column.new(field, default, sql_type_metadata, null) + def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc: + Column.new(field, default, sql_type_metadata, null, default_function, collation) end # Must return the MySQL error number from the exception, if the exception has an @@ -566,8 +563,8 @@ module ActiveRecord each_hash(result).map do |field| field_name = set_field_encoding(field[:Field]) sql_type = field[:Type] - type_metadata = fetch_type_metadata(sql_type, field[:Collation], field[:Extra]) - new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES") + type_metadata = fetch_type_metadata(sql_type, field[:Extra]) + new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation]) end end end @@ -686,6 +683,16 @@ module ActiveRecord end end + def table_options(table_name) + create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] + + # strip create_definitions and partition_options + raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip + + # strip AUTO_INCREMENT + raw_table_options.sub(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1') + end + # Maps logical Rails types to MySQL-specific data types. def type_to_sql(type, limit = nil, precision = nil, scale = nil) case type.to_s @@ -826,8 +833,8 @@ module ActiveRecord end end - def fetch_type_metadata(sql_type, collation = "", extra = "") - MysqlTypeMetadata.new(super(sql_type), collation: collation, extra: extra, strict: strict_mode?) + def fetch_type_metadata(sql_type, extra = "") + MysqlTypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?) end # MySQL is too stupid to create a temporary table for use subquery, so we have @@ -882,7 +889,7 @@ module ActiveRecord def add_column_sql(table_name, column_name, type, options = {}) td = create_table_definition(table_name) cd = td.new_column_definition(column_name, type, options) - schema_creation.visit_AddColumn cd + schema_creation.accept(AddColumnDefinition.new(cd)) end def change_column_sql(table_name, column_name, type, options = {}) diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index f4dda5154e..4b95b0681d 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -12,7 +12,7 @@ module ActiveRecord ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/ end - attr_reader :name, :null, :sql_type_metadata, :default, :default_function + attr_reader :name, :null, :sql_type_metadata, :default, :default_function, :collation delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true @@ -22,12 +22,13 @@ module ActiveRecord # +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>. # +sql_type_metadata+ is various information about the type of the column # +null+ determines if this column allows +NULL+ values. - def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil) + def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) @name = name @sql_type_metadata = sql_type_metadata @null = null @default = default @default_function = default_function + @collation = collation @table_name = nil end @@ -60,7 +61,7 @@ module ActiveRecord protected def attributes_for_hash - [self.class, name, default, sql_type_metadata, null, default_function] + [self.class, name, default, sql_type_metadata, null, default_function, collation] end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 21631be25c..e97e82f056 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -1,6 +1,6 @@ require 'active_record/connection_adapters/abstract_mysql_adapter' -gem 'mysql2', '~> 0.3.13' +gem 'mysql2', '~> 0.3.18' require 'mysql2' module ActiveRecord diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index b7755c4593..f175730551 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -40,8 +40,7 @@ module ActiveRecord PGconn.quote_ident(name.to_s) end - # Quote date/time values for use in SQL input. Includes microseconds - # if the value is a Time responding to usec. + # Quote date/time values for use in SQL input. def quoted_date(value) #:nodoc: if value.year <= 0 bce_year = format("%04d", -value.year + 1) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 2a229ebf42..662c6b4d38 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -8,6 +8,13 @@ module ActiveRecord o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array) super end + + def add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + super + end end module SchemaStatements @@ -159,18 +166,18 @@ module ActiveRecord # Returns the list of all column definitions for a table. def columns(table_name) # Limit, precision, and scale are all handled by the superclass. - column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod| + column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation| oid = oid.to_i fmod = fmod.to_i type_metadata = fetch_type_metadata(column_name, type, oid, fmod) default_value = extract_value_from_default(default) default_function = extract_default_function(default_value, default) - new_column(column_name, default_value, type_metadata, !notnull, default_function) + new_column(column_name, default_value, type_metadata, !notnull, default_function, collation) end end - def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil) # :nodoc: - PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function) + def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc: + PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function, collation) end # Returns the current database name. @@ -403,13 +410,15 @@ module ActiveRecord super end - # Changes the column of a table. - def change_column(table_name, column_name, type, options = {}) + def change_column(table_name, column_name, type, options = {}) #:nodoc: clear_cache! quoted_table_name = quote_table_name(table_name) quoted_column_name = quote_column_name(column_name) sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale], options[:array]) sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}" + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end if options[:using] sql << " USING #{options[:using]}" elsif options[:cast_as] diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 332ac9d88c..7e15c2ab26 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -772,7 +772,9 @@ module ActiveRecord def column_definitions(table_name) # :nodoc: exec_query(<<-end_sql, 'SCHEMA').rows SELECT a.attname, format_type(a.atttypid, a.atttypmod), - pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod + pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod, + (SELECT c.collname FROM pg_collation c, pg_type t + WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) FROM pg_attribute a LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 98aee77557..0f1759abaa 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -71,9 +71,9 @@ module ActiveRecord class RecordNotDestroyed < ActiveRecordError attr_reader :record - def initialize(record) + def initialize(message, record = nil) @record = record - super() + super(message) end end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index da8f4d027a..466175690e 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -148,7 +148,7 @@ module ActiveRecord # Attributes marked as readonly are silently ignored if the record is # being updated. def save!(*args) - create_or_update(*args) || raise(RecordNotSaved.new(nil, self)) + create_or_update(*args) || raise(RecordNotSaved.new("Failed to save the record", self)) end # Deletes the record in the database and freezes this instance to @@ -193,7 +193,7 @@ module ActiveRecord # and #destroy! raises ActiveRecord::RecordNotDestroyed. # See ActiveRecord::Callbacks for further details. def destroy! - destroy || raise(ActiveRecord::RecordNotDestroyed, self) + destroy || raise(RecordNotDestroyed.new("Failed to destroy the record", self)) end # Returns an instance of the specified +klass+ with the attributes of the diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index bdf4329446..a4a986e6ed 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -131,6 +131,10 @@ HEADER tbl.print ", id: false" end tbl.print ", force: :cascade" + + table_options = @connection.table_options(table) + tbl.print ", options: #{table_options.inspect}" unless table_options.blank? + tbl.puts " do |t|" # then dump all non-primary key columns diff --git a/activerecord/test/cases/adapters/mysql/table_options_test.rb b/activerecord/test/cases/adapters/mysql/table_options_test.rb new file mode 100644 index 0000000000..0e5b0e8aec --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/table_options_test.rb @@ -0,0 +1,42 @@ +require "cases/helper" +require 'support/schema_dumping_helper' + +class MysqlTableOptionsTest < ActiveRecord::TestCase + include SchemaDumpingHelper + + def setup + @connection = ActiveRecord::Base.connection + end + + def teardown + @connection.drop_table "mysql_table_options", if_exists: true + end + + test "table options with ENGINE" do + @connection.create_table "mysql_table_options", force: true, options: "ENGINE=MyISAM" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{ENGINE=MyISAM}, options + end + + test "table options with ROW_FORMAT" do + @connection.create_table "mysql_table_options", force: true, options: "ROW_FORMAT=REDUNDANT" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{ROW_FORMAT=REDUNDANT}, options + end + + test "table options with CHARSET" do + @connection.create_table "mysql_table_options", force: true, options: "CHARSET=utf8mb4" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{CHARSET=utf8mb4}, options + end + + test "table options with COLLATE" do + @connection.create_table "mysql_table_options", force: true, options: "COLLATE=utf8mb4_bin" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{COLLATE=utf8mb4_bin}, options + end +end diff --git a/activerecord/test/cases/adapters/mysql2/table_options_test.rb b/activerecord/test/cases/adapters/mysql2/table_options_test.rb new file mode 100644 index 0000000000..0e5b0e8aec --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/table_options_test.rb @@ -0,0 +1,42 @@ +require "cases/helper" +require 'support/schema_dumping_helper' + +class MysqlTableOptionsTest < ActiveRecord::TestCase + include SchemaDumpingHelper + + def setup + @connection = ActiveRecord::Base.connection + end + + def teardown + @connection.drop_table "mysql_table_options", if_exists: true + end + + test "table options with ENGINE" do + @connection.create_table "mysql_table_options", force: true, options: "ENGINE=MyISAM" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{ENGINE=MyISAM}, options + end + + test "table options with ROW_FORMAT" do + @connection.create_table "mysql_table_options", force: true, options: "ROW_FORMAT=REDUNDANT" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{ROW_FORMAT=REDUNDANT}, options + end + + test "table options with CHARSET" do + @connection.create_table "mysql_table_options", force: true, options: "CHARSET=utf8mb4" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{CHARSET=utf8mb4}, options + end + + test "table options with COLLATE" do + @connection.create_table "mysql_table_options", force: true, options: "COLLATE=utf8mb4_bin" + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{COLLATE=utf8mb4_bin}, options + end +end diff --git a/activerecord/test/cases/adapters/postgresql/collation_test.rb b/activerecord/test/cases/adapters/postgresql/collation_test.rb new file mode 100644 index 0000000000..17ef5f304c --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/collation_test.rb @@ -0,0 +1,53 @@ +require "cases/helper" +require 'support/schema_dumping_helper' + +class PostgresqlCollationTest < ActiveRecord::TestCase + include SchemaDumpingHelper + + def setup + @connection = ActiveRecord::Base.connection + @connection.create_table :postgresql_collations, force: true do |t| + t.string :string_c, collation: 'C' + t.text :text_posix, collation: 'POSIX' + end + end + + def teardown + @connection.drop_table :postgresql_collations, if_exists: true + end + + test "string column with collation" do + column = @connection.columns(:postgresql_collations).find { |c| c.name == 'string_c' } + assert_equal :string, column.type + assert_equal 'C', column.collation + end + + test "text column with collation" do + column = @connection.columns(:postgresql_collations).find { |c| c.name == 'text_posix' } + assert_equal :text, column.type + assert_equal 'POSIX', column.collation + end + + test "add column with collation" do + @connection.add_column :postgresql_collations, :title, :string, collation: 'C' + + column = @connection.columns(:postgresql_collations).find { |c| c.name == 'title' } + assert_equal :string, column.type + assert_equal 'C', column.collation + end + + test "change column with collation" do + @connection.add_column :postgresql_collations, :description, :string + @connection.change_column :postgresql_collations, :description, :text, collation: 'POSIX' + + column = @connection.columns(:postgresql_collations).find { |c| c.name == 'description' } + assert_equal :text, column.type + assert_equal 'POSIX', column.collation + end + + test "schema dump includes collation" do + output = dump_table_schema("postgresql_collations") + assert_match %r{t.string\s+"string_c",\s+collation: "C"$}, output + assert_match %r{t.text\s+"text_posix",\s+collation: "POSIX"$}, output + end +end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 14cdf37f46..2c4e2a875c 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -2131,11 +2131,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase car = Car.create! original_child = FailedBulb.create!(car: car) - assert_raise(ActiveRecord::RecordNotDestroyed) do + error = assert_raise(ActiveRecord::RecordNotDestroyed) do car.failed_bulbs = [FailedBulb.create!] end assert_equal [original_child], car.reload.failed_bulbs + assert_equal "Failed to destroy the record", error.message end test 'updates counter cache when default scope is given' do diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index 3ae4a6eade..73ac30e547 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -451,6 +451,7 @@ class CallbacksTest < ActiveRecord::TestCase assert !david.save exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } assert_equal exc.record, david + assert_equal "Failed to save the record", exc.message end david = ImmutableDeveloper.find(1) @@ -494,6 +495,7 @@ class CallbacksTest < ActiveRecord::TestCase assert !david.destroy exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } assert_equal exc.record, david + assert_equal "Failed to destroy the record", exc.message end assert_not_nil ImmutableDeveloper.find_by_id(1) diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 008503bc24..872fa595b4 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -1,5 +1,16 @@ ActiveRecord::Schema.define do + enable_extension!('uuid-ossp', ActiveRecord::Base.connection) + + create_table :uuid_parents, id: :uuid, force: true do |t| + t.string :name + end + + create_table :uuid_children, id: :uuid, force: true do |t| + t.string :name + t.uuid :uuid_parent_id + end + %w(postgresql_times postgresql_oids defaults postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name| drop_table table_name, if_exists: true diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 7b42f8a4a5..66f8f1611d 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -6,20 +6,6 @@ ActiveRecord::Schema.define do end end - #put adapter specific setup here - case adapter_name - when "PostgreSQL" - enable_extension!('uuid-ossp', ActiveRecord::Base.connection) - create_table :uuid_parents, id: :uuid, force: true do |t| - t.string :name - end - create_table :uuid_children, id: :uuid, force: true do |t| - t.string :name - t.uuid :uuid_parent_id - end - end - - # ------------------------------------------------------------------- # # # # Please keep these create table statements in alphabetical order # diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 0aa2152d56..49834fa8a2 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -883,6 +883,20 @@ this gem such as `whitelist_attributes` or `mass_assignment_sanitizer` options. * To re-enable the old finders, you can use the [activerecord-deprecated_finders gem](https://github.com/rails/activerecord-deprecated_finders). +* Rails 4.0 has changed to default join table for `has_and_belongs_to_many` relations to strip the common prefix off the second table name. Any existing `has_and_belongs_to_many` relationship between models with a common prefix must be specified with the `join_table` option. For example: + +```ruby +CatalogCategory < ActiveRecord::Base + has_and_belongs_to_many :catalog_products, join_table: 'catalog_categories_catalog_products' +end + +CatalogProduct < ActiveRecord::Base + has_and_belongs_to_many :catalog_categories, join_table: 'catalog_categories_catalog_products' +end +``` + +* Note that the the prefix takes scopes into account as well, so relations between `Catalog::Category` and `Catalog::Product` or `Catalog::Category` and `CatalogProduct` need to be updated similarly. + ### Active Resource Rails 4.0 extracted Active Resource to its own gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your Gemfile. |