Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' into nested_has_many_through

Conflicts:
	activerecord/CHANGELOG
	activerecord/lib/active_record/associations.rb
  • Loading branch information...
commit 3a7f43ca6ecf1735e1a82d4a68ac8f62b5cf2fcf 2 parents 1bc9004 + fd83f9d
Jon Leighton jonleighton authored
Showing with 3,057 additions and 1,857 deletions.
  1. +2 −2 Rakefile
  2. +1 −1  actionmailer/Rakefile
  3. +3 −3 actionmailer/lib/action_mailer/base.rb
  4. +5 −2 actionmailer/lib/action_mailer/test_case.rb
  5. +2 −0  actionpack/CHANGELOG
  6. +1 −1  actionpack/Rakefile
  7. +1 −1  actionpack/actionpack.gemspec
  8. +4 −4 actionpack/lib/abstract_controller/rendering.rb
  9. +2 −1  actionpack/lib/action_controller/metal/helpers.rb
  10. +4 −4 actionpack/lib/action_controller/metal/mime_responds.rb
  11. +11 −27 actionpack/lib/action_controller/metal/renderers.rb
  12. +23 −24 actionpack/lib/action_controller/metal/rendering.rb
  13. +7 −3 actionpack/lib/action_controller/metal/request_forgery_protection.rb
  14. +3 −1 actionpack/lib/action_controller/metal/url_for.rb
  15. +3 −1 actionpack/lib/action_controller/railties/paths.rb
  16. +4 −2 actionpack/lib/action_controller/test_case.rb
  17. +2 −2 actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
  18. +4 −2 actionpack/lib/action_dispatch/http/mime_negotiation.rb
  19. +35 −2 actionpack/lib/action_dispatch/http/mime_type.rb
  20. +8 −2 actionpack/lib/action_dispatch/http/upload.rb
  21. +42 −18 actionpack/lib/action_dispatch/http/url.rb
  22. +10 −3 actionpack/lib/action_dispatch/middleware/cookies.rb
  23. +85 −54 actionpack/lib/action_dispatch/routing/mapper.rb
  24. +24 −6 actionpack/lib/action_dispatch/routing/route_set.rb
  25. +7 −0 actionpack/lib/action_dispatch/routing/url_for.rb
  26. +4 −0 actionpack/lib/action_dispatch/testing/integration.rb
  27. +3 −4 actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
  28. +2 −2 actionpack/lib/action_view/helpers/form_helper.rb
  29. +5 −0 actionpack/lib/action_view/partials.rb
  30. +7 −1 actionpack/lib/action_view/template.rb
  31. +0 −8 actionpack/test/abstract_unit.rb
  32. +12 −0 actionpack/test/activerecord/active_record_store_test.rb
  33. +1 −1  actionpack/test/controller/action_pack_assertions_test.rb
  34. +72 −2 actionpack/test/controller/mime_responds_test.rb
  35. +10 −10 actionpack/test/controller/new_base/render_template_test.rb
  36. +3 −3 actionpack/test/controller/routing_test.rb
  37. +22 −0 actionpack/test/controller/runner_test.rb
  38. +22 −1 actionpack/test/controller/url_for_test.rb
  39. +57 −2 actionpack/test/dispatch/mime_type_test.rb
  40. +11 −3 actionpack/test/dispatch/request_test.rb
  41. +11 −0 actionpack/test/dispatch/routing_test.rb
  42. +12 −0 actionpack/test/dispatch/uploaded_file_test.rb
  43. +1 −0  actionpack/test/fixtures/star_star_mime/index.js.erb
  44. +6 −0 actionpack/test/lib/controller/fake_models.rb
  45. +44 −0 actionpack/test/template/asset_tag_helper_test.rb
  46. +27 −2 actionpack/test/template/lookup_context_test.rb
  47. +9 −0 actionpack/test/template/render_test.rb
  48. 0  activemodel/Rakefile
  49. +1 −1  activemodel/activemodel.gemspec
  50. +0 −6 activemodel/test/cases/helper.rb
  51. +38 −0 activerecord/CHANGELOG
  52. +1 −1  activerecord/Rakefile
  53. +21 −5 activerecord/lib/active_record/association_preload.rb
  54. +8 −552 activerecord/lib/active_record/associations.rb
  55. +8 −9 activerecord/lib/active_record/associations/association_collection.rb
  56. +1 −1  activerecord/lib/active_record/associations/belongs_to_association.rb
  57. +214 −0 activerecord/lib/active_record/associations/class_methods/join_dependency.rb
  58. +261 −0 activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb
  59. +34 −0 activerecord/lib/active_record/associations/class_methods/join_dependency/join_base.rb
  60. +80 −0 activerecord/lib/active_record/associations/class_methods/join_dependency/join_part.rb
  61. +2 −2 activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
  62. +2 −7 activerecord/lib/active_record/associations/has_many_association.rb
  63. +1 −1  activerecord/lib/active_record/associations/through_association_scope.rb
  64. +2 −1  activerecord/lib/active_record/attribute_methods/dirty.rb
  65. +4 −2 activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
  66. +1 −1  activerecord/lib/active_record/autosave_association.rb
  67. +61 −48 activerecord/lib/active_record/base.rb
  68. +1 −0  activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
  69. +1 −2  activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
  70. +1 −2  activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
  71. +5 −7 activerecord/lib/active_record/locking/optimistic.rb
  72. +173 −120 activerecord/lib/active_record/migration.rb
  73. +91 −0 activerecord/lib/active_record/migration/command_recorder.rb
  74. +6 −4 activerecord/lib/active_record/named_scope.rb
  75. +13 −13 activerecord/lib/active_record/nested_attributes.rb
  76. +9 −11 activerecord/lib/active_record/reflection.rb
  77. +1 −10 activerecord/lib/active_record/relation/finder_methods.rb
  78. +1 −1  activerecord/lib/active_record/relation/predicate_builder.rb
  79. +45 −13 activerecord/lib/active_record/relation/query_methods.rb
  80. +4 −5 activerecord/lib/active_record/schema.rb
  81. +5 −3 activerecord/lib/active_record/timestamp.rb
  82. +2 −5 activerecord/lib/active_record/validations/uniqueness.rb
  83. +2 −2 activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
  84. +2 −2 activerecord/lib/rails/generators/active_record/model/templates/migration.rb
  85. +2 −2 activerecord/lib/rails/generators/active_record/session_migration/templates/migration.rb
  86. +52 −0 activerecord/test/cases/associations/eager_test.rb
  87. +1 −2  activerecord/test/cases/associations/has_many_associations_test.rb
  88. +0 −1  activerecord/test/cases/associations/has_many_through_associations_test.rb
  89. +3 −4 activerecord/test/cases/associations/has_one_associations_test.rb
  90. +0 −2  activerecord/test/cases/associations/inverse_associations_test.rb
  91. +1 −1  activerecord/test/cases/associations/join_model_test.rb
  92. +4 −4 activerecord/test/cases/associations_test.rb
  93. +4 −4 activerecord/test/cases/autosave_association_test.rb
  94. +38 −41 activerecord/test/cases/base_test.rb
  95. +33 −0 activerecord/test/cases/clone_test.rb
  96. +4 −4 activerecord/test/cases/dirty_test.rb
  97. +103 −0 activerecord/test/cases/dup_test.rb
  98. +0 −5 activerecord/test/cases/helper.rb
  99. +1 −1  activerecord/test/cases/inheritance_test.rb
  100. +57 −0 activerecord/test/cases/invertible_migration_test.rb
  101. +1 −0  activerecord/test/cases/locking_test.rb
  102. +108 −0 activerecord/test/cases/migration/command_recorder_test.rb
  103. +47 −8 activerecord/test/cases/migration_test.rb
  104. +14 −1 activerecord/test/cases/nested_attributes_test.rb
  105. +29 −1 activerecord/test/cases/relations_test.rb
  106. +8 −0 activerecord/test/models/pet.rb
  107. +1 −1  activeresource/Rakefile
  108. +5 −3 activeresource/lib/active_resource/base.rb
  109. +3 −3 activeresource/test/cases/base_test.rb
  110. 0  activesupport/Rakefile
  111. +13 −9 activesupport/lib/active_support/callbacks.rb
  112. +9 −83 activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
  113. +1 −0  activesupport/lib/active_support/core_ext/module/synchronization.rb
  114. +1 −3 activesupport/lib/active_support/core_ext/object/to_param.rb
  115. +2 −2 activesupport/lib/active_support/core_ext/object/to_query.rb
  116. +12 −10 activesupport/lib/active_support/core_ext/string/output_safety.rb
  117. +3 −4 activesupport/lib/active_support/hash_with_indifferent_access.rb
  118. +0 −1  activesupport/lib/active_support/i18n.rb
  119. +5 −0 activesupport/test/core_ext/class/class_inheritable_attributes_test.rb
  120. +8 −0 activesupport/test/core_ext/hash_ext_test.rb
  121. +1 −0  activesupport/test/core_ext/module/synchronization_test.rb
  122. +4 −4 activesupport/test/core_ext/object/to_query_test.rb
  123. +0 −69 activesupport/test/core_ext/string_ext_test.rb
  124. +1 −1  railties/Rakefile
  125. +1 −1  railties/guides/assets/stylesheets/main.css
  126. +2 −7 railties/guides/rails_guides/helpers.rb
  127. +2 −2 railties/guides/source/3_0_release_notes.textile
  128. +0 −2  railties/guides/source/action_controller_overview.textile
  129. +0 −2  railties/guides/source/action_mailer_basics.textile
  130. +0 −2  railties/guides/source/action_view_overview.textile
  131. +0 −2  railties/guides/source/active_record_querying.textile
  132. +12 −14 railties/guides/source/active_record_validations_callbacks.textile
  133. +6 −2 railties/guides/source/active_support_core_extensions.textile
  134. +0 −2  railties/guides/source/association_basics.textile
  135. +0 −1  railties/guides/source/caching_with_rails.textile
  136. +0 −3  railties/guides/source/command_line.textile
  137. +106 −48 railties/guides/source/configuring.textile
  138. +1 −1  railties/guides/source/contribute.textile
  139. +8 −5 railties/guides/source/contributing_to_rails.textile
  140. +0 −2  railties/guides/source/debugging_rails_applications.textile
  141. +0 −2  railties/guides/source/form_helpers.textile
  142. +2 −4 railties/guides/source/generators.textile
  143. +0 −2  railties/guides/source/getting_started.textile
  144. +36 −41 railties/guides/source/i18n.textile
  145. +7 −7 railties/guides/source/index.html.erb
  146. +25 −0 railties/guides/source/layout.html.erb
  147. +0 −2  railties/guides/source/layouts_and_rendering.textile
  148. +1 −3 railties/guides/source/migrations.textile
  149. +0 −2  railties/guides/source/performance_testing.textile
  150. +0 −2  railties/guides/source/plugins.textile
  151. +0 −2  railties/guides/source/rails_application_templates.textile
  152. +0 −2  railties/guides/source/rails_on_rack.textile
  153. +1 −3 railties/guides/source/routing.textile
  154. +1 −3 railties/guides/source/security.textile
  155. +1 −3 railties/guides/source/testing.textile
  156. +1 −4 railties/lib/rails/commands.rb
  157. +4 −2 railties/lib/rails/engine.rb
  158. +2 −0  railties/lib/rails/generators.rb
  159. +1 −1  railties/lib/rails/generators/rails/app/templates/Rakefile
  160. +407 −326 railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js
  161. +1 −1  railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt
  162. +1 −3 railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
  163. +2 −2 railties/lib/rails/railtie.rb
  164. +0 −11 railties/lib/rails/test_help.rb
  165. +1 −8 railties/test/application/generators_test.rb
  166. +60 −0 railties/test/application/initializers/frameworks_test.rb
  167. +16 −0 railties/test/application/rake_test.rb
  168. +6 −6 railties/test/generators/migration_generator_test.rb
  169. +3 −3 railties/test/generators/model_generator_test.rb
  170. +6 −0 railties/test/generators_test.rb
  171. +19 −0 railties/test/railties/engine_test.rb
