mirror of https://github.com/rails/rails
Merge branch 'master' into feature/reselect-method
This commit is contained in:
commit
c8ff9bd63a
|
@ -31,7 +31,7 @@ addons:
|
|||
- postgresql-10
|
||||
- postgresql-client-10
|
||||
|
||||
bundler_args: --without test --jobs 3 --retry 3
|
||||
bundler_args: --jobs 3 --retry 3
|
||||
before_install:
|
||||
- "rm ${BUNDLE_GEMFILE}.lock"
|
||||
- "travis_retry gem update --system"
|
||||
|
|
1
Gemfile
1
Gemfile
|
@ -99,6 +99,7 @@ instance_eval File.read local_gemfile if File.exist? local_gemfile
|
|||
|
||||
group :test do
|
||||
gem "minitest-bisect"
|
||||
gem "minitest-retry"
|
||||
|
||||
platforms :mri do
|
||||
gem "stackprof"
|
||||
|
|
|
@ -306,7 +306,7 @@ GEM
|
|||
loofah (2.2.2)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.0)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
marcel (0.3.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
|
@ -323,6 +323,8 @@ GEM
|
|||
minitest-bisect (1.4.0)
|
||||
minitest-server (~> 1.0)
|
||||
path_expander (~> 1.0)
|
||||
minitest-retry (0.1.9)
|
||||
minitest (>= 5.0)
|
||||
minitest-server (1.0.5)
|
||||
minitest (~> 5.0)
|
||||
mono_logger (1.1.0)
|
||||
|
@ -538,6 +540,7 @@ DEPENDENCIES
|
|||
libxml-ruby
|
||||
listen (>= 3.0.5, < 3.2)
|
||||
minitest-bisect
|
||||
minitest-retry
|
||||
mysql2 (>= 0.4.10)
|
||||
nokogiri (>= 1.8.1)
|
||||
pg (>= 0.18.0)
|
||||
|
@ -576,4 +579,4 @@ DEPENDENCIES
|
|||
websocket-client-simple!
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.5
|
||||
1.16.6
|
||||
|
|
|
@ -152,9 +152,9 @@ class BaseTest < ActiveSupport::TestCase
|
|||
assert_equal(2, email.parts.length)
|
||||
assert_equal("multipart/mixed", email.mime_type)
|
||||
assert_equal("text/html", email.parts[0].mime_type)
|
||||
assert_equal("Attachment with content", email.parts[0].body.encoded)
|
||||
assert_equal("Attachment with content", email.parts[0].decoded)
|
||||
assert_equal("application/pdf", email.parts[1].mime_type)
|
||||
assert_equal("VGhpcyBpcyB0ZXN0IEZpbGUgY29udGVudA==\r\n", email.parts[1].body.encoded)
|
||||
assert_equal("This is test File content", email.parts[1].decoded)
|
||||
end
|
||||
|
||||
test "adds the given :body as part" do
|
||||
|
@ -162,9 +162,9 @@ class BaseTest < ActiveSupport::TestCase
|
|||
assert_equal(2, email.parts.length)
|
||||
assert_equal("multipart/mixed", email.mime_type)
|
||||
assert_equal("text/plain", email.parts[0].mime_type)
|
||||
assert_equal("I'm the eggman", email.parts[0].body.encoded)
|
||||
assert_equal("I'm the eggman", email.parts[0].decoded)
|
||||
assert_equal("application/pdf", email.parts[1].mime_type)
|
||||
assert_equal("VGhpcyBpcyB0ZXN0IEZpbGUgY29udGVudA==\r\n", email.parts[1].body.encoded)
|
||||
assert_equal("This is test File content", email.parts[1].decoded)
|
||||
end
|
||||
|
||||
test "can embed an inline attachment" do
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
* Add `ActionController::Parameters#each_value`.
|
||||
|
||||
*Lukáš Zapletal*
|
||||
|
||||
* Deprecate `ActionDispatch::Http::ParameterFilter` in favor of `ActiveSupport::ParameterFilter`.
|
||||
|
||||
*Yoshiyuki Kinjo*
|
||||
|
|
|
@ -352,7 +352,7 @@ module ActionController
|
|||
# the same way as <tt>Hash#each_value</tt>.
|
||||
def each_value(&block)
|
||||
@parameters.each_pair do |key, value|
|
||||
yield [convert_hashes_to_parameters(key, value)]
|
||||
yield convert_hashes_to_parameters(key, value)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -77,11 +77,15 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
|
|||
|
||||
test "each_value carries permitted status" do
|
||||
@params.permit!
|
||||
@params["person"].each_value { |value| assert(value.permitted?) if value == 32 }
|
||||
@params.each_value do |value|
|
||||
assert_predicate(value, :permitted?)
|
||||
end
|
||||
end
|
||||
|
||||
test "each_value carries unpermitted status" do
|
||||
@params["person"].each_value { |value| assert_not(value.permitted?) if value == 32 }
|
||||
@params.each_value do |value|
|
||||
assert_not_predicate(value, :permitted?)
|
||||
end
|
||||
end
|
||||
|
||||
test "each_key converts to hash for permitted" do
|
||||
|
|
|
@ -31,7 +31,7 @@ module ActiveJob
|
|||
# jobs. Since jobs share a single thread pool, long-running jobs will block
|
||||
# short-lived jobs. Fine for dev/test; bad for production.
|
||||
class AsyncAdapter
|
||||
# See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options.
|
||||
# See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ThreadPoolExecutor.html] for executor options.
|
||||
def initialize(**executor_options)
|
||||
@scheduler = Scheduler.new(**executor_options)
|
||||
end
|
||||
|
|
|
@ -197,7 +197,7 @@ module ActiveJob
|
|||
# assert_performed_jobs 2
|
||||
# end
|
||||
#
|
||||
# If a block is passed, that block should cause the specified number of
|
||||
# If a block is passed, asserts that the block will cause the specified number of
|
||||
# jobs to be performed.
|
||||
#
|
||||
# def test_jobs_again
|
||||
|
@ -279,7 +279,7 @@ module ActiveJob
|
|||
# end
|
||||
# end
|
||||
#
|
||||
# If a block is passed, that block should not cause any job to be performed.
|
||||
# If a block is passed, asserts that the block will not cause any job to be performed.
|
||||
#
|
||||
# def test_jobs_again
|
||||
# assert_no_performed_jobs do
|
||||
|
@ -347,7 +347,7 @@ module ActiveJob
|
|||
# end
|
||||
#
|
||||
#
|
||||
# If a block is passed, that block should cause the job to be
|
||||
# If a block is passed, asserts that the block will cause the job to be
|
||||
# enqueued with the given arguments.
|
||||
#
|
||||
# def test_assert_enqueued_with
|
||||
|
|
|
@ -474,5 +474,43 @@ module ActiveModel
|
|||
def _read_attribute(attr)
|
||||
__send__(attr)
|
||||
end
|
||||
|
||||
module AttrNames # :nodoc:
|
||||
DEF_SAFE_NAME = /\A[a-zA-Z_]\w*\z/
|
||||
|
||||
# We want to generate the methods via module_eval rather than
|
||||
# define_method, because define_method is slower on dispatch.
|
||||
# Evaluating many similar methods may use more memory as the instruction
|
||||
# sequences are duplicated and cached (in MRI). define_method may
|
||||
# be slower on dispatch, but if you're careful about the closure
|
||||
# created, then define_method will consume much less memory.
|
||||
#
|
||||
# But sometimes the database might return columns with
|
||||
# characters that are not allowed in normal method names (like
|
||||
# 'my_column(omg)'. So to work around this we first define with
|
||||
# the __temp__ identifier, and then use alias method to rename
|
||||
# it to what we want.
|
||||
#
|
||||
# We are also defining a constant to hold the frozen string of
|
||||
# the attribute name. Using a constant means that we do not have
|
||||
# to allocate an object on each call to the attribute method.
|
||||
# Making it frozen means that it doesn't get duped when used to
|
||||
# key the @attributes in read_attribute.
|
||||
def self.define_attribute_accessor_method(mod, attr_name, writer: false)
|
||||
method_name = "#{attr_name}#{'=' if writer}"
|
||||
if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)
|
||||
yield method_name, "'#{attr_name}'.freeze"
|
||||
else
|
||||
safe_name = attr_name.unpack1("h*")
|
||||
const_name = "ATTR_#{safe_name}"
|
||||
const_set(const_name, attr_name) unless const_defined?(const_name)
|
||||
temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
|
||||
attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
|
||||
yield temp_method_name, attr_name_expr
|
||||
mod.send(:alias_method, method_name, temp_method_name)
|
||||
mod.send(:undef_method, temp_method_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,17 +29,16 @@ module ActiveModel
|
|||
private
|
||||
|
||||
def define_method_attribute=(name)
|
||||
safe_name = name.unpack1("h*")
|
||||
ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name
|
||||
|
||||
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
||||
def __temp__#{safe_name}=(value)
|
||||
name = ::ActiveModel::AttributeMethods::AttrNames::ATTR_#{safe_name}
|
||||
write_attribute(name, value)
|
||||
end
|
||||
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
|
||||
undef_method :__temp__#{safe_name}=
|
||||
STR
|
||||
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
||||
generated_attribute_methods, name, writer: true,
|
||||
) do |temp_method_name, attr_name_expr|
|
||||
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||
def #{temp_method_name}(value)
|
||||
name = #{attr_name_expr}
|
||||
write_attribute(name, value)
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
||||
|
@ -97,15 +96,4 @@ module ActiveModel
|
|||
write_attribute(attribute_name, value)
|
||||
end
|
||||
end
|
||||
|
||||
module AttributeMethods #:nodoc:
|
||||
AttrNames = Module.new {
|
||||
def self.set_name_cache(name, value)
|
||||
const_name = "ATTR_#{name}"
|
||||
unless const_defined? const_name
|
||||
const_set const_name, -value
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
* Add `reselect` method. This is short-hand for `unscope(:select).select(fields)`.
|
||||
* Fix collection cache key with limit and custom select to avoid ambiguous timestamp column error.
|
||||
|
||||
Fixes #33056.
|
||||
|
||||
*Federico Martinez*
|
||||
|
||||
* Add `reselect` method. This is a short-hand for `unscope(:select).select(fields)`.
|
||||
|
||||
Fixes #27340.
|
||||
|
||||
*Willian Gustavo Veiga*
|
||||
|
||||
|
|
|
@ -22,15 +22,6 @@ module ActiveRecord
|
|||
delegate :column_for_attribute, to: :class
|
||||
end
|
||||
|
||||
AttrNames = Module.new {
|
||||
def self.set_name_cache(name, value)
|
||||
const_name = "ATTR_#{name}"
|
||||
unless const_defined? const_name
|
||||
const_set const_name, -value
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
|
||||
|
||||
class GeneratedAttributeMethods < Module #:nodoc:
|
||||
|
@ -270,21 +261,14 @@ module ActiveRecord
|
|||
def respond_to?(name, include_private = false)
|
||||
return false unless super
|
||||
|
||||
case name
|
||||
when :to_partial_path
|
||||
name = "to_partial_path"
|
||||
when :to_model
|
||||
name = "to_model"
|
||||
else
|
||||
name = name.to_s
|
||||
end
|
||||
|
||||
# If the result is true then check for the select case.
|
||||
# For queries selecting a subset of columns, return false for unselected columns.
|
||||
# We check defined?(@attributes) not to issue warnings if called on objects that
|
||||
# have been allocated but not yet initialized.
|
||||
if defined?(@attributes) && self.class.column_names.include?(name)
|
||||
return has_attribute?(name)
|
||||
if defined?(@attributes)
|
||||
if name = self.class.symbol_column_to_string(name.to_sym)
|
||||
return has_attribute?(name)
|
||||
end
|
||||
end
|
||||
|
||||
true
|
||||
|
|
|
@ -8,42 +8,19 @@ module ActiveRecord
|
|||
module ClassMethods # :nodoc:
|
||||
private
|
||||
|
||||
# We want to generate the methods via module_eval rather than
|
||||
# define_method, because define_method is slower on dispatch.
|
||||
# Evaluating many similar methods may use more memory as the instruction
|
||||
# sequences are duplicated and cached (in MRI). define_method may
|
||||
# be slower on dispatch, but if you're careful about the closure
|
||||
# created, then define_method will consume much less memory.
|
||||
#
|
||||
# But sometimes the database might return columns with
|
||||
# characters that are not allowed in normal method names (like
|
||||
# 'my_column(omg)'. So to work around this we first define with
|
||||
# the __temp__ identifier, and then use alias method to rename
|
||||
# it to what we want.
|
||||
#
|
||||
# We are also defining a constant to hold the frozen string of
|
||||
# the attribute name. Using a constant means that we do not have
|
||||
# to allocate an object on each call to the attribute method.
|
||||
# Making it frozen means that it doesn't get duped when used to
|
||||
# key the @attributes in read_attribute.
|
||||
def define_method_attribute(name)
|
||||
safe_name = name.unpack1("h*")
|
||||
temp_method = "__temp__#{safe_name}"
|
||||
|
||||
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
|
||||
sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
|
||||
|
||||
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
||||
def #{temp_method}
|
||||
#{sync_with_transaction_state}
|
||||
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
|
||||
_read_attribute(name) { |n| missing_attribute(n, caller) }
|
||||
end
|
||||
STR
|
||||
|
||||
generated_attribute_methods.module_eval do
|
||||
alias_method name, temp_method
|
||||
undef_method temp_method
|
||||
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
||||
generated_attribute_methods, name
|
||||
) do |temp_method_name, attr_name_expr|
|
||||
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||
def #{temp_method_name}
|
||||
#{sync_with_transaction_state}
|
||||
name = #{attr_name_expr}
|
||||
_read_attribute(name) { |n| missing_attribute(n, caller) }
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,19 +13,19 @@ module ActiveRecord
|
|||
private
|
||||
|
||||
def define_method_attribute=(name)
|
||||
safe_name = name.unpack1("h*")
|
||||
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
|
||||
sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
|
||||
|
||||
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
||||
def __temp__#{safe_name}=(value)
|
||||
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
|
||||
#{sync_with_transaction_state}
|
||||
_write_attribute(name, value)
|
||||
end
|
||||
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
|
||||
undef_method :__temp__#{safe_name}=
|
||||
STR
|
||||
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
||||
generated_attribute_methods, name, writer: true,
|
||||
) do |temp_method_name, attr_name_expr|
|
||||
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||
def #{temp_method_name}(value)
|
||||
name = #{attr_name_expr}
|
||||
#{sync_with_transaction_state}
|
||||
_write_attribute(name, value)
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -20,9 +20,9 @@ module ActiveRecord
|
|||
select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
|
||||
|
||||
if collection.has_limit_or_offset?
|
||||
query = collection.select(column)
|
||||
query = collection.select("#{column} AS collection_cache_key_timestamp")
|
||||
subquery_alias = "subquery_for_cache_key"
|
||||
subquery_column = "#{subquery_alias}.#{timestamp_column}"
|
||||
subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
|
||||
subquery = query.arel.as(subquery_alias)
|
||||
arel = Arel::SelectManager.new(subquery).project(select_values % subquery_column)
|
||||
else
|
||||
|
|
|
@ -348,8 +348,8 @@ module ActiveRecord
|
|||
#
|
||||
# create_table :taggings do |t|
|
||||
# t.references :tag, index: { name: 'index_taggings_on_tag_id' }
|
||||
# t.references :tagger, polymorphic: true, index: true
|
||||
# t.references :taggable, polymorphic: { default: 'Photo' }
|
||||
# t.references :tagger, polymorphic: true
|
||||
# t.references :taggable, polymorphic: { default: 'Photo' }, index: false
|
||||
# end
|
||||
def column(name, type, options = {})
|
||||
name = name.to_s
|
||||
|
|
|
@ -846,17 +846,17 @@ module ActiveRecord
|
|||
# [<tt>:null</tt>]
|
||||
# Whether the column allows nulls. Defaults to true.
|
||||
#
|
||||
# ====== Create a user_id bigint column
|
||||
# ====== Create a user_id bigint column without a index
|
||||
#
|
||||
# add_reference(:products, :user)
|
||||
# add_reference(:products, :user, index: false)
|
||||
#
|
||||
# ====== Create a user_id string column
|
||||
#
|
||||
# add_reference(:products, :user, type: :string)
|
||||
#
|
||||
# ====== Create supplier_id, supplier_type columns and appropriate index
|
||||
# ====== Create supplier_id, supplier_type columns
|
||||
#
|
||||
# add_reference(:products, :supplier, polymorphic: true, index: true)
|
||||
# add_reference(:products, :supplier, polymorphic: true)
|
||||
#
|
||||
# ====== Create a supplier_id column with a unique index
|
||||
#
|
||||
|
@ -884,7 +884,7 @@ module ActiveRecord
|
|||
#
|
||||
# ====== Remove the reference
|
||||
#
|
||||
# remove_reference(:products, :user, index: true)
|
||||
# remove_reference(:products, :user, index: false)
|
||||
#
|
||||
# ====== Remove polymorphic reference
|
||||
#
|
||||
|
@ -892,7 +892,7 @@ module ActiveRecord
|
|||
#
|
||||
# ====== Remove the reference with a foreign key
|
||||
#
|
||||
# remove_reference(:products, :user, index: true, foreign_key: true)
|
||||
# remove_reference(:products, :user, foreign_key: true)
|
||||
#
|
||||
def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
|
||||
if foreign_key
|
||||
|
|
|
@ -125,6 +125,8 @@ module ActiveRecord
|
|||
@advisory_locks_enabled = self.class.type_cast_config_to_boolean(
|
||||
config.fetch(:advisory_locks, true)
|
||||
)
|
||||
|
||||
check_version
|
||||
end
|
||||
|
||||
def replica?
|
||||
|
@ -502,6 +504,9 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
private
|
||||
def check_version
|
||||
end
|
||||
|
||||
def type_map
|
||||
@type_map ||= Type::TypeMap.new.tap do |mapping|
|
||||
initialize_type_map(mapping)
|
||||
|
|
|
@ -54,10 +54,6 @@ module ActiveRecord
|
|||
super(connection, logger, config)
|
||||
|
||||
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
|
||||
|
||||
if version < "5.5.8"
|
||||
raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.5.8."
|
||||
end
|
||||
end
|
||||
|
||||
def version #:nodoc:
|
||||
|
@ -535,6 +531,12 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
private
|
||||
def check_version
|
||||
if version < "5.5.8"
|
||||
raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.5.8."
|
||||
end
|
||||
end
|
||||
|
||||
def combine_multi_statements(total_sql)
|
||||
total_sql.each_with_object([]) do |sql, total_sql_chunks|
|
||||
previous_packet = total_sql_chunks.last
|
||||
|
|
|
@ -43,9 +43,14 @@ module ActiveRecord
|
|||
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
|
||||
conn_params.slice!(*valid_conn_param_keys)
|
||||
|
||||
# The postgres drivers don't allow the creation of an unconnected PG::Connection object,
|
||||
# so just pass a nil connection object for the time being.
|
||||
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
|
||||
conn = PG.connect(conn_params)
|
||||
ConnectionAdapters::PostgreSQLAdapter.new(conn, logger, conn_params, config)
|
||||
rescue ::PG::Error => error
|
||||
if error.message.include?("does not exist")
|
||||
raise ActiveRecord::NoDatabaseError
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -220,15 +225,11 @@ module ActiveRecord
|
|||
@local_tz = nil
|
||||
@max_identifier_length = nil
|
||||
|
||||
connect
|
||||
configure_connection
|
||||
add_pg_encoders
|
||||
@statements = StatementPool.new @connection,
|
||||
self.class.type_cast_config_to_integer(config[:statement_limit])
|
||||
|
||||
if postgresql_version < 90100
|
||||
raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1."
|
||||
end
|
||||
|
||||
add_pg_decoders
|
||||
|
||||
@type_map = Type::HashLookupTypeMap.new
|
||||
|
@ -410,6 +411,12 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
private
|
||||
def check_version
|
||||
if postgresql_version < 90100
|
||||
raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1."
|
||||
end
|
||||
end
|
||||
|
||||
# See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
|
||||
VALUE_LIMIT_VIOLATION = "22001"
|
||||
NUMERIC_VALUE_OUT_OF_RANGE = "22003"
|
||||
|
@ -699,12 +706,6 @@ module ActiveRecord
|
|||
def connect
|
||||
@connection = PG.connect(@connection_parameters)
|
||||
configure_connection
|
||||
rescue ::PG::Error => error
|
||||
if error.message.include?("does not exist")
|
||||
raise ActiveRecord::NoDatabaseError
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
||||
|
|
|
@ -105,11 +105,6 @@ module ActiveRecord
|
|||
|
||||
@active = true
|
||||
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
|
||||
|
||||
if sqlite_version < "3.8.0"
|
||||
raise "Your version of SQLite (#{sqlite_version}) is too old. Active Record supports SQLite >= 3.8."
|
||||
end
|
||||
|
||||
configure_connection
|
||||
end
|
||||
|
||||
|
@ -401,6 +396,12 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
private
|
||||
def check_version
|
||||
if sqlite_version < "3.8.0"
|
||||
raise "Your version of SQLite (#{sqlite_version}) is too old. Active Record supports SQLite >= 3.8."
|
||||
end
|
||||
end
|
||||
|
||||
def initialize_type_map(m = type_map)
|
||||
super
|
||||
register_class_with_limit m, %r(int)i, SQLite3Integer
|
||||
|
|
|
@ -344,34 +344,22 @@ module ActiveRecord
|
|||
# post = Post.allocate
|
||||
# post.init_with(coder)
|
||||
# post.title # => 'hello world'
|
||||
def init_with(coder)
|
||||
def init_with(coder, &block)
|
||||
coder = LegacyYamlAdapter.convert(self.class, coder)
|
||||
@attributes = self.class.yaml_encoder.decode(coder)
|
||||
|
||||
init_internals
|
||||
|
||||
@new_record = coder["new_record"]
|
||||
|
||||
self.class.define_attribute_methods
|
||||
|
||||
yield self if block_given?
|
||||
|
||||
_run_find_callbacks
|
||||
_run_initialize_callbacks
|
||||
|
||||
self
|
||||
attributes = self.class.yaml_encoder.decode(coder)
|
||||
init_with_attributes(attributes, coder["new_record"], &block)
|
||||
end
|
||||
|
||||
##
|
||||
# Initializer used for instantiating objects that have been read from the
|
||||
# database. +attributes+ should be an attributes object, and unlike the
|
||||
# Initialize an empty model object from +attributes+.
|
||||
# +attributes+ should be an attributes object, and unlike the
|
||||
# `initialize` method, no assignment calls are made per attribute.
|
||||
#
|
||||
# :nodoc:
|
||||
def init_from_db(attributes)
|
||||
def init_with_attributes(attributes, new_record = false)
|
||||
init_internals
|
||||
|
||||
@new_record = false
|
||||
@new_record = new_record
|
||||
@attributes = attributes
|
||||
|
||||
self.class.define_attribute_methods
|
||||
|
|
|
@ -388,6 +388,11 @@ module ActiveRecord
|
|||
@column_names ||= columns.map(&:name)
|
||||
end
|
||||
|
||||
def symbol_column_to_string(name_symbol) # :nodoc:
|
||||
@symbol_column_to_string_name_hash ||= column_names.index_by(&:to_sym)
|
||||
@symbol_column_to_string_name_hash[name_symbol]
|
||||
end
|
||||
|
||||
# Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
|
||||
# and columns used for single table inheritance have been removed.
|
||||
def content_columns
|
||||
|
@ -477,6 +482,7 @@ module ActiveRecord
|
|||
def reload_schema_from_cache
|
||||
@arel_table = nil
|
||||
@column_names = nil
|
||||
@symbol_column_to_string_name_hash = nil
|
||||
@attribute_types = nil
|
||||
@content_columns = nil
|
||||
@default_attributes = nil
|
||||
|
|
|
@ -426,7 +426,7 @@ module ActiveRecord
|
|||
existing_record.assign_attributes(assignable_attributes)
|
||||
association(association_name).initialize_attributes(existing_record)
|
||||
else
|
||||
method = "build_#{association_name}"
|
||||
method = :"build_#{association_name}"
|
||||
if respond_to?(method)
|
||||
send(method, assignable_attributes)
|
||||
else
|
||||
|
|
|
@ -209,7 +209,7 @@ module ActiveRecord
|
|||
# new instance of the class. Accepts only keys as strings.
|
||||
def instantiate_instance_of(klass, attributes, column_types = {}, &block)
|
||||
attributes = klass.attributes_builder.build_from_database(attributes, column_types)
|
||||
klass.allocate.init_from_db(attributes, &block)
|
||||
klass.allocate.init_with_attributes(attributes, &block)
|
||||
end
|
||||
|
||||
# Called by +instantiate+ to decide which class to use for a new
|
||||
|
|
|
@ -25,6 +25,8 @@ require "models/user"
|
|||
require "models/member"
|
||||
require "models/membership"
|
||||
require "models/sponsor"
|
||||
require "models/lesson"
|
||||
require "models/student"
|
||||
require "models/country"
|
||||
require "models/treaty"
|
||||
require "models/vertex"
|
||||
|
@ -275,7 +277,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
|
|||
def test_habtm_saving_multiple_relationships
|
||||
new_project = Project.new("name" => "Grimetime")
|
||||
amount_of_developers = 4
|
||||
developers = (0...amount_of_developers).collect { |i| Developer.create(name: "JME #{i}") }.reverse
|
||||
developers = (0...amount_of_developers).reverse_each.map { |i| Developer.create(name: "JME #{i}") }
|
||||
|
||||
new_project.developer_ids = [developers[0].id, developers[1].id]
|
||||
new_project.developers_with_callback_ids = [developers[2].id, developers[3].id]
|
||||
|
@ -780,6 +782,16 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal [projects(:active_record), projects(:action_controller)].map(&:id).sort, developer.project_ids.sort
|
||||
end
|
||||
|
||||
def test_singular_ids_are_reloaded_after_collection_concat
|
||||
student = Student.create(name: "Alberto Almagro")
|
||||
student.lesson_ids
|
||||
|
||||
lesson = Lesson.create(name: "DSI")
|
||||
student.lessons << lesson
|
||||
|
||||
assert_includes student.lesson_ids, lesson.id
|
||||
end
|
||||
|
||||
def test_scoped_find_on_through_association_doesnt_return_read_only_records
|
||||
tag = Post.find(1).tags.find_by_name("General")
|
||||
|
||||
|
|
|
@ -42,6 +42,20 @@ module ActiveRecord
|
|||
assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
|
||||
end
|
||||
|
||||
test "cache_key for relation with custom select and limit" do
|
||||
developers = Developer.where(salary: 100000).order(updated_at: :desc).limit(5)
|
||||
developers_with_select = developers.select("developers.*")
|
||||
last_developer_timestamp = developers.first.updated_at
|
||||
|
||||
assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers_with_select.cache_key)
|
||||
|
||||
/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers_with_select.cache_key
|
||||
|
||||
assert_equal ActiveSupport::Digest.hexdigest(developers_with_select.to_sql), $1
|
||||
assert_equal developers.count.to_s, $2
|
||||
assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
|
||||
end
|
||||
|
||||
test "cache_key for loaded relation" do
|
||||
developers = Developer.where(salary: 100000).order(updated_at: :desc).limit(5).load
|
||||
last_developer_timestamp = developers.first.updated_at
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
* Deprecate `ActiveSupport::Multibyte::Chars.consumes?` in favor of `String#is_utf8?`.
|
||||
|
||||
*Francesco Rodríguez*
|
||||
|
||||
* Fix duration being rounded to a full second.
|
||||
```
|
||||
time = DateTime.parse("2018-1-1")
|
||||
time += 0.51.seconds
|
||||
```
|
||||
Will now correctly add 0.51 second and not 1 full second.
|
||||
|
||||
*Edouard Chin*
|
||||
|
||||
* Deprecate `ActiveSupport::Multibyte::Unicode#normalize` and `ActiveSuppport::Multibyte::Chars#normalize`
|
||||
in favor of `String#unicode_normalize`
|
||||
|
||||
*Francesco Rodríguez*
|
||||
|
||||
* Deprecate `ActiveSupport::Multibyte::Unicode#downcase/upcase/swapcase` in favor of
|
||||
`String#downcase/upcase/swapcase`.
|
||||
|
||||
*Francesco Rodríguez*
|
||||
|
||||
* Add `ActiveSupport::ParameterFilter`.
|
||||
|
||||
*Yoshiyuki Kinjo*
|
||||
|
|
|
@ -411,8 +411,6 @@ module ActiveSupport
|
|||
# to the cache. If you do not want to write the cache when the cache is
|
||||
# not found, use #read_multi.
|
||||
#
|
||||
# Options are passed to the underlying cache implementation.
|
||||
#
|
||||
# Returns a hash with the data for each of the names. For example:
|
||||
#
|
||||
# cache.write("bim", "bam")
|
||||
|
@ -422,6 +420,17 @@ module ActiveSupport
|
|||
# # => { "bim" => "bam",
|
||||
# # "unknown_key" => "Fallback value for key: unknown_key" }
|
||||
#
|
||||
# Options are passed to the underlying cache implementation. For example:
|
||||
#
|
||||
# cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
|
||||
# "buzz"
|
||||
# end
|
||||
# # => {"fizz"=>"buzz"}
|
||||
# cache.read("fizz")
|
||||
# # => "buzz"
|
||||
# sleep(6)
|
||||
# cache.read("fizz")
|
||||
# # => nil
|
||||
def fetch_multi(*names)
|
||||
raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@ class DateTime
|
|||
# instance time. Do not use this method in combination with x.months, use
|
||||
# months_since instead!
|
||||
def since(seconds)
|
||||
self + Rational(seconds.round, 86400)
|
||||
self + Rational(seconds, 86400)
|
||||
end
|
||||
alias :in :since
|
||||
|
||||
|
|
|
@ -62,9 +62,9 @@ module ActiveSupport
|
|||
raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)
|
||||
|
||||
I18n.transliterate(
|
||||
ActiveSupport::Multibyte::Unicode.normalize(
|
||||
ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
|
||||
replacement: replacement)
|
||||
ActiveSupport::Multibyte::Unicode.tidy_bytes(string).unicode_normalize(:nfc),
|
||||
replacement: replacement
|
||||
)
|
||||
end
|
||||
|
||||
# Replaces special characters in a string so that it may be used as part of
|
||||
|
|
|
@ -17,7 +17,7 @@ module ActiveSupport #:nodoc:
|
|||
# through the +mb_chars+ method. Methods which would normally return a
|
||||
# String object now return a Chars object so methods can be chained.
|
||||
#
|
||||
# 'The Perfect String '.mb_chars.downcase.strip.normalize
|
||||
# 'The Perfect String '.mb_chars.downcase.strip
|
||||
# # => #<ActiveSupport::Multibyte::Chars:0x007fdc434ccc10 @wrapped_string="the perfect string">
|
||||
#
|
||||
# Chars objects are perfectly interchangeable with String objects as long as
|
||||
|
@ -76,6 +76,11 @@ module ActiveSupport #:nodoc:
|
|||
# Returns +true+ when the proxy class can handle the string. Returns
|
||||
# +false+ otherwise.
|
||||
def self.consumes?(string)
|
||||
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
||||
ActiveSupport::Multibyte::Chars.consumes? is deprecated and will be
|
||||
removed from Rails 6.1. Use string.is_utf8? instead.
|
||||
MSG
|
||||
|
||||
string.encoding == Encoding::UTF_8
|
||||
end
|
||||
|
||||
|
@ -108,7 +113,7 @@ module ActiveSupport #:nodoc:
|
|||
#
|
||||
# 'Café'.mb_chars.reverse.to_s # => 'éfaC'
|
||||
def reverse
|
||||
chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack("U*"))
|
||||
chars(@wrapped_string.scan(/\X/).reverse.join)
|
||||
end
|
||||
|
||||
# Limits the byte size of the string to a number of bytes without breaking
|
||||
|
@ -120,40 +125,12 @@ module ActiveSupport #:nodoc:
|
|||
slice(0...translate_offset(limit))
|
||||
end
|
||||
|
||||
# Converts characters in the string to uppercase.
|
||||
#
|
||||
# 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
|
||||
def upcase
|
||||
chars Unicode.upcase(@wrapped_string)
|
||||
end
|
||||
|
||||
# Converts characters in the string to lowercase.
|
||||
#
|
||||
# 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
|
||||
def downcase
|
||||
chars Unicode.downcase(@wrapped_string)
|
||||
end
|
||||
|
||||
# Converts characters in the string to the opposite case.
|
||||
#
|
||||
# 'El Cañón'.mb_chars.swapcase.to_s # => "eL cAÑÓN"
|
||||
def swapcase
|
||||
chars Unicode.swapcase(@wrapped_string)
|
||||
end
|
||||
|
||||
# Converts the first character to uppercase and the remainder to lowercase.
|
||||
#
|
||||
# 'über'.mb_chars.capitalize.to_s # => "Über"
|
||||
def capitalize
|
||||
(slice(0) || chars("")).upcase + (slice(1..-1) || chars("")).downcase
|
||||
end
|
||||
|
||||
# Capitalizes the first letter of every word, when possible.
|
||||
#
|
||||
# "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró"
|
||||
# "日本語".mb_chars.titleize.to_s # => "日本語"
|
||||
def titleize
|
||||
chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1) })
|
||||
chars(downcase.to_s.gsub(/\b('?\S)/u) { $1.upcase })
|
||||
end
|
||||
alias_method :titlecase, :titleize
|
||||
|
||||
|
@ -165,7 +142,24 @@ module ActiveSupport #:nodoc:
|
|||
# <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
|
||||
# ActiveSupport::Multibyte::Unicode.default_normalization_form
|
||||
def normalize(form = nil)
|
||||
chars(Unicode.normalize(@wrapped_string, form))
|
||||
form ||= Unicode.default_normalization_form
|
||||
|
||||
# See https://www.unicode.org/reports/tr15, Table 1
|
||||
if alias_form = Unicode::NORMALIZATION_FORM_ALIASES[form]
|
||||
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
||||
ActiveSupport::Multibyte::Chars#normalize is deprecated and will be
|
||||
removed from Rails 6.1. Use #unicode_normalize(:#{alias_form}) instead.
|
||||
MSG
|
||||
|
||||
send(:unicode_normalize, alias_form)
|
||||
else
|
||||
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
||||
ActiveSupport::Multibyte::Chars#normalize is deprecated and will be
|
||||
removed from Rails 6.1. Use #unicode_normalize instead.
|
||||
MSG
|
||||
|
||||
raise ArgumentError, "#{form} is not a valid normalization variant", caller
|
||||
end
|
||||
end
|
||||
|
||||
# Performs canonical decomposition on all the characters.
|
||||
|
@ -189,7 +183,7 @@ module ActiveSupport #:nodoc:
|
|||
# 'क्षि'.mb_chars.length # => 4
|
||||
# 'क्षि'.mb_chars.grapheme_length # => 3
|
||||
def grapheme_length
|
||||
Unicode.unpack_graphemes(@wrapped_string).length
|
||||
@wrapped_string.scan(/\X/).length
|
||||
end
|
||||
|
||||
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
|
||||
|
@ -205,7 +199,7 @@ module ActiveSupport #:nodoc:
|
|||
to_s.as_json(options)
|
||||
end
|
||||
|
||||
%w(capitalize downcase reverse tidy_bytes upcase).each do |method|
|
||||
%w(reverse tidy_bytes).each do |method|
|
||||
define_method("#{method}!") do |*args|
|
||||
@wrapped_string = send(method, *args).to_s
|
||||
self
|
||||
|
|
|
@ -6,10 +6,17 @@ module ActiveSupport
|
|||
extend self
|
||||
|
||||
# A list of all available normalization forms.
|
||||
# See http://www.unicode.org/reports/tr15/tr15-29.html for more
|
||||
# See https://www.unicode.org/reports/tr15/tr15-29.html for more
|
||||
# information about normalization.
|
||||
NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
|
||||
|
||||
NORMALIZATION_FORM_ALIASES = { # :nodoc:
|
||||
c: :nfc,
|
||||
d: :nfd,
|
||||
kc: :nfkc,
|
||||
kd: :nfkd
|
||||
}
|
||||
|
||||
# The Unicode version that is supported by the implementation
|
||||
UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"]
|
||||
|
||||
|
@ -100,31 +107,34 @@ module ActiveSupport
|
|||
# Default is ActiveSupport::Multibyte::Unicode.default_normalization_form.
|
||||
def normalize(string, form = nil)
|
||||
form ||= @default_normalization_form
|
||||
# See http://www.unicode.org/reports/tr15, Table 1
|
||||
case form
|
||||
when :d
|
||||
string.unicode_normalize(:nfd)
|
||||
when :c
|
||||
string.unicode_normalize(:nfc)
|
||||
when :kd
|
||||
string.unicode_normalize(:nfkd)
|
||||
when :kc
|
||||
string.unicode_normalize(:nfkc)
|
||||
|
||||
# See https://www.unicode.org/reports/tr15, Table 1
|
||||
if alias_form = NORMALIZATION_FORM_ALIASES[form]
|
||||
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
||||
ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be
|
||||
removed from Rails 6.1. Use String#unicode_normalize(:#{alias_form}) instead.
|
||||
MSG
|
||||
|
||||
string.unicode_normalize(alias_form)
|
||||
else
|
||||
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
||||
ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be
|
||||
removed from Rails 6.1. Use String#unicode_normalize instead.
|
||||
MSG
|
||||
|
||||
raise ArgumentError, "#{form} is not a valid normalization variant", caller
|
||||
end
|
||||
end
|
||||
|
||||
def downcase(string)
|
||||
string.downcase
|
||||
end
|
||||
%w(downcase upcase swapcase).each do |method|
|
||||
define_method(method) do |string|
|
||||
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
||||
ActiveSupport::Multibyte::Unicode##{method} is deprecated and
|
||||
will be removed from Rails 6.1. Use String methods directly.
|
||||
MSG
|
||||
|
||||
def upcase(string)
|
||||
string.upcase
|
||||
end
|
||||
|
||||
def swapcase(string)
|
||||
string.swapcase
|
||||
string.send(method)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -152,8 +152,8 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
|
|||
assert_equal DateTime.civil(2005, 2, 22, 11, 10, 10), DateTime.civil(2005, 2, 22, 10, 10, 10).since(3600)
|
||||
assert_equal DateTime.civil(2005, 2, 24, 10, 10, 10), DateTime.civil(2005, 2, 22, 10, 10, 10).since(86400 * 2)
|
||||
assert_equal DateTime.civil(2005, 2, 24, 11, 10, 35), DateTime.civil(2005, 2, 22, 10, 10, 10).since(86400 * 2 + 3600 + 25)
|
||||
assert_equal DateTime.civil(2005, 2, 22, 10, 10, 11), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1.333)
|
||||
assert_equal DateTime.civil(2005, 2, 22, 10, 10, 12), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1.667)
|
||||
assert_not_equal DateTime.civil(2005, 2, 22, 10, 10, 11), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1.333)
|
||||
assert_not_equal DateTime.civil(2005, 2, 22, 10, 10, 12), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1.667)
|
||||
end
|
||||
|
||||
def test_change
|
||||
|
|
|
@ -157,6 +157,16 @@ class TestJSONEncoding < ActiveSupport::TestCase
|
|||
assert_equal({ "foo" => "hello" }, JSON.parse(json))
|
||||
end
|
||||
|
||||
def test_struct_to_json_with_options_nested
|
||||
klass = Struct.new(:foo, :bar)
|
||||
struct = klass.new "hello", "world"
|
||||
parent_struct = klass.new struct, "world"
|
||||
json = parent_struct.to_json only: [:foo]
|
||||
|
||||
assert_equal({ "foo" => { "foo" => "hello" } }, JSON.parse(json))
|
||||
end
|
||||
|
||||
|
||||
def test_hash_should_pass_encoding_options_to_children_in_as_json
|
||||
person = {
|
||||
name: "John",
|
||||
|
|
|
@ -73,9 +73,15 @@ class MultibyteCharsTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
def test_consumes_utf8_strings
|
||||
assert @proxy_class.consumes?(UNICODE_STRING)
|
||||
assert @proxy_class.consumes?(ASCII_STRING)
|
||||
assert_not @proxy_class.consumes?(BYTE_STRING)
|
||||
ActiveSupport::Deprecation.silence do
|
||||
assert @proxy_class.consumes?(UNICODE_STRING)
|
||||
assert @proxy_class.consumes?(ASCII_STRING)
|
||||
assert_not @proxy_class.consumes?(BYTE_STRING)
|
||||
end
|
||||
end
|
||||
|
||||
def test_consumes_is_deprecated
|
||||
assert_deprecated { @proxy_class.consumes?(UNICODE_STRING) }
|
||||
end
|
||||
|
||||
def test_concatenation_should_return_a_proxy_class_instance
|
||||
|
@ -165,7 +171,9 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
|
|||
assert chars("").upcase.kind_of?(ActiveSupport::Multibyte.proxy_class)
|
||||
assert chars("").downcase.kind_of?(ActiveSupport::Multibyte.proxy_class)
|
||||
assert chars("").capitalize.kind_of?(ActiveSupport::Multibyte.proxy_class)
|
||||
assert chars("").normalize.kind_of?(ActiveSupport::Multibyte.proxy_class)
|
||||
ActiveSupport::Deprecation.silence do
|
||||
assert chars("").normalize.kind_of?(ActiveSupport::Multibyte.proxy_class)
|
||||
end
|
||||
assert chars("").decompose.kind_of?(ActiveSupport::Multibyte.proxy_class)
|
||||
assert chars("").compose.kind_of?(ActiveSupport::Multibyte.proxy_class)
|
||||
assert chars("").tidy_bytes.kind_of?(ActiveSupport::Multibyte.proxy_class)
|
||||
|
@ -383,10 +391,12 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
|
|||
def test_reverse_should_work_with_normalized_strings
|
||||
str = "bös"
|
||||
reversed_str = "söb"
|
||||
assert_equal chars(reversed_str).normalize(:kc), chars(str).normalize(:kc).reverse
|
||||
assert_equal chars(reversed_str).normalize(:c), chars(str).normalize(:c).reverse
|
||||
assert_equal chars(reversed_str).normalize(:d), chars(str).normalize(:d).reverse
|
||||
assert_equal chars(reversed_str).normalize(:kd), chars(str).normalize(:kd).reverse
|
||||
ActiveSupport::Deprecation.silence do
|
||||
assert_equal chars(reversed_str).normalize(:kc), chars(str).normalize(:kc).reverse
|
||||
assert_equal chars(reversed_str).normalize(:c), chars(str).normalize(:c).reverse
|
||||
assert_equal chars(reversed_str).normalize(:d), chars(str).normalize(:d).reverse
|
||||
assert_equal chars(reversed_str).normalize(:kd), chars(str).normalize(:kd).reverse
|
||||
end
|
||||
assert_equal chars(reversed_str).decompose, chars(str).decompose.reverse
|
||||
assert_equal chars(reversed_str).compose, chars(str).compose.reverse
|
||||
end
|
||||
|
@ -477,7 +487,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
|
|||
|
||||
def test_method_works_for_proxyed_methods
|
||||
assert_equal "ll", "hello".mb_chars.method(:slice).call(2..3) # Defined on Chars
|
||||
chars = "hello".mb_chars
|
||||
chars = +"hello".mb_chars
|
||||
assert_equal "Hello", chars.method(:capitalize!).call # Defined on Chars
|
||||
assert_equal "Hello", chars
|
||||
assert_equal "jello", "hello".mb_chars.method(:gsub).call(/h/, "j") # Defined on String
|
||||
|
@ -568,7 +578,9 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
|
|||
def test_composition_exclusion_is_set_up_properly
|
||||
# Normalization of DEVANAGARI LETTER QA breaks when composition exclusion isn't used correctly
|
||||
qa = [0x915, 0x93c].pack("U*")
|
||||
assert_equal qa, chars(qa).normalize(:c)
|
||||
ActiveSupport::Deprecation.silence do
|
||||
assert_equal qa, chars(qa).normalize(:c)
|
||||
end
|
||||
end
|
||||
|
||||
# Test for the Public Review Issue #29, bad explanation of composition might lead to a
|
||||
|
@ -578,17 +590,21 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
|
|||
[0x0B47, 0x0300, 0x0B3E],
|
||||
[0x1100, 0x0300, 0x1161]
|
||||
].map { |c| c.pack("U*") }.each do |c|
|
||||
assert_equal_codepoints c, chars(c).normalize(:c)
|
||||
ActiveSupport::Deprecation.silence do
|
||||
assert_equal_codepoints c, chars(c).normalize(:c)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_normalization_shouldnt_strip_null_bytes
|
||||
null_byte_str = "Test\0test"
|
||||
|
||||
assert_equal null_byte_str, chars(null_byte_str).normalize(:kc)
|
||||
assert_equal null_byte_str, chars(null_byte_str).normalize(:c)
|
||||
assert_equal null_byte_str, chars(null_byte_str).normalize(:d)
|
||||
assert_equal null_byte_str, chars(null_byte_str).normalize(:kd)
|
||||
ActiveSupport::Deprecation.silence do
|
||||
assert_equal null_byte_str, chars(null_byte_str).normalize(:kc)
|
||||
assert_equal null_byte_str, chars(null_byte_str).normalize(:c)
|
||||
assert_equal null_byte_str, chars(null_byte_str).normalize(:d)
|
||||
assert_equal null_byte_str, chars(null_byte_str).normalize(:kd)
|
||||
end
|
||||
assert_equal null_byte_str, chars(null_byte_str).decompose
|
||||
assert_equal null_byte_str, chars(null_byte_str).compose
|
||||
end
|
||||
|
@ -601,11 +617,13 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
|
|||
323 # COMBINING DOT BELOW
|
||||
].pack("U*")
|
||||
|
||||
assert_equal_codepoints "", chars("").normalize
|
||||
assert_equal_codepoints [44, 105, 106, 328, 323].pack("U*"), chars(comp_str).normalize(:kc).to_s
|
||||
assert_equal_codepoints [44, 307, 328, 323].pack("U*"), chars(comp_str).normalize(:c).to_s
|
||||
assert_equal_codepoints [44, 307, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:d).to_s
|
||||
assert_equal_codepoints [44, 105, 106, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:kd).to_s
|
||||
ActiveSupport::Deprecation.silence do
|
||||
assert_equal_codepoints "", chars("").normalize
|
||||
assert_equal_codepoints [44, 105, 106, 328, 323].pack("U*"), chars(comp_str).normalize(:kc).to_s
|
||||
assert_equal_codepoints [44, 307, 328, 323].pack("U*"), chars(comp_str).normalize(:c).to_s
|
||||
assert_equal_codepoints [44, 307, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:d).to_s
|
||||
assert_equal_codepoints [44, 105, 106, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:kd).to_s
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_compute_grapheme_length
|
||||
|
@ -719,6 +737,41 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
|
|||
assert_equal BYTE_STRING.dup.mb_chars.class, ActiveSupport::Multibyte::Chars
|
||||
end
|
||||
|
||||
def test_unicode_normalize_deprecation
|
||||
# String#unicode_normalize default form is `:nfc`, and
|
||||
# different than Multibyte::Unicode default, `:nkfc`.
|
||||
# Deprecation should suggest the right form if no params
|
||||
# are given and default is used.
|
||||
assert_deprecated(/unicode_normalize\(:nfkc\)/) do
|
||||
ActiveSupport::Multibyte::Unicode.normalize("")
|
||||
end
|
||||
|
||||
assert_deprecated(/unicode_normalize\(:nfd\)/) do
|
||||
ActiveSupport::Multibyte::Unicode.normalize("", :d)
|
||||
end
|
||||
end
|
||||
|
||||
def test_chars_normalize_deprecation
|
||||
# String#unicode_normalize default form is `:nfc`, and
|
||||
# different than Multibyte::Unicode default, `:nkfc`.
|
||||
# Deprecation should suggest the right form if no params
|
||||
# are given and default is used.
|
||||
assert_deprecated(/unicode_normalize\(:nfkc\)/) do
|
||||
"".mb_chars.normalize
|
||||
end
|
||||
|
||||
assert_deprecated(/unicode_normalize\(:nfc\)/) { "".mb_chars.normalize(:c) }
|
||||
assert_deprecated(/unicode_normalize\(:nfd\)/) { "".mb_chars.normalize(:d) }
|
||||
assert_deprecated(/unicode_normalize\(:nfkc\)/) { "".mb_chars.normalize(:kc) }
|
||||
assert_deprecated(/unicode_normalize\(:nfkd\)/) { "".mb_chars.normalize(:kd) }
|
||||
end
|
||||
|
||||
def test_unicode_deprecations
|
||||
assert_deprecated { ActiveSupport::Multibyte::Unicode.downcase("") }
|
||||
assert_deprecated { ActiveSupport::Multibyte::Unicode.upcase("") }
|
||||
assert_deprecated { ActiveSupport::Multibyte::Unicode.swapcase("") }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def string_from_classes(classes)
|
||||
|
|
|
@ -18,64 +18,72 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
def test_normalizations_C
|
||||
each_line_of_norm_tests do |*cols|
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
ActiveSupport::Deprecation.silence do
|
||||
each_line_of_norm_tests do |*cols|
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
|
||||
# CONFORMANCE:
|
||||
# 1. The following invariants must be true for all conformant implementations
|
||||
#
|
||||
# NFC
|
||||
# c2 == NFC(c1) == NFC(c2) == NFC(c3)
|
||||
assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}"
|
||||
assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}"
|
||||
assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}"
|
||||
#
|
||||
# c4 == NFC(c4) == NFC(c5)
|
||||
assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}"
|
||||
# CONFORMANCE:
|
||||
# 1. The following invariants must be true for all conformant implementations
|
||||
#
|
||||
# NFC
|
||||
# c2 == NFC(c1) == NFC(c2) == NFC(c3)
|
||||
assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}"
|
||||
assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}"
|
||||
assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}"
|
||||
#
|
||||
# c4 == NFC(c4) == NFC(c5)
|
||||
assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_normalizations_D
|
||||
each_line_of_norm_tests do |*cols|
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
#
|
||||
# NFD
|
||||
# c3 == NFD(c1) == NFD(c2) == NFD(c3)
|
||||
assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}"
|
||||
assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}"
|
||||
assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}"
|
||||
# c5 == NFD(c4) == NFD(c5)
|
||||
assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}"
|
||||
ActiveSupport::Deprecation.silence do
|
||||
each_line_of_norm_tests do |*cols|
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
#
|
||||
# NFD
|
||||
# c3 == NFD(c1) == NFD(c2) == NFD(c3)
|
||||
assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}"
|
||||
assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}"
|
||||
assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}"
|
||||
# c5 == NFD(c4) == NFD(c5)
|
||||
assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_normalizations_KC
|
||||
each_line_of_norm_tests do | *cols |
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
#
|
||||
# NFKC
|
||||
# c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5)
|
||||
assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}"
|
||||
ActiveSupport::Deprecation.silence do
|
||||
each_line_of_norm_tests do | *cols |
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
#
|
||||
# NFKC
|
||||
# c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5)
|
||||
assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_normalizations_KD
|
||||
each_line_of_norm_tests do | *cols |
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
#
|
||||
# NFKD
|
||||
# c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5)
|
||||
assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}"
|
||||
ActiveSupport::Deprecation.silence do
|
||||
each_line_of_norm_tests do | *cols |
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
#
|
||||
# NFKD
|
||||
# c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5)
|
||||
assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -18,64 +18,72 @@ class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
def test_normalizations_C
|
||||
each_line_of_norm_tests do |*cols|
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
ActiveSupport::Deprecation.silence do
|
||||
each_line_of_norm_tests do |*cols|
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
|
||||
# CONFORMANCE:
|
||||
# 1. The following invariants must be true for all conformant implementations
|
||||
#
|
||||
# NFC
|
||||
# c2 == NFC(c1) == NFC(c2) == NFC(c3)
|
||||
assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}"
|
||||
assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}"
|
||||
assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}"
|
||||
#
|
||||
# c4 == NFC(c4) == NFC(c5)
|
||||
assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}"
|
||||
# CONFORMANCE:
|
||||
# 1. The following invariants must be true for all conformant implementations
|
||||
#
|
||||
# NFC
|
||||
# c2 == NFC(c1) == NFC(c2) == NFC(c3)
|
||||
assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}"
|
||||
assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}"
|
||||
assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}"
|
||||
#
|
||||
# c4 == NFC(c4) == NFC(c5)
|
||||
assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_normalizations_D
|
||||
each_line_of_norm_tests do |*cols|
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
#
|
||||
# NFD
|
||||
# c3 == NFD(c1) == NFD(c2) == NFD(c3)
|
||||
assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}"
|
||||
assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}"
|
||||
assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}"
|
||||
# c5 == NFD(c4) == NFD(c5)
|
||||
assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}"
|
||||
ActiveSupport::Deprecation.silence do
|
||||
each_line_of_norm_tests do |*cols|
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
#
|
||||
# NFD
|
||||
# c3 == NFD(c1) == NFD(c2) == NFD(c3)
|
||||
assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}"
|
||||
assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}"
|
||||
assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}"
|
||||
# c5 == NFD(c4) == NFD(c5)
|
||||
assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_normalizations_KC
|
||||
each_line_of_norm_tests do | *cols |
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
#
|
||||
# NFKC
|
||||
# c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5)
|
||||
assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}"
|
||||
ActiveSupport::Deprecation.silence do
|
||||
each_line_of_norm_tests do | *cols |
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
#
|
||||
# NFKC
|
||||
# c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5)
|
||||
assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}"
|
||||
assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_normalizations_KD
|
||||
each_line_of_norm_tests do | *cols |
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
#
|
||||
# NFKD
|
||||
# c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5)
|
||||
assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}"
|
||||
ActiveSupport::Deprecation.silence do
|
||||
each_line_of_norm_tests do | *cols |
|
||||
col1, col2, col3, col4, col5, comment = *cols
|
||||
#
|
||||
# NFKD
|
||||
# c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5)
|
||||
assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}"
|
||||
assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -166,7 +166,7 @@ NOTE: Support for parsing XML parameters has been extracted into a gem named `ac
|
|||
The `params` hash will always contain the `:controller` and `:action` keys, but you should use the methods `controller_name` and `action_name` instead to access these values. Any other parameters defined by the routing, such as `:id`, will also be available. As an example, consider a listing of clients where the list can show either active or inactive clients. We can add a route which captures the `:status` parameter in a "pretty" URL:
|
||||
|
||||
```ruby
|
||||
get '/clients/:status' => 'clients#index', foo: 'bar'
|
||||
get '/clients/:status', to: 'clients#index', foo: 'bar'
|
||||
```
|
||||
|
||||
In this case, when a user opens the URL `/clients/active`, `params[:status]` will be set to "active". When this route is used, `params[:foo]` will also be set to "bar", as if it were passed in the query string. Your controller will also receive `params[:action]` as "index" and `params[:controller]` as "clients".
|
||||
|
|
|
@ -82,9 +82,9 @@ of two or more words, the model class name should follow the Ruby conventions,
|
|||
using the CamelCase form, while the table name must contain the words separated
|
||||
by underscores. Examples:
|
||||
|
||||
* Database Table - Plural with underscores separating words (e.g., `book_clubs`).
|
||||
* Model Class - Singular with the first letter of each word capitalized (e.g.,
|
||||
`BookClub`).
|
||||
* Database Table - Plural with underscores separating words (e.g., `book_clubs`).
|
||||
|
||||
| Model / Class | Table / Schema |
|
||||
| ---------------- | -------------- |
|
||||
|
|
|
@ -109,7 +109,7 @@ class CreateBooks < ActiveRecord::Migration[5.0]
|
|||
end
|
||||
|
||||
create_table :books do |t|
|
||||
t.belongs_to :author, index: true
|
||||
t.belongs_to :author
|
||||
t.datetime :published_at
|
||||
t.timestamps
|
||||
end
|
||||
|
@ -140,7 +140,7 @@ class CreateSuppliers < ActiveRecord::Migration[5.0]
|
|||
end
|
||||
|
||||
create_table :accounts do |t|
|
||||
t.belongs_to :supplier, index: true
|
||||
t.belongs_to :supplier
|
||||
t.string :account_number
|
||||
t.timestamps
|
||||
end
|
||||
|
@ -184,7 +184,7 @@ class CreateAuthors < ActiveRecord::Migration[5.0]
|
|||
end
|
||||
|
||||
create_table :books do |t|
|
||||
t.belongs_to :author, index: true
|
||||
t.belongs_to :author
|
||||
t.datetime :published_at
|
||||
t.timestamps
|
||||
end
|
||||
|
@ -231,8 +231,8 @@ class CreateAppointments < ActiveRecord::Migration[5.0]
|
|||
end
|
||||
|
||||
create_table :appointments do |t|
|
||||
t.belongs_to :physician, index: true
|
||||
t.belongs_to :patient, index: true
|
||||
t.belongs_to :physician
|
||||
t.belongs_to :patient
|
||||
t.datetime :appointment_date
|
||||
t.timestamps
|
||||
end
|
||||
|
@ -312,13 +312,13 @@ class CreateAccountHistories < ActiveRecord::Migration[5.0]
|
|||
end
|
||||
|
||||
create_table :accounts do |t|
|
||||
t.belongs_to :supplier, index: true
|
||||
t.belongs_to :supplier
|
||||
t.string :account_number
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :account_histories do |t|
|
||||
t.belongs_to :account, index: true
|
||||
t.belongs_to :account
|
||||
t.integer :credit_rating
|
||||
t.timestamps
|
||||
end
|
||||
|
@ -358,8 +358,8 @@ class CreateAssembliesAndParts < ActiveRecord::Migration[5.0]
|
|||
end
|
||||
|
||||
create_table :assemblies_parts, id: false do |t|
|
||||
t.belongs_to :assembly, index: true
|
||||
t.belongs_to :part, index: true
|
||||
t.belongs_to :assembly
|
||||
t.belongs_to :part
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -487,7 +487,7 @@ class CreatePictures < ActiveRecord::Migration[5.0]
|
|||
def change
|
||||
create_table :pictures do |t|
|
||||
t.string :name
|
||||
t.references :imageable, polymorphic: true, index: true
|
||||
t.references :imageable, polymorphic: true
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
@ -517,7 +517,7 @@ In your migrations/schema, you will add a references column to the model itself.
|
|||
class CreateEmployees < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
create_table :employees do |t|
|
||||
t.references :manager, index: true
|
||||
t.references :manager
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1266,7 +1266,7 @@ You can also pass in arbitrary local variables to any partial you are rendering
|
|||
|
||||
In this case, the partial will have access to a local variable `title` with the value "Products Page".
|
||||
|
||||
TIP: Rails also makes a counter variable available within a partial called by the collection, named after the title of the partial followed by `_counter`. For example, when rendering a collection `@products` the partial `_product.html.erb` can access the variable `product_counter` which indexes the number of times it has been rendered within the enclosing view.
|
||||
TIP: Rails also makes a counter variable available within a partial called by the collection, named after the title of the partial followed by `_counter`. For example, when rendering a collection `@products` the partial `_product.html.erb` can access the variable `product_counter` which indexes the number of times it has been rendered within the enclosing view. Note that it also applies for when the partial name was changed by using the `as:` option. For example, the counter variable for the code above would be `item_counter`.
|
||||
|
||||
You can also specify a second partial to be rendered between instances of the main partial by using the `:spacer_template` option:
|
||||
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
* Use Ids instead of memory addresses when displaying references in scaffold views.
|
||||
|
||||
Fixes #29200.
|
||||
|
||||
*Rasesh Patel*
|
||||
|
||||
* Adds support for multiple databases to `rails db:migrate:status`.
|
||||
Subtasks are also added to get the status of individual databases (eg. `rails db:migrate:status:animals`).
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
|
||||
<tr>
|
||||
<% attributes.reject(&:password_digest?).each do |attribute| -%>
|
||||
<td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
|
||||
<td><%%= <%= singular_table_name %>.<%= attribute.column_name %> %></td>
|
||||
<% end -%>
|
||||
<td><%%= link_to 'Show', <%= model_resource_name %> %></td>
|
||||
<td><%%= link_to 'Edit', edit_<%= singular_route_name %>_path(<%= singular_table_name %>) %></td>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<% attributes.reject(&:password_digest?).each do |attribute| -%>
|
||||
<p>
|
||||
<strong><%= attribute.human_name %>:</strong>
|
||||
<%%= @<%= singular_table_name %>.<%= attribute.name %> %>
|
||||
<%%= @<%= singular_table_name %>.<%= attribute.column_name %> %>
|
||||
</p>
|
||||
|
||||
<% end -%>
|
||||
|
|
|
@ -209,6 +209,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
|
|||
end
|
||||
|
||||
def test_new_application_doesnt_need_defaults
|
||||
run_generator
|
||||
assert_no_file "config/initializers/new_framework_defaults_6_0.rb"
|
||||
end
|
||||
|
||||
|
|
|
@ -435,8 +435,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_scaffold_generator_belongs_to
|
||||
run_generator ["account", "name", "currency:belongs_to"]
|
||||
def test_scaffold_generator_belongs_to_and_references
|
||||
run_generator ["account", "name", "currency:belongs_to", "user:references"]
|
||||
|
||||
assert_file "app/models/account.rb", /belongs_to :currency/
|
||||
|
||||
|
@ -449,7 +449,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
|
|||
|
||||
assert_file "app/controllers/accounts_controller.rb" do |content|
|
||||
assert_instance_method :account_params, content do |m|
|
||||
assert_match(/permit\(:name, :currency_id\)/, m)
|
||||
assert_match(/permit\(:name, :currency_id, :user_id\)/, m)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -457,6 +457,16 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
|
|||
assert_match(/^\W{4}<%= form\.text_field :name %>/, content)
|
||||
assert_match(/^\W{4}<%= form\.text_field :currency_id %>/, content)
|
||||
end
|
||||
|
||||
assert_file "app/views/accounts/index.html.erb" do |content|
|
||||
assert_match(/^\W{8}<td><%= account\.name %><\/td>/, content)
|
||||
assert_match(/^\W{8}<td><%= account\.user_id %><\/td>/, content)
|
||||
end
|
||||
|
||||
assert_file "app/views/accounts/show.html.erb" do |content|
|
||||
assert_match(/^\W{2}<%= @account\.name %>/, content)
|
||||
assert_match(/^\W{2}<%= @account\.user_id %>/, content)
|
||||
end
|
||||
end
|
||||
|
||||
def test_scaffold_generator_database
|
||||
|
|
|
@ -16,6 +16,9 @@ require "active_support/testing/autorun"
|
|||
require "active_support/testing/stream"
|
||||
require "active_support/testing/method_call_assertions"
|
||||
require "active_support/test_case"
|
||||
require "minitest/retry"
|
||||
|
||||
Minitest::Retry.use!(verbose: false, retry_count: 1)
|
||||
|
||||
RAILS_FRAMEWORK_ROOT = File.expand_path("../../..", __dir__)
|
||||
|
||||
|
|
Loading…
Reference in New Issue