diff options
86 files changed, 771 insertions, 217 deletions
@@ -61,12 +61,13 @@ platforms :ruby do end platforms :jruby do - gem 'activerecord-jdbcsqlite3-adapter', '>= 1.2.7' - - group :db do - gem 'activerecord-jdbcmysql-adapter', '>= 1.2.7' - gem 'activerecord-jdbcpostgresql-adapter', '>= 1.2.7' - end + git 'git://github.com/jruby/activerecord-jdbc-adapter.git' do + gem 'activerecord-jdbcsqlite3-adapter' + group :db do + gem 'activerecord-jdbcmysql-adapter' + gem 'activerecord-jdbcpostgresql-adapter' + end + end end # gems that are necessary for ActiveRecord tests with Oracle database diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb index aedcd81e52..9a1a27c8ed 100644 --- a/actionmailer/lib/action_mailer/delivery_methods.rb +++ b/actionmailer/lib/action_mailer/delivery_methods.rb @@ -64,7 +64,7 @@ module ActionMailer raise "Delivery method cannot be nil" when Symbol if klass = delivery_methods[method] - mail.delivery_method(klass, (send(:"#{method}_settings") || {}).merge(options || {})) + mail.delivery_method(klass,(send(:"#{method}_settings") || {}).merge!(options || {})) else raise "Invalid delivery method #{method.inspect}" end diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb index 20412c7bb2..61a037ea18 100644 --- a/actionmailer/test/delivery_methods_test.rb +++ b/actionmailer/test/delivery_methods_test.rb @@ -152,9 +152,6 @@ class MailDeliveryTest < ActiveSupport::TestCase assert_equal "overridden", delivery_method_instance.settings[:user_name] assert_equal "somethingobtuse", delivery_method_instance.settings[:password] assert_equal delivery_method_instance.settings.merge(overridden_options), delivery_method_instance.settings - - # make sure that overriding delivery method options per mail instance doesn't affect the Base setting - assert_equal settings, ActionMailer::Base.smtp_settings end test "non registered delivery methods raises errors" do diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index d848095e01..4e090ed526 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,10 @@ +* Fix an issue where :if and :unless controller action procs were being run + before checking for the correct action in the :only and :unless options. + + Fixes #11799 + + *Nicholas Jakobsen* + * Fix an issue where `assert_dom_equal` and `assert_dom_not_equal` were ignoring the passed failure message argument. diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 21c6191691..d6c941832f 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -38,7 +38,7 @@ module AbstractController def _normalize_callback_option(options, from, to) # :nodoc: if from = options[from] from = Array(from).map {|o| "action_name == '#{o}'"}.join(" || ") - options[to] = Array(options[to]) << from + options[to] = Array(options[to]).unshift(from) end end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 1faecc850b..67d261db77 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -59,7 +59,7 @@ module ActionController # <input type="text" name="post[address]" value="hyacintvej"> # # A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>. - # If the address input had been named <tt>post[address][street]</tt>, the params would have included + # If the address input had been named "post[address][street]", the params would have included # <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting. # # == Sessions diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 943fc15026..0e5dc1fc6c 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -514,12 +514,11 @@ module ActionDispatch @recall = recall.dup @set = set - normalize_recall! normalize_options! normalize_controller_action_id! use_relative_controller! normalize_controller! - normalize_action! + handle_nil_action! end def controller @@ -538,11 +537,6 @@ module ActionDispatch end end - # Set 'index' as default action for recall - def normalize_recall! - @recall[:action] ||= 'index' - end - def normalize_options! # If an explicit :controller was given, always make :action explicit # too, so that action expiry works as expected for things like @@ -558,8 +552,8 @@ module ActionDispatch options[:controller] = options[:controller].to_s end - if options.key?(:action) - options[:action] = (options[:action] || 'index').to_s + if options[:action] + options[:action] = options[:action].to_s end end @@ -569,6 +563,8 @@ module ActionDispatch # :controller, :action or :id is not found, don't pull any # more keys from the recall. def normalize_controller_action_id! + @recall[:action] ||= 'index' if current_controller + use_recall_for(:controller) or return use_recall_for(:action) or return use_recall_for(:id) @@ -590,11 +586,13 @@ module ActionDispatch @options[:controller] = controller.sub(%r{^/}, '') if controller end - # Move 'index' action from options to recall - def normalize_action! - if @options[:action] == 'index' - @recall[:action] = @options.delete(:action) + # This handles the case of action: nil being explicitly passed. + # It is identical to action: "index" + def handle_nil_action! + if options.has_key?(:action) && options[:action].nil? + options[:action] = 'index' end + recall[:action] = options.delete(:action) if options[:action] == 'index' end # Generates a path from routes, returns [path, params]. diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 4c82625e8e..3b5d7ef446 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -208,6 +208,10 @@ class FilterTest < ActionController::TestCase before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.instance_variable_set(:"@ran_proc_filter1", true)}, :except => :show_without_filter) { |c| c.instance_variable_set(:"@ran_proc_filter2", true)} end + class OnlyConditionalOptionsFilter < ConditionalFilterController + before_filter :ensure_login, :only => :index, :if => Proc.new {|c| c.instance_variable_set(:"@ran_conditional_index_proc", true) } + end + class ConditionalOptionsFilter < ConditionalFilterController before_filter :ensure_login, :if => Proc.new { |c| true } before_filter :clean_up_tmp, :if => Proc.new { |c| false } @@ -649,6 +653,11 @@ class FilterTest < ActionController::TestCase assert !assigns["ran_class_filter"] end + def test_running_only_condition_and_conditional_options + test_process(OnlyConditionalOptionsFilter, "show") + assert_not assigns["ran_conditional_index_proc"] + end + def test_running_before_and_after_condition_filters test_process(BeforeAndAfterConditionController) assert_equal %w( ensure_login clean_up_tmp), assigns["ran_filter"] diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 2d89969fd3..a9c62899b5 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -646,7 +646,6 @@ class RespondWithControllerTest < ActionController::TestCase Mime::Type.register_alias('text/html', :iphone) Mime::Type.register_alias('text/html', :touch) Mime::Type.register('text/x-mobile', :mobile) - Customer.send(:undef_method, :to_json) if Customer.method_defined?(:to_json) end def teardown diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index 2b36a399bb..f4bdd3e1d4 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -92,7 +92,7 @@ module RenderStreaming io.rewind assert_match "(undefined method `invalid!' for nil:NilClass)", io.read ensure - ActionView::Base.logger = _old + ActionController::Base.logger = _old end end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 98b34a872b..fd835795c0 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -775,10 +775,6 @@ class RenderTest < ActionController::TestCase @request.host = "www.nextangle.com" end - def teardown - ActionView::Base.logger = nil - end - # :ported: def test_simple_show get :hello_world diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec index cdac074973..87cb568300 100644 --- a/actionview/actionview.gemspec +++ b/actionview/actionview.gemspec @@ -5,7 +5,7 @@ Gem::Specification.new do |s| s.name = 'actionview' s.version = version s.summary = 'Rendering framework putting the V in MVC (part of Rails).' - s.description = 'Simple, battle-tested conventions and helpers for building web pages.' + s.description = '' s.required_ruby_version = '>= 1.9.3' diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f53cb6ad51..904ed0e362 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,44 @@ +* Assign inet/cidr attribute with `nil` value for invalid address. + + Example: + + record = User.new + + record.logged_in_from_ip # is type of an inet or a cidr + + # Before: + record.logged_in_from_ip = 'bad ip address' # raise exception + + # After: + record.logged_in_from_ip = 'bad ip address' # do not raise exception + record.logged_in_from_ip # => nil + record.logged_in_from_ip_before_type_cast # => 'bad ip address' + + *Paul Nikitochkin* + +* `add_to_target` now accepts a second optional `skip_callbacks` argument + + If truthy, it will skip the :before_add and :after_add callbacks. + + *Ben Woosley* + +* Fix interactions between `:before_add` callbacks and nested attributes + assignment of `has_many` associations, when the association was not + yet loaded: + + - A `:before_add` callback was being called when a nested attributes + assignment assigned to an existing record. + + - Nested Attributes assignment did not affect the record in the + association target when a `:before_add` callback triggered the + loading of the association + + *Jörg Schray* + +* Allow enable_extension migration method to be revertible. + + *Eric Tipton* + * Type cast hstore values on write, so that the value is consistent with reading from the database. diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 8ce02afef8..228c500f0a 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -290,7 +290,7 @@ module ActiveRecord # Returns true if the collection is empty. # - # If the collection has been loaded + # If the collection has been loaded # it is equivalent to <tt>collection.size.zero?</tt>. If the # collection has not been loaded, it is equivalent to # <tt>collection.exists?</tt>. If the collection has not already been @@ -366,8 +366,8 @@ module ActiveRecord target end - def add_to_target(record) - callback(:before_add, record) + def add_to_target(record, skip_callbacks = false) + callback(:before_add, record) unless skip_callbacks yield(record) if block_given? if association_scope.distinct_value && index = @target.index(record) @@ -376,7 +376,7 @@ module ActiveRecord @target << record end - callback(:after_add, record) + callback(:after_add, record) unless skip_callbacks set_inverse_instance(record) record diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 97e1bc5379..32244b1755 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -39,8 +39,8 @@ module ActiveRecord # Returns an array of the values of the first column in a select: # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3] def select_values(arel, name = nil) - result = select_rows(to_sql(arel, []), name) - result.map { |v| v[0] } + select_rows(to_sql(arel, []), name) + .map { |v| v[0] } end # Returns an array of arrays containing the field values. diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index d18b9c991f..c8fa4ef9af 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -15,7 +15,6 @@ module ActiveRecord return "'#{quote_string(value)}'" unless column case column.type - when :binary then "'#{quote_string(column.string_to_binary(value))}'" when :integer then value.to_i.to_s when :float then value.to_f.to_s else diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 0be4b5cb19..063b19871a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -16,9 +16,6 @@ module ActiveRecord # +columns+ attribute of said TableDefinition object, in order to be used # for generating a number of table creation or table changing SQL statements. class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key) #:nodoc: - def string_to_binary(value) - value - end def primary_key? primary_key || type.to_sym == :primary_key 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 daa9d902fe..4b11ea795c 100755..100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -246,8 +246,8 @@ module ActiveRecord # QUOTING ================================================== def quote(value, column = nil) - if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary) - s = column.class.string_to_binary(value).unpack("H*")[0] + if value.kind_of?(String) && column && column.type == :binary + s = value.unpack("H*")[0] "x'#{s}'" elsif value.kind_of?(BigDecimal) value.to_s("F") diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index cc02b313e1..bccfa41ad1 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -119,17 +119,7 @@ module ActiveRecord type_cast(default) end - # Used to convert from Strings to BLOBs - def string_to_binary(value) - self.class.string_to_binary(value) - end - class << self - # Used to convert from Strings to BLOBs - def string_to_binary(value) - value - end - # Used to convert from BLOBs to Strings def binary_to_string(value) value diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 92796c996e..92796c996e 100755..100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 15b5452850..15b5452850 100755..100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index 56dc9ea813..ef7b976d4f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -100,7 +100,11 @@ module ActiveRecord if string.nil? nil elsif String === string - IPAddr.new(string) + begin + IPAddr.new(string) + rescue ArgumentError + nil + end else string end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index e1475416eb..df489a5b1f 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -226,8 +226,8 @@ module ActiveRecord # QUOTING ================================================== def quote(value, column = nil) - if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary) - s = column.class.string_to_binary(value).unpack("H*")[0] + if value.kind_of?(String) && column && column.type == :binary + s = value.unpack("H*")[0] "x'#{s}'" else super diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index 9782a48055..01c73be849 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -73,7 +73,7 @@ module ActiveRecord [:create_table, :create_join_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column_default, :add_reference, :remove_reference, :transaction, - :drop_join_table, :drop_table, :execute_block, + :drop_join_table, :drop_table, :execute_block, :enable_extension, :change_column, :execute, :remove_columns, # irreversible methods need to be here too ].each do |method| class_eval <<-EOV, __FILE__, __LINE__ + 1 @@ -100,6 +100,7 @@ module ActiveRecord add_column: :remove_column, add_timestamps: :remove_timestamps, add_reference: :remove_reference, + enable_extension: :disable_extension }.each do |cmd, inv| [[inv, cmd], [cmd, inv]].uniq.each do |method, inverse| class_eval <<-EOV, __FILE__, __LINE__ + 1 diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index e53e8553ad..df28451bb7 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -465,19 +465,17 @@ module ActiveRecord association.build(attributes.except(*UNASSIGNABLE_KEYS)) end elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s } - unless association.loaded? || call_reject_if(association_name, attributes) + unless call_reject_if(association_name, attributes) # Make sure we are operating on the actual object which is in the association's # proxy_target array (either by finding it, or adding it if not found) - target_record = association.target.detect { |record| record == existing_record } - + # Take into account that the proxy_target may have changed due to callbacks + target_record = association.target.detect { |record| record.id.to_s == attributes['id'].to_s } if target_record existing_record = target_record else - association.add_to_target(existing_record) + association.add_to_target(existing_record, :skip_callbacks) end - end - if !call_reject_if(association_name, attributes) assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) end else diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 8a311039d5..daccab762f 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -320,11 +320,14 @@ db_namespace = namespace :db do # desc "Recreate the test database from an existent schema.rb file" task :load_schema => 'db:test:purge' do begin + should_reconnect = ActiveRecord::Base.connection_pool.active_connection? ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) ActiveRecord::Schema.verbose = false db_namespace["schema:load"].invoke ensure - ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) + if should_reconnect + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) + end end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 73d154e03e..f428f160cf 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -394,7 +394,7 @@ module ActiveRecord # returns either nil or the inverse association name that it finds. def automatic_inverse_of if can_find_inverse_of_automatically?(self) - inverse_name = active_record.name.downcase.to_sym + inverse_name = ActiveSupport::Inflector.underscore(active_record.name).to_sym begin reflection = klass.reflect_on_association(inverse_name) @@ -413,7 +413,7 @@ module ActiveRecord end # Checks if the inverse reflection that is returned from the - # +set_automatic_inverse_of+ method is a valid reflection. We must + # +automatic_inverse_of+ method is a valid reflection. We must # make sure that the reflection's active_record name matches up # with the current reflection's klass name. # diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index 489efac932..b8dd35c4c5 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -66,7 +66,7 @@ class PostgresqlByteaTest < ActiveRecord::TestCase def test_write_value data = "\u001F" record = ByteaDataType.create(payload: data) - refute record.new_record? + assert_not record.new_record? assert_equal(data, record.payload) end @@ -74,14 +74,14 @@ class PostgresqlByteaTest < ActiveRecord::TestCase data = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'assets', 'example.log')) assert(data.size > 1) record = ByteaDataType.create(payload: data) - refute record.new_record? + assert_not record.new_record? assert_equal(data, record.payload) assert_equal(data, ByteaDataType.where(id: record.id).first.payload) end def test_write_nil record = ByteaDataType.create(payload: nil) - refute record.new_record? + assert_not record.new_record? assert_equal(nil, record.payload) assert_equal(nil, ByteaDataType.where(id: record.id).first.payload) end diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index e946b8bb22..75b6f4f8ce 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -311,11 +311,11 @@ _SQL def test_update_tstzrange skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? new_tstzrange = Time.parse('2010-01-01 14:30:00 CDT')...Time.parse('2011-02-02 14:30:00 CET') - assert @first_range.tstz_range = new_tstzrange + @first_range.tstz_range = new_tstzrange assert @first_range.save assert @first_range.reload assert_equal new_tstzrange, @first_range.tstz_range - assert @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000') + @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000') assert @first_range.save assert @first_range.reload assert_nil @first_range.tstz_range @@ -335,11 +335,11 @@ _SQL skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? tz = ::ActiveRecord::Base.default_timezone new_tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0) - assert @first_range.ts_range = new_tsrange + @first_range.ts_range = new_tsrange assert @first_range.save assert @first_range.reload assert_equal new_tsrange, @first_range.ts_range - assert @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0) + @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0) assert @first_range.save assert @first_range.reload assert_nil @first_range.ts_range @@ -357,11 +357,11 @@ _SQL def test_update_numrange skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? new_numrange = BigDecimal.new('0.5')...BigDecimal.new('1') - assert @first_range.num_range = new_numrange + @first_range.num_range = new_numrange assert @first_range.save assert @first_range.reload assert_equal new_numrange, @first_range.num_range - assert @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5') + @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5') assert @first_range.save assert @first_range.reload assert_nil @first_range.num_range @@ -379,11 +379,11 @@ _SQL def test_update_daterange skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? new_daterange = Date.new(2012, 2, 3)...Date.new(2012, 2, 10) - assert @first_range.date_range = new_daterange + @first_range.date_range = new_daterange assert @first_range.save assert @first_range.reload assert_equal new_daterange, @first_range.date_range - assert @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3) + @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3) assert @first_range.save assert @first_range.reload assert_nil @first_range.date_range @@ -401,11 +401,11 @@ _SQL def test_update_int4range skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? new_int4range = 6...10 - assert @first_range.int4_range = new_int4range + @first_range.int4_range = new_int4range assert @first_range.save assert @first_range.reload assert_equal new_int4range, @first_range.int4_range - assert @first_range.int4_range = 3...3 + @first_range.int4_range = 3...3 assert @first_range.save assert @first_range.reload assert_nil @first_range.int4_range @@ -423,11 +423,11 @@ _SQL def test_update_int8range skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? new_int8range = 60000...10000000 - assert @first_range.int8_range = new_int8range + @first_range.int8_range = new_int8range assert @first_range.save assert @first_range.reload assert_equal new_int8range, @first_range.int8_range - assert @first_range.int8_range = 39999...39999 + @first_range.int8_range = 39999...39999 assert @first_range.save assert @first_range.reload assert_nil @first_range.int8_range @@ -435,10 +435,10 @@ _SQL def test_update_tsvector new_text_vector = "'new' 'text' 'vector'" - assert @first_tsvector.text_vector = new_text_vector + @first_tsvector.text_vector = new_text_vector assert @first_tsvector.save assert @first_tsvector.reload - assert @first_tsvector.text_vector = new_text_vector + @first_tsvector.text_vector = new_text_vector assert @first_tsvector.save assert @first_tsvector.reload assert_equal new_text_vector, @first_tsvector.text_vector @@ -479,11 +479,11 @@ _SQL def test_update_integer_array new_value = [32800,95000,29350,17000] - assert @first_array.commission_by_quarter = new_value + @first_array.commission_by_quarter = new_value assert @first_array.save assert @first_array.reload assert_equal new_value, @first_array.commission_by_quarter - assert @first_array.commission_by_quarter = new_value + @first_array.commission_by_quarter = new_value assert @first_array.save assert @first_array.reload assert_equal new_value, @first_array.commission_by_quarter @@ -491,11 +491,11 @@ _SQL def test_update_text_array new_value = ['robby','robert','rob','robbie'] - assert @first_array.nicknames = new_value + @first_array.nicknames = new_value assert @first_array.save assert @first_array.reload assert_equal new_value, @first_array.nicknames - assert @first_array.nicknames = new_value + @first_array.nicknames = new_value assert @first_array.save assert @first_array.reload assert_equal new_value, @first_array.nicknames @@ -503,7 +503,7 @@ _SQL def test_update_money new_value = BigDecimal.new('123.45') - assert @first_money.wealth = new_value + @first_money.wealth = new_value assert @first_money.save assert @first_money.reload assert_equal new_value, @first_money.wealth @@ -512,8 +512,8 @@ _SQL def test_update_number new_single = 789.012 new_double = 789012.345 - assert @first_number.single = new_single - assert @first_number.double = new_double + @first_number.single = new_single + @first_number.double = new_double assert @first_number.save assert @first_number.reload assert_equal new_single, @first_number.single @@ -521,7 +521,7 @@ _SQL end def test_update_time - assert @first_time.time_interval = '2 years 3 minutes' + @first_time.time_interval = '2 years 3 minutes' assert @first_time.save assert @first_time.reload assert_equal '2 years 00:03:00', @first_time.time_interval @@ -531,9 +531,9 @@ _SQL new_inet_address = '10.1.2.3/32' new_cidr_address = '10.0.0.0/8' new_mac_address = 'bc:de:f0:12:34:56' - assert @first_network_address.cidr_address = new_cidr_address - assert @first_network_address.inet_address = new_inet_address - assert @first_network_address.mac_address = new_mac_address + @first_network_address.cidr_address = new_cidr_address + @first_network_address.inet_address = new_inet_address + @first_network_address.mac_address = new_mac_address assert @first_network_address.save assert @first_network_address.reload assert_equal @first_network_address.cidr_address, new_cidr_address @@ -544,8 +544,8 @@ _SQL def test_update_bit_string new_bit_string = '11111111' new_bit_string_varying = '0xFF' - assert @first_bit_string.bit_string = new_bit_string - assert @first_bit_string.bit_string_varying = new_bit_string_varying + @first_bit_string.bit_string = new_bit_string + @first_bit_string.bit_string_varying = new_bit_string_varying assert @first_bit_string.save assert @first_bit_string.reload assert_equal new_bit_string, @first_bit_string.bit_string @@ -558,9 +558,23 @@ _SQL assert_raise(ActiveRecord::StatementInvalid) { assert @first_bit_string.save } end + def test_invalid_network_address + @first_network_address.cidr_address = 'invalid addr' + assert_nil @first_network_address.cidr_address + assert_equal 'invalid addr', @first_network_address.cidr_address_before_type_cast + assert @first_network_address.save + + @first_network_address.reload + + @first_network_address.inet_address = 'invalid addr' + assert_nil @first_network_address.inet_address + assert_equal 'invalid addr', @first_network_address.inet_address_before_type_cast + assert @first_network_address.save + end + def test_update_oid new_value = 567890 - assert @first_oid.obj_id = new_value + @first_oid.obj_id = new_value assert @first_oid.save assert @first_oid.reload assert_equal new_value, @first_oid.obj_id diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 724d1cbbf8..941e851aae 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -654,7 +654,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase sarah = Person.create!(:first_name => 'Sarah', :primary_contact_id => people(:susan).id, :gender => 'F', :number1_fan_id => 1) john = Person.create!(:first_name => 'John', :primary_contact_id => sarah.id, :gender => 'M', :number1_fan_id => 1) assert_equal sarah.agents, [john] - assert_equal people(:susan).agents.flat_map(&:agents), people(:susan).agents_of_agents + assert_equal people(:susan).agents.map(&:agents).flatten, people(:susan).agents_of_agents end def test_associate_existing_with_nonstandard_primary_key_on_belongs_to diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 71cf1237e8..2477e60e51 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -9,10 +9,24 @@ require 'models/rating' require 'models/comment' require 'models/car' require 'models/bulb' +require 'models/mixed_case_monkey' class AutomaticInverseFindingTests < ActiveRecord::TestCase fixtures :ratings, :comments, :cars + def test_has_one_and_belongs_to_should_find_inverse_automatically_on_multiple_word_name + monkey_reflection = MixedCaseMonkey.reflect_on_association(:man) + man_reflection = Man.reflect_on_association(:mixed_case_monkey) + + assert_respond_to monkey_reflection, :has_inverse? + assert monkey_reflection.has_inverse?, "The monkey reflection should have an inverse" + assert_equal man_reflection, monkey_reflection.inverse_of, "The monkey reflection's inverse should be the man reflection" + + assert_respond_to man_reflection, :has_inverse? + assert man_reflection.has_inverse?, "The man reflection should have an inverse" + assert_equal monkey_reflection, man_reflection.inverse_of, "The man reflection's inverse should be the monkey reflection" + end + def test_has_one_and_belongs_to_should_find_inverse_automatically car_reflection = Car.reflect_on_association(:bulb) bulb_reflection = Bulb.reflect_on_association(:car) @@ -406,7 +420,7 @@ class InverseHasManyTests < ActiveRecord::TestCase interest = Interest.create!(man: man) man.interests.find(interest.id) - refute man.interests.loaded? + assert_not man.interests.loaded? end def test_raise_record_not_found_error_when_invalid_ids_are_passed diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index c91cf89f6d..c91cf89f6d 100755..100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 2cad8a6d96..1b205d372f 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -242,6 +242,16 @@ module ActiveRecord add = @recorder.inverse_of :remove_belongs_to, [:table, :user] assert_equal [:add_reference, [:table, :user], nil], add end + + def test_invert_enable_extension + disable = @recorder.inverse_of :enable_extension, ['uuid-ossp'] + assert_equal [:disable_extension, ['uuid-ossp'], nil], disable + end + + def test_invert_disable_extension + enable = @recorder.inverse_of :disable_extension, ['uuid-ossp'] + assert_equal [:enable_extension, ['uuid-ossp'], nil], enable + end end end end diff --git a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb new file mode 100644 index 0000000000..43a69928b6 --- /dev/null +++ b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb @@ -0,0 +1,144 @@ +require "cases/helper" +require "models/pirate" +require "models/bird" + +class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase + Pirate.has_many(:birds_with_add_load, + :class_name => "Bird", + :before_add => proc { |p,b| + @@add_callback_called << b + p.birds_with_add_load.to_a + }) + Pirate.has_many(:birds_with_add, + :class_name => "Bird", + :before_add => proc { |p,b| @@add_callback_called << b }) + + Pirate.accepts_nested_attributes_for(:birds_with_add_load, + :birds_with_add, + :allow_destroy => true) + + def setup + @@add_callback_called = [] + @pirate = Pirate.new.tap do |pirate| + pirate.catchphrase = "Don't call me!" + pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}] + pirate.save! + end + @birds = @pirate.birds.to_a + end + + def bird_to_update + @birds[0] + end + + def bird_to_destroy + @birds[1] + end + + def existing_birds_attributes + @birds.map do |bird| + bird.attributes.slice("id","name") + end + end + + def new_birds + @pirate.birds_with_add.to_a - @birds + end + + def new_bird_attributes + [{'name' => "New Bird"}] + end + + def destroy_bird_attributes + [{'id' => bird_to_destroy.id.to_s, "_destroy" => true}] + end + + def update_new_and_destroy_bird_attributes + [{'id' => @birds[0].id.to_s, 'name' => 'New Name'}, + {'name' => "New Bird"}, + {'id' => bird_to_destroy.id.to_s, "_destroy" => true}] + end + + # Characterizing when :before_add callback is called + test ":before_add called for new bird when not loaded" do + assert_not @pirate.birds_with_add.loaded? + @pirate.birds_with_add_attributes = new_bird_attributes + assert_new_bird_with_callback_called + end + + test ":before_add called for new bird when loaded" do + @pirate.birds_with_add.load_target + @pirate.birds_with_add_attributes = new_bird_attributes + assert_new_bird_with_callback_called + end + + def assert_new_bird_with_callback_called + assert_equal(1, new_birds.size) + assert_equal(new_birds, @@add_callback_called) + end + + test ":before_add not called for identical assignment when not loaded" do + assert_not @pirate.birds_with_add.loaded? + @pirate.birds_with_add_attributes = existing_birds_attributes + assert_callbacks_not_called + end + + test ":before_add not called for identical assignment when loaded" do + @pirate.birds_with_add.load_target + @pirate.birds_with_add_attributes = existing_birds_attributes + assert_callbacks_not_called + end + + test ":before_add not called for destroy assignment when not loaded" do + assert_not @pirate.birds_with_add.loaded? + @pirate.birds_with_add_attributes = destroy_bird_attributes + assert_callbacks_not_called + end + + test ":before_add not called for deletion assignment when loaded" do + @pirate.birds_with_add.load_target + @pirate.birds_with_add_attributes = destroy_bird_attributes + assert_callbacks_not_called + end + + def assert_callbacks_not_called + assert_empty new_birds + assert_empty @@add_callback_called + end + + # Ensuring that the records in the association target are updated, + # whether the association is loaded before or not + test "Assignment updates records in target when not loaded" do + assert_not @pirate.birds_with_add.loaded? + @pirate.birds_with_add_attributes = update_new_and_destroy_bird_attributes + assert_assignment_affects_records_in_target(:birds_with_add) + end + + test "Assignment updates records in target when loaded" do + @pirate.birds_with_add.load_target + @pirate.birds_with_add_attributes = update_new_and_destroy_bird_attributes + assert_assignment_affects_records_in_target(:birds_with_add) + end + + test("Assignment updates records in target when not loaded" + + " and callback loads target") do + assert_not @pirate.birds_with_add_load.loaded? + @pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes + assert_assignment_affects_records_in_target(:birds_with_add_load) + end + + test("Assignment updates records in target when loaded" + + " and callback loads target") do + @pirate.birds_with_add_load.load_target + @pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes + assert_assignment_affects_records_in_target(:birds_with_add_load) + end + + def assert_assignment_affects_records_in_target(association_name) + association = @pirate.send(association_name) + assert association.detect {|b| b == bird_to_update }.name_changed?, + 'Update record not updated' + assert association.detect {|b| b == bird_to_destroy }.marked_for_destruction?, + 'Destroy record not marked for destruction' + end +end diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 85fbe01416..44b2064110 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -184,25 +184,6 @@ module ActiveRecord assert_equal "'lo\\\\l'", @quoter.quote('lo\l', FakeColumn.new(:binary)) end - def test_quote_binary_with_string_to_binary - col = Class.new(FakeColumn) { - def string_to_binary(value) - 'foo' - end - }.new(:binary) - assert_equal "'foo'", @quoter.quote('lo\l', col) - end - - def test_quote_as_mb_chars_binary_column_with_string_to_binary - col = Class.new(FakeColumn) { - def string_to_binary(value) - 'foo' - end - }.new(:binary) - string = ActiveSupport::Multibyte::Chars.new('lo\l') - assert_equal "'foo'", @quoter.quote(string, col) - end - def test_string_with_crazy_column assert_equal "'lo\\\\l'", @quoter.quote('lo\l', FakeColumn.new(:foo)) end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index bf9d395b2d..e1a760d240 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -170,6 +170,10 @@ class RelationTest < ActiveRecord::TestCase assert_equal topics(:fourth).title, topics.first.title end + def test_order_with_hash_and_symbol_generates_the_same_sql + assert_equal Topic.order(:id).to_sql, Topic.order(:id => :asc).to_sql + end + def test_raising_exception_on_invalid_hash_params assert_raise(ArgumentError) { Topic.order(:name, "id DESC", :id => :DeSc) } end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index abfc90474c..a5cb22aaf6 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -453,11 +453,6 @@ class TransactionTest < ActiveRecord::TestCase raise ActiveRecord::Rollback end end - - ensure - Topic.reset_column_information # reset the column information to get correct reading - Topic.connection.remove_column('topics', 'stuff') if Topic.column_names.include?('stuff') - Topic.reset_column_information # reset the column information again for other tests end def test_transactions_state_from_rollback diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 7dad8041f3..feb828de31 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -8,7 +8,9 @@ class Author < ActiveRecord::Base has_many :posts_sorted_by_id_limited, -> { order('posts.id').limit(1) }, :class_name => "Post" has_many :posts_with_categories, -> { includes(:categories) }, :class_name => "Post" has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order("posts.id") }, :class_name => "Post" + has_many :posts_containing_the_letter_a, :class_name => "Post" has_many :posts_with_special_categorizations, :class_name => 'PostWithSpecialCategorization' + has_many :posts_with_extension, :class_name => "Post" has_one :post_about_thinking, -> { where("posts.title like '%thinking%'") }, :class_name => 'Post' has_one :post_about_thinking_with_last_comment, -> { where("posts.title like '%thinking%'").includes(:last_comment) }, :class_name => 'Post' has_many :comments, through: :posts do @@ -30,6 +32,7 @@ class Author < ActiveRecord::Base has_many :welcome_posts, -> { where(:title => 'Welcome to the weblog') }, :class_name => 'Post' has_many :comments_desc, -> { order('comments.id DESC') }, :through => :posts, :source => :comments + has_many :limited_comments, -> { limit(1) }, :through => :posts, :source => :comments has_many :funky_comments, :through => :posts, :source => :comments has_many :ordered_uniq_comments, -> { distinct.order('comments.id') }, :through => :posts, :source => :comments has_many :ordered_uniq_comments_desc, -> { distinct.order('comments.id DESC') }, :through => :posts, :source => :comments diff --git a/activerecord/test/models/auto_id.rb b/activerecord/test/models/auto_id.rb index 82c6544bd5..d720e2be5e 100644 --- a/activerecord/test/models/auto_id.rb +++ b/activerecord/test/models/auto_id.rb @@ -1,4 +1,4 @@ class AutoId < ActiveRecord::Base - self.table_name = "auto_id_tests" - self.primary_key = "auto_id" + def self.table_name () "auto_id_tests" end + def self.primary_key () "auto_id" end end diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index 6d257dbe7e..a14a9febba 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -1,9 +1,12 @@ class Car < ActiveRecord::Base + has_many :bulbs has_many :funky_bulbs, class_name: 'FunkyBulb', dependent: :destroy has_many :foo_bulbs, -> { where(:name => 'foo') }, :class_name => "Bulb" + has_many :frickinawesome_bulbs, -> { where :frickinawesome => true }, :class_name => "Bulb" has_one :bulb + has_one :frickinawesome_bulb, -> { where :frickinawesome => true }, :class_name => "Bulb" has_many :tyres has_many :engines, :dependent => :destroy diff --git a/activerecord/test/models/citation.rb b/activerecord/test/models/citation.rb index 3d87eb795c..545aa8110d 100644 --- a/activerecord/test/models/citation.rb +++ b/activerecord/test/models/citation.rb @@ -1,3 +1,6 @@ class Citation < ActiveRecord::Base belongs_to :reference_of, :class_name => "Book", :foreign_key => :book2_id + + belongs_to :book1, :class_name => "Book", :foreign_key => :book1_id + belongs_to :book2, :class_name => "Book", :foreign_key => :book2_id end diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb index 566e0873f1..816c5e6937 100644 --- a/activerecord/test/models/club.rb +++ b/activerecord/test/models/club.rb @@ -2,6 +2,7 @@ class Club < ActiveRecord::Base has_one :membership has_many :memberships, :inverse_of => false has_many :members, :through => :memberships + has_many :current_memberships has_one :sponsor has_one :sponsored_member, :through => :sponsor, :source => :sponsorable, :source_type => "Member" belongs_to :category diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 8104c607b5..c5d4ec0833 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -49,6 +49,7 @@ class Firm < Company has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, :class_name => "Client" has_many :clients_like_ms_with_hash_conditions, -> { where(:name => 'Microsoft').order("id") }, :class_name => "Client" has_many :plain_clients, :class_name => 'Client' + has_many :readonly_clients, -> { readonly }, :class_name => 'Client' has_many :clients_using_primary_key, :class_name => 'Client', :primary_key => 'name', :foreign_key => 'firm_name' has_many :clients_using_primary_key_with_delete_all, :class_name => 'Client', @@ -166,6 +167,7 @@ class ExclusivelyDependentFirm < Company has_one :account, :foreign_key => "firm_id", :dependent => :delete has_many :dependent_sanitized_conditional_clients_of_firm, -> { order("id").where("name = 'BigShot Inc.'") }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all has_many :dependent_conditional_clients_of_firm, -> { order("id").where("name = ?", 'BigShot Inc.') }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all + has_many :dependent_hash_conditional_clients_of_firm, -> { order("id").where(:name => 'BigShot Inc.') }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all end class SpecialClient < Client diff --git a/activerecord/test/models/man.rb b/activerecord/test/models/man.rb index 4bff92dc98..f4d127730c 100644 --- a/activerecord/test/models/man.rb +++ b/activerecord/test/models/man.rb @@ -6,4 +6,5 @@ class Man < ActiveRecord::Base # These are "broken" inverse_of associations for the purposes of testing has_one :dirty_face, :class_name => 'Face', :inverse_of => :dirty_man has_many :secret_interests, :class_name => 'Interest', :inverse_of => :secret_man + has_one :mixed_case_monkey end diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb index 72095f9236..cc47c7bc18 100644 --- a/activerecord/test/models/member.rb +++ b/activerecord/test/models/member.rb @@ -2,6 +2,7 @@ class Member < ActiveRecord::Base has_one :current_membership has_one :selected_membership has_one :membership + has_many :fellow_members, :through => :club, :source => :members has_one :club, :through => :current_membership has_one :selected_club, :through => :selected_membership, :source => :club has_one :favourite_club, -> { where "memberships.favourite = ?", true }, :through => :membership, :source => :club diff --git a/activerecord/test/models/mixed_case_monkey.rb b/activerecord/test/models/mixed_case_monkey.rb index 763baefd91..4d37371777 100644 --- a/activerecord/test/models/mixed_case_monkey.rb +++ b/activerecord/test/models/mixed_case_monkey.rb @@ -1,3 +1,5 @@ class MixedCaseMonkey < ActiveRecord::Base self.primary_key = 'monkeyID' + + belongs_to :man end diff --git a/activerecord/test/models/movie.rb b/activerecord/test/models/movie.rb index c441be2bef..6384b4c801 100644 --- a/activerecord/test/models/movie.rb +++ b/activerecord/test/models/movie.rb @@ -1,3 +1,5 @@ class Movie < ActiveRecord::Base - self.primary_key = "movieid" + def self.primary_key + "movieid" + end end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 77564ffad4..93a7a2073c 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -122,6 +122,7 @@ class Post < ActiveRecord::Base has_many :secure_readers has_many :readers_with_person, -> { includes(:person) }, :class_name => "Reader" has_many :people, :through => :readers + has_many :secure_people, :through => :secure_readers has_many :single_people, :through => :readers has_many :people_with_callbacks, :source=>:person, :through => :readers, :before_add => lambda {|owner, reader| log(:added, :before, reader.first_name) }, diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb index 7f42a4b1f8..c094b726b4 100644 --- a/activerecord/test/models/project.rb +++ b/activerecord/test/models/project.rb @@ -1,6 +1,7 @@ class Project < ActiveRecord::Base has_and_belongs_to_many :developers, -> { distinct.order 'developers.name desc, developers.id desc' } has_and_belongs_to_many :readonly_developers, -> { readonly }, :class_name => "Developer" + has_and_belongs_to_many :selected_developers, -> { distinct.select "developers.*" }, :class_name => "Developer" has_and_belongs_to_many :non_unique_developers, -> { order 'developers.name desc, developers.id desc' }, :class_name => 'Developer' has_and_belongs_to_many :limited_developers, -> { limit 1 }, :class_name => "Developer" has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").distinct }, :class_name => "Developer" diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 40c8e97fc2..17035bf338 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -34,6 +34,7 @@ class Topic < ActiveRecord::Base has_many :replies, :dependent => :destroy, :foreign_key => "parent_id" has_many :approved_replies, -> { approved }, class_name: 'Reply', foreign_key: "parent_id", counter_cache: 'replies_count' + has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title" has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id" has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id" diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index be97b744c8..81c6246c5c 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,19 @@ +* Add String#remove(pattern) as a short-hand for the common pattern of String#gsub(pattern, '') + + *DHH* + +* Adds a new deprecation behaviour that raises an exception. Throwing this + line into +config/environments/development.rb+ + + ActiveSupport::Deprecation.behavior = :raise + + will cause the application to raise an +ActiveSupport::DeprecationException+ + on deprecations. + + Use this for aggressive deprecation cleanups. + + *Xavier Noria* + * Remove 'cow' => 'kine' irregular inflection from default inflections. *Andrew White* diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index af048d0c85..c60e833441 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -129,7 +129,7 @@ class Date options.fetch(:day, day) ) end - + # Allow Date to be compared with Time by converting to DateTime and relying on the <=> from there. def compare_with_coercion(other) if other.is_a?(Time) diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index fbf2877117..8930376ac8 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -235,6 +235,7 @@ module ActiveSupport value.map! { |i| deep_to_h(i) } value.length > 1 ? value : value.first end + end end diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 48190e1e66..534bbe3c42 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -47,7 +47,7 @@ class Object end # Same as #try, but will raise a NoMethodError exception if the receiving is not nil and - # does not implement the tried method. + # does not implemented the tried method. def try!(*a, &b) if a.empty? && block_given? yield self diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index a4cacaf789..49c0df6026 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -20,6 +20,16 @@ class String self end + # Returns a new string with all occurrences of the pattern removed. Short-hand for String#gsub(pattern, ''). + def remove(pattern) + gsub pattern, '' + end + + # Alters the string by removing all occurrences of the pattern. Short-hand for String#gsub!(pattern, ''). + def remove!(pattern) + gsub! pattern, '' + end + # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>: # # 'Once upon a time in a world far far away'.truncate(27) diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 90db180124..328b8c320a 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -1,14 +1,24 @@ require "active_support/notifications" module ActiveSupport + class DeprecationException < StandardError + end + class Deprecation # Default warning behaviors per Rails.env. DEFAULT_BEHAVIORS = { - :stderr => Proc.new { |message, callstack| + raise: ->(message, callstack) { + e = DeprecationException.new(message) + e.set_backtrace(callstack) + raise e + }, + + stderr: ->(message, callstack) { $stderr.puts(message) $stderr.puts callstack.join("\n ") if debug }, - :log => Proc.new { |message, callstack| + + log: ->(message, callstack) { logger = if defined?(Rails) && Rails.logger Rails.logger @@ -19,11 +29,13 @@ module ActiveSupport logger.warn message logger.debug callstack.join("\n ") if debug }, - :notify => Proc.new { |message, callstack| + + notify: ->(message, callstack) { ActiveSupport::Notifications.instrument("deprecation.rails", :message => message, :callstack => callstack) }, - :silence => Proc.new { |message, callstack| } + + silence: ->(message, callstack) {}, } module Behavior @@ -40,6 +52,7 @@ module ActiveSupport # # Available behaviors: # + # [+raise+] Raise <tt>ActiveSupport::DeprecationException</tt>. # [+stderr+] Log all deprecation warnings to +$stderr+. # [+log+] Log all deprecation warnings to +Rails.logger+. # [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+. @@ -52,7 +65,7 @@ module ActiveSupport # ActiveSupport::Deprecation.behavior = :stderr # ActiveSupport::Deprecation.behavior = [:stderr, :log] # ActiveSupport::Deprecation.behavior = MyCustomHandler - # ActiveSupport::Deprecation.behavior = proc { |message, callstack| + # ActiveSupport::Deprecation.behavior = ->(message, callstack) { # # custom stuff # } def behavior=(behavior) diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 2cb1f408b6..00727ef73c 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -70,13 +70,13 @@ module ActiveSupport alias :until :ago def inspect #:nodoc: - consolidated = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h } - parts = [:years, :months, :days, :minutes, :seconds].map do |length| - n = consolidated[length] - "#{n} #{n == 1 ? length.to_s.singularize : length.to_s}" if n.nonzero? - end.compact - parts = ["0 seconds"] if parts.empty? - parts.to_sentence(:locale => :en) + val_for = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h } + [:years, :months, :days, :minutes, :seconds]. + select {|unit| val_for[unit].nonzero?}. + tap {|units| units << :seconds if units.empty?}. + map {|unit| [unit, val_for[unit]]}. + map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}. + to_sentence(:locale => :en) end def as_json(options = nil) #:nodoc: diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index e03bb4ca0f..a33e2c58a9 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -41,7 +41,7 @@ module ActiveSupport end # +InheritableOptions+ provides a constructor to build an +OrderedOptions+ - # hash inherited from the another hash. + # hash inherited from another hash. # # Use this if you already have some hash and you want to create a new one based on it. # diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 12126fb9ef..2537af0394 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -277,6 +277,11 @@ class StringInflectionsTest < ActiveSupport::TestCase def test_truncate_should_not_be_html_safe assert !"Hello World!".truncate(12).html_safe? end + + def test_remove + assert_equal "Summer", "Fast Summer".remove(/Fast /) + assert_equal "Summer", "Fast Summer".remove!(/Fast /) + end def test_constantize run_constantize_tests_on do |string| diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index 9616e42f44..9674851b9d 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -98,6 +98,22 @@ class DeprecationTest < ActiveSupport::TestCase assert_match(/foo=nil/, @b) end + def test_raise_behaviour + ActiveSupport::Deprecation.behavior = :raise + + message = 'Revise this deprecated stuff now!' + callstack = %w(foo bar baz) + + begin + ActiveSupport::Deprecation.behavior.first.call(message, callstack) + rescue ActiveSupport::DeprecationException => e + assert_equal message, e.message + assert_equal callstack, e.backtrace + else + flunk 'the :raise deprecation behaviour should raise the expected exception' + end + end + def test_default_stderr_behavior ActiveSupport::Deprecation.behavior = :stderr behavior = ActiveSupport::Deprecation.behavior.first diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb index e0f85f4e7c..ce91c443e1 100644 --- a/activesupport/test/transliterate_test.rb +++ b/activesupport/test/transliterate_test.rb @@ -3,6 +3,7 @@ require 'abstract_unit' require 'active_support/inflector/transliterate' class TransliterateTest < ActiveSupport::TestCase + def test_transliterate_should_not_change_ascii_chars (0..127).each do |byte| char = [byte].pack("U") @@ -23,13 +24,12 @@ class TransliterateTest < ActiveSupport::TestCase def test_transliterate_should_work_with_custom_i18n_rules_and_uncomposed_utf8 char = [117, 776].pack("U*") # "ü" as ASCII "u" plus COMBINING DIAERESIS I18n.backend.store_translations(:de, :i18n => {:transliterate => {:rule => {"ü" => "ue"}}}) - default_locale, I18n.locale = I18n.locale, :de + I18n.locale = :de assert_equal "ue", ActiveSupport::Inflector.transliterate(char) - ensure - I18n.locale = default_locale end def test_transliterate_should_allow_a_custom_replacement_char assert_equal "a*b", ActiveSupport::Inflector.transliterate("a索b", "*") end + end diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb index d992028323..a025279e16 100644 --- a/activesupport/test/xml_mini_test.rb +++ b/activesupport/test/xml_mini_test.rb @@ -106,11 +106,7 @@ module XmlMiniTest module Nokogiri end setup do - @xml, @default_backend = ActiveSupport::XmlMini, ActiveSupport::XmlMini.backend - end - - teardown do - ActiveSupport::XmlMini.backend = @default_backend + @xml = ActiveSupport::XmlMini end test "#with_backend should switch backend and then switch back" do @@ -139,11 +135,7 @@ module XmlMiniTest module LibXML end setup do - @xml, @default_backend = ActiveSupport::XmlMini, ActiveSupport::XmlMini.backend - end - - teardown do - ActiveSupport::XmlMini.backend = @default_backend + @xml = ActiveSupport::XmlMini end test "#with_backend should be thread-safe" do diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 8be7a86d20..ff4d30b4c0 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -173,6 +173,19 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ * `Object#try` will now return nil instead of raise a NoMethodError if the receiving object does not implement the method, but you can still get the old behavior by using the new `Object#try!`. +* `String#to_date` now raises `Argument Error: invalid date` instead of `NoMethodError: undefined method 'div' for nil:NilClass` + when given an invalid date. It is now the same as `Date.parse`, and it accepts more invalid dates than 3.x, such as: + + ``` + # ActiveSupport 3.x + "asdf".to_date # => NoMethodError: undefined method `div' for nil:NilClass + "333".to_date # => NoMethodError: undefined method `div' for nil:NilClass + + # ActiveSupport 4 + "asdf".to_date # => ArgumentError: invalid date + "333".to_date # => Fri, 29 Nov 2013 + ``` + ### Deprecations * Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead. diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb index 0a0a958e30..9210c40c17 100644 --- a/guides/source/_welcome.html.erb +++ b/guides/source/_welcome.html.erb @@ -15,7 +15,7 @@ </p> <% end %> <p> - The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.14/">http://guides.rubyonrails.org/v3.2.14/</a>. + The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.13/">http://guides.rubyonrails.org/v3.2.13/</a>. </p> <p> The guides for Rails 2.3.x are available at <a href="http://guides.rubyonrails.org/v2.3.11/">http://guides.rubyonrails.org/v2.3.11/</a>. diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index 6fce5a1dc2..f7d2016784 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -1520,6 +1520,72 @@ number_with_precision(111.2345) # => 111.235 number_with_precision(111.2345, 2) # => 111.23 ``` +### SanitizeHelper + +The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements. + +#### sanitize + +This sanitize helper will html encode all tags and strip all attributes that aren’t specifically allowed. + +```ruby +sanitize @article.body +``` + +If either the :attributes or :tags options are passed, only the mentioned tags and attributes are allowed and nothing else. + +```ruby +sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) +``` + +To change defaults for multiple uses, for example adding table tags to the default: + +```ruby +class Application < Rails::Application + config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' +end +``` + +#### sanitize_css(style) + +Sanitizes a block of CSS code. + +#### strip_links(html) +Strips all link tags from text leaving just the link text. + +```ruby +strip_links("<a href="http://rubyonrails.org">Ruby on Rails</a>") +# => Ruby on Rails +``` + +```ruby +strip_links("emails to <a href="mailto:me@email.com">me@email.com</a>.") +# => emails to me@email.com. +``` + +```ruby +strip_links('Blog: <a href="http://myblog.com/">Visit</a>.') +# => Blog: Visit. +``` + +#### strip_tags(html) + +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 +strip_tags("Strip <i>these</i> tags!") +# => Strip these tags! +``` + +```ruby +strip_tags("<b>Bold</b> no more! <a href='more.html'>See more</a>") +# => Bold no more! See more +``` + +NB: The output may still contain unescaped ‘<’, ‘>’, ‘&’ characters and confuse browsers. + + Localized Views --------------- diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 7fe9b8b4af..ce571c6f96 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -514,13 +514,7 @@ SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5)) Post.where.not(author: author) ``` -In other words, this query can be generated by calling `where` with no argument, -then immediately chain with `not` passing `where` conditions. This will generate -SQL code like this: - -```sql -SELECT * FROM posts WHERE (author_id != 1) -``` +In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions. Ordering -------- diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 1915252122..ca023f7f66 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -96,13 +96,12 @@ INFO: The predicate for strings uses the Unicode-aware character class `[:space: WARNING: Note that numbers are not mentioned. In particular, 0 and 0.0 are **not** blank. -For example, this method from `ActionController::HttpAuthentication::Token::ControllerMethods` uses `blank?` for checking whether a token is present: +For example, this method from `ActionDispatch::Session::AbstractStore` uses `blank?` for checking whether a session key is present: ```ruby -def authenticate(controller, &login_procedure) - token, options = token_and_options(controller.request) - unless token.blank? - login_procedure.call(token, options) +def ensure_session_key! + if @key.blank? + raise ArgumentError, 'A key is required...' end end ``` @@ -421,9 +420,9 @@ NOTE: Defined in `active_support/core_ext/object/with_options.rb`. ### JSON support -Active Support provides a better implemention of `to_json` than the `json` gem ordinarily provides for Ruby objects. This is because some classes, like `Hash` and `OrderedHash` needs special handling in order to provide a proper JSON representation. +Active Support provides a better implemention of `to_json` than the +json+ gem ordinarily provides for Ruby objects. This is because some classes, like +Hash+ and +OrderedHash+ needs special handling in order to provide a proper JSON representation. -Active Support also provides an implementation of `as_json` for the `Process::Status` class. +Active Support also provides an implementation of `as_json` for the <tt>Process::Status</tt> class. NOTE: Defined in `active_support/core_ext/object/to_json.rb`. @@ -1249,6 +1248,18 @@ Calling `to_s` on a safe string returns a safe string, but coercion with `to_str Calling `dup` or `clone` on safe strings yields safe strings. +### `remove` + +The method `remove` will remove all occurrences of the pattern: + +```ruby +"Hello World".remove(/Hello /) => "World" +``` + +There's also the destructive version `String#remove!`. + +NOTE: Defined in `active_support/core_ext/string/filters.rb`. + ### `squish` The method `squish` strips leading and trailing whitespace, and substitutes runs of whitespace with a single space each: @@ -1988,7 +1999,7 @@ Produce a string representation of a number in human-readable words: 1234567890123456.to_s(:human) # => "1.23 Quadrillion" ``` -NOTE: Defined in `active_support/core_ext/numeric/conversions.rb`. +NOTE: Defined in `active_support/core_ext/numeric/formatting.rb`. Extensions to `Integer` ----------------------- @@ -2046,7 +2057,7 @@ BigDecimal.new(5.00, 6).to_s # => "5.0" ### `to_formatted_s` -The method `to_formatted_s` provides a default specifier of "F". This means that a simple call to `to_formatted_s` or `to_s` will result in floating point representation instead of engineering notation: +Te method `to_formatted_s` provides a default specifier of "F". This means that a simple call to `to_formatted_s` or `to_s` will result in floating point representation instead of engineering notation: ```ruby BigDecimal.new(5.00, 6).to_formatted_s # => "5.0" @@ -2433,7 +2444,7 @@ dup[1][2] = 4 array[1][2] == nil # => true ``` -NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`. +NOTE: Defined in `active_support/core_ext/array/deep_dup.rb`. ### Grouping @@ -2659,7 +2670,45 @@ hash[:b][:e] == nil # => true hash[:b][:d] == [3, 4] # => true ``` -NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`. +NOTE: Defined in `active_support/core_ext/hash/deep_dup.rb`. + +### Diffing + +The method `diff` returns a hash that represents a diff of the receiver and the argument with the following logic: + +* Pairs `key`, `value` that exist in both hashes do not belong to the diff hash. + +* If both hashes have `key`, but with different values, the pair in the receiver wins. + +* The rest is just merged. + +```ruby +{a: 1}.diff(a: 1) +# => {}, first rule + +{a: 1}.diff(a: 2) +# => {:a=>1}, second rule + +{a: 1}.diff(b: 2) +# => {:a=>1, :b=>2}, third rule + +{a: 1, b: 2, c: 3}.diff(b: 1, c: 3, d: 4) +# => {:a=>1, :b=>2, :d=>4}, all rules + +{}.diff({}) # => {} +{a: 1}.diff({}) # => {:a=>1} +{}.diff(a: 1) # => {:a=>1} +``` + +An important property of this diff hash is that you can retrieve the original hash by applying `diff` twice: + +```ruby +hash.diff(hash2).diff(hash2) == hash +``` + +Diffing hashes may be useful for error messages related to expected option hashes for example. + +NOTE: Defined in `active_support/core_ext/hash/diff.rb`. ### Working with Keys @@ -3794,13 +3843,13 @@ def default_helper_module! module_path = module_name.underscore helper module_path rescue MissingSourceFile => e - raise e unless e.is_missing? "helpers/#{module_path}_helper" + raise e unless e.is_missing? "#{module_path}_helper" rescue NameError => e raise e unless e.missing_name? "#{module_name}Helper" end ``` -NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`. +NOTE: Defined in `active_support/core_ext/name_error.rb`. Extensions to `LoadError` ------------------------- @@ -3823,4 +3872,4 @@ rescue NameError => e end ``` -NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`. +NOTE: Defined in `active_support/core_ext/load_error.rb`. diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 639a00817b..862742679c 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -905,7 +905,7 @@ Customizing the Pipeline ### CSS Compression There is currently one option for compressing CSS, YUI. The [YUI CSS -compressor]((http://yui.github.io/yuicompressor/css.html) provides +compressor](http://yui.github.io/yuicompressor/css.html) provides minification. The following line enables YUI compression, and requires the `yui-compressor` diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 9b80a65a44..e133e71d42 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -1137,6 +1137,12 @@ Controls what happens to the associated object when its owner is destroyed: * `:restrict_with_exception` causes an exception to be raised if there is an associated record * `:restrict_with_error` causes an error to be added to the owner if there is an associated object +It's necessary not to set or leave `:nullify` option for those associations +that have `NOT NULL` database constraints. If you don't set `dependent` to +destroy such associations you won't be able to change the associated object +because initial associated object foreign key will be set to unallowed `NULL` +value. + ##### `:foreign_key` By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly: diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index ec25e09222..034fef71c8 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -74,7 +74,7 @@ ports. If you have any problems with these libraries, you can install them manually by compiling the source code. Just follow the instructions at the [Red Hat/CentOS section of the Nokogiri tutorials](http://nokogiri.org/tutorials/installing_nokogiri.html#red_hat__centos) . -Also, SQLite3 and its development files for the `sqlite3` gem — in Ubuntu you're done with just +Also, SQLite3 and its development files for the `sqlite3-ruby` gem — in Ubuntu you're done with just ```bash $ sudo apt-get install sqlite3 libsqlite3-dev @@ -86,6 +86,7 @@ And if you are on Fedora or CentOS, you're done with $ sudo yum install sqlite3 sqlite3-devel ``` +<<<<<<< HEAD If you are on Arch Linux, you will need to run: ```bash @@ -100,6 +101,8 @@ For FreeBSD users, you're done with: Or compile the `databases/sqlite3` port. +======= +>>>>>>> ec8ef1e1055c4e1598da13f49d30261f07f4a9b4 Get a recent version of [Bundler](http://gembundler.com/) ```bash @@ -166,6 +169,7 @@ $ sudo yum install mysql-server mysql-devel $ sudo yum install postgresql-server postgresql-devel ``` +<<<<<<< HEAD If you are running Arch Linux, MySQL isn't supported anymore so you will need to use MariaDB instead (see [this announcement](https://www.archlinux.org/news/mariadb-replaces-mysql-in-repositories/)): @@ -185,6 +189,8 @@ Or install them through ports (they are located under the `databases` folder). If you run into troubles during the installation of MySQL, please see [the MySQL documentation](http://dev.mysql.com/doc/refman/5.1/en/freebsd-installation.html). +======= +>>>>>>> ec8ef1e1055c4e1598da13f49d30261f07f4a9b4 After that, run: ```bash diff --git a/guides/source/engines.md b/guides/source/engines.md index a77be917a2..9106b6382d 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -525,6 +525,14 @@ First, the `author_name` text field needs to be added to the `app/views/blorgh/p </div> ``` +Next, we need to update our `Blorgh::PostController#post_params` method to permit the new form parameter: + +```ruby +def post_params + params.require(:post).permit(:title, :text, :author_name) +end +``` + The `Blorgh::Post` model should then have some code to convert the `author_name` field into an actual `User` object and associate it as that post's `author` before the post is saved. It will also need to have an `attr_accessor` setup for this field so that the setter and getter methods are defined for it. To do all this, you'll need to add the `attr_accessor` for `author_name`, the association for the author and the `before_save` call into `app/models/blorgh/post.rb`. The `author` association will be hard-coded to the `User` class for the time being. diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index 11e8db9e88..3a18fb81d8 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -605,7 +605,7 @@ The object in the `params` hash is an instance of a subclass of IO. Depending on ```ruby def upload uploaded_io = params[:person][:picture] - File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'w') do |file| + File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'wb') do |file| file.write(uploaded_io.read) end end diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 784a61adc1..021aa070fb 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -310,6 +310,10 @@ end A controller is simply a class that is defined to inherit from `ApplicationController`. It's inside this class that you'll define methods that will become the actions for this controller. These actions will perform CRUD operations on the posts within our system. +NOTE: There are `public`, `private` and `protected` methods in `Ruby` +(for more details you can check on [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/)). +But only `public` methods can be actions for controllers. + If you refresh <http://localhost:3000/posts/new> now, you'll get a new error:  @@ -594,8 +598,9 @@ end ``` A couple of things to note. We use `Post.find` to find the post we're -interested in. We also use an instance variable (prefixed by `@`) to -hold a reference to the post object. We do this because Rails will pass all instance +interested in, passing in `params[:id]` to get the `:id` parameter from the +request. We also use an instance variable (prefixed by `@`) to hold a +reference to the post object. We do this because Rails will pass all instance variables to the view. Now, create a new file `app/views/posts/show.html.erb` with the following diff --git a/guides/source/i18n.md b/guides/source/i18n.md index facfb96d98..e4214fd74e 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -731,6 +731,19 @@ en: Then `User.model_name.human` will return "Dude" and `User.human_attribute_name("login")` will return "Handle". +You can also set a plural form for model names, adding as following: + +```ruby +en: + activerecord: + models: + user: + one: Dude + other: Dudes +``` + +Then `User.model_name.human(:count => 2)` will return "Dudes". With `:count => 1` or without params will return "Dude". + #### Error Message Scopes Active Record validation error messages can also be translated easily. Active Record gives you a couple of namespaces where you can place your message translations in order to provide different messages and translation for certain models, attributes, and/or validations. It also transparently takes single table inheritance into account. diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 26259408b4..c78eef5bf0 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -40,7 +40,7 @@ This file is as follows: ```ruby #!/usr/bin/env ruby APP_PATH = File.expand_path('../../config/application', __FILE__) -require File.expand_path('../../config/boot', __FILE__) +require_relative '../config/boot' require 'rails/commands' ``` diff --git a/guides/source/testing.md b/guides/source/testing.md index 62c9835fa4..0258202c18 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -66,18 +66,34 @@ Here's a sample YAML fixture file: ```yaml # lo & behold! I am a YAML comment! david: - name: David Heinemeier Hansson - birthday: 1979-10-15 - profession: Systems development + name: David Heinemeier Hansson + birthday: 1979-10-15 + profession: Systems development steve: - name: Steve Ross Kellock - birthday: 1974-09-27 - profession: guy with keyboard + name: Steve Ross Kellock + birthday: 1974-09-27 + profession: guy with keyboard ``` Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank space. You can place comments in a fixture file by using the # character in the first column. Keys which resemble YAML keywords such as 'yes' and 'no' are quoted so that the YAML Parser correctly interprets them. +If you are working with [associations](/association_basics.html), you can simply +define a reference node between two different fixtures. Here's an example with +a belongs_to/has_many association: + +```yaml +# In fixtures/categories.yml +about: + name: About + +# In fixtures/articles.yml +one: + title: Welcome to Rails! + body: Hello world! + category: about +``` + #### ERB'in It Up ERB allows you to embed Ruby code within templates. The YAML fixture format is pre-processed with ERB when Rails loads fixtures. This allows you to use Ruby to help you generate some sample data. For example, the following code generates a thousand users: @@ -357,28 +373,28 @@ Here's an extract of the assertions you can use with `minitest`, the default tes | Assertion | Purpose | | ---------------------------------------------------------------- | ------- | | `assert( test, [msg] )` | Ensures that `test` is true.| -| `refute( test, [msg] )` | Ensures that `test` is false.| +| `assert_not( test, [msg] )` | Ensures that `test` is false.| | `assert_equal( expected, actual, [msg] )` | Ensures that `expected == actual` is true.| -| `refute_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.| +| `assert_not_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.| | `assert_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is true.| -| `refute_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is false.| +| `assert_not_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is false.| | `assert_nil( obj, [msg] )` | Ensures that `obj.nil?` is true.| -| `refute_nil( obj, [msg] )` | Ensures that `obj.nil?` is false.| +| `assert_not_nil( obj, [msg] )` | Ensures that `obj.nil?` is false.| | `assert_match( regexp, string, [msg] )` | Ensures that a string matches the regular expression.| -| `refute_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.| +| `assert_no_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.| | `assert_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are within `delta` of each other.| -| `refute_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.| +| `assert_not_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.| | `assert_throws( symbol, [msg] ) { block }` | Ensures that the given block throws the symbol.| | `assert_raises( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.| | `assert_nothing_raised( exception1, exception2, ... ) { block }` | Ensures that the given block doesn't raise one of the given exceptions.| | `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class`.| -| `refute_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.| +| `assert_not_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.| | `assert_kind_of( class, obj, [msg] )` | Ensures that `obj` is or descends from `class`.| -| `refute_kind_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class` and is not descending from it.| +| `assert_not_kind_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class` and is not descending from it.| | `assert_respond_to( obj, symbol, [msg] )` | Ensures that `obj` responds to `symbol`.| -| `refute_respond_to( obj, symbol, [msg] )` | Ensures that `obj` does not respond to `symbol`.| +| `assert_not_respond_to( obj, symbol, [msg] )` | Ensures that `obj` does not respond to `symbol`.| | `assert_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is true.| -| `refute_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.| +| `assert_not_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.| | `assert_send( array, [msg] )` | Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true. This one is weird eh?| | `flunk( [msg] )` | Ensures failure. This is useful to explicitly mark a test that isn't finished yet.| @@ -990,6 +1006,47 @@ class UserControllerTest < ActionController::TestCase end ``` +Testing helpers +--------------- + +In order to test helpers, all you need to do is check that the output of the +helper method matches what you'd expect. Tests related to the helpers are +located under the `test/helpers` directory. Rails provides a generator which +generates both the helper and the test file: + +```bash +$ rails generate helper User + create app/helpers/user_helper.rb + invoke test_unit + create test/helpers/user_helper_test.rb +``` + +The generated test file contains the following code: + +```ruby +require 'test_helper' + +class UserHelperTest < ActionView::TestCase +end +``` + +A helper is just a simple module where you can define methods which are +available into your views. To test the output of the helper's methods, you just +have to use a mixin like this: + +```ruby +class UserHelperTest < ActionView::TestCase + include UserHelper + + test "should return the user name" do + # ... + end +end +``` + +Moreover, since the test class extends from `ActionView::TestCase`, you have +access to Rails' helper methods such as `link_to` or `pluralize`. + Other Testing Approaches ------------------------ @@ -998,6 +1055,7 @@ The built-in `test/unit` based testing is not the only way to test Rails applica * [NullDB](http://avdi.org/projects/nulldb/), a way to speed up testing by avoiding database use. * [Factory Girl](https://github.com/thoughtbot/factory_girl/tree/master), a replacement for fixtures. * [Machinist](https://github.com/notahat/machinist/tree/master), another replacement for fixtures. +* [Fixture Builder](https://github.com/rdy/fixture_builder), a tool that compiles Ruby factories into fixtures before a test run. * [MiniTest::Spec Rails](https://github.com/metaskills/minitest-spec-rails), use the MiniTest::Spec DSL within your rails tests. * [Shoulda](http://www.thoughtbot.com/projects/shoulda), an extension to `test/unit` with additional helpers, macros, and assertions. * [RSpec](http://relishapp.com/rspec), a behavior-driven development framework diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 73c783085e..391551938a 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -320,7 +320,7 @@ config.assets.js_compressor = :uglifier ### sass-rails -* `asset-url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")` +* `asset_url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")` Upgrading from Rails 3.1 to Rails 3.2 ------------------------------------- diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index bd0c796673..301e0e7e6c 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -185,7 +185,7 @@ $(document).ready -> ``` Obviously, you'll want to be a bit more sophisticated than that, but it's a -start. +start. You can see more about the events [in the jquery-ujs wiki](https://github.com/rails/jquery-ujs/wiki/ajax). ### form_tag diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 7a89e5c219..cc9722e59c 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,25 @@ +* rake notes now searches *.less files + + *Josh Crowder* + +* Generate nested route for namespaced controller generated using + `rails g controller`. + Fixes #11532. + + Example: + + rails g controller admin/dashboard index + + # Before: + get "dashboard/index" + + # After: + namespace :admin do + get "dashboard/index" + end + + *Prathamesh Sonpatki* + * Fix the event name of action_dispatch requests. *Rafael Mendonça França* diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb index 3e32576040..1f9568fb5f 100644 --- a/railties/lib/rails/api/task.rb +++ b/railties/lib/rails/api/task.rb @@ -16,7 +16,8 @@ module Rails :include => %w( README.rdoc lib/active_record/**/*.rb - ) + ), + :exclude => 'lib/active_record/vendor/*' }, 'activemodel' => { @@ -32,22 +33,23 @@ module Rails lib/abstract_controller/**/*.rb lib/action_controller/**/*.rb lib/action_dispatch/**/*.rb - ) + ), + :exclude => 'lib/action_controller/vendor/*' }, 'actionview' => { :include => %w( README.rdoc lib/action_view/**/*.rb - ), - :exclude => 'lib/action_view/vendor/*' + ) }, 'actionmailer' => { :include => %w( README.rdoc lib/action_mailer/**/*.rb - ) + ), + :exclude => 'lib/action_mailer/vendor/*' }, 'railties' => { diff --git a/railties/lib/rails/app_rails_loader.rb b/railties/lib/rails/app_rails_loader.rb index fbb83fa10e..4a17803f1c 100644 --- a/railties/lib/rails/app_rails_loader.rb +++ b/railties/lib/rails/app_rails_loader.rb @@ -2,7 +2,7 @@ require 'pathname' module Rails module AppRailsLoader - RUBY = Gem.ruby + RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"] EXECUTABLES = ['bin/rails', 'script/rails'] BUNDLER_WARNING = <<EOS Looks like your app's ./bin/rails is a stub that was generated by Bundler. diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index be8af5c46c..8000fc3b1e 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -351,13 +351,8 @@ module Rails Rails::Railtie::Configuration.eager_load_namespaces << base base.called_from = begin - call_stack = if Kernel.respond_to?(:caller_locations) - caller_locations.map(&:path) - else - # Remove the line number from backtraces making sure we don't leave anything behind - caller.map { |p| p.sub(/:\d+.*/, '') } - end - + # Remove the line number from backtraces making sure we don't leave anything behind + call_stack = caller.map { |p| p.sub(/:\d+.*/, '') } File.dirname(call_stack.detect { |p| p !~ %r[railties[\w.-]*/lib/rails|rack[\w.-]*/lib/rack] }) end end diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb index bae54623c6..822f35fb42 100644 --- a/railties/lib/rails/generators/rails/controller/controller_generator.rb +++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb @@ -10,11 +10,45 @@ module Rails def add_routes actions.reverse.each do |action| - route %{get "#{file_name}/#{action}"} + route generate_routing_code(action) end end hook_for :template_engine, :test_framework, :helper, :assets + + private + + # This method creates nested route entry for namespaced resources. + # For eg. rails g controller foo/bar/baz index + # Will generate - + # namespace :foo do + # namespace :bar do + # get "baz/index" + # end + # end + def generate_routing_code(action) + depth = class_path.length + # Create 'namespace' ladder + # namespace :foo do + # namespace :bar do + namespace_ladder = class_path.each_with_index.map do |ns, i| + %{#{" " * i * 2}namespace :#{ns} do\n } + end.join + + # Create route + # get "baz/index" + route = %{#{" " * depth * 2}get "#{file_name}/#{action}"\n} + + # Create `end` ladder + # end + # end + end_ladder = (1..depth).reverse_each.map do |i| + "#{" " * i * 2}end\n" + end.join + + # Combine the 3 parts to generate complete route entry + namespace_ladder + route + end_ladder + end end end end diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb index 290634290f..b806b922b7 100644 --- a/railties/lib/rails/source_annotation_extractor.rb +++ b/railties/lib/rails/source_annotation_extractor.rb @@ -82,7 +82,7 @@ class SourceAnnotationExtractor case item when /\.(builder|rb|coffee|rake)$/ /#\s*(#{tag}):?\s*(.*)$/ - when /\.(css|scss|sass|js)$/ + when /\.(css|scss|sass|less|js)$/ /\/\/\s*(#{tag}):?\s*(.*)$/ when /\.erb$/ /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/ diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb index 9a92c5f6ff..01d751e822 100644 --- a/railties/test/application/rake/notes_test.rb +++ b/railties/test/application/rake/notes_test.rb @@ -25,6 +25,7 @@ module ApplicationTests app_file "app/assets/stylesheets/application.css", "// TODO: note in css" app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss" app_file "app/assets/stylesheets/application.css.sass", "// TODO: note in sass" + app_file "app/assets/stylesheets/application.css.less", "// TODO: note in less" app_file "app/controllers/application_controller.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in ruby" app_file "lib/tasks/task.rake", "# TODO: note in rake" @@ -48,9 +49,10 @@ module ApplicationTests assert_match(/note in css/, output) assert_match(/note in scss/, output) assert_match(/note in sass/, output) + assert_match(/note in less/, output) assert_match(/note in rake/, output) - assert_equal 10, lines.size + assert_equal 11, lines.size lines.each do |line| assert_equal 4, line[0].size diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb index 5205deafd9..9c664a903a 100644 --- a/railties/test/generators/controller_generator_test.rb +++ b/railties/test/generators/controller_generator_test.rb @@ -82,4 +82,9 @@ class ControllerGeneratorTest < Rails::Generators::TestCase assert_instance_method :bar, controller end end + + def test_namespaced_routes_are_created_in_routes + run_generator ["admin/dashboard", "index"] + assert_file "config/routes.rb", /namespace :admin do\n\s+get "dashboard\/index"\n/ + end end |