diff options
31 files changed, 439 insertions, 98 deletions
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 02707d0b5f..d4a89a17ee 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -13,6 +13,7 @@ class BaseTest < ActiveSupport::TestCase def teardown ActionMailer::Base.asset_host = nil ActionMailer::Base.assets_dir = nil + ActionMailer::Base.preview_interceptors.clear end test "method call to mail does not raise error" do diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 66cef08b1b..c23577de9b 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,9 @@ +* Make logging of CSRF failures optional (but on by default) with the + `log_warning_on_csrf_failure` configuration setting in + `ActionController::RequestForgeryProtection`. + + *John Barton* + * Fix URL generation in controller tests with request-dependent `default_url_options` methods. diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index c88074d4c6..e3b1f5ae7c 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -68,6 +68,10 @@ module ActionController #:nodoc: config_accessor :allow_forgery_protection self.allow_forgery_protection = true if allow_forgery_protection.nil? + # Controls whether a CSRF failure logs a warning. On by default. + config_accessor :log_warning_on_csrf_failure + self.log_warning_on_csrf_failure = true + helper_method :form_authenticity_token helper_method :protect_against_forgery? end @@ -193,7 +197,9 @@ module ActionController #:nodoc: mark_for_same_origin_verification! if !verified_request? - logger.warn "Can't verify CSRF token authenticity" if logger + if logger && log_warning_on_csrf_failure + logger.warn "Can't verify CSRF token authenticity" + end handle_unverified_request end end diff --git a/actionpack/lib/action_dispatch/journey/gtg/builder.rb b/actionpack/lib/action_dispatch/journey/gtg/builder.rb index 7d2791714b..450588cda6 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/builder.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/builder.rb @@ -27,7 +27,7 @@ module ActionDispatch marked[s] = true # mark s s.group_by { |state| symbol(state) }.each do |sym, ps| - u = ps.map { |l| followpos(l) }.flatten + u = ps.flat_map { |l| followpos(l) } next if u.empty? if u.uniq == [DUMMY] @@ -90,7 +90,7 @@ module ActionDispatch firstpos(node.left) end when Nodes::Or - node.children.map { |c| firstpos(c) }.flatten.uniq + node.children.flat_map { |c| firstpos(c) }.uniq when Nodes::Unary firstpos(node.left) when Nodes::Terminal @@ -105,7 +105,7 @@ module ActionDispatch when Nodes::Star firstpos(node.left) when Nodes::Or - node.children.map { |c| lastpos(c) }.flatten.uniq + node.children.flat_map { |c| lastpos(c) }.uniq when Nodes::Cat if nullable?(node.right) lastpos(node.left) | lastpos(node.right) diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb index 58ad803841..254c2befc4 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb @@ -31,7 +31,7 @@ module ActionDispatch return if acceptance_states.empty? - memos = acceptance_states.map { |x| tt.memo(x) }.flatten.compact + memos = acceptance_states.flat_map { |x| tt.memo(x) }.compact MatchData.new(memos) end diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb index a5b19fcf06..e6212b1ee2 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb @@ -114,8 +114,8 @@ module ActionDispatch end def states - ss = @string_states.keys + @string_states.values.map(&:values).flatten - rs = @regexp_states.keys + @regexp_states.values.map(&:values).flatten + ss = @string_states.keys + @string_states.values.flat_map(&:values) + rs = @regexp_states.keys + @regexp_states.values.flat_map(&:values) (ss + rs).uniq end @@ -143,11 +143,11 @@ module ActionDispatch def move_regexp(t, a) return [] if t.empty? - t.map { |s| + t.flat_map { |s| if states = @regexp_states[s] states.map { |re, v| re === a ? v : nil } end - }.flatten.compact.uniq + }.compact.uniq end def move_string(t, a) diff --git a/actionpack/lib/action_dispatch/journey/nfa/dot.rb b/actionpack/lib/action_dispatch/journey/nfa/dot.rb index 5c33a872e5..47bf76bdbf 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/dot.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/dot.rb @@ -16,9 +16,9 @@ module ActionDispatch # end # " #{n.object_id} [label=\"#{label}\", shape=box];" #} - #memo_edges = memos.map { |k, memos| + #memo_edges = memos.flat_map { |k, memos| # (memos || []).map { |v| " #{k} -> #{v.object_id};" } - #}.flatten.uniq + #}.uniq <<-eodot digraph nfa { diff --git a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb index 5b40da6569..b23270db3c 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb @@ -34,7 +34,7 @@ module ActionDispatch return if acceptance_states.empty? - memos = acceptance_states.map { |x| tt.memo(x) }.flatten.compact + memos = acceptance_states.flat_map { |x| tt.memo(x) }.compact MatchData.new(memos) end diff --git a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb index a3017aeea1..66e414213a 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb @@ -42,7 +42,7 @@ module ActionDispatch end def states - (@table.keys + @table.values.map(&:keys).flatten).uniq + (@table.keys + @table.values.flat_map(&:keys)).uniq end # Returns a generalized transition graph with reduced states. The states @@ -93,7 +93,7 @@ module ActionDispatch # Returns set of NFA states to which there is a transition on ast symbol # +a+ from some state +s+ in +t+. def following_states(t, a) - Array(t).map { |s| inverted[s][a] }.flatten.uniq + Array(t).flat_map { |s| inverted[s][a] }.uniq end # Returns set of NFA states to which there is a transition on ast symbol @@ -107,7 +107,7 @@ module ActionDispatch end def alphabet - inverted.values.map(&:keys).flatten.compact.uniq.sort_by { |x| x.to_s } + inverted.values.flat_map(&:keys).compact.uniq.sort_by { |x| x.to_s } end # Returns a set of NFA states reachable from some NFA state +s+ in set @@ -131,9 +131,9 @@ module ActionDispatch end def transitions - @table.map { |to, hash| + @table.flat_map { |to, hash| hash.map { |from, sym| [from, sym, to] } - }.flatten(1) + } end private diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb index d37aa1fbe5..fb155e516f 100644 --- a/actionpack/lib/action_dispatch/journey/path/pattern.rb +++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb @@ -53,9 +53,9 @@ module ActionDispatch end def optional_names - @optional_names ||= spec.grep(Nodes::Group).map { |group| + @optional_names ||= spec.grep(Nodes::Group).flat_map { |group| group.grep(Nodes::Symbol) - }.flatten.map { |n| n.name }.uniq + }.map { |n| n.name }.uniq end class RegexpOffsets < Journey::Visitors::Visitor # :nodoc: diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 0b762aa9a4..357829e59f 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -708,7 +708,8 @@ module ActionDispatch options[:constraints] ||= {} unless shallow? - options[:shallow_path] = options[:path] if args.any? + options[:shallow_path] ||= options[:path] if options.key?(:path) + options[:shallow_prefix] ||= options[:as] if options.key?(:as) end if options[:constraints].is_a?(Hash) @@ -792,9 +793,16 @@ module ActionDispatch # end def namespace(path, options = {}) path = path.to_s - options = { :path => path, :as => path, :module => path, - :shallow_path => path, :shallow_prefix => path }.merge!(options) - scope(options) { yield } + + defaults = { + module: path, + path: options.fetch(:path, path), + as: options.fetch(:as, path), + shallow_path: options.fetch(:path, path), + shallow_prefix: options.fetch(:as, path) + } + + scope(defaults.merge!(options)) { yield } end # === Parameter Restriction @@ -1323,8 +1331,10 @@ module ActionDispatch end with_scope_level(:member) do - scope(parent_resource.member_scope) do - yield + if shallow? + shallow_scope(parent_resource.member_scope) { yield } + else + scope(parent_resource.member_scope) { yield } end end end @@ -1347,16 +1357,8 @@ module ActionDispatch end with_scope_level(:nested) do - if shallow? - with_exclusive_scope do - if @scope[:shallow_path].blank? - scope(parent_resource.nested_scope, nested_options) { yield } - else - scope(@scope[:shallow_path], :as => @scope[:shallow_prefix]) do - scope(parent_resource.nested_scope, nested_options) { yield } - end - end - end + if shallow? && nesting_depth > 1 + shallow_scope(parent_resource.nested_scope, nested_options) { yield } else scope(parent_resource.nested_scope, nested_options) { yield } end @@ -1558,21 +1560,23 @@ module ActionDispatch end end - def with_scope_level(kind, resource = parent_resource) + def with_scope_level(kind) old, @scope[:scope_level] = @scope[:scope_level], kind - old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource yield ensure @scope[:scope_level] = old - @scope[:scope_level_resource] = old_resource end def resource_scope(kind, resource) #:nodoc: - with_scope_level(kind, resource) do - scope(parent_resource.resource_scope) do - yield - end + old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource + @nesting.push(resource) + + with_scope_level(kind) do + scope(parent_resource.resource_scope) { yield } end + ensure + @nesting.pop + @scope[:scope_level_resource] = old_resource end def nested_options #:nodoc: @@ -1584,6 +1588,10 @@ module ActionDispatch options end + def nesting_depth #:nodoc: + @nesting.size + end + def param_constraint? #:nodoc: @scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp) end @@ -1596,18 +1604,20 @@ module ActionDispatch flag && resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s) end - def shallow_scoping? #:nodoc: - shallow? && @scope[:scope_level] == :member + def shallow_scope(path, options = {}) #:nodoc: + old_name_prefix, old_path = @scope[:as], @scope[:path] + @scope[:as], @scope[:path] = @scope[:shallow_prefix], @scope[:shallow_path] + + scope(path, options) { yield } + ensure + @scope[:as], @scope[:path] = old_name_prefix, old_path end def path_for_action(action, path) #:nodoc: - prefix = shallow_scoping? ? - "#{@scope[:shallow_path]}/#{parent_resource.shallow_scope}" : @scope[:path] - if canonical_action?(action, path.blank?) - prefix.to_s + @scope[:path].to_s else - "#{prefix}/#{action_path(action, path)}" + "#{@scope[:path]}/#{action_path(action, path)}" end end @@ -1645,7 +1655,7 @@ module ActionDispatch when :new [prefix, :new, name_prefix, member_name] when :member - [prefix, shallow_scoping? ? @scope[:shallow_prefix] : name_prefix, member_name] + [prefix, name_prefix, member_name] when :root [name_prefix, collection_name, prefix] else @@ -1786,6 +1796,7 @@ module ActionDispatch @set = set @scope = { :path_names => @set.resources_path_names } @concerns = {} + @nesting = [] end include Base diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 1f5fc06410..99229b3baf 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -289,6 +289,22 @@ module RequestForgeryProtectionTests end end + def test_should_not_warn_if_csrf_logging_disabled + old_logger = ActionController::Base.logger + logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + ActionController::Base.logger = logger + ActionController::Base.log_warning_on_csrf_failure = false + + begin + assert_blocked { post :index } + + assert_equal 0, logger.logged(:warn).size + ensure + ActionController::Base.logger = old_logger + ActionController::Base.log_warning_on_csrf_failure = true + end + end + def test_should_only_allow_same_origin_js_get_with_xhr_header assert_cross_origin_blocked { get :same_origin_js } assert_cross_origin_blocked { get :same_origin_js, format: 'js' } diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 1fa2cc6cf2..a47050adce 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1031,6 +1031,136 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'users/home#index', @response.body end + def test_namespaced_shallow_routes_with_module_option + draw do + namespace :foo, module: 'bar' do + resources :posts, only: [:index, :show] do + resources :comments, only: [:index, :show], shallow: true + end + end + end + + get '/foo/posts' + assert_equal '/foo/posts', foo_posts_path + assert_equal 'bar/posts#index', @response.body + + get '/foo/posts/1' + assert_equal '/foo/posts/1', foo_post_path('1') + assert_equal 'bar/posts#show', @response.body + + get '/foo/posts/1/comments' + assert_equal '/foo/posts/1/comments', foo_post_comments_path('1') + assert_equal 'bar/comments#index', @response.body + + get '/foo/comments/2' + assert_equal '/foo/comments/2', foo_comment_path('2') + assert_equal 'bar/comments#show', @response.body + end + + def test_namespaced_shallow_routes_with_path_option + draw do + namespace :foo, path: 'bar' do + resources :posts, only: [:index, :show] do + resources :comments, only: [:index, :show], shallow: true + end + end + end + + get '/bar/posts' + assert_equal '/bar/posts', foo_posts_path + assert_equal 'foo/posts#index', @response.body + + get '/bar/posts/1' + assert_equal '/bar/posts/1', foo_post_path('1') + assert_equal 'foo/posts#show', @response.body + + get '/bar/posts/1/comments' + assert_equal '/bar/posts/1/comments', foo_post_comments_path('1') + assert_equal 'foo/comments#index', @response.body + + get '/bar/comments/2' + assert_equal '/bar/comments/2', foo_comment_path('2') + assert_equal 'foo/comments#show', @response.body + end + + def test_namespaced_shallow_routes_with_as_option + draw do + namespace :foo, as: 'bar' do + resources :posts, only: [:index, :show] do + resources :comments, only: [:index, :show], shallow: true + end + end + end + + get '/foo/posts' + assert_equal '/foo/posts', bar_posts_path + assert_equal 'foo/posts#index', @response.body + + get '/foo/posts/1' + assert_equal '/foo/posts/1', bar_post_path('1') + assert_equal 'foo/posts#show', @response.body + + get '/foo/posts/1/comments' + assert_equal '/foo/posts/1/comments', bar_post_comments_path('1') + assert_equal 'foo/comments#index', @response.body + + get '/foo/comments/2' + assert_equal '/foo/comments/2', bar_comment_path('2') + assert_equal 'foo/comments#show', @response.body + end + + def test_namespaced_shallow_routes_with_shallow_path_option + draw do + namespace :foo, shallow_path: 'bar' do + resources :posts, only: [:index, :show] do + resources :comments, only: [:index, :show], shallow: true + end + end + end + + get '/foo/posts' + assert_equal '/foo/posts', foo_posts_path + assert_equal 'foo/posts#index', @response.body + + get '/foo/posts/1' + assert_equal '/foo/posts/1', foo_post_path('1') + assert_equal 'foo/posts#show', @response.body + + get '/foo/posts/1/comments' + assert_equal '/foo/posts/1/comments', foo_post_comments_path('1') + assert_equal 'foo/comments#index', @response.body + + get '/bar/comments/2' + assert_equal '/bar/comments/2', foo_comment_path('2') + assert_equal 'foo/comments#show', @response.body + end + + def test_namespaced_shallow_routes_with_shallow_prefix_option + draw do + namespace :foo, shallow_prefix: 'bar' do + resources :posts, only: [:index, :show] do + resources :comments, only: [:index, :show], shallow: true + end + end + end + + get '/foo/posts' + assert_equal '/foo/posts', foo_posts_path + assert_equal 'foo/posts#index', @response.body + + get '/foo/posts/1' + assert_equal '/foo/posts/1', foo_post_path('1') + assert_equal 'foo/posts#show', @response.body + + get '/foo/posts/1/comments' + assert_equal '/foo/posts/1/comments', foo_post_comments_path('1') + assert_equal 'foo/comments#index', @response.body + + get '/foo/comments/2' + assert_equal '/foo/comments/2', bar_comment_path('2') + assert_equal 'foo/comments#show', @response.body + end + def test_namespace_containing_numbers draw do namespace :v2 do @@ -3033,6 +3163,114 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/admin/posts/1/comments', admin_post_comments_path('1') end + def test_shallow_path_and_prefix_are_not_added_to_non_shallow_routes + draw do + scope shallow_path: 'projects', shallow_prefix: 'project' do + resources :projects do + resources :files, controller: 'project_files', shallow: true + end + end + end + + get '/projects' + assert_equal 'projects#index', @response.body + assert_equal '/projects', projects_path + + get '/projects/new' + assert_equal 'projects#new', @response.body + assert_equal '/projects/new', new_project_path + + post '/projects' + assert_equal 'projects#create', @response.body + + get '/projects/1' + assert_equal 'projects#show', @response.body + assert_equal '/projects/1', project_path('1') + + get '/projects/1/edit' + assert_equal 'projects#edit', @response.body + assert_equal '/projects/1/edit', edit_project_path('1') + + patch '/projects/1' + assert_equal 'projects#update', @response.body + + delete '/projects/1' + assert_equal 'projects#destroy', @response.body + + get '/projects/1/files' + assert_equal 'project_files#index', @response.body + assert_equal '/projects/1/files', project_files_path('1') + + get '/projects/1/files/new' + assert_equal 'project_files#new', @response.body + assert_equal '/projects/1/files/new', new_project_file_path('1') + + post '/projects/1/files' + assert_equal 'project_files#create', @response.body + + get '/projects/files/2' + assert_equal 'project_files#show', @response.body + assert_equal '/projects/files/2', project_file_path('2') + + get '/projects/files/2/edit' + assert_equal 'project_files#edit', @response.body + assert_equal '/projects/files/2/edit', edit_project_file_path('2') + + patch '/projects/files/2' + assert_equal 'project_files#update', @response.body + + delete '/projects/files/2' + assert_equal 'project_files#destroy', @response.body + end + + def test_scope_path_is_copied_to_shallow_path + draw do + scope path: 'foo' do + resources :posts do + resources :comments, shallow: true + end + end + end + + assert_equal '/foo/comments/1', comment_path('1') + end + + def test_scope_as_is_copied_to_shallow_prefix + draw do + scope as: 'foo' do + resources :posts do + resources :comments, shallow: true + end + end + end + + assert_equal '/comments/1', foo_comment_path('1') + end + + def test_scope_shallow_prefix_is_not_overwritten_by_as + draw do + scope as: 'foo', shallow_prefix: 'bar' do + resources :posts do + resources :comments, shallow: true + end + end + end + + assert_equal '/comments/1', bar_comment_path('1') + end + + def test_scope_shallow_path_is_not_overwritten_by_path + draw do + scope path: 'foo', shallow_path: 'bar' do + resources :posts do + resources :comments, shallow: true + end + end + end + + assert_equal '/bar/comments/1', comment_path('1') + end + private def draw(&block) diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc index 4c00755532..500be2a04a 100644 --- a/activemodel/README.rdoc +++ b/activemodel/README.rdoc @@ -49,7 +49,8 @@ behavior out of the box: send("#{attr}=", nil) end end - + + person = Person.new person.clear_name person.clear_age @@ -78,7 +79,21 @@ behavior out of the box: class Person include ActiveModel::Dirty - attr_accessor :name + define_attribute_methods :name + + def name + @name + end + + def name=(val) + name_will_change! unless val == @name + @name = val + end + + def save + # do persistence work + changes_applied + end end person = Person.new @@ -88,6 +103,7 @@ behavior out of the box: person.changed? # => true person.changed # => ['name'] person.changes # => { 'name' => [nil, 'bob'] } + person.save person.name = 'robert' person.save person.previous_changes # => {'name' => ['bob, 'robert']} @@ -116,7 +132,10 @@ behavior out of the box: "Name" end end - + + person = Person.new + person.name = nil + person.validate! person.errors.full_messages # => ["Name cannot be nil"] @@ -180,41 +199,41 @@ behavior out of the box: * Validation support - class Person - include ActiveModel::Validations + class Person + include ActiveModel::Validations - attr_accessor :first_name, :last_name + attr_accessor :first_name, :last_name - validates_each :first_name, :last_name do |record, attr, value| - record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z - end - end + validates_each :first_name, :last_name do |record, attr, value| + record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z + end + end - person = Person.new - person.first_name = 'zoolander' - person.valid? # => false + person = Person.new + person.first_name = 'zoolander' + person.valid? # => false {Learn more}[link:classes/ActiveModel/Validations.html] * Custom validators + + class HasNameValidator < ActiveModel::Validator + def validate(record) + record.errors[:name] = "must exist" if record.name.blank? + end + end + + class ValidatorPerson + include ActiveModel::Validations + validates_with HasNameValidator + attr_accessor :name + end - class ValidatorPerson - include ActiveModel::Validations - validates_with HasNameValidator - attr_accessor :name - end - - class HasNameValidator < ActiveModel::Validator - def validate(record) - record.errors[:name] = "must exist" if record.name.blank? - end - end - - p = ValidatorPerson.new - p.valid? # => false - p.errors.full_messages # => ["Name must exist"] - p.name = "Bob" - p.valid? # => true + p = ValidatorPerson.new + p.valid? # => false + p.errors.full_messages # => ["Name must exist"] + p.name = "Bob" + p.valid? # => true {Learn more}[link:classes/ActiveModel/Validator.html] diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index def28578f8..42d0365521 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -82,6 +82,13 @@ class ErrorsTest < ActiveModel::TestCase assert_equal({ foo: "omg" }, errors.messages) end + test "error access is indifferent" do + errors = ActiveModel::Errors.new(self) + errors[:foo] = "omg" + + assert_equal ["omg"], errors["foo"] + end + test "values returns an array of messages" do errors = ActiveModel::Errors.new(self) errors.set(:foo, "omg") diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 53f7591226..f725356cd9 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -530,8 +530,8 @@ module ActiveRecord # end # # @firm = Firm.first - # @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm - # @firm.invoices # selects all invoices by going through the Client join model + # @firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm + # @firm.invoices # selects all invoices by going through the Client join model # # Similarly you can go through a +has_one+ association on the join model: # diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index 2a8530af62..70e97432e4 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -23,7 +23,7 @@ module ActiveRecord reset_association owners, through_reflection.name - middle_records = through_records.map { |(_,rec)| rec }.flatten + middle_records = through_records.flat_map { |(_,rec)| rec } preloaders = preloader.preload(middle_records, source_reflection.name, diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 23edc8b955..baac9522b6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -459,7 +459,7 @@ module ActiveRecord end def bulk_change_table(table_name, operations) #:nodoc: - sqls = operations.map do |command, args| + sqls = operations.flat_map do |command, args| table, arguments = args.shift, args method = :"#{command}_sql" @@ -468,7 +468,7 @@ module ActiveRecord else raise "Unknown method called : #{method}(#{arguments.inspect})" end - end.flatten.join(", ") + end.join(", ") execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}") end diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb index e555c52281..cf71bc1597 100644 --- a/activerecord/test/cases/associations/callbacks_test.rb +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -159,7 +159,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase activerecord.reload assert activerecord.developers_with_callbacks.size == 2 end - log_array = activerecord.developers_with_callbacks.collect {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.flatten.sort + log_array = activerecord.developers_with_callbacks.flat_map {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.sort assert activerecord.developers_with_callbacks.clear assert_equal log_array, activerecord.developers_log.sort end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index d2f97df0fc..9651244372 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -76,9 +76,9 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase end def callbacks_for_model(model) - model.instance_variables.grep(/_callbacks$/).map do |ivar| + model.instance_variables.grep(/_callbacks$/).flat_map do |ivar| model.instance_variable_get(ivar) - end.flatten + end end end diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index 58a2ce2105..4680d5acb7 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -28,6 +28,10 @@ module ActiveSupport coder.represent_seq '!omap', map { |k,v| { k => v } } end + def select(*args, &block) + dup.tap { |hash| hash.select!(*args, &block) } + end + def reject(*args, &block) dup.tap { |hash| hash.reject!(*args, &block) } end diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb index 0b54026c64..460a61613e 100644 --- a/activesupport/test/ordered_hash_test.rb +++ b/activesupport/test/ordered_hash_test.rb @@ -120,7 +120,9 @@ class OrderedHashTest < ActiveSupport::TestCase end def test_select - assert_equal @keys, @ordered_hash.select { true }.map(&:first) + new_ordered_hash = @ordered_hash.select { true } + assert_equal @keys, new_ordered_hash.map(&:first) + assert_instance_of ActiveSupport::OrderedHash, new_ordered_hash end def test_delete_if @@ -143,6 +145,7 @@ class OrderedHashTest < ActiveSupport::TestCase assert_equal copy, @ordered_hash assert !new_ordered_hash.keys.include?('pink') assert @ordered_hash.keys.include?('pink') + assert_instance_of ActiveSupport::OrderedHash, new_ordered_hash end def test_clear diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index 6a355a5177..74f95bfcfd 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -1550,7 +1550,7 @@ end Sanitizes a block of CSS code. -#### strip_links(html) +#### strip_links(html) Strips all link tags from text leaving just the link text. ```ruby @@ -1568,9 +1568,9 @@ strip_links('Blog: <a href="http://myblog.com/">Visit</a>.') # => Blog: Visit. ``` -#### strip_tags(html) +#### strip_tags(html) -Strips all HTML tags from the html, including comments. +Strips all HTML tags from the html, including comments. This uses the html-scanner tokenizer and so its HTML parsing ability is limited by that of html-scanner. ```ruby @@ -1585,6 +1585,17 @@ strip_tags("<b>Bold</b> no more! <a href='more.html'>See more</a>") NB: The output may still contain unescaped '<', '>', '&' characters and confuse browsers. +### CsrfHelper + +Returns meta tags "csrf-param" and "csrf-token" with the name of the cross-site +request forgery protection parameter and token, respectively. + +```html +<%= csrf_meta_tags %> +``` + +NOTE: Regular forms generate hidden fields so they do not use these tags. More +details can be found in the [Rails Security Guide](security.html#cross-site-request-forgery-csrf). Localized Views --------------- diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index a8695ec034..aba3c9ed61 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -111,7 +111,9 @@ paintIt = (element, backgroundColor, textColor) -> element.style.color = textColor $ -> - $("a[data-background-color]").click -> + $("a[data-background-color]").click (e) -> + e.preventDefault() + backgroundColor = $(this).data("background-color") textColor = $(this).data("text-color") paintIt(this, backgroundColor, textColor) diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 5096f5324a..95f2c25a15 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,9 @@ +* Make the `rails:template` rake task load the application's initializers. + + Fixes #12133. + + *Robin Dupret* + * Introduce `Rails.gem_version` as a convenience method to return `Gem::Version.new(Rails.version)`, suggesting a more reliable way to perform version comparison. diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb index f7a0b99005..95bbdd4cdf 100644 --- a/railties/lib/rails/commands/plugin.rb +++ b/railties/lib/rails/commands/plugin.rb @@ -11,7 +11,7 @@ else end if File.exist?(railsrc) extra_args_string = File.read(railsrc) - extra_args = extra_args_string.split(/\n+/).map {|l| l.split}.flatten + extra_args = extra_args_string.split(/\n+/).flat_map {|l| l.split} puts "Using #{extra_args.join(" ")} from #{railsrc}" ARGV.insert(1, *extra_args) end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index afdbf5c241..625f031c94 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -188,7 +188,7 @@ module Rails # generate(:authenticated, "user session") def generate(what, *args) log :generate, what - argument = args.map {|arg| arg.to_s }.flatten.join(" ") + argument = args.flat_map {|arg| arg.to_s }.join(" ") in_root { run_ruby_script("bin/rails generate #{what} #{argument}", verbose: false) } end diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index 117bb37487..3eb66c07af 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -101,7 +101,7 @@ module Rails def filter_by(&block) all_paths.find_all(&block).flat_map { |path| paths = path.existent - paths - path.children.map { |p| yield(p) ? [] : p.existent }.flatten + paths - path.children.flat_map { |p| yield(p) ? [] : p.existent } }.uniq end end diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb index 3cf6a005ea..83e28090f8 100644 --- a/railties/lib/rails/source_annotation_extractor.rb +++ b/railties/lib/rails/source_annotation_extractor.rb @@ -115,7 +115,7 @@ class SourceAnnotationExtractor # Prints the mapping from filenames to annotations in +results+ ordered by filename. # The +options+ hash is passed to each annotation's +to_s+. def display(results, options={}) - options[:indent] = results.map { |f, a| a.map(&:line) }.flatten.max.to_s.size + options[:indent] = results.flat_map { |f, a| a.map(&:line) }.max.to_s.size results.keys.sort.each do |file| puts "#{file}:" results[file].each do |note| diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index e669315934..3c8f8c6b87 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -3,7 +3,7 @@ namespace :rails do task update: [ "update:configs", "update:bin" ] desc "Applies the template supplied by LOCATION=(/path/to/template) or URL" - task :template do + task template: :environment do template = ENV["LOCATION"] raise "No LOCATION value given. Please set LOCATION either as path to a file or a URL" if template.blank? template = File.expand_path(template) if template !~ %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 317e73245c..e8c8de9f73 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -271,5 +271,16 @@ module ApplicationTests end end end + + def test_template_load_initializers + app_file "config/initializers/dummy.rb", "puts 'Hello, World!'" + app_file "template.rb", "" + + output = Dir.chdir(app_path) do + `bundle exec rake rails:template LOCATION=template.rb` + end + + assert_match(/Hello, World!/, output) + end end end |