diff options
44 files changed, 287 insertions, 125 deletions
@@ -30,7 +30,7 @@ instance_eval File.read local_gemfile if File.exists? local_gemfile platforms :mri do group :test do gem 'ruby-prof', '~> 0.11.2' if RUBY_VERSION < '2.0' - gem 'debugger' if !ENV['TRAVIS'] && RUBY_VERSION < '2.0' + gem 'debugger' if !ENV['TRAVIS'] end end diff --git a/README.rdoc b/README.rdoc index bb9c418e0b..55b5efc916 100644 --- a/README.rdoc +++ b/README.rdoc @@ -18,7 +18,7 @@ you to present the data from database rows as objects and embellish these data o with business logic methods. Although most Rails models are backed by a database, models can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as provided by the ActiveModel module. You can read more about Active Record in its -{README}[link:/rails/rails/blob/master/activerecord/README.rdoc]. +{README}[link:/activerecord/README.rdoc]. The Controller layer is responsible for handling incoming HTTP requests and providing a suitable response. Usually this means returning \HTML, but Rails controllers can also @@ -29,7 +29,7 @@ In Rails, the Controller and View layers are handled together by Action Pack. These two layers are bundled in a single package due to their heavy interdependence. This is unlike the relationship between Active Record and Action Pack, which are independent. Each of these packages can be used independently outside of Rails. You -can read more about Action Pack in its {README}[link:/rails/rails/blob/master/actionpack/README.rdoc]. +can read more about Action Pack in its {README}[link:/actionpack/README.rdoc]. == Getting Started diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb index db48022b9f..02028d8e05 100644 --- a/actionpack/lib/abstract_controller/translation.rb +++ b/actionpack/lib/abstract_controller/translation.rb @@ -11,7 +11,7 @@ module AbstractController def translate(*args) key = args.first if key.is_a?(String) && (key[0] == '.') - key = "#{ controller_path.gsub('/', '.') }.#{ action_name }#{ key }" + key = "#{ controller_path.tr('/', '.') }.#{ action_name }#{ key }" args[0] = key end diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb index 61690d3e50..cd3daff065 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb @@ -8,7 +8,7 @@ <h2>Failure reasons:</h2> <ol> <% @exception.failures.each do |route, reason| %> - <li><code><%= route.inspect.gsub('\\', '') %></code> failed because <%= reason.downcase %></li> + <li><code><%= route.inspect.delete('\\') %></code> failed because <%= reason.downcase %></li> <% end %> </ol> </p> diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 4fbe156a72..c5f2b33602 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -181,7 +181,8 @@ module ActionDispatch if !via_all && options[:via].blank? msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \ - "If you want to expose your action to GET, use `get` in the router:\n\n" \ + "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \ + "If you want to expose your action to GET, use `get` in the router:\n" \ " Instead of: match \"controller#action\"\n" \ " Do: get \"controller#action\"" raise msg diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 1adc8225f1..8abd5d6e9c 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -778,7 +778,7 @@ module ActionView # see http://www.w3.org/TR/html4/types.html#type-name def sanitize_to_id(name) - name.to_s.gsub(']','').gsub(/[^-a-zA-Z0-9:.]/, "_") + name.to_s.delete(']').gsub(/[^-a-zA-Z0-9:.]/, "_") end end end diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index 1a1083bf00..47bd011d5e 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -109,7 +109,7 @@ module ActionView @cache.clear end - # Normalizes the arguments and passes it on to find_template. + # Normalizes the arguments and passes it on to find_templates. def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[]) cached(key, [name, prefix, partial], details, locals) do find_templates(name, prefix, partial, details) diff --git a/actionpack/test/template/debug_helper_test.rb b/actionpack/test/template/debug_helper_test.rb new file mode 100644 index 0000000000..42d06bd9ff --- /dev/null +++ b/actionpack/test/template/debug_helper_test.rb @@ -0,0 +1,8 @@ +require 'active_record_unit' + +class DebugHelperTest < ActionView::TestCase + def test_debug + company = Company.new(name: "firebase") + assert_match " name: firebase", debug(company) + end +end diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index 1f5d23dd8e..21e4eb3c86 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -1,5 +1,5 @@ module ActiveModel - # == Active \Model Conversions + # == Active \Model Conversion # # Handles default conversions: to_model, to_key, to_param, and to_partial_path. # diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f08049a443..a7b5ba9f3e 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,40 @@ ## Rails 4.0.0 (unreleased) ## +* Warn when `rake db:structure:dump` with a mysl database and + `mysqldump` is not in the PATH or fails. + Fixes #9518. + + *Yves Senn* + +* Remove `connection#structure_dump`, which is no longer used. *Yves Senn* + +* Make it possible to execute migrations without a transaction even + if the database adapter supports DDL transactions. + Fixes #9483. + + Example: + + class ChangeEnum < ActiveRecord::Migration + self.disable_ddl_transaction! + def up + execute "ALTER TYPE model_size ADD VALUE 'new_value'" + end + end + + *Yves Senn* + +* Assigning "0.0" to a nullable numeric column does not make it dirty. + Fix #9034. + + Example: + + product = Product.create price: 0.0 + product.price = '0.0' + product.changed? # => false (this used to return true) + product.changes # => {} (this used to return { price: [0.0, 0.0] }) + + *Yves Senn* + * Added functionality to unscope relations in a relations chain. For instance, if you are passed in a chain of relations as follows: diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc index 9fc6785d99..ed1e171d58 100644 --- a/activerecord/README.rdoc +++ b/activerecord/README.rdoc @@ -130,7 +130,7 @@ A short rundown of some of the major features: SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html]. -* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]. +* Logging support for Log4r[http://log4r.rubyforge.org] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]. ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT) ActiveRecord::Base.logger = Log4r::Logger.new('Application Log') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 513d1012ba..35e4eb19a4 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1115,7 +1115,7 @@ module ActiveRecord # :dependent behavior may affect other callbacks. # # * <tt>:destroy</tt> causes all the associated objects to also be destroyed - # * <tt>:delete_all</tt> causes all the asssociated objects to be deleted directly from the database (so callbacks will not execute) + # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not execute) # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed. # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 616ae1631f..6315dd9549 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -107,7 +107,11 @@ module ActiveRecord def changes_from_zero_to_string?(old, value) # For columns with old 0 and value non-empty string - old == 0 && value.is_a?(String) && value.present? && value != '0' + old == 0 && value.is_a?(String) && value.present? && non_zero?(value) + end + + def non_zero?(value) + value !~ /\A0+(\.0+)?\z/ end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 0cce8c7596..5b8de184fe 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -516,11 +516,6 @@ module ActiveRecord end alias :remove_belongs_to :remove_reference - # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the - # entire structure of the database. - def structure_dump - end - def dump_schema_information #:nodoc: sm_table = ActiveRecord::Migrator.schema_migrations_table_name diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 9826b18053..be4a30aed9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -332,20 +332,6 @@ module ActiveRecord # SCHEMA STATEMENTS ======================================== - def structure_dump #:nodoc: - if supports_views? - sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'" - else - sql = "SHOW TABLES" - end - - select_all(sql, 'SCHEMA').map { |table| - table.delete('Table_type') - sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}" - exec_query(sql, 'SCHEMA').first['Create Table'] + ";\n\n" - }.join - end - # Drops the database specified on the +name+ attribute # and creates it again using the provided +options+. def recreate_database(name, options = {}) diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index b2a9a54af1..3135465dfe 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -6,7 +6,8 @@ module ActiveRecord def collecting_queries_for_explain # :nodoc: current = Thread.current original, current[:available_queries_for_explain] = current[:available_queries_for_explain], [] - return yield, current[:available_queries_for_explain] + yield + return current[:available_queries_for_explain] ensure # Note that the return value above does not depend on this assigment. current[:available_queries_for_explain] = original diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 823595a128..62e8881c4c 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -330,6 +330,23 @@ module ActiveRecord # # For a list of commands that are reversible, please see # <tt>ActiveRecord::Migration::CommandRecorder</tt>. + # + # == Transactional Migrations + # + # If the database adapter supports DDL transactions, all migrations will + # automatically be wrapped in a transaction. There are queries that you + # can't execute inside a transaction though, and for these situations + # you can turn the automatic transactions off. + # + # class ChangeEnum < ActiveRecord::Migration + # self.disable_ddl_transaction! + # def up + # execute "ALTER TYPE model_size ADD VALUE 'new_value'" + # end + # end + # + # Remember that you can still open your own transactions, even if you + # are in a Migration with <tt>self.disable_ddl_transaction!</tt>. class Migration autoload :CommandRecorder, 'active_record/migration/command_recorder' @@ -351,6 +368,7 @@ module ActiveRecord class << self attr_accessor :delegate # :nodoc: + attr_accessor :disable_ddl_transaction # :nodoc: end def self.check_pending! @@ -365,8 +383,16 @@ module ActiveRecord new.migrate direction end - cattr_accessor :verbose + # Disable DDL transactions for this migration. + def self.disable_ddl_transaction! + @disable_ddl_transaction = true + end + + def disable_ddl_transaction # :nodoc: + self.class.disable_ddl_transaction + end + cattr_accessor :verbose attr_accessor :name, :version def initialize(name = self.class.name, version = nil) @@ -375,8 +401,8 @@ module ActiveRecord @connection = nil end + self.verbose = true # instantiate the delegate object after initialize is defined - self.verbose = true self.delegate = new # Reverses the migration commands for the given block and @@ -663,7 +689,7 @@ module ActiveRecord File.basename(filename) end - delegate :migrate, :announce, :write, :to => :migration + delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration private @@ -856,12 +882,12 @@ module ActiveRecord Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger begin - ddl_transaction do + ddl_transaction(migration) do migration.migrate(@direction) record_version_state_after_migrating(migration.version) end rescue => e - canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : "" + canceled_msg = use_transaction?(migration) ? "this and " : "" raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace end end @@ -935,12 +961,16 @@ module ActiveRecord end # Wrap the migration in a transaction only if supported by the adapter. - def ddl_transaction - if Base.connection.supports_ddl_transactions? + def ddl_transaction(migration) + if use_transaction?(migration) Base.transaction { yield } else yield end end + + def use_transaction?(migration) + !migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions? + end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index efbae108b9..ad54ba55b6 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -188,8 +188,7 @@ module ActiveRecord # Please see further details in the # {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain]. def explain - _, queries = collecting_queries_for_explain { exec_queries } - exec_explain(queries) + exec_explain(collecting_queries_for_explain { exec_queries }) end # Converts relation objects to Array. diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index e5b1be24f8..b7960936cf 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -5,7 +5,7 @@ module ActiveRecord extend ActiveSupport::Concern # WhereChain objects act as placeholder for queries in which #where does not have any parameter. - # In this case, #where must be chained with either #not, #like, or #not_like to return a new relation. + # In this case, #where must be chained with #not to return a new relation. class WhereChain def initialize(scope) @scope = scope diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb index 6b55af4205..bd9079b596 100644 --- a/activerecord/lib/active_record/serialization.rb +++ b/activerecord/lib/active_record/serialization.rb @@ -5,7 +5,7 @@ module ActiveRecord #:nodoc: include ActiveModel::Serializers::JSON included do - self.include_root_in_json = true + self.include_root_in_json = false end def serializable_hash(options = nil) diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index 17378969a5..10696258c9 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -57,7 +57,10 @@ module ActiveRecord args.concat(["--result-file", "#{filename}"]) args.concat(["--no-data"]) args.concat(["#{configuration['database']}"]) - Kernel.system(*args) + unless Kernel.system(*args) + $stderr.puts "Could not dump the database structure. "\ + "Make sure `mysqldump` is in your PATH and check the command output for warnings." + end end def structure_load(filename) diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb index 5164acf77f..4cf4bc4c61 100644 --- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb @@ -63,11 +63,6 @@ class MysqlReservedWordTest < ActiveRecord::TestCase assert_nothing_raised { @connection.rename_column(:group, :order, :values) } end - # dump structure of table with reserved word name - def test_structure_dump - assert_nothing_raised { @connection.structure_dump } - end - # introspect table with reserved word name def test_introspect assert_nothing_raised { @connection.columns(:group) } diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 1265cb927e..fedd9f603c 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -70,12 +70,6 @@ class MysqlConnectionTest < ActiveRecord::TestCase end end - def test_logs_name_structure_dump - @connection.structure_dump - assert_equal "SCHEMA", @connection.logged[0][1] - assert_equal "SCHEMA", @connection.logged[2][1] - end - def test_logs_name_show_variable @connection.show_variable 'foo' assert_equal "SCHEMA", @connection.logged[0][1] diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index 1017b0758d..98596a7f62 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -63,11 +63,6 @@ class MysqlReservedWordTest < ActiveRecord::TestCase assert_nothing_raised { @connection.rename_column(:group, :order, :values) } end - # dump structure of table with reserved word name - def test_structure_dump - assert_nothing_raised { @connection.structure_dump } - end - # introspect table with reserved word name def test_introspect assert_nothing_raised { @connection.columns(:group) } diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 3a6da0e59f..8664ee99d2 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -21,9 +21,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase :posts, :tags, :taggings, :comments, :sponsors, :members def test_belongs_to - Client.find(3).firm.name - assert_equal companies(:first_firm).name, Client.find(3).firm.name - assert_not_nil Client.find(3).firm, "Microsoft should have a firm" + firm = Client.find(3).firm + assert_not_nil firm + assert_equal companies(:first_firm).name, firm.name end def test_belongs_to_with_primary_key diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index c7d2ba6073..7b2034dadf 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -243,6 +243,21 @@ class DirtyTest < ActiveRecord::TestCase assert !pirate.changed? end + def test_float_zero_to_string_zero_not_marked_as_changed + data = NumericData.new :temperature => 0.0 + data.save! + + assert_not data.changed? + + data.temperature = '0' + assert_empty data.changes + + data.temperature = '0.0' + assert_empty data.changes + + data.temperature = '0.00' + assert_empty data.changes + end def test_zero_to_blank_marked_as_changed pirate = Pirate.new diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb index b1d276f9eb..6dac5db111 100644 --- a/activerecord/test/cases/explain_test.rb +++ b/activerecord/test/cases/explain_test.rb @@ -20,7 +20,7 @@ if ActiveRecord::Base.connection.supports_explain? end def test_collecting_queries_for_explain - result, queries = ActiveRecord::Base.collecting_queries_for_explain do + queries = ActiveRecord::Base.collecting_queries_for_explain do Car.where(:name => 'honda').to_a end @@ -28,7 +28,6 @@ if ActiveRecord::Base.connection.supports_explain? assert_match "SELECT", sql assert_match "honda", sql assert_equal [], binds - assert_equal [cars(:honda)], result end def test_exec_explain_with_no_binds diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb index a86b165c78..a222675918 100644 --- a/activerecord/test/cases/json_serialization_test.rb +++ b/activerecord/test/cases/json_serialization_test.rb @@ -6,7 +6,21 @@ require 'models/tagging' require 'models/tag' require 'models/comment' +module JsonSerializationHelpers + private + + def set_include_root_in_json(value) + original_root_in_json = ActiveRecord::Base.include_root_in_json + ActiveRecord::Base.include_root_in_json = value + yield + ensure + ActiveRecord::Base.include_root_in_json = original_root_in_json + end +end + class JsonSerializationTest < ActiveRecord::TestCase + include JsonSerializationHelpers + class NamespacedContact < Contact column :name, :string end @@ -23,20 +37,24 @@ class JsonSerializationTest < ActiveRecord::TestCase end def test_should_demodulize_root_in_json - @contact = NamespacedContact.new :name => 'whatever' - json = @contact.to_json - assert_match %r{^\{"namespaced_contact":\{}, json + set_include_root_in_json(true) do + @contact = NamespacedContact.new name: 'whatever' + json = @contact.to_json + assert_match %r{^\{"namespaced_contact":\{}, json + end end def test_should_include_root_in_json - json = @contact.to_json - - assert_match %r{^\{"contact":\{}, json - assert_match %r{"name":"Konata Izumi"}, json - assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) - assert_match %r{"awesome":true}, json - assert_match %r{"preferences":\{"shows":"anime"\}}, json + set_include_root_in_json(true) do + json = @contact.to_json + + assert_match %r{^\{"contact":\{}, json + assert_match %r{"name":"Konata Izumi"}, json + assert_match %r{"age":16}, json + assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_match %r{"awesome":true}, json + assert_match %r{"preferences":\{"shows":"anime"\}}, json + end end def test_should_encode_all_encodable_attributes @@ -141,6 +159,8 @@ end class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase fixtures :authors, :posts, :comments, :tags, :taggings + include JsonSerializationHelpers + def setup @david = authors(:david) @mary = authors(:mary) @@ -227,23 +247,21 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase end def test_should_allow_only_option_for_list_of_authors - ActiveRecord::Base.include_root_in_json = false - authors = [@david, @mary] - assert_equal %([{"name":"David"},{"name":"Mary"}]), ActiveSupport::JSON.encode(authors, :only => :name) - ensure - ActiveRecord::Base.include_root_in_json = true + set_include_root_in_json(false) do + authors = [@david, @mary] + assert_equal %([{"name":"David"},{"name":"Mary"}]), ActiveSupport::JSON.encode(authors, only: :name) + end end def test_should_allow_except_option_for_list_of_authors - ActiveRecord::Base.include_root_in_json = false - authors = [@david, @mary] - encoded = ActiveSupport::JSON.encode(authors, :except => [ - :name, :author_address_id, :author_address_extra_id, - :organization_id, :owned_essay_id - ]) - assert_equal %([{"id":1},{"id":2}]), encoded - ensure - ActiveRecord::Base.include_root_in_json = true + set_include_root_in_json(false) do + authors = [@david, @mary] + encoded = ActiveSupport::JSON.encode(authors, except: [ + :name, :author_address_id, :author_address_extra_id, + :organization_id, :owned_essay_id + ]) + assert_equal %([{"id":1},{"id":2}]), encoded + end end def test_should_allow_includes_for_list_of_authors @@ -262,17 +280,21 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase end def test_should_allow_options_for_hash_of_authors - authors_hash = { - 1 => @david, - 2 => @mary - } - assert_equal %({"1":{"author":{"name":"David"}}}), ActiveSupport::JSON.encode(authors_hash, :only => [1, :name]) + set_include_root_in_json(true) do + authors_hash = { + 1 => @david, + 2 => @mary + } + assert_equal %({"1":{"author":{"name":"David"}}}), ActiveSupport::JSON.encode(authors_hash, only: [1, :name]) + end end def test_should_be_able_to_encode_relation - authors_relation = Author.where(:id => [@david.id, @mary.id]) + set_include_root_in_json(true) do + authors_relation = Author.where(id: [@david.id, @mary.id]) - json = ActiveSupport::JSON.encode authors_relation, :only => :name - assert_equal '[{"author":{"name":"David"}},{"author":{"name":"Mary"}}]', json + json = ActiveSupport::JSON.encode authors_relation, only: :name + assert_equal '[{"author":{"name":"David"}},{"author":{"name":"Mary"}}]', json + end end end diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb index ee0c20747e..97efb94b66 100644 --- a/activerecord/test/cases/migration/logger_test.rb +++ b/activerecord/test/cases/migration/logger_test.rb @@ -7,6 +7,7 @@ module ActiveRecord self.use_transactional_fixtures = false Migration = Struct.new(:name, :version) do + def disable_ddl_transaction; false end def migrate direction # do nothing end @@ -34,4 +35,3 @@ module ActiveRecord end end end - diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index fa8dec0e15..960d28fcf5 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -239,9 +239,13 @@ class MigrationTest < ActiveRecord::TestCase assert_not Person.column_methods_hash.include?(:last_name) - migration = Struct.new(:name, :version) { - def migrate(x); raise 'Something broke'; end - }.new('zomg', 100) + migration = Class.new(ActiveRecord::Migration) { + def version; 100 end + def migrate(x) + add_column "people", "last_name", :string + raise 'Something broke' + end + }.new migrator = ActiveRecord::Migrator.new(:up, [migration], 100) @@ -250,7 +254,39 @@ class MigrationTest < ActiveRecord::TestCase assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message Person.reset_column_information + assert_not Person.column_methods_hash.include?(:last_name), + "On error, the Migrator should revert schema changes but it did not." + end + + def test_migration_without_transaction + unless ActiveRecord::Base.connection.supports_ddl_transactions? + skip "not supported on #{ActiveRecord::Base.connection.class}" + end + assert_not Person.column_methods_hash.include?(:last_name) + + migration = Class.new(ActiveRecord::Migration) { + self.disable_ddl_transaction! + + def version; 101 end + def migrate(x) + add_column "people", "last_name", :string + raise 'Something broke' + end + }.new + + migrator = ActiveRecord::Migrator.new(:up, [migration], 101) + e = assert_raise(StandardError) { migrator.migrate } + assert_equal "An error has occurred, all later migrations canceled:\n\nSomething broke", e.message + + Person.reset_column_information + assert Person.column_methods_hash.include?(:last_name), + "without ddl transactions, the Migrator should not rollback on error but it did." + ensure + Person.reset_column_information + if Person.column_methods_hash.include?(:last_name) + Person.connection.remove_column('people', 'last_name') + end end def test_schema_migrations_table_name diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 8156f99037..b936cca875 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -399,14 +399,6 @@ class PersistencesTest < ActiveRecord::TestCase assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') } end - def test_string_ids - # FIXME: Fix this failing test - skip "Failing test. We need this fixed before 4.0.0" - mv = Minivan.where(:minivan_id => 1234).first_or_initialize - assert mv.new_record? - assert_equal '1234', mv.minivan_id - end - def test_update_attribute_with_one_updated t = Topic.first t.update_attribute(:title, 'super_title') diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index eb9cb91e32..c46060a646 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -18,6 +18,10 @@ class SerializationTest < ActiveRecord::TestCase } end + def test_include_root_in_json_is_false_by_default + assert_equal false, ActiveRecord::Base.include_root_in_json, "include_root_in_json should be false by default but was not" + end + def test_serialize_should_be_reversible FORMATS.each do |format| @serialized = Contact.new.send("to_#{format}") diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb index 38b9dd02f0..dadcca5b7f 100644 --- a/activerecord/test/cases/tasks/mysql_rake_test.rb +++ b/activerecord/test/cases/tasks/mysql_rake_test.rb @@ -249,10 +249,21 @@ module ActiveRecord def test_structure_dump filename = "awesome-file.sql" - Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db") + Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db").returns(true) ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename) end + + def test_warn_when_external_structure_dump_fails + filename = "awesome-file.sql" + Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db").returns(false) + + warnings = capture(:stderr) do + ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename) + end + + assert_match(/Could not dump the database structure/, warnings) + end end class MySQLStructureLoadTest < ActiveRecord::TestCase diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index c47cb75274..9d26b8ba3e 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,11 +1,24 @@ ## Rails 4.0.0 (unreleased) ## * Fix deletion of empty directories in ActiveSupport::Cache::FileStore. - + *Charles Jones* ## Rails 4.0.0.beta1 (February 25, 2013) ## +* Improve singularizing a singular for multiple cases. + Fixes #2608 #1825 #2395. + + Example: + + # Before + 'address'.singularize # => 'addres' + + # After + 'address'.singularize # => 'address' + + *Mark McSpadden* + * Prevent `DateTime#change` from truncating the second fraction, when seconds do not need to be changed. diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index e3e1845868..f2d9df6d13 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -255,7 +255,6 @@ module ActiveSupport # a method is created that calls the before_foo method # on the object. def _compile_filter(filter) - method_name = "_callback_#{@kind}_#{next_id}" case filter when Array filter.map {|f| _compile_filter(f)} @@ -264,11 +263,13 @@ module ActiveSupport when String "(#{filter})" when Proc + method_name = "_callback_#{@kind}_#{next_id}" @klass.send(:define_method, method_name, &filter) return method_name if filter.arity <= 0 method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ") else + method_name = "_callback_#{@kind}_#{next_id}" @klass.send(:define_method, "#{method_name}_object") { filter } _normalize_legacy_filter(kind, filter) diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 5f85cedcf5..dc033ed11b 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -42,7 +42,7 @@ class ERB # html_escape_once('<< Accept & Checkout') # # => "<< Accept & Checkout" def html_escape_once(s) - result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP) { |special| HTML_ESCAPE[special] } + result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE) s.html_safe? ? result.html_safe : result end @@ -60,7 +60,7 @@ class ERB # json_escape('{"name":"john","created_at":"2010-04-28T01:39:31Z","id":1}') # # => {name:john,created_at:2010-04-28T01:39:31Z,id:1} def json_escape(s) - result = s.to_s.gsub(JSON_ESCAPE_REGEXP) { |special| JSON_ESCAPE[special] } + result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE) s.html_safe? ? result.html_safe : result end diff --git a/guides/.document b/guides/.document new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/guides/.document diff --git a/guides/code/getting_started/app/controllers/comments_controller.rb b/guides/code/getting_started/app/controllers/comments_controller.rb index 0082e9c8ec..0e3d2a6dde 100644 --- a/guides/code/getting_started/app/controllers/comments_controller.rb +++ b/guides/code/getting_started/app/controllers/comments_controller.rb @@ -1,7 +1,7 @@ class CommentsController < ApplicationController - http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy - + http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy + def create @post = Post.find(params[:post_id]) @comment = @post.comments.create(params[:comment].permit(:commenter, :body)) diff --git a/guides/code/getting_started/app/controllers/posts_controller.rb b/guides/code/getting_started/app/controllers/posts_controller.rb index 0398395200..6aa1409170 100644 --- a/guides/code/getting_started/app/controllers/posts_controller.rb +++ b/guides/code/getting_started/app/controllers/posts_controller.rb @@ -1,7 +1,7 @@ class PostsController < ApplicationController - http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show] - + http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show] + def index @posts = Post.all end diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index e65f7e5b18..5861fc3d54 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -269,6 +269,27 @@ permitted scalar values allowed), a `hobbies` attribute as an array of permitted scalar values, and a `family` attribute which is restricted to having a `name` (any permitted scalar values allowed, too). +#### More Examples + +You want to also use the permitted attributes in the `new` +action. This raises the problem that you can't use `require` on the +root-key because normally it does not exist when calling `new`: + +```ruby +# using `fetch` you can supply a default and use +# the Strong Parameters API from there. +params.fetch(:blog, {}).permit(:title, :author) +``` + +`accepts_nested_attributes_for` allows you update and destroy the +associated records. This is based on the `id` and `_destroy` +parameters: + +```ruby +# permit :id and :_destroy +params.require(:author).permit(:name, books_attributes: [:title, :id, :_destroy]) +``` + #### Outside the Scope of Strong Parameters The strong parameter API was designed with the most common use cases diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index dd59e2a8df..cb0a7c8026 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -845,7 +845,7 @@ Counter cache columns are added to the containing model's list of read-only attr ##### `:dependent` -If you set the `:dependent` option to `:destroy`, then deleting this object will call the `destroy` method on the associated object to delete that object. If you set the `:dependent` option to `:delete`, then deleting this object will delete the associated object _without_ calling its `destroy` method. +If you set the `:dependent` option to `:destroy`, then deleting this object will call the `destroy` method on the associated object to delete that object. If you set the `:dependent` option to `:delete`, then deleting this object will delete the associated object _without_ calling its `destroy` method. If you set the `:dependent` option to `:restrict`, then attempting to delete this object will result in a `ActiveRecord::DeleteRestrictionError` if there are any associated objects. WARNING: You should not specify this option on a `belongs_to` association that is connected with a `has_many` association on the other class. Doing so can lead to orphaned records in your database. diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 7909a00c47..cc4e369e7d 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -250,8 +250,6 @@ Your name can be added directly after the last word if you don't provide any cod You should not be the only person who looks at the code before you submit it. You know at least one other Rails developer, right? Show them what you’re doing and ask for feedback. Doing this in private before you push a patch out publicly is the “smoke test” for a patch: if you can’t convince one other developer of the beauty of your code, you’re unlikely to convince the core team either. -You might want also to check out the [RailsBridge BugMash](http://wiki.railsbridge.org/projects/railsbridge/wiki/BugMash) as a way to get involved in a group effort to improve Rails. This can help you get started and help you check your code when you're writing your first patches. - ### Commit Your Changes When you're happy with the code on your computer, you need to commit the changes to Git: diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 87f5e43157..a1d7e955c8 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -208,7 +208,7 @@ create app/assets/stylesheets/welcome.css.scss Most important of these are of course the controller, located at `app/controllers/welcome_controller.rb` and the view, located at `app/views/welcome/index.html.erb`. -Open the `app/views/welcome/index.html.erb` file in your text editor and edit it to contain a single line of code: +Open the `app/views/welcome/index.html.erb` file in your text editor. Delete all of the existing code in the file, and replace it with the following single line of code: ```html <h1>Hello, Rails!</h1> @@ -278,7 +278,7 @@ With the route defined, requests can now be made to `/posts/new` in the applicat ![Another routing error, uninitialized constant PostsController](images/getting_started/routing_error_no_controller.png) -This error is happening because this route need a controller to be defined. The route is attempting to find that controller so it can serve the request, but with the controller undefined, it just can't do that. The solution to this particular problem is simple: you need to create a controller called `PostsController`. You can do this by running this command: +This error occurs because the route needs to have a controller defined in order to serve the request. The solution to this particular problem is simple: create a controller called `PostsController`. You can do this by running this command: ```bash $ rails g controller posts diff --git a/guides/source/migrations.md b/guides/source/migrations.md index 89ae564c24..bd63970bea 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -61,6 +61,10 @@ migrations are wrapped in a transaction. If the database does not support this then when a migration fails the parts of it that succeeded will not be rolled back. You will have to rollback the changes that were made by hand. +NOTE: There are certain queries that can't run inside a transaction. If your +adapter supports DDL transactions you can use `disable_ddl_transaction!` to +disable them for a single migration. + If you wish for a migration to do something that Active Record doesn't know how to reverse, you can use `reversible`: @@ -180,7 +184,7 @@ end ``` If the migration name is of the form "CreateXXX" and is -followed by a list of column names and types then a migration creating the table +followed by a list of column names and types then a migration creating the table XXX with the columns listed will be generated. For example: ```bash |