From 8ae7008ac3d77cca42350007e99efe672e7b8e40 Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Tue, 19 Jul 2011 17:02:22 +0200 Subject: Fix fragment cache helper regression on cache miss introduced with 03d01ec7. Contains following patches cherry-picked from @lhahne's 3-0-stable branch: * Added tests for the output_buffer returned by CacheHelper (c476a6b) The output_buffer returned by CacheHelper should be html_safe if the original buffer is html_safe. * made sure that the possible new output_buffer created by CacheHelper is of the same type as the original (39a4f67) --- actionpack/lib/action_view/helpers/cache_helper.rb | 2 +- actionpack/test/controller/caching_test.rb | 49 ++++++++++++++++++++++ .../functional_caching/fragment_cached.html.erb | 1 + 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index f81ce3e31c..850dd5f448 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -54,7 +54,7 @@ module ActionView output_safe = output_buffer.html_safe? fragment = output_buffer.slice!(pos..-1) if output_safe - self.output_buffer = output_buffer.html_safe + self.output_buffer = output_buffer.class.new(output_buffer) end controller.write_fragment(name, fragment, options) end diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index da3314fe6d..f5a56880e6 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -745,6 +745,7 @@ class FunctionalFragmentCachingTest < ActionController::TestCase expected_body = <<-CACHED Hello This bit's fragment cached +Ciao CACHED assert_equal expected_body, @response.body @@ -786,3 +787,51 @@ CACHED assert_equal "

Builder

\n", @store.read('views/test.host/functional_caching/formatted_fragment_cached') end end + +class CacheHelperOutputBufferTest < ActionController::TestCase + + class MockController + def read_fragment(name, options) + return false + end + + def write_fragment(name, fragment, options) + fragment + end + end + + def setup + super + end + + def test_output_buffer + output_buffer = ActionView::OutputBuffer.new + controller = MockController.new + cache_helper = Object.new + cache_helper.extend(ActionView::Helpers::CacheHelper) + cache_helper.expects(:controller).returns(controller).at_least(0) + cache_helper.expects(:output_buffer).returns(output_buffer).at_least(0) + # if the output_buffer is changed, the new one should be html_safe and of the same type + cache_helper.expects(:output_buffer=).with(responds_with(:html_safe?, true)).with(instance_of(output_buffer.class)).at_least(0) + + assert_nothing_raised do + cache_helper.send :fragment_for, 'Test fragment name', 'Test fragment', &Proc.new{ nil } + end + end + + def test_safe_buffer + output_buffer = ActiveSupport::SafeBuffer.new + controller = MockController.new + cache_helper = Object.new + cache_helper.extend(ActionView::Helpers::CacheHelper) + cache_helper.expects(:controller).returns(controller).at_least(0) + cache_helper.expects(:output_buffer).returns(output_buffer).at_least(0) + # if the output_buffer is changed, the new one should be html_safe and of the same type + cache_helper.expects(:output_buffer=).with(responds_with(:html_safe?, true)).with(instance_of(output_buffer.class)).at_least(0) + + assert_nothing_raised do + cache_helper.send :fragment_for, 'Test fragment name', 'Test fragment', &Proc.new{ nil } + end + end + +end diff --git a/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb b/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb index c479adb897..fa5e6bd318 100644 --- a/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb +++ b/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb @@ -1,2 +1,3 @@ Hello <%= cache do %>This bit's fragment cached<% end %> +<%= 'Ciao' %> -- cgit v1.2.3 From 4535191c61b496b1a5e9dc57624581753fa71497 Mon Sep 17 00:00:00 2001 From: Ryan Oblak Date: Tue, 27 Sep 2011 22:39:31 -0700 Subject: Modified String#pluralize to take an optional count parameter. --- .../lib/active_support/core_ext/string/inflections.rb | 17 ++++++++++++++--- activesupport/test/core_ext/string_ext_test.rb | 12 ++++++++---- .../source/active_support_core_extensions.textile | 8 ++++++++ 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index c7ceeb9de4..fd91b3cacb 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -9,14 +9,25 @@ require 'active_support/inflector/transliterate' class String # Returns the plural form of the word in the string. # + # If the optional parameter +count+ is specified, + # the singular form will be returned if count == 1. + # For any other value of +count+ the plural will be returned. + # + # ==== Examples # "post".pluralize # => "posts" # "octopus".pluralize # => "octopi" # "sheep".pluralize # => "sheep" # "words".pluralize # => "words" # "the blue mailman".pluralize # => "the blue mailmen" # "CamelOctopus".pluralize # => "CamelOctopi" - def pluralize - ActiveSupport::Inflector.pluralize(self) + # "apple".pluralize(1) # => "apple" + # "apple".pluralize(2) # => "apples" + def pluralize(count = nil) + if count == 1 + self + else + ActiveSupport::Inflector.pluralize(self) + end end # The reverse of +pluralize+, returns the singular form of a word in a string. @@ -42,7 +53,7 @@ class String def constantize ActiveSupport::Inflector.constantize(self) end - + # +safe_constantize+ tries to find a declared constant with the name specified # in the string. It returns nil when the name is not in CamelCase # or is not initialized. See ActiveSupport::Inflector.safe_constantize diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 5c1dddaf96..4d876954cf 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -20,7 +20,7 @@ end class StringInflectionsTest < Test::Unit::TestCase include InflectorTestCases include ConstantizeTestCases - + def test_erb_escape string = [192, 60].pack('CC') expected = 192.chr + "<" @@ -64,6 +64,10 @@ class StringInflectionsTest < Test::Unit::TestCase end assert_equal("plurals", "plurals".pluralize) + + assert_equal("blargles", "blargle".pluralize(0)) + assert_equal("blargle", "blargle".pluralize(1)) + assert_equal("blargles", "blargle".pluralize(2)) end def test_singularize @@ -301,13 +305,13 @@ class StringInflectionsTest < Test::Unit::TestCase "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8').truncate(10) end end - + def test_constantize run_constantize_tests_on do |string| string.constantize end end - + def test_safe_constantize run_safe_constantize_tests_on do |string| string.safe_constantize @@ -381,7 +385,7 @@ class OutputSafetyTest < ActiveSupport::TestCase test "A fixnum is safe by default" do assert 5.html_safe? end - + test "a float is safe by default" do assert 5.7.html_safe? end diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 5aee001545..153a897b1c 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -1426,6 +1426,14 @@ The method +pluralize+ returns the plural of its receiver: As the previous example shows, Active Support knows some irregular plurals and uncountable nouns. Built-in rules can be extended in +config/initializers/inflections.rb+. That file is generated by the +rails+ command and has instructions in comments. ++pluralize+ can also take an optional +count+ parameter. If count == 1 the singular form will be returned. For any other value of +count+ the plural form will be returned: + + +"dude".pluralize(0) # => "dudes" +"dude".pluralize(1) # => "dude" +"dude".pluralize(2) # => "dudes" + + Active Record uses this method to compute the default table name that corresponds to a model: -- cgit v1.2.3 From 78de4fcd050b5f2f67a1bf974ba6960b4b017443 Mon Sep 17 00:00:00 2001 From: Dmitriy Kiriyenko Date: Thu, 4 Aug 2011 15:44:08 +0300 Subject: When running "rake db:drop" also drop test database in development environment. --- .../lib/active_record/railties/databases.rake | 26 +++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index b3316fd1a2..1009af850c 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -37,11 +37,7 @@ db_namespace = namespace :db do desc 'Create the database from config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)' task :create => :load_config do - # Make the test database at the same time as the development one, if it exists - if Rails.env.development? && ActiveRecord::Base.configurations['test'] - create_database(ActiveRecord::Base.configurations['test']) - end - create_database(ActiveRecord::Base.configurations[Rails.env]) + configs_for_environment.each { |config| create_database(config) } end def mysql_creation_options(config) @@ -138,12 +134,7 @@ db_namespace = namespace :db do desc 'Drops the database for the current Rails.env (use db:drop:all to drop all databases)' task :drop => :load_config do - config = ActiveRecord::Base.configurations[Rails.env || 'development'] - begin - drop_database(config) - rescue Exception => e - $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}" - end + configs_for_environment.each { |config| drop_database_and_rescue(config) } end def local_database?(config, &block) @@ -548,6 +539,19 @@ def drop_database(config) end end +def drop_database_and_rescue(config) + begin + drop_database(config) + rescue Exception => e + $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}" + end +end + +def configs_for_environment + environments = [Rails.env, ("test" if Rails.env.development?)] + ActiveRecord::Base.configurations.values_at(*environments).compact +end + def session_table_name ActiveRecord::SessionStore::Session.table_name end -- cgit v1.2.3 From 419d4c09dfcc5cbd898ce52d779603c6a29ac3bc Mon Sep 17 00:00:00 2001 From: Alexey Vakhov Date: Tue, 11 Oct 2011 12:31:27 +0400 Subject: Add ActionController#head example --- actionpack/lib/action_controller/metal/head.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index 8abcad55a2..a618533d09 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -9,6 +9,8 @@ module ActionController # # head :created, :location => person_path(@person) # + # head :created, :location => @person + # # It can also be used to return exceptional conditions: # # return head(:method_not_allowed) unless request.post? -- cgit v1.2.3 From 1f62c6d09f44d080a508e18ec2c44c0adb61b76e Mon Sep 17 00:00:00 2001 From: RAHUL CHAUDHARI Date: Tue, 11 Oct 2011 17:17:20 +0530 Subject: Fixed typos in active_support_core_extensions.textile --- railties/guides/source/active_support_core_extensions.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index addf5f78be..ecc25c4f1c 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -1760,7 +1760,7 @@ h4(#string-conversions). Conversions h5. +ord+ -Ruby 1.9 defines +ord+ to be the codepoint of the first character of the receiver. Active Support backports +ord+ for single-byte encondings like ASCII or ISO-8859-1 in Ruby 1.8: +Ruby 1.9 defines +ord+ to be the codepoint of the first character of the receiver. Active Support backports +ord+ for single-byte encodings like ASCII or ISO-8859-1 in Ruby 1.8: "a".ord # => 97 @@ -1774,7 +1774,7 @@ In Ruby 1.8 +ord+ doesn't work in general in UTF8 strings, use the multibyte sup "à".mb_chars.ord # => 224, in UTF8 -Note that the 224 is different in both examples. In ISO-8859-1 "à" is represented as a single byte, 224. Its single-character representattion in UTF8 has two bytes, namely 195 and 160, but its Unicode codepoint is 224. If we call +ord+ on the UTF8 string "à" the return value will be 195 in Ruby 1.8. That is not an error, because UTF8 is unsupported, the call itself would be bogus. +Note that the 224 is different in both examples. In ISO-8859-1 "à" is represented as a single byte, 224. Its single-character representation in UTF8 has two bytes, namely 195 and 160, but its Unicode codepoint is 224. If we call +ord+ on the UTF8 string "à" the return value will be 195 in Ruby 1.8. That is not an error, because UTF8 is unsupported, the call itself would be bogus. INFO: +ord+ is equivalent to +getbyte(0)+. -- cgit v1.2.3 From c317419359d9d868c4e8c12a2ba2e2f541eef3d6 Mon Sep 17 00:00:00 2001 From: Evgeniy Dolzhenko Date: Tue, 11 Oct 2011 14:13:08 +0100 Subject: Use .add instead of << to add errors --- activemodel/lib/active_model/validations/validates.rb | 4 ++-- activemodel/lib/active_model/validations/with.rb | 2 +- activemodel/lib/active_model/validator.rb | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb index b85c2453fb..fbceb81e8f 100644 --- a/activemodel/lib/active_model/validations/validates.rb +++ b/activemodel/lib/active_model/validations/validates.rb @@ -27,7 +27,7 @@ module ActiveModel # # class EmailValidator < ActiveModel::EachValidator # def validate_each(record, attribute, value) - # record.errors[attribute] << (options[:message] || "is not an email") unless + # record.errors.add attribute, (options[:message] || "is not an email") unless # value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i # end # end @@ -48,7 +48,7 @@ module ActiveModel # # class TitleValidator < ActiveModel::EachValidator # def validate_each(record, attribute, value) - # record.errors[attribute] << "must start with 'the'" unless value =~ /\Athe/i + # record.errors.add attribute, "must start with 'the'" unless value =~ /\Athe/i # end # end # diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index 83aae206a6..93a340eb39 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -32,7 +32,7 @@ module ActiveModel # class MyValidator < ActiveModel::Validator # def validate(record) # if some_complex_logic - # record.errors[:base] << "This record is invalid" + # record.errors.add :base, "This record is invalid" # end # end # diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 5304743389..0e444738ba 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -48,8 +48,8 @@ module ActiveModel #:nodoc: # # class MyValidator < ActiveModel::Validator # def validate(record) - # record.errors[:base] << "This is some custom error message" - # record.errors[:first_name] << "This is some complex validation" + # record.errors.add :base, "This is some custom error message" + # record.errors.add :first_name, "This is some complex validation" # # etc... # end # end @@ -68,7 +68,7 @@ module ActiveModel #:nodoc: # # class TitleValidator < ActiveModel::EachValidator # def validate_each(record, attribute, value) - # record.errors[attribute] << 'must be Mr. Mrs. or Dr.' unless value.in?(['Mr.', 'Mrs.', 'Dr.']) + # record.errors.add attribute, 'must be Mr. Mrs. or Dr.' unless value.in?(['Mr.', 'Mrs.', 'Dr.']) # end # end # -- cgit v1.2.3 From 3ca269674f66558f6ced4d956ecae4959dacd596 Mon Sep 17 00:00:00 2001 From: mhutchin Date: Tue, 11 Oct 2011 06:27:01 -0700 Subject: Heavy copy editing of the find_each and find_in_batches section --- .../guides/source/active_record_querying.textile | 60 ++++++++++++---------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 81d73c4ccc..6ac9197f44 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -82,7 +82,7 @@ Active Record provides five different ways of retrieving a single object. h5. Using a Primary Key -Using Model.find(primary_key), you can retrieve the object corresponding to the supplied _primary key_ and matching the supplied options (if any). For example: +Using Model.find(primary_key), you can retrieve the object corresponding to the specified _primary key_ that matches any supplied options. For example: # Find the client with primary key (id) 10. @@ -170,7 +170,7 @@ h4. Retrieving Multiple Objects h5. Using Multiple Primary Keys -Model.find(array_of_primary_key) also accepts an array of _primary keys_. An array of all the matching records for the supplied _primary keys_ is returned. For example: +Model.find(array_of_primary_key) accepts an array of _primary keys_, returning an array containing all of the matching records for the supplied _primary keys_. For example: # Find the clients with primary keys 1 and 10. @@ -188,24 +188,26 @@ WARNING: Model.find(array_of_primary_key) will raise an +ActiveRecord:: h4. Retrieving Multiple Objects in Batches -Sometimes you need to iterate over a large set of records. For example to send a newsletter to all users, to export some data, etc. +We often need to iterate over a large set of records, as when we send a newsletter to a large set of users, or when we export data. -The following may seem very straightforward, at first: +This may appear straightforward: -# Very inefficient when users table has thousands of rows. +# This is very inefficient when the users table has thousands of rows. User.all.each do |user| NewsLetter.weekly_deliver(user) end -But if the total number of rows in the table is very large, the above approach may vary from being underperforming to being plain impossible. +But this approach becomes increasingly impractical as the table size increases, since +User.all.each+ instructs Active Record to fetch _the entire table_ in a single pass, build a model object per row, and then keep the entire array of model objects in memory. Indeed, if we have a large number of records, the entire collection may exceed the amount of memory available. -This is because +User.all.each+ makes Active Record fetch _the entire table_, build a model object per row, and keep the entire array of model objects in memory. Sometimes that is just too many objects and requires too much memory. +Rails provides two methods that address this problem by dividing records into memory-friendly batches for processing. The first method, +find_each+, retrieves a batch of records and then yields _each_ record to the block individually as a model. The second method, +find_in_batches+, retrieves a batch of records and then yields _the entire batch_ to the block as an array of models. + +TIP: The +find_each+ and +find_in_batches+ methods are intended for use in the batch processing of a large number of records that wouldn't fit in memory all at once. If you just need to loop over a thousand records the regular find methods are the preferred option. h5. +find_each+ -To efficiently iterate over a large table, Active Record provides a batch finder method called +find_each+: +The +find_each+ method retrieves a batch of records and then yields _each_ record to the block individually as a model. In the following example, +find_each+ will retrieve 1000 records (the current default for both +find_each+ and +find_in_batches+) and then yield each record individually to the block as a model. This process is repeated until all of the records have been processed: User.find_each do |user| @@ -213,11 +215,15 @@ User.find_each do |user| end -*Configuring the batch size* +h6. Options for +find_each+ + +The +find_each+ method accepts most of the options allowed by the regular +find+ method, except for +:order+ and +:limit+, which are reserved for internal use by +find_each+. -Behind the scenes, +find_each+ fetches rows in batches of 1000 and yields them one by one. The size of the underlying batches is configurable via the +:batch_size+ option. +Two additional options, +:batch_size+ and +:start+, are available as well. -To fetch +User+ records in batches of 5000, we can use: +*+:batch_size+* + +The +:batch_size+ option allows you to specify the number of records to be retrieved in each batch, before being passed individually to the block. For example, to retrieve records in batches of 5000: User.find_each(:batch_size => 5000) do |user| @@ -225,37 +231,39 @@ User.find_each(:batch_size => 5000) do |user| end -*Starting batch find from a specific primary key* +*+:start+* -Records are fetched in ascending order of the primary key, which must be an integer. The +:start+ option allows you to configure the first ID of the sequence whenever the lowest ID is not the one you need. This may be useful, for example, to be able to resume an interrupted batch process, provided it saves the last processed ID as a checkpoint. +By default, records are fetched in ascending order of the primary key, which must be an integer. The +:start+ option allows you to configure the first ID of the sequence whenever the lowest ID is not the one you need. This would be useful, for example, if you wanted to resume an interrupted batch process, provided you saved the last processed ID as a checkpoint. -To send newsletters only to users with the primary key starting from 2000, we can use: +For example, to send newsletters only to users with the primary key starting from 2000, and to retrieve them in batches of 5000: -User.find_each(:batch_size => 5000, :start => 2000) do |user| +User.find_each(:start => 2000, :batch_size => 5000) do |user| NewsLetter.weekly_deliver(user) end -*Additional options* +Another example would be if you wanted multiple workers handling the same processing queue. You could have each worker handle 10000 records by setting the appropriate :start option on each worker. -+find_each+ accepts the same options as the regular +find+ method. However, +:order+ and +:limit+ are needed internally and hence not allowed to be passed explicitly. +NOTE: The +:include+ option allows you to name associations that should be loaded alongside with the models. h5. +find_in_batches+ -You can also work by chunks instead of row by row using +find_in_batches+. This method is analogous to +find_each+, but it yields arrays of models instead: +The +find_in_batches+ method is similar to +find_each+, since both retrieve batches of records. The difference is that +find_in_batches+ yields _batches_ to the block as an array of models, instead of individually. The following example will yield to the supplied block an array of up to 1000 invoices at a time, with the final block containing any remaining invoices: -# Works in chunks of 1000 invoices at a time. +# Give add_invoices an array of 1000 invoices at a time Invoice.find_in_batches(:include => :invoice_lines) do |invoices| export.add_invoices(invoices) end -The above will each time yield to the supplied block an array of 1000 invoices (or the remaining invoices, if less than 1000). - NOTE: The +:include+ option allows you to name associations that should be loaded alongside with the models. +h6. Options for +find_in_batches+ + +The +find_in_batches+ method accepts the same +:batch_size+ and +:start+ options as +find_each+, as well as most of the options allowed by the regular +find+ method, except for +:order+ and +:limit+, which are reserved for internal use by +find_in_batches+. + h3. Conditions The +where+ method allows you to specify conditions to limit the records returned, representing the +WHERE+-part of the SQL statement. Conditions can either be specified as a string, array, or hash. @@ -268,7 +276,7 @@ WARNING: Building your own conditions as pure strings can leave you vulnerable t h4. Array Conditions -Now what if that number could vary, say as an argument from somewhere? The find then becomes something like: +Now what if that number could vary, say as an argument from somewhere? The find would then take the form: Client.where("orders_count = ?", params[:orders]) @@ -276,7 +284,7 @@ Client.where("orders_count = ?", params[:orders]) Active Record will go through the first element in the conditions value and any additional elements will replace the question marks +(?)+ in the first element. -Or if you want to specify two conditions, you can do it like: +If you want to specify multiple conditions: Client.where("orders_count = ? AND locked = ?", params[:orders], false) @@ -284,19 +292,19 @@ Client.where("orders_count = ? AND locked = ?", params[:orders], false) In this example, the first question mark will be replaced with the value in +params[:orders]+ and the second will be replaced with the SQL representation of +false+, which depends on the adapter. -The reason for doing code like: +This code is highly preferable: Client.where("orders_count = ?", params[:orders]) -instead of: +to this code: Client.where("orders_count = #{params[:orders]}") -is because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database *as-is*. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string. +because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database *as-is*. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string. TIP: For more information on the dangers of SQL injection, see the "Ruby on Rails Security Guide":security.html#sql-injection. -- cgit v1.2.3 From c4e29541724433fd1c96715bde829209066ca205 Mon Sep 17 00:00:00 2001 From: Stephen Pike Date: Tue, 11 Oct 2011 09:32:23 -0400 Subject: Runtime conditions for associations should use procs The association guide previously recommended using a trick with single quote delaying of string interpolation in order to handle setting association conditions that would be evaluated at runtime. Using a proc is the new way as this no longer works. --- railties/guides/source/association_basics.textile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile index 479a3c1e30..6829eb8ef4 100644 --- a/railties/guides/source/association_basics.textile +++ b/railties/guides/source/association_basics.textile @@ -1229,17 +1229,15 @@ end If you use a hash-style +:conditions+ option, then record creation via this association will be automatically scoped using the hash. In this case, using +@customer.confirmed_orders.create+ or +@customer.confirmed_orders.build+ will create orders where the confirmed column has the value +true+. -If you need to evaluate conditions dynamically at runtime, you could use string interpolation in single quotes: +If you need to evaluate conditions dynamically at runtime, use a proc: class Customer < ActiveRecord::Base has_many :latest_orders, :class_name => "Order", - :conditions => 'orders.created_at > #{10.hours.ago.to_s(:db).inspect}' + :conditions => proc { "orders.created_at > #{10.hours.ago.to_s(:db).inspect}" } end -Be sure to use single quotes. - h6(#has_many-counter_sql). +:counter_sql+ Normally Rails automatically generates the proper SQL to count the association members. With the +:counter_sql+ option, you can specify a complete SQL statement to count them yourself. -- cgit v1.2.3 From 4a025f0080d05f6c27518a84cde0b93fa3821345 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Wed, 12 Oct 2011 15:44:19 +0900 Subject: status is a number in Rails 3 --- actionpack/lib/action_controller/metal/data_streaming.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb index 5e077dd7bd..0670a58d97 100644 --- a/actionpack/lib/action_controller/metal/data_streaming.rb +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -34,7 +34,7 @@ module ActionController #:nodoc: # If no content type is registered for the extension, default type 'application/octet-stream' will be used. # * :disposition - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). - # * :status - specifies the status code to send with the response. Defaults to '200 OK'. + # * :status - specifies the status code to send with the response. Defaults to 200. # * :url_based_filename - set to +true+ if you want the browser guess the filename from # the URL, which is necessary for i18n filenames on certain browsers # (setting :filename overrides this option). @@ -92,7 +92,7 @@ module ActionController #:nodoc: # If no content type is registered for the extension, default type 'application/octet-stream' will be used. # * :disposition - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). - # * :status - specifies the status code to send with the response. Defaults to '200 OK'. + # * :status - specifies the status code to send with the response. Defaults to 200. # # Generic data download: # -- cgit v1.2.3 From 532e6d59b50b241d51d2a423bab5962d9cf7b0d4 Mon Sep 17 00:00:00 2001 From: Craig Monson Date: Wed, 12 Oct 2011 10:34:27 -0400 Subject: quoting 'and' to make it more distinct. --- railties/guides/source/active_record_querying.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 6ac9197f44..2e1f89cb78 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -1024,7 +1024,7 @@ You can also use +find_last_by_*+ methods which will find the last record matchi You can specify an exclamation point (!) on the end of the dynamic finders to get them to raise an +ActiveRecord::RecordNotFound+ error if they do not return any records, like +Client.find_by_name!("Ryan")+ -If you want to find both by name and locked, you can chain these finders together by simply typing +and+ between the fields. For example, +Client.find_by_first_name_and_locked("Ryan", true)+. +If you want to find both by name and locked, you can chain these finders together by simply typing "+and+" between the fields. For example, +Client.find_by_first_name_and_locked("Ryan", true)+. WARNING: Up to and including Rails 3.1, when the number of arguments passed to a dynamic finder method is lesser than the number of fields, say Client.find_by_name_and_locked("Ryan"), the behavior is to pass +nil+ as the missing argument. This is *unintentional* and this behavior will be changed in Rails 3.2 to throw an +ArgumentError+. -- cgit v1.2.3 From e552fe16d43d89c37b85cf3d5e8c8e5afe150529 Mon Sep 17 00:00:00 2001 From: Steve Bourne Date: Wed, 12 Oct 2011 10:46:08 -0700 Subject: change activerecord query conditions example to avoid 'type' as column name 'Type' is a reserved column for STI. Changed conditions example to avoid using that column name as an example. The example isn't STI-related (and mentioning STI here is needless clutter), so changing to avoid accidentally encouraging users to use 'type' as a column name for other purposes. --- activerecord/lib/active_record/base.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 78159d13d4..7a9654ffd8 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -115,8 +115,8 @@ module ActiveRecord #:nodoc: # When joining tables, nested hashes or keys written in the form 'table_name.column_name' # can be used to qualify the table name of a particular condition. For instance: # - # Student.joins(:schools).where(:schools => { :type => 'public' }) - # Student.joins(:schools).where('schools.type' => 'public' ) + # Student.joins(:schools).where(:schools => { :category => 'public' }) + # Student.joins(:schools).where('schools.category' => 'public' ) # # == Overwriting default accessors # -- cgit v1.2.3 From e6b2e7fbeab495a9ce0533d3f778cca9cb268597 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Thu, 13 Oct 2011 02:17:01 +0530 Subject: refactor guide example --- railties/guides/source/security.textile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile index 0f100e0adf..8837e06de5 100644 --- a/railties/guides/source/security.textile +++ b/railties/guides/source/security.textile @@ -157,9 +157,9 @@ One possibility is to set the expiry time-stamp of the cookie with the session i class Session < ActiveRecord::Base def self.sweep(time = 1.hour) - time = time.split.inject { |count, unit| - count.to_i.send(unit) - } if time.is_a?(String) + if time.is_a?(String) + time = time.split.inject { |count, unit| count.to_i.send(unit) } + end delete_all "updated_at < '#{time.ago.to_s(:db)}'" end -- cgit v1.2.3 From 20728f551c2a429fc7c29c6ac17b3f44b57526b9 Mon Sep 17 00:00:00 2001 From: Mac Martine Date: Wed, 12 Oct 2011 17:06:44 -0700 Subject: remove user-specified delimiter from start when no area code is present (in number_to_phone) --- actionpack/lib/action_view/helpers/number_helper.rb | 2 +- actionpack/test/template/number_helper_test.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index ec6c2c8db3..7031694af4 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -69,7 +69,7 @@ module ActionView number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3") else number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3") - number.slice!(0, 1) if number.starts_with?('-') + number.slice!(0, 1) if number.starts_with?(delimiter) && !delimiter.blank? end str = [] diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 0e3475d98b..8d679aac1d 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -27,6 +27,7 @@ class NumberHelperTest < ActionView::TestCase assert_equal("800 555 1212", number_to_phone(8005551212, {:delimiter => " "})) assert_equal("(800) 555-1212 x 123", number_to_phone(8005551212, {:area_code => true, :extension => 123})) assert_equal("800-555-1212", number_to_phone(8005551212, :extension => " ")) + assert_equal("555.1212", number_to_phone(5551212, :delimiter => '.')) assert_equal("800-555-1212", number_to_phone("8005551212")) assert_equal("+1-800-555-1212", number_to_phone(8005551212, :country_code => 1)) assert_equal("+18005551212", number_to_phone(8005551212, :country_code => 1, :delimiter => '')) -- cgit v1.2.3 From e537a3fcb1fd16d169889dfbca13eece9cc3bdf1 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Thu, 13 Oct 2011 18:04:55 +1100 Subject: [engines guide] change comments resource generation Totally didn't like where this section was going, so scrapped it. Scaffold is great for some things (lol) but nested resources is definitely not one of them. Best to walk people through how to do it right, as they would in the real world --- railties/guides/source/engines.textile | 87 +++++++++++++--------------------- 1 file changed, 33 insertions(+), 54 deletions(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index db2db05e57..e22f3a7ae6 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -201,83 +201,62 @@ If you'd rather play around in the console, +rails console+ will also work just h4. Generating a comments resource -Now that the engine has the ability to create new blog posts, it only makes sense to add commenting functionality as well. +Now that the engine has the ability to create new blog posts, it only makes sense to add commenting functionality as well. To do get this, you'll need to generate a comment model, a comment controller and then modify the posts scaffold to display comments and allow people to create new ones. -To do this, you can run the scaffold generator this time and tell it to generate a +Comment+ resource instead, with the table having two columns: a +post_id+ integer and +text+ text column. +Run the model generator and tell it to generate a +Comment+ model, with the related table having two columns: a +post_id+ integer and +text+ text column. -$ rails generate scaffold Comment post_id:integer text:text +$ rails generate model Comment post_id:integer text:text +invoke active_record +create db/migrate/[timestamp]_create_blorgh_comments.rb +create app/models/blorgh/comment.rb +invoke test_unit +create test/unit/blorgh/comment_test.rb +create test/fixtures/blorgh/comments.yml -This generator call will generate almost the same files as it did the first time we called it for generating the +Post+ resource, but this time the files will be called things such as +app/controllers/blorgh/comments_controller.rb+ and +app/models/blorgh/comment.rb+. - -There's a few things wrong with how this generator has worked. It would be better if the comments resource was nested inside the posts resource in the routes, and if the controller created new comment entries inside a post. These are two very easy things to fix up. - -The +resources+ line from this generator is placed into the +config/routes.rb+ by the generator, but you're going to want to have comments nested underneath a post, and so it's a good idea to change these lines in the +config/routes.rb+ file: - - -Blorgh::Engine.routes.draw do - resources :comments - - resources :posts - -end - - -Into these: - - - Blorgh::Engine.routes.draw do - resources :posts do - resources :comments - end - end - +This generator call will generate just the necessary model files it needs, namespacing the files under a +blorgh+ directory and creating a model class called +Blorgh::Comment+. -That fixes the routes. For the controller, it's just as easy. When a request is made to this controller, it will be in the form of +post/:post_id/comments+. In order to find the comments that are being requested, the post is going to need to be fetched using something such as: +To show the comments on a post, edit +app/views/posts/show.html.erb+ and add this line before the "Edit" link: - -post = Post.find(params[:id]) - + +<%= render @post.comments %> + -Then to get the comments for this post it would be as simple as: +This line will require there to be a +has_many+ association for comments defined on the +Blorgh::Post+ model, which there isn't right now. To define one, open +app/models/blorgh/post.rb+ and add this line into the model: -post.comments +has_many :comments -Alternatively, the query to fetch the comments in actions such as the +index+ action would need to be changed from +Comment.all+ into +Comment.find_all_by_post_id(params[:post_id])+. However, the first way is cleaner and so it should be done that way. - -To fetch the post in the controller, add a +before_filter+ into the controller's class definition like this: +Turning the model into this: module Blorgh - class CommentsController < ApplicationController - before_filter :load_post - ... + class Post < ActiveRecord::Base + has_many :comments end end -This +before_filter+ will call the +load_post+ method before every request that comes into this controller. This method should be defined as a +private+ method after all the actions in the controller: - - -module Blorgh - class CommentsController < ApplicationController - before_filter :load_post +Because the +has_many+ is defined inside a class that is inside the +Blorgh+ module, Rails will know that you want to use the +Blorgh::Comment+ model for these objects. - # actions go here +Next, there needs to be a form so that comments can be created on a post. To add this, put this line underneath the call to +render @post.comments+ in +app/views/blorgh/posts/show.html.erb+: - private + +<%= render "comments/form" %> + - def load_post - @post = Post.find(params[:post_id]) - end - end -end - +Next, the partial that this line will render needs to exist. Create a new directory at +app/views/blorgh/comments+ and in it a new file called +_form.html.erb+ with this content to create the required partial: -With the post being loaded, the queries in the controller need to be altered in order to query within the scope of the relative post. All occurrences of +Comment+ in this controller should now be replaced with +@post.comments+ so that the queries are correctly scoped. + +<%= form_for [@post, @post.comments.build] do |f| %> +

