diff options
85 files changed, 1175 insertions, 708 deletions
diff --git a/README.rdoc b/README.rdoc deleted file mode 100644 index 6f94d14d8a..0000000000 --- a/README.rdoc +++ /dev/null @@ -1,24 +0,0 @@ -== About - -This is Nick's connection pool branch, wherein he attempts to rewrite Rails' connection handling code to -be more thread-safe, and to add connection pooling features. - -== Goals - -- Preserve Rails' lazy connection acquisition and caching strategy behavior as it exists prior to this work. -- Add ability to configure a connection pool to limit the number of connections made to a database in multiple-thread scenarios. -- Threads will block for a configurable timeout if the pool is exhausted until a connection is available. -- If none is available during the timeout period, an exception will be raised. -- Add a checkout/checkin API to reserve and release a connection to/from the pool. -- Add several different connection handling/pooling classes to serve different needs: - - proper fixed-size connection pool - - connection-per-thread with no maximum on the number of connections - - single thread cached connection - - pass-through to external connection pool (JRuby/JNDI data source connection pool) - -== TODO - -Remaining tasks: - -- Review and put the thing to real work. -- Look at whether existing clear_* or verify_* methods can be deprecated or removed. diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG index bdae0d4d3d..fc02ae8ffc 100644 --- a/actionmailer/CHANGELOG +++ b/actionmailer/CHANGELOG @@ -1,3 +1,8 @@ +* Add layout functionality to mailers [Pratik] + + Mailer layouts behaves just like controller layouts, except layout names need to + have '_mailer' postfix for them to be automatically picked up. + *2.1.0 (May 31st, 2008)* * Fixed that a return-path header would be ignored #7572 [joost] diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 72c94529b5..96e514e0db 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -246,7 +246,10 @@ module ActionMailer #:nodoc: # +implicit_parts_order+. class Base include AdvAttrAccessor, PartContainer - include ActionController::UrlWriter if Object.const_defined?(:ActionController) + if Object.const_defined?(:ActionController) + include ActionController::UrlWriter + include ActionController::Layout + end private_class_method :new #:nodoc: @@ -362,6 +365,7 @@ module ActionMailer #:nodoc: # The mail object instance referenced by this mailer. attr_reader :mail + attr_reader :template_name, :default_template_name, :action_name class << self attr_writer :mailer_name @@ -374,11 +378,16 @@ module ActionMailer #:nodoc: alias_method :controller_name, :mailer_name alias_method :controller_path, :mailer_name - def method_missing(method_symbol, *parameters)#:nodoc: - case method_symbol.id2name - when /^create_([_a-z]\w*)/ then new($1, *parameters).mail - when /^deliver_([_a-z]\w*)/ then new($1, *parameters).deliver! - when "new" then nil + def respond_to?(method_symbol, include_private = false) #:nodoc: + matches_dynamic_method?(method_symbol) || super + end + + def method_missing(method_symbol, *parameters) #:nodoc: + match = matches_dynamic_method?(method_symbol) + case match[1] + when 'create' then new(match[2], *parameters).mail + when 'deliver' then new(match[2], *parameters).deliver! + when 'new' then nil else super end end @@ -424,6 +433,12 @@ module ActionMailer #:nodoc: def template_root=(root) self.view_paths = ActionView::Base.process_view_paths(root) end + + private + def matches_dynamic_method?(method_name) #:nodoc: + method_name = method_name.to_s + /(create|deliver)_([_a-z]\w*)/.match(method_name) || /^(new)$/.match(method_name) + end end # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer @@ -519,6 +534,7 @@ module ActionMailer #:nodoc: @content_type ||= @@default_content_type.dup @implicit_parts_order ||= @@default_implicit_parts_order.dup @template ||= method_name + @default_template_name = @action_name = @template @mailer_name ||= self.class.name.underscore @parts ||= [] @headers ||= {} @@ -535,7 +551,22 @@ module ActionMailer #:nodoc: if opts[:file] && (opts[:file] !~ /\// && !opts[:file].respond_to?(:render)) opts[:file] = "#{mailer_name}/#{opts[:file]}" end - initialize_template_class(body).render(opts) + + begin + old_template, @template = @template, initialize_template_class(body) + layout = respond_to?(:pick_layout, true) ? pick_layout(opts) : false + @template.render(opts.merge(:layout => layout)) + ensure + @template = old_template + end + end + + def default_template_format + :html + end + + def candidate_for_layout?(options) + !@template.send(:_exempt_from_layout?, default_template_name) end def template_root diff --git a/actionmailer/lib/action_mailer/helpers.rb b/actionmailer/lib/action_mailer/helpers.rb index 9c5fcc6afb..5f6dcd77cd 100644 --- a/actionmailer/lib/action_mailer/helpers.rb +++ b/actionmailer/lib/action_mailer/helpers.rb @@ -72,7 +72,7 @@ module ActionMailer methods.flatten.each do |method| master_helper_module.module_eval <<-end_eval def #{method}(*args, &block) - controller.send!(%(#{method}), *args, &block) + controller.__send__(%(#{method}), *args, &block) end end_eval end @@ -92,7 +92,7 @@ module ActionMailer inherited_without_helper(child) begin child.master_helper_module = Module.new - child.master_helper_module.send! :include, master_helper_module + child.master_helper_module.__send__(:include, master_helper_module) child.helper child.name.to_s.underscore rescue MissingSourceFile => e raise unless e.is_missing?("helpers/#{child.name.to_s.underscore}_helper") diff --git a/actionmailer/test/fixtures/auto_layout_mailer/hello.html.erb b/actionmailer/test/fixtures/auto_layout_mailer/hello.html.erb new file mode 100644 index 0000000000..54950788f7 --- /dev/null +++ b/actionmailer/test/fixtures/auto_layout_mailer/hello.html.erb @@ -0,0 +1 @@ +Inside
\ No newline at end of file diff --git a/actionmailer/test/fixtures/explicit_layout_mailer/logout.html.erb b/actionmailer/test/fixtures/explicit_layout_mailer/logout.html.erb new file mode 100644 index 0000000000..0533a3b2fe --- /dev/null +++ b/actionmailer/test/fixtures/explicit_layout_mailer/logout.html.erb @@ -0,0 +1 @@ +You logged out
\ No newline at end of file diff --git a/actionmailer/test/fixtures/explicit_layout_mailer/signup.html.erb b/actionmailer/test/fixtures/explicit_layout_mailer/signup.html.erb new file mode 100644 index 0000000000..4789e888c6 --- /dev/null +++ b/actionmailer/test/fixtures/explicit_layout_mailer/signup.html.erb @@ -0,0 +1 @@ +We do not spam
\ No newline at end of file diff --git a/actionmailer/test/fixtures/layouts/auto_layout_mailer.html.erb b/actionmailer/test/fixtures/layouts/auto_layout_mailer.html.erb new file mode 100644 index 0000000000..932271450c --- /dev/null +++ b/actionmailer/test/fixtures/layouts/auto_layout_mailer.html.erb @@ -0,0 +1 @@ +Hello from layout <%= yield %>
\ No newline at end of file diff --git a/actionmailer/test/fixtures/layouts/spam.html.erb b/actionmailer/test/fixtures/layouts/spam.html.erb new file mode 100644 index 0000000000..619d6b16b4 --- /dev/null +++ b/actionmailer/test/fixtures/layouts/spam.html.erb @@ -0,0 +1 @@ +Spammer layout <%= yield %>
\ No newline at end of file diff --git a/actionmailer/test/mail_layout_test.rb b/actionmailer/test/mail_layout_test.rb new file mode 100644 index 0000000000..ffba9a16bd --- /dev/null +++ b/actionmailer/test/mail_layout_test.rb @@ -0,0 +1,78 @@ +require 'abstract_unit' + +class AutoLayoutMailer < ActionMailer::Base + def hello(recipient) + recipients recipient + subject "You have a mail" + from "tester@example.com" + end + + def spam(recipient) + recipients recipient + subject "You have a mail" + from "tester@example.com" + body render(:inline => "Hello, <%= @world %>", :layout => 'spam', :body => { :world => "Earth" }) + end + + def nolayout(recipient) + recipients recipient + subject "You have a mail" + from "tester@example.com" + body render(:inline => "Hello, <%= @world %>", :layout => false, :body => { :world => "Earth" }) + end +end + +class ExplicitLayoutMailer < ActionMailer::Base + layout 'spam', :except => [:logout] + + def signup(recipient) + recipients recipient + subject "You have a mail" + from "tester@example.com" + end + + def logout(recipient) + recipients recipient + subject "You have a mail" + from "tester@example.com" + end +end + +class LayoutMailerTest < Test::Unit::TestCase + def setup + set_delivery_method :test + ActionMailer::Base.perform_deliveries = true + ActionMailer::Base.deliveries = [] + + @recipient = 'test@localhost' + end + + def teardown + restore_delivery_method + end + + def test_should_pickup_default_layout + mail = AutoLayoutMailer.create_hello(@recipient) + assert_equal "Hello from layout Inside", mail.body.strip + end + + def test_should_pickup_layout_given_to_render + mail = AutoLayoutMailer.create_spam(@recipient) + assert_equal "Spammer layout Hello, Earth", mail.body.strip + end + + def test_should_respect_layout_false + mail = AutoLayoutMailer.create_nolayout(@recipient) + assert_equal "Hello, Earth", mail.body.strip + end + + def test_explicit_class_layout + mail = ExplicitLayoutMailer.create_signup(@recipient) + assert_equal "Spammer layout We do not spam", mail.body.strip + end + + def test_explicit_layout_exceptions + mail = ExplicitLayoutMailer.create_logout(@recipient) + assert_equal "You logged out", mail.body.strip + end +end diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index 882b07d675..f57c6f3fb8 100644 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -968,3 +968,55 @@ class MethodNamingTest < Test::Unit::TestCase end end end + +class RespondToTest < Test::Unit::TestCase + class RespondToMailer < ActionMailer::Base; end + + def setup + set_delivery_method :test + end + + def teardown + restore_delivery_method + end + + def test_should_respond_to_new + assert RespondToMailer.respond_to?(:new) + end + + def test_should_respond_to_create_with_template_suffix + assert RespondToMailer.respond_to?(:create_any_old_template) + end + + def test_should_respond_to_deliver_with_template_suffix + assert RespondToMailer.respond_to?(:deliver_any_old_template) + end + + def test_should_not_respond_to_new_with_template_suffix + assert !RespondToMailer.respond_to?(:new_any_old_template) + end + + def test_should_not_respond_to_create_with_template_suffix_unless_it_is_separated_by_an_underscore + assert !RespondToMailer.respond_to?(:createany_old_template) + end + + def test_should_not_respond_to_deliver_with_template_suffix_unless_it_is_separated_by_an_underscore + assert !RespondToMailer.respond_to?(:deliverany_old_template) + end + + def test_should_not_respond_to_create_with_template_suffix_if_it_begins_with_a_uppercase_letter + assert !RespondToMailer.respond_to?(:create_Any_old_template) + end + + def test_should_not_respond_to_deliver_with_template_suffix_if_it_begins_with_a_uppercase_letter + assert !RespondToMailer.respond_to?(:deliver_Any_old_template) + end + + def test_should_not_respond_to_create_with_template_suffix_if_it_begins_with_a_digit + assert !RespondToMailer.respond_to?(:create_1_template) + end + + def test_should_not_respond_to_deliver_with_template_suffix_if_it_begins_with_a_digit + assert !RespondToMailer.respond_to?(:deliver_1_template) + end +end diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index a3540e90e4..88af60ed62 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,21 @@ *Edge* +* Add support for shallow nesting of routes. #838 [S. Brent Faulkner] + + Example : + + map.resources :users, :shallow => true do |user| + user.resources :posts + end + + - GET /users/1/posts (maps to PostsController#index action as usual) + named route "user_posts" is added as usual. + + - GET /posts/2 (maps to PostsController#show action as if it were not nested) + Additionally, named route "post" is added too. + +* Added button_to_remote helper. #3641 [Donald Piret, Tarmo Tänav] + * Deprecate render_component. Please use render_component plugin from http://github.com/rails/render_component/tree/master [Pratik] * Routes may be restricted to lists of HTTP methods instead of a single method or :any. #407 [Brennan Dunn, Gaius Centus Novus] diff --git a/actionpack/lib/action_controller/assertions/selector_assertions.rb b/actionpack/lib/action_controller/assertions/selector_assertions.rb index 2264d5d3a3..bcbb570e4b 100644 --- a/actionpack/lib/action_controller/assertions/selector_assertions.rb +++ b/actionpack/lib/action_controller/assertions/selector_assertions.rb @@ -396,54 +396,31 @@ module ActionController # # The same, but shorter. # assert_select "ol>li", 4 def assert_select_rjs(*args, &block) - rjs_type = nil - arg = args.shift + rjs_type = args.first.is_a?(Symbol) ? args.shift : nil + id = args.first.is_a?(String) ? args.shift : nil # If the first argument is a symbol, it's the type of RJS statement we're looking # for (update, replace, insertion, etc). Otherwise, we're looking for just about # any RJS statement. - if arg.is_a?(Symbol) - rjs_type = arg - + if rjs_type if rjs_type == :insert - arg = args.shift - position = arg - insertion = "insert_#{arg}".to_sym - raise ArgumentError, "Unknown RJS insertion type #{arg}" unless RJS_STATEMENTS[insertion] + position = args.shift + insertion = "insert_#{position}".to_sym + raise ArgumentError, "Unknown RJS insertion type #{position}" unless RJS_STATEMENTS[insertion] statement = "(#{RJS_STATEMENTS[insertion]})" else raise ArgumentError, "Unknown RJS statement type #{rjs_type}" unless RJS_STATEMENTS[rjs_type] statement = "(#{RJS_STATEMENTS[rjs_type]})" end - arg = args.shift else statement = "#{RJS_STATEMENTS[:any]}" end - position ||= Regexp.new(RJS_INSERTIONS.join('|')) # Next argument we're looking for is the element identifier. If missing, we pick - # any element. - if arg.is_a?(String) - id = Regexp.quote(arg) - arg = args.shift - else - id = "[^\"]*" - end - - pattern = - case rjs_type - when :chained_replace, :chained_replace_html - Regexp.new("\\$\\(\"#{id}\"\\)#{statement}\\(#{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE) - when :remove, :show, :hide, :toggle - Regexp.new("#{statement}\\(\"#{id}\"\\)") - when :replace, :replace_html - Regexp.new("#{statement}\\(\"#{id}\", #{RJS_PATTERN_HTML}\\)") - when :insert, :insert_html - Regexp.new("Element.insert\\(\\\"#{id}\\\", \\{ #{position}: #{RJS_PATTERN_HTML} \\}\\);") - else - Regexp.union(Regexp.new("#{statement}\\(\"#{id}\", #{RJS_PATTERN_HTML}\\)"), - Regexp.new("Element.insert\\(\\\"#{id}\\\", \\{ #{position}: #{RJS_PATTERN_HTML} \\}\\);")) - end + # any element, otherwise we replace it in the statement. + pattern = Regexp.new( + id ? statement.gsub(RJS_ANY_ID, "\"#{id}\"") : statement + ) # Duplicate the body since the next step involves destroying it. matches = nil @@ -588,26 +565,23 @@ module ActionController protected unless const_defined?(:RJS_STATEMENTS) - RJS_STATEMENTS = { - :replace => /Element\.replace/, - :replace_html => /Element\.update/, - :chained_replace => /\.replace/, - :chained_replace_html => /\.update/, - :remove => /Element\.remove/, - :show => /Element\.show/, - :hide => /Element\.hide/, - :toggle => /Element\.toggle/ + RJS_PATTERN_HTML = "\"((\\\\\"|[^\"])*)\"" + RJS_ANY_ID = "\"([^\"])*\"" + RJS_STATEMENTS = { + :chained_replace => "\\$\\(#{RJS_ANY_ID}\\)\\.replace\\(#{RJS_PATTERN_HTML}\\)", + :chained_replace_html => "\\$\\(#{RJS_ANY_ID}\\)\\.update\\(#{RJS_PATTERN_HTML}\\)", + :replace_html => "Element\\.update\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)", + :replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)" } - RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})") - RJS_PATTERN_HTML = /"((\\"|[^"])*)"/ - RJS_INSERTIONS = [:top, :bottom, :before, :after] + [:remove, :show, :hide, :toggle].each do |action| + RJS_STATEMENTS[action] = "Element\\.#{action}\\(#{RJS_ANY_ID}\\)" + end + RJS_INSERTIONS = ["top", "bottom", "before", "after"] RJS_INSERTIONS.each do |insertion| - RJS_STATEMENTS["insert_#{insertion}".to_sym] = /Element.insert\(\"([^\"]*)\", \{ #{insertion.to_s.downcase}: #{RJS_PATTERN_HTML} \}\);/ + RJS_STATEMENTS["insert_#{insertion}".to_sym] = "Element.insert\\(#{RJS_ANY_ID}, \\{ #{insertion}: #{RJS_PATTERN_HTML} \\}\\)" end - RJS_STATEMENTS[:insert_html] = Regexp.new(RJS_INSERTIONS.collect do |insertion| - /Element.insert\(\"([^\"]*)\", \{ #{insertion.to_s.downcase}: #{RJS_PATTERN_HTML} \}\);/ - end.join('|')) - RJS_PATTERN_EVERYTHING = Regexp.new("#{RJS_STATEMENTS[:any]}\\(\"([^\"]*)\", #{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE) + RJS_STATEMENTS[:insert_html] = "Element.insert\\(#{RJS_ANY_ID}, \\{ (#{RJS_INSERTIONS.join('|')}): #{RJS_PATTERN_HTML} \\}\\)" + RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})") RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/ end @@ -621,8 +595,8 @@ module ActionController root = HTML::Node.new(nil) while true - next if body.sub!(RJS_PATTERN_EVERYTHING) do |match| - html = unescape_rjs($3) + next if body.sub!(RJS_STATEMENTS[:any]) do |match| + html = unescape_rjs(match) matches = HTML::Document.new(html).root.children.select { |n| n.tag? } root.children.concat matches "" diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 32c1dcbcaf..670a049497 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -260,10 +260,11 @@ module ActionController #:nodoc: include StatusCodes + cattr_reader :protected_instance_variables # Controller specific instance variables which will not be accessible inside views. - @@protected_view_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller - @action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params - @_flash @_response) + @@protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller + @action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params + @_flash @_response) # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets, # and images to a dedicated asset server away from the main web server. Example: @@ -393,16 +394,9 @@ module ActionController #:nodoc: # directive. Values should always be specified as strings. attr_internal :headers - # Holds the hash of variables that are passed on to the template class to be made available to the view. This hash - # is generated by taking a snapshot of all the instance variables in the current scope just before a template is rendered. - attr_accessor :assigns - # Returns the name of the action this controller is processing. attr_accessor :action_name - # Templates that are exempt from layouts - @@exempt_from_layout = Set.new([/\.rjs$/]) - class << self # Factory for the standard create, process loop where the controller is discarded after processing. def process(request, response) #:nodoc: @@ -520,13 +514,7 @@ module ActionController #:nodoc: protected :filter_parameters end - # Don't render layouts for templates with the given extensions. - def exempt_from_layout(*extensions) - regexps = extensions.collect do |extension| - extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/ - end - @@exempt_from_layout.merge regexps - end + delegate :exempt_from_layout, :to => 'ActionView::Base' end public @@ -538,7 +526,6 @@ module ActionController #:nodoc: assign_shortcuts(request, response) initialize_current_url assign_names - forget_variables_added_to_assigns log_processing @@ -863,7 +850,7 @@ module ActionController #:nodoc: raise DoubleRenderError, "Can only render or redirect once per action" if performed? if options.nil? - return render_for_file(default_template_name, nil, true) + return render(:file => default_template_name, :layout => true) elsif !extra_options.is_a?(Hash) raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}" else @@ -874,6 +861,9 @@ module ActionController #:nodoc: end end + response.layout = layout = pick_layout(options) + logger.info("Rendering template within #{layout}") if logger && layout + if content_type = options[:content_type] response.content_type = content_type.to_s end @@ -883,26 +873,21 @@ module ActionController #:nodoc: end if options.has_key?(:text) - render_for_text(options[:text], options[:status]) + text = layout ? @template.render(options.merge(:text => options[:text], :layout => layout)) : options[:text] + render_for_text(text, options[:status]) else if file = options[:file] - render_for_file(file, options[:status], nil, options[:locals] || {}) + render_for_file(file, options[:status], layout, options[:locals] || {}) elsif template = options[:template] - render_for_file(template, options[:status], true, options[:locals] || {}) + render_for_file(template, options[:status], layout, options[:locals] || {}) elsif inline = options[:inline] - add_variables_to_assigns - render_for_text(@template.render(options), options[:status]) + render_for_text(@template.render(options.merge(:layout => layout)), options[:status]) elsif action_name = options[:action] - template = default_template_name(action_name.to_s) - if options[:layout] && !template_exempt_from_layout?(template) - render_with_a_layout(:file => template, :status => options[:status], :layout => true) - else - render_with_no_layout(:file => template, :status => options[:status]) - end + render_for_file(default_template_name(action_name.to_s), options[:status], layout) elsif xml = options[:xml] response.content_type ||= Mime::XML @@ -916,12 +901,14 @@ module ActionController #:nodoc: elsif options[:partial] options[:partial] = default_template_name if options[:partial] == true - add_variables_to_assigns - render_for_text(@template.render(options), options[:status]) + if layout + render_for_text(@template.render(:text => @template.render(options), :layout => layout), options[:status]) + else + render_for_text(@template.render(options), options[:status]) + end elsif options[:update] - add_variables_to_assigns - @template.send! :evaluate_assigns + @template.send(:_evaluate_assigns_and_ivars) generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block) response.content_type = Mime::JS @@ -931,7 +918,7 @@ module ActionController #:nodoc: render_for_text(nil, options[:status]) else - render_for_file(default_template_name, options[:status], true) + render_for_file(default_template_name, options[:status], layout) end end end @@ -942,7 +929,6 @@ module ActionController #:nodoc: render(options, &block) ensure erase_render_results - forget_variables_added_to_assigns reset_variables_added_to_assigns end @@ -1127,10 +1113,9 @@ module ActionController #:nodoc: private - def render_for_file(template_path, status = nil, use_full_path = nil, locals = {}) #:nodoc: - add_variables_to_assigns + def render_for_file(template_path, status = nil, layout = nil, locals = {}) #:nodoc: logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger - render_for_text(@template.render(:file => template_path, :locals => locals), status) + render_for_text @template.render(:file => template_path, :locals => locals, :layout => layout), status end def render_for_text(text = nil, status = nil, append_response = false) #:nodoc: @@ -1165,7 +1150,6 @@ module ActionController #:nodoc: @_session = @_response.session @template = @_response.template - @assigns = @_response.template.assigns @_headers = @_response.headers end @@ -1229,27 +1213,10 @@ module ActionController #:nodoc: hidden_actions end - def add_variables_to_assigns - unless @variables_added - add_instance_variables_to_assigns - @variables_added = true - end - end - - def forget_variables_added_to_assigns - @variables_added = nil - end - def reset_variables_added_to_assigns @template.instance_variable_set("@assigns_added", nil) end - def add_instance_variables_to_assigns - (instance_variable_names - @@protected_view_variables).each do |var| - @assigns[var[1..-1]] = instance_variable_get(var) - end - end - def request_origin # this *needs* to be cached! # otherwise you'd get different results if calling it more than once @@ -1265,12 +1232,7 @@ module ActionController #:nodoc: end def template_exists?(template_name = default_template_name) - @template.file_exists?(template_name) - end - - def template_exempt_from_layout?(template_name = default_template_name) - template_name = @template.pick_template(template_name).to_s if @template - @@exempt_from_layout.any? { |ext| template_name =~ ext } + @template.send(:_pick_template, template_name) ? true : false rescue ActionView::MissingTemplate false end diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index f3535f8330..e72434074e 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -90,7 +90,7 @@ module ActionController #:nodoc: set_content_type!(controller, cache_path.extension) options = { :text => cache } options.merge!(:layout => true) if cache_layout? - controller.send!(:render, options) + controller.__send__(:render, options) false else controller.action_cache_path = cache_path @@ -121,7 +121,7 @@ module ActionController #:nodoc: end def content_for_layout(controller) - controller.response.layout && controller.response.template.instance_variable_get('@content_for_layout') + controller.response.layout && controller.response.template.instance_variable_get('@cached_content_for_layout') end end diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index 61559e9ec7..c7992d7769 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -83,13 +83,13 @@ module ActionController #:nodoc: controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}" action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}" - send!(controller_callback_method_name) if respond_to?(controller_callback_method_name, true) - send!(action_callback_method_name) if respond_to?(action_callback_method_name, true) + __send__(controller_callback_method_name) if respond_to?(controller_callback_method_name, true) + __send__(action_callback_method_name) if respond_to?(action_callback_method_name, true) end def method_missing(method, *arguments) return if @controller.nil? - @controller.send!(method, *arguments) + @controller.__send__(method, *arguments) end end end diff --git a/actionpack/lib/action_controller/cgi_process.rb b/actionpack/lib/action_controller/cgi_process.rb index 0ca27b30db..d381af1b84 100644 --- a/actionpack/lib/action_controller/cgi_process.rb +++ b/actionpack/lib/action_controller/cgi_process.rb @@ -48,7 +48,7 @@ module ActionController #:nodoc: def initialize(cgi, session_options = {}) @cgi = cgi @session_options = session_options - @env = @cgi.send!(:env_table) + @env = @cgi.__send__(:env_table) super() end @@ -107,7 +107,7 @@ module ActionController #:nodoc: end def method_missing(method_id, *arguments) - @cgi.send!(method_id, *arguments) rescue super + @cgi.__send__(method_id, *arguments) rescue super end private @@ -164,7 +164,7 @@ end_msg begin output.write(@cgi.header(@headers)) - if @cgi.send!(:env_table)['REQUEST_METHOD'] == 'HEAD' + if @cgi.__send__(:env_table)['REQUEST_METHOD'] == 'HEAD' return elsif @body.respond_to?(:call) # Flush the output now in case the @body Proc uses diff --git a/actionpack/lib/action_controller/components.rb b/actionpack/lib/action_controller/components.rb index a9b4559852..f446b91e7e 100644 --- a/actionpack/lib/action_controller/components.rb +++ b/actionpack/lib/action_controller/components.rb @@ -65,7 +65,7 @@ module ActionController #:nodoc: module HelperMethods def render_component(options) - @controller.send!(:render_component_as_string, options) + @controller.__send__(:render_component_as_string, options) end end diff --git a/actionpack/lib/action_controller/filters.rb b/actionpack/lib/action_controller/filters.rb index 1d7236f18a..9022b8b279 100644 --- a/actionpack/lib/action_controller/filters.rb +++ b/actionpack/lib/action_controller/filters.rb @@ -199,8 +199,8 @@ module ActionController #:nodoc: Proc.new do |controller, action| method.before(controller) - if controller.send!(:performed?) - controller.send!(:halt_filter_chain, method, :rendered_or_redirected) + if controller.__send__(:performed?) + controller.__send__(:halt_filter_chain, method, :rendered_or_redirected) else begin action.call @@ -223,8 +223,8 @@ module ActionController #:nodoc: def call(controller, &block) super - if controller.send!(:performed?) - controller.send!(:halt_filter_chain, method, :rendered_or_redirected) + if controller.__send__(:performed?) + controller.__send__(:halt_filter_chain, method, :rendered_or_redirected) end end end diff --git a/actionpack/lib/action_controller/helpers.rb b/actionpack/lib/action_controller/helpers.rb index ce5e8be54c..9cffa07b26 100644 --- a/actionpack/lib/action_controller/helpers.rb +++ b/actionpack/lib/action_controller/helpers.rb @@ -204,8 +204,8 @@ module ActionController #:nodoc: begin child.master_helper_module = Module.new - child.master_helper_module.send! :include, master_helper_module - child.send! :default_helper_module! + child.master_helper_module.__send__ :include, master_helper_module + child.__send__ :default_helper_module! rescue MissingSourceFile => e raise unless e.is_missing?("helpers/#{child.controller_path}_helper") end diff --git a/actionpack/lib/action_controller/http_authentication.rb b/actionpack/lib/action_controller/http_authentication.rb index 31db012ef2..2ed810db7d 100644 --- a/actionpack/lib/action_controller/http_authentication.rb +++ b/actionpack/lib/action_controller/http_authentication.rb @@ -117,7 +117,7 @@ module ActionController def authentication_request(controller, realm) controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}") - controller.send! :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized + controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized end end end diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb index 198a22e8dc..a98c1af7f9 100644 --- a/actionpack/lib/action_controller/integration.rb +++ b/actionpack/lib/action_controller/integration.rb @@ -444,7 +444,7 @@ EOF reset! unless @integration_session # reset the html_document variable, but only for new get/post calls @html_document = nil unless %w(cookies assigns).include?(method) - returning @integration_session.send!(method, *args) do + returning @integration_session.__send__(method, *args) do copy_session_variables! end end @@ -469,12 +469,12 @@ EOF self.class.fixture_table_names.each do |table_name| name = table_name.tr(".", "_") next unless respond_to?(name) - extras.send!(:define_method, name) { |*args| delegate.send(name, *args) } + extras.__send__(:define_method, name) { |*args| delegate.send(name, *args) } end end # delegate add_assertion to the test case - extras.send!(:define_method, :add_assertion) { test_result.add_assertion } + extras.__send__(:define_method, :add_assertion) { test_result.add_assertion } session.extend(extras) session.delegate = self session.test_result = @_result @@ -488,7 +488,7 @@ EOF def copy_session_variables! #:nodoc: return unless @integration_session %w(controller response request).each do |var| - instance_variable_set("@#{var}", @integration_session.send!(var)) + instance_variable_set("@#{var}", @integration_session.__send__(var)) end end diff --git a/actionpack/lib/action_controller/layout.rb b/actionpack/lib/action_controller/layout.rb index 8b6febe254..8432a008eb 100644 --- a/actionpack/lib/action_controller/layout.rb +++ b/actionpack/lib/action_controller/layout.rb @@ -3,11 +3,6 @@ module ActionController #:nodoc: def self.included(base) base.extend(ClassMethods) base.class_eval do - # NOTE: Can't use alias_method_chain here because +render_without_layout+ is already - # defined as a publicly exposed method - alias_method :render_with_no_layout, :render - alias_method :render, :render_with_a_layout - class << self alias_method_chain :inherited, :layout end @@ -221,10 +216,10 @@ module ActionController #:nodoc: # object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return # weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard. def active_layout(passed_layout = nil) - layout = passed_layout || self.class.default_layout(response.template.template_format) + layout = passed_layout || self.class.default_layout(default_template_format) active_layout = case layout when String then layout - when Symbol then send!(layout) + when Symbol then __send__(layout) when Proc then layout.call(self) end @@ -240,51 +235,24 @@ module ActionController #:nodoc: end end - protected - def render_with_a_layout(options = nil, extra_options = {}, &block) #:nodoc: - template_with_options = options.is_a?(Hash) - - if (layout = pick_layout(template_with_options, options)) && apply_layout?(template_with_options, options) - options = options.merge :layout => false if template_with_options - logger.info("Rendering template within #{layout}") if logger - - content_for_layout = render_with_no_layout(options, extra_options, &block) - erase_render_results - add_variables_to_assigns - @template.instance_variable_set("@content_for_layout", content_for_layout) - response.layout = layout - status = template_with_options ? options[:status] : nil - render_for_text(@template.render(layout), status) - else - render_with_no_layout(options, extra_options, &block) - end - end - - private - def apply_layout?(template_with_options, options) - return false if options == :update - template_with_options ? candidate_for_layout?(options) : !template_exempt_from_layout? - end - def candidate_for_layout?(options) - (options.has_key?(:layout) && options[:layout] != false) || - options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing).compact.empty? && - !template_exempt_from_layout?(options[:template] || default_template_name(options[:action])) + options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing, :update).compact.empty? && + !@template.__send__(:_exempt_from_layout?, options[:template] || default_template_name(options[:action])) end - def pick_layout(template_with_options, options) - if template_with_options - case layout = options[:layout] - when FalseClass - nil - when NilClass, TrueClass - active_layout if action_has_layout? - else - active_layout(layout) + def pick_layout(options) + if options.has_key?(:layout) + case layout = options.delete(:layout) + when FalseClass + nil + when NilClass, TrueClass + active_layout if action_has_layout? && !@template.__send__(:_exempt_from_layout?, default_template_name) + else + active_layout(layout) end else - active_layout if action_has_layout? + active_layout if action_has_layout? && candidate_for_layout?(options) end end @@ -304,7 +272,13 @@ module ActionController #:nodoc: end def layout_directory?(layout_name) - @template.file_exists?("#{File.join('layouts', layout_name)}.#{@template.template_format}") + @template.__send__(:_pick_template, "#{File.join('layouts', layout_name)}.#{@template.template_format}") ? true : false + rescue ActionView::MissingTemplate + false + end + + def default_template_format + response.template.template_format end end end diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb index 30564c7bb3..cc228c4230 100644 --- a/actionpack/lib/action_controller/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/polymorphic_routes.rb @@ -108,7 +108,7 @@ module ActionController args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options end - send!(named_route, *args) + __send__(named_route, *args) end # Returns the path component of a URL for the given record. It uses @@ -149,7 +149,7 @@ module ActionController if parent.is_a?(Symbol) || parent.is_a?(String) string << "#{parent}_" else - string << "#{RecordIdentifier.send!("singular_class_name", parent)}_" + string << "#{RecordIdentifier.__send__("singular_class_name", parent)}_" end end end @@ -157,7 +157,7 @@ module ActionController if record.is_a?(Symbol) || record.is_a?(String) route << "#{record}_" else - route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_" + route << "#{RecordIdentifier.__send__("#{inflection}_class_name", record)}_" end action_prefix(options) + namespace + route + routing_type(options).to_s diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb index a1a9d68a35..1492c4ec61 100644 --- a/actionpack/lib/action_controller/rescue.rb +++ b/actionpack/lib/action_controller/rescue.rb @@ -177,11 +177,8 @@ module ActionController #:nodoc: # Render detailed diagnostics for unhandled exceptions rescued from # a controller action. def rescue_action_locally(exception) - add_variables_to_assigns @template.instance_variable_set("@exception", exception) @template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub"))) - @template.send!(:assign_variables_from_controller) - @template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception))) response.content_type = Mime::HTML diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index b4ac0db262..31710caccb 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -85,16 +85,24 @@ module ActionController @new_path ||= "#{path}/#{new_action}" end + def shallow_path_prefix + @shallow_path_prefix ||= "#{path_prefix unless @options[:shallow]}" + end + def member_path - @member_path ||= "#{path}/:id" + @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id" end def nesting_path_prefix - @nesting_path_prefix ||= "#{path}/:#{singular}_id" + @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id" + end + + def shallow_name_prefix + @shallow_name_prefix ||= "#{name_prefix unless @options[:shallow]}" end def nesting_name_prefix - "#{name_prefix}#{singular}_" + "#{shallow_name_prefix}#{singular}_" end def action_separator @@ -141,6 +149,8 @@ module ActionController super end + alias_method :shallow_path_prefix, :path_prefix + alias_method :shallow_name_prefix, :name_prefix alias_method :member_path, :path alias_method :nesting_path_prefix, :path end @@ -318,6 +328,31 @@ module ActionController # comments_url(@article) # comment_url(@article, @comment) # + # * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member + # (ie. those with an :id parameter) will not use the parent path prefix or name prefix. + # + # The <tt>:shallow</tt> option is inherited by any nested resource(s). + # + # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources: + # + # map.resources :users, :shallow => true do |user| + # user.resources :posts do |post| + # post.resources :comments + # end + # end + # # --> GET /users/1/posts (maps to the PostsController#index action as usual) + # # also adds the usual named route called "user_posts" + # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested) + # # also adds the named route called "post" + # # --> GET /posts/2/comments (maps to the CommentsController#index action) + # # also adds the named route called "post_comments" + # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested) + # # also adds the named route called "comment" + # + # You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like: + # + # map.resources :users, :has_many => { :posts => :comments }, :shallow => true + # # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied. # # Examples: @@ -443,7 +478,7 @@ module ActionController map_associations(resource, options) if block_given? - with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block) + with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block) end end end @@ -460,21 +495,35 @@ module ActionController map_associations(resource, options) if block_given? - with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block) + with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block) end end end def map_associations(resource, options) + map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many] + path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}" name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}" - Array(options[:has_many]).each do |association| - resources(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace]) + Array(options[:has_one]).each do |association| + resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace], :shallow => options[:shallow]) end + end - Array(options[:has_one]).each do |association| - resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace]) + def map_has_many_associations(resource, associations, options) + case associations + when Hash + associations.each do |association,has_many| + map_has_many_associations(resource, association, options.merge(:has_many => has_many)) + end + when Array + associations.each do |association| + map_has_many_associations(resource, association, options) + end + when Symbol, String + resources(associations, :path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], :has_many => options[:has_many]) + else end end @@ -530,13 +579,13 @@ module ActionController action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) action_path ||= Base.resources_path_names[action] || action - map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options) + map_named_routes(map, "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options) end end end show_action_options = action_options_for("show", resource) - map_named_routes(map, "#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options) + map_named_routes(map, "#{resource.shallow_name_prefix}#{resource.singular}", resource.member_path, show_action_options) update_action_options = action_options_for("update", resource) map_unnamed_routes(map, resource.member_path, update_action_options) @@ -579,4 +628,4 @@ end class ActionController::Routing::RouteSet::Mapper include ActionController::Resources -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller/routing/optimisations.rb b/actionpack/lib/action_controller/routing/optimisations.rb index 0fe836606c..894d4109e4 100644 --- a/actionpack/lib/action_controller/routing/optimisations.rb +++ b/actionpack/lib/action_controller/routing/optimisations.rb @@ -103,9 +103,10 @@ module ActionController end # This case uses almost the same code as positional arguments, - # but add an args.last.to_query on the end + # but add a question mark and args.last.to_query on the end, + # unless the last arg is empty def generation_code - super.insert(-2, '?#{args.last.to_query}') + super.insert(-2, '#{\'?\' + args.last.to_query unless args.last.empty?}') end # To avoid generating "http://localhost/?host=foo.example.com" we diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb index 8dfc22f94f..9d48f289db 100644 --- a/actionpack/lib/action_controller/routing/route_set.rb +++ b/actionpack/lib/action_controller/routing/route_set.rb @@ -115,7 +115,7 @@ module ActionController def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false) reset! if regenerate Array(destinations).each do |dest| - dest.send! :include, @module + dest.__send__(:include, @module) end end @@ -353,7 +353,7 @@ module ActionController if generate_all # Used by caching to expire all paths for a resource return routes.collect do |route| - route.send!(method, options, merged, expire_on) + route.__send__(method, options, merged, expire_on) end.compact end @@ -361,7 +361,7 @@ module ActionController routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }] routes.each do |route| - results = route.send!(method, options, merged, expire_on) + results = route.__send__(method, options, merged, expire_on) return results if results && (!results.is_a?(Array) || results.first) end end diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index a11fa7cd10..cde1f2052b 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -3,6 +3,8 @@ require 'action_controller/test_case' module ActionController #:nodoc: class Base + attr_reader :assigns + # Process a test request called with a TestRequest object. def self.process_test(request) new.process_test(request) @@ -14,7 +16,12 @@ module ActionController #:nodoc: def process_with_test(*args) returning process_without_test(*args) do - add_variables_to_assigns + @assigns = {} + (instance_variable_names - @@protected_instance_variables).each do |var| + value = instance_variable_get(var) + @assigns[var[1..-1]] = value + response.template.assigns[var[1..-1]] = value if response + end end end @@ -211,7 +218,7 @@ module ActionController #:nodoc: # Returns the template of the file which was used to # render this response (or nil) def rendered_template - template._first_render + template.send(:_first_render) end # A shortcut to the flash. Returns an empty hash if no session flash exists. @@ -350,7 +357,7 @@ module ActionController #:nodoc: alias local_path path def method_missing(method_name, *args, &block) #:nodoc: - @tempfile.send!(method_name, *args, &block) + @tempfile.__send__(method_name, *args, &block) end end @@ -396,7 +403,7 @@ module ActionController #:nodoc: def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' @request.env['HTTP_ACCEPT'] = 'text/javascript, text/html, application/xml, text/xml, */*' - returning send!(request_method, action, parameters, session, flash) do + returning __send__(request_method, action, parameters, session, flash) do @request.env.delete 'HTTP_X_REQUESTED_WITH' @request.env.delete 'HTTP_ACCEPT' end @@ -429,7 +436,7 @@ module ActionController #:nodoc: def build_request_uri(action, parameters) unless @request.env['REQUEST_URI'] - options = @controller.send!(:rewrite_options, parameters) + options = @controller.__send__(:rewrite_options, parameters) options.update(:only_path => true, :action => action) url = ActionController::UrlRewriter.new(@request, parameters) diff --git a/actionpack/lib/action_controller/verification.rb b/actionpack/lib/action_controller/verification.rb index 35b12a7f13..7bf09ba6ea 100644 --- a/actionpack/lib/action_controller/verification.rb +++ b/actionpack/lib/action_controller/verification.rb @@ -80,7 +80,7 @@ module ActionController #:nodoc: # array (may also be a single value). def verify(options={}) before_filter :only => options[:only], :except => options[:except] do |c| - c.send! :verify_action, options + c.__send__ :verify_action, options end end end @@ -116,7 +116,7 @@ module ActionController #:nodoc: end def apply_redirect_to(redirect_to_option) # :nodoc: - (redirect_to_option.is_a?(Symbol) && redirect_to_option != :back) ? self.send!(redirect_to_option) : redirect_to_option + (redirect_to_option.is_a?(Symbol) && redirect_to_option != :back) ? self.__send__(redirect_to_option) : redirect_to_option end def apply_remaining_actions(options) # :nodoc: diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index c65048bfa0..6c4da9067d 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -162,7 +162,6 @@ module ActionView #:nodoc: attr_accessor :base_path, :assigns, :template_extension attr_accessor :controller - attr_accessor :_first_render, :_last_render attr_writer :template_format @@ -185,6 +184,17 @@ module ActionView #:nodoc: "deprecated and has no effect. Please remove it from your config files.", caller) end + # Templates that are exempt from layouts + @@exempt_from_layout = Set.new([/\.rjs$/]) + + # Don't render layouts for templates with the given extensions. + def self.exempt_from_layout(*extensions) + regexps = extensions.collect do |extension| + extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/ + end + @@exempt_from_layout.merge(regexps) + end + # Specify whether RJS responses should be wrapped in a try/catch block # that alert()s the caught exception (and then re-raises it). @@debug_rjs = false @@ -246,33 +256,20 @@ module ActionView #:nodoc: update_page(&block) elsif options.is_a?(Hash) options = options.reverse_merge(:locals => {}) - - if partial_layout = options.delete(:layout) - if block_given? - begin - @_proc_for_layout = block - concat(render(options.merge(:partial => partial_layout))) - ensure - @_proc_for_layout = nil - end - else - begin - original_content_for_layout, @content_for_layout = @content_for_layout, render(options) - render(options.merge(:partial => partial_layout)) - ensure - @content_for_layout = original_content_for_layout - end - end + if options[:layout] + _render_with_layout(options, local_assigns, &block) elsif options[:file] if options[:use_full_path] ActiveSupport::Deprecation.warn("use_full_path option has been deprecated and has no affect.", caller) end - pick_template(options[:file]).render_template(self, options[:locals]) + _pick_template(options[:file]).render_template(self, options[:locals]) elsif options[:partial] render_partial(options) elsif options[:inline] InlineTemplate.new(options[:inline], options[:type]).render(self, options[:locals]) + elsif options[:text] + options[:text] end end end @@ -290,74 +287,95 @@ module ActionView #:nodoc: end end - def file_exists?(template_path) - pick_template(template_path) ? true : false - rescue MissingTemplate - false - end + private + attr_accessor :_first_render, :_last_render - # Gets the extension for an existing template with the given template_path. - # Returns the format with the extension if that template exists. - # - # pick_template('users/show') - # # => 'users/show.html.erb' - # - # pick_template('users/legacy') - # # => 'users/legacy.rhtml' - # - def pick_template(template_path) - return template_path if template_path.respond_to?(:render) - - path = template_path.sub(/^\//, '') - if m = path.match(/(.*)\.(\w+)$/) - template_file_name, template_file_extension = m[1], m[2] - else - template_file_name = path - end + # Evaluate the local assigns and pushes them to the view. + def _evaluate_assigns_and_ivars #:nodoc: + unless @assigns_added + @assigns.each { |key, value| instance_variable_set("@#{key}", value) } - # OPTIMIZE: Checks to lookup template in view path - if template = self.view_paths["#{template_file_name}.#{template_format}"] - template - elsif template = self.view_paths[template_file_name] - template - elsif _first_render && template = self.view_paths["#{template_file_name}.#{_first_render.format_and_extension}"] - template - elsif template_format == :js && template = self.view_paths["#{template_file_name}.html"] - @template_format = :html - template - else - template = Template.new(template_path, view_paths) - - if self.class.warn_cache_misses && logger - logger.debug "[PERFORMANCE] Rendering a template that was " + - "not found in view path. Templates outside the view path are " + - "not cached and result in expensive disk operations. Move this " + - "file into #{view_paths.join(':')} or add the folder to your " + - "view path list" + if @controller + variables = @controller.instance_variables + variables -= @controller.protected_instance_variables if @controller.respond_to?(:protected_instance_variables) + variables.each {|name| instance_variable_set(name, @controller.instance_variable_get(name)) } + end + + @assigns_added = true end + end - template + def _set_controller_content_type(content_type) #:nodoc: + if controller.respond_to?(:response) + controller.response.content_type ||= content_type + end end - end - memoize :pick_template - private - # Evaluate the local assigns and pushes them to the view. - def evaluate_assigns - unless @assigns_added - assign_variables_from_controller - @assigns_added = true + def _pick_template(template_path) + return template_path if template_path.respond_to?(:render) + + path = template_path.sub(/^\//, '') + if m = path.match(/(.*)\.(\w+)$/) + template_file_name, template_file_extension = m[1], m[2] + else + template_file_name = path + end + + # OPTIMIZE: Checks to lookup template in view path + if template = self.view_paths["#{template_file_name}.#{template_format}"] + template + elsif template = self.view_paths[template_file_name] + template + elsif _first_render && template = self.view_paths["#{template_file_name}.#{_first_render.format_and_extension}"] + template + elsif template_format == :js && template = self.view_paths["#{template_file_name}.html"] + @template_format = :html + template + else + template = Template.new(template_path, view_paths) + + if self.class.warn_cache_misses && logger + logger.debug "[PERFORMANCE] Rendering a template that was " + + "not found in view path. Templates outside the view path are " + + "not cached and result in expensive disk operations. Move this " + + "file into #{view_paths.join(':')} or add the folder to your " + + "view path list" + end + + template end end + memoize :_pick_template - # Assigns instance variables from the controller to the view. - def assign_variables_from_controller - @assigns.each { |key, value| instance_variable_set("@#{key}", value) } + def _exempt_from_layout?(template_path) #:nodoc: + template = _pick_template(template_path).to_s + @@exempt_from_layout.any? { |ext| template =~ ext } + rescue ActionView::MissingTemplate + return false end - def set_controller_content_type(content_type) - if controller.respond_to?(:response) - controller.response.content_type ||= content_type + def _render_with_layout(options, local_assigns, &block) #:nodoc: + partial_layout = options.delete(:layout) + + if block_given? + begin + @_proc_for_layout = block + concat(render(options.merge(:partial => partial_layout))) + ensure + @_proc_for_layout = nil + end + else + begin + original_content_for_layout, @content_for_layout = @content_for_layout, render(options) + if (options[:inline] || options[:file] || options[:text]) + @cached_content_for_layout = @content_for_layout + render(:file => partial_layout, :locals => local_assigns) + else + render(options.merge(:partial => partial_layout)) + end + ensure + @content_for_layout = original_content_for_layout + end end end end diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 2ce818cd71..2f08583b16 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -255,6 +255,14 @@ module ActionView link_to_function(name, remote_function(options), html_options || options.delete(:html)) end + # Creates a button with an onclick event which calls a remote action + # via XMLHttpRequest + # The options for specifying the target with :url + # and defining callbacks is the same as link_to_remote. + def button_to_remote(name, options = {}, html_options = {}) + button_to_function(name, remote_function(options), html_options) + end + # Periodically calls the specified url (<tt>options[:url]</tt>) every # <tt>options[:frequency]</tt> seconds (default is 10). Usually used to # update a specified div (<tt>options[:update]</tt>) with the results @@ -1052,7 +1060,7 @@ module ActionView js_options['asynchronous'] = options[:type] != :synchronous js_options['method'] = method_option_to_s(options[:method]) if options[:method] - js_options['insertion'] = options[:position].to_s.downcase if options[:position] + js_options['insertion'] = "'#{options[:position].to_s.downcase}'" if options[:position] js_options['evalScripts'] = options[:script].nil? || options[:script] if options[:form] diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb index 443c49b870..373bb92dc4 100644 --- a/actionpack/lib/action_view/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -196,7 +196,7 @@ module ActionView path = "_#{partial_path}" end - pick_template(path) + _pick_template(path) end memoize :_pick_partial_template end diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index fa45edd436..d960335220 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -26,11 +26,11 @@ module ActionView def render(view, local_assigns = {}) compile(local_assigns) - view._first_render ||= self - view._last_render = self + view.send(:_first_render=, self) unless view.send(:_first_render) + view.send(:_last_render=, self) - view.send(:evaluate_assigns) - view.send(:set_controller_content_type, mime_type) if respond_to?(:mime_type) + view.send(:_evaluate_assigns_and_ivars) + view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type) view.send(method_name(local_assigns), local_assigns) do |*names| if proc = view.instance_variable_get("@_proc_for_layout") diff --git a/actionpack/lib/action_view/template_handlers/builder.rb b/actionpack/lib/action_view/template_handlers/builder.rb index 7d24a5c423..788dc93326 100644 --- a/actionpack/lib/action_view/template_handlers/builder.rb +++ b/actionpack/lib/action_view/template_handlers/builder.rb @@ -6,7 +6,7 @@ module ActionView include Compilable def compile(template) - "set_controller_content_type(Mime::XML);" + + "_set_controller_content_type(Mime::XML);" + "xml = ::Builder::XmlMarkup.new(:indent => 2);" + "self.output_buffer = xml.target!;" + template.source + diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index adbb37fd09..c69f9455b2 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -54,7 +54,7 @@ module ActionView private def method_missing(selector, *args) controller = TestController.new - return controller.send!(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector) + return controller.__send__(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector) super end end diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb index cce8f8dc21..08cbcbf302 100644 --- a/actionpack/test/controller/assert_select_test.rb +++ b/actionpack/test/controller/assert_select_test.rb @@ -599,6 +599,11 @@ class AssertSelectTest < Test::Unit::TestCase end end + def test_assert_select_rjs_raise_errors + assert_raises(ArgumentError) { assert_select_rjs(:destroy) } + assert_raises(ArgumentError) { assert_select_rjs(:insert, :left) } + end + # Simple selection from a single result. def test_nested_assert_select_rjs_with_single_result render_rjs do |page| diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index d49cc2a9aa..738c016c6e 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -84,11 +84,11 @@ class ControllerInstanceTests < Test::Unit::TestCase def test_action_methods @empty_controllers.each do |c| hide_mocha_methods_from_controller(c) - assert_equal Set.new, c.send!(:action_methods), "#{c.controller_path} should be empty!" + assert_equal Set.new, c.__send__(:action_methods), "#{c.controller_path} should be empty!" end @non_empty_controllers.each do |c| hide_mocha_methods_from_controller(c) - assert_equal Set.new(%w(public_action)), c.send!(:action_methods), "#{c.controller_path} should not be empty!" + assert_equal Set.new(%w(public_action)), c.__send__(:action_methods), "#{c.controller_path} should not be empty!" end end @@ -100,7 +100,7 @@ class ControllerInstanceTests < Test::Unit::TestCase :expects, :mocha, :mocha_inspect, :reset_mocha, :stubba_object, :stubba_method, :stubs, :verify, :__metaclass__, :__is_a__, :to_matcher, ] - controller.class.send!(:hide_action, *mocha_methods) + controller.class.__send__(:hide_action, *mocha_methods) end end @@ -140,7 +140,7 @@ class PerformActionTest < Test::Unit::TestCase def test_method_missing_is_not_an_action_name use_controller MethodMissingController - assert ! @controller.send!(:action_methods).include?('method_missing') + assert ! @controller.__send__(:action_methods).include?('method_missing') get :method_missing assert_response :success diff --git a/actionpack/test/controller/filter_params_test.rb b/actionpack/test/controller/filter_params_test.rb index c4de10181d..0b259a7980 100644 --- a/actionpack/test/controller/filter_params_test.rb +++ b/actionpack/test/controller/filter_params_test.rb @@ -27,7 +27,7 @@ class FilterParamTest < Test::Unit::TestCase test_hashes.each do |before_filter, after_filter, filter_words| FilterParamController.filter_parameter_logging(*filter_words) - assert_equal after_filter, @controller.send!(:filter_parameters, before_filter) + assert_equal after_filter, @controller.__send__(:filter_parameters, before_filter) filter_words.push('blah') FilterParamController.filter_parameter_logging(*filter_words) do |key, value| @@ -37,7 +37,7 @@ class FilterParamTest < Test::Unit::TestCase before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}} after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}} - assert_equal after_filter, @controller.send!(:filter_parameters, before_filter) + assert_equal after_filter, @controller.__send__(:filter_parameters, before_filter) end end diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 3652c482f1..dafa344473 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -111,15 +111,15 @@ class FilterTest < Test::Unit::TestCase end class OnlyConditionProcController < ConditionalFilterController - before_filter(:only => :show) {|c| c.assigns["ran_proc_filter"] = true } + before_filter(:only => :show) {|c| c.instance_variable_set(:"@ran_proc_filter", true) } end class ExceptConditionProcController < ConditionalFilterController - before_filter(:except => :show_without_filter) {|c| c.assigns["ran_proc_filter"] = true } + before_filter(:except => :show_without_filter) {|c| c.instance_variable_set(:"@ran_proc_filter", true) } end class ConditionalClassFilter - def self.filter(controller) controller.assigns["ran_class_filter"] = true end + def self.filter(controller) controller.instance_variable_set(:"@ran_class_filter", true) end end class OnlyConditionClassController < ConditionalFilterController @@ -131,7 +131,7 @@ class FilterTest < Test::Unit::TestCase end class AnomolousYetValidConditionController < ConditionalFilterController - before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.assigns["ran_proc_filter1"] = true }, :except => :show_without_filter) { |c| c.assigns["ran_proc_filter2"] = true} + before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.instance_variable_set(:"@ran_proc_filter1", true)}, :except => :show_without_filter) { |c| c.instance_variable_set(:"@ran_proc_filter2", true)} end class ConditionalOptionsFilter < ConditionalFilterController @@ -225,16 +225,16 @@ class FilterTest < Test::Unit::TestCase end class ProcController < PrependingController - before_filter(proc { |c| c.assigns["ran_proc_filter"] = true }) + before_filter(proc { |c| c.instance_variable_set(:"@ran_proc_filter", true) }) end class ImplicitProcController < PrependingController - before_filter { |c| c.assigns["ran_proc_filter"] = true } + before_filter { |c| c.instance_variable_set(:"@ran_proc_filter", true) } end class AuditFilter def self.filter(controller) - controller.assigns["was_audited"] = true + controller.instance_variable_set(:"@was_audited", true) end end @@ -242,12 +242,12 @@ class FilterTest < Test::Unit::TestCase def before(controller) @execution_log = "before" controller.class.execution_log << " before aroundfilter " if controller.respond_to? :execution_log - controller.assigns["before_ran"] = true + controller.instance_variable_set(:"@before_ran", true) end def after(controller) - controller.assigns["execution_log"] = @execution_log + " and after" - controller.assigns["after_ran"] = true + controller.instance_variable_set(:"@execution_log", @execution_log + " and after") + controller.instance_variable_set(:"@after_ran", true) controller.class.execution_log << " after aroundfilter " if controller.respond_to? :execution_log end end @@ -364,7 +364,7 @@ class FilterTest < Test::Unit::TestCase begin yield rescue ErrorToRescue => ex - controller.send! :render, :text => "I rescued this: #{ex.inspect}" + controller.__send__ :render, :text => "I rescued this: #{ex.inspect}" end end end @@ -726,9 +726,9 @@ end class ControllerWithProcFilter < PostsController around_filter(:only => :no_raise) do |c,b| - c.assigns['before'] = true + c.instance_variable_set(:"@before", true) b.call - c.assigns['after'] = true + c.instance_variable_set(:"@after", true) end end diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index c986941140..7e4c3e171a 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -243,7 +243,7 @@ class IntegrationTestUsesCorrectClass < ActionController::IntegrationTest reset! stub_integration_session(@integration_session) %w( get post head put delete ).each do |verb| - assert_nothing_raised("'#{verb}' should use integration test methods") { send!(verb, '/') } + assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') } end end end diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb index 71f110f241..1120fdbff5 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionpack/test/controller/layout_test.rb @@ -79,53 +79,6 @@ class LayoutAutoDiscoveryTest < Test::Unit::TestCase end end -class ExemptFromLayoutTest < Test::Unit::TestCase - def setup - @controller = LayoutTest.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - - def test_rjs_exempt_from_layout - assert @controller.send!(:template_exempt_from_layout?, 'test.rjs') - end - - def test_rhtml_and_rxml_not_exempt_from_layout - assert !@controller.send!(:template_exempt_from_layout?, 'test.rhtml') - assert !@controller.send!(:template_exempt_from_layout?, 'test.rxml') - end - - def test_other_extension_not_exempt_from_layout - assert !@controller.send!(:template_exempt_from_layout?, 'test.random') - end - - def test_add_extension_to_exempt_from_layout - ['rpdf', :rpdf].each do |ext| - assert_nothing_raised do - ActionController::Base.exempt_from_layout ext - end - assert @controller.send!(:template_exempt_from_layout?, "test.#{ext}") - end - end - - def test_add_regexp_to_exempt_from_layout - ActionController::Base.exempt_from_layout /\.rdoc/ - assert @controller.send!(:template_exempt_from_layout?, 'test.rdoc') - end - - def test_rhtml_exempt_from_layout_status_should_prevent_layout_render - ActionController::Base.exempt_from_layout :rhtml - - assert @controller.send!(:template_exempt_from_layout?, 'test.rhtml') - assert @controller.send!(:template_exempt_from_layout?, 'hello.rhtml') - - get :hello - assert_equal 'hello.rhtml', @response.body - ActionController::Base.exempt_from_layout.delete(/\.rhtml$/) - end -end - - class DefaultLayoutController < LayoutTest end @@ -179,8 +132,6 @@ class LayoutSetInResponseTest < Test::Unit::TestCase ActionController::Base.exempt_from_layout :rhtml @controller = RenderWithTemplateOptionController.new - assert @controller.send(:template_exempt_from_layout?, 'alt/hello.rhtml') - get :hello assert_equal "alt/hello.rhtml", @response.body.strip diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index e383fda384..c4a2bf3db3 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -8,6 +8,18 @@ module Fun end end +class MockLogger + attr_reader :logged + + def initialize + @logged = [] + end + + def method_missing(method, *args) + @logged << args.first + end +end + class TestController < ActionController::Base class LabellingFormBuilder < ActionView::Helpers::FormBuilder end @@ -371,6 +383,12 @@ class TestController < ActionController::Base end end + def update_page_with_view_method + render :update do |page| + page.replace_html 'person', pluralize(2, 'person') + end + end + def action_talk_to_layout # Action template sets variable that's picked up by layout end @@ -1022,6 +1040,13 @@ class RenderTest < Test::Unit::TestCase assert_match /\$37/, @response.body end + def test_update_page_with_view_method + get :update_page_with_view_method + assert_template nil + assert_equal 'text/javascript; charset=utf-8', @response.headers['type'] + assert_match /2 people/, @response.body + end + def test_yield_content_for assert_not_deprecated { get :yield_content_for } assert_equal "<title>Putting stuff in the title!</title>\n\nGreat stuff!\n", @response.body @@ -1372,3 +1397,21 @@ class LastModifiedRenderTest < Test::Unit::TestCase assert_equal @last_modified, @response.headers['Last-Modified'] end end + +class RenderingLoggingTest < Test::Unit::TestCase + def setup + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @controller = TestController.new + + @request.host = "www.nextangle.com" + end + + def test_logger_prints_layout_and_template_rendering_info + @controller.logger = MockLogger.new + get :layout_test + logged = @controller.logger.logged.find_all {|l| l =~ /render/i } + assert_equal "Rendering template within layouts/standard", logged[0] + assert_equal "Rendering test/hello_world", logged[1] + end +end diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 7830767723..1fea82e564 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -379,6 +379,31 @@ class ResourcesTest < Test::Unit::TestCase end end + def test_shallow_nested_restful_routes + with_routing do |set| + set.draw do |map| + map.resources :threads, :shallow => true do |map| + map.resources :messages do |map| + map.resources :comments + end + end + end + + assert_simply_restful_for :threads, + :shallow => true + assert_simply_restful_for :messages, + :name_prefix => 'thread_', + :path_prefix => 'threads/1/', + :shallow => true, + :options => { :thread_id => '1' } + assert_simply_restful_for :comments, + :name_prefix => 'message_', + :path_prefix => 'messages/2/', + :shallow => true, + :options => { :message_id => '2' } + end + end + def test_restful_routes_dont_generate_duplicates with_restful_routing :messages do routes = ActionController::Routing::Routes.routes @@ -429,6 +454,32 @@ class ResourcesTest < Test::Unit::TestCase end end + def test_resources_has_many_hash_should_become_nested_resources + with_routing do |set| + set.draw do |map| + map.resources :threads, :has_many => { :messages => [ :comments, { :authors => :threads } ] } + end + + assert_simply_restful_for :threads + assert_simply_restful_for :messages, :name_prefix => "thread_", :path_prefix => 'threads/1/', :options => { :thread_id => '1' } + assert_simply_restful_for :comments, :name_prefix => "thread_message_", :path_prefix => 'threads/1/messages/1/', :options => { :thread_id => '1', :message_id => '1' } + assert_simply_restful_for :authors, :name_prefix => "thread_message_", :path_prefix => 'threads/1/messages/1/', :options => { :thread_id => '1', :message_id => '1' } + assert_simply_restful_for :threads, :name_prefix => "thread_message_author_", :path_prefix => 'threads/1/messages/1/authors/1/', :options => { :thread_id => '1', :message_id => '1', :author_id => '1' } + end + end + + def test_shallow_resource_has_many_should_become_shallow_nested_resources + with_routing do |set| + set.draw do |map| + map.resources :messages, :has_many => [ :comments, :authors ], :shallow => true + end + + assert_simply_restful_for :messages, :shallow => true + assert_simply_restful_for :comments, :name_prefix => "message_", :path_prefix => 'messages/1/', :shallow => true, :options => { :message_id => '1' } + assert_simply_restful_for :authors, :name_prefix => "message_", :path_prefix => 'messages/1/', :shallow => true, :options => { :message_id => '1' } + end + end + def test_resource_has_one_should_become_nested_resources with_routing do |set| set.draw do |map| @@ -440,6 +491,17 @@ class ResourcesTest < Test::Unit::TestCase end end + def test_shallow_resource_has_one_should_become_shallow_nested_resources + with_routing do |set| + set.draw do |map| + map.resources :messages, :has_one => :logo, :shallow => true + end + + assert_simply_restful_for :messages, :shallow => true + assert_singleton_restful_for :logo, :name_prefix => 'message_', :path_prefix => 'messages/1/', :shallow => true, :options => { :message_id => '1' } + end + end + def test_singleton_resource_with_member_action [:put, :post].each do |method| with_singleton_resources :account, :member => { :reset => method } do @@ -744,6 +806,13 @@ class ResourcesTest < Test::Unit::TestCase options[:options] ||= {} options[:options][:controller] = options[:controller] || controller_name.to_s + if options[:shallow] + options[:shallow_options] ||= {} + options[:shallow_options][:controller] = options[:options][:controller] + else + options[:shallow_options] = options[:options] + end + new_action = ActionController::Base.resources_path_names[:new] || "new" edit_action = ActionController::Base.resources_path_names[:edit] || "edit" if options[:path_names] @@ -751,8 +820,10 @@ class ResourcesTest < Test::Unit::TestCase edit_action = options[:path_names][:edit] if options[:path_names][:edit] end - collection_path = "/#{options[:path_prefix]}#{options[:as] || controller_name}" - member_path = "#{collection_path}/1" + path = "#{options[:as] || controller_name}" + collection_path = "/#{options[:path_prefix]}#{path}" + shallow_path = "/#{options[:path_prefix] unless options[:shallow]}#{path}" + member_path = "#{shallow_path}/1" new_path = "#{collection_path}/#{new_action}" edit_member_path = "#{member_path}/#{edit_action}" formatted_edit_member_path = "#{member_path}/#{edit_action}.xml" @@ -760,10 +831,13 @@ class ResourcesTest < Test::Unit::TestCase with_options(options[:options]) do |controller| controller.assert_routing collection_path, :action => 'index' controller.assert_routing new_path, :action => 'new' - controller.assert_routing member_path, :action => 'show', :id => '1' - controller.assert_routing edit_member_path, :action => 'edit', :id => '1' controller.assert_routing "#{collection_path}.xml", :action => 'index', :format => 'xml' controller.assert_routing "#{new_path}.xml", :action => 'new', :format => 'xml' + end + + with_options(options[:shallow_options]) do |controller| + controller.assert_routing member_path, :action => 'show', :id => '1' + controller.assert_routing edit_member_path, :action => 'edit', :id => '1' controller.assert_routing "#{member_path}.xml", :action => 'show', :id => '1', :format => 'xml' controller.assert_routing formatted_edit_member_path, :action => 'edit', :id => '1', :format => 'xml' end @@ -771,18 +845,18 @@ class ResourcesTest < Test::Unit::TestCase assert_recognizes(options[:options].merge(:action => 'index'), :path => collection_path, :method => :get) assert_recognizes(options[:options].merge(:action => 'new'), :path => new_path, :method => :get) assert_recognizes(options[:options].merge(:action => 'create'), :path => collection_path, :method => :post) - assert_recognizes(options[:options].merge(:action => 'show', :id => '1'), :path => member_path, :method => :get) - assert_recognizes(options[:options].merge(:action => 'edit', :id => '1'), :path => edit_member_path, :method => :get) - assert_recognizes(options[:options].merge(:action => 'update', :id => '1'), :path => member_path, :method => :put) - assert_recognizes(options[:options].merge(:action => 'destroy', :id => '1'), :path => member_path, :method => :delete) - - assert_recognizes(options[:options].merge(:action => 'index', :format => 'xml'), :path => "#{collection_path}.xml", :method => :get) - assert_recognizes(options[:options].merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get) - assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{collection_path}.xml", :method => :post) - assert_recognizes(options[:options].merge(:action => 'show', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :get) - assert_recognizes(options[:options].merge(:action => 'edit', :id => '1', :format => 'xml'), :path => formatted_edit_member_path, :method => :get) - assert_recognizes(options[:options].merge(:action => 'update', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :put) - assert_recognizes(options[:options].merge(:action => 'destroy', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :delete) + assert_recognizes(options[:shallow_options].merge(:action => 'show', :id => '1'), :path => member_path, :method => :get) + assert_recognizes(options[:shallow_options].merge(:action => 'edit', :id => '1'), :path => edit_member_path, :method => :get) + assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1'), :path => member_path, :method => :put) + assert_recognizes(options[:shallow_options].merge(:action => 'destroy', :id => '1'), :path => member_path, :method => :delete) + + assert_recognizes(options[:options].merge(:action => 'index', :format => 'xml'), :path => "#{collection_path}.xml", :method => :get) + assert_recognizes(options[:options].merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get) + assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{collection_path}.xml", :method => :post) + assert_recognizes(options[:shallow_options].merge(:action => 'show', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :get) + assert_recognizes(options[:shallow_options].merge(:action => 'edit', :id => '1', :format => 'xml'), :path => formatted_edit_member_path, :method => :get) + assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :put) + assert_recognizes(options[:shallow_options].merge(:action => 'destroy', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :delete) yield options[:options] if block_given? end @@ -798,14 +872,24 @@ class ResourcesTest < Test::Unit::TestCase options[:options] ||= {} options[:options][:controller] = options[:controller] || controller_name.to_s + if options[:shallow] + options[:shallow_options] ||= {} + options[:shallow_options][:controller] = options[:options][:controller] + else + options[:shallow_options] = options[:options] + end + @controller = "#{options[:options][:controller].camelize}Controller".constantize.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new get :index, options[:options] options[:options].delete :action - full_prefix = "/#{options[:path_prefix]}#{options[:as] || controller_name}" + path = "#{options[:as] || controller_name}" + shallow_path = "/#{options[:path_prefix] unless options[:shallow]}#{path}" + full_path = "/#{options[:path_prefix]}#{path}" name_prefix = options[:name_prefix] + shallow_prefix = "#{options[:name_prefix] unless options[:shallow]}" new_action = "new" edit_action = "edit" @@ -814,15 +898,15 @@ class ResourcesTest < Test::Unit::TestCase edit_action = options[:path_names][:edit] || "edit" end - assert_named_route "#{full_prefix}", "#{name_prefix}#{controller_name}_path", options[:options] - assert_named_route "#{full_prefix}.xml", "formatted_#{name_prefix}#{controller_name}_path", options[:options].merge( :format => 'xml') - assert_named_route "#{full_prefix}/1", "#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1') - assert_named_route "#{full_prefix}/1.xml", "formatted_#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1', :format => 'xml') + assert_named_route "#{full_path}", "#{name_prefix}#{controller_name}_path", options[:options] + assert_named_route "#{full_path}.xml", "formatted_#{name_prefix}#{controller_name}_path", options[:options].merge(:format => 'xml') + assert_named_route "#{shallow_path}/1", "#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1') + assert_named_route "#{shallow_path}/1.xml", "formatted_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml') - assert_named_route "#{full_prefix}/#{new_action}", "new_#{name_prefix}#{singular_name}_path", options[:options] - assert_named_route "#{full_prefix}/#{new_action}.xml", "formatted_new_#{name_prefix}#{singular_name}_path", options[:options].merge( :format => 'xml') - assert_named_route "#{full_prefix}/1/#{edit_action}", "edit_#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1') - assert_named_route "#{full_prefix}/1/#{edit_action}.xml", "formatted_edit_#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1', :format => 'xml') + assert_named_route "#{full_path}/#{new_action}", "new_#{name_prefix}#{singular_name}_path", options[:options] + assert_named_route "#{full_path}/#{new_action}.xml", "formatted_new_#{name_prefix}#{singular_name}_path", options[:options].merge(:format => 'xml') + assert_named_route "#{shallow_path}/1/#{edit_action}", "edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1') + assert_named_route "#{shallow_path}/1/#{edit_action}.xml", "formatted_edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml') yield options[:options] if block_given? end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index f480e6dbe4..8bb1c49cbd 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -1694,6 +1694,12 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do controller.send(:multi_url, 7, "hello", 5, :baz => "bar") end + def test_named_route_url_method_with_ordered_parameters_and_empty_hash + controller = setup_named_route_test + assert_equal "http://named.route.test/people/go/7/hello/joe/5", + controller.send(:multi_url, 7, "hello", 5, {}) + end + def test_named_route_url_method_with_no_positional_arguments controller = setup_named_route_test assert_equal "http://named.route.test/people?baz=bar", diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb index abc9f930dd..a1f541fd7b 100644 --- a/actionpack/test/template/prototype_helper_test.rb +++ b/actionpack/test/template/prototype_helper_test.rb @@ -79,6 +79,8 @@ class PrototypeHelperTest < PrototypeHelperBaseTest link_to_remote("Remote outauthor", :failure => "alert(request.responseText)", :url => { :action => "whatnot", :a => '10', :b => '20' }) assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:false, evalScripts:true}); return false;\">Remote outauthor</a>), link_to_remote("Remote outauthor", :url => { :action => "whatnot" }, :type => :synchronous) + assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, insertion:'bottom'}); return false;\">Remote outauthor</a>), + link_to_remote("Remote outauthor", :url => { :action => "whatnot" }, :position => :bottom) end def test_link_to_remote_html_options @@ -91,6 +93,19 @@ class PrototypeHelperTest < PrototypeHelperBaseTest link_to_remote("Remote", { :url => { :action => "whatnot's" } }) end + def test_button_to_remote + assert_dom_equal %(<input class=\"fine\" type=\"button\" value=\"Remote outpost\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true});\" />), + button_to_remote("Remote outpost", { :url => { :action => "whatnot" }}, { :class => "fine" }) + assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onComplete:function(request){alert(request.reponseText)}});\" />), + button_to_remote("Remote outpost", :complete => "alert(request.reponseText)", :url => { :action => "whatnot" }) + assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onSuccess:function(request){alert(request.reponseText)}});\" />), + button_to_remote("Remote outpost", :success => "alert(request.reponseText)", :url => { :action => "whatnot" }) + assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.reponseText)}});\" />), + button_to_remote("Remote outpost", :failure => "alert(request.reponseText)", :url => { :action => "whatnot" }) + assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" onclick=\"new Ajax.Request('http://www.example.com/whatnot?a=10&b=20', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.reponseText)}});\" />), + button_to_remote("Remote outpost", :failure => "alert(request.reponseText)", :url => { :action => "whatnot", :a => '10', :b => '20' }) + end + def test_periodically_call_remote assert_dom_equal %(<script type="text/javascript">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Updater('schremser_bier', 'http://www.example.com/mehr_bier', {asynchronous:true, evalScripts:true})}, 10)\n//]]>\n</script>), periodically_call_remote(:update => "schremser_bier", :url => { :action => "mehr_bier" }) diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 7efe9901ca..460d2d82e5 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -4,7 +4,7 @@ module ActiveModel module Validations def self.included(base) # :nodoc: base.extend(ClassMethods) - base.send!(:include, ActiveSupport::Callbacks) + base.__send__(:include, ActiveSupport::Callbacks) base.define_callbacks :validate, :validate_on_create, :validate_on_update end diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index dcbe354733..58d0669770 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Connection pooling. #936 [Nick Sieger] + * Merge scoped :joins together instead of overwriting them. May expose scoping bugs in your code! #501 [Andrew White] * before_save, before_validation and before_destroy callbacks that return false will now ROLLBACK the transaction. Previously this would have been committed before the processing was aborted. #891 [Xavier Noria] diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 3ca93db10f..2470eb44b4 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1304,7 +1304,11 @@ module ActiveRecord end define_method("#{reflection.name.to_s.singularize}_ids") do - send(reflection.name).map { |record| record.id } + if send(reflection.name).loaded? + send(reflection.name).map(&:id) + else + send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map(&:id) + end end end diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index b9b7eb5f00..d7e152ed1d 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -101,9 +101,9 @@ module ActiveRecord class Scope attr_reader :proxy_scope, :proxy_options - + NON_DELEGATE_METHODS = %w(nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? respond_to?).to_set [].methods.each do |m| - unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|^find$|count|sum|average|maximum|minimum|paginate|first|last|empty\?|any\?|respond_to\?)/ + unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s) delegate m, :to => :proxy_found end end @@ -136,6 +136,10 @@ module ActiveRecord end end + def size + @found ? @found.length : count + end + def empty? @found ? @found.empty? : count.zero? end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 9ee80e6655..dae4ae8122 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -87,6 +87,8 @@ module ActiveRecord # </ol> def generate_message(attribute, message = :invalid, options = {}) + message, options[:default] = options[:default], message if options[:default].is_a?(Symbol) + defaults = @base.class.self_and_descendents_from_active_record.map do |klass| [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}", :"models.#{klass.name.underscore}.#{message}" ] @@ -95,7 +97,6 @@ module ActiveRecord defaults << options.delete(:default) defaults = defaults.compact.flatten << :"messages.#{message}" - model_name = @base.class.name key = defaults.shift value = @base.respond_to?(attribute) ? @base.send(attribute) : nil diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 1b31d28679..edca3c622b 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -223,10 +223,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase devel = Developer.find(1) proj = assert_no_queries { devel.projects.build("name" => "Projekt") } assert !devel.projects.loaded? - + assert_equal devel.projects.last, proj assert devel.projects.loaded? - + assert proj.new_record? devel.save assert !proj.new_record? @@ -251,10 +251,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase devel = Developer.find(1) proj = devel.projects.create("name" => "Projekt") assert !devel.projects.loaded? - + assert_equal devel.projects.last, proj assert devel.projects.loaded? - + assert !proj.new_record? assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated end @@ -274,10 +274,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_creation_respects_hash_condition post = categories(:general).post_with_conditions.build(:body => '') - + assert post.save assert_equal 'Yet Another Testing Title', post.title - + another_post = categories(:general).post_with_conditions.create(:body => '') assert !another_post.new_record? @@ -288,7 +288,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase dev = developers(:jamis) dev.projects << projects(:active_record) dev.projects << projects(:active_record) - + assert_equal 3, dev.projects.size assert_equal 1, dev.projects.uniq.size end @@ -415,13 +415,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase project.developers.class # force load target developer = project.developers.first - + assert_no_queries do assert project.developers.loaded? assert project.developers.include?(developer) end end - + def test_include_checks_if_record_exists_if_target_not_loaded project = projects(:active_record) developer = project.developers.first @@ -641,6 +641,22 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal [projects(:active_record).id], developers(:jamis).project_ids end + def test_get_ids_for_loaded_associations + developer = developers(:david) + developer.projects(true) + assert_queries(0) do + developer.project_ids + developer.project_ids + end + end + + def test_get_ids_for_unloaded_associations_does_not_load_them + developer = developers(:david) + assert !developer.projects.loaded? + assert_equal projects(:active_record, :action_controller).map(&:id).sort, developer.project_ids.sort + assert !developer.projects.loaded? + end + def test_assign_ids developer = Developer.new("name" => "Joe") developer.project_ids = projects(:active_record, :action_controller).map(&:id) diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 17fd88ce65..feac4b002b 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -384,7 +384,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase company = companies(:first_firm) new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") } assert !company.clients_of_firm.loaded? - + assert_equal "Another Client", new_client.name assert new_client.new_record? assert_equal new_client, company.clients_of_firm.last @@ -416,7 +416,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_build_many company = companies(:first_firm) new_clients = assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) } - + assert_equal 2, new_clients.size company.name += '-changed' assert_queries(3) { assert company.save } @@ -655,10 +655,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_creation_respects_hash_condition ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.build - + assert ms_client.save assert_equal 'Microsoft', ms_client.name - + another_ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.create assert !another_ms_client.new_record? @@ -830,6 +830,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids end + def test_get_ids_for_loaded_associations + company = companies(:first_firm) + company.clients(true) + assert_queries(0) do + company.client_ids + company.client_ids + end + end + + def test_get_ids_for_unloaded_associations_does_not_load_them + company = companies(:first_firm) + assert !company.clients.loaded? + assert_equal [companies(:first_client).id, companies(:second_client).id], company.client_ids + assert !company.clients.loaded? + end + def test_assign_ids firm = Firm.new("name" => "Apple") firm.client_ids = [companies(:first_client).id, companies(:second_client).id] @@ -900,7 +916,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 4, authors(:david).limited_comments.find(:all, :conditions => "comments.type = 'SpecialComment'", :limit => 9_000).length assert_equal 4, authors(:david).limited_comments.find_all_by_type('SpecialComment', :limit => 9_000).length end - + def test_find_all_include_over_the_same_table_for_through assert_equal 2, people(:michael).posts.find(:all, :include => :people).length end @@ -937,13 +953,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_include_loads_collection_if_target_uses_finder_sql firm = companies(:first_firm) client = firm.clients_using_sql.first - + firm.reload assert ! firm.clients_using_sql.loaded? assert firm.clients_using_sql.include?(client) assert firm.clients_using_sql.loaded? end - + def test_include_returns_false_for_non_matching_record_to_verify_scoping firm = companies(:first_firm) 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 353262c81b..0be050ec81 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -200,4 +200,24 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_count_with_include_should_alias_join_table assert_equal 2, people(:michael).posts.count(:include => :readers) end + + def test_get_ids + assert_equal [posts(:welcome).id, posts(:authorless).id].sort, people(:michael).post_ids.sort + end + + def test_get_ids_for_loaded_associations + person = people(:michael) + person.posts(true) + assert_queries(0) do + person.post_ids + person.post_ids + end + end + + def test_get_ids_for_unloaded_associations_does_not_load_them + person = people(:michael) + assert !person.posts.loaded? + assert_equal [posts(:welcome).id, posts(:authorless).id].sort, person.post_ids.sort + assert !person.posts.loaded? + end end diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index acc3b3016a..444debd255 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -256,4 +256,19 @@ class NamedScopeTest < ActiveRecord::TestCase def test_should_use_where_in_query_for_named_scope assert_equal Developer.find_all_by_name('Jamis'), Developer.find_all_by_id(Developer.jamises) end + + def test_size_should_use_count_when_results_are_not_loaded + topics = Topic.base + assert_queries(1) do + assert_sql(/COUNT/i) { topics.size } + end + end + + def test_size_should_use_length_when_results_are_loaded + topics = Topic.base + topics.reload # force load + assert_no_queries do + topics.size # use loaded (no query) + end + end end diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations_i18n_test.rb index 43592bcee3..090f347a20 100644 --- a/activerecord/test/cases/validations_i18n_test.rb +++ b/activerecord/test/cases/validations_i18n_test.rb @@ -675,6 +675,38 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase replied_topic.valid? assert_equal 'global message', replied_topic.errors.on(:replies) end + + def test_validations_with_message_symbol_must_translate + I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:custom_error => "I am a custom error"}}} + Topic.validates_presence_of :title, :message => :custom_error + @topic.title = nil + @topic.valid? + assert_equal "I am a custom error", @topic.errors.on(:title) + end + + def test_validates_with_message_symbol_must_translate_per_attribute + I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}} + Topic.validates_presence_of :title, :message => :custom_error + @topic.title = nil + @topic.valid? + assert_equal "I am a custom error", @topic.errors.on(:title) + end + + def test_validates_with_message_symbol_must_translate_per_model + I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:custom_error => "I am a custom error"}}}} + Topic.validates_presence_of :title, :message => :custom_error + @topic.title = nil + @topic.valid? + assert_equal "I am a custom error", @topic.errors.on(:title) + end + + def test_validates_with_message_string + Topic.validates_presence_of :title, :message => "I am a custom error" + @topic.title = nil + @topic.valid? + assert_equal "I am a custom error", @topic.errors.on(:title) + end + end class ActiveRecordValidationsGenerateMessageI18nTests < Test::Unit::TestCase @@ -855,4 +887,5 @@ class ActiveRecordValidationsGenerateMessageI18nTests < Test::Unit::TestCase def test_generate_message_even_with_default_message assert_equal "must be even", @topic.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10) end + end diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 4af30ea13f..9dc715b0fa 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -848,8 +848,13 @@ module ActiveResource # # my_group.to_xml(:skip_instruct => true) # # => <subsidiary_group> [...] </subsidiary_group> - def to_xml(options={}) - attributes.to_xml({:root => self.class.element_name}.merge(options)) + def encode(options={}) + case self.class.format + when ActiveResource::Formats[:xml] + self.class.format.encode(attributes, {:root => self.class.element_name}.merge(options)) + else + self.class.format.encode(attributes, options) + end end # A method to reload the attributes of this object from the remote web service. @@ -934,14 +939,14 @@ module ActiveResource # Update the resource on the remote service. def update - returning connection.put(element_path(prefix_options), to_xml, self.class.headers) do |response| + returning connection.put(element_path(prefix_options), encode, self.class.headers) do |response| load_attributes_from_response(response) end end # Create (i.e., save to the remote service) the new resource. def create - returning connection.post(collection_path, to_xml, self.class.headers) do |response| + returning connection.post(collection_path, encode, self.class.headers) do |response| self.id = id_from_response(response) load_attributes_from_response(response) end @@ -1007,7 +1012,7 @@ module ActiveResource end def split_options(options = {}) - self.class.send!(:split_options, options) + self.class.__send__(:split_options, options) end def method_missing(method_symbol, *arguments) #:nodoc: diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index d64fb79f1e..fe9c2d57fe 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -63,6 +63,13 @@ module ActiveResource # This class is used by ActiveResource::Base to interface with REST # services. class Connection + + HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept', + :put => 'Content-Type', + :post => 'Content-Type', + :delete => 'Accept' + } + attr_reader :site, :user, :password, :timeout attr_accessor :format @@ -106,25 +113,25 @@ module ActiveResource # Execute a GET request. # Used to get (find) resources. def get(path, headers = {}) - format.decode(request(:get, path, build_request_headers(headers)).body) + format.decode(request(:get, path, build_request_headers(headers, :get)).body) end # Execute a DELETE request (see HTTP protocol documentation if unfamiliar). # Used to delete resources. def delete(path, headers = {}) - request(:delete, path, build_request_headers(headers)) + request(:delete, path, build_request_headers(headers, :delete)) end # Execute a PUT request (see HTTP protocol documentation if unfamiliar). # Used to update resources. def put(path, body = '', headers = {}) - request(:put, path, body.to_s, build_request_headers(headers)) + request(:put, path, body.to_s, build_request_headers(headers, :put)) end # Execute a POST request. # Used to create new resources. def post(path, body = '', headers = {}) - request(:post, path, body.to_s, build_request_headers(headers)) + request(:post, path, body.to_s, build_request_headers(headers, :post)) end # Execute a HEAD request. @@ -187,12 +194,12 @@ module ActiveResource end def default_header - @default_header ||= { 'Content-Type' => format.mime_type } + @default_header ||= {} end # Builds headers for request to remote service. - def build_request_headers(headers) - authorization_header.update(default_header).update(headers) + def build_request_headers(headers, http_method=nil) + authorization_header.update(default_header).update(headers).update(http_format_header(http_method)) end # Sets authorization header @@ -200,6 +207,10 @@ module ActiveResource (@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {}) end + def http_format_header(http_method) + {HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type} + end + def logger #:nodoc: Base.logger end diff --git a/activeresource/lib/active_resource/custom_methods.rb b/activeresource/lib/active_resource/custom_methods.rb index 770116ceb7..24306f251d 100644 --- a/activeresource/lib/active_resource/custom_methods.rb +++ b/activeresource/lib/active_resource/custom_methods.rb @@ -30,7 +30,7 @@ module ActiveResource # Person.get(:active) # GET /people/active.xml # # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}] # - module CustomMethods + module CustomMethods def self.included(base) base.class_eval do extend ActiveResource::CustomMethods::ClassMethods @@ -83,24 +83,25 @@ module ActiveResource "#{prefix(prefix_options)}#{collection_name}/#{method_name}.#{format.extension}#{query_string(query_options)}" end end - + module InstanceMethods def get(method_name, options = {}) connection.get(custom_method_element_url(method_name, options), self.class.headers) end - - def post(method_name, options = {}, body = '') + + def post(method_name, options = {}, body = nil) + request_body = body.nil? ? encode : body if new? - connection.post(custom_method_new_element_url(method_name, options), (body.nil? ? to_xml : body), self.class.headers) + connection.post(custom_method_new_element_url(method_name, options), request_body, self.class.headers) else - connection.post(custom_method_element_url(method_name, options), body, self.class.headers) + connection.post(custom_method_element_url(method_name, options), request_body, self.class.headers) end end - + def put(method_name, options = {}, body = '') connection.put(custom_method_element_url(method_name, options), body, self.class.headers) end - + def delete(method_name, options = {}) connection.delete(custom_method_element_url(method_name, options), self.class.headers) end @@ -108,11 +109,11 @@ module ActiveResource private def custom_method_element_url(method_name, options = {}) - "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/#{method_name}.#{self.class.format.extension}#{self.class.send!(:query_string, options)}" + "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/#{method_name}.#{self.class.format.extension}#{self.class.__send__(:query_string, options)}" end - + def custom_method_new_element_url(method_name, options = {}) - "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}.#{self.class.format.extension}#{self.class.send!(:query_string, options)}" + "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}.#{self.class.format.extension}#{self.class.__send__(:query_string, options)}" end end end diff --git a/activeresource/lib/active_resource/formats/json_format.rb b/activeresource/lib/active_resource/formats/json_format.rb index df0d6ca372..9e269d4ded 100644 --- a/activeresource/lib/active_resource/formats/json_format.rb +++ b/activeresource/lib/active_resource/formats/json_format.rb @@ -2,22 +2,22 @@ module ActiveResource module Formats module JsonFormat extend self - + def extension "json" end - + def mime_type "application/json" end - - def encode(hash) + + def encode(hash, options={}) hash.to_json end - + def decode(json) ActiveSupport::JSON.decode(json) end end end -end
\ No newline at end of file +end diff --git a/activeresource/lib/active_resource/formats/xml_format.rb b/activeresource/lib/active_resource/formats/xml_format.rb index 5e97ffa776..86c6cec745 100644 --- a/activeresource/lib/active_resource/formats/xml_format.rb +++ b/activeresource/lib/active_resource/formats/xml_format.rb @@ -2,23 +2,23 @@ module ActiveResource module Formats module XmlFormat extend self - + def extension "xml" end - + def mime_type "application/xml" end - - def encode(hash) - hash.to_xml + + def encode(hash, options={}) + hash.to_xml(options) end - + def decode(xml) from_xml_data(Hash.from_xml(xml)) end - + private # Manipulate from_xml Hash, because xml_simple is not exactly what we # want for Active Resource. @@ -28,7 +28,7 @@ module ActiveResource else data end - end + end end end -end
\ No newline at end of file +end diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb index 554fc3bcfc..9ed532b48c 100644 --- a/activeresource/lib/active_resource/http_mock.rb +++ b/activeresource/lib/active_resource/http_mock.rb @@ -146,7 +146,7 @@ module ActiveResource attr_accessor :path, :method, :body, :headers def initialize(method, path, body = nil, headers = {}) - @method, @path, @body, @headers = method, path, body, headers.reverse_merge('Content-Type' => 'application/xml') + @method, @path, @body, @headers = method, path, body, headers.merge(ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[method] => 'application/xml') end def ==(other_request) diff --git a/activeresource/test/authorization_test.rb b/activeresource/test/authorization_test.rb index 9215227620..ead7f5c12f 100644 --- a/activeresource/test/authorization_test.rb +++ b/activeresource/test/authorization_test.rb @@ -19,7 +19,7 @@ class AuthorizationTest < Test::Unit::TestCase end def test_authorization_header - authorization_header = @authenticated_conn.send!(:authorization_header) + authorization_header = @authenticated_conn.__send__(:authorization_header) assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization'] authorization = authorization_header["Authorization"].to_s.split @@ -29,7 +29,7 @@ class AuthorizationTest < Test::Unit::TestCase def test_authorization_header_with_username_but_no_password @conn = ActiveResource::Connection.new("http://david:@localhost") - authorization_header = @conn.send!(:authorization_header) + authorization_header = @conn.__send__(:authorization_header) authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] @@ -38,7 +38,7 @@ class AuthorizationTest < Test::Unit::TestCase def test_authorization_header_with_password_but_no_username @conn = ActiveResource::Connection.new("http://:test123@localhost") - authorization_header = @conn.send!(:authorization_header) + authorization_header = @conn.__send__(:authorization_header) authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] @@ -47,7 +47,7 @@ class AuthorizationTest < Test::Unit::TestCase def test_authorization_header_with_decoded_credentials_from_url @conn = ActiveResource::Connection.new("http://my%40email.com:%31%32%33@localhost") - authorization_header = @conn.send!(:authorization_header) + authorization_header = @conn.__send__(:authorization_header) authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] @@ -58,7 +58,7 @@ class AuthorizationTest < Test::Unit::TestCase @authenticated_conn = ActiveResource::Connection.new("http://@localhost") @authenticated_conn.user = 'david' @authenticated_conn.password = 'test123' - authorization_header = @authenticated_conn.send!(:authorization_header) + authorization_header = @authenticated_conn.__send__(:authorization_header) assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization'] authorization = authorization_header["Authorization"].to_s.split @@ -69,7 +69,7 @@ class AuthorizationTest < Test::Unit::TestCase def test_authorization_header_explicitly_setting_username_but_no_password @conn = ActiveResource::Connection.new("http://@localhost") @conn.user = "david" - authorization_header = @conn.send!(:authorization_header) + authorization_header = @conn.__send__(:authorization_header) authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] @@ -79,7 +79,7 @@ class AuthorizationTest < Test::Unit::TestCase def test_authorization_header_explicitly_setting_password_but_no_username @conn = ActiveResource::Connection.new("http://@localhost") @conn.password = "test123" - authorization_header = @conn.send!(:authorization_header) + authorization_header = @conn.__send__(:authorization_header) authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] @@ -116,7 +116,7 @@ class AuthorizationTest < Test::Unit::TestCase protected def assert_response_raises(klass, code) assert_raise(klass, "Expected response code #{code} to raise #{klass}") do - @conn.send!(:handle_response, Response.new(code)) + @conn.__send__(:handle_response, Response.new(code)) end end end diff --git a/activeresource/test/base/custom_methods_test.rb b/activeresource/test/base/custom_methods_test.rb index 62c33ef9bc..ba5799edfb 100644 --- a/activeresource/test/base/custom_methods_test.rb +++ b/activeresource/test/base/custom_methods_test.rb @@ -10,8 +10,7 @@ class CustomMethodsTest < Test::Unit::TestCase @ryan = { :name => 'Ryan' }.to_xml(:root => 'person') @addy = { :id => 1, :street => '12345 Street' }.to_xml(:root => 'address') @addy_deep = { :id => 1, :street => '12345 Street', :zip => "27519" }.to_xml(:root => 'address') - @default_request_headers = { 'Content-Type' => 'application/xml' } - + ActiveResource::HttpMock.respond_to do |mock| mock.get "/people/1.xml", {}, @matz mock.get "/people/1/shallow.xml", {}, @matz diff --git a/activeresource/test/base/load_test.rb b/activeresource/test/base/load_test.rb index 737afb1748..a475fab34e 100644 --- a/activeresource/test/base/load_test.rb +++ b/activeresource/test/base/load_test.rb @@ -84,7 +84,7 @@ class BaseLoadTest < Test::Unit::TestCase end def test_load_collection_with_unknown_resource - Person.send!(:remove_const, :Address) if Person.const_defined?(:Address) + Person.__send__(:remove_const, :Address) if Person.const_defined?(:Address) assert !Person.const_defined?(:Address), "Address shouldn't exist until autocreated" addresses = silence_warnings { @person.load(:addresses => @addresses).addresses } assert Person.const_defined?(:Address), "Address should have been autocreated" @@ -100,7 +100,7 @@ class BaseLoadTest < Test::Unit::TestCase end def test_load_collection_with_single_unknown_resource - Person.send!(:remove_const, :Address) if Person.const_defined?(:Address) + Person.__send__(:remove_const, :Address) if Person.const_defined?(:Address) assert !Person.const_defined?(:Address), "Address shouldn't exist until autocreated" addresses = silence_warnings { @person.load(:addresses => [ @first_address ]).addresses } assert Person.const_defined?(:Address), "Address should have been autocreated" diff --git a/activeresource/test/base_test.rb b/activeresource/test/base_test.rb index 9a0789e0b4..7460fd45b0 100644 --- a/activeresource/test/base_test.rb +++ b/activeresource/test/base_test.rb @@ -468,7 +468,7 @@ class BaseTest < Test::Unit::TestCase def test_prefix assert_equal "/", Person.prefix - assert_equal Set.new, Person.send!(:prefix_parameters) + assert_equal Set.new, Person.__send__(:prefix_parameters) end def test_set_prefix @@ -504,7 +504,7 @@ class BaseTest < Test::Unit::TestCase def test_custom_prefix assert_equal '/people//', StreetAddress.prefix assert_equal '/people/1/', StreetAddress.prefix(:person_id => 1) - assert_equal [:person_id].to_set, StreetAddress.send!(:prefix_parameters) + assert_equal [:person_id].to_set, StreetAddress.__send__(:prefix_parameters) end def test_find_by_id @@ -607,10 +607,10 @@ class BaseTest < Test::Unit::TestCase def test_id_from_response p = Person.new resp = {'Location' => '/foo/bar/1'} - assert_equal '1', p.send!(:id_from_response, resp) + assert_equal '1', p.__send__(:id_from_response, resp) resp['Location'] << '.xml' - assert_equal '1', p.send!(:id_from_response, resp) + assert_equal '1', p.__send__(:id_from_response, resp) end def test_create_with_custom_prefix @@ -825,7 +825,7 @@ class BaseTest < Test::Unit::TestCase def test_to_xml matz = Person.find(1) - xml = matz.to_xml + xml = matz.encode assert xml.starts_with?('<?xml version="1.0" encoding="UTF-8"?>') assert xml.include?('<name>Matz</name>') assert xml.include?('<id type="integer">1</id>') diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb index 8e43e451ff..06f31f1b57 100644 --- a/activeresource/test/connection_test.rb +++ b/activeresource/test/connection_test.rb @@ -185,6 +185,6 @@ class ConnectionTest < Test::Unit::TestCase end def handle_response(response) - @conn.send!(:handle_response, response) + @conn.__send__(:handle_response, response) end end diff --git a/activeresource/test/format_test.rb b/activeresource/test/format_test.rb index 3c81803fce..365576a092 100644 --- a/activeresource/test/format_test.rb +++ b/activeresource/test/format_test.rb @@ -5,14 +5,22 @@ class FormatTest < Test::Unit::TestCase def setup @matz = { :id => 1, :name => 'Matz' } @david = { :id => 2, :name => 'David' } - + @programmers = [ @matz, @david ] end - + + def test_http_format_header_name + header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:get] + assert_equal 'Accept', header_name + + headers_names = [ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:put], ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:post]] + headers_names.each{|header_name| assert_equal 'Content-Type', header_name} + end + def test_formats_on_single_element for format in [ :json, :xml ] using_format(Person, format) do - ActiveResource::HttpMock.respond_to.get "/people/1.#{format}", {}, ActiveResource::Formats[format].encode(@david) + ActiveResource::HttpMock.respond_to.get "/people/1.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@david) assert_equal @david[:name], Person.find(1).name end end @@ -21,7 +29,7 @@ class FormatTest < Test::Unit::TestCase def test_formats_on_collection for format in [ :json, :xml ] using_format(Person, format) do - ActiveResource::HttpMock.respond_to.get "/people.#{format}", {}, ActiveResource::Formats[format].encode(@programmers) + ActiveResource::HttpMock.respond_to.get "/people.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@programmers) remote_programmers = Person.find(:all) assert_equal 2, remote_programmers.size assert remote_programmers.select { |p| p.name == 'David' } @@ -32,7 +40,7 @@ class FormatTest < Test::Unit::TestCase def test_formats_on_custom_collection_method for format in [ :json, :xml ] using_format(Person, format) do - ActiveResource::HttpMock.respond_to.get "/people/retrieve.#{format}?name=David", {}, ActiveResource::Formats[format].encode([@david]) + ActiveResource::HttpMock.respond_to.get "/people/retrieve.#{format}?name=David", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode([@david]) remote_programmers = Person.get(:retrieve, :name => 'David') assert_equal 1, remote_programmers.size assert_equal @david[:id], remote_programmers[0]['id'] @@ -40,13 +48,13 @@ class FormatTest < Test::Unit::TestCase end end end - + def test_formats_on_custom_element_method for format in [ :json, :xml ] using_format(Person, format) do ActiveResource::HttpMock.respond_to do |mock| - mock.get "/people/2.#{format}", {}, ActiveResource::Formats[format].encode(@david) - mock.get "/people/2/shallow.#{format}", {}, ActiveResource::Formats[format].encode(@david) + mock.get "/people/2.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@david) + mock.get "/people/2/shallow.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@david) end remote_programmer = Person.find(2).get(:shallow) assert_equal @david[:id], remote_programmer['id'] @@ -57,20 +65,24 @@ class FormatTest < Test::Unit::TestCase for format in [ :json, :xml ] ryan = ActiveResource::Formats[format].encode({ :name => 'Ryan' }) using_format(Person, format) do - ActiveResource::HttpMock.respond_to.post "/people/new/register.#{format}", {}, ryan, 201, 'Location' => "/people/5.#{format}" remote_ryan = Person.new(:name => 'Ryan') + ActiveResource::HttpMock.respond_to.post "/people.#{format}", {'Content-Type' => ActiveResource::Formats[format].mime_type}, ryan, 201, {'Location' => "/people/5.#{format}"} + remote_ryan.save + + remote_ryan = Person.new(:name => 'Ryan') + ActiveResource::HttpMock.respond_to.post "/people/new/register.#{format}", {'Content-Type' => ActiveResource::Formats[format].mime_type}, ryan, 201, {'Location' => "/people/5.#{format}"} assert_equal ActiveResource::Response.new(ryan, 201, {'Location' => "/people/5.#{format}"}), remote_ryan.post(:register) end end end - + def test_setting_format_before_site resource = Class.new(ActiveResource::Base) resource.format = :json resource.site = 'http://37s.sunrise.i:3000' assert_equal ActiveResource::Formats[:json], resource.connection.format end - + private def using_format(klass, mime_type_reference) previous_format = klass.format diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index 392ba99f4e..da8d28ec13 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -9,6 +9,15 @@ require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/module/model_naming' require 'active_support/core_ext/module/synchronization' +module ActiveSupport + module CoreExtensions + # Various extensions for the Ruby core Module class. + module Module + # Nothing here. Only defined for API documentation purposes. + end + end +end + class Module - include ActiveSupport::CoreExt::Module::ModelNaming + include ActiveSupport::CoreExtensions::Module end diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb index 1894e3b0a2..e640f64520 100644 --- a/activesupport/lib/active_support/core_ext/module/aliasing.rb +++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb @@ -1,70 +1,74 @@ -class Module - # Encapsulates the common pattern of: - # - # alias_method :foo_without_feature, :foo - # alias_method :foo, :foo_with_feature - # - # With this, you simply do: - # - # alias_method_chain :foo, :feature - # - # And both aliases are set up for you. - # - # Query and bang methods (foo?, foo!) keep the same punctuation: - # - # alias_method_chain :foo?, :feature - # - # is equivalent to - # - # alias_method :foo_without_feature?, :foo? - # alias_method :foo?, :foo_with_feature? - # - # so you can safely chain foo, foo?, and foo! with the same feature. - def alias_method_chain(target, feature) - # Strip out punctuation on predicates or bang methods since - # e.g. target?_without_feature is not a valid method name. - aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 - yield(aliased_target, punctuation) if block_given? - - with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}" - - alias_method without_method, target - alias_method target, with_method - - case - when public_method_defined?(without_method) - public target - when protected_method_defined?(without_method) - protected target - when private_method_defined?(without_method) - private target - end - end +module ActiveSupport + module CoreExtensions + module Module + # Encapsulates the common pattern of: + # + # alias_method :foo_without_feature, :foo + # alias_method :foo, :foo_with_feature + # + # With this, you simply do: + # + # alias_method_chain :foo, :feature + # + # And both aliases are set up for you. + # + # Query and bang methods (foo?, foo!) keep the same punctuation: + # + # alias_method_chain :foo?, :feature + # + # is equivalent to + # + # alias_method :foo_without_feature?, :foo? + # alias_method :foo?, :foo_with_feature? + # + # so you can safely chain foo, foo?, and foo! with the same feature. + def alias_method_chain(target, feature) + # Strip out punctuation on predicates or bang methods since + # e.g. target?_without_feature is not a valid method name. + aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 + yield(aliased_target, punctuation) if block_given? + + with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}" - # Allows you to make aliases for attributes, which includes - # getter, setter, and query methods. - # - # Example: - # - # class Content < ActiveRecord::Base - # # has a title attribute - # end - # - # class Email < Content - # alias_attribute :subject, :title - # end - # - # e = Email.find(1) - # e.title # => "Superstars" - # e.subject # => "Superstars" - # e.subject? # => true - # e.subject = "Megastars" - # e.title # => "Megastars" - def alias_attribute(new_name, old_name) - module_eval <<-STR, __FILE__, __LINE__+1 - def #{new_name}; self.#{old_name}; end - def #{new_name}?; self.#{old_name}?; end - def #{new_name}=(v); self.#{old_name} = v; end - STR + alias_method without_method, target + alias_method target, with_method + + case + when public_method_defined?(without_method) + public target + when protected_method_defined?(without_method) + protected target + when private_method_defined?(without_method) + private target + end + end + + # Allows you to make aliases for attributes, which includes + # getter, setter, and query methods. + # + # Example: + # + # class Content < ActiveRecord::Base + # # has a title attribute + # end + # + # class Email < Content + # alias_attribute :subject, :title + # end + # + # e = Email.find(1) + # e.title # => "Superstars" + # e.subject # => "Superstars" + # e.subject? # => true + # e.subject = "Megastars" + # e.title # => "Megastars" + def alias_attribute(new_name, old_name) + module_eval <<-STR, __FILE__, __LINE__+1 + def #{new_name}; self.#{old_name}; end + def #{new_name}?; self.#{old_name}?; end + def #{new_name}=(v); self.#{old_name} = v; end + STR + end + end end end diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb index 45f3e4bf5c..8beaff4b82 100644 --- a/activesupport/lib/active_support/core_ext/module/introspection.rb +++ b/activesupport/lib/active_support/core_ext/module/introspection.rb @@ -1,86 +1,90 @@ -class Module - # Returns the name of the module containing this one. - # - # p M::N.parent_name # => "M" - def parent_name - unless defined? @parent_name - @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil - end - @parent_name - end +module ActiveSupport + module CoreExtensions + module Module + # Returns the name of the module containing this one. + # + # p M::N.parent_name # => "M" + def parent_name + unless defined? @parent_name + @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil + end + @parent_name + end - # Returns the module which contains this one according to its name. - # - # module M - # module N - # end - # end - # X = M::N - # - # p M::N.parent # => M - # p X.parent # => M - # - # The parent of top-level and anonymous modules is Object. - # - # p M.parent # => Object - # p Module.new.parent # => Object - # - def parent - parent_name ? parent_name.constantize : Object - end + # Returns the module which contains this one according to its name. + # + # module M + # module N + # end + # end + # X = M::N + # + # p M::N.parent # => M + # p X.parent # => M + # + # The parent of top-level and anonymous modules is Object. + # + # p M.parent # => Object + # p Module.new.parent # => Object + # + def parent + parent_name ? parent_name.constantize : Object + end - # Returns all the parents of this module according to its name, ordered from - # nested outwards. The receiver is not contained within the result. - # - # module M - # module N - # end - # end - # X = M::N - # - # p M.parents # => [Object] - # p M::N.parents # => [M, Object] - # p X.parents # => [M, Object] - # - def parents - parents = [] - if parent_name - parts = parent_name.split('::') - until parts.empty? - parents << (parts * '::').constantize - parts.pop + # Returns all the parents of this module according to its name, ordered from + # nested outwards. The receiver is not contained within the result. + # + # module M + # module N + # end + # end + # X = M::N + # + # p M.parents # => [Object] + # p M::N.parents # => [M, Object] + # p X.parents # => [M, Object] + # + def parents + parents = [] + if parent_name + parts = parent_name.split('::') + until parts.empty? + parents << (parts * '::').constantize + parts.pop + end + end + parents << Object unless parents.include? Object + parents end - end - parents << Object unless parents.include? Object - parents - end - if RUBY_VERSION < '1.9' - # Returns the constants that have been defined locally by this object and - # not in an ancestor. This method is exact if running under Ruby 1.9. In - # previous versions it may miss some constants if their definition in some - # ancestor is identical to their definition in the receiver. - def local_constants - inherited = {} + if RUBY_VERSION < '1.9' + # Returns the constants that have been defined locally by this object and + # not in an ancestor. This method is exact if running under Ruby 1.9. In + # previous versions it may miss some constants if their definition in some + # ancestor is identical to their definition in the receiver. + def local_constants + inherited = {} + + ancestors.each do |anc| + next if anc == self + anc.constants.each { |const| inherited[const] = anc.const_get(const) } + end - ancestors.each do |anc| - next if anc == self - anc.constants.each { |const| inherited[const] = anc.const_get(const) } + constants.select do |const| + !inherited.key?(const) || inherited[const].object_id != const_get(const).object_id + end + end + else + def local_constants #:nodoc: + constants(false) + end end - constants.select do |const| - !inherited.key?(const) || inherited[const].object_id != const_get(const).object_id + # Returns the names of the constants defined locally rather than the + # constants themselves. See <tt>local_constants</tt>. + def local_constant_names + local_constants.map { |c| c.to_s } end end - else - def local_constants #:nodoc: - constants(false) - end - end - - # Returns the names of the constants defined locally rather than the - # constants themselves. See <tt>local_constants</tt>. - def local_constant_names - local_constants.map { |c| c.to_s } end end diff --git a/activesupport/lib/active_support/core_ext/module/model_naming.rb b/activesupport/lib/active_support/core_ext/module/model_naming.rb index 5518f5417b..3ec4f3ba11 100644 --- a/activesupport/lib/active_support/core_ext/module/model_naming.rb +++ b/activesupport/lib/active_support/core_ext/module/model_naming.rb @@ -11,12 +11,12 @@ module ActiveSupport end end - module CoreExt + module CoreExtensions module Module - module ModelNaming - def model_name - @model_name ||= ModelName.new(name) - end + # Returns an ActiveSupport::ModelName object for module. It can be + # used to retrieve all kinds of naming-related information. + def model_name + @model_name ||= ModelName.new(name) end end end diff --git a/activesupport/lib/active_support/core_ext/object/misc.rb b/activesupport/lib/active_support/core_ext/object/misc.rb index 8384a12327..06a7d05702 100644 --- a/activesupport/lib/active_support/core_ext/object/misc.rb +++ b/activesupport/lib/active_support/core_ext/object/misc.rb @@ -1,9 +1,4 @@ class Object - unless respond_to?(:send!) - # Anticipating Ruby 1.9 neutering send - alias send! send - end - # A Ruby-ized realization of the K combinator, courtesy of Mikael Brockman. # # def foo diff --git a/activesupport/lib/active_support/core_ext/rexml.rb b/activesupport/lib/active_support/core_ext/rexml.rb new file mode 100644 index 0000000000..af8ce3af47 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/rexml.rb @@ -0,0 +1,35 @@ +require 'rexml/document' +require 'rexml/entity' + +# Fixes the rexml vulnerability disclosed at: +# http://www.ruby-lang.org/en/news/2008/08/23/dos-vulnerability-in-rexml/ +# This fix is identical to rexml-expansion-fix version 1.0.1 + +unless REXML::VERSION > "3.1.7.2" + module REXML + class Entity < Child + undef_method :unnormalized + def unnormalized + document.record_entity_expansion! if document + v = value() + return nil if v.nil? + @unnormalized = Text::unnormalize(v, parent) + @unnormalized + end + end + class Document < Element + @@entity_expansion_limit = 10_000 + def self.entity_expansion_limit= val + @@entity_expansion_limit = val + end + + def record_entity_expansion! + @number_of_expansions ||= 0 + @number_of_expansions += 1 + if @number_of_expansions > @@entity_expansion_limit + raise "Number of entity expansions exceeded, processing aborted." + end + end + end + end +end diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index 079ecdd48e..9d8eb73908 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -78,7 +78,7 @@ module ActiveSupport #:nodoc: # # Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 def in_time_zone(zone = ::Time.zone) - ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.send!(:get_zone, zone)) + ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.__send__(:get_zone, zone)) end end end diff --git a/activesupport/lib/active_support/option_merger.rb b/activesupport/lib/active_support/option_merger.rb index c77bca1ac9..b563b093ed 100644 --- a/activesupport/lib/active_support/option_merger.rb +++ b/activesupport/lib/active_support/option_merger.rb @@ -11,7 +11,7 @@ module ActiveSupport private def method_missing(method, *arguments, &block) arguments << (arguments.last.respond_to?(:to_hash) ? @options.deep_merge(arguments.pop) : @options.dup) - @context.send!(method, *arguments, &block) + @context.__send__(method, *arguments, &block) end end end diff --git a/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb b/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb index 70a44eab8c..63d1ba6507 100644 --- a/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb +++ b/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb @@ -36,7 +36,11 @@ module Test # post :delete, :id => ... # end def assert_difference(expressions, difference = 1, message = nil, &block) - expression_evaluations = Array(expressions).collect{ |expression| lambda { eval(expression, block.send!(:binding)) } } + expression_evaluations = Array(expressions).map do |expression| + lambda do + eval(expression, block.__send__(:binding)) + end + end original_values = expression_evaluations.inject([]) { |memo, expression| memo << expression.call } yield diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 4866fa0dc8..75591b7c34 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -275,7 +275,7 @@ module ActiveSupport end def marshal_load(variables) - initialize(variables[0].utc, ::Time.send!(:get_zone, variables[1]), variables[2].utc) + initialize(variables[0].utc, ::Time.__send__(:get_zone, variables[1]), variables[2].utc) end # Ensure proxy class responds to all methods that underlying time instance responds to. diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb index 2e966a51be..da89b30c54 100644 --- a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb +++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb @@ -3,6 +3,9 @@ require 'strscan' module I18n module Backend class Simple + INTERPOLATION_RESERVED_KEYS = %w(scope default) + MATCH = /(\\\\)?\{\{([^\}]+)\}\}/ + # Accepts a list of paths to translation files. Loads translations from # plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml # for details. @@ -114,29 +117,29 @@ module I18n # the <tt>{{...}}</tt> key in a string (once for the string and once for the # interpolation). def interpolate(locale, string, values = {}) - return string if !string.is_a?(String) + return string unless string.is_a?(String) string = string.gsub(/%d/, '{{count}}').gsub(/%s/, '{{value}}') + if string.respond_to?(:force_encoding) - original_encoding = string.encoding - string.force_encoding(Encoding::BINARY) - end - s = StringScanner.new(string) - - while s.skip_until(/\{\{/) - s.string[s.pos - 3, 1] = '' and next if s.pre_match[-1, 1] == '\\' - start_pos = s.pos - 2 - key = s.scan_until(/\}\}/)[0..-3] - end_pos = s.pos - 1 + original_encoding = string.encoding + string.force_encoding(Encoding::BINARY) + end - raise ReservedInterpolationKey.new(key, string) if %w(scope default).include?(key) - raise MissingInterpolationArgument.new(key, string) unless values.has_key? key.to_sym + result = string.gsub(MATCH) do + escaped, pattern, key = $1, $2, $2.to_sym - s.string[start_pos..end_pos] = values[key.to_sym].to_s - s.unscan + if escaped + pattern + elsif INTERPOLATION_RESERVED_KEYS.include?(pattern) + raise ReservedInterpolationKey.new(pattern, string) + elsif !values.include?(key) + raise MissingInterpolationArgument.new(pattern, string) + else + values[key].to_s + end end - - result = s.string + result.force_encoding(original_encoding) if original_encoding result end diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/buffered_logger_test.rb index 6319c09210..28dd34334f 100644 --- a/activesupport/test/buffered_logger_test.rb +++ b/activesupport/test/buffered_logger_test.rb @@ -134,6 +134,7 @@ class BufferedLoggerTest < Test::Unit::TestCase a.join b.join - assert_equal "a\nb\nc\nx\ny\nz\n", @output.string + assert @output.string.include?("a\nb\nc\n") + assert @output.string.include?("x\ny\nz\n") end end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index fc8ed45358..7a414e946f 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -62,7 +62,7 @@ class HashExtTest < Test::Unit::TestCase @symbols = @symbols.with_indifferent_access @mixed = @mixed.with_indifferent_access - assert_equal 'a', @strings.send!(:convert_key, :a) + assert_equal 'a', @strings.__send__(:convert_key, :a) assert_equal 1, @strings.fetch('a') assert_equal 1, @strings.fetch(:a.to_s) @@ -75,9 +75,9 @@ class HashExtTest < Test::Unit::TestCase hashes.each do |name, hash| method_map.sort_by { |m| m.to_s }.each do |meth, expected| - assert_equal(expected, hash.send!(meth, 'a'), + assert_equal(expected, hash.__send__(meth, 'a'), "Calling #{name}.#{meth} 'a'") - assert_equal(expected, hash.send!(meth, :a), + assert_equal(expected, hash.__send__(meth, :a), "Calling #{name}.#{meth} :a") end end @@ -733,7 +733,7 @@ class HashToXmlTest < Test::Unit::TestCase def test_empty_string_works_for_typecast_xml_value assert_nothing_raised do - Hash.send!(:typecast_xml_value, "") + Hash.__send__(:typecast_xml_value, "") end end @@ -839,6 +839,27 @@ class QueryTest < Test::Unit::TestCase :person => {:id => [20, 10]} end + def test_expansion_count_is_limited + assert_raises RuntimeError do + attack_xml = <<-EOT + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE member [ + <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> + <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"> + <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;"> + <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;"> + <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;"> + <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;"> + <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> + ]> + <member> + &a; + </member> + EOT + Hash.from_xml(attack_xml) + end + end + private def assert_query_equal(expected, actual, message = nil) assert_equal expected.split('&'), actual.to_query.split('&') diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb index b0a746fdc7..e88dcb52d5 100644 --- a/activesupport/test/core_ext/object_and_class_ext_test.rb +++ b/activesupport/test/core_ext/object_and_class_ext_test.rb @@ -108,11 +108,6 @@ class ClassExtTest < Test::Unit::TestCase end class ObjectTests < Test::Unit::TestCase - def test_send_bang_aliases_send_before_19 - assert_respond_to 'a', :send! - assert_equal 1, 'a'.send!(:size) - end - def test_suppress_re_raises assert_raises(LoadError) { suppress(ArgumentError) {raise LoadError} } end diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 39c9c74c94..18ad784837 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -146,42 +146,42 @@ class DependenciesTest < Test::Unit::TestCase def test_directories_manifest_as_modules_unless_const_defined with_loading 'autoloading_fixtures' do assert_kind_of Module, ModuleFolder - Object.send! :remove_const, :ModuleFolder + Object.__send__ :remove_const, :ModuleFolder end end def test_module_with_nested_class with_loading 'autoloading_fixtures' do assert_kind_of Class, ModuleFolder::NestedClass - Object.send! :remove_const, :ModuleFolder + Object.__send__ :remove_const, :ModuleFolder end end def test_module_with_nested_inline_class with_loading 'autoloading_fixtures' do assert_kind_of Class, ModuleFolder::InlineClass - Object.send! :remove_const, :ModuleFolder + Object.__send__ :remove_const, :ModuleFolder end end def test_directories_may_manifest_as_nested_classes with_loading 'autoloading_fixtures' do assert_kind_of Class, ClassFolder - Object.send! :remove_const, :ClassFolder + Object.__send__ :remove_const, :ClassFolder end end def test_class_with_nested_class with_loading 'autoloading_fixtures' do assert_kind_of Class, ClassFolder::NestedClass - Object.send! :remove_const, :ClassFolder + Object.__send__ :remove_const, :ClassFolder end end def test_class_with_nested_inline_class with_loading 'autoloading_fixtures' do assert_kind_of Class, ClassFolder::InlineClass - Object.send! :remove_const, :ClassFolder + Object.__send__ :remove_const, :ClassFolder end end @@ -190,7 +190,7 @@ class DependenciesTest < Test::Unit::TestCase assert_kind_of Class, ClassFolder::ClassFolderSubclass assert_kind_of Class, ClassFolder assert_equal 'indeed', ClassFolder::ClassFolderSubclass::ConstantInClassFolder - Object.send! :remove_const, :ClassFolder + Object.__send__ :remove_const, :ClassFolder end end @@ -199,7 +199,7 @@ class DependenciesTest < Test::Unit::TestCase sibling = ModuleFolder::NestedClass.class_eval "NestedSibling" assert defined?(ModuleFolder::NestedSibling) assert_equal ModuleFolder::NestedSibling, sibling - Object.send! :remove_const, :ModuleFolder + Object.__send__ :remove_const, :ModuleFolder end end @@ -208,7 +208,7 @@ class DependenciesTest < Test::Unit::TestCase assert ! defined?(ModuleFolder) assert_raises(NameError) { ModuleFolder::Object } assert_raises(NameError) { ModuleFolder::NestedClass::Object } - Object.send! :remove_const, :ModuleFolder + Object.__send__ :remove_const, :ModuleFolder end end diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb index 4c1f96d505..74d2daa34b 100644 --- a/railties/lib/initializer.rb +++ b/railties/lib/initializer.rb @@ -488,7 +488,7 @@ Run `rake gems:install` to install the missing gems. # If assigned value cannot be matched to a TimeZone, an exception will be raised. def initialize_time_zone if configuration.time_zone - zone_default = Time.send!(:get_zone, configuration.time_zone) + zone_default = Time.__send__(:get_zone, configuration.time_zone) unless zone_default raise %{Value assigned to config.time_zone not recognized. Run "rake -D time" for a list of tasks for finding appropriate time zone names.} end diff --git a/railties/lib/tasks/documentation.rake b/railties/lib/tasks/documentation.rake index f4927a0ef1..4ef838626a 100644 --- a/railties/lib/tasks/documentation.rake +++ b/railties/lib/tasks/documentation.rake @@ -1,9 +1,9 @@ namespace :doc do - desc "Generate documentation for the application. Set custom template with TEMPLATE=/path/to/rdoc/template.rb" + desc "Generate documentation for the application. Set custom template with TEMPLATE=/path/to/rdoc/template.rb or title with TITLE=\"Custom Title\"" Rake::RDocTask.new("app") { |rdoc| rdoc.rdoc_dir = 'doc/app' rdoc.template = ENV['template'] if ENV['template'] - rdoc.title = "Rails Application Documentation" + rdoc.title = ENV['title'] || "Rails Application Documentation" rdoc.options << '--line-numbers' << '--inline-source' rdoc.options << '--charset' << 'utf-8' rdoc.rdoc_files.include('doc/README_FOR_APP') |