diff options
20 files changed, 166 insertions, 33 deletions
@@ -13,7 +13,7 @@ gem "rack-test", :git => "git://github.com/brynary/rack-test.git" gem "sprockets", :git => "git://github.com/sstephenson/sprockets.git" gem "coffee-script" -gem "sass", ">= 3.0" +gem "sass" gem "uglifier" gem "rake", ">= 0.8.7" diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index a59069cc37..447e25ca8a 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -18,5 +18,5 @@ Gem::Specification.new do |s| s.requirements << 'none' s.add_dependency('actionpack', version) - s.add_dependency('mail', '~> 2.2.16') + s.add_dependency('mail', '~> 2.3.0') end diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb index ce664bf301..0b076e1ff9 100644 --- a/actionmailer/test/abstract_unit.rb +++ b/actionmailer/test/abstract_unit.rb @@ -23,11 +23,6 @@ if "ruby".encoding_aware? end end -silence_warnings do - # These external dependencies have warnings :/ - require 'mail' -end - lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") $:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) @@ -35,6 +30,11 @@ require 'test/unit' require 'action_mailer' require 'action_mailer/test_case' +silence_warnings do + # These external dependencies have warnings :/ + require 'mail' +end + # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index d3c66800d9..f771737779 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.add_dependency('activemodel', version) s.add_dependency('rack-cache', '~> 1.0.0') s.add_dependency('builder', '~> 3.0.0') - s.add_dependency('i18n', '~> 0.5.0') + s.add_dependency('i18n', '~> 0.6.0beta1') s.add_dependency('rack', '~> 1.2.1') s.add_dependency('rack-test', '~> 0.5.7') s.add_dependency('rack-mount', '~> 0.7.1') diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index f7cb1f5b58..9bc847a1ab 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -57,7 +57,7 @@ module ActionView # +asset_host+ to a proc like this: # # ActionController::Base.asset_host = Proc.new { |source| - # "http://assets#{source.hash % 2 + 1}.example.com" + # "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com" # } # image_tag("rails.png") # # => <img alt="Rails" src="http://assets1.example.com/images/rails.png?1230601161" /> diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index 59e6ce878f..26ebae6546 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -5,7 +5,7 @@ module I18n class ExceptionHandler include Module.new { def call(exception, locale, key, options) - exception.is_a?(MissingTranslationData) ? super.html_safe : super + exception.is_a?(MissingTranslation) ? super.html_safe : super end } end @@ -17,8 +17,8 @@ module ActionView module TranslationHelper # Delegates to I18n#translate but also performs three additional functions. # - # First, it'll pass the :rescue_format => :html option to I18n so that any caught - # MissingTranslationData exceptions will be turned into inline spans that + # First, it'll pass the :rescue_format => :html option to I18n so that any + # thrown MissingTranslation messages will be turned into inline spans that # # * have a "translation-missing" class set, # * contain the missing key as a title attribute and diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index c8d50ebf75..23a7e17e65 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -32,6 +32,7 @@ class NumberHelperTest < ActionView::TestCase assert_equal("555-1234", number_to_phone(5551234)) assert_equal("800-555-1212", number_to_phone(8005551212)) assert_equal("(800) 555-1212", number_to_phone(8005551212, {:area_code => true})) + assert_equal("", number_to_phone("", {:area_code => true})) assert_equal("800 555 1212", number_to_phone(8005551212, {:delimiter => " "})) assert_equal("(800) 555-1212 x 123", number_to_phone(8005551212, {:area_code => true, :extension => 123})) assert_equal("800-555-1212", number_to_phone(8005551212, :extension => " ")) diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec index 9f80673bb8..ce69c4a201 100644 --- a/activemodel/activemodel.gemspec +++ b/activemodel/activemodel.gemspec @@ -19,6 +19,6 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', version) s.add_dependency('builder', '~> 3.0.0') - s.add_dependency('i18n', '~> 0.5.0') + s.add_dependency('i18n', '~> 0.6.0beta1') s.add_dependency('bcrypt-ruby', '~> 2.1.4') end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 4512e8c8ad..04c12f86b6 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1621,11 +1621,11 @@ end # Allows you to set all the attributes at once by passing in a hash with keys # matching the attribute names (which again matches the column names). # - # If +guard_protected_attributes+ is true (the default), then sensitive - # attributes can be protected from this form of mass-assignment by using - # the +attr_protected+ macro. Or you can alternatively specify which - # attributes *can* be accessed with the +attr_accessible+ macro. Then all the - # attributes not included in that won't be allowed to be mass-assigned. + # If any attributes are protected by either +attr_protected+ or + # +attr_accessible+ then only settable attributes will be assigned. + # + # The +guard_protected_attributes+ argument is now deprecated, use + # the +assign_attributes+ method if you want to bypass mass-assignment security. # # class User < ActiveRecord::Base # attr_protected :is_admin @@ -1635,11 +1635,16 @@ end # user.attributes = { :username => 'Phusion', :is_admin => true } # user.username # => "Phusion" # user.is_admin? # => false - # - # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false) - # user.is_admin? # => true - def attributes=(new_attributes, guard_protected_attributes = true) + def attributes=(new_attributes, guard_protected_attributes = nil) + unless guard_protected_attributes.nil? + message = "the use of 'guard_protected_attributes' will be removed from the next major release of rails, " + + "if you want to bypass mass-assignment security then look into using assign_attributes" + ActiveSupport::Deprecation.warn(message) + end + return unless new_attributes.is_a?(Hash) + + guard_protected_attributes ||= true if guard_protected_attributes assign_attributes(new_attributes) else diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e2b9a5d0d9..0c2afc180b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -98,6 +98,9 @@ module ActiveRecord # XML type when 'xml' :xml + # tsvector type + when 'tsvector' + :tsvector # Arrays when /^\D+\[\]$/ :string @@ -189,6 +192,11 @@ module ActiveRecord options = args.extract_options! column(args[0], 'xml', options) end + + def tsvector(*args) + options = args.extract_options! + column(args[0], 'tsvector', options) + end end ADAPTER_NAME = 'PostgreSQL' @@ -206,7 +214,8 @@ module ActiveRecord :date => { :name => "date" }, :binary => { :name => "bytea" }, :boolean => { :name => "boolean" }, - :xml => { :name => "xml" } + :xml => { :name => "xml" }, + :tsvector => { :name => "tsvector" } } # Returns 'PostgreSQL' as adapter name for identification purposes. diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index a916c88348..998d237ada 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -136,22 +136,27 @@ module ActiveRecord # Updates the attributes of the model from the passed-in hash and saves the # record, all wrapped in a transaction. If the object is invalid, the saving # will fail and false will be returned. - def update_attributes(attributes) + # + # When updating model attributes, mass-assignment security protection is respected. + # If no +:as+ option is supplied then the :default scope 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 = {}) # The following transaction covers any possible database side-effects of the # attributes assignment. For example, setting the IDs of a child collection. with_transaction_returning_status do - self.attributes = attributes + self.assign_attributes(attributes, options) save end end # Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead # of +save+, so an exception is raised if the record is invalid. - def update_attributes!(attributes) + def update_attributes!(attributes, options = {}) # The following transaction covers any possible database side-effects of the # attributes assignment. For example, setting the IDs of a child collection. with_transaction_returning_status do - self.attributes = attributes + self.assign_attributes(attributes, options) save! end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index a3d4b7f45a..57c9921ea8 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -279,8 +279,8 @@ module ActiveRecord unless record record = @klass.new do |r| - r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty? - r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty? + r.assign_attributes(protected_attributes_for_create) + r.assign_attributes(unprotected_attributes_for_create, :without_protection => true) end yield(record) if block_given? record.save if match.instantiator == :create diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index d1225a9ed9..4db4105389 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -56,8 +56,9 @@ module ActiveRecord column = klass.columns_hash[attribute.to_s] value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text? - if !options[:case_sensitive] && column.text? - relation = table[attribute].matches(value) + if !options[:case_sensitive] && value && column.text? + # will use SQL LOWER function before comparison + relation = table[attribute].lower.eq(table.lower(value)) else value = klass.connection.case_sensitive_modifier(value) relation = table[attribute].eq(value) diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 5bb8fa2f93..ce08e4c6a7 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -3,6 +3,9 @@ require "cases/helper" class PostgresqlArray < ActiveRecord::Base end +class PostgresqlTsvector < ActiveRecord::Base +end + class PostgresqlMoney < ActiveRecord::Base end @@ -34,6 +37,9 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase @connection.execute("INSERT INTO postgresql_arrays (commission_by_quarter, nicknames) VALUES ( '{35000,21000,18000,17000}', '{foo,bar,baz}' )") @first_array = PostgresqlArray.find(1) + @connection.execute("INSERT INTO postgresql_tsvectors (text_vector) VALUES (' ''text'' ''vector'' ')") + @first_tsvector = PostgresqlTsvector.find(1) + @connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('567.89'::money)") @connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('-567.89'::money)") @first_money = PostgresqlMoney.find(1) @@ -62,6 +68,10 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase assert_equal :string, @first_array.column_for_attribute(:nicknames).type end + def test_data_type_of_tsvector_types + assert_equal :tsvector, @first_tsvector.column_for_attribute(:text_vector).type + end + def test_data_type_of_money_types assert_equal :decimal, @first_money.column_for_attribute(:wealth).type end @@ -95,11 +105,26 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase assert_equal '{foo,bar,baz}', @first_array.nicknames end + def test_tsvector_values + assert_equal "'text' 'vector'", @first_tsvector.text_vector + end + def test_money_values assert_equal 567.89, @first_money.wealth assert_equal(-567.89, @second_money.wealth) end + def test_update_tsvector + new_text_vector = "'new' 'text' 'vector'" + assert @first_tsvector.text_vector = new_text_vector + assert @first_tsvector.save + assert @first_tsvector.reload + assert @first_tsvector.text_vector = new_text_vector + assert @first_tsvector.save + assert @first_tsvector.reload + assert_equal @first_tsvector.text_vector, new_text_vector + end + def test_number_values assert_equal 123.456, @first_number.single assert_equal 123456.789, @first_number.double diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index ef833857ce..5ee3b2d776 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -489,6 +489,12 @@ class BasicsTest < ActiveRecord::TestCase assert_equal 'value2', weird.send('a$b') end + def test_attributes_guard_protected_attributes_is_deprecated + attributes = { "title" => "An amazing title" } + topic = Topic.new + assert_deprecated { topic.send(:attributes=, attributes, false) } + end + def test_multiparameter_attributes_on_date attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" } topic = Topic.find(1) diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 3683e3430c..2044bc6e3f 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -491,6 +491,26 @@ class PersistencesTest < ActiveRecord::TestCase assert_equal "The First Topic", topic.title end + def test_update_attributes_as_admin + person = TightPerson.create + person.update_attributes({ "first_name" => 'Josh', "gender" => 'male', "comments" => 'from NZ' }, :as => :admin) + person.reload + + assert_equal 'Josh', person.first_name + assert_equal 'male', person.gender + assert_equal 'from NZ', person.comments + end + + def test_update_attributes_as_without_protection + person = TightPerson.create + person.update_attributes({ "first_name" => 'Josh', "gender" => 'male', "comments" => 'from NZ' }, :without_protection => true) + person.reload + + assert_equal 'Josh', person.first_name + assert_equal 'male', person.gender + assert_equal 'from NZ', person.comments + end + def test_update_attributes! Reply.validates_presence_of(:title) reply = Reply.find(2) @@ -512,6 +532,26 @@ class PersistencesTest < ActiveRecord::TestCase Reply.reset_callbacks(:validate) end + def test_update_attributes_as_admin + person = TightPerson.create + person.update_attributes!({ "first_name" => 'Josh', "gender" => 'male', "comments" => 'from NZ' }, :as => :admin) + person.reload + + assert_equal 'Josh', person.first_name + assert_equal 'male', person.gender + assert_equal 'from NZ', person.comments + end + + def test_update_attributes_as_without_protection + person = TightPerson.create + person.update_attributes!({ "first_name" => 'Josh', "gender" => 'male', "comments" => 'from NZ' }, :without_protection => true) + person.reload + + assert_equal 'Josh', person.first_name + assert_equal 'male', person.gender + assert_equal 'from NZ', person.comments + end + def test_destroyed_returns_boolean developer = Developer.first assert_equal false, developer.destroyed? diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 9b2c7c00df..e8f2f44189 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -203,6 +203,13 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{t.xml "data"}, output end end + + def test_schema_dump_includes_tsvector_shorthand_definition + output = standard_dump + if %r{create_table "postgresql_tsvectors"} =~ output + assert_match %r{t.tsvector "text_vector"}, output + end + end end def test_schema_dump_keeps_large_precision_integer_columns_as_decimal diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index b4f3dd034c..0f1b3667cc 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -162,6 +162,32 @@ class UniquenessValidationTest < ActiveRecord::TestCase end end + def test_validate_case_sensitive_uniqueness_with_special_sql_like_chars + Topic.validates_uniqueness_of(:title, :case_sensitive => true) + + t = Topic.new("title" => "I'm unique!") + assert t.save, "Should save t as unique" + + t2 = Topic.new("title" => "I'm %") + assert t2.save, "Should save t2 as unique" + + t3 = Topic.new("title" => "I'm uniqu_!") + assert t3.save, "Should save t3 as unique" + end + + def test_validate_case_insensitive_uniqueness_with_special_sql_like_chars + Topic.validates_uniqueness_of(:title, :case_sensitive => false) + + t = Topic.new("title" => "I'm unique!") + assert t.save, "Should save t as unique" + + t2 = Topic.new("title" => "I'm %") + assert t2.save, "Should save t2 as unique" + + t3 = Topic.new("title" => "I'm uniqu_!") + assert t3.save, "Should save t3 as unique" + end + def test_validate_case_sensitive_uniqueness Topic.validates_uniqueness_of(:title, :case_sensitive => true, :allow_nil => true) diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index f38f4f3b44..5cf9a207f3 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -1,6 +1,6 @@ ActiveRecord::Schema.define do - %w(postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings + %w(postgresql_tsvectors postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones).each do |table_name| execute "DROP TABLE IF EXISTS #{quote_table_name table_name}" end @@ -55,6 +55,14 @@ _SQL nicknames TEXT[] ); _SQL + + execute <<_SQL + CREATE TABLE postgresql_tsvectors ( + id SERIAL PRIMARY KEY, + text_vector tsvector + ); +_SQL + execute <<_SQL CREATE TABLE postgresql_moneys ( id SERIAL PRIMARY KEY, diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 968d6ff4d0..37a74a9e62 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -17,5 +17,5 @@ Gem::Specification.new do |s| s.files = Dir['CHANGELOG', 'README.rdoc', 'lib/**/*'] s.require_path = 'lib' - s.add_dependency('multi_json', '~> 1.0.0.rc3') + s.add_dependency('multi_json', '~> 1.0.0') end |