4 Rakefile 100644 → 100755
View
@@ -1,7 +1,7 @@
+#!/usr/bin/env rake
gem 'rdoc', '>= 2.5.10'
require 'rdoc'
-require 'rake'
require 'rdoc/task'
require 'net/http'
@@ -172,7 +172,7 @@ end
#
# Everything is automated and you do NOT need to run this task normally.
#
-# We publish a new verion by tagging, and pushing a tag does not trigger
+# We publish a new version by tagging, and pushing a tag does not trigger
# that webhook. Stable docs would be updated by any subsequent regular
# push, but if you want that to happen right away just run this.
#
2  actionmailer/Rakefile 100644 → 100755
View
@@ -1,4 +1,4 @@
-require 'rake'
+#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rake/gempackagetask'
6 actionmailer/lib/action_mailer/base.rb
View
@@ -234,8 +234,8 @@ module ActionMailer #:nodoc:
# default :sender => 'system@example.com'
# end
#
- # You can pass in any header value that a <tt>Mail::Message</tt>, out of the box, <tt>ActionMailer::Base</tt>
- # sets the following:
+ # You can pass in any header value that a <tt>Mail::Message</tt> accepts. Out of the box,
+ # <tt>ActionMailer::Base</tt> sets the following:
#
# * <tt>:mime_version => "1.0"</tt>
# * <tt>:charset => "UTF-8",</tt>
@@ -273,7 +273,7 @@ module ActionMailer #:nodoc:
# = Configuration options
#
# These options are specified on the class level, like
- # <tt>ActionMailer::Base.template_root = "/my/templates"</tt>
+ # <tt>ActionMailer::Base.raise_delivery_errors = true</tt>
#
# * <tt>default</tt> - You can pass this in at a class level as well as within the class itself as
# per the above section.
7 actionmailer/lib/action_mailer/test_case.rb
View
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/class/attribute'
+
module ActionMailer
class NonInferrableMailerError < ::StandardError
def initialize(name)
@@ -15,11 +17,11 @@ module Behavior
module ClassMethods
def tests(mailer)
- write_inheritable_attribute(:mailer_class, mailer)
+ self._mailer_class = mailer
end
def mailer_class
- if mailer = read_inheritable_attribute(:mailer_class)
+ if mailer = self._mailer_class
mailer
else
tests determine_default_mailer(name)
@@ -65,6 +67,7 @@ def read_fixture(action)
end
included do
+ class_attribute :_mailer_class
setup :initialize_test_deliveries
setup :set_expected_mail
end
2  actionpack/CHANGELOG
View
@@ -1,5 +1,7 @@
*Rails 3.1.0 (unreleased)*
+* Added config.action_controller.include_all_helpers. By default 'helper :all' is done in ActionController::Base, which includes all the helpers by default. Setting include_all_helpers to false will result in including only application_helper and helper corresponding to controller (like foo_helper for foo_controller). [Piotr Sarnacki]
+
* Added a convenience idiom to generate HTML5 data-* attributes in tag helpers from a :data hash of options:
tag("div", :data => {:name => 'Stephen', :city_state => %w(Chicago IL)})
2  actionpack/Rakefile 100644 → 100755
View
@@ -1,4 +1,4 @@
-require 'rake'
+#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rake/gempackagetask'
2  actionpack/actionpack.gemspec
View
@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
s.add_dependency('rack-cache', '~> 0.5.3')
- s.add_dependency('builder', '~> 2.1.2')
+ s.add_dependency('builder', '~> 3.0.0')
s.add_dependency('i18n', '~> 0.4.2')
s.add_dependency('rack', '~> 1.2.1')
s.add_dependency('rack-test', '~> 0.5.6')
8 actionpack/lib/abstract_controller/rendering.rb
View
@@ -119,7 +119,7 @@ def _prefix
controller_path
end
- private
+ private
# This method should return a hash with assigns.
# You can overwrite this configuration per controller.
@@ -128,7 +128,7 @@ def view_assigns
hash = {}
variables = instance_variable_names
variables -= protected_instance_variables if respond_to?(:protected_instance_variables)
- variables.each { |name| hash[name.to_s[1..-1]] = instance_variable_get(name) }
+ variables.each { |name| hash[name.to_s[1, name.length]] = instance_variable_get(name) }
hash
end
@@ -138,13 +138,13 @@ def _normalize_args(action=nil, options={})
case action
when NilClass
when Hash
- options, action = action, nil
+ options = action
when String, Symbol
action = action.to_s
key = action.include?(?/) ? :file : :action
options[key] = action
else
- options.merge!(:partial => action)
+ options[:partial] = action
end
options
3  actionpack/lib/action_controller/metal/helpers.rb
View
@@ -53,8 +53,9 @@ module Helpers
include AbstractController::Helpers
included do
- config_accessor :helpers_path
+ config_accessor :helpers_path, :include_all_helpers
self.helpers_path ||= []
+ self.include_all_helpers = true
end
module ClassMethods
8 actionpack/lib/action_controller/metal/mime_responds.rb
View
@@ -258,9 +258,8 @@ def collect_mimes_from_class_level #:nodoc:
# nil if :not_acceptable was sent to the client.
#
def retrieve_response_from_mimes(mimes=nil, &block)
- collector = Collector.new { default_render }
mimes ||= collect_mimes_from_class_level
- mimes.each { |mime| collector.send(mime) }
+ collector = Collector.new(mimes) { default_render }
block.call(collector) if block_given?
if format = request.negotiate_mime(collector.order)
@@ -277,8 +276,9 @@ class Collector #:nodoc:
include AbstractController::Collector
attr_accessor :order
- def initialize(&block)
+ def initialize(mimes, &block)
@order, @responses, @default_response = [], {}, block
+ mimes.each { |mime| send(mime) }
end
def any(*args, &block)
@@ -291,7 +291,7 @@ def any(*args, &block)
alias :all :any
def custom(mime_type, &block)
- mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)
+ mime_type = Mime::Type.lookup(mime_type.to_s) unless mime_type.is_a?(Mime::Type)
@order << mime_type
@responses[mime_type] ||= block
end
38 actionpack/lib/action_controller/metal/renderers.rb
View
@@ -15,30 +15,12 @@ module Renderers
end
module ClassMethods
- def _write_render_options
- renderers = _renderers.map do |name, value|
- <<-RUBY_EVAL
- if options.key?(:#{name})
- _process_options(options)
- return _render_option_#{name}(options.delete(:#{name}), options)
- end
- RUBY_EVAL
- end
-
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def _handle_render_options(options)
- #{renderers.join}
- end
- RUBY_EVAL
- end
-
def use_renderers(*args)
new = _renderers.dup
args.each do |key|
new[key] = RENDERERS[key]
end
self._renderers = new.freeze
- _write_render_options
end
alias use_renderer use_renderers
end
@@ -47,31 +29,33 @@ def render_to_body(options)
_handle_render_options(options) || super
end
+ def _handle_render_options(options)
+ _renderers.each do |name, value|
+ if options.key?(name.to_sym)
+ _process_options(options)
+ return send("_render_option_#{name}", options.delete(name.to_sym), options)
+ end
+ end
+ nil
+ end
+
RENDERERS = {}
def self.add(key, &block)
define_method("_render_option_#{key}", &block)
RENDERERS[key] = block
- All._write_render_options
end
module All
extend ActiveSupport::Concern
include Renderers
- INCLUDED = []
included do
self._renderers = RENDERERS
- _write_render_options
- INCLUDED << self
- end
-
- def self._write_render_options
- INCLUDED.each(&:_write_render_options)
end
end
add :json do |json, options|
- json = json.to_json(options) unless json.respond_to?(:to_str)
+ json = json.to_json(options) unless json.kind_of?(String)
json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
self.content_type ||= Mime::JSON
self.response_body = json
47 actionpack/lib/action_controller/metal/rendering.rb
View
@@ -20,36 +20,35 @@ def render(*args) #:nodoc:
private
- # Normalize arguments by catching blocks and setting them on :update.
- def _normalize_args(action=nil, options={}, &blk) #:nodoc:
- options = super
- options[:update] = blk if block_given?
- options
- end
-
- # Normalize both text and status options.
- def _normalize_options(options) #:nodoc:
- if options.key?(:text) && options[:text].respond_to?(:to_text)
- options[:text] = options[:text].to_text
- end
+ # Normalize arguments by catching blocks and setting them on :update.
+ def _normalize_args(action=nil, options={}, &blk) #:nodoc:
+ options = super
+ options[:update] = blk if block_given?
+ options
+ end
- if options[:status]
- options[:status] = Rack::Utils.status_code(options[:status])
- end
+ # Normalize both text and status options.
+ def _normalize_options(options) #:nodoc:
+ if options.key?(:text) && options[:text].respond_to?(:to_text)
+ options[:text] = options[:text].to_text
+ end
- super
+ if options[:status]
+ options[:status] = Rack::Utils.status_code(options[:status])
end
- # Process controller specific options, as status, content-type and location.
- def _process_options(options) #:nodoc:
- status, content_type, location = options.values_at(:status, :content_type, :location)
+ super
+ end
- self.status = status if status
- self.content_type = content_type if content_type
- self.headers["Location"] = url_for(location) if location
+ # Process controller specific options, as status, content-type and location.
+ def _process_options(options) #:nodoc:
+ status, content_type, location = options.values_at(:status, :content_type, :location)
- super
- end
+ self.status = status if status
+ self.content_type = content_type if content_type
+ self.headers["Location"] = url_for(location) if location
+ super
+ end
end
end
10 actionpack/lib/action_controller/metal/request_forgery_protection.rb
View
@@ -53,9 +53,13 @@ module ClassMethods
# class FooController < ApplicationController
# protect_from_forgery :except => :index
#
- # # you can disable csrf protection on controller-by-controller basis:
- # skip_before_filter :verify_authenticity_token
- # end
+ # You can disable csrf protection on controller-by-controller basis:
+ #
+ # skip_before_filter :verify_authenticity_token
+ #
+ # It can also be disabled for specific controller actions:
+ #
+ # skip_before_filter :verify_authenticity_token, :except => [:create]
#
# Valid Options:
#
4 actionpack/lib/action_controller/metal/url_for.rb
View
@@ -6,7 +6,8 @@ module UrlFor
def url_options
@_url_options ||= super.reverse_merge(
- :host => request.host_with_port,
+ :host => request.host,
+ :port => request.optional_port,
:protocol => request.protocol,
:_path_segments => request.symbolized_path_parameters
).freeze
@@ -20,5 +21,6 @@ def url_options
@_url_options
end
end
+
end
end
4 actionpack/lib/action_controller/railties/paths.rb
View
@@ -13,7 +13,9 @@ def self.with(app)
end
klass.helpers_path = paths
- klass.helper :all if klass.superclass == ActionController::Base
+ if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
+ klass.helper :all
+ end
end
end
end
6 actionpack/lib/action_controller/test_case.rb
View
@@ -1,6 +1,7 @@
require 'rack/session/abstract/id'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/to_query'
+require 'active_support/core_ext/class/attribute'
module ActionController
module TemplateAssertions
@@ -325,11 +326,11 @@ def tests(controller_class)
def controller_class=(new_class)
prepare_controller_class(new_class) if new_class
- write_inheritable_attribute(:controller_class, new_class)
+ self._controller_class = new_class
end
def controller_class
- if current_controller_class = read_inheritable_attribute(:controller_class)
+ if current_controller_class = self._controller_class
current_controller_class
else
self.controller_class = determine_default_controller_class(name)
@@ -442,6 +443,7 @@ def rescue_action_in_public!
included do
include ActionController::TemplateAssertions
include ActionDispatch::Assertions
+ class_attribute :_controller_class
setup :setup_controller_request_and_response
end
4 actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
View
@@ -1,5 +1,5 @@
require 'set'
-require 'active_support/core_ext/class/inheritable_attributes'
+require 'active_support/core_ext/class/attribute'
module HTML
class Sanitizer
@@ -60,7 +60,7 @@ def process_node(node, result, options)
class WhiteListSanitizer < Sanitizer
[:protocol_separator, :uri_attributes, :allowed_attributes, :allowed_tags, :allowed_protocols, :bad_tags,
:allowed_css_properties, :allowed_css_keywords, :shorthand_css_properties].each do |attr|
- class_inheritable_accessor attr, :instance_writer => false
+ class_attribute attr, :instance_writer => false
end
# A regular expression of the valid characters used to separate protocols like
6 actionpack/lib/action_dispatch/http/mime_negotiation.rb
View
@@ -36,19 +36,21 @@ def accepts
#
# GET /posts/5.xml | request.format => Mime::XML
# GET /posts/5.xhtml | request.format => Mime::HTML
- # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
+ # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first
#
def format(view_path = [])
formats.first
end
+ BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
+
def formats
accept = @env['HTTP_ACCEPT']
@env["action_dispatch.request.formats"] ||=
if parameters[:format]
Array(Mime[parameters[:format]])
- elsif xhr? || (accept && accept !~ /,\s*\*\/\*/)
+ elsif xhr? || (accept && accept !~ BROWSER_LIKE_ACCEPTS)
accepts
else
[Mime::HTML]
37 actionpack/lib/action_dispatch/http/mime_type.rb
View
@@ -80,6 +80,9 @@ def ==(item)
end
class << self
+
+ TRAILING_STAR_REGEXP = /(text|application)\/\*/
+
def lookup(string)
LOOKUP[string]
end
@@ -105,7 +108,11 @@ def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], s
def parse(accept_header)
if accept_header !~ /,/
- [Mime::Type.lookup(accept_header)]
+ if accept_header =~ TRAILING_STAR_REGEXP
+ parse_data_with_trailing_star($1)
+ else
+ [Mime::Type.lookup(accept_header)]
+ end
else
# keep track of creation order to keep the subsequent sort stable
list = []
@@ -113,7 +120,11 @@ def parse(accept_header)
params, q = header.split(/;\s*q=/)
if params
params.strip!
- list << AcceptItem.new(index, params, q) unless params.empty?
+ if params =~ TRAILING_STAR_REGEXP
+ parse_data_with_trailing_star($1).each { |m| list << AcceptItem.new(index, m.to_s, q) }
+ else
+ list << AcceptItem.new(index, params, q) unless params.empty?
+ end
end
end
list.sort!
@@ -160,6 +171,28 @@ def parse(accept_header)
list
end
end
+
+ # input: 'text'
+ # returend value: [Mime::JSON, Mime::XML, Mime::ICS, Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]
+ #
+ # input: 'application'
+ # returend value: [Mime::HTML, Mime::JS, Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM
+ def parse_data_with_trailing_star(input)
+ keys = Mime::LOOKUP.keys.select{|k| k.include?(input)}
+ Mime::LOOKUP.values_at(*keys).uniq
+ end
+
+ # This method is opposite of register method.
+ #
+ # Usage:
+ #
+ # Mime::Type.unregister("text/x-mobile", :mobile)
+ def unregister(string, symbol)
+ EXTENSION_LOOKUP.delete(symbol.to_s)
+ LOOKUP.delete(string)
+ symbol = symbol.to_s.upcase.intern
+ Mime.module_eval { remove_const(symbol) if const_defined?(symbol) }
+ end
end
def initialize(string, symbol = nil, synonyms = [])
10 actionpack/lib/action_dispatch/http/upload.rb
View
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/object/blank'
-
module ActionDispatch
module Http
class UploadedFile
@@ -13,6 +11,14 @@ def initialize(hash)
raise(ArgumentError, ':tempfile is required') unless @tempfile
end
+ def open
+ @tempfile.open
+ end
+
+ def path
+ @tempfile.path
+ end
+
def read(*args)
@tempfile.read(*args)
end
60 actionpack/lib/action_dispatch/http/url.rb
View
@@ -4,6 +4,27 @@ module URL
mattr_accessor :tld_length
self.tld_length = 1
+ def self.extract_domain(host, tld_length = @@tld_length)
+ return nil unless named_host?(host)
+
+ host.split('.').last(1 + tld_length).join('.')
+ end
+
+ def self.extract_subdomains(host, tld_length = @@tld_length)
+ return [] unless named_host?(host)
+ parts = host.split('.')
+ parts[0..-(tld_length+2)]
+ end
+
+ def self.extract_subdomain(host, tld_length = @@tld_length)
+ extract_subdomains(host, tld_length).join('.')
+ end
+
+ def self.named_host?(host)
+ !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
+ end
+
+
# Returns the complete URL used for this request.
def url
protocol + host_with_port + fullpath
@@ -36,10 +57,12 @@ def host_with_port
# Returns the port number of this request as an integer.
def port
- if raw_host_with_port =~ /:(\d+)$/
- $1.to_i
- else
- standard_port
+ @port ||= begin
+ if raw_host_with_port =~ /:(\d+)$/
+ $1.to_i
+ else
+ standard_port
+ end
end
end
@@ -56,10 +79,16 @@ def standard_port?
port == standard_port
end
- # Returns a \port suffix like ":8080" if the \port number of this request
+ # Returns a number \port suffix like 8080 if the \port number of this request
# is not the default HTTP \port 80 or HTTPS \port 443.
+ def optional_port
+ standard_port? ? nil : port
+ end
+
+ # Returns a string \port suffix, including colon, like ":8080" if the \port
+ # number of this request is not the default HTTP \port 80 or HTTPS \port 443.
def port_string
- port == standard_port ? '' : ":#{port}"
+ standard_port? ? '' : ":#{port}"
end
def server_port
@@ -69,9 +98,7 @@ def server_port
# Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
# a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
def domain(tld_length = @@tld_length)
- return nil unless named_host?(host)
-
- host.split('.').last(1 + tld_length).join('.')
+ ActionDispatch::Http::URL.extract_domain(host, tld_length)
end
# Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
@@ -79,20 +106,17 @@ def domain(tld_length = @@tld_length)
# such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
# in "www.rubyonrails.co.uk".
def subdomains(tld_length = @@tld_length)
- return [] unless named_host?(host)
- parts = host.split('.')
- parts[0..-(tld_length+2)]
+ ActionDispatch::Http::URL.extract_subdomains(host, tld_length)
end
+ # Returns all the \subdomains as a string, so <tt>"dev.www"</tt> would be
+ # returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
+ # such as 2 to catch <tt>["www"]</tt> instead of <tt>"www.rubyonrails"</tt>
+ # in "www.rubyonrails.co.uk".
def subdomain(tld_length = @@tld_length)
- subdomains(tld_length).join('.')
+ subdomains(tld_length)
end
- private
-
- def named_host?(host)
- !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
- end
end
end
end
13 actionpack/lib/action_dispatch/middleware/cookies.rb
View
@@ -16,17 +16,23 @@ def cookie_jar
# Examples for writing:
#
# # Sets a simple session cookie.
+ # # This cookie will be deleted when the user's browser is closed.
# cookies[:user_name] = "david"
#
+ # # Assign an array of values to a cookie.
+ # cookies[:lat_lon] = [47.68, -122.37]
+ #
# # Sets a cookie that expires in 1 hour.
# cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
#
# # Sets a signed cookie, which prevents a user from tampering with its value.
- # # You must specify a value in ActionController::Base.cookie_verifier_secret.
- # cookies.signed[:remember_me] = [current_user.id, current_user.salt]
+ # # The cookie is signed by your app's <tt>config.secret_token</tt> value.
+ # # Rails generates this value by default when you create a new Rails app.
+ # cookies.signed[:user_id] = current_user.id
#
# # Sets a "permanent" cookie (which expires in 20 years from now).
# cookies.permanent[:login] = "XJ-122"
+ #
# # You can also chain these methods:
# cookies.permanent.signed[:login] = "XJ-122"
#
@@ -34,6 +40,7 @@ def cookie_jar
#
# cookies[:user_name] # => "david"
# cookies.size # => 2
+ # cookies[:lat_lon] # => [47.68, -122.37]
#
# Example for deleting:
#
@@ -275,7 +282,7 @@ def ensure_secret_secure(secret)
"integrity hash for cookie session data. Use " +
"config.secret_token = \"some secret phrase of at " +
"least #{SECRET_MIN_LENGTH} characters\"" +
- "in config/application.rb"
+ "in config/initializers/secret_token.rb"
end
if secret.length < SECRET_MIN_LENGTH
139 actionpack/lib/action_dispatch/routing/mapper.rb
View
@@ -439,13 +439,13 @@ def map_method(method, *args, &block)
# This will create a number of routes for each of the posts and comments
# controller. For Admin::PostsController, Rails will create:
#
- # GET /admin/photos
- # GET /admin/photos/new
- # POST /admin/photos
- # GET /admin/photos/1
- # GET /admin/photos/1/edit
- # PUT /admin/photos/1
- # DELETE /admin/photos/1
+ # GET /admin/posts
+ # GET /admin/posts/new
+ # POST /admin/posts
+ # GET /admin/posts/1
+ # GET /admin/posts/1/edit
+ # PUT /admin/posts/1
+ # DELETE /admin/posts/1
#
# If you want to route /posts (without the prefix /admin) to
# Admin::PostsController, you could use
@@ -473,21 +473,19 @@ def map_method(method, *args, &block)
# not use scope. In the last case, the following paths map to
# PostsController:
#
- # GET /admin/photos
- # GET /admin/photos/new
- # POST /admin/photos
- # GET /admin/photos/1
- # GET /admin/photos/1/edit
- # PUT /admin/photos/1
- # DELETE /admin/photos/1
+ # GET /admin/posts
+ # GET /admin/posts/new
+ # POST /admin/posts
+ # GET /admin/posts/1
+ # GET /admin/posts/1/edit
+ # PUT /admin/posts/1
+ # DELETE /admin/posts/1
module Scoping
def initialize(*args) #:nodoc:
@scope = {}
super
end
- # Used to route <tt>/photos</tt> (without the prefix <tt>/admin</tt>)
- # to Admin::PostsController:
# === Supported options
# [:module]
# If you want to route /posts (without the prefix /admin) to
@@ -588,13 +586,13 @@ def controller(controller, options={})
#
# This generates the following routes:
#
- # admin_posts GET /admin/posts(.:format) {:action=>"index", :controller=>"admin/posts"}
- # admin_posts POST /admin/posts(.:format) {:action=>"create", :controller=>"admin/posts"}
- # new_admin_post GET /admin/posts/new(.:format) {:action=>"new", :controller=>"admin/posts"}
- # edit_admin_post GET /admin/posts/:id/edit(.:format) {:action=>"edit", :controller=>"admin/posts"}
- # admin_post GET /admin/posts/:id(.:format) {:action=>"show", :controller=>"admin/posts"}
- # admin_post PUT /admin/posts/:id(.:format) {:action=>"update", :controller=>"admin/posts"}
- # admin_post DELETE /admin/posts/:id(.:format) {:action=>"destroy", :controller=>"admin/posts"}
+ # admin_posts GET /admin/posts(.:format) {:action=>"index", :controller=>"admin/posts"}
+ # admin_posts POST /admin/posts(.:format) {:action=>"create", :controller=>"admin/posts"}
+ # new_admin_post GET /admin/posts/new(.:format) {:action=>"new", :controller=>"admin/posts"}
+ # edit_admin_post GET /admin/posts/:id/edit(.:format) {:action=>"edit", :controller=>"admin/posts"}
+ # admin_post GET /admin/posts/:id(.:format) {:action=>"show", :controller=>"admin/posts"}
+ # admin_post PUT /admin/posts/:id(.:format) {:action=>"update", :controller=>"admin/posts"}
+ # admin_post DELETE /admin/posts/:id(.:format) {:action=>"destroy", :controller=>"admin/posts"}
# === Supported options
#
# The +:path+, +:as+, +:module+, +:shallow_path+ and +:shallow_prefix+ all default to the name of the namespace.
@@ -707,61 +705,61 @@ def defaults(defaults = {})
end
private
- def scope_options
+ def scope_options #:nodoc:
@scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
end
- def merge_path_scope(parent, child)
+ def merge_path_scope(parent, child) #:nodoc:
Mapper.normalize_path("#{parent}/#{child}")
end
- def merge_shallow_path_scope(parent, child)
+ def merge_shallow_path_scope(parent, child) #:nodoc:
Mapper.normalize_path("#{parent}/#{child}")
end
- def merge_as_scope(parent, child)
+ def merge_as_scope(parent, child) #:nodoc:
parent ? "#{parent}_#{child}" : child
end
- def merge_shallow_prefix_scope(parent, child)
+ def merge_shallow_prefix_scope(parent, child) #:nodoc:
parent ? "#{parent}_#{child}" : child
end
- def merge_module_scope(parent, child)
+ def merge_module_scope(parent, child) #:nodoc:
parent ? "#{parent}/#{child}" : child
end
- def merge_controller_scope(parent, child)
+ def merge_controller_scope(parent, child) #:nodoc:
child
end
- def merge_path_names_scope(parent, child)
+ def merge_path_names_scope(parent, child) #:nodoc:
merge_options_scope(parent, child)
end
- def merge_constraints_scope(parent, child)
+ def merge_constraints_scope(parent, child) #:nodoc:
merge_options_scope(parent, child)
end
- def merge_defaults_scope(parent, child)
+ def merge_defaults_scope(parent, child) #:nodoc:
merge_options_scope(parent, child)
end
- def merge_blocks_scope(parent, child)
+ def merge_blocks_scope(parent, child) #:nodoc:
merged = parent ? parent.dup : []
merged << child if child
merged
end
- def merge_options_scope(parent, child)
+ def merge_options_scope(parent, child) #:nodoc:
(parent || {}).except(*override_keys(child)).merge(child)
end
- def merge_shallow_scope(parent, child)
+ def merge_shallow_scope(parent, child) #:nodoc:
child ? true : false
end
- def override_keys(child)
+ def override_keys(child) #:nodoc:
child.key?(:only) || child.key?(:except) ? [:only, :except] : []
end
end
@@ -969,6 +967,22 @@ def resource(*resources, &block)
# GET /photos/:id/edit
# PUT /photos/:id
# DELETE /photos/:id
+ #
+ # Resources can also be nested infinitely by using this block syntax:
+ #
+ # resources :photos do
+ # resources :comments
+ # end
+ #
+ # This generates the following comments routes:
+ #
+ # GET /photos/:id/comments/new
+ # POST /photos/:id/comments
+ # GET /photos/:id/comments/:id
+ # GET /photos/:id/comments/:id/edit
+ # PUT /photos/:id/comments/:id
+ # DELETE /photos/:id/comments/:id
+ #
# === Supported options
# [:path_names]
# Allows you to change the paths of the seven default actions.
@@ -977,6 +991,21 @@ def resource(*resources, &block)
# resources :posts, :path_names => { :new => "brand_new" }
#
# The above example will now change /posts/new to /posts/brand_new
+ #
+ # [:module]
+ # Set the module where the controller can be found. Defaults to nothing.
+ #
+ # resources :posts, :module => "admin"
+ #
+ # All requests to the posts resources will now go to +Admin::PostsController+.
+ #
+ # [:path]
+ #
+ # Set a path prefix for this resource.
+ #
+ # resources :posts, :path => "admin"
+ #
+ # All actions for this resource will now be at +/admin/posts+.
def resources(*resources, &block)
options = resources.extract_options!
@@ -1167,7 +1196,7 @@ def parent_resource #:nodoc:
@scope[:scope_level_resource]
end
- def apply_common_behavior_for(method, resources, options, &block)
+ def apply_common_behavior_for(method, resources, options, &block) #:nodoc:
if resources.length > 1
resources.each { |r| send(method, r, options, &block) }
return true
@@ -1197,23 +1226,23 @@ def apply_common_behavior_for(method, resources, options, &block)
false
end
- def action_options?(options)
+ def action_options?(options) #:nodoc:
options[:only] || options[:except]
end
- def scope_action_options?
+ def scope_action_options? #:nodoc:
@scope[:options].is_a?(Hash) && (@scope[:options][:only] || @scope[:options][:except])
end
- def scope_action_options
+ def scope_action_options #:nodoc:
@scope[:options].slice(:only, :except)
end
- def resource_scope?
+ def resource_scope? #:nodoc:
[:resource, :resources].include?(@scope[:scope_level])
end
- def resource_method_scope?
+ def resource_method_scope? #:nodoc:
[:collection, :member, :new].include?(@scope[:scope_level])
end
@@ -1239,7 +1268,7 @@ def with_scope_level(kind, resource = parent_resource)
@scope[:scope_level_resource] = old_resource
end
- def resource_scope(resource)
+ def resource_scope(resource) #:nodoc:
with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do
scope(parent_resource.resource_scope) do
yield
@@ -1247,30 +1276,30 @@ def resource_scope(resource)
end
end
- def nested_options
+ def nested_options #:nodoc:
{}.tap do |options|
options[:as] = parent_resource.member_name
options[:constraints] = { "#{parent_resource.singular}_id".to_sym => id_constraint } if id_constraint?
end
end
- def id_constraint?
+ def id_constraint? #:nodoc:
@scope[:constraints] && @scope[:constraints][:id].is_a?(Regexp)
end
- def id_constraint
+ def id_constraint #:nodoc:
@scope[:constraints][:id]
end
- def canonical_action?(action, flag)
+ def canonical_action?(action, flag) #:nodoc:
flag && resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
end
- def shallow_scoping?
+ def shallow_scoping? #:nodoc:
shallow? && @scope[:scope_level] == :member
end
- def path_for_action(action, path)
+ def path_for_action(action, path) #:nodoc:
prefix = shallow_scoping? ?
"#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path]
@@ -1281,11 +1310,11 @@ def path_for_action(action, path)
end
end
- def action_path(name, path = nil)
+ def action_path(name, path = nil) #:nodoc:
path || @scope[:path_names][name.to_sym] || name.to_s
end
- def prefix_name_for_action(as, action)
+ def prefix_name_for_action(as, action) #:nodoc:
if as
as.to_s
elsif !canonical_action?(action, @scope[:scope_level])
@@ -1293,12 +1322,14 @@ def prefix_name_for_action(as, action)
end
end
- def name_for_action(as, action)
+ def name_for_action(as, action) #:nodoc:
prefix = prefix_name_for_action(as, action)
prefix = Mapper.normalize_name(prefix) if prefix
name_prefix = @scope[:as]
if parent_resource
+ return nil if as.nil? && action.nil?
+
collection_name = parent_resource.collection_name
member_name = parent_resource.member_name
end
@@ -1323,7 +1354,7 @@ def name_for_action(as, action)
end
end
- module Shorthand
+ module Shorthand #:nodoc:
def match(*args)
if args.size == 1 && args.last.is_a?(Hash)
options = args.pop
30 actionpack/lib/action_dispatch/routing/route_set.rb
View
@@ -485,7 +485,8 @@ def generate(options, recall = {}, extras = false)
Generator.new(options, recall, self, extras).generate
end
- RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :script_name]
+ RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
+ :trailing_slash, :script_name, :anchor, :params, :only_path ]
def _generate_prefix(options = {})
nil
@@ -504,11 +505,8 @@ def url_for(options)
rewritten_url << (options[:protocol] || "http")
rewritten_url << "://" unless rewritten_url.match("://")
rewritten_url << rewrite_authentication(options)
-
- raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
-
- rewritten_url << options[:host]
- rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
+ rewritten_url << host_from_options(options)
+ rewritten_url << ":#{options.delete(:port)}" if options[:port]
end
script_name = options.delete(:script_name)
@@ -562,6 +560,26 @@ def recognize_path(path, environment = {})
end
private
+
+ def host_from_options(options)
+ computed_host = subdomain_and_domain(options) || options[:host]
+ unless computed_host
+ raise ArgumentError, "Missing host to link to! Please provide :host parameter or set default_url_options[:host]"
+ end
+ computed_host
+ end
+
+ def subdomain_and_domain(options)
+ return nil unless options[:subdomain] || options[:domain]
+ tld_length = options[:tld_length] || ActionDispatch::Http::URL.tld_length
+
+ host = ""
+ host << (options[:subdomain] || ActionDispatch::Http::URL.extract_subdomain(options[:host], tld_length))
+ host << "."
+ host << (options[:domain] || ActionDispatch::Http::URL.extract_domain(options[:host], tld_length))
+ host
+ end
+
def handle_positional_args(options)
return unless args = options.delete(:_positional_args)
7 actionpack/lib/action_dispatch/routing/url_for.rb
View
@@ -115,6 +115,13 @@ def url_options
# * <tt>:host</tt> - Specifies the host the link should be targeted at.
# If <tt>:only_path</tt> is false, this option must be
# provided either explicitly, or via +default_url_options+.
+ # * <tt>:subdomain</tt> - Specifies the subdomain of the link, using the +tld_length+
+ # to split the domain from the host.
+ # * <tt>:domain</tt> - Specifies the domain of the link, using the +tld_length+
+ # to split the subdomain from the host.
+ # * <tt>:tld_length</tt> - Number of labels the TLD id composed of, only used if
+ # <tt>:subdomain</tt> or <tt>:domain</tt> are supplied. Defaults to
+ # <tt>ActionDispatch::Http::URL.tld_length</tt>, which in turn defaults to 1.
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
4 actionpack/lib/action_dispatch/testing/integration.rb
View
@@ -363,6 +363,10 @@ def url_options
integration_session.url_options
end
+ def respond_to?(method, include_private = false)
+ integration_session.respond_to?(method, include_private) || super
+ end
+
# Delegate unhandled messages to the current session instance.
def method_missing(sym, *args, &block)
reset! unless integration_session
7 actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
View
@@ -57,8 +57,8 @@ def include_tag(*sources)
private
- def path_to_asset(source)
- asset_paths.compute_public_path(source, asset_name.to_s.pluralize, extension)
+ def path_to_asset(source, include_host = true)
+ asset_paths.compute_public_path(source, asset_name.to_s.pluralize, extension, include_host)
end
def compute_paths(*args)
@@ -77,9 +77,8 @@ def expand_sources(sources, recursive)
def ensure_sources!(sources)
sources.each do |source|
- asset_file_path!(path_to_asset(source))
+ asset_file_path!(path_to_asset(source, false))
end
- return sources
end
def collect_asset_files(*path)
4 actionpack/lib/action_view/helpers/form_helper.rb
View
@@ -2,7 +2,7 @@
require 'action_view/helpers/date_helper'
require 'action_view/helpers/tag_helper'
require 'action_view/helpers/form_tag_helper'
-require 'active_support/core_ext/class/inheritable_attributes'
+require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
@@ -1117,7 +1117,7 @@ class InstanceTag
class FormBuilder #:nodoc:
# The methods which wrap a form helper call.
- class_inheritable_accessor :field_helpers
+ class_attribute :field_helpers
self.field_helpers = (FormHelper.instance_method_names - ['form_for'])
attr_accessor :object_name, :object, :options
5 actionpack/lib/action_view/partials.rb
View
@@ -75,6 +75,11 @@ module ActionView
#
# <%= render :partial => "ad", :collection => @advertisements, :spacer_template => "ad_divider" %>
#
+ # If the given <tt>:collection</tt> is nil or empty, <tt>render</tt> will return nil. This will allow you
+ # to specify a text which will displayed instead by using this form:
+ #
+ # <%= render(:partial => "ad", :collection => @advertisements) || "There's no ad to be displayed" %>
+ #
# NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
# just keep domain objects, like Active Records, in there.
#
8 actionpack/lib/action_view/template.rb
View
@@ -184,6 +184,11 @@ def rerender(view)
end
end
+ # Used to store template data by template handlers.
+ def data
+ @data ||= {}
+ end
+
def inspect
@inspect ||=
if defined?(Rails.root)
@@ -269,7 +274,8 @@ def compile(view, mod) #:nodoc:
end
end
- code = @handler.call(self)
+ arity = @handler.respond_to?(:arity) ? @handler.arity : @handler.method(:call).arity
+ code = arity == 1 ? @handler.call(self) : @handler.call(self, view)
# Make sure that the resulting String to be evalled is in the
# encoding of the code
8 actionpack/test/abstract_unit.rb
View
@@ -36,14 +36,6 @@
require 'action_controller/caching'
require 'action_controller/caching/sweeping'
-begin
- require 'ruby-debug'
- Debugger.settings[:autoeval] = true
- Debugger.start
-rescue LoadError
- # Debugging disabled. `gem install ruby-debug` to enable.
-end
-
require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
module Rails
12 actionpack/test/activerecord/active_record_store_test.rb
View
@@ -23,6 +23,7 @@ def get_session_id
def call_reset_session
session[:foo]
reset_session
+ reset_session if params[:twice]
session[:foo] = "baz"
head :ok
end
@@ -94,6 +95,17 @@ def test_getting_nil_session_value
end
end
+ def test_calling_reset_session_twice_does_not_raise_errors
+ with_test_route_set do
+ get '/call_reset_session', :twice => "true"
+ assert_response :success
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: "baz"', response.body
+ end
+ end
+
def test_setting_session_value_after_session_reset
with_test_route_set do
get '/set_session_value'
2  actionpack/test/controller/action_pack_assertions_test.rb
View
@@ -173,7 +173,7 @@ def test_redirect_to_named_route
def test_string_constraint
with_routing do |set|
- set.draw do |map|
+ set.draw do
match "photos", :to => 'action_pack_assertions#nothing', :constraints => {:subdomain => "admin"}
end
end
74 actionpack/test/controller/mime_responds_test.rb
View
@@ -2,6 +2,14 @@
require 'controller/fake_models'
require 'active_support/core_ext/hash/conversions'
+class StarStarMimeController < ActionController::Base
+ layout nil
+
+ def index
+ render
+ end
+end
+
class RespondToController < ActionController::Base
layout :set_layout
@@ -89,7 +97,6 @@ def custom_type_handling
end
end
- Mime::Type.register("text/x-mobile", :mobile)
def custom_constant_handling
respond_to do |type|
@@ -126,7 +133,6 @@ def all_types_with_layout
end
end
- Mime::Type.register_alias("text/html", :iphone)
def iphone_with_html_response_type
request.format = :iphone if request.env["HTTP_ACCEPT"] == "text/iphone"
@@ -160,16 +166,43 @@ def set_layout
end
end
+class StarStarMimeControllerTest < ActionController::TestCase
+ tests StarStarMimeController
+
+ def test_javascript_with_format
+ @request.accept = "text/javascript"
+ get :index, :format => 'js'
+ assert_match "function addition(a,b){ return a+b; }", @response.body
+ end
+
+ def test_javascript_with_no_format
+ @request.accept = "text/javascript"
+ get :index
+ assert_match "function addition(a,b){ return a+b; }", @response.body
+ end
+
+ def test_javascript_with_no_format_only_star_star
+ @request.accept = "*/*"
+ get :index
+ assert_match "function addition(a,b){ return a+b; }", @response.body
+ end
+
+end
+
class RespondToControllerTest < ActionController::TestCase
tests RespondToController
def setup
super
@request.host = "www.example.com"
+ Mime::Type.register_alias("text/html", :iphone)
+ Mime::Type.register("text/x-mobile", :mobile)
end
def teardown
super
+ Mime::Type.unregister('text/x-mobile', :iphone)
+ Mime::Type.unregister('text/iphone', :mobile)
end
def test_html
@@ -216,6 +249,16 @@ def test_js_or_html
assert_response 406
end
+ def test_json_or_yaml_with_leading_star_star
+ @request.accept = "*/*, application/json"
+ get :json_xml_or_html
+ assert_equal 'HTML', @response.body
+
+ @request.accept = "*/* , application/json"
+ get :json_xml_or_html
+ assert_equal 'HTML', @response.body
+ end
+
def test_json_or_yaml
xhr :get, :json_or_yaml
assert_equal 'JSON', @response.body
@@ -556,6 +599,17 @@ def index
end
end
+class RenderJsonRespondWithController < RespondWithController
+ clear_respond_to
+ respond_to :json
+
+ def index
+ respond_with(resource) do |format|
+ format.json { render :json => RenderJsonTestException.new('boom') }
+ end
+ end
+end
+
class EmptyRespondWithController < ActionController::Base
def index
respond_with(Customer.new("david", 13))
@@ -572,6 +626,8 @@ def setup
def teardown
super
+ Mime::Type.unregister('text/x-mobile', :iphone)
+ Mime::Type.unregister('text/iphone', :mobile)
end
def test_using_resource
@@ -869,6 +925,13 @@ def test_block_inside_respond_with_is_rendered
assert_equal "JSON", @response.body
end
+ def test_render_json_object_responds_to_str_still_produce_json
+ @controller = RenderJsonRespondWithController.new
+ @request.accept = "application/json"
+ get :index, :format => :json
+ assert_equal %Q{{"message":"boom","error":"RenderJsonTestException"}}, @response.body
+ end
+
def test_no_double_render_is_raised
@request.accept = "text/html"
assert_raise ActionView::MissingTemplate do
@@ -952,6 +1015,13 @@ class MimeControllerLayoutsTest < ActionController::TestCase
def setup
super
@request.host = "www.example.com"
+ Mime::Type.register_alias("text/html", :iphone)
+ end
+
+ def teardown
+ super
+ Mime::Type.unregister('text/x-mobile', :iphone)
+ Mime::Type.unregister('text/iphone', :mobile)
end
def test_missing_layout_renders_properly
20 actionpack/test/controller/new_base/render_template_test.rb
View
@@ -4,16 +4,16 @@ module RenderTemplate
class WithoutLayoutController < ActionController::Base
self.view_paths = [ActionView::FixtureResolver.new(
- "test/basic.html.erb" => "Hello from basic.html.erb",
- "shared.html.erb" => "Elastica",
- "locals.html.erb" => "The secret is <%= secret %>",
- "xml_template.xml.builder" => "xml.html do\n xml.p 'Hello'\nend",
- "with_raw.html.erb" => "Hello <%=raw '<strong>this is raw</strong>' %>",
- "with_implicit_raw.html.erb"=> "Hello <%== '<strong>this is also raw</strong>' %>",
- "test/with_json.html.erb" => "<%= render :template => 'test/with_json.json' %>",
- "test/with_json.json.erb" => "<%= render :template => 'test/final' %>",
- "test/final.json.erb" => "{ final: json }",
- "test/with_error.html.erb" => "<%= idontexist %>"
+ "test/basic.html.erb" => "Hello from basic.html.erb",
+ "shared.html.erb" => "Elastica",
+ "locals.html.erb" => "The secret is <%= secret %>",
+ "xml_template.xml.builder" => "xml.html do\n xml.p 'Hello'\nend",
+ "with_raw.html.erb" => "Hello <%=raw '<strong>this is raw</strong>' %>",
+ "with_implicit_raw.html.erb" => "Hello <%== '<strong>this is also raw</strong>' %>",
+ "test/with_json.html.erb" => "<%= render :template => 'test/with_json.json' %>",
+ "test/with_json.json.erb" => "<%= render :template => 'test/final' %>",
+ "test/final.json.erb" => "{ final: json }",
+ "test/with_error.html.erb" => "<%= idontexist %>"
)]
def index
6 actionpack/test/controller/routing_test.rb
View
@@ -1444,7 +1444,7 @@ def test_escape_spaces_build_query_string
end
def test_expand_array_build_query_string
- assert_uri_equal '/foo?x[]=1&x[]=2', default_route_set.generate({:controller => 'foo', :x => [1, 2]})
+ assert_uri_equal '/foo?x%5B%5D=1&x%5B%5D=2', default_route_set.generate({:controller => 'foo', :x => [1, 2]})
end
def test_escape_spaces_build_query_string_selected_keys
@@ -1744,9 +1744,9 @@ def test_generate
assert_equal '/posts', @routes.generate({:controller => 'posts'}, {:controller => 'posts', :action => 'index'})
assert_equal '/posts/create', @routes.generate({:action => 'create'}, {:controller => 'posts'})
assert_equal '/posts?foo=bar', @routes.generate(:controller => 'posts', :foo => 'bar')
- assert_equal '/posts?foo[]=bar&foo[]=baz', @routes.generate(:controller => 'posts', :foo => ['bar', 'baz'])
+ assert_equal '/posts?foo%5B%5D=bar&foo%5B%5D=baz', @routes.generate(:controller => 'posts', :foo => ['bar', 'baz'])
assert_equal '/posts?page=2', @routes.generate(:controller => 'posts', :page => 2)
- assert_equal '/posts?q[foo][a]=b', @routes.generate(:controller => 'posts', :q => { :foo => { :a => 'b'}})
+ assert_equal '/posts?q%5Bfoo%5D%5Ba%5D=b', @routes.generate(:controller => 'posts', :q => { :foo => { :a => 'b'}})
assert_equal '/news.rss', @routes.generate(:controller => 'news', :action => 'index', :format => 'rss')
22 actionpack/test/controller/runner_test.rb
View
@@ -0,0 +1,22 @@
+require 'abstract_unit'
+require 'action_dispatch/testing/integration'
+
+module ActionDispatch
+ class RunnerTest < Test::Unit::TestCase
+ class MyRunner
+ include Integration::Runner
+
+ def initialize(session)
+ @integration_session = session
+ end
+
+ def hi; end
+ end
+
+ def test_respond_to?
+ runner = MyRunner.new(Class.new { def x; end }.new)
+ assert runner.respond_to?(:hi)
+ assert runner.respond_to?(:x)
+ end
+ end
+end
23 actionpack/test/controller/url_for_test.rb
View
@@ -17,7 +17,7 @@ def add_host!
end
def test_exception_is_thrown_without_host
- assert_raise RuntimeError do
+ assert_raise ArgumentError do
W.new.url_for :controller => 'c', :action => 'a', :id => 'i'
end
end
@@ -60,6 +60,27 @@ def test_host_may_be_overridden
)
end
+ def test_subdomain_may_be_changed
+ add_host!
+ assert_equal('http://api.basecamphq.com/c/a/i',
+ W.new.url_for(:subdomain => 'api', :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_domain_may_be_changed
+ add_host!
+ assert_equal('http://www.37signals.com/c/a/i',
+ W.new.url_for(:domain => '37signals.com', :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_tld_length_may_be_changed
+ add_host!
+ assert_equal('http://mobile.www.basecamphq.com/c/a/i',
+ W.new.url_for(:subdomain => 'mobile', :tld_length => 2, :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
def test_port
add_host!
assert_equal('http://www.basecamphq.com:3000/c/a/i',
59 actionpack/test/dispatch/mime_type_test.rb
View
@@ -6,10 +6,65 @@ class MimeTypeTest < ActiveSupport::TestCase
test "parse single" do
Mime::LOOKUP.keys.each do |mime_type|
- assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type)
+ unless mime_type == 'image/*'
+ assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type)
+ end
end
end
+ test "unregister" do
+ begin
+ Mime::Type.register("text/x-mobile", :mobile)
+ assert defined?(Mime::MOBILE)
+ assert_equal Mime::MOBILE, Mime::LOOKUP['text/x-mobile']
+ assert_equal Mime::MOBILE, Mime::EXTENSION_LOOKUP['mobile']
+
+ Mime::Type.unregister("text/x-mobile", :mobile)
+ assert !defined?(Mime::MOBILE), "Mime::MOBILE should not be defined"
+ assert !Mime::LOOKUP.has_key?('text/x-mobile'), "Mime::LOOKUP should not have key ['text/x-mobile]"
+ assert !Mime::EXTENSION_LOOKUP.has_key?('mobile'), "Mime::EXTENSION_LOOKUP should not have key ['mobile]"
+ ensure
+ Mime.module_eval { remove_const :MOBILE if const_defined?(:MOBILE) }
+ Mime::LOOKUP.reject!{|key,_| key == 'text/x-mobile'}
+ end
+ end
+
+ test "parse text with trailing star at the beginning" do
+ accept = "text/*, text/html, application/json, multipart/form-data"
+ expect = [Mime::JSON, Mime::XML, Mime::ICS, Mime::HTML, Mime::CSS, Mime::CSV, Mime::TEXT, Mime::YAML, Mime::JS, Mime::MULTIPART_FORM]
+ parsed = Mime::Type.parse(accept)
+ assert_equal expect.size, parsed.size
+ Range.new(0,expect.size-1).to_a.each do |index|
+ assert_equal expect[index], parsed[index], "Failed for index number #{index}"
+ end
+ end
+
+ test "parse text with trailing star in the end" do
+ accept = "text/html, application/json, multipart/form-data, text/*"
+ expect = [Mime::HTML, Mime::JSON, Mime::MULTIPART_FORM, Mime::XML, Mime::ICS, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]
+ parsed = Mime::Type.parse(accept)
+ assert_equal 10, parsed.size
+ Range.new(0,expect.size-1).to_a.each do |index|
+ assert_equal expect[index], parsed[index], "Failed for index number #{index}"
+ end
+ end
+
+ test "parse text with trailing star" do
+ accept = "text/*"
+ expect = [Mime::JSON, Mime::XML, Mime::ICS, Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT].sort_by(&:to_s)
+ parsed = Mime::Type.parse(accept)
+ assert_equal 9, parsed.size
+ assert_equal expect, parsed.sort_by(&:to_s)
+ end
+
+ test "parse application with trailing star" do
+ accept = "application/*"
+ expect = [Mime::HTML, Mime::JS, Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::PDF, Mime::URL_ENCODED_FORM].sort_by(&:to_s)
+ parsed = Mime::Type.parse(accept)
+ assert_equal 9, parsed.size
+ assert_equal expect, parsed.sort_by(&:to_s)
+ end
+
test "parse without q" do
accept = "text/xml,application/xhtml+xml,text/yaml,application/xml,text/html,image/png,text/plain,application/pdf,*/*"
expect = [Mime::HTML, Mime::XML, Mime::YAML, Mime::PNG, Mime::TEXT, Mime::PDF, Mime::ALL]
@@ -44,7 +99,7 @@ class MimeTypeTest < ActiveSupport::TestCase
assert_equal Mime::GIF, Mime::SET.last
end
ensure
- Mime.module_eval { remove_const :GIF if const_defined?(:GIF) }
+ Mime::Type.unregister('image/gif', :gif)
end
end
14 actionpack/test/dispatch/request_test.rb
View
@@ -164,12 +164,20 @@ class RequestTest < ActiveSupport::TestCase
assert !request.standard_port?
end
+ test "optional port" do
+ request = stub_request 'HTTP_HOST' => 'www.example.org:80'
+ assert_equal nil, request.optional_port
+
+ request = stub_request 'HTTP_HOST' => 'www.example.org:8080'
+ assert_equal 8080, request.optional_port
+ end
+
test "port string" do
request = stub_request 'HTTP_HOST' => 'www.example.org:80'
- assert_equal "", request.port_string
+ assert_equal '', request.port_string
request = stub_request 'HTTP_HOST' => 'www.example.org:8080'
- assert_equal ":8080", request.port_string
+ assert_equal ':8080', request.port_string
end
test "full path" do
@@ -392,7 +400,7 @@ class RequestTest < ActiveSupport::TestCase
mock_rack_env = { "QUERY_STRING" => "x[y]=1&x[y][][w]=2", "rack.input" => "foo" }
request = nil
begin
- request = stub_request(mock_rack_env)
+ request = stub_request(mock_rack_env)
request.parameters
rescue TypeError => e
# rack will raise a TypeError when parsing this query string
11 actionpack/test/dispatch/routing_test.rb
View
@@ -155,6 +155,11 @@ def self.matches?(request)
end
resources :replies do
+ collection do
+ get 'page/:page' => 'replies#index', :page => %r{\d+}
+ get ':page' => 'replies#index', :page => %r{\d+}
+ end
+
new do
post :preview
end
@@ -1241,6 +1246,12 @@ def test_match_shorthand_inside_namespace
end
end
+ def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper
+ with_test_routes do
+ assert_equal '/replies', replies_path
+ end
+ end
+
def test_scoped_controller_with_namespace_and_action
with_test_routes do
assert_equal '/account/twitter/callback', account_callback_path("twitter")
12 actionpack/test/dispatch/uploaded_file_test.rb
View
@@ -28,6 +28,18 @@ def test_tempfile
assert_equal 'foo', uf.tempfile
end
+ def test_delegates_path_to_tempfile
+ tf = Class.new { def path; 'thunderhorse' end }
+ uf = Http::UploadedFile.new(:tempfile => tf.new)
+ assert_equal 'thunderhorse', uf.path
+ end
+
+ def test_delegates_open_to_tempfile
+ tf = Class.new { def open; 'thunderhorse' end }
+ uf = Http::UploadedFile.new(:tempfile => tf.new)
+ assert_equal 'thunderhorse', uf.open
+ end
+
def test_delegates_to_tempfile
tf = Class.new { def read; 'thunderhorse' end }
uf = Http::UploadedFile.new(:tempfile => tf.new)
1  actionpack/test/fixtures/star_star_mime/index.js.erb
View
@@ -0,0 +1 @@
+function addition(a,b){ return a+b; }
6 actionpack/test/lib/controller/fake_models.rb
View
@@ -194,3 +194,9 @@ def each
a.each { |i| yield i }
end
end
+
+class RenderJsonTestException < Exception
+ def to_json(options = nil)
+ return { :error => self.class.name, :message => self.to_str }.to_json
+ end
+end
44 actionpack/test/template/asset_tag_helper_test.rb
View
@@ -680,6 +680,26 @@ def test_caching_javascript_include_tag_with_relative_url_root
FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js'))
end
+ def test_caching_javascript_include_tag_with_named_paths_and_relative_url_root_when_caching_off
+ ENV["RAILS_ASSET_ID"] = ""
+ @controller.config.relative_url_root = "/collaboration/hieraki"
+ config.perform_caching = false
+
+ assert_dom_equal(
+ %(<script src="/collaboration/hieraki/javascripts/robber.js" type="text/javascript"></script>),
+ javascript_include_tag('robber', :cache => true)
+ )
+
+ assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
+
+ assert_dom_equal(
+ %(<script src="/collaboration/hieraki/javascripts/robber.js" type="text/javascript"></script>),
+ javascript_include_tag('robber', :cache => "money", :recursive => true)
+ )
+
+ assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js'))
+ end
+
def test_caching_javascript_include_tag_when_caching_off
ENV["RAILS_ASSET_ID"] = ""
config.perform_caching = false
@@ -907,6 +927,30 @@ def test_caching_stylesheet_link_tag_with_relative_url_root
FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
end
+
+ def test_caching_stylesheet_link_tag_with_named_paths_and_relative_url_root_when_caching_off
+ ENV["RAILS_ASSET_ID"] = ""
+ @controller.config.relative_url_root = "/collaboration/hieraki"
+ config.perform_caching = false
+
+ assert_dom_equal(
+ %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />),
+ stylesheet_link_tag('robber', :cache => true)
+ )
+
+ assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
+
+ assert_dom_equal(
+ %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />),
+ stylesheet_link_tag('robber', :cache => "money")
+ )
+
+ assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
+ end
+
+
+
+
def test_caching_stylesheet_include_tag_when_caching_off
ENV["RAILS_ASSET_ID"] = ""
config.perform_caching = false
29 actionpack/test/template/lookup_context_test.rb
View
@@ -180,6 +180,16 @@ def teardown
assert_not_equal template, old_template
end
+
+ test "data can be stored in cached templates" do
+ template = @lookup_context.find("hello_world", "test")
+ template.data["cached"] = "data"
+ assert_equal "Hello world!", template.source
+
+ template = @lookup_context.find("hello_world", "test")
+ assert_equal "data", template.data["cached"]
+ assert_equal "Hello world!", template.source
+ end
end
class LookupContextWithFalseCaching < ActiveSupport::TestCase
@@ -205,7 +215,7 @@ def setup
assert_equal "Bar", template.source
end
- test "if no template was found in the second lookup, give it higher preference" do
+ test "if no template was found in the second lookup, with no cache, raise error" do
template = @lookup_context.find("foo", "test", true)
assert_equal "Foo", template.source
@@ -215,7 +225,7 @@ def setup
end
end
- test "if no template was cached in the first lookup, do not use the cache in the second" do
+ test "if no template was cached in the first lookup, retrieval should work in the second call" do
@resolver.hash.clear
assert_raise ActionView::MissingTemplate do
@lookup_context.find("foo", "test", true)
@@ -225,4 +235,19 @@ def setup
template = @lookup_context.find("foo", "test", true)
assert_equal "Foo", template.source
end
+
+ test "data can be stored as long as template was not updated" do
+ template = @lookup_context.find("foo", "test", true)
+ template.data["cached"] = "data"
+ assert_equal "Foo", template.source
+
+ template = @lookup_context.find("foo", "test", true)
+ assert_equal "data", template.data["cached"]
+ assert_equal "Foo", template.source
+
+ @resolver.hash["test/_foo.erb"][1] = Time.now.utc
+ template = @lookup_context.find("foo", "test", true)