aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile.lock2
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb6
-rw-r--r--actionview/test/template/form_helper/form_with_test.rb10
-rw-r--r--activejob/test/helper.rb1
-rw-r--r--activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb4
-rw-r--r--activemodel/lib/active_model/type/helpers/mutable.rb4
-rw-r--r--activemodel/lib/active_model/type/helpers/numeric.rb4
-rw-r--r--activemodel/lib/active_model/type/helpers/time_value.rb4
-rw-r--r--activerecord/CHANGELOG.md10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/column.rb3
-rw-r--r--activerecord/lib/active_record/fixture_set/file.rb7
-rw-r--r--activerecord/test/cases/adapter_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb2
-rw-r--r--activerecord/test/cases/fixtures_test.rb14
-rw-r--r--activerecord/test/cases/query_cache_test.rb14
-rw-r--r--activerecord/test/fixtures/naked/yml/courses_with_invalid_key.yml3
-rw-r--r--activesupport/lib/active_support/callbacks.rb12
-rw-r--r--activesupport/test/callbacks_test.rb83
-rw-r--r--guides/source/generators.md30
-rw-r--r--guides/source/security.md2
24 files changed, 190 insertions, 46 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index eb14330c2b..0763efc001 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -162,7 +162,7 @@ GEM
coffee-script (2.4.1)
coffee-script-source
execjs
- coffee-script-source (1.10.0)
+ coffee-script-source (1.12.2)
concurrent-ruby (1.0.2)
connection_pool (2.2.0)
cookiejar (0.3.3)
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 55389950ed..6c632b8e75 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -2293,10 +2293,6 @@ module ActionView
if options.key?(:skip_id)
options[:include_id] = !options.delete(:skip_id)
end
-
- if options.key?(:local)
- options[:remote] = !options.delete(:local)
- end
end
end
end
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 7bd473507b..ffc52569f2 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -461,7 +461,7 @@ module ActionView
end
# Creates a button element that defines a <tt>submit</tt> button,
- # <tt>reset</tt>button or a generic button which can be used in
+ # <tt>reset</tt> button or a generic button which can be used in
# JavaScript, for example. You can use the button tag as a regular
# submit tag but it isn't supported in legacy browsers. However,
# the button tag does allow for richer labels such as images and emphasis,
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index 75b898c3e9..9e80f0b2ee 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -171,6 +171,9 @@ module ActionView
# to ",").
# * <tt>:separator</tt> - Sets the separator between the
# fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter_pattern</tt> - Sets a custom regular expression used for
+ # deriving the placement of delimiter. Helpful when using currency formats
+ # like INR.
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
@@ -187,6 +190,9 @@ module ActionView
# number_with_delimiter(98765432.98, delimiter: " ", separator: ",")
# # => 98 765 432,98
#
+ # number_with_delimiter("123456.78",
+ # delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/) # => "1,23,456.78"
+ #
# number_with_delimiter("112a", raise: true) # => raise InvalidNumberError
def number_with_delimiter(number, options = {})
delegate_number_helper_method(:number_to_delimited, number, options)
diff --git a/actionview/test/template/form_helper/form_with_test.rb b/actionview/test/template/form_helper/form_with_test.rb
index 4efc93ca08..c078b47e14 100644
--- a/actionview/test/template/form_helper/form_with_test.rb
+++ b/actionview/test/template/form_helper/form_with_test.rb
@@ -116,6 +116,16 @@ class FormWithActsLikeFormTagTest < FormWithTest
assert_dom_equal expected, output_buffer
end
+
+ def test_form_with_with_block_in_erb_and_local_true
+ output_buffer = render_erb("<%= form_with(url: 'http://www.example.com', local: true) do %>Hello world!<% end %>")
+
+ expected = whole_form("http://www.example.com", local: true) do
+ "Hello world!"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
end
class FormWithActsLikeFormForTest < FormWithTest
diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb
index 758506b3c0..776f7788de 100644
--- a/activejob/test/helper.rb
+++ b/activejob/test/helper.rb
@@ -5,6 +5,7 @@ ActiveSupport.halt_callback_chains_on_return_false = false
GlobalID.app = "aj"
@adapter = ENV["AJ_ADAPTER"] || "inline"
+puts "Using #{@adapter}"
if ENV["AJ_INTEGRATION_TESTS"]
require "support/integration/helper"
diff --git a/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb b/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb
index facea12704..f783d286c5 100644
--- a/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb
+++ b/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb
@@ -1,7 +1,7 @@
module ActiveModel
module Type
- module Helpers
- class AcceptsMultiparameterTime < Module # :nodoc:
+ module Helpers # :nodoc: all
+ class AcceptsMultiparameterTime < Module
def initialize(defaults: {})
define_method(:cast) do |value|
if value.is_a?(Hash)
diff --git a/activemodel/lib/active_model/type/helpers/mutable.rb b/activemodel/lib/active_model/type/helpers/mutable.rb
index 4dddbe4e5e..f3a17a1698 100644
--- a/activemodel/lib/active_model/type/helpers/mutable.rb
+++ b/activemodel/lib/active_model/type/helpers/mutable.rb
@@ -1,7 +1,7 @@
module ActiveModel
module Type
- module Helpers
- module Mutable # :nodoc:
+ module Helpers # :nodoc: all
+ module Mutable
def cast(value)
deserialize(serialize(value))
end
diff --git a/activemodel/lib/active_model/type/helpers/numeric.rb b/activemodel/lib/active_model/type/helpers/numeric.rb
index 98533f8771..275822b738 100644
--- a/activemodel/lib/active_model/type/helpers/numeric.rb
+++ b/activemodel/lib/active_model/type/helpers/numeric.rb
@@ -1,7 +1,7 @@
module ActiveModel
module Type
- module Helpers
- module Numeric # :nodoc:
+ module Helpers # :nodoc: all
+ module Numeric
def cast(value)
value = \
case value
diff --git a/activemodel/lib/active_model/type/helpers/time_value.rb b/activemodel/lib/active_model/type/helpers/time_value.rb
index 721f9543ed..e57a52104b 100644
--- a/activemodel/lib/active_model/type/helpers/time_value.rb
+++ b/activemodel/lib/active_model/type/helpers/time_value.rb
@@ -2,8 +2,8 @@ require "active_support/core_ext/time/zones"
module ActiveModel
module Type
- module Helpers
- module TimeValue # :nodoc:
+ module Helpers # :nodoc: all
+ module TimeValue
def serialize(value)
value = apply_seconds_precision(value)
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 4b74527144..eac54e7566 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,13 @@
+* Notifications see frozen SQL string.
+
+ Fixes #23774
+
+ *Richard Monette*
+
+* RuntimeErrors are no longer translated to ActiveRecord::StatementInvalid.
+
+ *Richard Monette*
+
* Change the schema cache format to use YAML instead of Marshal.
*Kir Shatrov*
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index faccd1d641..947796eea0 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -10,9 +10,9 @@ module ActiveRecord
def to_sql(arel, binds = [])
if arel.respond_to?(:ast)
collected = visitor.accept(arel.ast, collector)
- collected.compile(binds, self)
+ collected.compile(binds, self).freeze
else
- arel
+ arel.dup.freeze
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 237367c8b3..284529b46e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -598,7 +598,12 @@ module ActiveRecord
def translate_exception(exception, message)
# override in derived class
- ActiveRecord::StatementInvalid.new(message)
+ case exception
+ when RuntimeError
+ exception
+ else
+ ActiveRecord::StatementInvalid.new(message)
+ end
end
def without_prepared_statement?(binds)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/column.rb b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
index c66d543752..1499c1681f 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
@@ -5,8 +5,7 @@ module ActiveRecord
delegate :extra, to: :sql_type_metadata, allow_nil: true
def unsigned?
- # enum and set types do not allow being defined as unsigned.
- !/\A(?:enum|set)\b/.match?(sql_type) && /\bunsigned\b/.match?(sql_type)
+ /\bunsigned(?: zerofill)?\z/.match?(sql_type)
end
def case_sensitive?
diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb
index 5ba354d758..6cf2e01179 100644
--- a/activerecord/lib/active_record/fixture_set/file.rb
+++ b/activerecord/lib/active_record/fixture_set/file.rb
@@ -66,10 +66,13 @@ module ActiveRecord
# Validate our unmarshalled data.
def validate(data)
unless Hash === data || YAML::Omap === data
- raise Fixture::FormatError, "fixture is not a hash"
+ raise Fixture::FormatError, "fixture is not a hash: #{@file}"
end
- raise Fixture::FormatError unless data.all? { |name, row| Hash === row }
+ invalid = data.reject { |_, row| Hash === row }
+ if invalid.any?
+ raise Fixture::FormatError, "fixture key is not a hash: #{@file}, keys: #{invalid.keys.inspect}"
+ end
data
end
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 8de69869a4..3fce0a1df1 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -285,13 +285,13 @@ module ActiveRecord
unless current_adapter?(:PostgreSQLAdapter)
def test_log_invalid_encoding
- error = assert_raise ActiveRecord::StatementInvalid do
+ error = assert_raises RuntimeError do
@connection.send :log, "SELECT 'ы' FROM DUAL" do
raise "ы".force_encoding(Encoding::ASCII_8BIT)
end
end
- assert_not_nil error.cause
+ assert_not_nil error.message
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
index 268800d538..a0823be143 100644
--- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
@@ -15,6 +15,7 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
t.bigint :unsigned_bigint, unsigned: true
t.float :unsigned_float, unsigned: true
t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2
+ t.column :unsigned_zerofill, "int unsigned zerofill"
end
end
@@ -48,7 +49,6 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
t.unsigned_bigint :unsigned_bigint_t
t.unsigned_float :unsigned_float_t
t.unsigned_decimal :unsigned_decimal_t, precision: 10, scale: 2
- t.column :unsigned_zerofill, "int unsigned zerofill"
end
@connection.columns("unsigned_types").select { |c| /^unsigned_/.match?(c.name) }.each do |column|
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index f3d0e4a1b1..ea3e8d7727 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -211,9 +211,19 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_dirty_dirty_yaml_file
- assert_raise(ActiveRecord::Fixture::FormatError) do
- ActiveRecord::FixtureSet.new(Account.connection, "courses", Course, FIXTURES_ROOT + "/naked/yml/courses")
+ fixture_path = FIXTURES_ROOT + "/naked/yml/courses"
+ error = assert_raise(ActiveRecord::Fixture::FormatError) do
+ ActiveRecord::FixtureSet.new(Account.connection, "courses", Course, fixture_path)
end
+ assert_equal "fixture is not a hash: #{fixture_path}.yml", error.to_s
+ end
+
+ def test_yaml_file_with_one_invalid_fixture
+ fixture_path = FIXTURES_ROOT + "/naked/yml/courses_with_invalid_key"
+ error = assert_raise(ActiveRecord::Fixture::FormatError) do
+ ActiveRecord::FixtureSet.new(Account.connection, "courses", Course, fixture_path)
+ end
+ assert_equal "fixture key is not a hash: #{fixture_path}.yml, keys: [\"two\"]", error.to_s
end
def test_yaml_file_with_invalid_column
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 90054ce83d..4a49bfe9b1 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -202,6 +202,20 @@ class QueryCacheTest < ActiveRecord::TestCase
ActiveSupport::Notifications.unsubscribe subscriber
end
+ def test_query_cache_does_not_allow_sql_key_mutation
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |_, _, _, _, payload|
+ payload[:sql].downcase!
+ end
+
+ assert_raises RuntimeError do
+ ActiveRecord::Base.cache do
+ assert_queries(1) { Task.find(1); Task.find(1) }
+ end
+ end
+ ensure
+ ActiveSupport::Notifications.unsubscribe subscriber
+ end
+
def test_cache_is_flat
Task.cache do
assert_queries(1) { Topic.find(1); Topic.find(1); }
diff --git a/activerecord/test/fixtures/naked/yml/courses_with_invalid_key.yml b/activerecord/test/fixtures/naked/yml/courses_with_invalid_key.yml
new file mode 100644
index 0000000000..6f9da79b45
--- /dev/null
+++ b/activerecord/test/fixtures/naked/yml/courses_with_invalid_key.yml
@@ -0,0 +1,3 @@
+one:
+ id: 1
+two: ['not a hash']
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index af8ddb176f..e6c79f2a38 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -109,16 +109,22 @@ module ActiveSupport
invoke_sequence = Proc.new do
skipped = nil
while true
- current, next_sequence = next_sequence, next_sequence.nested
+ current = next_sequence
current.invoke_before(env)
if current.final?
env.value = !env.halted && (!block_given? || yield)
elsif current.skip?(env)
(skipped ||= []) << current
+ next_sequence = next_sequence.nested
next
else
- target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
- target.send(method, *arguments, &block)
+ next_sequence = next_sequence.nested
+ begin
+ target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
+ target.send(method, *arguments, &block)
+ ensure
+ next_sequence = current
+ end
end
current.invoke_after(env)
skipped.pop.invoke_after(env) while skipped && skipped.first
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index aadc40ab84..22f66978f8 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -224,10 +224,51 @@ module CallbacksTest
define_callbacks :save
end
- class AroundPerson < MySuper
+ class MySlate < MySuper
attr_reader :history
attr_accessor :save_fails
+ def initialize
+ @history = []
+ end
+
+ def save
+ run_callbacks :save do
+ raise "inside save" if save_fails
+ @history << "running"
+ end
+ end
+
+ def no; false; end
+ def yes; true; end
+
+ def method_missing(sym, *)
+ case sym
+ when /^log_(.*)/
+ @history << $1
+ nil
+ when /^wrap_(.*)/
+ @history << "wrap_#$1"
+ yield
+ @history << "unwrap_#$1"
+ nil
+ when /^double_(.*)/
+ @history << "first_#$1"
+ yield
+ @history << "second_#$1"
+ yield
+ @history << "third_#$1"
+ else
+ super
+ end
+ end
+
+ def respond_to_missing?(sym)
+ sym =~ /^(log|wrap)_/ || super
+ end
+ end
+
+ class AroundPerson < MySlate
set_callback :save, :before, :nope, if: :no
set_callback :save, :before, :nope, unless: :yes
set_callback :save, :after, :tweedle
@@ -242,9 +283,6 @@ module CallbacksTest
set_callback :save, :around, :w0tno, if: :no
set_callback :save, :around, :tweedle_deedle
- def no; false; end
- def yes; true; end
-
def nope
@history << "boom"
end
@@ -283,17 +321,6 @@ module CallbacksTest
yield
@history << "tweedle deedle post"
end
-
- def initialize
- @history = []
- end
-
- def save
- run_callbacks :save do
- raise "inside save" if save_fails
- @history << "running"
- end
- end
end
class AroundPersonResult < MySuper
@@ -408,6 +435,32 @@ module CallbacksTest
end
end
+ class DoubleYieldTest < ActiveSupport::TestCase
+ class DoubleYieldModel < MySlate
+ set_callback :save, :around, :wrap_outer
+ set_callback :save, :around, :double_trouble
+ set_callback :save, :around, :wrap_inner
+ end
+
+ def test_double_save
+ double = DoubleYieldModel.new
+ double.save
+ assert_equal [
+ "wrap_outer",
+ "first_trouble",
+ "wrap_inner",
+ "running",
+ "unwrap_inner",
+ "second_trouble",
+ "wrap_inner",
+ "running",
+ "unwrap_inner",
+ "third_trouble",
+ "unwrap_outer",
+ ], double.history
+ end
+ end
+
class CallStackTest < ActiveSupport::TestCase
def test_tidy_call_stack
around = AroundPerson.new
diff --git a/guides/source/generators.md b/guides/source/generators.md
index 32bbdc554a..d0b6cef3fd 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -208,7 +208,15 @@ $ bin/rails generate scaffold User name:string
Looking at this output, it's easy to understand how generators work in Rails 3.0 and above. The scaffold generator doesn't actually generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication.
-Our first customization on the workflow will be to stop generating stylesheet, JavaScript and test fixture files for scaffolds. We can achieve that by changing our configuration to the following:
+If we want to avoid generating the default `app/assets/stylesheets/scaffolds.scss` file when scaffolding a new resource we can disable `scaffold_stylesheet`:
+
+```ruby
+ config.generators do |g|
+ g.scaffold_stylesheet false
+ end
+```
+
+The next customization on the workflow will be to stop generating stylesheet, JavaScript and test fixture files for scaffolds altogether. We can achieve that by changing our configuration to the following:
```ruby
config.generators do |g|
@@ -451,6 +459,26 @@ $ rails new thud -m https://gist.github.com/radar/722911/raw/
Whilst the final section of this guide doesn't cover how to generate the most awesome template known to man, it will take you through the methods available at your disposal so that you can develop it yourself. These same methods are also available for generators.
+Adding Command Line Arguments
+-----------------------------
+Rails generators can be easily modified to accept custom command line arguments. This functionality comes from [Thor](http://www.rubydoc.info/github/erikhuda/thor/master/Thor/Base/ClassMethods#class_option-instance_method):
+
+```
+class_option :scope, type: :string, default: 'read_products'
+```
+
+Now our generator can be invoked as follows:
+
+```bash
+rails generate initializer --scope write_products
+```
+
+The command line arguments are accessed through the `options` method inside the generator class. e.g:
+
+```ruby
+@scope = options['scope']
+```
+
Generator methods
-----------------
diff --git a/guides/source/security.md b/guides/source/security.md
index bb67eb75d9..a81a782cf2 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -377,7 +377,7 @@ In 2007 there was the first tailor-made trojan which stole information from an I
Having one single place in the admin interface or Intranet, where the input has not been sanitized, makes the entire application vulnerable. Possible exploits include stealing the privileged administrator's cookie, injecting an iframe to steal the administrator's password or installing malicious software through browser security holes to take over the administrator's computer.
-Refer to the Injection section for countermeasures against XSS. It is _recommended to use the SafeErb plugin_ also in an Intranet or administration interface.
+Refer to the Injection section for countermeasures against XSS.
**CSRF** Cross-Site Request Forgery (CSRF), also known as Cross-Site Reference Forgery (XSRF), is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface.