diff options
42 files changed, 485 insertions, 84 deletions
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 41a97823ed..a073018b42 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,3 +1,13 @@ +## Rails 3.2.3 (unreleased) ## + +* Upgrade mail version to 2.4.3 *ML* + + +## Rails 3.2.2 (March 1, 2012) ## + +* No changes. + + ## Rails 3.2.1 (January 26, 2012) ## * No changes. @@ -88,6 +98,7 @@ * Mail does not have "quoted_body", "quoted_subject" etc. All of these are accessed via body.encoded, subject.encoded etc * Every object in a Mail object returns an object, never a string. So Mail.body returns a Mail::Body class object, need to call #encoded or #decoded to get the string you want. + * Mail::Message#set_content_type does not exist, it is simply Mail::Message#content_type * Every mail message gets a unique message_id unless you specify one, had to change all the tests that check for equality with expected.encoded == actual.encoded to first replace their message_ids with control values diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index d7552e74e1..465d19e50d 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,12 +1,26 @@ ## Rails 3.2.3 (unreleased) ## +* Do not include the authenticity token in forms where remote: true as ajax forms use the meta-tag value *DHH* + * Turn off verbose mode of rack-cache, we still have X-Rack-Cache to check that info. Closes #5245. *Santiago Pastorino* * Fix #5238, rendered_format is not set when template is not rendered. *Piotr Sarnacki* +* Upgrade rack-cache to 1.2. *José Valim* + +* ActionController::SessionManagement is deprecated. *Santiago Pastorino* + +* Since the router holds references to many parts of the system like engines, controllers and the application itself, inspecting the route set can actually be really slow, therefore we default alias inspect to to_s. *José Valim* + +* Add a new line after the textarea opening tag. Closes #393 *Rafael Mendonça França* + +* Always pass a respond block from to responder. We should let the responder to decide what to do with the given overridden response block, and not short circuit it. *sikachu* + +* Fixes layout rendering regression from 3.2.2. *José Valim* + -## Rails 3.2.2 (unreleased) ## +## Rails 3.2.2 (March 1, 2012) ## * Format lookup for partials is derived from the format in which the template is being rendered. Closes #5025 part 2 *Santiago Pastorino* diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index cb3b793418..c482062592 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -237,8 +237,7 @@ module AbstractController # # If the specified layout is a: # String:: the String is the template name - # Symbol:: call the method specified by the symbol, which will return - # the template name + # Symbol:: call the method specified by the symbol, which will return the template name # false:: There is no layout # true:: raise an ArgumentError # nil:: Force default layout behavior with inheritance diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 7470897659..c985a8ca10 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -483,11 +483,6 @@ module ActionController end end - # Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local - def rescue_action_in_public! - @request.remote_addr = '208.77.188.166' # example.com - end - included do include ActionController::TemplateAssertions include ActionDispatch::Assertions diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index e3ad96ec1b..4ce878f26a 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -27,7 +27,9 @@ module ActionView # is added to simulate the verb over post. # * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to # pass custom authenticity token string, or to not add authenticity_token field at all - # (by passing <tt>false</tt>). + # (by passing <tt>false</tt>). If this is a remote form, the authenticity_token will by default + # not be included as the ajax handler will get it from the meta-tag (but you can force it to be + # rendered anyway in that case by passing <tt>true</tt>). # * A list of parameters to feed to the URL the form will be posted to. # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the # submit behavior. By default this behavior is an ajax submit. @@ -609,8 +611,17 @@ module ActionView # responsibility of the caller to escape all the values. html_options["action"] = url_for(url_for_options) html_options["accept-charset"] = "UTF-8" + html_options["data-remote"] = true if html_options.delete("remote") - html_options["authenticity_token"] = html_options.delete("authenticity_token") if html_options.has_key?("authenticity_token") + + if html_options["data-remote"] && html_options["authenticity_token"] == true + # Include the default authenticity_token, which is only generated when its set to nil, + # but we needed the true value to override the default of no authenticity_token on data-remote. + html_options["authenticity_token"] = nil + elsif html_options["data-remote"] + # The authenticity token is taken from the meta tag in this case + html_options["authenticity_token"] = false + end end end diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 6f76ab9596..145ef12b94 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -153,8 +153,6 @@ class PerformActionTest < ActionController::TestCase @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @request.host = "www.nextangle.com" - - rescue_action_in_public! end def test_process_should_be_precise @@ -206,7 +204,6 @@ class UrlOptionsTest < ActionController::TestCase def setup super @request.host = 'www.example.com' - rescue_action_in_public! end ## @@ -306,7 +303,6 @@ class DefaultUrlOptionsTest < ActionController::TestCase def setup super @request.host = 'www.example.com' - rescue_action_in_public! end def test_default_url_options_override @@ -357,7 +353,6 @@ class EmptyUrlOptionsTest < ActionController::TestCase def setup super @request.host = 'www.example.com' - rescue_action_in_public! end def test_ensure_url_for_works_as_expected_when_called_with_no_options_if_default_url_options_is_not_set diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index fd5a41a0bb..37b9de350b 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -37,6 +37,14 @@ module RequestForgeryProtectionActions render :inline => "<%= form_for(:some_resource, :authenticity_token => false ) {} %>" end + def form_for_remote + render :inline => "<%= form_for(:some_resource, :remote => true ) {} %>" + end + + def form_for_remote_with_token + render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => true ) {} %>" + end + def rescue_action(e) raise e end end @@ -103,6 +111,20 @@ module RequestForgeryProtectionTests assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token end + def test_should_render_form_without_token_tag_if_remote + assert_not_blocked do + get :form_for_remote + end + assert_no_match /authenticity_token/, response.body + end + + def test_should_render_form_with_token_tag_if_remote_and_authenticity_token_requested + assert_not_blocked do + get :form_for_remote_with_token + end + assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token + end + def test_should_allow_get assert_not_blocked { get :index } end diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 52d488a4ce..d3ea4c85dc 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,13 @@ +## Rails 3.2.3 (unreleased) ## + +* No changes. + + +## Rails 3.2.2 (March 1, 2012) ## + +* No changes. + + ## Rails 3.2.1 (January 26, 2012) ## * No changes. @@ -16,6 +26,7 @@ * Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior *Bogdan Gusiev* + ## Rails 3.1.0 (August 30, 2011) ## * Alternate I18n namespace lookup is no longer supported. @@ -44,7 +55,7 @@ * No changes. -* Rails 3.0.6 (April 5, 2011) +## Rails 3.0.6 (April 5, 2011) ## * Fix when database column name has some symbolic characters (e.g. Oracle CASE# VARCHAR2(20)) #5818 #6850 *Robert Pankowecki, Santiago Pastorino* diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index e1fb1e3231..f0041f5fee 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -83,7 +83,7 @@ module ActiveModel # end # end # - # When using the :default role : + # When using the :default role: # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default) @@ -94,7 +94,7 @@ module ActiveModel # customer.credit_rating = "Average" # customer.credit_rating # => "Average" # - # And using the :admin role : + # And using the :admin role: # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin) @@ -105,8 +105,9 @@ module ActiveModel # To start from an all-closed default and enable attributes as needed, # have a look at +attr_accessible+. # - # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_protected+ - # to sanitize attributes won't provide sufficient protection. + # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of + # +attr_protected+ to sanitize attributes provides basically the same + # functionality, but it makes a bit tricky to deal with nested attributes. def attr_protected(*args) options = args.extract_options! role = options[:as] || :default @@ -150,7 +151,7 @@ module ActiveModel # end # end # - # When using the :default role : + # When using the :default role: # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default) @@ -160,15 +161,16 @@ module ActiveModel # customer.credit_rating = "Average" # customer.credit_rating # => "Average" # - # And using the :admin role : + # And using the :admin role: # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin) # customer.name # => "David" # customer.credit_rating # => "Excellent" # - # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_accessible+ - # to sanitize attributes won't provide sufficient protection. + # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of + # +attr_accessible+ to sanitize attributes provides basically the same + # functionality, but it makes a bit tricky to deal with nested attributes. def attr_accessible(*args) options = args.extract_options! role = options[:as] || :default diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 3cf2f6a4e9..16c525d211 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,25 @@ +## Rails 3.2.3 (unreleased) ## + +* Added find_or_create_by_{attribute}! dynamic method. *Andrew White* + +* Whitelist all attribute assignment by default. Change the default for newly generated applications to whitelist all attribute assignment. Also update the generated model classes so users are reminded of the importance of attr_accessible. *NZKoz* + +* Update ActiveRecord::AttributeMethods#attribute_present? to return false for empty strings. *Jacobkg* + +* Fix associations when using per class databases. *larskanis* + +* Revert setting NOT NULL constraints in add_timestamps *fxn* + +* Fix mysql to use proper text types. Fixes #3931. *kennyj* + +* Fix #5069 - Protect foreign key from mass assignment through association builder. *byroot* + + +## Rails 3.2.2 (March 1, 2012) ## + +* No changes. + + ## Rails 3.2.1 (January 26, 2012) ## * The threshold for auto EXPLAIN is ignored if there's no logger. *fxn* @@ -192,7 +214,8 @@ *Brian Durand* -## Rails 3.1.3 (unreleased) ## + +## Rails 3.1.3 (November 20, 2011) ## * Perf fix: If we're deleting all records in an association, don't add a IN(..) clause to the query. *GH 3672* @@ -205,7 +228,8 @@ *Christos Zisopoulos and Kenny J* -## Rails 3.1.2 (unreleased) ## + +## Rails 3.1.2 (November 18, 2011) ## * Fix bug with PostgreSQLAdapter#indexes. When the search path has multiple schemas, spaces were not being stripped from the schema names after the first. @@ -252,6 +276,7 @@ *Kenny J* + ## Rails 3.1.1 (October 7, 2011) ## * Add deprecation for the preload_associations method. Fixes #3022. diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 0efa111d12..8ebb27b682 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1513,8 +1513,8 @@ module ActiveRecord # * <tt>Developer#projects.size</tt> # * <tt>Developer#projects.find(id)</tt> # * <tt>Developer#projects.exists?(...)</tt> - # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("project_id" => id)</tt>) - # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("project_id" => id); c.save; c</tt>) + # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>) + # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>) # The declaration may include an options hash to specialize the behavior of the association. # # === Options diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 9657cb081d..53d49fef2e 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -73,7 +73,9 @@ module ActiveRecord # association def build_through_record(record) @through_records[record.object_id] ||= begin - through_record = through_association.build(construct_join_attributes(record)) + ensure_mutable + + through_record = through_association.build through_record.send("#{source_reflection.name}=", record) through_record end diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index f95e5337c2..fd0e90aaf0 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -37,9 +37,7 @@ module ActiveRecord # situation it is more natural for the user to just create or modify their join records # directly as required. def construct_join_attributes(*records) - if source_reflection.macro != :belongs_to - raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection) - end + ensure_mutable join_attributes = { source_reflection.foreign_key => @@ -73,6 +71,12 @@ module ActiveRecord !owner[through_reflection.foreign_key].nil? end + def ensure_mutable + if source_reflection.macro != :belongs_to + raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection) + end + end + def ensure_not_nested if reflection.nested? raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 9c4d4cb274..9746efc7d1 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -208,6 +208,9 @@ module ActiveRecord #:nodoc: # # Now 'Bob' exist and is an 'admin' # User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true } # + # Adding an exclamation point (!) on to the end of <tt>find_or_create_by_</tt> will + # raise an <tt>ActiveRecord::RecordInvalid</tt> error if the new record is invalid. + # # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without # saving it first. Protected attributes won't be set unless they are given in a block. # @@ -439,7 +442,7 @@ module ActiveRecord #:nodoc: if self == ActiveRecord::Base ActiveRecord::Base else - connection_handler.connection_pools[name] ? self : superclass.arel_engine + connection_handler.retrieve_connection_pool(self) ? self : superclass.arel_engine end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 698da34d26..3af285c7c8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -95,10 +95,11 @@ module ActiveRecord @reserved_connections[current_connection_id] ||= checkout end - # Check to see if there is an active connection in this connection - # pool. + # Is there an open connection that is being used for the current thread? def active_connection? - active_connections.any? + @reserved_connections.fetch(current_connection_id) { + return false + }.in_use? end # Signal that the thread is finished with the current connection. @@ -225,8 +226,9 @@ connection. For example: ActiveRecord::Base.connection.close # - ConnectionTimeoutError: no connection can be obtained from the pool # within the timeout period. def checkout - # Checkout an available connection synchronize do + waited_time = 0 + loop do conn = @connections.find { |c| c.lease } @@ -242,17 +244,25 @@ connection. For example: ActiveRecord::Base.connection.close return conn end - @queue.wait(@timeout) + if waited_time >= @timeout + raise ConnectionTimeoutError, "could not obtain a database connection#{" within #{@timeout} seconds" if @timeout} (waited #{waited_time} seconds). The max pool size is currently #{@size}; consider increasing it." + end - if(active_connections.size < @connections.size) - next - else + # Sometimes our wait can end because a connection is available, + # but another thread can snatch it up first. If timeout hasn't + # passed but no connection is avail, looks like that happened -- + # loop and wait again, for the time remaining on our timeout. + before_wait = Time.now + @queue.wait( [@timeout - waited_time, 0].max ) + waited_time += (Time.now - before_wait) + + # Will go away in Rails 4, when we don't clean up + # after leaked connections automatically anymore. Right now, clean + # up after we've returned from a 'wait' if it looks like it's + # needed, then loop and try again. + if(active_connections.size >= @connections.size) clear_stale_cached_connections! - if @size == active_connections.size - raise ConnectionTimeoutError, "could not obtain a database connection#{" within #{@timeout} seconds" if @timeout}. The max pool size is currently #{@size}; consider increasing it." - end end - end end end @@ -268,11 +278,27 @@ connection. For example: ActiveRecord::Base.connection.close conn.expire @queue.signal end + + release conn end end private + def release(conn) + thread_id = nil + + if @reserved_connections[current_connection_id] == conn + thread_id = current_connection_id + else + thread_id = @reserved_connections.keys.find { |k| + @reserved_connections[k] == conn + } + end + + @reserved_connections.delete thread_id if thread_id + end + def new_connection ActiveRecord::Base.send(spec.adapter_method, spec.config) end @@ -344,9 +370,7 @@ connection. For example: ActiveRecord::Base.connection.close connection_pools.values.any? { |pool| pool.active_connection? } end - # Returns any connections in use by the current thread back to the pool, - # and also returns connections to the pool cached by threads that are no - # longer alive. + # Returns any connections in use by the current thread back to the pool. def clear_active_connections! @connection_pools.each_value {|pool| pool.release_connection } end diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb index b309df9b1b..80b17a27df 100644 --- a/activerecord/lib/active_record/dynamic_finder_match.rb +++ b/activerecord/lib/active_record/dynamic_finder_match.rb @@ -18,6 +18,10 @@ module ActiveRecord when /^find_by_([_a-zA-Z]\w*)\!$/ bang = true names = $1 + when /^find_or_create_by_([_a-zA-Z]\w*)\!$/ + bang = true + instantiator = :create + names = $1 when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/ instantiator = $1 == 'initialize' ? :new : :create names = $2 @@ -52,5 +56,13 @@ module ActiveRecord def bang? @bang end + + def save_record? + @instantiator == :create + end + + def save_method + bang? ? :save! : :save + end end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 0305e561c8..9a34927857 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -290,7 +290,7 @@ module ActiveRecord r.assign_attributes(unprotected_attributes_for_create, :without_protection => true) end yield(record) if block_given? - record.save if match.instantiator == :create + record.send(match.save_method) if match.save_record? end record diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index fab2cd77f0..f5bfa3603a 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -270,7 +270,7 @@ module ActiveRecord arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty? arel.take(connection.sanitize_limit(@limit_value)) if @limit_value - arel.skip(@offset_value) if @offset_value + arel.skip(@offset_value.to_i) if @offset_value arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty? diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 6836a2b4d0..4489c3e638 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -44,17 +44,33 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_associate_existing - posts(:thinking); people(:david) # Warm cache + post = posts(:thinking) + person = people(:david) assert_queries(1) do - posts(:thinking).people << people(:david) + post.people << person end assert_queries(1) do - assert posts(:thinking).people.include?(people(:david)) + assert post.people.include?(person) end - assert posts(:thinking).reload.people(true).include?(people(:david)) + assert post.reload.people(true).include?(person) + end + + def test_associate_existing_with_strict_mass_assignment_sanitizer + SecureReader.mass_assignment_sanitizer = :strict + + SecureReader.new + + post = posts(:thinking) + person = people(:david) + + assert_queries(1) do + post.secure_people << person + end + ensure + SecureReader.mass_assignment_sanitizer = :logger end def test_associate_existing_record_twice_should_add_to_target_twice diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index d170a13b23..5cecfa90e7 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -3,7 +3,11 @@ require "cases/helper" module ActiveRecord module ConnectionAdapters class ConnectionPoolTest < ActiveRecord::TestCase + attr_reader :pool + def setup + super + # Keep a duplicate pool so we do not bother others @pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec @@ -18,6 +22,72 @@ module ActiveRecord end end + def teardown + super + @pool.disconnect! + end + + def active_connections(pool) + pool.connections.find_all(&:in_use?) + end + + def test_checkout_after_close + connection = pool.connection + assert connection.in_use? + + connection.close + assert !connection.in_use? + + assert pool.connection.in_use? + end + + def test_released_connection_moves_between_threads + thread_conn = nil + + Thread.new { + pool.with_connection do |conn| + thread_conn = conn + end + }.join + + assert thread_conn + + Thread.new { + pool.with_connection do |conn| + assert_equal thread_conn, conn + end + }.join + end + + def test_with_connection + assert_equal 0, active_connections(pool).size + + main_thread = pool.connection + assert_equal 1, active_connections(pool).size + + Thread.new { + pool.with_connection do |conn| + assert conn + assert_equal 2, active_connections(pool).size + end + assert_equal 1, active_connections(pool).size + }.join + + main_thread.close + assert_equal 0, active_connections(pool).size + end + + def test_active_connection_in_use + assert !pool.active_connection? + main_thread = pool.connection + + assert pool.active_connection? + + main_thread.close + + assert !pool.active_connection? + end + def test_active_connection? assert !@pool.active_connection? assert @pool.connection diff --git a/activerecord/test/cases/dynamic_finder_match_test.rb b/activerecord/test/cases/dynamic_finder_match_test.rb index e576870317..db619faa83 100644 --- a/activerecord/test/cases/dynamic_finder_match_test.rb +++ b/activerecord/test/cases/dynamic_finder_match_test.rb @@ -83,6 +83,14 @@ module ActiveRecord assert_equal :create, m.instantiator end + def test_find_or_create! + m = DynamicFinderMatch.match(:find_or_create_by_foo!) + assert_equal :first, m.finder + assert m.bang?, 'should be banging' + assert_equal %w{ foo }, m.attribute_names + assert_equal :create, m.instantiator + end + def test_find_or_initialize m = DynamicFinderMatch.match(:find_or_initialize_by_foo) assert_equal :first, m.finder diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb index 235805a67c..810c1500cc 100644 --- a/activerecord/test/cases/finder_respond_to_test.rb +++ b/activerecord/test/cases/finder_respond_to_test.rb @@ -56,6 +56,16 @@ class FinderRespondToTest < ActiveRecord::TestCase assert_respond_to Topic, :find_or_create_by_title_and_author_name end + def test_should_respond_to_find_or_create_from_one_attribute_bang + ensure_topic_method_is_not_cached(:find_or_create_by_title!) + assert_respond_to Topic, :find_or_create_by_title! + end + + def test_should_respond_to_find_or_create_from_two_attributes_bang + ensure_topic_method_is_not_cached(:find_or_create_by_title_and_author_name!) + assert_respond_to Topic, :find_or_create_by_title_and_author_name! + end + def test_should_not_respond_to_find_by_one_missing_attribute assert !Topic.respond_to?(:find_by_undertitle) end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 5a67fcdc0a..4705252c05 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -862,6 +862,28 @@ class FinderTest < ActiveRecord::TestCase assert another.persisted? end + def test_find_or_create_from_one_attribute_bang + number_of_companies = Company.count + assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name!("") } + assert_equal number_of_companies, Company.count + sig38 = Company.find_or_create_by_name!("38signals") + assert_equal number_of_companies + 1, Company.count + assert_equal sig38, Company.find_or_create_by_name!("38signals") + assert sig38.persisted? + end + + def test_find_or_create_from_two_attributes_bang + number_of_companies = Company.count + assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name_and_firm_id!("", 17) } + assert_equal number_of_companies, Company.count + sig38 = Company.find_or_create_by_name_and_firm_id!("38signals", 17) + assert_equal number_of_companies + 1, Company.count + assert_equal sig38, Company.find_or_create_by_name_and_firm_id!("38signals", 17) + assert sig38.persisted? + assert_equal "38signals", sig38.name + assert_equal 17, sig38.firm_id + end + def test_find_or_create_from_two_attributes_with_one_being_an_aggregate number_of_customers = Customer.count created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth") @@ -1182,6 +1204,10 @@ class FinderTest < ActiveRecord::TestCase end end + def test_finder_with_offset_string + assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.find(:all, :offset => "3") } + end + protected def bind(statement, *vars) if vars.first.is_a?(Hash) diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index e704322b5d..b31d971309 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -85,6 +85,12 @@ class MultipleDbTest < ActiveRecord::TestCase end def test_arel_table_engines - assert_equal Entrant.arel_engine, Bird.arel_engine + assert_not_equal Entrant.arel_engine, Bird.arel_engine + assert_not_equal Entrant.arel_engine, Course.arel_engine + end + + def test_connection + assert_equal Entrant.arel_engine.connection, Bird.arel_engine.connection + assert_not_equal Entrant.arel_engine.connection, Course.arel_engine.connection end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index bf1eb6386a..5e0caf5fce 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -449,6 +449,18 @@ class RelationTest < ActiveRecord::TestCase assert_equal authors(:david), authors.find_or_create_by_name(:name => 'David') end + def test_dynamic_find_or_create_by_attributes_bang + authors = Author.scoped + + assert_raises(ActiveRecord::RecordInvalid) { authors.find_or_create_by_name!('') } + + lifo = authors.find_or_create_by_name!('Lifo') + assert_equal "Lifo", lifo.name + assert lifo.persisted? + + assert_equal authors(:david), authors.find_or_create_by_name!(:name => 'David') + end + def test_find_id authors = Author.scoped diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 36eb08d02f..dcfea3170c 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -1,8 +1,10 @@ class Person < ActiveRecord::Base has_many :readers + has_many :secure_readers has_one :reader has_many :posts, :through => :readers + has_many :secure_posts, :through => :secure_readers has_many :posts_with_no_comments, :through => :readers, :source => :post, :include => :comments, :conditions => 'comments.id is null' has_many :references diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 1cab78d8c7..0fc22ac6a3 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -115,8 +115,10 @@ class Post < ActiveRecord::Base has_many :named_categories, :through => :standard_categorizations has_many :readers + has_many :secure_readers has_many :readers_with_person, :include => :person, :class_name => "Reader" has_many :people, :through => :readers + has_many :secure_people, :through => :secure_readers has_many :single_people, :through => :readers has_many :people_with_callbacks, :source=>:person, :through => :readers, :before_add => lambda {|owner, reader| log(:added, :before, reader.first_name) }, diff --git a/activerecord/test/models/reader.rb b/activerecord/test/models/reader.rb index 0207a2bd92..59005ac604 100644 --- a/activerecord/test/models/reader.rb +++ b/activerecord/test/models/reader.rb @@ -3,3 +3,12 @@ class Reader < ActiveRecord::Base belongs_to :person, :inverse_of => :readers belongs_to :single_person, :class_name => 'Person', :foreign_key => :person_id, :inverse_of => :reader end + +class SecureReader < ActiveRecord::Base + self.table_name = "readers" + + belongs_to :secure_post, :class_name => "Post", :foreign_key => "post_id" + belongs_to :secure_person, :inverse_of => :secure_readers, :class_name => "Person", :foreign_key => "person_id" + + attr_accessible nil +end diff --git a/activeresource/CHANGELOG.md b/activeresource/CHANGELOG.md index 53afc56ee0..ef957c68ed 100644 --- a/activeresource/CHANGELOG.md +++ b/activeresource/CHANGELOG.md @@ -1,3 +1,13 @@ +## Rails 3.2.3 (unreleased) ## + +* No changes. + + +## Rails 3.2.2 (March 1, 2012) ## + +* No changes. + + ## Rails 3.2.1 (January 26, 2012) ## * Documentation fixes. diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index bdae96e7f2..f35c147e60 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,11 @@ +## Rails 3.2.3 (unreleased) ## + +* No changes. + +## Rails 3.2.2 (March 1, 2012) ## + +* No changes. + ## Rails 3.2.1 (January 26, 2012) ## * Documentation fixes and improvements. @@ -81,6 +89,7 @@ * ActiveSupport::BufferedLogger#flush is deprecated. Set sync on your filehandle, or tune your filesystem. + ## Rails 3.1.0 (August 30, 2011) ## * ActiveSupport::Dependencies#load and ActiveSupport::Dependencies#require now diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 4ac2f942e1..b02e952671 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,13 @@ +## Rails 3.2.3 (unreleased) ## + +* No changes. + + +## Rails 3.2.2 (March 1, 2012) ## + +* No changes. + + ## Rails 3.2.1 (January 26, 2012) ## * Documentation fixes. @@ -46,7 +56,19 @@ * Remove old 'config.paths.app.controller' API in favor of 'config.paths["app/controller"]' API *Guillermo Iguaran* -* Rails 3.1.1 +## Rails 3.1.2 (November 18, 2011) ## + +* Engines: don't blow up if db/seeds.rb is missing. + + *Jeremy Kemper* + +* `rails new foo --skip-test-unit` should not add the `:test` task to the rake default task. + *GH 2564* + + *José Valim* + + +## Rails 3.1.1 (October 7, 2011) ## * Add jquery-rails to Gemfile of plugins, test/dummy app needs it. Closes #3091. *Santiago Pastorino* @@ -59,16 +81,6 @@ Plugins developers need to special case their initializers that are meant to be run in the assets group by adding :group => :assets. -## Rails 3.1.2 (unreleased) ## - -* Engines: don't blow up if db/seeds.rb is missing. - - *Jeremy Kemper* - -* `rails new foo --skip-test-unit` should not add the `:test` task to the rake default task. - *GH 2564* - - *José Valim* ## Rails 3.1.0 (August 30, 2011) ## diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index e46410ce59..47392c1851 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -94,7 +94,7 @@ client = Client.find(10) The SQL equivalent of the above is: <sql> -SELECT * FROM clients WHERE (clients.id = 10) +SELECT * FROM clients WHERE (clients.id = 10) LIMIT 1 </sql> <tt>Model.find(primary_key)</tt> will raise an +ActiveRecord::RecordNotFound+ exception if no matching record is found. @@ -1109,6 +1109,8 @@ Client.where(:first_name => 'Andy').first_or_create!(:locked => false) # => ActiveRecord::RecordInvalid: Validation failed: Orders count can't be blank </ruby> +As with +first_or_create+ there is a +find_or_create_by!+ method but the +first_or_create!+ method is preferred for clarity. + h4. +first_or_initialize+ The +first_or_initialize+ method will work just like +first_or_create+ but it will not call +create+ but +new+. This means that a new model instance will be created in memory but won't be saved to the database. Continuing with the +first_or_create+ example, we now want the client named 'Nick': diff --git a/railties/guides/source/asset_pipeline.textile b/railties/guides/source/asset_pipeline.textile index ff2bd08602..08adedb23f 100644 --- a/railties/guides/source/asset_pipeline.textile +++ b/railties/guides/source/asset_pipeline.textile @@ -128,7 +128,7 @@ For example, these files: <plain> app/assets/javascripts/home.js lib/assets/javascripts/moovinator.js -vendor/assets/javascript/slider.js +vendor/assets/javascripts/slider.js </plain> would be referenced in a manifest like this: diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile index 9e22fc4588..6f0d985cac 100644 --- a/railties/guides/source/association_basics.textile +++ b/railties/guides/source/association_basics.textile @@ -1319,7 +1319,7 @@ If you need to evaluate conditions dynamically at runtime, use a proc: <ruby> class Customer < ActiveRecord::Base has_many :latest_orders, :class_name => "Order", - :conditions => proc { ["orders.created_at > ?, 10.hours.ago] } + :conditions => proc { ["orders.created_at > ?", 10.hours.ago] } end </ruby> diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index 98582946b4..11dbba0e3d 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -481,7 +481,7 @@ Rails has 5 initialization events which can be hooked into (listed in the order * +after_initialize+: Run directly after the initialization of the application, but before the application initializers are run. -To define an event for these hooks, use the block syntax within a +Rails::Aplication+, +Rails::Railtie+ or +Rails::Engine+ subclass: +To define an event for these hooks, use the block syntax within a +Rails::Application+, +Rails::Railtie+ or +Rails::Engine+ subclass: <ruby> module YourApp diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index 0823fb14e3..851d1bef69 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -235,14 +235,14 @@ end In addition to the routes for magazines, this declaration will also route ads to an +AdsController+. The ad URLs require a magazine: -|_.HTTP Verb |_.Path |_.action |_.used for | -|GET |/magazines/:id/ads |index |display a list of all ads for a specific magazine | -|GET |/magazines/:id/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine | -|POST |/magazines/:id/ads |create |create a new ad belonging to a specific magazine | -|GET |/magazines/:id/ads/:id |show |display a specific ad belonging to a specific magazine | -|GET |/magazines/:id/ads/:id/edit |edit |return an HTML form for editing an ad belonging to a specific magazine | -|PUT |/magazines/:id/ads/:id |update |update a specific ad belonging to a specific magazine | -|DELETE |/magazines/:id/ads/:id |destroy |delete a specific ad belonging to a specific magazine | +|_.HTTP Verb |_.Path |_.action |_.used for | +|GET |/magazines/:magazine_id/ads |index |display a list of all ads for a specific magazine | +|GET |/magazines/:magazine_id/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine | +|POST |/magazines/:magazine_id/ads |create |create a new ad belonging to a specific magazine | +|GET |/magazines/:magazine_id/ads/:id |show |display a specific ad belonging to a specific magazine | +|GET |/magazines/:magazine_id/ads/:id/edit |edit |return an HTML form for editing an ad belonging to a specific magazine | +|PUT |/magazines/:magazine_id/ads/:id |update |update a specific ad belonging to a specific magazine | +|DELETE |/magazines/:magazine_id/ads/:id |destroy |delete a specific ad belonging to a specific magazine | This will also create routing helpers such as +magazine_ads_url+ and +edit_magazine_ad_path+. These helpers take an instance of Magazine as the first parameter (+magazine_ads_url(@magazine)+). @@ -385,7 +385,7 @@ match ':controller/:action/:id/:user_id' An incoming path of +/photos/show/1/2+ will be dispatched to the +show+ action of the +PhotosController+. +params[:id]+ will be +"1"+, and +params[:user_id]+ will be +"2"+. -NOTE: You can't use +namespace+ or +:module+ with a +:controller+ path segment. If you need to do this then use a constraint on :controller that matches the namespace you require. e.g: +NOTE: You can't use +:namespace+ or +:module+ with a +:controller+ path segment. If you need to do this then use a constraint on :controller that matches the namespace you require. e.g: <ruby> match ':controller(/:action(/:id))', :controller => /admin\/[^\/]+/ diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 20321a502d..993dfe43ee 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -245,7 +245,7 @@ module Rails # # Additionally, an isolated engine will set its name according to namespace, so # MyEngine::Engine.engine_name will be "my_engine". It will also set MyEngine.table_name_prefix - # to "my_engine_", changing the MyEngine::Article model to use the my_engine_article table. + # to "my_engine_", changing the MyEngine::Article model to use the my_engine_articles table. # # == Using Engine's routes outside Engine # diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb index dcd3b276e3..1e26a313cd 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb +++ b/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb @@ -8,3 +8,8 @@ Rails.backtrace_cleaner.remove_silencers! # Load support files Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } + +# Load fixtures from the engine +if ActiveSupport::TestCase.method_defined?(:fixture_path=) + ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) +end diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb index f7e907a017..8e796aada9 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb @@ -8,10 +8,31 @@ module TestUnit check_class_collision :suffix => "ControllerTest" + argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" + def create_test_files template 'functional_test.rb', File.join('test/functional', controller_class_path, "#{controller_file_name}_controller_test.rb") end + + private + + def resource_attributes + key_value singular_table_name, "{ #{attributes_hash} }" + end + + def attributes_hash + return if accessible_attributes.empty? + + accessible_attributes.map do |a| + name = a.name + key_value name, "@#{singular_table_name}.#{name}" + end.sort.join(', ') + end + + def accessible_attributes + attributes.reject(&:reference?) + end end end end 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 9ec2e34545..19894443d5 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 @@ -19,7 +19,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase test "should create <%= singular_table_name %>" do assert_difference('<%= class_name %>.count') do - post :create, <%= key_value singular_table_name, "@#{singular_table_name}.attributes" %> + post :create, <%= resource_attributes %> end assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>)) @@ -36,7 +36,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase end test "should update <%= singular_table_name %>" do - put :update, <%= key_value :id, "@#{singular_table_name}" %>, <%= key_value singular_table_name, "@#{singular_table_name}.attributes" %> + put :update, <%= key_value :id, "@#{singular_table_name}" %>, <%= resource_attributes %> assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>)) end diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index 65b30b9fbd..7c28f5914f 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -75,6 +75,31 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_file "test/functional/users_controller_test.rb" do |content| assert_match(/class UsersControllerTest < ActionController::TestCase/, content) assert_match(/test "should get index"/, content) + + if RUBY_VERSION < "1.9" + assert_match(/post :create, :user => \{ :age => @user.age, :name => @user.name \}/, content) + assert_match(/put :update, :id => @user, :user => \{ :age => @user.age, :name => @user.name \}/, content) + else + assert_match(/post :create, user: \{ age: @user.age, name: @user.name \}/, content) + assert_match(/put :update, id: @user, user: \{ age: @user.age, name: @user.name \}/, content) + end + end + end + + def test_functional_tests_without_attributes + run_generator ["User"] + + assert_file "test/functional/users_controller_test.rb" do |content| + assert_match(/class UsersControllerTest < ActionController::TestCase/, content) + assert_match(/test "should get index"/, content) + + if RUBY_VERSION < "1.9" + assert_match(/post :create, :user => \{ \}/, content) + assert_match(/put :update, :id => @user, :user => \{ \}/, content) + else + assert_match(/post :create, user: \{ \}/, content) + assert_match(/put :update, id: @user, user: \{ \}/, content) + end end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 2db8090621..2710ef7dfd 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -62,8 +62,17 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end end - assert_file "test/functional/product_lines_controller_test.rb", - /class ProductLinesControllerTest < ActionController::TestCase/ + assert_file "test/functional/product_lines_controller_test.rb" do |test| + assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test) + + if RUBY_VERSION < "1.9" + assert_match(/post :create, :product_line => \{ :title => @product_line.title \}/, test) + assert_match(/put :update, :id => @product_line, :product_line => \{ :title => @product_line.title \}/, test) + else + assert_match(/post :create, product_line: \{ title: @product_line.title \}/, test) + assert_match(/put :update, id: @product_line, product_line: \{ title: @product_line.title \}/, test) + end + end # Views %w( @@ -85,6 +94,23 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "app/assets/stylesheets/product_lines.css" end + def test_functional_tests_without_attributes + run_generator ["product_line"] + + assert_file "test/functional/product_lines_controller_test.rb" do |content| + assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, content) + assert_match(/test "should get index"/, content) + + if RUBY_VERSION < "1.9" + assert_match(/post :create, :product_line => \{ \}/, content) + assert_match(/put :update, :id => @product_line, :product_line => \{ \}/, content) + else + assert_match(/post :create, product_line: \{ \}/, content) + assert_match(/put :update, id: @product_line, product_line: \{ \}/, content) + end + end + end + def test_scaffold_on_revoke run_generator run_generator ["product_line"], :behavior => :revoke |