diff options
-rw-r--r-- | Gemfile.lock | 4 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/journey/nodes/node.rb | 13 | ||||
-rw-r--r-- | actionview/CHANGELOG.md | 13 | ||||
-rw-r--r-- | actionview/app/assets/javascripts/rails-ujs/features/remote.coffee | 7 | ||||
-rw-r--r-- | actionview/app/assets/javascripts/rails-ujs/start.coffee | 8 | ||||
-rw-r--r-- | actionview/test/ujs/public/test/data-disable-with.js | 19 | ||||
-rw-r--r-- | actionview/test/ujs/public/test/data-disable.js | 19 | ||||
-rw-r--r-- | actionview/test/ujs/public/test/data-remote.js | 38 | ||||
-rw-r--r-- | actionview/test/ujs/public/test/settings.js | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation.rb | 4 | ||||
-rw-r--r-- | activerecord/test/cases/relations_test.rb | 39 | ||||
-rw-r--r-- | guides/source/rails_application_templates.md | 6 | ||||
-rw-r--r-- | guides/source/upgrading_ruby_on_rails.md | 2 | ||||
-rw-r--r-- | railties/CHANGELOG.md | 6 | ||||
-rw-r--r-- | railties/lib/rails/generators/actions.rb | 5 | ||||
-rw-r--r-- | railties/railties.gemspec | 2 | ||||
-rw-r--r-- | railties/test/generators/actions_test.rb | 16 |
17 files changed, 182 insertions, 21 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index f4f3e430ba..c374f4921a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -93,7 +93,7 @@ PATH activesupport (= 6.0.0.alpha) method_source rake (>= 0.8.7) - thor (>= 0.19.0, < 2.0) + thor (>= 0.20.3, < 2.0) GEM remote: https://rubygems.org/ @@ -304,7 +304,7 @@ GEM marcel (0.3.3) mimemagic (~> 0.3.2) memoist (0.16.0) - method_source (0.9.1) + method_source (0.9.2) mime-types (3.2.2) mime-types-data (~> 3.2015) mime-types-data (3.2018.0812) diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb index 32f632800c..086d6a3e07 100644 --- a/actionpack/lib/action_dispatch/journey/nodes/node.rb +++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb @@ -65,12 +65,12 @@ module ActionDispatch def literal?; false; end end - %w{ Symbol Slash Dot }.each do |t| - class_eval <<-eoruby, __FILE__, __LINE__ + 1 - class #{t} < Terminal; - def type; :#{t.upcase}; end - end - eoruby + class Slash < Terminal # :nodoc: + def type; :SLASH; end + end + + class Dot < Terminal # :nodoc: + def type; :DOT; end end class Symbol < Terminal # :nodoc: @@ -89,6 +89,7 @@ module ActionDispatch regexp == DEFAULT_EXP end + def type; :SYMBOL; end def symbol?; true; end end diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 237a25ba4f..f32bc3ab40 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,16 @@ +* Prevent non-primary mouse keys from triggering Rails UJS click handlers. + Firefox fires click events even if the click was triggered by non-primary mouse keys such as right- or scroll-wheel-clicks. + For example, right-clicking a link such as the one described below (with an underlying ajax request registered on click) should not cause that request to occur. + + ``` + <%= link_to 'Remote', remote_path, class: 'remote', remote: true, data: { type: :json } %> + ``` + + Fixes #34541 + + *Wolfgang Hobmaier* + + * Prevent `ActionView::TextHelper#word_wrap` from unexpectedly stripping white space from the _left_ side of lines. For example, given input like this: diff --git a/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee index b3448dabac..a5b61220bb 100644 --- a/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee @@ -82,9 +82,12 @@ Rails.formSubmitButtonClick = (e) -> setData(form, 'ujs:submit-button-formaction', button.getAttribute('formaction')) setData(form, 'ujs:submit-button-formmethod', button.getAttribute('formmethod')) -Rails.handleMetaClick = (e) -> +Rails.preventInsignificantClick = (e) -> link = this method = (link.getAttribute('data-method') or 'GET').toUpperCase() data = link.getAttribute('data-params') metaClick = e.metaKey or e.ctrlKey - e.stopImmediatePropagation() if metaClick and method is 'GET' and not data + insignificantMetaClick = metaClick and method is 'GET' and not data + primaryMouseKey = e.button is 0 + e.stopImmediatePropagation() if not primaryMouseKey or insignificantMetaClick + diff --git a/actionview/app/assets/javascripts/rails-ujs/start.coffee b/actionview/app/assets/javascripts/rails-ujs/start.coffee index 32a915ac0b..5c1214df59 100644 --- a/actionview/app/assets/javascripts/rails-ujs/start.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/start.coffee @@ -3,8 +3,8 @@ getData, $ refreshCSRFTokens, CSRFProtection enableElement, disableElement, handleDisabledElement - handleConfirm - handleRemote, formSubmitButtonClick, handleMetaClick + handleConfirm, preventInsignificantClick + handleRemote, formSubmitButtonClick, handleMethod } = Rails @@ -35,13 +35,14 @@ Rails.start = -> delegate document, Rails.buttonDisableSelector, 'ajax:complete', enableElement delegate document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement + delegate document, Rails.linkClickSelector, 'click', preventInsignificantClick delegate document, Rails.linkClickSelector, 'click', handleDisabledElement delegate document, Rails.linkClickSelector, 'click', handleConfirm - delegate document, Rails.linkClickSelector, 'click', handleMetaClick delegate document, Rails.linkClickSelector, 'click', disableElement delegate document, Rails.linkClickSelector, 'click', handleRemote delegate document, Rails.linkClickSelector, 'click', handleMethod + delegate document, Rails.buttonClickSelector, 'click', preventInsignificantClick delegate document, Rails.buttonClickSelector, 'click', handleDisabledElement delegate document, Rails.buttonClickSelector, 'click', handleConfirm delegate document, Rails.buttonClickSelector, 'click', disableElement @@ -60,6 +61,7 @@ Rails.start = -> delegate document, Rails.formSubmitSelector, 'ajax:send', disableElement delegate document, Rails.formSubmitSelector, 'ajax:complete', enableElement + delegate document, Rails.formInputClickSelector, 'click', preventInsignificantClick delegate document, Rails.formInputClickSelector, 'click', handleDisabledElement delegate document, Rails.formInputClickSelector, 'click', handleConfirm delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick diff --git a/actionview/test/ujs/public/test/data-disable-with.js b/actionview/test/ujs/public/test/data-disable-with.js index 645ad494c3..b5684e0938 100644 --- a/actionview/test/ujs/public/test/data-disable-with.js +++ b/actionview/test/ujs/public/test/data-disable-with.js @@ -322,6 +322,25 @@ asyncTest('ctrl-clicking on a link does not disables the link', 6, function() { start() }) +asyncTest('right/mouse-wheel-clicking on a link does not disables the link', 10, function() { + var link = $('a[data-disable-with]') + + App.checkEnabledState(link, 'Click me') + + link.triggerNative('click', { button: 1 }) + App.checkEnabledState(link, 'Click me') + + link.triggerNative('click', { button: 1 }) + App.checkEnabledState(link, 'Click me') + + link.triggerNative('click', { button: 2 }) + App.checkEnabledState(link, 'Click me') + + link.triggerNative('click', { button: 2 }) + App.checkEnabledState(link, 'Click me') + start() +}) + asyncTest('button[data-remote][data-disable-with] disables and re-enables', 6, function() { var button = $('button[data-remote][data-disable-with]') diff --git a/actionview/test/ujs/public/test/data-disable.js b/actionview/test/ujs/public/test/data-disable.js index 88dc801b2f..9f84c4647e 100644 --- a/actionview/test/ujs/public/test/data-disable.js +++ b/actionview/test/ujs/public/test/data-disable.js @@ -250,6 +250,25 @@ asyncTest('ctrl-clicking on a link does not disables the link', 6, function() { start() }) +asyncTest('right/mouse-wheel-clicking on a link does not disable the link', 10, function() { + var link = $('a[data-disable]') + + App.checkEnabledState(link, 'Click me') + + link.triggerNative('click', { button: 1 }) + App.checkEnabledState(link, 'Click me') + + link.triggerNative('click', { button: 1 }) + App.checkEnabledState(link, 'Click me') + + link.triggerNative('click', { button: 2 }) + App.checkEnabledState(link, 'Click me') + + link.triggerNative('click', { button: 2 }) + App.checkEnabledState(link, 'Click me') + start() +}) + asyncTest('button[data-remote][data-disable] disables and re-enables', 6, function() { var button = $('button[data-remote][data-disable]') diff --git a/actionview/test/ujs/public/test/data-remote.js b/actionview/test/ujs/public/test/data-remote.js index 3503c2cff3..55d39b0a52 100644 --- a/actionview/test/ujs/public/test/data-remote.js +++ b/actionview/test/ujs/public/test/data-remote.js @@ -63,6 +63,25 @@ asyncTest('ctrl-clicking on a link does not fire ajaxyness', 0, function() { setTimeout(function() { start() }, 13) }) +asyncTest('right/mouse-wheel-clicking on a link does not fire ajaxyness', 0, function() { + var link = $('a[data-remote]') + + // Ideally, we'd setup an iframe to intercept normal link clicks + // and add a test to make sure the iframe:loaded event is triggered. + // However, jquery doesn't actually cause a native `click` event and + // follow links using `trigger('click')`, it only fires bindings. + link + .removeAttr('data-params') + .bindNative('ajax:beforeSend', function() { + ok(false, 'ajax should not be triggered') + }) + + link.triggerNative('click', { button: 1 }) + link.triggerNative('click', { button: 2 }) + + setTimeout(function() { start() }, 13) +}) + asyncTest('ctrl-clicking on a link still fires ajax for non-GET links and for links with "data-params"', 2, function() { var link = $('a[data-remote]') @@ -148,6 +167,25 @@ asyncTest('clicking on a button with data-remote attribute', 5, function() { .triggerNative('click') }) +asyncTest('right/mouse-wheel-clicking on a button with data-remote attribute does not fire ajaxyness', 0, function() { + var button = $('button[data-remote]') + + // Ideally, we'd setup an iframe to intercept normal link clicks + // and add a test to make sure the iframe:loaded event is triggered. + // However, jquery doesn't actually cause a native `click` event and + // follow links using `trigger('click')`, it only fires bindings. + button + .removeAttr('data-params') + .bindNative('ajax:beforeSend', function() { + ok(false, 'ajax should not be triggered') + }) + + button.triggerNative('click', { button: 1 }) + button.triggerNative('click', { button: 2 }) + + setTimeout(function() { start() }, 13) +}) + asyncTest('changing a select option with data-remote attribute', 5, function() { buildSelect() diff --git a/actionview/test/ujs/public/test/settings.js b/actionview/test/ujs/public/test/settings.js index 05677f2595..682d044403 100644 --- a/actionview/test/ujs/public/test/settings.js +++ b/actionview/test/ujs/public/test/settings.js @@ -71,7 +71,7 @@ try { } catch (e) { _MouseEvent = function(type, options) { var evt = document.createEvent('MouseEvents') - evt.initMouseEvent(type, options.bubbles, options.cancelable, window, options.detail, 0, 0, 80, 20, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, null) + evt.initMouseEvent(type, options.bubbles, options.cancelable, window, options.detail, 0, 0, 80, 20, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, null) return evt } } diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d5b6082d13..ba221a333b 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -163,7 +163,7 @@ module ActiveRecord # Attempts to create a record with the given attributes in a table that has a unique constraint # on one or several of its columns. If a row already exists with one or several of these # unique constraints, the exception such an insertion would normally raise is caught, - # and the existing record with those attributes is found using #find_by. + # and the existing record with those attributes is found using #find_by!. # # This is similar to #find_or_create_by, but avoids the problem of stale reads between the SELECT # and the INSERT, as that method needs to first query the table, then attempt to insert a row @@ -173,7 +173,7 @@ module ActiveRecord # # * The underlying table must have the relevant columns defined with unique constraints. # * A unique constraint violation may be triggered by only one, or at least less than all, - # of the given attributes. This means that the subsequent #find_by may fail to find a + # of the given attributes. This means that the subsequent #find_by! may fail to find a # matching record, which will then raise an <tt>ActiveRecord::RecordNotFound</tt> exception, # rather than a record with the given attributes. # * While we avoid the race condition between SELECT -> INSERT from #find_or_create_by, diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index e471ee8039..756eeca35f 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1315,6 +1315,13 @@ class RelationTest < ActiveRecord::TestCase assert_not_equal subscriber, Subscriber.create_or_find_by(nick: "cat") end + def test_create_or_find_by_should_not_raise_due_to_validation_errors + assert_nothing_raised do + bird = Bird.create_or_find_by(color: "green") + assert_predicate bird, :invalid? + end + end + def test_create_or_find_by_with_non_unique_attributes Subscriber.create!(nick: "bob", name: "the builder") @@ -1334,6 +1341,38 @@ class RelationTest < ActiveRecord::TestCase end end + def test_create_or_find_by_with_bang + assert_nil Subscriber.find_by(nick: "bob") + + subscriber = Subscriber.create!(nick: "bob") + + assert_equal subscriber, Subscriber.create_or_find_by!(nick: "bob") + assert_not_equal subscriber, Subscriber.create_or_find_by!(nick: "cat") + end + + def test_create_or_find_by_with_bang_should_raise_due_to_validation_errors + assert_raises(ActiveRecord::RecordInvalid) { Bird.create_or_find_by!(color: "green") } + end + + def test_create_or_find_by_with_bang_with_non_unique_attributes + Subscriber.create!(nick: "bob", name: "the builder") + + assert_raises(ActiveRecord::RecordNotFound) do + Subscriber.create_or_find_by!(nick: "bob", name: "the cat") + end + end + + def test_create_or_find_by_with_bang_within_transaction + assert_nil Subscriber.find_by(nick: "bob") + + subscriber = Subscriber.create!(nick: "bob") + + Subscriber.transaction do + assert_equal subscriber, Subscriber.create_or_find_by!(nick: "bob") + assert_not_equal subscriber, Subscriber.create_or_find_by!(nick: "cat") + end + end + def test_find_or_initialize_by assert_nil Bird.find_by(name: "bob") diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index bc68a555c5..982df26987 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -195,6 +195,12 @@ You can also run commands as a super-user: rails_command "log:clear", sudo: true ``` +You can also run commands that should abort application generation if they fail: + +```ruby +rails_command "db:migrate", abort_on_failure: true +``` + ### route(routing_code) Adds a routing entry to the `config/routes.rb` file. In the steps above, we generated a person scaffold and also removed `README.rdoc`. Now, to make `PeopleController#index` the default page for the application: diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index a0553c1ccc..e74985c5b0 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -407,7 +407,7 @@ want to add this feature it will need to be turned on in an initializer. Rails 5 now supports per-form CSRF tokens to mitigate against code-injection attacks with forms created by JavaScript. With this option turned on, forms in your application will each have their -own CSRF token that is specified to the action and method for that form. +own CSRF token that is specific to the action and method for that form. config.action_controller.per_form_csrf_tokens = true diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 448fd48f10..91fa3aa8be 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,9 @@ +* Add an `abort_on_failure` boolean option to the generator method that shell + out (`generate`, `rake`, `rails_command`) to abort the generator if the + command fails. + + *David RodrÃguez* + * Remove `app/assets` and `app/javascript` from `eager_load_paths` and `autoload_paths`. *Gannon McGibbon* diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 78d2471890..4646a55316 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -221,9 +221,11 @@ module Rails # generate(:authenticated, "user session") def generate(what, *args) log :generate, what + + options = args.extract_options! argument = args.flat_map(&:to_s).join(" ") - in_root { run_ruby_script("bin/rails generate #{what} #{argument}", verbose: false) } + execute_command :rails, "generate #{what} #{argument}", options end # Runs the supplied rake task (invoked with 'rake ...') @@ -307,6 +309,7 @@ module Rails config = { verbose: false } config[:capture] = options[:capture] if options[:capture] + config[:abort_on_failure] = options[:abort_on_failure] if options[:abort_on_failure] in_root { run("#{sudo}#{extify(executor)} #{command} RAILS_ENV=#{env}", config) } end diff --git a/railties/railties.gemspec b/railties/railties.gemspec index 4e4a504c97..91407ee1ae 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -37,7 +37,7 @@ Gem::Specification.new do |s| s.add_dependency "actionpack", version s.add_dependency "rake", ">= 0.8.7" - s.add_dependency "thor", ">= 0.19.0", "< 2.0" + s.add_dependency "thor", ">= 0.20.3", "< 2.0" s.add_dependency "method_source" s.add_development_dependency "actionview", version diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index a2b35124c5..af475400a1 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -303,9 +303,21 @@ class ActionsTest < Rails::Generators::TestCase end def test_generate_should_run_script_generate_with_argument_and_options - assert_called_with(generator, :run_ruby_script, ["bin/rails generate model MyModel", verbose: false]) do - action :generate, "model", "MyModel" + run_generator + action :generate, "model", "MyModel" + assert_file "app/models/my_model.rb", /MyModel/ + end + + def test_generate_aborts_when_subprocess_fails_if_requested + run_generator + content = capture(:stderr) do + assert_raises SystemExit do + action :generate, "model", "MyModel:ADsad", abort_on_failure: true + action :generate, "model", "MyModel" + end end + assert_match(/wrong constant name MyModel:aDsad/, content) + assert_no_file "app/models/my_model.rb" end def test_rake_should_run_rake_command_with_default_env |