diff options
36 files changed, 281 insertions, 92 deletions
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 97e8ccc9a5..5097f6732d 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -224,6 +224,7 @@ module ActionDispatch self.valid_conditions.push(:controller, :action) @append = [] + @prepend = [] @disable_clear_and_finalize = false clear! end @@ -232,7 +233,6 @@ module ActionDispatch clear! unless @disable_clear_and_finalize eval_block(block) finalize! unless @disable_clear_and_finalize - nil end @@ -240,6 +240,10 @@ module ActionDispatch @append << block end + def prepend(&block) + @prepend << block + end + def eval_block(block) if block.arity == 1 raise "You are using the old router DSL which has been removed in Rails 3.1. " << @@ -262,8 +266,6 @@ module ActionDispatch end def clear! - # Clear the controller cache so we may discover new ones - @controller_constraints = nil @finalized = false routes.clear named_routes.clear @@ -271,6 +273,7 @@ module ActionDispatch :parameters_key => PARAMETERS_KEY, :request_class => request_class ) + @prepend.each { |blk| eval_block(blk) } end def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false) diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index e246646963..d4448a7b33 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -1,3 +1,4 @@ +require "active_support/core_ext/array/wrap" require "active_support/core_ext/enumerable" module ActionView @@ -29,6 +30,7 @@ module ActionView def initialize(paths, path, prefixes, partial, details, *) @path = path + prefixes = Array.wrap(prefixes) display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ") template_type = if partial "partial" diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb index 9c10decd60..0b4b0638b2 100644 --- a/actionpack/lib/sprockets/railtie.rb +++ b/actionpack/lib/sprockets/railtie.rb @@ -39,7 +39,7 @@ module Sprockets end end - app.routes.append do + app.routes.prepend do mount app.assets => assets.prefix end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index d128006404..25b1b4f745 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -358,6 +358,13 @@ class RequestTest < ActiveSupport::TestCase assert request.head? end + test "post masquerading as put" do + request = stub_request 'REQUEST_METHOD' => 'PUT', "rack.methodoverride.original_method" => "POST" + assert_equal "POST", request.method + assert_equal "PUT", request.request_method + assert request.put? + end + test "xml format" do request = stub_request request.expects(:parameters).at_least_once.returns({ :format => 'xml' }) diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb index f34a40795a..47b70f05ab 100644 --- a/actionpack/test/template/lookup_context_test.rb +++ b/actionpack/test/template/lookup_context_test.rb @@ -256,4 +256,13 @@ class TestMissingTemplate < ActiveSupport::TestCase end assert_match %r{Missing partial parent/foo, child/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message end + + test "if a single prefix is passed as a string and the lookup fails, MissingTemplate accepts it" do + e = assert_raise ActionView::MissingTemplate do + details = {:handlers=>[], :formats=>[], :locale=>[]} + @lookup_context.view_paths.find("foo", "parent", true, details) + end + assert_match %r{Missing partial parent/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message + end + end diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index 01eef762fd..483b577681 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -35,17 +35,17 @@ module ActiveModel # protected # # def account_params - # scope = admin ? :admin : :default - # sanitize_for_mass_assignment(params[:account], scope) + # role = admin ? :admin : :default + # sanitize_for_mass_assignment(params[:account], role) # end # # end # module ClassMethods # Attributes named in this macro are protected from mass-assignment - # whenever attributes are sanitized before assignment. A scope for the - # attributes is optional, if no scope is provided then :default is used. - # A scope can be defined by using the :as option. + # whenever attributes are sanitized before assignment. A role for the + # attributes is optional, if no role is provided then :default is used. + # A role can be defined by using the :as option. # # Mass-assignment to these attributes will simply be ignored, to assign # to them you can use direct writer methods. This is meant to protect @@ -67,7 +67,7 @@ module ActiveModel # end # end # - # When using a :default scope : + # When using the :default role : # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default) @@ -78,7 +78,7 @@ module ActiveModel # customer.credit_rating = "Average" # customer.credit_rating # => "Average" # - # And using the :admin scope : + # And using the :admin role : # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin) @@ -93,10 +93,10 @@ module ActiveModel # to sanitize attributes won't provide sufficient protection. def attr_protected(*args) options = args.extract_options! - scope = options[:as] || :default + role = options[:as] || :default self._protected_attributes = protected_attributes_configs.dup - self._protected_attributes[scope] = self.protected_attributes(scope) + args + self._protected_attributes[role] = self.protected_attributes(role) + args self._active_authorizer = self._protected_attributes end @@ -104,8 +104,8 @@ module ActiveModel # Specifies a white list of model attributes that can be set via # mass-assignment. # - # Like +attr_protected+, a scope for the attributes is optional, - # if no scope is provided then :default is used. A scope can be defined by + # Like +attr_protected+, a role for the attributes is optional, + # if no role is provided then :default is used. A role can be defined by # using the :as option. # # This is the opposite of the +attr_protected+ macro: Mass-assignment @@ -131,7 +131,7 @@ module ActiveModel # end # end # - # When using a :default scope : + # When using the :default role : # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default) @@ -141,7 +141,7 @@ module ActiveModel # customer.credit_rating = "Average" # customer.credit_rating # => "Average" # - # And using the :admin scope : + # And using the :admin role : # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin) @@ -152,20 +152,20 @@ module ActiveModel # to sanitize attributes won't provide sufficient protection. def attr_accessible(*args) options = args.extract_options! - scope = options[:as] || :default + role = options[:as] || :default self._accessible_attributes = accessible_attributes_configs.dup - self._accessible_attributes[scope] = self.accessible_attributes(scope) + args + self._accessible_attributes[role] = self.accessible_attributes(role) + args self._active_authorizer = self._accessible_attributes end - def protected_attributes(scope = :default) - protected_attributes_configs[scope] + def protected_attributes(role = :default) + protected_attributes_configs[role] end - def accessible_attributes(scope = :default) - accessible_attributes_configs[scope] + def accessible_attributes(role = :default) + accessible_attributes_configs[role] end def active_authorizers @@ -198,12 +198,12 @@ module ActiveModel protected - def sanitize_for_mass_assignment(attributes, scope = :default) - mass_assignment_authorizer(scope).sanitize(attributes) + def sanitize_for_mass_assignment(attributes, role = :default) + mass_assignment_authorizer(role).sanitize(attributes) end - def mass_assignment_authorizer(scope = :default) - self.class.active_authorizer[scope] + def mass_assignment_authorizer(role = :default) + self.class.active_authorizer[role] end end end diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb index b22ce874ea..43a12eed61 100644 --- a/activemodel/test/cases/mass_assignment_security_test.rb +++ b/activemodel/test/cases/mass_assignment_security_test.rb @@ -10,7 +10,7 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase assert_equal expected, sanitized end - def test_only_moderator_scope_attribute_accessible + def test_only_moderator_role_attribute_accessible user = SpecialUser.new expected = { "name" => "John Smith", "email" => "john@smith.com" } sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), :moderator) @@ -27,7 +27,7 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase assert_equal expected, sanitized end - def test_admin_scoped_attributes_accessible + def test_attributes_accessible_with_admin_role user = Person.new expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true } sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 78318b1be0..67af21c9a0 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -482,7 +482,7 @@ module ActiveRecord #:nodoc: # # Create a single new object # User.create(:first_name => 'Jamie') # - # # Create a single new object using the :admin mass-assignment security scope + # # Create a single new object using the :admin mass-assignment security role # User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin) # # # Create a single new object bypassing mass-assignment security @@ -1486,7 +1486,7 @@ MSG # # Instantiates a single new object # User.new(:first_name => 'Jamie') # - # # Instantiates a single new object using the :admin mass-assignment security scope + # # Instantiates a single new object using the :admin mass-assignment security role # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin) # # # Instantiates a single new object bypassing mass-assignment security @@ -1661,8 +1661,8 @@ MSG end # Allows you to set all the attributes for a particular mass-assignment - # security scope by passing in a hash of attributes with keys matching - # the attribute names (which again matches the column names) and the scope + # security role by passing in a hash of attributes with keys matching + # the attribute names (which again matches the column names) and the role # name using the :as option. # # To bypass mass-assignment security you can use the :without_protection => true @@ -1689,12 +1689,12 @@ MSG # user.is_admin? # => true def assign_attributes(new_attributes, options = {}) attributes = new_attributes.stringify_keys - scope = options[:as] || :default + role = options[:as] || :default multi_parameter_attributes = [] unless options[:without_protection] - attributes = sanitize_for_mass_assignment(attributes, scope) + attributes = sanitize_for_mass_assignment(attributes, role) end attributes.each do |k, v| diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 9a31675782..cdedcde0eb 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -94,7 +94,7 @@ module ActiveRecord relation = self.class.unscoped stmt = relation.where( - relation.table[self.class.primary_key].eq(quoted_id).and( + relation.table[self.class.primary_key].eq(id).and( relation.table[lock_col].eq(quote_value(previous_lock_value)) ) ).arel.compile_update(arel_attributes_values(false, false, attribute_names)) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index b4531ed35f..b9041f44d8 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -146,7 +146,7 @@ module ActiveRecord # will fail and false will be returned. # # When updating model attributes, mass-assignment security protection is respected. - # If no +:as+ option is supplied then the +:default+ scope will be used. + # If no +:as+ option is supplied then the +:default+ role will be used. # If you want to bypass the protection given by +attr_protected+ and # +attr_accessible+ then you can do so using the +:without_protection+ option. def update_attributes(attributes, options = {}) diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 869eebfa34..a8a52867ce 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -197,7 +197,7 @@ module ActiveRecord def execute_simple_calculation(operation, column_name, distinct) #:nodoc: # Postgresql doesn't like ORDER BY when there are no GROUP BY - relation = except(:order) + relation = reorder(nil) if operation == "count" && (relation.limit_value || relation.offset_value) # Shortcut when limit is zero. diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 636a709924..61baa55027 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -5,6 +5,7 @@ require 'models/job' require 'models/reader' require 'models/legacy_thing' require 'models/reference' +require 'models/string_key_object' class LockWithoutDefault < ActiveRecord::Base; end @@ -18,7 +19,40 @@ class ReadonlyFirstNamePerson < Person end class OptimisticLockingTest < ActiveRecord::TestCase - fixtures :people, :legacy_things, :references + fixtures :people, :legacy_things, :references, :string_key_objects + + def test_non_integer_lock_existing + s1 = StringKeyObject.find("record1") + s2 = StringKeyObject.find("record1") + assert_equal 0, s1.lock_version + assert_equal 0, s2.lock_version + + s1.name = 'updated record' + s1.save! + assert_equal 1, s1.lock_version + assert_equal 0, s2.lock_version + + s2.name = 'doubly updated record' + assert_raise(ActiveRecord::StaleObjectError) { s2.save! } + end + + def test_non_integer_lock_destroy + s1 = StringKeyObject.find("record1") + s2 = StringKeyObject.find("record1") + assert_equal 0, s1.lock_version + assert_equal 0, s2.lock_version + + s1.name = 'updated record' + s1.save! + assert_equal 1, s1.lock_version + assert_equal 0, s2.lock_version + assert_raise(ActiveRecord::StaleObjectError) { s2.destroy } + + assert s1.destroy + assert s1.frozen? + assert s1.destroyed? + assert_raises(ActiveRecord::RecordNotFound) { StringKeyObject.find("record1") } + end def test_lock_existing p1 = Person.find(1) diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index fbbae99e8b..c81015b7c2 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -87,7 +87,7 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase end end - def test_assign_attributes_uses_default_scope_when_no_scope_is_provided + def test_assign_attributes_uses_default_role_when_no_role_is_provided p = LoosePerson.new p.assign_attributes(attributes_hash) @@ -101,28 +101,28 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase assert_all_attributes(p) end - def test_assign_attributes_with_default_scope_and_attr_protected_attributes + def test_assign_attributes_with_default_role_and_attr_protected_attributes p = LoosePerson.new p.assign_attributes(attributes_hash, :as => :default) assert_default_attributes(p) end - def test_assign_attributes_with_admin_scope_and_attr_protected_attributes + def test_assign_attributes_with_admin_role_and_attr_protected_attributes p = LoosePerson.new p.assign_attributes(attributes_hash, :as => :admin) assert_admin_attributes(p) end - def test_assign_attributes_with_default_scope_and_attr_accessible_attributes + def test_assign_attributes_with_default_role_and_attr_accessible_attributes p = TightPerson.new p.assign_attributes(attributes_hash, :as => :default) assert_default_attributes(p) end - def test_assign_attributes_with_admin_scope_and_attr_accessible_attributes + def test_assign_attributes_with_admin_role_and_attr_accessible_attributes p = TightPerson.new p.assign_attributes(attributes_hash, :as => :admin) @@ -153,25 +153,25 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase assert_default_attributes(p, true) end - def test_new_with_admin_scope_with_attr_accessible_attributes + def test_new_with_admin_role_with_attr_accessible_attributes p = TightPerson.new(attributes_hash, :as => :admin) assert_admin_attributes(p) end - def test_new_with_admin_scope_with_attr_protected_attributes + def test_new_with_admin_role_with_attr_protected_attributes p = LoosePerson.new(attributes_hash, :as => :admin) assert_admin_attributes(p) end - def test_create_with_admin_scope_with_attr_accessible_attributes + def test_create_with_admin_role_with_attr_accessible_attributes p = TightPerson.create(attributes_hash, :as => :admin) assert_admin_attributes(p, true) end - def test_create_with_admin_scope_with_attr_protected_attributes + def test_create_with_admin_role_with_attr_protected_attributes p = LoosePerson.create(attributes_hash, :as => :admin) assert_admin_attributes(p, true) @@ -230,12 +230,12 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend) end - def test_has_one_build_with_admin_scope_with_attr_protected_attributes + def test_has_one_build_with_admin_role_with_attr_protected_attributes best_friend = @person.build_best_friend(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end - def test_has_one_build_with_admin_scope_with_attr_accessible_attributes + def test_has_one_build_with_admin_role_with_attr_accessible_attributes best_friend = @person.build_best_friend(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end @@ -257,12 +257,12 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend, true) end - def test_has_one_create_with_admin_scope_with_attr_protected_attributes + def test_has_one_create_with_admin_role_with_attr_protected_attributes best_friend = @person.create_best_friend(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_admin_scope_with_attr_accessible_attributes + def test_has_one_create_with_admin_role_with_attr_accessible_attributes best_friend = @person.create_best_friend(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end @@ -284,12 +284,12 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes + def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes + def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end @@ -318,12 +318,12 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend) end - def test_has_one_build_with_admin_scope_with_attr_protected_attributes + def test_has_one_build_with_admin_role_with_attr_protected_attributes best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end - def test_has_one_build_with_admin_scope_with_attr_accessible_attributes + def test_has_one_build_with_admin_role_with_attr_accessible_attributes best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end @@ -345,12 +345,12 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend, true) end - def test_has_one_create_with_admin_scope_with_attr_protected_attributes + def test_has_one_create_with_admin_role_with_attr_protected_attributes best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_admin_scope_with_attr_accessible_attributes + def test_has_one_create_with_admin_role_with_attr_accessible_attributes best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end @@ -372,12 +372,12 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes + def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes + def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end @@ -406,12 +406,12 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend) end - def test_has_one_build_with_admin_scope_with_attr_protected_attributes + def test_has_one_build_with_admin_role_with_attr_protected_attributes best_friend = @person.best_friends.build(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end - def test_has_one_build_with_admin_scope_with_attr_accessible_attributes + def test_has_one_build_with_admin_role_with_attr_accessible_attributes best_friend = @person.best_friends.build(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end @@ -433,12 +433,12 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend, true) end - def test_has_one_create_with_admin_scope_with_attr_protected_attributes + def test_has_one_create_with_admin_role_with_attr_protected_attributes best_friend = @person.best_friends.create(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_admin_scope_with_attr_accessible_attributes + def test_has_one_create_with_admin_role_with_attr_accessible_attributes best_friend = @person.best_friends.create(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end @@ -460,12 +460,12 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes + def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes best_friend = @person.best_friends.create!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes + def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes best_friend = @person.best_friends.create!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 864b3d4846..c215602567 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -462,4 +462,8 @@ class DefaultScopingTest < ActiveRecord::TestCase assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis)) assert_equal 10, DeveloperCalledJamis.unscoped.poor.length end + + def test_default_scope_order_ignored_by_aggregations + assert_equal DeveloperOrderedBySalary.all.count, DeveloperOrderedBySalary.count + end end diff --git a/activerecord/test/fixtures/string_key_objects.yml b/activerecord/test/fixtures/string_key_objects.yml new file mode 100644 index 0000000000..fa1299915b --- /dev/null +++ b/activerecord/test/fixtures/string_key_objects.yml @@ -0,0 +1,7 @@ +first: + id: record1 + name: first record + +second: + id: record2 + name: second record diff --git a/activerecord/test/models/string_key_object.rb b/activerecord/test/models/string_key_object.rb new file mode 100644 index 0000000000..f8d4c6e0e4 --- /dev/null +++ b/activerecord/test/models/string_key_object.rb @@ -0,0 +1,3 @@ +class StringKeyObject < ActiveRecord::Base + set_primary_key :id +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 9479242e4f..c20304c0e2 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -543,6 +543,12 @@ ActiveRecord::Schema.define do t.string :sponsorable_type end + create_table :string_key_objects, :id => false, :primary_key => :id, :force => true do |t| + t.string :id + t.string :name + t.integer :lock_version, :null => false, :default => 0 + end + create_table :students, :force => true do |t| t.string :name end diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 769ead9544..338104fd05 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -1,6 +1,7 @@ require 'date' require 'active_support/inflector/methods' require 'active_support/core_ext/date/zones' +require 'active_support/core_ext/module/remove_method' class Date DATE_FORMATS = { @@ -13,10 +14,10 @@ class Date } # Ruby 1.9 has Date#to_time which converts to localtime only. - remove_method :to_time if method_defined?(:to_time) + remove_possible_method :to_time # Ruby 1.9 has Date#xmlschema which converts to a string without the time component. - remove_method :xmlschema if method_defined?(:xmlschema) + remove_possible_method :xmlschema # Convert to a formatted string. See DATE_FORMATS for predefined formats. # diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb index aad4b61e16..c2a6476604 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -9,4 +9,16 @@ class Hash def with_indifferent_access ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self) end + + # Called when object is nested under an object that receives + # #with_indifferent_access. This method with be called on the current object + # by the enclosing object and is aliased to #with_indifferent_access by + # default. Subclasses of Hash may overwrite this method to return +self+ if + # converting to an +ActiveSupport::HashWithIndifferentAccess+ would not be + # desirable. + # + # b = {:b => 1} + # {:a => b}.with_indifferent_access["a"] # calls b.nested_under_indifferent_access + # + alias nested_under_indifferent_access with_indifferent_access end diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 79a0de7940..8ec4f6e09a 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -140,8 +140,8 @@ module ActiveSupport end def convert_value(value) - if value.class == Hash - self.class.new_from_hash_copying_default(value) + if value.is_a? Hash + value.nested_under_indifferent_access elsif value.is_a?(Array) value.dup.replace(value.map { |e| convert_value(e) }) else diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index dd2beef89d..a2c4f7bfda 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -135,6 +135,8 @@ module ActiveSupport # ordinalize(2) # => "2nd" # ordinalize(1002) # => "1002nd" # ordinalize(1003) # => "1003rd" + # ordinalize(-11) # => "-11th" + # ordinalize(-1021) # => "-1021st" def ordinalize(number) if (11..13).include?(number.to_i.abs % 100) "#{number}th" @@ -148,4 +150,4 @@ module ActiveSupport end end end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 392e33edbc..3e54134c5c 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -1,5 +1,6 @@ require 'active_support/log_subscriber' require 'active_support/buffered_logger' +require 'active_support/notifications' module ActiveSupport class LogSubscriber diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index fbc40d1b69..762a64a881 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -43,6 +43,10 @@ module ActiveSupport end end + def nested_under_indifferent_access + self + end + # Hash is ordered in Ruby 1.9! if RUBY_VERSION < '1.9' diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index dff8a8f4c4..d8f40ab8b1 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -139,7 +139,10 @@ module ActiveSupport protected def _dasherize(key) - key.gsub(/(?!^[_]*)_(?![_]*$)/, '-') + left = key.strip.rpartition(/^_*/) + right = left.pop.partition(/_*$/) + right.first.tr!('_ ', '--') + left.concat(right).join end # TODO: Add support for other encodings diff --git a/activesupport/test/class_cache_test.rb b/activesupport/test/class_cache_test.rb index 8445af8d25..fc2d54515d 100644 --- a/activesupport/test/class_cache_test.rb +++ b/activesupport/test/class_cache_test.rb @@ -58,7 +58,7 @@ module ActiveSupport assert @cache.key?(ClassCacheTest.name) end - def test_new_rejects_strings + def test_new_rejects_strings_when_called_on_a_new_string assert_deprecated do @cache.new ClassCacheTest.name end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 3ef080e1cb..4557a10688 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -16,6 +16,12 @@ class HashExtTest < Test::Unit::TestCase class SubclassingHash < Hash end + class NonIndifferentHash < Hash + def nested_under_indifferent_access + self + end + end + def setup @strings = { 'a' => 1, 'b' => 2 } @symbols = { :a => 1, :b => 2 } @@ -109,9 +115,12 @@ class HashExtTest < Test::Unit::TestCase assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys! end - def test_hash_subclass - flash = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access - assert_kind_of SubclassingHash, flash["foo"] + def test_nested_under_indifferent_access + foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access + assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"] + + foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access + assert_kind_of NonIndifferentHash, foo["foo"] end def test_indifferent_assorted diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb index 50168fa78f..bf99a701a6 100644 --- a/activesupport/test/ordered_hash_test.rb +++ b/activesupport/test/ordered_hash_test.rb @@ -243,6 +243,11 @@ class OrderedHashTest < Test::Unit::TestCase assert_equal @other_ordered_hash.keys, @ordered_hash.keys end + def test_nested_under_indifferent_access + flash = {:a => ActiveSupport::OrderedHash[:b, 1, :c, 2]}.with_indifferent_access + assert_kind_of ActiveSupport::OrderedHash, flash[:a] + end + def test_each_after_yaml_serialization values = [] @deserialized_ordered_hash = YAML.load(YAML.dump(@ordered_hash)) diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb index bf6b0b283d..e2b90ae16e 100644 --- a/activesupport/test/xml_mini_test.rb +++ b/activesupport/test/xml_mini_test.rb @@ -48,7 +48,7 @@ module XmlMiniTest assert_equal "__id", ActiveSupport::XmlMini.rename_key("__id") end - def test_rename_key_does_not_dasherize_multiple_leading_underscores + def test_rename_key_does_not_dasherize_multiple_trailing_underscores assert_equal "id__", ActiveSupport::XmlMini.rename_key("id__") end end @@ -87,6 +87,16 @@ module XmlMiniTest @xml.to_tag(:b, "Howdy", @options) assert_xml "<b>Howdy</b>" end + + test "#to_tag should dasherize the space when passed a string with spaces as a key" do + @xml.to_tag("New York", 33, @options) + assert_xml "<New---York type=\"integer\">33</New---York>" + end + + test "#to_tag should dasherize the space when passed a symbol with spaces as a key" do + @xml.to_tag(:"New York", 33, @options) + assert_xml "<New---York type=\"integer\">33</New---York>" + end # TODO: test the remaining functions hidden in #to_tag. end end diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile index 8c408ec06b..e0ccc7a6e6 100644 --- a/railties/guides/source/security.textile +++ b/railties/guides/source/security.textile @@ -372,7 +372,7 @@ def signup end </ruby> -Mass-assignment saves you much work, because you don't have to set each value individually. Simply pass a hash to the new() method, or assign attributes=(attributes) a hash value, to set the model's attributes to the values in the hash. The problem is that it is often used in conjunction with the parameters (params) hash available in the controller, which may be manipulated by an attacker. He may do so by changing the URL like this: +Mass-assignment saves you much work, because you don't have to set each value individually. Simply pass a hash to the +new+ method, or +assign_attributes=+ a hash value, to set the model's attributes to the values in the hash. The problem is that it is often used in conjunction with the parameters (params) hash available in the controller, which may be manipulated by an attacker. He may do so by changing the URL like this: <pre> "name":http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1 @@ -386,7 +386,7 @@ params[:user] # => {:name => “ow3ned”, :admin => true} So if you create a new user using mass-assignment, it may be too easy to become an administrator. -Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the <tt>attributes=</tt> method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example: +Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the <tt>attributes=</tt> method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3+. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example: <ruby> class Person < ActiveRecord::Base @@ -410,7 +410,7 @@ To avoid this, Rails provides two class methods in your Active Record class to c attr_protected :admin </ruby> -+attr_protected+ also optionally takes a scope option using :as which allows you to define multiple mass-assignment groupings. If no scope is defined then attributes will be added to the default group. ++attr_protected+ also optionally takes a role option using :as which allows you to define multiple mass-assignment groupings. If no role is defined then attributes will be added to the :default role. <ruby> attr_protected :last_login, :as => :admin @@ -433,7 +433,7 @@ params[:user] # => {:name => "ow3ned", :admin => true} @user.admin # => true </ruby> -When assigning attributes in Active Record using +attributes=+, or +update_attributes+ the :default scope will be used. To assign attributes using different scopes you should use +assign_attributes+ which accepts an optional :as options parameter. If no :as option is provided then the :default scope will be used. You can also bypass mass-assignment security by using the +:without_protection+ option. Here is an example: +When assigning attributes in Active Record using +attributes=+ the :default role will be used. To assign attributes using different roles you should use +assign_attributes+ which accepts an optional :as options parameter. If no :as option is provided then the :default role will be used. You can also bypass mass-assignment security by using the +:without_protection+ option. Here is an example: <ruby> @user = User.new @@ -451,7 +451,7 @@ When assigning attributes in Active Record using +attributes=+, or +update_attri @user.is_admin # => true </ruby> -In a similar way, +new+, +create+ and <tt>create!</tt> methods respect mass-assignment security and accepts either +:as+ or +:without_protection+ options. For example: +In a similar way, +new+, +create+, <tt>create!</tt>, +update_attributes+, and +update_attributes!+ methods all respect mass-assignment security and accept either +:as+ or +:without_protection+ options. For example: <ruby> @user = User.new({ :name => 'Sebastian', :is_admin => true }, :as => :admin) diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index dd01bbab1d..1e4d25f18c 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -157,7 +157,8 @@ module Rails middleware.use ::Rack::Lock unless config.allow_concurrency middleware.use ::Rack::Runtime - middleware.use ::Rails::Rack::Logger + middleware.use ::Rack::MethodOverride + middleware.use ::Rails::Rack::Logger # must come after Rack::MethodOverride to properly log overridden methods middleware.use ::ActionDispatch::ShowExceptions, config.consider_all_requests_local middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header @@ -171,7 +172,6 @@ module Rails end middleware.use ::ActionDispatch::ParamsParser - middleware.use ::Rack::MethodOverride middleware.use ::ActionDispatch::Head middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ETag, "no-cache" @@ -199,4 +199,4 @@ module Rails require "rails/console/helpers" end end -end
\ No newline at end of file +end diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index 4182757346..4a082aedb8 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -6,7 +6,8 @@ aliases = { "g" => "generate", "c" => "console", "s" => "server", - "db" => "dbconsole" + "db" => "dbconsole", + "r" => "runner" } command = ARGV.shift diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb new file mode 100644 index 0000000000..b03dc3132b --- /dev/null +++ b/railties/test/application/assets_test.rb @@ -0,0 +1,27 @@ +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class RoutingTest < Test::Unit::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + boot_rails + end + + test "assets routes have higher priority" do + app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();" + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do + match '*path', :to => lambda { |env| [200, { "Content-Type" => "text/html" }, "Not an asset"] } + end + RUBY + + get "/assets/demo.js" + assert_match "alert()", last_response.body + end + end +end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 01e6c49d9c..fd6dc46271 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -23,7 +23,8 @@ module ApplicationTests "Rack::Lock", "ActiveSupport::Cache::Strategy::LocalCache", "Rack::Runtime", - "Rails::Rack::Logger", + "Rack::MethodOverride", + "Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods "ActionDispatch::ShowExceptions", "ActionDispatch::RemoteIp", "Rack::Sendfile", @@ -36,7 +37,6 @@ module ApplicationTests "ActionDispatch::Session::CookieStore", "ActionDispatch::Flash", "ActionDispatch::ParamsParser", - "Rack::MethodOverride", "ActionDispatch::Head", "Rack::ConditionalGet", "Rack::ETag", diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb new file mode 100644 index 0000000000..a29244357c --- /dev/null +++ b/railties/test/application/rack/logger_test.rb @@ -0,0 +1,40 @@ +require "isolation/abstract_unit" +require "active_support/log_subscriber/test_helper" +require "rack/test" + +module ApplicationTests + module RackTests + class LoggerTest < Test::Unit::TestCase + include ActiveSupport::LogSubscriber::TestHelper + include Rack::Test::Methods + + def setup + build_app + require "#{app_path}/config/environment" + super + end + + def logs + @logs ||= @logger.logged(:info) + end + + test "logger logs proper HTTP verb and path" do + get "/blah" + wait + assert_match /^Started GET "\/blah"/, logs[0] + end + + test "logger logs HTTP verb override" do + post "/", {:_method => 'put'} + wait + assert_match /^Started PUT "\/"/, logs[0] + end + + test "logger logs HEAD requests" do + post "/", {:_method => 'head'} + wait + assert_match /^Started HEAD "\/"/, logs[0] + end + end + end +end diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index 62589c998d..e3a7f8a63c 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -1,14 +1,14 @@ require 'isolation/abstract_unit' +require 'rack/test' module ApplicationTests class RoutingTest < Test::Unit::TestCase include ActiveSupport::Testing::Isolation + include Rack::Test::Methods def setup build_app boot_rails - require 'rack/test' - extend Rack::Test::Methods end test "rails/info/properties in development" do diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb index e975950b85..e5debf29ae 100644 --- a/railties/test/railties/shared_tests.rb +++ b/railties/test/railties/shared_tests.rb @@ -15,11 +15,10 @@ module RailtiesTest boot_rails require 'rack/test' - require 'coffee_script' extend Rack::Test::Methods get "/assets/engine.js" - assert_match "alert();", last_response.body + assert_match "alert()", last_response.body end def test_copying_migrations |