diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2008-05-11 23:50:08 +0100 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2008-05-11 23:50:08 +0100 |
commit | cb50a2880759f311148abda55ab60be772b8aa51 (patch) | |
tree | 3f8f851f332c0976349de9dc2b4a722946b4cc5e | |
parent | 35634feb474cc55fbc95edeffe98cec241d45f23 (diff) | |
parent | 9a137506a1267ec5938fcec4d2ff135f15037459 (diff) | |
download | rails-cb50a2880759f311148abda55ab60be772b8aa51.tar.gz rails-cb50a2880759f311148abda55ab60be772b8aa51.tar.bz2 rails-cb50a2880759f311148abda55ab60be772b8aa51.zip |
Merge commit 'mainstream/master'
33 files changed, 393 insertions, 109 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index baba4ae5ed..2caaa40bf6 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,9 @@ *SVN* +* Fixed that forgery protection can be used without session tracking (Peter Jones) [#139] + +* Added session(:on) to turn session management back on in a controller subclass if the superclass turned it off (Peter Jones) [#136] + * InstanceTag#default_time_from_options with hash args uses Time.current as default; respects hash settings when time falls in system local spring DST gap [Geoff Buesing] * select_date defaults to Time.zone.today when config.time_zone is set [Geoff Buesing] diff --git a/actionpack/lib/action_controller/flash.rb b/actionpack/lib/action_controller/flash.rb index 692168f230..0148fb5c04 100644 --- a/actionpack/lib/action_controller/flash.rb +++ b/actionpack/lib/action_controller/flash.rb @@ -28,7 +28,6 @@ module ActionController #:nodoc: base.class_eval do include InstanceMethods alias_method_chain :assign_shortcuts, :flash - alias_method_chain :process_cleanup, :flash alias_method_chain :reset_session, :flash end end @@ -166,11 +165,7 @@ module ActionController #:nodoc: def assign_shortcuts_with_flash(request, response) #:nodoc: assign_shortcuts_without_flash(request, response) flash(:refresh) - end - - def process_cleanup_with_flash - flash.sweep if @_session - process_cleanup_without_flash + flash.sweep if @_session && !component_request? end end end diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 22dbc8bbc5..643ff7e5f4 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -33,11 +33,17 @@ module ActionController # Returns plural/singular for a record or class. Example: # - # partial_path(post) # => "posts/post" - # partial_path(Person) # => "people/person" - def partial_path(record_or_class) + # partial_path(post) # => "posts/post" + # partial_path(Person) # => "people/person" + # partial_path(Person, "admin/games") # => "admin/people/person" + def partial_path(record_or_class, controller_path = nil) klass = class_from_record_or_class(record_or_class) - "#{klass.name.tableize}/#{klass.name.demodulize.underscore}" + + if controller_path && controller_path.include?("/") + "#{File.dirname(controller_path)}/#{klass.name.tableize}/#{klass.name.demodulize.underscore}" + else + "#{klass.name.tableize}/#{klass.name.demodulize.underscore}" + end end # The DOM class convention is to use the singular form of an object or class. Examples: diff --git a/actionpack/lib/action_controller/request_forgery_protection.rb b/actionpack/lib/action_controller/request_forgery_protection.rb index 5daf14eb30..139e91ecf9 100644 --- a/actionpack/lib/action_controller/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/request_forgery_protection.rb @@ -105,12 +105,12 @@ module ActionController #:nodoc: # Sets the token value for the current session. Pass a <tt>:secret</tt> option # in +protect_from_forgery+ to add a custom salt to the hash. def form_authenticity_token - @form_authenticity_token ||= if request_forgery_protection_options[:secret] + @form_authenticity_token ||= if !session.respond_to?(:session_id) + raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session." + elsif request_forgery_protection_options[:secret] authenticity_token_from_session_id elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest) authenticity_token_from_cookie_session - elsif session.nil? - raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session." else raise InvalidAuthenticityToken, "No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store)." end diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb index 24ea8c7f2d..b142d18b47 100644 --- a/actionpack/lib/action_controller/routing/segments.rb +++ b/actionpack/lib/action_controller/routing/segments.rb @@ -244,11 +244,12 @@ module ActionController end class PathSegment < DynamicSegment #:nodoc: - RESERVED_PCHAR = "#{Segment::RESERVED_PCHAR}/" - UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze - def interpolation_chunk(value_code = "#{local_name}") - "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::PathSegment::UNSAFE_PCHAR)}" + "\#{#{value_code}}" + end + + def extract_value + "#{local_name} = hash[:#{key}] && hash[:#{key}].collect { |path_component| URI.escape(path_component, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}" end def default diff --git a/actionpack/lib/action_controller/session_management.rb b/actionpack/lib/action_controller/session_management.rb index 8680104420..80a3ddd2c5 100644 --- a/actionpack/lib/action_controller/session_management.rb +++ b/actionpack/lib/action_controller/session_management.rb @@ -69,11 +69,16 @@ module ActionController #:nodoc: # session :off, # :if => Proc.new { |req| !(req.format.html? || req.format.js?) } # + # # turn the session back on, useful when it was turned off in the + # # application controller, and you need it on in another controller + # session :on + # # All session options described for ActionController::Base.process_cgi # are valid arguments. def session(*args) options = args.extract_options! + options[:disabled] = false if args.delete(:on) options[:disabled] = true if !args.empty? options[:only] = [*options[:only]].map { |o| o.to_s } if options[:only] options[:except] = [*options[:except]].map { |o| o.to_s } if options[:except] diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index 3129ff414e..6c0a7ec25c 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -48,6 +48,11 @@ module ActionView # config.action_view.sanitized_allowed_attributes = 'id', 'class', 'style' # end # + # Please note that sanitizing user-provided text does not guarantee that the + # resulting markup is valid (conforming to a document type) or even well-formed. + # The output may still contain e.g. unescaped '<', '>', '&' characters and + # confuse browsers. + # def sanitize(html, options = {}) self.class.white_list_sanitizer.sanitize(html, options) end diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb index a708ecb3fb..6b294be6bd 100644 --- a/actionpack/lib/action_view/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -119,7 +119,7 @@ module ActionView "" end else - render_partial(ActionController::RecordIdentifier.partial_path(partial_path), partial_path, local_assigns) + render_partial(ActionController::RecordIdentifier.partial_path(partial_path, controller.class.controller_path), partial_path, local_assigns) end end @@ -147,7 +147,7 @@ module ActionView templates = Hash.new i = 0 collection.map do |element| - partial_path = ActionController::RecordIdentifier.partial_path(element) + partial_path = ActionController::RecordIdentifier.partial_path(element, controller.class.controller_path) template = templates[partial_path] ||= ActionView::PartialTemplate.new(self, partial_path, nil, local_assigns) template.counter = i i += 1 diff --git a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb index 32b26206c3..ed10e72953 100644 --- a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb +++ b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb @@ -1,49 +1,49 @@ require 'active_record_unit' -class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase - fixtures :developers, :projects, :developers_projects, :topics, :replies, :companies, :mascots +class RenderPartialWithRecordIdentificationController < ActionController::Base + def render_with_has_many_and_belongs_to_association + @developer = Developer.find(1) + render :partial => @developer.projects + end - class RenderPartialWithRecordIdentificationController < ActionController::Base - def render_with_has_many_and_belongs_to_association - @developer = Developer.find(1) - render :partial => @developer.projects - end - - def render_with_has_many_association - @topic = Topic.find(1) - render :partial => @topic.replies - end - - def render_with_named_scope - render :partial => Reply.base - end - - def render_with_has_many_through_association - @developer = Developer.find(:first) - render :partial => @developer.topics - end - - def render_with_has_one_association - @company = Company.find(1) - render :partial => @company.mascot - end - - def render_with_belongs_to_association - @reply = Reply.find(1) - render :partial => @reply.topic - end - - def render_with_record - @developer = Developer.find(:first) - render :partial => @developer - end - - def render_with_record_collection - @developers = Developer.find(:all) - render :partial => @developers - end + def render_with_has_many_association + @topic = Topic.find(1) + render :partial => @topic.replies + end + + def render_with_named_scope + render :partial => Reply.base + end + + def render_with_has_many_through_association + @developer = Developer.find(:first) + render :partial => @developer.topics + end + + def render_with_has_one_association + @company = Company.find(1) + render :partial => @company.mascot + end + + def render_with_belongs_to_association + @reply = Reply.find(1) + render :partial => @reply.topic end - RenderPartialWithRecordIdentificationController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ] + + def render_with_record + @developer = Developer.find(:first) + render :partial => @developer + end + + def render_with_record_collection + @developers = Developer.find(:all) + render :partial => @developers + end +end +RenderPartialWithRecordIdentificationController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ] + +class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase + fixtures :developers, :projects, :developers_projects, :topics, :replies, :companies, :mascots def setup @controller = RenderPartialWithRecordIdentificationController.new @@ -84,3 +84,108 @@ class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase assert_equal mascot.name, @response.body end end + +class RenderPartialWithRecordIdentificationController < ActionController::Base + def render_with_has_many_and_belongs_to_association + @developer = Developer.find(1) + render :partial => @developer.projects + end + + def render_with_has_many_association + @topic = Topic.find(1) + render :partial => @topic.replies + end + + def render_with_has_many_through_association + @developer = Developer.find(:first) + render :partial => @developer.topics + end + + def render_with_belongs_to_association + @reply = Reply.find(1) + render :partial => @reply.topic + end + + def render_with_record + @developer = Developer.find(:first) + render :partial => @developer + end + + def render_with_record_collection + @developers = Developer.find(:all) + render :partial => @developers + end +end +RenderPartialWithRecordIdentificationController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ] + +class Game < Struct.new(:name, :id) + def to_param + id.to_s + end +end + +module Fun + class NestedController < ActionController::Base + def render_with_record_in_nested_controller + render :partial => Game.new("Pong") + end + + def render_with_record_collection_in_nested_controller + render :partial => [ Game.new("Pong"), Game.new("Tank") ] + end + end + NestedController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ] + + module Serious + class NestedDeeperController < ActionController::Base + def render_with_record_in_deeper_nested_controller + render :partial => Game.new("Chess") + end + + def render_with_record_collection_in_deeper_nested_controller + render :partial => [ Game.new("Chess"), Game.new("Sudoku"), Game.new("Solitaire") ] + end + end + NestedDeeperController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ] + end +end + +class RenderPartialWithRecordIdentificationAndNestedControllersTest < ActiveRecordTestCase + def setup + @controller = Fun::NestedController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + super + end + + def test_render_with_record_in_nested_controller + get :render_with_record_in_nested_controller + assert_template 'fun/games/_game' + end + + def test_render_with_record_collection_in_nested_controller + get :render_with_record_collection_in_nested_controller + assert_template 'fun/games/_game' + end + +end + +class RenderPartialWithRecordIdentificationAndNestedDeeperControllersTest < ActiveRecordTestCase + def setup + @controller = Fun::Serious::NestedDeeperController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + super + end + + def test_render_with_record_in_deeper_nested_controller + get :render_with_record_in_deeper_nested_controller + assert_template 'fun/serious/games/_game' + end + + def test_render_with_record_collection_in_deeper_nested_controller + get :render_with_record_collection_in_deeper_nested_controller + assert_template 'fun/serious/games/_game' + end + +end
\ No newline at end of file diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb index def8613215..12c1eaea69 100644 --- a/actionpack/test/controller/record_identifier_test.rb +++ b/actionpack/test/controller/record_identifier_test.rb @@ -57,6 +57,18 @@ class RecordIdentifierTest < Test::Unit::TestCase assert_equal expected, partial_path(Comment) end + def test_partial_path_with_namespaced_controller_path + expected = "admin/#{@plural}/#{@singular}" + assert_equal expected, partial_path(@record, "admin/posts") + assert_equal expected, partial_path(@klass, "admin/posts") + end + + def test_partial_path_with_not_namespaced_controller_path + expected = "#{@plural}/#{@singular}" + assert_equal expected, partial_path(@record, "posts") + assert_equal expected, partial_path(@klass, "posts") + end + def test_dom_class assert_equal @singular, dom_class(@record) end @@ -100,4 +112,28 @@ class NestedRecordIdentifierTest < RecordIdentifierTest assert_equal expected, partial_path(@record) assert_equal expected, partial_path(Comment::Nested) end + + def test_partial_path_with_namespaced_controller_path + expected = "admin/comment/nesteds/nested" + assert_equal expected, partial_path(@record, "admin/posts") + assert_equal expected, partial_path(@klass, "admin/posts") + end + + def test_partial_path_with_deeper_namespaced_controller_path + expected = "deeper/admin/comment/nesteds/nested" + assert_equal expected, partial_path(@record, "deeper/admin/posts") + assert_equal expected, partial_path(@klass, "deeper/admin/posts") + end + + def test_partial_path_with_even_deeper_namespaced_controller_path + expected = "even/more/deeper/admin/comment/nesteds/nested" + assert_equal expected, partial_path(@record, "even/more/deeper/admin/posts") + assert_equal expected, partial_path(@klass, "even/more/deeper/admin/posts") + end + + def test_partial_path_with_not_namespaced_controller_path + expected = "comment/nesteds/nested" + assert_equal expected, partial_path(@record, "posts") + assert_equal expected, partial_path(@klass, "posts") + end end diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index d0c3c6e224..7022713e30 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -50,6 +50,14 @@ class CsrfCookieMonsterController < ActionController::Base protect_from_forgery :only => :index end +# sessions are turned off +class SessionOffController < ActionController::Base + protect_from_forgery :secret => 'foobar' + session :off + def rescue_action(e) raise e end + include RequestForgeryProtectionActions +end + class FreeCookieController < CsrfCookieMonsterController self.allow_forgery_protection = false @@ -224,3 +232,19 @@ class FreeCookieControllerTest < Test::Unit::TestCase end end end + +class SessionOffControllerTest < Test::Unit::TestCase + def setup + @controller = SessionOffController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @token = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123') + end + + def test_should_raise_correct_exception + @request.session = {} # session(:off) doesn't appear to work with controller tests + assert_raises(ActionController::InvalidAuthenticityToken) do + post :index, :authenticity_token => @token + end + end +end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 640afd58f8..b28f7bcdff 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -25,7 +25,7 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase ActionController::Routing.use_controllers! ['controller'] @set = ActionController::Routing::RouteSet.new @set.draw do |map| - map.connect ':controller/:action/:variable' + map.connect ':controller/:action/:variable/*additional' end safe, unsafe = %w(: @ & = + $ , ;), %w(^ / ? # [ ]) @@ -36,17 +36,19 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase end def test_route_generation_escapes_unsafe_path_characters - assert_equal "/contr#{@segment}oller/act#{@escaped}ion/var#{@escaped}iable", + assert_equal "/contr#{@segment}oller/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2", @set.generate(:controller => "contr#{@segment}oller", :action => "act#{@segment}ion", - :variable => "var#{@segment}iable") + :variable => "var#{@segment}iable", + :additional => ["add#{@segment}itional-1", "add#{@segment}itional-2"]) end def test_route_recognition_unescapes_path_components options = { :controller => "controller", :action => "act#{@segment}ion", - :variable => "var#{@segment}iable" } - assert_equal options, @set.recognize_path("/controller/act#{@escaped}ion/var#{@escaped}iable") + :variable => "var#{@segment}iable", + :additional => ["add#{@segment}itional-1", "add#{@segment}itional-2"] } + assert_equal options, @set.recognize_path("/controller/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2") end end diff --git a/actionpack/test/controller/session_management_test.rb b/actionpack/test/controller/session_management_test.rb index 495a9153f8..592b0b549d 100644 --- a/actionpack/test/controller/session_management_test.rb +++ b/actionpack/test/controller/session_management_test.rb @@ -13,6 +13,19 @@ class SessionManagementTest < Test::Unit::TestCase end end + class SessionOffOnController < ActionController::Base + session :off + session :on, :only => :tell + + def show + render :text => "done" + end + + def tell + render :text => "done" + end + end + class TestController < ActionController::Base session :off, :only => :show session :session_secure => true, :except => :show @@ -100,6 +113,15 @@ class SessionManagementTest < Test::Unit::TestCase assert_equal false, @request.session_options end + def test_session_off_then_on_globally + @controller = SessionOffOnController.new + get :show + assert_equal false, @request.session_options + get :tell + assert_instance_of Hash, @request.session_options + assert_equal false, @request.session_options[:disabled] + end + def test_session_off_conditionally @controller = TestController.new get :show diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index 04cc2a20d8..ba6c7f4299 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -12,6 +12,11 @@ class TestTest < Test::Unit::TestCase render :text => 'ignore me' end + def set_flash_now + flash.now["test_now"] = ">#{flash["test_now"]}<" + render :text => 'ignore me' + end + def set_session session['string'] = 'A wonder' session[:symbol] = 'it works' @@ -145,6 +150,11 @@ XML assert_equal '>value<', flash['test'] end + def test_process_with_flash_now + process :set_flash_now, nil, nil, { "test_now" => "value_now" } + assert_equal '>value_now<', flash['test_now'] + end + def test_process_with_session process :set_session assert_equal 'A wonder', session['string'], "A value stored in the session should be available by string key" diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 264ea79d4a..4a130bf5c0 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,9 @@ *SVN* +* Ensure hm:t preloading honours reflection options. Resolves #137. [Frederick Cheung] + +* Added protection against duplicate migration names (Aslak Hellesøy) [#112] + * Base#instantiate_time_object: eliminate check for Time.zone, since we can assume this is set if time_zone_aware_attributes is set to true [Geoff Buesing] * Time zone aware attribute methods use Time.zone.parse instead of #to_time for String arguments, so that offset information in String is respected. Resolves #105. [Scott Fleckenstein, Geoff Buesing] diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index da4ebdef51..a3d1f12b03 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -31,12 +31,12 @@ module ActiveRecord private def preload_one_association(records, association, preload_options={}) - reflection = reflections[association] - raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection - - # Not all records have the same class, so group then preload. - records.group_by(&:class).each do |klass, records| - reflection = klass.reflections[association] + class_to_reflection = {} + # Not all records have the same class, so group then preload + # group on the reflection itself so that if various subclass share the same association then we do not split them + # unncessarily + records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records| + raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection send("preload_#{reflection.macro}_association", records, reflection, preload_options) end end @@ -143,7 +143,8 @@ module ActiveRecord through_primary_key = through_reflection.primary_key_name unless through_records.empty? source = reflection.source_reflection.name - through_records.first.class.preload_associations(through_records, source) + #add conditions from reflection! + through_records.first.class.preload_associations(through_records, source, reflection.options) through_records.each do |through_record| add_preloaded_records_to_collection(id_to_record_map[through_record[through_primary_key].to_s], reflection.name, through_record.send(source)) @@ -251,12 +252,12 @@ module ActiveRecord conditions << append_conditions(options, preload_options) reflection.klass.find(:all, - :select => (options[:select] || "#{table_name}.*"), - :include => options[:include], + :select => (preload_options[:select] || options[:select] || "#{table_name}.*"), + :include => preload_options[:include] || options[:include], :conditions => [conditions, ids], :joins => options[:joins], - :group => options[:group], - :order => options[:order]) + :group => preload_options[:group] || options[:group], + :order => preload_options[:order] || options[:order]) end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 74299bd572..7999eec55d 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1910,6 +1910,8 @@ module ActiveRecord #:nodoc: # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'" # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'" def sanitize_sql_for_conditions(condition) + return nil if condition.blank? + case condition when Array; sanitize_sql_array(condition) when Hash; sanitize_sql_hash_for_conditions(condition) diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index af4fb6e83c..5cc9f4e197 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -8,6 +8,12 @@ module ActiveRecord end end + class DuplicateMigrationNameError < ActiveRecordError#:nodoc: + def initialize(name) + super("Multiple migrations have the name #{name}") + end + end + class UnknownMigrationVersionError < ActiveRecordError #:nodoc: def initialize(version) super("No migration with version number #{version}") @@ -440,6 +446,10 @@ module ActiveRecord if klasses.detect { |m| m.version == version } raise DuplicateMigrationVersionError.new(version) end + + if klasses.detect { |m| m.name == name.camelize } + raise DuplicateMigrationNameError.new(name.camelize) + end load(file) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 81b99f8e96..d43ebefc3b 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -71,6 +71,18 @@ module ActiveRecord # end # end # + # + # For testing complex named scopes, you can examine the scoping options using the + # <tt>proxy_options</tt> method on the proxy itself. + # + # class Shirt < ActiveRecord::Base + # named_scope :colored, lambda { |color| + # { :conditions => { :color => color } } + # } + # end + # + # expected_options = { :conditions => { :colored => 'red' } } + # assert_equal expected_options, Shirt.colored('red').proxy_options def named_scope(name, options = {}, &block) scopes[name] = lambda do |parent_scope, *args| Scope.new(parent_scope, case options diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index d25e8cd0da..b3a75121ed 100755 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -640,7 +640,7 @@ module ActiveRecord results = finder_class.with_exclusive_scope do connection.select_all( construct_finder_sql( - :select => "#{attr_name}", + :select => "#{connection.quote_column_name(attr_name)}", :from => "#{finder_class.quoted_table_name}", :conditions => [condition_sql, *condition_params] ) diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 67b57ceb42..3a3358e39b 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -275,6 +275,17 @@ class EagerAssociationTest < ActiveRecord::TestCase Author.find(:first, :order => 'authors.id').hello_post_comments.sort_by(&:id) end + def test_eager_with_has_many_through_join_model_with_conditions_on_top_level + assert_equal comments(:more_greetings), Author.find(authors(:david).id, :include => :comments_with_order_and_conditions).comments_with_order_and_conditions.first + end + + def test_eager_with_has_many_through_join_model_with_include + author_comments = Author.find(authors(:david).id, :include => :comments_with_include).comments_with_include.to_a + assert_no_queries do + author_comments.first.post.title + end + end + def test_eager_with_has_many_and_limit posts = Post.find(:all, :order => 'posts.id asc', :include => [ :author, :comments ], :limit => 2) assert_equal 2, posts.size @@ -592,4 +603,10 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15") end end + + def test_load_with_sti_sharing_association + assert_queries(2) do #should not do 1 query per subclass + Comment.find :all, :include => :post + end + end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 7b97afe42c..9e26e2ad58 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -48,6 +48,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 2, Firm.find(:first).clients.length end + def test_find_with_blank_conditions + [[], {}, nil, ""].each do |blank| + assert_equal 2, Firm.find(:first).clients.find(:all, :conditions => blank).size + end + end + def test_find_many_with_merged_options assert_equal 1, companies(:first_firm).limited_clients.size assert_equal 1, companies(:first_firm).limited_clients.find(:all).size @@ -851,4 +857,4 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert ! firm.clients.include?(client) end -end
\ No newline at end of file +end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 6be31b5f86..527856b4c0 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -984,6 +984,12 @@ if ActiveRecord::Base.connection.supports_migrations? end end + def test_migrator_with_duplicate_names + assert_raises(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do + ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate_names", nil) + end + end + def test_migrator_with_missing_version_numbers assert_raise(ActiveRecord::UnknownMigrationVersionError) do ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 500) diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index e99448c23e..30c074c9d8 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -112,4 +112,10 @@ class NamedScopeTest < ActiveRecord::TestCase assert_equal Topic.find(:all, scope), Topic.scoped(scope) end + + def test_proxy_options + expected_proxy_options = { :conditions => { :approved => true } } + assert_equal expected_proxy_options, Topic.approved.proxy_options + end + end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index e3ca8660ac..a4d9da4806 100755 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -5,6 +5,7 @@ require 'models/reply' require 'models/person' require 'models/developer' require 'models/warehouse_thing' +require 'models/guid' # The following methods in Topic are used in test_conditional_validation_* class Topic @@ -493,6 +494,13 @@ class ValidationsTest < ActiveRecord::TestCase end end + def test_validate_uniqueness_with_columns_which_are_sql_keywords + Guid.validates_uniqueness_of :key + g = Guid.new + g.key = "foo" + assert_nothing_raised { !g.valid? } + end + def test_validate_straight_inheritance_uniqueness w1 = IneptWizard.create(:name => "Rincewind", :city => "Ankh-Morpork") assert w1.valid?, "Saving w1" diff --git a/activerecord/test/migrations/duplicate_names/20080507052938_chunky.rb b/activerecord/test/migrations/duplicate_names/20080507052938_chunky.rb new file mode 100644 index 0000000000..5fe5089e18 --- /dev/null +++ b/activerecord/test/migrations/duplicate_names/20080507052938_chunky.rb @@ -0,0 +1,7 @@ +class Chunky < ActiveRecord::Migration + def self.up + end + + def self.down + end +end diff --git a/activerecord/test/migrations/duplicate_names/20080507053028_chunky.rb b/activerecord/test/migrations/duplicate_names/20080507053028_chunky.rb new file mode 100644 index 0000000000..5fe5089e18 --- /dev/null +++ b/activerecord/test/migrations/duplicate_names/20080507053028_chunky.rb @@ -0,0 +1,7 @@ +class Chunky < ActiveRecord::Migration + def self.up + end + + def self.down + end +end diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 2918139f7f..f63af27403 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -17,6 +17,10 @@ class Author < ActiveRecord::Base end has_many :comments, :through => :posts has_many :comments_containing_the_letter_e, :through => :posts, :source => :comments + has_many :comments_with_order_and_conditions, :through => :posts, :source => :comments, :order => 'comments.body', :conditions => "comments.body like 'Thank%'" + has_many :comments_with_include, :through => :posts, :source => :comments, :include => :post + + has_many :comments_desc, :through => :posts, :source => :comments, :order => 'comments.id DESC' has_many :limited_comments, :through => :posts, :source => :comments, :limit => 1 has_many :funky_comments, :through => :posts, :source => :comments diff --git a/activerecord/test/models/guid.rb b/activerecord/test/models/guid.rb new file mode 100644 index 0000000000..9208dc28fa --- /dev/null +++ b/activerecord/test/models/guid.rb @@ -0,0 +1,2 @@ +class Guid < ActiveRecord::Base +end
\ No newline at end of file diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 818237f076..423929fd55 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -403,6 +403,10 @@ ActiveRecord::Schema.define do create_table(t, :force => true) { } end + create_table :guids, :force => true do |t| + t.column :key, :string + end + except 'SQLite' do # fk_test_has_fk should be before fk_test_has_pk create_table :fk_test_has_fk, :force => true do |t| diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index f72825731e..185aff9088 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Remove unused JSON::RESERVED_WORDS, JSON.valid_identifier? and JSON.reserved_word? methods. Resolves #164. [Cheah Chu Yeow] + * Adding Date.current, which returns Time.zone.today if config.time_zone is set; otherwise returns Date.today [Geoff Buesing] * TimeWithZone: date part getter methods (#year #mon #day etc) are defined on class; no longer relying on method_missing [Geoff Buesing] diff --git a/activesupport/lib/active_support/json.rb b/activesupport/lib/active_support/json.rb index 914cf4f8fe..bbda2c9fa3 100644 --- a/activesupport/lib/active_support/json.rb +++ b/activesupport/lib/active_support/json.rb @@ -1,5 +1,3 @@ - - module ActiveSupport # If true, use ISO 8601 format for dates and times. Otherwise, fall back to the ActiveSupport legacy format. mattr_accessor :use_standard_json_time_format @@ -19,33 +17,6 @@ module ActiveSupport @escape_html_entities_in_json = value end end - - module JSON - RESERVED_WORDS = %w( - abstract delete goto private transient - boolean do if protected try - break double implements public typeof - byte else import return var - case enum in short void - catch export instanceof static volatile - char extends int super while - class final interface switch with - const finally long synchronized - continue float native this - debugger for new throw - default function package throws - ) #:nodoc: - - class << self - def valid_identifier?(key) #:nodoc: - key.to_s =~ /^[[:alpha:]_$][[:alnum:]_$]*$/ && !reserved_word?(key) - end - - def reserved_word?(key) #:nodoc: - RESERVED_WORDS.include?(key.to_s) - end - end - end end require 'active_support/json/encoding' diff --git a/railties/Rakefile b/railties/Rakefile index ef673c234e..4eaa1bef63 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -255,7 +255,7 @@ task :generate_rails_framework_doc do end task :generate_app_doc do - File.cp "doc/README_FOR_APP", "#{PKG_DESTINATION}/doc/README_FOR_APP" + cp "doc/README_FOR_APP", "#{PKG_DESTINATION}/doc/README_FOR_APP" system %{cd #{PKG_DESTINATION}; rake doc:app} end |