+ <%= f.label :text %>
+ <%= f.text_area :text %> +

+<% end %> +
h3. Hooking into application -- cgit v1.2.3 From 66b2dc059f6c6649731e762b28b5136bf053daac Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Thu, 13 Oct 2011 18:05:33 +1100 Subject: [engines guide] don't show actual timestamp in migration generation --- railties/guides/source/engines.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index e22f3a7ae6..2366de33d7 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -107,7 +107,7 @@ The first thing to generate for a blog engine is the +Post+ model and related co $ rails generate scaffold post title:string text:text invoke active_record -create db/migrate/20111006201642_create_blorgh_posts.rb +create db/migrate/[timestamp]_create_blorgh_posts.rb create app/models/blorgh/post.rb invoke test_unit create test/unit/blorgh/post_test.rb -- cgit v1.2.3 From b7cccae71e4c42415f8c0dab7b083ed7db54c0c1 Mon Sep 17 00:00:00 2001 From: Diego Carrion Date: Thu, 13 Oct 2011 16:27:10 -0300 Subject: improved ActiveResource's .element_path and .new_element_path methods documentation by specifing how .site should be declared in order to use prefix_options --- activeresource/lib/active_resource/base.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 03c4cc5b9e..fa5c4470eb 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -637,6 +637,10 @@ module ActiveResource # Post.element_path(1) # # => /posts/1.json # + # class Comment < ActiveResource::Base + # self.site = "http://37s.sunrise.i/posts/:post_id/" + # end + # # Comment.element_path(1, :post_id => 5) # # => /posts/5/comments/1.json # @@ -663,6 +667,10 @@ module ActiveResource # Post.new_element_path # # => /posts/new.json # + # class Comment < ActiveResource::Base + # self.site = "http://37s.sunrise.i/posts/:post_id/" + # end + # # Comment.collection_path(:post_id => 5) # # => /posts/5/comments/new.json def new_element_path(prefix_options = {}) -- cgit v1.2.3 From d32a90b9b77d3f7d8098ee6ddb5bf62cb8da0ed7 Mon Sep 17 00:00:00 2001 From: "Suraj N. Kurapati" Date: Thu, 13 Oct 2011 10:16:50 -0700 Subject: fix inconsistent alignment in Gemfile generator --- railties/lib/rails/generators/app_base.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 294563ad06..134d86fab0 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -150,7 +150,7 @@ module Rails gem 'rails', '#{Rails::VERSION::STRING}' # Bundle edge Rails instead: - # gem 'rails', :git => 'git://github.com/rails/rails.git' + # gem 'rails', :git => 'git://github.com/rails/rails.git' GEMFILE end end @@ -158,11 +158,11 @@ module Rails def gem_for_database # %w( mysql oracle postgresql sqlite3 frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql ) case options[:database] - when "oracle" then "ruby-oci8" - when "postgresql" then "pg" - when "frontbase" then "ruby-frontbase" - when "mysql" then "mysql2" - when "sqlserver" then "activerecord-sqlserver-adapter" + when "oracle" then "ruby-oci8" + when "postgresql" then "pg" + when "frontbase" then "ruby-frontbase" + when "mysql" then "mysql2" + when "sqlserver" then "activerecord-sqlserver-adapter" when "jdbcmysql" then "activerecord-jdbcmysql-adapter" when "jdbcsqlite3" then "activerecord-jdbcsqlite3-adapter" when "jdbcpostgresql" then "activerecord-jdbcpostgresql-adapter" -- cgit v1.2.3 From 0a9b6de4d255654a769bc5e874730a282c1ee370 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Thu, 13 Oct 2011 15:54:53 -0700 Subject: Tests gotta run in 1.8 too --- activerecord/test/models/admin/user.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb index 275a03c344..c12c88e195 100644 --- a/activerecord/test/models/admin/user.rb +++ b/activerecord/test/models/admin/user.rb @@ -1,4 +1,4 @@ class Admin::User < ActiveRecord::Base belongs_to :account - store :settings, accessors: [ :color, :homepage ] -end \ No newline at end of file + store :settings, :accessors => [ :color, :homepage ] +end -- cgit v1.2.3 From 8cbe826958f78e5de5723bcbd3c90ea381a79e48 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Thu, 13 Oct 2011 16:11:00 -0700 Subject: Rails 4 is just around the corner. Stuck with 1.8 until then. --- activerecord/test/cases/store_test.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index 3d056d93b6..fb77220875 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -4,9 +4,9 @@ require 'models/admin/user' class StoreTest < ActiveRecord::TestCase setup do - @john = Admin::User.create(name: 'John Doe', color: 'black') + @john = Admin::User.create(:name => 'John Doe', :color => 'black') end - + test "reading store attributes through accessors" do assert_equal 'black', @john.color assert_nil @john.homepage @@ -19,7 +19,7 @@ class StoreTest < ActiveRecord::TestCase assert_equal 'red', @john.color assert_equal '37signals.com', @john.homepage end - + test "accessing attributes not exposed by accessors" do @john.settings[:icecream] = 'graeters' @john.save -- cgit v1.2.3 From dadfa1e34f3ffbc2de98eae88b9715c41f4a1b66 Mon Sep 17 00:00:00 2001 From: PoTe Date: Thu, 13 Oct 2011 22:31:32 -0200 Subject: security reasonS should be plural --- railties/guides/source/action_controller_overview.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile index d8d66302fe..5019d49686 100644 --- a/railties/guides/source/action_controller_overview.textile +++ b/railties/guides/source/action_controller_overview.textile @@ -796,7 +796,7 @@ NOTE: Certain exceptions are only rescuable from the +ApplicationController+ cla h3. Force HTTPS protocol -Sometime you might want to force a particular controller to only be accessible via an HTTPS protocol for security reason. Since Rails 3.1 you can now use +force_ssl+ method in your controller to enforce that: +Sometime you might want to force a particular controller to only be accessible via an HTTPS protocol for security reasons. Since Rails 3.1 you can now use +force_ssl+ method in your controller to enforce that: class DinnerController -- cgit v1.2.3 From 45ced7e1beb0d996f0471e21f0f88efaf2b26124 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Thu, 13 Oct 2011 18:50:23 -0700 Subject: Failing tests for path parameter escaping --- actionpack/test/dispatch/routing_test.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index a71ac1bdc1..a0d2c51a7c 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2528,3 +2528,30 @@ class TestHttpMethods < ActionDispatch::IntegrationTest end end end + +class TestUriPathEscaping < ActionDispatch::IntegrationTest + Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| + app.draw do + match '/:segment' => lambda { |env| + path_params = env['action_dispatch.request.path_parameters'] + [200, { 'Content-Type' => 'text/plain' }, [path_params[:segment]]] + }, :as => :segment + end + end + + include Routes.url_helpers + def app; Routes end + + setup do + @path, @param = '/a%20b%2Fc+d', 'a b/c+d' + end + + test 'escapes generated path parameters' do + assert_equal @path, segment_path(:segment => @param) + end + + test 'unescapes recognized path parameters' do + get @path + assert_equal @param, @response.body + end +end -- cgit v1.2.3 From ec371606640f87289f4821f5f197709dd0ebe6f2 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Thu, 13 Oct 2011 21:40:42 -0700 Subject: Leave escaping up to Journey --- actionpack/lib/action_dispatch/routing/route_set.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index bc956ef216..e7bc431783 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -394,10 +394,9 @@ module ActionDispatch if name == :controller value elsif value.is_a?(Array) - value.map { |v| Journey::Router::Utils.escape_uri(v.to_param) }.join('/') - else - return nil unless param = value.to_param - param.split('/').map { |v| Journey::Router::Utils.escape_uri(v) }.join("/") + value.map { |v| v.to_param }.join('/') + elsif param = value.to_param + param end end -- cgit v1.2.3 From bceec4c3c3c8bb8d0747e0c58fd539f46228c37b Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Thu, 13 Oct 2011 21:41:30 -0700 Subject: / is allowed in URI fragments --- actionpack/lib/action_dispatch/http/url.rb | 2 +- actionpack/test/controller/url_rewriter_test.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 170c68f3e0..c8ddd07bfa 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -45,7 +45,7 @@ module ActionDispatch rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) rewritten_url << "?#{params.to_query}" unless params.empty? - rewritten_url << "##{Journey::Router::Utils.escape_uri(options[:anchor].to_param.to_s)}" if options[:anchor] + rewritten_url << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor] rewritten_url end diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index 89de4c1da4..f88903b10e 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -70,9 +70,9 @@ class UrlRewriterTests < ActionController::TestCase ) end - def test_anchor_should_be_cgi_escaped + def test_anchor_should_be_uri_escaped assert_equal( - 'http://test.host/c/a/i#anc%2Fhor', + 'http://test.host/c/a/i#anc/hor', @rewriter.rewrite(@routes, :controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anc/hor')) ) end -- cgit v1.2.3 From 401d00d296c0c4dafd0e1103051f6adf0ae56fc5 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Thu, 13 Oct 2011 21:42:15 -0700 Subject: Symbol captures may generate multiple path segments, so don't escape / -> %2F. Test splat escaping. --- actionpack/test/dispatch/routing_test.rb | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index a0d2c51a7c..cf22731823 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2536,22 +2536,32 @@ class TestUriPathEscaping < ActionDispatch::IntegrationTest path_params = env['action_dispatch.request.path_parameters'] [200, { 'Content-Type' => 'text/plain' }, [path_params[:segment]]] }, :as => :segment + + match '/*splat' => lambda { |env| + path_params = env['action_dispatch.request.path_parameters'] + [200, { 'Content-Type' => 'text/plain' }, [path_params[:splat]]] + }, :as => :splat end end include Routes.url_helpers def app; Routes end - setup do - @path, @param = '/a%20b%2Fc+d', 'a b/c+d' + test 'escapes generated path segment' do + assert_equal '/a%20b/c+d', segment_path(:segment => 'a b/c+d') + end + + test 'unescapes recognized path segment' do + get '/a%20b%2Fc+d' + assert_equal 'a b/c+d', @response.body end - test 'escapes generated path parameters' do - assert_equal @path, segment_path(:segment => @param) + test 'escapes generated path splat' do + assert_equal '/a%20b/c+d', splat_path(:splat => 'a b/c+d') end - test 'unescapes recognized path parameters' do - get @path - assert_equal @param, @response.body + test 'unescapes recognized path splat' do + get '/a%20b/c+d' + assert_equal 'a b/c+d', @response.body end end -- cgit v1.2.3 From 411613ab207d6ff61520ad78a10b046469db9a80 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Fri, 14 Oct 2011 07:42:06 +1100 Subject: [engines guide] improve wording for sentence describing comments/form partial --- railties/guides/source/engines.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index 2366de33d7..48c5cbf797 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -247,7 +247,7 @@ Next, there needs to be a form so that comments can be created on a post. To add <%= render "comments/form" %> -Next, the partial that this line will render needs to exist. Create a new directory at +app/views/blorgh/comments+ and in it a new file called +_form.html.erb+ with this content to create the required partial: +Next, the partial that this line will render needs to exist. Create a new directory at +app/views/blorgh/comments+ and in it a new file called +_form.html.erb+ which has this content to create the required partial: <%= form_for [@post, @post.comments.build] do |f| %> -- cgit v1.2.3 From 690dd2f64c01985ece8690b9ac1c1ff2dfb6d968 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Fri, 14 Oct 2011 07:45:00 +1100 Subject: [engines guide] correct render line is blorgh/comments/form --- railties/guides/source/engines.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index 48c5cbf797..faa9dff8b8 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -244,7 +244,7 @@ Because the +has_many+ is defined inside a class that is inside the +Blorgh+ mod Next, there needs to be a form so that comments can be created on a post. To add this, put this line underneath the call to +render @post.comments+ in +app/views/blorgh/posts/show.html.erb+: -<%= render "comments/form" %> +<%= render "blorgh/comments/form" %> Next, the partial that this line will render needs to exist. Create a new directory at +app/views/blorgh/comments+ and in it a new file called +_form.html.erb+ which has this content to create the required partial: -- cgit v1.2.3 From 2101bc9fd00f284ee5f86e72f35df9937289871b Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Fri, 14 Oct 2011 07:51:07 +1100 Subject: [engines guide] make it clear that the scaffold generator output isn't part of the actual command --- railties/guides/source/engines.textile | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index faa9dff8b8..0df3889193 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -106,6 +106,11 @@ The first thing to generate for a blog engine is the +Post+ model and related co $ rails generate scaffold post title:string text:text + + +This command will output this information: + + invoke active_record create db/migrate/[timestamp]_create_blorgh_posts.rb create app/models/blorgh/post.rb @@ -207,12 +212,12 @@ Run the model generator and tell it to generate a +Comment+ model, with the rela $ rails generate model Comment post_id:integer text:text -invoke active_record -create db/migrate/[timestamp]_create_blorgh_comments.rb -create app/models/blorgh/comment.rb -invoke test_unit -create test/unit/blorgh/comment_test.rb -create test/fixtures/blorgh/comments.yml + invoke active_record + create db/migrate/[timestamp]_create_blorgh_comments.rb + create app/models/blorgh/comment.rb + invoke test_unit + create test/unit/blorgh/comment_test.rb + create test/fixtures/blorgh/comments.yml This generator call will generate just the necessary model files it needs, namespacing the files under a +blorgh+ directory and creating a model class called +Blorgh::Comment+. -- cgit v1.2.3 From 2c8b8ce9eebce7f128d1811dde71f854fa8806c3 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Fri, 14 Oct 2011 07:51:45 +1100 Subject: [engines guide] make clear that the comment generator command is just one line --- railties/guides/source/engines.textile | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index 0df3889193..f642781c04 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -212,12 +212,17 @@ Run the model generator and tell it to generate a +Comment+ model, with the rela $ rails generate model Comment post_id:integer text:text - invoke active_record - create db/migrate/[timestamp]_create_blorgh_comments.rb - create app/models/blorgh/comment.rb - invoke test_unit - create test/unit/blorgh/comment_test.rb - create test/fixtures/blorgh/comments.yml + + +This will output the following: + + +invoke active_record +create db/migrate/[timestamp]_create_blorgh_comments.rb +create app/models/blorgh/comment.rb +invoke test_unit +create test/unit/blorgh/comment_test.rb +create test/fixtures/blorgh/comments.yml This generator call will generate just the necessary model files it needs, namespacing the files under a +blorgh+ directory and creating a model class called +Blorgh::Comment+. -- cgit v1.2.3 From 3d192c73854b82fba313f641250f2e36031c87b7 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Fri, 14 Oct 2011 18:05:45 +1100 Subject: [engines guide] Add heading for comments --- railties/guides/source/engines.textile | 1 + 1 file changed, 1 insertion(+) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index f642781c04..616ea16b7a 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -230,6 +230,7 @@ This generator call will generate just the necessary model files it needs, names To show the comments on a post, edit +app/views/posts/show.html.erb+ and add this line before the "Edit" link: +

Comments

<%= render @post.comments %>
-- cgit v1.2.3 From aae77d2839349eb5034d6a72bfc351a532f1e6d7 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Fri, 14 Oct 2011 18:06:23 +1100 Subject: [engines guide] complete comments resource generation --- railties/guides/source/engines.textile | 65 ++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index 616ea16b7a..12c454abd1 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -261,14 +261,79 @@ Next, there needs to be a form so that comments can be created on a post. To add Next, the partial that this line will render needs to exist. Create a new directory at +app/views/blorgh/comments+ and in it a new file called +_form.html.erb+ which has this content to create the required partial: +

New comment

<%= form_for [@post, @post.comments.build] do |f| %>

<%= f.label :text %>
<%= f.text_area :text %>

