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-10
|
||||||
- postgresql-client-10
|
- postgresql-client-10
|
||||||
|
|
||||||
bundler_args: --without test --jobs 3 --retry 3
|
bundler_args: --jobs 3 --retry 3
|
||||||
before_install:
|
before_install:
|
||||||
- "rm ${BUNDLE_GEMFILE}.lock"
|
- "rm ${BUNDLE_GEMFILE}.lock"
|
||||||
- "travis_retry gem update --system"
|
- "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
|
group :test do
|
||||||
gem "minitest-bisect"
|
gem "minitest-bisect"
|
||||||
|
gem "minitest-retry"
|
||||||
|
|
||||||
platforms :mri do
|
platforms :mri do
|
||||||
gem "stackprof"
|
gem "stackprof"
|
||||||
|
|
|
@ -306,7 +306,7 @@ GEM
|
||||||
loofah (2.2.2)
|
loofah (2.2.2)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
mail (2.7.0)
|
mail (2.7.1)
|
||||||
mini_mime (>= 0.1.1)
|
mini_mime (>= 0.1.1)
|
||||||
marcel (0.3.3)
|
marcel (0.3.3)
|
||||||
mimemagic (~> 0.3.2)
|
mimemagic (~> 0.3.2)
|
||||||
|
@ -323,6 +323,8 @@ GEM
|
||||||
minitest-bisect (1.4.0)
|
minitest-bisect (1.4.0)
|
||||||
minitest-server (~> 1.0)
|
minitest-server (~> 1.0)
|
||||||
path_expander (~> 1.0)
|
path_expander (~> 1.0)
|
||||||
|
minitest-retry (0.1.9)
|
||||||
|
minitest (>= 5.0)
|
||||||
minitest-server (1.0.5)
|
minitest-server (1.0.5)
|
||||||
minitest (~> 5.0)
|
minitest (~> 5.0)
|
||||||
mono_logger (1.1.0)
|
mono_logger (1.1.0)
|
||||||
|
@ -538,6 +540,7 @@ DEPENDENCIES
|
||||||
libxml-ruby
|
libxml-ruby
|
||||||
listen (>= 3.0.5, < 3.2)
|
listen (>= 3.0.5, < 3.2)
|
||||||
minitest-bisect
|
minitest-bisect
|
||||||
|
minitest-retry
|
||||||
mysql2 (>= 0.4.10)
|
mysql2 (>= 0.4.10)
|
||||||
nokogiri (>= 1.8.1)
|
nokogiri (>= 1.8.1)
|
||||||
pg (>= 0.18.0)
|
pg (>= 0.18.0)
|
||||||
|
@ -576,4 +579,4 @@ DEPENDENCIES
|
||||||
websocket-client-simple!
|
websocket-client-simple!
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.16.5
|
1.16.6
|
||||||
|
|
|
@ -152,9 +152,9 @@ class BaseTest < ActiveSupport::TestCase
|
||||||
assert_equal(2, email.parts.length)
|
assert_equal(2, email.parts.length)
|
||||||
assert_equal("multipart/mixed", email.mime_type)
|
assert_equal("multipart/mixed", email.mime_type)
|
||||||
assert_equal("text/html", email.parts[0].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("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
|
end
|
||||||
|
|
||||||
test "adds the given :body as part" do
|
test "adds the given :body as part" do
|
||||||
|
@ -162,9 +162,9 @@ class BaseTest < ActiveSupport::TestCase
|
||||||
assert_equal(2, email.parts.length)
|
assert_equal(2, email.parts.length)
|
||||||
assert_equal("multipart/mixed", email.mime_type)
|
assert_equal("multipart/mixed", email.mime_type)
|
||||||
assert_equal("text/plain", email.parts[0].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("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
|
end
|
||||||
|
|
||||||
test "can embed an inline attachment" do
|
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`.
|
* Deprecate `ActionDispatch::Http::ParameterFilter` in favor of `ActiveSupport::ParameterFilter`.
|
||||||
|
|
||||||
*Yoshiyuki Kinjo*
|
*Yoshiyuki Kinjo*
|
||||||
|
|
|
@ -352,7 +352,7 @@ module ActionController
|
||||||
# the same way as <tt>Hash#each_value</tt>.
|
# the same way as <tt>Hash#each_value</tt>.
|
||||||
def each_value(&block)
|
def each_value(&block)
|
||||||
@parameters.each_pair do |key, value|
|
@parameters.each_pair do |key, value|
|
||||||
yield [convert_hashes_to_parameters(key, value)]
|
yield convert_hashes_to_parameters(key, value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -77,11 +77,15 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
|
||||||
|
|
||||||
test "each_value carries permitted status" do
|
test "each_value carries permitted status" do
|
||||||
@params.permit!
|
@params.permit!
|
||||||
@params["person"].each_value { |value| assert(value.permitted?) if value == 32 }
|
@params.each_value do |value|
|
||||||
|
assert_predicate(value, :permitted?)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "each_value carries unpermitted status" do
|
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
|
end
|
||||||
|
|
||||||
test "each_key converts to hash for permitted" do
|
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
|
# jobs. Since jobs share a single thread pool, long-running jobs will block
|
||||||
# short-lived jobs. Fine for dev/test; bad for production.
|
# short-lived jobs. Fine for dev/test; bad for production.
|
||||||
class AsyncAdapter
|
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)
|
def initialize(**executor_options)
|
||||||
@scheduler = Scheduler.new(**executor_options)
|
@scheduler = Scheduler.new(**executor_options)
|
||||||
end
|
end
|
||||||
|
|
|
@ -197,7 +197,7 @@ module ActiveJob
|
||||||
# assert_performed_jobs 2
|
# assert_performed_jobs 2
|
||||||
# end
|
# 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.
|
# jobs to be performed.
|
||||||
#
|
#
|
||||||
# def test_jobs_again
|
# def test_jobs_again
|
||||||
|
@ -279,7 +279,7 @@ module ActiveJob
|
||||||
# end
|
# end
|
||||||
# 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
|
# def test_jobs_again
|
||||||
# assert_no_performed_jobs do
|
# assert_no_performed_jobs do
|
||||||
|
@ -347,7 +347,7 @@ module ActiveJob
|
||||||
# end
|
# 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.
|
# enqueued with the given arguments.
|
||||||
#
|
#
|
||||||
# def test_assert_enqueued_with
|
# def test_assert_enqueued_with
|
||||||
|
|
|
@ -474,5 +474,43 @@ module ActiveModel
|
||||||
def _read_attribute(attr)
|
def _read_attribute(attr)
|
||||||
__send__(attr)
|
__send__(attr)
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,17 +29,16 @@ module ActiveModel
|
||||||
private
|
private
|
||||||
|
|
||||||
def define_method_attribute=(name)
|
def define_method_attribute=(name)
|
||||||
safe_name = name.unpack1("h*")
|
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
||||||
ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name
|
generated_attribute_methods, name, writer: true,
|
||||||
|
) do |temp_method_name, attr_name_expr|
|
||||||
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||||
def __temp__#{safe_name}=(value)
|
def #{temp_method_name}(value)
|
||||||
name = ::ActiveModel::AttributeMethods::AttrNames::ATTR_#{safe_name}
|
name = #{attr_name_expr}
|
||||||
write_attribute(name, value)
|
write_attribute(name, value)
|
||||||
end
|
end
|
||||||
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
|
RUBY
|
||||||
undef_method :__temp__#{safe_name}=
|
end
|
||||||
STR
|
|
||||||
end
|
end
|
||||||
|
|
||||||
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
||||||
|
@ -97,15 +96,4 @@ module ActiveModel
|
||||||
write_attribute(attribute_name, value)
|
write_attribute(attribute_name, value)
|
||||||
end
|
end
|
||||||
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
|
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*
|
*Willian Gustavo Veiga*
|
||||||
|
|
||||||
|
|
|
@ -22,15 +22,6 @@ module ActiveRecord
|
||||||
delegate :column_for_attribute, to: :class
|
delegate :column_for_attribute, to: :class
|
||||||
end
|
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)
|
RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
|
||||||
|
|
||||||
class GeneratedAttributeMethods < Module #:nodoc:
|
class GeneratedAttributeMethods < Module #:nodoc:
|
||||||
|
@ -270,21 +261,14 @@ module ActiveRecord
|
||||||
def respond_to?(name, include_private = false)
|
def respond_to?(name, include_private = false)
|
||||||
return false unless super
|
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.
|
# If the result is true then check for the select case.
|
||||||
# For queries selecting a subset of columns, return false for unselected columns.
|
# 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
|
# We check defined?(@attributes) not to issue warnings if called on objects that
|
||||||
# have been allocated but not yet initialized.
|
# have been allocated but not yet initialized.
|
||||||
if defined?(@attributes) && self.class.column_names.include?(name)
|
if defined?(@attributes)
|
||||||
return has_attribute?(name)
|
if name = self.class.symbol_column_to_string(name.to_sym)
|
||||||
|
return has_attribute?(name)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
|
@ -8,42 +8,19 @@ module ActiveRecord
|
||||||
module ClassMethods # :nodoc:
|
module ClassMethods # :nodoc:
|
||||||
private
|
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)
|
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
|
sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
|
||||||
|
|
||||||
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
||||||
def #{temp_method}
|
generated_attribute_methods, name
|
||||||
#{sync_with_transaction_state}
|
) do |temp_method_name, attr_name_expr|
|
||||||
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
|
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||||
_read_attribute(name) { |n| missing_attribute(n, caller) }
|
def #{temp_method_name}
|
||||||
end
|
#{sync_with_transaction_state}
|
||||||
STR
|
name = #{attr_name_expr}
|
||||||
|
_read_attribute(name) { |n| missing_attribute(n, caller) }
|
||||||
generated_attribute_methods.module_eval do
|
end
|
||||||
alias_method name, temp_method
|
RUBY
|
||||||
undef_method temp_method
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,19 +13,19 @@ module ActiveRecord
|
||||||
private
|
private
|
||||||
|
|
||||||
def define_method_attribute=(name)
|
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
|
sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
|
||||||
|
|
||||||
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
||||||
def __temp__#{safe_name}=(value)
|
generated_attribute_methods, name, writer: true,
|
||||||
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
|
) do |temp_method_name, attr_name_expr|
|
||||||
#{sync_with_transaction_state}
|
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||||
_write_attribute(name, value)
|
def #{temp_method_name}(value)
|
||||||
end
|
name = #{attr_name_expr}
|
||||||
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
|
#{sync_with_transaction_state}
|
||||||
undef_method :__temp__#{safe_name}=
|
_write_attribute(name, value)
|
||||||
STR
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,9 @@ module ActiveRecord
|
||||||
select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
|
select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
|
||||||
|
|
||||||
if collection.has_limit_or_offset?
|
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_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)
|
subquery = query.arel.as(subquery_alias)
|
||||||
arel = Arel::SelectManager.new(subquery).project(select_values % subquery_column)
|
arel = Arel::SelectManager.new(subquery).project(select_values % subquery_column)
|
||||||
else
|
else
|
||||||
|
|
|
@ -348,8 +348,8 @@ module ActiveRecord
|
||||||
#
|
#
|
||||||
# create_table :taggings do |t|
|
# create_table :taggings do |t|
|
||||||
# t.references :tag, index: { name: 'index_taggings_on_tag_id' }
|
# t.references :tag, index: { name: 'index_taggings_on_tag_id' }
|
||||||
# t.references :tagger, polymorphic: true, index: true
|
# t.references :tagger, polymorphic: true
|
||||||
# t.references :taggable, polymorphic: { default: 'Photo' }
|
# t.references :taggable, polymorphic: { default: 'Photo' }, index: false
|
||||||
# end
|
# end
|
||||||
def column(name, type, options = {})
|
def column(name, type, options = {})
|
||||||
name = name.to_s
|
name = name.to_s
|
||||||
|
|
|
@ -846,17 +846,17 @@ module ActiveRecord
|
||||||
# [<tt>:null</tt>]
|
# [<tt>:null</tt>]
|
||||||
# Whether the column allows nulls. Defaults to true.
|
# 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
|
# ====== Create a user_id string column
|
||||||
#
|
#
|
||||||
# add_reference(:products, :user, type: :string)
|
# 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
|
# ====== Create a supplier_id column with a unique index
|
||||||
#
|
#
|
||||||
|
@ -884,7 +884,7 @@ module ActiveRecord
|
||||||
#
|
#
|
||||||
# ====== Remove the reference
|
# ====== Remove the reference
|
||||||
#
|
#
|
||||||
# remove_reference(:products, :user, index: true)
|
# remove_reference(:products, :user, index: false)
|
||||||
#
|
#
|
||||||
# ====== Remove polymorphic reference
|
# ====== Remove polymorphic reference
|
||||||
#
|
#
|
||||||
|
@ -892,7 +892,7 @@ module ActiveRecord
|
||||||
#
|
#
|
||||||
# ====== Remove the reference with a foreign key
|
# ====== 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)
|
def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
|
||||||
if foreign_key
|
if foreign_key
|
||||||
|
|
|
@ -125,6 +125,8 @@ module ActiveRecord
|
||||||
@advisory_locks_enabled = self.class.type_cast_config_to_boolean(
|
@advisory_locks_enabled = self.class.type_cast_config_to_boolean(
|
||||||
config.fetch(:advisory_locks, true)
|
config.fetch(:advisory_locks, true)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
check_version
|
||||||
end
|
end
|
||||||
|
|
||||||
def replica?
|
def replica?
|
||||||
|
@ -502,6 +504,9 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
def check_version
|
||||||
|
end
|
||||||
|
|
||||||
def type_map
|
def type_map
|
||||||
@type_map ||= Type::TypeMap.new.tap do |mapping|
|
@type_map ||= Type::TypeMap.new.tap do |mapping|
|
||||||
initialize_type_map(mapping)
|
initialize_type_map(mapping)
|
||||||
|
|
|
@ -54,10 +54,6 @@ module ActiveRecord
|
||||||
super(connection, logger, config)
|
super(connection, logger, config)
|
||||||
|
|
||||||
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
|
@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
|
end
|
||||||
|
|
||||||
def version #:nodoc:
|
def version #:nodoc:
|
||||||
|
@ -535,6 +531,12 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
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)
|
def combine_multi_statements(total_sql)
|
||||||
total_sql.each_with_object([]) do |sql, total_sql_chunks|
|
total_sql.each_with_object([]) do |sql, total_sql_chunks|
|
||||||
previous_packet = total_sql_chunks.last
|
previous_packet = total_sql_chunks.last
|
||||||
|
|
|
@ -43,9 +43,14 @@ module ActiveRecord
|
||||||
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
|
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
|
||||||
conn_params.slice!(*valid_conn_param_keys)
|
conn_params.slice!(*valid_conn_param_keys)
|
||||||
|
|
||||||
# The postgres drivers don't allow the creation of an unconnected PG::Connection object,
|
conn = PG.connect(conn_params)
|
||||||
# so just pass a nil connection object for the time being.
|
ConnectionAdapters::PostgreSQLAdapter.new(conn, logger, conn_params, config)
|
||||||
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
|
rescue ::PG::Error => error
|
||||||
|
if error.message.include?("does not exist")
|
||||||
|
raise ActiveRecord::NoDatabaseError
|
||||||
|
else
|
||||||
|
raise
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -220,15 +225,11 @@ module ActiveRecord
|
||||||
@local_tz = nil
|
@local_tz = nil
|
||||||
@max_identifier_length = nil
|
@max_identifier_length = nil
|
||||||
|
|
||||||
connect
|
configure_connection
|
||||||
add_pg_encoders
|
add_pg_encoders
|
||||||
@statements = StatementPool.new @connection,
|
@statements = StatementPool.new @connection,
|
||||||
self.class.type_cast_config_to_integer(config[:statement_limit])
|
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
|
add_pg_decoders
|
||||||
|
|
||||||
@type_map = Type::HashLookupTypeMap.new
|
@type_map = Type::HashLookupTypeMap.new
|
||||||
|
@ -410,6 +411,12 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
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
|
# See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
|
||||||
VALUE_LIMIT_VIOLATION = "22001"
|
VALUE_LIMIT_VIOLATION = "22001"
|
||||||
NUMERIC_VALUE_OUT_OF_RANGE = "22003"
|
NUMERIC_VALUE_OUT_OF_RANGE = "22003"
|
||||||
|
@ -699,12 +706,6 @@ module ActiveRecord
|
||||||
def connect
|
def connect
|
||||||
@connection = PG.connect(@connection_parameters)
|
@connection = PG.connect(@connection_parameters)
|
||||||
configure_connection
|
configure_connection
|
||||||
rescue ::PG::Error => error
|
|
||||||
if error.message.include?("does not exist")
|
|
||||||
raise ActiveRecord::NoDatabaseError
|
|
||||||
else
|
|
||||||
raise
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
||||||
|
|
|
@ -105,11 +105,6 @@ module ActiveRecord
|
||||||
|
|
||||||
@active = true
|
@active = true
|
||||||
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
|
@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
|
configure_connection
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -401,6 +396,12 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
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)
|
def initialize_type_map(m = type_map)
|
||||||
super
|
super
|
||||||
register_class_with_limit m, %r(int)i, SQLite3Integer
|
register_class_with_limit m, %r(int)i, SQLite3Integer
|
||||||
|
|
|
@ -344,34 +344,22 @@ module ActiveRecord
|
||||||
# post = Post.allocate
|
# post = Post.allocate
|
||||||
# post.init_with(coder)
|
# post.init_with(coder)
|
||||||
# post.title # => 'hello world'
|
# post.title # => 'hello world'
|
||||||
def init_with(coder)
|
def init_with(coder, &block)
|
||||||
coder = LegacyYamlAdapter.convert(self.class, coder)
|
coder = LegacyYamlAdapter.convert(self.class, coder)
|
||||||
@attributes = self.class.yaml_encoder.decode(coder)
|
attributes = self.class.yaml_encoder.decode(coder)
|
||||||
|
init_with_attributes(attributes, coder["new_record"], &block)
|
||||||
init_internals
|
|
||||||
|
|
||||||
@new_record = coder["new_record"]
|
|
||||||
|
|
||||||
self.class.define_attribute_methods
|
|
||||||
|
|
||||||
yield self if block_given?
|
|
||||||
|
|
||||||
_run_find_callbacks
|
|
||||||
_run_initialize_callbacks
|
|
||||||
|
|
||||||
self
|
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
# Initializer used for instantiating objects that have been read from the
|
# Initialize an empty model object from +attributes+.
|
||||||
# database. +attributes+ should be an attributes object, and unlike the
|
# +attributes+ should be an attributes object, and unlike the
|
||||||
# `initialize` method, no assignment calls are made per attribute.
|
# `initialize` method, no assignment calls are made per attribute.
|
||||||
#
|
#
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
def init_from_db(attributes)
|
def init_with_attributes(attributes, new_record = false)
|
||||||
init_internals
|
init_internals
|
||||||
|
|
||||||
@new_record = false
|
@new_record = new_record
|
||||||
@attributes = attributes
|
@attributes = attributes
|
||||||
|
|
||||||
self.class.define_attribute_methods
|
self.class.define_attribute_methods
|
||||||
|
|
|
@ -388,6 +388,11 @@ module ActiveRecord
|
||||||
@column_names ||= columns.map(&:name)
|
@column_names ||= columns.map(&:name)
|
||||||
end
|
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",
|
# 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.
|
# and columns used for single table inheritance have been removed.
|
||||||
def content_columns
|
def content_columns
|
||||||
|
@ -477,6 +482,7 @@ module ActiveRecord
|
||||||
def reload_schema_from_cache
|
def reload_schema_from_cache
|
||||||
@arel_table = nil
|
@arel_table = nil
|
||||||
@column_names = nil
|
@column_names = nil
|
||||||
|
@symbol_column_to_string_name_hash = nil
|
||||||
@attribute_types = nil
|
@attribute_types = nil
|
||||||
@content_columns = nil
|
@content_columns = nil
|
||||||
@default_attributes = nil
|
@default_attributes = nil
|
||||||
|
|
|
@ -426,7 +426,7 @@ module ActiveRecord
|
||||||
existing_record.assign_attributes(assignable_attributes)
|
existing_record.assign_attributes(assignable_attributes)
|
||||||
association(association_name).initialize_attributes(existing_record)
|
association(association_name).initialize_attributes(existing_record)
|
||||||
else
|
else
|
||||||
method = "build_#{association_name}"
|
method = :"build_#{association_name}"
|
||||||
if respond_to?(method)
|
if respond_to?(method)
|
||||||
send(method, assignable_attributes)
|
send(method, assignable_attributes)
|
||||||
else
|
else
|
||||||
|
|
|
@ -209,7 +209,7 @@ module ActiveRecord
|
||||||
# new instance of the class. Accepts only keys as strings.
|
# new instance of the class. Accepts only keys as strings.
|
||||||
def instantiate_instance_of(klass, attributes, column_types = {}, &block)
|
def instantiate_instance_of(klass, attributes, column_types = {}, &block)
|
||||||
attributes = klass.attributes_builder.build_from_database(attributes, column_types)
|
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
|
end
|
||||||
|
|
||||||
# Called by +instantiate+ to decide which class to use for a new
|
# Called by +instantiate+ to decide which class to use for a new
|
||||||
|
|
|
@ -25,6 +25,8 @@ require "models/user"
|
||||||
require "models/member"
|
require "models/member"
|
||||||
require "models/membership"
|
require "models/membership"
|
||||||
require "models/sponsor"
|
require "models/sponsor"
|
||||||
|
require "models/lesson"
|
||||||
|
require "models/student"
|
||||||
require "models/country"
|
require "models/country"
|
||||||
require "models/treaty"
|
require "models/treaty"
|
||||||
require "models/vertex"
|
require "models/vertex"
|
||||||
|
@ -275,7 +277,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
|
||||||
def test_habtm_saving_multiple_relationships
|
def test_habtm_saving_multiple_relationships
|
||||||
new_project = Project.new("name" => "Grimetime")
|
new_project = Project.new("name" => "Grimetime")
|
||||||
amount_of_developers = 4
|
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.developer_ids = [developers[0].id, developers[1].id]
|
||||||
new_project.developers_with_callback_ids = [developers[2].id, developers[3].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
|
assert_equal [projects(:active_record), projects(:action_controller)].map(&:id).sort, developer.project_ids.sort
|
||||||
end
|
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
|
def test_scoped_find_on_through_association_doesnt_return_read_only_records
|
||||||
tag = Post.find(1).tags.find_by_name("General")
|
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
|
assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
|
||||||
end
|
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
|
test "cache_key for loaded relation" do
|
||||||
developers = Developer.where(salary: 100000).order(updated_at: :desc).limit(5).load
|
developers = Developer.where(salary: 100000).order(updated_at: :desc).limit(5).load
|
||||||
last_developer_timestamp = developers.first.updated_at
|
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`.
|
* Add `ActiveSupport::ParameterFilter`.
|
||||||
|
|
||||||
*Yoshiyuki Kinjo*
|
*Yoshiyuki Kinjo*
|
||||||
|
|
|
@ -411,8 +411,6 @@ module ActiveSupport
|
||||||
# to the cache. If you do not want to write the cache when the cache is
|
# to the cache. If you do not want to write the cache when the cache is
|
||||||
# not found, use #read_multi.
|
# 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:
|
# Returns a hash with the data for each of the names. For example:
|
||||||
#
|
#
|
||||||
# cache.write("bim", "bam")
|
# cache.write("bim", "bam")
|
||||||
|
@ -422,6 +420,17 @@ module ActiveSupport
|
||||||
# # => { "bim" => "bam",
|
# # => { "bim" => "bam",
|
||||||
# # "unknown_key" => "Fallback value for key: unknown_key" }
|
# # "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)
|
def fetch_multi(*names)
|
||||||
raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
|
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
|
# instance time. Do not use this method in combination with x.months, use
|
||||||
# months_since instead!
|
# months_since instead!
|
||||||
def since(seconds)
|
def since(seconds)
|
||||||
self + Rational(seconds.round, 86400)
|
self + Rational(seconds, 86400)
|
||||||
end
|
end
|
||||||
alias :in :since
|
alias :in :since
|
||||||
|
|
||||||
|
|
|
@ -62,9 +62,9 @@ module ActiveSupport
|
||||||
raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)
|
raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)
|
||||||
|
|
||||||
I18n.transliterate(
|
I18n.transliterate(
|
||||||
ActiveSupport::Multibyte::Unicode.normalize(
|
ActiveSupport::Multibyte::Unicode.tidy_bytes(string).unicode_normalize(:nfc),
|
||||||
ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
|
replacement: replacement
|
||||||
replacement: replacement)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replaces special characters in a string so that it may be used as part of
|
# 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
|
# through the +mb_chars+ method. Methods which would normally return a
|
||||||
# String object now return a Chars object so methods can be chained.
|
# 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">
|
# # => #<ActiveSupport::Multibyte::Chars:0x007fdc434ccc10 @wrapped_string="the perfect string">
|
||||||
#
|
#
|
||||||
# Chars objects are perfectly interchangeable with String objects as long as
|
# 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
|
# Returns +true+ when the proxy class can handle the string. Returns
|
||||||
# +false+ otherwise.
|
# +false+ otherwise.
|
||||||
def self.consumes?(string)
|
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
|
string.encoding == Encoding::UTF_8
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -108,7 +113,7 @@ module ActiveSupport #:nodoc:
|
||||||
#
|
#
|
||||||
# 'Café'.mb_chars.reverse.to_s # => 'éfaC'
|
# 'Café'.mb_chars.reverse.to_s # => 'éfaC'
|
||||||
def reverse
|
def reverse
|
||||||
chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack("U*"))
|
chars(@wrapped_string.scan(/\X/).reverse.join)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Limits the byte size of the string to a number of bytes without breaking
|
# 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))
|
slice(0...translate_offset(limit))
|
||||||
end
|
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.
|
# Capitalizes the first letter of every word, when possible.
|
||||||
#
|
#
|
||||||
# "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró"
|
# "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró"
|
||||||
# "日本語".mb_chars.titleize.to_s # => "日本語"
|
# "日本語".mb_chars.titleize.to_s # => "日本語"
|
||||||
def titleize
|
def titleize
|
||||||
chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1) })
|
chars(downcase.to_s.gsub(/\b('?\S)/u) { $1.upcase })
|
||||||
end
|
end
|
||||||
alias_method :titlecase, :titleize
|
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
|
# <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
|
||||||
# ActiveSupport::Multibyte::Unicode.default_normalization_form
|
# ActiveSupport::Multibyte::Unicode.default_normalization_form
|
||||||
def normalize(form = nil)
|
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
|
end
|
||||||
|
|
||||||
# Performs canonical decomposition on all the characters.
|
# Performs canonical decomposition on all the characters.
|
||||||
|
@ -189,7 +183,7 @@ module ActiveSupport #:nodoc:
|
||||||
# 'क्षि'.mb_chars.length # => 4
|
# 'क्षि'.mb_chars.length # => 4
|
||||||
# 'क्षि'.mb_chars.grapheme_length # => 3
|
# 'क्षि'.mb_chars.grapheme_length # => 3
|
||||||
def grapheme_length
|
def grapheme_length
|
||||||
Unicode.unpack_graphemes(@wrapped_string).length
|
@wrapped_string.scan(/\X/).length
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
|
# 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)
|
to_s.as_json(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
%w(capitalize downcase reverse tidy_bytes upcase).each do |method|
|
%w(reverse tidy_bytes).each do |method|
|
||||||
define_method("#{method}!") do |*args|
|
define_method("#{method}!") do |*args|
|
||||||
@wrapped_string = send(method, *args).to_s
|
@wrapped_string = send(method, *args).to_s
|
||||||
self
|
self
|
||||||
|
|
|
@ -6,10 +6,17 @@ module ActiveSupport
|
||||||
extend self
|
extend self
|
||||||
|
|
||||||
# A list of all available normalization forms.
|
# 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.
|
# information about normalization.
|
||||||
NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
|
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
|
# The Unicode version that is supported by the implementation
|
||||||
UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"]
|
UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"]
|
||||||
|
|
||||||
|
@ -100,31 +107,34 @@ module ActiveSupport
|
||||||
# Default is ActiveSupport::Multibyte::Unicode.default_normalization_form.
|
# Default is ActiveSupport::Multibyte::Unicode.default_normalization_form.
|
||||||
def normalize(string, form = nil)
|
def normalize(string, form = nil)
|
||||||
form ||= @default_normalization_form
|
form ||= @default_normalization_form
|
||||||
# See http://www.unicode.org/reports/tr15, Table 1
|
|
||||||
case form
|
# See https://www.unicode.org/reports/tr15, Table 1
|
||||||
when :d
|
if alias_form = NORMALIZATION_FORM_ALIASES[form]
|
||||||
string.unicode_normalize(:nfd)
|
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
||||||
when :c
|
ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be
|
||||||
string.unicode_normalize(:nfc)
|
removed from Rails 6.1. Use String#unicode_normalize(:#{alias_form}) instead.
|
||||||
when :kd
|
MSG
|
||||||
string.unicode_normalize(:nfkd)
|
|
||||||
when :kc
|
string.unicode_normalize(alias_form)
|
||||||
string.unicode_normalize(:nfkc)
|
|
||||||
else
|
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
|
raise ArgumentError, "#{form} is not a valid normalization variant", caller
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def downcase(string)
|
%w(downcase upcase swapcase).each do |method|
|
||||||
string.downcase
|
define_method(method) do |string|
|
||||||
end
|
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.send(method)
|
||||||
string.upcase
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def swapcase(string)
|
|
||||||
string.swapcase
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
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, 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, 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, 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_not_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, 12), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1.667)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_change
|
def test_change
|
||||||
|
|
|
@ -157,6 +157,16 @@ class TestJSONEncoding < ActiveSupport::TestCase
|
||||||
assert_equal({ "foo" => "hello" }, JSON.parse(json))
|
assert_equal({ "foo" => "hello" }, JSON.parse(json))
|
||||||
end
|
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
|
def test_hash_should_pass_encoding_options_to_children_in_as_json
|
||||||
person = {
|
person = {
|
||||||
name: "John",
|
name: "John",
|
||||||
|
|
|
@ -73,9 +73,15 @@ class MultibyteCharsTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_consumes_utf8_strings
|
def test_consumes_utf8_strings
|
||||||
assert @proxy_class.consumes?(UNICODE_STRING)
|
ActiveSupport::Deprecation.silence do
|
||||||
assert @proxy_class.consumes?(ASCII_STRING)
|
assert @proxy_class.consumes?(UNICODE_STRING)
|
||||||
assert_not @proxy_class.consumes?(BYTE_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
|
end
|
||||||
|
|
||||||
def test_concatenation_should_return_a_proxy_class_instance
|
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("").upcase.kind_of?(ActiveSupport::Multibyte.proxy_class)
|
||||||
assert chars("").downcase.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("").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("").decompose.kind_of?(ActiveSupport::Multibyte.proxy_class)
|
||||||
assert chars("").compose.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)
|
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
|
def test_reverse_should_work_with_normalized_strings
|
||||||
str = "bös"
|
str = "bös"
|
||||||
reversed_str = "söb"
|
reversed_str = "söb"
|
||||||
assert_equal chars(reversed_str).normalize(:kc), chars(str).normalize(:kc).reverse
|
ActiveSupport::Deprecation.silence do
|
||||||
assert_equal chars(reversed_str).normalize(:c), chars(str).normalize(:c).reverse
|
assert_equal chars(reversed_str).normalize(:kc), chars(str).normalize(:kc).reverse
|
||||||
assert_equal chars(reversed_str).normalize(:d), chars(str).normalize(:d).reverse
|
assert_equal chars(reversed_str).normalize(:c), chars(str).normalize(:c).reverse
|
||||||
assert_equal chars(reversed_str).normalize(:kd), chars(str).normalize(:kd).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).decompose, chars(str).decompose.reverse
|
||||||
assert_equal chars(reversed_str).compose, chars(str).compose.reverse
|
assert_equal chars(reversed_str).compose, chars(str).compose.reverse
|
||||||
end
|
end
|
||||||
|
@ -477,7 +487,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
|
||||||
|
|
||||||
def test_method_works_for_proxyed_methods
|
def test_method_works_for_proxyed_methods
|
||||||
assert_equal "ll", "hello".mb_chars.method(:slice).call(2..3) # Defined on Chars
|
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.method(:capitalize!).call # Defined on Chars
|
||||||
assert_equal "Hello", chars
|
assert_equal "Hello", chars
|
||||||
assert_equal "jello", "hello".mb_chars.method(:gsub).call(/h/, "j") # Defined on String
|
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
|
def test_composition_exclusion_is_set_up_properly
|
||||||
# Normalization of DEVANAGARI LETTER QA breaks when composition exclusion isn't used correctly
|
# Normalization of DEVANAGARI LETTER QA breaks when composition exclusion isn't used correctly
|
||||||
qa = [0x915, 0x93c].pack("U*")
|
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
|
end
|
||||||
|
|
||||||
# Test for the Public Review Issue #29, bad explanation of composition might lead to a
|
# 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],
|
[0x0B47, 0x0300, 0x0B3E],
|
||||||
[0x1100, 0x0300, 0x1161]
|
[0x1100, 0x0300, 0x1161]
|
||||||
].map { |c| c.pack("U*") }.each do |c|
|
].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
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_normalization_shouldnt_strip_null_bytes
|
def test_normalization_shouldnt_strip_null_bytes
|
||||||
null_byte_str = "Test\0test"
|
null_byte_str = "Test\0test"
|
||||||
|
|
||||||
assert_equal null_byte_str, chars(null_byte_str).normalize(:kc)
|
ActiveSupport::Deprecation.silence do
|
||||||
assert_equal null_byte_str, chars(null_byte_str).normalize(:c)
|
assert_equal null_byte_str, chars(null_byte_str).normalize(:kc)
|
||||||
assert_equal null_byte_str, chars(null_byte_str).normalize(:d)
|
assert_equal null_byte_str, chars(null_byte_str).normalize(:c)
|
||||||
assert_equal null_byte_str, chars(null_byte_str).normalize(:kd)
|
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).decompose
|
||||||
assert_equal null_byte_str, chars(null_byte_str).compose
|
assert_equal null_byte_str, chars(null_byte_str).compose
|
||||||
end
|
end
|
||||||
|
@ -601,11 +617,13 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
|
||||||
323 # COMBINING DOT BELOW
|
323 # COMBINING DOT BELOW
|
||||||
].pack("U*")
|
].pack("U*")
|
||||||
|
|
||||||
assert_equal_codepoints "", chars("").normalize
|
ActiveSupport::Deprecation.silence do
|
||||||
assert_equal_codepoints [44, 105, 106, 328, 323].pack("U*"), chars(comp_str).normalize(:kc).to_s
|
assert_equal_codepoints "", chars("").normalize
|
||||||
assert_equal_codepoints [44, 307, 328, 323].pack("U*"), chars(comp_str).normalize(:c).to_s
|
assert_equal_codepoints [44, 105, 106, 328, 323].pack("U*"), chars(comp_str).normalize(:kc).to_s
|
||||||
assert_equal_codepoints [44, 307, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:d).to_s
|
assert_equal_codepoints [44, 307, 328, 323].pack("U*"), chars(comp_str).normalize(:c).to_s
|
||||||
assert_equal_codepoints [44, 105, 106, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:kd).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
|
end
|
||||||
|
|
||||||
def test_should_compute_grapheme_length
|
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
|
assert_equal BYTE_STRING.dup.mb_chars.class, ActiveSupport::Multibyte::Chars
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def string_from_classes(classes)
|
def string_from_classes(classes)
|
||||||
|
|
|
@ -18,64 +18,72 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_normalizations_C
|
def test_normalizations_C
|
||||||
each_line_of_norm_tests do |*cols|
|
ActiveSupport::Deprecation.silence do
|
||||||
col1, col2, col3, col4, col5, comment = *cols
|
each_line_of_norm_tests do |*cols|
|
||||||
|
col1, col2, col3, col4, col5, comment = *cols
|
||||||
|
|
||||||
# CONFORMANCE:
|
# CONFORMANCE:
|
||||||
# 1. The following invariants must be true for all conformant implementations
|
# 1. The following invariants must be true for all conformant implementations
|
||||||
#
|
#
|
||||||
# NFC
|
# NFC
|
||||||
# c2 == NFC(c1) == NFC(c2) == NFC(c3)
|
# 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(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(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}"
|
assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}"
|
||||||
#
|
#
|
||||||
# c4 == NFC(c4) == NFC(c5)
|
# 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(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}"
|
assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_normalizations_D
|
def test_normalizations_D
|
||||||
each_line_of_norm_tests do |*cols|
|
ActiveSupport::Deprecation.silence do
|
||||||
col1, col2, col3, col4, col5, comment = *cols
|
each_line_of_norm_tests do |*cols|
|
||||||
#
|
col1, col2, col3, col4, col5, comment = *cols
|
||||||
# NFD
|
#
|
||||||
# c3 == NFD(c1) == NFD(c2) == NFD(c3)
|
# NFD
|
||||||
assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}"
|
# c3 == NFD(c1) == NFD(c2) == NFD(c3)
|
||||||
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(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}"
|
||||||
assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}"
|
assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}"
|
||||||
# c5 == NFD(c4) == NFD(c5)
|
assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}"
|
||||||
assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}"
|
# c5 == NFD(c4) == NFD(c5)
|
||||||
assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}"
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_normalizations_KC
|
def test_normalizations_KC
|
||||||
each_line_of_norm_tests do | *cols |
|
ActiveSupport::Deprecation.silence do
|
||||||
col1, col2, col3, col4, col5, comment = *cols
|
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)
|
# NFKC
|
||||||
assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}"
|
# c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5)
|
||||||
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(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{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(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{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(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}"
|
||||||
assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{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
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_normalizations_KD
|
def test_normalizations_KD
|
||||||
each_line_of_norm_tests do | *cols |
|
ActiveSupport::Deprecation.silence do
|
||||||
col1, col2, col3, col4, col5, comment = *cols
|
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)
|
# NFKD
|
||||||
assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}"
|
# c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5)
|
||||||
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(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{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(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{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(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}"
|
||||||
assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -18,64 +18,72 @@ class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_normalizations_C
|
def test_normalizations_C
|
||||||
each_line_of_norm_tests do |*cols|
|
ActiveSupport::Deprecation.silence do
|
||||||
col1, col2, col3, col4, col5, comment = *cols
|
each_line_of_norm_tests do |*cols|
|
||||||
|
col1, col2, col3, col4, col5, comment = *cols
|
||||||
|
|
||||||
# CONFORMANCE:
|
# CONFORMANCE:
|
||||||
# 1. The following invariants must be true for all conformant implementations
|
# 1. The following invariants must be true for all conformant implementations
|
||||||
#
|
#
|
||||||
# NFC
|
# NFC
|
||||||
# c2 == NFC(c1) == NFC(c2) == NFC(c3)
|
# 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(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(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}"
|
assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}"
|
||||||
#
|
#
|
||||||
# c4 == NFC(c4) == NFC(c5)
|
# 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(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}"
|
assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_normalizations_D
|
def test_normalizations_D
|
||||||
each_line_of_norm_tests do |*cols|
|
ActiveSupport::Deprecation.silence do
|
||||||
col1, col2, col3, col4, col5, comment = *cols
|
each_line_of_norm_tests do |*cols|
|
||||||
#
|
col1, col2, col3, col4, col5, comment = *cols
|
||||||
# NFD
|
#
|
||||||
# c3 == NFD(c1) == NFD(c2) == NFD(c3)
|
# NFD
|
||||||
assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}"
|
# c3 == NFD(c1) == NFD(c2) == NFD(c3)
|
||||||
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(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}"
|
||||||
assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}"
|
assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}"
|
||||||
# c5 == NFD(c4) == NFD(c5)
|
assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}"
|
||||||
assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}"
|
# c5 == NFD(c4) == NFD(c5)
|
||||||
assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}"
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_normalizations_KC
|
def test_normalizations_KC
|
||||||
each_line_of_norm_tests do | *cols |
|
ActiveSupport::Deprecation.silence do
|
||||||
col1, col2, col3, col4, col5, comment = *cols
|
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)
|
# NFKC
|
||||||
assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}"
|
# c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5)
|
||||||
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(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{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(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{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(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}"
|
||||||
assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{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
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_normalizations_KD
|
def test_normalizations_KD
|
||||||
each_line_of_norm_tests do | *cols |
|
ActiveSupport::Deprecation.silence do
|
||||||
col1, col2, col3, col4, col5, comment = *cols
|
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)
|
# NFKD
|
||||||
assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}"
|
# c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5)
|
||||||
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(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{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(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{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(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}"
|
||||||
assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{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
|
||||||
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:
|
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
|
```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".
|
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
|
using the CamelCase form, while the table name must contain the words separated
|
||||||
by underscores. Examples:
|
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.,
|
* Model Class - Singular with the first letter of each word capitalized (e.g.,
|
||||||
`BookClub`).
|
`BookClub`).
|
||||||
|
* Database Table - Plural with underscores separating words (e.g., `book_clubs`).
|
||||||
|
|
||||||
| Model / Class | Table / Schema |
|
| Model / Class | Table / Schema |
|
||||||
| ---------------- | -------------- |
|
| ---------------- | -------------- |
|
||||||
|
|
|
@ -109,7 +109,7 @@ class CreateBooks < ActiveRecord::Migration[5.0]
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table :books do |t|
|
create_table :books do |t|
|
||||||
t.belongs_to :author, index: true
|
t.belongs_to :author
|
||||||
t.datetime :published_at
|
t.datetime :published_at
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
|
@ -140,7 +140,7 @@ class CreateSuppliers < ActiveRecord::Migration[5.0]
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table :accounts do |t|
|
create_table :accounts do |t|
|
||||||
t.belongs_to :supplier, index: true
|
t.belongs_to :supplier
|
||||||
t.string :account_number
|
t.string :account_number
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
|
@ -184,7 +184,7 @@ class CreateAuthors < ActiveRecord::Migration[5.0]
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table :books do |t|
|
create_table :books do |t|
|
||||||
t.belongs_to :author, index: true
|
t.belongs_to :author
|
||||||
t.datetime :published_at
|
t.datetime :published_at
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
|
@ -231,8 +231,8 @@ class CreateAppointments < ActiveRecord::Migration[5.0]
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table :appointments do |t|
|
create_table :appointments do |t|
|
||||||
t.belongs_to :physician, index: true
|
t.belongs_to :physician
|
||||||
t.belongs_to :patient, index: true
|
t.belongs_to :patient
|
||||||
t.datetime :appointment_date
|
t.datetime :appointment_date
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
|
@ -312,13 +312,13 @@ class CreateAccountHistories < ActiveRecord::Migration[5.0]
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table :accounts do |t|
|
create_table :accounts do |t|
|
||||||
t.belongs_to :supplier, index: true
|
t.belongs_to :supplier
|
||||||
t.string :account_number
|
t.string :account_number
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table :account_histories do |t|
|
create_table :account_histories do |t|
|
||||||
t.belongs_to :account, index: true
|
t.belongs_to :account
|
||||||
t.integer :credit_rating
|
t.integer :credit_rating
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
|
@ -358,8 +358,8 @@ class CreateAssembliesAndParts < ActiveRecord::Migration[5.0]
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table :assemblies_parts, id: false do |t|
|
create_table :assemblies_parts, id: false do |t|
|
||||||
t.belongs_to :assembly, index: true
|
t.belongs_to :assembly
|
||||||
t.belongs_to :part, index: true
|
t.belongs_to :part
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -487,7 +487,7 @@ class CreatePictures < ActiveRecord::Migration[5.0]
|
||||||
def change
|
def change
|
||||||
create_table :pictures do |t|
|
create_table :pictures do |t|
|
||||||
t.string :name
|
t.string :name
|
||||||
t.references :imageable, polymorphic: true, index: true
|
t.references :imageable, polymorphic: true
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
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]
|
class CreateEmployees < ActiveRecord::Migration[5.0]
|
||||||
def change
|
def change
|
||||||
create_table :employees do |t|
|
create_table :employees do |t|
|
||||||
t.references :manager, index: true
|
t.references :manager
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
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".
|
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:
|
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`.
|
* 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`).
|
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 %>| %>
|
<%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
|
||||||
<tr>
|
<tr>
|
||||||
<% attributes.reject(&:password_digest?).each do |attribute| -%>
|
<% attributes.reject(&:password_digest?).each do |attribute| -%>
|
||||||
<td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
|
<td><%%= <%= singular_table_name %>.<%= attribute.column_name %> %></td>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
<td><%%= link_to 'Show', <%= model_resource_name %> %></td>
|
<td><%%= link_to 'Show', <%= model_resource_name %> %></td>
|
||||||
<td><%%= link_to 'Edit', edit_<%= singular_route_name %>_path(<%= singular_table_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| -%>
|
<% attributes.reject(&:password_digest?).each do |attribute| -%>
|
||||||
<p>
|
<p>
|
||||||
<strong><%= attribute.human_name %>:</strong>
|
<strong><%= attribute.human_name %>:</strong>
|
||||||
<%%= @<%= singular_table_name %>.<%= attribute.name %> %>
|
<%%= @<%= singular_table_name %>.<%= attribute.column_name %> %>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<% end -%>
|
<% end -%>
|
||||||
|
|
|
@ -209,6 +209,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_new_application_doesnt_need_defaults
|
def test_new_application_doesnt_need_defaults
|
||||||
|
run_generator
|
||||||
assert_no_file "config/initializers/new_framework_defaults_6_0.rb"
|
assert_no_file "config/initializers/new_framework_defaults_6_0.rb"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -435,8 +435,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_scaffold_generator_belongs_to
|
def test_scaffold_generator_belongs_to_and_references
|
||||||
run_generator ["account", "name", "currency:belongs_to"]
|
run_generator ["account", "name", "currency:belongs_to", "user:references"]
|
||||||
|
|
||||||
assert_file "app/models/account.rb", /belongs_to :currency/
|
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_file "app/controllers/accounts_controller.rb" do |content|
|
||||||
assert_instance_method :account_params, content do |m|
|
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
|
||||||
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 :name %>/, content)
|
||||||
assert_match(/^\W{4}<%= form\.text_field :currency_id %>/, content)
|
assert_match(/^\W{4}<%= form\.text_field :currency_id %>/, content)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
def test_scaffold_generator_database
|
def test_scaffold_generator_database
|
||||||
|
|
|
@ -16,6 +16,9 @@ require "active_support/testing/autorun"
|
||||||
require "active_support/testing/stream"
|
require "active_support/testing/stream"
|
||||||
require "active_support/testing/method_call_assertions"
|
require "active_support/testing/method_call_assertions"
|
||||||
require "active_support/test_case"
|
require "active_support/test_case"
|
||||||
|
require "minitest/retry"
|
||||||
|
|
||||||
|
Minitest::Retry.use!(verbose: false, retry_count: 1)
|
||||||
|
|
||||||
RAILS_FRAMEWORK_ROOT = File.expand_path("../../..", __dir__)
|
RAILS_FRAMEWORK_ROOT = File.expand_path("../../..", __dir__)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue