Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge commit 'rails/master'

Conflicts:
	activerecord/lib/active_record/calculations.rb
	activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
	activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
  • Loading branch information...
commit 0e2fbd80e2420329738b891240d44a056cea1de4 2 parents eb3ae44 + 600a89f
Emilio Tagua miloops authored
Showing with 2,326 additions and 395 deletions.
  1. +1 −0  .gitignore
  2. +5 −5 actionmailer/test/mail_service_test.rb
  3. +2 −0  actionpack/CHANGELOG
  4. +71 −9 actionpack/examples/minimal.rb
  5. +1 −0  actionpack/examples/views/_collection.erb
  6. +1 −0  actionpack/examples/views/_hello.erb
  7. +10 −0 actionpack/examples/views/_many_partials.erb
  8. +10 −0 actionpack/examples/views/_partial.erb
  9. +1 −1  actionpack/examples/views/template.html.erb
  10. +10 −0 actionpack/lib/abstract_controller/helpers.rb
  11. +1 −1  actionpack/lib/action_controller/base.rb
  12. +2 −0  actionpack/lib/action_controller/metal/compatibility.rb
  13. +3 −2 actionpack/lib/action_controller/metal/http_authentication.rb
  14. +9 −5 actionpack/lib/action_controller/routing/resources.rb
  15. +25 −8 actionpack/lib/action_controller/routing/route_set.rb
  16. +2 −2 actionpack/lib/action_dispatch/http/request.rb
  17. +10 −3 actionpack/lib/action_dispatch/testing/assertions/selector.rb
  18. +25 −15 actionpack/lib/action_view/base.rb
  19. +1 −1  actionpack/lib/action_view/helpers/atom_feed_helper.rb
  20. +5 −0 actionpack/lib/action_view/helpers/form_helper.rb
  21. +67 −0 actionpack/lib/action_view/helpers/form_options_helper.rb
  22. +21 −11 actionpack/lib/action_view/helpers/text_helper.rb
  23. +1 −1  actionpack/lib/action_view/helpers/url_helper.rb
  24. +57 −57 actionpack/lib/action_view/render/partials.rb
  25. +8 −4 actionpack/lib/action_view/template/template.rb
  26. +3 −1 actionpack/lib/action_view/template/text.rb
  27. +7 −0 actionpack/test/controller/assert_select_test.rb
  28. +1 −1  actionpack/test/controller/caching_test.rb
  29. +25 −0 actionpack/test/controller/http_basic_authentication_test.rb
  30. +29 −6 actionpack/test/controller/http_digest_authentication_test.rb
  31. +0 −1  actionpack/test/controller/render_test.rb
  32. +44 −0 actionpack/test/controller/resources_test.rb
  33. +5 −3 actionpack/test/controller/routing_test.rb
  34. +3 −3 actionpack/test/controller/url_rewriter_test.rb
  35. +28 −0 actionpack/test/dispatch/request_test.rb
  36. +1 −0  actionpack/test/fixtures/test/greeting.xml.erb
  37. +29 −0 actionpack/test/template/atom_feed_helper_test.rb
  38. +1 −1  actionpack/test/template/body_parts_test.rb
  39. +16 −0 actionpack/test/template/form_helper_test.rb
  40. +34 −0 actionpack/test/template/form_options_helper_test.rb
  41. +23 −0 actionpack/test/template/text_helper_test.rb
  42. +8 −0 actionpack/test/template/url_helper_test.rb
  43. +5 −0 activemodel/CHANGELOG
  44. +1 −1  activemodel/CHANGES
  45. +8 −4 activemodel/lib/active_model/validations/length.rb
  46. +64 −0 activemodel/lib/active_model/validations/with.rb
  47. +14 −0 activemodel/test/cases/validations/length_validation_test.rb
  48. +116 −0 activemodel/test/cases/validations/with_validation_test.rb
  49. +2 −0  activerecord/CHANGELOG
  50. +2 −2 activerecord/Rakefile
  51. +1 −0  activerecord/lib/active_record.rb
  52. +49 −17 activerecord/lib/active_record/associations.rb
  53. +1 −0  activerecord/lib/active_record/associations/association_collection.rb
  54. +16 −0 activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
  55. +1 −0  activerecord/lib/active_record/associations/has_many_association.rb
  56. +6 −2 activerecord/lib/active_record/associations/has_many_through_association.rb
  57. +9 −3 activerecord/lib/active_record/associations/has_one_through_association.rb
  58. +1 −1  activerecord/lib/active_record/associations/through_association_scope.rb
  59. +1 −1  activerecord/lib/active_record/attribute_methods/dirty.rb
  60. +18 −10 activerecord/lib/active_record/base.rb
  61. +0 −1  activerecord/lib/active_record/calculations.rb
  62. +15 −0 activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
  63. +2 −2 activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
  64. +7 −0 activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
  65. +17 −8 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
  66. +22 −3 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
  67. +4 −0 activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
  68. +1 −1  activerecord/lib/active_record/reflection.rb
  69. +0 −1  activerecord/lib/active_record/schema_dumper.rb
  70. +1 −1  activerecord/lib/active_record/validations/uniqueness.rb
  71. +68 −0 activerecord/lib/active_record/validator.rb
  72. +12 −0 activerecord/test/cases/adapter_test.rb
  73. +0 −18 activerecord/test/cases/associations/belongs_to_associations_test.rb
  74. +56 −0 activerecord/test/cases/associations/habtm_join_table_test.rb
  75. +56 −2 activerecord/test/cases/associations/has_many_associations_test.rb
  76. +45 −1 activerecord/test/cases/associations/has_many_through_associations_test.rb
  77. +10 −0 activerecord/test/cases/associations/has_one_through_associations_test.rb
  78. +4 −4 activerecord/test/cases/associations/join_model_test.rb
  79. +46 −1 activerecord/test/cases/base_test.rb
  80. +6 −0 activerecord/test/cases/calculations_test.rb
  81. +34 −0 activerecord/test/cases/column_definition_test.rb
  82. +10 −0 activerecord/test/cases/dirty_test.rb
  83. +1 −1  activerecord/test/cases/fixtures_test.rb
  84. +5 −0 activerecord/test/cases/i18n_test.rb
  85. +39 −11 activerecord/test/cases/migration_test.rb
  86. +42 −0 activerecord/test/cases/modules_test.rb
  87. +6 −0 activerecord/test/cases/named_scope_test.rb
  88. +18 −0 activerecord/test/cases/pk_test.rb
  89. +2 −2 activerecord/test/cases/reflection_test.rb
  90. +19 −1 activerecord/test/cases/schema_dumper_test.rb
  91. +14 −3 activerecord/test/cases/validations/uniqueness_validation_test.rb
  92. +3 −0  activerecord/test/fixtures/posts.yml
  93. +1 −0  activerecord/test/models/author.rb
  94. +5 −1 activerecord/test/models/comment.rb
  95. +2 −0  activerecord/test/models/company.rb
  96. +1 −1  activerecord/test/models/company_in_module.rb
  97. +4 −0 activerecord/test/models/contract.rb
  98. +2 −0  activerecord/test/models/organization.rb
  99. +13 −2 activerecord/test/schema/postgresql_specific_schema.rb
  100. +4 −0 activerecord/test/schema/schema.rb
  101. +11 −0 activeresource/CHANGELOG
  102. +77 −3 activeresource/lib/active_resource/base.rb
  103. +61 −7 activeresource/lib/active_resource/connection.rb
  104. +11 −0 activeresource/lib/active_resource/exceptions.rb
  105. +20 −4 activeresource/lib/active_resource/validations.rb
  106. +16 −1 activeresource/test/base/load_test.rb
  107. +56 −21 activeresource/test/base_errors_test.rb
  108. +147 −0 activeresource/test/base_test.rb
  109. +42 −0 activeresource/test/connection_test.rb
  110. +4 −0 activeresource/test/fixtures/proxy.rb
  111. +1 −0  activesupport/lib/active_support/core_ext/array/conversions.rb
  112. +12 −4 activesupport/lib/active_support/core_ext/enumerable.rb
  113. +1 −0  activesupport/lib/active_support/core_ext/hash/conversions.rb
  114. +5 −4 activesupport/lib/active_support/core_ext/hash/deep_merge.rb
  115. +2 −1  activesupport/lib/active_support/core_ext/load_error.rb
  116. +8 −8 activesupport/lib/active_support/core_ext/time/calculations.rb
  117. +9 −9 activesupport/lib/active_support/deprecation/method_wrappers.rb
  118. +3 −0  activesupport/lib/active_support/inflector.rb
  119. +4 −1 activesupport/lib/active_support/json/backends/yaml.rb
  120. +1 −1  activesupport/lib/active_support/memoizable.rb
  121. +16 −1 activesupport/lib/active_support/multibyte/chars.rb
  122. +7 −0 activesupport/test/core_ext/array_ext_test.rb
  123. +1 −1  activesupport/test/core_ext/date_ext_test.rb
  124. +9 −0 activesupport/test/core_ext/enumerable_test.rb
  125. +19 −0 activesupport/test/core_ext/hash_ext_test.rb
  126. +23 −23 activesupport/test/core_ext/time_ext_test.rb
  127. +42 −35 activesupport/test/dependencies_test.rb
  128. +4 −0 activesupport/test/deprecation_test.rb
  129. +44 −0 activesupport/test/flush_cache_on_private_memoization_test.rb
  130. +10 −0 activesupport/test/inflector_test.rb
  131. +1 −1  activesupport/test/isolation_test.rb
  132. +5 −3 activesupport/test/json/decoding_test.rb
  133. +13 −2 activesupport/test/multibyte_chars_test.rb
  134. +8 −4 railties/lib/commands/dbconsole.rb
  135. +10 −1 railties/lib/generators/actions.rb
  136. +2 −4 railties/lib/generators/active_record/model/model_generator.rb
  137. +5 −1 railties/lib/generators/active_record/session_migration/session_migration_generator.rb
  138. +2 −1  railties/lib/generators/named_base.rb
  139. +1 −1  railties/lib/generators/rails/app/templates/config/boot.rb
  140. +3 −1 railties/lib/generators/test_unit/plugin/templates/test_helper.rb
  141. +2 −1  railties/lib/rails/configuration.rb
  142. +11 −3 railties/lib/tasks/databases.rake
  143. +15 −0 railties/test/generators/actions_test.rb
  144. +44 −0 railties/test/generators/model_generator_test.rb
  145. +18 −0 railties/test/generators/session_migration_generator_test.rb
  146. +2 −1  railties/test/initializer/path_test.rb