+ <%= f.submit %> <% end %>
+This form, when submitted, is going to attempt to post to a route of +posts/:post_id/comments+ within the engine. This route doesn't exist at the moment, but can be created by changing the +resources :posts+ line inside +config/routes.rb+ into these lines: + + +resources :posts do + resources :comments +end + + +The route now will exist, but the controller that this route goes to does not. To create it, run this command: + + +$ rails g controller comments + + +This will generate the following things: + + +create app/controllers/blorgh/comments_controller.rb +invoke erb + exist app/views/blorgh/comments +invoke test_unit +create test/functional/blorgh/comments_controller_test.rb +invoke helper +create app/helpers/blorgh/comments_helper.rb +invoke test_unit +create test/unit/helpers/blorgh/comments_helper_test.rb +invoke assets +invoke js +create app/assets/javascripts/blorgh/comments.js +invoke css +create app/assets/stylesheets/blorgh/comments.css + + +The form will be making a +POST+ request to +/posts/:post_id/comments+, which will correspond with the +create+ action in +Blorgh::CommentsController+. This action needs to be created and can be done by putting the following lines inside the class definition in +app/controllers/blorgh/comments_controller.rb+: + + +def create + @post = Post.find(params[:post_id]) + @comment = @post.comments.build(params[:comment]) + flash[:notice] = "Comment has been created!" + redirect_to post_path +end + + +This is the final part required to get the new comment form working. Displaying the comments however, is not quite right yet. If you were to create a comment right now you would see this error: + + + Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder], :formats=>[:html], :locale=>[:en, :en]}. Searched in: + * "/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views" + * "/Users/ryan/Sites/side_projects/blorgh/app/views" + + +The engine is unable to find the partial required for rendering the comments. Rails has looked firstly in the application's (+test/dummy+) +app/views+ directory and then in the engine's +app/views+ directory. When it can't find it, it will throw this error. The engine knows to look for +blorgh/comments/comment+ because the model object it is receiving is from the +Blorgh::Comment+ class. + +This partial will be responsible for rendering just the comment text, for now. Create a new file at +app/views/blorgh/comments/_comment.html.erb+ and put this line inside it: + + +<%= comment_counter + 1 %>. <%= comment.text %> + + +The +comment_counter+ local variable is given to us by the +<%= render @post.comments %>+ call, as it will define this automatically and increment the counter as it iterates through each comment. It's used in this example to display a small number next to each comment when it's created. + +That completes the comment function of the blogging engine. Now it's time to use it within an application. h3. Hooking into application -- cgit v1.2.3 From 6df79bf580cf9a0464fce948c212bd117c378cc9 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Fri, 14 Oct 2011 18:43:49 +1100 Subject: [engines guide] WIP for explaining how to hook engine into application --- railties/guides/source/engines.textile | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index 12c454abd1..1eb0dcb77b 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -204,6 +204,14 @@ If you'd rather play around in the console, +rails console+ will also work just => #
+One final thing is that the +posts+ resource for this engine should be the root of the engine. Whenever someone goes to the root path where the engine is mounted, they should be shown a list of posts. This can be made to happen if this line is inserted into the +config/routes.rb+ file inside the engine: + + +root :to => "posts#index" + + +Now people will only need to go to the root of the engine to see all the posts, rather than visiting +/posts+. + h4. Generating a comments resource Now that the engine has the ability to create new blog posts, it only makes sense to add commenting functionality as well. To do get this, you'll need to generate a comment model, a comment controller and then modify the posts scaffold to display comments and allow people to create new ones. @@ -337,11 +345,49 @@ That completes the comment function of the blogging engine. Now it's time to use h3. Hooking into application +Using an engine within an application is very easy. First, the engine needs to be specified inside the application's +Gemfile+. If there isn't an application handy to test this out in, generate one using the +rails new+ command outside of the engine directory like this: + + +$ rails new unicorn + + +Usually, specifying the engine inside the Gemfile would be done by specifying it as a normal, everyday gem. + + +gem 'devise' + + +Because the +blorgh+ engine is still under development, it will need to have a +:path+ option for its +Gemfile+ specification: + + +gem 'blorgh', :path => "/path/to/blorgh" + + +If the whole +blorgh+ engine directory is copied to +vendor/engines/blorgh+ then it could be specified in the +Gemfile+ like this: + + +gem 'blorgh', :path => "vendor/engines/blorgh" + + +As described earlier, by placing the gem in the +Gemfile+ it will be loaded when Rails is loaded, as it will first require +lib/blorgh.rb+ in the engine and then +lib/blorgh/engine.rb+, which is the file that defines the major pieces of functionality for the engine. + +To make the engine's functionality accessible from within an application, it needs to be mounted in that application's +config/routes.rb+ file: + + + mount Blorgh::Engine, :at => "blog" + + +NOTE: Other engines, such as Devise, handle this a little differently by making you specify custom helpers such as +devise_for+ in the routes. These helpers do exactly the same thing, mounting pieces of the engines's functionality at a pre-defined path which may be customizable. + + + +This line will mount the engine TODO: Application will provide a User foundation class which the engine hooks into through a configuration setting, configurable in the application's initializers. The engine will be mounted at the +/blog+ path in the application. h3. Overriding engine functionality TODO: Cover how to override engine functionality in the engine, such as controllers and views. + IDEA: I like Devise's +devise :controllers => { "sessions" => "sessions" }+ idea. Perhaps we could incorporate that into the guide? TODO: Mention how to use assets within an engine? TODO: Mention how to depend on external gems, like RedCarpet. -- cgit v1.2.3 From 410fa4cf7c710ff062c59c1c90357729c418be65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ba=CC=88uerlein?= Date: Fri, 14 Oct 2011 16:28:02 +0200 Subject: Includes stale record in StaleObjectError --- activerecord/lib/active_record/errors.rb | 9 +++++++++ activerecord/lib/active_record/locking/optimistic.rb | 4 ++-- activerecord/test/cases/locking_test.rb | 19 ++++++++++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 96870cb338..950fc7356c 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -99,6 +99,15 @@ module ActiveRecord # # Read more about optimistic locking in ActiveRecord::Locking module RDoc. class StaleObjectError < ActiveRecordError + attr_reader :record + + def initialize(record) + @record = record + end + + def message + "Attempted to update a stale object: #{record.class.name}" + end end # Raised when association is being configured improperly or diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index d9ad7e4132..cafe48cff6 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -103,7 +103,7 @@ module ActiveRecord affected_rows = connection.update stmt unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}" + raise ActiveRecord::StaleObjectError, self end affected_rows @@ -127,7 +127,7 @@ module ActiveRecord affected_rows = self.class.unscoped.where(predicate).delete_all unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}" + raise ActiveRecord::StaleObjectError, self end end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 61baa55027..e9bd7f07b6 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -125,6 +125,24 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_raise(ActiveRecord::StaleObjectError) { p2.save! } end + def test_lock_exception_record + p1 = Person.new(:first_name => 'mira') + assert_equal 0, p1.lock_version + + p1.first_name = 'mira2' + p1.save! + p2 = Person.find(p1.id) + assert_equal 0, p1.lock_version + assert_equal 0, p2.lock_version + + p1.first_name = 'mira3' + p1.save! + + p2.first_name = 'sue' + error = assert_raise(ActiveRecord::StaleObjectError) { p2.save! } + assert_equal(error.record.object_id, p2.object_id) + end + def test_lock_new_with_nil p1 = Person.new(:first_name => 'anika') p1.save! @@ -141,7 +159,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 1, p1.lock_version end - def test_lock_column_name_existing t1 = LegacyThing.find(1) t2 = LegacyThing.find(1) -- cgit v1.2.3 From c6f0461e898601578fa8160608d19fec319067fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ba=CC=88uerlein?= Date: Fri, 14 Oct 2011 18:20:41 +0200 Subject: Consider attempted action in exception message of ActiveRecord::StaleObjectError --- activerecord/lib/active_record/errors.rb | 9 +++++---- activerecord/lib/active_record/locking/optimistic.rb | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 950fc7356c..fc80f3081e 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -99,15 +99,16 @@ module ActiveRecord # # Read more about optimistic locking in ActiveRecord::Locking module RDoc. class StaleObjectError < ActiveRecordError - attr_reader :record + attr_reader :record, :attempted_action - def initialize(record) + def initialize(record, attempted_action) @record = record + @attempted_action = attempted_action end def message - "Attempted to update a stale object: #{record.class.name}" - end + "Attempted to #{attempted_action} a stale object: #{record.class.name}" + end end # Raised when association is being configured improperly or diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index cafe48cff6..2df3309648 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -103,7 +103,7 @@ module ActiveRecord affected_rows = connection.update stmt unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, self + raise ActiveRecord::StaleObjectError.new(self, "update") end affected_rows @@ -127,7 +127,7 @@ module ActiveRecord affected_rows = self.class.unscoped.where(predicate).delete_all unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, self + raise ActiveRecord::StaleObjectError.new(self, "destroy") end end -- cgit v1.2.3 From 3dbedd2823ff6c8a1f2f078ae9df9c6ceb275e1b Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Fri, 14 Oct 2011 21:09:53 -0700 Subject: Default timestamps to non-null --- .../connection_adapters/abstract/schema_definitions.rb | 2 +- .../connection_adapters/abstract/schema_statements.rb | 4 ++-- activerecord/test/cases/migration_test.rb | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 82f564e41d..989a4fcbca 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -252,7 +252,7 @@ module ActiveRecord # Appends :datetime columns :created_at and # :updated_at to the table. def timestamps(*args) - options = args.extract_options! + options = { :null => false }.merge(args.extract_options!) column(:created_at, :datetime, options) column(:updated_at, :datetime, options) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 8e3ba1297e..b4a9e29ef1 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -507,8 +507,8 @@ module ActiveRecord # ===== Examples # add_timestamps(:suppliers) def add_timestamps(table_name) - add_column table_name, :created_at, :datetime - add_column table_name, :updated_at, :datetime + add_column table_name, :created_at, :datetime, :null => false + add_column table_name, :updated_at, :datetime, :null => false end # Removes the timestamp columns (created_at and updated_at) from the table definition. diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 93a1249e43..4b5dd18be8 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -389,8 +389,8 @@ if ActiveRecord::Base.connection.supports_migrations? created_at_column = created_columns.detect {|c| c.name == 'created_at' } updated_at_column = created_columns.detect {|c| c.name == 'updated_at' } - assert created_at_column.null - assert updated_at_column.null + assert !created_at_column.null + assert !updated_at_column.null ensure Person.connection.drop_table table_name rescue nil end @@ -471,11 +471,11 @@ if ActiveRecord::Base.connection.supports_migrations? # Do a manual insertion if current_adapter?(:OracleAdapter) - Person.connection.execute "insert into people (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)" + Person.connection.execute "insert into people (id, wealth, created_at, updated_at) values (people_seq.nextval, 12345678901234567890.0123456789, 0, 0)" elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings - Person.connection.execute "insert into people (wealth) values ('12345678901234567890.0123456789')" + Person.connection.execute "insert into people (wealth, created_at, updated_at) values ('12345678901234567890.0123456789', 0, 0)" else - Person.connection.execute "insert into people (wealth) values (12345678901234567890.0123456789)" + Person.connection.execute "insert into people (wealth, created_at, updated_at) values (12345678901234567890.0123456789, 0, 0)" end # SELECT -- cgit v1.2.3 From f247c5f81175fc160de6be906ef87316f0528f38 Mon Sep 17 00:00:00 2001 From: Alexey Vakhov Date: Sat, 15 Oct 2011 11:34:33 +0400 Subject: Update AC::RecordIdentifier example --- actionpack/lib/action_controller/record_identifier.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 2036442cfe..9c38ff44d8 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -14,9 +14,9 @@ module ActionController # <% end %> # # # controller - # def destroy + # def update # post = Post.find(params[:id]) - # post.destroy + # post.update_attributes(params[:post]) # # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post) # end -- cgit v1.2.3 From 26204def8f28f190b9fef1144720288f87bd4fb4 Mon Sep 17 00:00:00 2001 From: mhutchin Date: Sat, 15 Oct 2011 05:32:13 -0700 Subject: [layouts and rendering] Copy editing to improve readability --- .../guides/source/layouts_and_rendering.textile | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile index 69ef05104c..6f0dc57090 100644 --- a/railties/guides/source/layouts_and_rendering.textile +++ b/railties/guides/source/layouts_and_rendering.textile @@ -348,9 +348,9 @@ h5. Finding Layouts To find the current layout, Rails first looks for a file in +app/views/layouts+ with the same base name as the controller. For example, rendering actions from the +PhotosController+ class will use +app/views/layouts/photos.html.erb+ (or +app/views/layouts/photos.builder+). If there is no such controller-specific layout, Rails will use +app/views/layouts/application.html.erb+ or +app/views/layouts/application.builder+. If there is no +.erb+ layout, Rails will use a +.builder+ layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions. -h6. Specifying Layouts on a per-Controller Basis +h6. Specifying Layouts for Controllers -You can override the automatic layout conventions in your controllers by using the +layout+ declaration in the controller. For example: +You can override the default layout conventions in your controllers by using the +layout+ declaration. For example: class ProductsController < ApplicationController @@ -359,9 +359,9 @@ class ProductsController < ApplicationController end -With this declaration, all methods within +ProductsController+ will use +app/views/layouts/inventory.html.erb+ for their layout. +With this declaration, all of the methods within +ProductsController+ will use +app/views/layouts/inventory.html.erb+ for their layout. -To assign a specific layout for the entire application, use a declaration in your +ApplicationController+ class: +To assign a specific layout for the entire application, use a +layout+ declaration in your +ApplicationController+ class: class ApplicationController < ActionController::Base @@ -370,7 +370,7 @@ class ApplicationController < ActionController::Base end -With this declaration, all views in the entire application will use +app/views/layouts/main.html.erb+ for their layout. +With this declaration, all of the views in the entire application will use +app/views/layouts/main.html.erb+ for their layout. h6. Choosing Layouts at Runtime @@ -392,9 +392,9 @@ class ProductsController < ApplicationController end
-Now, if the current user is a special user, they'll get a special layout when viewing a product. You can even use an inline method to determine the layout: +Now, if the current user is a special user, they'll get a special layout when viewing a product. -You can also decide the layout by passing a Proc object, the block you give the Proc will be given the +controller+ instance, so you can make decisions based on the current request. For example: +You can even use an inline method, such as a Proc, to determine the layout. For example, if you pass a Proc object, the block you give the Proc will be given the +controller+ instance, so the layout can be determined based on the current request: class ProductsController < ApplicationController @@ -404,7 +404,7 @@ end h6. Conditional Layouts -Layouts specified at the controller level support +:only+ and +:except+ options that take either a method name or an array of method names which correspond to method names within the controller: +Layouts specified at the controller level support the +:only+ and +:except+ options. These options take either a method name, or an array of method names, corresponding to method names within the controller: class ProductsController < ApplicationController @@ -416,7 +416,7 @@ With this declaration, the +product+ layout would be used for everything but the h6. Layout Inheritance -Layouts are shared downwards in the hierarchy, and more specific layouts always override more general ones. For example: +Layout declarations cascade downward in the hierarchy, and more specific layout declarations always override more general ones. For example: * +application_controller.rb+ @@ -495,9 +495,9 @@ def show end -Make sure you use +and return+ and not +&& return+ because while the former will work, the latter will not due to operator precedence in the Ruby Language. +Make sure to use +and return+ and not +&& return+, since +&& return+ will not work due to the operator precedence in the Ruby Language. -Note that the implicit render done by ActionController detects if +render+ has been called, and thus avoids this error. Therefore, the following will work without errors: +Note that the implicit render done by ActionController detects if +render+ has been called, so the following will work without errors: def show -- cgit v1.2.3 From 018e84af200859e72cc5b895c4a843f81c6b5931 Mon Sep 17 00:00:00 2001 From: "Karunakar (Ruby)" Date: Sun, 16 Oct 2011 01:00:57 +0530 Subject: railties README change to make consistent like others eg:activerecord --- railties/README.rdoc | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/railties/README.rdoc b/railties/README.rdoc index 95c43045b0..b77304fc3b 100644 --- a/railties/README.rdoc +++ b/railties/README.rdoc @@ -15,12 +15,18 @@ The latest version of Railties can be installed with RubyGems: * gem install railties -Documentation can be found at +== License -* http://api.rubyonrails.org +Railties is released under the MIT license. +== Support -== License +API documentation is at -Railties is released under the MIT license. +* http://api.rubyonrails.org + +Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here: + +* https://github.com/rails/rails/issues +You can find more usage information in the ActiveResource::Base documentation. -- cgit v1.2.3 From 4867bfabaa03b4c936dd766c7f2edf0919e05ca4 Mon Sep 17 00:00:00 2001 From: "Karunakar (Ruby)" Date: Sun, 16 Oct 2011 01:12:00 +0530 Subject: more doc changes on railties README --- railties/README.rdoc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/railties/README.rdoc b/railties/README.rdoc index b77304fc3b..ae40600401 100644 --- a/railties/README.rdoc +++ b/railties/README.rdoc @@ -15,6 +15,10 @@ The latest version of Railties can be installed with RubyGems: * gem install railties +Source code can be downloaded as part of the Rails project on GitHub + +* https://github.com/rails/rails/tree/master/railties + == License Railties is released under the MIT license. @@ -29,4 +33,3 @@ Bug reports and feature requests can be filed with the rest for the Ruby on Rail * https://github.com/rails/rails/issues -You can find more usage information in the ActiveResource::Base documentation. -- cgit v1.2.3 From 8f473dc9aea2099e6e7e47c929c6a9cec02c7902 Mon Sep 17 00:00:00 2001 From: Alexey Vakhov Date: Sat, 15 Oct 2011 23:54:26 +0400 Subject: HTMl -> HTML: html scanner comment fix --- actionpack/lib/action_controller/vendor/html-scanner/html/document.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb index 7fa3aead82..386820300a 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb @@ -4,7 +4,7 @@ require 'html/selector' require 'html/sanitizer' module HTML #:nodoc: - # A top-level HTMl document. You give it a body of text, and it will parse that + # A top-level HTML document. You give it a body of text, and it will parse that # text into a tree of nodes. class Document #:nodoc: -- cgit v1.2.3 From abf4f096e56a941ac4f908cb34c83bc82770ed56 Mon Sep 17 00:00:00 2001 From: mhutchin Date: Sat, 15 Oct 2011 15:59:26 -0700 Subject: [layouts and rendering] Copy editing --- .../guides/source/layouts_and_rendering.textile | 52 +++++++++++----------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile index 6f0dc57090..df7b9b364c 100644 --- a/railties/guides/source/layouts_and_rendering.textile +++ b/railties/guides/source/layouts_and_rendering.textile @@ -334,7 +334,7 @@ render :status => 500 render :status => :forbidden -Rails understands both numeric status codes and symbols for status codes. +Rails understands both numeric and symbolic status codes. h6. The +:location+ Option @@ -518,7 +518,7 @@ Another way to handle returning responses to an HTTP request is with +redirect_t redirect_to photos_url -You can use +redirect_to+ with any arguments that you could use with +link_to+ or +url_for+. In addition, there's a special redirect that sends the user back to the page they just came from: +You can use +redirect_to+ with any arguments that you could use with +link_to+ or +url_for+. There's also a special redirect that sends the user back to the page they just came from: redirect_to :back @@ -526,7 +526,7 @@ redirect_to :back h5. Getting a Different Redirect Status Code -Rails uses HTTP status code 302 (temporary redirect) when you call +redirect_to+. If you'd like to use a different status code (perhaps 301, permanent redirect), you can do so by using the +:status+ option: +Rails uses HTTP status code 302, a temporary redirect, when you call +redirect_to+. If you'd like to use a different status code, perhaps 301, a permanent redirect, you can use the +:status+ option: redirect_to photos_path, :status => 301 @@ -536,7 +536,7 @@ Just like the +:status+ option for +render+, +:status+ for +redirect_to+ accepts h5. The Difference Between +render+ and +redirect_to+ -Sometimes inexperienced developers conceive of +redirect_to+ as a sort of +goto+ command, moving execution from one place to another in your Rails code. This is _not_ correct. Your code stops running and waits for a new request for the browser. It just happens that you've told the browser what request it should make next, by sending back an HTTP 302 status code. +Sometimes inexperienced developers think of +redirect_to+ as a sort of +goto+ command, moving execution from one place to another in your Rails code. This is _not_ correct. Your code stops running and waits for a new request for the browser. It just happens that you've told the browser what request it should make next, by sending back an HTTP 302 status code. Consider these actions to see the difference: @@ -553,7 +553,7 @@ def show end -With the code in this form, there will likely be a problem if the +@book+ variable is +nil+. Remember, a +render :action+ doesn't run any code in the target action, so nothing will set up the +@books+ variable that the +index+ view is presumably depending on. One way to fix this is to redirect instead of rendering: +With the code in this form, there will likely be a problem if the +@book+ variable is +nil+. Remember, a +render :action+ doesn't run any code in the target action, so nothing will set up the +@books+ variable that the +index+ view will probably require. One way to fix this is to redirect instead of rendering: def index @@ -570,9 +570,9 @@ end With this code, the browser will make a fresh request for the index page, the code in the +index+ method will run, and all will be well. -The only downside to this code, is that it requires a round trip to the browser, the browser requested the show action with +/books/1+ and the controller finds that there are no books, so the controller sends out a 302 redirect response to the browser telling it to go to +/books/+, the browser complies and sends a new request back to the controller asking now for the +index+ action, the controller then gets all the books in the database and renders the index template, sending it back down to the browser which then shows it on your screen. +The only downside to this code is that it requires a round trip to the browser: the browser requested the show action with +/books/1+ and the controller finds that there are no books, so the controller sends out a 302 redirect response to the browser telling it to go to +/books/+, the browser complies and sends a new request back to the controller asking now for the +index+ action, the controller then gets all the books in the database and renders the index template, sending it back down to the browser which then shows it on your screen. -While in a small app, this added latency might not be a problem, it is something to think about when speed of response is of the essence. One way to handle this double request (though a contrived example) could be: +While in a small application, this added latency might not be a problem, it is something to think about if response time is a concern. We can demonstrate one way to handle this with a contrived example: def index @@ -588,7 +588,7 @@ def show end -Which would detect that there are no books, populate the +@books+ instance variable with all the books in the database and then directly render the +index.html.erb+ template returning it to the browser with a flash alert message telling the user what happened. +This would detect that there are no books with the specified ID, populate the +@books+ instance variable with all the books in the model, and then directly render the +index.html.erb+ template, returning it to the browser with a flash alert message to tell the user what happened. h4. Using +head+ To Build Header-Only Responses @@ -598,7 +598,7 @@ The +head+ method can be used to send responses with only headers to the browser head :bad_request -Which would produce the following header: +This would produce the following header: HTTP/1.1 400 Bad Request @@ -611,7 +611,7 @@ Set-Cookie: _blog_session=...snip...; path=/; HttpOnly Cache-Control: no-cache -Or you can use other HTTP headers to convey additional information: +Or you can use other HTTP headers to convey other information: head :created, :location => photo_path(@photo) @@ -633,15 +633,15 @@ Cache-Control: no-cache h3. Structuring Layouts -When Rails renders a view as a response, it does so by combining the view with the current layout (using the rules for finding the current layout that were covered earlier in this guide). Within a layout, you have access to three tools for combining different bits of output to form the overall response: +When Rails renders a view as a response, it does so by combining the view with the current layout, using the rules for finding the current layout that were covered earlier in this guide. Within a layout, you have access to three tools for combining different bits of output to form the overall response: * Asset tags * +yield+ and +content_for+ * Partials -h4. Asset Tags +h4. Asset Tag Helpers -Asset tags provide methods for generating HTML that links views to feeds, JavaScript, stylesheets, images, videos and audios. These are the six asset tags available in Rails: +Asset tag helpers provide methods for generating HTML that link views to feeds, JavaScript, stylesheets, images, videos and audios. There are six asset tag helpers available in Rails: * +auto_discovery_link_tag+ * +javascript_include_tag+ @@ -650,11 +650,11 @@ Asset tags provide methods for generating HTML that links views to feeds, JavaSc * +video_tag+ * +audio_tag+ -You can use these tags in layouts or other views, although the tags other than +image_tag+ are most commonly used in the +<head>+ section of a layout. +You can use these tags in layouts or other views, although the +auto_discovery_link_tag+, +javascript_include_tag+, and +stylesheet_link_tag+, are most commonly used in the +<head>+ section of a layout. -WARNING: The asset tags do _not_ verify the existence of the assets at the specified locations; they simply assume that you know what you're doing and generate the link. +WARNING: The asset tag helpers do _not_ verify the existence of the assets at the specified locations; they simply assume that you know what you're doing and generate the link. -h5. Linking to Feeds with +auto_discovery_link_tag+ +h5. Linking to Feeds with the +auto_discovery_link_tag+ The +auto_discovery_link_tag+ helper builds HTML that most browsers and newsreaders can use to detect the presences of RSS or ATOM feeds. It takes the type of the link (+:rss+ or +:atom+), a hash of options that are passed through to url_for, and a hash of options for the tag: @@ -663,13 +663,13 @@ The +auto_discovery_link_tag+ helper builds HTML that most browsers and newsread {:title => "RSS Feed"}) %> -There are three tag options available for +auto_discovery_link_tag+: +There are three tag options available for the +auto_discovery_link_tag+: -* +:rel+ specifies the +rel+ value in the link (defaults to "alternate") +* +:rel+ specifies the +rel+ value in the link. The default value is "alternate". * +:type+ specifies an explicit MIME type. Rails will generate an appropriate MIME type automatically. -* +:title+ specifies the title of the link +* +:title+ specifies the title of the link. The default value is the upshifted +:type+ value, for example, "ATOM" or "RSS". -h5. Linking to JavaScript Files with +javascript_include_tag+ +h5. Linking to JavaScript Files with the +javascript_include_tag+ The +javascript_include_tag+ helper returns an HTML +script+ tag for each source provided. Rails looks in +public/javascripts+ for these files by default, but you can specify a full path relative to the document root, or a URL, if you prefer. For example, to include +public/javascripts/main.js+: @@ -738,7 +738,7 @@ By default, the combined file will be delivered as +javascripts/all.js+. You can You can even use dynamic paths such as +cache/#{current_site}/main/display+. -h5. Linking to CSS Files with +stylesheet_link_tag+ +h5. Linking to CSS Files with the +stylesheet_link_tag+ The +stylesheet_link_tag+ helper returns an HTML +<link>+ tag for each source provided. Rails looks in +public/stylesheets+ for these files by default, but you can specify a full path relative to the document root, or a URL, if you prefer. For example, to include +public/stylesheets/main.css+: @@ -764,7 +764,7 @@ To include +http://example.com/main.css+: <%= stylesheet_link_tag "http://example.com/main.css" %> -By default, +stylesheet_link_tag+ creates links with +media="screen" rel="stylesheet" type="text/css"+. You can override any of these defaults by specifying an appropriate option (+:media+, +:rel+, or +:type+): +By default, the +stylesheet_link_tag+ creates links with +media="screen" rel="stylesheet" type="text/css"+. You can override any of these defaults by specifying an appropriate option (+:media+, +:rel+, or +:type+): <%= stylesheet_link_tag "main_print", :media => "print" %> @@ -797,7 +797,7 @@ By default, the combined file will be delivered as +stylesheets/all.css+. You ca You can even use dynamic paths such as +cache/#{current_site}/main/display+. -h5. Linking to Images with +image_tag+ +h5. Linking to Images with the +image_tag+ The +image_tag+ helper builds an HTML +<img />+ tag to the specified file. By default, files are loaded from +public/images+. @@ -846,7 +846,7 @@ In addition to the above special tags, you can supply a final hash of standard H :class => 'nav_bar' %> -h5. Linking to Videos with +video_tag+ +h5. Linking to Videos with the +video_tag+ The +video_tag+ helper builds an HTML 5 +<video>+ tag to the specified file. By default, files are loaded from +public/videos+. @@ -882,7 +882,7 @@ This will produce: -h5. Linking to Audio files with +audio_tag+ +h5. Linking to Audio Files with the +audio_tag+ The +audio_tag+ helper builds an HTML 5 +<audio>+ tag to the specified file. By default, files are loaded from +public/audios+. @@ -933,7 +933,7 @@ You can also create a layout with multiple yielding regions: The main body of the view will always render into the unnamed +yield+. To render content into a named +yield+, you use the +content_for+ method. -h4. Using +content_for+ +h4. Using the +content_for+ Method The +content_for+ method allows you to insert content into a named +yield+ block in your layout. For example, this view would work with the layout that you just saw: -- cgit v1.2.3 From 9210458547bbf5e79aa650e15226a0d5c58ea6c7 Mon Sep 17 00:00:00 2001 From: Vishnu Atrai Date: Sun, 16 Oct 2011 14:29:06 +0530 Subject: fix to remove warning in test cases --- activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb index 5ffd886dab..97adb6b297 100644 --- a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb +++ b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb @@ -21,7 +21,7 @@ class MysqlCaseSensitivityTest < ActiveRecord::TestCase CollationTest.create!(:string_ci_column => 'A') invalid = CollationTest.new(:string_ci_column => 'a') queries = assert_sql { invalid.save } - ci_uniqueness_query = queries.detect { |q| q.match /string_ci_column/ } + ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) } assert_no_match(/lower/i, ci_uniqueness_query) end @@ -29,7 +29,7 @@ class MysqlCaseSensitivityTest < ActiveRecord::TestCase CollationTest.create!(:string_cs_column => 'A') invalid = CollationTest.new(:string_cs_column => 'a') queries = assert_sql { invalid.save } - cs_uniqueness_query = queries.detect { |q| q.match /string_cs_column/ } + cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) } assert_match(/lower/i, cs_uniqueness_query) end end -- cgit v1.2.3 From bb8ee9264f8302ea9f669c96f46d9c3812d6f350 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Sat, 15 Oct 2011 09:51:30 +1100 Subject: [engines guide] improve intro to 'Hooking into an application' section --- railties/guides/source/engines.textile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index 126d09ab87..4f77852c40 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -343,9 +343,13 @@ The +comment_counter+ local variable is given to us by the +<%= render @post.com That completes the comment function of the blogging engine. Now it's time to use it within an application. -h3. Hooking into application +h3. Hooking into an application -Using an engine within an application is very easy. First, the engine needs to be specified inside the application's +Gemfile+. If there isn't an application handy to test this out in, generate one using the +rails new+ command outside of the engine directory like this: +Using an engine within an application is very easy. This section covers how to mount the engine into an application and the initial setup required for it, as well as linking the engine to a +User+ class provided by the application to provide ownership for posts and comments within the engine. + +h4. Mounting the engine + +First, the engine needs to be specified inside the application's +Gemfile+. If there isn't an application handy to test this out in, generate one using the +rails new+ command outside of the engine directory like this: $ rails new unicorn -- cgit v1.2.3 From 0f87cc1486e2b7f95fff69ead9e6c74344c89496 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Sat, 15 Oct 2011 09:52:07 +1100 Subject: [engines guide] Add 'Engine setup' section in 'Hooking into an application' section --- railties/guides/source/engines.textile | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index 4f77852c40..8d5c231124 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -378,14 +378,32 @@ As described earlier, by placing the gem in the +Gemfile+ it will be loaded when To make the engine's functionality accessible from within an application, it needs to be mounted in that application's +config/routes.rb+ file: - mount Blorgh::Engine, :at => "blog" +mount Blorgh::Engine, :at => "blog" +This line will mount the engine at +blog+ in the application. Making it accessible at +http://localhost:3000/blog+ when the application runs with +rails s+. + NOTE: Other engines, such as Devise, handle this a little differently by making you specify custom helpers such as +devise_for+ in the routes. These helpers do exactly the same thing, mounting pieces of the engines's functionality at a pre-defined path which may be customizable. +h4. Engine setup + +The engine contains migrations for the +blorgh_posts+ and +blorgh_comments+ table which need to be created in the application's database so that the engine's models can query them correctly. To copy these migrations into the application use this command: + + +$ rake blorgh:install:migrations + + +This command, when run for the first time will copy over all the migrations from the engine. When run the next time, it will only copy over migrations that haven't been copied over already. The first run for this command will output something such as this: + + +Copied migration [timestamp_1]_create_blorgh_posts.rb from blorgh +Copied migration [timestamp_2]_create_blorgh_comments.rb from blorgh + + +The first timestamp (+\[timestamp_1\]+) will be the current time and the second timestamp (+\[timestamp_2\]+) will be the current time plus a second. The reason for this is so that the migrations for the engine are run after any existing migrations in the application. +To run these migrations within the context of the application, simply run +rake db:migrate+. When accessing the engine through +http://localhost:3000/blog+, the posts will be empty. This is because the table created inside the application is different from the one created within the engine. Go ahead, play around with the newly mounted engine. You'll find that it's the same as when it was only an engine. -This line will mount the engine TODO: Application will provide a User foundation class which the engine hooks into through a configuration setting, configurable in the application's initializers. The engine will be mounted at the +/blog+ path in the application. h3. Overriding engine functionality -- cgit v1.2.3 From 434148024d742ec50662bf72c5cbb8e5664985e5 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Sun, 16 Oct 2011 09:53:26 +1100 Subject: [engines guide] beginning to cover using application's classes --- railties/guides/source/engines.textile | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index 8d5c231124..9cdca25329 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -404,7 +404,29 @@ The first timestamp (+\[timestamp_1\]+) will be the current time and the second To run these migrations within the context of the application, simply run +rake db:migrate+. When accessing the engine through +http://localhost:3000/blog+, the posts will be empty. This is because the table created inside the application is different from the one created within the engine. Go ahead, play around with the newly mounted engine. You'll find that it's the same as when it was only an engine. -TODO: Application will provide a User foundation class which the engine hooks into through a configuration setting, configurable in the application's initializers. The engine will be mounted at the +/blog+ path in the application. +h4. Using an application's classes + +When an engine is created, it may want to use specific classes from an application to provide links between the pieces of the engine and the pieces of the application. In the case of the +blorgh+ engine, making posts and comments have authors would make a lot of sense. + +Usually, an application would have a +User+ class that would provide the objects that would represent the posts' and comments' authors, but there could be a case where the application calls this class something different, such as +Person+. It's because of this reason that the engine should not hardcode the associations to be exactly for a +User+ class, but should allow for some flexibility around what the class is called. + +To keep it simple in this case, the application will have a class called +User+ which will represent the users of the application. It can be generated using this command: + + +rails g model user name:string + + +Also to keep it simple, the posts form will have a new text field called +author+ where users can elect to put their name. The engine will then take this name and create a new +User+ object from it or find one that already has that name, and then associate the post with it. + +First, the +author+ text field needs to be added to the +app/views/blorgh/posts/_form.html.erb+ partial inside the engine. This can be added above the +title+ field with this code: + + +
+ <%= f.label :author %>
+ <%= f.text_field :author %> +
+
+ h3. Overriding engine functionality -- cgit v1.2.3 From 5bf0ea6830f9148f6b45d2774bfd7f54eec4a267 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Mon, 17 Oct 2011 08:55:32 +1100 Subject: [engines guide] Complete section about using an application's class --- railties/guides/source/engines.textile | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index 9cdca25329..bc5fe7640a 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -404,7 +404,7 @@ The first timestamp (+\[timestamp_1\]+) will be the current time and the second To run these migrations within the context of the application, simply run +rake db:migrate+. When accessing the engine through +http://localhost:3000/blog+, the posts will be empty. This is because the table created inside the application is different from the one created within the engine. Go ahead, play around with the newly mounted engine. You'll find that it's the same as when it was only an engine. -h4. Using an application's classes +h4. Using a class provided by the application When an engine is created, it may want to use specific classes from an application to provide links between the pieces of the engine and the pieces of the application. In the case of the +blorgh+ engine, making posts and comments have authors would make a lot of sense. @@ -416,17 +416,38 @@ To keep it simple in this case, the application will have a class called +User+ rails g model user name:string
-Also to keep it simple, the posts form will have a new text field called +author+ where users can elect to put their name. The engine will then take this name and create a new +User+ object from it or find one that already has that name, and then associate the post with it. +Also to keep it simple, the posts form will have a new text field called +author_name_+ where users can elect to put their name. The engine will then take this name and create a new +User+ object from it or find one that already has that name, and then associate the post with it. -First, the +author+ text field needs to be added to the +app/views/blorgh/posts/_form.html.erb+ partial inside the engine. This can be added above the +title+ field with this code: +First, the +author_name+ text field needs to be added to the +app/views/blorgh/posts/_form.html.erb+ partial inside the engine. This can be added above the +title+ field with this code:
- <%= f.label :author %>
- <%= f.text_field :author %> + <%= f.label :author_name %>
+ <%= f.text_field :author_name %>
+The +Blorgh::Post+ model should then have some code to convert the +author_name+ field into an actual +User+ object and associate it as that post's +author+ before the post is saved. It will also need to have an +attr_accessor+ setup for this field so that the setter and getter methods are defined for it. + +To do all this, you'll need to add the +attr_accessor+ for +author_name+, the association for the author and the +before_save+ call into +app/models/blorgh/post.rb+. The +author+ association will be hard-coded to the +User+ class for the time being. + + +attr_accessor :author_name +belongs_to :author, :class_name => "User" + +before_save :set_author + +private + def set_author + self.author = User.find_or_create_by_name(author_name) + end + + +By defining that the +author+ association's object is represented by the +User+ class a link is established between the engine and the application. Now just before a post is saved it will be associated with a record from the +users+ table of the application. + +h4. Configuring an engine + +The next step is to make the class that represents a +User+ in the application customizable for the engine. This is because, as explained before, that class may not always be +User+. h3. Overriding engine functionality -- cgit v1.2.3 From 7d8c650e8663e0af722225e66f72a82dcb518b55 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Mon, 17 Oct 2011 17:12:31 +1100 Subject: [engines guide] [reviewing] remove excessive 'Now' --- railties/guides/source/engines.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index bc5fe7640a..d191fe07b6 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -443,7 +443,7 @@ private end
-By defining that the +author+ association's object is represented by the +User+ class a link is established between the engine and the application. Now just before a post is saved it will be associated with a record from the +users+ table of the application. +By defining that the +author+ association's object is represented by the +User+ class a link is established between the engine and the application. Just before a post is saved it will be associated with a record from the +users+ table of the application. h4. Configuring an engine -- cgit v1.2.3 From 180d4137ca8222cb90a285bfd60265ae93c56968 Mon Sep 17 00:00:00 2001 From: Martin Svalin Date: Tue, 11 Oct 2011 21:44:22 +0200 Subject: ActiveModel::Errors#generate_message without i18n_scope, and more test cases for #add --- activemodel/lib/active_model/errors.rb | 12 ++++++++---- activemodel/test/cases/errors_test.rb | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index d91e4a2b6a..f90030641d 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -300,13 +300,17 @@ module ActiveModel def generate_message(attribute, type = :invalid, options = {}) type = options.delete(:message) if options[:message].is_a?(Symbol) - defaults = @base.class.lookup_ancestors.map do |klass| - [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}", - :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ] + if @base.class.respond_to?(:i18n_scope) + defaults = @base.class.lookup_ancestors.map do |klass| + [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}", + :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ] + end + else + defaults = [] end defaults << options.delete(:message) - defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" + defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope) defaults << :"errors.attributes.#{attribute}.#{type}" defaults << :"errors.messages.#{type}" diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index 4c76bb43a8..784a2b2709 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -66,6 +66,20 @@ class ErrorsTest < ActiveModel::TestCase assert_equal ["can not be blank"], person.errors[:name] end + test "should be able to add an error with a symbol" do + person = Person.new + person.errors.add(:name, :blank) + message = person.errors.generate_message(:name, :blank) + assert_equal [message], person.errors[:name] + end + + test "should be able to add an error with a proc" do + person = Person.new + message = Proc.new { "can not be blank" } + person.errors.add(:name, message) + assert_equal ["can not be blank"], person.errors[:name] + end + test 'should respond to size' do person = Person.new person.errors.add(:name, "can not be blank") @@ -112,5 +126,12 @@ class ErrorsTest < ActiveModel::TestCase assert_equal ["is invalid"], hash[:email] end + test "generate_message should work without i18n_scope" do + person = Person.new + assert !Person.respond_to?(:i18n_scope) + assert_nothing_raised { + person.errors.generate_message(:name, :blank) + } + end end -- cgit v1.2.3 From 518b30cf486164c59b7120b7b5039aa85c962630 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Mon, 17 Oct 2011 20:25:45 +1100 Subject: [engines guide] Point out that we need to run rake db:migrate after creating user model --- railties/guides/source/engines.textile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index d191fe07b6..42c99d3405 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -416,6 +416,8 @@ To keep it simple in this case, the application will have a class called +User+ rails g model user name:string +The +rake db:migrate+ command needs to be run here to ensure that our application has the +users+ table for future use. + Also to keep it simple, the posts form will have a new text field called +author_name_+ where users can elect to put their name. The engine will then take this name and create a new +User+ object from it or find one that already has that name, and then associate the post with it. First, the +author_name+ text field needs to be added to the +app/views/blorgh/posts/_form.html.erb+ partial inside the engine. This can be added above the +title+ field with this code: -- cgit v1.2.3 From cb95cca02674acf1f52d4c8f1a70fa22617e35d9 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Mon, 17 Oct 2011 20:26:37 +1100 Subject: [engines guide] missed a spot in the 'Using a class section' + further text for 'Configuring an engine' section --- railties/guides/source/engines.textile | 71 +++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index 42c99d3405..6e27d823a7 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -445,11 +445,78 @@ private end
-By defining that the +author+ association's object is represented by the +User+ class a link is established between the engine and the application. Just before a post is saved it will be associated with a record from the +users+ table of the application. +By defining that the +author+ association's object is represented by the +User+ class a link is established between the engine and the application. There needs to be a way of associating the records in the +blorgh_posts+ table with the records in the +users+ table. Because the association is called +author+, there should be an +author_id+ column added to the +blorgh_posts+ table. + +To generate this new column, run this command within the engine: + + +$ rails g migration add_author_to_blorgh_posts author:references + + +NOTE: Due to the migration's name, Rails will automatically know that you want to add a column to a specific table and write that into the migration for you. You don't need to tell it any more than this. + +This migration will need to be run on the application. To do that, it must first be copied using this command: + + +$ rake blorgh:install:migrations + + +NOTE: Notice here that only _one_ migration was copied over here. This is because the first two migrations were copied over the first time this command was run. + +And then migrated using this command: + + +$ rake db:migrate + + +Now with all the pieces in place, an action will take place that will associate an author -- represented by a record in the +users+ table -- with a post, represented by the +blorgh_posts+ table from the engine. + +Finally, the author's name should be displayed on the post's page. Add this code above the "Title" output inside +app/views/blorgh/posts/show.html.erb+: + + +

