From 08525e3ef172873a5fa525b27f445012d9e226c3 Mon Sep 17 00:00:00 2001 From: Tim Ruffles Date: Fri, 26 Jul 2013 16:47:18 +0100 Subject: be more specific about csrf token and ajax - not whitelisted outside of jquery-rails [ci skip] --- actionview/lib/action_view/helpers/csrf_helper.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/actionview/lib/action_view/helpers/csrf_helper.rb b/actionview/lib/action_view/helpers/csrf_helper.rb index eeb0ed94b9..5af92c4ff2 100644 --- a/actionview/lib/action_view/helpers/csrf_helper.rb +++ b/actionview/lib/action_view/helpers/csrf_helper.rb @@ -12,8 +12,11 @@ module ActionView # These are used to generate the dynamic forms that implement non-remote links with # :method. # - # Note that regular forms generate hidden fields, and that Ajax calls are whitelisted, - # so they do not use these tags. + # You don't need to use these tags for regular forms as they generate their own hidden fields. + # + # For AJAX requests other than GETs, extract the "csrf-token" from the meta-tag and send as the + # "X-CSRF-Token" HTTP header. If you are using jQuery with jquery-rails this happens automatically. + # def csrf_meta_tags if protect_against_forgery? [ -- cgit v1.2.3 From 0aad463cfbe9853fd3a7ab0f8e4e0a34715fd62c Mon Sep 17 00:00:00 2001 From: Marc-Andre Lafortune Date: Thu, 5 Dec 2013 12:20:03 -0500 Subject: `find_in_batches` now returns an `Enumerator` when called without a block, so that it can be chained with other `Enumerable` methods. --- activerecord/CHANGELOG.md | 5 +++++ activerecord/lib/active_record/relation/batches.rb | 9 +++++++++ activerecord/test/cases/batches_test.rb | 13 +++++++++++++ 3 files changed, 27 insertions(+) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index ed076c04bb..1386c5d740 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,8 @@ +* `find_in_batches` now returns an `Enumerator` when called without a block, so that it + can be chained with other `Enumerable` methods. + + *Marc-André Lafortune* + * Polymorphic belongs_to associations with the `touch: true` option set update the timestamps of the old and new owner correctly when moved between owners of different types. diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 49b01909c6..87d6f12aa5 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -64,6 +64,14 @@ module ActiveRecord # group.each { |person| person.party_all_night! } # end # + # If you do not provide a block to #find_in_batches, it will return an Enumerator + # for chaining with other methods: + # + # Person.find_in_batches.with_index do |group, batch| + # puts "Processing group ##{batch}" + # group.each(&:recover_from_last_night!) + # end + # # ==== Options # * :batch_size - Specifies the size of the batch. Default to 1000. # * :start - Specifies the starting point for the batch processing. @@ -86,6 +94,7 @@ module ActiveRecord # the batch sizes. def find_in_batches(options = {}) options.assert_valid_keys(:start, :batch_size) + return to_enum(:find_in_batches, options) unless block_given? relation = self diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 38c2560d69..3b94d46710 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -170,4 +170,17 @@ class EachTest < ActiveRecord::TestCase end end end + + def test_find_in_batches_should_return_an_enumerator + enum = nil + assert_queries(0) do + enum = Post.find_in_batches(:batch_size => 1) + end + assert_queries(4) do + enum.first(4) do |batch| + assert_kind_of Array, batch + assert_kind_of Post, batch.first + end + end + end end -- cgit v1.2.3 From 8dd4aca4850c678f96d0a72098b3a080b51dea44 Mon Sep 17 00:00:00 2001 From: "Birkir A. Barkarson" Date: Wed, 13 Nov 2013 12:31:02 +0900 Subject: Fix breakage in XmlMini - Boolean parsing breaks on non strings (i.e. integer 1|0) - Symbol parsing breaks on non strings. - BigDecimal parsing breaks due to missing require. - Update changelog. --- activesupport/CHANGELOG.md | 7 ++ activesupport/lib/active_support/xml_mini.rb | 6 +- activesupport/test/xml_mini_test.rb | 125 +++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 2 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 104b5eafa1..944d8a0d1b 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,10 @@ +* Fix parsing bugs in `XmlMini` + + Symbols or boolean parsing would raise an error for non string values (e.g. + integers). Decimal parsing would fail due to a missing requirement. + + *Birkir A. Barkarson* + * Add `ActiveSupport::Testing::TimeHelpers#travel` and `#travel_to`. These methods change current time to the given time or time difference by stubbing `Time.now` and `Date.today` to return the time or date after the difference calculation, or the time or date that got passed into the diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index d082a0a499..009ee4db90 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -1,7 +1,9 @@ require 'time' require 'base64' +require 'bigdecimal' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/date_time/calculations' module ActiveSupport # = XmlMini @@ -56,13 +58,13 @@ module ActiveSupport # TODO use regexp instead of Date.parse unless defined?(PARSING) PARSING = { - "symbol" => Proc.new { |symbol| symbol.to_sym }, + "symbol" => Proc.new { |symbol| symbol.to_s.to_sym }, "date" => Proc.new { |date| ::Date.parse(date) }, "datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc }, "integer" => Proc.new { |integer| integer.to_i }, "float" => Proc.new { |float| float.to_f }, "decimal" => Proc.new { |number| BigDecimal(number) }, - "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.strip) }, + "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) }, "string" => Proc.new { |string| string.to_s }, "yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml }, "base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) }, diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb index d992028323..262e5e35c6 100644 --- a/activesupport/test/xml_mini_test.rb +++ b/activesupport/test/xml_mini_test.rb @@ -169,4 +169,129 @@ module XmlMiniTest end end end + + class ParsingTest < ActiveSupport::TestCase + def setup + @parsing = ActiveSupport::XmlMini::PARSING + end + + def test_symbol + parser = @parsing['symbol'] + assert_equal :symbol, parser.call('symbol') + assert_equal :symbol, parser.call(:symbol) + assert_equal :'123', parser.call(123) + assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } + end + + def test_date + parser = @parsing['date'] + assert_equal Date.new(2013,11,12), parser.call("2013-11-12T0211Z") + assert_raises(TypeError) { parser.call(1384190018) } + assert_raises(ArgumentError) { parser.call("not really a date") } + end + + def test_datetime + parser = @parsing['datetime'] + assert_equal Time.new(2013,11,12,02,11,00,0), parser.call("2013-11-12T02:11:00Z") + assert_equal DateTime.new(2013,11,12), parser.call("2013-11-12T0211Z") + assert_equal DateTime.new(2013,11,12,02,11), parser.call("2013-11-12T02:11Z") + assert_equal DateTime.new(2013,11,12,02,11), parser.call("2013-11-12T11:11+9") + assert_raises(ArgumentError) { parser.call("1384190018") } + end + + def test_integer + parser = @parsing['integer'] + assert_equal 123, parser.call(123) + assert_equal 123, parser.call(123.003) + assert_equal 123, parser.call("123") + assert_equal 0, parser.call("") + assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } + end + + def test_float + parser = @parsing['float'] + assert_equal 123, parser.call("123") + assert_equal 123.003, parser.call("123.003") + assert_equal 123.0, parser.call("123,003") + assert_equal 0.0, parser.call("") + assert_equal 123, parser.call(123) + assert_equal 123.05, parser.call(123.05) + assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } + end + + def test_decimal + parser = @parsing['decimal'] + assert_equal 123, parser.call("123") + assert_equal 123.003, parser.call("123.003") + assert_equal 123.0, parser.call("123,003") + assert_equal 0.0, parser.call("") + assert_equal 123, parser.call(123) + assert_raises(ArgumentError) { parser.call(123.04) } + assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } + end + + def test_boolean + parser = @parsing['boolean'] + [1, true, "1"].each do |value| + assert parser.call(value) + end + + [0, false, "0"].each do |value| + assert_not parser.call(value) + end + end + + def test_string + parser = @parsing['string'] + assert_equal "123", parser.call(123) + assert_equal "123", parser.call("123") + assert_equal "[]", parser.call("[]") + assert_equal "[]", parser.call([]) + assert_equal "{}", parser.call({}) + assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) } + end + + def test_yaml + yaml = < [ + {"sku"=>"BL394D", "quantity"=>4, "description"=>"Basketball"} + ] + } + parser = @parsing['yaml'] + assert_equal(expected, parser.call(yaml)) + assert_equal({1 => 'test'}, parser.call({1 => 'test'})) + assert_equal({"1 => 'test'"=>nil}, parser.call("{1 => 'test'}")) + end + + def test_base64Binary_and_binary + base64 = < 'base64') + assert_equal "IGNORED INPUT", parser.call("IGNORED INPUT", {}) + end + + end end -- cgit v1.2.3 From 6c252b5cb9efaf291359275e106116c7746d8915 Mon Sep 17 00:00:00 2001 From: seapy Date: Wed, 8 Jan 2014 21:57:25 +0900 Subject: Fixed typo [ci skip] singular is more suited to --- guides/source/layouts_and_rendering.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index c2d9772063..9788ebdc32 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -1121,11 +1121,11 @@ With this change, you can access an instance of the `@products` collection as th You can also pass in arbitrary local variables to any partial you are rendering with the `locals: {}` option: ```erb -<%= render partial: "products", collection: @products, +<%= render partial: "product", collection: @products, as: :item, locals: {title: "Products Page"} %> ``` -Would render a partial `_products.html.erb` once for each instance of `product` in the `@products` instance variable passing the instance to the partial as a local variable called `item` and to each partial, make the local variable `title` available with the value `Products Page`. +Would render a partial `_product.html.erb` once for each instance of `product` in the `@products` instance variable passing the instance to the partial as a local variable called `item` and to each partial, make the local variable `title` available with the value `Products Page`. TIP: Rails also makes a counter variable available within a partial called by the collection, named after the member of the collection followed by `_counter`. For example, if you're rendering `@products`, within the partial you can refer to `product_counter` to tell you how many times the partial has been rendered. This does not work in conjunction with the `as: :value` option. -- cgit v1.2.3 From 6a45a690a2626fa4a8ccefaa8be3fc0a10076ba5 Mon Sep 17 00:00:00 2001 From: Waynn Lue Date: Wed, 8 Jan 2014 18:02:25 -0800 Subject: change to "check out" --- actionpack/RUNNING_UNIT_TESTS.rdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/RUNNING_UNIT_TESTS.rdoc b/actionpack/RUNNING_UNIT_TESTS.rdoc index 08767ae133..3566821362 100644 --- a/actionpack/RUNNING_UNIT_TESTS.rdoc +++ b/actionpack/RUNNING_UNIT_TESTS.rdoc @@ -1,7 +1,7 @@ == Running with Rake The easiest way to run the unit tests is through Rake. The default task runs -the entire test suite for all classes. For more information, checkout the +the entire test suite for all classes. For more information, check out the full array of rake tasks with "rake -T" Rake can be found at http://rake.rubyforge.org -- cgit v1.2.3 From 755fd79ff36fad1e41d93e6e49c3f5e486f6745a Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 13 Jan 2014 15:42:10 +0100 Subject: setup Bundler in engines `bin/rails` stub. This is necessary when bundling gems locally using `BUNDLE_PATH`. Without this patch `bin/rails` fails with: ``` /Users/senny/.rbenv/versions/2.0.0-p353/lib/ruby/site_ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- rails/all (LoadError) from /Users/senny/.rbenv/versions/2.0.0-p353/lib/ruby/site_ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require' from bin/rails:7:in `
' ``` --- railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt index c8de9f3e0f..3ea6c6d7d4 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt @@ -3,5 +3,9 @@ ENGINE_ROOT = File.expand_path('../..', __FILE__) ENGINE_PATH = File.expand_path('../../lib/<%= name -%>/engine', __FILE__) +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) + require 'rails/all' require 'rails/engine/commands' -- cgit v1.2.3 From 4dbc62dabd9decbeed74f30f36062355e10def6b Mon Sep 17 00:00:00 2001 From: Waynn Lue Date: Mon, 13 Jan 2014 11:56:00 -0800 Subject: e-mail => email, and subject/verb agreement --- actionmailer/README.rdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc index c3dcd3c3e4..e425282fa8 100644 --- a/actionmailer/README.rdoc +++ b/actionmailer/README.rdoc @@ -74,7 +74,7 @@ Or you can just chain the methods together like: == Setting defaults -It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method default which you get for free from ActionMailer::Base. This method accepts a Hash as the parameter. You can use any of the headers e-mail messages has, like :from as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally, it is also possible to pass in a Proc that will get evaluated when it is needed. +It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method default which you get for free from ActionMailer::Base. This method accepts a Hash as the parameter. You can use any of the headers email messages have, like :from as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally, it is also possible to pass in a Proc that will get evaluated when it is needed. Note that every value you set with this method will get overwritten if you use the same key in your mailer method. -- cgit v1.2.3 From 6900b3e6ed05b48da360122595b59856f36601eb Mon Sep 17 00:00:00 2001 From: Waynn Lue Date: Mon, 13 Jan 2014 12:15:52 -0800 Subject: add punctuation --- actionpack/RUNNING_UNIT_TESTS.rdoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/RUNNING_UNIT_TESTS.rdoc b/actionpack/RUNNING_UNIT_TESTS.rdoc index 3566821362..ad1448f61b 100644 --- a/actionpack/RUNNING_UNIT_TESTS.rdoc +++ b/actionpack/RUNNING_UNIT_TESTS.rdoc @@ -2,9 +2,9 @@ The easiest way to run the unit tests is through Rake. The default task runs the entire test suite for all classes. For more information, check out the -full array of rake tasks with "rake -T" +full array of rake tasks with "rake -T". -Rake can be found at http://rake.rubyforge.org +Rake can be found at http://rake.rubyforge.org. == Running by hand -- cgit v1.2.3 From 3d5c4a16472edadb158eb879bb5e7ef30403a93c Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Thu, 16 Jan 2014 11:06:10 -0800 Subject: Remove duplicate configuration option for ActiveSupport [ci skip] Fixes rails/rails#13732 --- guides/source/configuring.md | 1 - 1 file changed, 1 deletion(-) diff --git a/guides/source/configuring.md b/guides/source/configuring.md index c30b806907..78dc84180c 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -449,7 +449,6 @@ There are a few configuration options available in Active Support: * `ActiveSupport::Deprecation.silenced` sets whether or not to display deprecation warnings. -* `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`. ### Configuring a Database -- cgit v1.2.3 From a099d7d97f80236185f1994a76a4366b2a5e21ab Mon Sep 17 00:00:00 2001 From: Jonathan Baudanza Date: Sun, 12 Jan 2014 17:53:16 -0500 Subject: psql implementation of #index_name_exists? --- activerecord/CHANGELOG.md | 10 ++++++++++ .../connection_adapters/postgresql/schema_statements.rb | 13 +++++++++++++ activerecord/test/cases/adapters/postgresql/schema_test.rb | 12 ++++++++++++ 3 files changed, 35 insertions(+) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index d9f8ee7097..e526343a22 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,13 @@ +* PostgreSQL implementation of SchemaStatements#index_name_exists? + + The database agnostic implementation does not detect with indexes that are + not supported by the ActiveRecord schema dumper. For example, expressions + indexes would not be detected. + + This fixes #11018. + + *Jonathan Baudanza* + * Currently Active Record can be configured via the environment variable `DATABASE_URL` or by manually injecting a hash of values which is what Rails does, reading in `database.yml` and setting Active Record appropriately. Active Record diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 571257f6dd..ae8ede4b42 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -126,6 +126,19 @@ module ActiveRecord SQL end + def index_name_exists?(table_name, index_name, default) + exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 + SELECT COUNT(*) + FROM pg_class t + INNER JOIN pg_index d ON t.oid = d.indrelid + INNER JOIN pg_class i ON d.indexrelid = i.oid + WHERE i.relkind = 'i' + AND i.relname = '#{index_name}' + AND t.relname = '#{table_name}' + AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) ) + SQL + end + # Returns an array of indexes for the given table. def indexes(table_name, name = nil) result = query(<<-SQL, 'SCHEMA') diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index e8dd188ec8..c67997c5a1 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -240,6 +240,18 @@ class SchemaTest < ActiveRecord::TestCase assert_nothing_raised { with_schema_search_path nil } end + def test_index_name_exists + with_schema_search_path(SCHEMA_NAME) do + assert @connection.index_name_exists?(TABLE_NAME, INDEX_A_NAME, true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_B_NAME, true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_C_NAME, true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_D_NAME, true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true) + assert_not @connection.index_name_exists?(TABLE_NAME, 'missing_index', true) + end + end + def test_dump_indexes_for_schema_one do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN) end -- cgit v1.2.3 From 0e9144dcecb1a21627ec66ca3578308c43368ee9 Mon Sep 17 00:00:00 2001 From: Anna Carey Date: Fri, 17 Jan 2014 16:49:10 -0500 Subject: set encoding to binmode for pipe --- activerecord/test/cases/base_test.rb | 2 ++ activerecord/test/cases/connection_management_test.rb | 2 ++ activesupport/lib/active_support/testing/isolation.rb | 2 ++ 3 files changed, 6 insertions(+) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 983bcd9826..ef1ebbb400 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1380,6 +1380,8 @@ class BasicsTest < ActiveRecord::TestCase }) rd, wr = IO.pipe + rd.binmode + wr.binmode ActiveRecord::Base.connection_handler.clear_all_connections! diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb index 00667cc52e..77d9ae9b8e 100644 --- a/activerecord/test/cases/connection_management_test.rb +++ b/activerecord/test/cases/connection_management_test.rb @@ -31,6 +31,8 @@ module ActiveRecord object_id = ActiveRecord::Base.connection.object_id rd, wr = IO.pipe + rd.binmode + wr.binmode pid = fork { rd.close diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index 75ead48376..908af176be 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -37,6 +37,8 @@ module ActiveSupport module Forking def run_in_isolation(&blk) read, write = IO.pipe + read.binmode + write.binmode pid = fork do read.close -- cgit v1.2.3 From bf89bfdc344cd14967432db9a5196609a3fb93d6 Mon Sep 17 00:00:00 2001 From: Charlie Jade Date: Sat, 18 Jan 2014 12:14:21 +0800 Subject: Correct the version number to 4 in guides[ci skip] --- guides/source/documents.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index ae47744e31..e4653b47fc 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -117,7 +117,7 @@ name: The Rails Initialization Process work_in_progress: true url: initialization.html - description: This guide explains the internals of the Rails initialization process as of Rails 3.1 + description: This guide explains the internals of the Rails initialization process as of Rails 4 - name: Extending Rails documents: -- cgit v1.2.3 From 7386ffc781fca07a0c656db49fdb54678caef809 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Sat, 18 Jan 2014 04:36:45 -0800 Subject: Restore ActiveRecord states after a rollback for models w/o callbacks This fixes a regression (#13744) that was caused by 67d8bb9. In 67d8bb9, we introduced lazy rollback for records, such that the record's internal states and attributes are not restored immediately after a transaction rollback, but deferred until they are first accessed. This optimization is only performed when the model does not have any transactional callbacks (e.g. `after_commit` and `after_create`). Unfortunately, the models used to test the affected codepaths all comes with some sort of transactional callbacks. Therefore this codepath remains largely untested until now and as a result there are a few issues in the implementation that remains hidden until now. First, the `sync_with_transaction_state` (or more accurately, `update_attributes_from_transaction_state`) would perform the synchronization prematurely before a transaction is finalized (i.e. comitted or rolled back). As a result, when the actuall rollback happens, the record will incorrectly assumes that its internal states match the transaction state, and neglect to perform the restore. Second, `update_attributes_from_transaction_state` calls `committed!` in some cases. This in turns checks for the `destroyed?` state which also requires synchronization with the transaction stae, which causes an infnite recurrsion. This fix works by deferring the synchronization until the transaction has been finalized (addressing the first point), and also unrolled the `committed!` and `rolledback!` logic in-place (addressing the second point). It should be noted that the primary purpose of the `committed!` and `rolledback!` methods are to trigger the relevant transactional callbacks. Since this code path is only entered when there are no transactional callbacks on the model, this shouldn't be necessary. By unrolling the method calls, the intention here (to restore the states when necessary) becomes more clear. --- activerecord/CHANGELOG.md | 8 ++++++++ .../active_record/connection_adapters/abstract/transaction.rb | 4 ++++ activerecord/lib/active_record/core.rb | 9 +++------ activerecord/test/cases/transactions_test.rb | 11 +++++++++++ 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index d289f616b8..304c48bf44 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,11 @@ +* ActiveRecord states are now correctly restored after a rollback for + models that did not define any transactional callbacks (i.e. + `after_commit`, `after_rollback` or `after_create`). + + Fixes #13744. + + *Godfrey Chan* + * Make `touch` fire the `after_commit` and `after_rollback` callbacks. *Harry Brundage* diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 2b6685499a..bc4884b538 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -23,6 +23,10 @@ module ActiveRecord @parent = nil end + def finalized? + @state + end + def committed? @state == :committed end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index cd8690d500..a4fe1efd33 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -397,13 +397,10 @@ module ActiveRecord end def update_attributes_from_transaction_state(transaction_state, depth) - if transaction_state && !has_transactional_callbacks? + if transaction_state && transaction_state.finalized? && !has_transactional_callbacks? unless @reflects_state[depth] - if transaction_state.committed? - committed! - elsif transaction_state.rolledback? - rolledback! - end + restore_transaction_record_state if transaction_state.rolledback? + clear_transaction_record_state @reflects_state[depth] = true end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 89dab16975..1664f1a096 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -430,17 +430,26 @@ class TransactionTest < ActiveRecord::TestCase end def test_restore_active_record_state_for_all_records_in_a_transaction + topic_without_callbacks = Class.new(ActiveRecord::Base) do + self.table_name = 'topics' + end + topic_1 = Topic.new(:title => 'test_1') topic_2 = Topic.new(:title => 'test_2') + topic_3 = topic_without_callbacks.new(:title => 'test_3') + Topic.transaction do assert topic_1.save assert topic_2.save + assert topic_3.save @first.save @second.destroy assert topic_1.persisted?, 'persisted' assert_not_nil topic_1.id assert topic_2.persisted?, 'persisted' assert_not_nil topic_2.id + assert topic_3.persisted?, 'persisted' + assert_not_nil topic_3.id assert @first.persisted?, 'persisted' assert_not_nil @first.id assert @second.destroyed?, 'destroyed' @@ -451,6 +460,8 @@ class TransactionTest < ActiveRecord::TestCase assert_nil topic_1.id assert !topic_2.persisted?, 'not persisted' assert_nil topic_2.id + assert !topic_3.persisted?, 'not persisted' + assert_nil topic_3.id assert @first.persisted?, 'persisted' assert_not_nil @first.id assert !@second.destroyed?, 'not destroyed' -- cgit v1.2.3 From e05c7912646d375a8ceefcc2897bc8d3384dd648 Mon Sep 17 00:00:00 2001 From: Washington Luiz Date: Sat, 18 Jan 2014 20:22:51 -0300 Subject: spring gem moved to rails/spring --- railties/lib/rails/generators/app_base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 1b50569c9e..55709b80ae 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -389,7 +389,7 @@ module Rails def spring_gemfile_entry return [] unless spring_install? - comment = 'Spring speeds up development by keeping your application running in the background. Read more: https://github.com/jonleighton/spring' + comment = 'Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring' GemfileEntry.new('spring', nil, comment, group: :development) end -- cgit v1.2.3 From b20d4453dda6121508a93ebfbe08aafc974abdd7 Mon Sep 17 00:00:00 2001 From: Rashmi Yadav Date: Sun, 19 Jan 2014 10:51:23 +0100 Subject: Spring in now under rails/spring [ci skip] --- guides/code/getting_started/Gemfile | 2 +- guides/source/4_1_release_notes.md | 4 ++-- guides/source/upgrading_ruby_on_rails.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile index a2155c43b9..ecb6e7aa1a 100644 --- a/guides/code/getting_started/Gemfile +++ b/guides/code/getting_started/Gemfile @@ -23,7 +23,7 @@ gem 'jbuilder', '~> 2.0' # bundle exec rake doc:rails generates the API under doc/api. gem 'sdoc', '~> 0.4.0', group: :doc -# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/jonleighton/spring +# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring', group: :development # Use ActiveModel has_secure_password diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 924e5d90db..f408d9fdac 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -64,7 +64,7 @@ Spring is running: ``` Have a look at the -[Spring README](https://github.com/jonleighton/spring/blob/master/README.md) to +[Spring README](https://github.com/rails/spring/blob/master/README.md) to see all available features. See the [Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#spring) @@ -267,7 +267,7 @@ for detailed changes. ### Notable changes * The [Spring application - preloader](https://github.com/jonleighton/spring) is now installed + preloader](https://github.com/rails/spring) is now installed by default for new applications. It uses the development group of the Gemfile, so will not be installed in production. ([Pull Request](https://github.com/rails/rails/pull/12958)) diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index ab8cabe48d..fda4205ed5 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -62,7 +62,7 @@ If you want to use Spring as your application preloader you need to: NOTE: User defined rake tasks will run in the `development` environment by default. If you want them to run in other environments consult the -[Spring README](https://github.com/jonleighton/spring#rake). +[Spring README](https://github.com/rails/spring#rake). ### `config/secrets.yml` -- cgit v1.2.3 From 7671590fe28b2f9c25c3a1468c3f920a492e68e6 Mon Sep 17 00:00:00 2001 From: robertomiranda Date: Sun, 19 Jan 2014 08:46:43 -0500 Subject: Update Changelog, Spring is under rails/spring [ci skip] --- railties/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index da7a4ce59a..84f8ad59fb 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -87,7 +87,7 @@ *Rafael Mendonça França* * The [Spring application - preloader](https://github.com/jonleighton/spring) is now installed + preloader](https://github.com/rails/spring) is now installed by default for new applications. It uses the development group of the Gemfile, so will not be installed in production. -- cgit v1.2.3 From 9040e04c7257125aa18f685fdafb554bf0d35507 Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Sun, 19 Jan 2014 18:43:51 -0800 Subject: Add link to upgrade guide for full list of deprecated finders [ci skip] --- guides/source/4_1_release_notes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 924e5d90db..c6efce8834 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -411,6 +411,9 @@ for detailed changes. * Remove implicit join references that were deprecated in 4.0. * Removed `activerecord-deprecated_finders` as a dependency. + Please see [upgrading + guide](upgrading_ruby_on_rails.html#upgrading-from-rails-3-2-to-rails-4-0-active-record) + for more info. * Removed usage of `implicit_readonly`. Please use `readonly` method explicitly to mark records as -- cgit v1.2.3 From 90228a168b3234c257ae4b1768bf615488f556d6 Mon Sep 17 00:00:00 2001 From: robertomiranda Date: Sun, 19 Jan 2014 20:59:54 -0500 Subject: Update Error Message when secrets.secret_key_base isn't given --- activesupport/lib/active_support/key_generator.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb index 598c46bce5..58a2289b08 100644 --- a/activesupport/lib/active_support/key_generator.rb +++ b/activesupport/lib/active_support/key_generator.rb @@ -58,10 +58,10 @@ module ActiveSupport def ensure_secret_secure(secret) if secret.blank? raise ArgumentError, "A secret is required to generate an " + - "integrity hash for cookie session data. Use " + - "config.secret_key_base = \"some secret phrase of at " + - "least #{SECRET_MIN_LENGTH} characters\"" + - "in config/initializers/secret_token.rb" + "integrity hash for cookie session data. Set a " + + "secret_key_base of at least " + + "#{SECRET_MIN_LENGTH} characters " + + "in config/initializers/secrets.yml" end if secret.length < SECRET_MIN_LENGTH -- cgit v1.2.3 From 610a3e8bdbb3ace84867c6db7d72a811f49e2dac Mon Sep 17 00:00:00 2001 From: Rajesh Thummalapally Date: Sun, 19 Jan 2014 23:34:33 -0500 Subject: Correcting link that is pointing to Rails blog [ci skip] --- guides/source/upgrading_ruby_on_rails.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index fda4205ed5..76c7f65dda 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -320,7 +320,7 @@ being used, you can update your form to use the `PUT` method instead: <%= form_for [ :update_name, @user ], method: :put do |f| %> ``` -For more on PATCH and why this change was made, see [this post](http://weblog.rubyonrails.org/2012/2/25/edge-rails-patch-is-the-new-primary-http-method-for-updates/) +For more on PATCH and why this change was made, see [this post](http://weblog.rubyonrails.org/2012/2/26/edge-rails-patch-is-the-new-primary-http-method-for-updates/) on the Rails blog. #### A note about media types -- cgit v1.2.3 From 7f255245bd7c6de1037454e4e344175a4239aca5 Mon Sep 17 00:00:00 2001 From: Arel English Date: Mon, 20 Jan 2014 00:37:48 -0500 Subject: Fix a typo that says you application [ci skip] --- guides/source/upgrading_ruby_on_rails.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 76c7f65dda..2055452935 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -148,7 +148,7 @@ part of the rewrite, the following features have been removed from the encoder: 2. Support for the `encode_json` hook 3. Option to encode `BigDecimal` objects as numbers instead of strings -If you application depends on one of these features, you can get them back by +If your application depends on one of these features, you can get them back by adding the [`activesupport-json_encoder`](https://github.com/rails/activesupport-json_encoder) gem to your Gemfile. -- cgit v1.2.3 From f63c6e57ee7b777ffe8d9e8ba907cd4b36f4040d Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Mon, 20 Jan 2014 08:56:08 -0200 Subject: Fix secrets.yml path in exception message The file is config/secrets.yml, not config/initializers/secrets.yml. --- activesupport/lib/active_support/key_generator.rb | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb index 58a2289b08..51d2da3a79 100644 --- a/activesupport/lib/active_support/key_generator.rb +++ b/activesupport/lib/active_support/key_generator.rb @@ -57,18 +57,16 @@ module ActiveSupport # secret they've provided is at least 30 characters in length. def ensure_secret_secure(secret) if secret.blank? - raise ArgumentError, "A secret is required to generate an " + - "integrity hash for cookie session data. Set a " + - "secret_key_base of at least " + - "#{SECRET_MIN_LENGTH} characters " + - "in config/initializers/secrets.yml" + raise ArgumentError, "A secret is required to generate an integrity hash " \ + "for cookie session data. Set a secret_key_base of at least " \ + "#{SECRET_MIN_LENGTH} characters in config/secrets.yml." end if secret.length < SECRET_MIN_LENGTH - raise ArgumentError, "Secret should be something secure, " + - "like \"#{SecureRandom.hex(16)}\". The value you " + - "provided, \"#{secret}\", is shorter than the minimum length " + - "of #{SECRET_MIN_LENGTH} characters" + raise ArgumentError, "Secret should be something secure, " \ + "like \"#{SecureRandom.hex(16)}\". The value you " \ + "provided, \"#{secret}\", is shorter than the minimum length " \ + "of #{SECRET_MIN_LENGTH} characters." end end end -- cgit v1.2.3 From 079ffc96c18752d069d1759f9667b7a905e506eb Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 20 Jan 2014 14:03:48 +0100 Subject: let's link to the gem README not to the upgrading guide. [ci skip] The PR #13767 added link to the upgrading guide to explain details about activerecord_deprecated-finders. However the link target features a stack of changes not releated at all. Also the relevant details are not very informative. I think we better link to the README so people can see what the gem is about and how to use it. /cc @chancancode @zzak --- guides/source/4_1_release_notes.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index e131d3348e..1980f4d4cf 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -411,8 +411,7 @@ for detailed changes. * Remove implicit join references that were deprecated in 4.0. * Removed `activerecord-deprecated_finders` as a dependency. - Please see [upgrading - guide](upgrading_ruby_on_rails.html#upgrading-from-rails-3-2-to-rails-4-0-active-record) + Please see [the gem README](https://github.com/rails/activerecord-deprecated_finders#active-record-deprecated-finders) for more info. * Removed usage of `implicit_readonly`. Please use `readonly` method -- cgit v1.2.3 From dfac4ced1d108d10ffd52fb79c354a15808d7969 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 20 Jan 2014 14:47:40 +0100 Subject: docs, hyperlink the `Mail gem` in Action Mailer guide. [ci skip] --- guides/source/action_mailer_basics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 61fd762304..83a62773d5 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -639,8 +639,8 @@ config.action_mailer.default_options = {from: 'no-reply@example.com'} ### Action Mailer Configuration for Gmail -As Action Mailer now uses the Mail gem, this becomes as simple as adding to your -`config/environments/$RAILS_ENV.rb` file: +As Action Mailer now uses the [Mail gem](https://github.com/mikel/mail), this +becomes as simple as adding to your `config/environments/$RAILS_ENV.rb` file: ```ruby config.action_mailer.delivery_method = :smtp -- cgit v1.2.3 From 080fc9cad39a98b6973cd7a7106f1bcb10d3ad02 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 20 Jan 2014 15:04:36 +0100 Subject: docs should say `email` not `Email`. [ci skip] --- actionmailer/lib/action_mailer/base.rb | 2 +- guides/source/3_0_release_notes.md | 2 +- guides/source/action_mailer_basics.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index a30e3e65da..41db62cc58 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -51,7 +51,7 @@ module ActionMailer # * mail - Allows you to specify email to be sent. # # The hash passed to the mail method allows you to specify any header that a Mail::Message - # will accept (any valid Email header including optional fields). + # will accept (any valid email header including optional fields). # # The mail method, if not passed a block, will inspect your views and send all the views with # the same name as the method, so the above action would send the +welcome.text.erb+ view diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md index cf9d694de7..dd81ec58f9 100644 --- a/guides/source/3_0_release_notes.md +++ b/guides/source/3_0_release_notes.md @@ -574,7 +574,7 @@ The following methods have been removed because they are no longer used in the f Action Mailer ------------- -Action Mailer has been given a new API with TMail being replaced out with the new [Mail](http://github.com/mikel/mail) as the Email library. Action Mailer itself has been given an almost complete re-write with pretty much every line of code touched. The result is that Action Mailer now simply inherits from Abstract Controller and wraps the Mail gem in a Rails DSL. This reduces the amount of code and duplication of other libraries in Action Mailer considerably. +Action Mailer has been given a new API with TMail being replaced out with the new [Mail](http://github.com/mikel/mail) as the email library. Action Mailer itself has been given an almost complete re-write with pretty much every line of code touched. The result is that Action Mailer now simply inherits from Abstract Controller and wraps the Mail gem in a Rails DSL. This reduces the amount of code and duplication of other libraries in Action Mailer considerably. * All mailers are now in `app/mailers` by default. * Can now send email using new API with three methods: `attachments`, `headers` and `mail`. diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 83a62773d5..293e999c14 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -138,7 +138,7 @@ When you call the `mail` method now, Action Mailer will detect the two templates Mailers are really just another way to render a view. Instead of rendering a view and sending out the HTTP protocol, they are just sending it out through the -Email protocols instead. Due to this, it makes sense to just have your +email protocols instead. Due to this, it makes sense to just have your controller tell the Mailer to send an email when a user is successfully created. Setting this up is painfully simple. @@ -164,7 +164,7 @@ class UsersController < ApplicationController respond_to do |format| if @user.save - # Tell the UserMailer to send a welcome Email after save + # Tell the UserMailer to send a welcome email after save UserMailer.welcome_email(@user).deliver format.html { redirect_to(@user, notice: 'User was successfully created.') } -- cgit v1.2.3 From 746abbcc31f795eaa8e31d7b3a94d63cc4d5c581 Mon Sep 17 00:00:00 2001 From: Amr Tamimi Date: Mon, 21 Oct 2013 15:40:39 +0300 Subject: Automatically convert dashes to underscores for url helpers --- actionpack/CHANGELOG.md | 13 +++++++++++++ actionpack/lib/action_dispatch/routing/mapper.rb | 3 ++- actionpack/test/dispatch/routing/inspector_test.rb | 4 +++- actionpack/test/dispatch/routing/route_set_test.rb | 2 ++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 24dc207656..4e67627d8b 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,16 @@ +* Add ability to set a prefix name for routes which have hyphen(s). + + get '/contact-us' => 'pages#contact' + get '/about-us' => 'pages#about_us' + + The above routes will inspected to + + Prefix Verb URI Pattern Controller#Action + contact_us GET /contact-us(.:format) pages#contact + about_us GET /about-us(.:format) pages#about_us + + *Amr Tamimi* + * Fix stream closing when sending file with `ActionController::Live` included. Fixes #12381 diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 18f37dc732..d724633245 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1440,7 +1440,7 @@ module ActionDispatch path = path_for_action(action, options.delete(:path)) action = action.to_s.dup - if action =~ /^[\w\/]+$/ + if action =~ /^[\w\-\/]+$/ options[:action] ||= action unless action.include?("/") else action = nil @@ -1636,6 +1636,7 @@ module ActionDispatch when :root [name_prefix, collection_name, prefix] else + prefix.gsub!(/\-/, '_') if prefix.is_a?(String) [name_prefix, member_name, prefix] end diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index 18a52f13a7..2746c683ba 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -37,6 +37,7 @@ module ActionDispatch end engine.routes.draw do get '/cart', :to => 'cart#show' + get '/view-cart', :to => 'cart#show' end output = draw do @@ -50,7 +51,8 @@ module ActionDispatch " blog /blog Blog::Engine", "", "Routes for Blog::Engine:", - " cart GET /cart(.:format) cart#show" + " cart GET /cart(.:format) cart#show", + "view_cart GET /view-cart(.:format) cart#show" ], output end diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb index 0e488d2b88..3831c4c585 100644 --- a/actionpack/test/dispatch/routing/route_set_test.rb +++ b/actionpack/test/dispatch/routing/route_set_test.rb @@ -30,10 +30,12 @@ module ActionDispatch draw do get 'foo', to: SimpleApp.new('foo#index') get 'bar', to: SimpleApp.new('bar#index') + get 'foo-bar', to: SimpleApp.new('bar#index') end assert_equal '/foo', url_helpers.foo_path assert_equal '/bar', url_helpers.bar_path + assert_equal '/foo-bar', url_helpers.foo_bar_path end test "url helpers are updated when route is updated" do -- cgit v1.2.3 From f9f32e04ad57c37353a756673794a41026f65a34 Mon Sep 17 00:00:00 2001 From: Mikko Johansson Date: Mon, 20 Jan 2014 17:31:11 +0200 Subject: Automatically convert dashes to underscores in shorthand routes --- actionpack/lib/action_dispatch/routing/mapper.rb | 1 + actionpack/test/dispatch/routing_test.rb | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index d724633245..6a4d7c3afa 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1410,6 +1410,7 @@ module ActionDispatch path_without_format = _path.to_s.sub(/\(\.:format\)$/, '') if using_match_shorthand?(path_without_format, route_options) route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1') + route_options[:to].tr!("-", "_") end decomposed_match(_path, route_options) diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 795911497e..497c3e568c 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2912,6 +2912,16 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert @response.ok?, 'route with trailing slash and with QUERY_STRING should work' end + def test_shorthand_route_with_dashes_in_path + draw do + get '/about-us/index' + end + + get '/about-us/index' + assert_equal 'about_us#index', @response.body + assert_equal '/about-us/index', about_us_index_path + end + private def draw(&block) -- cgit v1.2.3 From bf191318afb62a66fe37bd2649ecabfc4c8744a6 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Mon, 20 Jan 2014 16:34:22 +0000 Subject: Tidy up tests and CHANGELOG for #12598 --- actionpack/CHANGELOG.md | 15 +++++++++++++-- actionpack/test/dispatch/routing/inspector_test.rb | 17 ++++++++++++++--- actionpack/test/dispatch/routing/route_set_test.rb | 2 -- actionpack/test/dispatch/routing_test.rb | 10 ++++++++++ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 4e67627d8b..a7231b9e59 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,9 +1,20 @@ -* Add ability to set a prefix name for routes which have hyphen(s). +* Automatically convert dashes to underscores for shorthand routes, e.g: + + get '/our-work/latest' + + When running `rake routes` you will get the following output: + + Prefix Verb URI Pattern Controller#Action + our_work_latest GET /our-work/latest(.:format) our_work#latest + + *Mikko Johansson* + +* Automatically convert dashes to underscores for url helpers, e.g: get '/contact-us' => 'pages#contact' get '/about-us' => 'pages#about_us' - The above routes will inspected to + When running `rake routes` you will get the following output: Prefix Verb URI Pattern Controller#Action contact_us GET /contact-us(.:format) pages#contact diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index 2746c683ba..8045464284 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -37,7 +37,6 @@ module ActionDispatch end engine.routes.draw do get '/cart', :to => 'cart#show' - get '/view-cart', :to => 'cart#show' end output = draw do @@ -51,8 +50,7 @@ module ActionDispatch " blog /blog Blog::Engine", "", "Routes for Blog::Engine:", - " cart GET /cart(.:format) cart#show", - "view_cart GET /view-cart(.:format) cart#show" + " cart GET /cart(.:format) cart#show" ], output end @@ -162,6 +160,19 @@ module ActionDispatch ], output end + def test_rake_routes_shows_routes_with_dashes + output = draw do + get 'about-us' => 'pages#about_us' + get 'our-work/latest' + end + + assert_equal [ + " Prefix Verb URI Pattern Controller#Action", + " about_us GET /about-us(.:format) pages#about_us", + "our_work_latest GET /our-work/latest(.:format) our_work#latest" + ], output + end + class RackApp def self.call(env) end diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb index 3831c4c585..0e488d2b88 100644 --- a/actionpack/test/dispatch/routing/route_set_test.rb +++ b/actionpack/test/dispatch/routing/route_set_test.rb @@ -30,12 +30,10 @@ module ActionDispatch draw do get 'foo', to: SimpleApp.new('foo#index') get 'bar', to: SimpleApp.new('bar#index') - get 'foo-bar', to: SimpleApp.new('bar#index') end assert_equal '/foo', url_helpers.foo_path assert_equal '/bar', url_helpers.bar_path - assert_equal '/foo-bar', url_helpers.foo_bar_path end test "url helpers are updated when route is updated" do diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 497c3e568c..ebea5a8792 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2912,6 +2912,16 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert @response.ok?, 'route with trailing slash and with QUERY_STRING should work' end + def test_route_with_dashes_in_path + draw do + get '/contact-us', to: 'pages#contact_us' + end + + get '/contact-us' + assert_equal 'pages#contact_us', @response.body + assert_equal '/contact-us', contact_us_path + end + def test_shorthand_route_with_dashes_in_path draw do get '/about-us/index' -- cgit v1.2.3 From 41722dd4440c992b3fd4e6181e9ddd0c7c3709e6 Mon Sep 17 00:00:00 2001 From: anilmaurya Date: Mon, 20 Jan 2014 14:37:53 +0530 Subject: moving controller_name assignment before model name condition --- railties/lib/rails/generators/resource_helpers.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb index a01eb57651..7329ee9f48 100644 --- a/railties/lib/rails/generators/resource_helpers.rb +++ b/railties/lib/rails/generators/resource_helpers.rb @@ -15,12 +15,10 @@ module Rails # Set controller variables on initialization. def initialize(*args) #:nodoc: super + controller_name = name if options[:model_name] - controller_name = name self.name = options[:model_name] assign_names!(self.name) - else - controller_name = name end if name == name.pluralize && name.singularize != name.pluralize && !options[:force_plural] -- cgit v1.2.3 From cafe31a078276dbf941bd8b30f0caddc878c0830 Mon Sep 17 00:00:00 2001 From: Jason Meller Date: Sat, 18 Jan 2014 00:51:34 -0500 Subject: Ensure #second acts like #first AR finder This commit bring the famous ordinal Array instance methods defined in ActiveSupport into ActiveRecord as fully-fledged finders. These finders ensure a default ascending order of the table's primary key, and utilize the OFFSET SQL verb to locate the user's desired record. If an offset is defined in the query, calling #second adds to the offset to get the actual desired record. Fixes #13743. --- activerecord/CHANGELOG.md | 31 +++++++ activerecord/lib/active_record/querying.rb | 1 + activerecord/lib/active_record/relation.rb | 4 +- .../lib/active_record/relation/finder_methods.rb | 94 ++++++++++++++++++++-- activerecord/test/cases/base_test.rb | 2 +- activerecord/test/cases/calculations_test.rb | 18 +++-- activerecord/test/cases/finder_test.rb | 90 ++++++++++++++++++++- activerecord/test/cases/relations_test.rb | 24 +++--- .../test/cases/scoping/named_scoping_test.rb | 8 +- activerecord/test/fixtures/topics.yml | 7 ++ 10 files changed, 245 insertions(+), 34 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 304c48bf44..170818cd1c 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,34 @@ +* Ensure `second` through `fifth` methods act like the `first` finder. + + The famous ordinal Array instance methods defined in ActiveSupport + (`first`, `second`, `third`, `fourth`, and `fifth`) are now available as + full-fledged finders in ActiveRecord. The biggest benefit of this is ordering + of the records returned now defaults to the table's primary key in ascending order. + + Example: + + User.all.second + + # Before + # => 'SELECT "users".* FROM "users"' + + # After + # => SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 OFFSET 1' + + User.offset(3).second + + # Before + # => 'SELECT "users".* FROM "users" LIMIT -1 OFFSET 3' # sqlite3 gem + # => 'SELECT "users".* FROM "users" OFFSET 3' # pg gem + # => 'SELECT `users`.* FROM `users` LIMIT 18446744073709551615 OFFSET 3' # mysql2 gem + + # After + # => SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 OFFSET 4' + + Fixes #13743. + + *Jason Meller* + * ActiveRecord states are now correctly restored after a rollback for models that did not define any transactional callbacks (i.e. `after_commit`, `after_rollback` or `after_create`). diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index fd4c973504..ef138c6f80 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -1,6 +1,7 @@ module ActiveRecord module Querying delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, to: :all + delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, to: :all delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all delegate :find_by, :find_by!, to: :all diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 745c6cf349..f152891888 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -20,10 +20,11 @@ module ActiveRecord alias :model :klass alias :loaded? :loaded - def initialize(klass, table, values = {}) + def initialize(klass, table, values = {}, offsets = {}) @klass = klass @table = table @values = values + @offsets = offsets @loaded = false end @@ -498,6 +499,7 @@ module ActiveRecord @first = @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil @should_eager_load = @join_dependency = nil @records = [] + @offsets = {} self end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 4984dbd277..f2ac351a8b 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -127,9 +127,9 @@ module ActiveRecord # def first(limit = nil) if limit - find_first_with_limit(limit) + find_nth_with_limit(offset_value, limit) else - find_first + find_nth(offset_value) end end @@ -172,6 +172,86 @@ module ActiveRecord last or raise RecordNotFound end + # Find the second record. + # If no order is defined it will order by primary key. + # + # Person.second # returns the second object fetched by SELECT * FROM people + # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4) + # Person.where(["user_name = :u", { u: user_name }]).second + def second + find_nth(offset_value ? offset_value + 1 : 1) + end + + # Same as +second+ but raises ActiveRecord::RecordNotFound if no record + # is found. + def second! + second or raise RecordNotFound + end + + # Find the third record. + # If no order is defined it will order by primary key. + # + # Person.third # returns the third object fetched by SELECT * FROM people + # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5) + # Person.where(["user_name = :u", { u: user_name }]).third + def third + find_nth(offset_value ? offset_value + 2 : 2) + end + + # Same as +third+ but raises ActiveRecord::RecordNotFound if no record + # is found. + def third! + third or raise RecordNotFound + end + + # Find the fourth record. + # If no order is defined it will order by primary key. + # + # Person.fourth # returns the fourth object fetched by SELECT * FROM people + # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6) + # Person.where(["user_name = :u", { u: user_name }]).fourth + def fourth + find_nth(offset_value ? offset_value + 3 : 3) + end + + # Same as +fourth+ but raises ActiveRecord::RecordNotFound if no record + # is found. + def fourth! + fourth or raise RecordNotFound + end + + # Find the fifth record. + # If no order is defined it will order by primary key. + # + # Person.fifth # returns the fifth object fetched by SELECT * FROM people + # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7) + # Person.where(["user_name = :u", { u: user_name }]).fifth + def fifth + find_nth(offset_value ? offset_value + 4 : 4) + end + + # Same as +fifth+ but raises ActiveRecord::RecordNotFound if no record + # is found. + def fifth! + fifth or raise RecordNotFound + end + + # Find the forty-second record. Also known as accessing "the reddit". + # If no order is defined it will order by primary key. + # + # Person.forty_two # returns the forty-second object fetched by SELECT * FROM people + # Person.offset(3).forty_two # returns the fifth object from OFFSET 3 (which is OFFSET 44) + # Person.where(["user_name = :u", { u: user_name }]).forty_two + def forty_two + find_nth(offset_value ? offset_value + 41 : 41) + end + + # Same as +forty_two+ but raises ActiveRecord::RecordNotFound if no record + # is found. + def forty_two! + forty_two or raise RecordNotFound + end + # Returns +true+ if a record exists in the table that matches the +id+ or # conditions given, or +false+ otherwise. The argument can take six forms: # @@ -364,19 +444,19 @@ module ActiveRecord end end - def find_first + def find_nth(offset) if loaded? @records.first else - @first ||= find_first_with_limit(1).first + @offsets[offset] ||= find_nth_with_limit(offset, 1).first end end - def find_first_with_limit(limit) + def find_nth_with_limit(offset, limit) if order_values.empty? && primary_key - order(arel_table[primary_key].asc).limit(limit).to_a + order(arel_table[primary_key].asc).limit(limit).offset(offset).to_a else - limit(limit).to_a + limit(limit).offset(offset).to_a end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index ef1ebbb400..8a0b0b9589 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -321,7 +321,7 @@ class BasicsTest < ActiveRecord::TestCase def test_load topics = Topic.all.merge!(:order => 'id').to_a - assert_equal(4, topics.size) + assert_equal(5, topics.size) assert_equal(topics(:first).title, topics.first.title) end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 2f6913167d..0bc81ffe56 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -466,14 +466,14 @@ class CalculationsTest < ActiveRecord::TestCase def test_distinct_is_honored_when_used_with_count_operation_after_group # Count the number of authors for approved topics approved_topics_count = Topic.group(:approved).count(:author_name)[true] - assert_equal approved_topics_count, 3 + assert_equal approved_topics_count, 4 # Count the number of distinct authors for approved Topics distinct_authors_for_approved_count = Topic.group(:approved).distinct.count(:author_name)[true] - assert_equal distinct_authors_for_approved_count, 2 + assert_equal distinct_authors_for_approved_count, 3 end def test_pluck - assert_equal [1,2,3,4], Topic.order(:id).pluck(:id) + assert_equal [1,2,3,4,5], Topic.order(:id).pluck(:id) end def test_pluck_without_column_names @@ -509,7 +509,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_pluck_with_qualified_column_name - assert_equal [1,2,3,4], Topic.order(:id).pluck("topics.id") + assert_equal [1,2,3,4,5], Topic.order(:id).pluck("topics.id") end def test_pluck_auto_table_name_prefix @@ -557,11 +557,13 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_multiple_columns assert_equal [ [1, "The First Topic"], [2, "The Second Topic of the day"], - [3, "The Third Topic of the day"], [4, "The Fourth Topic of the day"] + [3, "The Third Topic of the day"], [4, "The Fourth Topic of the day"], + [5, "The Fifth Topic of the day"] ], Topic.order(:id).pluck(:id, :title) assert_equal [ [1, "The First Topic", "David"], [2, "The Second Topic of the day", "Mary"], - [3, "The Third Topic of the day", "Carl"], [4, "The Fourth Topic of the day", "Carl"] + [3, "The Third Topic of the day", "Carl"], [4, "The Fourth Topic of the day", "Carl"], + [5, "The Fifth Topic of the day", "Jason"] ], Topic.order(:id).pluck(:id, :title, :author_name) end @@ -587,7 +589,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_pluck_replaces_select_clause taks_relation = Topic.select(:approved, :id).order(:id) - assert_equal [1,2,3,4], taks_relation.pluck(:id) - assert_equal [false, true, true, true], taks_relation.pluck(:approved) + assert_equal [1,2,3,4,5], taks_relation.pluck(:id) + assert_equal [false, true, true, true, true], taks_relation.pluck(:approved) end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 9b575557de..9cd1e0ace8 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -254,6 +254,94 @@ class FinderTest < ActiveRecord::TestCase end end + def test_second + assert_equal topics(:second).title, Topic.second.title + end + + def test_second_with_offset + assert_equal topics(:fifth), Topic.offset(3).second + end + + def test_second_have_primary_key_order_by_default + expected = topics(:second) + expected.touch # PostgreSQL changes the default order if no order clause is used + assert_equal expected, Topic.second + end + + def test_model_class_responds_to_second_bang + assert Topic.second! + Topic.delete_all + assert_raises ActiveRecord::RecordNotFound do + Topic.second! + end + end + + def test_third + assert_equal topics(:third).title, Topic.third.title + end + + def test_third_with_offset + assert_equal topics(:fifth), Topic.offset(2).third + end + + def test_third_have_primary_key_order_by_default + expected = topics(:third) + expected.touch # PostgreSQL changes the default order if no order clause is used + assert_equal expected, Topic.third + end + + def test_model_class_responds_to_third_bang + assert Topic.third! + Topic.delete_all + assert_raises ActiveRecord::RecordNotFound do + Topic.third! + end + end + + def test_fourth + assert_equal topics(:fourth).title, Topic.fourth.title + end + + def test_fourth_with_offset + assert_equal topics(:fifth), Topic.offset(1).fourth + end + + def test_fourth_have_primary_key_order_by_default + expected = topics(:fourth) + expected.touch # PostgreSQL changes the default order if no order clause is used + assert_equal expected, Topic.fourth + end + + def test_model_class_responds_to_fourth_bang + assert Topic.fourth! + Topic.delete_all + assert_raises ActiveRecord::RecordNotFound do + Topic.fourth! + end + end + + def test_fifth + assert_equal topics(:fifth).title, Topic.fifth.title + end + + def test_fifth_with_offset + assert_equal topics(:fifth), Topic.offset(0).fifth + end + + def test_fifth_have_primary_key_order_by_default + expected = topics(:fifth) + expected.touch # PostgreSQL changes the default order if no order clause is used + assert_equal expected, Topic.fifth + end + + def test_model_class_responds_to_fifth_bang + assert Topic.fifth! + Topic.delete_all + assert_raises ActiveRecord::RecordNotFound do + Topic.fifth! + end + end + def test_last_bang_present assert_nothing_raised do assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").last! @@ -267,7 +355,7 @@ class FinderTest < ActiveRecord::TestCase end def test_model_class_responds_to_last_bang - assert_equal topics(:fourth), Topic.last! + assert_equal topics(:fifth), Topic.last! assert_raises ActiveRecord::RecordNotFound do Topic.delete_all Topic.last! diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index afd5a69cef..9227c2b72f 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -65,7 +65,7 @@ class RelationTest < ActiveRecord::TestCase def test_scoped topics = Topic.all assert_kind_of ActiveRecord::Relation, topics - assert_equal 4, topics.size + assert_equal 5, topics.size end def test_to_json @@ -86,14 +86,14 @@ class RelationTest < ActiveRecord::TestCase def test_scoped_all topics = Topic.all.to_a assert_kind_of Array, topics - assert_no_queries { assert_equal 4, topics.size } + assert_no_queries { assert_equal 5, topics.size } end def test_loaded_all topics = Topic.all assert_queries(1) do - 2.times { assert_equal 4, topics.to_a.size } + 2.times { assert_equal 5, topics.to_a.size } end assert topics.loaded? @@ -164,27 +164,27 @@ class RelationTest < ActiveRecord::TestCase def test_finding_with_order topics = Topic.order('id') - assert_equal 4, topics.to_a.size + assert_equal 5, topics.to_a.size assert_equal topics(:first).title, topics.first.title end def test_finding_with_arel_order topics = Topic.order(Topic.arel_table[:id].asc) - assert_equal 4, topics.to_a.size + assert_equal 5, topics.to_a.size assert_equal topics(:first).title, topics.first.title end def test_finding_with_assoc_order topics = Topic.order(:id => :desc) - assert_equal 4, topics.to_a.size - assert_equal topics(:fourth).title, topics.first.title + assert_equal 5, topics.to_a.size + assert_equal topics(:fifth).title, topics.first.title end def test_finding_with_reverted_assoc_order topics = Topic.order(:id => :asc).reverse_order - assert_equal 4, topics.to_a.size - assert_equal topics(:fourth).title, topics.first.title + assert_equal 5, topics.to_a.size + assert_equal topics(:fifth).title, topics.first.title end def test_order_with_hash_and_symbol_generates_the_same_sql @@ -197,19 +197,19 @@ class RelationTest < ActiveRecord::TestCase def test_finding_last_with_arel_order topics = Topic.order(Topic.arel_table[:id].asc) - assert_equal topics(:fourth).title, topics.last.title + assert_equal topics(:fifth).title, topics.last.title end def test_finding_with_order_concatenated topics = Topic.order('author_name').order('title') - assert_equal 4, topics.to_a.size + assert_equal 5, topics.to_a.size assert_equal topics(:fourth).title, topics.first.title end def test_finding_with_reorder topics = Topic.order('author_name').order('title').reorder('id').to_a topics_titles = topics.map{ |t| t.title } - assert_equal ['The First Topic', 'The Second Topic of the day', 'The Third Topic of the day', 'The Fourth Topic of the day'], topics_titles + assert_equal ['The First Topic', 'The Second Topic of the day', 'The Third Topic of the day', 'The Fourth Topic of the day', 'The Fifth Topic of the day'], topics_titles end def test_finding_with_order_and_take diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 72c9787b84..086977d9a2 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -344,13 +344,13 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_scopes_batch_finders - assert_equal 3, Topic.approved.count + assert_equal 4, Topic.approved.count - assert_queries(4) do + assert_queries(5) do Topic.approved.find_each(:batch_size => 1) {|t| assert t.approved? } end - assert_queries(2) do + assert_queries(3) do Topic.approved.find_in_batches(:batch_size => 2) do |group| group.each {|t| assert t.approved? } end @@ -366,7 +366,7 @@ class NamedScopingTest < ActiveRecord::TestCase def test_scopes_on_relations # Topic.replied approved_topics = Topic.all.approved.order('id DESC') - assert_equal topics(:fourth), approved_topics.first + assert_equal topics(:fifth), approved_topics.first replied_approved_topics = approved_topics.replied assert_equal topics(:third), replied_approved_topics.first diff --git a/activerecord/test/fixtures/topics.yml b/activerecord/test/fixtures/topics.yml index 2b042bd135..bf049abbf1 100644 --- a/activerecord/test/fixtures/topics.yml +++ b/activerecord/test/fixtures/topics.yml @@ -40,3 +40,10 @@ fourth: type: Reply parent_id: 3 +fifth: + id: 5 + title: The Fifth Topic of the day + author_name: Jason + written_on: 2013-07-13t12:11:00.0099+01:00 + content: Omakase + approved: true -- cgit v1.2.3 From de3bf3ec3d7dfa81870aca5b8ed8c237e093fe9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 20 Jan 2014 20:45:08 -0200 Subject: Remove unneded argument This variable is internal and should not be exposed to end users --- activerecord/lib/active_record/relation.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index f152891888..0fc63343ab 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -20,11 +20,11 @@ module ActiveRecord alias :model :klass alias :loaded? :loaded - def initialize(klass, table, values = {}, offsets = {}) + def initialize(klass, table, values = {}) @klass = klass @table = table @values = values - @offsets = offsets + @offsets = {} @loaded = false end -- cgit v1.2.3 From 06a00038efbbaef127ad8908fffea6799c577440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 20 Jan 2014 22:58:14 -0200 Subject: When applying changes or reseting changes create the right class Before this patch after the changes are applied the changes can be only accessed using string keys, but before symbols are also accepted. After this change every state of the model will be consistent. --- activemodel/lib/active_model/dirty.rb | 6 +++--- activemodel/test/cases/dirty_test.rb | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 58d87e6f7f..72e2224926 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -167,13 +167,13 @@ module ActiveModel # Removes current changes and makes them accessible through +previous_changes+. def changes_applied @previously_changed = changes - @changed_attributes = {} + @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new end # Removes all dirty data: current changes and previous changes def reset_changes - @previously_changed = {} - @changed_attributes = {} + @previously_changed = ActiveSupport::HashWithIndifferentAccess.new + @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new end # Handle *_change for +method_missing+. diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb index 54427a1513..efadb2600f 100644 --- a/activemodel/test/cases/dirty_test.rb +++ b/activemodel/test/cases/dirty_test.rb @@ -83,6 +83,14 @@ class DirtyTest < ActiveModel::TestCase assert_not_nil @model.changes['name'] end + test "be cosistent with symbols arguments after the changes are applied" do + @model.name = "David" + assert @model.attribute_changed?(:name) + @model.save + @model.name = 'Rafael' + assert @model.attribute_changed?(:name) + end + test "attribute mutation" do @model.instance_variable_set("@name", "Yam") assert !@model.name_changed? -- cgit v1.2.3 From b8302bcfdaec2a9e7658262d6feeb535c572922d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 20 Jan 2014 23:00:17 -0200 Subject: Forgot to push this change in the parent commit --- activemodel/lib/active_model/dirty.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 72e2224926..98ffffeb10 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -136,7 +136,7 @@ module ActiveModel # person.save # person.previous_changes # => {"name" => ["bob", "robert"]} def previous_changes - @previously_changed ||= {} + @previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new end # Returns a hash of the attributes with unsaved changes indicating their original -- cgit v1.2.3 From 9322e071e48a6f2ee00de736d7630f71f4dfcb31 Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Tue, 21 Jan 2014 09:46:01 -0200 Subject: Remove missed usage of @first variable With the introduction of `#second` method and friends, we added an offsets hash which replaced the @first variable, so removing it from the reset method to avoid creating an unused variable now. Introduced in bc625080308e4853ae3036f2ad74fe3826e463ef. --- activerecord/lib/active_record/relation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 0fc63343ab..17af887abc 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -496,7 +496,7 @@ module ActiveRecord end def reset - @first = @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil + @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil @should_eager_load = @join_dependency = nil @records = [] @offsets = {} -- cgit v1.2.3 From 691709dd6741757e5c4459c8942857ee019b68a0 Mon Sep 17 00:00:00 2001 From: Alexander Balashov Date: Thu, 28 Mar 2013 16:03:02 +0400 Subject: Fail early with "Primary key not included in the custom select clause" in find_in_batches Before this patch find_in_batches raises this error only on second iteration. So you will know about the problem only when you get the batch size threshold. --- activerecord/CHANGELOG.md | 8 ++++++++ activerecord/lib/active_record/relation/batches.rb | 7 ++----- activerecord/test/cases/batches_test.rb | 4 +++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 170818cd1c..3ca689e713 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,11 @@ +* Fail early with "Primary key not included in the custom select clause" + in find_in_batches. + + Before this patch find_in_batches raises this error only on second iteration. + So you will know about the problem only when you get the batch size threshold. + + *Alexander Balashov* + * Ensure `second` through `fifth` methods act like the `first` finder. The famous ordinal Array instance methods defined in ActiveSupport diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 49b01909c6..f02e2365f7 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -102,16 +102,13 @@ module ActiveRecord while records.any? records_size = records.size primary_key_offset = records.last.id + raise "Primary key not included in the custom select clause" unless primary_key_offset yield records break if records_size < batch_size - if primary_key_offset - records = relation.where(table[primary_key].gt(primary_key_offset)).to_a - else - raise "Primary key not included in the custom select clause" - end + records = relation.where(table[primary_key].gt(primary_key_offset)).to_a end end diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 38c2560d69..ebb36e4940 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -46,7 +46,9 @@ class EachTest < ActiveRecord::TestCase def test_each_should_raise_if_select_is_set_without_id assert_raise(RuntimeError) do - Post.select(:title).find_each(:batch_size => 1) { |post| post } + Post.select(:title).find_each(batch_size: 1) { |post| + flunk "should not call this block" + } end end -- cgit v1.2.3 From 6f0aa1d64dafd72275c934ed14529ccfada55d1d Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Tue, 21 Jan 2014 14:35:33 +0100 Subject: Active Record changelog wording and formatting. [ci skip] --- activerecord/CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 3ca689e713..25a61b3614 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,8 +1,9 @@ * Fail early with "Primary key not included in the custom select clause" - in find_in_batches. + in `find_in_batches`. - Before this patch find_in_batches raises this error only on second iteration. - So you will know about the problem only when you get the batch size threshold. + Before this patch, the exception was raised after the first batch was + yielded to the block. This means that you only get it, when you hit the + `batch_size` treshold. This could shadow the issue in development. *Alexander Balashov* @@ -13,6 +14,8 @@ full-fledged finders in ActiveRecord. The biggest benefit of this is ordering of the records returned now defaults to the table's primary key in ascending order. + Fixes #13743. + Example: User.all.second @@ -33,8 +36,6 @@ # After # => SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 OFFSET 4' - Fixes #13743. - *Jason Meller* * ActiveRecord states are now correctly restored after a rollback for -- cgit v1.2.3 From a4cc88d0851343ac16e2294c06c5a4101189c410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 20 Jan 2014 21:57:47 -0200 Subject: Extract all attribute changed work to its own method This will make easier to hook on this feature to customize the behavior --- activerecord/lib/active_record/attribute_methods/dirty.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 19e81abba5..f7065d183f 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -43,6 +43,13 @@ module ActiveRecord def write_attribute(attr, value) attr = attr.to_s + save_changed_attribute(attr, value) + + # Carry on. + super(attr, value) + end + + def save_changed_attribute(attr, value) # The attribute already has an unsaved change. if attribute_changed?(attr) old = changed_attributes[attr] @@ -51,9 +58,6 @@ module ActiveRecord old = clone_attribute_value(:read_attribute, attr) changed_attributes[attr] = old if _field_changed?(attr, old, value) end - - # Carry on. - super(attr, value) end def update_record(*) -- cgit v1.2.3 From a57a2bcf4a2c29519d553277e4439790ca443cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 20 Jan 2014 21:59:20 -0200 Subject: Make enum feature work with dirty methods To make this possible we have to override the save_changed_attribute hook. --- activerecord/CHANGELOG.md | 18 +++++++++++++++ activerecord/lib/active_record/enum.rb | 19 +++++++++++++++- activerecord/test/cases/enum_test.rb | 40 ++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 170818cd1c..9de076961b 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,21 @@ +* Make enum fields work as expected with the `ActiveModel::Dirty` API. + + Before this change, using the dirty API would have surprising results: + + conversation = Conversation.new + conversation.status = :active + conversation.status = :archived + conversation.status_was # => 0 + + After this change, the same code would result in: + + conversation = Conversation.new + conversation.status = :active + conversation.status = :archived + conversation.status_was # => "active" + + *Rafael Mendonça França* + * Ensure `second` through `fifth` methods act like the `first` finder. The famous ordinal Array instance methods defined in ActiveSupport diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 3deb2d65f8..06e87cf854 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -63,6 +63,12 @@ module ActiveRecord # # Conversation.where("status <> ?", Conversation.statuses[:archived]) module Enum + DEFINED_ENUMS = [] # :nodoc: + + def enum_attribute?(attr_name) # :nodoc: + DEFINED_ENUMS.include?(attr_name.to_sym) + end + def enum(definitions) klass = self definitions.each do |name, values| @@ -70,6 +76,8 @@ module ActiveRecord enum_values = ActiveSupport::HashWithIndifferentAccess.new name = name.to_sym + DEFINED_ENUMS.unshift name + # def self.statuses statuses end klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values } @@ -114,7 +122,16 @@ module ActiveRecord private def _enum_methods_module @_enum_methods_module ||= begin - mod = Module.new + mod = Module.new do + def save_changed_attribute(attr_name, value) + if self.class.enum_attribute?(attr_name) + old = clone_attribute_value(:read_attribute, attr_name) + changed_attributes[attr_name] = self.class.public_send(attr_name.pluralize).key old + else + super + end + end + end include mod mod end diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index 1f98801e93..0fe7dfe4ea 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -51,6 +51,46 @@ class EnumTest < ActiveRecord::TestCase assert @book.written? end + test "enum changed attributes" do + old_status = @book.status + @book.status = :published + assert_equal old_status, @book.changed_attributes[:status] + end + + test "enum changes" do + old_status = @book.status + @book.status = :published + assert_equal [old_status, 'published'], @book.changes[:status] + end + + test "enum attribute was" do + old_status = @book.status + @book.status = :published + assert_equal old_status, @book.attribute_was(:status) + end + + test "enum attribute changed" do + @book.status = :published + assert @book.attribute_changed?(:status) + end + + test "enum attribute changed to" do + @book.status = :published + assert @book.attribute_changed?(:status, to: 'published') + end + + test "enum attribute changed from" do + old_status = @book.status + @book.status = :published + assert @book.attribute_changed?(:status, from: old_status) + end + + test "enum attribute changed from old status to new status" do + old_status = @book.status + @book.status = :published + assert @book.attribute_changed?(:status, from: old_status, to: 'published') + end + test "assign non existing value raises an error" do e = assert_raises(ArgumentError) do @book.status = :unknown -- cgit v1.2.3 From a0520fceff9148ebfbb2e09745ba1416bceef2bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 20 Jan 2014 23:10:48 -0200 Subject: Add more tests for the dirty feature for enums --- .../lib/active_record/attribute_methods/dirty.rb | 1 - activerecord/lib/active_record/enum.rb | 26 +++++++++++++----- activerecord/test/cases/enum_test.rb | 32 ++++++++++++++++++++++ activerecord/test/models/book.rb | 1 + activerecord/test/schema/schema.rb | 1 + 5 files changed, 53 insertions(+), 8 deletions(-) diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index f7065d183f..68168bb729 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -45,7 +45,6 @@ module ActiveRecord save_changed_attribute(attr, value) - # Carry on. super(attr, value) end diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 06e87cf854..0990a4a871 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -123,14 +123,26 @@ module ActiveRecord def _enum_methods_module @_enum_methods_module ||= begin mod = Module.new do - def save_changed_attribute(attr_name, value) - if self.class.enum_attribute?(attr_name) - old = clone_attribute_value(:read_attribute, attr_name) - changed_attributes[attr_name] = self.class.public_send(attr_name.pluralize).key old - else - super + private + def save_changed_attribute(attr_name, value) + if self.class.enum_attribute?(attr_name) + if attribute_changed?(attr_name) + old = changed_attributes[attr_name] + + if self.class.public_send(attr_name.pluralize)[old] == value + changed_attributes.delete(attr_name) + end + else + old = clone_attribute_value(:read_attribute, attr_name) + + if old != value + changed_attributes[attr_name] = self.class.public_send(attr_name.pluralize).key old + end + end + else + super + end end - end end include mod mod diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index 0fe7dfe4ea..9fbfebcca2 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -91,6 +91,38 @@ class EnumTest < ActiveRecord::TestCase assert @book.attribute_changed?(:status, from: old_status, to: 'published') end + test "enum didn't change" do + old_status = @book.status + @book.status = old_status + assert_not @book.attribute_changed?(:status) + end + + test "persist changes that are dirty" do + old_status = @book.status + @book.status = :published + assert @book.attribute_changed?(:status) + @book.status = :written + assert @book.attribute_changed?(:status) + end + + test "reverted changes that are not dirty" do + old_status = @book.status + @book.status = :published + assert @book.attribute_changed?(:status) + @book.status = old_status + assert_not @book.attribute_changed?(:status) + end + + test "reverted changes are not dirty going from nil to value and back" do + book = Book.create!(nullable_status: nil) + + book.nullable_status = :married + assert book.attribute_changed?(:nullable_status) + + book.nullable_status = nil + assert_not book.attribute_changed?(:nullable_status) + end + test "assign non existing value raises an error" do e = assert_raises(ArgumentError) do @book.status = :unknown diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb index 4cb2c7606b..2170018068 100644 --- a/activerecord/test/models/book.rb +++ b/activerecord/test/models/book.rb @@ -9,6 +9,7 @@ class Book < ActiveRecord::Base enum status: [:proposed, :written, :published] enum read_status: {unread: 0, reading: 2, read: 3} + enum nullable_status: [:single, :married] def published! super diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 9a7d918a25..99a53434f6 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -97,6 +97,7 @@ ActiveRecord::Schema.define do t.column :name, :string t.column :status, :integer, default: 0 t.column :read_status, :integer, default: 0 + t.column :nullable_status, :integer end create_table :booleans, force: true do |t| -- cgit v1.2.3 From e011258c30b61f30e40fb2d9b2f58eb1f700dfd5 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Tue, 21 Jan 2014 17:17:13 +0100 Subject: prepend table name for `Relation#select` columns. This fixes a bug where `select(:id)` combined with `joins()` raised: ``` ActiveRecord::StatementInvalid: SQLite3::SQLException: ambiguous column name: id: SELECT id, authors.author_address_id FROM "posts" INNER JOIN "authors" ON "authors"."id" = "posts"."author_id" ORDER BY posts.id LIMIT 3 ``` The `select_values` are still String and Symbols because other parts (mainly calculations.rb) rely on that fact. /cc @tenderlove --- activerecord/CHANGELOG.md | 10 ++++++++++ activerecord/lib/active_record/relation/query_methods.rb | 5 ++++- activerecord/test/cases/relations_test.rb | 6 ++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 25a61b3614..9b2e9c4660 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,13 @@ +* Prepend table name for column names passed to `Relation#select`. + + Example: + + Post.select(:id) + # Before: => SELECT id FROM "posts" + # After: => SELECT "posts"."id" FROM "posts" + + *Yves Senn* + * Fail early with "Primary key not included in the custom select clause" in `find_in_batches`. diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 979216bee7..d392f759bd 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -987,7 +987,10 @@ module ActiveRecord def build_select(arel, selects) if !selects.empty? - arel.project(*selects) + expanded_select = selects.map do |field| + columns_hash.key?(field.to_s) ? arel_table[field] : field + end + arel.project(*expanded_select) elsif from_value arel.project(Arel.star) else diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 9227c2b72f..e874c93110 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1505,6 +1505,12 @@ class RelationTest < ActiveRecord::TestCase end end + test "joins with select" do + posts = Post.joins(:author).select("id", "authors.author_address_id").order("posts.id").limit(3) + assert_equal [1, 2, 4], posts.map(&:id) + assert_equal [1, 1, 1], posts.map(&:author_address_id) + end + test "delegations do not leak to other classes" do Topic.all.by_lifo assert Topic.all.class.method_defined?(:by_lifo) -- cgit v1.2.3 From b0a8ef140e4cc95fa5d3888699fd3d72fd720172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 21 Jan 2014 14:33:16 -0200 Subject: `has_one` and `belongs_to` accessors don't add ORDER BY to the queries anymore. Since Rails 4.0, we add an ORDER BY in the `first` method to ensure consistent results among different database engines. But for singular associations this behavior is not needed since we will have one record to return. As this ORDER BY option can lead some performance issues we are removing it for singular associations accessors. Fixes #12623. --- activerecord/CHANGELOG.md | 11 +++++++++++ .../lib/active_record/associations/singular_association.rb | 2 +- .../test/cases/associations/belongs_to_associations_test.rb | 7 +++++++ .../test/cases/associations/has_one_associations_test.rb | 7 +++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 9b2e9c4660..a108a1b2a9 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,14 @@ +* `has_one` and `belongs_to` accessors don't add ORDER BY to the queries anymore. + + Since Rails 4.0, we add an ORDER BY in the `first` method to ensure consistent results + among different database engines. But for singular associations this behavior is not needed + since we will have one record to return. As this ORDER BY option can lead some performance + issues we are removing it for singular associations accessors. + + Fixes #12623. + + *Rafael Mendonça França* + * Prepend table name for column names passed to `Relation#select`. Example: diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index e4500af5b2..399aff378a 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -39,7 +39,7 @@ module ActiveRecord end def find_target - if record = scope.first + if record = scope.take set_inverse_instance record end end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 3205d0c28b..2283ba66db 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -28,6 +28,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal companies(:first_firm).name, firm.name end + def test_belongs_to_does_not_use_order_by + ActiveRecord::SQLCounter.clear_log + Client.find(3).firm + ensure + assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, 'ORDER BY was used in the query' + end + def test_belongs_to_with_primary_key client = Client.create(:name => "Primary key client", :firm_name => companies(:first_firm).name) assert_equal companies(:first_firm).name, client.firm_with_primary_key.name diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 5a41461edf..d4edef03d6 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -22,6 +22,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit end + def test_has_one_does_not_use_order_by + ActiveRecord::SQLCounter.clear_log + companies(:first_firm).account + ensure + assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, 'ORDER BY was used in the query' + end + def test_has_one_cache_nils firm = companies(:another_firm) assert_queries(1) { assert_nil firm.account } -- cgit v1.2.3 From 7e25da9eed040bb6b732d8fe390fed9913c812d3 Mon Sep 17 00:00:00 2001 From: Kuldeep Aggarwal Date: Wed, 22 Jan 2014 02:50:23 +0530 Subject: add missing information for `id` field when used with `select` [ci skip] --- activerecord/lib/active_record/associations/collection_proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index e3fc908444..10434734e3 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -84,7 +84,7 @@ module ActiveRecord # # Be careful because this also means you're initializing a model # object with only the fields that you've selected. If you attempt - # to access a field that is not in the initialized record you'll + # to access a field except +id+ that is not in the initialized record you'll # receive: # # person.pets.select(:name).first.person_id -- cgit v1.2.3 From 5620e62f3f1920e70199a870a0a3c4feaedb9f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 21 Jan 2014 20:32:52 -0200 Subject: Store the enum values in the DEFINED_ENUM constant This will make simpler to compare if the values changed in the save_changed_attribute method. --- activerecord/lib/active_record/enum.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 0990a4a871..77a5fe9ae0 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -63,10 +63,10 @@ module ActiveRecord # # Conversation.where("status <> ?", Conversation.statuses[:archived]) module Enum - DEFINED_ENUMS = [] # :nodoc: + DEFINED_ENUMS = {} # :nodoc: - def enum_attribute?(attr_name) # :nodoc: - DEFINED_ENUMS.include?(attr_name.to_sym) + def enum_mapping_for(attr_name) # :nodoc: + DEFINED_ENUMS[attr_name.to_sym] end def enum(definitions) @@ -76,8 +76,6 @@ module ActiveRecord enum_values = ActiveSupport::HashWithIndifferentAccess.new name = name.to_sym - DEFINED_ENUMS.unshift name - # def self.statuses statuses end klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values } @@ -115,6 +113,8 @@ module ActiveRecord # def active!() update! status: :active end define_method("#{value}!") { update! name => value } end + + DEFINED_ENUMS[name] = enum_values end end end @@ -125,18 +125,18 @@ module ActiveRecord mod = Module.new do private def save_changed_attribute(attr_name, value) - if self.class.enum_attribute?(attr_name) + if (mapping = self.class.enum_mapping_for(attr_name)) if attribute_changed?(attr_name) old = changed_attributes[attr_name] - if self.class.public_send(attr_name.pluralize)[old] == value + if mapping[old] == value changed_attributes.delete(attr_name) end else old = clone_attribute_value(:read_attribute, attr_name) if old != value - changed_attributes[attr_name] = self.class.public_send(attr_name.pluralize).key old + changed_attributes[attr_name] = mapping.key old end end else -- cgit v1.2.3 From 55f6c8c908fea2609cbc8503f8d87460fd1b16b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 21 Jan 2014 20:35:21 -0200 Subject: Use string as keys --- activerecord/lib/active_record/enum.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 77a5fe9ae0..53dde5e564 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -66,7 +66,7 @@ module ActiveRecord DEFINED_ENUMS = {} # :nodoc: def enum_mapping_for(attr_name) # :nodoc: - DEFINED_ENUMS[attr_name.to_sym] + DEFINED_ENUMS[attr_name.to_s] end def enum(definitions) @@ -114,7 +114,7 @@ module ActiveRecord define_method("#{value}!") { update! name => value } end - DEFINED_ENUMS[name] = enum_values + DEFINED_ENUMS[name.to_s] = enum_values end end end -- cgit v1.2.3 From 03855e790de2224519f55382e3c32118be31eeff Mon Sep 17 00:00:00 2001 From: Jason Meller Date: Tue, 21 Jan 2014 17:34:39 -0500 Subject: Ensure AR #second, #third, etc. finders work through associations This commit fixes two regressions introduced in cafe31a078 where newly created finder methods #second, #third, #forth, and #fifth caused a NoMethodError error on reload associations and where we were pulling the wrong element out of cached associations. Examples: some_book.authors.reload.second # Before # => NoMethodError: undefined method 'first' for nil:NilClass # After # => # some_book.first.authors.first some_book.first.authors.second # Before # => # # => # # After # => # # => # Fixes #13783. --- .../associations/collection_association.rb | 30 +++- .../active_record/associations/collection_proxy.rb | 26 ++++ .../lib/active_record/relation/finder_methods.rb | 16 +-- .../associations/has_many_associations_test.rb | 151 ++++++++++++--------- .../test/cases/autosave_association_test.rb | 10 +- activerecord/test/cases/calculations_test.rb | 4 +- activerecord/test/cases/finder_test.rb | 4 +- activerecord/test/cases/inheritance_test.rb | 4 +- activerecord/test/fixtures/companies.yml | 8 ++ 9 files changed, 168 insertions(+), 85 deletions(-) diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 52531a3520..89b7945c78 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -96,11 +96,31 @@ module ActiveRecord end def first(*args) - first_or_last(:first, *args) + first_nth_or_last(:first, *args) + end + + def second(*args) + first_nth_or_last(:second, *args) + end + + def third(*args) + first_nth_or_last(:third, *args) + end + + def fourth(*args) + first_nth_or_last(:fourth, *args) + end + + def fifth(*args) + first_nth_or_last(:fifth, *args) + end + + def forty_two(*args) + first_nth_or_last(:forty_two, *args) end def last(*args) - first_or_last(:last, *args) + first_nth_or_last(:last, *args) end def build(attributes = {}, &block) @@ -526,7 +546,7 @@ module ActiveRecord # * target already loaded # * owner is new record # * target contains new or changed record(s) - def fetch_first_or_last_using_find?(args) + def fetch_first_nth_or_last_using_find?(args) if args.first.is_a?(Hash) true else @@ -564,10 +584,10 @@ module ActiveRecord end # Fetches the first/last using SQL if possible, otherwise from the target array. - def first_or_last(type, *args) + def first_nth_or_last(type, *args) args.shift if args.first.is_a?(Hash) && args.first.empty? - collection = fetch_first_or_last_using_find?(args) ? scope : load_target + collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target collection.send(type, *args).tap do |record| set_inverse_instance record if record.is_a? ActiveRecord::Base end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 10434734e3..a67b834a80 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -170,6 +170,32 @@ module ActiveRecord @association.first(*args) end + # Same as +first+ except returns only the second record. + def second(*args) + @association.second(*args) + end + + # Same as +first+ except returns only the third record. + def third(*args) + @association.third(*args) + end + + # Same as +first+ except returns only the fourth record. + def fourth(*args) + @association.fourth(*args) + end + + # Same as +first+ except returns only the fifth record. + def fifth(*args) + @association.fifth(*args) + end + + # Same as +first+ except returns only the forty second record. + # Also known as accessing "the reddit". + def forty_two(*args) + @association.forty_two(*args) + end + # Returns the last record, or the last +n+ records, from the collection. # If the collection is empty, the first form returns +nil+, and the second # form returns an empty array. diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index f2ac351a8b..2dd1e6f14b 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -129,7 +129,7 @@ module ActiveRecord if limit find_nth_with_limit(offset_value, limit) else - find_nth(offset_value) + find_nth(:first, offset_value) end end @@ -179,7 +179,7 @@ module ActiveRecord # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4) # Person.where(["user_name = :u", { u: user_name }]).second def second - find_nth(offset_value ? offset_value + 1 : 1) + find_nth(:second, offset_value ? offset_value + 1 : 1) end # Same as +second+ but raises ActiveRecord::RecordNotFound if no record @@ -195,7 +195,7 @@ module ActiveRecord # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5) # Person.where(["user_name = :u", { u: user_name }]).third def third - find_nth(offset_value ? offset_value + 2 : 2) + find_nth(:third, offset_value ? offset_value + 2 : 2) end # Same as +third+ but raises ActiveRecord::RecordNotFound if no record @@ -211,7 +211,7 @@ module ActiveRecord # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6) # Person.where(["user_name = :u", { u: user_name }]).fourth def fourth - find_nth(offset_value ? offset_value + 3 : 3) + find_nth(:fourth, offset_value ? offset_value + 3 : 3) end # Same as +fourth+ but raises ActiveRecord::RecordNotFound if no record @@ -227,7 +227,7 @@ module ActiveRecord # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7) # Person.where(["user_name = :u", { u: user_name }]).fifth def fifth - find_nth(offset_value ? offset_value + 4 : 4) + find_nth(:fifth, offset_value ? offset_value + 4 : 4) end # Same as +fifth+ but raises ActiveRecord::RecordNotFound if no record @@ -243,7 +243,7 @@ module ActiveRecord # Person.offset(3).forty_two # returns the fifth object from OFFSET 3 (which is OFFSET 44) # Person.where(["user_name = :u", { u: user_name }]).forty_two def forty_two - find_nth(offset_value ? offset_value + 41 : 41) + find_nth(:forty_two, offset_value ? offset_value + 41 : 41) end # Same as +forty_two+ but raises ActiveRecord::RecordNotFound if no record @@ -444,9 +444,9 @@ module ActiveRecord end end - def find_nth(offset) + def find_nth(ordinal, offset) if loaded? - @records.first + @records.send(ordinal) else @offsets[offset] ||= find_nth_with_limit(offset, 1).first end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index e45efb0161..9fdace8ac1 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -215,6 +215,31 @@ class HasManyAssociationsTest < ActiveRecord::TestCase bulbs.first({}) end + assert_no_queries do + bulbs.second() + bulbs.second({}) + end + + assert_no_queries do + bulbs.third() + bulbs.third({}) + end + + assert_no_queries do + bulbs.fourth() + bulbs.fourth({}) + end + + assert_no_queries do + bulbs.fifth() + bulbs.fifth({}) + end + + assert_no_queries do + bulbs.forty_two() + bulbs.forty_two({}) + end + assert_no_queries do bulbs.last() bulbs.last({}) @@ -242,11 +267,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first def test_counting_with_counter_sql - assert_equal 2, Firm.all.merge!(:order => "id").first.clients.count + assert_equal 3, Firm.all.merge!(:order => "id").first.clients.count end def test_counting - assert_equal 2, Firm.all.merge!(:order => "id").first.plain_clients.count + assert_equal 3, Firm.all.merge!(:order => "id").first.plain_clients.count end def test_counting_with_single_hash @@ -254,7 +279,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_counting_with_column_name_and_hash - assert_equal 2, Firm.all.merge!(:order => "id").first.plain_clients.count(:name) + assert_equal 3, Firm.all.merge!(:order => "id").first.plain_clients.count(:name) end def test_counting_with_association_limit @@ -264,17 +289,17 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_finding - assert_equal 2, Firm.all.merge!(:order => "id").first.clients.length + assert_equal 3, Firm.all.merge!(:order => "id").first.clients.length end def test_finding_array_compatibility - assert_equal 2, Firm.order(:id).find{|f| f.id > 0}.clients.length + assert_equal 3, Firm.order(:id).find{|f| f.id > 0}.clients.length end def test_find_many_with_merged_options assert_equal 1, companies(:first_firm).limited_clients.size assert_equal 1, companies(:first_firm).limited_clients.to_a.size - assert_equal 2, companies(:first_firm).limited_clients.limit(nil).to_a.size + assert_equal 3, companies(:first_firm).limited_clients.limit(nil).to_a.size end def test_find_should_append_to_association_order @@ -283,8 +308,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_dynamic_find_should_respect_association_order - assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.where("type = 'Client'").first - assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client') + assert_equal companies(:another_first_firm_client), companies(:first_firm).clients_sorted_desc.where("type = 'Client'").first + assert_equal companies(:another_first_firm_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client') end def test_cant_save_has_many_readonly_association @@ -297,7 +322,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_finding_with_different_class_name_and_order - assert_equal "Microsoft", Firm.all.merge!(:order => "id").first.clients_sorted_desc.first.name + assert_equal "Apex", Firm.all.merge!(:order => "id").first.clients_sorted_desc.first.name end def test_finding_with_foreign_key @@ -355,7 +380,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_find_all firm = Firm.all.merge!(:order => "id").first - assert_equal 2, firm.clients.where("#{QUOTED_TYPE} = 'Client'").to_a.length + assert_equal 3, firm.clients.where("#{QUOTED_TYPE} = 'Client'").to_a.length assert_equal 1, firm.clients.where("name = 'Summit'").to_a.length end @@ -364,7 +389,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert ! firm.clients.loaded? - assert_queries(3) do + assert_queries(4) do firm.clients.find_each(:batch_size => 1) {|c| assert_equal firm.id, c.firm_id } end @@ -434,15 +459,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_find_grouped all_clients_of_firm1 = Client.all.merge!(:where => "firm_id = 1").to_a grouped_clients_of_firm1 = Client.all.merge!(:where => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count').to_a - assert_equal 2, all_clients_of_firm1.size + assert_equal 3, all_clients_of_firm1.size assert_equal 1, grouped_clients_of_firm1.size end def test_find_scoped_grouped assert_equal 1, companies(:first_firm).clients_grouped_by_firm_id.size assert_equal 1, companies(:first_firm).clients_grouped_by_firm_id.length - assert_equal 2, companies(:first_firm).clients_grouped_by_name.size - assert_equal 2, companies(:first_firm).clients_grouped_by_name.length + assert_equal 3, companies(:first_firm).clients_grouped_by_name.size + assert_equal 3, companies(:first_firm).clients_grouped_by_name.length end def test_find_scoped_grouped_having @@ -470,17 +495,17 @@ class HasManyAssociationsTest < ActiveRecord::TestCase force_signal37_to_load_all_clients_of_firm natural = Client.new("name" => "Natural Company") companies(:first_firm).clients_of_firm << natural - assert_equal 2, companies(:first_firm).clients_of_firm.size # checking via the collection - assert_equal 2, companies(:first_firm).clients_of_firm(true).size # checking using the db + assert_equal 3, companies(:first_firm).clients_of_firm.size # checking via the collection + assert_equal 3, companies(:first_firm).clients_of_firm(true).size # checking using the db assert_equal natural, companies(:first_firm).clients_of_firm.last end def test_adding_using_create first_firm = companies(:first_firm) - assert_equal 2, first_firm.plain_clients.size - first_firm.plain_clients.create(:name => "Natural Company") - assert_equal 3, first_firm.plain_clients.length assert_equal 3, first_firm.plain_clients.size + first_firm.plain_clients.create(:name => "Natural Company") + assert_equal 4, first_firm.plain_clients.length + assert_equal 4, first_firm.plain_clients.size end def test_create_with_bang_on_has_many_when_parent_is_new_raises @@ -519,8 +544,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_adding_a_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")]) - assert_equal 3, companies(:first_firm).clients_of_firm.size - assert_equal 3, companies(:first_firm).clients_of_firm(true).size + assert_equal 4, companies(:first_firm).clients_of_firm.size + assert_equal 4, companies(:first_firm).clients_of_firm(true).size end def test_transactions_when_adding_to_persisted @@ -573,7 +598,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase company = companies(:first_firm) # company already has one client company.clients_of_firm.build("name" => "Another Client") company.clients_of_firm.build("name" => "Yet Another Client") - assert_equal 3, company.clients_of_firm.size + assert_equal 4, company.clients_of_firm.size end def test_collection_not_empty_after_building @@ -649,14 +674,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase Firm.column_names Client.column_names - assert_equal 1, first_firm.clients_of_firm.size + assert_equal 2, first_firm.clients_of_firm.size first_firm.clients_of_firm.reset assert_queries(1) do first_firm.clients_of_firm.create(:name => "Superstars") end - assert_equal 2, first_firm.clients_of_firm.size + assert_equal 3, first_firm.clients_of_firm.size end def test_create @@ -669,7 +694,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_create_many companies(:first_firm).clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}]) - assert_equal 3, companies(:first_firm).clients_of_firm(true).size + assert_equal 4, companies(:first_firm).clients_of_firm(true).size end def test_create_followed_by_save_does_not_load_target @@ -681,8 +706,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_deleting force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first) - assert_equal 0, companies(:first_firm).clients_of_firm.size - assert_equal 0, companies(:first_firm).clients_of_firm(true).size + assert_equal 1, companies(:first_firm).clients_of_firm.size + assert_equal 1, companies(:first_firm).clients_of_firm(true).size end def test_deleting_before_save @@ -779,8 +804,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_deleting_a_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.create("name" => "Another Client") - assert_equal 2, companies(:first_firm).clients_of_firm.size - companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1]]) + assert_equal 3, companies(:first_firm).clients_of_firm.size + companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1], companies(:first_firm).clients_of_firm[2]]) assert_equal 0, companies(:first_firm).clients_of_firm.size assert_equal 0, companies(:first_firm).clients_of_firm(true).size end @@ -789,7 +814,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase force_signal37_to_load_all_clients_of_firm companies(:first_firm).dependent_clients_of_firm.create("name" => "Another Client") clients = companies(:first_firm).dependent_clients_of_firm.to_a - assert_equal 2, clients.count + assert_equal 3, clients.count assert_difference "Client.count", -(clients.count) do companies(:first_firm).dependent_clients_of_firm.delete_all @@ -799,7 +824,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_delete_all_with_not_yet_loaded_association_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.create("name" => "Another Client") - assert_equal 2, companies(:first_firm).clients_of_firm.size + assert_equal 3, companies(:first_firm).clients_of_firm.size companies(:first_firm).clients_of_firm.reset companies(:first_firm).clients_of_firm.delete_all assert_equal 0, companies(:first_firm).clients_of_firm.size @@ -832,7 +857,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_clearing_an_association_collection firm = companies(:first_firm) client_id = firm.clients_of_firm.first.id - assert_equal 1, firm.clients_of_firm.size + assert_equal 2, firm.clients_of_firm.size firm.clients_of_firm.clear @@ -866,7 +891,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_clearing_a_dependent_association_collection firm = companies(:first_firm) client_id = firm.dependent_clients_of_firm.first.id - assert_equal 1, firm.dependent_clients_of_firm.size + assert_equal 2, firm.dependent_clients_of_firm.size assert_equal 1, Client.find_by_id(client_id).client_of # :delete_all is called on each client since the dependent options is :destroy @@ -897,7 +922,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_clearing_an_exclusively_dependent_association_collection firm = companies(:first_firm) client_id = firm.exclusively_dependent_clients_of_firm.first.id - assert_equal 1, firm.exclusively_dependent_clients_of_firm.size + assert_equal 2, firm.exclusively_dependent_clients_of_firm.size assert_equal [], Client.destroyed_client_ids[firm.id] @@ -953,10 +978,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_delete_all_association_with_primary_key_deletes_correct_records firm = Firm.first # break the vanilla firm_id foreign key - assert_equal 2, firm.clients.count + assert_equal 3, firm.clients.count firm.clients.first.update_columns(firm_id: nil) - assert_equal 1, firm.clients(true).count - assert_equal 1, firm.clients_using_primary_key_with_delete_all.count + assert_equal 2, firm.clients(true).count + assert_equal 2, firm.clients_using_primary_key_with_delete_all.count old_record = firm.clients_using_primary_key_with_delete_all.first firm = Firm.first firm.destroy @@ -988,8 +1013,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase force_signal37_to_load_all_clients_of_firm summit = Client.find_by_name('Summit') companies(:first_firm).clients_of_firm.delete(summit) - assert_equal 1, companies(:first_firm).clients_of_firm.size - assert_equal 1, companies(:first_firm).clients_of_firm(true).size + assert_equal 2, companies(:first_firm).clients_of_firm.size + assert_equal 2, companies(:first_firm).clients_of_firm(true).size assert_equal 2, summit.client_of end @@ -1026,8 +1051,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first) end - assert_equal 0, companies(:first_firm).reload.clients_of_firm.size - assert_equal 0, companies(:first_firm).clients_of_firm(true).size + assert_equal 1, companies(:first_firm).reload.clients_of_firm.size + assert_equal 1, companies(:first_firm).clients_of_firm(true).size end def test_destroying_by_fixnum_id @@ -1037,8 +1062,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id) end - assert_equal 0, companies(:first_firm).reload.clients_of_firm.size - assert_equal 0, companies(:first_firm).clients_of_firm(true).size + assert_equal 1, companies(:first_firm).reload.clients_of_firm.size + assert_equal 1, companies(:first_firm).clients_of_firm(true).size end def test_destroying_by_string_id @@ -1048,21 +1073,21 @@ class HasManyAssociationsTest < ActiveRecord::TestCase companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id.to_s) end - assert_equal 0, companies(:first_firm).reload.clients_of_firm.size - assert_equal 0, companies(:first_firm).clients_of_firm(true).size + assert_equal 1, companies(:first_firm).reload.clients_of_firm.size + assert_equal 1, companies(:first_firm).clients_of_firm(true).size end def test_destroying_a_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.create("name" => "Another Client") - assert_equal 2, companies(:first_firm).clients_of_firm.size + assert_equal 3, companies(:first_firm).clients_of_firm.size assert_difference "Client.count", -2 do companies(:first_firm).clients_of_firm.destroy([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1]]) end - assert_equal 0, companies(:first_firm).reload.clients_of_firm.size - assert_equal 0, companies(:first_firm).clients_of_firm(true).size + assert_equal 1, companies(:first_firm).reload.clients_of_firm.size + assert_equal 1, companies(:first_firm).clients_of_firm(true).size end def test_destroy_all @@ -1078,7 +1103,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_dependence firm = companies(:first_firm) - assert_equal 2, firm.clients.size + assert_equal 3, firm.clients.size firm.destroy assert Client.all.merge!(:where => "firm_id=#{firm.id}").to_a.empty? end @@ -1091,14 +1116,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_destroy_dependent_when_deleted_from_association # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first firm = Firm.all.merge!(:order => "id").first - assert_equal 2, firm.clients.size + assert_equal 3, firm.clients.size client = firm.clients.first firm.clients.delete(client) assert_raise(ActiveRecord::RecordNotFound) { Client.find(client.id) } assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(client.id) } - assert_equal 1, firm.clients.size + assert_equal 2, firm.clients.size end def test_three_levels_of_dependence @@ -1113,12 +1138,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_dependence_with_transaction_support_on_failure firm = companies(:first_firm) clients = firm.clients - assert_equal 2, clients.length + assert_equal 3, clients.length clients.last.instance_eval { def overwrite_to_raise() raise "Trigger rollback" end } firm.destroy rescue "do nothing" - assert_equal 2, Client.all.merge!(:where => "firm_id=#{firm.id}").to_a.size + assert_equal 3, Client.all.merge!(:where => "firm_id=#{firm.id}").to_a.size end def test_dependence_on_account @@ -1239,7 +1264,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_get_ids - assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids + assert_equal [companies(:first_client).id, companies(:second_client).id, companies(:another_first_firm_client).id], companies(:first_firm).client_ids end def test_get_ids_for_loaded_associations @@ -1254,7 +1279,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_get_ids_for_unloaded_associations_does_not_load_them company = companies(:first_firm) assert !company.clients.loaded? - assert_equal [companies(:first_client).id, companies(:second_client).id], company.client_ids + assert_equal [companies(:first_client).id, companies(:second_client).id, companies(:another_first_firm_client).id], company.client_ids assert !company.clients.loaded? end @@ -1263,7 +1288,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_get_ids_for_ordered_association - assert_equal [companies(:second_client).id, companies(:first_client).id], companies(:first_firm).clients_ordered_by_name_ids + assert_equal [companies(:another_first_firm_client).id, companies(:second_client).id, companies(:first_client).id], companies(:first_firm).clients_ordered_by_name_ids end def test_get_ids_for_association_on_new_record_does_not_try_to_find_records @@ -1357,9 +1382,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal false, firm.clients.include?(client) end - def test_calling_first_or_last_on_association_should_not_load_association + def test_calling_first_nth_or_last_on_association_should_not_load_association firm = companies(:first_firm) firm.clients.first + firm.clients.second firm.clients.last assert !firm.clients.loaded? end @@ -1384,30 +1410,33 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_queries 1 do firm.clients.first + firm.clients.second firm.clients.last end assert firm.clients.loaded? end - def test_calling_first_or_last_on_existing_record_with_create_should_not_load_association + def test_calling_first_nth_or_last_on_existing_record_with_create_should_not_load_association firm = companies(:first_firm) firm.clients.create(:name => 'Foo') assert !firm.clients.loaded? - assert_queries 2 do + assert_queries 3 do firm.clients.first + firm.clients.second firm.clients.last end assert !firm.clients.loaded? end - def test_calling_first_or_last_on_new_record_should_not_run_queries + def test_calling_first_nth_or_last_on_new_record_should_not_run_queries firm = Firm.new assert_no_queries do firm.clients.first + firm.clients.second firm.clients.last end end @@ -1494,7 +1523,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_calling_many_should_return_true_if_more_than_one firm = companies(:first_firm) assert firm.clients.many? - assert_equal 2, firm.clients.size + assert_equal 3, firm.clients.size end def test_joins_with_namespaced_model_should_use_correct_type diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index fe5de44409..703f805188 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -401,7 +401,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa assert_equal new_client, companies(:first_firm).clients_of_firm.last assert !companies(:first_firm).save assert !new_client.persisted? - assert_equal 1, companies(:first_firm).clients_of_firm(true).size + assert_equal 2, companies(:first_firm).clients_of_firm(true).size end def test_adding_before_save @@ -455,7 +455,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa company.name += '-changed' assert_queries(2) { assert company.save } assert new_client.persisted? - assert_equal 2, company.clients_of_firm(true).size + assert_equal 3, company.clients_of_firm(true).size end def test_build_many_before_save @@ -464,7 +464,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa company.name += '-changed' assert_queries(3) { assert company.save } - assert_equal 3, company.clients_of_firm(true).size + assert_equal 4, company.clients_of_firm(true).size end def test_build_via_block_before_save @@ -475,7 +475,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa company.name += '-changed' assert_queries(2) { assert company.save } assert new_client.persisted? - assert_equal 2, company.clients_of_firm(true).size + assert_equal 3, company.clients_of_firm(true).size end def test_build_many_via_block_before_save @@ -488,7 +488,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa company.name += '-changed' assert_queries(3) { assert company.save } - assert_equal 3, company.clients_of_firm(true).size + assert_equal 4, company.clients_of_firm(true).size end def test_replace_on_new_object diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 0bc81ffe56..db999f90ab 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -278,7 +278,7 @@ class CalculationsTest < ActiveRecord::TestCase c = Company.group("UPPER(#{QUOTED_TYPE})").count(:all) assert_equal 2, c[nil] assert_equal 1, c['DEPENDENTFIRM'] - assert_equal 4, c['CLIENT'] + assert_equal 5, c['CLIENT'] assert_equal 2, c['FIRM'] end @@ -286,7 +286,7 @@ class CalculationsTest < ActiveRecord::TestCase c = Company.group("UPPER(companies.#{QUOTED_TYPE})").count(:all) assert_equal 2, c[nil] assert_equal 1, c['DEPENDENTFIRM'] - assert_equal 4, c['CLIENT'] + assert_equal 5, c['CLIENT'] assert_equal 2, c['FIRM'] end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 9cd1e0ace8..47c2217161 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -900,8 +900,8 @@ class FinderTest < ActiveRecord::TestCase end def test_select_values - assert_equal ["1","2","3","4","5","6","7","8","9", "10"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s } - assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy", "Ex Nihilo Part Deux"], Company.connection.select_values("SELECT name FROM companies ORDER BY id") + assert_equal ["1","2","3","4","5","6","7","8","9", "10", "11"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s } + assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy", "Ex Nihilo Part Deux", "Apex"], Company.connection.select_values("SELECT name FROM companies ORDER BY id") end def test_select_rows diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index d2b5a06b55..e2ff2aa451 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -222,9 +222,9 @@ class InheritanceTest < ActiveRecord::TestCase end def test_inheritance_condition - assert_equal 10, Company.count + assert_equal 11, Company.count assert_equal 2, Firm.count - assert_equal 4, Client.count + assert_equal 5, Client.count end def test_alt_inheritance_condition diff --git a/activerecord/test/fixtures/companies.yml b/activerecord/test/fixtures/companies.yml index 0766e92027..ab9d5378ad 100644 --- a/activerecord/test/fixtures/companies.yml +++ b/activerecord/test/fixtures/companies.yml @@ -57,3 +57,11 @@ odegy: id: 9 name: Odegy type: ExclusivelyDependentFirm + +another_first_firm_client: + id: 11 + type: Client + firm_id: 1 + client_of: 1 + name: Apex + firm_name: 37signals -- cgit v1.2.3 From fc913d40160dd0c07bc7d2aa0825badded56bbb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 21 Jan 2014 22:38:07 -0200 Subject: Fix typo --- activemodel/test/cases/dirty_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb index efadb2600f..8b55901a65 100644 --- a/activemodel/test/cases/dirty_test.rb +++ b/activemodel/test/cases/dirty_test.rb @@ -83,7 +83,7 @@ class DirtyTest < ActiveModel::TestCase assert_not_nil @model.changes['name'] end - test "be cosistent with symbols arguments after the changes are applied" do + test "be consistent with symbols arguments after the changes are applied" do @model.name = "David" assert @model.attribute_changed?(:name) @model.save -- cgit v1.2.3 From 43675f014c8d0913650823a5a3e92c8555a3caed Mon Sep 17 00:00:00 2001 From: Kelsey Schlarman Date: Tue, 21 Jan 2014 18:19:51 -0800 Subject: Calling reset on a collection association should unload the assocation Need to define #reset on CollectionProxy. --- activerecord/CHANGELOG.md | 6 ++++++ .../active_record/associations/collection_proxy.rb | 21 +++++++++++++++++++++ activerecord/test/cases/associations_test.rb | 9 +++++++++ 3 files changed, 36 insertions(+) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 0f544d1b5d..59d586c6f8 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,9 @@ +* Calling reset on a collection association should unload the assocation. + + Fixes #13777. + + *Kelsey Schlarman* + * Make enum fields work as expected with the `ActiveModel::Dirty` API. Before this change, using the dirty API would have surprising results: diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index a67b834a80..08bc409816 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -1004,6 +1004,27 @@ module ActiveRecord proxy_association.reload self end + + # Unloads the association + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets # fetches pets from the database + # # => [#] + # + # person.pets # uses the pets cache + # # => [#] + # + # person.pets.reset # clears the pets cache + # + # person.pets # fetches pets from the database + # # => [#] + def reset + proxy_association.reset + proxy_association.reset_scope + end end end end diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 48e6fc5cd4..f663b5490c 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -255,6 +255,15 @@ class AssociationProxyTest < ActiveRecord::TestCase assert_equal man, man.interests.where("1=1").first.man end end + + def test_reset_unloads_target + david = authors(:david) + david.posts.reload + + assert david.posts.loaded? + david.posts.reset + assert !david.posts.loaded? + end end class OverridingAssociationsTest < ActiveRecord::TestCase -- cgit v1.2.3 From 9e63ead4ae3ba19dcb128f769414be8b21f65f7b Mon Sep 17 00:00:00 2001 From: Semenyuk Dmitriy Date: Wed, 22 Jan 2014 15:27:54 +0600 Subject: [ci skip] Added missing `file` delivery method --- guides/source/action_mailer_basics.md | 2 +- guides/source/configuring.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 61fd762304..e965480020 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -611,7 +611,7 @@ files (environment.rb, production.rb, etc...) |`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:
  • `:address` - Allows you to use a remote mail server. Just change it from its default "localhost" setting.
  • `:port` - On the off chance that your mail server doesn't run on port 25, you can change it.
  • `:domain` - If you need to specify a HELO domain, you can do it here.
  • `:user_name` - If your mail server requires authentication, set the username in this setting.
  • `:password` - If your mail server requires authentication, set the password in this setting.
  • `:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`.
  • `:enable_starttls_auto` - Set this to `false` if there is a problem with your server certificate that you cannot resolve.
| |`sendmail_settings`|Allows you to override options for the `:sendmail` delivery method.
  • `:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.
  • `:arguments` - The command line arguments to be passed to sendmail. Defaults to `-i -t`.
| |`raise_delivery_errors`|Whether or not errors should be raised if the email fails to be delivered. This only works if the external email server is configured for immediate delivery.| -|`delivery_method`|Defines a delivery method. Possible values are `:smtp` (default), `:sendmail`, `:file` and `:test`.| +|`delivery_method`|Defines a delivery method. Possible values are:
  • `:smtp` (default), can be configured by using `config.action_mailer.smtp_settings`.
  • `:sendmail`, can be configured by using `config.action_mailer.sendmail_settings`.
  • `:file`: save emails to files; can be configured by using `config.action_mailer.file_settings`.
  • `:test`: save emails to `ActionMailer::Base.deliveries` array.
See [API docs](http://api.rubyonrails.org/classes/ActionMailer/Base.html) for more info.| |`perform_deliveries`|Determines whether deliveries are actually carried out when the `deliver` method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing.| |`deliveries`|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.| |`default_options`|Allows you to set default values for the `mail` method options (`:from`, `:reply_to`, etc.).| diff --git a/guides/source/configuring.md b/guides/source/configuring.md index c30b806907..c954db514b 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -404,7 +404,7 @@ There are a number of settings available on `config.action_mailer`: * `config.action_mailer.raise_delivery_errors` specifies whether to raise an error if email delivery cannot be completed. It defaults to true. -* `config.action_mailer.delivery_method` defines the delivery method. The allowed values are `:smtp` (default), `:sendmail`, and `:test`. +* `config.action_mailer.delivery_method` defines the delivery method and defaults to `:smtp`. See the [configuration section in the Action Mailer guide](http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration) for more info. * `config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default. It can be convenient to set it to false for testing. -- cgit v1.2.3 From ce91efed652bbcec42dbc68e421f717fb2ecc9b8 Mon Sep 17 00:00:00 2001 From: kei Date: Wed, 22 Jan 2014 22:13:38 +0900 Subject: Remove duplicate merge --- activerecord/lib/active_record/relation.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 17af887abc..fb213dc6f7 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -535,7 +535,6 @@ module ActiveRecord } binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }] - binds.merge!(Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]) Hash[equalities.map { |where| name = where.left.name -- cgit v1.2.3 From 72403003085ff6413ddf53e34561175e3dd68168 Mon Sep 17 00:00:00 2001 From: Keenan Brock Date: Wed, 22 Jan 2014 09:28:19 -0500 Subject: put core at the beginning so other classes can modify the behavior --- activerecord/lib/active_record/base.rb | 2 +- activerecord/lib/active_record/timestamp.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 1d3ec75aa1..9ec1feea97 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -294,6 +294,7 @@ module ActiveRecord #:nodoc: extend Enum extend Delegation::DelegateCache + include Core include Persistence include NoTouching include ReadonlyAttributes @@ -320,7 +321,6 @@ module ActiveRecord #:nodoc: include Reflection include Serialization include Store - include Core end ActiveSupport.run_load_hooks(:active_record, Base) diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index e0541b7681..09b37b7183 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -37,8 +37,8 @@ module ActiveRecord end def initialize_dup(other) # :nodoc: - clear_timestamp_attributes super + clear_timestamp_attributes end private -- cgit v1.2.3 From 1c2c5527032f7a79aff53d29e9640e27957f1b49 Mon Sep 17 00:00:00 2001 From: Adrien Coquio Date: Wed, 22 Jan 2014 15:22:35 +0100 Subject: Add failing test for ActiveModel::Errors#has_key? method From the doc, this method should return false and not nil if there is no errors for this key --- activemodel/test/cases/errors_test.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index bbd186d83d..481f274936 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -54,6 +54,11 @@ class ErrorsTest < ActiveModel::TestCase assert errors.has_key?(:foo), 'errors should have key :foo' end + def test_has_no_key + errors = ActiveModel::Errors.new(self) + assert_equal false, errors.has_key?(:name), 'errors should not have key :name' + end + test "clear errors" do person = Person.new person.validate! -- cgit v1.2.3 From 2a29c7d3b7d4cb5779ffb3d728f1e6e91d677067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 22 Jan 2014 13:24:48 -0200 Subject: Make CollectionProxy#reset return self --- activerecord/lib/active_record/associations/collection_proxy.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 08bc409816..eba688866c 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -1005,7 +1005,7 @@ module ActiveRecord self end - # Unloads the association + # Unloads the association. Returns +self+. # # class Person < ActiveRecord::Base # has_many :pets @@ -1024,6 +1024,7 @@ module ActiveRecord def reset proxy_association.reset proxy_association.reset_scope + self end end end -- cgit v1.2.3 From 8f17a834ae98cea28ceff22bf11ce346b867bc90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 22 Jan 2014 13:25:12 -0200 Subject: Improve the CHANGELOG entry [ci skip] --- activerecord/CHANGELOG.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 59d586c6f8..7db99d4aeb 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,7 +1,19 @@ -* Calling reset on a collection association should unload the assocation. +* Reset the collection association when calling `reset` on it. + + Before: + + post.comments.loaded? # => true + post.comments.reset + post.comments.loaded? # => true + + After: + + post.comments.loaded? # => true + post.comments.reset + post.comments.loaded? # => false Fixes #13777. - + *Kelsey Schlarman* * Make enum fields work as expected with the `ActiveModel::Dirty` API. -- cgit v1.2.3 From 8cbd500035aa64a5440d5ccc44209cfd902118fc Mon Sep 17 00:00:00 2001 From: Keenan Brock Date: Wed, 22 Jan 2014 12:55:46 -0500 Subject: Move changed_attributes into dirty.rb Move serialization dirty into serialization.rb --- .../lib/active_record/attribute_methods/dirty.rb | 22 +++++++++++++++++++++- .../attribute_methods/serialization.rb | 8 ++++++++ activerecord/lib/active_record/core.rb | 16 ++-------------- activerecord/lib/active_record/inheritance.rb | 10 ++++++++++ activerecord/lib/active_record/scoping.rb | 5 +++++ activerecord/lib/active_record/timestamp.rb | 2 +- 6 files changed, 47 insertions(+), 16 deletions(-) diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 19e81abba5..c10f400477 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -38,7 +38,27 @@ module ActiveRecord end end + def initialize_dup(other) # :nodoc: + super + init_changed_attributes + end + private + def initialize_internals_callback + super + init_changed_attributes + end + + def init_changed_attributes + @changed_attributes = nil + # Intentionally avoid using #column_defaults since overridden defaults (as is done in + # optimistic locking) won't get written unless they get marked as changed + self.class.columns.each do |c| + attr, orig_value = c.name, c.default + changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr]) + end + end + # Wrap write_attribute to remember original attribute value. def write_attribute(attr, value) attr = attr.to_s @@ -67,7 +87,7 @@ module ActiveRecord # Serialized attributes should always be written in case they've been # changed in place. def keys_for_partial_write - changed | (attributes.keys & self.class.serialized_attributes.keys) + changed end def _field_changed?(attr, old, value) diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index d484659190..3227464032 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -115,6 +115,14 @@ module ActiveRecord end end + def should_record_timestamps? + super || (self.record_timestamps && (attributes.keys & self.class.serialized_attributes.keys).present?) + end + + def keys_for_partial_write + super | (attributes.keys & self.class.serialized_attributes.keys) + end + def type_cast_attribute_for_write(column, value) if column && coder = self.class.serialized_attributes[column.name] Attribute.new(coder, value, :unserialized) diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index a4fe1efd33..6f02c763fe 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -182,9 +182,7 @@ module ActiveRecord @column_types = self.class.column_types init_internals - init_changed_attributes - ensure_proper_type - populate_with_current_scope_attributes + initialize_internals_callback # +options+ argument is only needed to make protected_attributes gem easier to hook. # Remove it when we drop support to this gem. @@ -255,16 +253,12 @@ module ActiveRecord run_callbacks(:initialize) unless _initialize_callbacks.empty? - @changed_attributes = {} - init_changed_attributes - @aggregation_cache = {} @association_cache = {} @attributes_cache = {} @new_record = true - ensure_proper_type super end @@ -440,13 +434,7 @@ module ActiveRecord @reflects_state = [false] end - def init_changed_attributes - # Intentionally avoid using #column_defaults since overridden defaults (as is done in - # optimistic locking) won't get written unless they get marked as changed - self.class.columns.each do |c| - attr, orig_value = c.name, c.default - changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr]) - end + def initialize_internals_callback end # This method is needed to make protected_attributes gem easier to hook. diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index da73112e90..08fc91c9df 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -195,8 +195,18 @@ module ActiveRecord end end + def initialize_dup(other) + super + ensure_proper_type + end + private + def initialize_internals_callback + super + ensure_proper_type + end + # Sets the attribute used for single table inheritance to this class name if this is not the # ActiveRecord::Base descendant. # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb index 0cf3d59985..3e43591672 100644 --- a/activerecord/lib/active_record/scoping.rb +++ b/activerecord/lib/active_record/scoping.rb @@ -27,6 +27,11 @@ module ActiveRecord end end + def initialize_internals_callback + super + populate_with_current_scope_attributes + end + # This class stores the +:current_scope+ and +:ignore_default_scope+ values # for different classes. The registry is stored as a thread local, which is # accessed through +ScopeRegistry.current+. diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 09b37b7183..7178bed560 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -71,7 +71,7 @@ module ActiveRecord end def should_record_timestamps? - self.record_timestamps && (!partial_writes? || changed? || (attributes.keys & self.class.serialized_attributes.keys).present?) + self.record_timestamps && (!partial_writes? || changed?) end def timestamp_attributes_for_create_in_model -- cgit v1.2.3 From b97035df64f5b2f912425c4a7fcb6e6bb3ddab8d Mon Sep 17 00:00:00 2001 From: Adrien Coquio Date: Wed, 22 Jan 2014 15:49:31 +0100 Subject: Fix ActiveModel::Errors#has_key? return value --- activemodel/lib/active_model/errors.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 010c4bb6f9..9c3bc913e1 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -94,7 +94,7 @@ module ActiveModel # person.errors.include?(:name) # => true # person.errors.include?(:age) # => false def include?(attribute) - (v = messages[attribute]) && v.any? + messages[attribute].present? end # aliases include? alias :has_key? :include? -- cgit v1.2.3 From db95c7dcebcd240ba46cc5df4eb2b8f240f497bb Mon Sep 17 00:00:00 2001 From: Adrien Coquio Date: Wed, 22 Jan 2014 21:25:58 +0100 Subject: Update ActiveModel::Errors.has_key? test --- activemodel/test/cases/errors_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index 481f274936..def28578f8 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -51,7 +51,7 @@ class ErrorsTest < ActiveModel::TestCase def test_has_key? errors = ActiveModel::Errors.new(self) errors[:foo] = 'omg' - assert errors.has_key?(:foo), 'errors should have key :foo' + assert_equal true, errors.has_key?(:foo), 'errors should have key :foo' end def test_has_no_key -- cgit v1.2.3 From 96dd3016d1797e9ef6b59bce57f1baa5fd2a2210 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Thu, 23 Jan 2014 21:56:28 -0500 Subject: test boolean and number json param parsing --- actionpack/test/dispatch/request/json_params_parsing_test.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index dba9ab688f..c609075e6b 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -23,6 +23,13 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest ) end + test "parses boolean and number json params for application json" do + assert_parses( + {"item" => {"enabled" => false, "count" => 10}}, + "{\"item\": {\"enabled\": false, \"count\": 10}}", { 'CONTENT_TYPE' => 'application/json' } + ) + end + test "parses json params for application jsonrequest" do assert_parses( {"person" => {"name" => "David"}}, -- cgit v1.2.3 From aae53d2175b306bf9e085a86d9e5d8a194fcf63b Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Thu, 23 Jan 2014 22:28:24 -0500 Subject: unify param.require tests --- .../test/controller/parameters/parameters_require_test.rb | 10 ---------- actionpack/test/controller/required_params_test.rb | 8 ++++++++ 2 files changed, 8 insertions(+), 10 deletions(-) delete mode 100644 actionpack/test/controller/parameters/parameters_require_test.rb diff --git a/actionpack/test/controller/parameters/parameters_require_test.rb b/actionpack/test/controller/parameters/parameters_require_test.rb deleted file mode 100644 index bdaba8d2d8..0000000000 --- a/actionpack/test/controller/parameters/parameters_require_test.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'abstract_unit' -require 'action_controller/metal/strong_parameters' - -class ParametersRequireTest < ActiveSupport::TestCase - test "required parameters must be present not merely not nil" do - assert_raises(ActionController::ParameterMissing) do - ActionController::Parameters.new(person: {}).require(:person) - end - end -end diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb index 343d57c300..25d0337212 100644 --- a/actionpack/test/controller/required_params_test.rb +++ b/actionpack/test/controller/required_params_test.rb @@ -25,3 +25,11 @@ class ActionControllerRequiredParamsTest < ActionController::TestCase assert_response :ok end end + +class ParametersRequireTest < ActiveSupport::TestCase + test "required parameters must be present not merely not nil" do + assert_raises(ActionController::ParameterMissing) do + ActionController::Parameters.new(person: {}).require(:person) + end + end +end -- cgit v1.2.3 From 9b254df181373578a0d78dbf8ae14d55b65289c1 Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Fri, 24 Jan 2014 12:14:02 +0530 Subject: Remove unused assignment to fix warnings in enum test. --- activerecord/test/cases/enum_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index 9fbfebcca2..8719f45e76 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -98,7 +98,6 @@ class EnumTest < ActiveRecord::TestCase end test "persist changes that are dirty" do - old_status = @book.status @book.status = :published assert @book.attribute_changed?(:status) @book.status = :written -- cgit v1.2.3 From 1adaf6ca9fcaaa96cdb2438d6b95067f8f927ce3 Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Fri, 24 Jan 2014 12:32:17 +0530 Subject: `requies` => `requires` --- guides/source/asset_pipeline.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index bce5d6c55f..b592442e9d 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -1145,7 +1145,7 @@ config.assets.digest = true ``` Rails 4 no longer sets default config values for Sprockets in `test.rb`, so -`test.rb` now requies Sprockets configuration. The old defaults in the test +`test.rb` now requires Sprockets configuration. The old defaults in the test environment are: `config.assets.compile = true`, `config.assets.compress = false`, `config.assets.debug = false` and `config.assets.digest = false`. -- cgit v1.2.3 From 5b31da48813cb37ca162b3ea7c224018b96f3033 Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Fri, 24 Jan 2014 12:37:24 +0530 Subject: `easiy` => `easy` --- guides/source/configuring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 78dc84180c..f6ad6d8f6b 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -580,7 +580,7 @@ Rails will now prepend "/app1" when generating links. #### Using Passenger -Passenger makes it easiy to run your application in a subdirectory. You can find +Passenger makes it easy to run your application in a subdirectory. You can find the relevant configuration in the [passenger manual](http://www.modrails.com/documentation/Users%20guide%20Apache.html#deploying_rails_to_sub_uri). -- cgit v1.2.3 From 3e28b7c2bbd278381e9cf89086c0d3ac62a58f72 Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Fri, 24 Jan 2014 12:40:21 +0530 Subject: `framwork` => `framework` --- guides/source/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/testing.md b/guides/source/testing.md index 165eca739a..b5119829b3 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -361,7 +361,7 @@ NOTE: The execution of each test method stops as soon as any error or an asserti When a test fails you are presented with the corresponding backtrace. By default Rails filters that backtrace and will only print lines relevant to your -application. This eliminates the framwork noise and helps to focus on your +application. This eliminates the framework noise and helps to focus on your code. However there are situations when you want to see the full backtrace. simply set the `BACKTRACE` environment variable to enable this behavior: -- cgit v1.2.3 From c1f8a0d61409b6c9fa16847b0ecf694cc4d4cecf Mon Sep 17 00:00:00 2001 From: Maurizio De Santis Date: Thu, 23 Jan 2014 14:50:29 +0100 Subject: Fix `rake routes` error when `Rails::Engine` with empty routes is mounted; fixes rails/rails#13810 Squash --- actionpack/CHANGELOG.md | 6 ++++++ actionpack/lib/action_dispatch/routing/inspector.rb | 6 +++--- actionpack/test/dispatch/routing/inspector_test.rb | 21 +++++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index a7231b9e59..9add810f81 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,9 @@ +* Fix `rake routes` error when `Rails::Engine` with empty routes is mounted. + + Fixes #13810. + + *Maurizio De Santis* + * Automatically convert dashes to underscores for shorthand routes, e.g: get '/our-work/latest' diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index f612e91aef..71a0c5e826 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -194,9 +194,9 @@ module ActionDispatch end def widths(routes) - [routes.map { |r| r[:name].length }.max, - routes.map { |r| r[:verb].length }.max, - routes.map { |r| r[:path].length }.max] + [routes.map { |r| r[:name].length }.max || 0, + routes.map { |r| r[:verb].length }.max || 0, + routes.map { |r| r[:path].length }.max || 0] end end diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index 8045464284..74515e3f57 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -54,6 +54,27 @@ module ActionDispatch ], output end + def test_displaying_routes_for_engines_without_routes + engine = Class.new(Rails::Engine) do + def self.inspect + "Blog::Engine" + end + end + engine.routes.draw do + end + + output = draw do + mount engine => "/blog", as: "blog" + end + + assert_equal [ + "Prefix Verb URI Pattern Controller#Action", + " blog /blog Blog::Engine", + "", + "Routes for Blog::Engine:" + ], output + end + def test_cart_inspect output = draw do get '/cart', :to => 'cart#show' -- cgit v1.2.3 From cf6c00d027e44a52bfd1df19e2a9cec52fea2c7c Mon Sep 17 00:00:00 2001 From: Byron Bischoff Date: Fri, 24 Jan 2014 10:00:31 -0800 Subject: app_rails_loader.rb should check if bin/rails is a File before calling File.read(exe); closes #13825 --- railties/lib/rails/app_rails_loader.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/app_rails_loader.rb b/railties/lib/rails/app_rails_loader.rb index 1610751844..56f05b3844 100644 --- a/railties/lib/rails/app_rails_loader.rb +++ b/railties/lib/rails/app_rails_loader.rb @@ -55,7 +55,7 @@ EOS end def self.find_executable - EXECUTABLES.find { |exe| File.exist?(exe) } + EXECUTABLES.find { |exe| File.file?(exe) } end end end -- cgit v1.2.3 From 0a43bf3146f0796fe23da50fa5c40e28b1398139 Mon Sep 17 00:00:00 2001 From: Guillermo Iguaran Date: Fri, 24 Jan 2014 14:10:46 -0500 Subject: Add a test-case for GH #13825 --- railties/test/app_rails_loader_test.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/railties/test/app_rails_loader_test.rb b/railties/test/app_rails_loader_test.rb index 92cb3233d8..1d3b80253a 100644 --- a/railties/test/app_rails_loader_test.rb +++ b/railties/test/app_rails_loader_test.rb @@ -22,8 +22,14 @@ class AppRailsLoaderTest < ActiveSupport::TestCase exe = "#{script_dir}/rails" test "is not in a Rails application if #{exe} is not found in the current or parent directories" do - File.stubs(:exist?).with('bin/rails').returns(false) - File.stubs(:exist?).with('script/rails').returns(false) + File.stubs(:file?).with('bin/rails').returns(false) + File.stubs(:file?).with('script/rails').returns(false) + + assert !Rails::AppRailsLoader.exec_app_rails + end + + test "is not in a Rails application if #{exe} exists but is a folder" do + FileUtils.mkdir_p(exe) assert !Rails::AppRailsLoader.exec_app_rails end -- cgit v1.2.3 From 098a960266dbcabaa6169940520f1f4cf2169cdf Mon Sep 17 00:00:00 2001 From: John Olmsted & Strand McCutchen Date: Fri, 24 Jan 2014 11:24:06 -0800 Subject: Reordered classes in AR Validation #validates_with example [ci skip] Person called GoodnessValidator before it was defined. This change will compile the example correctly. --- guides/source/active_record_validations.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index efa826e8df..b04c7a90e2 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -616,10 +616,6 @@ The default error message is _"has already been taken"_. This helper passes the record to a separate class for validation. ```ruby -class Person < ActiveRecord::Base - validates_with GoodnessValidator -end - class GoodnessValidator < ActiveModel::Validator def validate(record) if record.first_name == "Evil" @@ -627,6 +623,10 @@ class GoodnessValidator < ActiveModel::Validator end end end + +class Person < ActiveRecord::Base + validates_with GoodnessValidator +end ``` NOTE: Errors added to `record.errors[:base]` relate to the state of the record @@ -644,10 +644,6 @@ Like all other validations, `validates_with` takes the `:if`, `:unless` and validator class as `options`: ```ruby -class Person < ActiveRecord::Base - validates_with GoodnessValidator, fields: [:first_name, :last_name] -end - class GoodnessValidator < ActiveModel::Validator def validate(record) if options[:fields].any?{|field| record.send(field) == "Evil" } @@ -655,6 +651,10 @@ class GoodnessValidator < ActiveModel::Validator end end end + +class Person < ActiveRecord::Base + validates_with GoodnessValidator, fields: [:first_name, :last_name] +end ``` Note that the validator will be initialized *only once* for the whole application -- cgit v1.2.3 From 3a0ddf33945219ea65777c1813902d783245739d Mon Sep 17 00:00:00 2001 From: Kuldeep Aggarwal Date: Sat, 25 Jan 2014 02:29:23 +0530 Subject: Fix `ActiveRecord::RecordNotFound` error message with custom primary key --- .../lib/active_record/relation/finder_methods.rb | 4 +-- activerecord/test/cases/finder_test.rb | 31 +++++++++++++++++----- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 2dd1e6f14b..01d46f7676 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -311,9 +311,9 @@ module ActiveRecord conditions = " [#{conditions}]" if conditions if Array(ids).size == 1 - error = "Couldn't find #{@klass.name} with #{primary_key}=#{ids}#{conditions}" + error = "Couldn't find #{@klass.name} with '#{primary_key}'=#{ids}#{conditions}" else - error = "Couldn't find all #{@klass.name.pluralize} with IDs " + error = "Couldn't find all #{@klass.name.pluralize} with '#{primary_key}': " error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})" end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 47c2217161..b1eded6494 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -951,14 +951,23 @@ class FinderTest < ActiveRecord::TestCase end def test_find_one_message_with_custom_primary_key - Toy.primary_key = :name - begin - Toy.find 'Hello World!' - rescue ActiveRecord::RecordNotFound => e - assert_equal 'Couldn\'t find Toy with name=Hello World!', e.message + table_with_custom_primary_key do |model| + model.primary_key = :name + e = assert_raises(ActiveRecord::RecordNotFound) do + model.find 'Hello World!' + end + assert_equal %Q{Couldn't find MercedesCar with 'name'=Hello World!}, e.message + end + end + + def test_find_some_message_with_custom_primary_key + table_with_custom_primary_key do |model| + model.primary_key = :name + e = assert_raises(ActiveRecord::RecordNotFound) do + model.find 'Hello', 'World!' + end + assert_equal %Q{Couldn't find all MercedesCars with 'name': (Hello, World!) (found 0 results, but was looking for 2)}, e.message end - ensure - Toy.reset_primary_key end def test_find_without_primary_key @@ -979,4 +988,12 @@ class FinderTest < ActiveRecord::TestCase ActiveRecord::Base.send(:replace_bind_variables, statement, vars) end end + + def table_with_custom_primary_key + yield(Class.new(Toy) do + def self.name + 'MercedesCar' + end + end) + end end -- cgit v1.2.3 From e2f3e0dc50a30c8600df6dc8106dca67d4cd0961 Mon Sep 17 00:00:00 2001 From: Francesco Rodriguez Date: Fri, 24 Jan 2014 22:25:15 -0300 Subject: Remove unused argument. --- actionpack/test/controller/http_token_authentication_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb index ebf6d224aa..86b94652ce 100644 --- a/actionpack/test/controller/http_token_authentication_test.rb +++ b/actionpack/test/controller/http_token_authentication_test.rb @@ -21,7 +21,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase private def authenticate - authenticate_or_request_with_http_token do |token, options| + authenticate_or_request_with_http_token do |token, _| token == 'lifo' end end -- cgit v1.2.3 From 19a4ef305d84f4aad633c25f850967bbc375da84 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Mon, 20 Jan 2014 04:21:44 -0800 Subject: Rewrote the tests for has_secure_password --- activemodel/test/cases/secure_password_new_test.rb | 181 +++++++++++++++++++++ activemodel/test/models/oauthed_user.rb | 2 +- activemodel/test/models/user.rb | 2 +- 3 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 activemodel/test/cases/secure_password_new_test.rb diff --git a/activemodel/test/cases/secure_password_new_test.rb b/activemodel/test/cases/secure_password_new_test.rb new file mode 100644 index 0000000000..174d18ec8b --- /dev/null +++ b/activemodel/test/cases/secure_password_new_test.rb @@ -0,0 +1,181 @@ +require 'cases/helper' +require 'models/user' +require 'models/visitor' + +require 'active_support/all' +class SecurePasswordTest < ActiveModel::TestCase + setup do + ActiveModel::SecurePassword.min_cost = true + + @user = User.new + @visitor = Visitor.new + + # Simulate loading an existing user from the DB + @existing_user = User.new + @existing_user.password_digest = BCrypt::Password.create('password', cost: BCrypt::Engine::MIN_COST) + end + + teardown do + ActiveModel::SecurePassword.min_cost = false + end + + test "create and updating without validations" do + assert @visitor.valid?(:create), 'visitor should be valid' + assert @visitor.valid?(:update), 'visitor should be valid' + + @visitor.password = '123' + @visitor.password_confirmation = '456' + + assert @visitor.valid?(:create), 'visitor should be valid' + assert @visitor.valid?(:update), 'visitor should be valid' + end + + test "create a new user with validation and a blank password" do + @user.password = '' + assert !@user.valid?(:create), 'user should be invalid' + assert_equal 1, @user.errors.count + assert_equal ["can't be blank"], @user.errors[:password] + end + + test "create a new user with validation and a nil password" do + @user.password = nil + assert !@user.valid?(:create), 'user should be invalid' + assert_equal 1, @user.errors.count + assert_equal ["can't be blank"], @user.errors[:password] + end + + test "create a new user with validation and a blank password confirmation" do + @user.password = 'password' + @user.password_confirmation = '' + assert !@user.valid?(:create), 'user should be invalid' + assert_equal 1, @user.errors.count + assert_equal ["doesn't match Password"], @user.errors[:password_confirmation] + end + + test "create a new user with validation and a nil password confirmation" do + @user.password = 'password' + @user.password_confirmation = nil + assert @user.valid?(:create), 'user should be valid' + end + + test "create a new user with validation and an incorrect password confirmation" do + @user.password = 'password' + @user.password_confirmation = 'something else' + assert !@user.valid?(:create), 'user should be invalid' + assert_equal 1, @user.errors.count + assert_equal ["doesn't match Password"], @user.errors[:password_confirmation] + end + + test "create a new user with validation and a correct password confirmation" do + @user.password = 'password' + @user.password_confirmation = 'something else' + assert !@user.valid?(:create), 'user should be invalid' + assert_equal 1, @user.errors.count + assert_equal ["doesn't match Password"], @user.errors[:password_confirmation] + end + + test "update an existing user with validation and no change in password" do + assert @existing_user.valid?(:update), 'user should be valid' + end + + test "updating an existing user with validation and a blank password" do + @existing_user.password = '' + assert @existing_user.valid?(:update), 'user should be valid' + end + + test "updating an existing user with validation and a blank password and password_confirmation" do + @existing_user.password = '' + @existing_user.password_confirmation = '' + assert @existing_user.valid?(:update), 'user should be valid' + end + + test "updating an existing user with validation and a nil password" do + @existing_user.password = nil + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["can't be blank"], @existing_user.errors[:password] + end + + test "updating an existing user with validation and a blank password confirmation" do + @existing_user.password = 'password' + @existing_user.password_confirmation = '' + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation] + end + + test "updating an existing user with validation and a nil password confirmation" do + @existing_user.password = 'password' + @existing_user.password_confirmation = nil + assert @existing_user.valid?(:update), 'user should be valid' + end + + test "updating an existing user with validation and an incorrect password confirmation" do + @existing_user.password = 'password' + @existing_user.password_confirmation = 'something else' + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation] + end + + test "updating an existing user with validation and a correct password confirmation" do + @existing_user.password = 'password' + @existing_user.password_confirmation = 'something else' + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation] + end + + test "updating an existing user with validation and a blank password digest" do + @existing_user.password_digest = '' + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["can't be blank"], @existing_user.errors[:password] + end + + test "updating an existing user with validation and a nil password digest" do + @existing_user.password_digest = nil + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["can't be blank"], @existing_user.errors[:password] + end + + test "setting a blank password should not change an existing password" do + @existing_user.password = '' + assert @existing_user.password_digest == 'password' + end + + test "setting a nil password should clear an existing password" do + @existing_user.password = nil + assert_equal nil, @existing_user.password_digest + end + + test "authenticate" do + @user.password = "secret" + + assert !@user.authenticate("wrong") + assert @user.authenticate("secret") + end + + test "Password digest cost defaults to bcrypt default cost when min_cost is false" do + ActiveModel::SecurePassword.min_cost = false + + @user.password = "secret" + assert_equal BCrypt::Engine::DEFAULT_COST, @user.password_digest.cost + end + + test "Password digest cost honors bcrypt cost attribute when min_cost is false" do + ActiveModel::SecurePassword.min_cost = false + BCrypt::Engine.cost = 5 + + @user.password = "secret" + assert_equal BCrypt::Engine.cost, @user.password_digest.cost + end + + test "Password digest cost can be set to bcrypt min cost to speed up tests" do + ActiveModel::SecurePassword.min_cost = true + + @user.password = "secret" + assert_equal BCrypt::Engine::MIN_COST, @user.password_digest.cost + end +end diff --git a/activemodel/test/models/oauthed_user.rb b/activemodel/test/models/oauthed_user.rb index 9750bc19d4..1bbb24a37b 100644 --- a/activemodel/test/models/oauthed_user.rb +++ b/activemodel/test/models/oauthed_user.rb @@ -7,5 +7,5 @@ class OauthedUser has_secure_password(validations: false) - attr_accessor :password_digest, :password_salt + attr_accessor :password_digest end diff --git a/activemodel/test/models/user.rb b/activemodel/test/models/user.rb index 4b11df12bf..cbe259b1ad 100644 --- a/activemodel/test/models/user.rb +++ b/activemodel/test/models/user.rb @@ -7,5 +7,5 @@ class User has_secure_password - attr_accessor :password_digest, :password_salt + attr_accessor :password_digest end -- cgit v1.2.3 From 20490adcbf00cd382e8e310415955a427b93e398 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Mon, 20 Jan 2014 04:27:42 -0800 Subject: Restored the ability to clear the password with user.password= nil (see the docs) --- activemodel/lib/active_model/secure_password.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index d824a66784..e4af1efa65 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -100,7 +100,9 @@ module ActiveModel # user.password = 'mUc3m00RsqyRe' # user.password_digest # => "$2a$10$4LEA7r4YmNHtvlAvHhsYAeZmk/xeUVtMTYqwIvYY76EW5GUqDiP4." def password=(unencrypted_password) - unless unencrypted_password.blank? + if unencrypted_password.nil? + self.password_digest = nil + elsif unencrypted_password.present? @password = unencrypted_password cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost) -- cgit v1.2.3 From 8ca59237dd4951efcc9861142222254a134911ca Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Mon, 20 Jan 2014 05:04:19 -0800 Subject: Got all the new tests passing --- activemodel/lib/active_model/secure_password.rb | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index e4af1efa65..c0e60fbb8a 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -57,11 +57,15 @@ module ActiveModel include InstanceMethodsOnActivation if options.fetch(:validations, true) - validates_confirmation_of :password, if: :password_confirmation_required? - validates_presence_of :password, on: :create - validates_presence_of :password_confirmation, if: :password_confirmation_required? + # This ensures the model has a password by checking whether the password_digest + # is present, so that this works with both new and existing records. However, + # when there is an error, the message is added to the password attribute instead + # so that the error message will makes sense to the end-user. + validate do |record| + record.errors.add(:password, :blank) unless record.password_digest.present? + end - before_create { raise "Password digest missing on new record" if password_digest.blank? } + validates_confirmation_of :password, if: ->{ self.password.present? } end if respond_to?(:attributes_protected_by_default) @@ -112,12 +116,6 @@ module ActiveModel def password_confirmation=(unencrypted_password) @password_confirmation = unencrypted_password end - - private - - def password_confirmation_required? - password_confirmation && password.present? - end end end end -- cgit v1.2.3 From b6ddbfb15897dbd7f5043f3bfd9c83ffcbe2c2c4 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Mon, 20 Jan 2014 05:06:03 -0800 Subject: Removed old tests --- activemodel/test/cases/secure_password_new_test.rb | 181 --------------------- activemodel/test/cases/secure_password_test.rb | 179 +++++++++++++------- activemodel/test/models/oauthed_user.rb | 11 -- 3 files changed, 121 insertions(+), 250 deletions(-) delete mode 100644 activemodel/test/cases/secure_password_new_test.rb delete mode 100644 activemodel/test/models/oauthed_user.rb diff --git a/activemodel/test/cases/secure_password_new_test.rb b/activemodel/test/cases/secure_password_new_test.rb deleted file mode 100644 index 174d18ec8b..0000000000 --- a/activemodel/test/cases/secure_password_new_test.rb +++ /dev/null @@ -1,181 +0,0 @@ -require 'cases/helper' -require 'models/user' -require 'models/visitor' - -require 'active_support/all' -class SecurePasswordTest < ActiveModel::TestCase - setup do - ActiveModel::SecurePassword.min_cost = true - - @user = User.new - @visitor = Visitor.new - - # Simulate loading an existing user from the DB - @existing_user = User.new - @existing_user.password_digest = BCrypt::Password.create('password', cost: BCrypt::Engine::MIN_COST) - end - - teardown do - ActiveModel::SecurePassword.min_cost = false - end - - test "create and updating without validations" do - assert @visitor.valid?(:create), 'visitor should be valid' - assert @visitor.valid?(:update), 'visitor should be valid' - - @visitor.password = '123' - @visitor.password_confirmation = '456' - - assert @visitor.valid?(:create), 'visitor should be valid' - assert @visitor.valid?(:update), 'visitor should be valid' - end - - test "create a new user with validation and a blank password" do - @user.password = '' - assert !@user.valid?(:create), 'user should be invalid' - assert_equal 1, @user.errors.count - assert_equal ["can't be blank"], @user.errors[:password] - end - - test "create a new user with validation and a nil password" do - @user.password = nil - assert !@user.valid?(:create), 'user should be invalid' - assert_equal 1, @user.errors.count - assert_equal ["can't be blank"], @user.errors[:password] - end - - test "create a new user with validation and a blank password confirmation" do - @user.password = 'password' - @user.password_confirmation = '' - assert !@user.valid?(:create), 'user should be invalid' - assert_equal 1, @user.errors.count - assert_equal ["doesn't match Password"], @user.errors[:password_confirmation] - end - - test "create a new user with validation and a nil password confirmation" do - @user.password = 'password' - @user.password_confirmation = nil - assert @user.valid?(:create), 'user should be valid' - end - - test "create a new user with validation and an incorrect password confirmation" do - @user.password = 'password' - @user.password_confirmation = 'something else' - assert !@user.valid?(:create), 'user should be invalid' - assert_equal 1, @user.errors.count - assert_equal ["doesn't match Password"], @user.errors[:password_confirmation] - end - - test "create a new user with validation and a correct password confirmation" do - @user.password = 'password' - @user.password_confirmation = 'something else' - assert !@user.valid?(:create), 'user should be invalid' - assert_equal 1, @user.errors.count - assert_equal ["doesn't match Password"], @user.errors[:password_confirmation] - end - - test "update an existing user with validation and no change in password" do - assert @existing_user.valid?(:update), 'user should be valid' - end - - test "updating an existing user with validation and a blank password" do - @existing_user.password = '' - assert @existing_user.valid?(:update), 'user should be valid' - end - - test "updating an existing user with validation and a blank password and password_confirmation" do - @existing_user.password = '' - @existing_user.password_confirmation = '' - assert @existing_user.valid?(:update), 'user should be valid' - end - - test "updating an existing user with validation and a nil password" do - @existing_user.password = nil - assert !@existing_user.valid?(:update), 'user should be invalid' - assert_equal 1, @existing_user.errors.count - assert_equal ["can't be blank"], @existing_user.errors[:password] - end - - test "updating an existing user with validation and a blank password confirmation" do - @existing_user.password = 'password' - @existing_user.password_confirmation = '' - assert !@existing_user.valid?(:update), 'user should be invalid' - assert_equal 1, @existing_user.errors.count - assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation] - end - - test "updating an existing user with validation and a nil password confirmation" do - @existing_user.password = 'password' - @existing_user.password_confirmation = nil - assert @existing_user.valid?(:update), 'user should be valid' - end - - test "updating an existing user with validation and an incorrect password confirmation" do - @existing_user.password = 'password' - @existing_user.password_confirmation = 'something else' - assert !@existing_user.valid?(:update), 'user should be invalid' - assert_equal 1, @existing_user.errors.count - assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation] - end - - test "updating an existing user with validation and a correct password confirmation" do - @existing_user.password = 'password' - @existing_user.password_confirmation = 'something else' - assert !@existing_user.valid?(:update), 'user should be invalid' - assert_equal 1, @existing_user.errors.count - assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation] - end - - test "updating an existing user with validation and a blank password digest" do - @existing_user.password_digest = '' - assert !@existing_user.valid?(:update), 'user should be invalid' - assert_equal 1, @existing_user.errors.count - assert_equal ["can't be blank"], @existing_user.errors[:password] - end - - test "updating an existing user with validation and a nil password digest" do - @existing_user.password_digest = nil - assert !@existing_user.valid?(:update), 'user should be invalid' - assert_equal 1, @existing_user.errors.count - assert_equal ["can't be blank"], @existing_user.errors[:password] - end - - test "setting a blank password should not change an existing password" do - @existing_user.password = '' - assert @existing_user.password_digest == 'password' - end - - test "setting a nil password should clear an existing password" do - @existing_user.password = nil - assert_equal nil, @existing_user.password_digest - end - - test "authenticate" do - @user.password = "secret" - - assert !@user.authenticate("wrong") - assert @user.authenticate("secret") - end - - test "Password digest cost defaults to bcrypt default cost when min_cost is false" do - ActiveModel::SecurePassword.min_cost = false - - @user.password = "secret" - assert_equal BCrypt::Engine::DEFAULT_COST, @user.password_digest.cost - end - - test "Password digest cost honors bcrypt cost attribute when min_cost is false" do - ActiveModel::SecurePassword.min_cost = false - BCrypt::Engine.cost = 5 - - @user.password = "secret" - assert_equal BCrypt::Engine.cost, @user.password_digest.cost - end - - test "Password digest cost can be set to bcrypt min cost to speed up tests" do - ActiveModel::SecurePassword.min_cost = true - - @user.password = "secret" - assert_equal BCrypt::Engine::MIN_COST, @user.password_digest.cost - end -end diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb index 0314803af6..174d18ec8b 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -1,77 +1,160 @@ require 'cases/helper' require 'models/user' -require 'models/oauthed_user' require 'models/visitor' +require 'active_support/all' class SecurePasswordTest < ActiveModel::TestCase setup do ActiveModel::SecurePassword.min_cost = true @user = User.new @visitor = Visitor.new - @oauthed_user = OauthedUser.new + + # Simulate loading an existing user from the DB + @existing_user = User.new + @existing_user.password_digest = BCrypt::Password.create('password', cost: BCrypt::Engine::MIN_COST) end teardown do ActiveModel::SecurePassword.min_cost = false end - test "blank password" do - @user.password = @visitor.password = '' - assert !@user.valid?(:create), 'user should be invalid' + test "create and updating without validations" do assert @visitor.valid?(:create), 'visitor should be valid' - end + assert @visitor.valid?(:update), 'visitor should be valid' + + @visitor.password = '123' + @visitor.password_confirmation = '456' - test "nil password" do - @user.password = @visitor.password = nil - assert !@user.valid?(:create), 'user should be invalid' assert @visitor.valid?(:create), 'visitor should be valid' + assert @visitor.valid?(:update), 'visitor should be valid' end - test "blank password doesn't override previous password" do - @user.password = 'test' + test "create a new user with validation and a blank password" do @user.password = '' - assert_equal @user.password, 'test' + assert !@user.valid?(:create), 'user should be invalid' + assert_equal 1, @user.errors.count + assert_equal ["can't be blank"], @user.errors[:password] + end + + test "create a new user with validation and a nil password" do + @user.password = nil + assert !@user.valid?(:create), 'user should be invalid' + assert_equal 1, @user.errors.count + assert_equal ["can't be blank"], @user.errors[:password] + end + + test "create a new user with validation and a blank password confirmation" do + @user.password = 'password' + @user.password_confirmation = '' + assert !@user.valid?(:create), 'user should be invalid' + assert_equal 1, @user.errors.count + assert_equal ["doesn't match Password"], @user.errors[:password_confirmation] end - test "password must be present" do - assert !@user.valid?(:create) - assert_equal 1, @user.errors.size + test "create a new user with validation and a nil password confirmation" do + @user.password = 'password' + @user.password_confirmation = nil + assert @user.valid?(:create), 'user should be valid' end - test "match confirmation" do - @user.password = @visitor.password = "thiswillberight" - @user.password_confirmation = @visitor.password_confirmation = "wrong" + test "create a new user with validation and an incorrect password confirmation" do + @user.password = 'password' + @user.password_confirmation = 'something else' + assert !@user.valid?(:create), 'user should be invalid' + assert_equal 1, @user.errors.count + assert_equal ["doesn't match Password"], @user.errors[:password_confirmation] + end - assert !@user.valid? - assert @visitor.valid? + test "create a new user with validation and a correct password confirmation" do + @user.password = 'password' + @user.password_confirmation = 'something else' + assert !@user.valid?(:create), 'user should be invalid' + assert_equal 1, @user.errors.count + assert_equal ["doesn't match Password"], @user.errors[:password_confirmation] + end - @user.password_confirmation = "thiswillberight" + test "update an existing user with validation and no change in password" do + assert @existing_user.valid?(:update), 'user should be valid' + end - assert @user.valid? + test "updating an existing user with validation and a blank password" do + @existing_user.password = '' + assert @existing_user.valid?(:update), 'user should be valid' end - test "authenticate" do - @user.password = "secret" + test "updating an existing user with validation and a blank password and password_confirmation" do + @existing_user.password = '' + @existing_user.password_confirmation = '' + assert @existing_user.valid?(:update), 'user should be valid' + end - assert !@user.authenticate("wrong") - assert @user.authenticate("secret") + test "updating an existing user with validation and a nil password" do + @existing_user.password = nil + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["can't be blank"], @existing_user.errors[:password] + end + + test "updating an existing user with validation and a blank password confirmation" do + @existing_user.password = 'password' + @existing_user.password_confirmation = '' + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation] end - test "User should not be created with blank digest" do - assert_raise RuntimeError do - @user.run_callbacks :create - end - @user.password = "supersecretpassword" - assert_nothing_raised do - @user.run_callbacks :create - end + test "updating an existing user with validation and a nil password confirmation" do + @existing_user.password = 'password' + @existing_user.password_confirmation = nil + assert @existing_user.valid?(:update), 'user should be valid' end - test "Oauthed user can be created with blank digest" do - assert_nothing_raised do - @oauthed_user.run_callbacks :create - end + test "updating an existing user with validation and an incorrect password confirmation" do + @existing_user.password = 'password' + @existing_user.password_confirmation = 'something else' + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation] + end + + test "updating an existing user with validation and a correct password confirmation" do + @existing_user.password = 'password' + @existing_user.password_confirmation = 'something else' + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation] + end + + test "updating an existing user with validation and a blank password digest" do + @existing_user.password_digest = '' + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["can't be blank"], @existing_user.errors[:password] + end + + test "updating an existing user with validation and a nil password digest" do + @existing_user.password_digest = nil + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["can't be blank"], @existing_user.errors[:password] + end + + test "setting a blank password should not change an existing password" do + @existing_user.password = '' + assert @existing_user.password_digest == 'password' + end + + test "setting a nil password should clear an existing password" do + @existing_user.password = nil + assert_equal nil, @existing_user.password_digest + end + + test "authenticate" do + @user.password = "secret" + + assert !@user.authenticate("wrong") + assert @user.authenticate("secret") end test "Password digest cost defaults to bcrypt default cost when min_cost is false" do @@ -95,24 +178,4 @@ class SecurePasswordTest < ActiveModel::TestCase @user.password = "secret" assert_equal BCrypt::Engine::MIN_COST, @user.password_digest.cost end - - test "blank password_confirmation does not result in a confirmation error" do - @user.password = "" - @user.password_confirmation = "" - assert @user.valid?(:update), "user should be valid" - end - - test "password_confirmation validations will not be triggered if password_confirmation is not sent" do - @user.password = "password" - assert @user.valid?(:create) - end - - test "will not save if confirmation is blank but password is not" do - @user.password = "password" - @user.password_confirmation = "" - assert_not @user.valid?(:create) - - @user.password_confirmation = "password" - assert @user.valid?(:create) - end end diff --git a/activemodel/test/models/oauthed_user.rb b/activemodel/test/models/oauthed_user.rb deleted file mode 100644 index 1bbb24a37b..0000000000 --- a/activemodel/test/models/oauthed_user.rb +++ /dev/null @@ -1,11 +0,0 @@ -class OauthedUser - extend ActiveModel::Callbacks - include ActiveModel::Validations - include ActiveModel::SecurePassword - - define_model_callbacks :create - - has_secure_password(validations: false) - - attr_accessor :password_digest -end -- cgit v1.2.3 From 98705d88cd8ec705b80a032f8c166072b4e6fffd Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Fri, 24 Jan 2014 19:57:07 -0800 Subject: Some minor fixes --- activemodel/lib/active_model/secure_password.rb | 4 ++-- activemodel/test/cases/secure_password_test.rb | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index c0e60fbb8a..01739d8ae4 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -60,12 +60,12 @@ module ActiveModel # This ensures the model has a password by checking whether the password_digest # is present, so that this works with both new and existing records. However, # when there is an error, the message is added to the password attribute instead - # so that the error message will makes sense to the end-user. + # so that the error message will make sense to the end-user. validate do |record| record.errors.add(:password, :blank) unless record.password_digest.present? end - validates_confirmation_of :password, if: ->{ self.password.present? } + validates_confirmation_of :password, if: ->{ password.present? } end if respond_to?(:attributes_protected_by_default) diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb index 174d18ec8b..82fd291064 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -2,7 +2,6 @@ require 'cases/helper' require 'models/user' require 'models/visitor' -require 'active_support/all' class SecurePasswordTest < ActiveModel::TestCase setup do ActiveModel::SecurePassword.min_cost = true -- cgit v1.2.3 From 345555cd4cfd6fad68752292e5780387672e167e Mon Sep 17 00:00:00 2001 From: Byron Bischoff Date: Fri, 24 Jan 2014 13:07:02 -0800 Subject: Transform dashes to underscores in resource route names Fixes #13824 --- actionpack/lib/action_dispatch/routing/mapper.rb | 8 ++++---- actionpack/test/dispatch/routing/inspector_test.rb | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 6a4d7c3afa..d5eb770cb1 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1442,7 +1442,7 @@ module ActionDispatch action = action.to_s.dup if action =~ /^[\w\-\/]+$/ - options[:action] ||= action unless action.include?("/") + options[:action] ||= action.tr('-', '_') unless action.include?("/") else action = nil end @@ -1607,10 +1607,11 @@ module ActionDispatch def prefix_name_for_action(as, action) #:nodoc: if as - as.to_s + prefix = as elsif !canonical_action?(action, @scope[:scope_level]) - action.to_s + prefix = action end + prefix.to_s.tr('-', '_') if prefix end def name_for_action(as, action) #:nodoc: @@ -1637,7 +1638,6 @@ module ActionDispatch when :root [name_prefix, collection_name, prefix] else - prefix.gsub!(/\-/, '_') if prefix.is_a?(String) [name_prefix, member_name, prefix] end diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index 74515e3f57..ff33dd5652 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -185,12 +185,22 @@ module ActionDispatch output = draw do get 'about-us' => 'pages#about_us' get 'our-work/latest' + + resources :photos, only: [:show] do + get 'user-favorites', on: :collection + get 'preview-photo', on: :member + get 'summary-text' + end end assert_equal [ - " Prefix Verb URI Pattern Controller#Action", - " about_us GET /about-us(.:format) pages#about_us", - "our_work_latest GET /our-work/latest(.:format) our_work#latest" + " Prefix Verb URI Pattern Controller#Action", + " about_us GET /about-us(.:format) pages#about_us", + " our_work_latest GET /our-work/latest(.:format) our_work#latest", + "user_favorites_photos GET /photos/user-favorites(.:format) photos#user_favorites", + " preview_photo_photo GET /photos/:id/preview-photo(.:format) photos#preview_photo", + " photo_summary_text GET /photos/:photo_id/summary-text(.:format) photos#summary_text", + " photo GET /photos/:id(.:format) photos#show" ], output end -- cgit v1.2.3 From a31a44a266ad2e2ad2b9ce5aa56cd5e1d108d48a Mon Sep 17 00:00:00 2001 From: Andrew White Date: Sat, 25 Jan 2014 07:38:29 +0000 Subject: Add additional tests for #13824 --- actionpack/test/dispatch/routing_test.rb | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index ebea5a8792..26821bdb56 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2932,6 +2932,32 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/about-us/index', about_us_index_path end + def test_resource_routes_with_dashes_in_path + draw do + resources :photos, only: [:show] do + get 'user-favorites', on: :collection + get 'preview-photo', on: :member + get 'summary-text' + end + end + + get '/photos/user-favorites' + assert_equal 'photos#user_favorites', @response.body + assert_equal '/photos/user-favorites', user_favorites_photos_path + + get '/photos/1/preview-photo' + assert_equal 'photos#preview_photo', @response.body + assert_equal '/photos/1/preview-photo', preview_photo_photo_path('1') + + get '/photos/1/summary-text' + assert_equal 'photos#summary_text', @response.body + assert_equal '/photos/1/summary-text', photo_summary_text_path('1') + + get '/photos/1' + assert_equal 'photos#show', @response.body + assert_equal '/photos/1', photo_path('1') + end + private def draw(&block) -- cgit v1.2.3 From 9cc4dadb0b38ae1f686e16aebee97fc633fc6347 Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Sat, 25 Jan 2014 14:04:09 +0530 Subject: Remove old comments about suppressing warnings. --- guides/source/contributing_to_ruby_on_rails.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 814237ba22..5ba6d8581c 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -136,7 +136,7 @@ You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` als The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings. -As of this writing (December, 2010) they are especially noisy with Ruby 1.9. If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag: +If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag: ```bash $ RUBYOPT=-W0 bundle exec rake test -- cgit v1.2.3 From 08bbe5dc2eaaa99be2ab8296e00d27d8b19e12d5 Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Tue, 21 Jan 2014 00:36:44 +0530 Subject: Replace Post with Article in getting started guide [ci skip] - Used Article model instead of Post as it may confuse with 'post' requests - Tried to wrap the guide to 80 chars lines as much as possible. - Removed unused image - Fixes #13764 --- .../getting_started/article_with_comments.png | Bin 0 -> 25229 bytes guides/assets/images/getting_started/challenge.png | Bin 31976 -> 30248 bytes .../images/getting_started/confirm_dialog.png | Bin 29542 -> 26420 bytes .../forbidden_attributes_for_new_article.png | Bin 0 -> 15598 bytes .../forbidden_attributes_for_new_post.png | Bin 19490 -> 0 bytes .../images/getting_started/form_with_errors.png | Bin 14031 -> 18076 bytes .../index_action_with_edit_link.png | Bin 9772 -> 18024 bytes .../assets/images/getting_started/new_article.png | Bin 0 -> 9387 bytes guides/assets/images/getting_started/new_post.png | Bin 5090 -> 0 bytes .../images/getting_started/post_with_comments.png | Bin 18496 -> 0 bytes .../routing_error_no_controller.png | Bin 5519 -> 6108 bytes .../getting_started/show_action_for_articles.png | Bin 0 -> 2991 bytes .../getting_started/show_action_for_posts.png | Bin 2991 -> 0 bytes .../template_is_missing_articles_new.png | Bin 0 -> 10138 bytes .../template_is_missing_posts_new.png | Bin 11688 -> 0 bytes .../getting_started/undefined_method_post_path.png | Bin 9217 -> 0 bytes .../unknown_action_create_for_articles.png | Bin 0 -> 7594 bytes .../unknown_action_create_for_posts.png | Bin 6852 -> 0 bytes .../unknown_action_new_for_articles.png | Bin 0 -> 7858 bytes .../unknown_action_new_for_posts.png | Bin 6998 -> 0 bytes guides/source/getting_started.md | 836 +++++++++++---------- 21 files changed, 429 insertions(+), 407 deletions(-) create mode 100644 guides/assets/images/getting_started/article_with_comments.png create mode 100644 guides/assets/images/getting_started/forbidden_attributes_for_new_article.png delete mode 100644 guides/assets/images/getting_started/forbidden_attributes_for_new_post.png create mode 100644 guides/assets/images/getting_started/new_article.png delete mode 100644 guides/assets/images/getting_started/new_post.png delete mode 100644 guides/assets/images/getting_started/post_with_comments.png create mode 100644 guides/assets/images/getting_started/show_action_for_articles.png delete mode 100644 guides/assets/images/getting_started/show_action_for_posts.png create mode 100644 guides/assets/images/getting_started/template_is_missing_articles_new.png delete mode 100644 guides/assets/images/getting_started/template_is_missing_posts_new.png delete mode 100644 guides/assets/images/getting_started/undefined_method_post_path.png create mode 100644 guides/assets/images/getting_started/unknown_action_create_for_articles.png delete mode 100644 guides/assets/images/getting_started/unknown_action_create_for_posts.png create mode 100644 guides/assets/images/getting_started/unknown_action_new_for_articles.png delete mode 100644 guides/assets/images/getting_started/unknown_action_new_for_posts.png diff --git a/guides/assets/images/getting_started/article_with_comments.png b/guides/assets/images/getting_started/article_with_comments.png new file mode 100644 index 0000000000..1918e9bf28 Binary files /dev/null and b/guides/assets/images/getting_started/article_with_comments.png differ diff --git a/guides/assets/images/getting_started/challenge.png b/guides/assets/images/getting_started/challenge.png index 4a30e49e6d..cc12162677 100644 Binary files a/guides/assets/images/getting_started/challenge.png and b/guides/assets/images/getting_started/challenge.png differ diff --git a/guides/assets/images/getting_started/confirm_dialog.png b/guides/assets/images/getting_started/confirm_dialog.png index 1a13eddd91..e57d4b409e 100644 Binary files a/guides/assets/images/getting_started/confirm_dialog.png and b/guides/assets/images/getting_started/confirm_dialog.png differ diff --git a/guides/assets/images/getting_started/forbidden_attributes_for_new_article.png b/guides/assets/images/getting_started/forbidden_attributes_for_new_article.png new file mode 100644 index 0000000000..e263f7f8b2 Binary files /dev/null and b/guides/assets/images/getting_started/forbidden_attributes_for_new_article.png differ diff --git a/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png b/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png deleted file mode 100644 index 6c78e52173..0000000000 Binary files a/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png and /dev/null differ diff --git a/guides/assets/images/getting_started/form_with_errors.png b/guides/assets/images/getting_started/form_with_errors.png index 6910e1647e..04ff8b1e2d 100644 Binary files a/guides/assets/images/getting_started/form_with_errors.png and b/guides/assets/images/getting_started/form_with_errors.png differ diff --git a/guides/assets/images/getting_started/index_action_with_edit_link.png b/guides/assets/images/getting_started/index_action_with_edit_link.png index bf23cba231..22f994d993 100644 Binary files a/guides/assets/images/getting_started/index_action_with_edit_link.png and b/guides/assets/images/getting_started/index_action_with_edit_link.png differ diff --git a/guides/assets/images/getting_started/new_article.png b/guides/assets/images/getting_started/new_article.png new file mode 100644 index 0000000000..89fc0b2605 Binary files /dev/null and b/guides/assets/images/getting_started/new_article.png differ diff --git a/guides/assets/images/getting_started/new_post.png b/guides/assets/images/getting_started/new_post.png deleted file mode 100644 index b20b0192d4..0000000000 Binary files a/guides/assets/images/getting_started/new_post.png and /dev/null differ diff --git a/guides/assets/images/getting_started/post_with_comments.png b/guides/assets/images/getting_started/post_with_comments.png deleted file mode 100644 index e13095ff8f..0000000000 Binary files a/guides/assets/images/getting_started/post_with_comments.png and /dev/null differ diff --git a/guides/assets/images/getting_started/routing_error_no_controller.png b/guides/assets/images/getting_started/routing_error_no_controller.png index 35ee4f348f..ae83b6a68c 100644 Binary files a/guides/assets/images/getting_started/routing_error_no_controller.png and b/guides/assets/images/getting_started/routing_error_no_controller.png differ diff --git a/guides/assets/images/getting_started/show_action_for_articles.png b/guides/assets/images/getting_started/show_action_for_articles.png new file mode 100644 index 0000000000..9467df6a07 Binary files /dev/null and b/guides/assets/images/getting_started/show_action_for_articles.png differ diff --git a/guides/assets/images/getting_started/show_action_for_posts.png b/guides/assets/images/getting_started/show_action_for_posts.png deleted file mode 100644 index 9467df6a07..0000000000 Binary files a/guides/assets/images/getting_started/show_action_for_posts.png and /dev/null differ diff --git a/guides/assets/images/getting_started/template_is_missing_articles_new.png b/guides/assets/images/getting_started/template_is_missing_articles_new.png new file mode 100644 index 0000000000..ba630cfc23 Binary files /dev/null and b/guides/assets/images/getting_started/template_is_missing_articles_new.png differ diff --git a/guides/assets/images/getting_started/template_is_missing_posts_new.png b/guides/assets/images/getting_started/template_is_missing_posts_new.png deleted file mode 100644 index f03db05fb8..0000000000 Binary files a/guides/assets/images/getting_started/template_is_missing_posts_new.png and /dev/null differ diff --git a/guides/assets/images/getting_started/undefined_method_post_path.png b/guides/assets/images/getting_started/undefined_method_post_path.png deleted file mode 100644 index c29cb2f54f..0000000000 Binary files a/guides/assets/images/getting_started/undefined_method_post_path.png and /dev/null differ diff --git a/guides/assets/images/getting_started/unknown_action_create_for_articles.png b/guides/assets/images/getting_started/unknown_action_create_for_articles.png new file mode 100644 index 0000000000..ed89c4f3d7 Binary files /dev/null and b/guides/assets/images/getting_started/unknown_action_create_for_articles.png differ diff --git a/guides/assets/images/getting_started/unknown_action_create_for_posts.png b/guides/assets/images/getting_started/unknown_action_create_for_posts.png deleted file mode 100644 index 8fdd4c574a..0000000000 Binary files a/guides/assets/images/getting_started/unknown_action_create_for_posts.png and /dev/null differ diff --git a/guides/assets/images/getting_started/unknown_action_new_for_articles.png b/guides/assets/images/getting_started/unknown_action_new_for_articles.png new file mode 100644 index 0000000000..e8f2b9a16a Binary files /dev/null and b/guides/assets/images/getting_started/unknown_action_new_for_articles.png differ diff --git a/guides/assets/images/getting_started/unknown_action_new_for_posts.png b/guides/assets/images/getting_started/unknown_action_new_for_posts.png deleted file mode 100644 index 7e72feee38..0000000000 Binary files a/guides/assets/images/getting_started/unknown_action_new_for_posts.png and /dev/null differ diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index dbcedba800..9c09ecc99c 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -315,18 +315,19 @@ Now that you've seen how to create a controller, an action and a view, let's create something with a bit more substance. In the Blog application, you will now create a new _resource_. A resource is the -term used for a collection of similar objects, such as posts, people or animals. +term used for a collection of similar objects, such as articles, people or +animals. You can create, read, update and destroy items for a resource and these operations are referred to as _CRUD_ operations. Rails provides a `resources` method which can be used to declare a standard REST -resource. Here's what `config/routes.rb` should look like after the _post resource_ -is declared. +resource. Here's what `config/routes.rb` should look like after the +_article resource_ is declared. ```ruby Blog::Application.routes.draw do - resources :posts + resources :articles root 'welcome#index' end @@ -335,88 +336,92 @@ end If you run `rake routes`, you'll see that it has defined routes for all the standard RESTful actions. The meaning of the prefix column (and other columns) will be seen later, but for now notice that Rails has inferred the -singular form `post` and makes meaningful use of the distinction. +singular form `article` and makes meaningful use of the distinction. ```bash $ rake routes - Prefix Verb URI Pattern Controller#Action - posts GET /posts(.:format) posts#index - POST /posts(.:format) posts#create - new_post GET /posts/new(.:format) posts#new -edit_post GET /posts/:id/edit(.:format) posts#edit - post GET /posts/:id(.:format) posts#show - PATCH /posts/:id(.:format) posts#update - PUT /posts/:id(.:format) posts#update - DELETE /posts/:id(.:format) posts#destroy - root / welcome#index -``` - -In the next section, you will add the ability to create new posts in your + Prefix Verb URI Pattern Controller#Action + articles GET /articles(.:format) articles#index + POST /articles(.:format) articles#create + new_article GET /articles/new(.:format) articles#new +edit_article GET /articles/:id/edit(.:format) articles#edit + article GET /articles/:id(.:format) articles#show + PATCH /articles/:id(.:format) articles#update + PUT /articles/:id(.:format) articles#update + DELETE /articles/:id(.:format) articles#destroy + root GET / welcome#index +``` + +In the next section, you will add the ability to create new articles in your application and be able to view them. This is the "C" and the "R" from CRUD: creation and reading. The form for doing this will look like this: -![The new post form](images/getting_started/new_post.png) +![The new article form](images/getting_started/new_article.png) It will look a little basic for now, but that's ok. We'll look at improving the styling for it afterwards. ### Laying down the ground work -The first thing that you are going to need to create a new post within the -application is a place to do that. A great place for that would be at `/posts/new`. -With the route already defined, requests can now be made to `/posts/new` in the -application. Navigate to and you'll see a -routing error: +The first thing that you are going to need to create a new article within the +application is a place to do that. A great place for that would be at +`/articles/new`. +With the route already defined, requests can now be made to `/articles/new` in +the application. Navigate to and you'll see +a routing error: -![Another routing error, uninitialized constant PostsController](images/getting_started/routing_error_no_controller.png) +![Another routing error, uninitialized constant ArticlesController](images/getting_started/routing_error_no_controller.png) 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: +a controller called `ArticlesController`. You can do this by running this +command: ```bash -$ rails g controller posts +$ rails g controller articles ``` -If you open up the newly generated `app/controllers/posts_controller.rb` you'll -see a fairly empty controller: +If you open up the newly generated `app/controllers/articles_controller.rb` +you'll see a fairly empty controller: ```ruby -class PostsController < ApplicationController +class ArticlesController < ApplicationController end ``` -A controller is simply a class that is defined to inherit from `ApplicationController`. +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 +for this controller. These actions will perform CRUD operations on the articles within our system. -NOTE: There are `public`, `private` and `protected` methods in Ruby, +NOTE: There are `public`, `private` and `protected` methods in Ruby, but only `public` methods can be actions for controllers. For more details check out [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/). -If you refresh now, you'll get a new error: +If you refresh now, you'll get a new error: -![Unknown action new for PostsController!](images/getting_started/unknown_action_new_for_posts.png) +![Unknown action new for ArticlesController!](images/getting_started/unknown_action_new_for_articles.png) -This error indicates that Rails cannot find the `new` action inside the `PostsController` -that you just generated. This is because when controllers are generated in Rails -they are empty by default, unless you tell it your wanted actions during the -generation process. +This error indicates that Rails cannot find the `new` action inside the +`ArticlesController` that you just generated. This is because when controllers +are generated in Rails they are empty by default, unless you tell it +your wanted actions during the generation process. To manually define an action inside a controller, all you need to do is to -define a new method inside the controller. Open `app/controllers/posts_controller.rb` -and inside the `PostsController` class, define a `new` method like this: +define a new method inside the controller. +Open `app/controllers/articles_controller.rb` and inside the `ArticlesController` +class, define a `new` method like this: ```ruby def new end ``` -With the `new` method defined in `PostsController`, if you refresh -you'll see another error: +With the `new` method defined in `ArticlesController`, if you refresh + you'll see another error: -![Template is missing for posts/new](images/getting_started/template_is_missing_posts_new.png) +![Template is missing for articles/new](images/getting_started/template_is_missing_articles_new.png) You're getting this error now because Rails expects plain actions like this one to have views associated with them to display their information. With no view @@ -426,16 +431,16 @@ In the above image, the bottom line has been truncated. Let's see what the full thing looks like:
-Missing template posts/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views" +Missing template articles/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views"
That's quite a lot of text! Let's quickly go through and understand what each part of it does. The first part identifies what template is missing. In this case, it's the -`posts/new` template. Rails will first look for this template. If not found, +`articles/new` template. Rails will first look for this template. If not found, then it will attempt to load a template called `application/new`. It looks for -one here because the `PostsController` inherits from `ApplicationController`. +one here because the `ArticlesController` inherits from `ApplicationController`. The next part of the message contains a hash. The `:locale` key in this hash simply indicates what spoken language template should be retrieved. By default, @@ -451,34 +456,35 @@ Templates within a basic Rails application like this are kept in a single location, but in more complex applications it could be many different paths. The simplest template that would work in this case would be one located at -`app/views/posts/new.html.erb`. The extension of this file name is key: the +`app/views/articles/new.html.erb`. The extension of this file name is key: the first extension is the _format_ of the template, and the second extension is the _handler_ that will be used. Rails is attempting to find a template called -`posts/new` within `app/views` for the application. The format for this template -can only be `html` and the handler must be one of `erb`, `builder` or `coffee`. -Because you want to create a new HTML form, you will be using the `ERB` -language. Therefore the file should be called `posts/new.html.erb` and needs to -be located inside the `app/views` directory of the application. +`articles/new` within `app/views` for the application. The format for this +template can only be `html` and the handler must be one of `erb`, `builder` or +`coffee`. Because you want to create a new HTML form, you will be using the `ERB` +language. Therefore the file should be called `articles/new.html.erb` and needs +to be located inside the `app/views` directory of the application. -Go ahead now and create a new file at `app/views/posts/new.html.erb` and write -this content in it: +Go ahead now and create a new file at `app/views/articles/new.html.erb` and +write this content in it: ```html -

New Post

+

New Article

``` -When you refresh you'll now see that the page -has a title. The route, controller, action and view are now working -harmoniously! It's time to create the form for a new post. +When you refresh you'll now see that the +page has a title. The route, controller, action and view are now working +harmoniously! It's time to create the form for a new article. ### The first form To create a form within this template, you will use a form builder. The primary form builder for Rails is provided by a helper -method called `form_for`. To use this method, add this code into `app/views/posts/new.html.erb`: +method called `form_for`. To use this method, add this code into +`app/views/articles/new.html.erb`: ```html+erb -<%= form_for :post do |f| %> +<%= form_for :article do |f| %>

<%= f.label :title %>
<%= f.text_field :title %> @@ -499,71 +505,72 @@ If you refresh the page now, you'll see the exact same form as in the example. Building forms in Rails is really just that easy! When you call `form_for`, you pass it an identifying object for this -form. In this case, it's the symbol `:post`. This tells the `form_for` +form. In this case, it's the symbol `:article`. This tells the `form_for` helper what this form is for. Inside the block for this method, the `FormBuilder` object - represented by `f` - is used to build two labels and two -text fields, one each for the title and text of a post. Finally, a call to +text fields, one each for the title and text of an article. Finally, a call to `submit` on the `f` object will create a submit button for the form. There's one problem with this form though. If you inspect the HTML that is generated, by viewing the source of the page, you will see that the `action` -attribute for the form is pointing at `/posts/new`. This is a problem because +attribute for the form is pointing at `/articles/new`. This is a problem because this route goes to the very page that you're on right at the moment, and that -route should only be used to display the form for a new post. +route should only be used to display the form for a new article. The form needs to use a different URL in order to go somewhere else. This can be done quite simply with the `:url` option of `form_for`. Typically in Rails, the action that is used for new form submissions like this is called "create", and so the form should be pointed to that action. -Edit the `form_for` line inside `app/views/posts/new.html.erb` to look like this: +Edit the `form_for` line inside `app/views/articles/new.html.erb` to look like +this: ```html+erb -<%= form_for :post, url: posts_path do |f| %> +<%= form_for :article, url: articles_path do |f| %> ``` -In this example, the `posts_path` helper is passed to the `:url` option. +In this example, the `articles_path` helper is passed to the `:url` option. To see what Rails will do with this, we look back at the output of `rake routes`: ```bash $ rake routes - Prefix Verb URI Pattern Controller#Action - posts GET /posts(.:format) posts#index - POST /posts(.:format) posts#create - new_post GET /posts/new(.:format) posts#new -edit_post GET /posts/:id/edit(.:format) posts#edit - post GET /posts/:id(.:format) posts#show - PATCH /posts/:id(.:format) posts#update - PUT /posts/:id(.:format) posts#update - DELETE /posts/:id(.:format) posts#destroy - root / welcome#index -``` - -The `posts_path` helper tells Rails to point the form -to the URI Pattern associated with the `posts` prefix; and + Prefix Verb URI Pattern Controller#Action + articles GET /articles(.:format) articles#index + POST /articles(.:format) articles#create + new_article GET /articles/new(.:format) articles#new +edit_article GET /articles/:id/edit(.:format) articles#edit + article GET /articles/:id(.:format) articles#show + PATCH /articles/:id(.:format) articles#update + PUT /articles/:id(.:format) articles#update + DELETE /articles/:id(.:format) articles#destroy + root GET / welcome#index +``` + +The `articles_path` helper tells Rails to point the form +to the URI Pattern associated with the `articles` prefix; and the form will (by default) send a `POST` request to that route. This is associated with the -`create` action of the current controller, the `PostsController`. +`create` action of the current controller, the `ArticlesController`. With the form and its associated route defined, you will be able to fill in the form and then click the submit button to begin the process of creating a new -post, so go ahead and do that. When you submit the form, you should see a +article, so go ahead and do that. When you submit the form, you should see a familiar error: -![Unknown action create for PostsController](images/getting_started/unknown_action_create_for_posts.png) +![Unknown action create for ArticlesController](images/getting_started/unknown_action_create_for_articles.png) -You now need to create the `create` action within the `PostsController` for this -to work. +You now need to create the `create` action within the `ArticlesController` for +this to work. -### Creating posts +### Creating articles To make the "Unknown action" go away, you can define a `create` action within -the `PostsController` class in `app/controllers/posts_controller.rb`, underneath -the `new` action: +the `ArticlesController` class in `app/controllers/articles_controller.rb`, +underneath the `new` action: ```ruby -class PostsController < ApplicationController +class ArticlesController < ApplicationController def new end @@ -574,7 +581,7 @@ end If you re-submit the form now, you'll see another familiar error: a template is missing. That's ok, we can ignore that for now. What the `create` action should -be doing is saving our new post to a database. +be doing is saving our new article to the database. When a form is submitted, the fields of the form are sent to Rails as _parameters_. These parameters can then be referenced inside the controller @@ -583,12 +590,12 @@ look like, change the `create` action to this: ```ruby def create - render text: params[:post].inspect + render text: params[:article].inspect end ``` The `render` method here is taking a very simple hash with a key of `text` and -value of `params[:post].inspect`. The `params` method is the object which +value of `params[:article].inspect`. The `params` method is the object which represents the parameters (or fields) coming in from the form. The `params` method returns an `ActiveSupport::HashWithIndifferentAccess` object, which allows you to access the keys of the hash using either strings or symbols. In @@ -598,14 +605,14 @@ If you re-submit the form one more time you'll now no longer get the missing template error. Instead, you'll see something that looks like the following: ```ruby -{"title"=>"First post!", "text"=>"This is my first post."} +{"title"=>"First article!", "text"=>"This is my first article."} ``` -This action is now displaying the parameters for the post that are coming in +This action is now displaying the parameters for the article that are coming in from the form. However, this isn't really all that helpful. Yes, you can see the parameters but nothing in particular is being done with them. -### Creating the Post model +### Creating the Article model Models in Rails use a singular name, and their corresponding database tables use a plural name. Rails provides a generator for creating models, which @@ -613,17 +620,17 @@ most Rails developers tend to use when creating new models. To create the new model, run this command in your terminal: ```bash -$ rails generate model Post title:string text:text +$ rails generate model Article title:string text:text ``` -With that command we told Rails that we want a `Post` model, together +With that command we told Rails that we want a `Article` model, together with a _title_ attribute of type string, and a _text_ attribute -of type text. Those attributes are automatically added to the `posts` -table in the database and mapped to the `Post` model. +of type text. Those attributes are automatically added to the `articles` +table in the database and mapped to the `Article` model. Rails responded by creating a bunch of files. For -now, we're only interested in `app/models/post.rb` and -`db/migrate/20120419084633_create_posts.rb` (your name could be a bit +now, we're only interested in `app/models/article.rb` and +`db/migrate/20140120191729_create_articles.rb` (your name could be a bit different). The latter is responsible for creating the database structure, which is what we'll look at next. @@ -642,13 +649,13 @@ and it's possible to undo a migration after it's been applied to your database. Migration filenames include a timestamp to ensure that they're processed in the order that they were created. -If you look in the `db/migrate/20120419084633_create_posts.rb` file (remember, +If you look in the `db/migrate/20140120191729_create_articles.rb` file (remember, yours will have a slightly different name), here's what you'll find: ```ruby -class CreatePosts < ActiveRecord::Migration +class CreateArticles < ActiveRecord::Migration def change - create_table :posts do |t| + create_table :articles do |t| t.string :title t.text :text @@ -658,12 +665,12 @@ class CreatePosts < ActiveRecord::Migration end ``` -The above migration creates a method named `change` which will be called when you -run this migration. The action defined in this method is also reversible, which -means Rails knows how to reverse the change made by this migration, in case you -want to reverse it later. When you run this migration it will create a -`posts` table with one string column and a text column. It also creates two -timestamp fields to allow Rails to track post creation and update times. +The above migration creates a method named `change` which will be called when +you run this migration. The action defined in this method is also reversible, +which means Rails knows how to reverse the change made by this migration, +in case you want to reverse it later. When you run this migration it will create +an `articles` table with one string column and a text column. It also creates +two timestamp fields to allow Rails to track article creation and update times. TIP: For more information about migrations, refer to [Rails Database Migrations](migrations.html). @@ -674,14 +681,14 @@ At this point, you can use a rake command to run the migration: $ rake db:migrate ``` -Rails will execute this migration command and tell you it created the Posts +Rails will execute this migration command and tell you it created the Articles table. ```bash -== CreatePosts: migrating ==================================================== --- create_table(:posts) +== CreateArticles: migrating ================================================== +-- create_table(:articles) -> 0.0019s -== CreatePosts: migrated (0.0020s) =========================================== +== CreateArticles: migrated (0.0020s) ========================================= ``` NOTE. Because you're working in the development environment by default, this @@ -692,34 +699,35 @@ invoking the command: `rake db:migrate RAILS_ENV=production`. ### Saving data in the controller -Back in `PostsController`, we need to change the `create` action -to use the new `Post` model to save the data in the database. Open `app/controllers/posts_controller.rb` -and change the `create` action to look like this: +Back in `ArticlesController`, we need to change the `create` action +to use the new `Article` model to save the data in the database. +Open `app/controllers/articles_controller.rb` and change the `create` action to +look like this: ```ruby def create - @post = Post.new(params[:post]) + @article = Article.new(params[:article]) - @post.save - redirect_to @post + @article.save + redirect_to @article end ``` Here's what's going on: every Rails model can be initialized with its respective attributes, which are automatically mapped to the respective database columns. In the first line we do just that -(remember that `params[:post]` contains the attributes we're interested in). -Then, `@post.save` is responsible for saving the model in the database. +(remember that `params[:article]` contains the attributes we're interested in). +Then, `@article.save` is responsible for saving the model in the database. Finally, we redirect the user to the `show` action, which we'll define later. -TIP: As we'll see later, `@post.save` returns a boolean indicating -whether the model was saved or not. +TIP: As we'll see later, `@article.save` returns a boolean indicating +whether the article was saved or not. If you now go to - you'll *almost* be able to create a post. Try -it! You should get an error that looks like this: + you'll *almost* be able to create an +article. Try it! You should get an error that looks like this: -![Forbidden attributes for new post](images/getting_started/forbidden_attributes_for_new_post.png) +![Forbidden attributes for new article](images/getting_started/forbidden_attributes_for_new_article.png) Rails has several security features that help you write secure applications, and you're running into one of them now. This one is called @@ -730,28 +738,28 @@ look like this: ```ruby def create - @post = Post.new(post_params) + @article = Article.new(article_params) - @post.save - redirect_to @post + @article.save + redirect_to @article end private - def post_params - params.require(:post).permit(:title, :text) + def article_params + params.require(:article).permit(:title, :text) end ``` See the `permit`? It allows us to accept both `title` and `text` in this action. -TIP: Note that `def post_params` is private. This new approach prevents an +TIP: Note that `def article_params` is private. This new approach prevents an attacker from setting the model's attributes by manipulating the hash passed to the model. For more information, refer to -[this blog post about Strong Parameters](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/). +[this blog article about Strong Parameters](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/). -### Showing Posts +### Showing Articles If you submit the form again now, Rails will complain about not finding the `show` action. That's not very useful though, so let's add the @@ -761,68 +769,70 @@ As we have seen in the output of `rake routes`, the route for `show` action is as follows: ``` -post GET /posts/:id(.:format) posts#show +article GET /articles/:id(.:format) articles#show ``` The special syntax `:id` tells rails that this route expects an `:id` -parameter, which in our case will be the id of the post. +parameter, which in our case will be the id of the article. As we did before, we need to add the `show` action in -`app/controllers/posts_controller.rb` and its respective view. +`app/controllers/articles_controller.rb` and its respective view. ```ruby def show - @post = Post.find(params[:id]) + @article = Article.find(params[:id]) end ``` -A couple of things to note. We use `Post.find` to find the post we're +A couple of things to note. We use `Article.find` to find the article we're 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 +reference to the article 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 +Now, create a new file `app/views/articles/show.html.erb` with the following content: ```html+erb

Title: - <%= @post.title %> + <%= @article.title %>

Text: - <%= @post.text %> + <%= @article.text %>

``` -With this change, you should finally be able to create new posts. -Visit and give it a try! +With this change, you should finally be able to create new articles. +Visit and give it a try! -![Show action for posts](images/getting_started/show_action_for_posts.png) +![Show action for articles](images/getting_started/show_action_for_articles.png) -### Listing all posts +### Listing all articles -We still need a way to list all our posts, so let's do that. +We still need a way to list all our articles, so let's do that. The route for this as per output of `rake routes` is: ``` -posts GET /posts(.:format) posts#index +articles GET /articles(.:format) articles#index ``` -Add the corresponding `index` action for that route inside the `PostsController` in the `app/controllers/posts_controller.rb` file: +Add the corresponding `index` action for that route inside the +`ArticlesController` in the `app/controllers/articles_controller.rb` file: ```ruby def index - @posts = Post.all + @articles = Article.all end ``` -And then finally, add view for this action, located at `app/views/posts/index.html.erb`: +And then finally, add view for this action, located at +`app/views/articles/index.html.erb`: ```html+erb -

Listing posts

+

Listing articles

@@ -830,70 +840,71 @@ And then finally, add view for this action, located at `app/views/posts/index.ht - <% @posts.each do |post| %> + <% @articles.each do |article| %> - - + + <% end %>
Text
<%= post.title %><%= post.text %><%= article.title %><%= article.text %>
``` -Now if you go to `http://localhost:3000/posts` you will see a list of all the -posts that you have created. +Now if you go to `http://localhost:3000/articles` you will see a list of all the +articles that you have created. ### Adding links -You can now create, show, and list posts. Now let's add some links to +You can now create, show, and list articles. Now let's add some links to navigate through pages. Open `app/views/welcome/index.html.erb` and modify it as follows: ```html+erb

Hello, Rails!

-<%= link_to 'My Blog', controller: 'posts' %> +<%= link_to 'My Blog', controller: 'articles' %> ``` The `link_to` method is one of Rails' built-in view helpers. It creates a hyperlink based on text to display and where to go - in this case, to the path -for posts. +for articles. -Let's add links to the other views as well, starting with adding this "New Post" -link to `app/views/posts/index.html.erb`, placing it above the `` tag: +Let's add links to the other views as well, starting with adding this +"New Article" link to `app/views/articles/index.html.erb`, placing it above the +`
` tag: ```erb -<%= link_to 'New post', new_post_path %> +<%= link_to 'New article', new_article_path %> ``` -This link will allow you to bring up the form that lets you create a new post. -You should also add a link to this template - `app/views/posts/new.html.erb` - -to go back to the `index` action. Do this by adding this underneath the form in -this template: +This link will allow you to bring up the form that lets you create a new article. +You should also add a link to this template - `app/views/articles/new.html.erb` +- to go back to the `index` action. Do this by adding this underneath the form +in this template: ```erb -<%= form_for :post do |f| %> +<%= form_for :article do |f| %> ... <% end %> -<%= link_to 'Back', posts_path %> +<%= link_to 'Back', articles_path %> ``` -Finally, add another link to the `app/views/posts/show.html.erb` template to go -back to the `index` action as well, so that people who are viewing a single post -can go back and view the whole list again: +Finally, add another link to the `app/views/articles/show.html.erb` template to +go back to the `index` action as well, so that people who are viewing a single +article can go back and view the whole list again: ```html+erb

Title: - <%= @post.title %> + <%= @article.title %>

Text: - <%= @post.text %> + <%= @article.text %>

-<%= link_to 'Back', posts_path %> +<%= link_to 'Back', articles_path %> ``` TIP: If you want to link to an action in the same controller, you don't @@ -906,87 +917,88 @@ and restart the web server when a change is made. ### Adding Some Validation -The model file, `app/models/post.rb` is about as simple as it can get: +The model file, `app/models/article.rb` is about as simple as it can get: ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base end ``` -There isn't much to this file - but note that the `Post` class inherits from +There isn't much to this file - but note that the `Article` class inherits from `ActiveRecord::Base`. Active Record supplies a great deal of functionality to your Rails models for free, including basic database CRUD (Create, Read, Update, Destroy) operations, data validation, as well as sophisticated search support and the ability to relate multiple models to one another. Rails includes methods to help you validate the data that you send to models. -Open the `app/models/post.rb` file and edit it: +Open the `app/models/article.rb` file and edit it: ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base validates :title, presence: true, length: { minimum: 5 } end ``` -These changes will ensure that all posts have a title that is at least five +These changes will ensure that all articles have a title that is at least five characters long. Rails can validate a variety of conditions in a model, including the presence or uniqueness of columns, their format, and the existence of associated objects. Validations are covered in detail in [Active Record Validations](active_record_validations.html) -With the validation now in place, when you call `@post.save` on an invalid -post, it will return `false`. If you open `app/controllers/posts_controller.rb` -again, you'll notice that we don't check the result of calling `@post.save` -inside the `create` action. If `@post.save` fails in this situation, we need to -show the form back to the user. To do this, change the `new` and `create` -actions inside `app/controllers/posts_controller.rb` to these: +With the validation now in place, when you call `@article.save` on an invalid +article, it will return `false`. If you open +`app/controllers/articles_controller.rb` again, you'll notice that we don't +check the result of calling `@article.save` inside the `create` action. +If `@article.save` fails in this situation, we need to show the form back to the +user. To do this, change the `new` and `create` actions inside +`app/controllers/articles_controller.rb` to these: ```ruby def new - @post = Post.new + @article = Article.new end def create - @post = Post.new(post_params) + @article = Article.new(article_params) - if @post.save - redirect_to @post + if @article.save + redirect_to @article else render 'new' end end private - def post_params - params.require(:post).permit(:title, :text) + def article_params + params.require(:article).permit(:title, :text) end ``` -The `new` action is now creating a new instance variable called `@post`, and +The `new` action is now creating a new instance variable called `@article`, and you'll see why that is in just a few moments. Notice that inside the `create` action we use `render` instead of `redirect_to` -when `save` returns `false`. The `render` method is used so that the `@post` +when `save` returns `false`. The `render` method is used so that the `@article` object is passed back to the `new` template when it is rendered. This rendering -is done within the same request as the form submission, whereas the `redirect_to` -will tell the browser to issue another request. +is done within the same request as the form submission, whereas the +`redirect_to` will tell the browser to issue another request. If you reload - and -try to save a post without a title, Rails will send you back to the + and +try to save an article without a title, Rails will send you back to the form, but that's not very useful. You need to tell the user that something went wrong. To do that, you'll modify -`app/views/posts/new.html.erb` to check for error messages: +`app/views/articles/new.html.erb` to check for error messages: ```html+erb -<%= form_for :post, url: posts_path do |f| %> - <% if @post.errors.any? %> +<%= form_for :article, url: articles_path do |f| %> + <% if @article.errors.any? %>
-

<%= pluralize(@post.errors.count, "error") %> prohibited - this post from being saved:

+

<%= pluralize(@article.errors.count, "error") %> prohibited + this article from being saved:

    - <% @post.errors.full_messages.each do |msg| %> + <% @article.errors.full_messages.each do |msg| %>
  • <%= msg %>
  • <% end %>
@@ -1007,57 +1019,58 @@ something went wrong. To do that, you'll modify

<% end %> -<%= link_to 'Back', posts_path %> +<%= link_to 'Back', articles_path %> ``` A few things are going on. We check if there are any errors with -`@post.errors.any?`, and in that case we show a list of all -errors with `@post.errors.full_messages`. +`@article.errors.any?`, and in that case we show a list of all +errors with `@article.errors.full_messages`. `pluralize` is a rails helper that takes a number and a string as its arguments. If the number is greater than one, the string will be automatically pluralized. -The reason why we added `@post = Post.new` in the `PostsController` is that -otherwise `@post` would be `nil` in our view, and calling -`@post.errors.any?` would throw an error. +The reason why we added `@article = Article.new` in the `ArticlesController` is +that otherwise `@article` would be `nil` in our view, and calling +`@article.errors.any?` would throw an error. TIP: Rails automatically wraps fields that contain an error with a div with class `field_with_errors`. You can define a css rule to make them standout. -Now you'll get a nice error message when saving a post without title when you -attempt to do just that on the new post form [(http://localhost:3000/posts/new)](http://localhost:3000/posts/new). +Now you'll get a nice error message when saving an article without title when +you attempt to do just that on the new article form +[(http://localhost:3000/articles/new)](http://localhost:3000/articles/new). ![Form With Errors](images/getting_started/form_with_errors.png) -### Updating Posts +### Updating Articles We've covered the "CR" part of CRUD. Now let's focus on the "U" part, updating -posts. +articles. -The first step we'll take is adding an `edit` action to the `PostsController`. +The first step we'll take is adding an `edit` action to the `ArticlesController`. ```ruby def edit - @post = Post.find(params[:id]) + @article = Article.find(params[:id]) end ``` The view will contain a form similar to the one we used when creating -new posts. Create a file called `app/views/posts/edit.html.erb` and make +new articles. Create a file called `app/views/articles/edit.html.erb` and make it look as follows: ```html+erb -

Editing post

+

Editing article

-<%= form_for :post, url: post_path(@post), method: :patch do |f| %> - <% if @post.errors.any? %> +<%= form_for :article, url: article_path(@article), method: :patch do |f| %> + <% if @article.errors.any? %>
-

<%= pluralize(@post.errors.count, "error") %> prohibited - this post from being saved:

+

<%= pluralize(@article.errors.count, "error") %> prohibited + this article from being saved:

    - <% @post.errors.full_messages.each do |msg| %> + <% @article.errors.full_messages.each do |msg| %>
  • <%= msg %>
  • <% end %>
@@ -1078,7 +1091,7 @@ it look as follows:

<% end %> -<%= link_to 'Back', posts_path %> +<%= link_to 'Back', articles_path %> ``` This time we point the form to the `update` action, which is not defined yet @@ -1090,40 +1103,42 @@ via the `PATCH` HTTP method which is the HTTP method you're expected to use to TIP: By default forms built with the _form_for_ helper are sent via `POST`. -Next we need to create the `update` action in `app/controllers/posts_controller.rb`: +Next we need to create the `update` action in +`app/controllers/articles_controller.rb`: ```ruby def update - @post = Post.find(params[:id]) + @article = Article.find(params[:id]) - if @post.update(post_params) - redirect_to @post + if @article.update(article_params) + redirect_to @article else render 'edit' end end private - def post_params - params.require(:post).permit(:title, :text) + def article_params + params.require(:article).permit(:title, :text) end ``` The new method, `update`, is used when you want to update a record that already exists, and it accepts a hash containing the attributes that you want to update. As before, if there was an error updating the -post we want to show the form back to the user. +article we want to show the form back to the user. -We reuse the `post_params` method that we defined earlier for the create action. +We reuse the `article_params` method that we defined earlier for the create +action. TIP: You don't need to pass all attributes to `update`. For -example, if you'd call `@post.update(title: 'A new title')` +example, if you'd call `@article.update(title: 'A new title')` Rails would only update the `title` attribute, leaving all other attributes untouched. Finally, we want to show a link to the `edit` action in the list of all the -posts, so let's add that now to `app/views/posts/index.html.erb` to make it -appear next to the "Show" link: +articles, so let's add that now to `app/views/articles/index.html.erb` to make +it appear next to the "Show" link: ```html+erb
@@ -1133,26 +1148,26 @@ appear next to the "Show" link: -<% @posts.each do |post| %> +<% @articles.each do |article| %> - - - - + + + + <% end %>
<%= post.title %><%= post.text %><%= link_to 'Show', post_path(post) %><%= link_to 'Edit', edit_post_path(post) %><%= article.title %><%= article.text %><%= link_to 'Show', article_path(article) %><%= link_to 'Edit', edit_article_path(article) %>
``` -And we'll also add one to the `app/views/posts/show.html.erb` template as well, -so that there's also an "Edit" link on a post's page. Add this at the bottom of -the template: +And we'll also add one to the `app/views/articles/show.html.erb` template as +well, so that there's also an "Edit" link on an article's page. Add this at the +bottom of the template: ```html+erb ... -<%= link_to 'Back', posts_path %> -| <%= link_to 'Edit', edit_post_path(@post) %> +<%= link_to 'Back', articles_path %> +| <%= link_to 'Edit', edit_article_path(@article) %> ``` And here's how our app looks so far: @@ -1169,17 +1184,17 @@ underscore. TIP: You can read more about partials in the [Layouts and Rendering in Rails](layouts_and_rendering.html) guide. -Create a new file `app/views/posts/_form.html.erb` with the following +Create a new file `app/views/articles/_form.html.erb` with the following content: ```html+erb -<%= form_for @post do |f| %> - <% if @post.errors.any? %> +<%= form_for @article do |f| %> + <% if @article.errors.any? %>
-

<%= pluralize(@post.errors.count, "error") %> prohibited - this post from being saved:

+

<%= pluralize(@article.errors.count, "error") %> prohibited + this article from being saved:

    - <% @post.errors.full_messages.each do |msg| %> + <% @article.errors.full_messages.each do |msg| %>
  • <%= msg %>
  • <% end %>
@@ -1203,41 +1218,41 @@ content: Everything except for the `form_for` declaration remained the same. The reason we can use this shorter, simpler `form_for` declaration -to stand in for either of the other forms is that `@post` is a *resource* +to stand in for either of the other forms is that `@article` is a *resource* corresponding to a full set of RESTful routes, and Rails is able to infer which URI and method to use. For more information about this use of `form_for`, see [Resource-oriented style](//api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for-label-Resource-oriented+style). -Now, let's update the `app/views/posts/new.html.erb` view to use this new +Now, let's update the `app/views/articles/new.html.erb` view to use this new partial, rewriting it completely: ```html+erb -

New post

+

New article

<%= render 'form' %> -<%= link_to 'Back', posts_path %> +<%= link_to 'Back', articles_path %> ``` -Then do the same for the `app/views/posts/edit.html.erb` view: +Then do the same for the `app/views/articles/edit.html.erb` view: ```html+erb -

Edit post

+

Edit article

<%= render 'form' %> -<%= link_to 'Back', posts_path %> +<%= link_to 'Back', articles_path %> ``` -### Deleting Posts +### Deleting Articles -We're now ready to cover the "D" part of CRUD, deleting posts from the +We're now ready to cover the "D" part of CRUD, deleting articles from the database. Following the REST convention, the route for -deleting posts as per output of `rake routes` is: +deleting articles as per output of `rake routes` is: ```ruby -DELETE /posts/:id(.:format) posts#destroy +DELETE /articles/:id(.:format) articles#destroy ``` The `delete` routing method should be used for routes that destroy @@ -1245,19 +1260,19 @@ resources. If this was left as a typical `get` route, it could be possible for people to craft malicious URLs like this: ```html -look at this cat! +look at this cat! ``` We use the `delete` method for destroying resources, and this route is mapped to -the `destroy` action inside `app/controllers/posts_controller.rb`, which doesn't -exist yet, but is provided below: +the `destroy` action inside `app/controllers/articles_controller.rb`, which +doesn't exist yet, but is provided below: ```ruby def destroy - @post = Post.find(params[:id]) - @post.destroy + @article = Article.find(params[:id]) + @article.destroy - redirect_to posts_path + redirect_to articles_path end ``` @@ -1266,12 +1281,12 @@ them from the database. Note that we don't need to add a view for this action since we're redirecting to the `index` action. Finally, add a 'Destroy' link to your `index` action template -(`app/views/posts/index.html.erb`) to wrap everything +(`app/views/articles/index.html.erb`) to wrap everything together. ```html+erb -

Listing Posts

-<%= link_to 'New post', new_post_path %> +

Listing Articles

+<%= link_to 'New article', new_article_path %> @@ -1279,13 +1294,13 @@ together. -<% @posts.each do |post| %> +<% @articles.each do |article| %> - - - - - + + + + <% end %> @@ -1304,7 +1319,7 @@ Without this file, the confirmation dialog box wouldn't appear. ![Confirm Dialog](images/getting_started/confirm_dialog.png) Congratulations, you can now create, show, list, update and destroy -posts. +articles. TIP: In general, Rails encourages the use of resources objects in place of declaring routes manually. @@ -1315,23 +1330,23 @@ Adding a Second Model --------------------- It's time to add a second model to the application. The second model will handle -comments on posts. +comments on articles. ### Generating a Model We're going to see the same generator that we used before when creating -the `Post` model. This time we'll create a `Comment` model to hold -reference of post comments. Run this command in your terminal: +the `Article` model. This time we'll create a `Comment` model to hold +reference of article comments. Run this command in your terminal: ```bash -$ rails generate model Comment commenter:string body:text post:references +$ rails generate model Comment commenter:string body:text article:references ``` This command will generate four files: | File | Purpose | | -------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -| db/migrate/20100207235629_create_comments.rb | Migration to create the comments table in your database (your name will include a different timestamp) | +| db/migrate/20140120201010_create_comments.rb | Migration to create the comments table in your database (your name will include a different timestamp) | | app/models/comment.rb | The Comment model | | test/models/comment_test.rb | Testing harness for the comments model | | test/fixtures/comments.yml | Sample comments for use in testing | @@ -1340,12 +1355,12 @@ First, take a look at `app/models/comment.rb`: ```ruby class Comment < ActiveRecord::Base - belongs_to :post + belongs_to :article end ``` -This is very similar to the `Post` model that you saw earlier. The difference -is the line `belongs_to :post`, which sets up an Active Record _association_. +This is very similar to the `Article` model that you saw earlier. The difference +is the line `belongs_to :article`, which sets up an Active Record _association_. You'll learn a little about associations in the next section of this guide. In addition to the model, Rails has also made a migration to create the @@ -1357,7 +1372,7 @@ class CreateComments < ActiveRecord::Migration create_table :comments do |t| t.string :commenter t.text :body - t.references :post, index: true + t.references :article, index: true t.timestamps end @@ -1386,26 +1401,27 @@ run against the current database, so in this case you will just see: ### Associating Models Active Record associations let you easily declare the relationship between two -models. In the case of comments and posts, you could write out the relationships -this way: +models. In the case of comments and articles, you could write out the +relationships this way: -* Each comment belongs to one post. -* One post can have many comments. +* Each comment belongs to one article. +* One article can have many comments. In fact, this is very close to the syntax that Rails uses to declare this association. You've already seen the line of code inside the `Comment` model -(app/models/comment.rb) that makes each comment belong to a Post: +(app/models/comment.rb) that makes each comment belong to an Article: ```ruby class Comment < ActiveRecord::Base - belongs_to :post + belongs_to :article end ``` -You'll need to edit `app/models/post.rb` to add the other side of the association: +You'll need to edit `app/models/article.rb` to add the other side of the +association: ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base has_many :comments validates :title, presence: true, length: { minimum: 5 } @@ -1413,29 +1429,31 @@ end ``` These two declarations enable a good bit of automatic behavior. For example, if -you have an instance variable `@post` containing a post, you can retrieve all -the comments belonging to that post as an array using `@post.comments`. +you have an instance variable `@article` containing an article, you can retrieve +all the comments belonging to that article as an array using +`@article.comments`. TIP: For more information on Active Record associations, see the [Active Record Associations](association_basics.html) guide. ### Adding a Route for Comments -As with the `welcome` controller, we will need to add a route so that Rails knows -where we would like to navigate to see `comments`. Open up the +As with the `welcome` controller, we will need to add a route so that Rails +knows where we would like to navigate to see `comments`. Open up the `config/routes.rb` file again, and edit it as follows: ```ruby -resources :posts do +resources :articles do resources :comments end ``` -This creates `comments` as a _nested resource_ within `posts`. This is another -part of capturing the hierarchical relationship that exists between posts and -comments. +This creates `comments` as a _nested resource_ within `articles`. This is +another part of capturing the hierarchical relationship that exists between +articles and comments. -TIP: For more information on routing, see the [Rails Routing](routing.html) guide. +TIP: For more information on routing, see the [Rails Routing](routing.html) +guide. ### Generating a Controller @@ -1459,27 +1477,27 @@ This creates six files and one empty directory: | app/assets/stylesheets/comment.css.scss | Cascading style sheet for the controller | Like with any blog, our readers will create their comments directly after -reading the post, and once they have added their comment, will be sent back to -the post show page to see their comment now listed. Due to this, our +reading the article, and once they have added their comment, will be sent back +to the article show page to see their comment now listed. Due to this, our `CommentsController` is there to provide a method to create comments and delete spam comments when they arrive. -So first, we'll wire up the Post show template -(`app/views/posts/show.html.erb`) to let us make a new comment: +So first, we'll wire up the Article show template +(`app/views/articles/show.html.erb`) to let us make a new comment: ```html+erb

Title: - <%= @post.title %> + <%= @article.title %>

Text: - <%= @post.text %> + <%= @article.text %>

Add a comment:

-<%= form_for([@post, @post.comments.build]) do |f| %> +<%= form_for([@article, @article.comments.build]) do |f| %>

<%= f.label :commenter %>
<%= f.text_field :commenter %> @@ -1493,22 +1511,22 @@ So first, we'll wire up the Post show template

<% end %> -<%= link_to 'Back', posts_path %> -| <%= link_to 'Edit', edit_post_path(@post) %> +<%= link_to 'Back', articles_path %> +| <%= link_to 'Edit', edit_article_path(@article) %> ``` -This adds a form on the `Post` show page that creates a new comment by +This adds a form on the `Article` show page that creates a new comment by calling the `CommentsController` `create` action. The `form_for` call here uses -an array, which will build a nested route, such as `/posts/1/comments`. +an array, which will build a nested route, such as `/articles/1/comments`. Let's wire up the `create` in `app/controllers/comments_controller.rb`: ```ruby class CommentsController < ApplicationController def create - @post = Post.find(params[:post_id]) - @comment = @post.comments.create(comment_params) - redirect_to post_path(@post) + @article = Article.find(params[:article_id]) + @comment = @article.comments.create(comment_params) + redirect_to article_path(@article) end private @@ -1518,35 +1536,36 @@ class CommentsController < ApplicationController end ``` -You'll see a bit more complexity here than you did in the controller for posts. -That's a side-effect of the nesting that you've set up. Each request for a -comment has to keep track of the post to which the comment is attached, thus the -initial call to the `find` method of the `Post` model to get the post in question. +You'll see a bit more complexity here than you did in the controller for +articles. That's a side-effect of the nesting that you've set up. Each request +for a comment has to keep track of the article to which the comment is attached, +thus the initial call to the `find` method of the `Article` model to get the +article in question. In addition, the code takes advantage of some of the methods available for an -association. We use the `create` method on `@post.comments` to create and save -the comment. This will automatically link the comment so that it belongs to that -particular post. +association. We use the `create` method on `@article.comments` to create and +save the comment. This will automatically link the comment so that it belongs to +that particular article. -Once we have made the new comment, we send the user back to the original post -using the `post_path(@post)` helper. As we have already seen, this calls the -`show` action of the `PostsController` which in turn renders the `show.html.erb` -template. This is where we want the comment to show, so let's add that to the -`app/views/posts/show.html.erb`. +Once we have made the new comment, we send the user back to the original article +using the `article_path(@article)` helper. As we have already seen, this calls +the `show` action of the `ArticlesController` which in turn renders the +`show.html.erb` template. This is where we want the comment to show, so let's +add that to the `app/views/articles/show.html.erb`. ```html+erb

Title: - <%= @post.title %> + <%= @article.title %>

Text: - <%= @post.text %> + <%= @article.text %>

Comments

-<% @post.comments.each do |comment| %> +<% @article.comments.each do |comment| %>

Commenter: <%= comment.commenter %> @@ -1559,7 +1578,7 @@ template. This is where we want the comment to show, so let's add that to the <% end %>

Add a comment:

-<%= form_for([@post, @post.comments.build]) do |f| %> +<%= form_for([@article, @article.comments.build]) do |f| %>

<%= f.label :commenter %>
<%= f.text_field :commenter %> @@ -1573,26 +1592,26 @@ template. This is where we want the comment to show, so let's add that to the

<% end %> -<%= link_to 'Edit Post', edit_post_path(@post) %> | -<%= link_to 'Back to Posts', posts_path %> +<%= link_to 'Edit Article', edit_article_path(@article) %> | +<%= link_to 'Back to Articles', articles_path %> ``` -Now you can add posts and comments to your blog and have them show up in the +Now you can add articles and comments to your blog and have them show up in the right places. -![Post with Comments](images/getting_started/post_with_comments.png) +![Article with Comments](images/getting_started/article_with_comments.png) Refactoring ----------- -Now that we have posts and comments working, take a look at the -`app/views/posts/show.html.erb` template. It is getting long and awkward. We can -use partials to clean it up. +Now that we have articles and comments working, take a look at the +`app/views/articles/show.html.erb` template. It is getting long and awkward. We +can use partials to clean it up. ### Rendering Partial Collections -First, we will make a comment partial to extract showing all the comments for the -post. Create the file `app/views/comments/_comment.html.erb` and put the +First, we will make a comment partial to extract showing all the comments for +the article. Create the file `app/views/comments/_comment.html.erb` and put the following into it: ```html+erb @@ -1607,25 +1626,25 @@ following into it:

``` -Then you can change `app/views/posts/show.html.erb` to look like the +Then you can change `app/views/articles/show.html.erb` to look like the following: ```html+erb

Title: - <%= @post.title %> + <%= @article.title %>

Text: - <%= @post.text %> + <%= @article.text %>

Comments

-<%= render @post.comments %> +<%= render @article.comments %>

Add a comment:

-<%= form_for([@post, @post.comments.build]) do |f| %> +<%= form_for([@article, @article.comments.build]) do |f| %>

<%= f.label :commenter %>
<%= f.text_field :commenter %> @@ -1639,13 +1658,13 @@ following:

<% end %> -<%= link_to 'Edit Post', edit_post_path(@post) %> | -<%= link_to 'Back to Posts', posts_path %> +<%= link_to 'Edit Article', edit_article_path(@article) %> | +<%= link_to 'Back to Articles', articles_path %> ``` This will now render the partial in `app/views/comments/_comment.html.erb` once -for each comment that is in the `@post.comments` collection. As the `render` -method iterates over the `@post.comments` collection, it assigns each +for each comment that is in the `@article.comments` collection. As the `render` +method iterates over the `@article.comments` collection, it assigns each comment to a local variable named the same as the partial, in this case `comment` which is then available in the partial for us to show. @@ -1655,7 +1674,7 @@ Let us also move that new comment section out to its own partial. Again, you create a file `app/views/comments/_form.html.erb` containing: ```html+erb -<%= form_for([@post, @post.comments.build]) do |f| %> +<%= form_for([@article, @article.comments.build]) do |f| %>

<%= f.label :commenter %>
<%= f.text_field :commenter %> @@ -1670,27 +1689,27 @@ create a file `app/views/comments/_form.html.erb` containing: <% end %> ``` -Then you make the `app/views/posts/show.html.erb` look like the following: +Then you make the `app/views/articles/show.html.erb` look like the following: ```html+erb

Title: - <%= @post.title %> + <%= @article.title %>

Text: - <%= @post.text %> + <%= @article.text %>

Comments

-<%= render @post.comments %> +<%= render @article.comments %>

Add a comment:

<%= render "comments/form" %> -<%= link_to 'Edit Post', edit_post_path(@post) %> | -<%= link_to 'Back to Posts', posts_path %> +<%= link_to 'Edit Article', edit_article_path(@article) %> | +<%= link_to 'Back to Articles', articles_path %> ``` The second render just defines the partial template we want to render, @@ -1698,8 +1717,8 @@ The second render just defines the partial template we want to render, string and realize that you want to render the `_form.html.erb` file in the `app/views/comments` directory. -The `@post` object is available to any partials rendered in the view because we -defined it as an instance variable. +The `@article` object is available to any partials rendered in the view because +we defined it as an instance variable. Deleting Comments ----------------- @@ -1723,30 +1742,30 @@ So first, let's add the delete link in the

- <%= link_to 'Destroy Comment', [comment.post, comment], + <%= link_to 'Destroy Comment', [comment.article, comment], method: :delete, data: { confirm: 'Are you sure?' } %>

``` Clicking this new "Destroy Comment" link will fire off a `DELETE -/posts/:post_id/comments/:id` to our `CommentsController`, which can then use -this to find the comment we want to delete, so let's add a `destroy` action to our -controller (`app/controllers/comments_controller.rb`): +/articles/:article_id/comments/:id` to our `CommentsController`, which can then +use this to find the comment we want to delete, so let's add a `destroy` action +to our controller (`app/controllers/comments_controller.rb`): ```ruby class CommentsController < ApplicationController def create - @post = Post.find(params[:post_id]) - @comment = @post.comments.create(comment_params) - redirect_to post_path(@post) + @article = Article.find(params[:article_id]) + @comment = @article.comments.create(comment_params) + redirect_to article_path(@article) end def destroy - @post = Post.find(params[:post_id]) - @comment = @post.comments.find(params[:id]) + @article = Article.find(params[:article_id]) + @comment = @article.comments.find(params[:id]) @comment.destroy - redirect_to post_path(@post) + redirect_to article_path(@article) end private @@ -1756,20 +1775,20 @@ class CommentsController < ApplicationController end ``` -The `destroy` action will find the post we are looking at, locate the comment -within the `@post.comments` collection, and then remove it from the -database and send us back to the show action for the post. +The `destroy` action will find the article we are looking at, locate the comment +within the `@article.comments` collection, and then remove it from the +database and send us back to the show action for the article. ### Deleting Associated Objects -If you delete a post then its associated comments will also need to be deleted. -Otherwise they would simply occupy space in the database. Rails allows you to -use the `dependent` option of an association to achieve this. Modify the Post -model, `app/models/post.rb`, as follows: +If you delete an article then its associated comments will also need to be +deleted. Otherwise they would simply occupy space in the database. Rails allows +you to use the `dependent` option of an association to achieve this. Modify the +Article model, `app/models/article.rb`, as follows: ```ruby -class Post < ActiveRecord::Base +class Article < ActiveRecord::Base has_many :comments, dependent: :destroy validates :title, presence: true, length: { minimum: 5 } @@ -1782,33 +1801,34 @@ Security ### Basic Authentication If you were to publish your blog online, anybody would be able to add, edit and -delete posts or delete comments. +delete articles or delete comments. Rails provides a very simple HTTP authentication system that will work nicely in this situation. -In the `PostsController` we need to have a way to block access to the various +In the `ArticlesController` we need to have a way to block access to the various actions if the person is not authenticated, here we can use the Rails `http_basic_authenticate_with` method, allowing access to the requested action if that method allows it. To use the authentication system, we specify it at the top of our -`PostsController`, in this case, we want the user to be authenticated on every -action, except for `index` and `show`, so we write that in `app/controllers/posts_controller.rb`: +`ArticlesController`, in this case, we want the user to be authenticated on +every action, except for `index` and `show`, so we write that in +`app/controllers/articles_controller.rb`: ```ruby -class PostsController < ApplicationController +class ArticlesController < ApplicationController http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show] def index - @posts = Post.all + @articles = Article.all end # snipped for brevity ``` -We also only want to allow authenticated users to delete comments, so in the +We also want to allow only authenticated users to delete comments, so in the `CommentsController` (`app/controllers/comments_controller.rb`) we write: ```ruby @@ -1817,21 +1837,22 @@ class CommentsController < ApplicationController http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy def create - @post = Post.find(params[:post_id]) + @article = Article.find(params[:article_id]) ... end # snipped for brevity ``` -Now if you try to create a new post, you will be greeted with a basic HTTP +Now if you try to create a new article, you will be greeted with a basic HTTP Authentication challenge ![Basic HTTP Authentication Challenge](images/getting_started/challenge.png) Other authentication methods are available for Rails applications. Two popular -authentication add-ons for Rails are the [Devise](https://github.com/plataformatec/devise) -rails engine and the [Authlogic](https://github.com/binarylogic/authlogic) gem, +authentication add-ons for Rails are the +[Devise](https://github.com/plataformatec/devise) rails engine and +the [Authlogic](https://github.com/binarylogic/authlogic) gem, along with a number of others. @@ -1887,15 +1908,16 @@ cannot be automatically detected by Rails and corrected. Two very common sources of data that are not UTF-8: -* Your text editor: Most text editors (such as TextMate), default to saving files as - UTF-8. If your text editor does not, this can result in special characters that you - enter in your templates (such as é) to appear as a diamond with a question mark inside - in the browser. This also applies to your i18n translation files. - Most editors that do not already default to UTF-8 (such as some versions of - Dreamweaver) offer a way to change the default to UTF-8. Do so. -* Your database: Rails defaults to converting data from your database into UTF-8 at - the boundary. However, if your database is not using UTF-8 internally, it may not - be able to store all characters that your users enter. For instance, if your database - is using Latin-1 internally, and your user enters a Russian, Hebrew, or Japanese - character, the data will be lost forever once it enters the database. If possible, - use UTF-8 as the internal storage of your database. +* Your text editor: Most text editors (such as TextMate), default to saving + files as UTF-8. If your text editor does not, this can result in special + characters that you enter in your templates (such as é) to appear as a diamond + with a question mark inside in the browser. This also applies to your i18n + translation files. Most editors that do not already default to UTF-8 (such as + some versions of Dreamweaver) offer a way to change the default to UTF-8. Do + so. +* Your database: Rails defaults to converting data from your database into UTF-8 + at the boundary. However, if your database is not using UTF-8 internally, it + may not be able to store all characters that your users enter. For instance, + if your database is using Latin-1 internally, and your user enters a Russian, + Hebrew, or Japanese character, the data will be lost forever once it enters + the database. If possible, use UTF-8 as the internal storage of your database. -- cgit v1.2.3 From fe63933ceec7223beef06ff6f8d1e5b795e17f20 Mon Sep 17 00:00:00 2001 From: Robin Dupret Date: Sat, 25 Jan 2014 22:12:28 +0100 Subject: Add a missing changelog entry for #13825 [ci skip] --- railties/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 84f8ad59fb..01c80d3f58 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,9 @@ +* Ensure that `bin/rails` is a file before trying to execute it. + + Fixes #13825. + + *bronzle* + * Use single quotes in generated files. *Cristian Mircea Messel*, *Chulki Lee* -- cgit v1.2.3 From 9d0fceb55da9a0b421c81091388d8484e2aedfd6 Mon Sep 17 00:00:00 2001 From: Adrien Lamothe Date: Sat, 25 Jan 2014 23:04:36 -0800 Subject: Correct grammar from '... allowing both thread web servers ...' to '... allowing both threaded web servers ...'. --- guides/code/getting_started/config/environments/production.rb | 2 +- .../generators/rails/app/templates/config/environments/production.rb.tt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/code/getting_started/config/environments/production.rb b/guides/code/getting_started/config/environments/production.rb index 93d44723fb..8c514e065e 100644 --- a/guides/code/getting_started/config/environments/production.rb +++ b/guides/code/getting_started/config/environments/production.rb @@ -5,7 +5,7 @@ Blog::Application.configure do config.cache_classes = true # Eager load code on boot. This eager loads most of Rails and - # your application in memory, allowing both thread web servers + # your application in memory, allowing both threaded web servers # and those relying on copy on write to perform better. # Rake tasks automatically ignore this option for performance. config.eager_load = true diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 3baa382bd6..d2f041aa27 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -5,7 +5,7 @@ Rails.application.configure do config.cache_classes = true # Eager load code on boot. This eager loads most of Rails and - # your application in memory, allowing both thread web servers + # your application in memory, allowing both threaded web servers # and those relying on copy on write to perform better. # Rake tasks automatically ignore this option for performance. config.eager_load = true -- cgit v1.2.3 From 35fd81672e02497c31f0b3cab965f4a38090229a Mon Sep 17 00:00:00 2001 From: Andrew White Date: Sun, 26 Jan 2014 11:38:30 +0000 Subject: Add the ability to intercept emails before previewing To support the ability for tools like CSS style inliners to operate on emails being previewed this commit adds a hook in a similar fashion to the existing delivery interceptor hook, e.g: class CSSInlineStyler def self.previewing_email(message) # inline CSS styles end end ActionMailer::Base.register_preview_interceptor CSSInlineStyler Fixes #13622. --- actionmailer/CHANGELOG.md | 15 +++++++++++ actionmailer/lib/action_mailer/base.rb | 15 +++++++++++ actionmailer/lib/action_mailer/preview.rb | 39 +++++++++++++++++++++++++-- actionmailer/test/base_test.rb | 45 ++++++++++++++++++++++++++++--- 4 files changed, 109 insertions(+), 5 deletions(-) diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 1867a392eb..ede8cfccbf 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,3 +1,18 @@ +* Add the ability to intercept emails before previewing in a similar fashion + to how emails can be intercepted before delivery, e.g: + + class CSSInlineStyler + def self.previewing_email(message) + # inline CSS styles + end + end + + ActionMailer::Base.register_preview_interceptor CSSInlineStyler + + Fixes #13622. + + *Andrew White* + * Add mailer previews feature based on 37 Signals mail_view gem *Andrew White* diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 41db62cc58..5af0217973 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -330,6 +330,21 @@ module ActionMailer # An overview of all previews is accessible at http://localhost:3000/rails/mailers # on a running development server instance. # + # Previews can also be intercepted in a similar manner as deliveries can be by registering + # a preview interceptor that has a previewing_email method: + # + # class CssInlineStyler + # def self.previewing_email(message) + # # inline CSS styles + # end + # end + # + # config.action_mailer.register_preview_interceptor :css_inline_styler + # + # Note that interceptors need to be registered both with register_interceptor + # and register_preview_interceptor if they should operate on both sending and + # previewing emails. + # # = Configuration options # # These options are specified on the class level, like diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb index ecceaf8c70..0efd702e4d 100644 --- a/actionmailer/lib/action_mailer/preview.rb +++ b/actionmailer/lib/action_mailer/preview.rb @@ -10,6 +10,31 @@ module ActionMailer # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews" # class_attribute :preview_path, instance_writer: false + + # :nodoc: + mattr_accessor :preview_interceptors, instance_writer: false + self.preview_interceptors = [] + + # Register one or more Interceptors which will be called before mail is previewed. + def register_preview_interceptors(*interceptors) + interceptors.flatten.compact.each { |interceptor| register_preview_interceptor(interceptor) } + end + + # Register am Interceptor which will be called before mail is previewed. + # Either a class or a string can be passed in as the Interceptor. If a + # string is passed in it will be constantized. + def register_preview_interceptor(interceptor) + preview_interceptor = case interceptor + when String, Symbol + interceptor.to_s.camelize.constantize + else + interceptor + end + + unless preview_interceptors.include?(preview_interceptor) + preview_interceptors << preview_interceptor + end + end end end @@ -23,10 +48,14 @@ module ActionMailer descendants end - # Returns the mail object for the given email name + # Returns the mail object for the given email name. The registered preview + # interceptors will be informed so that they can transform the message + # as they would if the mail was actually being delivered. def call(email) preview = self.new - preview.public_send(email) + message = preview.public_send(email) + inform_preview_interceptors(message) + message end # Returns all of the available email previews @@ -68,6 +97,12 @@ module ActionMailer def preview_path? #:nodoc: Base.preview_path? end + + def inform_preview_interceptors(message) #:nodoc: + Base.preview_interceptors.each do |interceptor| + interceptor.previewing_email(message) + end + end end end end diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index c1759d9b92..454e1afe97 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -539,12 +539,18 @@ class BaseTest < ActiveSupport::TestCase end class MyInterceptor - def self.delivering_email(mail) - end + def self.delivering_email(mail); end + def self.previewing_email(mail); end end class MySecondInterceptor - def self.delivering_email(mail) + def self.delivering_email(mail); end + def self.previewing_email(mail); end + end + + class BaseMailerPreview < ActionMailer::Preview + def welcome + BaseMailer.welcome end end @@ -570,6 +576,39 @@ class BaseTest < ActiveSupport::TestCase mail.deliver end + test "you can register a preview interceptor to the mail object that gets passed the mail object before previewing" do + ActionMailer::Base.register_preview_interceptor(MyInterceptor) + mail = BaseMailer.welcome + BaseMailerPreview.stubs(:welcome).returns(mail) + MyInterceptor.expects(:previewing_email).with(mail) + BaseMailerPreview.call(:welcome) + end + + test "you can register a preview interceptor using its stringified name to the mail object that gets passed the mail object before previewing" do + ActionMailer::Base.register_preview_interceptor("BaseTest::MyInterceptor") + mail = BaseMailer.welcome + BaseMailerPreview.stubs(:welcome).returns(mail) + MyInterceptor.expects(:previewing_email).with(mail) + BaseMailerPreview.call(:welcome) + end + + test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before previewing" do + ActionMailer::Base.register_preview_interceptor(:"base_test/my_interceptor") + mail = BaseMailer.welcome + BaseMailerPreview.stubs(:welcome).returns(mail) + MyInterceptor.expects(:previewing_email).with(mail) + BaseMailerPreview.call(:welcome) + end + + test "you can register multiple preview interceptors to the mail object that both get passed the mail object before previewing" do + ActionMailer::Base.register_preview_interceptors("BaseTest::MyInterceptor", MySecondInterceptor) + mail = BaseMailer.welcome + BaseMailerPreview.stubs(:welcome).returns(mail) + MyInterceptor.expects(:previewing_email).with(mail) + MySecondInterceptor.expects(:previewing_email).with(mail) + BaseMailerPreview.call(:welcome) + end + test "being able to put proc's into the defaults hash and they get evaluated on mail sending" do mail1 = ProcMailer.welcome['X-Proc-Method'] yesterday = 1.day.ago -- cgit v1.2.3 From 7d86352f83079458dd04df2ee5e1b38179c3aac3 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Sun, 26 Jan 2014 11:50:36 +0000 Subject: Don't use a class_attribute for ActionMailer::Base.preview_path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since preview_path is read from ActionMailer::Base when previewing, subclasses can’t change it so don’t there's no need for the extra overhead imposed by using it. --- actionmailer/lib/action_mailer/preview.rb | 8 ++------ actionmailer/lib/action_mailer/railtie.rb | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb index 0efd702e4d..fde0e86f5b 100644 --- a/actionmailer/lib/action_mailer/preview.rb +++ b/actionmailer/lib/action_mailer/preview.rb @@ -9,7 +9,7 @@ module ActionMailer # # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews" # - class_attribute :preview_path, instance_writer: false + mattr_accessor :preview_path, instance_writer: false # :nodoc: mattr_accessor :preview_interceptors, instance_writer: false @@ -85,7 +85,7 @@ module ActionMailer protected def load_previews #:nodoc: - if preview_path? + if preview_path Dir["#{preview_path}/**/*_preview.rb"].each{ |file| require_dependency file } end end @@ -94,10 +94,6 @@ module ActionMailer Base.preview_path end - def preview_path? #:nodoc: - Base.preview_path? - end - def inform_preview_interceptors(message) #:nodoc: Base.preview_interceptors.each do |interceptor| interceptor.previewing_email(message) diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index c893ddfef5..8d1e40297b 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -46,7 +46,7 @@ module ActionMailer end config.after_initialize do - if ActionMailer::Base.preview_path? + if ActionMailer::Base.preview_path ActiveSupport::Dependencies.autoload_paths << ActionMailer::Base.preview_path end end -- cgit v1.2.3 From 4df9cc29c15634de8de5912d2b40766f04e58c03 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Sun, 26 Jan 2014 12:03:32 +0000 Subject: Support underscored symbols in Action Mailer config We allow the use of underscored symbols to represent classes throughout other parts of Rails so it seems incongruous that it's not supported in `register_interceptor` and `register_observer`. --- actionmailer/CHANGELOG.md | 5 +++++ actionmailer/lib/action_mailer/base.rb | 23 ++++++++++++++++++----- actionmailer/test/base_test.rb | 14 ++++++++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index ede8cfccbf..c264c710f6 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,3 +1,8 @@ +* Support the use of underscored symbols when registering interceptors and + observers like we do elsewhere within Rails. + + *Andrew White* + * Add the ability to intercept emails before previewing in a similar fashion to how emails can be intercepted before delivery, e.g: diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 5af0217973..76814d336b 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -444,18 +444,31 @@ module ActionMailer end # Register an Observer which will be notified when mail is delivered. - # Either a class or a string can be passed in as the Observer. If a string is passed in - # it will be constantized. + # Either a class, string or symbol can be passed in as the Observer. + # If a string or symbol is passed in it will be camelized and constantized. def register_observer(observer) - delivery_observer = (observer.is_a?(String) ? observer.constantize : observer) + delivery_observer = case observer + when String, Symbol + observer.to_s.camelize.constantize + else + observer + end + Mail.register_observer(delivery_observer) end # Register an Interceptor which will be called before mail is sent. - # Either a class or a string can be passed in as the Interceptor. If a string is passed in + # Either a class, string or symbol can be passed in as the Interceptor. + # If a string or symbol is passed in it will be camelized and constantized. # it will be constantized. def register_interceptor(interceptor) - delivery_interceptor = (interceptor.is_a?(String) ? interceptor.constantize : interceptor) + delivery_interceptor = case interceptor + when String, Symbol + interceptor.to_s.camelize.constantize + else + interceptor + end + Mail.register_interceptor(delivery_interceptor) end diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 454e1afe97..02707d0b5f 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -530,6 +530,13 @@ class BaseTest < ActiveSupport::TestCase mail.deliver end + test "you can register an observer using its symbolized underscored name to the mail object that gets informed on email delivery" do + ActionMailer::Base.register_observer(:"base_test/my_observer") + mail = BaseMailer.welcome + MyObserver.expects(:delivered_email).with(mail) + mail.deliver + end + test "you can register multiple observers to the mail object that both get informed on email delivery" do ActionMailer::Base.register_observers("BaseTest::MyObserver", MySecondObserver) mail = BaseMailer.welcome @@ -568,6 +575,13 @@ class BaseTest < ActiveSupport::TestCase mail.deliver end + test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before delivery" do + ActionMailer::Base.register_interceptor(:"base_test/my_interceptor") + mail = BaseMailer.welcome + MyInterceptor.expects(:delivering_email).with(mail) + mail.deliver + end + test "you can register multiple interceptors to the mail object that both get passed the mail object before delivery" do ActionMailer::Base.register_interceptors("BaseTest::MyInterceptor", MySecondInterceptor) mail = BaseMailer.welcome -- cgit v1.2.3 From 718d3b0bc53bb5da4e5fc32d1a27f2119e6c747c Mon Sep 17 00:00:00 2001 From: Robin Dupret Date: Sun, 26 Jan 2014 13:11:26 +0100 Subject: Remove an extra comment [ci skip] --- actionmailer/lib/action_mailer/base.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 76814d336b..388f694590 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -460,7 +460,6 @@ module ActionMailer # Register an Interceptor which will be called before mail is sent. # Either a class, string or symbol can be passed in as the Interceptor. # If a string or symbol is passed in it will be camelized and constantized. - # it will be constantized. def register_interceptor(interceptor) delivery_interceptor = case interceptor when String, Symbol -- cgit v1.2.3 From b5944928700cfc13ed8f615acb54116a3039f345 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Sun, 26 Jan 2014 16:56:33 +0000 Subject: Maintain current timezone when changing time during DST overlap Currently if a time is changed during DST overlap in the autumn then the method `period_for_local` will return the DST period. However if the original time is not DST then this can be surprising and is not what is generally wanted. This commit changes that behavior to maintain the current period if it's in the list of periods returned by `periods_for_local`. It is possible to alter the behavior of `period_for_local` by specifying a second argument but since we may be change from another time that could be either DST or not then this would give inconsistent results. Fixes #12163. --- activesupport/CHANGELOG.md | 12 ++++++++++++ activesupport/lib/active_support/time_with_zone.rb | 12 +++++++++--- activesupport/lib/active_support/values/time_zone.rb | 4 ++++ activesupport/test/core_ext/time_with_zone_test.rb | 5 +++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index adaeb99c87..ba450b85c4 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,15 @@ +* Maintain the current timezone when calling `change` during DST overlap + + Currently if a time is changed during DST overlap in the autumn then the method + `period_for_local` will return the DST period. However if the original time is + not DST then this can be surprising and is not what is generally wanted. This + commit changes that behavior to maintain the current period if it's in the list + of periods returned by `periods_for_local`. + + Fixes #12163. + + *Andrew White* + * Added `Hash#compact` and `Hash#compact!` for removing items with nil value from hash. *Celestino Gomes* diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 50db7da9d9..d47aee00f4 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -45,7 +45,7 @@ module ActiveSupport def initialize(utc_time, time_zone, local_time = nil, period = nil) @utc, @time_zone, @time = utc_time, time_zone, local_time - @period = @utc ? period : get_period_and_ensure_valid_local_time + @period = @utc ? period : get_period_and_ensure_valid_local_time(period) end # Returns a Time or DateTime instance that represents the time in +time_zone+. @@ -292,6 +292,12 @@ module ActiveSupport end end + def change(options) + new_time = time.change(options) + periods = time_zone.periods_for_local(new_time) + self.class.new(nil, time_zone, new_time, periods.include?(period) ? period : nil) + end + %w(year mon month day mday wday yday hour min sec usec nsec to_date).each do |method_name| class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{method_name} # def month @@ -367,12 +373,12 @@ module ActiveSupport end private - def get_period_and_ensure_valid_local_time + def get_period_and_ensure_valid_local_time(period) # we don't want a Time.local instance enforcing its own DST rules as well, # so transfer time values to a utc constructor if necessary @time = transfer_time_values_to_utc_constructor(@time) unless @time.utc? begin - @time_zone.period_for_local(@time) + period || @time_zone.period_for_local(@time) rescue ::TZInfo::PeriodNotFound # time is in the "spring forward" hour gap, so we're moving the time forward one hour and trying again @time += 1.hour diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index beaac42fa1..eb785d46ce 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -352,6 +352,10 @@ module ActiveSupport tzinfo.period_for_local(time, dst) end + def periods_for_local(time) #:nodoc: + tzinfo.periods_for_local(time) + end + def self.find_tzinfo(name) TZInfo::TimezoneProxy.new(MAPPING[name] || name) end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 5494824a40..43fe572bfc 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -511,6 +511,11 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.change(:sec => 30).inspect end + def test_change_at_dst_boundary + twz = ActiveSupport::TimeWithZone.new(Time.at(1319936400).getutc, ActiveSupport::TimeZone['Madrid']) + assert_equal twz, twz.change(:min => 0) + end + def test_advance assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Mon, 31 Dec 2001 19:00:00 EST -05:00", @twz.advance(:years => 2).inspect -- cgit v1.2.3 From 70588be101fd3bc6beecca08dc884823158968dc Mon Sep 17 00:00:00 2001 From: Greg Saks Date: Sun, 26 Jan 2014 14:21:05 -0500 Subject: Fix order syntax in find_by_sql example --- guides/source/active_record_querying.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 3783be50c0..d164b08d93 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1455,7 +1455,7 @@ If you'd like to use your own SQL to find records in a table you can use `find_b ```ruby Client.find_by_sql("SELECT * FROM clients INNER JOIN orders ON clients.id = orders.client_id - ORDER clients.created_at desc") + ORDER BY clients.created_at desc") ``` `find_by_sql` provides you with a simple way of making custom calls to the database and retrieving instantiated objects. -- cgit v1.2.3 From 0df1f914104073b70f8d8976d0d5adc3b2a1e44e Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sun, 26 Jan 2014 20:47:56 +0100 Subject: revises references to :allow_(nil|blank) in some docs [ci skip] [Steven Yang & Xavier Noria] Closes #11247. --- activemodel/lib/active_model/validations.rb | 2 -- activemodel/lib/active_model/validations/absence.rb | 2 +- activemodel/lib/active_model/validations/acceptance.rb | 6 ++---- activemodel/lib/active_model/validations/confirmation.rb | 2 +- activemodel/lib/active_model/validations/exclusion.rb | 6 +----- activemodel/lib/active_model/validations/format.rb | 6 +----- activemodel/lib/active_model/validations/inclusion.rb | 6 +----- activemodel/lib/active_model/validations/numericality.rb | 2 +- activemodel/lib/active_model/validations/presence.rb | 2 +- activemodel/lib/active_model/validations/validates.rb | 4 +++- 10 files changed, 12 insertions(+), 26 deletions(-) diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 6be44b5d63..08928a713d 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -126,8 +126,6 @@ module ActiveModel # Options: # * :on - Specifies the context where this validation is active # (e.g. on: :create or on: :custom_validation_context) - # * :allow_nil - Skip validation if attribute is +nil+. - # * :allow_blank - Skip validation if attribute is blank. # * :if - Specifies a method, proc or string to call to determine # if the validation should occur (e.g. if: :allow_validation, # or if: Proc.new { |user| user.signup_step > 2 }). The method, diff --git a/activemodel/lib/active_model/validations/absence.rb b/activemodel/lib/active_model/validations/absence.rb index 1a1863370b..9b5416fb1d 100644 --- a/activemodel/lib/active_model/validations/absence.rb +++ b/activemodel/lib/active_model/validations/absence.rb @@ -21,7 +21,7 @@ module ActiveModel # * :message - A custom error message (default is: "must be blank"). # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See ActiveModel::Validation#validates for more information def validates_absence_of(*attr_names) validates_with AbsenceValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index 139de16326..ac5e79859b 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -38,8 +38,6 @@ module ActiveModel # Configuration options: # * :message - A custom error message (default is: "must be # accepted"). - # * :allow_nil - Skip validation if attribute is +nil+ (default - # is +true+). # * :accept - Specifies value that is considered accepted. # The default value is a string "1", which makes it easy to relate to # an HTML checkbox. This should be set to +true+ if you are validating @@ -47,8 +45,8 @@ module ActiveModel # before validation. # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. - # See ActiveModel::Validation#validates for more information + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See ActiveModel::Validation#validates for more information. def validates_acceptance_of(*attr_names) validates_with AcceptanceValidator, _merge_attributes(attr_names) end diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb index b0542661af..a51523912f 100644 --- a/activemodel/lib/active_model/validations/confirmation.rb +++ b/activemodel/lib/active_model/validations/confirmation.rb @@ -57,7 +57,7 @@ module ActiveModel # confirmation"). # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See ActiveModel::Validation#validates for more information def validates_confirmation_of(*attr_names) validates_with ConfirmationValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb index 48bf5cd802..f342d27275 100644 --- a/activemodel/lib/active_model/validations/exclusion.rb +++ b/activemodel/lib/active_model/validations/exclusion.rb @@ -34,13 +34,9 @@ module ActiveModel # Range#cover?, otherwise with include?. # * :message - Specifies a custom error message (default is: "is # reserved"). - # * :allow_nil - If set to true, skips this validation if the - # attribute is +nil+ (default is +false+). - # * :allow_blank - If set to true, skips this validation if the - # attribute is blank(default is +false+). # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See ActiveModel::Validation#validates for more information def validates_exclusion_of(*attr_names) validates_with ExclusionValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb index f0fe22438f..ff3e95da34 100644 --- a/activemodel/lib/active_model/validations/format.rb +++ b/activemodel/lib/active_model/validations/format.rb @@ -91,10 +91,6 @@ module ActiveModel # # Configuration options: # * :message - A custom error message (default is: "is invalid"). - # * :allow_nil - If set to true, skips this validation if the - # attribute is +nil+ (default is +false+). - # * :allow_blank - If set to true, skips this validation if the - # attribute is blank (default is +false+). # * :with - Regular expression that if the attribute matches will # result in a successful validation. This can be provided as a proc or # lambda returning regular expression which will be called at runtime. @@ -107,7 +103,7 @@ module ActiveModel # beginning or end of the string. These anchors are ^ and $. # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See ActiveModel::Validation#validates for more information def validates_format_of(*attr_names) validates_with FormatValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb index b344095807..c84025f083 100644 --- a/activemodel/lib/active_model/validations/inclusion.rb +++ b/activemodel/lib/active_model/validations/inclusion.rb @@ -34,13 +34,9 @@ module ActiveModel # * :within - A synonym(or alias) for :in # * :message - Specifies a custom error message (default is: "is # not included in the list"). - # * :allow_nil - If set to +true+, skips this validation if the - # attribute is +nil+ (default is +false+). - # * :allow_blank - If set to +true+, skips this validation if the - # attribute is blank (default is +false+). # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See ActiveModel::Validation#validates for more information def validates_inclusion_of(*attr_names) validates_with InclusionValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index c8d3236463..a9fb9804d4 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -110,7 +110,7 @@ module ActiveModel # * :even - Specifies the value must be an even number. # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+ . + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ . # See ActiveModel::Validation#validates for more information # # The following checks can also be supplied with a proc or a symbol which diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb index ab8c8359fc..5d593274eb 100644 --- a/activemodel/lib/active_model/validations/presence.rb +++ b/activemodel/lib/active_model/validations/presence.rb @@ -29,7 +29,7 @@ module ActiveModel # * :message - A custom error message (default is: "can't be blank"). # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See ActiveModel::Validation#validates for more information def validates_presence_of(*attr_names) validates_with PresenceValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb index bf588b7bd0..ae8d377fdf 100644 --- a/activemodel/lib/active_model/validations/validates.rb +++ b/activemodel/lib/active_model/validations/validates.rb @@ -83,7 +83,9 @@ module ActiveModel # or unless: Proc.new { |user| user.signup_step <= 2 }). The # method, proc or string should return or evaluate to a +true+ or # +false+ value. - # * :strict - if the :strict option is set to true + # * :allow_nil - Skip validation if the attribute is +nil+. + # * :allow_blank - Skip validation if the attribute is blank. + # * :strict - If the :strict option is set to true # will raise ActiveModel::StrictValidationFailed instead of adding the error. # :strict option can also be set to any other exception. # -- cgit v1.2.3 From 225bcadfed9f901d97867f9f9371c0e62379cb0f Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sun, 26 Jan 2014 21:36:17 +0100 Subject: API guidelines: revises warning about +...+ [ci skip] Modern RDoc accepts a few more things in +...+. In particular symbols work now. The current regexp in RDoc is https://github.com/rdoc/rdoc/blob/v4.1.1/lib/rdoc/markup/attribute_manager.rb#L133. --- guides/source/api_documentation_guidelines.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index 311cc23cf0..3f1243bc4d 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -163,7 +163,10 @@ class Array end ``` -WARNING: Using a pair of `+...+` for fixed-width font only works with **words**; that is: anything matching `\A\w+\z`. For anything else use `...`, notably symbols, setters, inline snippets, etc. +WARNING: Using `+...+` for fixed-width font only works with simple content like +ordinary method names, symbols, paths (with forward slashes), etc. Please use +`...` for everything else, notably class or module names with a +namespace as in `ActiveRecord::Base`. ### Regular Font -- cgit v1.2.3 From e1e17a55d246d485f546766606580e8e4919442b Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sun, 26 Jan 2014 22:10:05 +0100 Subject: adds a section about booleans in the API guidelines [ci skip] --- guides/source/api_documentation_guidelines.md | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index 3f1243bc4d..7466b4ff48 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -128,6 +128,53 @@ On the other hand, regular comments do not use an arrow: # polymorphic_url(record) # same as comment_url(record) ``` +Booleans +-------- + +In predicates and flags prefer documenting boolean semantics over exact values. + +When "true" or "false" are used as defined in Ruby use regular font. The +singletons `true` and `false` need fixed-width font. Please avoid terms like +"truthy", Ruby defines what is true and false in the language, and thus those +words have a technical meaning and need no substitutes. + +As a rule of thumb, do not document singletons unless absolutely necessary. That +prevents artificial constructs like `!!` or ternaries, allows refactors, and the +code does not need to rely on the exact values returned by methods being called +in the implementation. + +For example: + +```markdown +`config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default +``` + +the user does not need to know which is the actual default value of the flag, +and so we only document its boolean semantics. + +An example with a predicate: + +```ruby +# Returns true if the collection is empty. +# +# If the collection has been loaded +# it is equivalent to collection.size.zero?. If the +# collection has not been loaded, it is equivalent to +# collection.exists?. If the collection has not already been +# loaded and you are going to fetch the records anyway it is better to +# check collection.length.zero?. +def empty? + if loaded? + size.zero? + else + @target.blank? && !scope.exists? + end +end +``` + +The API is careful not to commit to any particular value, the predicate has +predicate semantics, that's enough. + Filenames --------- -- cgit v1.2.3 From 4cfc467594da86090efa63f1852fb82df9458c2b Mon Sep 17 00:00:00 2001 From: Parker Selbert Date: Tue, 16 Jul 2013 16:16:33 -0400 Subject: Customize subsecond digits when encoding DateWithTime The subsecond fraction digits had been hardcoded to 3. This forced all timestamps to include the subsecond digits with no way to customize the value. While the subsecond format is part of the ISO8601 spec, it is not adhered to by all parsers (notably mobile clients). This adds the ability to customize the number of digits used, optionally setting them to 0 in order to eliminate the subsecond fraction entirely: ActiveSupport::JSON::Encoding.subsecond_fraction_digits = 0 --- activesupport/lib/active_support/json/encoding.rb | 7 +++++- activesupport/lib/active_support/time_with_zone.rb | 3 ++- activesupport/test/core_ext/time_with_zone_test.rb | 29 ++++++++++++++++------ 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 2859075e10..23896316e2 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/module/delegation' module ActiveSupport class << self delegate :use_standard_json_time_format, :use_standard_json_time_format=, + :subsecond_fraction_digits, :subsecond_fraction_digits=, :escape_html_entities_in_json, :escape_html_entities_in_json=, :encode_big_decimal_as_string, :encode_big_decimal_as_string=, :json_encoder, :json_encoder=, @@ -60,7 +61,7 @@ module ActiveSupport end # Mark these as private so we don't leak encoding-specific constructs - private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES, + private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES, :ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString # Convert an object into a "JSON-ready" representation composed of @@ -105,6 +106,10 @@ module ActiveSupport # as a safety measure. attr_accessor :escape_html_entities_in_json + # Configures the inclusion of subsecond resolution when serializing instances + # of ActiveSupport::TimeWithZone. + attr_accessor :subsecond_fraction_digits + # Sets the encoder used by Rails to encode Ruby objects into JSON strings # in +Object#to_json+ and +ActiveSupport::JSON.encode+. attr_accessor :json_encoder diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index d47aee00f4..26d4e686fd 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -154,7 +154,8 @@ module ActiveSupport # # => "2005/02/01 05:15:10 -1000" def as_json(options = nil) if ActiveSupport::JSON::Encoding.use_standard_json_time_format - xmlschema(3) + digits = ActiveSupport::JSON::Encoding.subsecond_fraction_digits || 3 + xmlschema(digits) else %(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 43fe572bfc..4ea8d2bed6 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -67,17 +67,25 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_to_json_with_use_standard_json_time_format_config_set_to_false - old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, false - assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(@twz) - ensure - ActiveSupport.use_standard_json_time_format = old + with_standard_json_time_format(false) do + assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(@twz) + end end def test_to_json_with_use_standard_json_time_format_config_set_to_true - old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, true - assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(@twz) + with_standard_json_time_format(true) do + assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(@twz) + end + end + + def test_to_json_with_custom_subsecond_resolution + with_standard_json_time_format(true) do + ActiveSupport::JSON::Encoding.subsecond_fraction_digits = 0 + + assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(@twz) + end ensure - ActiveSupport.use_standard_json_time_format = old + ActiveSupport::JSON::Encoding.subsecond_fraction_digits = nil end def test_to_json_when_wrapping_a_date_time @@ -814,6 +822,13 @@ class TimeWithZoneTest < ActiveSupport::TestCase ensure old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end + + def with_standard_json_time_format(boolean = true) + old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean + yield + ensure + ActiveSupport.use_standard_json_time_format = old + end end class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase -- cgit v1.2.3 From ef17225173131541f0f5495ea31fc51ea46cb236 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Sun, 26 Jan 2014 20:32:34 +0000 Subject: Consolidate JSON encoding tests in one file --- activesupport/test/core_ext/time_with_zone_test.rb | 35 ------------- activesupport/test/json/encoding_test.rb | 59 ++++++++++++++++++---- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 4ea8d2bed6..e9bf43667a 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -1,6 +1,5 @@ require 'abstract_unit' require 'active_support/time' -require 'active_support/json' class TimeWithZoneTest < ActiveSupport::TestCase @@ -66,33 +65,6 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal 'EDT', ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).zone #dst end - def test_to_json_with_use_standard_json_time_format_config_set_to_false - with_standard_json_time_format(false) do - assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(@twz) - end - end - - def test_to_json_with_use_standard_json_time_format_config_set_to_true - with_standard_json_time_format(true) do - assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(@twz) - end - end - - def test_to_json_with_custom_subsecond_resolution - with_standard_json_time_format(true) do - ActiveSupport::JSON::Encoding.subsecond_fraction_digits = 0 - - assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(@twz) - end - ensure - ActiveSupport::JSON::Encoding.subsecond_fraction_digits = nil - end - - def test_to_json_when_wrapping_a_date_time - twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone) - assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(twz) - end - def test_nsec local = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)) with_zone = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], local) @@ -822,13 +794,6 @@ class TimeWithZoneTest < ActiveSupport::TestCase ensure old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end - - def with_standard_json_time_format(boolean = true) - old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean - yield - ensure - ActiveSupport.use_standard_json_time_format = old - end end class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 78cf4819f9..ef76d63f29 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -3,6 +3,7 @@ require 'securerandom' require 'abstract_unit' require 'active_support/core_ext/string/inflections' require 'active_support/json' +require 'active_support/time' class TestJSONEncoding < ActiveSupport::TestCase class Foo @@ -226,21 +227,17 @@ class TestJSONEncoding < ActiveSupport::TestCase end def test_time_to_json_includes_local_offset - prev = ActiveSupport.use_standard_json_time_format - ActiveSupport.use_standard_json_time_format = true - with_env_tz 'US/Eastern' do - assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10)) + with_standard_json_time_format(true) do + with_env_tz 'US/Eastern' do + assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10)) + end end - ensure - ActiveSupport.use_standard_json_time_format = prev end def test_hash_with_time_to_json - prev = ActiveSupport.use_standard_json_time_format - ActiveSupport.use_standard_json_time_format = false - assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json - ensure - ActiveSupport.use_standard_json_time_format = prev + with_standard_json_time_format(false) do + assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json + end end def test_nested_hash_with_float @@ -453,6 +450,39 @@ EXPECTED assert_nil h.as_json_called end + def test_twz_to_json_with_use_standard_json_time_format_config_set_to_false + with_standard_json_time_format(false) do + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone) + assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(time) + end + end + + def test_twz_to_json_with_use_standard_json_time_format_config_set_to_true + with_standard_json_time_format(true) do + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone) + assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(time) + end + end + + def test_twz_to_json_with_custom_subsecond_resolution + with_standard_json_time_format(true) do + ActiveSupport::JSON::Encoding.subsecond_fraction_digits = 0 + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone) + assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time) + end + ensure + ActiveSupport::JSON::Encoding.subsecond_fraction_digits = nil + end + + def test_twz_to_json_when_wrapping_a_date_time + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + time = ActiveSupport::TimeWithZone.new(DateTime.new(2000), zone) + assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time) + end + protected def object_keys(json_object) @@ -465,4 +495,11 @@ EXPECTED ensure old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end + + def with_standard_json_time_format(boolean = true) + old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean + yield + ensure + ActiveSupport.use_standard_json_time_format = old + end end -- cgit v1.2.3 From e3c382e3d69e7f25593cf45a4acc1b74bb93d057 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Sun, 26 Jan 2014 20:39:16 +0000 Subject: Rename subsecond_fraction_digits option to time_precision --- activesupport/lib/active_support/json/encoding.rb | 8 ++++---- activesupport/lib/active_support/time_with_zone.rb | 2 +- activesupport/test/json/encoding_test.rb | 6 +++--- guides/source/configuring.md | 2 ++ 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 23896316e2..bafd629848 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -4,7 +4,7 @@ require 'active_support/core_ext/module/delegation' module ActiveSupport class << self delegate :use_standard_json_time_format, :use_standard_json_time_format=, - :subsecond_fraction_digits, :subsecond_fraction_digits=, + :time_precision, :time_precision=, :escape_html_entities_in_json, :escape_html_entities_in_json=, :encode_big_decimal_as_string, :encode_big_decimal_as_string=, :json_encoder, :json_encoder=, @@ -106,9 +106,9 @@ module ActiveSupport # as a safety measure. attr_accessor :escape_html_entities_in_json - # Configures the inclusion of subsecond resolution when serializing instances - # of ActiveSupport::TimeWithZone. - attr_accessor :subsecond_fraction_digits + # Sets the precision of encoded time values. + # Defaults to 3 (equivalent to millisecond precision) + attr_accessor :time_precision # Sets the encoder used by Rails to encode Ruby objects into JSON strings # in +Object#to_json+ and +ActiveSupport::JSON.encode+. diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 26d4e686fd..1ed18fa473 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -154,7 +154,7 @@ module ActiveSupport # # => "2005/02/01 05:15:10 -1000" def as_json(options = nil) if ActiveSupport::JSON::Encoding.use_standard_json_time_format - digits = ActiveSupport::JSON::Encoding.subsecond_fraction_digits || 3 + digits = ActiveSupport::JSON::Encoding.time_precision || 3 xmlschema(digits) else %(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index ef76d63f29..52d2708d1e 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -466,15 +466,15 @@ EXPECTED end end - def test_twz_to_json_with_custom_subsecond_resolution + def test_twz_to_json_with_custom_time_precision with_standard_json_time_format(true) do - ActiveSupport::JSON::Encoding.subsecond_fraction_digits = 0 + ActiveSupport::JSON::Encoding.time_precision = 0 zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone) assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time) end ensure - ActiveSupport::JSON::Encoding.subsecond_fraction_digits = nil + ActiveSupport::JSON::Encoding.time_precision = nil end def test_twz_to_json_when_wrapping_a_date_time diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 4afd0600c4..693991304b 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -441,6 +441,8 @@ There are a few configuration options available in Active Support: * `config.active_support.use_standard_json_time_format` enables or disables serializing dates to ISO 8601 format. Defaults to `true`. +* `config.active_support.time_precision` sets the precision of JSON encoded time values. Defaults to `3`. + * `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`. * `ActiveSupport::Cache::Store.logger` specifies the logger to use within cache store operations. -- cgit v1.2.3 From 9484f4b821d90136b106cb7e0e3ebaad31493ee2 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Sun, 26 Jan 2014 20:55:21 +0000 Subject: Add CHANGELOG entry for #11464 --- activesupport/CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index ba450b85c4..95bf5601f2 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,12 @@ +* Add `ActiveSupport::JSON::Encoding.time_precision` as a way to configure the + precision of encoded time values: + + Time.utc(2000, 1, 1).as_json # => "2000-01-01T00:00:00.000Z" + ActiveSupport::JSON::Encoding.time_precision = 0 + Time.utc(2000, 1, 1).as_json # => "2000-01-01T00:00:00Z" + + *Parker Selbert* + * Maintain the current timezone when calling `change` during DST overlap Currently if a time is changed during DST overlap in the autumn then the method -- cgit v1.2.3 From c0965004486f2ea5a9656ba718a3377c9614f97d Mon Sep 17 00:00:00 2001 From: Andrew White Date: Sun, 26 Jan 2014 21:09:06 +0000 Subject: Add support for JSON time_precision to Time and DateTime --- .../lib/active_support/core_ext/object/json.rb | 8 ++++---- activesupport/lib/active_support/json/encoding.rb | 1 + activesupport/lib/active_support/time_with_zone.rb | 3 +-- activesupport/test/json/encoding_test.rb | 20 +++++++++++++++++++- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb index 1675145ffe..8e08cfbf26 100644 --- a/activesupport/lib/active_support/core_ext/object/json.rb +++ b/activesupport/lib/active_support/core_ext/object/json.rb @@ -16,12 +16,12 @@ require 'active_support/core_ext/module/aliasing' # otherwise they will always use to_json gem implementation, which is backwards incompatible in # several cases (for instance, the JSON implementation for Hash does not work) with inheritance # and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json. -# +# # On the other hand, we should avoid conflict with ::JSON.{generate,dump}(obj). Unfortunately, the # JSON gem's encoder relies on its own to_json implementation to encode objects. Since it always # passes a ::JSON::State object as the only argument to to_json, we can detect that and forward the # calls to the original to_json method. -# +# # It should be noted that when using ::JSON.{generate,dump} directly, ActiveSupport's encoder is # bypassed completely. This means that as_json won't be invoked and the JSON gem will simply # ignore any options it does not natively understand. This also means that ::JSON.{generate,dump} @@ -163,7 +163,7 @@ end class Time def as_json(options = nil) #:nodoc: if ActiveSupport.use_standard_json_time_format - xmlschema(3) + xmlschema(ActiveSupport::JSON::Encoding.time_precision) else %(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) end @@ -183,7 +183,7 @@ end class DateTime def as_json(options = nil) #:nodoc: if ActiveSupport.use_standard_json_time_format - xmlschema(3) + xmlschema(ActiveSupport::JSON::Encoding.time_precision) else strftime('%Y/%m/%d %H:%M:%S %z') end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index bafd629848..f29d42276d 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -166,6 +166,7 @@ module ActiveSupport self.use_standard_json_time_format = true self.escape_html_entities_in_json = true self.json_encoder = JSONGemEncoder + self.time_precision = 3 end end end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 1ed18fa473..626438c9e4 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -154,8 +154,7 @@ module ActiveSupport # # => "2005/02/01 05:15:10 -1000" def as_json(options = nil) if ActiveSupport::JSON::Encoding.use_standard_json_time_format - digits = ActiveSupport::JSON::Encoding.time_precision || 3 - xmlschema(digits) + xmlschema(ActiveSupport::JSON::Encoding.time_precision) else %(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) end diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 52d2708d1e..c4283ee79a 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -474,7 +474,25 @@ EXPECTED assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time) end ensure - ActiveSupport::JSON::Encoding.time_precision = nil + ActiveSupport::JSON::Encoding.time_precision = 3 + end + + def test_time_to_json_with_custom_time_precision + with_standard_json_time_format(true) do + ActiveSupport::JSON::Encoding.time_precision = 0 + assert_equal "\"2000-01-01T00:00:00Z\"", ActiveSupport::JSON.encode(Time.utc(2000)) + end + ensure + ActiveSupport::JSON::Encoding.time_precision = 3 + end + + def test_datetime_to_json_with_custom_time_precision + with_standard_json_time_format(true) do + ActiveSupport::JSON::Encoding.time_precision = 0 + assert_equal "\"2000-01-01T00:00:00+00:00\"", ActiveSupport::JSON.encode(DateTime.new(2000)) + end + ensure + ActiveSupport::JSON::Encoding.time_precision = 3 end def test_twz_to_json_when_wrapping_a_date_time -- cgit v1.2.3 From dd339bb0adc3bb1f0c376c4352f769ae2ab02b62 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Sun, 26 Jan 2014 21:22:23 +0000 Subject: Make ActiveSupport::TimeWithZone#xmlschema consistent Both Time#xmlschema and DateTime#xmlschema can accept nil values for the fraction_digits parameter. This commit makes this so for TimeWithZone values as well. --- activesupport/lib/active_support/time_with_zone.rb | 4 ++-- activesupport/test/core_ext/time_with_zone_test.rb | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 626438c9e4..d459af1778 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -132,8 +132,8 @@ module ActiveSupport end def xmlschema(fraction_digits = 0) - fraction = if fraction_digits > 0 - (".%06i" % time.usec)[0, fraction_digits + 1] + fraction = if fraction_digits.to_i > 0 + (".%06i" % time.usec)[0, fraction_digits.to_i + 1] end "#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}" diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index e9bf43667a..8e25f1e2f2 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -111,6 +111,10 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal "1999-12-31T19:00:00.001234-05:00", @twz.xmlschema(12) end + def test_xmlschema_with_nil_fractional_seconds + assert_equal "1999-12-31T19:00:00-05:00", @twz.xmlschema(nil) + end + def test_to_yaml assert_match(/^--- 2000-01-01 00:00:00(\.0+)?\s*Z\n/, @twz.to_yaml) end -- cgit v1.2.3 From 8b14b114348c1bf8a88689028e25240d457dff56 Mon Sep 17 00:00:00 2001 From: Washington Luiz Date: Mon, 27 Jan 2014 02:18:56 -0300 Subject: Display value when raising due to unscope() issues Hopefully make it easier to debug errors. e.g Before: RuntimeError: unscope(where: "deleted_at") failed: unscoping String is unimplemented. After: RuntimeError: unscope(where: "deleted_at") failed: unscoping String "'t'='t'" is unimplemented. --- activerecord/lib/active_record/relation/query_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index d392f759bd..993f628fa3 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -877,7 +877,7 @@ module ActiveRecord subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right) subrelation.name == target_value else - raise "unscope(where: #{target_value.inspect}) failed: unscoping #{rel.class} is unimplemented." + raise "unscope(where: #{target_value.inspect}) failed: unscoping #{rel.class} \"#{rel}\" is unimplemented." end end -- cgit v1.2.3 From 9690bcf503e4d7bc3c2fe392c623b790c0392d5e Mon Sep 17 00:00:00 2001 From: Francis Go Date: Mon, 27 Jan 2014 17:10:21 +1100 Subject: Ruby on Rails 4.1 Release Notes: Fix spelling [ci skip] --- guides/source/4_1_release_notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 1980f4d4cf..8894011580 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -585,7 +585,7 @@ for detailed changes. [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling)) * Deprecated `ActiveSupport.encode_big_decimal_as_string` option. This feature has - been extracetd into the [activesupport-json_encoder](https://github.com/rails/activesupport-json_encoder) + been extracted into the [activesupport-json_encoder](https://github.com/rails/activesupport-json_encoder) gem. ([Pull Request](https://github.com/rails/rails/pull/13060) / [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling)) -- cgit v1.2.3 From 5540dfccd88c809c2922f21f4c1558465bce26dd Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 27 Jan 2014 08:05:13 +0100 Subject: docs, clarify usage of `action_mailer.default_options`. [ci skip]. Closes #13820. --- guides/source/configuring.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 693991304b..387778316a 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -413,10 +413,18 @@ There are a number of settings available on `config.action_mailer`: * `config.action_mailer.default_options` configures Action Mailer defaults. Use to set options like `from` or `reply_to` for every mailer. These default to: ```ruby - :mime_version => "1.0", - :charset => "UTF-8", - :content_type => "text/plain", - :parts_order => [ "text/plain", "text/enriched", "text/html" ] + mime_version: "1.0", + charset: "UTF-8", + content_type: "text/plain", + parts_order: ["text/plain", "text/enriched", "text/html"] + ``` + + Assign a hash to set additional options: + + ```ruby + config.action_mailer.default_options = { + from: "noreply@example.com" + } ``` * `config.action_mailer.observers` registers observers which will be notified when mail is delivered. -- cgit v1.2.3 From 8855163bdbce23fa539be2c1ef2a49f1aabc6458 Mon Sep 17 00:00:00 2001 From: Vince Puzzella Date: Sat, 18 Jan 2014 11:34:38 -0500 Subject: Ability to specify multiple contexts when defining a validation. Example: validates_presence_of :name, on: [:update, :custom_validation_context] --- activemodel/CHANGELOG.md | 20 ++++++++++++++++++++ activemodel/lib/active_model/validations.rb | 14 +++++++++----- .../cases/validations/validations_context_test.rb | 16 ++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 09fdd84844..0148066bac 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,23 @@ +* Ability to specify multiple contexts when defining a validation. + + Example: + + class Person + include ActiveModel::Validations + + attr_reader :name + validates_presence_of :name, on: [:verify, :approve] + end + + person = Person.new + person.valid? # => true + person.valid?(:verify) # => false + person.errors.full_messages_for(:name) # => ["Name can't be blank"] + person.valid?(:approve) # => false + person.errors.full_messages_for(:name) # => ["Name can't be blank"] + + *Vince Puzzella* + * `attribute_changed?` now accepts parameters which check the old and new value of the attribute `model.name_changed?(from: "Pete", to: "Ringo")` diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 08928a713d..8e76edf945 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -66,8 +66,10 @@ module ActiveModel # end # # Options: - # * :on - Specifies the context where this validation is active - # (e.g. on: :create or on: :custom_validation_context) + # * :on - Specifies the contexts where this validation is active. + # You can pass a symbol or an array of symbols. + # (e.g. on: :create or on: :custom_validation_context or + # on: [:create, :custom_validation_context]) # * :allow_nil - Skip validation if attribute is +nil+. # * :allow_blank - Skip validation if attribute is blank. # * :if - Specifies a method, proc or string to call to determine @@ -124,8 +126,10 @@ module ActiveModel # end # # Options: - # * :on - Specifies the context where this validation is active - # (e.g. on: :create or on: :custom_validation_context) + # * :on - Specifies the contexts where this validation is active. + # You can pass a symbol or an array of symbols. + # (e.g. on: :create or on: :custom_validation_context or + # on: [:create, :custom_validation_context]) # * :if - Specifies a method, proc or string to call to determine # if the validation should occur (e.g. if: :allow_validation, # or if: Proc.new { |user| user.signup_step > 2 }). The method, @@ -141,7 +145,7 @@ module ActiveModel options = options.dup options[:if] = Array(options[:if]) options[:if].unshift lambda { |o| - o.validation_context == options[:on] + Array(options[:on]).include?(o.validation_context) } end args << options diff --git a/activemodel/test/cases/validations/validations_context_test.rb b/activemodel/test/cases/validations/validations_context_test.rb index 5f99b320a6..a3daace4a8 100644 --- a/activemodel/test/cases/validations/validations_context_test.rb +++ b/activemodel/test/cases/validations/validations_context_test.rb @@ -36,4 +36,20 @@ class ValidationsContextTest < ActiveModel::TestCase assert topic.invalid?(:create), "Validation does run on create if 'on' is set to create" assert topic.errors[:base].include?(ERROR_MESSAGE) end + + test "with a class that adds errors on multiple contexts and validating a new model with no arguments" do + Topic.validates_with(ValidatorThatAddsErrors, on: [:context1, :context2]) + topic = Topic.new + assert topic.valid?, "Validation doesn't run when 'on' is set to context1 and context2" + end + + test "with a class that adds errors on multiple contexts and validating a new model" do + Topic.validates_with(ValidatorThatAddsErrors, on: [:context1, :context2]) + topic = Topic.new + assert topic.invalid?(:context1), "Validation does run on context1 when 'on' is set to context1 and context2" + assert topic.errors[:base].include?(ERROR_MESSAGE) + topic = Topic.new + assert topic.invalid?(:context2), "Validation does run on context2 when 'on' is set to context1 and context2" + assert topic.errors[:base].include?(ERROR_MESSAGE) + end end -- cgit v1.2.3 From 433628a45c2f5dd04b115af1b5579dac75255c67 Mon Sep 17 00:00:00 2001 From: Kassio Borges Date: Sun, 26 Jan 2014 20:05:34 -0200 Subject: Rails config for raise on missing translations Add a config to setup whether raise exception for missing translation or not. --- actionview/CHANGELOG.md | 7 +++++++ actionview/lib/action_view/base.rb | 4 ++++ actionview/lib/action_view/helpers/translation_helper.rb | 8 ++++---- actionview/test/template/translation_helper_test.rb | 10 ++++++++++ guides/source/configuring.md | 2 ++ .../rails/app/templates/config/environments/development.rb.tt | 3 +++ .../rails/app/templates/config/environments/test.rb.tt | 3 +++ 7 files changed, 33 insertions(+), 4 deletions(-) diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 19877ca8cb..960f867d99 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,10 @@ +* Added `config.action_view.raise_on_missing_translations` to define whether an + error should be raised for missing translations. + + Fixes #13196 + + *Kassio Borges* + * Improved ERB dependency detection. New argument types and formattings for the `render` calls can be matched. diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb index 8eb7072d0c..455ce531ae 100644 --- a/actionview/lib/action_view/base.rb +++ b/actionview/lib/action_view/base.rb @@ -153,6 +153,10 @@ module ActionView #:nodoc: # Specify default_formats that can be rendered. cattr_accessor :default_formats + # Specify whether an error should be raised for missing translations + cattr_accessor :raise_on_missing_translations + @@raise_on_missing_translations = false + class_attribute :_routes class_attribute :logger diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index 3ae1df04fe..0bc40874d9 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -38,10 +38,10 @@ module ActionView # If the user has specified rescue_format then pass it all through, otherwise use # raise and do the work ourselves - if options.key?(:raise) || options.key?(:rescue_format) - raise_error = options[:raise] || options[:rescue_format] - else - raise_error = false + options[:raise] ||= ActionView::Base.raise_on_missing_translations + + raise_error = options[:raise] || options.key?(:rescue_format) + unless raise_error options[:raise] = true end diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb index 269714fad0..c4770840fb 100644 --- a/actionview/test/template/translation_helper_test.rb +++ b/actionview/test/template/translation_helper_test.rb @@ -53,6 +53,16 @@ class TranslationHelperTest < ActiveSupport::TestCase assert_equal false, translate(:"translations.missing", :rescue_format => nil).html_safe? end + def test_raises_missing_translation_message_with_raise_config_option + ActionView::Base.raise_on_missing_translations = true + + assert_raise(I18n::MissingTranslationData) do + translate("translations.missing") + end + ensure + ActionView::Base.raise_on_missing_translations = false + end + def test_raises_missing_translation_message_with_raise_option assert_raise(I18n::MissingTranslationData) do translate(:"translations.missing", :raise => true) diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 387778316a..38f7162fcf 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -386,6 +386,8 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. The default setting is `true`, which uses the partial at `/admin/posts/_post.erb`. Setting the value to `false` would render `/posts/_post.erb`, which is the same behavior as rendering from a non-namespaced controller such as `PostsController`. +* `config.action_view.raise_on_missing_translations` determines whether an error should be raised for missing translations + ### Configuring Action Mailer There are a number of settings available on `config.action_mailer`: diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index cce4743a33..de12565a73 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -35,4 +35,7 @@ Rails.application.configure do # Raises helpful error messages. config.assets.raise_runtime_errors = true <%- end -%> + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index a90361725b..053f5b66d7 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -33,4 +33,7 @@ Rails.application.configure do # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true end -- cgit v1.2.3 From abe648452f04be52ee3f95031f0ea01f98fdcf40 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 27 Jan 2014 11:23:10 +0100 Subject: mention #13314 in 4.1 release notes. refs #12140. [ci skip] --- guides/source/4_1_release_notes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 8894011580..2426046335 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -502,6 +502,9 @@ for detailed changes. * Don't create or drop the test database if RAILS_ENV is specified explicitly. +* `Relation` no longer has mutator methods like `#map!` and `#delete_if`. Convert + to an `Array` by calling `#to_a` before using these methods. ([Pull Request](https://github.com/rails/rails/pull/13314)) + Active Model ------------ -- cgit v1.2.3 From 5336ce265a75d3e472bf801e04cac48ee16d9a76 Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Mon, 27 Jan 2014 08:17:20 -0200 Subject: Merge tests about multiple validation contexts --- .../test/cases/validations/validations_context_test.rb | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/activemodel/test/cases/validations/validations_context_test.rb b/activemodel/test/cases/validations/validations_context_test.rb index a3daace4a8..9ad20e8453 100644 --- a/activemodel/test/cases/validations/validations_context_test.rb +++ b/activemodel/test/cases/validations/validations_context_test.rb @@ -4,7 +4,6 @@ require 'cases/helper' require 'models/topic' class ValidationsContextTest < ActiveModel::TestCase - def teardown Topic.reset_callbacks(:validate) Topic._validators.clear @@ -37,19 +36,16 @@ class ValidationsContextTest < ActiveModel::TestCase assert topic.errors[:base].include?(ERROR_MESSAGE) end - test "with a class that adds errors on multiple contexts and validating a new model with no arguments" do - Topic.validates_with(ValidatorThatAddsErrors, on: [:context1, :context2]) - topic = Topic.new - assert topic.valid?, "Validation doesn't run when 'on' is set to context1 and context2" - end - test "with a class that adds errors on multiple contexts and validating a new model" do Topic.validates_with(ValidatorThatAddsErrors, on: [:context1, :context2]) + topic = Topic.new - assert topic.invalid?(:context1), "Validation does run on context1 when 'on' is set to context1 and context2" + assert topic.valid?, "Validation ran with no context given when 'on' is set to context1 and context2" + + assert topic.invalid?(:context1), "Validation did not run on context1 when 'on' is set to context1 and context2" assert topic.errors[:base].include?(ERROR_MESSAGE) - topic = Topic.new - assert topic.invalid?(:context2), "Validation does run on context2 when 'on' is set to context1 and context2" + + assert topic.invalid?(:context2), "Validation did not run on context2 when 'on' is set to context1 and context2" assert topic.errors[:base].include?(ERROR_MESSAGE) end end -- cgit v1.2.3 From 801baeed69fb5b28d9a05b657addb8f4204794ed Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Mon, 27 Jan 2014 08:22:26 -0200 Subject: Use the new clear_validators! api to reset validators in tests --- activemodel/test/cases/validations/absence_validation_test.rb | 6 +++--- activemodel/test/cases/validations/acceptance_validation_test.rb | 2 +- .../test/cases/validations/conditional_validation_test.rb | 2 +- .../test/cases/validations/confirmation_validation_test.rb | 4 ++-- activemodel/test/cases/validations/exclusion_validation_test.rb | 6 +++--- activemodel/test/cases/validations/format_validation_test.rb | 8 ++++---- .../cases/validations/i18n_generate_message_validation_test.rb | 2 +- activemodel/test/cases/validations/i18n_validation_test.rb | 4 ++-- activemodel/test/cases/validations/inclusion_validation_test.rb | 6 +++--- activemodel/test/cases/validations/length_validation_test.rb | 4 ++-- .../test/cases/validations/numericality_validation_test.rb | 4 ++-- activemodel/test/cases/validations/presence_validation_test.rb | 6 +++--- activemodel/test/cases/validations/validates_test.rb | 6 +++--- activemodel/test/cases/validations/validations_context_test.rb | 3 +-- activemodel/test/cases/validations/with_validation_test.rb | 3 +-- activemodel/test/cases/validations_test.rb | 9 +-------- 16 files changed, 33 insertions(+), 42 deletions(-) diff --git a/activemodel/test/cases/validations/absence_validation_test.rb b/activemodel/test/cases/validations/absence_validation_test.rb index c05d71de5a..795ce16d28 100644 --- a/activemodel/test/cases/validations/absence_validation_test.rb +++ b/activemodel/test/cases/validations/absence_validation_test.rb @@ -6,9 +6,9 @@ require 'models/custom_reader' class AbsenceValidationTest < ActiveModel::TestCase teardown do - Topic.reset_callbacks(:validate) - Person.reset_callbacks(:validate) - CustomReader.reset_callbacks(:validate) + Topic.clear_validators! + Person.clear_validators! + CustomReader.clear_validators! end def test_validate_absences diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb index dc413bef30..d9d8c6b0f8 100644 --- a/activemodel/test/cases/validations/acceptance_validation_test.rb +++ b/activemodel/test/cases/validations/acceptance_validation_test.rb @@ -8,7 +8,7 @@ require 'models/person' class AcceptanceValidationTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_terms_of_service_agreement_no_acceptance diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb index 5049d6dd61..1261937b56 100644 --- a/activemodel/test/cases/validations/conditional_validation_test.rb +++ b/activemodel/test/cases/validations/conditional_validation_test.rb @@ -6,7 +6,7 @@ require 'models/topic' class ConditionalValidationTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_if_validation_using_method_true diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb index f03de2c24a..4957ba5d0a 100644 --- a/activemodel/test/cases/validations/confirmation_validation_test.rb +++ b/activemodel/test/cases/validations/confirmation_validation_test.rb @@ -7,7 +7,7 @@ require 'models/person' class ConfirmationValidationTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_no_title_confirmation @@ -49,7 +49,7 @@ class ConfirmationValidationTest < ActiveModel::TestCase p.karma = "None" assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end def test_title_confirmation_with_i18n_attribute diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb index 81455ba519..1ce41f9bc9 100644 --- a/activemodel/test/cases/validations/exclusion_validation_test.rb +++ b/activemodel/test/cases/validations/exclusion_validation_test.rb @@ -7,7 +7,7 @@ require 'models/person' class ExclusionValidationTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_validates_exclusion_of @@ -50,7 +50,7 @@ class ExclusionValidationTest < ActiveModel::TestCase p.karma = "Lifo" assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end def test_validates_exclusion_of_with_lambda @@ -87,6 +87,6 @@ class ExclusionValidationTest < ActiveModel::TestCase assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end end diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb index 26e8dbf19c..0f91b73cd7 100644 --- a/activemodel/test/cases/validations/format_validation_test.rb +++ b/activemodel/test/cases/validations/format_validation_test.rb @@ -7,7 +7,7 @@ require 'models/person' class PresenceValidationTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_validate_format @@ -68,11 +68,11 @@ class PresenceValidationTest < ActiveModel::TestCase assert t.invalid? assert_equal ["can't be Invalid title"], t.errors[:title] end - + def test_validate_format_of_with_multiline_regexp_should_raise_error assert_raise(ArgumentError) { Topic.validates_format_of(:title, with: /^Valid Title$/) } end - + def test_validate_format_of_with_multiline_regexp_and_option assert_nothing_raised(ArgumentError) do Topic.validates_format_of(:title, with: /^Valid Title$/, multiline: true) @@ -144,6 +144,6 @@ class PresenceValidationTest < ActiveModel::TestCase p.karma = "1234" assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end end diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb index 40a5aee997..93600c587a 100644 --- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb @@ -4,7 +4,7 @@ require 'models/person' class I18nGenerateMessageValidationTest < ActiveModel::TestCase def setup - Person.reset_callbacks(:validate) + Person.clear_validators! @person = Person.new end diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb index e29771d6b7..d10010537e 100644 --- a/activemodel/test/cases/validations/i18n_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_validation_test.rb @@ -6,7 +6,7 @@ require 'models/person' class I18nValidationTest < ActiveModel::TestCase def setup - Person.reset_callbacks(:validate) + Person.clear_validators! @person = Person.new @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend @@ -16,7 +16,7 @@ class I18nValidationTest < ActiveModel::TestCase end def teardown - Person.reset_callbacks(:validate) + Person.clear_validators! I18n.load_path.replace @old_load_path I18n.backend = @old_backend end diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb index 8b90856869..3a8f3080e1 100644 --- a/activemodel/test/cases/validations/inclusion_validation_test.rb +++ b/activemodel/test/cases/validations/inclusion_validation_test.rb @@ -8,7 +8,7 @@ require 'models/person' class InclusionValidationTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_validates_inclusion_of_range @@ -105,7 +105,7 @@ class InclusionValidationTest < ActiveModel::TestCase p.karma = "monkey" assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end def test_validates_inclusion_of_with_lambda @@ -142,6 +142,6 @@ class InclusionValidationTest < ActiveModel::TestCase assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end end diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb index 8b2f886cc4..046ffcb16f 100644 --- a/activemodel/test/cases/validations/length_validation_test.rb +++ b/activemodel/test/cases/validations/length_validation_test.rb @@ -6,7 +6,7 @@ require 'models/person' class LengthValidationTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_validates_length_of_with_allow_nil @@ -354,7 +354,7 @@ class LengthValidationTest < ActiveModel::TestCase p.karma = "The Smiths" assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end def test_validates_length_of_for_infinite_maxima diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb index 84332ed014..f77cf47fb7 100644 --- a/activemodel/test/cases/validations/numericality_validation_test.rb +++ b/activemodel/test/cases/validations/numericality_validation_test.rb @@ -9,7 +9,7 @@ require 'bigdecimal' class NumericalityValidationTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end NIL = [nil] @@ -157,7 +157,7 @@ class NumericalityValidationTest < ActiveModel::TestCase p.karma = "1234" assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end def test_validates_numericality_with_invalid_args diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb index 2f228cfa83..ecf16d1e16 100644 --- a/activemodel/test/cases/validations/presence_validation_test.rb +++ b/activemodel/test/cases/validations/presence_validation_test.rb @@ -8,9 +8,9 @@ require 'models/custom_reader' class PresenceValidationTest < ActiveModel::TestCase teardown do - Topic.reset_callbacks(:validate) - Person.reset_callbacks(:validate) - CustomReader.reset_callbacks(:validate) + Topic.clear_validators! + Person.clear_validators! + CustomReader.clear_validators! end def test_validate_presences diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb index c1914b32bc..699a872e42 100644 --- a/activemodel/test/cases/validations/validates_test.rb +++ b/activemodel/test/cases/validations/validates_test.rb @@ -11,9 +11,9 @@ class ValidatesTest < ActiveModel::TestCase teardown :reset_callbacks def reset_callbacks - Person.reset_callbacks(:validate) - Topic.reset_callbacks(:validate) - PersonWithValidator.reset_callbacks(:validate) + Person.clear_validators! + Topic.clear_validators! + PersonWithValidator.clear_validators! end def test_validates_with_messages_empty diff --git a/activemodel/test/cases/validations/validations_context_test.rb b/activemodel/test/cases/validations/validations_context_test.rb index 9ad20e8453..005bf118c6 100644 --- a/activemodel/test/cases/validations/validations_context_test.rb +++ b/activemodel/test/cases/validations/validations_context_test.rb @@ -5,8 +5,7 @@ require 'models/topic' class ValidationsContextTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) - Topic._validators.clear + Topic.clear_validators! end ERROR_MESSAGE = "Validation error from validator" diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb index 93716f1433..736c2deea8 100644 --- a/activemodel/test/cases/validations/with_validation_test.rb +++ b/activemodel/test/cases/validations/with_validation_test.rb @@ -6,8 +6,7 @@ require 'models/topic' class ValidatesWithTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) - Topic._validators.clear + Topic.clear_validators! end ERROR_MESSAGE = "Validation error from validator" diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index 039b6b8872..bee8ece992 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -10,17 +10,10 @@ require 'active_support/json' require 'active_support/xml_mini' class ValidationsTest < ActiveModel::TestCase - class CustomStrictValidationException < StandardError; end - def setup - Topic._validators.clear - end - - # Most of the tests mess with the validations of Topic, so lets repair it all the time. - # Other classes we mess with will be dealt with in the specific tests def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_single_field_validation -- cgit v1.2.3 From 26fb57b58dad6555222bb7270f20d9fb1ccb7534 Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Mon, 27 Jan 2014 08:24:33 -0200 Subject: Fix doc markup of clear_validators! --- activemodel/lib/active_model/validations.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 8e76edf945..e9674d5143 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -201,12 +201,12 @@ module ActiveModel # # # # # ] # - # If one runs Person.clear_validators! and then checks to see what + # If one runs Person.clear_validators! and then checks to see what # validators this class has, you would obtain: # # Person.validators # => [] # - # Also, the callback set by +validate :cannot_be_robot+ will be erased + # Also, the callback set by validate :cannot_be_robot will be erased # so that: # # Person._validate_callbacks.empty? # => true -- cgit v1.2.3 From 9673d5df2764f8a2f84fc739b480f3040926c0cd Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 27 Jan 2014 13:57:25 +0100 Subject: docs, remove deprecation entry in favor of abe6484. [ci skip] --- guides/source/4_1_release_notes.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 2426046335..477268f4bc 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -422,11 +422,6 @@ for detailed changes. * Deprecated `quoted_locking_column` method, which isn't used anywhere. -* Deprecated the delegation of Array bang methods for associations. - To use them, instead first call `#to_a` on the association to access the - array to be acted - on. ([Pull Request](https://github.com/rails/rails/pull/12129)) - * Deprecated `ConnectionAdapters::SchemaStatements#distinct`, as it is no longer used by internals. ([Pull Request](https://github.com/rails/rails/pull/10556)) -- cgit v1.2.3 From 31616068032beb537768787ff3a206b062eb192e Mon Sep 17 00:00:00 2001 From: Andrew White Date: Mon, 27 Jan 2014 09:08:56 +0000 Subject: Clear filtered request attributes between requests in tests The request attributes filtered_parameters, filtered_env and filtered_path are memoized for performance reasons. However this can cause unusual behavior in tests where there are multiple calls to get, post, etc. Fixes #13803. --- actionpack/CHANGELOG.md | 7 +++++++ actionpack/lib/action_controller/test_case.rb | 3 +++ actionpack/test/controller/log_subscriber_test.rb | 11 +++++++++++ actionpack/test/controller/test_case_test.rb | 8 ++++++++ 4 files changed, 29 insertions(+) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 9add810f81..23bb01d678 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,10 @@ +* Ensure that `request.filtered_parameters` is reset between calls to `process` + in `ActionController::TestCase`. + + Fixes #13803. + + *Andrew White* + * Fix `rake routes` error when `Rails::Engine` with empty routes is mounted. Fixes #13810. diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 5ed3d2ebc1..cf11ce1a9b 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -213,6 +213,9 @@ module ActionController # Clear the combined params hash in case it was already referenced. @env.delete("action_dispatch.request.parameters") + # Clear the filter cache variables so they're not stale + @filtered_parameters = @filtered_env = @filtered_path = nil + params = self.request_parameters.dup %w(controller action only_path).each do |k| params.delete(k) diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index 075347be52..18037b3d2f 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -137,6 +137,17 @@ class ACLogSubscriberTest < ActionController::TestCase assert_equal 'Parameters: {"id"=>"10"}', logs[1] end + def test_multiple_process_with_parameters + get :show, :id => '10' + get :show, :id => '20' + + wait + + assert_equal 6, logs.size + assert_equal 'Parameters: {"id"=>"10"}', logs[1] + assert_equal 'Parameters: {"id"=>"20"}', logs[4] + end + def test_process_action_with_wrapped_parameters @request.env['CONTENT_TYPE'] = 'application/json' post :show, :id => '10', :name => 'jose' diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index de0476dbde..5ff4a383ec 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -706,6 +706,14 @@ XML assert @request.params[:foo].blank? end + def test_filtered_parameters_reset_between_requests + get :no_op, :foo => "bar" + assert_equal "bar", @request.filtered_parameters[:foo] + + get :no_op, :foo => "baz" + assert_equal "baz", @request.filtered_parameters[:foo] + end + def test_symbolized_path_params_reset_after_request get :test_params, :id => "foo" assert_equal "foo", @request.symbolized_path_parameters[:id] -- cgit v1.2.3 From 20317f3d4df3ef0e876b5b213551f0d7d81d0c19 Mon Sep 17 00:00:00 2001 From: Kuldeep Aggarwal Date: Tue, 28 Jan 2014 00:13:35 +0530 Subject: use the new clear_validators! api everywhere to reset validators in tests --- activemodel/test/cases/validations/acceptance_validation_test.rb | 2 +- activerecord/test/cases/persistence_test.rb | 4 ++-- .../test/cases/validations/i18n_generate_message_validation_test.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb index d9d8c6b0f8..e78aa1adaf 100644 --- a/activemodel/test/cases/validations/acceptance_validation_test.rb +++ b/activemodel/test/cases/validations/acceptance_validation_test.rb @@ -63,6 +63,6 @@ class AcceptanceValidationTest < ActiveModel::TestCase p.karma = "1" assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 6f1e518f45..b9f0624f76 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -740,7 +740,7 @@ class PersistenceTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordInvalid) { reply.update!(title: nil, content: "Have a nice evening") } ensure - Reply.reset_callbacks(:validate) + Reply.clear_validators! end def test_update_attributes! @@ -761,7 +761,7 @@ class PersistenceTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(title: nil, content: "Have a nice evening") } ensure - Reply.reset_callbacks(:validate) + Reply.clear_validators! end def test_destroyed_returns_boolean diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb index a73c3bf1af..13d4d85afa 100644 --- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb @@ -3,7 +3,7 @@ require 'models/topic' class I18nGenerateMessageValidationTest < ActiveRecord::TestCase def setup - Topic.reset_callbacks(:validate) + Topic.clear_validators! @topic = Topic.new I18n.backend = I18n::Backend::Simple.new end -- cgit v1.2.3 From 7a372c84290d8e911ca594af90e79dbec4993ead Mon Sep 17 00:00:00 2001 From: Teo Ljungberg Date: Mon, 27 Jan 2014 22:21:28 +0100 Subject: Replace File.exists? with File.exist? To quell warnings on ruby 2.1 --- railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt index 3ea6c6d7d4..c3314d7e68 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt @@ -5,7 +5,7 @@ ENGINE_PATH = File.expand_path('../../lib/<%= name -%>/engine', __FILE__) # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) require 'rails/all' require 'rails/engine/commands' -- cgit v1.2.3 From 3858a247bdab01c62d99a4d1307311ee346aa76e Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Mon, 6 Jan 2014 12:01:15 +0100 Subject: Add CreateMigration action This Thor-action isolates the logic whether to (over-)write migration and what is shown to the user. It's modelled after Thor's CreateFile-action. This solves the issue that removing a non-existing migration, tried to remove the template-path (#13588). Related issues: #12674 --- railties/CHANGELOG.md | 6 + .../rails/generators/actions/create_migration.rb | 68 +++++++++++ railties/lib/rails/generators/migration.rb | 38 +++--- railties/test/generators/create_migration_test.rb | 134 +++++++++++++++++++++ 4 files changed, 230 insertions(+), 16 deletions(-) create mode 100644 railties/lib/rails/generators/actions/create_migration.rb create mode 100644 railties/test/generators/create_migration_test.rb diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 01c80d3f58..4ac6a99662 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,9 @@ +* Added Thor-action for creation of migrations. + + Fixes #13588 and #12674. + + *Gert Goet* + * Ensure that `bin/rails` is a file before trying to execute it. Fixes #13825. diff --git a/railties/lib/rails/generators/actions/create_migration.rb b/railties/lib/rails/generators/actions/create_migration.rb new file mode 100644 index 0000000000..9c3332927f --- /dev/null +++ b/railties/lib/rails/generators/actions/create_migration.rb @@ -0,0 +1,68 @@ +require 'thor/actions/create_file' + +module Rails + module Generators + module Actions + class CreateMigration < Thor::Actions::CreateFile + + def migration_dir + File.dirname(@destination) + end + + def migration_file_name + @base.migration_file_name + end + + def identical? + exists? && File.binread(existing_migration) == render + end + + def revoke! + say_destination = exists? ? relative_existing_migration : relative_destination + say_status :remove, :red, say_destination + return unless exists? + ::FileUtils.rm_rf(existing_migration) unless pretend? + existing_migration + end + + def relative_existing_migration + base.relative_to_original_destination_root(existing_migration) + end + + def existing_migration + @existing_migration ||= begin + @base.class.migration_exists?(migration_dir, migration_file_name) || + File.exist?(@destination) && @destination + end + end + alias :exists? :existing_migration + + protected + + def on_conflict_behavior(&block) + options = base.options.merge(config) + if identical? + say_status :identical, :blue, relative_existing_migration + elsif options[:force] + say_status :remove, :green, relative_existing_migration + say_status :create, :green + unless pretend? + ::FileUtils.rm_rf(existing_migration) + block.call + end + elsif options[:skip] + say_status :skip, :yellow + else + say_status :conflict, :red + raise Error, "Another migration is already named #{migration_file_name}: " + + "#{existing_migration}. Use --force to replace this migration file." + end + end + + def say_status(status, color, message = relative_destination) + base.shell.say_status(status, message, color) if config[:verbose] + end + end + end + end +end diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb index 3566f96f5e..cd388e590a 100644 --- a/railties/lib/rails/generators/migration.rb +++ b/railties/lib/rails/generators/migration.rb @@ -1,4 +1,5 @@ require 'active_support/concern' +require 'rails/generators/actions/create_migration' module Rails module Generators @@ -29,6 +30,19 @@ module Rails end end + def create_migration(destination, data, config = {}, &block) + action Rails::Generators::Actions::CreateMigration.new(self, destination, block || data.to_s, config) + end + + def set_migration_assigns!(destination) + destination = File.expand_path(destination, self.destination_root) + + migration_dir = File.dirname(destination) + @migration_number = self.class.next_migration_number(migration_dir) + @migration_file_name = File.basename(destination, '.rb') + @migration_class_name = @migration_file_name.camelize + end + # Creates a migration template at the given destination. The difference # to the default template method is that the migration version is appended # to the destination file name. @@ -37,26 +51,18 @@ module Rails # available as instance variables in the template to be rendered. # # migration_template "migration.rb", "db/migrate/add_foo_to_bar.rb" - def migration_template(source, destination=nil, config={}) - destination = File.expand_path(destination || source, self.destination_root) + def migration_template(source, destination, config = {}) + source = File.expand_path(find_in_source_paths(source.to_s)) - migration_dir = File.dirname(destination) - @migration_number = self.class.next_migration_number(migration_dir) - @migration_file_name = File.basename(destination).sub(/\.rb$/, '') - @migration_class_name = @migration_file_name.camelize + set_migration_assigns!(destination) + context = instance_eval('binding') - destination = self.class.migration_exists?(migration_dir, @migration_file_name) + dir, base = File.split(destination) + numbered_destination = File.join(dir, ["%migration_number%", base].join('_')) - if !(destination && options[:skip]) && behavior == :invoke - if destination && options.force? - remove_file(destination) - elsif destination - raise Error, "Another migration is already named #{@migration_file_name}: #{destination}. Use --force to remove the old migration file and replace it." - end - destination = File.join(migration_dir, "#{@migration_number}_#{@migration_file_name}.rb") + create_migration numbered_destination, nil, config do + ERB.new(::File.binread(source), nil, '-', '@output_buffer').result(context) end - - template(source, destination, config) end end end diff --git a/railties/test/generators/create_migration_test.rb b/railties/test/generators/create_migration_test.rb new file mode 100644 index 0000000000..e16a77479a --- /dev/null +++ b/railties/test/generators/create_migration_test.rb @@ -0,0 +1,134 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/migration/migration_generator' + +class CreateMigrationTest < Rails::Generators::TestCase + include GeneratorsTestHelper + + class Migrator < Rails::Generators::MigrationGenerator + include Rails::Generators::Migration + + def self.next_migration_number(dirname) + current_migration_number(dirname) + 1 + end + end + + tests Migrator + + def default_destination_path + "db/migrate/create_articles.rb" + end + + def create_migration(destination_path = default_destination_path, config = {}, generator_options = {}, &block) + migration_name = File.basename(destination_path, '.rb') + generator([migration_name], generator_options) + generator.set_migration_assigns!(destination_path) + + dir, base = File.split(destination_path) + timestamped_destination_path = File.join(dir, ["%migration_number%", base].join('_')) + + @migration = Rails::Generators::Actions::CreateMigration.new(generator, timestamped_destination_path, block || "contents", config) + end + + def migration_exists!(*args) + @existing_migration = create_migration(*args) + invoke! + @generator = nil + end + + def invoke! + capture(:stdout) { @migration.invoke! } + end + + def revoke! + capture(:stdout) { @migration.revoke! } + end + + def test_invoke + create_migration + + assert_match(/create db\/migrate\/1_create_articles.rb\n/, invoke!) + assert_file @migration.destination + end + + def test_invoke_pretended + create_migration(default_destination_path, {}, { pretend: true }) + + assert_no_file @migration.destination + end + + def test_invoke_when_exists + migration_exists! + create_migration + + assert_equal @existing_migration.destination, @migration.existing_migration + end + + def test_invoke_when_exists_identical + migration_exists! + create_migration + + assert_match(/identical db\/migrate\/1_create_articles.rb\n/, invoke!) + assert @migration.identical? + end + + def test_invoke_when_exists_not_identical + migration_exists! + create_migration { "different content" } + + assert_raise(Rails::Generators::Error) { invoke! } + end + + def test_invoke_forced_when_exists_not_identical + dest = "db/migrate/migration.rb" + migration_exists!(dest) + create_migration(dest, force: true) { "different content" } + + stdout = invoke! + assert_match(/remove db\/migrate\/1_migration.rb\n/, stdout) + assert_match(/create db\/migrate\/2_migration.rb\n/, stdout) + assert_file @migration.destination + assert_no_file @existing_migration.destination + end + + def test_invoke_forced_pretended_when_exists_not_identical + migration_exists! + create_migration(default_destination_path, { force: true }, { pretend: true }) do + "different content" + end + + stdout = invoke! + assert_match(/remove db\/migrate\/1_create_articles.rb\n/, stdout) + assert_match(/create db\/migrate\/2_create_articles.rb\n/, stdout) + assert_no_file @migration.destination + end + + def test_invoke_skipped_when_exists_not_identical + migration_exists! + create_migration(default_destination_path, {}, { skip: true }) { "different content" } + + assert_match(/skip db\/migrate\/2_create_articles.rb\n/, invoke!) + assert_no_file @migration.destination + end + + def test_revoke + migration_exists! + create_migration + + assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!) + assert_no_file @existing_migration.destination + end + + def test_revoke_pretended + migration_exists! + create_migration(default_destination_path, {}, { pretend: true }) + + assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!) + assert_file @existing_migration.destination + end + + def test_revoke_when_no_exists + create_migration + + assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!) + end +end -- cgit v1.2.3 From f93ce1bee992485de1d93e755a4c738c66b3b678 Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Sat, 25 Jan 2014 19:55:48 +0530 Subject: Add docs about behaviour of replacing a has_one associate object, that the previous one is deleted even if the new one doesn't get persisted to database. Fixes #13197 . [ci skip] --- activerecord/lib/active_record/associations.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 714f623af3..f3f77e21c0 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1213,7 +1213,8 @@ module ActiveRecord # Returns the associated object. +nil+ is returned if none is found. # [association=(associate)] # Assigns the associate object, extracts the primary key, sets it as the foreign key, - # and saves the associate object. + # and saves the associate object. To avoid database inconsistencies, permanently deletes an existing + # associated object when assigning a new one, even if the new one isn't saved to database. # [build_association(attributes = {})] # Returns a new object of the associated type that has been instantiated # with +attributes+ and linked to this object through a foreign key, but has not -- cgit v1.2.3 From b9cd5a29dd4c6142b19c861fbf1a67452320b3dd Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Tue, 28 Jan 2014 08:16:20 -0200 Subject: Fix indent on test case [ci skip] --- activerecord/test/cases/associations/has_many_associations_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 9fdace8ac1..cf1e50890e 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -487,9 +487,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [1], posts(:welcome).comments.select { |c| c.id == 1 }.map(&:id) end - def test_select_without_foreign_key + def test_select_without_foreign_key assert_equal companies(:first_firm).accounts.first.credit_limit, companies(:first_firm).accounts.select(:credit_limit).first.credit_limit - end + end def test_adding force_signal37_to_load_all_clients_of_firm -- cgit v1.2.3 From 69ab91ae9396f0101afd13871f179a7f779d3178 Mon Sep 17 00:00:00 2001 From: Lukasz Sarnacki Date: Thu, 23 Jan 2014 16:31:52 +0100 Subject: Log which keys were set to nil in deep_munge deep_munge solves CVE-2013-0155 security vulnerability, but its behaviour is definately confuisng. This commit adds logging to deep_munge. It logs keys for which values were set to nil. Also mentions in guides were added. --- actionpack/CHANGELOG.md | 8 ++++ actionpack/lib/action_controller/log_subscriber.rb | 9 +++++ actionpack/lib/action_dispatch/request/utils.rb | 13 +++++-- guides/source/action_controller_overview.md | 4 ++ guides/source/configuring.md | 4 ++ guides/source/security.md | 43 ++++++++++++++++++++++ 6 files changed, 77 insertions(+), 4 deletions(-) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 23bb01d678..fc05ae3cec 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -11,6 +11,14 @@ *Maurizio De Santis* +* Log which keys were affected by deep munge. + + Deep munge solves CVE-2013-0155 security vulnerability, but its + behaviour is definately confusing, so now at least information + about for which keys values were set to nil is visible in logs. + + *Łukasz Sarnacki* + * Automatically convert dashes to underscores for shorthand routes, e.g: get '/our-work/latest' diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 9279d8bcea..823a1050b5 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -53,6 +53,15 @@ module ActionController debug("Unpermitted parameters: #{unpermitted_keys.join(", ")}") end + def deep_munge(event) + message = "Value for params[:#{event.payload[:keys].join('][:')}] was set"\ + "to nil, because it was one of [], [null] or [null, null, ...]."\ + "Go to http://guides.rubyonrails.org/security.html#unsafe-query-generation"\ + "for more information."\ + + debug(message) + end + %w(write_fragment read_fragment exist_fragment? expire_fragment expire_page write_page).each do |method| class_eval <<-METHOD, __FILE__, __LINE__ + 1 diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb index a6dca9741c..9d4f1aa3c5 100644 --- a/actionpack/lib/action_dispatch/request/utils.rb +++ b/actionpack/lib/action_dispatch/request/utils.rb @@ -7,18 +7,23 @@ module ActionDispatch class << self # Remove nils from the params hash - def deep_munge(hash) + def deep_munge(hash, keys = []) return hash unless perform_deep_munge hash.each do |k, v| + keys << k case v when Array - v.grep(Hash) { |x| deep_munge(x) } + v.grep(Hash) { |x| deep_munge(x, keys) } v.compact! - hash[k] = nil if v.empty? + if v.empty? + hash[k] = nil + ActiveSupport::Notifications.instrument("deep_munge.action_controller", keys: keys) + end when Hash - deep_munge(v) + deep_munge(v, keys) end + keys.pop end hash diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index f394daa6aa..c55637eb0a 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -112,6 +112,10 @@ NOTE: The actual URL in this example will be encoded as "/clients?ids%5b%5d=1&id The value of `params[:ids]` will now be `["1", "2", "3"]`. Note that parameter values are always strings; Rails makes no attempt to guess or cast the type. +NOTE: Values such as `[]`, `[nil]` or `[nil, nil, ...]` in `params` are replaced +with `nil` for security reasons by default. See [Security Guide](security.html#unsafe-query-generation) +for more information. + To send a hash you include the key name inside the brackets: ```html diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 38f7162fcf..5e0010725e 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -352,6 +352,10 @@ value. Defaults to `'encrypted cookie'`. * `config.action_dispatch.encrypted_signed_cookie_salt` sets the signed encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. +* `config.action_dispatch.perform_deep_munge` configures whether `deep_munge` + method should be performed on the parameters. See [Security Guide](security.html#unsafe-query-generation) + for more information. It defaults to true. + * `ActionDispatch::Callbacks.before` takes a block of code to run before the request. * `ActionDispatch::Callbacks.to_prepare` takes a block to run after `ActionDispatch::Callbacks.before`, but before the request. Runs for every request in `development` mode, but only once for `production` or environments with `cache_classes` set to `true`. diff --git a/guides/source/security.md b/guides/source/security.md index cffe7c85f1..70fb066b64 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -915,6 +915,49 @@ Content-Type: text/html Under certain circumstances this would present the malicious HTML to the victim. However, this only seems to work with Keep-Alive connections (and many browsers are using one-time connections). But you can't rely on this. _In any case this is a serious bug, and you should update your Rails to version 2.0.5 or 2.1.2 to eliminate Header Injection (and thus response splitting) risks._ +Unsafe Query Generation +----------------------- + +Due to the way Active Record interprets parameters in combination with the way +that Rack parses query parameters it was possible to issue unexpected database +queries with `IS NULL` where clauses. As a response to that security issue +([CVE-2012-2660](https://groups.google.com/forum/#!searchin/rubyonrails-security/deep_munge/rubyonrails-security/8SA-M3as7A8/Mr9fi9X4kNgJ), +[CVE-2012-2694](https://groups.google.com/forum/#!searchin/rubyonrails-security/deep_munge/rubyonrails-security/jILZ34tAHF4/7x0hLH-o0-IJ) +and [CVE-2013-0155](https://groups.google.com/forum/#!searchin/rubyonrails-security/CVE-2012-2660/rubyonrails-security/c7jT-EeN9eI/L0u4e87zYGMJ)) +`deep_munge` method was introduced as a solution to keep Rails secure by default. + +Example of vulnerable code that could be used by attacker, if `deep_munge` +wasn't performed is: + +```ruby +unless params[:token].nil? + user = User.find_by_token(params[:token]) + user.reset_password! +end +``` + +When `params[:token]` is one of: `[]`, `[nil]`, `[nil, nil, ...]` or +`['foo', nil]` it will bypass the test for `nil`, but `IS NULL` or +`IN ('foo', NULL)` where clauses still will be added to the SQL query. + +To keep rails secure by default, `deep_munge` replaces some of the values with +`nil`. Below table shows what the parameters look like based on `JSON` sent in +request: + +| JSON | Parameters | +|-----------------------------------|--------------------------| +| `{ "person": null }` | `{ :person => nil }` | +| `{ "person": [] }` | `{ :person => nil }` | +| `{ "person": [null] }` | `{ :person => nil }` | +| `{ "person": [null, null, ...] }` | `{ :person => nil }` | +| `{ "person": ["foo", null] }` | `{ :person => ["foo"] }` | + +It is possible to return to old behaviour and disable `deep_munge` configuring +your application if you are aware of the risk and know how to handle it: + +```ruby +config.action_dispatch.perform_deep_munge = false +``` Default Headers --------------- -- cgit v1.2.3 From c1d99344476e1046a609f64776621542b399c929 Mon Sep 17 00:00:00 2001 From: Tsutomu Kuroda Date: Fri, 9 Aug 2013 23:46:27 +0900 Subject: Handle aliased attributes in AR::Relation#select, #order, etc. With this we can write `Model#select(:aliased)`, `Model#order(:aliased)`, `Model#reoder(aliased: :desc)`, etc. Supplementary work to 54122067acaad39b277a5363c6d11d6804c7bf6b. --- activerecord/CHANGELOG.md | 4 +++ .../lib/active_record/relation/query_methods.rb | 6 ++++- activerecord/test/cases/relation/mutation_test.rb | 4 +++ activerecord/test/cases/relations_test.rb | 31 ++++++++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 7db99d4aeb..2e103ba354 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,7 @@ +* Handle aliased attributes `select()`, `order()` and `reorder()`. + + *Tsutomu Kuroda* + * Reset the collection association when calling `reset` on it. Before: diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 993f628fa3..88fc47fada 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -234,7 +234,9 @@ module ActiveRecord def select!(*fields) # :nodoc: fields.flatten! - + fields.map! do |field| + klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field + end self.select_values += fields self end @@ -1048,9 +1050,11 @@ module ActiveRecord order_args.map! do |arg| case arg when Symbol + arg = klass.attribute_alias(arg).to_sym if klass.attribute_alias?(arg) table[arg].asc when Hash arg.map { |field, dir| + field = klass.attribute_alias(field).to_sym if klass.attribute_alias?(field) table[field].send(dir) } else diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index 7cb2a19bee..4fafa668fb 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -14,6 +14,10 @@ module ActiveRecord def relation_delegate_class(klass) self.class.relation_delegate_class(klass) end + + def attribute_alias?(name) + false + end end def relation diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index e874c93110..e390d37871 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -206,12 +206,36 @@ class RelationTest < ActiveRecord::TestCase assert_equal topics(:fourth).title, topics.first.title end + def test_finding_with_order_by_aliased_attributes + topics = Topic.order(:heading) + assert_equal 5, topics.to_a.size + assert_equal topics(:fifth).title, topics.first.title + end + + def test_finding_with_assoc_order_by_aliased_attributes + topics = Topic.order(heading: :desc) + assert_equal 5, topics.to_a.size + assert_equal topics(:third).title, topics.first.title + end + def test_finding_with_reorder topics = Topic.order('author_name').order('title').reorder('id').to_a topics_titles = topics.map{ |t| t.title } assert_equal ['The First Topic', 'The Second Topic of the day', 'The Third Topic of the day', 'The Fourth Topic of the day', 'The Fifth Topic of the day'], topics_titles end + def test_finding_with_reorder_by_aliased_attributes + topics = Topic.order('author_name').reorder(:heading) + assert_equal 5, topics.to_a.size + assert_equal topics(:fifth).title, topics.first.title + end + + def test_finding_with_assoc_reorder_by_aliased_attributes + topics = Topic.order('author_name').reorder(heading: :desc) + assert_equal 5, topics.to_a.size + assert_equal topics(:third).title, topics.first.title + end + def test_finding_with_order_and_take entrants = Entrant.order("id ASC").limit(2).to_a @@ -775,6 +799,13 @@ class RelationTest < ActiveRecord::TestCase assert_equal david.salary, developer.salary end + def test_select_takes_an_aliased_attribute + first = topics(:first) + + topic = Topic.where(id: first.id).select(:heading).first + assert_equal first.heading, topic.heading + end + def test_select_argument_error assert_raises(ArgumentError) { Developer.select } end -- cgit v1.2.3 From fe8ffcacd6abd8d4af6db113275af0257c15bdc2 Mon Sep 17 00:00:00 2001 From: sowjanya Date: Tue, 28 Jan 2014 16:53:30 -0800 Subject: Added documentation for css_compressor --- guides/source/asset_pipeline.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index bce5d6c55f..40e0770177 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -938,7 +938,7 @@ Customizing the Pipeline ### CSS Compression -There is currently one option for compressing CSS, YUI. The [YUI CSS +One of the options for compressing CSS is YUI. The [YUI CSS compressor](http://yui.github.io/yuicompressor/css.html) provides minification. @@ -948,6 +948,11 @@ gem. ```ruby config.assets.css_compressor = :yui ``` +The other option for compressing CSS if you have the sass-rails gem installed is + +```ruby +config.assets.css_compressor = :sass +``` ### JavaScript Compression -- cgit v1.2.3 From dd6488de51461ff660f49110bec091317efaca08 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 28 Jan 2014 17:36:03 -0800 Subject: scope is not necessary --- actionpack/lib/action_dispatch/middleware/reloader.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb index 2f6968eb2e..432a072b1b 100644 --- a/actionpack/lib/action_dispatch/middleware/reloader.rb +++ b/actionpack/lib/action_dispatch/middleware/reloader.rb @@ -26,8 +26,8 @@ module ActionDispatch class Reloader include ActiveSupport::Callbacks - define_callbacks :prepare, :scope => :name - define_callbacks :cleanup, :scope => :name + define_callbacks :prepare + define_callbacks :cleanup # Add a prepare callback. Prepare callbacks are run before each request, prior # to ActionDispatch::Callback's before callbacks. -- cgit v1.2.3 From f142527eb30626904cb1e655a1a28801f08b8acf Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 28 Jan 2014 17:42:26 -0800 Subject: always use a block for cleanup / prepare callbacks so we can clean the method signature --- actionpack/lib/action_dispatch/middleware/reloader.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb index 432a072b1b..15b5a48535 100644 --- a/actionpack/lib/action_dispatch/middleware/reloader.rb +++ b/actionpack/lib/action_dispatch/middleware/reloader.rb @@ -1,3 +1,5 @@ +require 'active_support/deprecation/reporting' + module ActionDispatch # ActionDispatch::Reloader provides prepare and cleanup callbacks, # intended to assist with code reloading during development. @@ -25,6 +27,7 @@ module ActionDispatch # class Reloader include ActiveSupport::Callbacks + include ActiveSupport::Deprecation::Reporting define_callbacks :prepare define_callbacks :cleanup @@ -32,12 +35,18 @@ module ActionDispatch # Add a prepare callback. Prepare callbacks are run before each request, prior # to ActionDispatch::Callback's before callbacks. def self.to_prepare(*args, &block) + unless block_given? + warn "to_prepare without a block is deprecated. Please use a block" + end set_callback(:prepare, *args, &block) end # Add a cleanup callback. Cleanup callbacks are run after each request is # complete (after #close is called on the response body). def self.to_cleanup(*args, &block) + unless block_given? + warn "to_cleanup without a block is deprecated. Please use a block" + end set_callback(:cleanup, *args, &block) end -- cgit v1.2.3 From 66e533f9b13f2ea1f56a19246af55621cc368489 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 29 Jan 2014 01:56:20 -0300 Subject: Correctly send the string given to lock! and reload(:lock) to the lock scope - fixes #13788 As per the documentation at lock!, if the :lock option is a string it should use the given SQL to generate the lock statement. --- activerecord/CHANGELOG.md | 10 ++++++++++ activerecord/lib/active_record/persistence.rb | 2 +- activerecord/test/cases/locking_test.rb | 11 +++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 2e103ba354..ddf5592a78 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,13 @@ +* Correctly send an user provided statement to a `lock!()` call. + + person.lock! 'FOR SHARE NOWAIT' + # Before: SELECT * ... LIMIT 1 FOR UPDATE + # After: SELECT * ... LIMIT 1 FOR SHARE NOWAIT + + Fixes #13788. + + *Maurício Linhares* + * Handle aliased attributes `select()`, `order()` and `reorder()`. *Tsutomu Kuroda* diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 460fbdb3f8..b1b35ed940 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -389,7 +389,7 @@ module ActiveRecord fresh_object = if options && options[:lock] - self.class.unscoped { self.class.lock.find(id) } + self.class.unscoped { self.class.lock(options[:lock]).find(id) } else self.class.unscoped { self.class.find(id) } end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index a16ed963fe..c373dc1511 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -431,6 +431,17 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db? assert_equal old, person.reload.first_name end + if current_adapter?(:PostgreSQLAdapter) + def test_lock_sending_custom_lock_statement + Person.transaction do + person = Person.find(1) + assert_sql(/LIMIT 1 FOR SHARE NOWAIT/) do + person.lock!('FOR SHARE NOWAIT') + end + end + end + end + if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) def test_no_locks_no_wait first, second = duel { Person.find 1 } -- cgit v1.2.3 From b23ffd0dac895aa3fd3afd8d9be36794941731b2 Mon Sep 17 00:00:00 2001 From: Lukasz Sarnacki Date: Fri, 10 Jan 2014 12:57:50 +0100 Subject: Allow session serializer key in config.session_store MessageEncryptor has :serializer option, where any serializer object can be passed. This commit make it possible to set this serializer from configuration level. There are predefined serializers (:marshal_serializer, :json_serialzier) and custom serializer can be passed as String, Symbol (camelized and constantized in ActionDispatch::Session namepspace) or serializer object. Default :json_serializer was also added to generators to provide secure defalt. --- actionpack/CHANGELOG.md | 14 +++++++++ actionpack/lib/action_dispatch.rb | 10 ++++--- .../lib/action_dispatch/middleware/cookies.rb | 16 +++++++++-- .../middleware/session/json_serializer.rb | 13 +++++++++ .../middleware/session/marshal_serializer.rb | 14 +++++++++ actionpack/test/dispatch/cookies_test.rb | 33 ++++++++++++++++++++++ .../lib/active_support/message_encryptor.rb | 2 +- guides/source/action_controller_overview.md | 22 +++++++++++++++ railties/lib/rails/application.rb | 3 +- .../config/initializers/session_store.rb.tt | 2 +- railties/test/generators/app_generator_test.rb | 2 +- 11 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 actionpack/lib/action_dispatch/middleware/session/json_serializer.rb create mode 100644 actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index fc05ae3cec..60b0195510 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -49,6 +49,20 @@ *Alessandro Diaferia* +* Add `:serializer` option for `config.session_store :cookie_store`. This + changes default serializer when using `:cookie_store` to + `ActionDispatch::Session::MarshalSerializer` which is wrapper on Marshal. + + It is also possible to pass: + + * `:json_serializer` which is secure wrapper on JSON using `JSON.parse` and + `JSON.generate` methods with quirks mode; + * any other Symbol or String like `:my_custom_serializer` which will be + camelized and constantized in `ActionDispatch::Session` namespace; + * serializer object with `load` and `dump` methods defined. + + *Łukasz Sarnacki* + * Allow an absolute controller path inside a module scope. Fixes #12777. Example: diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 920e651b08..36dcca2905 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -82,10 +82,12 @@ module ActionDispatch end module Session - autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store' - autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store' - autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store' - autoload :CacheStore, 'action_dispatch/middleware/session/cache_store' + autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store' + autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store' + autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store' + autoload :CacheStore, 'action_dispatch/middleware/session/cache_store' + autoload :JsonSerializer, 'action_dispatch/middleware/session/json_serializer' + autoload :MarshalSerializer, 'action_dispatch/middleware/session/marshal_serializer' end mattr_accessor :test_app diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index fe110d7938..f9f034952e 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -89,6 +89,7 @@ module ActionDispatch ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze SECRET_TOKEN = "action_dispatch.secret_token".freeze SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze + SESSION_SERIALIZER = "action_dispatch.session_serializer".freeze # Cookies can typically store 4096 bytes. MAX_COOKIE_SIZE = 4096 @@ -210,7 +211,8 @@ module ActionDispatch encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '', secret_token: env[SECRET_TOKEN], secret_key_base: env[SECRET_KEY_BASE], - upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present? + upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?, + session_serializer: env[SESSION_SERIALIZER] } end @@ -435,7 +437,7 @@ module ActionDispatch @options = options secret = key_generator.generate_key(@options[:encrypted_cookie_salt]) sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt]) - @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: serializer) end def [](name) @@ -462,6 +464,16 @@ module ActionDispatch rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage nil end + + def serializer + serializer = @options[:session_serializer] || :marshal_serializer + case serializer + when Symbol, String + ActionDispatch::Session.const_get(serializer.to_s.camelize) + else + serializer + end + end end # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore diff --git a/actionpack/lib/action_dispatch/middleware/session/json_serializer.rb b/actionpack/lib/action_dispatch/middleware/session/json_serializer.rb new file mode 100644 index 0000000000..d341853f7a --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/session/json_serializer.rb @@ -0,0 +1,13 @@ +module ActionDispatch + module Session + class JsonSerializer + def self.load(value) + JSON.parse(value, quirks_mode: true) + end + + def self.dump(value) + JSON.generate(value, quirks_mode: true) + end + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb b/actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb new file mode 100644 index 0000000000..26622f682d --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb @@ -0,0 +1,14 @@ +module ActionDispatch + module Session + class MarshalSerializer + def self.load(value) + Marshal.load(value) + end + + def self.dump(value) + Marshal.dump(value) + end + end + end +end + diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 91ac13e7c6..b19ce905f5 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -379,6 +379,39 @@ class CookiesTest < ActionController::TestCase assert_equal 'bar', cookies.encrypted[:foo] end + class ActionDispatch::Session::CustomJsonSerializer + def self.load(value) + JSON.load(value) + " and loaded" + end + + def self.dump(value) + JSON.dump(value + " was dumped") + end + end + + def test_encrypted_cookie_using_custom_json_serializer + @request.env["action_dispatch.session_serializer"] = :custom_json_serializer + get :set_encrypted_cookie + assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo] + end + + def test_encrypted_cookie_using_serializer_object + @request.env["action_dispatch.session_serializer"] = ActionDispatch::Session::CustomJsonSerializer + get :set_encrypted_cookie + assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo] + end + + def test_encrypted_cookie_using_json_serializer + @request.env["action_dispatch.session_serializer"] = :json_serializer + get :set_encrypted_cookie + cookies = @controller.send :cookies + assert_not_equal 'bar', cookies[:foo] + assert_raises TypeError do + cookies.signed[:foo] + end + assert_equal 'bar', cookies.encrypted[:foo] + end + def test_accessing_nonexistant_encrypted_cookie_should_not_raise_invalid_message get :set_encrypted_cookie assert_nil @controller.send(:cookies).encrypted[:non_existant_attribute] diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 7773611e11..b019ad0dec 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -12,7 +12,7 @@ module ActiveSupport # This can be used in situations similar to the MessageVerifier, but # where you don't want users to be able to determine the value of the payload. # - # salt = SecureRandom.random_bytes(64) + # salt = SecureRandom.random_bytes(64) # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt) # => "\x89\xE0\x156\xAC..." # crypt = ActiveSupport::MessageEncryptor.new(key) # => # # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..." diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index c55637eb0a..0234120b45 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -381,6 +381,28 @@ You can also pass a `:domain` key and specify the domain name for the cookie: YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com" ``` +You can pass `:serializer` key to specify serializer for serializing session: + +```ruby +YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', serializer: :json_serializer +``` + +Default serializer is `:marshal_serializer`. When Symbol or String is passed it +will look for appropriate class in `ActionDispatch::Session` namespace, so +passing `:my_custom_serializer` would load +`ActionDispatch::Session::MyCustomSerializer`. + +```ruby +YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', serializer: :my_custom_serializer +``` + +It is also possible to pass serializer object with defined `load` and `dump` +public methods: + +```ruby +YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', serializer: MyCustomSerializer +``` + Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in `config/initializers/secret_token.rb` ```ruby diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 05acd78d98..36432e56ba 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -205,7 +205,8 @@ module Rails "action_dispatch.http_auth_salt" => config.action_dispatch.http_auth_salt, "action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt, "action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt, - "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt + "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt, + "action_dispatch.session_serializer" => config.session_options[:serializer] }) end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt index 2bb9b82c61..923d423287 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt @@ -1,3 +1,3 @@ # Be sure to restart your server when you modify this file. -Rails.application.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %> +Rails.application.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %>, serializer: :json_serializer diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index ddecee2ca1..8aa306c8e0 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -433,7 +433,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_new_hash_style run_generator [destination_root] assert_file "config/initializers/session_store.rb" do |file| - assert_match(/config.session_store :cookie_store, key: '_.+_session'/, file) + assert_match(/config.session_store :cookie_store, key: '_.+_session', serializer: :json_serializer/, file) end end -- cgit v1.2.3 From 0f156100a2d25fba820016c684cbc3d3fadbc1bd Mon Sep 17 00:00:00 2001 From: Guillermo Iguaran Date: Wed, 29 Jan 2014 13:52:25 -0500 Subject: Update CHANGELOG properly with GH #13692 [ci-skip] --- actionpack/CHANGELOG.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 60b0195510..f836b69042 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,17 @@ +* Add `:serializer` option for `config.session_store :cookie_store`. This + changes default serializer when using `:cookie_store` to + `ActionDispatch::Session::MarshalSerializer` which is wrapper on Marshal. + + It is also possible to pass: + + * `:json_serializer` which is secure wrapper on JSON using `JSON.parse` and + `JSON.generate` methods with quirks mode; + * any other Symbol or String like `:my_custom_serializer` which will be + camelized and constantized in `ActionDispatch::Session` namespace; + * serializer object with `load` and `dump` methods defined. + + *Łukasz Sarnacki + Matt Aimonetti* + * Ensure that `request.filtered_parameters` is reset between calls to `process` in `ActionController::TestCase`. @@ -49,20 +63,6 @@ *Alessandro Diaferia* -* Add `:serializer` option for `config.session_store :cookie_store`. This - changes default serializer when using `:cookie_store` to - `ActionDispatch::Session::MarshalSerializer` which is wrapper on Marshal. - - It is also possible to pass: - - * `:json_serializer` which is secure wrapper on JSON using `JSON.parse` and - `JSON.generate` methods with quirks mode; - * any other Symbol or String like `:my_custom_serializer` which will be - camelized and constantized in `ActionDispatch::Session` namespace; - * serializer object with `load` and `dump` methods defined. - - *Łukasz Sarnacki* - * Allow an absolute controller path inside a module scope. Fixes #12777. Example: -- cgit v1.2.3 From 9ed66648b59b160b43c83c349263e8cb97eaa088 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Fri, 24 Jan 2014 18:05:33 -0800 Subject: Fixed a bug in AR::Base#respond_to? Before: >> ActiveRecord::Base.respond_to?(:find_by_something) NoMethodError: undefined method `abstract_class?' for Object:Class After: >> ActiveRecord::Base.respond_to?(:find_by_something) => false --- activerecord/lib/active_record/dynamic_matchers.rb | 8 ++++++-- activerecord/test/cases/finder_respond_to_test.rb | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb index 5caab09038..e94b74063e 100644 --- a/activerecord/lib/active_record/dynamic_matchers.rb +++ b/activerecord/lib/active_record/dynamic_matchers.rb @@ -6,8 +6,12 @@ module ActiveRecord # then we can remove the indirection. def respond_to?(name, include_private = false) - match = Method.match(self, name) - match && match.valid? || super + if self == Base + super + else + match = Method.match(self, name) + match && match.valid? || super + end end private diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb index 3ff22f222f..6ab2657c44 100644 --- a/activerecord/test/cases/finder_respond_to_test.rb +++ b/activerecord/test/cases/finder_respond_to_test.rb @@ -5,6 +5,11 @@ class FinderRespondToTest < ActiveRecord::TestCase fixtures :topics + def test_should_preserve_normal_respond_to_behaviour_on_base + assert_respond_to ActiveRecord::Base, :new + assert !ActiveRecord::Base.respond_to?(:find_by_something) + end + def test_should_preserve_normal_respond_to_behaviour_and_respond_to_newly_added_method class << Topic; self; end.send(:define_method, :method_added_for_finder_respond_to_test) { } assert_respond_to Topic, :method_added_for_finder_respond_to_test -- cgit v1.2.3 From 7e8e91c439c1a877f867cd7ba634f7297ccef04b Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Fri, 24 Jan 2014 18:28:05 -0800 Subject: `scope` now raises on "dangerous" name conflicts Similar to dangerous attribute methods, a scope name conflict is dangerous if it conflicts with an existing class method defined within `ActiveRecord::Base` but not its ancestors. See also #13389. *Godfrey Chan*, *Philippe Creux* --- activerecord/CHANGELOG.md | 10 ++++ .../lib/active_record/attribute_methods.rb | 26 ++++++++-- activerecord/lib/active_record/scoping/named.rb | 6 +++ .../test/cases/scoping/named_scoping_test.rb | 57 ++++++++++++++++++++++ 4 files changed, 95 insertions(+), 4 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index ddf5592a78..aaa0fb07ab 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,13 @@ +* `scope` now raises on "dangerous" name conflicts + + Similar to dangerous attribute methods, a scope name conflict is + dangerous if it conflicts with an existing class method defined within + `ActiveRecord::Base` but not its ancestors. + + See also #13389. + + *Godfrey Chan*, *Philippe Creux* + * Correctly send an user provided statement to a `lock!()` call. person.lock! 'FOR SHARE NOWAIT' diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 73761520f7..ccbff8d1ff 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -110,16 +110,34 @@ module ActiveRecord end end - # A method name is 'dangerous' if it is already defined by Active Record, but + # A method name is 'dangerous' if it is already (re)defined by Active Record, but # not by any ancestors. (So 'puts' is not dangerous but 'save' is.) def dangerous_attribute_method?(name) # :nodoc: method_defined_within?(name, Base) end - def method_defined_within?(name, klass, sup = klass.superclass) # :nodoc: + def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc: if klass.method_defined?(name) || klass.private_method_defined?(name) - if sup.method_defined?(name) || sup.private_method_defined?(name) - klass.instance_method(name).owner != sup.instance_method(name).owner + if superklass.method_defined?(name) || superklass.private_method_defined?(name) + klass.instance_method(name).owner != superklass.instance_method(name).owner + else + true + end + else + false + end + end + + # A class method is 'dangerous' if it is already (re)defined by Active Record, but + # not by any ancestors. (So 'puts' is not dangerous but 'new' is.) + def dangerous_class_method?(method_name) + class_method_defined_within?(method_name, Base) + end + + def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc + if klass.respond_to?(name, true) + if superklass.respond_to?(name, true) + klass.method(name).owner != superklass.method(name).owner else true end diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 2a5718f388..49cadb66d0 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -139,6 +139,12 @@ module ActiveRecord # Article.published.featured.latest_article # Article.featured.titles def scope(name, body, &block) + if dangerous_class_method?(name) + raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ + "on the model \"#{self.name}\", but Active Record already defined " \ + "a class method with the same name." + end + extension = Module.new(&block) if block singleton_class.send(:define_method, name) do |*args| diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 086977d9a2..9dc26cfd4d 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -266,6 +266,63 @@ class NamedScopingTest < ActiveRecord::TestCase assert_equal 'lifo', topic.author_name end + def test_reserved_scope_names + klass = Class.new(ActiveRecord::Base) do + self.table_name = "topics" + + scope :approved, -> { where(approved: true) } + + class << self + public + def pub; end + + private + def pri; end + + protected + def pro; end + end + end + + subklass = Class.new(klass) + + conflicts = [ + :create, # public class method on AR::Base + :relation, # private class method on AR::Base + :new, # redefined class method on AR::Base + :all, # a default scope + ] + + non_conflicts = [ + :find_by_title, # dynamic finder method + :approved, # existing scope + :pub, # existing public class method + :pri, # existing private class method + :pro, # existing protected class method + :open, # a ::Kernel method + ] + + conflicts.each do |name| + assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do + klass.class_eval { scope name, ->{ where(approved: true) } } + end + + assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do + subklass.class_eval { scope name, ->{ where(approved: true) } } + end + end + + non_conflicts.each do |name| + assert_nothing_raised do + klass.class_eval { scope name, ->{ where(approved: true) } } + end + + assert_nothing_raised do + subklass.class_eval { scope name, ->{ where(approved: true) } } + end + end + end + # Method delegation for scope names which look like /\A[a-zA-Z_]\w*[!?]?\z/ # has been done by evaluating a string with a plain def statement. For scope # names which contain spaces this approach doesn't work. -- cgit v1.2.3 From 40f0257e05d8735d94684043ea7be7295fbbae57 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Mon, 27 Jan 2014 01:39:52 -0800 Subject: `enum` now raises on "dangerous" name conflicts Dangerous name conflicts includes instance or class method conflicts with methods defined within `ActiveRecord::Base` but not its ancestors, as well as conflicts with methods generated by other enums on the same class. Fixes #13389. --- activerecord/CHANGELOG.md | 11 +++++++ activerecord/lib/active_record/enum.rb | 46 ++++++++++++++++++++++++-- activerecord/test/cases/enum_test.rb | 59 ++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index aaa0fb07ab..6226ce844f 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,14 @@ +* `enum` now raises on "dangerous" name conflicts + + Dangerous name conflicts includes instance or class method conflicts + with methods defined within `ActiveRecord::Base` but not its ancestors, + as well as conflicts with methods generated by other enums on the same + class. + + Fixes #13389. + + *Godfrey Chan* + * `scope` now raises on "dangerous" name conflicts Similar to dangerous attribute methods, a scope name conflict is diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 53dde5e564..059bfe9a0f 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -77,10 +77,12 @@ module ActiveRecord name = name.to_sym # def self.statuses statuses end + detect_enum_conflict!(name, name.to_s.pluralize, true) klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values } _enum_methods_module.module_eval do # def status=(value) self[:status] = statuses[value] end + klass.send(:detect_enum_conflict!, name, "#{name}=") define_method("#{name}=") { |value| if enum_values.has_key?(value) || value.blank? self[name] = enum_values[value] @@ -95,23 +97,28 @@ module ActiveRecord } # def status() statuses.key self[:status] end + klass.send(:detect_enum_conflict!, name, name) define_method(name) { enum_values.key self[name] } # def status_before_type_cast() statuses.key self[:status] end + klass.send(:detect_enum_conflict!, name, "#{name}_before_type_cast") define_method("#{name}_before_type_cast") { enum_values.key self[name] } pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index pairs.each do |value, i| enum_values[value] = i - # scope :active, -> { where status: 0 } - klass.scope value, -> { klass.where name => i } - # def active?() status == 0 end + klass.send(:detect_enum_conflict!, name, "#{value}?") define_method("#{value}?") { self[name] == i } # def active!() update! status: :active end + klass.send(:detect_enum_conflict!, name, "#{value}!") define_method("#{value}!") { update! name => value } + + # scope :active, -> { where status: 0 } + klass.send(:detect_enum_conflict!, name, value, true) + klass.scope value, -> { klass.where name => i } end DEFINED_ENUMS[name.to_s] = enum_values @@ -148,5 +155,38 @@ module ActiveRecord mod end end + + ENUM_CONFLICT_MESSAGE = \ + "You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \ + "this will generate a %{type} method \"%{method}\", which is already defined " \ + "by %{source}." + + def detect_enum_conflict!(enum_name, method_name, klass_method = false) + if klass_method && dangerous_class_method?(method_name) + raise ArgumentError, ENUM_CONFLICT_MESSAGE % { + enum: enum_name, + klass: self.name, + type: 'class', + method: method_name, + source: 'Active Record' + } + elsif !klass_method && dangerous_attribute_method?(method_name) + raise ArgumentError, ENUM_CONFLICT_MESSAGE % { + enum: enum_name, + klass: self.name, + type: 'instance', + method: method_name, + source: 'Active Record' + } + elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module) + raise ArgumentError, ENUM_CONFLICT_MESSAGE % { + enum: enum_name, + klass: self.name, + type: 'instance', + method: method_name, + source: 'another enum' + } + end + end end end diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index 8719f45e76..5cac630a3a 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -163,4 +163,63 @@ class EnumTest < ActiveRecord::TestCase test "_before_type_cast returns the enum label (required for form fields)" do assert_equal "proposed", @book.status_before_type_cast end + + test "reserved enum names" do + klass = Class.new(ActiveRecord::Base) do + self.table_name = "books" + enum status: [:proposed, :written, :published] + end + + conflicts = [ + :column, # generates class method .columns, which conflicts with an AR method + :logger, # generates #logger, which conflicts with an AR method + :attributes, # generates #attributes=, which conflicts with an AR method + ] + + conflicts.each_with_index do |name, i| + assert_raises(ArgumentError, "enum name `#{name}` should not be allowed") do + klass.class_eval { enum name => ["value_#{i}"] } + end + end + end + + test "reserved enum values" do + klass = Class.new(ActiveRecord::Base) do + self.table_name = "books" + enum status: [:proposed, :written, :published] + end + + conflicts = [ + :new, # generates a scope that conflicts with an AR class method + :valid, # generates #valid?, which conflicts with an AR method + :save, # generates #save!, which conflicts with an AR method + :proposed, # same value as an existing enum + ] + + conflicts.each_with_index do |value, i| + assert_raises(ArgumentError, "enum value `#{value}` should not be allowed") do + klass.class_eval { enum "status_#{i}" => [value] } + end + end + end + + test "overriding enum method should not raise" do + assert_nothing_raised do + klass = Class.new(ActiveRecord::Base) do + self.table_name = "books" + + def published! + super + "do publish work..." + end + + enum status: [:proposed, :written, :published] + + def written! + super + "do written work..." + end + end + end + end end -- cgit v1.2.3 From a2cf795784bfc602dadf5db85186f770396aebd8 Mon Sep 17 00:00:00 2001 From: Marc-Andre Lafortune Date: Wed, 29 Jan 2014 14:30:36 -0500 Subject: Mention find_each in find_in_batches doc [ci skip] --- activerecord/lib/active_record/relation/batches.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index f02e2365f7..e98b4712f5 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -64,6 +64,8 @@ module ActiveRecord # group.each { |person| person.party_all_night! } # end # + # To be yielded each record one by one, use #find_each instead. + # # ==== Options # * :batch_size - Specifies the size of the batch. Default to 1000. # * :start - Specifies the starting point for the batch processing. -- cgit v1.2.3 From 7d6592ebd0b04aab2415fce8098e21212bc8c5c3 Mon Sep 17 00:00:00 2001 From: schneems Date: Wed, 29 Jan 2014 14:16:39 -0600 Subject: Enhance errors while retrieving database config Right now if there is an error retrieving database configuration the intent of the error (what the code was trying to do while you got the error) could be more explicit. Instead of this error: ``` Invalid DATABASE_URL: nil (erb):9:in `rescue in
' (erb):6:in `
' /Users/schneems/.rbenv/versions/2.1.0/lib/ruby/2.1.0/erb.rb:850:in `eval' /Users/schneems/.rbenv/versions/2.1.0/lib/ruby/2.1.0/erb.rb:850:in `result' /Users/schneems/Documents/projects/rails/railties/lib/rails/application/configuration.rb:98:in `database_configuration' /Users/schneems/Documents/projects/rails/activerecord/lib/active_record/railtie.rb:41:in `block in ' /Users/schneems/Documents/projects/rails/railties/lib/rails/railtie.rb:237:in `instance_exec' /Users/schneems/Documents/projects/rails/railties/lib/rails/railtie.rb:237:in `block in run_tasks_blocks' /Users/schneems/Documents/projects/rails/railties/lib/rails/railtie.rb:237:in `each' /Users/schneems/Documents/projects/rails/railties/lib/rails/railtie.rb:237:in `run_tasks_blocks' /Users/schneems/Documents/projects/rails/railties/lib/rails/application.rb:339:in `block in run_tasks_blocks' /Users/schneems/Documents/projects/rails/railties/lib/rails/engine/railties.rb:13:in `each' ``` I propose we issue this error: ``` Cannot load `Rails.application.database_configuration`: Invalid DATABASE_URL: nil (erb):9:in `rescue in
' (erb):6:in `
' /Users/schneems/.rbenv/versions/2.1.0/lib/ruby/2.1.0/erb.rb:850:in `eval' /Users/schneems/.rbenv/versions/2.1.0/lib/ruby/2.1.0/erb.rb:850:in `result' /Users/schneems/Documents/projects/rails/railties/lib/rails/application/configuration.rb:98:in `database_configuration' /Users/schneems/Documents/projects/rails/activerecord/lib/active_record/railtie.rb:41:in `block in ' /Users/schneems/Documents/projects/rails/railties/lib/rails/railtie.rb:237:in `instance_exec' /Users/schneems/Documents/projects/rails/railties/lib/rails/railtie.rb:237:in `block in run_tasks_blocks' /Users/schneems/Documents/projects/rails/railties/lib/rails/railtie.rb:237:in `each' /Users/schneems/Documents/projects/rails/railties/lib/rails/railtie.rb:237:in `run_tasks_blocks' /Users/schneems/Documents/projects/rails/railties/lib/rails/application.rb:339:in `block in run_tasks_blocks' /Users/schneems/Documents/projects/rails/railties/lib/rails/engine/railties.rb:13:in `each' ``` --- railties/lib/rails/application/configuration.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index e902205a13..20e3de32aa 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -109,6 +109,8 @@ module Rails raise "YAML syntax error occurred while parsing #{paths["config/database"].first}. " \ "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ "Error: #{e.message}" + rescue => e + raise e, "Cannot load `Rails.application.database_configuration`:\n#{e.message}", e.backtrace end def log_level -- cgit v1.2.3 From aba9fc084759d391217c2b7491505e75941b8f00 Mon Sep 17 00:00:00 2001 From: sowjanya Date: Wed, 29 Jan 2014 10:42:07 -0800 Subject: Added some style changes in asset pipeline documentation --- guides/source/asset_pipeline.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 40e0770177..0422dda0d8 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -496,16 +496,11 @@ In this example, `require_self` is used. This puts the CSS contained within the file (if any) at the precise location of the `require_self` call. If `require_self` is called more than once, only the last call is respected. -NOTE. If you want to use multiple Sass files, you should generally use the [Sass -`@import` -rule](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#import) instead -of these Sprockets directives. Using Sprockets directives all Sass files exist -within their own scope, making variables or mixins only available within the -document they were defined in. You can do file globbing as well using -`@import "*"`, and `@import "**/*"` to add the whole tree equivalent to how -`require_tree` works. Check the [sass-rails -documentation](https://github.com/rails/sass-rails#features) for more info and -important caveats. +NOTE. If you want to use multiple Sass files, you should generally use the [Sass `@import` rule](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#import) +instead of these Sprockets directives. Using Sprockets directives all Sass files exist within +their own scope, making variables or mixins only available within the document they were defined in. +You can do file globbing as well using `@import "*"`, and `@import "**/*"` to add the whole tree +equivalent to how `require_tree` works. Check the [sass-rails documentation](https://github.com/rails/sass-rails#features) for more info and important caveats. You can have as many manifest files as you need. For example, the `admin.css` and `admin.js` manifest could contain the JS and CSS files that are used for the -- cgit v1.2.3 From 42566626e9f9ab8d56194a32fd7e674a20c34fb6 Mon Sep 17 00:00:00 2001 From: Kassio Borges Date: Wed, 29 Jan 2014 18:07:52 -0200 Subject: Fix documentation of new controller filters api [ci skip] The api for filters with classes change and the guides weren't updated. Now the class must respond for methods with the same name as the filter, so the `before_action` calls a `before` method, and so on. The method `#filter` has been deprecated in 4.0.0 and has been removed in 4.1.0: #7560 --- activesupport/CHANGELOG.md | 7 +++++++ guides/source/4_1_release_notes.md | 3 +++ guides/source/action_controller_overview.md | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 95bf5601f2..5f9591ccb1 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,10 @@ +* Remove the deprecation about the `#filter` method + + Filter objects should now rely on method corresponding to the filter type + (e.g. `#before`) + + *Aaron Patterson* + * Add `ActiveSupport::JSON::Encoding.time_precision` as a way to configure the precision of encoded time values: diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 477268f4bc..7399bfb5de 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -567,6 +567,9 @@ for detailed changes. * Removed deprecated `assert_present` and `assert_blank` methods, use `assert object.blank?` and `assert object.present?` instead. +* Remove deprecated `#filter` method for filter objects, use the corresponding + method instead (e.g. `#before` for a before filter). + ### Deprecations * Deprecated `Numeric#{ago,until,since,from_now}`, the user is expected to diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 0234120b45..6c82375ea1 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -709,7 +709,7 @@ class ApplicationController < ActionController::Base end class LoginFilter - def self.filter(controller) + def self.before(controller) unless controller.send(:logged_in?) controller.flash[:error] = "You must be logged in to access this section" controller.redirect_to controller.new_login_url @@ -718,7 +718,7 @@ class LoginFilter end ``` -Again, this is not an ideal example for this filter, because it's not run in the scope of the controller but gets the controller passed as an argument. The filter class has a class method `filter` which gets run before or after the action, depending on if it's a before or after filter. Classes used as around filters can also use the same `filter` method, which will get run in the same way. The method must `yield` to execute the action. Alternatively, it can have both a `before` and an `after` method that are run before and after the action. +Again, this is not an ideal example for this filter, because it's not run in the scope of the controller but gets the controller passed as an argument. The filter class must implement a method with the same name as the filter, so for the `before_action` filter the class must implement a `before` method, and so on. The `around` method must `yield` to execute the action. Request Forgery Protection -------------------------- -- cgit v1.2.3 From c9346322b15c15f51234c33a3db1b3895ffe84ab Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Wed, 29 Jan 2014 00:54:13 -0300 Subject: Fixing issue with activerecord serialization not being able to dump a record after loading it from YAML - fixes #13861 --- activerecord/CHANGELOG.md | 12 +++++++++++ .../lib/active_record/attribute_methods.rb | 5 +++++ .../attribute_methods/serialization.rb | 11 +++++++++++ activerecord/lib/active_record/core.rb | 2 +- activerecord/test/cases/store_test.rb | 23 ++++++++++++++++++++++ 5 files changed, 52 insertions(+), 1 deletion(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index fe0d7b2b35..3422474d14 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,15 @@ +* ActiveRecord objects can now be correctly dumped, loaded and dumped again without issues. + + Previously, if you did `YAML.dump`, `YAML.load` and then `YAML.dump` again + in an ActiveRecord model that used serialization it would fail at the last + dump due to the fields not being correctly serialized before being dumped + to YAML. Now it is possible to dump and load the same object as many times + as needed without any issues. + + Fixes #13861. + + *Maurício Linhares* + * `find_in_batches` now returns an `Enumerator` when called without a block, so that it can be chained with other `Enumerable` methods. diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index ccbff8d1ff..9326c9c117 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -278,6 +278,11 @@ module ActiveRecord } end + # Placeholder so it can be overriden when needed by serialization + def attributes_for_coder # :nodoc: + attributes + end + # Returns an #inspect-like string for the value of the # attribute +attr_name+. String attributes are truncated upto 50 # characters, Date and Time attributes are returned in the diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 3227464032..93e3ca8000 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -76,6 +76,7 @@ module ActiveRecord end class Attribute < Struct.new(:coder, :value, :state) # :nodoc: + def unserialized_value(v = value) state == :serialized ? unserialize(v) : value end @@ -164,6 +165,16 @@ module ActiveRecord super end end + + def attributes_for_coder + attribute_names.each_with_object({}) do |name,attrs| + attrs[name] = if self.class.serialized_attributes.include?(name) + @attributes[name].serialized_value + else + read_attribute(name) + end + end + end end end end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 6f02c763fe..6303fe5ee4 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -275,7 +275,7 @@ module ActiveRecord # Post.new.encode_with(coder) # coder # => {"attributes" => {"id" => nil, ... }} def encode_with(coder) - coder['attributes'] = attributes + coder['attributes'] = attributes_for_coder end # Returns true if +comparison_object+ is the same exact object, or +comparison_object+ diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index 6f632b4d8d..e24df6abe9 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -166,4 +166,27 @@ class StoreTest < ActiveRecord::TestCase test "YAML coder initializes the store when a Nil value is given" do assert_equal({}, @john.params) end + + test "attributes_for_coder should return stored fields already serialized" do + attributes = { + "id" => @john.id, + "name"=> @john.name, + "settings" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\ncolor: black\n", + "preferences" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nremember_login: true\n", + "json_data" => "{\"height\":\"tall\"}", "json_data_empty"=>"{\"is_a_good_guy\":true}", + "params" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess {}\n", + "account_id"=> @john.account_id } + assert_equal attributes, @john.attributes_for_coder + end + + test "dump, load and dump again a model" do + dumped = YAML.dump( @john ) + loaded = YAML.load( dumped ) + assert_equal @john, loaded + + second_dump = YAML.dump( loaded ) + assert_equal dumped, second_dump + assert_equal @john, YAML.load( second_dump ) + end + end -- cgit v1.2.3 From 5977e7e4d534463c53af1acef960bccade8c8ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 29 Jan 2014 20:48:43 -0200 Subject: Aesthetic --- activerecord/CHANGELOG.md | 2 +- .../lib/active_record/attribute_methods/serialization.rb | 11 +++++------ activerecord/test/cases/store_test.rb | 13 +++++++------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 3422474d14..7df4720ea5 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,4 +1,4 @@ -* ActiveRecord objects can now be correctly dumped, loaded and dumped again without issues. +* Active Record objects can now be correctly dumped, loaded and dumped again without issues. Previously, if you did `YAML.dump`, `YAML.load` and then `YAML.dump` again in an ActiveRecord model that used serialization it would fail at the last diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 93e3ca8000..67abbbc2a0 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -76,7 +76,6 @@ module ActiveRecord end class Attribute < Struct.new(:coder, :value, :state) # :nodoc: - def unserialized_value(v = value) state == :serialized ? unserialize(v) : value end @@ -167,12 +166,12 @@ module ActiveRecord end def attributes_for_coder - attribute_names.each_with_object({}) do |name,attrs| + attribute_names.each_with_object({}) do |name, attrs| attrs[name] = if self.class.serialized_attributes.include?(name) - @attributes[name].serialized_value - else - read_attribute(name) - end + @attributes[name].serialized_value + else + read_attribute(name) + end end end end diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index e24df6abe9..978cee9cfb 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -175,18 +175,19 @@ class StoreTest < ActiveRecord::TestCase "preferences" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nremember_login: true\n", "json_data" => "{\"height\":\"tall\"}", "json_data_empty"=>"{\"is_a_good_guy\":true}", "params" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess {}\n", - "account_id"=> @john.account_id } + "account_id"=> @john.account_id + } + assert_equal attributes, @john.attributes_for_coder end test "dump, load and dump again a model" do - dumped = YAML.dump( @john ) - loaded = YAML.load( dumped ) + dumped = YAML.dump(@john) + loaded = YAML.load(dumped) assert_equal @john, loaded - second_dump = YAML.dump( loaded ) + second_dump = YAML.dump(loaded) assert_equal dumped, second_dump - assert_equal @john, YAML.load( second_dump ) + assert_equal @john, YAML.load(second_dump) end - end -- cgit v1.2.3 From b2bb1aaf66673a4d5bcb63ed0f5c15023c99d3c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 21 Jan 2014 23:49:02 -0200 Subject: Implement a simple stub feature to use in the Time travel helpers --- .../lib/active_support/testing/time_helpers.rb | 48 ++++++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index 94230e56ba..0e48456715 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -1,7 +1,48 @@ module ActiveSupport module Testing + class SimpleStubs + Stub = Struct.new(:object, :method_name, :original_method) + + def initialize + @stubs = {} + end + + def stub_object(object, method_name, return_value) + key = [object.object_id, method_name] + + if (stub = @stubs[key]) + unstub_object(stub) + end + + @stubs[key] = Stub.new(object, method_name, object.method(method_name)) + + object.define_singleton_method(method_name) { return_value } + end + + def unstub_all! + @stubs.each do |_, stub| + unstub_object(stub) + end + @stubs = {} + end + + def unstub_object(stub) + stub.object.define_singleton_method(stub.method_name, stub.original_method) + end + end + # Containing helpers that helps you test passage of time. module TimeHelpers + def before_setup + super + @simple_stubs = SimpleStubs.new + end + + def after_teardown #:nodoc: + @simple_stubs.unstub_all! + super + end + # Change current time to the time in the future or in the past by a given time difference by # stubbing +Time.now+ and +Date.today+. Note that the stubs are automatically removed # at the end of each test. @@ -41,13 +82,12 @@ module ActiveSupport # end # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 def travel_to(date_or_time, &block) - Time.stubs now: date_or_time.to_time - Date.stubs today: date_or_time.to_date + @simple_stubs.stub_object(Time, :now, date_or_time.to_time) + @simple_stubs.stub_object(Date, :today, date_or_time.to_date) if block_given? block.call - Time.unstub :now - Date.unstub :today + @simple_stubs.unstub_all! end end end -- cgit v1.2.3 From 049a10d4051a48136199fcdfd77bf35df9e9ad11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 29 Jan 2014 21:07:46 -0200 Subject: Alias the original method first to avoid warnings --- activesupport/lib/active_support/testing/time_helpers.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index 0e48456715..cd56ae1883 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -14,8 +14,11 @@ module ActiveSupport unstub_object(stub) end - @stubs[key] = Stub.new(object, method_name, object.method(method_name)) + new_name = "__simple_stub__#{method_name}" + @stubs[key] = Stub.new(object, method_name, new_name) + + object.singleton_class.send :alias_method, new_name, method_name object.define_singleton_method(method_name) { return_value } end @@ -27,7 +30,9 @@ module ActiveSupport end def unstub_object(stub) - stub.object.define_singleton_method(stub.method_name, stub.original_method) + stub.object.singleton_class.send :undef_method, stub.method_name + stub.object.singleton_class.send :alias_method, stub.method_name, stub.original_method + stub.object.singleton_class.send :undef_method, stub.original_method end end -- cgit v1.2.3 From 6dce4367c2bba894bb94e27cdfe4c56fdcc2c3df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 29 Jan 2014 21:10:10 -0200 Subject: Use instance method instead of before hook --- .../lib/active_support/testing/time_helpers.rb | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index cd56ae1883..77b8ba261e 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -38,13 +38,8 @@ module ActiveSupport # Containing helpers that helps you test passage of time. module TimeHelpers - def before_setup - super - @simple_stubs = SimpleStubs.new - end - def after_teardown #:nodoc: - @simple_stubs.unstub_all! + simple_stubs.unstub_all! super end @@ -87,14 +82,18 @@ module ActiveSupport # end # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 def travel_to(date_or_time, &block) - @simple_stubs.stub_object(Time, :now, date_or_time.to_time) - @simple_stubs.stub_object(Date, :today, date_or_time.to_date) + simple_stubs.stub_object(Time, :now, date_or_time.to_time) + simple_stubs.stub_object(Date, :today, date_or_time.to_date) if block_given? block.call - @simple_stubs.unstub_all! + simple_stubs.unstub_all! end end + + def simple_stubs + @simple_stubs ||= SimpleStubs.new + end end end end -- cgit v1.2.3 From 7cf9a1c6a6cba5565817aa11edc227b6369685e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 29 Jan 2014 21:16:52 -0200 Subject: Change the class and method visibility --- .../lib/active_support/testing/time_helpers.rb | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index 77b8ba261e..af19c5a9f5 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -1,6 +1,6 @@ module ActiveSupport module Testing - class SimpleStubs + class SimpleStubs # :nodoc: Stub = Struct.new(:object, :method_name, :original_method) def initialize @@ -29,11 +29,13 @@ module ActiveSupport @stubs = {} end - def unstub_object(stub) - stub.object.singleton_class.send :undef_method, stub.method_name - stub.object.singleton_class.send :alias_method, stub.method_name, stub.original_method - stub.object.singleton_class.send :undef_method, stub.original_method - end + private + + def unstub_object(stub) + stub.object.singleton_class.send :undef_method, stub.method_name + stub.object.singleton_class.send :alias_method, stub.method_name, stub.original_method + stub.object.singleton_class.send :undef_method, stub.original_method + end end # Containing helpers that helps you test passage of time. @@ -91,9 +93,11 @@ module ActiveSupport end end - def simple_stubs - @simple_stubs ||= SimpleStubs.new - end + private + + def simple_stubs + @simple_stubs ||= SimpleStubs.new + end end end end -- cgit v1.2.3 From 17cb1266c7b91c934dcef2afe38ea4dab677ef91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 29 Jan 2014 21:17:05 -0200 Subject: Store the singleton_class in a local variable --- activesupport/lib/active_support/testing/time_helpers.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index af19c5a9f5..84cbdf9608 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -32,9 +32,10 @@ module ActiveSupport private def unstub_object(stub) - stub.object.singleton_class.send :undef_method, stub.method_name - stub.object.singleton_class.send :alias_method, stub.method_name, stub.original_method - stub.object.singleton_class.send :undef_method, stub.original_method + singleton_class = stub.object.singleton_class + singleton_class.send :undef_method, stub.method_name + singleton_class.send :alias_method, stub.method_name, stub.original_method + singleton_class.send :undef_method, stub.original_method end end -- cgit v1.2.3 From 06e06cd454f3699d970c7dc8404170ac236c7743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 29 Jan 2014 21:22:15 -0200 Subject: Use each_value --- activesupport/lib/active_support/testing/time_helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index 84cbdf9608..0ee6332d6f 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -23,7 +23,7 @@ module ActiveSupport end def unstub_all! - @stubs.each do |_, stub| + @stubs.each_value do |stub| unstub_object(stub) end @stubs = {} -- cgit v1.2.3 From 642106e277334e75ff1ae8d8a03f5fef37cf0671 Mon Sep 17 00:00:00 2001 From: Marc-Andre Lafortune Date: Wed, 29 Jan 2014 15:02:13 -0500 Subject: find_in_batches should not mutate its argument --- activerecord/lib/active_record/relation/batches.rb | 4 ++-- activerecord/test/cases/batches_test.rb | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index dfcfef2ad2..666cef80a9 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -104,8 +104,8 @@ module ActiveRecord logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size") end - start = options.delete(:start) - batch_size = options.delete(:batch_size) || 1000 + start = options[:start] + batch_size = options[:batch_size] || 1000 relation = relation.reorder(batch_order).limit(batch_size) records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 1161b57514..8216d74cb3 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -153,6 +153,12 @@ class EachTest < ActiveRecord::TestCase assert_equal special_posts_ids, posts.map(&:id) end + def test_find_in_batches_should_not_modify_passed_options + assert_nothing_raised do + Post.find_in_batches({ batch_size: 42, start: 1 }.freeze){} + end + end + def test_find_in_batches_should_use_any_column_as_primary_key nick_order_subscribers = Subscriber.order('nick asc') start_nick = nick_order_subscribers.second.nick -- cgit v1.2.3 From fd487860db3097104cdb8d589f3931d75b767721 Mon Sep 17 00:00:00 2001 From: Guillermo Iguaran Date: Thu, 30 Jan 2014 01:12:23 -0500 Subject: Modify the session serializer implementation Rename allowed options to :marshal and :json, for custom serializers only allow the use of custom classes. --- actionpack/CHANGELOG.md | 15 ++++++++------- actionpack/lib/action_dispatch/middleware/cookies.rb | 8 +++++--- actionpack/test/dispatch/cookies_test.rb | 12 +++--------- guides/source/action_controller_overview.md | 16 +++++----------- .../templates/config/initializers/session_store.rb.tt | 2 +- railties/test/generators/app_generator_test.rb | 2 +- 6 files changed, 23 insertions(+), 32 deletions(-) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index f836b69042..417847cc50 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,14 +1,15 @@ * Add `:serializer` option for `config.session_store :cookie_store`. This - changes default serializer when using `:cookie_store` to - `ActionDispatch::Session::MarshalSerializer` which is wrapper on Marshal. + changes default serializer when using `:cookie_store`. - It is also possible to pass: + It is possible to pass: - * `:json_serializer` which is secure wrapper on JSON using `JSON.parse` and + * `:json` which is a secure wrapper on JSON using `JSON.parse` and `JSON.generate` methods with quirks mode; - * any other Symbol or String like `:my_custom_serializer` which will be - camelized and constantized in `ActionDispatch::Session` namespace; - * serializer object with `load` and `dump` methods defined. + * `:marshal` which is a wrapper on Marshal; + * serializer class with `load` and `dump` methods defined. + + For new apps `:json` option is added by default and :marshal is used + when no option is specified. *Łukasz Sarnacki + Matt Aimonetti* diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index f9f034952e..23d0ecd529 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -466,10 +466,12 @@ module ActionDispatch end def serializer - serializer = @options[:session_serializer] || :marshal_serializer + serializer = @options[:session_serializer] || :marshal case serializer - when Symbol, String - ActionDispatch::Session.const_get(serializer.to_s.camelize) + when :marshal + ActionDispatch::Session::MarshalSerializer + when :json + ActionDispatch::Session::JsonSerializer else serializer end diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index b19ce905f5..6101acdc25 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -379,7 +379,7 @@ class CookiesTest < ActionController::TestCase assert_equal 'bar', cookies.encrypted[:foo] end - class ActionDispatch::Session::CustomJsonSerializer + class CustomJsonSerializer def self.load(value) JSON.load(value) + " and loaded" end @@ -389,20 +389,14 @@ class CookiesTest < ActionController::TestCase end end - def test_encrypted_cookie_using_custom_json_serializer - @request.env["action_dispatch.session_serializer"] = :custom_json_serializer - get :set_encrypted_cookie - assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo] - end - def test_encrypted_cookie_using_serializer_object - @request.env["action_dispatch.session_serializer"] = ActionDispatch::Session::CustomJsonSerializer + @request.env["action_dispatch.session_serializer"] = CustomJsonSerializer get :set_encrypted_cookie assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo] end def test_encrypted_cookie_using_json_serializer - @request.env["action_dispatch.session_serializer"] = :json_serializer + @request.env["action_dispatch.session_serializer"] = :json get :set_encrypted_cookie cookies = @controller.send :cookies assert_not_equal 'bar', cookies[:foo] diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 6c82375ea1..9eaf03dd82 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -384,20 +384,14 @@ YourApp::Application.config.session_store :cookie_store, key: '_your_app_session You can pass `:serializer` key to specify serializer for serializing session: ```ruby -YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', serializer: :json_serializer +YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', serializer: :json ``` -Default serializer is `:marshal_serializer`. When Symbol or String is passed it -will look for appropriate class in `ActionDispatch::Session` namespace, so -passing `:my_custom_serializer` would load -`ActionDispatch::Session::MyCustomSerializer`. +The default serializer for new application is `:json`. For compatibility with +old applications `:marshal` is used when `serializer` option is not specified. -```ruby -YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', serializer: :my_custom_serializer -``` - -It is also possible to pass serializer object with defined `load` and `dump` -public methods: +It is also possible to pass a custom serializer class with `load` and `dump` +public methods defined: ```ruby YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', serializer: MyCustomSerializer diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt index 923d423287..097fcb4bb0 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt @@ -1,3 +1,3 @@ # Be sure to restart your server when you modify this file. -Rails.application.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %>, serializer: :json_serializer +Rails.application.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %>, serializer: :json diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 8aa306c8e0..700935fd8d 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -433,7 +433,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_new_hash_style run_generator [destination_root] assert_file "config/initializers/session_store.rb" do |file| - assert_match(/config.session_store :cookie_store, key: '_.+_session', serializer: :json_serializer/, file) + assert_match(/config.session_store :cookie_store, key: '_.+_session', serializer: :json/, file) end end -- cgit v1.2.3 From 9632c986b4df02cc6c51dbbc2768403bd8e8c07f Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Thu, 30 Jan 2014 10:46:06 +0100 Subject: docs, `references` is only used with `includes`. Closes #13727. There is no gain in `referencing` tables that are not used for preloading. Furthermore it will break if polymorphic associations are invloved. This is because `references_eager_loaded_tables?` uses all `reference_values` to decide wether to `eager_load` or `preload`. --- .../lib/active_record/relation/query_methods.rb | 23 +++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 88fc47fada..14470f22aa 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -120,6 +120,9 @@ module ActiveRecord # Will throw an error, but this will work: # # User.includes(:posts).where('posts.name = ?', 'example').references(:posts) + # + # Note that +includes+ works with association names while +references+ needs + # the actual table name. def includes(*args) check_if_method_has_arguments!(:includes, args) spawn.includes!(*args) @@ -163,24 +166,26 @@ module ActiveRecord self end - # Used to indicate that an association is referenced by an SQL string, and should - # therefore be JOINed in any query rather than loaded separately. + # Use to indicate that the given +table_names+ are referenced by an SQL string, + # and should therefore be JOINed in any query rather than loaded separately. + # This method only works in conjuction with +includes+. + # See #includes for more details. # # User.includes(:posts).where("posts.name = 'foo'") # # => Doesn't JOIN the posts table, resulting in an error. # # User.includes(:posts).where("posts.name = 'foo'").references(:posts) # # => Query now knows the string references posts, so adds a JOIN - def references(*args) - check_if_method_has_arguments!(:references, args) - spawn.references!(*args) + def references(*table_names) + check_if_method_has_arguments!(:references, table_names) + spawn.references!(*table_names) end - def references!(*args) # :nodoc: - args.flatten! - args.map!(&:to_s) + def references!(*table_names) # :nodoc: + table_names.flatten! + table_names.map!(&:to_s) - self.references_values |= args + self.references_values |= table_names self end -- cgit v1.2.3 From 02f9f3314244513fce0a94acef08318d67d6707f Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Thu, 30 Jan 2014 11:12:46 +0100 Subject: tidy CHANGELOGs [ci skip] --- actionmailer/CHANGELOG.md | 10 ++++++---- actionpack/CHANGELOG.md | 4 ++-- actionview/CHANGELOG.md | 18 ++++++++++-------- activemodel/CHANGELOG.md | 7 +++++-- activesupport/CHANGELOG.md | 4 ++-- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index c264c710f6..5a61746700 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -4,7 +4,11 @@ *Andrew White* * Add the ability to intercept emails before previewing in a similar fashion - to how emails can be intercepted before delivery, e.g: + to how emails can be intercepted before delivery. + + Fixes #13622. + + Example: class CSSInlineStyler def self.previewing_email(message) @@ -14,11 +18,9 @@ ActionMailer::Base.register_preview_interceptor CSSInlineStyler - Fixes #13622. - *Andrew White* -* Add mailer previews feature based on 37 Signals mail_view gem +* Add mailer previews feature based on 37 Signals mail_view gem. *Andrew White* diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 417847cc50..394b1473d3 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -5,13 +5,13 @@ * `:json` which is a secure wrapper on JSON using `JSON.parse` and `JSON.generate` methods with quirks mode; - * `:marshal` which is a wrapper on Marshal; + * `:marshal` which is a wrapper on Marshal; * serializer class with `load` and `dump` methods defined. For new apps `:json` option is added by default and :marshal is used when no option is specified. - *Łukasz Sarnacki + Matt Aimonetti* + *Łukasz Sarnacki*, *Matt Aimonetti* * Ensure that `request.filtered_parameters` is reset between calls to `process` in `ActionController::TestCase`. diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 960f867d99..c370f3df51 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,20 +1,20 @@ * Added `config.action_view.raise_on_missing_translations` to define whether an error should be raised for missing translations. - Fixes #13196 + Fixes #13196. *Kassio Borges* * Improved ERB dependency detection. New argument types and formattings for the `render` calls can be matched. - Fixes #13074 and #13116 + Fixes #13074, #13116. *João Britto* -* Use `display:none` instead of `display:inline` for hidden fields +* Use `display:none` instead of `display:inline` for hidden fields. - Fixes #6403 + Fixes #6403. *Gaelian Ditchburn* @@ -82,11 +82,11 @@ *Yves Senn* -* Use `set_backtrace` instead of instance variable `@backtrace` in ActionView exceptions +* Use `set_backtrace` instead of instance variable `@backtrace` in ActionView exceptions. *Shimpei Makimoto* -* Fix `simple_format` escapes own output when passing `sanitize: true` +* Fix `simple_format` escapes own output when passing `sanitize: true`. *Paul Seidemann* @@ -104,7 +104,9 @@ *Bogdan Gusiev* -* Ability to pass block to `select` helper +* Ability to pass a block to the `select` helper. + + Example: <%= select(report, "campaign_ids") do %> <% available_campaigns.each do |c| -%> @@ -184,7 +186,7 @@ * Fix default rendered format problem when calling `render` without :content_type option. It should return :html. Fix #11393. - *Gleb Mazovetskiy* *Oleg* *kennyj* + *Gleb Mazovetskiy*, *Oleg*, *kennyj* * Fix `link_to` with block and url hashes. diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 0148066bac..6585808fa2 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -18,9 +18,12 @@ *Vince Puzzella* -* `attribute_changed?` now accepts parameters which check the old and new value of the attribute +* `attribute_changed?` now accepts a hash to check if the attribute was + changed `:from` and/or `:to` a given value. - `model.name_changed?(from: "Pete", to: "Ringo")` + Example: + + model.name_changed?(from: "Pete", to: "Ringo") *Tejas Dinkar* diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 5f9591ccb1..008d71701c 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,7 +1,7 @@ -* Remove the deprecation about the `#filter` method +* Remove the deprecation about the `#filter` method. Filter objects should now rely on method corresponding to the filter type - (e.g. `#before`) + (e.g. `#before`). *Aaron Patterson* -- cgit v1.2.3 From 6b16c2788186d45c70bd1d9fc476407e3e265439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 29 Jan 2014 22:24:48 -0200 Subject: Add `travel_back` to remove stubs from `travel` and `travel_to` --- activesupport/CHANGELOG.md | 4 ++++ .../lib/active_support/testing/time_helpers.rb | 17 ++++++++++++++--- activesupport/test/test_test.rb | 12 ++++++++++++ guides/source/4_1_release_notes.md | 3 +++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 008d71701c..eecb13207c 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,7 @@ +* Add `travel_back` to remove stubs from `travel` and `travel_to`. + + *Rafael Mendonça França* + * Remove the deprecation about the `#filter` method. Filter objects should now rely on method corresponding to the filter type diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index 0ee6332d6f..b183b8d8ef 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -42,7 +42,7 @@ module ActiveSupport # Containing helpers that helps you test passage of time. module TimeHelpers def after_teardown #:nodoc: - simple_stubs.unstub_all! + travel_back super end @@ -81,7 +81,7 @@ module ActiveSupport # # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 # travel_to Time.new(2004, 11, 24, 01, 04, 44) do - # User.create.created_at # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 # end # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 def travel_to(date_or_time, &block) @@ -90,10 +90,21 @@ module ActiveSupport if block_given? block.call - simple_stubs.unstub_all! + travel_back end end + # Return the current time back to its original state. + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel_to Time.new(2004, 11, 24, 01, 04, 44) + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # travel_back + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + def travel_back + simple_stubs.unstub_all! + end + private def simple_stubs diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index 8a71ef4324..1e539d8d06 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -201,4 +201,16 @@ class TimeHelperTest < ActiveSupport::TestCase assert_not_equal expected_time, Time.now assert_not_equal Date.new(2004, 11, 24), Date.today end + + def test_time_helper_travel_back + expected_time = Time.new(2004, 11, 24, 01, 04, 44) + + travel_to expected_time + assert_equal expected_time, Time.now + assert_equal Date.new(2004, 11, 24), Date.today + travel_back + + assert_not_equal expected_time, Time.now + assert_not_equal Date.new(2004, 11, 24), Date.today + end end diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 7399bfb5de..3668ffe44c 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -607,6 +607,9 @@ for detailed changes. `Time.now` and `Date.today`. ([Pull Request](https://github.com/rails/rails/pull/12824)) +* Added `ActiveSupport::Testing::TimeHelpers#travel_back`. This method return + the current time to the original state. ([Pull Request](https://github.com/rails/rails/pull/13884)) + * Added `Numeric#in_milliseconds`, like `1.hour.in_milliseconds`, so we can feed them to JavaScript functions like `getTime()`. ([Commit](https://github.com/rails/rails/commit/423249504a2b468d7a273cbe6accf4f21cb0e643)) -- cgit v1.2.3 From 7abb6e00c0f1d6cc98b10b0e7620dfb9786449a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 29 Jan 2014 22:41:30 -0200 Subject: Remove automatic removal of Date/Time stubs after each test case This behavior is only work out-of-box with minitest and also add a downside to run after each test case, even if we don't used the travel or travel_to methods --- activerecord/test/cases/mixin_test.rb | 6 +++++- activesupport/CHANGELOG.md | 8 ++++++++ activesupport/lib/active_support/testing/time_helpers.rb | 11 ++--------- activesupport/test/test_test.rb | 4 ++++ activesupport/test/time_zone_test.rb | 3 +++ 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/activerecord/test/cases/mixin_test.rb b/activerecord/test/cases/mixin_test.rb index ad0d5cce27..7ddb2bfee1 100644 --- a/activerecord/test/cases/mixin_test.rb +++ b/activerecord/test/cases/mixin_test.rb @@ -6,10 +6,14 @@ end class TouchTest < ActiveRecord::TestCase fixtures :mixins - def setup + setup do travel_to Time.now end + teardown do + travel_back + end + def test_update stamped = Mixin.new diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index eecb13207c..17890b2668 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,11 @@ +* Remove behavior that automatically remove the Date/Time stubs, added by `travel` + and `travel_to` methods, after each test case. + + Now users have to use the `travel_back` or the block version of `travel` and + `travel_to` methods to clean the stubs. + + *Rafael Mendonça França* + * Add `travel_back` to remove stubs from `travel` and `travel_to`. *Rafael Mendonça França* diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index b183b8d8ef..4c6bca5ada 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -41,14 +41,8 @@ module ActiveSupport # Containing helpers that helps you test passage of time. module TimeHelpers - def after_teardown #:nodoc: - travel_back - super - end - # Change current time to the time in the future or in the past by a given time difference by - # stubbing +Time.now+ and +Date.today+. Note that the stubs are automatically removed - # at the end of each test. + # stubbing +Time.now+ and +Date.today+. # # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 # travel 1.day @@ -68,8 +62,7 @@ module ActiveSupport end # Change current time to the given time by stubbing +Time.now+ and +Date.today+ to return the - # time or date passed into this method. Note that the stubs are automatically removed - # at the end of each test. + # time or date passed into this method. # # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 # travel_to Time.new(2004, 11, 24, 01, 04, 44) diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index 1e539d8d06..0fa08c0e3a 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -162,6 +162,10 @@ class TimeHelperTest < ActiveSupport::TestCase Time.stubs now: Time.now end + teardown do + travel_back + end + def test_time_helper_travel expected_time = Time.now + 1.day travel 1.day diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 1107b48460..cd79efbe8c 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -97,6 +97,7 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].today + travel_back end def test_tomorrow @@ -108,6 +109,7 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST assert_equal Date.new(2000, 1, 3), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].tomorrow + travel_back end def test_yesterday @@ -119,6 +121,7 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday travel_to(Time.utc(2000, 1, 2, 5)) # midnight Jan 2 EST assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeZone['Eastern Time (US & Canada)'].yesterday + travel_back end def test_local -- cgit v1.2.3 From fa1f20e6549f962112948f5b3c27d09ab5e5faaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 30 Jan 2014 10:13:58 -0200 Subject: Improve documentation [ci skip] --- activesupport/lib/active_support/testing/time_helpers.rb | 7 ++++--- guides/source/4_1_release_notes.md | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index 4c6bca5ada..9e0a3d6345 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -41,7 +41,7 @@ module ActiveSupport # Containing helpers that helps you test passage of time. module TimeHelpers - # Change current time to the time in the future or in the past by a given time difference by + # Changes current time to the time in the future or in the past by a given time difference by # stubbing +Time.now+ and +Date.today+. # # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 @@ -61,7 +61,7 @@ module ActiveSupport travel_to Time.now + duration, &block end - # Change current time to the given time by stubbing +Time.now+ and +Date.today+ to return the + # Changes current time to the given time by stubbing +Time.now+ and +Date.today+ to return the # time or date passed into this method. # # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 @@ -87,7 +87,8 @@ module ActiveSupport end end - # Return the current time back to its original state. + # Returns the current time back to its original state, by removing the stubs added by + # `travel` and `travel_to`. # # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 # travel_to Time.new(2004, 11, 24, 01, 04, 44) diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 3668ffe44c..4e75bf400c 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -607,8 +607,9 @@ for detailed changes. `Time.now` and `Date.today`. ([Pull Request](https://github.com/rails/rails/pull/12824)) -* Added `ActiveSupport::Testing::TimeHelpers#travel_back`. This method return - the current time to the original state. ([Pull Request](https://github.com/rails/rails/pull/13884)) +* Added `ActiveSupport::Testing::TimeHelpers#travel_back`. This method returns + the current time to the original state, by removing the stubs added by `travel` + and `travel_to`. ([Pull Request](https://github.com/rails/rails/pull/13884)) * Added `Numeric#in_milliseconds`, like `1.hour.in_milliseconds`, so we can feed them to JavaScript functions like -- cgit v1.2.3 From d4d041c1fe55c10a786a4a1a7ff0c34867944b2f Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Thu, 30 Jan 2014 12:05:02 +0100 Subject: fix typo and indent. [ci skip] --- activesupport/lib/active_support/core_ext/module/concerning.rb | 2 +- guides/source/getting_started.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb index b22dc5ff1e..07a392404e 100644 --- a/activesupport/lib/active_support/core_ext/module/concerning.rb +++ b/activesupport/lib/active_support/core_ext/module/concerning.rb @@ -63,7 +63,7 @@ class Module # # == Mix-in noise exiled to its own file: # - # Once our chunk of behavior starts pushing the scroll-to-understand it + # Once our chunk of behavior starts pushing the scroll-to-understand it's # boundary, we give in and move it to a separate file. At this size, the # overhead feels in good proportion to the size of our extraction, despite # diluting our at-a-glance sense of how things really work. diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 9c09ecc99c..0aa0b49b15 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -23,7 +23,7 @@ prerequisites installed: * The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer * The [RubyGems](http://rubygems.org) packaging system - * To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org) +* To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org) * A working installation of the [SQLite3 Database](http://www.sqlite.org) Rails is a web application framework running on the Ruby programming language. -- cgit v1.2.3 From b7fcad8ff04411a8d00f85094b172b6b99402190 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 29 Jan 2014 21:17:10 -0500 Subject: Fix regression on `.select_*` methods. This was a common pattern: ``` query = author.posts.select(:title) connection.select_one(query) ``` However `.select` returns a ActiveRecord::AssociationRelation, which has the bind information, so we can use that to get the right sql query. Also fix select_rows on postgress and sqlite3 that were not using the binds [fixes #7538] [fixes #12017] [related #13731] [related #12056] --- activerecord/CHANGELOG.md | 8 ++++++++ .../abstract/database_statements.rb | 17 +++++++++++++--- .../connection_adapters/mysql2_adapter.rb | 2 +- .../connection_adapters/mysql_adapter.rb | 4 ++-- .../postgresql/database_statements.rb | 4 ++-- .../connection_adapters/postgresql_adapter.rb | 8 -------- .../connection_adapters/sqlite3_adapter.rb | 4 ++-- activerecord/test/cases/adapter_test.rb | 23 ++++++++++++++++++++++ 8 files changed, 52 insertions(+), 18 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 7df4720ea5..a6400a169b 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,11 @@ +* Fix regressions on `select_*` methods. + When `select_*` methods receive a `Relation` object, they should be able to get the arel/binds from it. + Also fix regressions on select_rows that was ignoring the binds. + + Fixes #7538, #12017, #13731, #12056. + + *arthurnn* + * Active Record objects can now be correctly dumped, loaded and dumped again without issues. Previously, if you did `YAML.dump`, `YAML.load` and then `YAML.dump` again 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 c90915c509..6eb59cc398 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -20,6 +20,14 @@ module ActiveRecord # Returns an ActiveRecord::Result instance. def select_all(arel, name = nil, binds = []) + if arel.is_a?(Relation) + relation = arel + arel = relation.arel + if !binds || binds.empty? + binds = relation.bind_values + end + end + select(to_sql(arel, binds), name, binds) end @@ -39,13 +47,16 @@ 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) - select_rows(to_sql(arel, []), name) - .map { |v| v[0] } + binds = [] + if arel.is_a?(Relation) + arel, binds = arel.arel, arel.bind_values + end + select_rows(to_sql(arel, binds), name, binds).map(&:first) end # Returns an array of arrays containing the field values. # Order is the same as that returned by +columns+. - def select_rows(sql, name = nil) + def select_rows(sql, name = nil, binds = []) end undef_method :select_rows diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 6d8e994654..b07b0cb826 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -213,7 +213,7 @@ module ActiveRecord # Returns an array of arrays containing the field values. # Order is the same as that returned by +columns+. - def select_rows(sql, name = nil) + def select_rows(sql, name = nil, binds = []) execute(sql, name).to_a end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 7dbaa272a3..49f0bfbcde 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -213,9 +213,9 @@ module ActiveRecord # DATABASE STATEMENTS ====================================== - def select_rows(sql, name = nil) + def select_rows(sql, name = nil, binds = []) @connection.query_with_result = true - rows = exec_query(sql, name).rows + rows = exec_query(sql, name, binds).rows @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped rows end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index f349c37724..51ee2829b2 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -46,8 +46,8 @@ module ActiveRecord # Executes a SELECT query and returns an array of rows. Each row is an # array of field values. - def select_rows(sql, name = nil) - select_raw(sql, name).last + def select_rows(sql, name = nil, binds = []) + exec_query(sql, name, binds).rows end # Executes an INSERT query and returns the new record's ID diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index a471383041..9618ba4087 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -942,14 +942,6 @@ module ActiveRecord exec_query(sql, name, binds) end - def select_raw(sql, name = nil) - res = execute(sql, name) - results = result_as_array(res) - fields = res.fields - res.clear - return fields, results - end - # Returns the list of a table's column names, data types, and default values. # # The underlying query is roughly: diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 170dddb08e..3c5f7a981e 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -347,8 +347,8 @@ module ActiveRecord end alias :create :insert_sql - def select_rows(sql, name = nil) - exec_query(sql, name).rows + def select_rows(sql, name = nil, binds = []) + exec_query(sql, name, binds).rows end def begin_db_transaction #:nodoc: diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index b67e70ec7e..0eb1231c79 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -1,5 +1,7 @@ require "cases/helper" require "models/book" +require "models/post" +require "models/author" module ActiveRecord class AdapterTest < ActiveRecord::TestCase @@ -179,6 +181,27 @@ module ActiveRecord assert result.is_a?(ActiveRecord::Result) end + def test_select_methods_passing_a_association_relation + author = Author.create!(name: 'john') + Post.create!(author: author, title: 'foo', body: 'bar') + query = author.posts.select(:title) + assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values)) + assert_equal({"title" => "foo"}, @connection.select_one(query)) + assert @connection.select_all(query).is_a?(ActiveRecord::Result) + assert_equal "foo", @connection.select_value(query) + assert_equal ["foo"], @connection.select_values(query) + end + + def test_select_methods_passing_a_relation + Post.create!(title: 'foo', body: 'bar') + query = Post.where(title: 'foo').select(:title) + assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values)) + assert_equal({"title" => "foo"}, @connection.select_one(query)) + assert @connection.select_all(query).is_a?(ActiveRecord::Result) + assert_equal "foo", @connection.select_value(query) + assert_equal ["foo"], @connection.select_values(query) + end + test "type_to_sql returns a String for unmapped types" do assert_equal "special_db_type", @connection.type_to_sql(:special_db_type) end -- cgit v1.2.3 From abc19c37aea561e7b087cee9d60c5698483f3136 Mon Sep 17 00:00:00 2001 From: Washington Luiz Date: Tue, 28 Jan 2014 00:59:11 -0300 Subject: Let `unscope` ignore non Arel scope.where_values --- activerecord/lib/active_record/relation/query_methods.rb | 2 -- activerecord/test/cases/scoping/default_scoping_test.rb | 10 ++++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 14470f22aa..860063426a 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -883,8 +883,6 @@ module ActiveRecord when Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right) subrelation.name == target_value - else - raise "unscope(where: #{target_value.inspect}) failed: unscoping #{rel.class} \"#{rel}\" is unimplemented." end end diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 71754cf0a2..170e9a49eb 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -149,6 +149,16 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal expected, received end + def test_unscope_string_where_clauses_involved + dev_relation = Developer.order('salary DESC').where("created_at > ?", 1.year.ago) + expected = dev_relation.collect { |dev| dev.name } + + dev_ordered_relation = DeveloperOrderedBySalary.where(name: 'Jamis').where("created_at > ?", 1.year.ago) + received = dev_ordered_relation.unscope(where: [:name]).collect { |dev| dev.name } + + assert_equal expected, received + end + def test_unscope_with_grouping_attributes expected = Developer.order('salary DESC').collect { |dev| dev.name } received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect { |dev| dev.name } -- cgit v1.2.3 From 1f9586fd4725f5e81177cc6adba879b4869f71af Mon Sep 17 00:00:00 2001 From: Josh Jordan Date: Tue, 28 Jan 2014 16:51:01 -0500 Subject: Do not discard query parameters on requests that use wrap_parameters --- actionpack/CHANGELOG.md | 5 +++++ .../lib/action_controller/metal/params_wrapper.rb | 15 +++++++++++---- actionpack/test/controller/params_wrapper_test.rb | 20 ++++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 23bb01d678..82e9e1535e 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,8 @@ +* Do not discard query parameters that form a hash with the same root key as + the wrapper_key for a request using wrap_parameters + + *Josh Jordan* + * Ensure that `request.filtered_parameters` is reset between calls to `process` in `ActionController::TestCase`. diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index c9f1d8dcb4..2ca8955741 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -231,7 +231,12 @@ module ActionController # by the metal call stack. def process_action(*args) if _wrapper_enabled? - wrapped_hash = _wrap_parameters request.request_parameters + if request.parameters[_wrapper_key].present? + wrapped_hash = _extract_parameters(request.parameters) + else + wrapped_hash = _wrap_parameters request.request_parameters + end + wrapped_keys = request.request_parameters.keys wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys) @@ -259,14 +264,16 @@ module ActionController # Returns the list of parameters which will be selected for wrapped. def _wrap_parameters(parameters) - value = if include_only = _wrapper_options.include + { _wrapper_key => _extract_parameters(parameters) } + end + + def _extract_parameters(parameters) + if include_only = _wrapper_options.include parameters.slice(*include_only) else exclude = _wrapper_options.exclude || [] parameters.except(*(exclude + EXCLUDE_PARAMETERS)) end - - { _wrapper_key => value } end # Checks if we should perform parameters wrapping. diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index d87e2b85b0..11ccb6cf3b 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -188,6 +188,26 @@ class ParamsWrapperTest < ActionController::TestCase assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }}) end end + + def test_preserves_query_string_params + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + get :parse, { 'user' => { 'username' => 'nixon' } } + assert_parameters( + {'user' => { 'username' => 'nixon' } } + ) + end + end + + def test_empty_parameter_set + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, {} + assert_parameters( + {'user' => { } } + ) + end + end end class NamespacedParamsWrapperTest < ActionController::TestCase -- cgit v1.2.3 From 4506dd2f07be824fd7e0eb6165c29994aeb1bfcd Mon Sep 17 00:00:00 2001 From: Lauro Caetano Date: Thu, 30 Jan 2014 22:58:00 -0200 Subject: Associations now raise `ArgumentError` on name conflicts. Dangerous association names conflicts include instance or class methods already defined by `ActiveRecord::Base`. --- activerecord/CHANGELOG.md | 16 ++++++++++++++++ .../active_record/associations/builder/association.rb | 6 ++++++ .../cases/associations/belongs_to_associations_test.rb | 10 ++++++++++ .../cases/associations/has_many_associations_test.rb | 10 ++++++++++ .../test/cases/associations/has_one_associations_test.rb | 10 ++++++++++ 5 files changed, 52 insertions(+) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index a6400a169b..f3322dd7f6 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,19 @@ +* Associations now raise `ArgumentError` on name conflicts. + + Dangerous association names conflicts include instance or class methods already + defined by `ActiveRecord::Base`. + + Example: + + class Car < ActiveRecord::Base + has_many :errors + end + # Will raise ArgumentError. + + Fixes #13217. + + *Lauro Caetano* + * Fix regressions on `select_*` methods. When `select_*` methods receive a `Relation` object, they should be able to get the arel/binds from it. Also fix regressions on select_rows that was ignoring the binds. diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index 3911d1b520..f085fd1cfd 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -26,6 +26,12 @@ module ActiveRecord::Associations::Builder attr_reader :name, :scope, :options def self.build(model, name, scope, options, &block) + if model.dangerous_attribute_method?(name) + raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \ + "this will conflict with a method #{name} already defined by Active Record. " \ + "Please choose a different association name." + end + builder = create_builder model, name, scope, options, &block reflection = builder.build(model) define_accessors model, reflection diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 2283ba66db..9340bc0a83 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -853,4 +853,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert post.save assert_equal post.author_id, author2.id end + + test 'dangerous association name raises ArgumentError' do + [:errors, 'errors', :save, 'save'].each do |name| + assert_raises(ArgumentError, "Association #{name} should not be allowed") do + Class.new(ActiveRecord::Base) do + belongs_to name + end + end + end + end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index cf1e50890e..a86fb15719 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1820,4 +1820,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase topic.approved_replies.create! end end + + test 'dangerous association name raises ArgumentError' do + [:errors, 'errors', :save, 'save'].each do |name| + assert_raises(ArgumentError, "Association #{name} should not be allowed") do + Class.new(ActiveRecord::Base) do + has_many name + end + end + end + end end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index d4edef03d6..a4650ccdf2 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -564,4 +564,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end end end + + test 'dangerous association name raises ArgumentError' do + [:errors, 'errors', :save, 'save'].each do |name| + assert_raises(ArgumentError, "Association #{name} should not be allowed") do + Class.new(ActiveRecord::Base) do + has_one name + end + end + end + end end -- cgit v1.2.3 From 16648979f72e4543cb62cf803dc6c4c6b03715d4 Mon Sep 17 00:00:00 2001 From: Tim Fenney Date: Fri, 31 Jan 2014 00:50:09 -0500 Subject: Remove unused variable. --- activerecord/test/cases/enum_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index 5cac630a3a..1b95708cb3 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -205,7 +205,7 @@ class EnumTest < ActiveRecord::TestCase test "overriding enum method should not raise" do assert_nothing_raised do - klass = Class.new(ActiveRecord::Base) do + Class.new(ActiveRecord::Base) do self.table_name = "books" def published! -- cgit v1.2.3 From 464d47eaaf76995ae8eaa5205d250ff859fed795 Mon Sep 17 00:00:00 2001 From: Rohit Paul Kuruvilla Date: Tue, 21 Jan 2014 23:07:18 +0530 Subject: Updated association_basics.md [ci skip] Updated association_basics.md to include how to write migrations for self joins Update association_basics.md Corrected the update Update association_basics.md Typo fix Changed :employee to :manager --- guides/source/association_basics.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 9867d2dc3f..5ec6ae0f21 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -490,6 +490,19 @@ end With this setup, you can retrieve `@employee.subordinates` and `@employee.manager`. +In your migrations/schema, you will add a references column to the model itself. + +```ruby +class CreateEmployees < ActiveRecord::Migration + def change + create_table :employees do |t| + t.references :manager + t.timestamps + end + end +end +``` + Tips, Tricks, and Warnings -------------------------- -- cgit v1.2.3 From 0a4466b7e8429ef57a30acb476017e3fc450f763 Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Fri, 31 Jan 2014 00:25:52 -0800 Subject: Document default trim mode for Erubis and affected ERB tags [ci skip] Fixes #12963 --- guides/source/configuring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 5e0010725e..669086edbe 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -378,7 +378,7 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`. * `config.action_view.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action View. Set to `nil` to disable logging. -* `config.action_view.erb_trim_mode` gives the trim mode to be used by ERB. It defaults to `'-'`. See the [ERB documentation](http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/) for more information. +* `config.action_view.erb_trim_mode` gives the trim mode to be used by ERB. It defaults to `'-'`, which turns on trimming of tail spaces and newline when using `<%= -%>` or `<%= =%>`. See the [Erubis documentation](http://www.kuwata-lab.com/erubis/users-guide.06.html#topics-trimspaces) for more information. * `config.action_view.embed_authenticity_token_in_remote_forms` allows you to set the default behavior for `authenticity_token` in forms with `:remote => true`. By default it's set to false, which means that remote forms will not include `authenticity_token`, which is helpful when you're fragment-caching the form. Remote forms get the authenticity from the `meta` tag, so embedding is unnecessary unless you support browsers without JavaScript. In such case you can either pass `:authenticity_token => true` as a form option or set this config setting to `true` -- cgit v1.2.3 From 8c7e8b4f18a18ee616e5351f8fa3581cdee79e23 Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Fri, 31 Jan 2014 08:02:56 -0200 Subject: Minor changelog improvements [ci skip] --- actionview/CHANGELOG.md | 2 +- activerecord/CHANGELOG.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index c370f3df51..30dbc20f18 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -18,7 +18,7 @@ *Gaelian Ditchburn* -* The `video_tag` helper accepts a number as `:size` +* The `video_tag` helper accepts a number as `:size`. The `:size` option of the `video_tag` helper now can be specified with a stringified number. The `width` and `height` attributes of diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f3322dd7f6..8c09284e53 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -16,7 +16,7 @@ * Fix regressions on `select_*` methods. When `select_*` methods receive a `Relation` object, they should be able to get the arel/binds from it. - Also fix regressions on select_rows that was ignoring the binds. + Also fix regressions on `select_rows` that was ignoring the binds. Fixes #7538, #12017, #13731, #12056. @@ -25,7 +25,7 @@ * Active Record objects can now be correctly dumped, loaded and dumped again without issues. Previously, if you did `YAML.dump`, `YAML.load` and then `YAML.dump` again - in an ActiveRecord model that used serialization it would fail at the last + in an Active Record model that used serialization it would fail at the last dump due to the fields not being correctly serialized before being dumped to YAML. Now it is possible to dump and load the same object as many times as needed without any issues. @@ -39,7 +39,7 @@ *Marc-André Lafortune* -* `enum` now raises on "dangerous" name conflicts +* `enum` now raises on "dangerous" name conflicts. Dangerous name conflicts includes instance or class method conflicts with methods defined within `ActiveRecord::Base` but not its ancestors, @@ -50,7 +50,7 @@ *Godfrey Chan* -* `scope` now raises on "dangerous" name conflicts +* `scope` now raises on "dangerous" name conflicts. Similar to dangerous attribute methods, a scope name conflict is dangerous if it conflicts with an existing class method defined within -- cgit v1.2.3 From 63f8fabe4939ad59d597dfea441002ef5b16d2f4 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Fri, 31 Jan 2014 17:13:12 +0000 Subject: Maintain the current timezone in wrap_with_time_zone Extend the solution from the fix for #12163 to the general case where `Time` methods are wrapped with a time zone. Fixes #12596. --- activesupport/CHANGELOG.md | 9 +++++++++ activesupport/lib/active_support/time_with_zone.rb | 9 ++------- activesupport/test/core_ext/time_with_zone_test.rb | 5 +++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 17890b2668..b44df1b8a9 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,12 @@ +* Maintain the current timezone when calling `wrap_with_time_zone` + + Extend the solution from the fix for #12163 to the general case where `Time` + methods are wrapped with a time zone. + + Fixes #12596. + + *Andrew White* + * Remove behavior that automatically remove the Date/Time stubs, added by `travel` and `travel_to` methods, after each test case. diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index d459af1778..c25c97cfa8 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -292,12 +292,6 @@ module ActiveSupport end end - def change(options) - new_time = time.change(options) - periods = time_zone.periods_for_local(new_time) - self.class.new(nil, time_zone, new_time, periods.include?(period) ? period : nil) - end - %w(year mon month day mday wday yday hour min sec usec nsec to_date).each do |method_name| class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{method_name} # def month @@ -396,7 +390,8 @@ module ActiveSupport def wrap_with_time_zone(time) if time.acts_like?(:time) - self.class.new(nil, time_zone, time) + periods = time_zone.periods_for_local(time) + self.class.new(nil, time_zone, time, periods.include?(period) ? period : nil) elsif time.is_a?(Range) wrap_with_time_zone(time.begin)..wrap_with_time_zone(time.end) else diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 8e25f1e2f2..7fe4d4a6b2 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -500,6 +500,11 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal twz, twz.change(:min => 0) end + def test_round_at_dst_boundary + twz = ActiveSupport::TimeWithZone.new(Time.at(1319936400).getutc, ActiveSupport::TimeZone['Madrid']) + assert_equal twz, twz.round + end + def test_advance assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Mon, 31 Dec 2001 19:00:00 EST -05:00", @twz.advance(:years => 2).inspect -- cgit v1.2.3 From 0b101804444e2cc57740b1c79cbd19f340f46cbf Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 31 Jan 2014 11:54:42 -0800 Subject: FilterRedirect is referenced at the class level from the Response We can just require the file rather than going through the autoload indirection --- actionpack/lib/action_dispatch.rb | 1 - actionpack/lib/action_dispatch/http/response.rb | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 36dcca2905..9b26845190 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -75,7 +75,6 @@ module ActionDispatch autoload :Parameters autoload :ParameterFilter autoload :FilterParameters - autoload :FilterRedirect autoload :Upload autoload :UploadedFile, 'action_dispatch/http/upload' autoload :URL diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 7b2655b2d8..bc13ee00f1 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/module/attribute_accessors' +require 'action_dispatch/http/filter_redirect' require 'monitor' module ActionDispatch # :nodoc: -- cgit v1.2.3 From e8fcd599ba6a301dbddb084f7369320ca3c49ff3 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 31 Jan 2014 12:00:54 -0800 Subject: only ask for the location filters once --- actionpack/lib/action_dispatch/http/filter_redirect.rb | 9 +++++---- actionview/lib/action_view.rb | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb index 900ce1c646..cd603649c3 100644 --- a/actionpack/lib/action_dispatch/http/filter_redirect.rb +++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb @@ -5,7 +5,8 @@ module ActionDispatch FILTERED = '[FILTERED]'.freeze # :nodoc: def filtered_location - if !location_filter.empty? && location_filter_match? + filters = location_filter + if !filters.empty? && location_filter_match?(filters) FILTERED else location @@ -15,15 +16,15 @@ module ActionDispatch private def location_filter - if request.present? + if request request.env['action_dispatch.redirect_filter'] || [] else [] end end - def location_filter_match? - location_filter.any? do |filter| + def location_filter_match?(filters) + filters.any? do |filter| if String === filter location.include?(filter) elsif Regexp === filter diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb index 5c729345dc..0eb29b7ebb 100644 --- a/actionview/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -28,6 +28,8 @@ require 'action_view/version' module ActionView extend ActiveSupport::Autoload + ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' + eager_autoload do autoload :Base autoload :Context @@ -81,8 +83,6 @@ module ActionView autoload :TestCase - ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' - def self.eager_load! super ActionView::Helpers.eager_load! -- cgit v1.2.3 From 3fbff7811bc7142e6f4142f807dd7b6ebd766a13 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 31 Jan 2014 12:05:50 -0800 Subject: just require the template resolver LookupContext is eagerly loaded, and FallbackFileSystemResolver is referenced at the class level. Just require the resolver from the eagerly loaded class rather than jumping through autoload hoops --- actionview/lib/action_view.rb | 1 - actionview/lib/action_view/lookup_context.rb | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb index 0eb29b7ebb..50712e0830 100644 --- a/actionview/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -56,7 +56,6 @@ module ActionView autoload_at "action_view/template/resolver" do autoload :Resolver autoload :PathResolver - autoload :FileSystemResolver autoload :OptimizedFileSystemResolver autoload :FallbackFileSystemResolver end diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index e07d9b6314..76c9890776 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -1,6 +1,7 @@ require 'thread_safe' require 'active_support/core_ext/module/remove_method' require 'active_support/core_ext/module/attribute_accessors' +require 'action_view/template/resolver' module ActionView # = Action View Lookup Context -- cgit v1.2.3 From 9b2a017aa82f95911280ed597e4bf3193c9399e9 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 31 Jan 2014 14:10:31 -0800 Subject: this class depends on JoinHelper, so we should require it --- activerecord/lib/active_record/associations/association_scope.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 17f056e764..5a0ba9e6b1 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -1,3 +1,5 @@ +require 'active_record/associations/join_helper' + module ActiveRecord module Associations class AssociationScope #:nodoc: -- cgit v1.2.3 From a34c10f73e739142794a2e6366328051fcc91238 Mon Sep 17 00:00:00 2001 From: Mauricio Linhares Date: Sat, 1 Feb 2014 01:11:03 -0300 Subject: Fixes issue with parsing whitespace content back from database - fixes #13907 --- activerecord/CHANGELOG.md | 11 +++++++++++ .../connection_adapters/postgresql/array_parser.rb | 5 +++-- activerecord/test/cases/adapters/postgresql/array_test.rb | 12 ++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 8c09284e53..22f86fb2e1 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,14 @@ +* Parsing PostgreSQL arrays with empty strings now works correctly. + + Previously, if you tried to parse `{"1","","2","","3"}` the result + would be `["1","2","3"]`, removing the empty strings from the array, + which would be incorrect. Now it will correctly produce `["1","","2","","3"]` + as the result of parsing the above PostgreSQL array. + + Fixes #13907. + + *Maurício Linhares* + * Associations now raise `ArgumentError` on name conflicts. Dangerous association names conflicts include instance or class methods already diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb b/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb index 20de8d1982..0b218f2bfd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb @@ -91,8 +91,9 @@ module ActiveRecord end def add_item_to_array(array, current_item, quoted) - if current_item.length == 0 - elsif !quoted && current_item == 'NULL' + return if !quoted && current_item.length == 0 + + if !quoted && current_item == 'NULL' array.push nil else array.push current_item diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index d71e2aa2bb..3090f4478f 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -93,6 +93,18 @@ class PostgresqlArrayTest < ActiveRecord::TestCase assert_cycle(:tags, [[['1'], ['2']], [['2'], ['3']]]) end + def test_with_empty_strings + assert_cycle(:tags, [ '1', '2', '', '4', '', '5' ]) + end + + def test_with_multi_dimensional_empty_strings + assert_cycle(:tags, [[['1', '2'], ['', '4'], ['', '5']]]) + end + + def test_with_arbitrary_whitespace + assert_cycle(:tags, [[['1', '2'], [' ', '4'], [' ', '5']]]) + end + def test_multi_dimensional_with_integers assert_cycle(:ratings, [[[1], [7]], [[8], [10]]]) end -- cgit v1.2.3 From cd93d7175e3f92c77744110204dc9194a3aa592c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sat, 1 Feb 2014 16:16:06 -0200 Subject: Make arel methods private API Since its conception arel was made to be private API of Active Record. If users want to use arel features directly we should provide a way using the Active Record API without exposing the arel implementation. --- activerecord/lib/active_record/core.rb | 4 ++-- activerecord/lib/active_record/relation/query_methods.rb | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 6303fe5ee4..d691192cfd 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -138,12 +138,12 @@ module ActiveRecord # class Post < ActiveRecord::Base # scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) } # end - def arel_table + def arel_table # :nodoc: @arel_table ||= Arel::Table.new(table_name, arel_engine) end # Returns the Arel engine. - def arel_engine + def arel_engine # :nodoc: @arel_engine ||= if Base == self || connection_handler.retrieve_connection_pool(self) self diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 860063426a..077f09b67d 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -824,11 +824,12 @@ module ActiveRecord end # Returns the Arel object associated with the relation. - def arel + def arel # :nodoc: @arel ||= build_arel end - # Like #arel, but ignores the default scope of the model. + private + def build_arel arel = Arel::SelectManager.new(table.engine, table) @@ -854,8 +855,6 @@ module ActiveRecord arel end - private - def symbol_unscoping(scope) if !VALID_UNSCOPING_VALUES.include?(scope) raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}." -- cgit v1.2.3 From 682a579b25deb1142b8f445ad9ae0c661bc58564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sat, 1 Feb 2014 16:27:58 -0200 Subject: Remove warnings for already defined methods --- activerecord/test/cases/scoping/named_scoping_test.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 9dc26cfd4d..f0ad9ebb8a 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -314,7 +314,9 @@ class NamedScopingTest < ActiveRecord::TestCase non_conflicts.each do |name| assert_nothing_raised do - klass.class_eval { scope name, ->{ where(approved: true) } } + silence_warnings do + klass.class_eval { scope name, ->{ where(approved: true) } } + end end assert_nothing_raised do -- cgit v1.2.3 From 85d820b1693a52faddf1f838512e132906272e41 Mon Sep 17 00:00:00 2001 From: David Celis Date: Fri, 31 Jan 2014 17:42:21 -0800 Subject: Don't require BigDecimal serialization extension Rails currently provides an extension to BigDecimal that redefines how it is serialized to YAML. However, as noted in #12467, this does not work as expected. When ActiveSupport is required, BigDecimal YAML serialization does not maintain the object type. It instead ends up serializing the number represented by the BigDecimal itself which, when loaded by YAML later, becomes a Float: ```ruby require 'yaml' require 'bigdecimal' yaml = BigDecimal('13.37').to_yaml YAML.load(yaml).class require 'active_support/all' yaml = BigDecimal('13.37').to_yaml YAML.load(yaml).class ``` @tenderlove posits that we should deprecate the custom BigDecimal serialization and let Ruby handle it. For the time being, users who require this serialization for backwards compatibility can manually `require 'active_support/core_ext/big_decimal/yaml_conversions'`. This will close #12467 and deprecate the custom BigDecimal#to_yaml. Signed-off-by: David Celis --- activesupport/CHANGELOG.md | 8 ++++++++ .../lib/active_support/core_ext/big_decimal/conversions.rb | 8 -------- .../active_support/core_ext/big_decimal/yaml_conversions.rb | 13 +++++++++++++ .../test/core_ext/big_decimal/yaml_conversions_test.rb | 11 +++++++++++ activesupport/test/core_ext/bigdecimal_test.rb | 9 +-------- 5 files changed, 33 insertions(+), 16 deletions(-) create mode 100644 activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb create mode 100644 activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index b44df1b8a9..f15bd1b8d0 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,11 @@ +* Deprecate custom `BigDecimal` serialization + + Deprecate the custom `BigDecimal` serialization that is included when requiring + `active_support/all` as a fix for #12467. Let Ruby handle YAML serialization + for `BigDecimal` instead. + + *David Celis* + * Maintain the current timezone when calling `wrap_with_time_zone` Extend the solution from the fix for #12163 to the general case where `Time` diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb index 39b8cea807..54b49e6d04 100644 --- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb +++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb @@ -1,15 +1,7 @@ require 'bigdecimal' require 'bigdecimal/util' -require 'yaml' class BigDecimal - YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' } - - def encode_with(coder) - string = to_s - coder.represent_scalar(nil, YAML_MAPPING[string] || string) - end - # Backport this method if it doesn't exist unless method_defined?(:to_d) def to_d diff --git a/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb new file mode 100644 index 0000000000..aa2ed4d6fb --- /dev/null +++ b/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb @@ -0,0 +1,13 @@ +ActiveSupport::Deprecation.warn 'core_ext/big_decimal/yaml_conversions is deprecated and will be removed in the future.' + +require 'bigdecimal' +require 'yaml' + +class BigDecimal + YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' } + + def encode_with(coder) + string = to_s + coder.represent_scalar(nil, YAML_MAPPING[string] || string) + end +end diff --git a/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb b/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb new file mode 100644 index 0000000000..49020e0567 --- /dev/null +++ b/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb @@ -0,0 +1,11 @@ +require 'abstract_unit' +require 'active_support/core_ext/big_decimal/yaml_conversions' + +class BigDecimalYamlConversionsTest < ActiveSupport::TestCase + def test_to_yaml + assert_match("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml) + assert_match("--- .Inf\n", BigDecimal.new('Infinity').to_yaml) + assert_match("--- .NaN\n", BigDecimal.new('NaN').to_yaml) + assert_match("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml) + end +end diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb index b386e55d6c..61bf3f81e6 100644 --- a/activesupport/test/core_ext/bigdecimal_test.rb +++ b/activesupport/test/core_ext/bigdecimal_test.rb @@ -2,18 +2,11 @@ require 'abstract_unit' require 'active_support/core_ext/big_decimal' class BigDecimalTest < ActiveSupport::TestCase - def test_to_yaml - assert_match("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml) - assert_match("--- .Inf\n", BigDecimal.new('Infinity').to_yaml) - assert_match("--- .NaN\n", BigDecimal.new('NaN').to_yaml) - assert_match("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml) - end - def test_to_d bd = BigDecimal.new '10' assert_equal bd, bd.to_d end - + def test_to_s bd = BigDecimal.new '0.01' assert_equal '0.01', bd.to_s -- cgit v1.2.3 From c87b27ebde4c5a0bc172ffce59faaadb20301dec Mon Sep 17 00:00:00 2001 From: David Celis Date: Sat, 1 Feb 2014 10:26:50 -0800 Subject: Remove BigDecimal#to_d This was backported for Ruby 1.8 support and is no longer needed. Signed-off-by: David Celis --- .../lib/active_support/core_ext/big_decimal/conversions.rb | 7 ------- activesupport/test/core_ext/bigdecimal_test.rb | 5 ----- 2 files changed, 12 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb index 54b49e6d04..843c592669 100644 --- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb +++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb @@ -2,13 +2,6 @@ require 'bigdecimal' require 'bigdecimal/util' class BigDecimal - # Backport this method if it doesn't exist - unless method_defined?(:to_d) - def to_d - self - end - end - DEFAULT_STRING_FORMAT = 'F' def to_formatted_s(*args) if args[0].is_a?(Symbol) diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb index 61bf3f81e6..423a3f2e9d 100644 --- a/activesupport/test/core_ext/bigdecimal_test.rb +++ b/activesupport/test/core_ext/bigdecimal_test.rb @@ -2,11 +2,6 @@ require 'abstract_unit' require 'active_support/core_ext/big_decimal' class BigDecimalTest < ActiveSupport::TestCase - def test_to_d - bd = BigDecimal.new '10' - assert_equal bd, bd.to_d - end - def test_to_s bd = BigDecimal.new '0.01' assert_equal '0.01', bd.to_s -- cgit v1.2.3 From 8c679fe0ca77e8cc6de6ae408a556995f3801ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sat, 1 Feb 2014 18:29:41 -0200 Subject: Fix isolated tests --- .../lib/active_support/core_ext/big_decimal/yaml_conversions.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb index aa2ed4d6fb..46ba93ead4 100644 --- a/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +++ b/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb @@ -2,6 +2,7 @@ ActiveSupport::Deprecation.warn 'core_ext/big_decimal/yaml_conversions is deprec require 'bigdecimal' require 'yaml' +require 'active_support/core_ext/big_decimal/conversions' class BigDecimal YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' } -- cgit v1.2.3 From 4ac114471cc14de44b77ced3dd4a0676c13b818b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sat, 1 Feb 2014 18:29:53 -0200 Subject: Assert the file is deprecated --- activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb b/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb index 49020e0567..e634679d20 100644 --- a/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb +++ b/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb @@ -1,8 +1,8 @@ require 'abstract_unit' -require 'active_support/core_ext/big_decimal/yaml_conversions' class BigDecimalYamlConversionsTest < ActiveSupport::TestCase def test_to_yaml + assert_deprecated { require 'active_support/core_ext/big_decimal/yaml_conversions' } assert_match("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml) assert_match("--- .Inf\n", BigDecimal.new('Infinity').to_yaml) assert_match("--- .NaN\n", BigDecimal.new('NaN').to_yaml) -- cgit v1.2.3 From 4b4db54e6b48813b24c807b0156eb58bc4694cd9 Mon Sep 17 00:00:00 2001 From: Attila Domokos Date: Sun, 2 Feb 2014 10:27:18 -0600 Subject: Adding an documentation example and a test to button_to with path I did not see in the docs that `button_to` supports not only URLs but paths as well. I documented this functionality with a unit tests and added an example to the docs as well. --- actionview/lib/action_view/helpers/url_helper.rb | 5 +++++ actionview/test/template/url_helper_test.rb | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 56dd7a4390..3ccace1274 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -232,6 +232,11 @@ module ActionView # #
# # " # + # <%= button_to "New", new_articles_path %> + # # => "
+ # #
+ # # " + # # <%= button_to [:make_happy, @user] do %> # Make happy <%= @user.name %> # <% end %> diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb index deba33510a..7e978e15d2 100644 --- a/actionview/test/template/url_helper_test.rb +++ b/actionview/test/template/url_helper_test.rb @@ -56,6 +56,13 @@ class UrlHelperTest < ActiveSupport::TestCase assert_dom_equal %{
}, button_to("Hello", "http://www.example.com") end + def test_button_to_with_path + assert_dom_equal( + %{
}, + button_to("Hello", article_path("Hello".html_safe)) + ) + end + def test_button_to_with_straight_url_and_request_forgery self.request_forgery = true -- cgit v1.2.3 From 00ebbb79456dccd45d813886d81f1619c0161af4 Mon Sep 17 00:00:00 2001 From: Kevin Casey Date: Sun, 2 Feb 2014 11:47:06 -0800 Subject: fix HABTM w/out primary key errors on destruction --- .../lib/active_record/associations/has_many_association.rb | 2 +- .../associations/has_and_belongs_to_many_associations_test.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 72e0891702..6457182195 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -111,7 +111,7 @@ module ActiveRecord records.each(&:destroy!) update_counter(-records.length) unless inverse_updates_counter_cache? else - if records == :all + if records == :all || !reflection.klass.primary_key scope = self.scope else scope = self.scope.where(reflection.klass.primary_key => records) diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 8aee7ff40e..bac1cb8e2d 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -775,6 +775,16 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert project.developers.include?(developer) end + def test_destruction_does_not_error_without_primary_key + redbeard = pirates(:redbeard) + george = parrots(:george) + redbeard.parrots << george + assert_equal 2, george.pirates.count + Pirate.includes(:parrots).where(parrot: redbeard.parrot).find(redbeard.id).destroy + assert_equal 1, george.pirates.count + assert_equal [], Pirate.where(id: redbeard.id) + end + test "has and belongs to many associations on new records use null relations" do projects = Developer.new.projects assert_no_queries do -- cgit v1.2.3 From ca1121af6aeaa848267dd5d73fef10f1e9ea35a3 Mon Sep 17 00:00:00 2001 From: Fred Wu Date: Mon, 3 Feb 2014 12:02:07 +1100 Subject: Fixes the camelCase variable name in the docs [ci skip] --- activerecord/lib/active_record/associations.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index f3f77e21c0..051f7dff53 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -672,8 +672,8 @@ module ActiveRecord # class Asset < ActiveRecord::Base # belongs_to :attachable, polymorphic: true # - # def attachable_type=(sType) - # super(sType.to_s.classify.constantize.base_class.to_s) + # def attachable_type=(klass) + # super(klass.to_s.classify.constantize.base_class.to_s) # end # end # -- cgit v1.2.3 From 8f0460d7468bc5d2a63acc2494b356ac7a0d5a22 Mon Sep 17 00:00:00 2001 From: Calvin Tam Date: Mon, 3 Feb 2014 21:09:29 +1100 Subject: Fixed minor typo [ci skip] --- guides/source/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/testing.md b/guides/source/testing.md index 33cd3e868b..169fd75cfa 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -339,7 +339,7 @@ NOTE: The execution of each test method stops as soon as any error or an asserti When a test fails you are presented with the corresponding backtrace. By default Rails filters that backtrace and will only print lines relevant to your -application. This eliminates the framwork noise and helps to focus on your +application. This eliminates the framework noise and helps to focus on your code. However there are situations when you want to see the full backtrace. simply set the `BACKTRACE` environment variable to enable this behavior: -- cgit v1.2.3 From 67d4dc2bdb27648a61e153996d629b44834a9de4 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 3 Feb 2014 11:59:01 +0100 Subject: `rails_guides/helpers.rb` requires `YAML` to run. Resolve the following error when running `bundle exec rake guides:generate:html`: ``` /Users/senny/.rbenv/versions/2.0.0-p353/bin/ruby rails_guides.rb Generating 2_2_release_notes.md as 2_2_release_notes.html /Users/senny/Projects/rails/guides/rails_guides/helpers.rb:17:in `documents_by_section': uninitialized constant RailsGuides::Helpers::YAML (ActionView::Template::Error) from /Users/senny/Projects/rails/guides/rails_guides/helpers.rb:32:in `docs_for_menu' from /Users/senny/Projects/rails/guides/source/layout.html.erb:56:in `block in ___sers_senny__rojects_rails_guides_source_layout_html_erb___3094858039481335962_70118647133480' from /Users/senny/Projects/rails/guides/source/layout.html.erb:54:in `each' from /Users/senny/Projects/rails/guides/source/layout.html.erb:54:in `___sers_senny__rojects_rails_guides_source_layout_html_erb___3094858039481335962_70118647133480' from /Users/senny/Projects/rails/actionview/lib/action_view/template.rb:143:in `block in render' from /Users/senny/Projects/rails/activesupport/lib/active_support/notifications.rb:161:in `instrument' from /Users/senny/Projects/rails/actionview/lib/action_view/template.rb:337:in `instrument' from /Users/senny/Projects/rails/actionview/lib/action_view/template.rb:141:in `render' from /Users/senny/Projects/rails/actionview/lib/action_view/renderer/template_renderer.rb:61:in `render_with_layout' from /Users/senny/Projects/rails/actionview/lib/action_view/renderer/template_renderer.rb:47:in `render_template' from /Users/senny/Projects/rails/actionview/lib/action_view/renderer/template_renderer.rb:17:in `render' from /Users/senny/Projects/rails/actionview/lib/action_view/renderer/renderer.rb:42:in `render_template' from /Users/senny/Projects/rails/actionview/lib/action_view/renderer/renderer.rb:23:in `render' from /Users/senny/Projects/rails/actionview/lib/action_view/helpers/rendering_helper.rb:24:in `render' from /Users/senny/Projects/rails/guides/rails_guides/markdown.rb:160:in `render_page' from /Users/senny/Projects/rails/guides/rails_guides/markdown.rb:25:in `render' from /Users/senny/Projects/rails/guides/rails_guides/generator.rb:205:in `block in generate_guide' from /Users/senny/Projects/rails/guides/rails_guides/generator.rb:195:in `open' from /Users/senny/Projects/rails/guides/rails_guides/generator.rb:195:in `generate_guide' from /Users/senny/Projects/rails/guides/rails_guides/generator.rb:144:in `block in generate_guides' from /Users/senny/Projects/rails/guides/rails_guides/generator.rb:142:in `each' from /Users/senny/Projects/rails/guides/rails_guides/generator.rb:142:in `generate_guides' from /Users/senny/Projects/rails/guides/rails_guides/generator.rb:97:in `generate' from rails_guides.rb:63:in `
' rake aborted! Command failed with status (1): [/Users/senny/.rbenv/versions/2.0.0-p353/bi...] /Users/senny/Projects/rails/guides/Rakefile:11:in `block (3 levels) in ' Tasks: TOP => guides:generate:html (See full trace by running task with --trace) ``` --- guides/rails_guides/helpers.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guides/rails_guides/helpers.rb b/guides/rails_guides/helpers.rb index 760b196abd..169453400f 100644 --- a/guides/rails_guides/helpers.rb +++ b/guides/rails_guides/helpers.rb @@ -1,3 +1,5 @@ +require 'yaml' + module RailsGuides module Helpers def guide(name, url, options = {}, &block) -- cgit v1.2.3 From c46b0d1023490bdbfac6b0db6426b3bf8075ebeb Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 3 Feb 2014 11:55:16 +0100 Subject: docs, reference to ruby-lang.org. refs #13492. [ci skip] --- guides/source/getting_started.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 0aa0b49b15..bdb1a61bfb 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -84,21 +84,25 @@ Open up a command line prompt. On Mac OS X open Terminal.app, on Windows choose dollar sign `$` should be run in the command line. Verify that you have a current version of Ruby installed: +TIP. A number of tools exist to help you quickly install Ruby and Ruby +on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org), +while Mac OS X users can use [Rails One Click](http://railsoneclick.com). + ```bash $ ruby -v ruby 2.0.0p353 ``` +If you don't have Ruby installed have a look at +[ruby-lang.org](https://www.ruby-lang.org/en/downloads/) for possible ways to +install Ruby on your platform. + To install Rails, use the `gem install` command provided by RubyGems: ```bash $ gem install rails ``` -TIP. A number of tools exist to help you quickly install Ruby and Ruby -on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org), -while Mac OS X users can use [Rails One Click](http://railsoneclick.com). - To verify that you have everything installed correctly, you should be able to run the following: -- cgit v1.2.3 From b7c7cb1a6379f861c04a1273c67fa8b46212f527 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 3 Feb 2014 13:27:23 +0100 Subject: docs, revisit polymorphic associations with STI example. [ci skip] This is a follow up to #13926. /cc @fxn --- activerecord/lib/active_record/associations.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 051f7dff53..918806e8b4 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -669,11 +669,14 @@ module ActiveRecord # and member posts that use the posts table for STI. In this case, there must be a +type+ # column in the posts table. # + # Note: The attachable_type= method is being called when assigning an +attachable+. + # The +class_name+ of the +attachable+ is being passed as a String. + # # class Asset < ActiveRecord::Base # belongs_to :attachable, polymorphic: true # - # def attachable_type=(klass) - # super(klass.to_s.classify.constantize.base_class.to_s) + # def attachable_type=(class_name) + # super(class_name.constantize.base_class.to_s) # end # end # -- cgit v1.2.3 From 584a46479b3cbcac37b948a7fe38fa3acc71c35b Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 3 Feb 2014 14:48:59 +0100 Subject: pass `habtm :autosave` to underlying `hm:t` association. Closes #13923. --- activerecord/CHANGELOG.md | 7 +++++ activerecord/lib/active_record/associations.rb | 2 +- .../test/cases/autosave_association_test.rb | 33 +++++++++++++++++----- activerecord/test/models/pirate.rb | 1 + 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 78c0ab1cfd..fe488a45f9 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,10 @@ +* Pass `has_and_belongs_to_many` `:autosave` option to + the underlying `has_many :through` association. + + Fixes #13923. + + *Yves Senn* + * PostgreSQL implementation of SchemaStatements#index_name_exists? The database agnostic implementation does not detect with indexes that are diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 918806e8b4..86f5e6647b 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1585,7 +1585,7 @@ module ActiveRecord hm_options[:through] = middle_reflection.name hm_options[:source] = join_model.right_reflection.name - [:before_add, :after_add, :before_remove, :after_remove].each do |k| + [:before_add, :after_add, :before_remove, :after_remove, :autosave].each do |k| hm_options[k] = options[k] if options.key? k end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 703f805188..7a0c335627 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -1183,15 +1183,15 @@ module AutosaveAssociationOnACollectionAssociationTests end def test_should_default_invalid_error_from_i18n - I18n.backend.store_translations(:en, :activerecord => {:errors => { :models => - { @association_name.to_s.singularize.to_sym => { :blank => "cannot be blank" } } + I18n.backend.store_translations(:en, activerecord: {errors: { models: + { @associated_model_name.to_s.to_sym => { blank: "cannot be blank" } } }}) - @pirate.send(@association_name).build(:name => '') + @pirate.send(@association_name).build(name: '') assert !@pirate.valid? assert_equal ["cannot be blank"], @pirate.errors["#{@association_name}.name"] - assert_equal ["#{@association_name.to_s.titleize} name cannot be blank"], @pirate.errors.full_messages + assert_equal ["#{@association_name.to_s.humanize} name cannot be blank"], @pirate.errors.full_messages assert @pirate.errors[@association_name].empty? ensure I18n.backend = I18n::Backend::Simple.new @@ -1307,6 +1307,7 @@ class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase def setup super @association_name = :birds + @associated_model_name = :bird @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @child_1 = @pirate.birds.create(:name => 'Posideons Killer') @@ -1319,14 +1320,32 @@ end class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? + def setup + super + @association_name = :autosaved_parrots + @associated_model_name = :parrot + @habtm = true + + @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") + @child_1 = @pirate.parrots.create(name: 'Posideons Killer') + @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne') + end + + include AutosaveAssociationOnACollectionAssociationTests +end + +class TestAutosaveAssociationOnAHasAndBelongsToManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase + self.use_transactional_fixtures = false unless supports_savepoints? + def setup super @association_name = :parrots + @associated_model_name = :parrot @habtm = true - @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") - @child_1 = @pirate.parrots.create(:name => 'Posideons Killer') - @child_2 = @pirate.parrots.create(:name => 'Killer bandita Dionne') + @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?") + @child_1 = @pirate.parrots.create(name: 'Posideons Killer') + @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne') end include AutosaveAssociationOnACollectionAssociationTests diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index 170fc2ffe3..7bb0caf44b 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -13,6 +13,7 @@ class Pirate < ActiveRecord::Base :after_add => proc {|p,pa| p.ship_log << "after_adding_proc_parrot_#{pa.id || ''}"}, :before_remove => proc {|p,pa| p.ship_log << "before_removing_proc_parrot_#{pa.id}"}, :after_remove => proc {|p,pa| p.ship_log << "after_removing_proc_parrot_#{pa.id}"} + has_and_belongs_to_many :autosaved_parrots, class_name: "Parrot", autosave: true has_many :treasures, :as => :looter has_many :treasure_estimates, :through => :treasures, :source => :price_estimates -- cgit v1.2.3 From 06dde5fbf5a6a2446564d948578f8a138b5aebac Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 3 Feb 2014 14:52:01 +0100 Subject: some wording format changes. [ci skip] --- activerecord/CHANGELOG.md | 10 +++++----- activerecord/lib/active_record/associations.rb | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index fe488a45f9..272f05eb79 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -5,13 +5,13 @@ *Yves Senn* -* PostgreSQL implementation of SchemaStatements#index_name_exists? +* PostgreSQL implementation of `SchemaStatements#index_name_exists?`. The database agnostic implementation does not detect with indexes that are not supported by the ActiveRecord schema dumper. For example, expressions indexes would not be detected. - This fixes #11018. + Fixes #11018. *Jonathan Baudanza* @@ -884,7 +884,7 @@ *Severin Schoepke* -* `ActiveRecord::Store` works together with PG `hstore` columns. +* `ActiveRecord::Store` works together with PostgreSQL `hstore` columns. Fixes #12452. @@ -1170,7 +1170,7 @@ *Yves Senn* , *Severin Schoepke* -* Fix multidimensional PG arrays containing non-string items. +* Fix multidimensional PostgreSQL arrays containing non-string items. *Yves Senn* @@ -1188,7 +1188,7 @@ *Richard Schneeman* -* Removed redundant override of `xml` column definition for PG, +* Removed redundant override of `xml` column definition for PostgreSQL, in order to use `xml` column type instead of `text`. *Paul Nikitochkin*, *Michael Nikitochkin* diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 86f5e6647b..b5e21cbede 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -670,7 +670,7 @@ module ActiveRecord # column in the posts table. # # Note: The attachable_type= method is being called when assigning an +attachable+. - # The +class_name+ of the +attachable+ is being passed as a String. + # The +class_name+ of the +attachable+ is passed as a String. # # class Asset < ActiveRecord::Base # belongs_to :attachable, polymorphic: true -- cgit v1.2.3 From dd493d3b6f25147227db4c5d119d6b48c31f42e6 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 3 Feb 2014 16:42:02 +0100 Subject: docs, be clear that `options` is a hash. Closes #11904. [ci skip]. --- activesupport/lib/active_support/cache.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 53154aef27..2b7f5943b5 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -452,7 +452,7 @@ module ActiveSupport # Clear the entire cache. Be careful with this method since it could # affect other processes if shared cache is being used. # - # Options are passed to the underlying cache implementation. + # The options hash is passed to the underlying cache implementation. # # All implementations may not support this method. def clear(options = nil) -- cgit v1.2.3 From 9b66d6d47f87d31fb360f48542520d9216e77dc9 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Mon, 3 Feb 2014 15:29:26 -0500 Subject: Make sure transaction state resets after commit [fixes #12566] --- activerecord/lib/active_record/transactions.rb | 2 +- activerecord/test/cases/transaction_callbacks_test.rb | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index c33ffeece0..ec3e8f281b 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -295,7 +295,7 @@ module ActiveRecord def committed! #:nodoc: run_callbacks :commit if destroyed? || persisted? ensure - clear_transaction_record_state + @_start_transaction_state.clear end # Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 7e7d95841b..3d64ecb464 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -16,6 +16,11 @@ class TransactionCallbacksTest < ActiveRecord::TestCase after_commit :do_after_commit, on: :create + attr_accessor :save_on_after_create + after_create do + self.save! if save_on_after_create + end + def history @history ||= [] end @@ -107,6 +112,16 @@ class TransactionCallbacksTest < ActiveRecord::TestCase assert_equal [], reply.history end + def test_only_call_after_commit_on_create_and_doesnt_leaky + r = ReplyWithCallbacks.new(content: 'foo') + r.save_on_after_create = true + r.save! + r.content = 'bar' + r.save! + r.save! + assert_equal [:commit_on_create], r.history + end + def test_only_call_after_commit_on_update_after_transaction_commits_for_existing_record_on_touch add_transaction_execution_blocks @first -- cgit v1.2.3 From c71e96b90123c9be1f8835314893b373ffd09d0f Mon Sep 17 00:00:00 2001 From: Uday Kadaboina Date: Mon, 3 Feb 2014 19:49:59 -0500 Subject: Upgraded jquery-rails gem version --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 88f3686e95..d1eb460fbc 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ gem 'mocha', '~> 0.14', require: false gem 'rack-cache', '~> 1.2' gem 'bcrypt-ruby', '~> 3.1.2' -gem 'jquery-rails', '~> 2.2.0' +gem 'jquery-rails', '~> 3.1.0' gem 'turbolinks' gem 'coffee-rails', '~> 4.0.0' -- cgit v1.2.3 From b10f45ce7504448119c6fbd6c6d0b3d3e5e9d13c Mon Sep 17 00:00:00 2001 From: edogawaconan Date: Tue, 4 Feb 2014 11:46:59 +0900 Subject: Missing closing parenthesis. --- guides/source/i18n.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 8dfb17a681..d72717fa3b 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -214,7 +214,7 @@ This approach has almost the same set of advantages as setting the locale from t Getting the locale from `params` and setting it accordingly is not hard; including it in every URL and thus **passing it through the requests** is. To include an explicit option in every URL (e.g. `link_to( books_url(locale: I18n.locale))`) would be tedious and probably impossible, of course. -Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000515, which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000503) and helper methods dependent on it (by implementing/overriding this method). +Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000515), which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000503) and helper methods dependent on it (by implementing/overriding this method). We can include something like this in our `ApplicationController` then: -- cgit v1.2.3 From 7d196cf360321466c0eefc474bfad1be7e3ea7ab Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Tue, 4 Feb 2014 10:27:46 +0100 Subject: `#to_param` returns `nil` if `to_key` returns `nil`. Closes #11399. The documentation of `#to_key` (http://api.rubyonrails.org/classes/ActiveModel/Conversion.html#method-i-to_key) states that it returns `nil` if there are no key attributes. `to_param` needs to be aware of that fact and return `nil` as well. Previously it raised the following exception: ``` 1) Error: ConversionTest#test_to_param_returns_nil_if_to_key_is_nil: NoMethodError: undefined method `join' for nil:NilClass /Users/senny/Projects/rails/activemodel/lib/active_model/conversion.rb:65:in `to_param' /Users/senny/Projects/rails/activemodel/test/cases/conversion_test.rb:34:in `block in ' ``` --- activemodel/CHANGELOG.md | 4 ++++ activemodel/lib/active_model/conversion.rb | 2 +- activemodel/test/cases/conversion_test.rb | 10 ++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 6585808fa2..500d8dc42f 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,7 @@ +* `#to_param` returns `nil` if `#to_key` returns `nil`. Fixes #11399. + + *Yves Senn* + * Ability to specify multiple contexts when defining a validation. Example: diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index 6b0a752566..0a19ef686d 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -62,7 +62,7 @@ module ActiveModel # person = Person.create # person.to_param # => "1" def to_param - persisted? ? to_key.join('-') : nil + (persisted? && key = to_key) ? key.join('-') : nil end # Returns a +string+ identifying the path associated with the object. diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb index 3bb177591d..c5cfbf909d 100644 --- a/activemodel/test/cases/conversion_test.rb +++ b/activemodel/test/cases/conversion_test.rb @@ -24,6 +24,16 @@ class ConversionTest < ActiveModel::TestCase assert_equal "1", Contact.new(id: 1).to_param end + test "to_param returns nil if to_key is nil" do + klass = Class.new(Contact) do + def persisted? + true + end + end + + assert_nil klass.new.to_param + end + test "to_partial_path default implementation returns a string giving a relative path" do assert_equal "contacts/contact", Contact.new.to_partial_path assert_equal "helicopters/helicopter", Helicopter.new.to_partial_path, -- cgit v1.2.3 From c7abc516f8fd7ccc4ae2bb528404ffb8a46ba4ba Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Tue, 4 Feb 2014 13:37:11 +0100 Subject: docs, link MySQL manual for multi column indexes. [ci ckip]. Closes #9131. --- guides/source/active_record_validations.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index b04c7a90e2..0ea2b86bb4 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -575,7 +575,9 @@ This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, -you must create a unique index in your database. +you must create a unique index on both columns in your database. See +[the MySQL manual](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html) +for more details about multi column indexes. ```ruby class Account < ActiveRecord::Base -- cgit v1.2.3 From d3edf1ea82ea21b5e8a647a32179aba003eaad39 Mon Sep 17 00:00:00 2001 From: Greg Molnar Date: Tue, 4 Feb 2014 13:54:22 +0100 Subject: add missing sharp [ci skip] --- guides/source/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/testing.md b/guides/source/testing.md index 169fd75cfa..47d5f154c5 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -937,7 +937,7 @@ Here's a unit test to test a mailer named `UserMailer` whose action `invite` is require 'test_helper' class UserMailerTest < ActionMailer::TestCase - tests UserMailer + # tests UserMailer test "invite" do # Send the email, then test that it got queued email = UserMailer.create_invite('me@example.com', -- cgit v1.2.3 From 404cb36ee391ac7445fa90dfec17fbda524c6227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 4 Feb 2014 12:13:14 -0200 Subject: Add CHANGELOG entry for #13935 [ci skip] --- activerecord/CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 272f05eb79..d14b5b27f8 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,12 @@ +* Make sure transaction state gets reset after a commit operation on the record. + + If a new transaction was open inside a callback, the record was loosing track + of the transaction level state, and it was leaking that state. + + Fixes #12566. + + *arthurnn* + * Pass `has_and_belongs_to_many` `:autosave` option to the underlying `has_many :through` association. -- cgit v1.2.3 From e9bfa28b1ac610a1e5d6ef75b152e44a2754aa8e Mon Sep 17 00:00:00 2001 From: Logan Hasson Date: Tue, 4 Feb 2014 11:43:26 -0500 Subject: [ci skip] Add missing 'task' to note on Running Migrations --- guides/source/migrations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/migrations.md b/guides/source/migrations.md index 5d5c2724b1..64c4e1e07e 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -642,7 +642,7 @@ method for all the migrations that have not yet been run. If there are no such migrations, it exits. It will run these migrations in order based on the date of the migration. -Note that running the `db:migrate` also invokes the `db:schema:dump` task, which +Note that running the `db:migrate` task also invokes the `db:schema:dump` task, which will update your `db/schema.rb` file to match the structure of your database. If you specify a target version, Active Record will run the required migrations -- cgit v1.2.3 From f92142dce19906cd62382f7938cd748dd48b7d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 4 Feb 2014 15:27:57 -0200 Subject: Document that enum conditions must use the ordinal value [ci skip] --- activerecord/lib/active_record/enum.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 059bfe9a0f..4aa323fb00 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -62,6 +62,8 @@ module ActiveRecord # Use that class method when you need to know the ordinal value of an enum: # # Conversation.where("status <> ?", Conversation.statuses[:archived]) + # + # Where conditions on an enum attribute must use the ordinal value of an enum. module Enum DEFINED_ENUMS = {} # :nodoc: -- cgit v1.2.3 From 3ba0eeda02e85c312d2867afc4aa9afea50d93ec Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Wed, 5 Feb 2014 08:52:59 +0100 Subject: docs, AR already auto-detects primary keys. Closes #13946. [ci skip] This behavior was introduced since Rails 3.1 (207f266ccaaa9cd04cd2a7513ae5598c4358b510) but the docs were still out of date. --- .../active_record/connection_adapters/abstract/schema_statements.rb | 6 +++--- activerecord/test/cases/primary_keys_test.rb | 6 +++++- activerecord/test/models/mixed_case_monkey.rb | 2 -- 3 files changed, 8 insertions(+), 6 deletions(-) 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 88bf15bc18..ad069f5e53 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -120,9 +120,9 @@ module ActiveRecord # The name of the primary key, if one is to be added automatically. # Defaults to +id+. If :id is false this option is ignored. # - # Also note that this just sets the primary key in the table. You additionally - # need to configure the primary key in the model via +self.primary_key=+. - # Models do NOT auto-detect the primary key from their table definition. + # Note that Active Record models will automatically detect their + # primary key. This can be avoided by using +self.primary_key=+ on the model + # to define the key explicitly. # # [:options] # Any extra options you want appended to the table definition. diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 1b915387be..51ddd406ed 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -180,6 +180,11 @@ class PrimaryKeysTest < ActiveRecord::TestCase assert !col1.equal?(col2) end end + + def test_auto_detect_primary_key_from_schema + MixedCaseMonkey.reset_primary_key + assert_equal "monkeyID", MixedCaseMonkey.primary_key + end end class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase @@ -214,4 +219,3 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter) end end end - diff --git a/activerecord/test/models/mixed_case_monkey.rb b/activerecord/test/models/mixed_case_monkey.rb index 4d37371777..1c35006665 100644 --- a/activerecord/test/models/mixed_case_monkey.rb +++ b/activerecord/test/models/mixed_case_monkey.rb @@ -1,5 +1,3 @@ class MixedCaseMonkey < ActiveRecord::Base - self.primary_key = 'monkeyID' - belongs_to :man end -- cgit v1.2.3 From 38777949f0796d5bb64e3e7470057b376948873d Mon Sep 17 00:00:00 2001 From: Vajrasky Kok Date: Wed, 5 Feb 2014 16:51:34 +0800 Subject: Fixed typo in column_definition_test.rb. --- activerecord/test/cases/column_definition_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index dbb2f223cd..c7b64f29c3 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -82,7 +82,7 @@ module ActiveRecord assert_equal "", not_null_text_column.default end - def test_has_default_should_return_false_for_blog_and_test_data_types + def test_has_default_should_return_false_for_blob_and_text_data_types blob_column = MysqlAdapter::Column.new("title", nil, "blob") assert !blob_column.has_default? @@ -116,7 +116,7 @@ module ActiveRecord assert_equal "", not_null_text_column.default end - def test_has_default_should_return_false_for_blog_and_test_data_types + def test_has_default_should_return_false_for_blob_and_text_data_types blob_column = Mysql2Adapter::Column.new("title", nil, "blob") assert !blob_column.has_default? -- cgit v1.2.3 From 128fa3b9a410d2b5b05f24b931d16d7e46865817 Mon Sep 17 00:00:00 2001 From: Calvin Tam Date: Wed, 5 Feb 2014 19:55:06 +1100 Subject: Fixed typos [ci skip] --- guides/source/configuring.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 669086edbe..4728bdcb27 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -570,7 +570,7 @@ $ rails runner 'puts ActiveRecord::Base.connections' Since pool is not in the `ENV['DATABASE_URL']` provided connection information its information is merged in. Since `adapter` is duplicate, the `ENV['DATABASE_URL']` connection information wins. -The only way to explicitly not use the connection information in `ENV['DATABASE_URL']` is to specify an explicit URL connectinon using the `"url"` sub key: +The only way to explicitly not use the connection information in `ENV['DATABASE_URL']` is to specify an explicit URL connection using the `"url"` sub key: ``` $ cat config/database.yml @@ -716,7 +716,7 @@ Rails will now prepend "/app1" when generating links. #### Using Passenger -Passenger makes it easiy to run your application in a subdirectory. You can find +Passenger makes it easily to run your application in a subdirectory. You can find the relevant configuration in the [passenger manual](http://www.modrails.com/documentation/Users%20guide%20Apache.html#deploying_rails_to_sub_uri). -- cgit v1.2.3 From 10867a77bef192ddbbf42331292a5f66eaafc1de Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Wed, 5 Feb 2014 11:14:24 +0100 Subject: Removed unused commented code [ci skip] As discussed in #13940 this can be removed We can document this somewhere else in testing guides. --- guides/source/testing.md | 1 - 1 file changed, 1 deletion(-) diff --git a/guides/source/testing.md b/guides/source/testing.md index 47d5f154c5..408c072575 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -937,7 +937,6 @@ Here's a unit test to test a mailer named `UserMailer` whose action `invite` is require 'test_helper' class UserMailerTest < ActionMailer::TestCase - # tests UserMailer test "invite" do # Send the email, then test that it got queued email = UserMailer.create_invite('me@example.com', -- cgit v1.2.3 From 1df4dcf7dd9ec5cf8fed3c04d9427ad925f2b83b Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Wed, 5 Feb 2014 15:16:28 +0100 Subject: remove gemnasium batch from README. [ci skip] The rails/rails gemnasium page (https://gemnasium.com/rails/rails) is not helping much. It lists 0 dependencies so there is not much to track. Also our gems mostly depend on other gems we control. There is not much point in monitoring them. The batch was introduced with #3909. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index fcdcc2d87c..cf93453d3a 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,6 @@ We encourage you to contribute to Ruby on Rails! Please check out the ## Code Status * [![Build Status](https://api.travis-ci.org/rails/rails.png)](https://travis-ci.org/rails/rails) -* [![Dependencies](https://gemnasium.com/rails/rails.png?travis)](https://gemnasium.com/rails/rails) ## License -- cgit v1.2.3 From 4c3e11d30a9295404c54646a5d0c8a47991c35d4 Mon Sep 17 00:00:00 2001 From: Marc-Andre Lafortune Date: Tue, 4 Feb 2014 01:51:21 -0500 Subject: Remove obsolete line (was needed for Ruby 1.8.7 support) --- activesupport/test/core_ext/enumerable_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 6781e3c20e..6c32622e71 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -8,7 +8,6 @@ class SummablePayment < Payment end class EnumerableTests < ActiveSupport::TestCase - Enumerator = [].each.class class GenericEnumerable include Enumerable -- cgit v1.2.3 From 39b2cc1900b0cb10057d46d7e2ed54f8e0b1e26f Mon Sep 17 00:00:00 2001 From: Marc-Andre Lafortune Date: Tue, 4 Feb 2014 01:57:27 -0500 Subject: Remove obsolete test (builtin group_by is now used) --- activesupport/test/core_ext/enumerable_test.rb | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 6c32622e71..6b3e8364c5 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -20,26 +20,6 @@ class EnumerableTests < ActiveSupport::TestCase end end - def test_group_by - names = %w(marcel sam david jeremy) - klass = Struct.new(:name) - objects = (1..50).map do - klass.new names.sample - end - - enum = GenericEnumerable.new(objects) - grouped = enum.group_by { |object| object.name } - - grouped.each do |name, group| - assert group.all? { |person| person.name == name } - end - - assert_equal objects.uniq.map(&:name), grouped.keys - assert({}.merge(grouped), "Could not convert ActiveSupport::OrderedHash into Hash") - assert_equal Enumerator, enum.group_by.class - assert_equal grouped, enum.group_by.each(&:name) - end - def test_sums enum = GenericEnumerable.new([5, 15, 10]) assert_equal 30, enum.sum -- cgit v1.2.3 From 4499ab5fdeca46416cfc4a50a376eba4eb8e16b4 Mon Sep 17 00:00:00 2001 From: Marc-Andre Lafortune Date: Tue, 4 Feb 2014 01:47:40 -0500 Subject: Strengthen test with different nb of rows and columns --- activerecord/test/cases/result_test.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/activerecord/test/cases/result_test.rb b/activerecord/test/cases/result_test.rb index b6c583dbf5..edaeb85211 100644 --- a/activerecord/test/cases/result_test.rb +++ b/activerecord/test/cases/result_test.rb @@ -5,14 +5,16 @@ module ActiveRecord def result Result.new(['col_1', 'col_2'], [ ['row 1 col 1', 'row 1 col 2'], - ['row 2 col 1', 'row 2 col 2'] + ['row 2 col 1', 'row 2 col 2'], + ['row 3 col 1', 'row 3 col 2'], ]) end def test_to_hash_returns_row_hashes assert_equal [ {'col_1' => 'row 1 col 1', 'col_2' => 'row 1 col 2'}, - {'col_1' => 'row 2 col 1', 'col_2' => 'row 2 col 2'} + {'col_1' => 'row 2 col 1', 'col_2' => 'row 2 col 2'}, + {'col_1' => 'row 3 col 1', 'col_2' => 'row 3 col 2'}, ], result.to_hash end -- cgit v1.2.3 From d37f395be86d131d0218dadd189771f99b06874f Mon Sep 17 00:00:00 2001 From: Marc-Andre Lafortune Date: Wed, 29 Jan 2014 14:53:54 -0500 Subject: Return sized enumerator from Batches#find_in_batches --- activerecord/CHANGELOG.md | 6 ++++++ activerecord/lib/active_record/relation/batches.rb | 13 +++++++++---- activerecord/test/cases/batches_test.rb | 10 ++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index d14b5b27f8..4bc31457c0 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,9 @@ +* `find_in_batches` now returns an `Enumerator` that can calculate its size. + + See also #13938. + + *Marc-André Lafortune* + * Make sure transaction state gets reset after a commit operation on the record. If a new transaction was open inside a callback, the record was loosing track diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 666cef80a9..a06da659cb 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -96,17 +96,22 @@ module ActiveRecord # the batch sizes. def find_in_batches(options = {}) options.assert_valid_keys(:start, :batch_size) - return to_enum(:find_in_batches, options) unless block_given? relation = self + start = options[:start] + batch_size = options[:batch_size] || 1000 + + unless block_given? + return to_enum(:find_in_batches, options) do + total = start ? where(table[primary_key].gteq(start)).size : size + (total - 1).div(batch_size) + 1 + end + end if logger && (arel.orders.present? || arel.taken.present?) logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size") end - start = options[:start] - batch_size = options[:batch_size] || 1000 - relation = relation.reorder(batch_order).limit(batch_size) records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 8216d74cb3..4ee09a640e 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -191,4 +191,14 @@ class EachTest < ActiveRecord::TestCase end end end + + if Enumerator.method_defined? :size + def test_find_in_batches_should_return_a_sized_enumerator + assert_equal 11, Post.find_in_batches(:batch_size => 1).size + assert_equal 6, Post.find_in_batches(:batch_size => 2).size + assert_equal 4, Post.find_in_batches(:batch_size => 2, :start => 4).size + assert_equal 4, Post.find_in_batches(:batch_size => 3).size + assert_equal 1, Post.find_in_batches(:batch_size => 10_000).size + end + end end -- cgit v1.2.3 From 13d2696c10726afecd393753fcac88c5a9907d8c Mon Sep 17 00:00:00 2001 From: Marc-Andre Lafortune Date: Wed, 29 Jan 2014 15:42:07 -0500 Subject: Return sized enumerator from Batches#find_each --- activerecord/CHANGELOG.md | 8 +++++++- activerecord/lib/active_record/relation/batches.rb | 4 +++- activerecord/lib/active_record/result.rb | 2 +- activerecord/test/cases/batches_test.rb | 8 ++++++++ activerecord/test/cases/result_test.rb | 6 ++++++ activesupport/lib/active_support/core_ext/enumerable.rb | 2 +- 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 4bc31457c0..af723c61bd 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,4 +1,10 @@ -* `find_in_batches` now returns an `Enumerator` that can calculate its size. +<<<<<<< HEAD +* `find_in_batches`, `find_each` now + return an `Enumerator` that can calculate its size. +======= +* `find_in_batches`, `find_each`, `Result#each` now returns an `Enumerator` + that can calculate its size. +>>>>>>> 5863938... Return sized enumerator from Result#each See also #13938. diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index a06da659cb..29fc150b3d 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -52,7 +52,9 @@ module ActiveRecord records.each { |record| yield record } end else - enum_for :find_each, options + enum_for :find_each, options do + options[:start] ? where(table[primary_key].gteq(options[:start])).size : size + end end end diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index 469451e2f4..228b2aa60f 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -54,7 +54,7 @@ module ActiveRecord if block_given? hash_rows.each { |row| yield row } else - hash_rows.to_enum + hash_rows.to_enum { @rows.size } end end diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 4ee09a640e..c12fa03015 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -35,6 +35,14 @@ class EachTest < ActiveRecord::TestCase end end + if Enumerator.method_defined? :size + def test_each_should_return_a_sized_enumerator + assert_equal 11, Post.find_each(:batch_size => 1).size + assert_equal 5, Post.find_each(:batch_size => 2, :start => 7).size + assert_equal 11, Post.find_each(:batch_size => 10_000).size + end + end + def test_each_enumerator_should_execute_one_query_per_batch assert_queries(@total + 1) do Post.find_each(:batch_size => 1).with_index do |post, index| diff --git a/activerecord/test/cases/result_test.rb b/activerecord/test/cases/result_test.rb index edaeb85211..2131b32a0c 100644 --- a/activerecord/test/cases/result_test.rb +++ b/activerecord/test/cases/result_test.rb @@ -30,5 +30,11 @@ module ActiveRecord assert_kind_of Integer, index end end + + if Enumerator.method_defined? :size + def test_each_without_block_returns_a_sized_enumerator + assert_equal 3, result.each.size + end + end end end diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 4501b7ff58..a2dec41c87 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -35,7 +35,7 @@ module Enumerable if block_given? Hash[map { |elem| [yield(elem), elem] }] else - to_enum :index_by + to_enum(:index_by) { size } end end -- cgit v1.2.3 From a476020567a47f5fbec3629707d5bf31b400a284 Mon Sep 17 00:00:00 2001 From: Marc-Andre Lafortune Date: Tue, 4 Feb 2014 02:18:03 -0500 Subject: Return sized enumerator from Enumerable#index_by --- activerecord/CHANGELOG.md | 7 +------ activesupport/lib/active_support/core_ext/enumerable.rb | 2 +- activesupport/test/core_ext/enumerable_test.rb | 4 ++++ 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index af723c61bd..f1d51b9de1 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,10 +1,5 @@ -<<<<<<< HEAD -* `find_in_batches`, `find_each` now +* `find_in_batches`, `find_each`, `Result#each` and `Enumerable#index_by` now return an `Enumerator` that can calculate its size. -======= -* `find_in_batches`, `find_each`, `Result#each` now returns an `Enumerator` - that can calculate its size. ->>>>>>> 5863938... Return sized enumerator from Result#each See also #13938. diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index a2dec41c87..1343beb87a 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -35,7 +35,7 @@ module Enumerable if block_given? Hash[map { |elem| [yield(elem), elem] }] else - to_enum(:index_by) { size } + to_enum(:index_by) { size if respond_to?(:size) } end end diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 6b3e8364c5..6fcf6e8743 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -73,6 +73,10 @@ class EnumerableTests < ActiveSupport::TestCase assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) }, payments.index_by { |p| p.price }) assert_equal Enumerator, payments.index_by.class + if Enumerator.method_defined? :size + assert_equal nil, payments.index_by.size + assert_equal 42, (1..42).index_by.size + end assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) }, payments.index_by.each { |p| p.price }) end -- cgit v1.2.3 From 26698fb91d88dca0f860adcb80528d8d3f0f6285 Mon Sep 17 00:00:00 2001 From: Sean Walbran Date: Wed, 5 Feb 2014 17:19:51 -0600 Subject: fix interplay of humanize and html_escape --- activesupport/lib/active_support/inflector/methods.rb | 2 +- activesupport/test/core_ext/string_ext_test.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index cdee4c2ca5..b642d87d76 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -117,7 +117,7 @@ module ActiveSupport result.gsub!(/([a-z\d]*)/i) { |match| "#{inflections.acronyms[match] || match.downcase}" } - result.gsub!(/^\w/) { $&.upcase } if options.fetch(:capitalize, true) + result.gsub!(/^\w/) { |match| match.upcase } if options.fetch(:capitalize, true) result end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index d4f8ba8cdd..072b970a2d 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -161,6 +161,10 @@ class StringInflectionsTest < ActiveSupport::TestCase end end + def test_humanize_with_html_escape + assert_equal 'Hello', ERB::Util.html_escape("hello").humanize + end + def test_ord assert_equal 97, 'a'.ord assert_equal 97, 'abc'.ord -- cgit v1.2.3 From 326e6527497126b2ea3627e377b6a4b5c9191bef Mon Sep 17 00:00:00 2001 From: Hincu Petru Date: Mon, 3 Feb 2014 09:51:05 +0000 Subject: Fixed "Hash#to_param confused by empty hash values #13892" --- activesupport/lib/active_support/core_ext/object/to_param.rb | 1 + activesupport/test/core_ext/object/to_param_test.rb | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb index 3b137ce6ae..e40846e7d6 100644 --- a/activesupport/lib/active_support/core_ext/object/to_param.rb +++ b/activesupport/lib/active_support/core_ext/object/to_param.rb @@ -51,6 +51,7 @@ class Hash # # This method is also aliased as +to_query+. def to_param(namespace = nil) + return (namespace ? nil.to_query(namespace) : '') if empty? collect do |key, value| value.to_query(namespace ? "#{namespace}[#{key}]" : key) end.sort! * '&' diff --git a/activesupport/test/core_ext/object/to_param_test.rb b/activesupport/test/core_ext/object/to_param_test.rb index bd7c6c422a..eae68ed184 100644 --- a/activesupport/test/core_ext/object/to_param_test.rb +++ b/activesupport/test/core_ext/object/to_param_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require 'active_support/core_ext/object/to_param' +require 'active_support/core_ext/object/to_query' class ToParamTest < ActiveSupport::TestCase def test_object @@ -16,4 +17,14 @@ class ToParamTest < ActiveSupport::TestCase assert_equal true, true.to_param assert_equal false, false.to_param end + + def test_nested_empty_hash + hash1 = {a: 1, b: {c: 3, d: {}}}.to_param + hash2 = {p: 12, b: {c: 3, e: nil, f: ''}}.to_param + hash3 = {b: {c: 3, k: {}, f: '' }}.to_param + + assert_equal 'a=1&b[c]=3&b[d]=', CGI::unescape(hash1) + assert_equal 'b[c]=3&b[e]=&b[f]=&p=12', CGI::unescape(hash2) + assert_equal 'b[c]=3&b[f]=&b[k]=', CGI::unescape(hash3) + end end -- cgit v1.2.3 From 88b064dfae924f28ec4750ea5a3a91b855481546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 5 Feb 2014 23:42:43 -0200 Subject: Move test to the right file --- activesupport/test/core_ext/object/to_param_test.rb | 11 ----------- activesupport/test/core_ext/object/to_query_test.rb | 9 +++++++++ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/activesupport/test/core_ext/object/to_param_test.rb b/activesupport/test/core_ext/object/to_param_test.rb index eae68ed184..bd7c6c422a 100644 --- a/activesupport/test/core_ext/object/to_param_test.rb +++ b/activesupport/test/core_ext/object/to_param_test.rb @@ -1,6 +1,5 @@ require 'abstract_unit' require 'active_support/core_ext/object/to_param' -require 'active_support/core_ext/object/to_query' class ToParamTest < ActiveSupport::TestCase def test_object @@ -17,14 +16,4 @@ class ToParamTest < ActiveSupport::TestCase assert_equal true, true.to_param assert_equal false, false.to_param end - - def test_nested_empty_hash - hash1 = {a: 1, b: {c: 3, d: {}}}.to_param - hash2 = {p: 12, b: {c: 3, e: nil, f: ''}}.to_param - hash3 = {b: {c: 3, k: {}, f: '' }}.to_param - - assert_equal 'a=1&b[c]=3&b[d]=', CGI::unescape(hash1) - assert_equal 'b[c]=3&b[e]=&b[f]=&p=12', CGI::unescape(hash2) - assert_equal 'b[c]=3&b[f]=&b[k]=', CGI::unescape(hash3) - end end diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb index 92f996f9a4..a53d7781f9 100644 --- a/activesupport/test/core_ext/object/to_query_test.rb +++ b/activesupport/test/core_ext/object/to_query_test.rb @@ -46,6 +46,15 @@ class ToQueryTest < ActiveSupport::TestCase :person => {:id => [20, 10]} end + def test_nested_empty_hash + assert_query_equal 'a=1&b%5Bc%5D=3&b%5Bd%5D=', + { a: 1, b: { c: 3, d: {} } } + assert_query_equal 'b%5Bc%5D=3&b%5Be%5D=&b%5Bf%5D=&p=12', + { p: 12, b: { c: 3, e: nil, f: '' } } + assert_query_equal 'b%5Bc%5D=3&b%5Bf%5D=&b%5Bk%5D=', + { b: { c: 3, k: {}, f: '' } } + end + private def assert_query_equal(expected, actual) assert_equal expected.split('&'), actual.to_query.split('&') -- cgit v1.2.3 From d6e3fd775b8a0277b07b06c22c29f66dbafe6559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 5 Feb 2014 23:47:16 -0200 Subject: Test with a blank value --- activesupport/test/core_ext/object/to_query_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb index a53d7781f9..51275e36bf 100644 --- a/activesupport/test/core_ext/object/to_query_test.rb +++ b/activesupport/test/core_ext/object/to_query_test.rb @@ -49,8 +49,8 @@ class ToQueryTest < ActiveSupport::TestCase def test_nested_empty_hash assert_query_equal 'a=1&b%5Bc%5D=3&b%5Bd%5D=', { a: 1, b: { c: 3, d: {} } } - assert_query_equal 'b%5Bc%5D=3&b%5Be%5D=&b%5Bf%5D=&p=12', - { p: 12, b: { c: 3, e: nil, f: '' } } + assert_query_equal 'b%5Bc%5D=false&b%5Be%5D=&b%5Bf%5D=&p=12', + { p: 12, b: { c: false, e: nil, f: '' } } assert_query_equal 'b%5Bc%5D=3&b%5Bf%5D=&b%5Bk%5D=', { b: { c: 3, k: {}, f: '' } } end -- cgit v1.2.3 From ab51b285e2cccdc0cbdcd2daa04a7fd2fbb661ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 5 Feb 2014 23:55:17 -0200 Subject: Refatoring the method to avoid shot-circuit return --- activesupport/lib/active_support/core_ext/object/to_param.rb | 11 +++++++---- activesupport/test/core_ext/object/to_query_test.rb | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb index e40846e7d6..13be0038c2 100644 --- a/activesupport/lib/active_support/core_ext/object/to_param.rb +++ b/activesupport/lib/active_support/core_ext/object/to_param.rb @@ -51,9 +51,12 @@ class Hash # # This method is also aliased as +to_query+. def to_param(namespace = nil) - return (namespace ? nil.to_query(namespace) : '') if empty? - collect do |key, value| - value.to_query(namespace ? "#{namespace}[#{key}]" : key) - end.sort! * '&' + if empty? + namespace ? nil.to_query(namespace) : '' + else + collect do |key, value| + value.to_query(namespace ? "#{namespace}[#{key}]" : key) + end.sort! * '&' + end end end diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb index 51275e36bf..a892471e0f 100644 --- a/activesupport/test/core_ext/object/to_query_test.rb +++ b/activesupport/test/core_ext/object/to_query_test.rb @@ -47,6 +47,8 @@ class ToQueryTest < ActiveSupport::TestCase end def test_nested_empty_hash + assert_equal '', + {}.to_query assert_query_equal 'a=1&b%5Bc%5D=3&b%5Bd%5D=', { a: 1, b: { c: 3, d: {} } } assert_query_equal 'b%5Bc%5D=false&b%5Be%5D=&b%5Bf%5D=&p=12', -- cgit v1.2.3 From 7aa4b7dc6bd6ecea1f5009c5549ea69dd59d96d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 5 Feb 2014 23:55:50 -0200 Subject: Add CHANGELOG entry Closes #13909 --- activesupport/CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index fa2f2384f9..0dc74ecd8c 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,17 @@ +* Fix `to_param` behavior when there are nested empty hashes. + + Before: + + params = {c: 3, d: {}}.to_param # => "&c=3" + + After: + + params = {c: 3, d: {}}.to_param # => "c=3&d=" + + Fixes #13892. + + *Hincu Petru* + * Deprecate custom `BigDecimal` serialization Deprecate the custom `BigDecimal` serialization that is included when requiring -- cgit v1.2.3 From c82dbc6a1c1a269fb21de8fd2722fc680ab7ea54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 6 Feb 2014 01:03:41 -0200 Subject: Fix to_query with empty arrays too --- activesupport/lib/active_support/core_ext/object/to_query.rb | 7 ++++++- activesupport/test/core_ext/object/to_query_test.rb | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb index 5d5fcf00e0..37352fa608 100644 --- a/activesupport/lib/active_support/core_ext/object/to_query.rb +++ b/activesupport/lib/active_support/core_ext/object/to_query.rb @@ -18,7 +18,12 @@ class Array # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding" def to_query(key) prefix = "#{key}[]" - collect { |value| value.to_query(prefix) }.join '&' + + if empty? + nil.to_query(prefix) + else + collect { |value| value.to_query(prefix) }.join '&' + end end end diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb index a892471e0f..f887a9e613 100644 --- a/activesupport/test/core_ext/object/to_query_test.rb +++ b/activesupport/test/core_ext/object/to_query_test.rb @@ -55,6 +55,8 @@ class ToQueryTest < ActiveSupport::TestCase { p: 12, b: { c: false, e: nil, f: '' } } assert_query_equal 'b%5Bc%5D=3&b%5Bf%5D=&b%5Bk%5D=', { b: { c: 3, k: {}, f: '' } } + assert_query_equal 'a%5B%5D=&b=3', + {a: [], b: 3} end private -- cgit v1.2.3 From b9ead0feb0d8d82d549e481d4be25418e3b1b03d Mon Sep 17 00:00:00 2001 From: dpmehta02 Date: Wed, 5 Feb 2014 02:26:24 -0800 Subject: update contribution doc grammar. [ci skip] - Manually applied from #13951. - Discussion at #13947. - Removed trailing whitespace from https://github.com/dpmehta02/rails/commit/18044e86af93672dfc38befbe974261e87b3518d /cc @dpmehta02 --- guides/source/contributing_to_ruby_on_rails.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 814237ba22..af2305f1b7 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -201,7 +201,8 @@ If your comment simply says "+1", then odds are that other reviewers aren't goin Contributing to the Rails Documentation --------------------------------------- -Ruby on Rails has two main sets of documentation: the guides help you in learning about Ruby on Rails, and the API is a reference. +Ruby on Rails has two main sets of documentation: the guides, which help you +learn about Ruby on Rails, and the API, which serves as a reference. You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see [Translating Rails Guides](https://wiki.github.com/rails/docrails/translating-rails-guides). @@ -258,10 +259,10 @@ more if the source code is mounted in `/vagrant` as happens in the recommended workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box). As a compromise, test what your code obviously affects, and if the change is -not in railties run the whole test suite of the affected component. If all is -green that's enough to propose your contribution. We have [Travis CI](https://travis-ci.org/rails/rails) -as a safety net for catching unexpected breakages -elsewhere. +not in railties, run the whole test suite of the affected component. If all +tests are passing, that's enough to propose your contribution. We have +[Travis CI](https://travis-ci.org/rails/rails) as a safety net for catching +unexpected breakages elsewhere. TIP: Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted. -- cgit v1.2.3 From 580f0b61dc99c6854fa930a761d28a3ab08163f7 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Thu, 6 Feb 2014 11:43:16 +0100 Subject: synchronize 4.1 release notes with CHANGELOGS. [ci skip] /cc @chancancode --- actionpack/CHANGELOG.md | 2 +- activerecord/CHANGELOG.md | 22 ++++++----- activesupport/CHANGELOG.md | 5 ++- guides/source/4_1_release_notes.md | 81 ++++++++++++++++++++++++++++++++++++-- railties/CHANGELOG.md | 2 +- 5 files changed, 95 insertions(+), 17 deletions(-) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 911c916028..b63fb4a9b6 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -91,7 +91,7 @@ *Andrew White* -* Show full route constraints in error message +* Show full route constraints in error message. When an optimized helper fails to generate, show the full route constraints in the error message. Previously it would only show the contraints that were diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f1d51b9de1..e06ed5764e 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -59,14 +59,16 @@ *Lauro Caetano* * Fix regressions on `select_*` methods. - When `select_*` methods receive a `Relation` object, they should be able to get the arel/binds from it. + When `select_*` methods receive a `Relation` object, they should be able to + get the arel/binds from it. Also fix regressions on `select_rows` that was ignoring the binds. Fixes #7538, #12017, #13731, #12056. *arthurnn* -* Active Record objects can now be correctly dumped, loaded and dumped again without issues. +* Active Record objects can now be correctly dumped, loaded and dumped again + without issues. Previously, if you did `YAML.dump`, `YAML.load` and then `YAML.dump` again in an Active Record model that used serialization it would fail at the last @@ -154,12 +156,14 @@ *Rafael Mendonça França* -* `has_one` and `belongs_to` accessors don't add ORDER BY to the queries anymore. +* `has_one` and `belongs_to` accessors don't add ORDER BY to the queries + anymore. - Since Rails 4.0, we add an ORDER BY in the `first` method to ensure consistent results - among different database engines. But for singular associations this behavior is not needed - since we will have one record to return. As this ORDER BY option can lead some performance - issues we are removing it for singular associations accessors. + Since Rails 4.0, we add an ORDER BY in the `first` method to ensure + consistent results among different database engines. But for singular + associations this behavior is not needed since we will have one record to + return. As this ORDER BY option can lead some performance issues we are + removing it for singular associations accessors. Fixes #12623. @@ -227,7 +231,7 @@ *Harry Brundage* -* Enable partial indexes for sqlite >= 3.8.0 +* Enable partial indexes for `sqlite >= 3.8.0`. See http://www.sqlite.org/partialindex.html @@ -355,7 +359,7 @@ This ensures that `change_table` and `create_table` will use similar objects. - Fixes #13577 and #13503. + Fixes #13577, #13503. *Nishant Modak*, *Prathamesh Sonpatki*, *Rafael Mendonça França* diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 0dc74ecd8c..9a62bd5a77 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -12,7 +12,7 @@ *Hincu Petru* -* Deprecate custom `BigDecimal` serialization +* Deprecate custom `BigDecimal` serialization. Deprecate the custom `BigDecimal` serialization that is included when requiring `active_support/all` as a fix for #12467. Let Ruby handle YAML serialization @@ -76,7 +76,8 @@ *Andrew White* -* Added `Hash#compact` and `Hash#compact!` for removing items with nil value from hash. +* Added `Hash#compact` and `Hash#compact!` for removing items with nil value + from hash. *Celestino Gomes* diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 4e75bf400c..170b071df8 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -175,6 +175,8 @@ conversation.active? # => false conversation.status # => "archived" Conversation.archived # => Relation for all archived Conversations + +Conversation.statuses # => { "active" => 0, "archived" => 1 } ``` See its @@ -241,6 +243,7 @@ unless they use `xhr`. Upgrade your tests to be explicit about expecting XmlHttpRequests. Instead of `post :create, format: :js`, switch to the explicit `xhr :post, :create, format: :js`. + Railties -------- @@ -278,7 +281,7 @@ for detailed changes. * Exposed `MiddlewareStack#unshift` to environment configuration. ([Pull Request](https://github.com/rails/rails/pull/12479)) -* Add `Application#message_verifier` method to return a message +* Added `Application#message_verifier` method to return a message verifier. ([Pull Request](https://github.com/rails/rails/pull/12995)) * The `test_help.rb` file which is required by the default generated test @@ -288,6 +291,7 @@ for detailed changes. with `config.active_record.maintain_test_schema = false`. ([Pull Request](https://github.com/rails/rails/pull/13528)) + Action Pack ----------- @@ -335,6 +339,18 @@ for detailed changes. * Separated Action View completely from Action Pack. ([Pull Request](https://github.com/rails/rails/pull/11032)) +* Log which keys were affected by deep + munge. ([Pull Request](https://github.com/rails/rails/pull/13813)) + +* New config option `config.action_dispatch.perform_deep_munge` to opt out of + params "deep munging" that was used to address security vulnerability + CVE-2013-0155. ([Pull Request](https://github.com/rails/rails/pull/13188)) + +* Added `:serializer` option for `config.session_store :cookie_store`. This + changes default serializer when using + `:cookie_store`. ([Pull Request](https://github.com/rails/rails/pull/13692)) + + Action Mailer ------------- @@ -344,9 +360,13 @@ for detailed changes. ### Notable changes +* Added mailer previews feature based on 37 Signals mail_view + gem. ([Commit](https://github.com/rails/rails/commit/d6dec7fcb6b8fddf8c170182d4fe64ecfc7b2261)) + * Instrument the generation of Action Mailer messages. The time it takes to generate a message is written to the log. ([Pull Request](https://github.com/rails/rails/pull/12556)) + Active Record ------------- @@ -495,11 +515,32 @@ for detailed changes. object. Helper methods used by multiple fixtures should be defined on modules included in `ActiveRecord::FixtureSet.context_class`. ([Pull Request](https://github.com/rails/rails/pull/13022)) -* Don't create or drop the test database if RAILS_ENV is specified explicitly. +* Don't create or drop the test database if RAILS_ENV is specified + explicitly. ([Pull Request](https://github.com/rails/rails/pull/13629)) * `Relation` no longer has mutator methods like `#map!` and `#delete_if`. Convert to an `Array` by calling `#to_a` before using these methods. ([Pull Request](https://github.com/rails/rails/pull/13314)) +* `find_in_batches`, `find_each`, `Result#each` and `Enumerable#index_by` now + return an `Enumerator` that can calculate its + size. ([Pull Request](https://github.com/rails/rails/pull/13938)) + +* `scope` and `endum` now raise on "dangerous" name + conflicts. ([Pull Request](https://github.com/rails/rails/pull/13450)) + +* `second` through `fifth` methods act like the `first` + finder. ([Pull Request](https://github.com/rails/rails/pull/13757)) + +* Make `touch` fire the `after_commit` and `after_rollback` + callbacks. ([Pull Request](https://github.com/rails/rails/pull/12031)) + +* Enable partial indexes for `sqlite >= + 3.8.0`. ([Pull Request](https://github.com/rails/rails/pull/13350)) + +* Make `change_column_null` + revertable. ([Commit](https://github.com/rails/rails/commit/724509a9d5322ff502aefa90dd282ba33a281a96)) + + Active Model ------------ @@ -517,6 +558,13 @@ for detailed changes. * Added new API methods `reset_changes` and `changes_applied` to `ActiveModel::Dirty` that control changes state. +* Ability to specify multiple contexts when defining a + validation. ([Pull Request](https://github.com/rails/rails/pull/13754)) + +* `attribute_changed?` now accepts a hash to check if the attribute was changed + `:from` and/or `:to` a given + value. ([Pull Request](https://github.com/rails/rails/pull/13131)) + Active Support -------------- @@ -570,6 +618,9 @@ for detailed changes. * Remove deprecated `#filter` method for filter objects, use the corresponding method instead (e.g. `#before` for a before filter). +* Removed 'cow' => 'kine' irregular inflection from default + inflections. ([Commit](https://github.com/rails/rails/commit/c300dca9963bda78b8f358dbcb59cabcdc5e1dc9)) + ### Deprecations * Deprecated `Numeric#{ago,until,since,from_now}`, the user is expected to @@ -591,6 +642,9 @@ for detailed changes. ([Pull Request](https://github.com/rails/rails/pull/13060) / [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling)) +* Deprecate custom `BigDecimal` + serialization. ([Pull Request](https://github.com/rails/rails/pull/13911)) + ### Notable changes * `ActiveSupport`'s JSON encoder has been rewritten to take advantage of the @@ -620,11 +674,30 @@ for detailed changes. `at_middle_of_day` as aliases. ([Pull Request](https://github.com/rails/rails/pull/10879)) +* Added `Date#all_week/month/quarter/year` for generating date + ranges. ([Pull Request](https://github.com/rails/rails/pull/9685)) + +* Added `Time.zone.yesterday` and + `Time.zone.tomorrow`. ([Pull Request](https://github.com/rails/rails/pull/12822)) + * Added `String#remove(pattern)` as a short-hand for the common pattern of `String#gsub(pattern,'')`. ([Commit](https://github.com/rails/rails/commit/5da23a3f921f0a4a3139495d2779ab0d3bd4cb5f)) -* Removed 'cow' => 'kine' irregular inflection from default - inflections. ([Commit](https://github.com/rails/rails/commit/c300dca9963bda78b8f358dbcb59cabcdc5e1dc9)) +* Added `Hash#compact` and `Hash#compact!` for removing items with nil value + from hash. ([Pull Request](https://github.com/rails/rails/pull/13632)) + +* `blank?` and `present?` commit to return + singletons. ([Commit](https://github.com/rails/rails/commit/126dc47665c65cd129967cbd8a5926dddd0aa514)) + +* Default the new `I18n.enforce_available_locales` config to `true`, meaning + `I18n` will make sure that all locales passed to it must be declared in the + `available_locales` + list. ([Pull Request](https://github.com/rails/rails/commit/8e21ae37ad9fef6b7393a84f9b5f2e18a831e49a)) + +* Introduce Module#concerning: a natural, low-ceremony way to separate + responsibilities within a + class. ([Commit](https://github.com/rails/rails/commit/1eee0ca6de975b42524105a59e0521d18b38ab81)) + Credits ------- diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 4ac6a99662..5ea025924b 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,6 +1,6 @@ * Added Thor-action for creation of migrations. - Fixes #13588 and #12674. + Fixes #13588, #12674. *Gert Goet* -- cgit v1.2.3 From 7f648bc70e76b7a386a41f882ddd97105334f590 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Thu, 6 Feb 2014 12:18:46 +0100 Subject: docs, Associations also raise on name conflicts. [ci skip] Follow up to https://github.com/rails/rails/commit/580f0b61dc99c6854fa930a761d28a3ab08163f7#commitcomment-5293470 --- guides/source/4_1_release_notes.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 170b071df8..90e6b2fcbc 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -525,8 +525,9 @@ for detailed changes. return an `Enumerator` that can calculate its size. ([Pull Request](https://github.com/rails/rails/pull/13938)) -* `scope` and `endum` now raise on "dangerous" name - conflicts. ([Pull Request](https://github.com/rails/rails/pull/13450)) +* `scope`, `enum` and Associations now raise on "dangerous" name + conflicts. ([Pull Request](https://github.com/rails/rails/pull/13450), + [Pull Request](https://github.com/rails/rails/pull/13896)) * `second` through `fifth` methods act like the `first` finder. ([Pull Request](https://github.com/rails/rails/pull/13757)) -- cgit v1.2.3 From 8806768e9f1a2648085f7826d9a0032457182bdb Mon Sep 17 00:00:00 2001 From: Emil Soman Date: Wed, 5 Feb 2014 13:02:38 +0530 Subject: Add config to disable schema dump after migration * Add a config on Active Record named `dump_schema_after_migration` * Schema dump doesn't happen if the config is set to false * Set default value of the config to true * Set config in generated production environment file to false * Update configuration guide * Update CHANGELOG --- activerecord/CHANGELOG.md | 8 ++++++ activerecord/lib/active_record/core.rb | 9 +++++++ .../lib/active_record/railties/databases.rake | 2 +- guides/source/configuring.md | 6 +++++ railties/CHANGELOG.md | 7 +++++ .../templates/config/environments/production.rb.tt | 5 ++++ railties/test/application/configuration_test.rb | 17 ++++++++++++ railties/test/application/rake/migrations_test.rb | 31 ++++++++++++++++++++++ 8 files changed, 84 insertions(+), 1 deletion(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index d14b5b27f8..aeb3d328e5 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,11 @@ +* Add flag to disable schema dump after migration. + + Add a config parameter on Active Record named `dump_schema_after_migration` + which is true by default. Now schema dump does not happen at the + end of migration rake task if `dump_schema_after_migration` is false. + + *Emil Soman* + * Make sure transaction state gets reset after a commit operation on the record. If a new transaction was open inside a callback, the record was loosing track diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index d691192cfd..d9aaf8597f 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -76,6 +76,15 @@ module ActiveRecord mattr_accessor :timestamped_migrations, instance_writer: false self.timestamped_migrations = true + ## + # :singleton-method: + # Specify whether schema dump should happen at the end of the + # db:migrate rake task. This is true by default, which is useful for the + # development environment. This should ideally be false in the production + # environment where dumping schema is rarely needed. + mattr_accessor :dump_schema_after_migration, instance_writer: false + self.dump_schema_after_migration = true + # :nodoc: mattr_accessor :maintain_test_schema, instance_accessor: false diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 561387a179..9be734e88e 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -34,7 +34,7 @@ db_namespace = namespace :db do ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration| ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope) end - db_namespace['_dump'].invoke + db_namespace['_dump'].invoke if ActiveRecord::Base.dump_schema_after_migration end task :_dump do diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 669086edbe..561a69749a 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -292,6 +292,12 @@ All these configuration options are delegated to the `I18n` library. * `config.active_record.maintain_test_schema` is a boolean value which controls whether Active Record should try to keep your test database schema up-to-date with `db/schema.rb` (or `db/structure.sql`) when you run your tests. The default is true. +* `config.active_record.dump_schema_after_migration` is a flag which + controls whether or not schema dump should happen (`db/schema.rb` or + `db/structure.sql`) when you run migrations. This is set to false in + `config/environments/production.rb` which is generated by Rails. The + default value is true if this configuration is not set. + The MySQL adapter adds one additional configuration option: * `ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default. diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 4ac6a99662..7aeabf735f 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,10 @@ +* Set `dump_schema_after_migration` config values in production. + + Set `config.active_record.dump_schema_after_migration` as false + in the generated `config/environments/production.rb` file. + + *Emil Soman* + * Added Thor-action for creation of migrations. Fixes #13588 and #12674. diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index d2f041aa27..d9cc60d656 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -81,4 +81,9 @@ Rails.application.configure do # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new + <%- unless options.skip_active_record? -%> + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + <%- end -%> end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 02d8b2c91d..b814479540 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -781,5 +781,22 @@ module ApplicationTests assert_not Rails.configuration.respond_to?(:method_missing) assert Rails.configuration.respond_to?(:method_missing, true) end + + test "config.active_record.dump_schema_after_migration is false on production" do + build_app + ENV["RAILS_ENV"] = "production" + + require "#{app_path}/config/environment" + + assert_not ActiveRecord::Base.dump_schema_after_migration + end + + test "config.active_record.dump_schema_after_migration is true by default on development" do + ENV["RAILS_ENV"] = "development" + + require "#{app_path}/config/environment" + + assert ActiveRecord::Base.dump_schema_after_migration + end end end diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb index 33c753868c..b7fd5d02c5 100644 --- a/railties/test/application/rake/migrations_test.rb +++ b/railties/test/application/rake/migrations_test.rb @@ -153,6 +153,37 @@ module ApplicationTests assert_match(/up\s+\d{3,}\s+Add email to users/, output) end end + + test 'schema generation when dump_schema_after_migration is set' do + add_to_config('config.active_record.dump_schema_after_migration = false') + + Dir.chdir(app_path) do + `rails generate model book title:string; + bundle exec rake db:migrate` + + assert !File.exist?("db/schema.rb") + end + + add_to_config('config.active_record.dump_schema_after_migration = true') + + Dir.chdir(app_path) do + `rails generate model author name:string; + bundle exec rake db:migrate` + + structure_dump = File.read("db/schema.rb") + assert_match(/create_table "authors"/, structure_dump) + end + end + + test 'default schema generation after migration' do + Dir.chdir(app_path) do + `rails generate model book title:string; + bundle exec rake db:migrate` + + structure_dump = File.read("db/schema.rb") + assert_match(/create_table "books"/, structure_dump) + end + end end end end -- cgit v1.2.3 From f8d740af933e81648f69c65fb8229dd21beb4abb Mon Sep 17 00:00:00 2001 From: Ionatan Wiznia Date: Thu, 6 Feb 2014 20:11:40 +0100 Subject: Tests that skips a controller filters that was set up using a class --- actionpack/test/controller/filters_test.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index d3efca5b6f..c87494aa64 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -225,6 +225,10 @@ class FilterTest < ActionController::TestCase skip_before_filter :clean_up_tmp, if: -> { true } end + class ClassController < ConditionalFilterController + before_filter ConditionalClassFilter + end + class PrependingController < TestController prepend_before_filter :wonderful_life # skip_before_filter :fire_flash @@ -610,6 +614,18 @@ class FilterTest < ActionController::TestCase assert_equal %w( ensure_login ), assigns["ran_filter"] end + def test_skipping_class_filters + test_process(ClassController) + assert_equal true, assigns["ran_class_filter"] + + skipping_class_controller = Class.new(ClassController) do + skip_before_filter ConditionalClassFilter + end + + test_process(skipping_class_controller) + assert_nil assigns['ran_class_filter'] + end + def test_running_collection_condition_filters test_process(ConditionalCollectionFilterController) assert_equal %w( ensure_login ), assigns["ran_filter"] -- cgit v1.2.3 From 2fdbd599be4729fdc271a74557e8f45dec33c0f8 Mon Sep 17 00:00:00 2001 From: Calvin Tam Date: Fri, 7 Feb 2014 20:02:34 +1100 Subject: Fixed spelling error: `extracetd` => `extracted` --- guides/source/4_1_release_notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 171572c77c..606a9cd5d1 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -566,7 +566,7 @@ for detailed changes. [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling)) * Deprecated `ActiveSupport.encode_big_decimal_as_string` option. This feature has - been extracetd into the [activesupport-json_encoder](https://github.com/rails/activesupport-json_encoder) + been extracted into the [activesupport-json_encoder](https://github.com/rails/activesupport-json_encoder) gem. ([Pull Request](https://github.com/rails/rails/pull/13060) / [More Details](upgrading_ruby_on_rails.html#changes-in-json-handling)) -- cgit v1.2.3 From bb87c16ad92c13ca91d8e349c0f0b277431b81af Mon Sep 17 00:00:00 2001 From: Matthew Nicholas Bradley Date: Fri, 7 Feb 2014 14:03:39 +0000 Subject: Fix wordy sentence --- guides/source/getting_started.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index bdb1a61bfb..738bf0be4d 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -367,12 +367,11 @@ styling for it afterwards. ### Laying down the ground work -The first thing that you are going to need to create a new article within the -application is a place to do that. A great place for that would be at -`/articles/new`. -With the route already defined, requests can now be made to `/articles/new` in -the application. Navigate to and you'll see -a routing error: +Firstly, you need a place within the application to create a new article. A +great place for that would be at `/articles/new`. With the route already +defined, requests can now be made to `/articles/new` in the application. +Navigate to and you'll see a routing +error: ![Another routing error, uninitialized constant ArticlesController](images/getting_started/routing_error_no_controller.png) -- cgit v1.2.3 From 47860b62b3c9a915c00fd379b705c545d4c6eb0d Mon Sep 17 00:00:00 2001 From: Philipe Fatio Date: Fri, 7 Feb 2014 11:06:55 +0100 Subject: Require action_view to fix missing constant Previously, requiring action_view/view_paths did cause an uninitialized constant error for ENCODING_FLAG, which is defined in action_view. --- actionpack/CHANGELOG.md | 5 +++++ actionpack/lib/abstract_controller/rendering.rb | 1 + 2 files changed, 6 insertions(+) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index b63fb4a9b6..642b847588 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,8 @@ +* Properly require `action_view` in `AbstractController::Rendering` to prevent + uninitialized constant error for `ENCODING_FLAG`. + + *Philipe Fatio* + * Do not discard query parameters that form a hash with the same root key as the `wrapper_key` for a request using `wrap_parameters`. diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 7be61d94c9..f24b03ad16 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -1,5 +1,6 @@ require 'active_support/concern' require 'active_support/core_ext/class/attribute' +require 'action_view' require 'action_view/view_paths' require 'set' -- cgit v1.2.3 From 8fffcfc674bc15fed1a20934962fa35b5c907549 Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Thu, 6 Feb 2014 00:07:35 +0530 Subject: [Testing Guide] Mention :unauthorized in assert_response explanation [ci skip] --- guides/source/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/testing.md b/guides/source/testing.md index 408c072575..34573d2b0a 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -401,7 +401,7 @@ Rails adds some custom assertions of its own to the `test/unit` framework: | `assert_no_difference(expressions, message = nil, &block)` | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.| | `assert_recognizes(expected_options, path, extras={}, message=nil)` | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.| | `assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)` | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.| -| `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range| +| `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.| | `assert_redirected_to(options = {}, message=nil)` | Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on.| | `assert_template(expected = nil, message=nil)` | Asserts that the request was rendered with the appropriate template file.| -- cgit v1.2.3 From 3f90787cd25a45a621fbb7238f69ee96f26e8f74 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Fri, 7 Feb 2014 17:08:09 -0800 Subject: Adjust test value so that timezone has no effect If the test is run in a timezone that is behind UTC it fails because the time generated is ahead of 0000-01-01 00:00:00. Just increase the time subtracted so that timezone has no effect. --- activerecord/test/cases/adapters/postgresql/timestamp_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb index 89210866f0..4d29a20e66 100644 --- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb +++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb @@ -77,7 +77,7 @@ class TimestampTest < ActiveRecord::TestCase end def test_bc_timestamp - date = Date.new(0) - 1.second + date = Date.new(0) - 1.week Developer.create!(:name => "aaron", :updated_at => date) assert_equal date, Developer.find_by_name("aaron").updated_at end -- cgit v1.2.3 From 519deb6f509e804ad2c937df4f583785f2168c9c Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Sat, 8 Feb 2014 13:42:10 +0100 Subject: docs, Cookie values are String based. Closes #12860. [ci skip] --- actionpack/lib/action_dispatch/middleware/cookies.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 23d0ecd529..b3c3ab6bb9 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -23,8 +23,8 @@ module ActionDispatch # # This cookie will be deleted when the user's browser is closed. # cookies[:user_name] = "david" # - # # Assign an array of values to a cookie. - # cookies[:lat_lon] = [47.68, -122.37] + # # Cookie values are String based. Other data types need to be serialized. + # cookies[:lat_lon] = JSON.dump([47.68, -122.37]) # # # Sets a cookie that expires in 1 hour. # cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now } @@ -42,10 +42,10 @@ module ActionDispatch # # Examples of reading: # - # cookies[:user_name] # => "david" - # cookies.size # => 2 - # cookies[:lat_lon] # => [47.68, -122.37] - # cookies.signed[:login] # => "XJ-122" + # cookies[:user_name] # => "david" + # cookies.size # => 2 + # JSON.load(cookies[:lat_lon]) # => [47.68, -122.37] + # cookies.signed[:login] # => "XJ-122" # # Example for deleting: # @@ -63,7 +63,7 @@ module ActionDispatch # # The option symbols for setting cookies are: # - # * :value - The cookie's value or list of values (as an array). + # * :value - The cookie's value. # * :path - The path for which this cookie applies. Defaults to the root # of the application. # * :domain - The domain for which this cookie applies so you can -- cgit v1.2.3 From 50d828c0afe9c0fb94d4c1e86fb6c71916a32ab6 Mon Sep 17 00:00:00 2001 From: Robin Dupret Date: Sat, 8 Feb 2014 16:31:12 +0100 Subject: Rely on backticks instead of tt tags [ci skip] Since the language in code blocks is inferred, if the code contains tt tags, the block will be parsed as XML for instance while it is Ruby. --- actionpack/lib/action_dispatch/middleware/cookies.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index b3c3ab6bb9..3d1614142d 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -30,8 +30,8 @@ module ActionDispatch # cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now } # # # Sets a signed cookie, which prevents users from tampering with its value. - # # The cookie is signed by your app's secrets.secret_key_base value. - # # It can be read using the signed method cookies.signed[:name] + # # The cookie is signed by your app's `secrets.secret_key_base` value. + # # It can be read using the signed method `cookies.signed[:name]` # cookies.signed[:user_id] = current_user.id # # # Sets a "permanent" cookie (which expires in 20 years from now). -- cgit v1.2.3 From 5e3d466d52fa4e9a42c3a1f8773a7c31da875e48 Mon Sep 17 00:00:00 2001 From: Kevin Casey Date: Sat, 8 Feb 2014 08:44:10 -0800 Subject: context in validation goes through has many relationship --- activerecord/lib/active_record/autosave_association.rb | 2 +- .../test/cases/associations/has_many_associations_test.rb | 10 ++++++++++ activerecord/test/models/pirate.rb | 8 ++++++++ activerecord/test/models/ship.rb | 8 ++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index e9622ca0c1..4f58d06f35 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -301,7 +301,7 @@ module ActiveRecord def association_valid?(reflection, record) return true if record.destroyed? || record.marked_for_destruction? - unless valid = record.valid? + unless valid = record.valid?(self.validation_context) if reflection.options[:autosave] record.errors.each do |attribute, message| attribute = "#{reflection.name}.#{attribute}" diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index a86fb15719..321440cab7 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -22,6 +22,8 @@ require 'models/engine' require 'models/categorization' require 'models/minivan' require 'models/speedometer' +require 'models/pirate' +require 'models/ship' class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase fixtures :authors, :posts, :comments @@ -1830,4 +1832,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end end + + test 'has_many_association passes context validation to validate children' do + pirate = FamousPirate.new + pirate.famous_ships << ship = FamousShip.new + assert_equal true, pirate.valid? + assert_equal false, pirate.valid?(:conference) + assert_equal "can't be blank", ship.errors[:name].first + end end diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index 7bb0caf44b..8510c596a7 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -85,3 +85,11 @@ end class DestructivePirate < Pirate has_one :dependent_ship, :class_name => 'Ship', :foreign_key => :pirate_id, :dependent => :destroy end + +class FamousPirate < ActiveRecord::Base + self.table_name = 'pirates' + + has_many :famous_ships + + validates_presence_of :catchphrase, on: :conference +end diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb index 3da031946f..7a369b9d9a 100644 --- a/activerecord/test/models/ship.rb +++ b/activerecord/test/models/ship.rb @@ -17,3 +17,11 @@ class Ship < ActiveRecord::Base false end end + +class FamousShip < ActiveRecord::Base + self.table_name = 'ships' + + belongs_to :famous_pirate + + validates_presence_of :name, on: :conference +end -- cgit v1.2.3 From 77577149f71e1fa0df15dbc02ae7c33349dddba8 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Sat, 8 Feb 2014 10:00:09 -0800 Subject: Updated the cookie docs to use the safer JSON.{generate,parse} cc @senny --- actionpack/lib/action_dispatch/middleware/cookies.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 3d1614142d..531654895b 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -24,7 +24,7 @@ module ActionDispatch # cookies[:user_name] = "david" # # # Cookie values are String based. Other data types need to be serialized. - # cookies[:lat_lon] = JSON.dump([47.68, -122.37]) + # cookies[:lat_lon] = JSON.generate([47.68, -122.37]) # # # Sets a cookie that expires in 1 hour. # cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now } @@ -42,10 +42,10 @@ module ActionDispatch # # Examples of reading: # - # cookies[:user_name] # => "david" - # cookies.size # => 2 - # JSON.load(cookies[:lat_lon]) # => [47.68, -122.37] - # cookies.signed[:login] # => "XJ-122" + # cookies[:user_name] # => "david" + # cookies.size # => 2 + # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37] + # cookies.signed[:login] # => "XJ-122" # # Example for deleting: # -- cgit v1.2.3 From 1ea61cb9bc95bf3857012850ffb475836e5d88e8 Mon Sep 17 00:00:00 2001 From: Lauro Caetano Date: Sat, 8 Feb 2014 18:46:32 -0200 Subject: Add test case for autosave HasMany with accepts_nested_attributes. It should not save the parent record when the nested attributes are invalid. Test case to cover #8194. --- .../test/cases/autosave_association_test.rb | 29 ++++++++++++++++++++++ activerecord/test/models/electron.rb | 2 ++ activerecord/test/models/molecule.rb | 2 ++ 3 files changed, 33 insertions(+) diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 7a0c335627..c55dd685a1 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -17,6 +17,8 @@ require 'models/tag' require 'models/tagging' require 'models/treasure' require 'models/eye' +require 'models/electron' +require 'models/molecule' class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase def test_should_not_add_the_same_callbacks_multiple_times_for_has_one @@ -343,6 +345,33 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test end end +class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase + def test_invalid_adding_with_nested_attributes + molecule = Molecule.new + valid_electron = Electron.new(name: 'electron') + invalid_electron = Electron.new + + molecule.electrons = [valid_electron, invalid_electron] + molecule.save + + assert_not invalid_electron.valid? + assert valid_electron.valid? + assert_not molecule.persisted?, 'Molecule should not be persisted when its electrons are invalid' + end + + def test_valid_adding_with_nested_attributes + molecule = Molecule.new + valid_electron = Electron.new(name: 'electron') + + molecule.electrons = [valid_electron] + molecule.save + + assert valid_electron.valid? + assert molecule.persisted? + assert_equal 1, molecule.electrons.count + end +end + class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase fixtures :companies, :people diff --git a/activerecord/test/models/electron.rb b/activerecord/test/models/electron.rb index 35af9f679b..6fc270673f 100644 --- a/activerecord/test/models/electron.rb +++ b/activerecord/test/models/electron.rb @@ -1,3 +1,5 @@ class Electron < ActiveRecord::Base belongs_to :molecule + + validates_presence_of :name end diff --git a/activerecord/test/models/molecule.rb b/activerecord/test/models/molecule.rb index 69325b8d29..26870c8f88 100644 --- a/activerecord/test/models/molecule.rb +++ b/activerecord/test/models/molecule.rb @@ -1,4 +1,6 @@ class Molecule < ActiveRecord::Base belongs_to :liquid has_many :electrons + + accepts_nested_attributes_for :electrons end -- cgit v1.2.3 From 4fa8c8b52f2ee7155b18cee2f3fc978075c68db1 Mon Sep 17 00:00:00 2001 From: Noah Lindner Date: Sat, 8 Feb 2014 15:35:12 -0800 Subject: Fixed an issue where reloading of removed dependencies would cause an unexpected circular dependency error --- activesupport/lib/active_support/dependencies.rb | 8 ++++++++ activesupport/test/dependencies_test.rb | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 6be19771f5..7ea3ff7d3f 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -665,6 +665,14 @@ module ActiveSupport #:nodoc: constants = normalized.split('::') to_remove = constants.pop + # Remove the file path from the loaded list. + file_path = search_for_file(const.underscore) + if file_path + expanded = File.expand_path(file_path) + expanded.sub!(/\.rb\z/, '') + self.loaded.delete(expanded) + end + if constants.empty? parent = Object else diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 00bec5bd9d..4ca63b3417 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -948,6 +948,18 @@ class DependenciesTest < ActiveSupport::TestCase Object.class_eval { remove_const :A if const_defined?(:A) } end + def test_access_unloaded_constants_for_reload + with_autoloading_fixtures do + assert_kind_of Module, A + assert_kind_of Class, A::B # Necessary to load A::B for the test + ActiveSupport::Dependencies.mark_for_unload(A::B) + ActiveSupport::Dependencies.remove_unloadable_constants! + + A::B # Make sure no circular dependency error + end + end + + def test_autoload_once_paths_should_behave_when_recursively_loading with_loading 'dependencies', 'autoloading_fixtures' do ActiveSupport::Dependencies.autoload_once_paths = [ActiveSupport::Dependencies.autoload_paths.last] -- cgit v1.2.3 From ccd1c435ee646bdbdc54cba16ae3c7e6d504d2fb Mon Sep 17 00:00:00 2001 From: Mikko Johansson Date: Sun, 9 Feb 2014 02:02:44 +0200 Subject: Skips tests on Windows that create files with illegal characters --- actionpack/test/dispatch/static_test.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index 5bd1806b21..01cca69288 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -136,10 +136,15 @@ module StaticTests def with_static_file(file) path = "#{FIXTURE_LOAD_PATH}/#{public_path}" + file - File.open(path, "wb+") { |f| f.write(file) } + begin + File.open(path, "wb+") { |f| f.write(file) } + rescue Errno::EPROTO + skip "Couldn't create a file #{path}" + end + yield file ensure - File.delete(path) + File.delete(path) if File.exists? path end end -- cgit v1.2.3 From 28a450d590a03f4b77993b50b24c0abdace460ee Mon Sep 17 00:00:00 2001 From: Mikko Johansson Date: Sun, 9 Feb 2014 02:52:32 +0200 Subject: Skips linked folder tests if symlink is invalid --- activerecord/test/cases/fixtures_test.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index f3a4887a85..37c6af74da 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -628,7 +628,9 @@ class LoadAllFixturesTest < ActiveRecord::TestCase self.class.fixture_path = FIXTURES_ROOT + "/all" self.class.fixtures :all - assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort + if File.symlink? FIXTURES_ROOT + "/all/admin" + assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort + end ensure ActiveRecord::FixtureSet.reset_cache end @@ -639,7 +641,9 @@ class LoadAllFixturesWithPathnameTest < ActiveRecord::TestCase self.class.fixture_path = Pathname.new(FIXTURES_ROOT).join('all') self.class.fixtures :all - assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort + if File.symlink? FIXTURES_ROOT + "/all/admin" + assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort + end ensure ActiveRecord::FixtureSet.reset_cache end -- cgit v1.2.3 From 02a3c0e771b3e09173412f93d8699d4825a366d6 Mon Sep 17 00:00:00 2001 From: Kevin Casey Date: Sat, 8 Feb 2014 17:38:54 -0800 Subject: Reaper has access to threadsafe active? call --- .../active_record/connection_adapters/abstract/connection_pool.rb | 2 +- .../lib/active_record/connection_adapters/abstract_adapter.rb | 6 ++++++ .../lib/active_record/connection_adapters/postgresql_adapter.rb | 7 ++++++- activerecord/test/cases/connection_pool_test.rb | 2 +- activerecord/test/cases/reaper_test.rb | 2 +- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index cebe741daa..759e162e19 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -393,7 +393,7 @@ module ActiveRecord synchronize do stale = Time.now - @dead_connection_timeout connections.dup.each do |conn| - if conn.in_use? && stale > conn.last_use && !conn.active? + if conn.in_use? && stale > conn.last_use && !conn.active_threadsafe? remove conn end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 3c94bad208..11b28a4858 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -262,6 +262,12 @@ module ActiveRecord def active? end + # Adapter should redefine this if it needs a threadsafe way to approximate + # if the connection is active + def active_threadsafe? + active? + end + # Disconnects from the database if already connected, and establishes a # new connection with the database. Implementors should call super if they # override the default implementation. diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 9618ba4087..36c7462419 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -586,11 +586,16 @@ module ActiveRecord # Is this connection alive and ready for queries? def active? - @connection.connect_poll != PG::PGRES_POLLING_FAILED + @connection.query 'SELECT 1' + true rescue PGError false end + def active_threadsafe? + @connection.connect_poll != PG::PGRES_POLLING_FAILED + end + # Close then reopen the connection. def reconnect! super diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 2da51ea015..1cf215017b 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -142,7 +142,7 @@ module ActiveRecord connections = @pool.connections.dup connections.each do |conn| - conn.extend(Module.new { def active?; false; end; }) + conn.extend(Module.new { def active_threadsafe?; false; end; }) end @pool.reap diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb index e53a27d5dd..b62a41c08e 100644 --- a/activerecord/test/cases/reaper_test.rb +++ b/activerecord/test/cases/reaper_test.rb @@ -69,7 +69,7 @@ module ActiveRecord conn = pool.checkout count = pool.connections.length - conn.extend(Module.new { def active?; false; end; }) + conn.extend(Module.new { def active_threadsafe?; false; end; }) while count == pool.connections.length Thread.pass -- cgit v1.2.3 From cbd10e27d1fa6e07d39dbb6fb42ce3420c5959db Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Sun, 2 Feb 2014 14:55:35 -0800 Subject: Add missing test for response destructuring. --- actionpack/test/dispatch/response_test.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 4501ea095c..82d29867fe 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -217,6 +217,15 @@ class ResponseTest < ActiveSupport::TestCase assert_not @response.respond_to?(:method_missing) assert @response.respond_to?(:method_missing, true) end + + test "can be destructured into status, headers and an enumerable body" do + response = ActionDispatch::Response.new(404, { 'Content-Type' => 'text/plain' }, ['Not Found']) + status, headers, body = response + + assert_equal 404, status + assert_equal({ 'Content-Type' => 'text/plain' }, headers) + assert_equal ['Not Found'], body.each.to_a + end end class ResponseIntegrationTest < ActionDispatch::IntegrationTest -- cgit v1.2.3 From dfe9cf1ace7cd4d9f47ddfcf0071e2aec7c20166 Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Sun, 9 Feb 2014 04:20:56 +0200 Subject: Remove outdated TODO from url_for test, this test was fixed in 900a2d30 --- actionpack/test/controller/url_for_test.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index d2b4952759..a8035e5bd7 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -204,9 +204,6 @@ module AbstractController end def test_relative_url_root_is_respected - # ROUTES TODO: Tests should not have to pass :relative_url_root directly. This - # should probably come from routes. - add_host! assert_equal('https://www.basecamphq.com/subdir/c/a/i', W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https', :script_name => '/subdir') -- cgit v1.2.3 From 76af5c1d30b41c25f473c58f0333236b54bf7049 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 8 Feb 2014 23:21:07 -0800 Subject: use feature detection to decide which implementation to use Decouple the code from the particular Ruby version. --- activesupport/lib/active_support/multibyte/unicode.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 84799c2399..7e518d8c39 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -213,7 +213,7 @@ module ActiveSupport end # Ruby >= 2.1 has String#scrub, which is faster than the workaround used for < 2.1. - if RUBY_VERSION >= '2.1' + if '<3'.respond_to?(:scrub) # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent # resulting in a valid UTF-8 string. # -- cgit v1.2.3 From 069bc273853c90194606b1725113d77ae39e2edd Mon Sep 17 00:00:00 2001 From: Dan Kang Date: Sat, 8 Feb 2014 17:40:08 -0800 Subject: Prevent [response].flatten from recursing infinitely. Returning `self` from within the array returned by `to_ary` caused this. Instead, we can just substitute another object. It provides the `each` behavior required by the rack spec. --- actionpack/lib/action_dispatch/http/response.rb | 2 +- actionpack/test/dispatch/response_test.rb | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index bc13ee00f1..2c6bcf7b7b 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -313,7 +313,7 @@ module ActionDispatch # :nodoc: header.delete CONTENT_TYPE [status, header, []] else - [status, header, self] + [status, header, Rack::BodyProxy.new(self){}] end end end diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 82d29867fe..959a3bc5cd 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -226,6 +226,15 @@ class ResponseTest < ActiveSupport::TestCase assert_equal({ 'Content-Type' => 'text/plain' }, headers) assert_equal ['Not Found'], body.each.to_a end + + test "[response].flatten does not recurse infinitely" do + Timeout.timeout(1) do # use a timeout to prevent it stalling indefinitely + status, headers, body = [@response].flatten + assert_equal @response.status, status + assert_equal @response.headers, headers + assert_equal @response.body, body.each.to_a.join + end + end end class ResponseIntegrationTest < ActionDispatch::IntegrationTest -- cgit v1.2.3 From a09c07890a9aee7a40dc145db6cad62b5c50718c Mon Sep 17 00:00:00 2001 From: SHIBATA Hiroshi Date: Sun, 9 Feb 2014 17:39:55 +0900 Subject: use File.exist? instead of File.exists? --- actionpack/test/dispatch/static_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index 01cca69288..afdda70748 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -144,7 +144,7 @@ module StaticTests yield file ensure - File.delete(path) if File.exists? path + File.delete(path) if File.exist? path end end -- cgit v1.2.3 From 8d7923b7eb0dd638d1426aadde2b2d9835ecf68d Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Sun, 9 Feb 2014 11:30:49 +0200 Subject: FilterParameters is referenced at the class level from the Request Since it's already required in the file, we don't need to use autoload too. This commit is symmetrical change to 0b10180 for Response. --- actionpack/lib/action_dispatch.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 9b26845190..a56d827b1a 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -74,7 +74,6 @@ module ActionDispatch autoload :MimeNegotiation autoload :Parameters autoload :ParameterFilter - autoload :FilterParameters autoload :Upload autoload :UploadedFile, 'action_dispatch/http/upload' autoload :URL -- cgit v1.2.3 From dbe3345c4f84f603b8a6d121229d1cc9f5d3be8c Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Sun, 9 Feb 2014 03:06:19 +0200 Subject: Fix grammar of internal comment and modify it's location --- activesupport/lib/active_support/core_ext/module/attr_internal.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/activesupport/lib/active_support/core_ext/module/attr_internal.rb index db07d549b0..67f0e0335d 100644 --- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb +++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb @@ -27,7 +27,8 @@ class Module def attr_internal_define(attr_name, type) internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '') - class_eval do # class_eval is necessary on 1.9 or else the methods a made private + # class_eval is necessary on 1.9 or else the methods are made private + class_eval do # use native attr_* methods as they are faster on some Ruby implementations send("attr_#{type}", internal_name) end -- cgit v1.2.3 From 17b0edde5782c424dbae6020e09a51831e8de7f9 Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Sun, 9 Feb 2014 03:07:33 +0200 Subject: Specify what #starts_with? we're talking about. Also added a note what kind of exception we should expect for this internal comment. --- activesupport/lib/active_support/dependencies.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 6be19771f5..b519ef9f71 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -407,7 +407,8 @@ module ActiveSupport #:nodoc: end def load_once_path?(path) - # to_s works around a ruby1.9 issue where #starts_with?(Pathname) will always return false + # to_s works around a ruby1.9 issue where String#starts_with?(Pathname) + # will raise a TypeError: no implicit conversion of Pathname into String autoload_once_paths.any? { |base| path.starts_with? base.to_s } end -- cgit v1.2.3 From 4e4a92e78a29d2508702336a440cb0d14cbf1f39 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Tue, 4 Feb 2014 13:37:11 +0100 Subject: docs, link MySQL manual for multi column indexes. [ci ckip]. Closes #9131. --- guides/source/active_record_validations.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index efa826e8df..b0bd16791d 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -575,7 +575,9 @@ This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, -you must create a unique index in your database. +you must create a unique index on both columns in your database. See +[the MySQL manual](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html) +for more details about multi column indexes. ```ruby class Account < ActiveRecord::Base -- cgit v1.2.3 From 605c81b9de24535993046d9a50408ad98feee9c0 Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Sun, 9 Feb 2014 09:39:22 +0200 Subject: Use full-length version of multiple from c7abc51 --- guides/source/active_record_validations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index b0bd16791d..990c820868 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -577,7 +577,7 @@ so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create a unique index on both columns in your database. See [the MySQL manual](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html) -for more details about multi column indexes. +for more details about multiple column indexes. ```ruby class Account < ActiveRecord::Base -- cgit v1.2.3 From 7afd92e60b38b34404a38592d6e3e652c1623b6a Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sun, 26 Jan 2014 22:10:05 +0100 Subject: adds a section about booleans in the API guidelines [ci skip] --- guides/source/api_documentation_guidelines.md | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index 311cc23cf0..622d57a943 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -128,6 +128,53 @@ On the other hand, regular comments do not use an arrow: # polymorphic_url(record) # same as comment_url(record) ``` +Booleans +-------- + +In predicates and flags prefer documenting boolean semantics over exact values. + +When "true" or "false" are used as defined in Ruby use regular font. The +singletons `true` and `false` need fixed-width font. Please avoid terms like +"truthy", Ruby defines what is true and false in the language, and thus those +words have a technical meaning and need no substitutes. + +As a rule of thumb, do not document singletons unless absolutely necessary. That +prevents artificial constructs like `!!` or ternaries, allows refactors, and the +code does not need to rely on the exact values returned by methods being called +in the implementation. + +For example: + +```markdown +`config.action_mailer.perform_deliveries` specifies whether mail will actually be delivered and is true by default +``` + +the user does not need to know which is the actual default value of the flag, +and so we only document its boolean semantics. + +An example with a predicate: + +```ruby +# Returns true if the collection is empty. +# +# If the collection has been loaded +# it is equivalent to collection.size.zero?. If the +# collection has not been loaded, it is equivalent to +# collection.exists?. If the collection has not already been +# loaded and you are going to fetch the records anyway it is better to +# check collection.length.zero?. +def empty? + if loaded? + size.zero? + else + @target.blank? && !scope.exists? + end +end +``` + +The API is careful not to commit to any particular value, the predicate has +predicate semantics, that's enough. + Filenames --------- -- cgit v1.2.3 From ae7580ac287df477e7f5dd57136d5eec3813629d Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Sun, 9 Feb 2014 10:03:30 +0200 Subject: Fixed a grammatical error in Booleans section of API documentation guide from e1e17a5 --- guides/source/api_documentation_guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index 622d57a943..26afa72b8d 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -172,7 +172,7 @@ def empty? end ``` -The API is careful not to commit to any particular value, the predicate has +The API is careful not to commit to any particular value, the method has predicate semantics, that's enough. Filenames -- cgit v1.2.3 From 424b2d8594121456ed347957714a20c62d09a5cd Mon Sep 17 00:00:00 2001 From: Dmitry Polushkin Date: Sun, 9 Feb 2014 12:04:26 +0000 Subject: move alias method `sanitize_conditions` to a correct place --- activerecord/lib/active_record/sanitization.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index dacaec26b7..5a71c13d91 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -29,6 +29,7 @@ module ActiveRecord end end alias_method :sanitize_sql, :sanitize_sql_for_conditions + alias_method :sanitize_conditions, :sanitize_sql # Accepts an array, hash, or string of SQL conditions and sanitizes # them into a valid SQL fragment for a SET clause. @@ -122,8 +123,6 @@ module ActiveRecord end end - alias_method :sanitize_conditions, :sanitize_sql - def replace_bind_variables(statement, values) #:nodoc: raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size) bound = values.dup -- cgit v1.2.3 From ec43584431f61ad56f335033b650a906f44fbf40 Mon Sep 17 00:00:00 2001 From: Dmitry Polushkin Date: Sun, 9 Feb 2014 12:05:42 +0000 Subject: add activerecord test coverage for `sanitize_sql_array` check it is handles empty statement --- activerecord/test/cases/sanitize_test.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index 766b2ff2ef..954eab8022 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -46,4 +46,9 @@ class SanitizeTest < ActiveRecord::TestCase select_author_sql = Post.send(:sanitize_sql_array, ['id in (:post_ids)', post_ids: david_posts]) assert_match(sub_query_pattern, select_author_sql, 'should sanitize `Relation` as subquery for named bind variables') end + + def test_sanitize_sql_array_handles_empty_statement + select_author_sql = Post.send(:sanitize_sql_array, ['']) + assert_equal('', select_author_sql) + end end -- cgit v1.2.3 From a5034444299412c96cc286cf84a15077c8be425e Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Sun, 9 Feb 2014 08:57:06 +0200 Subject: Remove end of line whitespace from bb87c16 --- guides/source/getting_started.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 738bf0be4d..3e560bdd86 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -367,10 +367,10 @@ styling for it afterwards. ### Laying down the ground work -Firstly, you need a place within the application to create a new article. A -great place for that would be at `/articles/new`. With the route already -defined, requests can now be made to `/articles/new` in the application. -Navigate to and you'll see a routing +Firstly, you need a place within the application to create a new article. A +great place for that would be at `/articles/new`. With the route already +defined, requests can now be made to `/articles/new` in the application. +Navigate to and you'll see a routing error: ![Another routing error, uninitialized constant ArticlesController](images/getting_started/routing_error_no_controller.png) -- cgit v1.2.3 From f25e41167d64d113d391dca46b3094098aaeb5d2 Mon Sep 17 00:00:00 2001 From: Jed Hartman Date: Sun, 9 Feb 2014 09:36:53 -0800 Subject: Clarifying confusing phrasing --- guides/source/getting_started.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 3e560bdd86..53b6bf82bb 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -880,9 +880,9 @@ Let's add links to the other views as well, starting with adding this ``` This link will allow you to bring up the form that lets you create a new article. -You should also add a link to this template - `app/views/articles/new.html.erb` -- to go back to the `index` action. Do this by adding this underneath the form -in this template: + +Also add a link in `app/views/articles/new.html.erb`, underneath the form, to +go back to the `index` action: ```erb <%= form_for :article do |f| %> -- cgit v1.2.3 From 72e11abeaf6500fa81ee2260fa446fcabcc2a944 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sun, 9 Feb 2014 23:58:09 +0530 Subject: Simplify doc [ci skip] The `as` option was already explained in a previous example and doesn't need to be repeated. Explain only the `locals` option which the example is meant for. --- guides/source/layouts_and_rendering.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index 0cb213521c..93e25d619e 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -1123,7 +1123,7 @@ You can also pass in arbitrary local variables to any partial you are rendering as: :item, locals: {title: "Products Page"} %> ``` -Would render a partial `_product.html.erb` once for each instance of `product` in the `@products` instance variable passing the instance to the partial as a local variable called `item` and to each partial, make the local variable `title` available with the value `Products Page`. +In this case, the partial will have access to a local variable `title` with the value "Products Page". TIP: Rails also makes a counter variable available within a partial called by the collection, named after the member of the collection followed by `_counter`. For example, if you're rendering `@products`, within the partial you can refer to `product_counter` to tell you how many times the partial has been rendered. This does not work in conjunction with the `as: :value` option. -- cgit v1.2.3 From 5af7cab02ddd95e6813738e9de69b64fe560dbcd Mon Sep 17 00:00:00 2001 From: Dmitry Polushkin Date: Sun, 9 Feb 2014 18:41:41 +0000 Subject: add actionmailer test coverage for undefined delivery method --- actionmailer/lib/action_mailer/base.rb | 2 +- actionmailer/test/delivery_methods_test.rb | 29 ++++++++++++++++++++--------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 388f694590..eb8cca9ee4 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -764,7 +764,7 @@ module ActionMailer m.charset = charset = headers[:charset] # Set configure delivery behavior - wrap_delivery_behavior!(headers.delete(:delivery_method),headers.delete(:delivery_method_options)) + wrap_delivery_behavior!(headers.delete(:delivery_method), headers.delete(:delivery_method_options)) # Assign all headers except parts_order, content_type and body assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path) diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb index 20412c7bb2..609903620b 100644 --- a/actionmailer/test/delivery_methods_test.rb +++ b/actionmailer/test/delivery_methods_test.rb @@ -38,8 +38,10 @@ class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase end test "default sendmail settings" do - settings = {location: '/usr/sbin/sendmail', - arguments: '-i -t'} + settings = { + location: '/usr/sbin/sendmail', + arguments: '-i -t' + } assert_equal settings, ActionMailer::Base.sendmail_settings end end @@ -138,13 +140,15 @@ class MailDeliveryTest < ActiveSupport::TestCase end test "default delivery options can be overridden per mail instance" do - settings = { address: "localhost", - port: 25, - domain: 'localhost.localdomain', - user_name: nil, - password: nil, - authentication: nil, - enable_starttls_auto: true } + settings = { + address: "localhost", + port: 25, + domain: 'localhost.localdomain', + user_name: nil, + password: nil, + authentication: nil, + enable_starttls_auto: true + } assert_equal settings, ActionMailer::Base.smtp_settings overridden_options = {user_name: "overridden", password: "somethingobtuse"} mail_instance = DeliveryMailer.welcome(delivery_method_options: overridden_options) @@ -164,6 +168,13 @@ class MailDeliveryTest < ActiveSupport::TestCase end end + test "undefined delivery methods raises errors" do + DeliveryMailer.delivery_method = nil + assert_raise RuntimeError do + DeliveryMailer.welcome.deliver + end + end + test "does not perform deliveries if requested" do DeliveryMailer.perform_deliveries = false DeliveryMailer.deliveries.clear -- cgit v1.2.3 From 8acd58f23cac478c2f5f8a51a9b591a98773baec Mon Sep 17 00:00:00 2001 From: Dmitry Polushkin Date: Sun, 9 Feb 2014 01:34:14 +0000 Subject: add test coverage for activemodel Dirty#reset_changes --- activemodel/test/cases/dirty_test.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb index 8b55901a65..2853476c91 100644 --- a/activemodel/test/cases/dirty_test.rb +++ b/activemodel/test/cases/dirty_test.rb @@ -41,6 +41,10 @@ class DirtyTest < ActiveModel::TestCase def save changes_applied end + + def reload + reset_changes + end end setup do @@ -157,4 +161,19 @@ class DirtyTest < ActiveModel::TestCase @model.size = 1 assert @model.size_changed? end + + test "reload should reset all changes" do + @model.name = 'Dmitry' + @model.name_changed? + @model.save + @model.name = 'Bob' + + assert_equal [nil, 'Dmitry'], @model.previous_changes['name'] + assert_equal 'Dmitry', @model.changed_attributes['name'] + + @model.reload + + assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.previous_changes + assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes + end end -- cgit v1.2.3 From 462d7cb3148e95c9a793d33fd882a99f0d9c57c2 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Sun, 9 Feb 2014 10:36:45 -0800 Subject: Set the :shallow_path as each scope is generated If we set :shallow_path when shallow is called it can result in incorrect paths if the resource is inside a namespace because namespace itself sets the :shallow_path option to the namespace path. We fix this by removing the :shallow_path option from shallow as that should only be turning shallow routes on and not otherwise affecting the scope. To do this we need to treat the :shallow option to resources differently to other scope options and move it to before the nested block is called. This change also has the positive side effect of making the behavior of the :shallow option consistent with the shallow method. Fixes #12498. --- actionpack/CHANGELOG.md | 8 +++ actionpack/lib/action_dispatch/routing/mapper.rb | 13 +++- actionpack/test/dispatch/routing_test.rb | 75 ++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 642b847588..15541d58b5 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,11 @@ +* Set the `:shallow_path` scope option as each scope is generated rather than + waiting until the `shallow` option is set. Also make the behavior of the + `:shallow` resource option consistent with the behavior of the `shallow` method. + + Fixes #12498. + + *Andrew White*, *Aleksi Aalto* + * Properly require `action_view` in `AbstractController::Rendering` to prevent uninitialized constant error for `ENCODING_FLAG`. diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index d5eb770cb1..0b762aa9a4 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -707,6 +707,10 @@ module ActionDispatch options[:path] = args.flatten.join('/') if args.any? options[:constraints] ||= {} + unless shallow? + options[:shallow_path] = options[:path] if args.any? + end + if options[:constraints].is_a?(Hash) defaults = options[:constraints].select do |k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum)) @@ -1369,7 +1373,7 @@ module ActionDispatch end def shallow - scope(:shallow => true, :shallow_path => @scope[:path]) do + scope(:shallow => true) do yield end end @@ -1490,6 +1494,13 @@ module ActionDispatch return true end + if options.delete(:shallow) + shallow do + send(method, resources.pop, options, &block) + end + return true + end + if resource_scope? nested { send(method, resources.pop, options, &block) } return true diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 26821bdb56..1fa2cc6cf2 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1889,6 +1889,65 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'notes#destroy', @response.body end + def test_shallow_option_nested_resources_within_scope + draw do + scope '/hello' do + resources :notes, :shallow => true do + resources :trackbacks + end + end + end + + get '/hello/notes/1/trackbacks' + assert_equal 'trackbacks#index', @response.body + assert_equal '/hello/notes/1/trackbacks', note_trackbacks_path(:note_id => 1) + + get '/hello/notes/1/edit' + assert_equal 'notes#edit', @response.body + assert_equal '/hello/notes/1/edit', edit_note_path(:id => '1') + + get '/hello/notes/1/trackbacks/new' + assert_equal 'trackbacks#new', @response.body + assert_equal '/hello/notes/1/trackbacks/new', new_note_trackback_path(:note_id => 1) + + get '/hello/trackbacks/1' + assert_equal 'trackbacks#show', @response.body + assert_equal '/hello/trackbacks/1', trackback_path(:id => '1') + + get '/hello/trackbacks/1/edit' + assert_equal 'trackbacks#edit', @response.body + assert_equal '/hello/trackbacks/1/edit', edit_trackback_path(:id => '1') + + put '/hello/trackbacks/1' + assert_equal 'trackbacks#update', @response.body + + post '/hello/notes/1/trackbacks' + assert_equal 'trackbacks#create', @response.body + + delete '/hello/trackbacks/1' + assert_equal 'trackbacks#destroy', @response.body + + get '/hello/notes' + assert_equal 'notes#index', @response.body + + post '/hello/notes' + assert_equal 'notes#create', @response.body + + get '/hello/notes/new' + assert_equal 'notes#new', @response.body + assert_equal '/hello/notes/new', new_note_path + + get '/hello/notes/1' + assert_equal 'notes#show', @response.body + assert_equal '/hello/notes/1', note_path(:id => 1) + + put '/hello/notes/1' + assert_equal 'notes#update', @response.body + + delete '/hello/notes/1' + assert_equal 'notes#destroy', @response.body + end + def test_custom_resource_routes_are_scoped draw do resources :customers do @@ -2958,6 +3017,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/photos/1', photo_path('1') end + def test_shallow_path_inside_namespace_is_not_added_twice + draw do + namespace :admin do + shallow do + resources :posts do + resources :comments + end + end + end + end + + get '/admin/posts/1/comments' + assert_equal 'admin/comments#index', @response.body + assert_equal '/admin/posts/1/comments', admin_post_comments_path('1') + end + private def draw(&block) -- cgit v1.2.3 From 395bcb5cfc516870bb9ba53e776a3ac70eea4952 Mon Sep 17 00:00:00 2001 From: Gaurish Sharma Date: Mon, 10 Feb 2014 00:53:51 +0530 Subject: `Rails.threadsafe!` mode is deprecated [ci skip] --- guides/source/configuring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/configuring.md b/guides/source/configuring.md index d3bd5c1c18..7b72e27b96 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -939,4 +939,4 @@ ActiveRecord::ConnectionTimeoutError - could not obtain a database connection wi If you get the above error, you might want to increase the size of connection pool by incrementing the `pool` option in `database.yml` -NOTE. If you have enabled `Rails.threadsafe!` mode then there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections. +NOTE. As Rails is multi-threaded by default, there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections. -- cgit v1.2.3 From 2868bab4dfb5aa98afdb1e471ba274f82ea3aeb5 Mon Sep 17 00:00:00 2001 From: Ben Cullen-Kerney Date: Wed, 1 Jan 2014 21:25:40 -0800 Subject: A pass over the Getting Started guide sections 1, 2, and 3 [ci skip] * Add note about managing Ruby environments * Point to curated lists of Ruby learning resources * Expound on DRY and Convention over Configuration * Remove note on superuser--thankfuly coders new to Rails don't have to sudo anymore :) * Installation/verification instructions for SQLite3, since it's a dependency --- guides/source/getting_started.md | 49 ++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 53b6bf82bb..1a792f8408 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -21,19 +21,22 @@ application from scratch. It does not assume that you have any prior experience with Rails. However, to get the most out of it, you need to have some prerequisites installed: -* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer -* The [RubyGems](http://rubygems.org) packaging system -* To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org) -* A working installation of the [SQLite3 Database](http://www.sqlite.org) +* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer. +* The [RubyGems](http://rubygems.org) packaging system, which is installed with Ruby + versions 1.9 and later. To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org). +* A working installation of the [SQLite3 Database](http://www.sqlite.org). Rails is a web application framework running on the Ruby programming language. If you have no prior experience with Ruby, you will find a very steep learning -curve diving straight into Rails. There are some good free resources on the -Internet for learning Ruby, including: +curve diving straight into Rails. There are several curated lists of online resources +for learning Ruby: -* [Mr. Neighborly's Humble Little Ruby Book](http://www.humblelittlerubybook.com) -* [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/) -* [Why's (Poignant) Guide to Ruby](http://mislav.uniqpath.com/poignant-guide/) +* [Official Ruby Programming Language website](https://www.ruby-lang.org/en/documentation/) +* [reSRC's List of Free Programming Books](http://resrc.io/list/10/list-of-free-programming-books/#ruby) + +Be aware that some resources, while still excellent, cover versions of Ruby as old as +1.6, and commonly 1.8, and will not include some syntax that you will see in day-to-day +development with Rails. What is Rails? -------------- @@ -54,11 +57,13 @@ learned elsewhere, you may have a less happy experience. The Rails philosophy includes two major guiding principles: -* DRY - "Don't Repeat Yourself" - suggests that writing the same code over and - over again is a bad thing. -* Convention Over Configuration - means that Rails makes assumptions about what - you want to do and how you're going to do it, rather than requiring you to - specify every little thing through endless configuration files. +* **Don't Repeat Yourself:** DRY is a principle of software development which + states that "Every piece of knowledge must have a single, unambiguous, authoritative + representation within a system." By not writing the same information over and over + again, our code is more maintainable, more extensible, and less buggy. +* **Convention Over Configuration:** Rails has opinions about the best way to do many + things in a web application, and defaults to this set of conventions, rather than + require that you specify every minutiae through endless configuration files. Creating a New Rails Project ---------------------------- @@ -73,9 +78,9 @@ By following along with this guide, you'll create a Rails project called (very) simple weblog. Before you can start building the application, you need to make sure that you have Rails itself installed. -TIP: The examples below use `#` and `$` to denote superuser and regular -user terminal prompts respectively in a UNIX-like OS. If you are using -Windows, your prompt will look something like `c:\source_code>` +TIP: The examples below use `$` to represent your terminal prompt in a UNIX-like OS, +though it may have been customized to appear differently. If you are using Windows, +your prompt will look something like `c:\source_code>` ### Installing Rails @@ -97,6 +102,16 @@ If you don't have Ruby installed have a look at [ruby-lang.org](https://www.ruby-lang.org/en/downloads/) for possible ways to install Ruby on your platform. +Many popular UNIX-like OSes ship with an acceptable version of SQLite3. Windows +users and others can find installation instructions at [the SQLite3 website](http://www.sqlite.org). +Verify that it is correctly installed and in your PATH: + +```bash +$ sqlite3 --version +``` + +The program should report its version. + To install Rails, use the `gem install` command provided by RubyGems: ```bash -- cgit v1.2.3 From 07c70245a128cfe42f134be8759963dc98f1a63e Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 10 Feb 2014 08:50:06 +0100 Subject: docs, mention that the current inflection rules are frozen. [ci skip] Closes #13993. --- activesupport/lib/active_support/inflections.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb index 4ea6abfa12..d844e70750 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -1,5 +1,9 @@ require 'active_support/inflector/inflections' +# Define the standard inflection rules. These define a starting point for +# new projects and are not considered complete. The current set of inflection +# rules is frozen. This means, we do not change them to become more complete. +# This is a safety measure to keep existing applications from breaking. module ActiveSupport Inflector.inflections(:en) do |inflect| inflect.plural(/$/, 's') -- cgit v1.2.3 From ae28e4beb3d9b395ee269999111b6598802da63f Mon Sep 17 00:00:00 2001 From: Justin Coyne Date: Sat, 1 Feb 2014 22:13:07 -0600 Subject: Fix tidy_bytes for JRuby The previous implementation was broken because JRuby (1.7.10) doesn't have a code converter for UTF-8 to UTF8-MAC. --- activesupport/CHANGELOG.md | 7 +++++++ activesupport/lib/active_support/multibyte/unicode.rb | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 9a62bd5a77..43bfeff079 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,10 @@ +* Fix the implementation of Multibyte::Unicode.tidy_bytes for JRuby + + The existing implementation caused JRuby to raise the error: + `Encoding::ConverterNotFoundError: code converter not found (UTF-8 to UTF8-MAC)` + + *Justin Coyne* + * Fix `to_param` behavior when there are nested empty hashes. Before: diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 7e518d8c39..ea3cdcd024 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -233,16 +233,16 @@ module ActiveSupport # We're going to 'transcode' bytes from UTF-8 when possible, then fall back to # CP1252 when we get errors. The final string will be 'converted' back to UTF-8 # before returning. - reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_8_MAC) + reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_16LE) source = string.dup - out = ''.force_encoding(Encoding::UTF_8_MAC) + out = ''.force_encoding(Encoding::UTF_16LE) loop do reader.primitive_convert(source, out) _, _, _, error_bytes, _ = reader.primitive_errinfo break if error_bytes.nil? - out << error_bytes.encode(Encoding::UTF_8_MAC, Encoding::Windows_1252, invalid: :replace, undef: :replace) + out << error_bytes.encode(Encoding::UTF_16LE, Encoding::Windows_1252, invalid: :replace, undef: :replace) end reader.finish -- cgit v1.2.3 From 56fa194daa171f50b73f08ec0a52f845dc0f5a51 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 10 Feb 2014 15:32:01 +0100 Subject: docs, update broken link. [ci skip] from https://github.com/rack/rack/blob/master/lib/rack/server.rb#L289-L300. --- guides/source/initialization.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 5e2e0ad3e3..ec3cec5c6f 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -270,7 +270,7 @@ def parse_options(args) options = default_options # Don't evaluate CGI ISINDEX parameters. - # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html + # http://www.meb.uni-bonn.de/docs/cgi/cl.html args.clear if ENV.include?("REQUEST_METHOD") options.merge! opt_parser.parse! args @@ -522,7 +522,7 @@ I18n and Rails configuration are all being defined here. ### Back to `config/environment.rb` The rest of `config/application.rb` defines the configuration for the -`Rails::Application` which will be used once the application is fully +`Rails::Application` which will be used once the application is fully initialized. When `config/application.rb` has finished loading Rails and defined the application namespace, we go back to `config/environment.rb`, where the application is initialized. For example, if the application was called -- cgit v1.2.3 From 3b868cc99d4e417ac2090529ba41c4918454913d Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 10 Feb 2014 15:51:51 +0100 Subject: docs, link to HTTP and CGI header definitions. [ci skip] This was a reaction to: https://github.com/rails/rails/pull/9700#issuecomment-34550210 --- actionpack/test/dispatch/rack_test.rb | 2 +- guides/source/testing.md | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/actionpack/test/dispatch/rack_test.rb b/actionpack/test/dispatch/rack_test.rb index 42067854ee..ef1964fd19 100644 --- a/actionpack/test/dispatch/rack_test.rb +++ b/actionpack/test/dispatch/rack_test.rb @@ -119,7 +119,7 @@ class RackRequestTest < BaseRackTest assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host end - test "cgi environment variables" do + test "CGI environment variables" do assert_equal "Basic", @request.auth_type assert_equal 0, @request.content_length assert_equal nil, @request.content_mime_type diff --git a/guides/source/testing.md b/guides/source/testing.md index 34573d2b0a..1880dfb9ce 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -518,8 +518,10 @@ You also have access to three instance variables in your functional tests: ### Setting Headers and CGI variables -Headers and cgi variables can be set directly on the `@request` -instance variable: +[HTTP headers](http://tools.ietf.org/search/rfc2616#section-5.3) +and +[CGI variables](http://tools.ietf.org/search/rfc3875#section-4.1) +can be set directly on the `@request` instance variable: ```ruby # setting a HTTP Header -- cgit v1.2.3 From 73a431a50b42e56697fed801d27470cd7d8f7990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 10 Feb 2014 19:33:48 -0200 Subject: Avoid using deprecated arel constants --- activerecord/lib/active_record/associations/join_dependency.rb | 4 ++-- activerecord/lib/active_record/associations/join_helper.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 295dccf34e..27069157be 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -163,7 +163,7 @@ module ActiveRecord def make_outer_joins(parent, child) tables = table_aliases_for(parent, child) - join_type = Arel::OuterJoin + join_type = Arel::Nodes::OuterJoin joins = make_constraints parent, child, tables, join_type joins.concat child.children.flat_map { |c| make_outer_joins(child, c) } @@ -171,7 +171,7 @@ module ActiveRecord def make_inner_joins(parent, child) tables = child.tables - join_type = Arel::InnerJoin + join_type = Arel::Nodes::InnerJoin joins = make_constraints parent, child, tables, join_type joins.concat child.children.flat_map { |c| make_inner_joins(child, c) } diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb index f345d16841..3471936b9f 100644 --- a/activerecord/lib/active_record/associations/join_helper.rb +++ b/activerecord/lib/active_record/associations/join_helper.rb @@ -4,7 +4,7 @@ module ActiveRecord module JoinHelper #:nodoc: def join_type - Arel::InnerJoin + Arel::Nodes::InnerJoin end private -- cgit v1.2.3 From 67e6deea1ccb0ff6264ff0796264ec90c190e35a Mon Sep 17 00:00:00 2001 From: Matthew Nicholas Bradley Date: Mon, 10 Feb 2014 22:55:37 +0000 Subject: Add missing directory slashes [ci skip] --- guides/source/getting_started.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 53b6bf82bb..d1117689f0 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -147,20 +147,20 @@ of the files and folders that Rails created by default: | File/Folder | Purpose | | ----------- | ------- | -|app|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.| -|bin|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.| +|app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.| +|bin/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.| |config/|Configure your application's routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html).| |config.ru|Rack configuration for Rack based servers used to start the application.| -|db|Contains your current database schema, as well as the database migrations.| +|db/|Contains your current database schema, as well as the database migrations.| |Gemfile
Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see [the Bundler website](http://gembundler.com).| -|lib|Extended modules for your application.| -|log|Application log files.| -|public|The only folder seen by the world as-is. Contains static files and compiled assets.| +|lib/|Extended modules for your application.| +|log/|Application log files.| +|public/|The only folder seen by the world as-is. Contains static files and compiled assets.| |Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.| |README.rdoc|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.| -|test|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).| -|tmp|Temporary files (like cache, pid, and session files).| -|vendor|A place for all third-party code. In a typical Rails application this includes vendored gems.| +|test/|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).| +|tmp/|Temporary files (like cache, pid, and session files).| +|vendor/|A place for all third-party code. In a typical Rails application this includes vendored gems.| Hello, Rails! ------------- -- cgit v1.2.3 From 92f567ab30f240a1de152061a6eee76ca6c4da86 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Tue, 11 Feb 2014 09:06:28 +0100 Subject: docs, hide inflector comment targeting only contributors. [ci skip] This is a follow up to: https://github.com/rails/rails/commit/07c70245a128cfe42f134be8759963dc98f1a63e As suggested by @fxn this comment should not be visible in the API: https://github.com/rails/rails/commit/07c70245a128cfe42f134be8759963dc98f1a63e#commitcomment-5331658 --- activesupport/lib/active_support/inflections.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb index d844e70750..2ca1124e76 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -1,9 +1,11 @@ require 'active_support/inflector/inflections' -# Define the standard inflection rules. These define a starting point for +#-- +# Defines the standard inflection rules. These are the starting point for # new projects and are not considered complete. The current set of inflection # rules is frozen. This means, we do not change them to become more complete. # This is a safety measure to keep existing applications from breaking. +#++ module ActiveSupport Inflector.inflections(:en) do |inflect| inflect.plural(/$/, 's') -- cgit v1.2.3 From b927d67decb9d4e5103b5991b7e26a4dab4eca92 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Tue, 4 Feb 2014 09:31:48 -0800 Subject: Renamed session_serializer option to cookies_serializer --- actionpack/lib/action_dispatch.rb | 2 - .../lib/action_dispatch/middleware/cookies.rb | 45 ++++++++++++++-------- .../middleware/session/json_serializer.rb | 13 ------- .../middleware/session/marshal_serializer.rb | 14 ------- actionpack/test/dispatch/cookies_test.rb | 12 +++--- guides/source/action_controller_overview.md | 43 +++++++++++++-------- railties/lib/rails/application.rb | 2 +- 7 files changed, 64 insertions(+), 67 deletions(-) delete mode 100644 actionpack/lib/action_dispatch/middleware/session/json_serializer.rb delete mode 100644 actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index a56d827b1a..3dd2e2a45c 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -84,8 +84,6 @@ module ActionDispatch autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store' autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store' autoload :CacheStore, 'action_dispatch/middleware/session/cache_store' - autoload :JsonSerializer, 'action_dispatch/middleware/session/json_serializer' - autoload :MarshalSerializer, 'action_dispatch/middleware/session/marshal_serializer' end mattr_accessor :test_app diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 531654895b..7e8a395d93 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -89,7 +89,7 @@ module ActionDispatch ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze SECRET_TOKEN = "action_dispatch.secret_token".freeze SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze - SESSION_SERIALIZER = "action_dispatch.session_serializer".freeze + COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze # Cookies can typically store 4096 bytes. MAX_COOKIE_SIZE = 4096 @@ -212,7 +212,7 @@ module ActionDispatch secret_token: env[SECRET_TOKEN], secret_key_base: env[SECRET_KEY_BASE], upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?, - session_serializer: env[SESSION_SERIALIZER] + serializer: env[COOKIES_SERIALIZER] } end @@ -374,14 +374,40 @@ module ActionDispatch end end + class JsonSerializer + def self.load(value) + JSON.parse(value, quirks_mode: true) + end + + def self.dump(value) + JSON.generate(value, quirks_mode: true) + end + end + + module SerializedCookieJars + protected + def serializer + serializer = @options[:serializer] || :marshal + case serializer + when :marshal + Marshal + when :json + JsonSerializer + else + serializer + end + end + end + class SignedCookieJar #:nodoc: include ChainedCookieJars + include SerializedCookieJars def initialize(parent_jar, key_generator, options = {}) @parent_jar = parent_jar @options = options secret = key_generator.generate_key(@options[:signed_cookie_salt]) - @verifier = ActiveSupport::MessageVerifier.new(secret) + @verifier = ActiveSupport::MessageVerifier.new(secret, serializer: serializer) end def [](name) @@ -426,6 +452,7 @@ module ActionDispatch class EncryptedCookieJar #:nodoc: include ChainedCookieJars + include SerializedCookieJars def initialize(parent_jar, key_generator, options = {}) if ActiveSupport::LegacyKeyGenerator === key_generator @@ -464,18 +491,6 @@ module ActionDispatch rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage nil end - - def serializer - serializer = @options[:session_serializer] || :marshal - case serializer - when :marshal - ActionDispatch::Session::MarshalSerializer - when :json - ActionDispatch::Session::JsonSerializer - else - serializer - end - end end # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore diff --git a/actionpack/lib/action_dispatch/middleware/session/json_serializer.rb b/actionpack/lib/action_dispatch/middleware/session/json_serializer.rb deleted file mode 100644 index d341853f7a..0000000000 --- a/actionpack/lib/action_dispatch/middleware/session/json_serializer.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ActionDispatch - module Session - class JsonSerializer - def self.load(value) - JSON.parse(value, quirks_mode: true) - end - - def self.dump(value) - JSON.generate(value, quirks_mode: true) - end - end - end -end diff --git a/actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb b/actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb deleted file mode 100644 index 26622f682d..0000000000 --- a/actionpack/lib/action_dispatch/middleware/session/marshal_serializer.rb +++ /dev/null @@ -1,14 +0,0 @@ -module ActionDispatch - module Session - class MarshalSerializer - def self.load(value) - Marshal.load(value) - end - - def self.dump(value) - Marshal.dump(value) - end - end - end -end - diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 6101acdc25..25a2fe199c 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -379,28 +379,28 @@ class CookiesTest < ActionController::TestCase assert_equal 'bar', cookies.encrypted[:foo] end - class CustomJsonSerializer + class CustomSerializer def self.load(value) - JSON.load(value) + " and loaded" + value.to_s + " and loaded" end def self.dump(value) - JSON.dump(value + " was dumped") + value.to_s + " was dumped" end end def test_encrypted_cookie_using_serializer_object - @request.env["action_dispatch.session_serializer"] = CustomJsonSerializer + @request.env["action_dispatch.cookies_serializer"] = CustomSerializer get :set_encrypted_cookie assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo] end def test_encrypted_cookie_using_json_serializer - @request.env["action_dispatch.session_serializer"] = :json + @request.env["action_dispatch.cookies_serializer"] = :json get :set_encrypted_cookie cookies = @controller.send :cookies assert_not_equal 'bar', cookies[:foo] - assert_raises TypeError do + assert_raises ::JSON::ParserError do cookies.signed[:foo] end assert_equal 'bar', cookies.encrypted[:foo] diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 9eaf03dd82..b142279991 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -381,22 +381,6 @@ You can also pass a `:domain` key and specify the domain name for the cookie: YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com" ``` -You can pass `:serializer` key to specify serializer for serializing session: - -```ruby -YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', serializer: :json -``` - -The default serializer for new application is `:json`. For compatibility with -old applications `:marshal` is used when `serializer` option is not specified. - -It is also possible to pass a custom serializer class with `load` and `dump` -public methods defined: - -```ruby -YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', serializer: MyCustomSerializer -``` - Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in `config/initializers/secret_token.rb` ```ruby @@ -588,6 +572,33 @@ end Note that while for session values you set the key to `nil`, to delete a cookie value you should use `cookies.delete(:key)`. +Rails also provides a signed cookie jar and an encrypted cookie jar for storing +sensitive data. The signed cookie jar appends a cryptographic signature on the +cookie values to protect their integrity. The encrypted cookie jar encrypts the +values in addition to signing them, so that they cannot be read by the end user. +Refer to the [API documentation](http://api.rubyonrails.org/classes/ActionDispatch/Cookies.html) +for more details. + +These special cookie jars use a serializer to serialize the assigned values into +strings and deserializes them into Ruby objects on read. + +You can specify what serializer to use: + +```ruby +YourApp::Application.config.cookies_serializer :json +``` + +The possible options are `:marshal` or `:json`. The default serializer for new +applications is `:json`. For compatibility with old applications with existing +cookies, `:marshal` is used when `serializer` option is not specified. + +It is also possible to pass a custom serializer class or object that responds +to `load` and `dump`: + +```ruby +YourApp::Application.config.cookies_serializer MyCustomSerializer +``` + Rendering XML and JSON data --------------------------- diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 36432e56ba..d018247c5a 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -206,7 +206,7 @@ module Rails "action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt, "action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt, "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt, - "action_dispatch.session_serializer" => config.session_options[:serializer] + "action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer }) end end -- cgit v1.2.3 From 54641fa2e3d248bf89e1a9954e06d6bbefa7bafa Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Wed, 5 Feb 2014 03:15:11 -0800 Subject: Just very so slightly better test coverage --- actionpack/test/dispatch/cookies_test.rb | 72 +++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 25a2fe199c..8c52ed348d 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -11,6 +11,16 @@ require 'active_support/key_generator' require 'active_support/message_verifier' class CookiesTest < ActionController::TestCase + class CustomSerializer + def self.load(value) + value.to_s + " and loaded" + end + + def self.dump(value) + value.to_s + " was dumped" + end + end + class TestController < ActionController::Base def authenticate cookies["user_name"] = "david" @@ -364,35 +374,60 @@ class CookiesTest < ActionController::TestCase assert_equal 45, @controller.send(:cookies).signed[:user_id] end + def test_signed_cookie_using_default_serializer + get :set_signed_cookie + cookies = @controller.send :cookies + assert_not_equal 45, cookies[:user_id] + assert_equal 45, cookies.signed[:user_id] + end + + def test_signed_cookie_using_marshal_serializer + @request.env["action_dispatch.cookies_serializer"] = :marshal + get :set_signed_cookie + cookies = @controller.send :cookies + assert_not_equal 45, cookies[:user_id] + assert_equal 45, cookies.signed[:user_id] + end + + def test_signed_cookie_using_json_serializer + @request.env["action_dispatch.cookies_serializer"] = :json + get :set_signed_cookie + cookies = @controller.send :cookies + assert_not_equal 45, cookies[:user_id] + assert_equal 45, cookies.signed[:user_id] + end + + def test_signed_cookie_using_custom_serializer + @request.env["action_dispatch.cookies_serializer"] = CustomSerializer + get :set_signed_cookie + assert_not_equal 45, cookies[:user_id] + assert_equal '45 was dumped and loaded', cookies.signed[:user_id] + end + def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature get :set_signed_cookie assert_nil @controller.send(:cookies).signed[:non_existant_attribute] end - def test_encrypted_cookie + def test_encrypted_cookie_using_default_serializer get :set_encrypted_cookie cookies = @controller.send :cookies assert_not_equal 'bar', cookies[:foo] - assert_raises TypeError do + assert_raise TypeError do cookies.signed[:foo] end assert_equal 'bar', cookies.encrypted[:foo] end - class CustomSerializer - def self.load(value) - value.to_s + " and loaded" - end - - def self.dump(value) - value.to_s + " was dumped" - end - end - - def test_encrypted_cookie_using_serializer_object - @request.env["action_dispatch.cookies_serializer"] = CustomSerializer + def test_encrypted_cookie_using_marshal_serializer + @request.env["action_dispatch.cookies_serializer"] = :marshal get :set_encrypted_cookie - assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo] + cookies = @controller.send :cookies + assert_not_equal 'bar', cookies[:foo] + assert_raises TypeError do + cookies.signed[:foo] + end + assert_equal 'bar', cookies.encrypted[:foo] end def test_encrypted_cookie_using_json_serializer @@ -406,6 +441,13 @@ class CookiesTest < ActionController::TestCase assert_equal 'bar', cookies.encrypted[:foo] end + def test_encrypted_cookie_using_custom_serializer + @request.env["action_dispatch.cookies_serializer"] = CustomSerializer + get :set_encrypted_cookie + assert_not_equal 45, cookies.encrypted[:foo] + assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo] + end + def test_accessing_nonexistant_encrypted_cookie_should_not_raise_invalid_message get :set_encrypted_cookie assert_nil @controller.send(:cookies).encrypted[:non_existant_attribute] -- cgit v1.2.3 From fafe8ece9d406cfbb197cc424baaa15a5772fae5 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Wed, 5 Feb 2014 03:17:28 -0800 Subject: Added HybridSerializer to upgrade existing marshal cookies (wip: need tests) --- actionpack/lib/action_dispatch/middleware/cookies.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 7e8a395d93..fa94f9c9e4 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -384,6 +384,18 @@ module ActionDispatch end end + class HybridSerializer < JsonSerializer + MARSHAL_SIGNATURE = "\x04\x08".freeze + + def self.load(value) + if value.start_with?(MARSHAL_SIGNATURE) + Marshal.load(value) + else + super + end + end + end + module SerializedCookieJars protected def serializer @@ -393,6 +405,8 @@ module ActionDispatch Marshal when :json JsonSerializer + when :hybrid + HybridSerializer else serializer end -- cgit v1.2.3 From 25f68ac6a25b5b2ee2b30832ec052e5649d4f10c Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Wed, 5 Feb 2014 03:24:49 -0800 Subject: Removed an old test --- actionpack/test/dispatch/cookies_test.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 8c52ed348d..6162cea6b7 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -369,11 +369,6 @@ class CookiesTest < ActionController::TestCase assert_equal 'Jamie', @controller.send(:cookies).permanent[:user_name] end - def test_signed_cookie - get :set_signed_cookie - assert_equal 45, @controller.send(:cookies).signed[:user_id] - end - def test_signed_cookie_using_default_serializer get :set_signed_cookie cookies = @controller.send :cookies -- cgit v1.2.3 From d4b7aa735a0044da6b751cb72ce5d4fd979476d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sat, 8 Feb 2014 01:23:06 -0200 Subject: Tests for the HybridSerializer --- actionpack/test/dispatch/cookies_test.rb | 68 ++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 6162cea6b7..985abeb215 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -399,6 +399,38 @@ class CookiesTest < ActionController::TestCase assert_equal '45 was dumped and loaded', cookies.signed[:user_id] end + def test_signed_cookie_using_hybrid_serializer_can_read_from_marshal_dumped_value + @request.env["action_dispatch.cookies_serializer"] = :hybrid + + key_generator = @request.env["action_dispatch.key_generator"] + signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"] + secret = key_generator.generate_key(signed_cookie_salt) + legacy_value = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal).generate(45) + @request.headers["Cookie"] = "user_id=#{legacy_value}" + + get :get_signed_cookie + + cookies = @controller.send :cookies + assert_not_equal 45, cookies[:user_id] + assert_equal 45, cookies.signed[:user_id] + end + + def test_signed_cookie_using_hybrid_serializer_can_read_from_json_dumped_value + @request.env["action_dispatch.cookies_serializer"] = :hybrid + + key_generator = @request.env["action_dispatch.key_generator"] + signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"] + secret = key_generator.generate_key(signed_cookie_salt) + legacy_value = ActiveSupport::MessageVerifier.new(secret, serializer: JSON).generate(45) + @request.headers["Cookie"] = "user_id=#{legacy_value}" + + get :get_signed_cookie + + cookies = @controller.send :cookies + assert_not_equal 45, cookies[:user_id] + assert_equal 45, cookies.signed[:user_id] + end + def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature get :set_signed_cookie assert_nil @controller.send(:cookies).signed[:non_existant_attribute] @@ -443,6 +475,42 @@ class CookiesTest < ActionController::TestCase assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo] end + def test_encrypted_cookie_using_hybrid_serializer_can_read_from_marshal_dumped_value + @request.env["action_dispatch.cookies_serializer"] = :hybrid + + key_generator = @request.env["action_dispatch.key_generator"] + encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"] + encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"] + secret = key_generator.generate_key(encrypted_cookie_salt) + sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) + legacy_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: Marshal).encrypt_and_sign(45) + @request.headers["Cookie"] = "user_id=#{legacy_value}" + + get :get_encrypted_cookie + + cookies = @controller.send :cookies + assert_not_equal 45, cookies[:user_id] + assert_equal 45, cookies.encrypted[:user_id] + end + + def test_encrypted_cookie_using_hybrid_serializer_can_read_from_marshal_json_value + @request.env["action_dispatch.cookies_serializer"] = :hybrid + + key_generator = @request.env["action_dispatch.key_generator"] + encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"] + encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"] + secret = key_generator.generate_key(encrypted_cookie_salt) + sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) + legacy_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON).encrypt_and_sign(45) + @request.headers["Cookie"] = "user_id=#{legacy_value}" + + get :get_encrypted_cookie + + cookies = @controller.send :cookies + assert_not_equal 45, cookies[:user_id] + assert_equal 45, cookies.encrypted[:user_id] + end + def test_accessing_nonexistant_encrypted_cookie_should_not_raise_invalid_message get :set_encrypted_cookie assert_nil @controller.send(:cookies).encrypted[:non_existant_attribute] -- cgit v1.2.3 From 6de4888e0485d50c7d5e3b97a485e126b561ef6c Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Sat, 8 Feb 2014 10:16:43 -0800 Subject: Fixed minor typo in test code --- actionpack/test/dispatch/cookies_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 985abeb215..4da19933f0 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -493,7 +493,7 @@ class CookiesTest < ActionController::TestCase assert_equal 45, cookies.encrypted[:user_id] end - def test_encrypted_cookie_using_hybrid_serializer_can_read_from_marshal_json_value + def test_encrypted_cookie_using_hybrid_serializer_can_read_from_json_dumped_value @request.env["action_dispatch.cookies_serializer"] = :hybrid key_generator = @request.env["action_dispatch.key_generator"] -- cgit v1.2.3 From ba6861d032013416034cdb20012962b97460795f Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Sat, 8 Feb 2014 11:28:53 -0800 Subject: Changed the tests to ensure HybridSerializer actually migrates the cookies (currently failing) --- actionpack/test/dispatch/cookies_test.rb | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 4da19933f0..0c3d3d687c 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -399,20 +399,24 @@ class CookiesTest < ActionController::TestCase assert_equal '45 was dumped and loaded', cookies.signed[:user_id] end - def test_signed_cookie_using_hybrid_serializer_can_read_from_marshal_dumped_value + def test_signed_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json @request.env["action_dispatch.cookies_serializer"] = :hybrid key_generator = @request.env["action_dispatch.key_generator"] signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"] secret = key_generator.generate_key(signed_cookie_salt) - legacy_value = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal).generate(45) - @request.headers["Cookie"] = "user_id=#{legacy_value}" + + marshal_value = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal).generate(45) + @request.headers["Cookie"] = "user_id=#{marshal_value}" get :get_signed_cookie cookies = @controller.send :cookies assert_not_equal 45, cookies[:user_id] assert_equal 45, cookies.signed[:user_id] + + json_value = ActiveSupport::MessageVerifier.new(secret, serializer: JSON).generate(45) + assert_equal @response.cookies['user_id'], json_value end def test_signed_cookie_using_hybrid_serializer_can_read_from_json_dumped_value @@ -421,8 +425,8 @@ class CookiesTest < ActionController::TestCase key_generator = @request.env["action_dispatch.key_generator"] signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"] secret = key_generator.generate_key(signed_cookie_salt) - legacy_value = ActiveSupport::MessageVerifier.new(secret, serializer: JSON).generate(45) - @request.headers["Cookie"] = "user_id=#{legacy_value}" + json_value = ActiveSupport::MessageVerifier.new(secret, serializer: JSON).generate(45) + @request.headers["Cookie"] = "user_id=#{json_value}" get :get_signed_cookie @@ -475,7 +479,7 @@ class CookiesTest < ActionController::TestCase assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo] end - def test_encrypted_cookie_using_hybrid_serializer_can_read_from_marshal_dumped_value + def test_encrypted_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json @request.env["action_dispatch.cookies_serializer"] = :hybrid key_generator = @request.env["action_dispatch.key_generator"] @@ -483,14 +487,18 @@ class CookiesTest < ActionController::TestCase encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"] secret = key_generator.generate_key(encrypted_cookie_salt) sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) - legacy_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: Marshal).encrypt_and_sign(45) - @request.headers["Cookie"] = "user_id=#{legacy_value}" + + marshal_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: Marshal).encrypt_and_sign(45) + @request.headers["Cookie"] = "user_id=#{marshal_value}" get :get_encrypted_cookie cookies = @controller.send :cookies assert_not_equal 45, cookies[:user_id] assert_equal 45, cookies.encrypted[:user_id] + + json_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON).encrypt_and_sign(45) + assert_equal @response.cookies["user_id"], json_value end def test_encrypted_cookie_using_hybrid_serializer_can_read_from_json_dumped_value @@ -501,8 +509,8 @@ class CookiesTest < ActionController::TestCase encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"] secret = key_generator.generate_key(encrypted_cookie_salt) sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) - legacy_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON).encrypt_and_sign(45) - @request.headers["Cookie"] = "user_id=#{legacy_value}" + json_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON).encrypt_and_sign(45) + @request.headers["Cookie"] = "user_id=#{json_value}" get :get_encrypted_cookie -- cgit v1.2.3 From a6ce984b49519de7701aa13d04300c9d03cf8f72 Mon Sep 17 00:00:00 2001 From: Guillermo Iguaran Date: Sat, 8 Feb 2014 23:56:40 -0500 Subject: Convert FlashHash in a Hash with indifferent access --- actionpack/lib/action_dispatch/middleware/flash.rb | 19 +++++++++++++++---- actionpack/test/controller/flash_hash_test.rb | 10 ++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 89003e7a5e..419bcb8a73 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -50,13 +50,14 @@ module ActionDispatch end def []=(k, v) + k = k.to_s @flash[k] = v @flash.discard(k) v end def [](k) - @flash[k] + @flash[k.to_s] end # Convenience accessor for flash.now[:alert]=. @@ -92,7 +93,7 @@ module ActionDispatch end def initialize(flashes = {}, discard = []) #:nodoc: - @discard = Set.new(discard) + @discard = Set.new(stringify_array(discard)) @flashes = flashes @now = nil end @@ -106,16 +107,17 @@ module ActionDispatch end def []=(k, v) + k = k.to_s @discard.delete k @flashes[k] = v end def [](k) - @flashes[k] + @flashes[k.to_s] end def update(h) #:nodoc: - @discard.subtract h.keys + @discard.subtract stringify_array(h.keys) @flashes.update h self end @@ -129,6 +131,7 @@ module ActionDispatch end def delete(key) + key = key.to_s @discard.delete key @flashes.delete key self @@ -186,6 +189,7 @@ module ActionDispatch # flash.keep # keeps the entire flash # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded def keep(k = nil) + k = k.to_s if k @discard.subtract Array(k || keys) k ? self[k] : self end @@ -195,6 +199,7 @@ module ActionDispatch # flash.discard # discard the entire flash at the end of the current action # flash.discard(:warning) # discard only the "warning" entry at the end of the current action def discard(k = nil) + k = k.to_s if k @discard.merge Array(k || keys) k ? self[k] : self end @@ -231,6 +236,12 @@ module ActionDispatch def now_is_loaded? @now end + + def stringify_array(array) + array.map do |item| + item.kind_of?(Symbol) ? item.to_s : item + end + end end def initialize(app) diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb index 5490d9394b..50b36a0567 100644 --- a/actionpack/test/controller/flash_hash_test.rb +++ b/actionpack/test/controller/flash_hash_test.rb @@ -67,6 +67,16 @@ module ActionDispatch assert_equal({'flashes' => {'message' => 'Hello'}, 'discard' => %w[message]}, hash.to_session_value) end + def test_from_session_value_on_json_serializer + decrypted_data = "{ \"session_id\":\"d98bdf6d129618fc2548c354c161cfb5\", \"flash\":{\"discard\":[], \"flashes\":{\"message\":\"hey you\"}} }" + session = ActionDispatch::Cookies::JsonSerializer.load(decrypted_data) + hash = Flash::FlashHash.from_session_value(session['flash']) + + assert_equal({'discard' => %w[message], 'flashes' => { 'message' => 'hey you'}}, hash.to_session_value) + assert_equal "hey you", hash[:message] + assert_equal "hey you", hash["message"] + end + def test_empty? assert @hash.empty? @hash['zomg'] = 'bears' -- cgit v1.2.3 From a668beffd64106a1e1fedb71cc25eaaa11baf0c1 Mon Sep 17 00:00:00 2001 From: Guillermo Iguaran Date: Sun, 9 Feb 2014 00:35:10 -0500 Subject: Stringify the incoming hash in FlashHash Stringify the incoming as well to handle incoming symbol keys from marshalled sessions --- actionpack/lib/action_dispatch/middleware/flash.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 419bcb8a73..1e45a38e5f 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/hash/keys' + module ActionDispatch class Request < Rack::Request # Access the contents of the flash. Use flash["notice"] to @@ -94,7 +96,7 @@ module ActionDispatch def initialize(flashes = {}, discard = []) #:nodoc: @discard = Set.new(stringify_array(discard)) - @flashes = flashes + @flashes = flashes.stringify_keys @now = nil end -- cgit v1.2.3 From ead947a3b2bc672b6064a6d0d33905d45299d22e Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Sun, 9 Feb 2014 01:12:11 -0800 Subject: Re-write legacy (marshal) cookies on read --- .../lib/action_dispatch/middleware/cookies.rb | 60 ++++++++++++++-------- actionpack/test/dispatch/cookies_test.rb | 32 ++++++------ 2 files changed, 57 insertions(+), 35 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index fa94f9c9e4..2af45d43bb 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -384,29 +384,48 @@ module ActionDispatch end end - class HybridSerializer < JsonSerializer - MARSHAL_SIGNATURE = "\x04\x08".freeze - + # Passing the NullSerializer downstream to the Message{Encryptor,Verifier} + # allows us to handle the (de)serialization step within the cookie jar, + # which gives us the opportunity to detect and migrate legacy cookies. + class NullSerializer def self.load(value) - if value.start_with?(MARSHAL_SIGNATURE) - Marshal.load(value) - else - super - end + value + end + + def self.dump(value) + value end end module SerializedCookieJars + MARSHAL_SIGNATURE = "\x04\x08".freeze + protected + def needs_migration?(value) + @options[:serializer] == :hybrid && value.start_with?(MARSHAL_SIGNATURE) + end + + def serialize(name, value) + serializer.dump(value) + end + + def deserialize(name, value) + if value + if needs_migration?(value) + self[name] = Marshal.load(value) + else + serializer.load(value) + end + end + end + def serializer serializer = @options[:serializer] || :marshal case serializer when :marshal Marshal - when :json + when :json, :hybrid JsonSerializer - when :hybrid - HybridSerializer else serializer end @@ -421,21 +440,21 @@ module ActionDispatch @parent_jar = parent_jar @options = options secret = key_generator.generate_key(@options[:signed_cookie_salt]) - @verifier = ActiveSupport::MessageVerifier.new(secret, serializer: serializer) + @verifier = ActiveSupport::MessageVerifier.new(secret, serializer: NullSerializer) end def [](name) if signed_message = @parent_jar[name] - verify(signed_message) + deserialize name, verify(signed_message) end end def []=(name, options) if options.is_a?(Hash) options.symbolize_keys! - options[:value] = @verifier.generate(options[:value]) + options[:value] = @verifier.generate(serialize(name, options[:value])) else - options = { :value => @verifier.generate(options) } + options = { :value => @verifier.generate(serialize(name, options)) } end raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE @@ -459,7 +478,7 @@ module ActionDispatch def [](name) if signed_message = @parent_jar[name] - verify(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message) + deserialize(name, verify(signed_message)) || verify_and_upgrade_legacy_signed_message(name, signed_message) end end end @@ -478,12 +497,12 @@ module ActionDispatch @options = options secret = key_generator.generate_key(@options[:encrypted_cookie_salt]) sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt]) - @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: serializer) + @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: NullSerializer) end def [](name) if encrypted_message = @parent_jar[name] - decrypt_and_verify(encrypted_message) + deserialize name, decrypt_and_verify(encrypted_message) end end @@ -493,7 +512,8 @@ module ActionDispatch else options = { :value => options } end - options[:value] = @encryptor.encrypt_and_sign(options[:value]) + + options[:value] = @encryptor.encrypt_and_sign(serialize(name, options[:value])) raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE @parent_jar[name] = options @@ -516,7 +536,7 @@ module ActionDispatch def [](name) if encrypted_or_signed_message = @parent_jar[name] - decrypt_and_verify(encrypted_or_signed_message) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message) + deserialize(name, decrypt_and_verify(encrypted_or_signed_message)) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message) end end end diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 0c3d3d687c..ba7aaa338d 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -415,8 +415,8 @@ class CookiesTest < ActionController::TestCase assert_not_equal 45, cookies[:user_id] assert_equal 45, cookies.signed[:user_id] - json_value = ActiveSupport::MessageVerifier.new(secret, serializer: JSON).generate(45) - assert_equal @response.cookies['user_id'], json_value + verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON) + assert_equal 45, verifier.verify(@response.cookies['user_id']) end def test_signed_cookie_using_hybrid_serializer_can_read_from_json_dumped_value @@ -433,6 +433,8 @@ class CookiesTest < ActionController::TestCase cookies = @controller.send :cookies assert_not_equal 45, cookies[:user_id] assert_equal 45, cookies.signed[:user_id] + + assert_nil @response.cookies["user_id"] end def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature @@ -475,7 +477,7 @@ class CookiesTest < ActionController::TestCase def test_encrypted_cookie_using_custom_serializer @request.env["action_dispatch.cookies_serializer"] = CustomSerializer get :set_encrypted_cookie - assert_not_equal 45, cookies.encrypted[:foo] + assert_not_equal 'bar', cookies.encrypted[:foo] assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo] end @@ -488,17 +490,17 @@ class CookiesTest < ActionController::TestCase secret = key_generator.generate_key(encrypted_cookie_salt) sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) - marshal_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: Marshal).encrypt_and_sign(45) - @request.headers["Cookie"] = "user_id=#{marshal_value}" + marshal_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: Marshal).encrypt_and_sign("bar") + @request.headers["Cookie"] = "foo=#{marshal_value}" get :get_encrypted_cookie cookies = @controller.send :cookies - assert_not_equal 45, cookies[:user_id] - assert_equal 45, cookies.encrypted[:user_id] + assert_not_equal "bar", cookies[:foo] + assert_equal "bar", cookies.encrypted[:foo] - json_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON).encrypt_and_sign(45) - assert_equal @response.cookies["user_id"], json_value + encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON) + assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) end def test_encrypted_cookie_using_hybrid_serializer_can_read_from_json_dumped_value @@ -509,14 +511,16 @@ class CookiesTest < ActionController::TestCase encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"] secret = key_generator.generate_key(encrypted_cookie_salt) sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) - json_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON).encrypt_and_sign(45) - @request.headers["Cookie"] = "user_id=#{json_value}" + json_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON).encrypt_and_sign("bar") + @request.headers["Cookie"] = "foo=#{json_value}" get :get_encrypted_cookie cookies = @controller.send :cookies - assert_not_equal 45, cookies[:user_id] - assert_equal 45, cookies.encrypted[:user_id] + assert_not_equal "bar", cookies[:foo] + assert_equal "bar", cookies.encrypted[:foo] + + assert_nil @response.cookies["foo"] end def test_accessing_nonexistant_encrypted_cookie_should_not_raise_invalid_message @@ -834,8 +838,6 @@ class CookiesTest < ActionController::TestCase assert_equal "dhh", cookies['user_name'] end - - def test_setting_request_cookies_is_indifferent_access cookies.clear cookies[:user_name] = "andrew" -- cgit v1.2.3 From 3a89386fcf8a87c93aaf1571ca8ee4a234086ea8 Mon Sep 17 00:00:00 2001 From: Guillermo Iguaran Date: Sun, 9 Feb 2014 11:05:25 -0500 Subject: Remove serializer option from session_store.rb template --- .../rails/app/templates/config/initializers/session_store.rb.tt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt index 097fcb4bb0..2bb9b82c61 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt @@ -1,3 +1,3 @@ # Be sure to restart your server when you modify this file. -Rails.application.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %>, serializer: :json +Rails.application.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %> -- cgit v1.2.3 From cd5960e9761c8618a89bf40a3048e330fd08143c Mon Sep 17 00:00:00 2001 From: Guillermo Iguaran Date: Sun, 9 Feb 2014 11:47:38 -0500 Subject: Fix AppGeneratorTest: serializer option was removed from session_store --- railties/test/generators/app_generator_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 700935fd8d..ddecee2ca1 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -433,7 +433,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_new_hash_style run_generator [destination_root] assert_file "config/initializers/session_store.rb" do |file| - assert_match(/config.session_store :cookie_store, key: '_.+_session', serializer: :json/, file) + assert_match(/config.session_store :cookie_store, key: '_.+_session'/, file) end end -- cgit v1.2.3 From b97e087321f33283d836c5b5964976c88230349a Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Tue, 11 Feb 2014 00:38:36 -0800 Subject: Fixed broken flash tests --- actionpack/lib/action_dispatch/middleware/flash.rb | 2 +- actionpack/test/controller/flash_test.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 1e45a38e5f..b82f0f0825 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -120,7 +120,7 @@ module ActionDispatch def update(h) #:nodoc: @discard.subtract stringify_array(h.keys) - @flashes.update h + @flashes.update h.stringify_keys self end diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 9ceab91e42..25a4857eba 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -175,13 +175,13 @@ class FlashTest < ActionController::TestCase assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed assert_nil flash.discard(:unknown) # non existent key passed - assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard().to_hash) # nothing passed - assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed + assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard().to_hash) # nothing passed + assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed assert_equal(:foo_indeed, flash.keep(:foo)) # valid key passed assert_nil flash.keep(:unknown) # non existent key passed - assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep().to_hash) # nothing passed - assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed + assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep().to_hash) # nothing passed + assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed end def test_redirect_to_with_alert -- cgit v1.2.3 From 9fc7a6fcedd3adc820d9d481d9362313c356747b Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Tue, 11 Feb 2014 00:43:37 -0800 Subject: Missed FlashHash#replace --- actionpack/lib/action_dispatch/middleware/flash.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index b82f0f0825..4821d2a899 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -160,7 +160,7 @@ module ActionDispatch def replace(h) #:nodoc: @discard.clear - @flashes.replace h + @flashes.replace h.stringify_keys self end -- cgit v1.2.3 From ecf04f19b0754de8a2937acd9b03e42e94a570aa Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Tue, 11 Feb 2014 00:45:44 -0800 Subject: Added changelog entry for Flash changes [ci skip] --- actionpack/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 15541d58b5..d3177df1c3 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,7 @@ +* `FlashHash` now behaves like a `HashWithIndifferentAccess`. + + *Guillermo Iguaran* + * Set the `:shallow_path` scope option as each scope is generated rather than waiting until the `shallow` option is set. Also make the behavior of the `:shallow` resource option consistent with the behavior of the `shallow` method. -- cgit v1.2.3 From 0b86a6e950ed78822470793deddbec41c6d105f5 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Tue, 11 Feb 2014 02:13:09 -0800 Subject: Updated CHANGELOG, docs, guides and release notes. Also added a `cookies_serializer.rb` initializer to the app template. --- actionpack/CHANGELOG.md | 29 +++++++++++----------- guides/source/4_1_release_notes.md | 6 ++--- guides/source/action_controller_overview.md | 19 ++++++++------ guides/source/upgrading_ruby_on_rails.md | 13 ++++++++++ .../config/initializers/cookies_serializer.rb | 3 +++ 5 files changed, 44 insertions(+), 26 deletions(-) create mode 100644 railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index d3177df1c3..342f670e78 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,17 @@ +* Add new config option `config.action_dispatch.cookies_serializer` for + specifying a serializer for the signed and encrypted cookie jars. + + The possible values are: + + * `:json` - serialize cookie values with `JSON` + * `:marshal` - serialize cookie values with `Marshal` + * `:hybrid` - transparently migrate existing `Marshal` cookie values to `JSON` + + For new apps `:json` option is added by default and `:marshal` is used + when no option is specified to maintain backwards compatibility. + + *Łukasz Sarnacki*, *Matt Aimonetti*, *Guillermo Iguaran*, *Godfrey Chan*, *Rafael Mendonça França* + * `FlashHash` now behaves like a `HashWithIndifferentAccess`. *Guillermo Iguaran* @@ -20,21 +34,6 @@ *Josh Jordan* -* Add `:serializer` option for `config.session_store :cookie_store`. This - changes default serializer when using `:cookie_store`. - - It is possible to pass: - - * `:json` which is a secure wrapper on JSON using `JSON.parse` and - `JSON.generate` methods with quirks mode; - * `:marshal` which is a wrapper on Marshal; - * serializer class with `load` and `dump` methods defined. - - For new apps `:json` option is added by default and :marshal is used - when no option is specified. - - *Łukasz Sarnacki*, *Matt Aimonetti* - * Ensure that `request.filtered_parameters` is reset between calls to `process` in `ActionController::TestCase`. diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index 90e6b2fcbc..8fcfc71351 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -346,10 +346,8 @@ for detailed changes. params "deep munging" that was used to address security vulnerability CVE-2013-0155. ([Pull Request](https://github.com/rails/rails/pull/13188)) -* Added `:serializer` option for `config.session_store :cookie_store`. This - changes default serializer when using - `:cookie_store`. ([Pull Request](https://github.com/rails/rails/pull/13692)) - +* New config option `config.action_dispatch.cookies_serializer` for specifying + a serializer for the signed and encrypted cookie jars. (Pull Requests [1](https://github.com/rails/rails/pull/13692), [2](https://github.com/rails/rails/pull/13945) / [More Details](upgrading_ruby_on_rails.html#cookies-serializer)) Action Mailer ------------- diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index b142279991..222d86afe9 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -585,18 +585,23 @@ strings and deserializes them into Ruby objects on read. You can specify what serializer to use: ```ruby -YourApp::Application.config.cookies_serializer :json +Rails.application.config.action_dispatch.cookies_serializer = :json ``` -The possible options are `:marshal` or `:json`. The default serializer for new -applications is `:json`. For compatibility with old applications with existing -cookies, `:marshal` is used when `serializer` option is not specified. +The default serializer for new applications is `:json`. For compatibility with +old applications with existing cookies, `:marshal` is used when `serializer` +option is not specified. -It is also possible to pass a custom serializer class or object that responds -to `load` and `dump`: +You may also set this option to `:hybrid`, in which case Rails would transparently +deserialize existing (`Marshal`-serialized) cookies on read and re-write them in +the `JSON` format. This is useful for migrating existing applications to the +`:json` serializer. + +It is also possible to pass a custom serializer that responds to `load` and +`dump`: ```ruby -YourApp::Application.config.cookies_serializer MyCustomSerializer +Rails.application.config.action_dispatch.cookies_serializer = MyCustomSerializer ``` Rendering XML and JSON data diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 2055452935..8aae3bbc1a 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -98,6 +98,19 @@ If your test helper contains a call to is now done automatically when you `require 'test_help'`, although leaving this line in your helper is not harmful in any way. +### Cookies serializer + +Applications created before Rails 4.1 uses `Marshal` to serialize cookie values into +the signed and encrypted cookie jars. If you want to use the new `JSON`-based format +in your application, you can add an initializer file with the following content: + + ```ruby + Rails.application.config.cookies_serializer :hybrid + ``` + +This would transparently migrate your existing `Marshal`-serialized cookies into the +new `JSON`-based format. + ### Changes in JSON handling There are a few major changes related to JSON handling in Rails 4.1. diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb new file mode 100644 index 0000000000..7a06a89f0f --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb @@ -0,0 +1,3 @@ +# Be sure to restart your server when you modify this file. + +Rails.application.config.action_dispatch.cookies_serializer = :json \ No newline at end of file -- cgit v1.2.3 From 7a3ef9842b3cbfe6dbe14700086824d163ce4d51 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Tue, 11 Feb 2014 02:55:48 -0800 Subject: Migrate hash-based cookie values correctly --- actionpack/lib/action_dispatch/middleware/cookies.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 2af45d43bb..31341dba63 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -181,7 +181,7 @@ module ActionDispatch def verify_and_upgrade_legacy_signed_message(name, signed_message) @legacy_verifier.verify(signed_message).tap do |value| - self[name] = value + self[name] = { value: value } end rescue ActiveSupport::MessageVerifier::InvalidSignature nil @@ -412,7 +412,9 @@ module ActionDispatch def deserialize(name, value) if value if needs_migration?(value) - self[name] = Marshal.load(value) + Marshal.load(value).tap do |value| + self[name] = { value: value } + end else serializer.load(value) end -- cgit v1.2.3 From dafc0eef4dd3393864e7b28bf74c8e7834083d60 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Tue, 11 Feb 2014 03:56:35 -0800 Subject: rm warning about variable shadowing --- actionpack/lib/action_dispatch/middleware/cookies.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 31341dba63..18e64704f6 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -412,8 +412,8 @@ module ActionDispatch def deserialize(name, value) if value if needs_migration?(value) - Marshal.load(value).tap do |value| - self[name] = { value: value } + Marshal.load(value).tap do |v| + self[name] = { value: v } end else serializer.load(value) -- cgit v1.2.3 From 4b11dcca74da2852249bc156e29af55bf11926ad Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Tue, 11 Feb 2014 13:58:04 +0100 Subject: test case to illustrate current PostgreSQL composite behavior. --- .../cases/adapters/postgresql/composite_test.rb | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 activerecord/test/cases/adapters/postgresql/composite_test.rb diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb new file mode 100644 index 0000000000..7202cce390 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +require "cases/helper" +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlCompositeTest < ActiveRecord::TestCase + class PostgresqlComposite < ActiveRecord::Base + self.table_name = "postgresql_composites" + end + + def teardown + @connection.execute 'DROP TABLE IF EXISTS postgresql_composites' + @connection.execute 'DROP TYPE IF EXISTS full_address' + end + + def setup + @connection = ActiveRecord::Base.connection + @connection.transaction do + @connection.execute <<-SQL + CREATE TYPE full_address AS + ( + city VARCHAR(90), + street VARCHAR(90) + ); + SQL + @connection.create_table('postgresql_composites') do |t| + t.column :address, :full_address + end + end + end + + def test_composite_mapping + @connection.execute "INSERT INTO postgresql_composites VALUES (1, ROW('Paris', 'Champs-Élysées'));" + composite = PostgresqlComposite.first + assert_equal "(Paris,Champs-Élysées)", composite.address + + composite.address = "(Paris,Rue Basse)" + composite.save! + + assert_equal '(Paris,"Rue Basse")', composite.reload.address + end +end -- cgit v1.2.3 From 3a0cc5c059b49452057359cab6017f98d93dd916 Mon Sep 17 00:00:00 2001 From: Angelo capilleri Date: Tue, 11 Feb 2014 16:28:39 +0100 Subject: add patch in HTTP Verb Constraints [ci skip] --- guides/source/routing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/routing.md b/guides/source/routing.md index 70d4722068..9c495bf09d 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -631,7 +631,7 @@ This will define a `user_path` method that will be available in controllers, hel ### HTTP Verb Constraints -In general, you should use the `get`, `post`, `put` and `delete` methods to constrain a route to a particular verb. You can use the `match` method with the `:via` option to match multiple verbs at once: +In general, you should use the `get`, `post`, `put`, `patch` and `delete` methods to constrain a route to a particular verb. You can use the `match` method with the `:via` option to match multiple verbs at once: ```ruby match 'photos', to: 'photos#show', via: [:get, :post] -- cgit v1.2.3 From ec0664a6eb8906fcd31a53a1efad69bdc7fe6f5b Mon Sep 17 00:00:00 2001 From: devlin zed Date: Tue, 11 Feb 2014 10:44:45 -0500 Subject: Don't symbolize tainted data. `I18n.locale=` symbolizes its argument, so passing it `params[:locale]` allows one to DOS your application by visiting `...?locale=` URLS repeatedly, with unique values, until the never-GCed symbols monopolize the available memory. --- guides/source/i18n.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/guides/source/i18n.md b/guides/source/i18n.md index d72717fa3b..088080721e 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -145,7 +145,11 @@ The _setting part_ is easy. You can set the locale in a `before_action` in the ` before_action :set_locale def set_locale - I18n.locale = params[:locale] || I18n.default_locale + if %w[en fr].include?(params[:locale]) + I18n.locale = params[:locale] + else + I18n.locale = I18n.default_locale + end end ``` -- cgit v1.2.3 From e00ab2dab9671603dcc8ab571baf1b1df100f99b Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 11 Feb 2014 17:16:31 +0100 Subject: Revert "Don't symbolize tainted data." [ci skip] Reason: i18n whitelists now locales without passing through symbols, see https://github.com/svenfuchs/i18n/blob/master/lib/i18n.rb#L278. Therefore, this snippet is no longer a good practice. This reverts commit ec0664a6eb8906fcd31a53a1efad69bdc7fe6f5b. --- guides/source/i18n.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 088080721e..d72717fa3b 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -145,11 +145,7 @@ The _setting part_ is easy. You can set the locale in a `before_action` in the ` before_action :set_locale def set_locale - if %w[en fr].include?(params[:locale]) - I18n.locale = params[:locale] - else - I18n.locale = I18n.default_locale - end + I18n.locale = params[:locale] || I18n.default_locale end ``` -- cgit v1.2.3 From c5034d60dba0cd31a6a8c612ee35d63b8127793a Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 11 Feb 2014 14:08:12 -0800 Subject: add a send so `apply` can be called. Fixes #13510 THIS IS A HUGE HACK. Thor does not allow us to define public methods without turning them in to "thor tasks". That means we cannot subclass the `apply` method and make it public, so we have to make the method private and call `send` on it. --- railties/lib/rails/tasks/framework.rake | 2 +- railties/test/application/rake/templates_test.rb | 32 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 railties/test/application/rake/templates_test.rb diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index e669315934..94e8f83e86 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -10,7 +10,7 @@ namespace :rails do require 'rails/generators' require 'rails/generators/rails/app/app_generator' generator = Rails::Generators::AppGenerator.new [Rails.root], {}, destination_root: Rails.root - generator.apply template, verbose: false + generator.send :apply, template, verbose: false end namespace :templates do diff --git a/railties/test/application/rake/templates_test.rb b/railties/test/application/rake/templates_test.rb new file mode 100644 index 0000000000..1fca80debd --- /dev/null +++ b/railties/test/application/rake/templates_test.rb @@ -0,0 +1,32 @@ +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class TemplatesTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + require "rails/all" + super + end + + def teardown + super + teardown_app + end + + def test_rake_template + Dir.chdir(app_path) do + cmd = "bundle exec rake rails:template LOCATION=foo" + r,w = IO.pipe + Process.waitpid Process.spawn(cmd, out: w, err: w) + w.close + assert_match(/Could not find.*foo/, r.read) + r.close + end + end + end + end +end + -- cgit v1.2.3 From 2862cd8e11fd76f943c224c12e90bd04c05eadd4 Mon Sep 17 00:00:00 2001 From: kayvan Date: Sun, 9 Feb 2014 10:26:10 -0800 Subject: adding connection parameter to check_pending for migrations --- activerecord/lib/active_record/migration.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index b57da73969..b510e78460 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -385,8 +385,8 @@ module ActiveRecord attr_accessor :delegate # :nodoc: attr_accessor :disable_ddl_transaction # :nodoc: - def check_pending! - raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration? + def check_pending!(connection = Base.connection) + raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?(connection) end def load_schema_if_pending! @@ -830,7 +830,7 @@ module ActiveRecord SchemaMigration.all.map { |x| x.version.to_i }.sort end - def current_version + def current_version(connection = Base.connection) sm_table = schema_migrations_table_name if Base.connection.table_exists?(sm_table) get_all_versions.max || 0 @@ -839,8 +839,8 @@ module ActiveRecord end end - def needs_migration? - current_version < last_version + def needs_migration?(connection = Base.connection) + current_version(connection) < last_version end def last_version -- cgit v1.2.3 From f34e0c4a207ddb7b2f25f43f68e0efad881cfbea Mon Sep 17 00:00:00 2001 From: kayvan Date: Sun, 9 Feb 2014 11:00:45 -0800 Subject: adding missed change --- activerecord/lib/active_record/migration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index b510e78460..b6b02322d7 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -832,7 +832,7 @@ module ActiveRecord def current_version(connection = Base.connection) sm_table = schema_migrations_table_name - if Base.connection.table_exists?(sm_table) + if connection.table_exists?(sm_table) get_all_versions.max || 0 else 0 -- cgit v1.2.3 From 2c7471a95dd818fbad80bd4925ac7c9745884363 Mon Sep 17 00:00:00 2001 From: Christian Wesselhoeft Date: Tue, 11 Feb 2014 23:11:02 -0800 Subject: Hide bundler output for `rails new` if quiet option is specified. --- railties/lib/rails/generators/app_base.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 55709b80ae..815894144a 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -412,7 +412,8 @@ module Rails require 'bundler' Bundler.with_clean_env do - print `"#{Gem.ruby}" "#{_bundle_command}" #{command}` + output = `"#{Gem.ruby}" "#{_bundle_command}" #{command}` + print output unless options[:quiet] end end -- cgit v1.2.3 From 42b2e3276b7c09c7e02b1d1d4f5a72a69644d657 Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Wed, 12 Feb 2014 09:53:28 +0530 Subject: [Testing Guide] Explain usage of assert_redirected_to with named routes and Active Record objects [ci skip] --- guides/source/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/testing.md b/guides/source/testing.md index 1880dfb9ce..07f3aad1e6 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -402,7 +402,7 @@ Rails adds some custom assertions of its own to the `test/unit` framework: | `assert_recognizes(expected_options, path, extras={}, message=nil)` | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.| | `assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)` | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.| | `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.| -| `assert_redirected_to(options = {}, message=nil)` | Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on.| +| `assert_redirected_to(options = {}, message=nil)` | Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on. You can also pass named routes such as `assert_redirected_to root_path` and Active Record objects such as `assert_redirected_to @article`.| | `assert_template(expected = nil, message=nil)` | Asserts that the request was rendered with the appropriate template file.| You'll see the usage of some of these assertions in the next chapter. -- cgit v1.2.3 From af66c4697c2ca3d67c853cae6c900b124c354e90 Mon Sep 17 00:00:00 2001 From: Erik Michaels-Ober Date: Wed, 12 Feb 2014 12:55:47 +0100 Subject: Update Travis settings for Rubinius /cc @brixen --- .travis.yml | 4 ++-- Gemfile | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3ddaf86fb2..4233b136a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ rvm: - 1.9.3 - 2.0.0 - 2.1.0 - - rbx + - rbx-2 - jruby env: - "GEM=railties" @@ -17,7 +17,7 @@ env: - "GEM=ar:postgresql" matrix: allow_failures: - - rvm: rbx + - rvm: rbx-2 - rvm: jruby fast_finish: true notifications: diff --git a/Gemfile b/Gemfile index d1eb460fbc..753b1d423a 100644 --- a/Gemfile +++ b/Gemfile @@ -76,11 +76,6 @@ platforms :jruby do end end -platforms :rbx do - gem 'psych', '~> 2.0' - gem 'rubysl', '~> 2.0' -end - # gems that are necessary for ActiveRecord tests with Oracle database if ENV['ORACLE_ENHANCED'] platforms :ruby do -- cgit v1.2.3 From 37e30d2548e586a5080554a5d5065dd82289fbfe Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Wed, 12 Feb 2014 17:17:00 +0100 Subject: do not crash when `config/secrets.yml` is blank. --- railties/CHANGELOG.md | 4 ++++ railties/lib/rails/application.rb | 3 ++- railties/test/application/configuration_test.rb | 8 ++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index a57d56f4aa..bade9ef543 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +* Do not crash when `config/secrets.yml` is empty. + + *Yves Senn* + * Set `dump_schema_after_migration` config values in production. Set `config.active_record.dump_schema_after_migration` as false diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 36432e56ba..314a789b96 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -308,7 +308,8 @@ module Rails yaml = config.paths["config/secrets"].first if File.exist?(yaml) require "erb" - env_secrets = YAML.load(ERB.new(IO.read(yaml)).result)[Rails.env] + all_secrets = YAML.load(ERB.new(IO.read(yaml)).result) || {} + env_secrets = all_secrets[Rails.env] secrets.merge!(env_secrets.symbolize_keys) if env_secrets end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index b814479540..b2d0e7e202 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -336,6 +336,14 @@ module ApplicationTests assert_equal 'myamazonsecretaccesskey', app.secrets.aws_secret_access_key end + test "blank config/secrets.yml does not crash the loading process" do + app_file 'config/secrets.yml', <<-YAML + YAML + require "#{app_path}/config/environment" + + assert_nil app.secrets.not_defined + end + test "protect from forgery is the default in a new app" do make_basic_app -- cgit v1.2.3 From 5b793a8add2d8fa57cde48ece3a9e20870a398f1 Mon Sep 17 00:00:00 2001 From: Iain Beeston Date: Wed, 12 Feb 2014 17:40:52 +0000 Subject: Added tests to render helper that expect `render partial: @foo` to automatically call @foo.to_partial_path Calling `render @foo` allows local variables but not options to be passed to the partial renderer. The correct way to render an object AND pass options to the partial renderer is to pass the object in the `:partial` parameter. However, there were previously no tests for this behaviour (in `render_helper_test.rb` at least). --- actionview/test/fixtures/customers/_customer.xml.erb | 1 + actionview/test/template/render_test.rb | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 actionview/test/fixtures/customers/_customer.xml.erb diff --git a/actionview/test/fixtures/customers/_customer.xml.erb b/actionview/test/fixtures/customers/_customer.xml.erb new file mode 100644 index 0000000000..d3f1e0768f --- /dev/null +++ b/actionview/test/fixtures/customers/_customer.xml.erb @@ -0,0 +1 @@ +<%= greeting %><%= customer.name %> \ No newline at end of file diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index 055a273cc3..db5d99755c 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -304,6 +304,16 @@ module RenderTestCases assert_equal "Hola: david", @controller_view.render('customer_greeting', :greeting => 'Hola', :customer_greeting => Customer.new("david")) end + def test_render_partial_with_object_uses_render_partial_path + assert_equal "Hello: lifo", + @controller_view.render(:partial => Customer.new("lifo"), :locals => {:greeting => "Hello"}) + end + + def test_render_partial_with_object_and_format_uses_render_partial_path + assert_equal "Hellolifo", + @controller_view.render(:partial => Customer.new("lifo"), :formats => :xml, :locals => {:greeting => "Hello"}) + end + def test_render_partial_using_object assert_equal "Hello: lifo", @controller_view.render(Customer.new("lifo"), :greeting => "Hello") -- cgit v1.2.3 From 4bf7071d630114fced0e474a93ba9fc16bae10e7 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 12 Feb 2014 15:19:17 -0800 Subject: `execute` is a hotspot, so let's reduce branches --- .../active_record/connection_adapters/abstract_mysql_adapter.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) 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 7768c24967..23edc8b955 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -298,11 +298,7 @@ module ActiveRecord # Executes the SQL statement in the context of this connection. def execute(sql, name = nil) - if name == :skip_logging - @connection.query(sql) - else - log(sql, name) { @connection.query(sql) } - end + log(sql, name) { @connection.query(sql) } end # MysqlAdapter has to free a result after using it, so we use this method to write @@ -775,7 +771,7 @@ module ActiveRecord end.compact.join(', ') # ...and send them all in one query - execute("SET #{encoding} #{variable_assignments}", :skip_logging) + @connection.query "SET #{encoding} #{variable_assignments}" end end end -- cgit v1.2.3 From 3b645fd3ed60c682b255bf31e7df08cfc6a37321 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Thu, 19 Dec 2013 01:43:02 +1030 Subject: Terminate the backend ourselves on PG 9.2+ This should make it harder to accidentally break this test. --- .../cases/adapters/postgresql/connection_test.rb | 48 +++++++++++++--------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 90cca7d3e6..4715fa002d 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -91,40 +91,50 @@ module ActiveRecord assert_operator plan.length, :>, 0 end - # Must have with_manual_interventions set to true for this - # test to run. + # Must have PostgreSQL >= 9.2, or with_manual_interventions set to + # true for this test to run. + # # When prompted, restart the PostgreSQL server with the # "-m fast" option or kill the individual connection assuming # you know the incantation to do that. # To restart PostgreSQL 9.1 on OS X, installed via MacPorts, ... # sudo su postgres -c "pg_ctl restart -D /opt/local/var/db/postgresql91/defaultdb/ -m fast" - if ARTest.config['with_manual_interventions'] - def test_reconnection_after_actual_disconnection_with_verify - original_connection_pid = @connection.query('select pg_backend_pid()') + def test_reconnection_after_actual_disconnection_with_verify + original_connection_pid = @connection.query('select pg_backend_pid()') - # Sanity check. - assert @connection.active? + # Sanity check. + assert @connection.active? + if @connection.send(:postgresql_version) >= 90200 + secondary_connection = ActiveRecord::Base.connection_pool.checkout + secondary_connection.query("select pg_terminate_backend(#{original_connection_pid.first.first})") + ActiveRecord::Base.connection_pool.checkin(secondary_connection) + elsif ARTest.config['with_manual_interventions'] puts 'Kill the connection now (e.g. by restarting the PostgreSQL ' + 'server with the "-m fast" option) and then press enter.' $stdin.gets + else + # We're not capable of terminating the backend ourselves, and + # we're not allowed to seek assistance; bail out without + # actually testing anything. + return + end - @connection.verify! + @connection.verify! - assert @connection.active? + assert @connection.active? - # If we get no exception here, then either we re-connected successfully, or - # we never actually got disconnected. - new_connection_pid = @connection.query('select pg_backend_pid()') + # If we get no exception here, then either we re-connected successfully, or + # we never actually got disconnected. + new_connection_pid = @connection.query('select pg_backend_pid()') - assert_not_equal original_connection_pid, new_connection_pid, - "umm -- looks like you didn't break the connection, because we're still " + - "successfully querying with the same connection pid." + assert_not_equal original_connection_pid, new_connection_pid, + "umm -- looks like you didn't break the connection, because we're still " + + "successfully querying with the same connection pid." - # Repair all fixture connections so other tests won't break. - @fixture_connections.each do |c| - c.verify! - end + # Repair all fixture connections so other tests won't break. + @fixture_connections.each do |c| + c.verify! end end -- cgit v1.2.3 From f9b6b865e60ea770cc34e9946f6df1604f20dd27 Mon Sep 17 00:00:00 2001 From: Lukasz Strzalkowski Date: Thu, 13 Feb 2014 15:59:09 +0100 Subject: Variant negotiation Allow setting `request.variant` as an array - an order in which they will be rendered. For example: request.variant = [:tablet, :phone] respond_to do |format| format.html.none format.html.phone # this gets rendered end --- .../lib/action_controller/metal/mime_responds.rb | 28 +++++++++++++++------- .../lib/action_dispatch/http/mime_negotiation.rb | 6 +++-- actionpack/test/controller/mime/respond_to_test.rb | 21 ++++++++++++++++ actionpack/test/dispatch/request_test.rb | 6 ++++- actionview/lib/action_view/rendering.rb | 2 +- 5 files changed, 51 insertions(+), 12 deletions(-) diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index d5e08b7034..c8076af0c8 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -236,6 +236,18 @@ module ActionController #:nodoc: # end # end # + # You can also set an array of variants: + # + # request.variant = [:tablet, :phone] + # + # which will work similarly to formats and MIME types negotiation. If there will be no + # :tablet variant declared, :phone variant will be picked: + # + # respond_to do |format| + # format.html.none + # format.html.phone # this gets rendered + # end + # # Be sure to check the documentation of +respond_with+ and # ActionController::MimeResponds.respond_to for more examples. def respond_to(*mimes, &block) @@ -488,7 +500,7 @@ module ActionController #:nodoc: response else # `format.html{ |variant| variant.phone }` - variant block syntax variant_collector = VariantCollector.new(@variant) - response.call(variant_collector) #call format block with variants collector + response.call(variant_collector) # call format block with variants collector variant_collector.variant end end @@ -519,15 +531,15 @@ module ActionController #:nodoc: end def variant - key = if @variant.nil? - :none - elsif @variants.has_key?(@variant) - @variant + if @variant.nil? + @variants[:none] + elsif (@variants.keys & @variant).any? + @variant.each do |v| + return @variants[v] if @variants.key?(v) + end else - :any + @variants[:any] end - - @variants[key] end end end diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index c33ba201e1..b75d7ffe9d 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -68,10 +68,12 @@ module ActionDispatch # Sets the \variant for template. def variant=(variant) - if variant.is_a? Symbol + if variant.is_a?(Symbol) + @variant = [variant] + elsif variant.is_a?(Array) @variant = variant else - raise ArgumentError, "request.variant must be set to a Symbol, not a #{variant.class}. " \ + raise ArgumentError, "request.variant must be set to a Symbol or Array, not a #{variant.class}. " \ "For security reasons, never directly set the variant to a user-provided value, " \ "like params[:variant].to_sym. Check user-provided value against a whitelist first, " \ "then set the variant: request.variant = :tablet if params[:variant] == 'tablet'" diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb index 84e4936f31..2f05017ec9 100644 --- a/actionpack/test/controller/mime/respond_to_test.rb +++ b/actionpack/test/controller/mime/respond_to_test.rb @@ -740,4 +740,25 @@ class RespondToControllerTest < ActionController::TestCase assert_equal "text/javascript", @response.content_type assert_equal "tablet", @response.body end + + def test_variant_negotiation_inline_syntax + @request.variant = [:tablet, :phone] + get :variant_inline_syntax_without_block + assert_equal "text/html", @response.content_type + assert_equal "phone", @response.body + end + + def test_variant_negotiation_block_syntax + @request.variant = [:tablet, :phone] + get :variant_plus_none_for_format + assert_equal "text/html", @response.content_type + assert_equal "phone", @response.body + end + + def test_variant_negotiation_without_block + @request.variant = [:tablet, :phone] + get :variant_inline_syntax_without_block + assert_equal "text/html", @response.content_type + assert_equal "phone", @response.body + end end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index f79fe47897..df47520850 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -846,8 +846,12 @@ class RequestTest < ActiveSupport::TestCase test "setting variant" do request = stub_request + request.variant = :mobile - assert_equal :mobile, request.variant + assert_equal [:mobile], request.variant + + request.variant = [:phone, :tablet] + assert_equal [:phone, :tablet], request.variant end test "setting variant with non symbol value" do diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index 99b95fdfb7..7c17220d14 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -94,7 +94,7 @@ module ActionView variant = options[:variant] lookup_context.rendered_format = nil if options[:formats] - lookup_context.variants = [variant] if variant + lookup_context.variants = variant if variant view_renderer.render(view_context, options) end -- cgit v1.2.3 From c3872522895dc98be0081c4457f834951dbd11bc Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Thu, 13 Feb 2014 10:41:43 -0500 Subject: Dont use Enumarator on join_association --- .../active_record/associations/join_dependency/join_association.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index 0cd2e1a816..cee3c9999f 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -25,7 +25,8 @@ module ActiveRecord joins = [] tables = tables.reverse - scope_chain_iter = scope_chain.reverse_each + scope_chain_index = 0 + scope_chain = scope_chain.reverse # The chain starts with the target table, but we want to end with it here (makes # more sense in this context), so we reverse @@ -44,13 +45,14 @@ module ActiveRecord constraint = build_constraint(klass, table, key, foreign_table, foreign_key) - scope_chain_items = scope_chain_iter.next.map do |item| + scope_chain_items = scope_chain[scope_chain_index].map do |item| if item.is_a?(Relation) item else ActiveRecord::Relation.create(klass, table).instance_exec(node, &item) end end + scope_chain_index += 1 scope_chain_items.concat [klass.send(:build_default_scope)].compact -- cgit v1.2.3 From ea3af7ee14217470b019225a03cccced1120f211 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Thu, 13 Feb 2014 16:45:56 +0100 Subject: tests are responsible to clean up afterwards. remove created state after test execution, not before the next test. This prevents the leak of the `ex` table outside of a single test. --- .../adapters/postgresql/postgresql_adapter_test.rb | 196 ++++++++++++--------- 1 file changed, 114 insertions(+), 82 deletions(-) diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 131080913c..019406dd84 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -6,21 +6,21 @@ module ActiveRecord class PostgreSQLAdapterTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))') end def test_bad_connection assert_raise ActiveRecord::NoDatabaseError do configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'should_not_exist-cinco-dog-db') connection = ActiveRecord::Base.postgresql_connection(configuration) - connection.exec_query('drop table if exists ex') + connection.exec_query('SELECT 1') end end def test_valid_column - column = @connection.columns('ex').find { |col| col.name == 'id' } - assert @connection.valid_type?(column.type) + with_example_table do + column = @connection.columns('ex').find { |col| col.name == 'id' } + assert @connection.valid_type?(column.type) + end end def test_invalid_column @@ -28,7 +28,9 @@ module ActiveRecord end def test_primary_key - assert_equal 'id', @connection.primary_key('ex') + with_example_table do + assert_equal 'id', @connection.primary_key('ex') + end end def test_primary_key_works_tables_containing_capital_letters @@ -36,15 +38,15 @@ module ActiveRecord end def test_non_standard_primary_key - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(data character varying(255) primary key)') - assert_equal 'data', @connection.primary_key('ex') + with_example_table 'data character varying(255) primary key' do + assert_equal 'data', @connection.primary_key('ex') + end end def test_primary_key_returns_nil_for_no_pk - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(id integer)') - assert_nil @connection.primary_key('ex') + with_example_table 'id integer' do + assert_nil @connection.primary_key('ex') + end end def test_primary_key_raises_error_if_table_not_found @@ -54,32 +56,40 @@ module ActiveRecord end def test_insert_sql_with_proprietary_returning_clause - id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number") - assert_equal "5150", id + with_example_table do + id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number") + assert_equal "5150", id + end end def test_insert_sql_with_quoted_schema_and_table_name - id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)') - expect = @connection.query('select max(id) from ex').first.first - assert_equal expect, id + with_example_table do + id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)') + expect = @connection.query('select max(id) from ex').first.first + assert_equal expect, id + end end def test_insert_sql_with_no_space_after_table_name - id = @connection.insert_sql("insert into ex(number) values(5150)") - expect = @connection.query('select max(id) from ex').first.first - assert_equal expect, id + with_example_table do + id = @connection.insert_sql("insert into ex(number) values(5150)") + expect = @connection.query('select max(id) from ex').first.first + assert_equal expect, id + end end def test_multiline_insert_sql - id = @connection.insert_sql(<<-SQL) - insert into ex( - number) - values( - 5152 - ) - SQL - expect = @connection.query('select max(id) from ex').first.first - assert_equal expect, id + with_example_table do + id = @connection.insert_sql(<<-SQL) + insert into ex( + number) + values( + 5152 + ) + SQL + expect = @connection.query('select max(id) from ex').first.first + assert_equal expect, id + end end def test_insert_sql_with_returning_disabled @@ -135,29 +145,31 @@ module ActiveRecord end def test_pk_and_sequence_for - pk, seq = @connection.pk_and_sequence_for('ex') - assert_equal 'id', pk - assert_equal @connection.default_sequence_name('ex', 'id'), seq + with_example_table do + pk, seq = @connection.pk_and_sequence_for('ex') + assert_equal 'id', pk + assert_equal @connection.default_sequence_name('ex', 'id'), seq + end end def test_pk_and_sequence_for_with_non_standard_primary_key - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(code serial primary key)') - pk, seq = @connection.pk_and_sequence_for('ex') - assert_equal 'code', pk - assert_equal @connection.default_sequence_name('ex', 'code'), seq + with_example_table 'code serial primary key' do + pk, seq = @connection.pk_and_sequence_for('ex') + assert_equal 'code', pk + assert_equal @connection.default_sequence_name('ex', 'code'), seq + end end def test_pk_and_sequence_for_returns_nil_if_no_seq - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(id integer primary key)') - assert_nil @connection.pk_and_sequence_for('ex') + with_example_table 'id integer primary key' do + assert_nil @connection.pk_and_sequence_for('ex') + end end def test_pk_and_sequence_for_returns_nil_if_no_pk - @connection.exec_query('drop table if exists ex') - @connection.exec_query('create table ex(id integer)') - assert_nil @connection.pk_and_sequence_for('ex') + with_example_table 'id integer' do + assert_nil @connection.pk_and_sequence_for('ex') + end end def test_pk_and_sequence_for_returns_nil_if_table_not_found @@ -165,23 +177,27 @@ module ActiveRecord end def test_exec_insert_number - insert(@connection, 'number' => 10) + with_example_table do + insert(@connection, 'number' => 10) - result = @connection.exec_query('SELECT number FROM ex WHERE number = 10') + result = @connection.exec_query('SELECT number FROM ex WHERE number = 10') - assert_equal 1, result.rows.length - assert_equal "10", result.rows.last.last + assert_equal 1, result.rows.length + assert_equal "10", result.rows.last.last + end end def test_exec_insert_string - str = 'いただきます!' - insert(@connection, 'number' => 10, 'data' => str) + with_example_table do + str = 'いただきます!' + insert(@connection, 'number' => 10, 'data' => str) - result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10') + result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10') - value = result.rows.last.last + value = result.rows.last.last - assert_equal str, value + assert_equal str, value + end end def test_table_alias_length @@ -191,44 +207,50 @@ module ActiveRecord end def test_exec_no_binds - result = @connection.exec_query('SELECT id, data FROM ex') - assert_equal 0, result.rows.length - assert_equal 2, result.columns.length - assert_equal %w{ id data }, result.columns - - string = @connection.quote('foo') - @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") - result = @connection.exec_query('SELECT id, data FROM ex') - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length - - assert_equal [['1', 'foo']], result.rows + with_example_table do + result = @connection.exec_query('SELECT id, data FROM ex') + assert_equal 0, result.rows.length + assert_equal 2, result.columns.length + assert_equal %w{ id data }, result.columns + + string = @connection.quote('foo') + @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") + result = @connection.exec_query('SELECT id, data FROM ex') + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length + + assert_equal [['1', 'foo']], result.rows + end end def test_exec_with_binds - string = @connection.quote('foo') - @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") - result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]]) + with_example_table do + string = @connection.quote('foo') + @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") + result = @connection.exec_query( + 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]]) - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [['1', 'foo']], result.rows + assert_equal [['1', 'foo']], result.rows + end end def test_exec_typecasts_bind_vals - string = @connection.quote('foo') - @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") + with_example_table do + string = @connection.quote('foo') + @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") - column = @connection.columns('ex').find { |col| col.name == 'id' } - result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']]) + column = @connection.columns('ex').find { |col| col.name == 'id' } + result = @connection.exec_query( + 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']]) - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [['1', 'foo']], result.rows + assert_equal [['1', 'foo']], result.rows + end end def test_substitute_at @@ -240,9 +262,11 @@ module ActiveRecord end def test_partial_index - @connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100" - index = @connection.indexes('ex').find { |idx| idx.name == 'partial' } - assert_equal "(number > 100)", index.where + with_example_table do + @connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100" + index = @connection.indexes('ex').find { |idx| idx.name == 'partial' } + assert_equal "(number > 100)", index.where + end end def test_columns_for_distinct_zero_orders @@ -300,6 +324,14 @@ module ActiveRecord ctx.exec_insert(sql, 'SQL', binds) end + def with_example_table(definition = nil) + definition ||= 'id serial primary key, number integer, data character varying(255)' + @connection.exec_query("create table ex(#{definition})") + yield + ensure + @connection.exec_query('drop table if exists ex') + end + def connection_without_insert_returning ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false)) end -- cgit v1.2.3 From e1d4673102f7c4e58964a6de864402ae9a615688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hubert=20D=C4=85browski?= Date: Wed, 12 Feb 2014 22:23:16 +0100 Subject: Drop the correct index after reverting a migration Previously when reverting a migration which added a named index it would instead drop a corresponding index with matching columns but without a name. --- activerecord/CHANGELOG.md | 12 ++++++++++ .../active_record/migration/command_recorder.rb | 7 +++++- .../test/cases/invertible_migration_test.rb | 28 ++++++++++++++++++++++ .../test/cases/migration/command_recorder_test.rb | 6 ++--- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 1fd9003009..d8bb31a3a7 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,15 @@ +* When inverting add_index use the index name if present instead of + the columns. + + If there are two indices with matching columns and one of them is + explicitly named then reverting the migration adding the named one + would instead drop the unnamed one. + + The inversion of add_index will now drop the index by its name if + it is present. + + *Hubert Dąbrowski* + * Add flag to disable schema dump after migration. Add a config parameter on Active Record named `dump_schema_after_migration` diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index 9139ad953c..c44d8c1665 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -140,7 +140,12 @@ module ActiveRecord def invert_add_index(args) table, columns, options = *args - [:remove_index, [table, (options || {}).merge(column: columns)]] + options ||= {} + + index_name = options[:name] + options_hash = index_name ? { name: index_name } : { column: columns } + + [:remove_index, [table, options_hash]] end def invert_remove_index(args) diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb index 428145d00b..debacf815c 100644 --- a/activerecord/test/cases/invertible_migration_test.rb +++ b/activerecord/test/cases/invertible_migration_test.rb @@ -106,6 +106,22 @@ module ActiveRecord end end + class RevertNamedIndexMigration1 < SilentMigration + def change + create_table("horses") do |t| + t.column :content, :string + t.column :remind_at, :datetime + end + add_index :horses, :content + end + end + + class RevertNamedIndexMigration2 < SilentMigration + def change + add_index :horses, :content, name: "horses_index_named" + end + end + def teardown %w[horses new_horses].each do |table| if ActiveRecord::Base.connection.table_exists?(table) @@ -255,5 +271,17 @@ module ActiveRecord ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = '' end + def test_migrate_revert_add_index_with_name + RevertNamedIndexMigration1.new.migrate(:up) + RevertNamedIndexMigration2.new.migrate(:up) + RevertNamedIndexMigration2.new.migrate(:down) + + connection = ActiveRecord::Base.connection + assert connection.index_exists?(:horses, :content), + "index on content should exist" + assert !connection.index_exists?(:horses, :content, name: "horses_index_named"), + "horses_index_named index should not exist" + end + end end diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 35b656ee43..a925cf4c05 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -174,13 +174,13 @@ module ActiveRecord end def test_invert_add_index - remove = @recorder.inverse_of :add_index, [:table, [:one, :two], options: true] - assert_equal [:remove_index, [:table, {column: [:one, :two], options: true}]], remove + remove = @recorder.inverse_of :add_index, [:table, [:one, :two]] + assert_equal [:remove_index, [:table, {column: [:one, :two]}]], remove end def test_invert_add_index_with_name remove = @recorder.inverse_of :add_index, [:table, [:one, :two], name: "new_index"] - assert_equal [:remove_index, [:table, {column: [:one, :two], name: "new_index"}]], remove + assert_equal [:remove_index, [:table, {name: "new_index"}]], remove end def test_invert_add_index_with_no_options -- cgit v1.2.3 From 00a4af9ab7e2008fe4e1a0cb1f31109a231d7279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Strza=C5=82kowski?= Date: Thu, 13 Feb 2014 18:05:55 +0100 Subject: Check if variant array contains only symbols --- actionpack/lib/action_dispatch/http/mime_negotiation.rb | 4 ++-- actionpack/test/dispatch/request_test.rb | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index b75d7ffe9d..b803ce8b6f 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -70,10 +70,10 @@ module ActionDispatch def variant=(variant) if variant.is_a?(Symbol) @variant = [variant] - elsif variant.is_a?(Array) + elsif variant.is_a?(Array) && variant.any? && variant.all?{ |v| v.is_a?(Symbol) } @variant = variant else - raise ArgumentError, "request.variant must be set to a Symbol or Array, not a #{variant.class}. " \ + raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols, not a #{variant.class}. " \ "For security reasons, never directly set the variant to a user-provided value, " \ "like params[:variant].to_sym. Check user-provided value against a whitelist first, " \ "then set the variant: request.variant = :tablet if params[:variant] == 'tablet'" diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index df47520850..40e32cb4d3 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -852,6 +852,14 @@ class RequestTest < ActiveSupport::TestCase request.variant = [:phone, :tablet] assert_equal [:phone, :tablet], request.variant + + assert_raise ArgumentError do + request.variant = [:phone, "tablet"] + end + + assert_raise ArgumentError do + request.variant = "yolo" + end end test "setting variant with non symbol value" do -- cgit v1.2.3 From f9e4c3c7c0c4152b62fe9202a9d12262884bb118 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 13 Feb 2014 11:03:00 -0800 Subject: speed up the collection proxy reader method, but slow down the constructor --- .../lib/active_record/associations/collection_association.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 89b7945c78..03ca00fa70 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -24,6 +24,10 @@ module ActiveRecord # If you need to work on all current children, new and existing records, # +load_target+ and the +loaded+ flag are your friends. class CollectionAssociation < Association #:nodoc: + def initialize(owner, reflection) + super + @proxy = CollectionProxy.create(klass, self) + end # Implements the reader method, e.g. foo.items for Foo.has_many :items def reader(force_reload = false) @@ -33,7 +37,7 @@ module ActiveRecord reload end - @proxy ||= CollectionProxy.create(klass, self) + @proxy end # Implements the writer method, e.g. foo.items= for Foo.has_many :items -- cgit v1.2.3 From b2831904e5304d23d20844d8aa0e03c6b81a8d25 Mon Sep 17 00:00:00 2001 From: Philippe Creux Date: Thu, 13 Feb 2014 10:46:22 -0800 Subject: Add hint to error message of task db:migrate:down --- activerecord/lib/active_record/railties/databases.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 561387a179..0a2f726525 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -75,7 +75,7 @@ db_namespace = namespace :db do # desc 'Runs the "down" for a given migration VERSION.' task :down => [:environment, :load_config] do version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil - raise 'VERSION is required' unless version + raise 'VERSION is required - To go down one migration, run db:rollback' unless version ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version) db_namespace['_dump'].invoke end -- cgit v1.2.3 From bfc34fc0050ce61650701676dd45553aa82214c0 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 13 Feb 2014 20:38:33 +0100 Subject: No variant should also be picked up by variant.any if variant.none is not defined (just like any other variant) --- actionpack/lib/action_controller/metal/mime_responds.rb | 2 +- actionpack/test/controller/mime/respond_to_test.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index c8076af0c8..1974bbf529 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -532,7 +532,7 @@ module ActionController #:nodoc: def variant if @variant.nil? - @variants[:none] + @variants[:none] || @variants[:any] elsif (@variants.keys & @variant).any? @variant.each do |v| return @variants[v] if @variants.key?(v) diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb index 2f05017ec9..499c62cc35 100644 --- a/actionpack/test/controller/mime/respond_to_test.rb +++ b/actionpack/test/controller/mime/respond_to_test.rb @@ -671,6 +671,10 @@ class RespondToControllerTest < ActionController::TestCase end def test_variant_any_any + get :variant_any_any + assert_equal "text/html", @response.content_type + assert_equal "any", @response.body + @request.variant = :phone get :variant_any_any assert_equal "text/html", @response.content_type -- cgit v1.2.3 From 0fddc3c1c07e83249e4b6ff1ed5f6b7f74e41c1f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 13 Feb 2014 12:13:24 -0800 Subject: JoinHelper is never reused, so there is no need to separate --- activerecord/lib/active_record/associations.rb | 1 - .../associations/association_scope.rb | 30 +++++++++++++++--- .../lib/active_record/associations/join_helper.rb | 36 ---------------------- 3 files changed, 25 insertions(+), 42 deletions(-) delete mode 100644 activerecord/lib/active_record/associations/join_helper.rb diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index b5e21cbede..142d21ce92 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -130,7 +130,6 @@ module ActiveRecord autoload :JoinDependency, 'active_record/associations/join_dependency' autoload :AssociationScope, 'active_record/associations/association_scope' autoload :AliasTracker, 'active_record/associations/alias_tracker' - autoload :JoinHelper, 'active_record/associations/join_helper' end # Clears out the association cache. diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 5a0ba9e6b1..69deb5f2e0 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -1,10 +1,6 @@ -require 'active_record/associations/join_helper' - module ActiveRecord module Associations class AssociationScope #:nodoc: - include JoinHelper - attr_reader :association, :alias_tracker delegate :klass, :owner, :reflection, :interpolate, :to => :association @@ -21,8 +17,32 @@ module ActiveRecord add_constraints(scope) end + def join_type + Arel::Nodes::InnerJoin + end + private + def construct_tables + chain.map do |reflection| + alias_tracker.aliased_table_for( + table_name_for(reflection), + table_alias_for(reflection, reflection != self.reflection) + ) + end + end + + + def table_alias_for(reflection, join = false) + name = "#{reflection.plural_name}_#{alias_suffix}" + name << "_join" if join + name + end + + def join(table, constraint) + table.create_join(table, table.create_on(constraint), join_type) + end + def column_for(table_name, column_name) columns = alias_tracker.connection.schema_cache.columns_hash(table_name) columns[column_name] @@ -115,7 +135,7 @@ module ActiveRecord # the owner klass.table_name else - super + reflection.table_name end end diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb deleted file mode 100644 index 3471936b9f..0000000000 --- a/activerecord/lib/active_record/associations/join_helper.rb +++ /dev/null @@ -1,36 +0,0 @@ -module ActiveRecord - module Associations - # Helper class module which gets mixed into JoinDependency::JoinAssociation and AssociationScope - module JoinHelper #:nodoc: - - def join_type - Arel::Nodes::InnerJoin - end - - private - - def construct_tables - chain.map do |reflection| - alias_tracker.aliased_table_for( - table_name_for(reflection), - table_alias_for(reflection, reflection != self.reflection) - ) - end - end - - def table_name_for(reflection) - reflection.table_name - end - - def table_alias_for(reflection, join = false) - name = "#{reflection.plural_name}_#{alias_suffix}" - name << "_join" if join - name - end - - def join(table, constraint) - table.create_join(table, table.create_on(constraint), join_type) - end - end - end -end -- cgit v1.2.3 From 500b1df43e7dd86a911a1477ced0dac1dc8d8305 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 13 Feb 2014 15:33:18 -0800 Subject: rm delegate methods that are not actually used --- activerecord/lib/active_record/associations/association_scope.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 69deb5f2e0..b7aaae15c7 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -3,8 +3,8 @@ module ActiveRecord class AssociationScope #:nodoc: attr_reader :association, :alias_tracker - delegate :klass, :owner, :reflection, :interpolate, :to => :association - delegate :chain, :scope_chain, :options, :source_options, :active_record, :to => :reflection + delegate :klass, :owner, :reflection, :to => :association + delegate :chain, :scope_chain, :options, :to => :reflection def initialize(association) @association = association -- cgit v1.2.3 From e804d07a92498b3c3565c4178d0ac591c5934cd2 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 13 Feb 2014 14:12:29 -0800 Subject: :scissors: whitespace --- activerecord/lib/active_record/associations/association_scope.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index b7aaae15c7..dcd2cbcbd5 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -32,7 +32,6 @@ module ActiveRecord end end - def table_alias_for(reflection, join = false) name = "#{reflection.plural_name}_#{alias_suffix}" name << "_join" if join -- cgit v1.2.3 From 7d897abeccb8533d770ac1d0768eca20ec2f3971 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 13 Feb 2014 15:40:18 -0800 Subject: remove more delegate methods --- .../lib/active_record/associations/association_scope.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index dcd2cbcbd5..15691e26df 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -3,8 +3,8 @@ module ActiveRecord class AssociationScope #:nodoc: attr_reader :association, :alias_tracker - delegate :klass, :owner, :reflection, :to => :association - delegate :chain, :scope_chain, :options, :to => :reflection + delegate :klass, :reflection, :to => :association + delegate :chain, :scope_chain, :to => :reflection def initialize(association) @association = association @@ -13,8 +13,10 @@ module ActiveRecord def scope scope = klass.unscoped - scope.extending! Array(options[:extend]) - add_constraints(scope) + scope.extending! Array(reflection.options[:extend]) + + owner = association.owner + add_constraints(scope, owner) end def join_type @@ -59,7 +61,7 @@ module ActiveRecord bind_value scope, column, value end - def add_constraints(scope) + def add_constraints(scope, owner) tables = construct_tables chain.each_with_index do |reflection, i| @@ -105,7 +107,7 @@ module ActiveRecord # Exclude the scope of the association itself, because that # was already merged in the #scope method. scope_chain[i].each do |scope_chain_item| - item = eval_scope(klass, scope_chain_item) + item = eval_scope(klass, scope_chain_item, owner) if scope_chain_item == self.reflection.scope scope.merge! item.except(:where, :includes, :bind) @@ -138,7 +140,7 @@ module ActiveRecord end end - def eval_scope(klass, scope) + def eval_scope(klass, scope, owner) if scope.is_a?(Relation) scope else -- cgit v1.2.3 From 8e6d0fd4343cc68aab1e2b46363a930130fca6e5 Mon Sep 17 00:00:00 2001 From: Karthik T Date: Tue, 4 Feb 2014 11:08:23 +0800 Subject: Getting started guide fixes & Explain X-SendFile a little better, with links Explain how form_for :article is able to pull in the properties of @article Make it clear that article_id is generated due to the association set up Add link to the rails function that uses X-Sendfile. Add links to apache and nginx docs for the header --- guides/source/asset_pipeline.md | 7 ++++++- guides/source/getting_started.md | 10 ++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 0422dda0d8..e67b7fe4cb 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -1018,7 +1018,8 @@ The X-Sendfile header is a directive to the web server to ignore the response from the application, and instead serve a specified file from disk. This option is off by default, but can be enabled if your server supports it. When enabled, this passes responsibility for serving the file to the web server, which is -faster. +faster. Have a look at [send_file](http://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_file) +on how to use this feature. Apache and nginx support this option, which can be enabled in `config/environments/production.rb`: @@ -1033,6 +1034,10 @@ option, take care to paste this configuration option only into `production.rb` and any other environments you define with production behavior (not `application.rb`). +TIP: For further details have a look at the docs of your production web server: +- [Apache](https://tn123.org/mod_xsendfile/) +- [Nginx](http://wiki.nginx.org/XSendfile) + Assets Cache Store ------------------ diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index bdb1a61bfb..652ce1cd8d 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -1105,7 +1105,11 @@ The `method: :patch` option tells Rails that we want this form to be submitted via the `PATCH` HTTP method which is the HTTP method you're expected to use to **update** resources according to the REST protocol. -TIP: By default forms built with the _form_for_ helper are sent via `POST`. +The first parameter of the `form_tag` can be an object, say, `@article` which would +cause the helper to fill in the form with the fields of the object. Passing in a +symbol (`:article`) with the same name as the instance variable (`@article`) also +automagically leads to the same behavior. This is what is happening here. More details +can be found in [form_for documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for). Next we need to create the `update` action in `app/controllers/articles_controller.rb`: @@ -1376,7 +1380,9 @@ class CreateComments < ActiveRecord::Migration create_table :comments do |t| t.string :commenter t.text :body - t.references :article, index: true + + # this line adds an integer column called `article_id`. + t.references :article, index: true t.timestamps end -- cgit v1.2.3 From 680f7d906e6fba521f6a9343d5d5b8c1376bbe76 Mon Sep 17 00:00:00 2001 From: Rajarshi Das Date: Fri, 14 Feb 2014 16:36:45 +0530 Subject: remove unused fixtures from sqlite3 test cases --- activerecord/test/cases/adapters/sqlite3/copy_table_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb index e78cb88562..b478db749d 100644 --- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb @@ -1,7 +1,7 @@ require "cases/helper" class CopyTableTest < ActiveRecord::TestCase - fixtures :customers, :companies, :comments, :binaries + fixtures :customers def setup @connection = ActiveRecord::Base.connection -- cgit v1.2.3 From 0e19e3542866e2f7be35c079abb815212b9a61ab Mon Sep 17 00:00:00 2001 From: tbpgr Date: Fri, 14 Feb 2014 22:02:23 +0900 Subject: fix guide active_support_core_extensions. add Note to String#indent [ci skip] --- guides/source/active_support_core_extensions.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 59dfefd22f..2ad09f599b 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -1403,6 +1403,8 @@ The third argument, `indent_empty_lines`, is a flag that says whether empty line The `indent!` method performs indentation in-place. +NOTE: Defined in `active_support/core_ext/string/indent.rb`. + ### Access #### `at(position)` -- cgit v1.2.3 From 4f5aff92887b0a5c206abcaefa6881ae5f34d679 Mon Sep 17 00:00:00 2001 From: Leo Antoli Date: Fri, 14 Feb 2014 14:24:52 +0100 Subject: updated Travis build status image url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cf93453d3a..7f079536d2 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ We encourage you to contribute to Ruby on Rails! Please check out the ## Code Status -* [![Build Status](https://api.travis-ci.org/rails/rails.png)](https://travis-ci.org/rails/rails) +* [![Build Status](https://travis-ci.org/rails/rails.png?branch=master)](https://travis-ci.org/rails/rails) ## License -- cgit v1.2.3 From aae455f636774ee1e4c706eb41520bf83be6a8c0 Mon Sep 17 00:00:00 2001 From: Kassio Borges Date: Fri, 14 Feb 2014 14:17:12 -0200 Subject: fix path shown in mailer's templates --- railties/lib/rails/generators/erb/mailer/templates/view.html.erb | 2 +- railties/lib/rails/generators/erb/mailer/templates/view.text.erb | 2 +- railties/test/generators/mailer_generator_test.rb | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/railties/lib/rails/generators/erb/mailer/templates/view.html.erb b/railties/lib/rails/generators/erb/mailer/templates/view.html.erb index 8bb7c2b768..b5045671b3 100644 --- a/railties/lib/rails/generators/erb/mailer/templates/view.html.erb +++ b/railties/lib/rails/generators/erb/mailer/templates/view.html.erb @@ -1,5 +1,5 @@

<%= class_name %>#<%= @action %>

- <%%= @greeting %>, find me in app/views/<%= @path %> + <%%= @greeting %>, find me in <%= @path %>

diff --git a/railties/lib/rails/generators/erb/mailer/templates/view.text.erb b/railties/lib/rails/generators/erb/mailer/templates/view.text.erb index 6d597256a6..342285df19 100644 --- a/railties/lib/rails/generators/erb/mailer/templates/view.text.erb +++ b/railties/lib/rails/generators/erb/mailer/templates/view.text.erb @@ -1,3 +1,3 @@ <%= class_name %>#<%= @action %> -<%%= @greeting %>, find me in app/views/<%= @path %> +<%%= @greeting %>, find me in <%= @path %> diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb index d209801f60..25649881eb 100644 --- a/railties/test/generators/mailer_generator_test.rb +++ b/railties/test/generators/mailer_generator_test.rb @@ -69,12 +69,12 @@ class MailerGeneratorTest < Rails::Generators::TestCase def test_invokes_default_text_template_engine run_generator assert_file "app/views/notifier/foo.text.erb" do |view| - assert_match(%r(app/views/notifier/foo\.text\.erb), view) + assert_match(%r(\sapp/views/notifier/foo\.text\.erb), view) assert_match(/<%= @greeting %>/, view) end assert_file "app/views/notifier/bar.text.erb" do |view| - assert_match(%r(app/views/notifier/bar\.text\.erb), view) + assert_match(%r(\sapp/views/notifier/bar\.text\.erb), view) assert_match(/<%= @greeting %>/, view) end end @@ -82,12 +82,12 @@ class MailerGeneratorTest < Rails::Generators::TestCase def test_invokes_default_html_template_engine run_generator assert_file "app/views/notifier/foo.html.erb" do |view| - assert_match(%r(app/views/notifier/foo\.html\.erb), view) + assert_match(%r(\sapp/views/notifier/foo\.html\.erb), view) assert_match(/<%= @greeting %>/, view) end assert_file "app/views/notifier/bar.html.erb" do |view| - assert_match(%r(app/views/notifier/bar\.html\.erb), view) + assert_match(%r(\sapp/views/notifier/bar\.html\.erb), view) assert_match(/<%= @greeting %>/, view) end end -- cgit v1.2.3 From 848e377a2017234e3831599346918fb8d413fd28 Mon Sep 17 00:00:00 2001 From: Dave Jachimiak Date: Fri, 14 Feb 2014 11:36:03 -0500 Subject: Add verb to sanitization note --- guides/source/security.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/security.md b/guides/source/security.md index 70fb066b64..ece431dae7 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -549,7 +549,7 @@ Injection is very tricky, because the same code or parameter can be malicious in ### Whitelists versus Blacklists -NOTE: _When sanitizing, protecting or verifying something, whitelists over blacklists._ +NOTE: _When sanitizing, protecting or verifying something, prefer whitelists over blacklists._ A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTML tags. This is opposed to a whitelist which lists the good e-mail addresses, public actions, good HTML tags and so on. Although sometimes it is not possible to create a whitelist (in a SPAM filter, for example), _prefer to use whitelist approaches_: -- cgit v1.2.3 From 279212d8b12e03a17fd45a33c74f37f8ac844650 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 13 Feb 2014 15:42:32 -0800 Subject: remove scope_chain delegate --- activerecord/lib/active_record/associations/association_scope.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 15691e26df..f936bcb80e 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -4,7 +4,7 @@ module ActiveRecord attr_reader :association, :alias_tracker delegate :klass, :reflection, :to => :association - delegate :chain, :scope_chain, :to => :reflection + delegate :chain, :to => :reflection def initialize(association) @association = association @@ -16,7 +16,7 @@ module ActiveRecord scope.extending! Array(reflection.options[:extend]) owner = association.owner - add_constraints(scope, owner) + add_constraints(scope, owner, reflection.scope_chain) end def join_type @@ -61,7 +61,7 @@ module ActiveRecord bind_value scope, column, value end - def add_constraints(scope, owner) + def add_constraints(scope, owner, scope_chain) tables = construct_tables chain.each_with_index do |reflection, i| -- cgit v1.2.3 From a8775bba58fb9dd3b0a51a812fa6ec43d8084ff6 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 13 Feb 2014 15:58:28 -0800 Subject: remove chain delegate --- .../lib/active_record/associations/association_scope.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index f936bcb80e..a6e453a3f1 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -4,7 +4,6 @@ module ActiveRecord attr_reader :association, :alias_tracker delegate :klass, :reflection, :to => :association - delegate :chain, :to => :reflection def initialize(association) @association = association @@ -16,7 +15,9 @@ module ActiveRecord scope.extending! Array(reflection.options[:extend]) owner = association.owner - add_constraints(scope, owner, reflection.scope_chain) + scope_chain = reflection.scope_chain + chain = reflection.chain + add_constraints(scope, owner, scope_chain, chain) end def join_type @@ -25,7 +26,7 @@ module ActiveRecord private - def construct_tables + def construct_tables(chain) chain.map do |reflection| alias_tracker.aliased_table_for( table_name_for(reflection), @@ -61,8 +62,8 @@ module ActiveRecord bind_value scope, column, value end - def add_constraints(scope, owner, scope_chain) - tables = construct_tables + def add_constraints(scope, owner, scope_chain, chain) + tables = construct_tables(chain) chain.each_with_index do |reflection, i| table, foreign_table = tables.shift, tables.first -- cgit v1.2.3 From 22a1a5ac8c4631f29cfeac451c361e8da1dd2261 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 14 Feb 2014 11:38:26 -0800 Subject: remove railties changes. fixes #14054 Squashed commit of the following: commit 96991e8e919edfb20cc4120bca4e36ed51175d57 Author: Aaron Patterson Date: Fri Feb 14 11:29:24 2014 -0800 Revert "gems can be added or skipped from the template" This reverts commit 8beb42cfbc41753ae4dbb91e16abcd1fb7d00356. Conflicts: railties/lib/rails/generators/rails/app/app_generator.rb railties/test/generators/app_generator_test.rb commit 35599c0e657245ef14ac0f28c9189ad16acf40e6 Author: Aaron Patterson Date: Fri Feb 14 11:26:53 2014 -0800 Revert "oops, template replay needs to happen after bundle. :orz:" This reverts commit 9104702be61253f9448ca070a22fc86bb4299555. Conflicts: railties/lib/rails/generators/rails/app/app_generator.rb commit f519c3902c313db8e906a49251c91643b8e6499e Author: Aaron Patterson Date: Fri Feb 14 11:25:51 2014 -0800 Revert "only ask for these ivars if the target responds to them" This reverts commit 656d412546cd97d5660c634c2a41c799d3f9e211. commit aa524a9428e3e4c45fe221f10a66a08efb827ab5 Author: Aaron Patterson Date: Fri Feb 14 11:25:39 2014 -0800 Revert "refactor generator tests to use block form of Tempfile" This reverts commit 65251820ef0ab7f3cffb38130de3dd41af8d72be. commit 7d3740549fa4dfa62e3761f8d4bc6d6d441256e7 Author: Aaron Patterson Date: Fri Feb 14 11:25:25 2014 -0800 Revert "add a more restricted codepath for templates fixes #13390" This reverts commit 2875b4a66e38e4333da887a4afbed33358999298. commit 525df0af1001918986cdfce59539fd2d52c4f32c Author: Aaron Patterson Date: Fri Feb 14 11:25:11 2014 -0800 Revert "add a send so `apply` can be called. Fixes #13510" This reverts commit c5034d60dba0cd31a6a8c612ee35d63b8127793a. --- railties/lib/rails/generators/app_base.rb | 95 ++-------------------- .../rails/generators/rails/app/app_generator.rb | 4 +- railties/lib/rails/tasks/framework.rake | 2 +- railties/test/application/rake/templates_test.rb | 32 -------- railties/test/generators/app_generator_test.rb | 67 --------------- railties/test/generators/generator_test.rb | 1 - 6 files changed, 10 insertions(+), 191 deletions(-) delete mode 100644 railties/test/application/rake/templates_test.rb diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 815894144a..f1f79d8378 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -14,7 +14,6 @@ module Rails DATABASES.concat(JDBC_DATABASES) attr_accessor :rails_template - attr_accessor :app_template add_shebang_option! argument :app_path, type: :string @@ -27,9 +26,6 @@ module Rails class_option :template, type: :string, aliases: '-m', desc: "Path to some #{name} template (can be a filesystem path or URL)" - class_option :app_template, type: :string, aliases: '-n', - desc: "Path to some #{name} template (can be a filesystem path or URL)" - class_option :skip_gemfile, type: :boolean, default: false, desc: "Don't create a Gemfile" @@ -126,10 +122,6 @@ module Rails }.curry[@gem_filter] end - def remove_gem(name) - add_gem_entry_filter { |gem| gem.name != name } - end - def builder @builder ||= begin builder_class = get_builder_class @@ -149,92 +141,21 @@ module Rails FileUtils.cd(destination_root) unless options[:pretend] end - class TemplateRecorder < ::BasicObject # :nodoc: - attr_reader :gems - - def initialize(target) - @target = target - # unfortunately, instance eval has access to these ivars - @app_const = target.send :app_const if target.respond_to?(:app_const, true) - @app_const_base = target.send :app_const_base if target.respond_to?(:app_const_base, true) - @app_name = target.send :app_name if target.respond_to?(:app_name, true) - @commands = [] - @gems = [] - end - - def gemfile_entry(*args) - @target.send :gemfile_entry, *args - end - - def add_gem_entry_filter(*args, &block) - @target.send :add_gem_entry_filter, *args, &block - end - - def remove_gem(*args, &block) - @target.send :remove_gem, *args, &block - end - - def method_missing(name, *args, &block) - @commands << [name, args, block] - end - - def respond_to_missing?(method, priv = false) - super || @target.respond_to?(method, priv) - end - - def replay! - @commands.each do |name, args, block| - @target.send name, *args, &block - end - end - end - def apply_rails_template - @recorder = TemplateRecorder.new self - - apply(rails_template, target: self) if rails_template - apply(app_template, target: @recorder) if app_template + apply rails_template if rails_template rescue Thor::Error, LoadError, Errno::ENOENT => e raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}" end - def replay_template - @recorder.replay! if @recorder - end - - def apply(path, config={}) - verbose = config.fetch(:verbose, true) - target = config.fetch(:target, self) - is_uri = path =~ /^https?\:\/\// - path = find_in_source_paths(path) unless is_uri - - say_status :apply, path, verbose - shell.padding += 1 if verbose - - if is_uri - contents = open(path, "Accept" => "application/x-thor-template") {|io| io.read } - else - contents = open(path) {|io| io.read } - end - - target.instance_eval(contents, path) - shell.padding -= 1 if verbose - end - def set_default_accessors! self.destination_root = File.expand_path(app_path, destination_root) - self.rails_template = expand_template options[:template] - self.app_template = expand_template options[:app_template] - end - - def expand_template(name) - case name - when /^https?:\/\// - name - when String - File.expand_path(name, Dir.pwd) - else - name + self.rails_template = case options[:template] + when /^https?:\/\// + options[:template] + when String + File.expand_path(options[:template], Dir.pwd) + else + options[:template] end end diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index d2eca5b2fb..83cb1dc0d5 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -166,7 +166,6 @@ module Rails end public_task :set_default_accessors! - public_task :apply_rails_template public_task :create_root def create_root_files @@ -236,8 +235,7 @@ module Rails build(:leftovers) end - public_task :run_bundle - public_task :replay_template + public_task :apply_rails_template, :run_bundle public_task :generate_spring_binstubs protected diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 94e8f83e86..e669315934 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -10,7 +10,7 @@ namespace :rails do require 'rails/generators' require 'rails/generators/rails/app/app_generator' generator = Rails::Generators::AppGenerator.new [Rails.root], {}, destination_root: Rails.root - generator.send :apply, template, verbose: false + generator.apply template, verbose: false end namespace :templates do diff --git a/railties/test/application/rake/templates_test.rb b/railties/test/application/rake/templates_test.rb deleted file mode 100644 index 1fca80debd..0000000000 --- a/railties/test/application/rake/templates_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require "isolation/abstract_unit" - -module ApplicationTests - module RakeTests - class TemplatesTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation - - def setup - build_app - require "rails/all" - super - end - - def teardown - super - teardown_app - end - - def test_rake_template - Dir.chdir(app_path) do - cmd = "bundle exec rake rails:template LOCATION=foo" - r,w = IO.pipe - Process.waitpid Process.spawn(cmd, out: w, err: w) - w.close - assert_match(/Could not find.*foo/, r.read) - r.close - end - end - end - end -end - diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index ddecee2ca1..5811379e35 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -163,73 +163,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_arbitrary_code - output = Tempfile.open('my_template') do |template| - template.puts 'puts "You are using Rails version #{Rails::VERSION::STRING}."' - template.close - run_generator([destination_root, "-m", template.path]) - end - assert_match 'You are using', output - end - - def test_add_gemfile_entry - Tempfile.open('my_template') do |template| - template.puts 'gemfile_entry "tenderlove"' - template.flush - template.close - run_generator([destination_root, "-n", template.path]) - assert_file "Gemfile", /tenderlove/ - end - end - - def test_add_skip_entry - Tempfile.open 'my_template' do |template| - template.puts 'add_gem_entry_filter { |gem| gem.name != "jbuilder" }' - template.close - - run_generator([destination_root, "-n", template.path]) - assert_file "Gemfile" do |contents| - assert_no_match 'jbuilder', contents - end - end - end - - def test_remove_gem - Tempfile.open 'my_template' do |template| - template.puts 'remove_gem "jbuilder"' - template.close - - run_generator([destination_root, "-n", template.path]) - assert_file "Gemfile" do |contents| - assert_no_match 'jbuilder', contents - end - end - end - - def test_skip_turbolinks_when_it_is_not_on_gemfile - Tempfile.open 'my_template' do |template| - template.puts 'add_gem_entry_filter { |gem| gem.name != "turbolinks" }' - template.flush - - run_generator([destination_root, "-n", template.path]) - assert_file "Gemfile" do |contents| - assert_no_match 'turbolinks', contents - end - - assert_file "app/views/layouts/application.html.erb" do |contents| - assert_no_match 'turbolinks', contents - end - - assert_file "app/views/layouts/application.html.erb" do |contents| - assert_no_match('data-turbolinks-track', contents) - end - - assert_file "app/assets/javascripts/application.js" do |contents| - assert_no_match 'turbolinks', contents - end - end - end - def test_config_another_database run_generator([destination_root, "-d", "mysql"]) assert_file "config/database.yml", /mysql/ diff --git a/railties/test/generators/generator_test.rb b/railties/test/generators/generator_test.rb index 94d2c1bf50..7871399dd7 100644 --- a/railties/test/generators/generator_test.rb +++ b/railties/test/generators/generator_test.rb @@ -1,7 +1,6 @@ require 'active_support/test_case' require 'active_support/testing/autorun' require 'rails/generators/app_base' -require 'rails/generators/rails/app/app_generator' module Rails module Generators -- cgit v1.2.3 From 40a015f7305affc046049ad17e16b8cc85763da7 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 14 Feb 2014 13:57:17 -0800 Subject: remove klass delegator --- .../active_record/associations/association_scope.rb | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index a6e453a3f1..42702528f0 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -3,21 +3,22 @@ module ActiveRecord class AssociationScope #:nodoc: attr_reader :association, :alias_tracker - delegate :klass, :reflection, :to => :association + delegate :reflection, :to => :association def initialize(association) @association = association - @alias_tracker = AliasTracker.new klass.connection + @alias_tracker = AliasTracker.new association.klass.connection end def scope + klass = association.klass scope = klass.unscoped scope.extending! Array(reflection.options[:extend]) owner = association.owner scope_chain = reflection.scope_chain chain = reflection.chain - add_constraints(scope, owner, scope_chain, chain) + add_constraints(scope, owner, scope_chain, chain, klass) end def join_type @@ -26,10 +27,10 @@ module ActiveRecord private - def construct_tables(chain) + def construct_tables(chain, klass) chain.map do |reflection| alias_tracker.aliased_table_for( - table_name_for(reflection), + table_name_for(reflection, klass), table_alias_for(reflection, reflection != self.reflection) ) end @@ -62,15 +63,15 @@ module ActiveRecord bind_value scope, column, value end - def add_constraints(scope, owner, scope_chain, chain) - tables = construct_tables(chain) + def add_constraints(scope, owner, scope_chain, chain, assoc_klass) + tables = construct_tables(chain, assoc_klass) chain.each_with_index do |reflection, i| table, foreign_table = tables.shift, tables.first if reflection.source_macro == :belongs_to if reflection.options[:polymorphic] - key = reflection.association_primary_key(self.klass) + key = reflection.association_primary_key(assoc_klass) else key = reflection.association_primary_key end @@ -103,7 +104,7 @@ module ActiveRecord end is_first_chain = i == 0 - klass = is_first_chain ? self.klass : reflection.klass + klass = is_first_chain ? assoc_klass : reflection.klass # Exclude the scope of the association itself, because that # was already merged in the #scope method. @@ -130,7 +131,7 @@ module ActiveRecord reflection.name end - def table_name_for(reflection) + def table_name_for(reflection, klass) if reflection == self.reflection # If this is a polymorphic belongs_to, we want to get the klass from the # association because it depends on the polymorphic_type attribute of -- cgit v1.2.3 From a35325e32491d566fbb58001f8e68bed86d06955 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 14 Feb 2014 14:02:18 -0800 Subject: remove the reflection delegate --- .../associations/association_scope.rb | 29 +++++++++++----------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 42702528f0..2046d94a3d 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -3,8 +3,6 @@ module ActiveRecord class AssociationScope #:nodoc: attr_reader :association, :alias_tracker - delegate :reflection, :to => :association - def initialize(association) @association = association @alias_tracker = AliasTracker.new association.klass.connection @@ -12,13 +10,14 @@ module ActiveRecord def scope klass = association.klass + reflection = association.reflection scope = klass.unscoped scope.extending! Array(reflection.options[:extend]) owner = association.owner scope_chain = reflection.scope_chain chain = reflection.chain - add_constraints(scope, owner, scope_chain, chain, klass) + add_constraints(scope, owner, scope_chain, chain, klass, reflection) end def join_type @@ -27,17 +26,17 @@ module ActiveRecord private - def construct_tables(chain, klass) + def construct_tables(chain, klass, refl) chain.map do |reflection| alias_tracker.aliased_table_for( - table_name_for(reflection, klass), - table_alias_for(reflection, reflection != self.reflection) + table_name_for(reflection, klass, refl), + table_alias_for(reflection, refl, reflection != refl) ) end end - def table_alias_for(reflection, join = false) - name = "#{reflection.plural_name}_#{alias_suffix}" + def table_alias_for(reflection, refl, join = false) + name = "#{reflection.plural_name}_#{alias_suffix(refl)}" name << "_join" if join name end @@ -63,8 +62,8 @@ module ActiveRecord bind_value scope, column, value end - def add_constraints(scope, owner, scope_chain, chain, assoc_klass) - tables = construct_tables(chain, assoc_klass) + def add_constraints(scope, owner, scope_chain, chain, assoc_klass, refl) + tables = construct_tables(chain, assoc_klass, refl) chain.each_with_index do |reflection, i| table, foreign_table = tables.shift, tables.first @@ -111,7 +110,7 @@ module ActiveRecord scope_chain[i].each do |scope_chain_item| item = eval_scope(klass, scope_chain_item, owner) - if scope_chain_item == self.reflection.scope + if scope_chain_item == refl.scope scope.merge! item.except(:where, :includes, :bind) end @@ -127,12 +126,12 @@ module ActiveRecord scope end - def alias_suffix - reflection.name + def alias_suffix(refl) + refl.name end - def table_name_for(reflection, klass) - if reflection == self.reflection + def table_name_for(reflection, klass, refl) + if reflection == refl # If this is a polymorphic belongs_to, we want to get the klass from the # association because it depends on the polymorphic_type attribute of # the owner -- cgit v1.2.3 From db3f50c767f3933a43c23ec3a7b9ef02668d048a Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 14 Feb 2014 14:04:39 -0800 Subject: clean up add_constraints signature --- .../lib/active_record/associations/association_scope.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 2046d94a3d..9590d28129 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -9,15 +9,13 @@ module ActiveRecord end def scope - klass = association.klass + klass = association.klass reflection = association.reflection - scope = klass.unscoped - scope.extending! Array(reflection.options[:extend]) + scope = klass.unscoped + owner = association.owner - owner = association.owner - scope_chain = reflection.scope_chain - chain = reflection.chain - add_constraints(scope, owner, scope_chain, chain, klass, reflection) + scope.extending! Array(reflection.options[:extend]) + add_constraints(scope, owner, klass, reflection) end def join_type @@ -62,7 +60,10 @@ module ActiveRecord bind_value scope, column, value end - def add_constraints(scope, owner, scope_chain, chain, assoc_klass, refl) + def add_constraints(scope, owner, assoc_klass, refl) + chain = refl.chain + scope_chain = refl.scope_chain + tables = construct_tables(chain, assoc_klass, refl) chain.each_with_index do |reflection, i| -- cgit v1.2.3 From 3b675f05ffb11cdbfd3f7d416906ff16941e8367 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 14 Feb 2014 14:13:52 -0800 Subject: pass the tracker down the stack and construct it in the scope method --- .../associations/association_scope.rb | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 9590d28129..1f094d42b3 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -1,21 +1,21 @@ module ActiveRecord module Associations class AssociationScope #:nodoc: - attr_reader :association, :alias_tracker + attr_reader :association def initialize(association) @association = association - @alias_tracker = AliasTracker.new association.klass.connection end def scope - klass = association.klass - reflection = association.reflection - scope = klass.unscoped - owner = association.owner + klass = association.klass + reflection = association.reflection + scope = klass.unscoped + owner = association.owner + alias_tracker = AliasTracker.new klass.connection scope.extending! Array(reflection.options[:extend]) - add_constraints(scope, owner, klass, reflection) + add_constraints(scope, owner, klass, reflection, alias_tracker) end def join_type @@ -24,7 +24,7 @@ module ActiveRecord private - def construct_tables(chain, klass, refl) + def construct_tables(chain, klass, refl, alias_tracker) chain.map do |reflection| alias_tracker.aliased_table_for( table_name_for(reflection, klass, refl), @@ -43,28 +43,28 @@ module ActiveRecord table.create_join(table, table.create_on(constraint), join_type) end - def column_for(table_name, column_name) + def column_for(table_name, column_name, alias_tracker) columns = alias_tracker.connection.schema_cache.columns_hash(table_name) columns[column_name] end - def bind_value(scope, column, value) + def bind_value(scope, column, value, alias_tracker) substitute = alias_tracker.connection.substitute_at( column, scope.bind_values.length) scope.bind_values += [[column, value]] substitute end - def bind(scope, table_name, column_name, value) - column = column_for table_name, column_name - bind_value scope, column, value + def bind(scope, table_name, column_name, value, tracker) + column = column_for table_name, column_name, tracker + bind_value scope, column, value, tracker end - def add_constraints(scope, owner, assoc_klass, refl) + def add_constraints(scope, owner, assoc_klass, refl, tracker) chain = refl.chain scope_chain = refl.scope_chain - tables = construct_tables(chain, assoc_klass, refl) + tables = construct_tables(chain, assoc_klass, refl, tracker) chain.each_with_index do |reflection, i| table, foreign_table = tables.shift, tables.first @@ -83,12 +83,12 @@ module ActiveRecord end if reflection == chain.last - bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key] + bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker scope = scope.where(table[key].eq(bind_val)) if reflection.type value = owner.class.base_class.name - bind_val = bind scope, table.table_name, reflection.type.to_s, value + bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker scope = scope.where(table[reflection.type].eq(bind_val)) end else @@ -96,7 +96,7 @@ module ActiveRecord if reflection.type value = chain[i + 1].klass.base_class.name - bind_val = bind scope, table.table_name, reflection.type.to_s, value + bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker scope = scope.where(table[reflection.type].eq(bind_val)) end -- cgit v1.2.3 From 8e6ef92fd9ede38aa435afeaef6370ff65191a0b Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 14 Feb 2014 14:20:07 -0800 Subject: pass the association and connection to the scope method --- activerecord/lib/active_record/associations/association.rb | 2 +- .../lib/active_record/associations/association_scope.rb | 10 ++-------- activerecord/test/cases/associations/association_scope_test.rb | 6 ++++-- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 67ea489b22..2dba33898c 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -94,7 +94,7 @@ module ActiveRecord # actually gets built. def association_scope if klass - @association_scope ||= AssociationScope.new(self).scope + @association_scope ||= AssociationScope.new.scope(self, klass.connection) end end diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 1f094d42b3..cd2911e0a4 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -1,18 +1,12 @@ module ActiveRecord module Associations class AssociationScope #:nodoc: - attr_reader :association - - def initialize(association) - @association = association - end - - def scope + def scope(association, connection) klass = association.klass reflection = association.reflection scope = klass.unscoped owner = association.owner - alias_tracker = AliasTracker.new klass.connection + alias_tracker = AliasTracker.new connection scope.extending! Array(reflection.options[:extend]) add_constraints(scope, owner, klass, reflection, alias_tracker) diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb index d38648202e..f9793277e5 100644 --- a/activerecord/test/cases/associations/association_scope_test.rb +++ b/activerecord/test/cases/associations/association_scope_test.rb @@ -6,8 +6,10 @@ module ActiveRecord module Associations class AssociationScopeTest < ActiveRecord::TestCase test 'does not duplicate conditions' do - association_scope = AssociationScope.new(Author.new.association(:welcome_posts)) - wheres = association_scope.scope.where_values.map(&:right) + association_scope = AssociationScope.new + scope = association_scope.scope(Author.new.association(:welcome_posts), + Author.connection) + wheres = scope.where_values.map(&:right) assert_equal wheres.uniq, wheres end end -- cgit v1.2.3 From 213b2fbf40f6a1ce8381749bd5ba734f20bd4b21 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 14 Feb 2014 14:23:04 -0800 Subject: make a singleton for AssociationScope AssociationScope no longer maintains state, so we're safe to keep a singleton and save on GC time --- activerecord/lib/active_record/associations/association.rb | 2 +- activerecord/lib/active_record/associations/association_scope.rb | 6 ++++++ activerecord/test/cases/associations/association_scope_test.rb | 3 +-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 2dba33898c..4e46256862 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -94,7 +94,7 @@ module ActiveRecord # actually gets built. def association_scope if klass - @association_scope ||= AssociationScope.new.scope(self, klass.connection) + @association_scope ||= AssociationScope.scope(self, klass.connection) end end diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index cd2911e0a4..1bc998d20c 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -1,6 +1,12 @@ module ActiveRecord module Associations class AssociationScope #:nodoc: + INSTANCE = new + + def self.scope(association, connection) + INSTANCE.scope association, connection + end + def scope(association, connection) klass = association.klass reflection = association.reflection diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb index f9793277e5..c78b036f53 100644 --- a/activerecord/test/cases/associations/association_scope_test.rb +++ b/activerecord/test/cases/associations/association_scope_test.rb @@ -6,8 +6,7 @@ module ActiveRecord module Associations class AssociationScopeTest < ActiveRecord::TestCase test 'does not duplicate conditions' do - association_scope = AssociationScope.new - scope = association_scope.scope(Author.new.association(:welcome_posts), + scope = AssociationScope.scope(Author.new.association(:welcome_posts), Author.connection) wheres = scope.where_values.map(&:right) assert_equal wheres.uniq, wheres -- cgit v1.2.3 From c24ea241e224b2d58e3184fa119beddac096b1f2 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 14 Feb 2014 16:47:03 -0800 Subject: make most parameters to the AliasTracker required This helps with our sanity. The class is internal, we can refactor to a "nice" API later. --- activerecord/lib/active_record/associations/alias_tracker.rb | 8 +++----- activerecord/lib/active_record/associations/join_dependency.rb | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 0c23029981..7d938509db 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -8,13 +8,13 @@ module ActiveRecord attr_reader :aliases, :table_joins, :connection # table_joins is an array of arel joins which might conflict with the aliases we assign here - def initialize(connection = Base.connection, table_joins = []) + def initialize(connection, table_joins = []) @aliases = Hash.new { |h,k| h[k] = initial_count_for(k) } @table_joins = table_joins @connection = connection end - def aliased_table_for(table_name, aliased_name = nil) + def aliased_table_for(table_name, aliased_name) table_alias = aliased_name_for(table_name, aliased_name) if table_alias == table_name @@ -24,9 +24,7 @@ module ActiveRecord end end - def aliased_name_for(table_name, aliased_name = nil) - aliased_name ||= table_name - + def aliased_name_for(table_name, aliased_name) if aliases[table_name].zero? # If it's zero, we can have our table_name aliases[table_name] = 1 diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 27069157be..1c8a44d0c5 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -94,7 +94,7 @@ module ActiveRecord # def initialize(base, associations, joins) @alias_tracker = AliasTracker.new(base.connection, joins) - @alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1 + @alias_tracker.aliased_name_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1 tree = self.class.make_tree associations @join_root = JoinBase.new base, build(tree, base) @join_root.children.each { |child| construct_tables! @join_root, child } -- cgit v1.2.3 From 494a26d798966a900127d3d4df6bada152896222 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 14 Feb 2014 16:51:24 -0800 Subject: stop exposing table_joins --- activerecord/lib/active_record/associations/alias_tracker.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 7d938509db..f41f7bffff 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -5,12 +5,11 @@ module ActiveRecord # Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and # ActiveRecord::Associations::ThroughAssociationScope class AliasTracker # :nodoc: - attr_reader :aliases, :table_joins, :connection + attr_reader :aliases, :connection # table_joins is an array of arel joins which might conflict with the aliases we assign here def initialize(connection, table_joins = []) - @aliases = Hash.new { |h,k| h[k] = initial_count_for(k) } - @table_joins = table_joins + @aliases = Hash.new { |h,k| h[k] = initial_count_for(k, table_joins) } @connection = connection end @@ -46,7 +45,7 @@ module ActiveRecord private - def initial_count_for(name) + def initial_count_for(name, table_joins) return 0 if Arel::Table === table_joins # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase -- cgit v1.2.3 From 4e823b61190388219868744a34dcfe926bad511c Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 14 Feb 2014 17:40:21 -0800 Subject: guarantee a list in the alias tracker so we can remove a conditional --- activerecord/lib/active_record/associations/alias_tracker.rb | 6 ++---- .../lib/active_record/associations/association_scope.rb | 2 +- activerecord/lib/active_record/relation/finder_methods.rb | 10 +++++++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index f41f7bffff..ea8e1f5054 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -8,8 +8,8 @@ module ActiveRecord attr_reader :aliases, :connection # table_joins is an array of arel joins which might conflict with the aliases we assign here - def initialize(connection, table_joins = []) - @aliases = Hash.new { |h,k| h[k] = initial_count_for(k, table_joins) } + def initialize(connection, table_joins) + @aliases = Hash.new { |h,k| h[k] = initial_count_for(k, table_joins) } @connection = connection end @@ -46,8 +46,6 @@ module ActiveRecord private def initial_count_for(name, table_joins) - return 0 if Arel::Table === table_joins - # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase quoted_name = connection.quote_table_name(name).downcase diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 1bc998d20c..63e81a17aa 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -12,7 +12,7 @@ module ActiveRecord reflection = association.reflection scope = klass.unscoped owner = association.owner - alias_tracker = AliasTracker.new connection + alias_tracker = AliasTracker.new(connection, []) scope.extending! Array(reflection.options[:extend]) add_constraints(scope, owner, klass, reflection, alias_tracker) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 01d46f7676..7099bdd285 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -347,7 +347,15 @@ module ActiveRecord end def construct_relation_for_association_calculations - apply_join_dependency(self, construct_join_dependency(arel.froms.first)) + from = arel.froms.first + if Arel::Table === from + apply_join_dependency(self, construct_join_dependency) + else + # FIXME: as far as I can tell, `from` will always be an Arel::Table. + # There are no tests that test this branch, but presumably it's + # possible for `from` to be a list? + apply_join_dependency(self, construct_join_dependency(from)) + end end def apply_join_dependency(relation, join_dependency) -- cgit v1.2.3 From bfc776f7bb114e90cf91f16f5892be636ed2f0c8 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 14 Feb 2014 18:01:12 -0800 Subject: add factory methods for empty alias trackers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If we know the alias tracker is empty, we can create one that doesn't use a hash with default block for counting. ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') ActiveRecord::Schema.define do create_table :posts, force: true do |t| t.integer :comments_count end create_table :comments, force: true do |t| t.integer :post_id end end class Post < ActiveRecord::Base; has_many :comments; end class Comment < ActiveRecord::Base; belongs_to :post, counter_cache: true; end 10.times { Comment.create!(post: Post.create!) } record = Post.first association_name = :comments Benchmark.ips do |x| reflection = record.class.reflect_on_association(association_name) association = reflection.association_class.new(record, reflection) x.report('assoc') do reflection.association_class.new(record, reflection) end x.report('reader') do association.reader;nil end x.report('combined') do reflection.association_class.new(record, reflection).reader;nil end end [aaron@higgins rails (tracker)]$ TEST=ips bundle exec ruby ../1bb5456b5e035343df9d/gistfile1.rb -- create_table(:posts, {:force=>true}) -> 0.0062s -- create_table(:comments, {:force=>true}) -> 0.0003s Calculating ------------------------------------- assoc 833 i/100ms reader 28703 i/100ms combined 839 i/100ms ------------------------------------------------- assoc 9010.3 (±3.8%) i/s - 44982 in 5.000022s reader 3214523.4 (±5.5%) i/s - 16016274 in 5.001136s combined 8841.0 (±5.8%) i/s - 44467 in 5.049269s [aaron@higgins rails (tracker)]$ TEST=ips bundle exec ruby ../1bb5456b5e035343df9d/gistfile1.rb -- create_table(:posts, {:force=>true}) -> 0.0060s -- create_table(:comments, {:force=>true}) -> 0.0003s Calculating ------------------------------------- assoc 888 i/100ms reader 29217 i/100ms combined 900 i/100ms ------------------------------------------------- assoc 9674.3 (±3.3%) i/s - 48840 in 5.054022s reader 2988474.8 (±6.9%) i/s - 14842236 in 4.998230s combined 9674.0 (±3.1%) i/s - 48600 in 5.028694s --- .../active_record/associations/alias_tracker.rb | 57 ++++++++++++++-------- .../associations/association_scope.rb | 2 +- .../active_record/associations/join_dependency.rb | 2 +- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index ea8e1f5054..85109aee6c 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -7,10 +7,43 @@ module ActiveRecord class AliasTracker # :nodoc: attr_reader :aliases, :connection + def self.empty(connection) + new connection, Hash.new(0) + end + + def self.create(connection, table_joins) + if table_joins.empty? + empty connection + else + aliases = Hash.new { |h,k| + h[k] = initial_count_for(connection, k, table_joins) + } + new connection, aliases + end + end + + def self.initial_count_for(connection, name, table_joins) + # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase + quoted_name = connection.quote_table_name(name).downcase + + counts = table_joins.map do |join| + if join.is_a?(Arel::Nodes::StringJoin) + # Table names + table aliases + join.left.downcase.scan( + /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/ + ).size + else + join.left.table_name == name ? 1 : 0 + end + end + + counts.sum + end + # table_joins is an array of arel joins which might conflict with the aliases we assign here - def initialize(connection, table_joins) - @aliases = Hash.new { |h,k| h[k] = initial_count_for(k, table_joins) } - @connection = connection + def initialize(connection, aliases) + @aliases = aliases + @connection = connection end def aliased_table_for(table_name, aliased_name) @@ -45,24 +78,6 @@ module ActiveRecord private - def initial_count_for(name, table_joins) - # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase - quoted_name = connection.quote_table_name(name).downcase - - counts = table_joins.map do |join| - if join.is_a?(Arel::Nodes::StringJoin) - # Table names + table aliases - join.left.downcase.scan( - /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/ - ).size - else - join.left.table_name == name ? 1 : 0 - end - end - - counts.sum - end - def truncate(name) name.slice(0, connection.table_alias_length - 2) end diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 63e81a17aa..27fd9e35db 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -12,7 +12,7 @@ module ActiveRecord reflection = association.reflection scope = klass.unscoped owner = association.owner - alias_tracker = AliasTracker.new(connection, []) + alias_tracker = AliasTracker.empty connection scope.extending! Array(reflection.options[:extend]) add_constraints(scope, owner, klass, reflection, alias_tracker) diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 1c8a44d0c5..94f69d4c2d 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -93,7 +93,7 @@ module ActiveRecord # joins # => [] # def initialize(base, associations, joins) - @alias_tracker = AliasTracker.new(base.connection, joins) + @alias_tracker = AliasTracker.create(base.connection, joins) @alias_tracker.aliased_name_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1 tree = self.class.make_tree associations @join_root = JoinBase.new base, build(tree, base) -- cgit v1.2.3 From 5f295aebdbb15e2000cef5c9b8a2e28c5cc3db1b Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sat, 15 Feb 2014 10:27:15 +0100 Subject: implements new option :month_format_string for date select helpers [Closes #13618] --- actionview/CHANGELOG.md | 14 ++++++++++ actionview/lib/action_view/helpers/date_helper.rb | 33 ++++++++++++++++------- actionview/test/template/date_helper_test.rb | 10 +++++++ 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 30dbc20f18..a0f298a6b1 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,17 @@ +* Date select helpers accept a format string for the months selector via the + new option `:month_format_string`. + + When rendered, the format string gets passed keys `:number` (integer), and + `:name` (string), in order to be able to interpolate them as in + + '%{name} (%02d)' + + for example. + + This option is motivated by #13618. + + *Xavier Noria* + * Added `config.action_view.raise_on_missing_translations` to define whether an error should be raised for missing translations. diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index 3d091c4a00..698f0ca31c 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -169,6 +169,9 @@ module ActionView # "2 - February" instead of "February"). # * :use_month_names - Set to an array with 12 month names if you want to customize month names. # Note: You can also use Rails' i18n functionality for this. + # * :month_format_string - Set to a format string. The string gets passed keys +:number+ (integer) + # and +:name+ (string). A format string would be something like "%{name} (%02d)" for example. + # See Kernel.sprintf for documentation on format sequences. # * :date_separator - Specifies a string to separate the date fields. Default is "" (i.e. nothing). # * :start_year - Set the start year for the year select. Default is Date.today.year - 5if # you are creating new record. While editing existing record, :start_year defaults to @@ -850,24 +853,36 @@ module ActionView I18n.translate(key, :locale => @options[:locale]) end - # Lookup month name for number. - # month_name(1) => "January" + # Looks up month names by number (1-based): # - # If :use_month_numbers option is passed - # month_name(1) => 1 + # month_name(1) # => "January" # - # If :use_two_month_numbers option is passed - # month_name(1) => '01' + # If the :use_month_numbers option is passed: # - # If :add_month_numbers option is passed - # month_name(1) => "1 - January" + # month_name(1) # => 1 + # + # If the :use_two_month_numbers option is passed: + # + # month_name(1) # => '01' + # + # If the :add_month_numbers option is passed: + # + # month_name(1) # => "1 - January" + # + # If the :month_format_string option is passed: + # + # month_name(1) # => "January (01)" + # + # depending on the format string. def month_name(number) if @options[:use_month_numbers] number elsif @options[:use_two_digit_numbers] - sprintf "%02d", number + '%02d' % number elsif @options[:add_month_numbers] "#{number} - #{month_names[number]}" + elsif format_string = @options[:month_format_string] + format_string % {number: number, name: month_names[number]} else month_names[number] end diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb index 5f09aef249..6f77c3c99d 100644 --- a/actionview/test/template/date_helper_test.rb +++ b/actionview/test/template/date_helper_test.rb @@ -326,6 +326,16 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, select_month(8, :add_month_numbers => true) end + def test_select_month_with_format_string + expected = %(\n" + + format_string = '%{name} (%02d)' + assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), :month_format_string => format_string) + assert_dom_equal expected, select_month(8, :month_format_string => format_string) + end + def test_select_month_with_numbers_and_names_with_abbv expected = %(
Title
<%= post.title %><%= post.text %><%= link_to 'Show', post_path(post) %><%= link_to 'Edit', edit_post_path(post) %><%= link_to 'Destroy', post_path(post), + <%= article.title %><%= article.text %><%= link_to 'Show', article_path(article) %><%= link_to 'Edit', edit_article_path(article) %><%= link_to 'Destroy', article_path(article), method: :delete, data: { confirm: 'Are you sure?' } %>