1  .gitignore
View
@@ -11,6 +11,7 @@ actionpack/pkg
activemodel/test/fixtures/fixture_database.sqlite3
actionmailer/pkg
activesupport/pkg
+activesupport/test/fixtures/isolation_test
railties/pkg
railties/test/500.html
railties/test/fixtures/tmp
10 actionmailer/test/mail_service_test.rb
View
@@ -894,11 +894,11 @@ def test_file_delivery_should_create_a_file
tmp_location = ActionMailer::Base.file_settings[:location]
TestMailer.deliver_cc_bcc(@recipient)
- assert File.exists? tmp_location
- assert File.directory? tmp_location
- assert File.exists? File.join(tmp_location, @recipient)
- assert File.exists? File.join(tmp_location, 'nobody@loudthinking.com')
- assert File.exists? File.join(tmp_location, 'root@loudthinking.com')
+ assert File.exists?(tmp_location)
+ assert File.directory?(tmp_location)
+ assert File.exists?(File.join(tmp_location, @recipient))
+ assert File.exists?(File.join(tmp_location, 'nobody@loudthinking.com'))
+ assert File.exists?(File.join(tmp_location, 'root@loudthinking.com'))
end
def test_recursive_multipart_processing
2  actionpack/CHANGELOG
View
@@ -1,5 +1,7 @@
*Edge*
+* Introduce grouped_collection_select helper. #1249 [Dan Codeape, Erik Ostrom]
+
* Make sure javascript_include_tag/stylesheet_link_tag does not append ".js" or ".css" onto external urls. #1664 [Matthew Rudy Jacobs]
* Ruby 1.9: fix Content-Length for multibyte send_data streaming. #2661 [Sava Chankov]
80 actionpack/examples/minimal.rb
View
@@ -3,13 +3,19 @@
ENV['NO_RELOAD'] ||= '1'
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
+$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib"
require 'action_controller'
require 'action_controller/new_base' if ENV['NEW']
+require 'action_view'
require 'benchmark'
class Runner
- def initialize(app)
- @app = app
+ def initialize(app, output)
+ @app, @output = app, output
+ end
+
+ def puts(*)
+ super if @output
end
def call(env)
@@ -18,16 +24,22 @@ def call(env)
end
def report(env, response)
+ return unless ENV["DEBUG"]
out = env['rack.errors']
out.puts response[0], response[1].to_yaml, '---'
response[2].each { |part| out.puts part }
out.puts '---'
end
- def self.run(app, n, label = nil)
- puts '=' * label.size, label, '=' * label.size if label
- env = { 'n' => n, 'rack.input' => StringIO.new(''), 'rack.errors' => $stdout }
- t = Benchmark.realtime { new(app).call(env) }
+ def self.puts(*)
+ super if @output
+ end
+
+ def self.run(app, n, label, output = true)
+ @output = output
+ puts label, '=' * label.size if label
+ env = Rack::MockRequest.env_for("/").merge('n' => n, 'rack.input' => StringIO.new(''), 'rack.errors' => $stdout)
+ t = Benchmark.realtime { new(app, output).call(env) }
puts "%d ms / %d req = %.1f usec/req" % [10**3 * t, n, 10**6 * t / n]
puts
end
@@ -36,10 +48,38 @@ def self.run(app, n, label = nil)
N = (ENV['N'] || 1000).to_i
+module ActionController::Rails2Compatibility
+ instance_methods.each do |name|
+ remove_method name
+ end
+end
+
class BasePostController < ActionController::Base
+ append_view_path "#{File.dirname(__FILE__)}/views"
+
+ def overhead
+ self.response_body = ''
+ end
+
def index
render :text => ''
end
+
+ def partial
+ render :partial => "/partial"
+ end
+
+ def many_partials
+ render :partial => "/many_partials"
+ end
+
+ def partial_collection
+ render :partial => "/collection", :collection => [1,2,3,4,5,6,7,8,9,10]
+ end
+
+ def show_template
+ render :template => "template"
+ end
end
OK = [200, {}, []]
@@ -51,6 +91,28 @@ def index
end
end
-Runner.run(MetalPostController, N, 'metal')
-Runner.run(HttpPostController.action(:index), N, 'http') if defined? HttpPostController
-Runner.run(BasePostController.action(:index), N, 'base')
+unless ENV["PROFILE"]
+ Runner.run(BasePostController.action(:overhead), N, 'overhead', false)
+ Runner.run(BasePostController.action(:index), N, 'index', false)
+ Runner.run(BasePostController.action(:partial), N, 'partial', false)
+ Runner.run(BasePostController.action(:many_partials), N, 'many_partials', false)
+ Runner.run(BasePostController.action(:partial_collection), N, 'collection', false)
+ Runner.run(BasePostController.action(:show_template), N, 'template', false)
+
+ (ENV["M"] || 1).to_i.times do
+ Runner.run(BasePostController.action(:overhead), N, 'overhead')
+ Runner.run(BasePostController.action(:index), N, 'index')
+ Runner.run(BasePostController.action(:partial), N, 'partial')
+ Runner.run(BasePostController.action(:many_partials), N, 'many_partials')
+ Runner.run(BasePostController.action(:partial_collection), N, 'collection')
+ Runner.run(BasePostController.action(:show_template), N, 'template')
+ end
+else
+ Runner.run(BasePostController.action(:many_partials), N, 'many_partials')
+ require "ruby-prof"
+ RubyProf.start
+ Runner.run(BasePostController.action(:many_partials), N, 'many_partials')
+ result = RubyProf.stop
+ printer = RubyProf::CallStackPrinter.new(result)
+ printer.print(File.open("output.html", "w"))
+end
1  actionpack/examples/views/_collection.erb
View
@@ -0,0 +1 @@
+<%= collection %>
1  actionpack/examples/views/_hello.erb
View
@@ -0,0 +1 @@
+Hello
10 actionpack/examples/views/_many_partials.erb
View
@@ -0,0 +1,10 @@
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
10 actionpack/examples/views/_partial.erb
View
@@ -0,0 +1,10 @@
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
2  actionpack/examples/views/template.html.erb
View
@@ -1 +1 @@
-Hello <%= @name %>
+Hello
10 actionpack/lib/abstract_controller/helpers.rb
View
@@ -4,8 +4,16 @@ module Helpers
include RenderingController
+ def self.next_serial
+ @helper_serial ||= 0
+ @helper_serial += 1
+ end
+
included do
extlib_inheritable_accessor(:_helpers) { Module.new }
+ extlib_inheritable_accessor(:_helper_serial) do
+ AbstractController::Helpers.next_serial
+ end
end
module ClassMethods
@@ -58,6 +66,8 @@ def #{meth}(*args, &blk)
# of the helper module. Any methods defined in the block
# will be helpers.
def helper(*args, &block)
+ self._helper_serial = AbstractController::Helpers.next_serial + 1
+
args.flatten.each do |arg|
case arg
when Module
2  actionpack/lib/action_controller/base.rb
View
@@ -41,7 +41,7 @@ class Base < Metal
module ImplicitRender
def send_action(*)
ret = super
- default_render unless performed?
+ default_render unless response_body
ret
end
2  actionpack/lib/action_controller/metal/compatibility.rb
View
@@ -64,6 +64,8 @@ class ::ActionController::ActionControllerError < StandardError #:nodoc:
cattr_accessor :ip_spoofing_check
self.ip_spoofing_check = true
+
+ cattr_accessor :trusted_proxies
end
# For old tests
5 actionpack/lib/action_controller/metal/http_authentication.rb
View
@@ -141,7 +141,7 @@ def authorization(request)
end
def decode_credentials(request)
- ActiveSupport::Base64.decode64(authorization(request).split.last || '')
+ ActiveSupport::Base64.decode64(authorization(request).split(' ', 2).last || '')
end
def encode_credentials(user_name, password)
@@ -197,9 +197,10 @@ def validate_digest_response(request, realm, &password_procedure)
return false unless password
method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
+ uri = credentials[:uri][0,1] == '/' ? request.request_uri : request.url
[true, false].any? do |password_is_ha1|
- expected = expected_response(method, request.env['REQUEST_URI'], credentials, password, password_is_ha1)
+ expected = expected_response(method, uri, credentials, password, password_is_ha1)
expected == credentials[:response]
end
end
14 actionpack/lib/action_controller/routing/resources.rb
View
@@ -320,9 +320,10 @@ def initialize(entity, options)
# notes.resources :attachments
# end
#
- # * <tt>:path_names</tt> - Specify different names for the 'new' and 'edit' actions. For example:
+ # * <tt>:path_names</tt> - Specify different path names for the actions. For example:
# # new_products_path == '/productos/nuevo'
- # map.resources :products, :as => 'productos', :path_names => { :new => 'nuevo', :edit => 'editar' }
+ # # bids_product_path(1) == '/productos/1/licitacoes'
+ # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
#
# You can also set default action names from an environment, like this:
# config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
@@ -528,13 +529,13 @@ def map_resource(entities, options = {}, &block)
resource = Resource.new(entities, options)
with_options :controller => resource.controller do |map|
+ map_associations(resource, options)
+
map_collection_actions(map, resource)
map_default_collection_actions(map, resource)
map_new_actions(map, resource)
map_member_actions(map, resource)
- map_associations(resource, options)
-
if block_given?
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
end
@@ -589,7 +590,10 @@ def map_collection_actions(map, resource)
resource.collection_methods.each do |method, actions|
actions.each do |action|
[method].flatten.each do |m|
- map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
+ action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
+ action_path ||= action
+
+ map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
end
end
end
33 actionpack/lib/action_controller/routing/route_set.rb
View
@@ -407,9 +407,24 @@ def generate(options, recall = {}, method=:generate)
# don't use the recalled keys when determining which routes to check
routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }]
- routes.each do |route|
+ routes[1].each_with_index do |route, index|
results = route.__send__(method, options, merged, expire_on)
- return results if results && (!results.is_a?(Array) || results.first)
+ if results && (!results.is_a?(Array) || results.first)
+
+ # Compare results with Rails 3.0 behavior
+ if routes[0][index] != route
+ routes[0].each do |route2|
+ new_results = route2.__send__(method, options, merged, expire_on)
+ if new_results && (!new_results.is_a?(Array) || new_results.first)
+ ActiveSupport::Deprecation.warn "The URL you generated will use the first matching route in routes.rb rather than the \"best\" match. " +
+ "In Rails 3.0 #{new_results} would of been generated instead of #{results}"
+ break
+ end
+ end
+ end
+
+ return results
+ end
end
end
@@ -448,7 +463,10 @@ def routes_by_controller
@routes_by_controller ||= Hash.new do |controller_hash, controller|
controller_hash[controller] = Hash.new do |action_hash, action|
action_hash[action] = Hash.new do |key_hash, keys|
- key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys)
+ key_hash[keys] = [
+ routes_for_controller_and_action_and_keys(controller, action, keys),
+ deprecated_routes_for_controller_and_action_and_keys(controller, action, keys)
+ ]
end
end
end
@@ -460,17 +478,16 @@ def routes_for(options, merged, expire_on)
merged = options if expire_on[:controller]
action = merged[:action] || 'index'
- routes_by_controller[controller][action][merged.keys]
+ routes_by_controller[controller][action][merged.keys][1]
end
- def routes_for_controller_and_action(controller, action)
- selected = routes.select do |route|
+ def routes_for_controller_and_action_and_keys(controller, action, keys)
+ routes.select do |route|
route.matches_controller_and_action? controller, action
end
- (selected.length == routes.length) ? routes : selected
end
- def routes_for_controller_and_action_and_keys(controller, action, keys)
+ def deprecated_routes_for_controller_and_action_and_keys(controller, action, keys)
selected = routes.select do |route|
route.matches_controller_and_action? controller, action
end
4 actionpack/lib/action_dispatch/http/request.rb
View
@@ -246,7 +246,7 @@ def remote_ip
remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].scan(/[^,\s]+/)
unless remote_addr_list.blank?
- not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES}
+ not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES || addr =~ ActionController::Base.trusted_proxies}
return not_trusted_addrs.first unless not_trusted_addrs.empty?
end
remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')
@@ -265,7 +265,7 @@ def remote_ip
end
if remote_ips
- while remote_ips.size > 1 && TRUSTED_PROXIES =~ remote_ips.last.strip
+ while remote_ips.size > 1 && (TRUSTED_PROXIES =~ remote_ips.last.strip || ActionController::Base.trusted_proxies =~ remote_ips.last.strip)
remote_ips.pop
end
13 actionpack/lib/action_dispatch/testing/assertions/selector.rb
View
@@ -345,14 +345,17 @@ def count_description(min, max) #:nodoc:
#
# Use the first argument to narrow down assertions to only statements
# of that type. Possible values are <tt>:replace</tt>, <tt>:replace_html</tt>,
- # <tt>:show</tt>, <tt>:hide</tt>, <tt>:toggle</tt>, <tt>:remove</tt> and
- # <tt>:insert_html</tt>.
+ # <tt>:show</tt>, <tt>:hide</tt>, <tt>:toggle</tt>, <tt>:remove</tta>,
+ # <tt>:insert_html</tt> and <tt>:redirect</tt>.
#
# Use the argument <tt>:insert</tt> followed by an insertion position to narrow
# down the assertion to only statements that insert elements in that
# position. Possible values are <tt>:top</tt>, <tt>:bottom</tt>, <tt>:before</tt>
# and <tt>:after</tt>.
#
+ # Use the argument <tt>:redirect</tt> follwed by a path to check that an statement
+ # which redirects to the specified path is generated.
+ #
# Using the <tt>:remove</tt> statement, you will be able to pass a block, but it will
# be ignored as there is no HTML passed for this statement.
#
@@ -399,6 +402,9 @@ def count_description(min, max) #:nodoc:
#
# # The same, but shorter.
# assert_select "ol>li", 4
+ #
+ # # Checking for a redirect.
+ # assert_select_rjs :redirect, root_path
def assert_select_rjs(*args, &block)
rjs_type = args.first.is_a?(Symbol) ? args.shift : nil
id = args.first.is_a?(String) ? args.shift : nil
@@ -576,7 +582,8 @@ def assert_select_email(&block)
: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}\\)"
+ :replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)",
+ :redirect => "window.location.href = #{RJS_ANY_ID}"
}
[:remove, :show, :hide, :toggle].each do |action|
RJS_STATEMENTS[action] = "Element\\.#{action}\\(#{RJS_ANY_ID}\\)"
40 actionpack/lib/action_view/base.rb
View
@@ -164,6 +164,9 @@ def initialize(paths, path, template_format = nil)
#
# See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details.
class Base
+ module Subclasses
+ end
+
include Helpers, Rendering, Partials, ::ERB::Util
extend ActiveSupport::Memoizable
@@ -195,7 +198,9 @@ def self.cache_template_loading?
attr_internal :request, :layout
- delegate :controller_path, :to => :controller, :allow_nil => true
+ def controller_path
+ @controller_path ||= controller && controller.controller_path
+ end
delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
:flash, :action_name, :controller_name, :to => :controller
@@ -210,30 +215,35 @@ def self.process_view_paths(value)
ActionView::PathSet.new(Array(value))
end
+ extlib_inheritable_accessor :helpers
attr_reader :helpers
- class ProxyModule < Module
- def initialize(receiver)
- @receiver = receiver
- end
+ def self.for_controller(controller)
+ @views ||= {}
- def include(*args)
- super(*args)
- @receiver.extend(*args)
- end
- end
+ # TODO: Decouple this so helpers are a separate concern in AV just like
+ # they are in AC.
+ if controller.class.respond_to?(:_helper_serial)
+ klass = @views[controller.class._helper_serial] ||= Class.new(self) do
+ Subclasses.const_set(controller.class.name.gsub(/::/, '__'), self)
- def self.for_controller(controller)
- new(controller.class.view_paths, {}, controller).tap do |view|
- view.helpers.include(controller._helpers) if controller.respond_to?(:_helpers)
+ if controller.respond_to?(:_helpers)
+ include controller._helpers
+ self.helpers = controller._helpers
+ end
+ end
+ else
+ klass = self
end
+
+ klass.new(controller.class.view_paths, {}, controller)
end
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil, formats = nil)#:nodoc:
@formats = formats || [:html]
@assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) }
@controller = controller
- @helpers = ProxyModule.new(self)
+ @helpers = self.class.helpers || Module.new
@_content_for = Hash.new {|h,k| h[k] = "" }
self.view_paths = view_paths
end
@@ -248,7 +258,7 @@ def view_paths=(paths)
def with_template(current_template)
_evaluate_assigns_and_ivars
last_template, self.template = template, current_template
- last_formats, self.formats = formats, [current_template.mime_type.to_sym] + Mime::SET.symbols
+ last_formats, self.formats = formats, current_template.formats
yield
ensure
self.template, self.formats = last_template, last_formats
2  actionpack/lib/action_view/helpers/atom_feed_helper.rb
View
@@ -98,7 +98,7 @@ def atom_feed(options = {}, &block)
options[:schema_date] = "2005" # The Atom spec copyright date
end
- xml = options[:xml] || eval("xml", block.binding)
+ xml = options.delete(:xml) || eval("xml", block.binding)
xml.instruct!
if options[:instruct]
options[:instruct].each do |target,attrs|
5 actionpack/lib/action_view/helpers/form_helper.rb
View
@@ -738,6 +738,7 @@ def to_label_tag(text = nil, options = {})
options = options.stringify_keys
tag_value = options.delete("value")
name_and_id = options.dup
+ name_and_id["id"] = name_and_id["for"]
add_default_name_and_id_for_value(tag_value, name_and_id)
options.delete("index")
options["for"] ||= name_and_id["id"]
@@ -936,6 +937,10 @@ def self.model_name
@model_name ||= Struct.new(:partial_path).new(name.demodulize.underscore.sub!(/_builder$/, ''))
end
+ def to_model
+ self
+ end
+
def initialize(object_name, object, template, options, proc)
@nested_child_index = {}
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
67 actionpack/lib/action_view/helpers/form_options_helper.rb
View
@@ -162,6 +162,60 @@ def collection_select(object, method, collection, value_method, text_method, opt
InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
end
+
+ # Returns <tt><select></tt>, <tt><optgroup></tt> and <tt><option></tt> tags for the collection of existing return values of
+ # +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
+ # be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
+ # or <tt>:include_blank</tt> in the +options+ hash.
+ #
+ # Parameters:
+ # * +object+ - The instance of the class to be used for the select tag
+ # * +method+ - The attribute of +object+ corresponding to the select tag
+ # * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
+ # * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
+ # array of child objects representing the <tt><option></tt> tags.
+ # * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
+ # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
+ # * +option_key_method+ - The name of a method which, when called on a child object of a member of
+ # +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
+ # * +option_value_method+ - The name of a method which, when called on a child object of a member of
+ # +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
+ #
+ # Example object structure for use with this method:
+ # class Continent < ActiveRecord::Base
+ # has_many :countries
+ # # attribs: id, name
+ # end
+ # class Country < ActiveRecord::Base
+ # belongs_to :continent
+ # # attribs: id, name, continent_id
+ # end
+ # class City < ActiveRecord::Base
+ # belongs_to :country
+ # # attribs: id, name, country_id
+ # end
+ #
+ # Sample usage:
+ # grouped_collection_select(:city, :country_id, @continents, :countries, :name, :id, :name)
+ #
+ # Possible output:
+ # <select name="city[country_id]">
+ # <optgroup label="Africa">
+ # <option value="1">South Africa</option>
+ # <option value="3">Somalia</option>
+ # </optgroup>
+ # <optgroup label="Europe">
+ # <option value="7" selected="selected">Denmark</option>
+ # <option value="2">Ireland</option>
+ # </optgroup>
+ # </select>
+ #
+ def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
+ InstanceTag.new(object, method, self, options.delete(:object)).to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
+ end
+
+
+
# Return select and option tags for the given object and method, using
# #time_zone_options_for_select to generate the list of option tags.
#
@@ -490,6 +544,15 @@ def to_collection_select_tag(collection, value_method, text_method, options, htm
)
end
+ def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
+ html_options = html_options.stringify_keys
+ add_default_name_and_id(html_options)
+ value = value(object)
+ content_tag(
+ "select", add_options(option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value), options, value), html_options
+ )
+ end
+
def to_time_zone_select_tag(priority_zones, options, html_options)
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
@@ -524,6 +587,10 @@ def collection_select(method, collection, value_method, text_method, options = {
@template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
end
+ def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
+ @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options))
+ end
+
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
@template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
end
32 actionpack/lib/action_view/helpers/text_helper.rb
View
@@ -33,13 +33,15 @@ def concat(string, unused_binding = nil)
end
# Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt>
- # (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...").
+ # (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...")
+ # for a total length not exceeding <tt>:length</tt>.
+ #
# Pass a <tt>:separator</tt> to truncate +text+ at a natural break.
#
# ==== Examples
#
# truncate("Once upon a time in a world far far away")
- # # => Once upon a time in a world f...
+ # # => Once upon a time in a world...
#
# truncate("Once upon a time in a world far far away", :separator => ' ')
# # => Once upon a time in a world...
@@ -48,19 +50,19 @@ def concat(string, unused_binding = nil)
# # => Once upon a...
#
# truncate("And they found that many people were sleeping better.", :length => 25, "(clipped)")
- # # => And they found that many (clipped)
+ # # => And they found t(clipped)
#
- # truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 15)
- # # => And they found... (continued)
+ # truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 25)
+ # # => And they f... (continued)
#
# You can still use <tt>truncate</tt> with the old API that accepts the
# +length+ as its optional second and the +ellipsis+ as its
# optional third parameter:
# truncate("Once upon a time in a world far far away", 14)
- # # => Once upon a time in a world f...
+ # # => Once upon a...
#
- # truncate("And they found that many people were sleeping better.", 15, "... (continued)")
- # # => And they found... (continued)
+ # truncate("And they found that many people were sleeping better.", 25, "... (continued)")
+ # # => And they f... (continued)
def truncate(text, *args)
options = args.extract_options!
unless args.empty?
@@ -239,12 +241,20 @@ def word_wrap(text, *args)
#
# textilize("Visit the Rails website "here":http://www.rubyonrails.org/.)
# # => "<p>Visit the Rails website <a href="http://www.rubyonrails.org/">here</a>.</p>"
- def textilize(text)
+ #
+ # textilize("This is worded <strong>strongly</strong>")
+ # # => "<p>This is worded <strong>strongly</strong></p>"
+ #
+ # textilize("This is worded <strong>strongly</strong>", :filter_html)
+ # # => "<p>This is worded &lt;strong&gt;strongly&lt;/strong&gt;</p>"
+ #
+ def textilize(text, *options)
+ options ||= [:hard_breaks]
+
if text.blank?
""
else
- textilized = RedCloth.new(text, [ :hard_breaks ])
- textilized.hard_breaks = true if textilized.respond_to?(:hard_breaks=)
+ textilized = RedCloth.new(text, options)
textilized.to_html
end
end
2  actionpack/lib/action_view/helpers/url_helper.rb
View
@@ -568,7 +568,7 @@ def convert_options_to_javascript!(html_options, url = '')
when confirm && popup
"if (#{confirm_javascript_function(confirm)}) { #{popup_javascript_function(popup)} };return false;"
when confirm && method
- "if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method)} };return false;"
+ "if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method, url, href)} };return false;"
when confirm
"return #{confirm_javascript_function(confirm)};"
when method
114 actionpack/lib/action_view/render/partials.rb
View
@@ -177,71 +177,70 @@ def self.partial_names
@partial_names ||= Hash.new {|h,k| h[k] = ActiveSupport::ConcurrentHash.new }
end
- def initialize(view_context, options, formats)
- object = options[:partial]
-
- @view, @formats = view_context, formats
- @options = options || {}
-
- if object.is_a?(String)
- @path = object
- elsif
- @object = object
- @path = partial_path unless collection
- end
-
- @locals = options[:locals] || {}
- @object ||= @options[:object] || @locals[:object]
- @layout = options[:layout]
+ def self.formats
+ @formats ||= Hash.new {|h,k| h[k] = Hash.new{|h,k| h[k] = Hash.new {|h,k| h[k] = {}}}}
end
- def render(&block)
- template = find if @path
+ def initialize(view_context, options, block)
+ partial = options[:partial]
- if collection
- render_collection(template, &block)
- else
- render_object(template, &block)
- end
- end
+ @view = view_context
+ @options = options
+ @locals = options[:locals] || {}
+ @block = block
- def render_template(template, &block)
- @options[:_template] = template
- @locals[:object] = @locals[template.variable_name] = @object
- @locals[@options[:as]] = @object if @options[:as]
+ # Set up some instance variables to speed up memoizing
+ @partial_names = self.class.partial_names[@view.controller.class]
+ @templates = self.class.formats
+ @format = view_context.formats
- content = @view._render_single_template(template, @locals, &block)
- return content if block_given? || !@layout
- find(@layout).render(@view, @locals) { content }
+ # Set up the object and path
+ @object = partial.is_a?(String) ? options[:object] : partial
+ @path = partial_path(partial)
end
- def render_object(template, &block)
- @object ||= @locals[template.variable_name]
- render_template(template, &block)
+ def render
+ return render_collection if collection
+
+ template = find_template
+ render_template(template, @object || @locals[template.variable_name])
end
- def render_collection(passed_template = nil, &block)
- @options[:_template] = passed_template
+ def render_collection
+ # Even if no template is rendered, this will ensure that the MIME type
+ # for the empty response is the same as the provided template
+ @options[:_template] = default_template = find_template
return nil if collection.blank?
if @options.key?(:spacer_template)
- spacer = @view.render_partial(
- :partial => @options[:spacer_template], :_details => @options[:_details])
+ spacer = find_template(@options[:spacer_template]).render(@view, @locals)
end
- index = 0
+ segments = []
- collection.map do |@object|
- @path = partial_path
- template = passed_template || find
+ collection.each_with_index do |object, index|
+ template = default_template || find_template(partial_path(object))
@locals[template.counter_name] = index
- index += 1
+ segments << render_template(template, object)
+ end
- render_template(template, &block)
- end.join(spacer)
+ segments.join(spacer)
end
+ def render_template(template, object = @object)
+ @options[:_template] ||= template
+
+ # TODO: is locals[:object] really necessary?
+ @locals[:object] = @locals[template.variable_name] = object
+ @locals[@options[:as]] = object if @options[:as]
+
+ content = @view._render_single_template(template, @locals, &@block)
+ return content if @block || !@options[:layout]
+ find_template(@options[:layout]).render(@view, @locals) { content }
+ end
+
+
private
def collection
@collection ||= if @object.respond_to?(:to_ary)
@@ -251,21 +250,22 @@ def collection
end
end
- def find(path = @path)
- prefix = @view.controller.controller_path unless path.include?(?/)
- @view.find(path, {:formats => @view.formats}, prefix, true)
+ def find_template(path = @path)
+ return if !path
+ @templates[path][@view.controller_path][@format][I18n.locale] ||= begin
+ prefix = @view.controller.controller_path unless path.include?(?/)
+ @view.find(path, {:formats => @view.formats}, prefix, true)
+ end
end
def partial_path(object = @object)
- self.class.partial_names[@view.controller.class][object.class] ||= begin
- return nil unless object.class.respond_to?(:model_name)
+ return object if object.is_a?(String)
+ @partial_names[object.class] ||= begin
+ return nil unless object.respond_to?(:to_model)
- name = object.class.model_name
- path = @view.controller_path
- if path && path.include?(?/)
- File.join(File.dirname(path), name.partial_path)
- else
- name.partial_path
+ object.to_model.class.model_name.partial_path.dup.tap do |partial|
+ path = @view.controller_path
+ partial.insert(0, "#{File.dirname(path)}/") if path.include?(?/)
end
end
end
@@ -279,7 +279,7 @@ def render_partial(options)
end
def _render_partial(options, &block) #:nodoc:
- PartialRenderer.new(self, options, formats).render(&block)
+ PartialRenderer.new(self, options, block).render
end
end
12 actionpack/lib/action_view/template/template.rb
View
@@ -7,19 +7,22 @@
module ActionView
class Template
extend TemplateHandlers
- attr_reader :source, :identifier, :handler, :mime_type, :details
+ attr_reader :source, :identifier, :handler, :mime_type, :formats, :details
def initialize(source, identifier, handler, details)
@source = source
@identifier = identifier
@handler = handler
@details = details
+ @method_names = {}
format = details.delete(:format) || begin
# TODO: Clean this up
handler.respond_to?(:default_format) ? handler.default_format.to_sym.to_s : "html"
end
@mime_type = Mime::Type.lookup_by_extension(format.to_s)
+ @formats = [format.to_sym]
+ @formats << :html if format == :js
@details[:formats] = Array.wrap(format.to_sym)
end
@@ -30,12 +33,12 @@ def render(view, locals, &blk)
# TODO: Figure out how to abstract this
def variable_name
- identifier[%r'_?(\w+)(\.\w+)*$', 1].to_sym
+ @variable_name ||= identifier[%r'_?(\w+)(\.\w+)*$', 1].to_sym
end
# TODO: Figure out how to abstract this
def counter_name
- "#{variable_name}_counter".to_sym
+ @counter_name ||= "#{variable_name}_counter".to_sym
end
# TODO: kill hax
@@ -90,7 +93,8 @@ def #{method_name}(local_assigns)
def build_method_name(locals)
# TODO: is locals.keys.hash reliably the same?
- "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_")
+ @method_names[locals.keys.hash] ||=
+ "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_")
end
end
end
4 actionpack/lib/action_view/template/text.rb
View
@@ -15,7 +15,9 @@ def identifier() self end
def render(*) self end
def mime_type() @content_type end
-
+
+ def formats() [mime_type] end
+
def partial?() false end
end
end
7 actionpack/test/controller/assert_select_test.rb
View
@@ -257,6 +257,13 @@ def test_assert_select_rjs_for_positioned_insert_should_fail_when_mixing_argumen
end
assert_raise(Assertion) {assert_select_rjs :insert, :top, "test2"}
end
+
+ def test_assert_select_rjs_for_redirect_to
+ render_rjs do |page|
+ page.redirect_to '/'
+ end
+ assert_select_rjs :redirect, '/'
+ end
def test_elect_with_xml_namespace_attributes
render_html %Q{<link xlink:href="http://nowhere.com"></link>}
2  actionpack/test/controller/caching_test.rb
View
@@ -51,7 +51,7 @@ def setup
ActionController::Routing::Routes.clear!
ActionController::Routing::Routes.draw do |map|
- map.main '', :controller => 'posts'
+ map.main '', :controller => 'posts', :format => nil
map.formatted_posts 'posts.:format', :controller => 'posts'
map.resources :posts
map.connect ':controller/:action/:id'
25 actionpack/test/controller/http_basic_authentication_test.rb
View
@@ -4,6 +4,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
class DummyController < ActionController::Base
before_filter :authenticate, :only => :index
before_filter :authenticate_with_request, :only => :display
+ before_filter :authenticate_long_credentials, :only => :show
def index
render :text => "Hello Secret"
@@ -12,6 +13,10 @@ def index
def display
render :text => 'Definitely Maybe'
end
+
+ def show
+ render :text => 'Only for loooooong credentials'
+ end
private
@@ -28,6 +33,12 @@ def authenticate_with_request
request_http_basic_authentication("SuperSecret")
end
end
+
+ def authenticate_long_credentials
+ authenticate_or_request_with_http_basic do |username, password|
+ username == '1234567890123456789012345678901234567890' && password == '1234567890123456789012345678901234567890'
+ end
+ end
end
AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION']
@@ -42,6 +53,13 @@ def authenticate_with_request
assert_response :success
assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}"
end
+ test "successful authentication with #{header.downcase} and long credentials" do
+ @request.env[header] = encode_credentials('1234567890123456789012345678901234567890', '1234567890123456789012345678901234567890')
+ get :show
+
+ assert_response :success
+ assert_equal 'Only for loooooong credentials', @response.body, "Authentication failed for request header #{header} and long credentials"
+ end
end
AUTH_HEADERS.each do |header|
@@ -52,6 +70,13 @@ def authenticate_with_request
assert_response :unauthorized
assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}"
end
+ test "unsuccessful authentication with #{header.downcase} and long credentials" do
+ @request.env[header] = encode_credentials('h4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0r', 'worldworldworldworldworldworldworldworld')
+ get :show
+
+ assert_response :unauthorized
+ assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header} and long credentials"
+ end
end
test "authentication request without credential" do
35 actionpack/test/controller/http_digest_authentication_test.rb
View
@@ -136,7 +136,7 @@ def authenticate_with_request
assert_equal 'Definitely Maybe', @response.body
end
- test "authentication request with request-uri that doesn't match credentials digest-uri" do
+ test "authentication request with request-uri that doesn't match credentials digest-uri" do
@request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
@request.env['REQUEST_URI'] = "/http_digest_authentication_test/dummy_digest/altered/uri"
get :display
@@ -145,10 +145,33 @@ def authenticate_with_request
assert_equal "Authentication Failed", @response.body
end
- test "authentication request with absolute uri" do
- @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri => "http://test.host/http_digest_authentication_test/dummy_digest/display",
+ test "authentication request with absolute request uri (as in webrick)" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
+ @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest"
+
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authentication request with absolute uri in credentials (as in IE)" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest",
:username => 'pretty', :password => 'please')
- @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest/display"
+
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authentication request with absolute uri in both request and credentials (as in Webrick with IE)" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest",
+ :username => 'pretty', :password => 'please')
+ @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest"
+
get :display
assert_response :success
@@ -202,11 +225,11 @@ def encode_credentials(options)
credentials = decode_credentials(@response.headers['WWW-Authenticate'])
credentials.merge!(options)
- credentials.reverse_merge!(:uri => "#{@request.env['REQUEST_URI']}")
+ credentials.merge!(:uri => @request.env['REQUEST_URI'].to_s)
ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1])
end
def decode_credentials(header)
ActionController::HttpAuthentication::Digest.decode_credentials(@response.headers['WWW-Authenticate'])
end
-end
+end
1  actionpack/test/controller/render_test.rb
View
@@ -1237,7 +1237,6 @@ def test_partial_collection_with_locals
def test_partial_collection_with_spacer
get :partial_collection_with_spacer
assert_equal "Hello: davidonly partialHello: mary", @response.body
- assert_template :partial => 'test/_partial_only'
assert_template :partial => '_customer'
end
44 actionpack/test/controller/resources_test.rb
View
@@ -76,6 +76,50 @@ def test_default_restful_routes
end
end
+ def test_override_paths_for_member_and_collection_methods
+ collection_methods = { 'rss' => :get, 'reorder' => :post, 'csv' => :post }
+ member_methods = { 'rss' => :get, :atom => :get, :upload => :post, :fix => :post }
+ path_names = {:new => 'nuevo', 'rss' => 'canal', :fix => 'corrigir' }
+
+ with_restful_routing :messages,
+ :collection => collection_methods,
+ :member => member_methods,
+ :path_names => path_names do
+
+ assert_restful_routes_for :messages,
+ :collection => collection_methods,
+ :member => member_methods,
+ :path_names => path_names do |options|
+ member_methods.each do |action, method|
+ assert_recognizes(options.merge(:action => action.to_s, :id => '1'),
+ :path => "/messages/1/#{path_names[action] || action}",
+ :method => method)
+ end
+
+ collection_methods.each do |action, method|
+ assert_recognizes(options.merge(:action => action),
+ :path => "/messages/#{path_names[action] || action}",
+ :method => method)
+ end
+ end
+
+ assert_restful_named_routes_for :messages,
+ :collection => collection_methods,
+ :member => member_methods,
+ :path_names => path_names do |options|
+
+ collection_methods.keys.each do |action|
+ assert_named_route "/messages/#{path_names[action] || action}", "#{action}_messages_path", :action => action
+ end
+
+ member_methods.keys.each do |action|
+ assert_named_route "/messages/1/#{path_names[action] || action}", "#{action}_message_path", :action => action, :id => "1"
+ end
+
+ end
+ end
+ end
+
def test_override_paths_for_default_restful_actions
resource = ActionController::Resources::Resource.new(:messages,
:path_names => {:new => 'nuevo', :edit => 'editar'})
8 actionpack/test/controller/routing_test.rb
View
@@ -1610,7 +1610,7 @@ def order_query_string(qs)
end
end
-class RouteSetTest < Test::Unit::TestCase
+class RouteSetTest < ActiveSupport::TestCase
def set
@set ||= ROUTING::RouteSet.new
end
@@ -2191,8 +2191,10 @@ def test_generate_finds_best_fit
map.connect "/ws/people", :controller => "people", :action => "index", :ws => true
end
- url = set.generate(:controller => "people", :action => "index", :ws => true)
- assert_equal "/ws/people", url
+ assert_deprecated {
+ url = set.generate(:controller => "people", :action => "index", :ws => true)
+ assert_equal "/ws/people", url
+ }
end
def test_generate_changes_controller_module
6 actionpack/test/controller/url_rewriter_test.rb
View
@@ -304,7 +304,7 @@ def test_path_generation_for_symbol_parameter_keys
def test_named_routes_with_nil_keys
ActionController::Routing::Routes.clear!
ActionController::Routing::Routes.draw do |map|
- map.main '', :controller => 'posts'
+ map.main '', :controller => 'posts', :format => nil
map.resources :posts
map.connect ':controller/:action/:id'
end
@@ -314,9 +314,9 @@ def test_named_routes_with_nil_keys
controller = kls.new
params = {:action => :index, :controller => :posts, :format => :xml}
- assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params))
+ assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params))
params[:format] = nil
- assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params))
+ assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params))
ensure
ActionController::Routing::Routes.load!
end
28 actionpack/test/dispatch/request_test.rb
View
@@ -72,6 +72,34 @@ def teardown
assert_equal '9.9.9.9', request.remote_ip
end
+ test "remote ip with user specified trusted proxies" do
+ ActionController::Base.trusted_proxies = /^67\.205\.106\.73$/i
+
+ request = stub_request 'REMOTE_ADDR' => '67.205.106.73',
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '172.16.0.1,67.205.106.73',
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '67.205.106.73,172.16.0.1',
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '67.205.106.74,172.16.0.1',
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ assert_equal '67.205.106.74', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,67.205.106.73'
+ assert_equal 'unknown', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 67.205.106.73'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ ActionController::Base.trusted_proxies = nil
+ end
+
test "domains" do
request = stub_request 'HTTP_HOST' => 'www.rubyonrails.org'
assert_equal "rubyonrails.org", request.domain
1  actionpack/test/fixtures/test/greeting.xml.erb
View
@@ -0,0 +1 @@
+<p>This is grand!</p>
29 actionpack/test/template/atom_feed_helper_test.rb
View
@@ -157,6 +157,26 @@ class ScrollsController < ActionController::Base
end
end
EOT
+ FEEDS["provide_builder"] = <<-'EOT'
+ # we pass in the new_xml to the helper so it doesn't
+ # call anything on the original builder
+ new_xml = Builder::XmlMarkup.new(:target=>'')
+ atom_feed(:xml => new_xml) do |feed|
+ feed.title("My great blog!")
+ feed.updated((@scrolls.first.created_at))
+
+ for scroll in @scrolls
+ feed.entry(scroll) do |entry|
+ entry.title(scroll.title)
+ entry.content(scroll.body, :type => 'html')
+
+ entry.author do |author|
+ author.name("DHH")
+ end
+ end
+ end
+ end
+ EOT
def index
@scrolls = [
Scroll.new(1, "1", "Hello One", "Something <i>COOL!</i>", Time.utc(2007, 12, 12, 15), Time.utc(2007, 12, 12, 15)),
@@ -202,6 +222,15 @@ def test_entry_should_only_use_published_if_created_at_is_present
end
end
+ def test_providing_builder_to_atom_feed
+ with_restful_routing(:scrolls) do
+ get :index, :id=>"provide_builder"
+ # because we pass in the non-default builder, the content generated by the
+ # helper should go 'nowhere'. Leaving the response body blank.
+ assert @response.body.blank?
+ end
+ end
+
def test_entry_with_prefilled_options_should_use_those_instead_of_querying_the_record
with_restful_routing(:scrolls) do
get :index, :id => "entry_options"
2  actionpack/test/template/body_parts_test.rb
View
@@ -4,7 +4,7 @@ class BodyPartsTest < ActionController::TestCase
RENDERINGS = [Object.new, Object.new, Object.new]
class TestController < ActionController::Base
- def performed?() true end
+ def response_body() "" end
def index
RENDERINGS.each do |rendering|
16 actionpack/test/template/form_helper_test.rb
View
@@ -157,6 +157,22 @@ def test_label_with_for_attribute_as_string
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, "for" => "my_for"))
end
+ def test_label_with_id_attribute_as_symbol
+ assert_dom_equal('<label for="post_title" id="my_id">Title</label>', label(:post, :title, nil, :id => "my_id"))
+ end
+
+ def test_label_with_id_attribute_as_string
+ assert_dom_equal('<label for="post_title" id="my_id">Title</label>', label(:post, :title, nil, "id" => "my_id"))
+ end
+
+ def test_label_with_for_and_id_attributes_as_symbol
+ assert_dom_equal('<label for="my_for" id="my_id">Title</label>', label(:post, :title, nil, :for => "my_for", :id => "my_id"))
+ end
+
+ def test_label_with_for_and_id_attributes_as_string
+ assert_dom_equal('<label for="my_for" id="my_id">Title</label>', label(:post, :title, nil, "for" => "my_for", "id" => "my_id"))
+ end
+
def test_label_for_radio_buttons_with_value
assert_dom_equal('<label for="post_title_great_title">The title goes here</label>', label("post", "title", "The title goes here", :value => "great_title"))
assert_dom_equal('<label for="post_title_great_title">The title goes here</label>', label("post", "title", "The title goes here", :value => "great title"))
34 actionpack/test/template/form_options_helper_test.rb
View
@@ -763,6 +763,40 @@ def test_time_zone_select_with_default_time_zone_and_value
html
end
+ def test_grouped_collection_select
+ @continents = [
+ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
+ Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
+ ]
+
+ @post = Post.new
+ @post.origin = 'dk'
+
+ assert_dom_equal(
+ %Q{<select id="post_origin" name="post[origin]"><optgroup label="&lt;Africa&gt;"><option value="&lt;sa&gt;">&lt;South Africa&gt;</option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk" selected="selected">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>},
+ grouped_collection_select("post", "origin", @continents, :countries, :continent_name, :country_id, :country_name)
+ )
+ end
+
+ def test_grouped_collection_select_under_fields_for
+ @continents = [
+ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
+ Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
+ ]
+
+ @post = Post.new
+ @post.origin = 'dk'
+
+ fields_for :post, @post do |f|
+ concat f.grouped_collection_select("origin", @continents, :countries, :continent_name, :country_id, :country_name)
+ end
+
+ assert_dom_equal(
+ %Q{<select id="post_origin" name="post[origin]"><optgroup label="&lt;Africa&gt;"><option value="&lt;sa&gt;">&lt;South Africa&gt;</option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk" selected="selected">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>},
+ output_buffer
+ )
+ end
+
private
def dummy_posts
23 actionpack/test/template/text_helper_test.rb
View
@@ -1,5 +1,10 @@
require 'abstract_unit'
require 'testing_sandbox'
+begin
+ require 'redcloth'
+rescue LoadError
+ $stderr.puts "Skipping textilize tests. `gem install RedCloth` to enable."
+end
class TextHelperTest < ActionView::TestCase
tests ActionView::Helpers::TextHelper
@@ -528,4 +533,22 @@ def test_cycle_no_instance_variable_clashes
assert_equal("red", cycle("red", "blue"))
assert_equal(%w{Specialized Fuji Giant}, @cycles)
end
+
+ if defined? RedCloth
+ def test_textilize
+ assert_equal("<p><strong>This is Textile!</strong> Rejoice!</p>", textilize("*This is Textile!* Rejoice!"))
+ end
+
+ def test_textilize_with_blank
+ assert_equal("", textilize(""))
+ end
+
+ def test_textilize_with_options
+ assert_equal("<p>This is worded &lt;strong&gt;strongly&lt;/strong&gt;</p>", textilize("This is worded <strong>strongly</strong>", :filter_html))
+ end
+
+ def test_textilize_with_hard_breaks
+ assert_equal("<p>This is one scary world.<br />\n True.</p>", textilize("This is one scary world.\n True."))
+ end
+ end
end
8 actionpack/test/template/url_helper_test.rb
View
@@ -220,6 +220,14 @@ def test_link_tag_using_post_javascript_and_confirm
)
end
+ def test_link_tag_using_delete_javascript_and_href_and_confirm
+ assert_dom_equal(
+ "<a href='\#' onclick=\"if (confirm('Are you serious?')) { var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = 'http://www.example.com';var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); m.setAttribute('value', 'delete'); f.appendChild(m);f.submit(); };return false;\">Destroy</a>",
+ link_to("Destroy", "http://www.example.com", :method => :delete, :href => '#', :confirm => "Are you serious?"),
+ "When specifying url, form should be generated with it, but not this.href"
+ )
+ end
+
def test_link_tag_using_post_javascript_and_popup
assert_raise(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :method => :post, :confirm => "Are you serious?") }
end
5 activemodel/CHANGELOG
View
@@ -0,0 +1,5 @@
+*Edge*
+
+* Introduce validates_with to encapsulate attribute validations in a class. #2630 [Jeff Dean]
+
+* Extracted from Active Record and Active Resource.
2  activemodel/CHANGES
View
@@ -9,4 +9,4 @@ Changes from extracting bits to ActiveModel
klass.add_observer(self)
klass.class_eval 'def after_find() end' unless
klass.respond_to?(:after_find)
- end
+ end
12 activemodel/lib/active_model/validations/length.rb
View
@@ -66,10 +66,14 @@ def validates_length_of(*attrs)
validates_each(attrs, options) do |record, attr, value|
value = options[:tokenizer].call(value) if value.kind_of?(String)
- if value.nil? or value.size < option_value.begin
- record.errors.add(attr, :too_short, :default => custom_message || options[:too_short], :count => option_value.begin)
- elsif value.size > option_value.end
- record.errors.add(attr, :too_long, :default => custom_message || options[:too_long], :count => option_value.end)
+
+ min, max = option_value.begin, option_value.end
+ max = max - 1 if option_value.exclude_end?
+
+ if value.nil? || value.size < min
+ record.errors.add(attr, :too_short, :default => custom_message || options[:too_short], :count => min)
+ elsif value.size > max
+ record.errors.add(attr, :too_long, :default => custom_message || options[:too_long], :count => max)
end
end
when :is, :minimum, :maximum
64 activemodel/lib/active_model/validations/with.rb
View
@@ -0,0 +1,64 @@
+module ActiveModel
+ module Validations
+ module ClassMethods
+
+ # Passes the record off to the class or classes specified and allows them to add errors based on more complex conditions.
+ #
+ # class Person < ActiveRecord::Base
+ # validates_with MyValidator
+ # end
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def validate
+ # if some_complex_logic
+ # record.errors[:base] << "This record is invalid"
+ # end
+ # end
+ #
+ # private
+ # def some_complex_logic
+ # # ...
+ # end
+ # end
+ #
+ # You may also pass it multiple classes, like so:
+ #
+ # class Person < ActiveRecord::Base
+ # validates_with MyValidator, MyOtherValidator, :on => :create
+ # end
+ #
+ # Configuration options:
+ # * <tt>on</tt> - Specifies when this validation is active (<tt>:create</tt> or <tt>:update</tt>
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
+ # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
+ # The method, proc or string should return or evaluate to a true or false value.
+ # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
+ # The method, proc or string should return or evaluate to a true or false value.
+ #
+ # If you pass any additional configuration options, they will be passed to the class and available as <tt>options</tt>:
+ #
+ # class Person < ActiveRecord::Base
+ # validates_with MyValidator, :my_custom_key => "my custom value"
+ # end
+ #
+ # class MyValidator < ActiveRecord::Validator
+ # def validate
+ # options[:my_custom_key] # => "my custom value"
+ # end
+ # end
+ #
+ def validates_with(*args)
+ configuration = args.extract_options!
+
+ send(validation_method(configuration[:on]), configuration) do |record|
+ args.each do |klass|
+ klass.new(record, configuration.except(:on, :if, :unless)).validate
+ end
+ end
+ end
+ end
+ end
+end
+
+
14 activemodel/test/cases/validations/length_validation_test.rb
View
@@ -112,6 +112,20 @@ def test_validates_length_of_using_within
assert t.valid?
end
+ def test_validates_length_of_using_within_with_exclusive_range
+ Topic.validates_length_of(:title, :within => 4...10)
+
+ t = Topic.new("title" => "9 chars!!")
+ assert t.valid?
+
+ t.title = "Now I'm 10"
+ assert !t.valid?
+ assert_equal ["is too long (maximum is 9 characters)"], t.errors[:title]
+
+ t.title = "Four"
+ assert t.valid?
+ end
+
def test_optionally_validates_length_of_using_within
Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true
116 activemodel/test/cases/validations/with_validation_test.rb
View
@@ -0,0 +1,116 @@
+# encoding: utf-8
+require 'cases/helper'
+
+require 'models/topic'
+
+class ValidatesWithTest < ActiveRecord::TestCase
+ include ActiveModel::ValidationsRepairHelper
+
+ repair_validations(Topic)
+
+ ERROR_MESSAGE = "Validation error from validator"
+ OTHER_ERROR_MESSAGE = "Validation error from other validator"
+
+ class ValidatorThatAddsErrors < ActiveRecord::Validator
+ def validate()
+ record.errors[:base] << ERROR_MESSAGE
+ end
+ end
+
+ class OtherValidatorThatAddsErrors < ActiveRecord::Validator
+ def validate()
+ record.errors[:base] << OTHER_ERROR_MESSAGE
+ end
+ end
+
+ class ValidatorThatDoesNotAddErrors < ActiveRecord::Validator
+ def validate()
+ end
+ end
+
+ class ValidatorThatValidatesOptions < ActiveRecord::Validator
+ def validate()
+ if options[:field] == :first_name
+ record.errors[:base] << ERROR_MESSAGE
+ end
+ end
+ end
+
+ test "vaidation with class that adds errors" do
+ Topic.validates_with(ValidatorThatAddsErrors)
+ topic = Topic.new
+ assert !topic.valid?, "A class that adds errors causes the record to be invalid"
+ assert topic.errors[:base].include?(ERROR_MESSAGE)
+ end
+
+ test "with a class that returns valid" do
+ Topic.validates_with(ValidatorThatDoesNotAddErrors)
+ topic = Topic.new
+ assert topic.valid?, "A class that does not add errors does not cause the record to be invalid"
+ end
+
+ test "with a class that adds errors on update and a new record" do
+ Topic.validates_with(ValidatorThatAddsErrors, :on => :update)
+ topic = Topic.new
+ assert topic.valid?, "Validation doesn't run on create if 'on' is set to update"
+ end
+
+ test "with a class that adds errors on create and a new record" do
+ Topic.validates_with(ValidatorThatAddsErrors, :on => :create)
+ topic = Topic.new
+ assert !topic.valid?, "Validation does run on create if 'on' is set to create"
+ assert topic.errors[:base].include?(ERROR_MESSAGE)
+ end
+
+ test "with multiple classes" do
+ Topic.validates_with(ValidatorThatAddsErrors, OtherValidatorThatAddsErrors)
+ topic = Topic.new
+ assert !topic.valid?
+ assert topic.errors[:base].include?(ERROR_MESSAGE)
+ assert topic.errors[:base].include?(OTHER_ERROR_MESSAGE)
+ end
+
+ test "with if statements that return false" do
+ Topic.validates_with(ValidatorThatAddsErrors, :if => "1 == 2")
+ topic = Topic.new
+ assert topic.valid?
+ end
+
+ test "with if statements that return true" do
+ Topic.validates_with(ValidatorThatAddsErrors, :if => "1 == 1")
+ topic = Topic.new
+ assert !topic.valid?
+ assert topic.errors[:base].include?(ERROR_MESSAGE)
+ end
+
+ test "with unless statements that return true" do
+ Topic.validates_with(ValidatorThatAddsErrors, :unless => "1 == 1")
+ topic = Topic.new
+ assert topic.valid?
+ end
+
+ test "with unless statements that returns false" do
+ Topic.validates_with(ValidatorThatAddsErrors, :unless => "1 == 2")
+ topic = Topic.new
+ assert !topic.valid?
+ assert topic.errors[:base].include?(ERROR_MESSAGE)
+ end
+
+ test "passes all non-standard configuration options to the validator class" do
+ topic = Topic.new
+ validator = mock()
+ validator.expects(:new).with(topic, {:foo => :bar}).returns(validator)
+ validator.expects(:validate)
+
+ Topic.validates_with(validator, :if => "1 == 1", :foo => :bar)
+ assert topic.valid?
+ end
+
+ test "validates_with with options" do
+ Topic.validates_with(ValidatorThatValidatesOptions, :field => :first_name)
+ topic = Topic.new
+ assert !topic.valid?
+ assert topic.errors[:base].include?(ERROR_MESSAGE)
+ end
+
+end
2  activerecord/CHANGELOG
View
@@ -1,5 +1,7 @@
*Edge*
+* PostgreSQL: XML datatype support. #1874 [Leonardo Borges]
+
* quoted_date converts time-like objects to ActiveRecord::Base.default_timezone before serialization. This allows you to use Time.now in find conditions and have it correctly be serialized as the current time in UTC when default_timezone == :utc. #2946 [Geoff Buesing]
* SQLite: drop support for 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper]
4 activerecord/Rakefile
View
@@ -64,8 +64,8 @@ end
namespace :mysql do
desc 'Build the MySQL test databases'
task :build_databases do
- %x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest )
- %x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest2 )
+ %x( echo "create DATABASE activerecord_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER})
+ %x( echo "create DATABASE activerecord_unittest2 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER})
end
desc 'Drop the MySQL test databases'
1  activerecord/lib/active_record.rb
View
@@ -74,6 +74,7 @@ def self.load_all!
autoload :TestCase, 'active_record/test_case'
autoload :Timestamp, 'active_record/timestamp'
autoload :Transactions, 'active_record/transactions'
+ autoload :Validator, 'active_record/validator'
autoload :Validations, 'active_record/validations'
module AttributeMethods
66 activerecord/lib/active_record/associations.rb
View
@@ -42,11 +42,12 @@ def initialize(reflection)
end
end
- class HasManyThroughCantAssociateThroughHasManyReflection < ActiveRecordError #:nodoc:
+ class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
end
end
+
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
@@ -416,6 +417,32 @@ def association_instance_set(name, association)
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
# @firm.invoices # selects all invoices by going through the Client join model.
#
+ # Similarly you can go through a +has_one+ association on the join model:
+ #
+ # class Group < ActiveRecord::Base
+ # has_many :users
+ # has_many :avatars, :through => :users
+ # end
+ #
+ # class User < ActiveRecord::Base
+ # belongs_to :group
+ # has_one :avatar
+ # end
+ #
+ # class Avatar < ActiveRecord::Base
+ # belongs_to :user
+ # end
+ #
+ # @group = Group.first
+ # @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group
+ # @group.avatars # selects all avatars by going through the User join model.
+ #
+ # An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are
+ # *read-only*. For example, the following would not work following the previous example:
+ #
+ # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around.
+ # @group.avatars.delete(@group.avatars.last) # so would this
+ #
# === Polymorphic Associations
#
# Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
@@ -819,7 +846,7 @@ module ClassMethods
# [:through]
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
- # or <tt>has_many</tt> association on the join model.
+ # <tt>has_one</tt> or <tt>has_many</tt> association on the join model.
# [:source]
# Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
@@ -1367,7 +1394,7 @@ def collection_accessor_methods(reflection, association_proxy_class, writer = tr
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
ids = (new_value || []).reject { |nid| nid.blank? }
- send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
+ send("#{reflection.name}=", reflection.klass.find(ids))
end
end
end
@@ -1656,7 +1683,7 @@ def construct_finder_sql_with_included_associations(options, join_dependency)
relation.join(joins)
relation.where(construct_conditions(options[:conditions], scope))
- relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
+ relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
relation.project(column_aliases(join_dependency))
relation.group(construct_group(options[:group], options[:having], scope))
@@ -1685,18 +1712,23 @@ def select_limited_ids_array(options, join_dependency)
end
def construct_finder_sql_for_association_limiting(options, join_dependency)
- # Only join tables referenced in order or conditions since this is particularly slow on the pre-query.
- tables_from_conditions = conditions_tables(options)
- tables_from_order = order_tables(options)
- all_tables = tables_from_conditions + tables_from_order
- options[:joins] = all_tables.uniq.map {|table|
- join_dependency.joins_for_table_name(table)
- }.flatten.compact.uniq.collect { |assoc| assoc.association_join }.join
-
- construct_finder_sql(options.merge(
- :select => connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", construct_order(options[:order], scope(:find)).join(","))
- )
- )
+ scope = scope(:find)
+
+ relation = arel_table(options[:from])
+
+ joins = join_dependency.join_associations.collect{|join| join.association_join }.join
+ joins << construct_join(options[:joins], scope)
+ relation.join(joins)
+
+ relation.where(construct_conditions(options[:conditions], scope))
+ relation.project(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", construct_order(options[:order], scope(:find)).join(",")))
+
+ relation.group(construct_group(options[:group], options[:having], scope))
+ relation.order(construct_order(options[:order], scope))
+ relation.take(construct_limit(options[:limit], scope))
+ relation.skip(construct_limit(options[:offset], scope))
+
+ sanitize_sql(relation.to_sql)