+ Author: + <%= @post.author.name %> +

+
+ +NOTE: For posts created previously, this will break the +show+ page for them. We recommend deleting these posts and starting again, or manually assigning an author using +rails c+. h4. Configuring an engine -The next step is to make the class that represents a +User+ in the application customizable for the engine. This is because, as explained before, that class may not always be +User+. +The next step is to make the class that represents a +User+ in the application customizable for the engine. This is because, as explained before, that class may not always be +User+. To make this customizable, the engine will have a configuration setting called +user_class+ that will be used to specify what the class representing users is inside the application. + +To define this configuration setting, you should use a +mattr_accessor+ inside the +Blorgh+ module for the engine, located at +lib/blorgh.rb+ inside the engine. Inside this module, put this line: + + +mattr_accessor :user_class + + +This method works like its brothers +attr_accessor+ and +cattr_accessor+, but provides a setter and getter method on the module with the specified name. To use it, it must be referenced using +Blorgh.user_class+. + +The next step is switching the +Blorgh::Post+ model over to this new setting. For the +belongs_to+ association inside this model (+app/models/blorgh/post.rb+), it will now become this: + + +belongs_to :author, :class_name => Blorgh.user_class + + +The +set_author+ method also located in this class should also use this class: + + +self.author = Blorgh.user_class.constantize.find_or_create_by_name(author_name) + + +To set this configuration setting within the application, an initializer should be used. By using an initializer, the configuration will be set up before the application starts and makes references to the classes of the engine which may depend on this configuration setting existing. + +Create a new initializer at +config/initializers/blorgh.rb+ inside the application where the +blorgh+ engine is installed and put this content in it: + + +Blorgh.user_class = "User" + + +WARNING: It's very important here to use the +String+ version of the class, rather than the class itself. If you were to use the class, Rails would attempt to load that class and then reference the related table, which could lead to problems if the table wasn't already existing. Therefore, a +String+ should be used and then converted to a class using +constantize+ in the engine later on. + + h3. Overriding engine functionality -- cgit v1.2.3 From 86919a6da17e164e6bb9e1f355052e552929141e Mon Sep 17 00:00:00 2001 From: Pascal Lindelauf Date: Mon, 17 Oct 2011 14:45:45 +0200 Subject: Clarified that the config.assets.initialize_on_precompile directive needs to be set in application.rb --- railties/guides/source/asset_pipeline.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/asset_pipeline.textile b/railties/guides/source/asset_pipeline.textile index afaf0f6fe3..6eb4ae49e3 100644 --- a/railties/guides/source/asset_pipeline.textile +++ b/railties/guides/source/asset_pipeline.textile @@ -349,7 +349,7 @@ bundle exec rake assets:precompile For faster asset precompiles, you can partially load your application by setting -+config.assets.initialize_on_precompile+ to false, though in that case templates ++config.assets.initialize_on_precompile+ to false in +config/application.rb+, though in that case templates cannot see application objects or methods. *Heroku requires this to be false.* WARNING: If you set +config.assets.initialize_on_precompile+ to false, be sure to -- cgit v1.2.3 From 826a85069627060c11baf932423702b1228dd4df Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Mon, 17 Oct 2011 19:14:29 +0530 Subject: fix a typo and slightly reword has_secure_password comment --- activemodel/lib/active_model/secure_password.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index 7a109d9a52..db78864c67 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -32,8 +32,8 @@ module ActiveModel # User.find_by_name("david").try(:authenticate, "notright") # => nil # User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user def has_secure_password - # Load bcrypt-ruby only when has_secured_password is used to avoid make ActiveModel - # (and by extension the entire framework) dependent on a binary library. + # Load bcrypt-ruby only when has_secure_password is used. + # This is to avoid ActiveModel (and by extension the entire framework) being dependent on a binary library. gem 'bcrypt-ruby', '~> 3.0.0' require 'bcrypt' -- cgit v1.2.3 From 8dffc62a9b957c91575f7c014f50806e86d64505 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Mon, 17 Oct 2011 19:15:24 +0530 Subject: use variables from test setup --- activemodel/test/cases/secure_password_test.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb index 6950c3be1f..4338a3fc53 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -10,15 +10,13 @@ class SecurePasswordTest < ActiveModel::TestCase end test "blank password" do - user = User.new - user.password = '' - assert !user.valid?, 'user should be invalid' + @user.password = '' + assert !@user.valid?, 'user should be invalid' end test "nil password" do - user = User.new - user.password = nil - assert !user.valid?, 'user should be invalid' + @user.password = nil + assert !@user.valid?, 'user should be invalid' end test "password must be present" do -- cgit v1.2.3 From 8510a0bb5a8a65e4bc067ee5a7d98aae965b47a5 Mon Sep 17 00:00:00 2001 From: Aaron Christy Date: Sat, 15 Oct 2011 19:53:36 -0400 Subject: Exclude _destroy parameter in :all_blank check (issue #2937) --- activerecord/CHANGELOG | 5 +++++ activerecord/lib/active_record/nested_attributes.rb | 5 +++-- activerecord/test/cases/nested_attributes_test.rb | 8 ++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 0f6a31d679..46076dac61 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -29,6 +29,11 @@ [Andrés Mejía] +* Fix nested attributes bug where _destroy parameter is taken into account + during :reject_if => :all_blank (fixes #2937) + + [Aaron Christy] + *Rails 3.1.1 (October 7, 2011)* * Add deprecation for the preload_associations method. Fixes #3022. diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 2dbebfcaf8..d2065d701f 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -220,7 +220,7 @@ module ActiveRecord # validates_presence_of :member # end module ClassMethods - REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |_, value| value.blank? } } + REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } } # Defines an attributes writer for the specified association(s). If you # are using attr_protected or attr_accessible, then you @@ -239,7 +239,8 @@ module ActiveRecord # is specified, a record will be built for all attribute hashes that # do not have a _destroy value that evaluates to true. # Passing :all_blank instead of a Proc will create a proc - # that will reject a record where all the attributes are blank. + # that will reject a record where all the attributes are blank excluding + # any value for _destroy. # [:limit] # Allows you to specify the maximum number of the associated records that # can be processed with the nested attributes. If the size of the diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 67a9ed6cd8..2ae9cb4888 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -45,6 +45,14 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase end end + def test_should_not_build_a_new_record_using_reject_all_even_if_destroy_is_given + pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") + pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => '', :_destroy => '0'}] + pirate.save! + + assert pirate.birds_with_reject_all_blank.empty? + end + def test_should_not_build_a_new_record_if_reject_all_blank_returns_false pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => ''}] -- cgit v1.2.3 From 91be318120654ad698cfb24c63194e3dfe754b4c Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Mon, 17 Oct 2011 23:13:42 +0530 Subject: Bump sprockets --- actionpack/actionpack.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 96d583730a..062ebed4c2 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -24,7 +24,7 @@ Gem::Specification.new do |s| s.add_dependency('rack', '~> 1.3.2') s.add_dependency('rack-test', '~> 0.6.1') s.add_dependency('journey', '~> 1.0.0') - s.add_dependency('sprockets', '~> 2.0.2') + s.add_dependency('sprockets', '~> 2.0.3') s.add_dependency('erubis', '~> 2.7.0') s.add_development_dependency('tzinfo', '~> 0.3.29') -- cgit v1.2.3 From 124bb176380387b65c8643208fa50c475d8f2a49 Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Tue, 18 Oct 2011 11:09:59 +0530 Subject: Bump Rack to 1.3.5 --- actionpack/actionpack.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 96d583730a..78b9a4d5a8 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.add_dependency('rack-cache', '~> 1.1') s.add_dependency('builder', '~> 3.0.0') s.add_dependency('i18n', '~> 0.6') - s.add_dependency('rack', '~> 1.3.2') + s.add_dependency('rack', '~> 1.3.5') s.add_dependency('rack-test', '~> 0.6.1') s.add_dependency('journey', '~> 1.0.0') s.add_dependency('sprockets', '~> 2.0.2') -- cgit v1.2.3 From d21405a07ce9264ed92b9c7385141e05ccea565c Mon Sep 17 00:00:00 2001 From: "Rahul P. Chaudhari" Date: Mon, 17 Oct 2011 00:30:07 +0530 Subject: Added test case for postgresql database --- railties/test/generators/app_generator_test.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 133efb872f..2dcce4e884 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -143,6 +143,16 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_config_postgresql_database + run_generator([destination_root, "-d", "postgresql"]) + assert_file "config/database.yml", /postgresql/ + unless defined?(JRUBY_VERSION) + assert_file "Gemfile", /^gem\s+["']pg["']$/ + else + assert_file "Gemfile", /^gem\s+["']activerecord-jdbcpostgresql-adapter["']$/ + end + end + def test_config_jdbcmysql_database run_generator([destination_root, "-d", "jdbcmysql"]) assert_file "config/database.yml", /mysql/ -- cgit v1.2.3 From c547e968f5709f174ee13818b35429cbe585b5dd Mon Sep 17 00:00:00 2001 From: Steven Anderson Date: Mon, 17 Oct 2011 16:28:00 +0100 Subject: Added environment rake task to engines --- railties/lib/rails/tasks/engine.rake | 1 + 1 file changed, 1 insertion(+) diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake index 2152e811f5..eea8abe7d2 100644 --- a/railties/lib/rails/tasks/engine.rake +++ b/railties/lib/rails/tasks/engine.rake @@ -2,6 +2,7 @@ task "load_app" do namespace :app do load APP_RAKEFILE end + task :environment => "app:environment" if !defined?(ENGINE_PATH) || !ENGINE_PATH ENGINE_PATH = find_engine_path(APP_RAKEFILE) -- cgit v1.2.3 From 40d1555091433d827e23e92e9f816e11a2db679b Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 18 Oct 2011 17:06:32 +0100 Subject: Added test for rake environment in an engine --- railties/test/railties/shared_tests.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb index 21fde49ff7..7653e52d26 100644 --- a/railties/test/railties/shared_tests.rb +++ b/railties/test/railties/shared_tests.rb @@ -21,6 +21,23 @@ module RailtiesTest assert_match "alert()", last_response.body end + def test_rake_environment_can_be_called_in_the_engine_or_plugin + boot_rails + + @plugin.write "Rakefile", <<-RUBY + APP_RAKEFILE = '#{app_path}/Rakefile' + load 'rails/tasks/engine.rake' + task :foo => :environment do + puts "Task ran" + end + RUBY + + Dir.chdir(@plugin.path) do + output = `bundle exec rake foo` + assert_match "Task ran", output + end + end + def test_copying_migrations @plugin.write "db/migrate/1_create_users.rb", <<-RUBY class CreateUsers < ActiveRecord::Migration -- cgit v1.2.3 From 76af2818a6b729955ad799b5346da7ed115440ea Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 18 Oct 2011 10:07:40 -0700 Subject: use now() for dates in pg --- activerecord/test/cases/migration_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 4b5dd18be8..6ce7ae7877 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -475,7 +475,7 @@ if ActiveRecord::Base.connection.supports_migrations? elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings Person.connection.execute "insert into people (wealth, created_at, updated_at) values ('12345678901234567890.0123456789', 0, 0)" else - Person.connection.execute "insert into people (wealth, created_at, updated_at) values (12345678901234567890.0123456789, 0, 0)" + Person.connection.execute "insert into people (wealth, created_at, updated_at) values (12345678901234567890.0123456789, now(), now())" end # SELECT -- cgit v1.2.3 From 7f184aaf4352140c7e3bd33761058bb05737e11b Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 18 Oct 2011 16:03:58 -0200 Subject: Use again Rack's ContentLength middleware --- railties/lib/rails/commands/server.rb | 1 - railties/lib/rails/rack.rb | 1 - railties/lib/rails/rack/content_length.rb | 38 ------------------------------- 3 files changed, 40 deletions(-) delete mode 100644 railties/lib/rails/rack/content_length.rb diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index 23392276d5..71e3711ba1 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -78,7 +78,6 @@ module Rails middlewares = [] middlewares << [Rails::Rack::LogTailer, log_path] unless options[:daemonize] middlewares << [Rails::Rack::Debugger] if options[:debugger] - middlewares << [Rails::Rack::ContentLength] Hash.new(middlewares) end diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb index d4a41b217e..d1ee96f7fd 100644 --- a/railties/lib/rails/rack.rb +++ b/railties/lib/rails/rack.rb @@ -1,6 +1,5 @@ module Rails module Rack - autoload :ContentLength, "rails/rack/content_length" autoload :Debugger, "rails/rack/debugger" autoload :Logger, "rails/rack/logger" autoload :LogTailer, "rails/rack/log_tailer" diff --git a/railties/lib/rails/rack/content_length.rb b/railties/lib/rails/rack/content_length.rb deleted file mode 100644 index 6839af4152..0000000000 --- a/railties/lib/rails/rack/content_length.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'action_dispatch' -require 'rack/utils' - -module Rails - module Rack - # Sets the Content-Length header on responses with fixed-length bodies. - class ContentLength - include ::Rack::Utils - - def initialize(app, sendfile=nil) - @app = app - @sendfile = sendfile - end - - def call(env) - status, headers, body = @app.call(env) - headers = HeaderHash.new(headers) - - if !STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i) && - !headers['Content-Length'] && - !headers['Transfer-Encoding'] && - !(@sendfile && headers[@sendfile]) - - old_body = body - body, length = [], 0 - old_body.each do |part| - body << part - length += bytesize(part) - end - old_body.close if old_body.respond_to?(:close) - headers['Content-Length'] = length.to_s - end - - [status, headers, body] - end - end - end -end \ No newline at end of file -- cgit v1.2.3 From d44702c830669e2647c073133bd6b09d7016424d Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 18 Oct 2011 16:14:38 -0200 Subject: Use again Rack's ContentLength middleware part 2 --- railties/lib/rails/commands/server.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index 71e3711ba1..33f9ac017c 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -78,6 +78,7 @@ module Rails middlewares = [] middlewares << [Rails::Rack::LogTailer, log_path] unless options[:daemonize] middlewares << [Rails::Rack::Debugger] if options[:debugger] + middlewares << [Rack::ContentLength] Hash.new(middlewares) end -- cgit v1.2.3 From 6a28c512e358a95bbabd24af9d2b0608df787158 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 18 Oct 2011 11:12:18 -0700 Subject: reset prepared statement when schema changes imapact statement results. fixes #3335 --- .../connection_adapters/postgresql_adapter.rb | 54 +++++++++++++++++----- .../test/cases/adapters/postgresql/schema_test.rb | 8 ++++ 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index d859843260..b7f346e050 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -278,6 +278,11 @@ module ActiveRecord cache.clear end + def delete(sql_key) + dealloc cache[sql_key] + cache.delete sql_key + end + private def cache @cache[$$] @@ -1030,27 +1035,54 @@ module ActiveRecord end private + FEATURE_NOT_SUPPORTED = "0A000" # :nodoc: + def exec_no_cache(sql, binds) @connection.async_exec(sql) end def exec_cache(sql, binds) - sql_key = "#{schema_search_path}-#{sql}" + begin + stmt_key = prepare_statement sql + + # Clear the queue + @connection.get_last_result + @connection.send_query_prepared(stmt_key, binds.map { |col, val| + type_cast(val, col) + }) + @connection.block + @connection.get_last_result + rescue PGError => e + # Get the PG code for the failure. Annoyingly, the code for + # prepared statements whose return value may have changed is + # FEATURE_NOT_SUPPORTED. Check here for more details: + # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573 + code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE) + if FEATURE_NOT_SUPPORTED == code + @statements.delete sql_key(sql) + retry + else + raise e + end + end + end + + # Returns the statement identifier for the client side cache + # of statements + def sql_key(sql) + "#{schema_search_path}-#{sql}" + end + + # Prepare the statement if it hasn't been prepared, return + # the statement key. + def prepare_statement(sql) + sql_key = sql_key(sql) unless @statements.key? sql_key nextkey = @statements.next_key @connection.prepare nextkey, sql @statements[sql_key] = nextkey end - - key = @statements[sql_key] - - # Clear the queue - @connection.get_last_result - @connection.send_query_prepared(key, binds.map { |col, val| - type_cast(val, col) - }) - @connection.block - @connection.get_last_result + @statements[sql_key] end # The internal PostgreSQL identifier of the money data type. diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index b01eabc840..c8f8714f66 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -62,6 +62,14 @@ class SchemaTest < ActiveRecord::TestCase @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE" end + def test_schema_change_with_prepared_stmt + @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]] + @connection.exec_query "alter table developers add column zomg int", 'sql', [] + @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]] + ensure + @connection.exec_query "alter table developers drop column if exists zomg", 'sql', [] + end + def test_table_exists? [Thing1, Thing2, Thing3, Thing4].each do |klass| name = klass.table_name -- cgit v1.2.3 From bed5825f775bdf2a1af6eec9dc1f4071dbde5ead Mon Sep 17 00:00:00 2001 From: Alexey Vakhov Date: Tue, 18 Oct 2011 22:57:55 +0400 Subject: Remove superfluous assignment in cookies --- actionpack/lib/action_dispatch/middleware/cookies.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 8c4615c0c1..a4ffd40a66 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -174,7 +174,7 @@ module ActionDispatch options = { :value => value } end - value = @cookies[key.to_s] = value + @cookies[key.to_s] = value handle_options(options) -- cgit v1.2.3 From c2f3c9bf56534f8f50b4e47a7e18e822f6600bbe Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 18 Oct 2011 13:30:01 -0700 Subject: only use now() on pg! --- activerecord/test/cases/migration_test.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 6ce7ae7877..b3f1785f44 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -474,8 +474,10 @@ if ActiveRecord::Base.connection.supports_migrations? Person.connection.execute "insert into people (id, wealth, created_at, updated_at) values (people_seq.nextval, 12345678901234567890.0123456789, 0, 0)" elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings Person.connection.execute "insert into people (wealth, created_at, updated_at) values ('12345678901234567890.0123456789', 0, 0)" - else + elsif current_adapter?(:PostgreSQLAdapter) Person.connection.execute "insert into people (wealth, created_at, updated_at) values (12345678901234567890.0123456789, now(), now())" + else + Person.connection.execute "insert into people (wealth, created_at, updated_at) values (12345678901234567890.0123456789, 0, 0)" end # SELECT -- cgit v1.2.3 From 044e6ac245c24b91f7f815e2155bb7ac030ce831 Mon Sep 17 00:00:00 2001 From: Andy Lindeman Date: Tue, 18 Oct 2011 23:45:56 -0300 Subject: Fixes the defaults for config.cache_classes --- railties/guides/source/configuring.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index baf944cf8d..58b92e7f9e 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -64,7 +64,7 @@ NOTE. The +config.asset_path+ configuration is ignored if the asset pipeline is * +config.autoload_paths+ accepts an array of paths from which Rails will autoload constants. Default is all directories under +app+. -* +config.cache_classes+ controls whether or not application classes and modules should be reloaded on each request. Defaults to true in development mode, and false in test and production modes. Can also be enabled with +threadsafe!+. +* +config.cache_classes+ controls whether or not application classes and modules should be reloaded on each request. Defaults to false in development mode, and true in test and production modes. Can also be enabled with +threadsafe!+. * +config.action_view.cache_template_loading+ controls whether or not templates should be reloaded on each request. Defaults to whatever is set for +config.cache_classes+. -- cgit v1.2.3 From 3a746f7c48936bac1c08dcf229c7c8fc74fdfc13 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Wed, 19 Oct 2011 12:31:06 -0500 Subject: Use toplevel Rack::ContentLength --- railties/lib/rails/commands/server.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index 33f9ac017c..20484a10c8 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -78,7 +78,7 @@ module Rails middlewares = [] middlewares << [Rails::Rack::LogTailer, log_path] unless options[:daemonize] middlewares << [Rails::Rack::Debugger] if options[:debugger] - middlewares << [Rack::ContentLength] + middlewares << [::Rack::ContentLength] Hash.new(middlewares) end -- cgit v1.2.3 From afde6fdd5ef3e6b0693a7e330777e85ef4cffddb Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 19 Oct 2011 12:59:33 -0500 Subject: Added X-Request-Id tracking and TaggedLogging to easily log that and other production concerns --- actionpack/CHANGELOG | 2 + actionpack/lib/action_dispatch.rb | 1 + actionpack/lib/action_dispatch/http/request.rb | 10 ++++ .../lib/action_dispatch/middleware/request_id.rb | 38 ++++++++++++ actionpack/test/dispatch/request_id_test.rb | 59 +++++++++++++++++++ activesupport/CHANGELOG | 7 +++ activesupport/lib/active_support.rb | 1 + activesupport/lib/active_support/tagged_logging.rb | 68 ++++++++++++++++++++++ activesupport/test/tagged_logging_test.rb | 34 +++++++++++ railties/CHANGELOG | 2 + .../config/environments/production.rb | 3 + railties/lib/rails/application.rb | 2 + railties/lib/rails/application/bootstrap.rb | 4 +- railties/lib/rails/application/configuration.rb | 2 +- railties/lib/rails/rack.rb | 1 + railties/lib/rails/rack/logger.rb | 4 +- railties/lib/rails/rack/tagged_logging.rb | 39 +++++++++++++ 17 files changed, 272 insertions(+), 5 deletions(-) create mode 100644 actionpack/lib/action_dispatch/middleware/request_id.rb create mode 100644 actionpack/test/dispatch/request_id_test.rb create mode 100644 activesupport/lib/active_support/tagged_logging.rb create mode 100644 activesupport/test/tagged_logging_test.rb create mode 100644 railties/lib/rails/rack/tagged_logging.rb diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 29992a36b1..e7886facb9 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.2.0 (unreleased)* +* Added ActionDispatch::RequestId middleware that'll make a unique X-Request-Id header available to the response and enables the ActionDispatch::Request#uuid method. This makes it easy to trace requests from end-to-end in the stack and to identify individual requests in mixed logs like Syslog [DHH] + * Limit the number of options for select_year to 1000. Pass the :max_years_allowed option to set your own limit. diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 7f972fc281..c13850f378 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -47,6 +47,7 @@ module ActionDispatch end autoload_under 'middleware' do + autoload :RequestId autoload :BestStandardsSupport autoload :Callbacks autoload :Cookies diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 37d0a3e0b8..7a5237dcf3 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -177,6 +177,16 @@ module ActionDispatch @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s end + # Returns the unique request id, which is based off either the X-Request-Id header that can + # be generated by a firewall, load balancer, or web server or by the RequestId middleware + # (which sets the action_dispatch.request_id environment variable). + # + # This unique ID is useful for tracing a request from end-to-end as part of logging or debugging. + # This relies on the rack variable set by the ActionDispatch::RequestId middleware. + def uuid + @uuid ||= env["action_dispatch.request_id"] + end + # Returns the lowercase name of the HTTP server software. def server_software (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb new file mode 100644 index 0000000000..968ad6c28d --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -0,0 +1,38 @@ +require 'digest/md5' + +module ActionDispatch + # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through + # ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header. + # + # The unique request id is either based off the X-Request-Id header in the request, which would typically be generated + # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the + # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only. + # + # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files + # from multiple pieces of the stack. + class RequestId + def initialize(app) + @app = app + end + + def call(env) + env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id + + status, headers, body = @app.call(env) + + headers["X-Request-Id"] = env["action_dispatch.request_id"] + [ status, headers, body ] + end + + private + def external_request_id(env) + if env["HTTP_X_REQUEST_ID"].present? + env["HTTP_X_REQUEST_ID"].gsub(/[^\w\d\-]/, "").first(255) + end + end + + def internal_request_id + SecureRandom.uuid + end + end +end diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb new file mode 100644 index 0000000000..bdadbf6644 --- /dev/null +++ b/actionpack/test/dispatch/request_id_test.rb @@ -0,0 +1,59 @@ +require 'abstract_unit' + +class RequestIdTest < ActiveSupport::TestCase + test "passing on the request id from the outside" do + assert_equal "external-uu-rid", stub_request('HTTP_X_REQUEST_ID' => 'external-uu-rid').uuid + end + + test "ensure that only alphanumeric uurids are accepted" do + assert_equal "X-Hacked-HeaderStuff", stub_request('HTTP_X_REQUEST_ID' => '; X-Hacked-Header: Stuff').uuid + end + + test "ensure that 255 char limit on the request id is being enforced" do + assert_equal "X" * 255, stub_request('HTTP_X_REQUEST_ID' => 'X' * 500).uuid + end + + test "generating a request id when none is supplied" do + assert_match /\w+-\w+-\w+-\w+-\w+/, stub_request.uuid + end + + private + def stub_request(env = {}) + ActionDispatch::RequestId.new(->(env) { [ 200, env, [] ] }).call(env) + ActionDispatch::Request.new(env) + end +end + +# FIXME: Testing end-to-end doesn't seem to work +# +# class RequestIdResponseTest < ActionDispatch::IntegrationTest +# class TestController < ActionController::Base +# def index +# head :ok +# end +# end +# +# test "request id is passed all the way to the response" do +# with_test_route_set do +# get '/' +# puts @response.headers.inspect +# assert_equal "internal-uu-rid", @response.headers["X-Request-Id"] +# end +# end +# +# +# private +# def with_test_route_set +# with_routing do |set| +# set.draw do +# match ':action', to: ::RequestIdResponseTest::TestController +# end +# +# @app = self.class.build_app(set) do |middleware| +# middleware.use ActionDispatch::RequestId +# end +# +# yield +# end +# end +# end \ No newline at end of file diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 63f406cd9f..d3a838cff0 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,12 @@ *Rails 3.2.0 (unreleased)* +* Added ActiveSupport:TaggedLogging that can wrap any standard Logger class to provide tagging capabilities [DHH] + + Logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) + Logger.tagged("BCX") { Logger.info "Stuff" } # Logs "[BCX] Stuff" + Logger.tagged("BCX", "Jason") { Logger.info "Stuff" } # Logs "[BCX] [Jason] Stuff" + Logger.tagged("BCX") { Logger.tagged("Jason") { Logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff" + * Added safe_constantize that constantizes a string but returns nil instead of an exception if the constant (or part of it) does not exist [Ryan Oblak] * ActiveSupport::OrderedHash is now marked as extractable when using Array#extract_options! [Prem Sichanugrist] diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index cc9ea5cffa..ff78e718f2 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -71,6 +71,7 @@ module ActiveSupport autoload :OrderedOptions autoload :Rescuable autoload :StringInquirer + autoload :TaggedLogging autoload :XmlMini end diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb new file mode 100644 index 0000000000..0d8504bc1f --- /dev/null +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -0,0 +1,68 @@ +module ActiveSupport + # Wraps any standard Logger class to provide tagging capabilities. Examples: + # + # Logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) + # Logger.tagged("BCX") { Logger.info "Stuff" } # Logs "[BCX] Stuff" + # Logger.tagged("BCX", "Jason") { Logger.info "Stuff" } # Logs "[BCX] [Jason] Stuff" + # Logger.tagged("BCX") { Logger.tagged("Jason") { Logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff" + # + # This is used by the default Rails.logger as configured by Railties to make it easy to stamp log lines + # with subdomains, request ids, and anything else to aid debugging of multi-user production applications. + class TaggedLogging + def initialize(logger) + @logger = logger + @tags = [] + end + + def tagged(*tags) + new_tags = Array.wrap(tags).flatten + @tags += new_tags + yield + ensure + new_tags.size.times { @tags.pop } + end + + + def add(severity, message = nil, progname = nil, &block) + @logger.add(severity, "#{tags}#{message}", progname, &block) + end + + + def fatal(progname = nil, &block) + add(@logger.class::FATAL, progname, &block) + end + + def error(progname = nil, &block) + add(@logger.class::ERROR, progname, &block) + end + + def warn(progname = nil, &block) + add(@logger.class::WARN, progname, &block) + end + + def info(progname = nil, &block) + add(@logger.class::INFO, progname, &block) + end + + def debug(progname = nil, &block) + add(@logger.class::DEBUG, progname, &block) + end + + def unknown(progname = nil, &block) + add(@logger.class::UNKNOWN, progname, &block) + end + + + def method_missing(method, *args) + @logger.send(method, *args) + end + + + private + def tags + if @tags.any? + @tags.collect { |tag| "[#{tag}]" }.join(" ") + " " + end + end + end +end diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb new file mode 100644 index 0000000000..a1504c6ce4 --- /dev/null +++ b/activesupport/test/tagged_logging_test.rb @@ -0,0 +1,34 @@ +require 'abstract_unit' +require 'active_support/core_ext/logger' +require 'active_support/tagged_logging' + +class TaggedLoggingTest < ActiveSupport::TestCase + setup do + @output = StringIO.new + @logger = ActiveSupport::TaggedLogging.new(Logger.new(@output)) + end + + test "tagged once" do + @logger.tagged("BCX") { @logger.info "Funky time" } + assert_equal "[BCX] Funky time\n", @output.string + end + + test "tagged twice" do + @logger.tagged("BCX") { @logger.tagged("Jason") { @logger.info "Funky time" } } + assert_equal "[BCX] [Jason] Funky time\n", @output.string + end + + test "tagged thrice at once" do + @logger.tagged("BCX", "Jason", "New") { @logger.info "Funky time" } + assert_equal "[BCX] [Jason] [New] Funky time\n", @output.string + end + + test "mixed levels of tagging" do + @logger.tagged("BCX") do + @logger.tagged("Jason") { @logger.info "Funky time" } + @logger.info "Junky time!" + end + + assert_equal "[BCX] [Jason] Funky time\n[BCX] Junky time!\n", @output.string + end +end diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 187dd2428f..181019f851 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.2.0 (unreleased)* +* Added Rails::Rack::TaggedLogging middleware by default that will apply any tags set in config.log_tags to the newly ActiveSupport::TaggedLogging Rails.logger. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications [DHH] + * Default options to `rails new` can be set in ~/.railsrc [Guillermo Iguaran] * Added destroy alias to Rails engines. [Guillermo Iguaran] diff --git a/railties/guides/code/getting_started/config/environments/production.rb b/railties/guides/code/getting_started/config/environments/production.rb index 6ab63d30a6..4618191d6b 100644 --- a/railties/guides/code/getting_started/config/environments/production.rb +++ b/railties/guides/code/getting_started/config/environments/production.rb @@ -33,6 +33,9 @@ Blog::Application.configure do # See everything in the log (default is :info) # config.log_level = :debug + # Prepend all log lines with the following tags + # config.log_tags = [ :subdomain, :uuid ] + # Use a different logger for distributed setups # config.logger = SyslogLogger.new diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index cbb2d23238..a097cfd1be 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -164,6 +164,8 @@ module Rails middleware.use ::Rack::Lock unless config.allow_concurrency middleware.use ::Rack::Runtime middleware.use ::Rack::MethodOverride + middleware.use ::ActionDispatch::RequestId + middleware.use ::Rails::Rack::TaggedLogging, config.log_tags middleware.use ::Rails::Rack::Logger # must come after Rack::MethodOverride to properly log overridden methods middleware.use ::ActionDispatch::ShowExceptions, config.consider_all_requests_local middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb index 0aff05b681..c2cb121e42 100644 --- a/railties/lib/rails/application/bootstrap.rb +++ b/railties/lib/rails/application/bootstrap.rb @@ -24,12 +24,12 @@ module Rails initializer :initialize_logger, :group => :all do Rails.logger ||= config.logger || begin path = config.paths["log"].first - logger = ActiveSupport::BufferedLogger.new(path) + logger = ActiveSupport::TaggedLogging.new(ActiveSupport::BufferedLogger.new(path)) logger.level = ActiveSupport::BufferedLogger.const_get(config.log_level.to_s.upcase) logger.auto_flushing = false if Rails.env.production? logger rescue StandardError - logger = ActiveSupport::BufferedLogger.new(STDERR) + logger = ActiveSupport::TaggedLogging.new(ActiveSupport::BufferedLogger.new(STDERR)) logger.level = ActiveSupport::BufferedLogger::WARN logger.warn( "Rails Error: Unable to access log file. Please ensure that #{path} exists and is chmod 0666. " + diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 448521d2f0..8f5b28faf8 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -8,7 +8,7 @@ module Rails attr_accessor :allow_concurrency, :asset_host, :asset_path, :assets, :cache_classes, :cache_store, :consider_all_requests_local, :dependency_loading, :filter_parameters, - :force_ssl, :helpers_paths, :logger, :preload_frameworks, + :force_ssl, :helpers_paths, :logger, :log_tags, :preload_frameworks, :reload_plugins, :secret_token, :serve_static_assets, :ssl_options, :static_cache_control, :session_options, :time_zone, :whiny_nils diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb index d1ee96f7fd..b78293e570 100644 --- a/railties/lib/rails/rack.rb +++ b/railties/lib/rails/rack.rb @@ -3,5 +3,6 @@ module Rails autoload :Debugger, "rails/rack/debugger" autoload :Logger, "rails/rack/logger" autoload :LogTailer, "rails/rack/log_tailer" + autoload :TaggedLogging, "rails/rack/tagged_logging" end end diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb index 3be262de08..4d388c4d10 100644 --- a/railties/lib/rails/rack/logger.rb +++ b/railties/lib/rails/rack/logger.rb @@ -21,8 +21,8 @@ module Rails request = ActionDispatch::Request.new(env) path = request.filtered_path - info "\n\nStarted #{request.request_method} \"#{path}\" " \ - "for #{request.ip} at #{Time.now.to_default_s}" + info "\n\n" + info "Started #{request.request_method} \"#{path}\" for #{request.ip} at #{Time.now.to_default_s}" end def after_dispatch(env) diff --git a/railties/lib/rails/rack/tagged_logging.rb b/railties/lib/rails/rack/tagged_logging.rb new file mode 100644 index 0000000000..7980319b37 --- /dev/null +++ b/railties/lib/rails/rack/tagged_logging.rb @@ -0,0 +1,39 @@ +module Rails + module Rack + # Enables easy tagging of any logging activity that occurs within the Rails request cycle. The tags are configured via the + # config.log_tags setting. The tags can either be strings, procs taking a request argument, or the symbols :uuid or :subdomain. + # The latter two are then automatically expanded to request.uuid and request.subdaomins.first -- the two most common tags + # desired in production logs. + class TaggedLogging + def initialize(app, tags = nil) + @app, @tags = app, tags + end + + def call(env) + if @tags + Rails.logger.tagged(compute_tags(env)) { @app.call(env) } + else + @app.call(env) + end + end + + private + def compute_tags(env) + request = ActionDispatch::Request.new(env) + + @tags.collect do |tag| + case tag + when Proc + tag.call(request) + when :uuid + request.uuid + when :subdomain + request.subdomains.first + else + tag + end + end + end + end + end +end -- cgit v1.2.3 From 4a4927f753d7f41c0903ab3adfb73be8490c382e Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 19 Oct 2011 13:24:28 -0500 Subject: Simplify TaggedLogging symbol shortcuts (thanks Jose!) --- railties/lib/rails/rack/tagged_logging.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/railties/lib/rails/rack/tagged_logging.rb b/railties/lib/rails/rack/tagged_logging.rb index 7980319b37..c519d7c3e6 100644 --- a/railties/lib/rails/rack/tagged_logging.rb +++ b/railties/lib/rails/rack/tagged_logging.rb @@ -1,9 +1,8 @@ module Rails module Rack # Enables easy tagging of any logging activity that occurs within the Rails request cycle. The tags are configured via the - # config.log_tags setting. The tags can either be strings, procs taking a request argument, or the symbols :uuid or :subdomain. - # The latter two are then automatically expanded to request.uuid and request.subdaomins.first -- the two most common tags - # desired in production logs. + # config.log_tags setting. The tags can either be strings, procs taking a request argument, or symbols representing method + # names on request (so :uuid will result in request.uuid being added as a tag). class TaggedLogging def initialize(app, tags = nil) @app, @tags = app, tags @@ -25,10 +24,8 @@ module Rails case tag when Proc tag.call(request) - when :uuid - request.uuid - when :subdomain - request.subdomains.first + when Symbol + request.send(tag) else tag end -- cgit v1.2.3 From ddbb2cae3146fc125375a0aae61bbaca9328b797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 19 Oct 2011 22:15:25 +0300 Subject: Require securerandom as it is the proper dependency. --- actionpack/lib/action_dispatch/middleware/request_id.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index 968ad6c28d..c515798d48 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -1,4 +1,4 @@ -require 'digest/md5' +require 'securerandom' module ActionDispatch # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through -- cgit v1.2.3 From 1b50207ed3a2f545763b8c0b3afcd35d9d36d4e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 19 Oct 2011 22:17:54 +0300 Subject: Require missing string access dependency. --- actionpack/lib/action_dispatch/middleware/request_id.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index c515798d48..cdddc55aae 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -1,4 +1,5 @@ require 'securerandom' +require 'active_support/core_ext/string/access' module ActionDispatch # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through -- cgit v1.2.3 From c9ca86c29d4f4d8e1181c20ac0da8e1027a14344 Mon Sep 17 00:00:00 2001 From: Martin Svalin Date: Wed, 19 Oct 2011 18:47:28 +0200 Subject: New #added? method on ActiveModel::Errors The #added? method makes it possible to check if a specific error has been added, using the same parameters as for #add. --- activemodel/CHANGELOG | 2 ++ activemodel/lib/active_model/errors.rb | 30 ++++++++++++++++++------ activemodel/test/cases/errors_test.rb | 43 ++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 7 deletions(-) diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG index 3d26d646b0..bf077bef35 100644 --- a/activemodel/CHANGELOG +++ b/activemodel/CHANGELOG @@ -1,3 +1,5 @@ +* Added ActiveModel::Errors#added? to check if a specific error has been added [Martin Svalin] + * Add ability to define strict validation(with :strict => true option) that always raises exception when fails [Bogdan Gusiev] * Deprecate "Model.model_name.partial_path" in favor of "model.to_partial_path" [Grant Hutchins, Peter Jaros] diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index f90030641d..6aa0d2a16f 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -213,13 +213,7 @@ module ActiveModel # If +message+ is a symbol, it will be translated using the appropriate scope (see +translate_error+). # If +message+ is a proc, it will be called, allowing for things like Time.now to be used within an error. def add(attribute, message = nil, options = {}) - message ||= :invalid - - if message.is_a?(Symbol) - message = generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS)) - elsif message.is_a?(Proc) - message = message.call - end + message = normalize_message(attribute, message, options) if options[:strict] raise ActiveModel::StrictValidationFailed, message end @@ -244,6 +238,15 @@ module ActiveModel end end + # Returns true if an error on the attribute with the given message is present, false otherwise. + # +message+ is treated the same as for +add+. + # p.errors.add :name, :blank + # p.errors.added? :name, :blank # => true + def added?(attribute, message = nil, options = {}) + message = normalize_message(attribute, message, options) + self[attribute].include? message + end + # Returns all the full error messages in an array. # # class Company @@ -329,6 +332,19 @@ module ActiveModel I18n.translate(key, options) end + + private + def normalize_message(attribute, message, options) + message ||= :invalid + + if message.is_a?(Symbol) + generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS)) + elsif message.is_a?(Proc) + message.call + else + message + end + end end class StrictValidationFailed < StandardError diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index 784a2b2709..8ddedb160a 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -80,6 +80,49 @@ class ErrorsTest < ActiveModel::TestCase assert_equal ["can not be blank"], person.errors[:name] end + test "added? should be true if that error was added" do + person = Person.new + person.errors.add(:name, "can not be blank") + assert person.errors.added?(:name, "can not be blank") + end + + test "added? should handle when message is a symbol" do + person = Person.new + person.errors.add(:name, :blank) + assert person.errors.added?(:name, :blank) + end + + test "added? should handle when message is a proc" do + person = Person.new + message = Proc.new { "can not be blank" } + person.errors.add(:name, message) + assert person.errors.added?(:name, message) + end + + test "added? should default message to :invalid" do + person = Person.new + person.errors.add(:name, :invalid) + assert person.errors.added?(:name) + end + + test "added? should be true when several errors are present, and we ask for one of them" do + person = Person.new + person.errors.add(:name, "can not be blank") + person.errors.add(:name, "is invalid") + assert person.errors.added?(:name, "can not be blank") + end + + test "added? should be false if no errors are present" do + person = Person.new + assert !person.errors.added?(:name) + end + + test "added? should be false when an error is present, but we check for another error" do + person = Person.new + person.errors.add(:name, "is invalid") + assert !person.errors.added?(:name, "can not be blank") + end + test 'should respond to size' do person = Person.new person.errors.add(:name, "can not be blank") -- cgit v1.2.3 From ada78066fdbccffb1da092a2470211fa252b3c99 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 19 Oct 2011 14:45:42 -0500 Subject: Blah, SecureRandom#uuid is not supported in 1.8.7 -- cant wait for Rails 4.0 to drop compatibility with 1.8.x --- actionpack/lib/action_dispatch/middleware/request_id.rb | 2 +- actionpack/test/dispatch/request_id_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index cdddc55aae..4728e9f71e 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -33,7 +33,7 @@ module ActionDispatch end def internal_request_id - SecureRandom.uuid + SecureRandom.hex(16) end end end diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb index bdadbf6644..230ff54889 100644 --- a/actionpack/test/dispatch/request_id_test.rb +++ b/actionpack/test/dispatch/request_id_test.rb @@ -14,7 +14,7 @@ class RequestIdTest < ActiveSupport::TestCase end test "generating a request id when none is supplied" do - assert_match /\w+-\w+-\w+-\w+-\w+/, stub_request.uuid + assert_match /\w+/, stub_request.uuid end private -- cgit v1.2.3 From 18dbfcb36369ebb800a22325f689ff4cf27ef467 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 19 Oct 2011 14:53:34 -0500 Subject: Programatically define the log level methods and use the Logger constants instead (SyslogLogger didnt define them as I expected) --- activesupport/lib/active_support/tagged_logging.rb | 31 ++++++---------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index 0d8504bc1f..0cabb528ef 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -1,3 +1,5 @@ +require 'logger' + module ActiveSupport # Wraps any standard Logger class to provide tagging capabilities. Examples: # @@ -27,29 +29,12 @@ module ActiveSupport @logger.add(severity, "#{tags}#{message}", progname, &block) end - - def fatal(progname = nil, &block) - add(@logger.class::FATAL, progname, &block) - end - - def error(progname = nil, &block) - add(@logger.class::ERROR, progname, &block) - end - - def warn(progname = nil, &block) - add(@logger.class::WARN, progname, &block) - end - - def info(progname = nil, &block) - add(@logger.class::INFO, progname, &block) - end - - def debug(progname = nil, &block) - add(@logger.class::DEBUG, progname, &block) - end - - def unknown(progname = nil, &block) - add(@logger.class::UNKNOWN, progname, &block) + %w( fatal error warn info debug unkown ).each do |severity| + eval <<-EOM, nil, __FILE__, __LINE__ + 1 + def #{severity}(progname = nil, &block) + add(Logger::#{severity.upcase}, progname, &block) + end + EOM end -- cgit v1.2.3 From f1fecd9b4e38c289b678bc2aadb406265963c528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 19 Oct 2011 22:09:36 +0200 Subject: Make tests run on 1.8.x, add integration setup. --- .../lib/action_dispatch/middleware/request_id.rb | 5 +- actionpack/test/dispatch/request_id_test.rb | 84 ++++++++++++---------- 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index 4728e9f71e..f4d721f9bf 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -18,20 +18,19 @@ module ActionDispatch def call(env) env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id - status, headers, body = @app.call(env) headers["X-Request-Id"] = env["action_dispatch.request_id"] [ status, headers, body ] end - + private def external_request_id(env) if env["HTTP_X_REQUEST_ID"].present? env["HTTP_X_REQUEST_ID"].gsub(/[^\w\d\-]/, "").first(255) end end - + def internal_request_id SecureRandom.hex(16) end diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb index 230ff54889..ece8353810 100644 --- a/actionpack/test/dispatch/request_id_test.rb +++ b/actionpack/test/dispatch/request_id_test.rb @@ -8,52 +8,58 @@ class RequestIdTest < ActiveSupport::TestCase test "ensure that only alphanumeric uurids are accepted" do assert_equal "X-Hacked-HeaderStuff", stub_request('HTTP_X_REQUEST_ID' => '; X-Hacked-Header: Stuff').uuid end - + test "ensure that 255 char limit on the request id is being enforced" do assert_equal "X" * 255, stub_request('HTTP_X_REQUEST_ID' => 'X' * 500).uuid end - + test "generating a request id when none is supplied" do assert_match /\w+/, stub_request.uuid end private - def stub_request(env = {}) - ActionDispatch::RequestId.new(->(env) { [ 200, env, [] ] }).call(env) - ActionDispatch::Request.new(env) - end + + def stub_request(env = {}) + ActionDispatch::RequestId.new(lambda { |env| [ 200, env, [] ] }).call(env) + ActionDispatch::Request.new(env) + end end -# FIXME: Testing end-to-end doesn't seem to work -# -# class RequestIdResponseTest < ActionDispatch::IntegrationTest -# class TestController < ActionController::Base -# def index -# head :ok -# end -# end -# -# test "request id is passed all the way to the response" do -# with_test_route_set do -# get '/' -# puts @response.headers.inspect -# assert_equal "internal-uu-rid", @response.headers["X-Request-Id"] -# end -# end -# -# -# private -# def with_test_route_set -# with_routing do |set| -# set.draw do -# match ':action', to: ::RequestIdResponseTest::TestController -# end -# -# @app = self.class.build_app(set) do |middleware| -# middleware.use ActionDispatch::RequestId -# end -# -# yield -# end -# end -# end \ No newline at end of file +class RequestIdResponseTest < ActionDispatch::IntegrationTest + class TestController < ActionController::Base + def index + head :ok + end + end + + test "request id is passed all the way to the response" do + with_test_route_set do + get '/' + assert_match(/\w+/, @response.headers["X-Request-Id"]) + end + end + + test "request id given on request is passed all the way to the response" do + with_test_route_set do + get '/', {}, 'HTTP_X_REQUEST_ID' => 'X' * 500 + assert_equal "X" * 255, @response.headers["X-Request-Id"] + end + end + + + private + + def with_test_route_set + with_routing do |set| + set.draw do + match '/', :to => ::RequestIdResponseTest::TestController.action(:index) + end + + @app = self.class.build_app(set) do |middleware| + middleware.use ActionDispatch::RequestId + end + + yield + end + end +end \ No newline at end of file -- cgit v1.2.3 From 4ef74536940ea4c8c7f8c2cb0252bfe5f0db6fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 19 Oct 2011 22:10:43 +0200 Subject: Load object/blank and make use of presence. --- actionpack/lib/action_dispatch/middleware/request_id.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index f4d721f9bf..d7bb9c58df 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -1,5 +1,6 @@ require 'securerandom' require 'active_support/core_ext/string/access' +require 'active_support/core_ext/object/blank' module ActionDispatch # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through @@ -26,8 +27,8 @@ module ActionDispatch private def external_request_id(env) - if env["HTTP_X_REQUEST_ID"].present? - env["HTTP_X_REQUEST_ID"].gsub(/[^\w\d\-]/, "").first(255) + if request_id = env["HTTP_X_REQUEST_ID"].presence + request_id.gsub(/[^\w\d\-]/, "").first(255) end end -- cgit v1.2.3 From 6c126015a676c376f1646713fbb739049a783238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 19 Oct 2011 22:26:56 +0200 Subject: Ensure TaggegLogging is thread safe. --- activesupport/lib/active_support/tagged_logging.rb | 35 ++++++++++++++-------- activesupport/test/tagged_logging_test.rb | 30 ++++++++++++++++++- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index 0cabb528ef..aff416a9eb 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -13,20 +13,20 @@ module ActiveSupport class TaggedLogging def initialize(logger) @logger = logger - @tags = [] + @tags = Hash.new { |h,k| h[k] = [] } end - def tagged(*tags) - new_tags = Array.wrap(tags).flatten - @tags += new_tags + def tagged(*new_tags) + tags = current_tags + new_tags = Array.wrap(new_tags).flatten + tags.concat new_tags yield ensure - new_tags.size.times { @tags.pop } + new_tags.size.times { tags.pop } end - def add(severity, message = nil, progname = nil, &block) - @logger.add(severity, "#{tags}#{message}", progname, &block) + @logger.add(severity, "#{tags_text}#{message}", progname, &block) end %w( fatal error warn info debug unkown ).each do |severity| @@ -37,17 +37,26 @@ module ActiveSupport EOM end + def flush(*args) + @tags.delete(Thread.current) + @logger.flush(*args) if @logger.respond_to?(:flush) + end def method_missing(method, *args) @logger.send(method, *args) end - - private - def tags - if @tags.any? - @tags.collect { |tag| "[#{tag}]" }.join(" ") + " " - end + protected + + def tags_text + tags = current_tags + if tags.any? + tags.collect { |tag| "[#{tag}]" }.join(" ") + " " end + end + + def current_tags + @tags[Thread.current] + end end end diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb index a1504c6ce4..b12b12f32c 100644 --- a/activesupport/test/tagged_logging_test.rb +++ b/activesupport/test/tagged_logging_test.rb @@ -3,9 +3,15 @@ require 'active_support/core_ext/logger' require 'active_support/tagged_logging' class TaggedLoggingTest < ActiveSupport::TestCase + class MyLogger < ::Logger + def flush(*) + info "[FLUSHED]" + end + end + setup do @output = StringIO.new - @logger = ActiveSupport::TaggedLogging.new(Logger.new(@output)) + @logger = ActiveSupport::TaggedLogging.new(MyLogger.new(@output)) end test "tagged once" do @@ -23,6 +29,28 @@ class TaggedLoggingTest < ActiveSupport::TestCase assert_equal "[BCX] [Jason] [New] Funky time\n", @output.string end + test "keeps each tag in their own thread" do + @logger.tagged("BCX") do + Thread.new do + @logger.tagged("OMG") { @logger.info "Cool story bro" } + end.join + @logger.info "Funky time" + end + assert_equal "[OMG] Cool story bro\n[BCX] Funky time\n", @output.string + end + + test "cleans up the taggings on flush" do + @logger.tagged("BCX") do + Thread.new do + @logger.tagged("OMG") do + @logger.flush + @logger.info "Cool story bro" + end + end.join + end + assert_equal "[FLUSHED]\nCool story bro\n", @output.string + end + test "mixed levels of tagging" do @logger.tagged("BCX") do @logger.tagged("Jason") { @logger.info "Funky time" } -- cgit v1.2.3 From c83d9a11c00bc13e1f8f0fa0e8fb6185cacd5fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 19 Oct 2011 22:39:11 +0200 Subject: Unify logger and taggedlogging middleware as both address logging concerns. --- railties/CHANGELOG | 2 +- railties/lib/rails/application.rb | 3 +-- railties/lib/rails/rack.rb | 1 - railties/lib/rails/rack/logger.rb | 38 +++++++++++++++++++++---------- railties/lib/rails/rack/tagged_logging.rb | 36 ----------------------------- 5 files changed, 28 insertions(+), 52 deletions(-) delete mode 100644 railties/lib/rails/rack/tagged_logging.rb diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 181019f851..7f7b24804d 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,6 +1,6 @@ *Rails 3.2.0 (unreleased)* -* Added Rails::Rack::TaggedLogging middleware by default that will apply any tags set in config.log_tags to the newly ActiveSupport::TaggedLogging Rails.logger. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications [DHH] +* Updated Rails::Rack::Logger middleware to apply any tags set in config.log_tags to the newly ActiveSupport::TaggedLogging Rails.logger. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications [DHH] * Default options to `rails new` can be set in ~/.railsrc [Guillermo Iguaran] diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index a097cfd1be..82fffe86bb 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -165,8 +165,7 @@ module Rails middleware.use ::Rack::Runtime middleware.use ::Rack::MethodOverride middleware.use ::ActionDispatch::RequestId - middleware.use ::Rails::Rack::TaggedLogging, config.log_tags - middleware.use ::Rails::Rack::Logger # must come after Rack::MethodOverride to properly log overridden methods + middleware.use ::Rails::Rack::Logger, config.log_tags # must come after Rack::MethodOverride to properly log overridden methods middleware.use ::ActionDispatch::ShowExceptions, config.consider_all_requests_local middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies if config.action_dispatch.x_sendfile_header.present? diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb index b78293e570..d1ee96f7fd 100644 --- a/railties/lib/rails/rack.rb +++ b/railties/lib/rails/rack.rb @@ -3,6 +3,5 @@ module Rails autoload :Debugger, "rails/rack/debugger" autoload :Logger, "rails/rack/logger" autoload :LogTailer, "rails/rack/log_tailer" - autoload :TaggedLogging, "rails/rack/tagged_logging" end end diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb index 4d388c4d10..89de10c83d 100644 --- a/railties/lib/rails/rack/logger.rb +++ b/railties/lib/rails/rack/logger.rb @@ -1,32 +1,46 @@ require 'active_support/core_ext/time/conversions' +require 'active_support/core_ext/object/blank' module Rails module Rack # Log the request started and flush all loggers after it. class Logger < ActiveSupport::LogSubscriber - def initialize(app) - @app = app + def initialize(app, tags=nil) + @app, @tags = app, tags.presence end def call(env) - before_dispatch(env) - @app.call(env) - ensure - after_dispatch(env) + if @tags + Rails.logger.tagged(compute_tags(env)) { call_app(env) } + else + call_app(env) + end end protected - def before_dispatch(env) + def call_app(env) request = ActionDispatch::Request.new(env) path = request.filtered_path - - info "\n\n" - info "Started #{request.request_method} \"#{path}\" for #{request.ip} at #{Time.now.to_default_s}" + Rails.logger.info "\n\nStarted #{request.request_method} \"#{path}\" for #{request.ip} at #{Time.now.to_default_s}" + @app.call(env) + ensure + ActiveSupport::LogSubscriber.flush_all! end - def after_dispatch(env) - ActiveSupport::LogSubscriber.flush_all! + def compute_tags(env) + request = ActionDispatch::Request.new(env) + + @tags.collect do |tag| + case tag + when Proc + tag.call(request) + when Symbol + request.send(tag) + else + tag + end + end end end end diff --git a/railties/lib/rails/rack/tagged_logging.rb b/railties/lib/rails/rack/tagged_logging.rb deleted file mode 100644 index c519d7c3e6..0000000000 --- a/railties/lib/rails/rack/tagged_logging.rb +++ /dev/null @@ -1,36 +0,0 @@ -module Rails - module Rack - # Enables easy tagging of any logging activity that occurs within the Rails request cycle. The tags are configured via the - # config.log_tags setting. The tags can either be strings, procs taking a request argument, or symbols representing method - # names on request (so :uuid will result in request.uuid being added as a tag). - class TaggedLogging - def initialize(app, tags = nil) - @app, @tags = app, tags - end - - def call(env) - if @tags - Rails.logger.tagged(compute_tags(env)) { @app.call(env) } - else - @app.call(env) - end - end - - private - def compute_tags(env) - request = ActionDispatch::Request.new(env) - - @tags.collect do |tag| - case tag - when Proc - tag.call(request) - when Symbol - request.send(tag) - else - tag - end - end - end - end - end -end -- cgit v1.2.3 From 930dc335d7067b17191257faf03fcecba3351caa Mon Sep 17 00:00:00 2001 From: Martin Svalin Date: Wed, 19 Oct 2011 23:06:08 +0200 Subject: Removed mention of deprecated ActiveModel::Errors#on --- activemodel/lib/active_model/errors.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index d91e4a2b6a..6db3ca7340 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -205,9 +205,8 @@ module ActiveModel messages.dup end - # Adds +message+ to the error messages on +attribute+, which will be returned on a call to - # on(attribute) for the same attribute. More than one error can be added to the same - # +attribute+ in which case an array will be returned on a call to on(attribute). + # Adds +message+ to the error messages on +attribute+. More than one error can be added to the same + # +attribute+. # If no +message+ is supplied, :invalid is assumed. # # If +message+ is a symbol, it will be translated using the appropriate scope (see +translate_error+). -- cgit v1.2.3 From 1359152345e84b951becf687ab6f26e30a9af3ce Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 19 Oct 2011 16:16:44 -0500 Subject: Encourage use of tagged logging even when using a different logger --- railties/guides/code/getting_started/config/environments/production.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/code/getting_started/config/environments/production.rb b/railties/guides/code/getting_started/config/environments/production.rb index 4618191d6b..dee8acfdfe 100644 --- a/railties/guides/code/getting_started/config/environments/production.rb +++ b/railties/guides/code/getting_started/config/environments/production.rb @@ -37,7 +37,7 @@ Blog::Application.configure do # config.log_tags = [ :subdomain, :uuid ] # Use a different logger for distributed setups - # config.logger = SyslogLogger.new + # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) # Use a different cache store in production # config.cache_store = :mem_cache_store -- cgit v1.2.3 From be27bf17e309fcb43fab45cfa1d31cea074a6a72 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 19 Oct 2011 16:24:00 -0500 Subject: Fix the proper production.rb --- .../rails/app/templates/config/environments/production.rb.tt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 64e2c09467..50f2df3d35 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 @@ -33,8 +33,11 @@ # See everything in the log (default is :info) # config.log_level = :debug + # Prepend all log lines with the following tags + # config.log_tags = [ :subdomain, :uuid ] + # Use a different logger for distributed setups - # config.logger = SyslogLogger.new + # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) # Use a different cache store in production # config.cache_store = :mem_cache_store -- cgit v1.2.3 From 9694ca1a3bd070886802a6b9f7532adfe5b60cc4 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Wed, 19 Oct 2011 18:26:19 +1100 Subject: [engines guide] mention that we cover similar ground to the Getting Started Guide --- railties/guides/source/engines.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index 6e27d823a7..73bbe486e5 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -98,7 +98,7 @@ Also in the test directory is the +test/integration+ directory, where integratio h3. Providing engine functionality -The engine that this guide covers will provide posting and commenting functionality. +The engine that this guide covers will provide posting and commenting functionality and follows a similar thread to the "Getting Started Guide":getting-started.html, with some new twists. h4. Generating a post resource -- cgit v1.2.3 From 8ede74e73a45b16a81064a175b3174848469498b Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Thu, 20 Oct 2011 08:46:20 +1100 Subject: [engines guide] Rather than calling name, require a to_s method to be defined on the 'User' class --- railties/guides/source/engines.textile | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index 73bbe486e5..02054c972a 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -476,11 +476,26 @@ Finally, the author's name should be displayed on the post's page. Add this code

