diff options
35 files changed, 494 insertions, 72 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index a90a7b37f7..3eba2281c4 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,50 @@ *Rails 3.1.0 (unreleased)* +* Wildcard route will always matching the optional format segment by default. For example if you have this route: + + map '*pages' => 'pages#show' + + by requesting '/foo/bar.json', your `params[:pages]` will be equals to "foo/bar" with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `:format => false` like this: + + map '*pages' => 'pages#show', :format => false + +* Added Base.http_basic_authenticate_with to do simple http basic authentication with a single class method call [DHH] + + class PostsController < ApplicationController + USER_NAME, PASSWORD = "dhh", "secret" + + before_filter :authenticate, :except => [ :index ] + + def index + render :text => "Everyone can see me!" + end + + def edit + render :text => "I'm only accessible if you know the password" + end + + private + def authenticate + authenticate_or_request_with_http_basic do |user_name, password| + user_name == USER_NAME && password == PASSWORD + end + end + end + + ..can now be written as + + class PostsController < ApplicationController + http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index + + def index + render :text => "Everyone can see me!" + end + + def edit + render :text => "I'm only accessible if you know the password" + end + end + * Allow you to add `force_ssl` into controller to force browser to transfer data via HTTPS protocol on that particular controller. You can also specify `:only` or `:except` to specific it to particular action. [DHH and Prem Sichanugrist] * Allow FormHelper#form_for to specify the :method as a direct option instead of through the :html hash [DHH] diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index f6bc5e0d37..651f3b164a 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -26,7 +26,7 @@ Gem::Specification.new do |s| s.add_dependency('i18n', '~> 0.5.0') s.add_dependency('rack', '~> 1.2.1') s.add_dependency('rack-test', '~> 0.5.7') - s.add_dependency('rack-mount', '~> 0.6.13') + s.add_dependency('rack-mount', '~> 0.7.1') s.add_dependency('tzinfo', '~> 0.3.23') s.add_dependency('erubis', '~> 2.6.6') end diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 1943ca4436..95992c2698 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -14,7 +14,7 @@ module AbstractController # Override AbstractController::Base's process_action to run the # process_action callbacks around the normal behavior. def process_action(method_name, *args) - run_callbacks(:process_action, action_name) do + run_callbacks(:process_action, method_name) do super end end diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 39c804d707..b98429792d 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -8,9 +8,7 @@ module ActionController # === Simple \Basic example # # class PostsController < ApplicationController - # USER_NAME, PASSWORD = "dhh", "secret" - # - # before_filter :authenticate, :except => [ :index ] + # http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index # # def index # render :text => "Everyone can see me!" @@ -19,15 +17,7 @@ module ActionController # def edit # render :text => "I'm only accessible if you know the password" # end - # - # private - # def authenticate - # authenticate_or_request_with_http_basic do |user_name, password| - # user_name == USER_NAME && password == PASSWORD - # end - # end - # end - # + # end # # === Advanced \Basic example # @@ -115,6 +105,18 @@ module ActionController extend self module ControllerMethods + extend ActiveSupport::Concern + + module ClassMethods + def http_basic_authenticate_with(options = {}) + before_filter(options.except(:name, :password, :realm)) do + authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password| + name == options[:name] && password == options[:password] + end + end + end + end + def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure) authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm) end @@ -378,7 +380,6 @@ module ActionController # # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L] module Token - extend self module ControllerMethods @@ -458,6 +459,5 @@ module ActionController controller.__send__ :render, :text => "HTTP Token: Access denied.\n", :status => :unauthorized end end - end end diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb index cfa7004048..4b301c0d90 100644 --- a/actionpack/lib/action_controller/metal/implicit_render.rb +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -1,9 +1,13 @@ module ActionController module ImplicitRender - def send_action(*) - ret = super - default_render unless response_body - ret + def send_action(method, *args) + if respond_to?(method, true) + ret = super + default_render unless response_body + ret + else + default_render + end end def default_render @@ -11,10 +15,8 @@ module ActionController end def method_for_action(action_name) - super || begin - if template_exists?(action_name.to_s, _prefixes) - "default_render" - end + super || if template_exists?(action_name.to_s, _prefixes) + action_name.to_s end end end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 14c424f24b..35be0b3a27 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -104,10 +104,16 @@ module ActionDispatch @options.reverse_merge!(:controller => /.+?/) end + # Add a constraint for wildcard route to make it non-greedy and match the + # optional format part of the route by default + if path.match(/\*([^\/]+)$/) && @options[:format] != false + @options.reverse_merge!(:"#{$1}" => /.+?/) + end + if @options[:format] == false @options.delete(:format) path - elsif path.include?(":format") || path.end_with?('/') || path.match(/^\/?\*/) + elsif path.include?(":format") || path.end_with?('/') path else "#{path}(.:format)" diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb index 01c650a494..bd3e13e6fa 100644 --- a/actionpack/test/controller/http_basic_authentication_test.rb +++ b/actionpack/test/controller/http_basic_authentication_test.rb @@ -6,6 +6,8 @@ class HttpBasicAuthenticationTest < ActionController::TestCase before_filter :authenticate_with_request, :only => :display before_filter :authenticate_long_credentials, :only => :show + http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search + def index render :text => "Hello Secret" end @@ -17,6 +19,10 @@ class HttpBasicAuthenticationTest < ActionController::TestCase def show render :text => 'Only for loooooong credentials' end + + def search + render :text => 'All inline' + end private @@ -104,6 +110,16 @@ class HttpBasicAuthenticationTest < ActionController::TestCase assert assigns(:logged_in) assert_equal 'Definitely Maybe', @response.body end + + test "authenticate with class method" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials('David', 'Goliath') + get :search + assert_response :success + + @request.env['HTTP_AUTHORIZATION'] = encode_credentials('David', 'WRONG!') + get :search + assert_response :unauthorized + end private diff --git a/actionpack/test/action_dispatch/routing/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb index e21b271907..b6c08ffc33 100644 --- a/actionpack/test/action_dispatch/routing/mapper_test.rb +++ b/actionpack/test/dispatch/mapper_test.rb @@ -25,6 +25,10 @@ module ActionDispatch def conditions routes.map { |x| x[1] } end + + def requirements + routes.map { |x| x[2] } + end end def test_initialize @@ -50,8 +54,34 @@ module ActionDispatch def test_map_wildcard fakeset = FakeSet.new mapper = Mapper.new fakeset - mapper.match '/*path', :to => 'pages#show', :as => :page + mapper.match '/*path', :to => 'pages#show' + assert_equal '/*path(.:format)', fakeset.conditions.first[:path_info] + assert_equal(/.+?/, fakeset.requirements.first[:path]) + end + + def test_map_wildcard_with_other_element + fakeset = FakeSet.new + mapper = Mapper.new fakeset + mapper.match '/*path/foo/:bar', :to => 'pages#show' + assert_equal '/*path/foo/:bar(.:format)', fakeset.conditions.first[:path_info] + assert_nil fakeset.requirements.first[:path] + end + + def test_map_wildcard_with_multiple_wildcard + fakeset = FakeSet.new + mapper = Mapper.new fakeset + mapper.match '/*foo/*bar', :to => 'pages#show' + assert_equal '/*foo/*bar(.:format)', fakeset.conditions.first[:path_info] + assert_nil fakeset.requirements.first[:foo] + assert_equal(/.+?/, fakeset.requirements.first[:bar]) + end + + def test_map_wildcard_with_format_false + fakeset = FakeSet.new + mapper = Mapper.new fakeset + mapper.match '/*path', :to => 'pages#show', :format => false assert_equal '/*path', fakeset.conditions.first[:path_info] + assert_nil fakeset.requirements.first[:path] end end end diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index aca2fef170..fd1f824a39 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -2720,11 +2720,11 @@ class DateHelperTest < ActionView::TestCase end def test_time_tag_pubdate_option - assert_match /<time.*pubdate="pubdate">.*<\/time>/, time_tag(Time.now, :pubdate => true) + assert_match(/<time.*pubdate="pubdate">.*<\/time>/, time_tag(Time.now, :pubdate => true)) end def test_time_tag_with_given_text - assert_match /<time.*>Right now<\/time>/, time_tag(Time.now, 'Right now') + assert_match(/<time.*>Right now<\/time>/, time_tag(Time.now, 'Right now')) end def test_time_tag_with_different_format diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 3b6a7d7208..e536d2b408 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,8 @@ *Rails 3.1.0 (unreleased)* +* ConnectionManagement middleware is changed to clean up the connection pool + after the rack body has been flushed. + * Added an update_column method on ActiveRecord. This new method updates a given attribute on an object, skipping validations and callbacks. It is recommended to use #update_attribute unless you are sure you do not want to execute any callback, including the modification of the updated_at column. It should not be called on new records. diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index fcdd31ddea..5f06452247 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -17,6 +17,11 @@ module ActiveRecord @primary_key ||= reset_primary_key end + # Returns a quoted version of the primary key name, used to construct SQL statements. + def quoted_primary_key + @quoted_primary_key ||= connection.quote_column_name(primary_key) + end + def reset_primary_key #:nodoc: key = self == base_class ? get_primary_key(base_class.name) : base_class.primary_key @@ -43,7 +48,12 @@ module ActiveRecord end attr_accessor :original_primary_key - attr_writer :primary_key + + # Attribute writer for the primary key column + def primary_key=(value) + @quoted_primary_key = nil + @primary_key = value + end # Sets the name of the primary key column to use to the given value, # or (if the value is nil or false) to the value returned by the given @@ -53,6 +63,7 @@ module ActiveRecord # set_primary_key "sysid" # end def set_primary_key(value = nil, &block) + @quoted_primary_key = nil @primary_key ||= '' self.original_primary_key = @primary_key value &&= value.to_s diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b778b0c0f0..fe81c7dc2f 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -437,9 +437,10 @@ module ActiveRecord #:nodoc: self._attr_readonly = [] class << self # Class methods - delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped + delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped + delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped delegate :find_each, :find_in_batches, :to => :scoped - delegate :select, :group, :order, :except, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped + delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped # Executes a custom SQL query against your database and returns all the results. The results will 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 4297c26413..b4db1eed18 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -151,6 +151,12 @@ module ActiveRecord @reserved_connections[current_connection_id] ||= checkout end + # Check to see if there is an active connection in this connection + # pool. + def active_connection? + @reserved_connections.key? current_connection_id + end + # Signal that the thread is finished with the current connection. # #release_connection releases the connection-thread association # and returns the connection to the pool. @@ -346,6 +352,12 @@ module ActiveRecord @connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec) end + # Returns true if there are any active connections among the connection + # pools that the ConnectionHandler is managing. + def active_connections? + 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. @@ -405,18 +417,40 @@ module ActiveRecord end class ConnectionManagement + class Proxy # :nodoc: + attr_reader :body, :testing + + def initialize(body, testing = false) + @body = body + @testing = testing + end + + def each(&block) + body.each(&block) + end + + def close + body.close if body.respond_to?(:close) + + # Don't return connection (and perform implicit rollback) if + # this request is a part of integration test + ActiveRecord::Base.clear_active_connections! unless testing + end + end + def initialize(app) @app = app end def call(env) - @app.call(env) - ensure - # Don't return connection (and perform implicit rollback) if - # this request is a part of integration test - unless env.key?("rack.test") - ActiveRecord::Base.clear_active_connections! - end + testing = env.key?('rack.test') + + status, headers, body = @app.call(env) + + [status, headers, Proxy.new(body, testing)] + rescue + ActiveRecord::Base.clear_active_connections! unless testing + raise end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index d88720c8bf..bcd3abc08d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -116,7 +116,11 @@ module ActiveRecord connection_handler.remove_connection(klass) end - delegate :clear_active_connections!, :clear_reloadable_connections!, + def clear_active_connections! + connection_handler.clear_active_connections! + end + + delegate :clear_reloadable_connections!, :clear_all_connections!,:verify_active_connections!, :to => :connection_handler end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index ae61d6ce94..32229a8410 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -222,7 +222,7 @@ module ActiveRecord # SCHEMA STATEMENTS ======================================== - def tables(name = nil) #:nodoc: + def tables(name = 'SCHEMA') #:nodoc: sql = <<-SQL SELECT name FROM sqlite_master @@ -350,7 +350,7 @@ module ActiveRecord end def table_structure(table_name) - structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})").to_hash + structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? structure end diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index afadbf03ef..d31e321440 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -23,6 +23,9 @@ module ActiveRecord return unless logger.debug? payload = event.payload + + return if 'SCHEMA' == payload[:name] + name = '%s (%.1fms)' % [payload[:name], event.duration] sql = payload[:sql].squeeze(' ') binds = nil diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 8e545f9cad..896daf516e 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -12,7 +12,7 @@ module ActiveRecord # These are explicitly delegated to improve performance (avoids method_missing) delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a - delegate :table_name, :primary_key, :to => :klass + delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :to => :klass attr_reader :table, :klass, :loaded attr_accessor :extensions diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index bf5a60f458..d52b84179f 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -83,7 +83,7 @@ module ActiveRecord private def batch_order - "#{table_name}.#{primary_key} ASC" + "#{quoted_table_name}.#{quoted_primary_key} ASC" end end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 563843f3cc..8fa315bdf3 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -183,7 +183,9 @@ module ActiveRecord def exists?(id = nil) id = id.id if ActiveRecord::Base === id - relation = select("1").limit(1) + join_dependency = construct_join_dependency_for_association_find + relation = construct_relation_for_association_find(join_dependency) + relation = relation.except(:select).select("1").limit(1) case id when Array, Hash @@ -192,14 +194,13 @@ module ActiveRecord relation = relation.where(table[primary_key].eq(id)) if id end - relation.first ? true : false + connection.select_value(relation.to_sql) ? true : false end protected def find_with_associations - including = (@eager_load_values + @includes_values).uniq - join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, []) + join_dependency = construct_join_dependency_for_association_find relation = construct_relation_for_association_find(join_dependency) rows = connection.select_all(relation.to_sql, 'SQL', relation.bind_values) join_dependency.instantiate(rows) @@ -207,6 +208,11 @@ module ActiveRecord [] end + def construct_join_dependency_for_association_find + including = (@eager_load_values + @includes_values).uniq + ActiveRecord::Associations::JoinDependency.new(@klass, including, []) + end + def construct_relation_for_association_calculations including = (@eager_load_values + @includes_values).uniq join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 9470e7c6c5..02b7056989 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -62,6 +62,10 @@ module ActiveRecord relation end + def reorder(*args) + except(:order).order(args) + end + def joins(*args) return self if args.compact.blank? diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index dc0e0da4c5..6620464d6a 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -83,4 +83,14 @@ class EachTest < ActiveRecord::TestCase Post.find_in_batches(:batch_size => post_count + 1) {|batch| assert_kind_of Array, batch } end end + + def test_find_in_batches_should_quote_batch_order + c = Post.connection + assert_sql(/ORDER BY #{c.quote_table_name('posts')}.#{c.quote_column_name('id')}/) do + Post.find_in_batches(:batch_size => 1) do |batch| + assert_kind_of Array, batch + assert_kind_of Post, batch.first + end + end + end end diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb new file mode 100644 index 0000000000..abf317768f --- /dev/null +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -0,0 +1,33 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class ConnectionHandlerTest < ActiveRecord::TestCase + def setup + @handler = ConnectionHandler.new + @handler.establish_connection 'america', Base.connection_pool.spec + @klass = Struct.new(:name).new('america') + end + + def test_retrieve_connection + assert @handler.retrieve_connection(@klass) + end + + def test_active_connections? + assert !@handler.active_connections? + assert @handler.retrieve_connection(@klass) + assert @handler.active_connections? + @handler.clear_active_connections! + assert !@handler.active_connections? + end + + def test_retrieve_connection_pool_with_ar_base + assert_nil @handler.retrieve_connection_pool(ActiveRecord::Base) + end + + def test_retrieve_connection_pool + assert_not_nil @handler.retrieve_connection_pool(@klass) + end + end + end +end diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb index c535119972..85871aebdf 100644 --- a/activerecord/test/cases/connection_management_test.rb +++ b/activerecord/test/cases/connection_management_test.rb @@ -1,25 +1,82 @@ require "cases/helper" -class ConnectionManagementTest < ActiveRecord::TestCase - def setup - @env = {} - @app = stub('App') - @management = ActiveRecord::ConnectionAdapters::ConnectionManagement.new(@app) - - @connections_cleared = false - ActiveRecord::Base.stubs(:clear_active_connections!).with { @connections_cleared = true } - end +module ActiveRecord + module ConnectionAdapters + class ConnectionManagementTest < ActiveRecord::TestCase + class App + attr_reader :calls + def initialize + @calls = [] + end - test "clears active connections after each call" do - @app.expects(:call).with(@env) - @management.call(@env) - assert @connections_cleared - end + def call(env) + @calls << env + [200, {}, ['hi mom']] + end + end + + def setup + @env = {} + @app = App.new + @management = ConnectionManagement.new(@app) + + # make sure we have an active connection + assert ActiveRecord::Base.connection + assert ActiveRecord::Base.connection_handler.active_connections? + end + + def test_app_delegation + manager = ConnectionManagement.new(@app) + + manager.call @env + assert_equal [@env], @app.calls + end + + def test_connections_are_active_after_call + @management.call(@env) + assert ActiveRecord::Base.connection_handler.active_connections? + end + + def test_body_responds_to_each + _, _, body = @management.call(@env) + bits = [] + body.each { |bit| bits << bit } + assert_equal ['hi mom'], bits + end + + def test_connections_are_cleared_after_body_close + _, _, body = @management.call(@env) + body.close + assert !ActiveRecord::Base.connection_handler.active_connections? + end + + def test_active_connections_are_not_cleared_on_body_close_during_test + @env['rack.test'] = true + _, _, body = @management.call(@env) + body.close + assert ActiveRecord::Base.connection_handler.active_connections? + end + + def test_connections_closed_if_exception + app = Class.new(App) { def call(env); raise; end }.new + explosive = ConnectionManagement.new(app) + assert_raises(RuntimeError) { explosive.call(@env) } + assert !ActiveRecord::Base.connection_handler.active_connections? + end + + def test_connections_not_closed_if_exception_and_test + @env['rack.test'] = true + app = Class.new(App) { def call(env); raise; end }.new + explosive = ConnectionManagement.new(app) + assert_raises(RuntimeError) { explosive.call(@env) } + assert ActiveRecord::Base.connection_handler.active_connections? + end - test "doesn't clear active connections when running in a test case" do - @env['rack.test'] = true - @app.expects(:call).with(@env) - @management.call(@env) - assert !@connections_cleared + test "doesn't clear active connections when running in a test case" do + @env['rack.test'] = true + @management.call(@env) + assert ActiveRecord::Base.connection_handler.active_connections? + end + end end end diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 7ac14fa8d6..f92f4e62c5 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -18,6 +18,14 @@ module ActiveRecord end end + def test_active_connection? + assert !@pool.active_connection? + assert @pool.connection + assert @pool.active_connection? + @pool.release_connection + assert !@pool.active_connection? + end + def test_pool_caches_columns columns = @pool.columns['posts'] assert_equal columns, @pool.columns['posts'] diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 543981b4a0..014588b6d9 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -74,6 +74,11 @@ class FinderTest < ActiveRecord::TestCase end end + def test_exists_does_not_instantiate_records + Developer.expects(:instantiate).never + Developer.exists? + end + def test_find_by_array_of_one_id assert_kind_of(Array, Topic.find([ 1 ])) assert_equal(1, Topic.find([ 1 ]).length) @@ -203,6 +208,14 @@ class FinderTest < ActiveRecord::TestCase end end + def test_model_class_responds_to_first_bang + assert_equal topics(:first), Topic.order(:id).first! + assert_raises ActiveRecord::RecordNotFound do + Topic.delete_all + Topic.first! + end + end + def test_last_bang_present assert_nothing_raised do assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").last! @@ -215,6 +228,14 @@ class FinderTest < ActiveRecord::TestCase end end + def test_model_class_responds_to_last_bang + assert_equal topics(:fourth), Topic.last! + assert_raises ActiveRecord::RecordNotFound do + Topic.delete_all + Topic.last! + end + end + def test_unexisting_record_exception_handling assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1).parent diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index cbaaca764b..8ebde933b4 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -22,6 +22,33 @@ class LogSubscriberTest < ActiveRecord::TestCase ActiveRecord::Base.logger = logger end + def test_schema_statements_are_ignored + event = Struct.new(:duration, :payload) + + logger = Class.new(ActiveRecord::LogSubscriber) { + attr_accessor :debugs + + def initialize + @debugs = [] + super + end + + def debug message + @debugs << message + end + }.new + assert_equal 0, logger.debugs.length + + logger.sql(event.new(0, { :sql => 'hi mom!' })) + assert_equal 1, logger.debugs.length + + logger.sql(event.new(0, { :sql => 'hi mom!', :name => 'foo' })) + assert_equal 2, logger.debugs.length + + logger.sql(event.new(0, { :sql => 'hi mom!', :name => 'SCHEMA' })) + assert_equal 2, logger.debugs.length + end + def test_basic_query_logging Developer.all wait diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index fb050c3e52..9b20ea08de 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -64,7 +64,7 @@ class NamedScopeTest < ActiveRecord::TestCase assert Reply.scopes.include?(:base) assert_equal Reply.find(:all), Reply.base end - + def test_classes_dont_inherit_subclasses_scopes assert !ActiveRecord::Base.scopes.include?(:base) end @@ -246,6 +246,12 @@ class NamedScopeTest < ActiveRecord::TestCase assert_no_queries { assert topics.any? } end + def test_model_class_should_respond_to_any + assert Topic.any? + Topic.delete_all + assert !Topic.any? + end + def test_many_should_not_load_results topics = Topic.base assert_queries(2) do @@ -280,6 +286,15 @@ class NamedScopeTest < ActiveRecord::TestCase assert Topic.base.many? end + def test_model_class_should_respond_to_many + Topic.delete_all + assert !Topic.many? + Topic.create! + assert !Topic.many? + Topic.create! + assert Topic.many? + end + def test_should_build_on_top_of_scope topic = Topic.approved.build({}) assert topic.approved diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 8e6141eb81..9aa13f04cd 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -457,7 +457,7 @@ class PersistencesTest < ActiveRecord::TestCase assert_equal prev_month, developer.updated_at developer.reload - assert_equal prev_month, developer.updated_at + assert_equal prev_month.to_i, developer.updated_at.to_i end def test_update_column_with_one_changed_and_one_updated diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 63d8c7d1c1..05a41d8a0a 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -136,4 +136,13 @@ class PrimaryKeysTest < ActiveRecord::TestCase assert_nil ActiveRecord::Base.connection.primary_key('developers_projects') end end + + def test_quoted_primary_key_after_set_primary_key + k = Class.new( ActiveRecord::Base ) + assert_equal k.connection.quote_column_name("id"), k.quoted_primary_key + k.primary_key = "foo" + assert_equal k.connection.quote_column_name("foo"), k.quoted_primary_key + k.set_primary_key "bar" + assert_equal k.connection.quote_column_name("bar"), k.quoted_primary_key + end end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 7369aaea1d..30a783d5a2 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -429,9 +429,9 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal expected, received end - def test_except_and_order_overrides_default_scope_order + def test_reorder_overrides_default_scope_order expected = Developer.order('name DESC').collect { |dev| dev.name } - received = DeveloperOrderedBySalary.except(:order).order('name DESC').collect { |dev| dev.name } + received = DeveloperOrderedBySalary.reorder('name DESC').collect { |dev| dev.name } assert_equal expected, received end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 4c5c871251..2304c20a5f 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -151,6 +151,12 @@ class RelationTest < ActiveRecord::TestCase assert_equal topics(:fourth).title, topics.first.title end + def test_finding_with_reorder + topics = Topic.order('author_name').order('title').reorder('id').all + topics_titles = topics.map{ |t| t.title } + assert_equal ['The First Topic', 'The Second Topic of the day', 'The Third Topic of the day', 'The Fourth Topic of the day'], topics_titles + end + def test_finding_with_order_and_take entrants = Entrant.order("id ASC").limit(2).to_a diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 2c5d9e67e3..f3a10b8b92 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -962,6 +962,26 @@ Client.exists? The above returns +false+ if the +clients+ table is empty and +true+ otherwise. +You can also use +any?+ and +many?+ to check for existence on a model or relation. + +<ruby> +# via a model +Post.any? +Post.many? + +# via a named scope +Post.recent.any? +Post.recent.many? + +# via a relation +Post.where(:published => true).any? +Post.where(:published => true).many? + +# via an association +Post.first.categories.any? +Post.first.categories.many? +</ruby> + h3. Calculations This section uses count as an example method in this preamble, but the options described apply to all sub-sections. diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index c447fd911a..58b75b9a1d 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -557,6 +557,18 @@ match '*a/foo/*b' => 'test#index' would match +zoo/woo/foo/bar/baz+ with +params[:a]+ equals +"zoo/woo"+, and +params[:b]+ equals +"bar/baz"+. +NOTE: Starting from Rails 3.1, wildcard route will always matching the optional format segment by default. For example if you have this route: + +<ruby> +map '*pages' => 'pages#show' +</ruby> + +NOTE: By requesting +"/foo/bar.json"+, your +params[:pages]+ will be equals to +"foo/bar"+ with the request format of JSON. If you want the old 3.0.x behavior back, you could supply +:format => false+ like this: + +<ruby> +map '*pages' => 'pages#show', :format => false +</ruby> + h4. Redirection You can redirect any path to another path using the +redirect+ helper in your router: diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 4fc23fe277..ee265366ff 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -360,11 +360,11 @@ module Rails def isolate_namespace(mod) engine_name(generate_railtie_name(mod)) - name = engine_name - self.routes.default_scope = {:module => name} + self.routes.default_scope = { :module => ActiveSupport::Inflector.underscore(mod.name) } self.isolated = true unless mod.respond_to?(:_railtie) + name = engine_name _railtie = self mod.singleton_class.instance_eval do define_method(:_railtie) do diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 0ce00db3c4..20797a2b0c 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -559,6 +559,45 @@ module RailtiesTest assert_match /name="post\[title\]"/, last_response.body end + test "isolated engine should set correct route module prefix for nested namespace" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + module Awesome + class Engine < ::Rails::Engine + isolate_namespace Bukkits::Awesome + end + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + mount Bukkits::Awesome::Engine => "/bukkits", :as => "bukkits" + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Awesome::Engine.routes.draw do + match "/foo" => "foo#index" + end + RUBY + + @plugin.write "app/controllers/bukkits/awesome/foo_controller.rb", <<-RUBY + class Bukkits::Awesome::FooController < ActionController::Base + def index + render :text => "ok" + end + end + RUBY + + add_to_config("config.action_dispatch.show_exceptions = false") + + boot_rails + + get("/bukkits/foo") + assert_equal "ok", last_response.body + end + test "loading seed data" do @plugin.write "db/seeds.rb", <<-RUBY Bukkits::Engine.config.bukkits_seeds_loaded = true |