Author: - <%= @post.author.name %> + <%= @post.author %>

-NOTE: For posts created previously, this will break the +show+ page for them. We recommend deleting these posts and starting again, or manually assigning an author using +rails c+. +WARNING: For posts created previously, this will break the +show+ page for them. We recommend deleting these posts and starting again, or manually assigning an author using +rails c+. + +By outputting +@post.author+ using the +<%=+ tag the +to_s+ method will be called on the object. By default, this will look quite ugly: + + +# + + +This is undesirable and it would be much better to have the user's name there. To do this, add a +to_s+ method to the +User+ class within the application: + + +def to_s + name + + +Now instead of the ugly Ruby object output the author's name will be displayed. h4. Configuring an engine -- cgit v1.2.3 From 1cc6105d4dd088e3f2b8026e7e49f4e9d8d7d6ea Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Thu, 20 Oct 2011 08:46:41 +1100 Subject: [engines guide] wrap up 'Configuring an Engine' section --- railties/guides/source/engines.textile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index 02054c972a..f0d8f1a165 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -531,7 +531,9 @@ Blorgh.user_class = "User" WARNING: It's very important here to use the +String+ version of the class, rather than the class itself. If you were to use the class, Rails would attempt to load that class and then reference the related table, which could lead to problems if the table wasn't already existing. Therefore, a +String+ should be used and then converted to a class using +constantize+ in the engine later on. +Go ahead and try to create a new post. You will see that it works exactly in the same way as before, except this time the engine is using the configuration setting in +config/initializers/blorgh.rb+ to learn what the class is. +There are now no strict dependencies on what the class is, only what the class's API must be. The engine simply requires this class to define a +find_or_create_by_name+ method which returns an object of that class to be associated with a post when it's created. h3. Overriding engine functionality -- cgit v1.2.3 From 4e9729df3f03ee16445082b7d386ee14b4370461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 20 Oct 2011 00:38:20 +0200 Subject: Don't use :path in Gemfile for --dev except for Rails. --- railties/lib/rails/generators/app_base.rb | 2 +- railties/lib/rails/generators/rails/app/app_generator.rb | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 134d86fab0..40961f0c3e 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -138,7 +138,7 @@ module Rails if options.dev? <<-GEMFILE.strip_heredoc gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}' - gem 'journey', :path => '#{Rails::Generators::JOURNEY_DEV_PATH}' + gem 'journey', :git => 'git://github.com/rails/journey.git' GEMFILE elsif options.edge? <<-GEMFILE.strip_heredoc diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index c8648d19f8..3e32f758a4 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -144,8 +144,6 @@ module Rails # We need to store the RAILS_DEV_PATH in a constant, otherwise the path # can change in Ruby 1.8.7 when we FileUtils.cd. RAILS_DEV_PATH = File.expand_path("../../../../../..", File.dirname(__FILE__)) - JOURNEY_DEV_PATH = File.expand_path("../../../../../../../journey", File.dirname(__FILE__)) - RESERVED_NAMES = %w[application destroy benchmarker profiler plugin runner test] class AppGenerator < AppBase -- cgit v1.2.3 From 3e2d35b05006099163f6492019536bbfa1d82509 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Thu, 20 Oct 2011 17:10:46 +1100 Subject: [engines guide] add missing end for to_s method --- railties/guides/source/engines.textile | 1 + 1 file changed, 1 insertion(+) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index f0d8f1a165..5a12896b8e 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -493,6 +493,7 @@ This is undesirable and it would be much better to have the user's name there. T def to_s name +end Now instead of the ugly Ruby object output the author's name will be displayed. -- cgit v1.2.3 From 13e6f0cea83b2699e76b4f21ff87b267514d18d6 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Thu, 20 Oct 2011 17:32:34 +1100 Subject: [engines guide] some fixes for the author id migration including proof of not copying over other migrations again --- railties/guides/source/engines.textile | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index 5a12896b8e..0688dc4c99 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -450,10 +450,10 @@ By defining that the +author+ association's object is represented by the +User+ To generate this new column, run this command within the engine: -$ rails g migration add_author_to_blorgh_posts author:references +$ rails g migration add_author_id_to_blorgh_posts author_id:integer -NOTE: Due to the migration's name, Rails will automatically know that you want to add a column to a specific table and write that into the migration for you. You don't need to tell it any more than this. +NOTE: Due to the migration's name and the column specification after it, Rails will automatically know that you want to add a column to a specific table and write that into the migration for you. You don't need to tell it any more than this. This migration will need to be run on the application. To do that, it must first be copied using this command: @@ -461,9 +461,15 @@ This migration will need to be run on the application. To do that, it must first $ rake blorgh:install:migrations -NOTE: Notice here that only _one_ migration was copied over here. This is because the first two migrations were copied over the first time this command was run. +Notice here that only _one_ migration was copied over here. This is because the first two migrations were copied over the first time this command was run. -And then migrated using this command: + + NOTE: Migration [timestamp]_create_blorgh_posts.rb from blorgh has been skipped. Migration with the same name already exists. + NOTE: Migration [timestamp]_create_blorgh_comments.rb from blorgh has been skipped. Migration with the same name already exists. + Copied migration [timestamp]_add_author_id_to_blorgh_posts.rb from blorgh + + +Run this migration using this command: $ rake db:migrate -- cgit v1.2.3 From 951a325c99ef2845f29ef95c85230ac2e835a31c Mon Sep 17 00:00:00 2001 From: Marc Bowes Date: Thu, 20 Oct 2011 10:00:42 +0300 Subject: Remove the unneeded `\d` when sanitizing `X-Request-Id`. --- actionpack/lib/action_dispatch/middleware/request_id.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index d7bb9c58df..bee446c8a5 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -28,7 +28,7 @@ module ActionDispatch private def external_request_id(env) if request_id = env["HTTP_X_REQUEST_ID"].presence - request_id.gsub(/[^\w\d\-]/, "").first(255) + request_id.gsub(/[^\w\-]/, "").first(255) end end -- cgit v1.2.3 From 5c5d5b3d2510d20998eafdd87a4458e0406e1690 Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Thu, 20 Oct 2011 12:59:27 +0530 Subject: Adding ActionDispatch::RequestId in middleware test --- railties/test/application/middleware_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 093cb6ca2a..4703a59326 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -30,6 +30,7 @@ module ApplicationTests "ActiveSupport::Cache::Strategy::LocalCache", "Rack::Runtime", "Rack::MethodOverride", + "ActionDispatch::RequestId", "Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods "ActionDispatch::ShowExceptions", "ActionDispatch::RemoteIp", -- cgit v1.2.3 From ee1223c1ee90899819a88c39db3e77b3692c68ee Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Thu, 20 Oct 2011 18:30:50 +1100 Subject: [engines guide] explanation of overriding views + referencing routes from an engine within an application --- railties/guides/source/engines.textile | 59 ++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile index 0688dc4c99..da56f3d0ed 100644 --- a/railties/guides/source/engines.textile +++ b/railties/guides/source/engines.textile @@ -542,10 +542,65 @@ Go ahead and try to create a new post. You will see that it works exactly in the There are now no strict dependencies on what the class is, only what the class's API must be. The engine simply requires this class to define a +find_or_create_by_name+ method which returns an object of that class to be associated with a post when it's created. -h3. Overriding engine functionality +h3. Extending engine functionality -TODO: Cover how to override engine functionality in the engine, such as controllers and views. +This section looks at overriding or adding functionality to the views, controllers and models provided by an engine. +h4. Overriding views + +When Rails looks for a view to render, it will first look in the +app/views+ directory of the application. If it cannot find the view there, then it will check in the +app/views+ directories of all engines which have this directory. + +In the +blorgh+ engine, there is a currently a file at +app/views/blorgh/posts/index.html.erb+. When the engine is asked to render the view for +Blorgh::PostsController+'s +index+ action, it will first see if it can find it at +app/views/blorgh/posts/index.html.erb+ within the application and then if it cannot it will look inside the engine. + +By overriding this view in the application, by simply creating a new file at +app/views/blorgh/posts/index.html.erb+, you can completely change what this view would normally output. + +Try this now by creating a new file at +app/views/blorgh/posts/index.html.erb+ and put this content in it: + + +

Posts

+<%= link_to "New Post", new_post_path %> +<% @posts.each do |post| %> +

<%= post.title %>

+ By <%= post.author %> + <%= simple_format(post.text) %> +
+<% end %> +
+ +Rather than looking like the default scaffold, the page will now look like this: + +!images/engines_post_override.png(Engine scaffold overriden)! + +h4. Controllers + +TODO: Explain how to extend a controller. IDEA: I like Devise's +devise :controllers => { "sessions" => "sessions" }+ idea. Perhaps we could incorporate that into the guide? + +h4. Models + +TODO: Explain how to extend models provided by an engine. + +h4. Routes + +Within the application, you may wish to link to some area within the engine. Due to the fact that the engine's routes are isolated (by the +isolate_namespace+ call within the +lib/blorgh/engine.rb+ file), you will need to prefix these routes with the engine name. This means rather than having something such as: + + +<%= link_to "Blog posts", posts_path %> + + +It needs to be written as: + + +<%= link_to "Blog posts", blorgh.posts_path %> + + +This allows for the engine _and_ the application to both have a +posts_path+ routing helper and to not interfere with each other. You may also reference another engine's routes from inside an engine using this same syntax. + +If you wish to reference the application inside the engine in a similar way, use the +main_app+ helper: + + +<%= link_to "Home", main_app.root_path %> + + TODO: Mention how to use assets within an engine? TODO: Mention how to depend on external gems, like RedCarpet. -- cgit v1.2.3 From 66880cfe416b75f472762f2c6b485bad9759c751 Mon Sep 17 00:00:00 2001 From: Andrew France Date: Thu, 20 Oct 2011 12:32:11 +0200 Subject: Correct ActionMailer subject i18n lookup scope. The :actionmailer i18n scope part is not used and according to https://github.com/rails/rails/pull/2542 the implementation is correct. --- actionmailer/lib/action_mailer/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index ac49702ced..8f2c567e3e 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -524,7 +524,7 @@ module ActionMailer #:nodoc: # # * :subject - The subject of the message, if this is omitted, Action Mailer will # ask the Rails I18n class for a translated :subject in the scope of - # [:actionmailer, mailer_scope, action_name] or if this is missing, will translate the + # [mailer_scope, action_name] or if this is missing, will translate the # humanized version of the action_name # * :to - Who the message is destined for, can be a string of addresses, or an array # of addresses. -- cgit v1.2.3 From 274c3fad5087306a64ca91d044756221f5ff862c Mon Sep 17 00:00:00 2001 From: Martin Svalin Date: Thu, 20 Oct 2011 16:31:10 +0200 Subject: Fixed misleading docs for String#to_formatted_s(:db) --- activesupport/lib/active_support/core_ext/array/conversions.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 3b22e8b4f9..f3d06ecb2f 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -39,10 +39,10 @@ class Array # # Blog.all.to_formatted_s # => "First PostSecond PostThird Post" # - # Adding in the :db argument as the format yields a prettier - # output: + # Adding in the :db argument as the format yields a comma separated + # id list: # - # Blog.all.to_formatted_s(:db) # => "First Post,Second Post,Third Post" + # Blog.all.to_formatted_s(:db) # => "1,2,3" def to_formatted_s(format = :default) case format when :db -- cgit v1.2.3 From ee9d9fb5fab3cfaa5055e5fb4225b720d3818c94 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 20 Oct 2011 08:44:55 -0700 Subject: Merge pull request #3258 from ileitch/3-1-stable Postgres: Do not attempt to deallocate a statement if the connection is no longer active. --- .../connection_adapters/postgresql_adapter.rb | 8 +++++++- .../cases/adapters/postgresql/statement_pool_test.rb | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index b7f346e050..3d084bb178 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -289,7 +289,13 @@ module ActiveRecord end def dealloc(key) - @connection.query "DEALLOCATE #{key}" + @connection.query "DEALLOCATE #{key}" if connection_active? + end + + def connection_active? + @connection.status == PGconn::CONNECTION_OK + rescue PGError + false end end diff --git a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb index a82c6f67d6..f1c4b85126 100644 --- a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb @@ -2,6 +2,16 @@ require 'cases/helper' module ActiveRecord::ConnectionAdapters class PostgreSQLAdapter < AbstractAdapter + class InactivePGconn + def query(*args) + raise PGError + end + + def status + PGconn::CONNECTION_BAD + end + end + class StatementPoolTest < ActiveRecord::TestCase def test_cache_is_per_pid return skip('must support fork') unless Process.respond_to?(:fork) @@ -18,6 +28,12 @@ module ActiveRecord::ConnectionAdapters Process.waitpid pid assert $?.success?, 'process should exit successfully' end + + def test_dealloc_does_not_raise_on_inactive_connection + cache = StatementPool.new InactivePGconn.new, 10 + cache['foo'] = 'bar' + assert_nothing_raised { cache.clear } + end end end end -- cgit v1.2.3 From 48e1f8ba4c02ed81ba70fee459de8bc569197fc4 Mon Sep 17 00:00:00 2001 From: Pepe Hipolito Date: Thu, 20 Oct 2011 15:37:35 -0400 Subject: Fixed typo. a long the way => along the way --- railties/guides/source/initialization.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile index 32b41fdd2c..2299573a9f 100644 --- a/railties/guides/source/initialization.textile +++ b/railties/guides/source/initialization.textile @@ -7,7 +7,7 @@ This guide explains the internals of the initialization process in Rails as of R endprologue. -This guide goes through every single file, class and method call that is required to boot up the Ruby on Rails stack for a default Rails 3.1 application, explaining each part in detail a long the way. For this guide, we will be focusing on how the two most common methods (+rails server+ and Passenger) boot a Rails application. +This guide goes through every single file, class and method call that is required to boot up the Ruby on Rails stack for a default Rails 3.1 application, explaining each part in detail along the way. For this guide, we will be focusing on how the two most common methods (+rails server+ and Passenger) boot a Rails application. NOTE: Paths in this guide are relative to Rails or a Rails application unless otherwise specified. -- cgit v1.2.3 From 0cd93a6e2bbcdd2ead0d6e4b739dfd8def04d8d5 Mon Sep 17 00:00:00 2001 From: Pepe Hipolito Date: Thu, 20 Oct 2011 15:58:51 -0400 Subject: Fixed typo. Ruby will know to look for this file at => Ruby will know how to look for this file at --- railties/guides/source/initialization.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile index 2299573a9f..f88405a2fd 100644 --- a/railties/guides/source/initialization.textile +++ b/railties/guides/source/initialization.textile @@ -243,7 +243,7 @@ In this file there are a lot of lines such as this inside the +ActiveSupport+ mo autoload :Inflector
-Due to the overriding of the +autoload+ method, Ruby will know to look for this file at +activesupport/lib/active_support/inflector.rb+ when the +Inflector+ class is first referenced. +Due to the overriding of the +autoload+ method, Ruby will know how to look for this file at +activesupport/lib/active_support/inflector.rb+ when the +Inflector+ class is first referenced. The +active_support/lib/active_support/version.rb+ that is also required here simply defines an +ActiveSupport::VERSION+ constant which defines a couple of constants inside this module, the main constant of this is +ActiveSupport::VERSION::STRING+ which returns the current version of ActiveSupport. -- cgit v1.2.3 From d64e0955d04a40355e312ca4ee57b2c9a8e248cc Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Thu, 20 Oct 2011 17:13:01 -0400 Subject: Use new migration style in HABTM join table Yes, we're on Rails 3.1 now. --- activerecord/lib/active_record/associations.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 0952ea2829..2449df079a 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1424,16 +1424,12 @@ module ActiveRecord # join table with a migration such as this: # # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration - # def self.up + # def change # create_table :developers_projects, :id => false do |t| # t.integer :developer_id # t.integer :project_id # end # end - # - # def self.down - # drop_table :developers_projects - # end # end # # Adds the following methods for retrieval and query: -- cgit v1.2.3 From f0be2cb19274bb6029f84ca274a72c4d7fbee2d3 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Thu, 20 Oct 2011 17:15:28 -0400 Subject: Add some note on adding index to HABTM table --- activerecord/lib/active_record/associations.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 2449df079a..34684ad2f5 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1432,6 +1432,10 @@ module ActiveRecord # end # end # + # It's also a good idea to add indexes to each of those columns to speed up the joins process. + # However, in MySQL it is advised to add a compound index for both of the columns as MySQL only + # uses one index per table during the lookup. + # # Adds the following methods for retrieval and query: # # [collection(force_reload = false)] -- cgit v1.2.3 From c495cbcda94bec7a49a88631fc575cc04bde8e88 Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Fri, 21 Oct 2011 15:12:00 +0530 Subject: Checking blank if tag might coming nil or blank In log it should not show the empty array. --- activesupport/lib/active_support/tagged_logging.rb | 3 ++- activesupport/test/tagged_logging_test.rb | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index aff416a9eb..a59fc26d5d 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/object/blank' require 'logger' module ActiveSupport @@ -18,7 +19,7 @@ module ActiveSupport def tagged(*new_tags) tags = current_tags - new_tags = Array.wrap(new_tags).flatten + new_tags = Array.wrap(new_tags).flatten.reject(&:blank?) tags.concat new_tags yield ensure diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb index b12b12f32c..17c4214dfc 100644 --- a/activesupport/test/tagged_logging_test.rb +++ b/activesupport/test/tagged_logging_test.rb @@ -29,6 +29,11 @@ class TaggedLoggingTest < ActiveSupport::TestCase assert_equal "[BCX] [Jason] [New] Funky time\n", @output.string end + test "tagged once with blank and nil" do + @logger.tagged(nil, "", "New") { @logger.info "Funky time" } + assert_equal "[New] Funky time\n", @output.string + end + test "keeps each tag in their own thread" do @logger.tagged("BCX") do Thread.new do -- cgit v1.2.3 From d565fda5f27abf0097adf7b3e232a52f7ae5c1e9 Mon Sep 17 00:00:00 2001 From: Brian Durand Date: Fri, 21 Oct 2011 12:43:52 -0500 Subject: Fix threading issues with BufferedLogger. --- .../lib/active_support/buffered_logger.rb | 44 ++++++++++++++---- activesupport/test/buffered_logger_test.rb | 53 ++++++++++++++++++++++ 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb index 26412cd7f4..136e245859 100644 --- a/activesupport/lib/active_support/buffered_logger.rb +++ b/activesupport/lib/active_support/buffered_logger.rb @@ -25,22 +25,28 @@ module ActiveSupport # Silences the logger for the duration of the block. def silence(temporary_level = ERROR) if silencer + old_logger_level = @tmp_levels[Thread.current] begin - old_logger_level, self.level = level, temporary_level + @tmp_levels[Thread.current] = temporary_level yield self ensure - self.level = old_logger_level + if old_logger_level + @tmp_levels[Thread.current] = old_logger_level + else + @tmp_levels.delete(Thread.current) + end end else yield self end end - attr_accessor :level + attr_writer :level attr_reader :auto_flushing def initialize(log, level = DEBUG) @level = level + @tmp_levels = {} @buffer = Hash.new { |h,k| h[k] = [] } @auto_flushing = 1 @guard = Mutex.new @@ -62,8 +68,12 @@ module ActiveSupport end end + def level + @tmp_levels[Thread.current] || @level + end + def add(severity, message = nil, progname = nil, &block) - return if @level > severity + return if level > severity message = (message || (block && block.call) || progname).to_s # If a newline is necessary then create a new message ending with a newline. # Ensures that the original message is not mutated. @@ -84,7 +94,7 @@ module ActiveSupport end # end def #{severity.downcase}? # def debug? - #{severity} >= @level # DEBUG >= @level + #{severity} >= level # DEBUG >= @level end # end EOT end @@ -105,13 +115,15 @@ module ActiveSupport def flush @guard.synchronize do - buffer.each do |content| - @log.write(content) - end + write_buffer(buffer) # Important to do this even if buffer was empty or else @buffer will # accumulate empty arrays for each request where nothing was logged. clear_buffer + + # Clear buffers associated with dead threads or else spawned threads + # that don't call flush will result in a memory leak. + flush_dead_buffers end end @@ -133,5 +145,21 @@ module ActiveSupport def clear_buffer @buffer.delete(Thread.current) end + + # Find buffers created by threads that are no longer alive and flush them to the log + # in order to prevent memory leaks from spawned threads. + def flush_dead_buffers #:nodoc: + @buffer.keys.reject{|thread| thread.alive?}.each do |thread| + buffer = @buffer[thread] + write_buffer(buffer) + @buffer.delete(thread) + end + end + + def write_buffer(buffer) + buffer.each do |content| + @log.write(content) + end + end end end diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/buffered_logger_test.rb index 21049d685b..8699862d9e 100644 --- a/activesupport/test/buffered_logger_test.rb +++ b/activesupport/test/buffered_logger_test.rb @@ -198,4 +198,57 @@ class BufferedLoggerTest < Test::Unit::TestCase end assert byte_string.include?(BYTE_STRING) end + + def test_silence_only_current_thread + @logger.auto_flushing = true + run_thread_a = false + + a = Thread.new do + while !run_thread_a do + sleep(0.001) + end + @logger.info("x") + run_thread_a = false + end + + @logger.silence do + run_thread_a = true + @logger.info("a") + while run_thread_a do + sleep(0.001) + end + end + + a.join + + assert @output.string.include?("x") + assert !@output.string.include?("a") + end + + def test_flush_dead_buffers + @logger.auto_flushing = false + + a = Thread.new do + @logger.info("a") + end + + keep_running = true + b = Thread.new do + @logger.info("b") + while keep_running + sleep(0.001) + end + end + + @logger.info("x") + a.join + @logger.flush + + + assert @output.string.include?("x") + assert @output.string.include?("a") + assert !@output.string.include?("b") + + keep_running = false + end end -- cgit v1.2.3 From 2b04c2f66e3cf5abbbf118eaa1e692b9e1380e4e Mon Sep 17 00:00:00 2001 From: Brian Durand Date: Fri, 21 Oct 2011 13:13:29 -0500 Subject: Add ActionDispatch::Session::CacheStore as a generic way of storing sessions in a cache. --- actionpack/lib/action_dispatch.rb | 1 + .../middleware/session/cache_store.rb | 50 ++++++ .../test/dispatch/session/cache_store_test.rb | 181 +++++++++++++++++++++ .../source/action_controller_overview.textile | 10 +- railties/guides/source/rails_on_rack.textile | 5 +- railties/guides/source/security.textile | 4 +- 6 files changed, 243 insertions(+), 8 deletions(-) create mode 100644 actionpack/lib/action_dispatch/middleware/session/cache_store.rb create mode 100644 actionpack/test/dispatch/session/cache_store_test.rb diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index c13850f378..1e92d14542 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -83,6 +83,7 @@ module ActionDispatch 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' end autoload_under 'testing' do diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb new file mode 100644 index 0000000000..d3b6fd12fa --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb @@ -0,0 +1,50 @@ +require 'action_dispatch/middleware/session/abstract_store' +require 'rack/session/memcache' + +module ActionDispatch + module Session + # Session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful + # if you don't store critical data in your sessions and you don't need them to live for extended periods + # of time. + class CacheStore < AbstractStore + # Create a new store. The cache to use can be passed in the :cache option. If it is + # not specified, Rails.cache will be used. + def initialize(app, options = {}) + @cache = options[:cache] || Rails.cache + options[:expire_after] ||= @cache.options[:expires_in] + super + end + + # Get a session from the cache. + def get_session(env, sid) + sid ||= generate_sid + session = @cache.read(cache_key(sid)) + session ||= {} + [sid, session] + end + + # Set a session in the cache. + def set_session(env, sid, session, options) + key = cache_key(sid) + if session + @cache.write(key, session, :expires_in => options[:expire_after]) + else + @cache.delete(key) + end + sid + end + + # Remove a session from the cache. + def destroy_session(env, sid, options) + @cache.delete(cache_key(sid)) + generate_sid + end + + private + # Turn the session id into a cache key. + def cache_key(sid) + "_session_id:#{sid}" + end + end + end +end diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb new file mode 100644 index 0000000000..73e056de23 --- /dev/null +++ b/actionpack/test/dispatch/session/cache_store_test.rb @@ -0,0 +1,181 @@ +require 'abstract_unit' + +class CacheStoreTest < ActionDispatch::IntegrationTest + class TestController < ActionController::Base + def no_session_access + head :ok + end + + def set_session_value + session[:foo] = "bar" + head :ok + end + + def set_serialized_session_value + session[:foo] = SessionAutoloadTest::Foo.new + head :ok + end + + def get_session_value + render :text => "foo: #{session[:foo].inspect}" + end + + def get_session_id + render :text => "#{request.session_options[:id]}" + end + + def call_reset_session + session[:bar] + reset_session + session[:bar] = "baz" + head :ok + end + + def rescue_action(e) raise end + end + + def test_setting_and_getting_session_value + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + + get '/get_session_value' + assert_response :success + assert_equal 'foo: "bar"', response.body + end + end + + def test_getting_nil_session_value + with_test_route_set do + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + end + end + + def test_getting_session_value_after_session_reset + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + session_cookie = cookies.send(:hash_for)['_session_id'] + + get '/call_reset_session' + assert_response :success + assert_not_equal [], headers['Set-Cookie'] + + cookies << session_cookie # replace our new session_id with our old, pre-reset session_id + + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from cache" + end + end + + def test_getting_from_nonexistent_session + with_test_route_set do + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + assert_nil cookies['_session_id'], "should only create session on write, not read" + end + end + + def test_setting_session_value_after_session_reset + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + session_id = cookies['_session_id'] + + get '/call_reset_session' + assert_response :success + assert_not_equal [], headers['Set-Cookie'] + + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + + get '/get_session_id' + assert_response :success + assert_not_equal session_id, response.body + end + end + + def test_getting_session_id + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + session_id = cookies['_session_id'] + + get '/get_session_id' + assert_response :success + assert_equal session_id, response.body, "should be able to read session id without accessing the session hash" + end + end + + def test_deserializes_unloaded_class + with_test_route_set do + with_autoload_path "session_autoload_test" do + get '/set_serialized_session_value' + assert_response :success + assert cookies['_session_id'] + end + with_autoload_path "session_autoload_test" do + get '/get_session_id' + assert_response :success + end + with_autoload_path "session_autoload_test" do + get '/get_session_value' + assert_response :success + assert_equal 'foo: #', response.body, "should auto-load unloaded class" + end + end + end + + def test_doesnt_write_session_cookie_if_session_id_is_already_exists + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + + get '/get_session_value' + assert_response :success + assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists" + end + end + + def test_prevents_session_fixation + with_test_route_set do + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + session_id = cookies['_session_id'] + + reset! + + get '/set_session_value', :_session_id => session_id + assert_response :success + assert_not_equal session_id, cookies['_session_id'] + end + end + + private + def with_test_route_set + with_routing do |set| + set.draw do + match ':action', :to => ::CacheStoreTest::TestController + end + + @app = self.class.build_app(set) do |middleware| + cache = ActiveSupport::Cache::MemoryStore.new + middleware.use ActionDispatch::Session::CacheStore, :key => '_session_id', :cache => cache + middleware.delete "ActionDispatch::ShowExceptions" + end + + yield + end + end +end diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile index 5019d49686..b34c223b31 100644 --- a/railties/guides/source/action_controller_overview.textile +++ b/railties/guides/source/action_controller_overview.textile @@ -166,10 +166,10 @@ h3. Session Your application has a session for each user in which you can store small amounts of data that will be persisted between requests. The session is only available in the controller and the view and can use one of a number of different storage mechanisms: -* CookieStore - Stores everything on the client. -* DRbStore - Stores the data on a DRb server. -* MemCacheStore - Stores the data in a memcache. -* ActiveRecordStore - Stores the data in a database using Active Record. +* ActionDispatch::Session::CookieStore - Stores everything on the client. +* ActiveRecord::SessionStore - Stores the data in a database using Active Record. +* ActionDispatch::Session::CacheStore - Stores the data in the Rails cache. +* ActionDispatch::Session::MemCacheStore - Stores the data in a memcached cluster (this is a legacy implementation; consider using CacheStore instead). All session stores use a cookie to store a unique ID for each session (you must use a cookie, Rails will not allow you to pass the session ID in the URL as this is less secure). @@ -177,6 +177,8 @@ For most stores this ID is used to look up the session data on the server, e.g. The CookieStore can store around 4kB of data -- much less than the others -- but this is usually enough. Storing large amounts of data in the session is discouraged no matter which session store your application uses. You should especially avoid storing complex objects (anything other than basic Ruby objects, the most common example being model instances) in the session, as the server might not be able to reassemble them between requests, which will result in an error. +If your user sessions don't store critical data or don't need to be around for long periods (for instance if you just use the flash for messaging), you can consider using ActionDispatch::Session::CacheStore. This will store sessions using the cache implementation you have configured for your application. The advantage of this is that you can use your existing cache infrastructure for storing sessions without requiring any additional setup or administration. The downside, of course, is that the sessions will be ephemeral and could disappear at any time. + Read more about session storage in the "Security Guide":security.html. If you need a different session storage mechanism, you can change it in the +config/initializers/session_store.rb+ file: diff --git a/railties/guides/source/rails_on_rack.textile b/railties/guides/source/rails_on_rack.textile index 57c03b54dc..d6cbd84b1f 100644 --- a/railties/guides/source/rails_on_rack.textile +++ b/railties/guides/source/rails_on_rack.textile @@ -166,8 +166,9 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol |+Rack::Lock+|Sets env["rack.multithread"] flag to +true+ and wraps the application within a Mutex.| |+ActionController::Failsafe+|Returns HTTP Status +500+ to the client if an exception gets raised while dispatching.| |+ActiveRecord::QueryCache+|Enables the Active Record query cache.| -|+ActionController::Session::CookieStore+|Uses the cookie based session store.| -|+ActionController::Session::MemCacheStore+|Uses the memcached based session store.| +|+ActionDispatch::Session::CookieStore+|Uses the cookie based session store.| +|+ActionDispatch::Session::CacheStore+|Uses the Rails cache based session store.| +|+ActionDispatch::Session::MemCacheStore+|Uses the memcached based session store.| |+ActiveRecord::SessionStore+|Uses the database based session store.| |+Rack::MethodOverride+|Sets HTTP method based on +_method+ parameter or env["HTTP_X_HTTP_METHOD_OVERRIDE"].| |+Rack::Head+|Discards the response body if the client sends a +HEAD+ request.| diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile index 8837e06de5..a499ef3d39 100644 --- a/railties/guides/source/security.textile +++ b/railties/guides/source/security.textile @@ -82,9 +82,9 @@ This will also be a good idea, if you modify the structure of an object and old h4. Session Storage --- _Rails provides several storage mechanisms for the session hashes. The most important are SessionStore and CookieStore._ +-- _Rails provides several storage mechanisms for the session hashes. The most important are ActiveRecord::SessionStore and ActionDispatch::Session::CookieStore._ -There are a number of session storages, i.e. where Rails saves the session hash and session id. Most real-live applications choose SessionStore (or one of its derivatives) over file storage due to performance and maintenance reasons. SessionStore keeps the session id and hash in a database table and saves and retrieves the hash on every request. +There are a number of session storages, i.e. where Rails saves the session hash and session id. Most real-live applications choose ActiveRecord::SessionStore (or one of its derivatives) over file storage due to performance and maintenance reasons. ActiveRecord::SessionStore keeps the session id and hash in a database table and saves and retrieves the hash on every request. Rails 2 introduced a new default session storage, CookieStore. CookieStore saves the session hash directly in a cookie on the client-side. The server retrieves the session hash from the cookie and eliminates the need for a session id. That will greatly increase the speed of the application, but it is a controversial storage option and you have to think about the security implications of it: -- cgit v1.2.3 From ec93f363cab7270c1469b420a52a21e306a89c30 Mon Sep 17 00:00:00 2001 From: Brian Durand Date: Fri, 21 Oct 2011 13:28:24 -0500 Subject: Fix ActiveSupport::Cache::FileStore.cleanup to actually work. --- .../lib/active_support/cache/file_store.rb | 21 +++++++++++-- activesupport/test/caching_test.rb | 34 ++++++++++++++++++++++ railties/guides/source/caching_with_rails.textile | 6 +++- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index b431041b76..bc5d94b5a7 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -26,11 +26,26 @@ module ActiveSupport FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)}) end + # Cleanup the cache by removing old entries. By default this will delete entries + # that haven't been accessed in one day. You can change this behavior by passing + # in a +not_accessed_in+ option. Any entry not accessed in that number of seconds + # in the past will be deleted. Alternatively, you can pass in +:expired_only+ with + # +true+ to only delete expired entries. def cleanup(options = nil) options = merged_options(options) - each_key(options) do |key| - entry = read_entry(key, options) - delete_entry(key, options) if entry && entry.expired? + expired_only = options[:expired_only] + timestamp = Time.now - (options[:not_accessed_in] || 1.day.to_i) + search_dir(cache_path) do |fname| + if expired_only + key = file_path_key(fname) + entry = read_entry(key, options) + delete_entry(key, options) if entry && entry.expired? + else + if File.atime(fname) <= timestamp + key = file_path_key(fname) + delete_entry(key, options) + end + end end end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index cb5362525f..1b2f6d061c 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -557,6 +557,29 @@ class FileStoreTest < ActiveSupport::TestCase key = @cache_with_pathname.send(:key_file_path, "views/index?id=1") assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key) end + + def test_cleanup_with_not_accessed_in + @cache.write(1, "aaaaaaaaaa") + @cache.write(2, "bbbbbbbbbb") + @cache.write(3, "cccccccccc") + sleep(2) + @cache.read(2) + @cache.cleanup(:not_accessed_in => 1) + assert_equal false, @cache.exist?(1) + assert_equal true, @cache.exist?(2) + assert_equal false, @cache.exist?(3) + end + + def test_cleanup_with_expired_only + @cache.write(1, "aaaaaaaaaa", :expires_in => 0.001) + @cache.write(2, "bbbbbbbbbb") + @cache.write(3, "cccccccccc", :expires_in => 0.001) + sleep(0.002) + @cache.cleanup(:expired_only => 0.001) + assert_equal false, @cache.exist?(1) + assert_equal true, @cache.exist?(2) + assert_equal false, @cache.exist?(3) + end # Because file systems have a maximum filename size, filenames > max size should be split in to directories # If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B @@ -646,6 +669,17 @@ class MemoryStoreTest < ActiveSupport::TestCase assert_equal true, @cache.exist?(2) assert_equal false, @cache.exist?(1) end + + def test_cleanup_removes_expired_entries + @cache.write(1, "aaaaaaaaaa", :expires_in => 0.001) + @cache.write(2, "bbbbbbbbbb") + @cache.write(3, "cccccccccc", :expires_in => 0.001) + sleep(0.002) + @cache.cleanup + assert_equal false, @cache.exist?(1) + assert_equal true, @cache.exist?(2) + assert_equal false, @cache.exist?(3) + end end uses_memcached 'memcached backed store' do diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index 4273d0dd64..721c791a33 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -289,7 +289,11 @@ ActionController::Base.cache_store = :file_store, "/path/to/cache/directory" With this cache store, multiple server processes on the same host can share a cache. Servers processes running on different hosts could share a cache by using a shared file system, but that set up would not be ideal and is not recommended. The cache store is appropriate for low to medium traffic sites that are served off one or two hosts. -Note that the cache will grow until the disk is full unless you periodically clear out old entries. +Note that the cache will grow until the disk is full unless you periodically clear out old entries. You can call +ActiveSupport::Cache::FileStore#cleanup+ to remove entries older than a specified time. + + +Rails.cache.cleanup(:not_accessed_in => 2.days) + h4. ActiveSupport::Cache::MemCacheStore -- cgit v1.2.3 From f092be821db4a2e8f142e8f0b9d08e497ccf2eb2 Mon Sep 17 00:00:00 2001 From: Greg Reinacker Date: Fri, 21 Oct 2011 17:30:39 -0600 Subject: preserve decimal column attributes after migration --- .../connection_adapters/sqlite_adapter.rb | 3 ++ activerecord/test/cases/migration_test.rb | 36 ++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 1932a849ee..e0e957a12c 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -413,6 +413,8 @@ module ActiveRecord self.limit = options[:limit] if options.include?(:limit) self.default = options[:default] if include_default self.null = options[:null] if options.include?(:null) + self.precision = options[:precision] if options.include?(:precision) + self.scale = options[:scale] if options.include?(:scale) end end end @@ -467,6 +469,7 @@ module ActiveRecord @definition.column(column_name, column.type, :limit => column.limit, :default => column.default, + :precision => column.precision, :scale => column.scale, :null => column.null) end @definition.primary_key(primary_key(from)) if primary_key(from) diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index b3f1785f44..49944eced9 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -518,6 +518,42 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal 7, wealth_column.scale end + # Test SQLite adapter specifically for decimal types with precision and scale + # attributes, since these need to be maintained in schema but aren't actually + # used in SQLite itself + if current_adapter?(:SQLite3Adapter) + def test_change_column_with_new_precision_and_scale + Person.delete_all + Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7 + Person.reset_column_information + + Person.connection.change_column 'people', 'wealth', :decimal, :precision => 12, :scale => 8 + Person.reset_column_information + + wealth_column = Person.columns_hash['wealth'] + assert_equal 12, wealth_column.precision + assert_equal 8, wealth_column.scale + end + + def test_change_column_preserve_other_column_precision_and_scale + Person.delete_all + Person.connection.add_column 'people', 'last_name', :string + Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7 + Person.reset_column_information + + wealth_column = Person.columns_hash['wealth'] + assert_equal 9, wealth_column.precision + assert_equal 7, wealth_column.scale + + Person.connection.change_column 'people', 'last_name', :string, :null => false + Person.reset_column_information + + wealth_column = Person.columns_hash['wealth'] + assert_equal 9, wealth_column.precision + assert_equal 7, wealth_column.scale + end + end + def test_native_types Person.delete_all Person.connection.add_column "people", "last_name", :string -- cgit v1.2.3 From 50be4231e9bd475cda905ab489d4bfc9ebd887f7 Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Sat, 22 Oct 2011 15:51:55 +0530 Subject: Middleware details updated for 3.2 --- railties/guides/source/command_line.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index f6b33d283c..d3f73e06e0 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -389,7 +389,7 @@ Action Pack version 3.1.0 Active Resource version 3.1.0 Action Mailer version 3.1.0 Active Support version 3.1.0 -Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::RemoteIp, ActionDispatch::Callbacks, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::MethodOverride, ActionDispatch::Head +Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, ActionDispatch::Head, Rack::ConditionalGet, Rack::ETag, ActionDispatch::BestStandardsSupport Application root /home/foobar/commandsapp Environment development Database adapter sqlite3 -- cgit v1.2.3 From 6d5b92c735130a99186ef6f2ce6abe3f8745a8a5 Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Sat, 22 Oct 2011 15:53:11 +0530 Subject: Rails version to 3.2.0.beta in docs --- railties/guides/source/command_line.textile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index d3f73e06e0..ffa198891a 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -382,13 +382,13 @@ About your application's environment Ruby version 1.8.7 (x86_64-linux) RubyGems version 1.3.6 Rack version 1.1 -Rails version 3.1.0 +Rails version 3.2.0.beta JavaScript Runtime Node.js (V8) -Active Record version 3.1.0 -Action Pack version 3.1.0 -Active Resource version 3.1.0 -Action Mailer version 3.1.0 -Active Support version 3.1.0 +Active Record version 3.2.0.beta +Action Pack version 3.2.0.beta +Active Resource version 3.2.0.beta +Action Mailer version 3.2.0.beta +Active Support version 3.2.0.beta Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, ActionDispatch::Head, Rack::ConditionalGet, Rack::ETag, ActionDispatch::BestStandardsSupport Application root /home/foobar/commandsapp Environment development -- cgit v1.2.3 From 4ea21b6de260dd4cd610f88ae8d19a04977b5c2a Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Sat, 22 Oct 2011 15:54:55 +0530 Subject: Updated rack version in docs for 3.2 --- railties/guides/source/command_line.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index ffa198891a..3f8643eca3 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -381,7 +381,7 @@ $ rake about About your application's environment Ruby version 1.8.7 (x86_64-linux) RubyGems version 1.3.6 -Rack version 1.1 +Rack version 1.3 Rails version 3.2.0.beta JavaScript Runtime Node.js (V8) Active Record version 3.2.0.beta -- cgit v1.2.3 From 50dfd58fdb10557d9f14dc22494f96cf55237330 Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Sat, 22 Oct 2011 16:30:48 +0530 Subject: Warnings removed from RequestIdTest --- actionpack/test/dispatch/request_id_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb index ece8353810..b6e8b6c3ad 100644 --- a/actionpack/test/dispatch/request_id_test.rb +++ b/actionpack/test/dispatch/request_id_test.rb @@ -14,13 +14,13 @@ class RequestIdTest < ActiveSupport::TestCase end test "generating a request id when none is supplied" do - assert_match /\w+/, stub_request.uuid + assert_match(/\w+/, stub_request.uuid) end private def stub_request(env = {}) - ActionDispatch::RequestId.new(lambda { |env| [ 200, env, [] ] }).call(env) + ActionDispatch::RequestId.new(lambda { |environment| [ 200, environment, [] ] }).call(env) ActionDispatch::Request.new(env) end end -- cgit v1.2.3 From bc5d334c7a340c29a4f9a594101832e1abe831a5 Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Sat, 22 Oct 2011 19:16:43 +0530 Subject: Using middleware name to show proper name in the info --- railties/lib/rails/info.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index a1e15092b2..2ba09d7b90 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -91,7 +91,7 @@ module Rails end property 'Middleware' do - Rails.configuration.middleware.map(&:inspect) + Rails.configuration.middleware.map(&:name) end # The application's location on the filesystem. -- cgit v1.2.3 From c930170581ac10acf8c3d9433f3ccd5f72b4e6bf Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Sat, 22 Oct 2011 21:47:33 +0530 Subject: No need to check ruby version here As rake 0.9.2.2 is out with the same fix as 0.9.3.beta having --- Gemfile | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index f63260a260..4ac6bf764d 100644 --- a/Gemfile +++ b/Gemfile @@ -19,12 +19,7 @@ end # it being automatically loaded by sprockets gem "uglifier", ">= 1.0.3", :require => false -# Temp fix until rake 0.9.3 is out -if RUBY_VERSION >= "1.9.3" - gem "rake", "0.9.3.beta.1" -else - gem "rake", ">= 0.8.7" -end +gem "rake", ">= 0.8.7" gem "mocha", ">= 0.9.8" group :doc do -- cgit v1.2.3 From 1c1c3fc2c06fc02e67a8272adf2c2d2381e005b4 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sat, 22 Oct 2011 23:53:52 +0530 Subject: minor fixes in the composed_of doc --- activerecord/lib/active_record/aggregations.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 81ddbba51e..5a8addc4e4 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -172,8 +172,8 @@ module ActiveRecord # with this option. # * :mapping - Specifies the mapping of entity attributes to attributes of the value # object. Each mapping is represented as an array where the first item is the name of the - # entity attribute and the second item is the name the attribute in the value object. The - # order in which mappings are defined determine the order in which attributes are sent to the + # entity attribute and the second item is the name of the attribute in the value object. The + # order in which mappings are defined determines the order in which attributes are sent to the # value class constructor. # * :allow_nil - Specifies that the value object will not be instantiated when all mapped # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all @@ -191,7 +191,8 @@ module ActiveRecord # # Option examples: # composed_of :temperature, :mapping => %w(reading celsius) - # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money } + # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), + # :converter => Proc.new { |balance| balance.to_money } # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ] # composed_of :gps_location # composed_of :gps_location, :allow_nil => true -- cgit v1.2.3 From 259741ae7324c18782bf97f236230c496bccf948 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 22 Oct 2011 12:28:06 -0700 Subject: Revert "Merge pull request #3405 from arunagw/middleware_name" This reverts commit c33090523c3504a2a125d7f725a7f2a4b99ff3c0, reversing changes made to 1c1c3fc2c06fc02e67a8272adf2c2d2381e005b4. --- railties/lib/rails/info.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index 2ba09d7b90..a1e15092b2 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -91,7 +91,7 @@ module Rails end property 'Middleware' do - Rails.configuration.middleware.map(&:name) + Rails.configuration.middleware.map(&:inspect) end # The application's location on the filesystem. -- cgit v1.2.3 From a458833705cd3e9a0909d0710da84e2009f097ba Mon Sep 17 00:00:00 2001 From: Guillermo Iguaran Date: Sat, 22 Oct 2011 14:34:05 -0500 Subject: Remove Turn from default Gemfile. We still looking for a best presentation for tests output. --- railties/lib/rails/generators/app_base.rb | 11 ----------- railties/lib/rails/generators/rails/app/templates/Gemfile | 2 -- railties/test/generators/app_generator_test.rb | 15 --------------- 3 files changed, 28 deletions(-) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 40961f0c3e..10fdfdd8a9 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -190,17 +190,6 @@ module Rails end end - def turn_gemfile_entry - unless RUBY_VERSION < "1.9.2" || options[:skip_test_unit] - <<-GEMFILE.strip_heredoc - group :test do - # Pretty printed test output - gem 'turn', :require => false - end - GEMFILE - end - end - def assets_gemfile_entry <<-GEMFILE.strip_heredoc # Gems used only for assets and not required diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 910cd16950..d3b8f4d595 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -21,5 +21,3 @@ source 'http://rubygems.org' # To use debugger # <%= ruby_debugger_gemfile_entry %> - -<%= turn_gemfile_entry -%> diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 133efb872f..4b74bd6a4b 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -242,21 +242,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_inclusion_of_turn_gem_in_gemfile - run_generator - assert_file "Gemfile" do |contents| - assert_match(/gem 'turn'/, contents) unless RUBY_VERSION < '1.9.2' - assert_no_match(/gem 'turn'/, contents) if RUBY_VERSION < '1.9.2' - end - end - - def test_turn_gem_is_not_included_in_gemfile_if_skipping_test_unit - run_generator [destination_root, "--skip-test-unit"] - assert_file "Gemfile" do |contents| - assert_no_match(/gem 'turn'/, contents) unless RUBY_VERSION < '1.9.2' - end - end - def test_inclusion_of_ruby_debug run_generator assert_file "Gemfile" do |contents| -- cgit v1.2.3 From 227c31f7b6b13fcfc187d20618ed14d2970314c7 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sun, 23 Oct 2011 01:34:02 +0530 Subject: edge doesnt provide turn gem in the gemfile anymore --- railties/guides/code/getting_started/Gemfile | 5 ----- 1 file changed, 5 deletions(-) diff --git a/railties/guides/code/getting_started/Gemfile b/railties/guides/code/getting_started/Gemfile index 51774934cd..898510dcaa 100644 --- a/railties/guides/code/getting_started/Gemfile +++ b/railties/guides/code/getting_started/Gemfile @@ -25,8 +25,3 @@ gem 'jquery-rails' # To use debugger # gem 'ruby-debug19', :require => 'ruby-debug' - -group :test do - # Pretty printed test output - gem 'turn', :require => false -end -- cgit v1.2.3 From e75c18bff811b95e12634cdbf04df244a227d46d Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 23 Oct 2011 18:07:00 +0300 Subject: Ignore .rbx directories (rbx compiled bytecode files) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ab0950d7dd..437a1067f8 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ railties/tmp .rvmrc .rbenv-version RDOC_MAIN.rdoc +.rbx -- cgit v1.2.3 From 244dcfea477ace65963b41de122f7dab7904900b Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Mon, 24 Oct 2011 14:11:20 +0530 Subject: ActionPack test fix for RBX --- actionpack/test/controller/mime_responds_test.rb | 4 +++- actionpack/test/template/html-scanner/tag_node_test.rb | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 2aa73f1650..e91e11a8a7 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -656,7 +656,9 @@ class RespondWithControllerTest < ActionController::TestCase @request.accept = "application/json" get :using_hash_resource assert_equal "application/json", @response.content_type - assert_equal %Q[{"result":{"name":"david","id":13}}], @response.body + assert @response.body.include?("result") + assert @response.body.include?('"name":"david"') + assert @response.body.include?('"id":13') end def test_using_hash_resource_with_post diff --git a/actionpack/test/template/html-scanner/tag_node_test.rb b/actionpack/test/template/html-scanner/tag_node_test.rb index 0d87f1bd42..3b72243e7d 100644 --- a/actionpack/test/template/html-scanner/tag_node_test.rb +++ b/actionpack/test/template/html-scanner/tag_node_test.rb @@ -55,7 +55,12 @@ class TagNodeTest < Test::Unit::TestCase def test_to_s node = tag("") - assert_equal %(), node.to_s + node = node.to_s + assert node.include?('a') + assert node.include?('b="c"') + assert node.include?('d="f"') + assert node.include?('g="h') + assert node.include?('i') end def test_tag -- cgit v1.2.3 From 17bf04ff0dbb2f11dd7bc1a87df035a96f9432ef Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Mon, 24 Oct 2011 14:43:46 +0200 Subject: registers PR #2419 in the CHANGELOG --- activerecord/CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 46076dac61..798d875034 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,8 @@ *Rails 3.2.0 (unreleased)* +* In development mode the db:drop task also drops the test database. For symmetry with + the db:create task. [Dmitriy Kiriyenko] + * Added ActiveRecord::Base.store for declaring simple single-column key/value stores [DHH] class User < ActiveRecord::Base -- cgit v1.2.3 From 9dd168c40262e850885fcb3dd0028b9067bb627f Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Mon, 24 Oct 2011 14:46:28 +0200 Subject: minor revision to some new code in databases.rake --- activerecord/lib/active_record/railties/databases.rake | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 705ffe9dd7..f8fa65ea87 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -548,8 +548,9 @@ def drop_database_and_rescue(config) end def configs_for_environment - environments = [Rails.env, ("test" if Rails.env.development?)] - ActiveRecord::Base.configurations.values_at(*environments).compact + environments = [Rails.env] + environments << 'test' if Rails.env.development? + ActiveRecord::Base.configurations.values_at(*environments) end def session_table_name -- cgit v1.2.3 From b6fc41275c2e300c9e4ce55e48eedaac811c5fac Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Mon, 24 Oct 2011 21:56:27 +0530 Subject: Unused variable removed --- activesupport/test/buffered_logger_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/buffered_logger_test.rb index 8699862d9e..386006677b 100644 --- a/activesupport/test/buffered_logger_test.rb +++ b/activesupport/test/buffered_logger_test.rb @@ -233,7 +233,7 @@ class BufferedLoggerTest < Test::Unit::TestCase end keep_running = true - b = Thread.new do + Thread.new do @logger.info("b") while keep_running sleep(0.001) -- cgit v1.2.3 From 98fbb50ebf531af28deb1312baf943c24990ad25 Mon Sep 17 00:00:00 2001 From: Alexey Vakhov Date: Mon, 24 Oct 2011 22:38:10 +0400 Subject: Remove needless to_param in scaffold functional test --- .../generators/test_unit/scaffold/templates/functional_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb index 01fe6dda7a..9ec2e34545 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb @@ -26,23 +26,23 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase end test "should show <%= singular_table_name %>" do - get :show, <%= key_value :id, "@#{singular_table_name}.to_param" %> + get :show, <%= key_value :id, "@#{singular_table_name}" %> assert_response :success end test "should get edit" do - get :edit, <%= key_value :id, "@#{singular_table_name}.to_param" %> + get :edit, <%= key_value :id, "@#{singular_table_name}" %> assert_response :success end test "should update <%= singular_table_name %>" do - put :update, <%= key_value :id, "@#{singular_table_name}.to_param" %>, <%= key_value singular_table_name, "@#{singular_table_name}.attributes" %> + put :update, <%= key_value :id, "@#{singular_table_name}" %>, <%= key_value singular_table_name, "@#{singular_table_name}.attributes" %> assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>)) end test "should destroy <%= singular_table_name %>" do assert_difference('<%= class_name %>.count', -1) do - delete :destroy, <%= key_value :id, "@#{singular_table_name}.to_param" %> + delete :destroy, <%= key_value :id, "@#{singular_table_name}" %> end assert_redirected_to <%= index_helper %>_path -- cgit v1.2.3 From ddce29bfa12462fde2342a0c2bd0eefd420c0eab Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Wed, 26 Oct 2011 00:41:41 +0530 Subject: safeguard against configs missing environment or the database key --- 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 f8fa65ea87..44848b3391 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -550,7 +550,7 @@ end def configs_for_environment environments = [Rails.env] environments << 'test' if Rails.env.development? - ActiveRecord::Base.configurations.values_at(*environments) + ActiveRecord::Base.configurations.values_at(*environments).compact.reject { |config| config['database'].blank? } end def session_table_name -- cgit v1.2.3 From 771ca79f74db1e5a19703ad92472515dbebb70c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 25 Oct 2011 22:22:43 +0200 Subject: Revert "Merge pull request #3395 from bdurand/fix_file_store_cleanup" Tests were failing on Travis-CI. This reverts commit 79d01a8f16e20c556a086a2f07e3ccb4400f9819, reversing changes made to b838570bd69ff13d677fb43e79f10d6f3168c696. --- .../lib/active_support/cache/file_store.rb | 21 ++----------- activesupport/test/caching_test.rb | 34 ---------------------- railties/guides/source/caching_with_rails.textile | 6 +--- 3 files changed, 4 insertions(+), 57 deletions(-) diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index bc5d94b5a7..b431041b76 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -26,26 +26,11 @@ module ActiveSupport FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)}) end - # Cleanup the cache by removing old entries. By default this will delete entries - # that haven't been accessed in one day. You can change this behavior by passing - # in a +not_accessed_in+ option. Any entry not accessed in that number of seconds - # in the past will be deleted. Alternatively, you can pass in +:expired_only+ with - # +true+ to only delete expired entries. def cleanup(options = nil) options = merged_options(options) - expired_only = options[:expired_only] - timestamp = Time.now - (options[:not_accessed_in] || 1.day.to_i) - search_dir(cache_path) do |fname| - if expired_only - key = file_path_key(fname) - entry = read_entry(key, options) - delete_entry(key, options) if entry && entry.expired? - else - if File.atime(fname) <= timestamp - key = file_path_key(fname) - delete_entry(key, options) - end - end + each_key(options) do |key| + entry = read_entry(key, options) + delete_entry(key, options) if entry && entry.expired? end end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 1b2f6d061c..cb5362525f 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -557,29 +557,6 @@ class FileStoreTest < ActiveSupport::TestCase key = @cache_with_pathname.send(:key_file_path, "views/index?id=1") assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key) end - - def test_cleanup_with_not_accessed_in - @cache.write(1, "aaaaaaaaaa") - @cache.write(2, "bbbbbbbbbb") - @cache.write(3, "cccccccccc") - sleep(2) - @cache.read(2) - @cache.cleanup(:not_accessed_in => 1) - assert_equal false, @cache.exist?(1) - assert_equal true, @cache.exist?(2) - assert_equal false, @cache.exist?(3) - end - - def test_cleanup_with_expired_only - @cache.write(1, "aaaaaaaaaa", :expires_in => 0.001) - @cache.write(2, "bbbbbbbbbb") - @cache.write(3, "cccccccccc", :expires_in => 0.001) - sleep(0.002) - @cache.cleanup(:expired_only => 0.001) - assert_equal false, @cache.exist?(1) - assert_equal true, @cache.exist?(2) - assert_equal false, @cache.exist?(3) - end # Because file systems have a maximum filename size, filenames > max size should be split in to directories # If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B @@ -669,17 +646,6 @@ class MemoryStoreTest < ActiveSupport::TestCase assert_equal true, @cache.exist?(2) assert_equal false, @cache.exist?(1) end - - def test_cleanup_removes_expired_entries - @cache.write(1, "aaaaaaaaaa", :expires_in => 0.001) - @cache.write(2, "bbbbbbbbbb") - @cache.write(3, "cccccccccc", :expires_in => 0.001) - sleep(0.002) - @cache.cleanup - assert_equal false, @cache.exist?(1) - assert_equal true, @cache.exist?(2) - assert_equal false, @cache.exist?(3) - end end uses_memcached 'memcached backed store' do diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index 721c791a33..4273d0dd64 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -289,11 +289,7 @@ ActionController::Base.cache_store = :file_store, "/path/to/cache/directory" With this cache store, multiple server processes on the same host can share a cache. Servers processes running on different hosts could share a cache by using a shared file system, but that set up would not be ideal and is not recommended. The cache store is appropriate for low to medium traffic sites that are served off one or two hosts. -Note that the cache will grow until the disk is full unless you periodically clear out old entries. You can call +ActiveSupport::Cache::FileStore#cleanup+ to remove entries older than a specified time. - - -Rails.cache.cleanup(:not_accessed_in => 2.days) - +Note that the cache will grow until the disk is full unless you periodically clear out old entries. h4. ActiveSupport::Cache::MemCacheStore -- cgit v1.2.3 From 057a268d45e8432341bd73ae4c141bb926381f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 25 Oct 2011 22:23:55 +0200 Subject: Revert "Ignore .rbx directories (rbx compiled bytecode files)" This should go in your ~/.gitignore. This reverts commit e75c18bff811b95e12634cdbf04df244a227d46d. --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 437a1067f8..ab0950d7dd 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,3 @@ railties/tmp .rvmrc .rbenv-version RDOC_MAIN.rdoc -.rbx -- cgit v1.2.3 From 139562f241136ca89209ddaffaf10b0b3a599b39 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Tue, 25 Oct 2011 13:30:50 -0700 Subject: Clean up .gitignore and make a note about using global ignores --- .gitignore | 47 ++++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index ab0950d7dd..aa14cee911 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,23 @@ -pkg -.bundle -Gemfile.lock +# Don't put *.swp, *.bak, etc here; those belong in a global ~/.gitignore. +# Check out http://help.github.com/ignore-files/ for how to set that up. + debug.log -doc/rdoc -activemodel/doc -activeresource/doc -activerecord/doc -activerecord/sqlnet.log -actionpack/doc -actionmailer/doc -activesupport/doc -activesupport/test/tmp -activemodel/test/fixtures/fixture_database.sqlite3 -actionpack/test/tmp -activesupport/test/fixtures/isolation_test -dist -railties/test/500.html -railties/test/fixtures/tmp -railties/test/initializer/root/log -railties/doc -railties/guides/output -railties/tmp -.rvmrc -.rbenv-version -RDOC_MAIN.rdoc +/.bundle +/.rbenv-version +/.rvmrc +/Gemfile.lock +/pkg +/dist +/doc/rdoc +/*/doc +/*/test/tmp +/activerecord/sqlnet.log +/activemodel/test/fixtures/fixture_database.sqlite3 +/activesupport/test/fixtures/isolation_test +/railties/test/500.html +/railties/test/fixtures/tmp +/railties/test/initializer/root/log +/railties/doc +/railties/guides/output +/railties/tmp +/RDOC_MAIN.rdoc -- cgit v1.2.3