Instiki 0.17.2: Security Release
This release upgrades Instiki to Rails 2.3.4, which patches two security holes in Rails. See http://weblog.rubyonrails.org/2009/9/4/ruby-on-rails-2-3-4 There are also some new features, and the usual boatload of bugfixes. See the CHANGELOG for details.
This commit is contained in:
parent
34c4306867
commit
4bdf703ab2
31
CHANGELOG
31
CHANGELOG
|
@ -1,3 +1,34 @@
|
|||
* 0.17.2
|
||||
|
||||
Security: Updated to Rails 2.3.4
|
||||
* Fixes Timing Weakness in Rails MessageVerifier and the Cookie Store
|
||||
http://weblog.rubyonrails.org/2009/9/4/timing-weakness-in-ruby-on-rails
|
||||
* Fixes XSS Vulnerability in Rails
|
||||
http://weblog.rubyonrails.org/2009/9/4/xss-vulnerability-in-ruby-on-rails
|
||||
|
||||
New Features:
|
||||
* Syntax colouring (`ruby` and `html`) for code blocks.
|
||||
* Updated for itex2MML 1.3.10 (supports \rlap{} and \underline{}). You should upgrade that, too.
|
||||
* Add a "Create New Page" Link to the Search Page. (Based on an idea by nowa)
|
||||
* Updated to Rails 2.3.4
|
||||
|
||||
Bugs Fixed:
|
||||
* Wikilinks to published webs should be to the published action. This didn't work
|
||||
right for inter-web links. (Reported by Mike Shulman)
|
||||
* Use .size, rather than .length for ActiveRecord associations. A huge memory saving
|
||||
in building the recently_revised page.
|
||||
* Refactor the upgrade_instiki rake task, to make it database-agnostic. (Many thanks to James Herdman)
|
||||
* Web#files_path and Web#blatex_pngs_path now return Pathname objects. (Thanks, again, to James Herdman)
|
||||
* Workaround for Mozilla Bug 449396. (Reported by Andrew Stacey)
|
||||
* Correctly Set noindex,nofollow On /diff Pages.
|
||||
* Page-renaming javascript deals correctly with page names containing ampersands, slashes, and other garbage.
|
||||
* List of Wanted Pages should not include redirected pages.
|
||||
* The Regexp, used in Maruku to detect "email" headers (used, e.g., for S5 slideshow metadata) could, for some inputs, interact badly with Instiki's Chunk Handler. Fixed.
|
||||
* Ensure "rollback" locks page for editing.
|
||||
* Generate relative URLs, when possible. (Patch by Dennis Knauf)
|
||||
* Expire revisions of an edited page. Use a `before_save` hook to deal with the situation where a page's name has been changed.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
* 0.17
|
||||
|
||||
New features:
|
||||
|
|
|
@ -258,7 +258,7 @@ module Instiki
|
|||
module VERSION #:nodoc:
|
||||
MAJOR = 0
|
||||
MINOR = 17
|
||||
TINY = 0
|
||||
TINY = 2
|
||||
SUFFIX = '(MML+)'
|
||||
PRERELEASE = false
|
||||
if PRERELEASE
|
||||
|
|
4
vendor/rails/actionmailer/CHANGELOG
vendored
4
vendor/rails/actionmailer/CHANGELOG
vendored
|
@ -1,3 +1,7 @@
|
|||
*2.3.4 (September 4, 2009)*
|
||||
|
||||
* Minor bug fixes.
|
||||
|
||||
*2.3.3 (July 12, 2009)*
|
||||
|
||||
* No changes, just a version bump.
|
||||
|
|
2
vendor/rails/actionmailer/Rakefile
vendored
2
vendor/rails/actionmailer/Rakefile
vendored
|
@ -54,7 +54,7 @@ spec = Gem::Specification.new do |s|
|
|||
s.rubyforge_project = "actionmailer"
|
||||
s.homepage = "http://www.rubyonrails.org"
|
||||
|
||||
s.add_dependency('actionpack', '= 2.3.3' + PKG_BUILD)
|
||||
s.add_dependency('actionpack', '= 2.3.4' + PKG_BUILD)
|
||||
|
||||
s.has_rdoc = true
|
||||
s.requirements << 'none'
|
||||
|
|
|
@ -543,6 +543,7 @@ module ActionMailer #:nodoc:
|
|||
@headers ||= {}
|
||||
@body ||= {}
|
||||
@mime_version = @@default_mime_version.dup if @@default_mime_version
|
||||
@sent_on ||= Time.now
|
||||
end
|
||||
|
||||
def render_message(method_name, body)
|
||||
|
|
|
@ -2,7 +2,7 @@ module ActionMailer
|
|||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 3
|
||||
TINY = 3
|
||||
TINY = 4
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
|
|
@ -18,7 +18,6 @@ class TestMailer < ActionMailer::Base
|
|||
@recipients = recipient
|
||||
@subject = "[Signed up] Welcome #{recipient}"
|
||||
@from = "system@loudthinking.com"
|
||||
@sent_on = Time.local(2004, 12, 12)
|
||||
@body["recipient"] = recipient
|
||||
end
|
||||
|
||||
|
@ -356,12 +355,14 @@ class ActionMailerTest < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def test_signed_up
|
||||
Time.stubs(:now => Time.now)
|
||||
|
||||
expected = new_mail
|
||||
expected.to = @recipient
|
||||
expected.subject = "[Signed up] Welcome #{@recipient}"
|
||||
expected.body = "Hello there, \n\nMr. #{@recipient}"
|
||||
expected.from = "system@loudthinking.com"
|
||||
expected.date = Time.local(2004, 12, 12)
|
||||
expected.date = Time.now
|
||||
|
||||
created = nil
|
||||
assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) }
|
||||
|
|
11
vendor/rails/actionpack/CHANGELOG
vendored
11
vendor/rails/actionpack/CHANGELOG
vendored
|
@ -1,3 +1,12 @@
|
|||
*2.3.4 (September 4, 2009)*
|
||||
|
||||
* Sanitize multibyte strings before escaping them with escape_once. CVE-2009-3009
|
||||
|
||||
* Introduce grouped_collection_select helper. #1249 [Dan Codeape, Erik Ostrom]
|
||||
|
||||
* Ruby 1.9: fix Content-Length for multibyte send_data streaming. #2661 [Sava Chankov]
|
||||
|
||||
|
||||
*2.3.3 (July 12, 2009)*
|
||||
|
||||
* Fixed that TestResponse.cookies was returning cookies unescaped #1867 [Doug McInnes]
|
||||
|
@ -7,6 +16,8 @@
|
|||
|
||||
* Fixed that redirection would just log the options, not the final url (which lead to "Redirected to #<Post:0x23150b8>") [DHH]
|
||||
|
||||
* Don't check authenticity tokens for any AJAX requests [Ross Kaffenberger/Bryan Helmkamp]
|
||||
|
||||
* Added ability to pass in :public => true to fresh_when, stale?, and expires_in to make the request proxy cachable #2095 [Gregg Pollack]
|
||||
|
||||
* Fixed that passing a custom form builder would be forwarded to nested fields_for calls #2023 [Eloy Duran/Nate Wiger]
|
||||
|
|
4
vendor/rails/actionpack/Rakefile
vendored
4
vendor/rails/actionpack/Rakefile
vendored
|
@ -29,7 +29,7 @@ Rake::TestTask.new(:test_action_pack) do |t|
|
|||
|
||||
# make sure we include the tests in alphabetical order as on some systems
|
||||
# this will not happen automatically and the tests (as a whole) will error
|
||||
t.test_files = Dir.glob( "test/[cft]*/**/*_test.rb" ).sort
|
||||
t.test_files = Dir.glob( "test/[cftv]*/**/*_test.rb" ).sort
|
||||
|
||||
t.verbose = true
|
||||
#t.warning = true
|
||||
|
@ -79,7 +79,7 @@ spec = Gem::Specification.new do |s|
|
|||
s.has_rdoc = true
|
||||
s.requirements << 'none'
|
||||
|
||||
s.add_dependency('activesupport', '= 2.3.3' + PKG_BUILD)
|
||||
s.add_dependency('activesupport', '= 2.3.4' + PKG_BUILD)
|
||||
s.add_dependency('rack', '~> 1.0.0')
|
||||
|
||||
s.require_path = 'lib'
|
||||
|
|
87
vendor/rails/actionpack/examples/minimal.rb
vendored
Normal file
87
vendor/rails/actionpack/examples/minimal.rb
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
$:.push File.join(File.dirname(__FILE__), "..", "lib")
|
||||
$:.push File.join(File.dirname(__FILE__), "..", "..", "activesupport", "lib")
|
||||
require "action_controller"
|
||||
|
||||
class Runner
|
||||
def initialize(app, output)
|
||||
@app, @output = app, output
|
||||
end
|
||||
|
||||
def puts(*)
|
||||
super if @output
|
||||
end
|
||||
|
||||
def call(env)
|
||||
env['n'].to_i.times { @app.call(env) }
|
||||
@app.call(env).tap { |response| report(env, response) }
|
||||
end
|
||||
|
||||
def report(env, response)
|
||||
if ENV["DEBUG"]
|
||||
out = env['rack.errors']
|
||||
p response.headers
|
||||
out.puts response.status, response.headers.to_yaml, '---'
|
||||
response.body.each { |part| out.puts part }
|
||||
out.puts '---'
|
||||
end
|
||||
end
|
||||
|
||||
def self.puts(*)
|
||||
super if @output
|
||||
end
|
||||
|
||||
def self.run(app, n, label = nil, uri = "/", output = true)
|
||||
@output = output
|
||||
puts label, '=' * label.size if label
|
||||
env = Rack::MockRequest.env_for(uri).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
|
||||
end
|
||||
|
||||
N = (ENV['N'] || 1000).to_i
|
||||
|
||||
class BasePostController < ActionController::Base
|
||||
append_view_path "#{File.dirname(__FILE__)}/views"
|
||||
|
||||
def index
|
||||
render :text => 'Hello'
|
||||
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
|
||||
|
||||
# p BasePostController.call(Rack::MockRequest.env_for("/?action=index").merge("REQUEST_URI" => "/")).body
|
||||
|
||||
Runner.run(BasePostController, N, 'index', "/?action=index", false)
|
||||
Runner.run(BasePostController, N, 'partial', "/?action=partial", false)
|
||||
Runner.run(BasePostController, N, 'many partials', "/?action=many_partials", false)
|
||||
Runner.run(BasePostController, N, 'collection', "/?action=partial_collection", false)
|
||||
Runner.run(BasePostController, N, 'template', "/?action=show_template", false)
|
||||
|
||||
(ENV["M"] || 1).to_i.times do
|
||||
Runner.run(BasePostController, N, 'index', "/?action=index")
|
||||
Runner.run(BasePostController, N, 'partial', "/?action=partial")
|
||||
Runner.run(BasePostController, N, 'many partials', "/?action=many_partials")
|
||||
Runner.run(BasePostController, N, 'collection', "/?action=partial_collection")
|
||||
Runner.run(BasePostController, N, 'template', "/?action=show_template")
|
||||
end
|
||||
# Runner.run(BasePostController.action(:many_partials), N, 'index')
|
||||
# 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')
|
1
vendor/rails/actionpack/examples/views/_collection.erb
vendored
Normal file
1
vendor/rails/actionpack/examples/views/_collection.erb
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<%= collection %>
|
1
vendor/rails/actionpack/examples/views/_hello.erb
vendored
Normal file
1
vendor/rails/actionpack/examples/views/_hello.erb
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Hello
|
10
vendor/rails/actionpack/examples/views/_many_partials.erb
vendored
Normal file
10
vendor/rails/actionpack/examples/views/_many_partials.erb
vendored
Normal file
|
@ -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
vendor/rails/actionpack/examples/views/_partial.erb
vendored
Normal file
10
vendor/rails/actionpack/examples/views/_partial.erb
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
<%= "Hello" %>
|
||||
<%= "Hello" %>
|
||||
<%= "Hello" %>
|
||||
<%= "Hello" %>
|
||||
<%= "Hello" %>
|
||||
<%= "Hello" %>
|
||||
<%= "Hello" %>
|
||||
<%= "Hello" %>
|
||||
<%= "Hello" %>
|
||||
<%= "Hello" %>
|
1
vendor/rails/actionpack/examples/views/layouts/alt.html.erb
vendored
Normal file
1
vendor/rails/actionpack/examples/views/layouts/alt.html.erb
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
+ <%= yield %> +
|
1
vendor/rails/actionpack/examples/views/layouts/kaigi.html.erb
vendored
Normal file
1
vendor/rails/actionpack/examples/views/layouts/kaigi.html.erb
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Hello <%= yield %> Goodbye
|
1
vendor/rails/actionpack/examples/views/template.html.erb
vendored
Normal file
1
vendor/rails/actionpack/examples/views/template.html.erb
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Hello
|
|
@ -31,12 +31,8 @@ rescue LoadError
|
|||
end
|
||||
end
|
||||
|
||||
begin
|
||||
gem 'rack', '~> 1.1.0'
|
||||
gem 'rack', '~> 1.0.0'
|
||||
require 'rack'
|
||||
rescue Gem::LoadError
|
||||
require 'action_controller/vendor/rack-1.1.pre/rack'
|
||||
end
|
||||
|
||||
module ActionController
|
||||
# TODO: Review explicit to see if they will automatically be handled by
|
||||
|
|
|
@ -64,7 +64,9 @@ module ActionController
|
|||
# Support partial arguments for hash redirections
|
||||
if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
|
||||
if options.all? {|(key, value)| @response.redirected_to[key] == value}
|
||||
::ActiveSupport::Deprecation.warn("Using assert_redirected_to with partial hash arguments is deprecated. Specify the full set arguments instead", caller)
|
||||
callstack = caller.dup
|
||||
callstack.slice!(0, 2)
|
||||
::ActiveSupport::Deprecation.warn("Using assert_redirected_to with partial hash arguments is deprecated. Specify the full set arguments instead", callstack)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
|
|
@ -493,7 +493,12 @@ module ActionController #:nodoc:
|
|||
filtered_parameters[key] = filter_parameters(value)
|
||||
elsif value.is_a?(Array)
|
||||
filtered_parameters[key] = value.collect do |item|
|
||||
case item
|
||||
when Hash, Array
|
||||
filter_parameters(item)
|
||||
else
|
||||
item
|
||||
end
|
||||
end
|
||||
elsif block_given?
|
||||
key = key.dup
|
||||
|
@ -814,7 +819,6 @@ module ActionController #:nodoc:
|
|||
# render :text => proc { |response, output|
|
||||
# 10_000_000.times do |i|
|
||||
# output.write("This is line #{i}\n")
|
||||
# output.flush
|
||||
# end
|
||||
# }
|
||||
#
|
||||
|
|
|
@ -51,7 +51,7 @@ module ActionController #:nodoc:
|
|||
protected
|
||||
# Returns the cookie container, which operates as described above.
|
||||
def cookies
|
||||
CookieJar.new(self)
|
||||
@cookies ||= CookieJar.new(self)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,13 +2,12 @@ module ActionController
|
|||
# Dispatches requests to the appropriate controller and takes care of
|
||||
# reloading the app after each request when Dependencies.load? is true.
|
||||
class Dispatcher
|
||||
@@cache_classes = true
|
||||
|
||||
class << self
|
||||
def define_dispatcher_callbacks(cache_classes)
|
||||
@@cache_classes = cache_classes
|
||||
unless cache_classes
|
||||
unless self.middleware.include?(Reloader)
|
||||
self.middleware.insert_after(Failsafe, Reloader)
|
||||
end
|
||||
|
||||
ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
|
||||
end
|
||||
|
||||
|
@ -79,7 +78,7 @@ module ActionController
|
|||
# DEPRECATE: Remove arguments, since they are only used by CGI
|
||||
def initialize(output = $stdout, request = nil, response = nil)
|
||||
@output = output
|
||||
@app = @@middleware.build(lambda { |env| self.dup._call(env) })
|
||||
build_middleware_stack if @@cache_classes
|
||||
end
|
||||
|
||||
def dispatch
|
||||
|
@ -103,7 +102,18 @@ module ActionController
|
|||
end
|
||||
|
||||
def call(env)
|
||||
if @@cache_classes
|
||||
@app.call(env)
|
||||
else
|
||||
Reloader.run do
|
||||
# When class reloading is turned on, we will want to rebuild the
|
||||
# middleware stack every time we process a request. If we don't
|
||||
# rebuild the middleware stack, then the stack may contain references
|
||||
# to old classes metal classes, which will b0rk class reloading.
|
||||
build_middleware_stack
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def _call(env)
|
||||
|
@ -114,5 +124,10 @@ module ActionController
|
|||
def flush_logger
|
||||
Base.logger.flush
|
||||
end
|
||||
|
||||
private
|
||||
def build_middleware_stack
|
||||
@app = @@middleware.build(lambda { |env| self.dup._call(env) })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -139,7 +139,7 @@ module ActionController
|
|||
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)
|
||||
|
@ -195,9 +195,10 @@ module ActionController
|
|||
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
|
||||
|
|
|
@ -47,6 +47,8 @@ module ActionController
|
|||
false
|
||||
end
|
||||
rescue Exception => e # YAML, XML or Ruby code block errors
|
||||
logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
|
||||
|
||||
raise
|
||||
{ "body" => request.raw_post,
|
||||
"content_type" => request.content_type,
|
||||
|
@ -67,5 +69,9 @@ module ActionController
|
|||
|
||||
nil
|
||||
end
|
||||
|
||||
def logger
|
||||
defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
require 'thread'
|
||||
|
||||
module ActionController
|
||||
class Reloader
|
||||
@@default_lock = Mutex.new
|
||||
cattr_accessor :default_lock
|
||||
|
||||
class BodyWrapper
|
||||
def initialize(body)
|
||||
def initialize(body, lock)
|
||||
@body = body
|
||||
@lock = lock
|
||||
end
|
||||
|
||||
def close
|
||||
@body.close if @body.respond_to?(:close)
|
||||
ensure
|
||||
Dispatcher.cleanup_application
|
||||
@lock.unlock
|
||||
end
|
||||
|
||||
def method_missing(*args, &block)
|
||||
|
@ -20,13 +27,11 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
def self.run(lock = @@default_lock)
|
||||
lock.lock
|
||||
begin
|
||||
Dispatcher.reload_application
|
||||
status, headers, body = @app.call(env)
|
||||
status, headers, body = yield
|
||||
# We do not want to call 'cleanup_application' in an ensure block
|
||||
# because the returned Rack response body may lazily generate its data. This
|
||||
# is for example the case if one calls
|
||||
|
@ -39,7 +44,11 @@ module ActionController
|
|||
# completely finished. So we wrap the body in a BodyWrapper class so that
|
||||
# when the Rack handler calls #close during the end of the request, we get to
|
||||
# run our cleanup code.
|
||||
[status, headers, BodyWrapper.new(body)]
|
||||
[status, headers, BodyWrapper.new(body, lock)]
|
||||
rescue Exception
|
||||
lock.unlock
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -81,12 +81,13 @@ module ActionController #:nodoc:
|
|||
|
||||
# Returns true or false if a request is verified. Checks:
|
||||
#
|
||||
# * is the format restricted? By default, only HTML and AJAX requests are checked.
|
||||
# * is the format restricted? By default, only HTML requests are checked.
|
||||
# * is it a GET request? Gets should be safe and idempotent
|
||||
# * Does the form_authenticity_token match the given token value from the params?
|
||||
def verified_request?
|
||||
!protect_against_forgery? ||
|
||||
request.method == :get ||
|
||||
request.xhr? ||
|
||||
!verifiable_request_format? ||
|
||||
form_authenticity_token == params[request_forgery_protection_token]
|
||||
end
|
||||
|
|
|
@ -317,9 +317,10 @@ module ActionController
|
|||
# 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' }
|
||||
|
@ -525,16 +526,16 @@ module ActionController
|
|||
resource = Resource.new(entities, options)
|
||||
|
||||
with_options :controller => resource.controller do |map|
|
||||
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
|
||||
|
||||
map_collection_actions(map, resource)
|
||||
map_default_collection_actions(map, resource)
|
||||
map_new_actions(map, resource)
|
||||
map_member_actions(map, resource)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -542,16 +543,16 @@ module ActionController
|
|||
resource = SingletonResource.new(entities, options)
|
||||
|
||||
with_options :controller => resource.controller do |map|
|
||||
map_collection_actions(map, resource)
|
||||
map_new_actions(map, resource)
|
||||
map_member_actions(map, resource)
|
||||
map_default_singleton_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
|
||||
|
||||
map_collection_actions(map, resource)
|
||||
map_new_actions(map, resource)
|
||||
map_member_actions(map, resource)
|
||||
map_default_singleton_actions(map, resource)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -586,7 +587,10 @@ module ActionController
|
|||
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
|
||||
|
|
|
@ -166,6 +166,12 @@ module ActionController # :nodoc:
|
|||
str
|
||||
end
|
||||
|
||||
def flush #:nodoc:
|
||||
ActiveSupport::Deprecation.warn(
|
||||
'Calling output.flush is no longer needed for streaming output ' +
|
||||
'because ActionController::Response automatically handles it', caller)
|
||||
end
|
||||
|
||||
def set_cookie(key, value)
|
||||
if value.has_key?(:http_only)
|
||||
ActiveSupport::Deprecation.warn(
|
||||
|
|
|
@ -271,6 +271,9 @@ module ActionController
|
|||
|
||||
ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
|
||||
|
||||
mattr_accessor :generate_best_match
|
||||
self.generate_best_match = true
|
||||
|
||||
# The root paths which may contain controller files
|
||||
mattr_accessor :controller_paths
|
||||
self.controller_paths = []
|
||||
|
|
|
@ -405,11 +405,14 @@ module ActionController
|
|||
end
|
||||
|
||||
# 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 }]
|
||||
future_routes, deprecated_routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }]
|
||||
routes = Routing.generate_best_match ? deprecated_routes : future_routes
|
||||
|
||||
routes.each do |route|
|
||||
routes.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)
|
||||
return results
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -448,7 +451,10 @@ module ActionController
|
|||
@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,10 +466,11 @@ module ActionController
|
|||
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)
|
||||
ActiveSupport::Deprecation.warn "routes_for_controller_and_action() has been deprecated. Please use routes_for()"
|
||||
selected = routes.select do |route|
|
||||
route.matches_controller_and_action? controller, action
|
||||
end
|
||||
|
@ -471,6 +478,12 @@ module ActionController
|
|||
end
|
||||
|
||||
def routes_for_controller_and_action_and_keys(controller, action, keys)
|
||||
routes.select do |route|
|
||||
route.matches_controller_and_action? controller, action
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require 'active_support/core_ext/string/bytesize'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
# Methods for sending arbitrary data and for streaming files to the browser,
|
||||
# instead of rendering.
|
||||
|
@ -137,7 +139,7 @@ module ActionController #:nodoc:
|
|||
# instead. See ActionController::Base#render for more information.
|
||||
def send_data(data, options = {}) #:doc:
|
||||
logger.info "Sending data #{options[:filename]}" if logger
|
||||
send_file_headers! options.merge(:length => data.size)
|
||||
send_file_headers! options.merge(:length => data.bytesize)
|
||||
@performed_render = false
|
||||
render :status => options[:status], :text => data
|
||||
end
|
||||
|
|
|
@ -184,7 +184,7 @@ module ActionController
|
|||
path = rewrite_path(options)
|
||||
rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
|
||||
rewritten_url << "##{options[:anchor]}" if options[:anchor]
|
||||
rewritten_url << "##{CGI.escape(options[:anchor].to_param.to_s)}" if options[:anchor]
|
||||
|
||||
rewritten_url
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@ module ActionPack #:nodoc:
|
|||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 3
|
||||
TINY = 3
|
||||
TINY = 4
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
|
|
@ -98,7 +98,7 @@ module ActionView
|
|||
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|
|
||||
|
|
|
@ -726,6 +726,7 @@ module ActionView
|
|||
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"]
|
||||
|
@ -860,7 +861,7 @@ module ActionView
|
|||
|
||||
private
|
||||
def add_default_name_and_id_for_value(tag_value, options)
|
||||
if tag_value
|
||||
unless tag_value.nil?
|
||||
pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
|
||||
specified_id = options["id"]
|
||||
add_default_name_and_id(options)
|
||||
|
|
|
@ -162,6 +162,60 @@ module ActionView
|
|||
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 @@ module ActionView
|
|||
)
|
||||
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)
|
||||
|
@ -508,7 +571,8 @@ module ActionView
|
|||
option_tags = "<option value=\"\">#{options[:include_blank] if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
|
||||
end
|
||||
if value.blank? && options[:prompt]
|
||||
("<option value=\"\">#{options[:prompt].kind_of?(String) ? options[:prompt] : 'Please select'}</option>\n") + option_tags
|
||||
prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('support.select.prompt', :default => 'Please select')
|
||||
"<option value=\"\">#{prompt}</option>\n" + option_tags
|
||||
else
|
||||
option_tags
|
||||
end
|
||||
|
@ -524,6 +588,10 @@ module ActionView
|
|||
@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
|
||||
|
|
|
@ -103,7 +103,7 @@ module ActionView
|
|||
# escape_once("<< Accept & Checkout")
|
||||
# # => "<< Accept & Checkout"
|
||||
def escape_once(html)
|
||||
html.to_s.gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] }
|
||||
ActiveSupport::Multibyte.clean(html.to_s).gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] }
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -33,30 +33,31 @@ module ActionView
|
|||
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>.
|
||||
#
|
||||
# ==== 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", :length => 14)
|
||||
# # => 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?
|
||||
|
@ -234,12 +235,20 @@ module ActionView
|
|||
#
|
||||
# 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 <strong>strongly</strong></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
|
||||
|
|
|
@ -568,7 +568,7 @@ module ActionView
|
|||
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
|
||||
|
|
|
@ -108,3 +108,7 @@
|
|||
# The variable :count is also available
|
||||
body: "There were problems with the following fields:"
|
||||
|
||||
support:
|
||||
select:
|
||||
# default value for :prompt => true in FormOptionsHelper
|
||||
prompt: "Please select"
|
16
vendor/rails/actionpack/test/abstract_unit.rb
vendored
16
vendor/rails/actionpack/test/abstract_unit.rb
vendored
|
@ -43,3 +43,19 @@ ActionController::Base.view_paths = FIXTURE_LOAD_PATH
|
|||
CACHED_VIEW_PATHS = ActionView::Base.cache_template_loading? ?
|
||||
ActionController::Base.view_paths :
|
||||
ActionController::Base.view_paths.map {|path| ActionView::Template::EagerPath.new(path.to_s)}
|
||||
|
||||
class DummyMutex
|
||||
def lock
|
||||
@locked = true
|
||||
end
|
||||
|
||||
def unlock
|
||||
@locked = false
|
||||
end
|
||||
|
||||
def locked?
|
||||
@locked
|
||||
end
|
||||
end
|
||||
|
||||
ActionController::Reloader.default_lock = DummyMutex.new
|
|
@ -52,7 +52,7 @@ class PageCachingTest < ActionController::TestCase
|
|||
ActionController::Base.perform_caching = true
|
||||
|
||||
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'
|
||||
|
|
|
@ -118,4 +118,10 @@ class CookieTest < ActionController::TestCase
|
|||
get :delete_cookie_with_path
|
||||
assert_equal ["user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT"], @response.headers["Set-Cookie"]
|
||||
end
|
||||
|
||||
def test_cookies_persist_throughout_request
|
||||
get :authenticate
|
||||
cookies = @controller.send(:cookies)
|
||||
assert_equal 'david', cookies['user_name']
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,25 +2,17 @@ require 'abstract_unit'
|
|||
|
||||
class DispatcherTest < Test::Unit::TestCase
|
||||
Dispatcher = ActionController::Dispatcher
|
||||
Reloader = ActionController::Reloader
|
||||
|
||||
def setup
|
||||
ENV['REQUEST_METHOD'] = 'GET'
|
||||
|
||||
Dispatcher.middleware = ActionController::MiddlewareStack.new do |middleware|
|
||||
middlewares = File.expand_path(File.join(File.dirname(__FILE__), "../../lib/action_controller/middlewares.rb"))
|
||||
middleware.instance_eval(File.read(middlewares))
|
||||
end
|
||||
|
||||
# Clear callbacks as they are redefined by Dispatcher#define_dispatcher_callbacks
|
||||
Dispatcher.instance_variable_set("@prepare_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
Dispatcher.instance_variable_set("@before_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
Dispatcher.instance_variable_set("@after_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
|
||||
reset_dispatcher
|
||||
Dispatcher.stubs(:require_dependency)
|
||||
end
|
||||
|
||||
def teardown
|
||||
ENV.delete 'REQUEST_METHOD'
|
||||
reset_dispatcher
|
||||
end
|
||||
|
||||
def test_clears_dependencies_after_dispatch_if_in_loading_mode
|
||||
|
@ -41,6 +33,34 @@ class DispatcherTest < Test::Unit::TestCase
|
|||
dispatch
|
||||
end
|
||||
|
||||
def test_builds_middleware_stack_only_during_initialization_if_not_in_loading_mode
|
||||
dispatcher = create_dispatcher
|
||||
assert_not_nil dispatcher.instance_variable_get(:"@app")
|
||||
dispatcher.instance_variable_set(:"@app", lambda { |env| })
|
||||
dispatcher.expects(:build_middleware_stack).never
|
||||
dispatcher.call(nil)
|
||||
dispatcher.call(nil)
|
||||
end
|
||||
|
||||
def test_rebuilds_middleware_stack_on_every_request_if_in_loading_mode
|
||||
dispatcher = create_dispatcher(false)
|
||||
dispatcher.instance_variable_set(:"@app", lambda { |env| })
|
||||
dispatcher.expects(:build_middleware_stack).twice
|
||||
dispatcher.call(nil)
|
||||
Reloader.default_lock.unlock
|
||||
dispatcher.call(nil)
|
||||
end
|
||||
|
||||
def test_doesnt_wrap_call_in_reloader_if_not_in_loading_mode
|
||||
Reloader.expects(:run).never
|
||||
dispatch
|
||||
end
|
||||
|
||||
def test_wraps_call_in_reloader_if_in_loading_mode
|
||||
Reloader.expects(:run).once
|
||||
dispatch(false)
|
||||
end
|
||||
|
||||
# Stub out dispatch error logger
|
||||
class << Dispatcher
|
||||
def log_failsafe_exception(status, exception); end
|
||||
|
@ -99,6 +119,25 @@ class DispatcherTest < Test::Unit::TestCase
|
|||
Dispatcher.new.call({'rack.input' => StringIO.new('')})
|
||||
end
|
||||
|
||||
def create_dispatcher(cache_classes = true)
|
||||
Dispatcher.define_dispatcher_callbacks(cache_classes)
|
||||
Dispatcher.new
|
||||
end
|
||||
|
||||
def reset_dispatcher
|
||||
Dispatcher.middleware = ActionController::MiddlewareStack.new do |middleware|
|
||||
middlewares = File.expand_path(File.join(File.dirname(__FILE__), "../../lib/action_controller/middlewares.rb"))
|
||||
middleware.instance_eval(File.read(middlewares))
|
||||
end
|
||||
|
||||
# Clear callbacks as they are redefined by Dispatcher#define_dispatcher_callbacks
|
||||
Dispatcher.instance_variable_set("@prepare_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
Dispatcher.instance_variable_set("@before_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
Dispatcher.instance_variable_set("@after_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
|
||||
Dispatcher.define_dispatcher_callbacks(true)
|
||||
end
|
||||
|
||||
def assert_subclasses(howmany, klass, message = klass.subclasses.inspect)
|
||||
assert_equal howmany, klass.subclasses.size, message
|
||||
end
|
||||
|
|
|
@ -24,7 +24,8 @@ class FilterParamTest < Test::Unit::TestCase
|
|||
[{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'],
|
||||
[{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'],
|
||||
[{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana'],
|
||||
[{'baz'=>[{'foo'=>'baz'}]}, {'baz'=>[{'foo'=>'[FILTERED]'}]}, %w(foo)]]
|
||||
[{'baz'=>[{'foo'=>'baz'}]}, {'baz'=>[{'foo'=>'[FILTERED]'}]}, %w(foo)],
|
||||
[{'baz'=>[{'foo'=>'baz'}, 1, 2, 3]}, {'baz'=>[{'foo'=>'[FILTERED]'}, 1, 2, 3]}, %w(foo)]]
|
||||
|
||||
test_hashes.each do |before_filter, after_filter, filter_words|
|
||||
FilterParamController.filter_parameter_logging(*filter_words)
|
||||
|
|
|
@ -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"
|
||||
|
@ -13,6 +14,10 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
|
|||
render :text => 'Definitely Maybe'
|
||||
end
|
||||
|
||||
def show
|
||||
render :text => 'Only for loooooong credentials'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authenticate
|
||||
|
@ -28,6 +33,12 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
|
|||
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 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
|
|||
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 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
|
|||
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
|
||||
|
|
|
@ -138,10 +138,33 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
|
|||
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
|
||||
|
@ -199,7 +222,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class BaseRackTest < Test::Unit::TestCase
|
||||
class BaseRackTest < ActiveSupport::TestCase
|
||||
def setup
|
||||
@env = {
|
||||
"HTTP_MAX_FORWARDS" => "10",
|
||||
|
@ -261,6 +261,23 @@ class RackResponseTest < BaseRackTest
|
|||
body.each { |part| parts << part }
|
||||
assert_equal ["0", "1", "2", "3", "4"], parts
|
||||
end
|
||||
|
||||
def test_streaming_block_with_flush_is_deprecated
|
||||
@response.body = Proc.new do |response, output|
|
||||
5.times do |n|
|
||||
output.write(n)
|
||||
output.flush
|
||||
end
|
||||
end
|
||||
|
||||
assert_deprecated(/output.flush is no longer needed/) do
|
||||
@response.prepare!
|
||||
status, headers, body = @response.to_a
|
||||
|
||||
parts = []
|
||||
body.each { |part| parts << part }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class RackResponseHeadersTest < BaseRackTest
|
||||
|
|
|
@ -236,7 +236,7 @@ class RedirectTest < ActionController::TestCase
|
|||
def test_redirect_with_partial_params
|
||||
get :module_redirect
|
||||
|
||||
assert_deprecated do
|
||||
assert_deprecated(/test_redirect_with_partial_params/) do
|
||||
assert_redirected_to :action => 'hello_world'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,35 +22,62 @@ class ReloaderTests < ActiveSupport::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def setup_and_return_body(app = lambda { })
|
||||
Dispatcher.expects(:reload_application)
|
||||
reloader = Reloader.new(app)
|
||||
headers, status, body = reloader.call({ })
|
||||
body
|
||||
def setup
|
||||
@lock = Mutex.new
|
||||
end
|
||||
|
||||
def test_it_reloads_the_application_before_the_request
|
||||
def test_it_reloads_the_application_before_yielding
|
||||
Dispatcher.expects(:reload_application)
|
||||
reloader = Reloader.new(lambda {
|
||||
Reloader.run(@lock) do
|
||||
[200, { "Content-Type" => "text/html" }, [""]]
|
||||
})
|
||||
reloader.call({ })
|
||||
end
|
||||
end
|
||||
|
||||
def test_it_locks_before_yielding
|
||||
lock = DummyMutex.new
|
||||
Dispatcher.expects(:reload_application)
|
||||
Reloader.run(lock) do
|
||||
assert lock.locked?
|
||||
[200, { "Content-Type" => "text/html" }, [""]]
|
||||
end
|
||||
assert lock.locked?
|
||||
end
|
||||
|
||||
def test_it_unlocks_upon_calling_close_on_body
|
||||
lock = DummyMutex.new
|
||||
Dispatcher.expects(:reload_application)
|
||||
headers, status, body = Reloader.run(lock) do
|
||||
[200, { "Content-Type" => "text/html" }, [""]]
|
||||
end
|
||||
body.close
|
||||
assert !lock.locked?
|
||||
end
|
||||
|
||||
def test_it_unlocks_if_app_object_raises_exception
|
||||
lock = DummyMutex.new
|
||||
Dispatcher.expects(:reload_application)
|
||||
assert_raise(RuntimeError) do
|
||||
Reloader.run(lock) do
|
||||
raise "oh no!"
|
||||
end
|
||||
end
|
||||
assert !lock.locked?
|
||||
end
|
||||
|
||||
def test_returned_body_object_always_responds_to_close
|
||||
body = setup_and_return_body(lambda {
|
||||
status, headers, body = Reloader.run(@lock) do
|
||||
[200, { "Content-Type" => "text/html" }, [""]]
|
||||
})
|
||||
end
|
||||
assert body.respond_to?(:close)
|
||||
end
|
||||
|
||||
def test_returned_body_object_behaves_like_underlying_object
|
||||
body = setup_and_return_body(lambda {
|
||||
status, headers, body = Reloader.run(@lock) do
|
||||
b = MyBody.new
|
||||
b << "hello"
|
||||
b << "world"
|
||||
[200, { "Content-Type" => "text/html" }, b]
|
||||
})
|
||||
end
|
||||
assert_equal 2, body.size
|
||||
assert_equal "hello", body[0]
|
||||
assert_equal "world", body[1]
|
||||
|
@ -60,20 +87,20 @@ class ReloaderTests < ActiveSupport::TestCase
|
|||
|
||||
def test_it_calls_close_on_underlying_object_when_close_is_called_on_body
|
||||
close_called = false
|
||||
body = setup_and_return_body(lambda {
|
||||
status, headers, body = Reloader.run(@lock) do
|
||||
b = MyBody.new do
|
||||
close_called = true
|
||||
end
|
||||
[200, { "Content-Type" => "text/html" }, b]
|
||||
})
|
||||
end
|
||||
body.close
|
||||
assert close_called
|
||||
end
|
||||
|
||||
def test_returned_body_object_responds_to_all_methods_supported_by_underlying_object
|
||||
body = setup_and_return_body(lambda {
|
||||
status, headers, body = Reloader.run(@lock) do
|
||||
[200, { "Content-Type" => "text/html" }, MyBody.new]
|
||||
})
|
||||
end
|
||||
assert body.respond_to?(:size)
|
||||
assert body.respond_to?(:each)
|
||||
assert body.respond_to?(:foo)
|
||||
|
@ -82,16 +109,16 @@ class ReloaderTests < ActiveSupport::TestCase
|
|||
|
||||
def test_it_doesnt_clean_up_the_application_after_call
|
||||
Dispatcher.expects(:cleanup_application).never
|
||||
body = setup_and_return_body(lambda {
|
||||
status, headers, body = Reloader.run(@lock) do
|
||||
[200, { "Content-Type" => "text/html" }, MyBody.new]
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def test_it_cleans_up_the_application_when_close_is_called_on_body
|
||||
Dispatcher.expects(:cleanup_application)
|
||||
body = setup_and_return_body(lambda {
|
||||
status, headers, body = Reloader.run(@lock) do
|
||||
[200, { "Content-Type" => "text/html" }, MyBody.new]
|
||||
})
|
||||
end
|
||||
body.close
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,16 +30,36 @@ class JsonParamsParsingTest < ActionController::IntegrationTest
|
|||
)
|
||||
end
|
||||
|
||||
private
|
||||
def assert_parses(expected, actual, headers = {})
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.connect ':action', :controller => "json_params_parsing_test/test"
|
||||
test "logs error if parsing unsuccessful" do
|
||||
with_test_routing do
|
||||
begin
|
||||
$stderr = StringIO.new
|
||||
json = "[\"person]\": {\"name\": \"David\"}}"
|
||||
post "/parse", json, {'CONTENT_TYPE' => 'application/json'}
|
||||
assert_response :error
|
||||
$stderr.rewind && err = $stderr.read
|
||||
assert err =~ /Error occurred while parsing request parameters/
|
||||
ensure
|
||||
$stderr = STDERR
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def assert_parses(expected, actual, headers = {})
|
||||
with_test_routing do
|
||||
post "/parse", actual, headers
|
||||
assert_response :ok
|
||||
assert_equal(expected, TestController.last_request_parameters)
|
||||
end
|
||||
end
|
||||
|
||||
def with_test_routing
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.connect ':action', :controller => "json_params_parsing_test/test"
|
||||
end
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,6 +38,21 @@ class XmlParamsParsingTest < ActionController::IntegrationTest
|
|||
end
|
||||
end
|
||||
|
||||
test "logs error if parsing unsuccessful" do
|
||||
with_test_routing do
|
||||
begin
|
||||
$stderr = StringIO.new
|
||||
xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar></pineapple>"
|
||||
post "/parse", xml, default_headers
|
||||
assert_response :error
|
||||
$stderr.rewind && err = $stderr.read
|
||||
assert err =~ /Error occurred while parsing request parameters/
|
||||
ensure
|
||||
$stderr = STDERR
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "parses multiple files" do
|
||||
xml = <<-end_body
|
||||
<person>
|
||||
|
|
|
@ -155,10 +155,6 @@ module RequestForgeryProtectionTests
|
|||
def test_should_allow_xhr_post_without_token
|
||||
assert_nothing_raised { xhr :post, :index }
|
||||
end
|
||||
def test_should_not_allow_xhr_post_with_html_without_token
|
||||
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
|
||||
assert_raise(ActionController::InvalidAuthenticityToken) { xhr :post, :index }
|
||||
end
|
||||
|
||||
def test_should_allow_xhr_put_without_token
|
||||
assert_nothing_raised { xhr :put, :index }
|
||||
|
@ -168,6 +164,11 @@ module RequestForgeryProtectionTests
|
|||
assert_nothing_raised { xhr :delete, :index }
|
||||
end
|
||||
|
||||
def test_should_allow_xhr_post_with_encoded_form_content_type_without_token
|
||||
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
|
||||
assert_nothing_raised { xhr :post, :index }
|
||||
end
|
||||
|
||||
def test_should_allow_post_with_token
|
||||
post :index, :authenticity_token => @token
|
||||
assert_response :success
|
||||
|
|
|
@ -76,6 +76,50 @@ class ResourcesTest < ActionController::TestCase
|
|||
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'})
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require 'abstract_unit'
|
||||
require 'controller/fake_controllers'
|
||||
require 'action_controller/routing/route_set'
|
||||
|
||||
class MilestonesController < ActionController::Base
|
||||
def index() head :ok end
|
||||
|
@ -742,7 +743,7 @@ class MockController
|
|||
end
|
||||
end
|
||||
|
||||
class LegacyRouteSetTests < Test::Unit::TestCase
|
||||
class LegacyRouteSetTests < ActiveSupport::TestCase
|
||||
attr_reader :rs
|
||||
|
||||
def setup
|
||||
|
@ -758,6 +759,10 @@ class LegacyRouteSetTests < Test::Unit::TestCase
|
|||
@rs.clear!
|
||||
end
|
||||
|
||||
def test_routes_for_controller_and_action_deprecated
|
||||
assert_deprecated { @rs.routes_for_controller_and_action("controller", "action") }
|
||||
end
|
||||
|
||||
def test_default_setup
|
||||
@rs.draw {|m| m.connect ':controller/:action/:id' }
|
||||
assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content"))
|
||||
|
@ -1605,7 +1610,7 @@ class RouteTest < Test::Unit::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
class RouteSetTest < Test::Unit::TestCase
|
||||
class RouteSetTest < ActiveSupport::TestCase
|
||||
def set
|
||||
@set ||= ROUTING::RouteSet.new
|
||||
end
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# encoding: utf-8
|
||||
require 'abstract_unit'
|
||||
|
||||
module TestFileUtils
|
||||
def file_name() File.basename(__FILE__) end
|
||||
def file_path() File.expand_path(__FILE__) end
|
||||
def file_data() File.open(file_path, 'rb') { |f| f.read } end
|
||||
def file_data() @data ||= File.open(file_path, 'rb') { |f| f.read } end
|
||||
end
|
||||
|
||||
class SendFileController < ActionController::Base
|
||||
|
@ -15,6 +16,7 @@ class SendFileController < ActionController::Base
|
|||
|
||||
def file() send_file(file_path, options) end
|
||||
def data() send_data(file_data, options) end
|
||||
def multibyte_text_data() send_data("Кирилица\n祝您好運", options) end
|
||||
|
||||
def rescue_action(e) raise end
|
||||
end
|
||||
|
@ -49,6 +51,7 @@ class SendFileTest < ActionController::TestCase
|
|||
require 'stringio'
|
||||
output = StringIO.new
|
||||
output.binmode
|
||||
output.string.force_encoding(file_data.encoding) if output.string.respond_to?(:force_encoding)
|
||||
assert_nothing_raised { response.body.call(response, output) }
|
||||
assert_equal file_data, output.string
|
||||
end
|
||||
|
@ -158,4 +161,11 @@ class SendFileTest < ActionController::TestCase
|
|||
assert_equal ActionController::Base::DEFAULT_RENDER_STATUS_CODE, @response.status
|
||||
end
|
||||
end
|
||||
|
||||
def test_send_data_content_length_header
|
||||
@controller.headers = {}
|
||||
@controller.options = { :type => :text, :filename => 'file_with_utf8_text' }
|
||||
process('multibyte_text_data')
|
||||
assert_equal '29', @controller.headers['Content-Length']
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,6 +46,20 @@ class UrlRewriterTests < ActionController::TestCase
|
|||
)
|
||||
end
|
||||
|
||||
def test_anchor_should_call_to_param
|
||||
assert_equal(
|
||||
'http://test.host/c/a/i#anchor',
|
||||
@rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anchor'))
|
||||
)
|
||||
end
|
||||
|
||||
def test_anchor_should_be_cgi_escaped
|
||||
assert_equal(
|
||||
'http://test.host/c/a/i#anc%2Fhor',
|
||||
@rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anc/hor'))
|
||||
)
|
||||
end
|
||||
|
||||
def test_overwrite_params
|
||||
@params[:controller] = 'hi'
|
||||
@params[:action] = 'bye'
|
||||
|
@ -110,6 +124,18 @@ class UrlWriterTests < ActionController::TestCase
|
|||
)
|
||||
end
|
||||
|
||||
def test_anchor_should_call_to_param
|
||||
assert_equal('/c/a#anchor',
|
||||
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor'))
|
||||
)
|
||||
end
|
||||
|
||||
def test_anchor_should_be_cgi_escaped
|
||||
assert_equal('/c/a#anc%2Fhor',
|
||||
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anc/hor'))
|
||||
)
|
||||
end
|
||||
|
||||
def test_default_host
|
||||
add_host!
|
||||
assert_equal('http://www.basecamphq.com/c/a/i',
|
||||
|
@ -304,7 +330,7 @@ class UrlWriterTests < ActionController::TestCase
|
|||
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
|
||||
|
|
23
vendor/rails/actionpack/test/fixtures/public/absolute/test.css
vendored
Normal file
23
vendor/rails/actionpack/test/fixtures/public/absolute/test.css
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
/* bank.css */
|
||||
|
||||
/* robber.css */
|
||||
|
||||
/* version.1.0.css */
|
||||
|
||||
/* bank.css */
|
||||
|
||||
/* bank.css */
|
||||
|
||||
/* robber.css */
|
||||
|
||||
/* version.1.0.css */
|
||||
|
||||
/* bank.css */
|
||||
|
||||
/* robber.css */
|
||||
|
||||
/* version.1.0.css */
|
||||
|
||||
/* robber.css */
|
||||
|
||||
/* version.1.0.css */
|
63
vendor/rails/actionpack/test/fixtures/public/absolute/test.js
vendored
Normal file
63
vendor/rails/actionpack/test/fixtures/public/absolute/test.js
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
// prototype js
|
||||
|
||||
// effects js
|
||||
|
||||
// dragdrop js
|
||||
|
||||
// controls js
|
||||
|
||||
// prototype js
|
||||
|
||||
// effects js
|
||||
|
||||
// dragdrop js
|
||||
|
||||
// controls js
|
||||
|
||||
// application js
|
||||
|
||||
// bank js
|
||||
|
||||
// robber js
|
||||
|
||||
// version.1.0 js
|
||||
|
||||
// application js
|
||||
|
||||
// bank js
|
||||
|
||||
// prototype js
|
||||
|
||||
// effects js
|
||||
|
||||
// dragdrop js
|
||||
|
||||
// controls js
|
||||
|
||||
// prototype js
|
||||
|
||||
// effects js
|
||||
|
||||
// dragdrop js
|
||||
|
||||
// controls js
|
||||
|
||||
// application js
|
||||
|
||||
// bank js
|
||||
|
||||
// robber js
|
||||
|
||||
// version.1.0 js
|
||||
|
||||
// application js
|
||||
|
||||
// bank js
|
||||
|
||||
// robber js
|
||||
|
||||
// version.1.0 js
|
||||
|
||||
// robber js
|
||||
|
||||
// version.1.0 js
|
|
@ -143,6 +143,26 @@ class ScrollsController < ActionController::Base
|
|||
end
|
||||
entry.tag!('app:edited', Time.now)
|
||||
|
||||
entry.author do |author|
|
||||
author.name("DHH")
|
||||
end
|
||||
end
|
||||
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
|
||||
|
@ -194,6 +214,15 @@ class AtomFeedTest < ActionController::TestCase
|
|||
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"
|
||||
|
|
|
@ -145,6 +145,22 @@ class FormHelperTest < ActionView::TestCase
|
|||
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"))
|
||||
|
@ -295,6 +311,16 @@ class FormHelperTest < ActionView::TestCase
|
|||
)
|
||||
end
|
||||
|
||||
def test_radio_button_with_booleans
|
||||
assert_dom_equal('<input id="post_secret_true" name="post[secret]" type="radio" value="true" />',
|
||||
radio_button("post", "secret", true)
|
||||
)
|
||||
|
||||
assert_dom_equal('<input id="post_secret_false" name="post[secret]" type="radio" value="false" />',
|
||||
radio_button("post", "secret", false)
|
||||
)
|
||||
end
|
||||
|
||||
def test_text_area
|
||||
assert_dom_equal(
|
||||
'<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
|
||||
|
|
27
vendor/rails/actionpack/test/template/form_options_helper_i18n_test.rb
vendored
Normal file
27
vendor/rails/actionpack/test/template/form_options_helper_i18n_test.rb
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
require 'abstract_unit'
|
||||
|
||||
class FormOptionsHelperI18nTests < ActionView::TestCase
|
||||
tests ActionView::Helpers::FormOptionsHelper
|
||||
|
||||
def setup
|
||||
@prompt_message = 'Select!'
|
||||
I18n.backend.send(:init_translations)
|
||||
I18n.backend.store_translations :en, :support => { :select => { :prompt => @prompt_message } }
|
||||
end
|
||||
|
||||
def teardown
|
||||
I18n.backend = I18n::Backend::Simple.new
|
||||
end
|
||||
|
||||
def test_select_with_prompt_true_translates_prompt_message
|
||||
I18n.expects(:translate).with('support.select.prompt', { :default => 'Please select' })
|
||||
select('post', 'category', [], :prompt => true)
|
||||
end
|
||||
|
||||
def test_select_with_translated_prompt
|
||||
assert_dom_equal(
|
||||
%Q(<select id="post_category" name="post[category]"><option value="">#{@prompt_message}</option>\n</select>),
|
||||
select('post', 'category', [], :prompt => true)
|
||||
)
|
||||
end
|
||||
end
|
|
@ -763,6 +763,40 @@ class FormOptionsHelperTest < ActionView::TestCase
|
|||
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="<Africa>"><option value="<sa>"><South Africa></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="<Africa>"><option value="<sa>"><South Africa></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
|
||||
|
|
|
@ -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
|
||||
|
@ -517,4 +522,22 @@ class TextHelperTest < ActionView::TestCase
|
|||
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 <strong>strongly</strong></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
|
||||
|
|
|
@ -219,6 +219,14 @@ class UrlHelperTest < ActionView::TestCase
|
|||
)
|
||||
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
|
||||
|
|
7
vendor/rails/activerecord/CHANGELOG
vendored
7
vendor/rails/activerecord/CHANGELOG
vendored
|
@ -1,3 +1,10 @@
|
|||
*2.3.4 (September 4, 2009)*
|
||||
|
||||
* PostgreSQL: XML datatype support. #1874 [Leonardo Borges]
|
||||
|
||||
* SQLite: deprecate the 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper]
|
||||
|
||||
|
||||
*2.3.3 (July 12, 2009)*
|
||||
|
||||
* Added :primary_key option to belongs_to associations. #765 [Szymon Nowak, Philip Hallstrom, Noel Rocha]
|
||||
|
|
28
vendor/rails/activerecord/Rakefile
vendored
28
vendor/rails/activerecord/Rakefile
vendored
|
@ -24,14 +24,30 @@ PKG_FILES = FileList[
|
|||
"lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "Rakefile"
|
||||
].exclude(/\bCVS\b|~$/)
|
||||
|
||||
def run_without_aborting(*tasks)
|
||||
errors = []
|
||||
|
||||
tasks.each do |task|
|
||||
begin
|
||||
Rake::Task[task].invoke
|
||||
rescue Exception
|
||||
errors << task
|
||||
end
|
||||
end
|
||||
|
||||
abort "Errors running #{errors.join(', ')}" if errors.any?
|
||||
end
|
||||
|
||||
desc 'Run mysql, sqlite, and postgresql tests by default'
|
||||
task :default => :test
|
||||
|
||||
desc 'Run mysql, sqlite, and postgresql tests'
|
||||
task :test => defined?(JRUBY_VERSION) ?
|
||||
task :test do
|
||||
tasks = defined?(JRUBY_VERSION) ?
|
||||
%w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
|
||||
%w(test_mysql test_sqlite3 test_postgresql)
|
||||
run_without_aborting(*tasks)
|
||||
end
|
||||
|
||||
for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb )
|
||||
Rake::TestTask.new("test_#{adapter}") { |t|
|
||||
|
@ -53,8 +69,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'
|
||||
|
@ -75,8 +91,8 @@ task :rebuild_mysql_databases => 'mysql:rebuild_databases'
|
|||
namespace :postgresql do
|
||||
desc 'Build the PostgreSQL test databases'
|
||||
task :build_databases do
|
||||
%x( createdb activerecord_unittest )
|
||||
%x( createdb activerecord_unittest2 )
|
||||
%x( createdb -E UTF8 activerecord_unittest )
|
||||
%x( createdb -E UTF8 activerecord_unittest2 )
|
||||
end
|
||||
|
||||
desc 'Drop the PostgreSQL test databases'
|
||||
|
@ -176,7 +192,7 @@ spec = Gem::Specification.new do |s|
|
|||
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
||||
end
|
||||
|
||||
s.add_dependency('activesupport', '= 2.3.3' + PKG_BUILD)
|
||||
s.add_dependency('activesupport', '= 2.3.4' + PKG_BUILD)
|
||||
|
||||
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
|
||||
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
|
||||
|
|
1
vendor/rails/activerecord/examples/.gitignore
vendored
Normal file
1
vendor/rails/activerecord/examples/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
performance.sql
|
162
vendor/rails/activerecord/examples/performance.rb
vendored
Executable file
162
vendor/rails/activerecord/examples/performance.rb
vendored
Executable file
|
@ -0,0 +1,162 @@
|
|||
#!/usr/bin/env ruby -KU
|
||||
|
||||
TIMES = (ENV['N'] || 10000).to_i
|
||||
|
||||
require 'rubygems'
|
||||
gem 'addressable', '~>2.0'
|
||||
gem 'faker', '~>0.3.1'
|
||||
gem 'rbench', '~>0.2.3'
|
||||
require 'addressable/uri'
|
||||
require 'faker'
|
||||
require 'rbench'
|
||||
|
||||
__DIR__ = File.dirname(__FILE__)
|
||||
$:.unshift "#{__DIR__}/../lib"
|
||||
require 'active_record'
|
||||
|
||||
conn = { :adapter => 'mysql',
|
||||
:database => 'activerecord_unittest',
|
||||
:username => 'rails', :password => '',
|
||||
:encoding => 'utf8' }
|
||||
|
||||
conn[:socket] = Pathname.glob(%w[
|
||||
/opt/local/var/run/mysql5/mysqld.sock
|
||||
/tmp/mysqld.sock
|
||||
/tmp/mysql.sock
|
||||
/var/mysql/mysql.sock
|
||||
/var/run/mysqld/mysqld.sock
|
||||
]).find { |path| path.socket? }
|
||||
|
||||
ActiveRecord::Base.establish_connection(conn)
|
||||
|
||||
class User < ActiveRecord::Base
|
||||
connection.create_table :users, :force => true do |t|
|
||||
t.string :name, :email
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
has_many :exhibits
|
||||
end
|
||||
|
||||
class Exhibit < ActiveRecord::Base
|
||||
connection.create_table :exhibits, :force => true do |t|
|
||||
t.belongs_to :user
|
||||
t.string :name
|
||||
t.text :notes
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
belongs_to :user
|
||||
|
||||
def look; attributes end
|
||||
def feel; look; user.name end
|
||||
|
||||
def self.look(exhibits) exhibits.each { |e| e.look } end
|
||||
def self.feel(exhibits) exhibits.each { |e| e.feel } end
|
||||
end
|
||||
|
||||
sqlfile = "#{__DIR__}/performance.sql"
|
||||
|
||||
if File.exists?(sqlfile)
|
||||
mysql_bin = %w[mysql mysql5].select { |bin| `which #{bin}`.length > 0 }
|
||||
`#{mysql_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} < #{sqlfile}`
|
||||
else
|
||||
puts 'Generating data...'
|
||||
|
||||
# pre-compute the insert statements and fake data compilation,
|
||||
# so the benchmarks below show the actual runtime for the execute
|
||||
# method, minus the setup steps
|
||||
|
||||
# Using the same paragraph for all exhibits because it is very slow
|
||||
# to generate unique paragraphs for all exhibits.
|
||||
notes = Faker::Lorem.paragraphs.join($/)
|
||||
today = Date.today
|
||||
|
||||
puts 'Inserting 10,000 users and exhibits...'
|
||||
10_000.times do
|
||||
user = User.create(
|
||||
:created_at => today,
|
||||
:name => Faker::Name.name,
|
||||
:email => Faker::Internet.email
|
||||
)
|
||||
|
||||
Exhibit.create(
|
||||
:created_at => today,
|
||||
:name => Faker::Company.name,
|
||||
:user => user,
|
||||
:notes => notes
|
||||
)
|
||||
end
|
||||
|
||||
mysqldump_bin = %w[mysqldump mysqldump5].select { |bin| `which #{bin}`.length > 0 }
|
||||
`#{mysqldump_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} exhibits users > #{sqlfile}`
|
||||
end
|
||||
|
||||
RBench.run(TIMES) do
|
||||
column :times
|
||||
column :ar
|
||||
|
||||
report 'Model#id', (TIMES * 100).ceil do
|
||||
ar_obj = Exhibit.find(1)
|
||||
|
||||
ar { ar_obj.id }
|
||||
end
|
||||
|
||||
report 'Model.new (instantiation)' do
|
||||
ar { Exhibit.new }
|
||||
end
|
||||
|
||||
report 'Model.new (setting attributes)' do
|
||||
attrs = { :name => 'sam' }
|
||||
ar { Exhibit.new(attrs) }
|
||||
end
|
||||
|
||||
report 'Model.first' do
|
||||
ar { Exhibit.first.look }
|
||||
end
|
||||
|
||||
report 'Model.all limit(100)', (TIMES / 10).ceil do
|
||||
ar { Exhibit.look Exhibit.all(:limit => 100) }
|
||||
end
|
||||
|
||||
report 'Model.all limit(100) with relationship', (TIMES / 10).ceil do
|
||||
ar { Exhibit.feel Exhibit.all(:limit => 100, :include => :user) }
|
||||
end
|
||||
|
||||
report 'Model.all limit(10,000)', (TIMES / 1000).ceil do
|
||||
ar { Exhibit.look Exhibit.all(:limit => 10000) }
|
||||
end
|
||||
|
||||
exhibit = {
|
||||
:name => Faker::Company.name,
|
||||
:notes => Faker::Lorem.paragraphs.join($/),
|
||||
:created_at => Date.today
|
||||
}
|
||||
|
||||
report 'Model.create' do
|
||||
ar { Exhibit.create(exhibit) }
|
||||
end
|
||||
|
||||
report 'Resource#attributes=' do
|
||||
attrs_first = { :name => 'sam' }
|
||||
attrs_second = { :name => 'tom' }
|
||||
ar { exhibit = Exhibit.new(attrs_first); exhibit.attributes = attrs_second }
|
||||
end
|
||||
|
||||
report 'Resource#update' do
|
||||
ar { Exhibit.first.update_attributes(:name => 'bob') }
|
||||
end
|
||||
|
||||
report 'Resource#destroy' do
|
||||
ar { Exhibit.first.destroy }
|
||||
end
|
||||
|
||||
report 'Model.transaction' do
|
||||
ar { Exhibit.transaction { Exhibit.new } }
|
||||
end
|
||||
|
||||
summary 'Total'
|
||||
end
|
||||
|
||||
ActiveRecord::Migration.drop_table "exhibits"
|
||||
ActiveRecord::Migration.drop_table "users"
|
|
@ -34,11 +34,13 @@ module ActiveRecord
|
|||
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
|
||||
HasManyThroughCantAssociateThroughHasManyReflection = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection', 'ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection')
|
||||
|
||||
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.")
|
||||
|
@ -410,6 +412,32 @@ module ActiveRecord
|
|||
# @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
|
||||
|
@ -759,7 +787,7 @@ module ActiveRecord
|
|||
# [: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
|
||||
|
@ -1241,7 +1269,11 @@ module ActiveRecord
|
|||
|
||||
if association_proxy_class == HasOneThroughAssociation
|
||||
association.create_through_record(new_value)
|
||||
if new_record?
|
||||
association_instance_set(reflection.name, new_value.nil? ? nil : association)
|
||||
else
|
||||
self.send(reflection.name, new_value)
|
||||
end
|
||||
else
|
||||
association.replace(new_value)
|
||||
association_instance_set(reflection.name, new_value.nil? ? nil : association)
|
||||
|
@ -1293,7 +1325,7 @@ module ActiveRecord
|
|||
|
||||
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
|
||||
|
@ -1838,7 +1870,7 @@ module ActiveRecord
|
|||
descendant
|
||||
end.flatten.compact
|
||||
|
||||
remove_duplicate_results!(reflection.class_name.constantize, parent_records, associations[name]) unless parent_records.empty?
|
||||
remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -208,6 +208,7 @@ module ActiveRecord
|
|||
# Note that this method will _always_ remove records from the database
|
||||
# ignoring the +:dependent+ option.
|
||||
def destroy(*records)
|
||||
records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)}
|
||||
remove_records(records) do |records, old_records|
|
||||
old_records.each { |record| record.destroy }
|
||||
end
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
module ActiveRecord
|
||||
module Associations
|
||||
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
|
||||
def initialize(owner, reflection)
|
||||
super
|
||||
@primary_key_list = {}
|
||||
end
|
||||
|
||||
def create(attributes = {})
|
||||
create_record(attributes) { |record| insert_record(record) }
|
||||
end
|
||||
|
@ -17,6 +22,12 @@ module ActiveRecord
|
|||
@reflection.reset_column_information
|
||||
end
|
||||
|
||||
def has_primary_key?
|
||||
return @has_primary_key unless @has_primary_key.nil?
|
||||
@has_primary_key = (ActiveRecord::Base.connection.supports_primary_key? &&
|
||||
ActiveRecord::Base.connection.primary_key(@reflection.options[:join_table]))
|
||||
end
|
||||
|
||||
protected
|
||||
def construct_find_options!(options)
|
||||
options[:joins] = @join_sql
|
||||
|
@ -29,6 +40,11 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def insert_record(record, force = true, validate = true)
|
||||
if has_primary_key?
|
||||
raise ActiveRecord::ConfigurationError,
|
||||
"Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})."
|
||||
end
|
||||
|
||||
if record.new_record?
|
||||
if force
|
||||
record.save!
|
||||
|
|
|
@ -74,6 +74,7 @@ module ActiveRecord
|
|||
"#{@reflection.primary_key_name} = NULL",
|
||||
"#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
|
||||
)
|
||||
@owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -17,7 +17,17 @@ module ActiveRecord
|
|||
|
||||
def create(attrs = nil)
|
||||
transaction do
|
||||
self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association)
|
||||
object = if attrs
|
||||
@reflection.klass.send(:with_scope, :create => attrs) {
|
||||
@reflection.create_association
|
||||
}
|
||||
else
|
||||
@reflection.create_association
|
||||
end
|
||||
raise_on_type_mismatch(object)
|
||||
add_record_to_target_with_callbacks(object) do |r|
|
||||
insert_record(object, false)
|
||||
end
|
||||
object
|
||||
end
|
||||
end
|
||||
|
@ -44,7 +54,7 @@ module ActiveRecord
|
|||
options[:select] = construct_select(options[:select])
|
||||
options[:from] ||= construct_from
|
||||
options[:joins] = construct_joins(options[:joins])
|
||||
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
|
||||
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include]
|
||||
end
|
||||
|
||||
def insert_record(record, force = true, validate = true)
|
||||
|
@ -96,7 +106,7 @@ module ActiveRecord
|
|||
# Construct attributes for :through pointing to owner and associate.
|
||||
def construct_join_attributes(associate)
|
||||
# TODO: revist this to allow it for deletion, supposing dependent option is supported
|
||||
raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many
|
||||
raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
|
||||
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
|
||||
if @reflection.options[:source_type]
|
||||
join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
|
||||
|
|
|
@ -9,8 +9,14 @@ module ActiveRecord
|
|||
|
||||
if current_object
|
||||
new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
|
||||
elsif new_value
|
||||
if @owner.new_record?
|
||||
self.target = new_value
|
||||
through_association = @owner.send(:association_instance_get, @reflection.through_reflection.name)
|
||||
through_association.build(construct_join_attributes(new_value))
|
||||
else
|
||||
@owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value))) if new_value
|
||||
@owner.send(@reflection.through_reflection.name, klass.create(construct_join_attributes(new_value)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -249,9 +249,10 @@ module ActiveRecord
|
|||
unless valid = association.valid?
|
||||
if reflection.options[:autosave]
|
||||
unless association.marked_for_destruction?
|
||||
association.errors.each do |attribute, message|
|
||||
attribute = "#{reflection.name}_#{attribute}"
|
||||
errors.add(attribute, message) unless errors.on(attribute)
|
||||
association.errors.each_error do |attribute, error|
|
||||
error = error.dup
|
||||
error.attribute = "#{reflection.name}_#{attribute}"
|
||||
errors.add(error) unless errors.on(error.attribute)
|
||||
end
|
||||
end
|
||||
else
|
||||
|
|
|
@ -1364,7 +1364,7 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
defaults << options[:default] if options[:default]
|
||||
defaults.flatten!
|
||||
defaults << attribute_key_name.humanize
|
||||
defaults << attribute_key_name.to_s.humanize
|
||||
options[:count] ||= 1
|
||||
I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes]))
|
||||
end
|
||||
|
@ -2294,20 +2294,24 @@ module ActiveRecord #:nodoc:
|
|||
# And for value objects on a composed_of relationship:
|
||||
# { :address => Address.new("123 abc st.", "chicago") }
|
||||
# # => "address_street='123 abc st.' and address_city='chicago'"
|
||||
def sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name)
|
||||
def sanitize_sql_hash_for_conditions(attrs, default_table_name = quoted_table_name)
|
||||
attrs = expand_hash_conditions_for_aggregates(attrs)
|
||||
|
||||
conditions = attrs.map do |attr, value|
|
||||
table_name = default_table_name
|
||||
|
||||
unless value.is_a?(Hash)
|
||||
attr = attr.to_s
|
||||
|
||||
# Extract table name from qualified attribute names.
|
||||
if attr.include?('.')
|
||||
table_name, attr = attr.split('.', 2)
|
||||
table_name = connection.quote_table_name(table_name)
|
||||
attr_table_name, attr = attr.split('.', 2)
|
||||
attr_table_name = connection.quote_table_name(attr_table_name)
|
||||
else
|
||||
attr_table_name = table_name
|
||||
end
|
||||
|
||||
attribute_condition("#{table_name}.#{connection.quote_column_name(attr)}", value)
|
||||
attribute_condition("#{attr_table_name}.#{connection.quote_column_name(attr)}", value)
|
||||
else
|
||||
sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
|
||||
end
|
||||
|
@ -3028,16 +3032,22 @@ module ActiveRecord #:nodoc:
|
|||
|
||||
def execute_callstack_for_multiparameter_attributes(callstack)
|
||||
errors = []
|
||||
callstack.each do |name, values|
|
||||
callstack.each do |name, values_with_empty_parameters|
|
||||
begin
|
||||
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
|
||||
# in order to allow a date to be set without a year, we must keep the empty values.
|
||||
# Otherwise, we wouldn't be able to distinguish it from a date with an empty day.
|
||||
values = values_with_empty_parameters.reject(&:nil?)
|
||||
|
||||
if values.empty?
|
||||
send(name + "=", nil)
|
||||
else
|
||||
|
||||
value = if Time == klass
|
||||
instantiate_time_object(name, values)
|
||||
elsif Date == klass
|
||||
begin
|
||||
values = values_with_empty_parameters.collect do |v| v.nil? ? 1 : v end
|
||||
Date.new(*values)
|
||||
rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
|
||||
instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
|
||||
|
@ -3065,10 +3075,8 @@ module ActiveRecord #:nodoc:
|
|||
attribute_name = multiparameter_name.split("(").first
|
||||
attributes[attribute_name] = [] unless attributes.include?(attribute_name)
|
||||
|
||||
unless value.empty?
|
||||
attributes[attribute_name] <<
|
||||
[ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
|
||||
end
|
||||
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
||||
attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_value ]
|
||||
end
|
||||
|
||||
attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
|
||||
|
|
|
@ -190,6 +190,8 @@ module ActiveRecord
|
|||
sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
|
||||
if options[:from]
|
||||
sql << " FROM #{options[:from]} "
|
||||
elsif scope && scope[:from] && !use_workaround
|
||||
sql << " FROM #{scope[:from]} "
|
||||
else
|
||||
sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
|
||||
sql << " FROM #{connection.quote_table_name(table_name)} "
|
||||
|
|
|
@ -277,7 +277,6 @@ module ActiveRecord
|
|||
add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key
|
||||
column_sql
|
||||
end
|
||||
alias to_s :to_sql
|
||||
|
||||
private
|
||||
|
||||
|
@ -316,6 +315,20 @@ module ActiveRecord
|
|||
@base = base
|
||||
end
|
||||
|
||||
#Handles non supported datatypes - e.g. XML
|
||||
def method_missing(symbol, *args)
|
||||
if symbol.to_s == 'xml'
|
||||
xml_column_fallback(args)
|
||||
end
|
||||
end
|
||||
|
||||
def xml_column_fallback(*args)
|
||||
case @base.adapter_name.downcase
|
||||
when 'sqlite', 'mysql'
|
||||
options = args.extract_options!
|
||||
column(args[0], :text, options)
|
||||
end
|
||||
end
|
||||
# Appends a primary key definition to the table definition.
|
||||
# Can be called multiple times, but this is probably not a good idea.
|
||||
def primary_key(name)
|
||||
|
@ -508,7 +521,7 @@ module ActiveRecord
|
|||
# concatenated together. This string can then be prepended and appended to
|
||||
# to generate the final SQL to create the table.
|
||||
def to_sql
|
||||
@columns * ', '
|
||||
@columns.map(&:to_sql) * ', '
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -706,3 +719,4 @@ module ActiveRecord
|
|||
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ module ActiveRecord
|
|||
# See also TableDefinition#column for details on how to create columns.
|
||||
def create_table(table_name, options = {})
|
||||
table_definition = TableDefinition.new(self)
|
||||
table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name)) unless options[:id] == false
|
||||
table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
|
||||
|
||||
yield table_definition
|
||||
|
||||
|
@ -321,7 +321,7 @@ module ActiveRecord
|
|||
schema_migrations_table.column :version, :string, :null => false
|
||||
end
|
||||
add_index sm_table, :version, :unique => true,
|
||||
:name => 'unique_schema_migrations'
|
||||
:name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
|
||||
|
||||
# Backwards-compatibility: if we find schema_info, assume we've
|
||||
# migrated up to that point:
|
||||
|
|
|
@ -54,6 +54,13 @@ module ActiveRecord
|
|||
false
|
||||
end
|
||||
|
||||
# Can this adapter determine the primary key for tables not attached
|
||||
# to an ActiveRecord class, such as join tables? Backend specific, as
|
||||
# the abstract adapter always returns +false+.
|
||||
def supports_primary_key?
|
||||
false
|
||||
end
|
||||
|
||||
# Does this adapter support using DISTINCT within COUNT? This is +true+
|
||||
# for all adapters except sqlite.
|
||||
def supports_count_distinct?
|
||||
|
|
|
@ -52,12 +52,7 @@ module ActiveRecord
|
|||
socket = config[:socket]
|
||||
username = config[:username] ? config[:username].to_s : 'root'
|
||||
password = config[:password].to_s
|
||||
|
||||
if config.has_key?(:database)
|
||||
database = config[:database]
|
||||
else
|
||||
raise ArgumentError, "No database specified. Missing argument: database."
|
||||
end
|
||||
|
||||
# Require the MySQL driver and define Mysql::Result.all_hashes
|
||||
unless defined? Mysql
|
||||
|
@ -80,7 +75,7 @@ module ActiveRecord
|
|||
module ConnectionAdapters
|
||||
class MysqlColumn < Column #:nodoc:
|
||||
def extract_default(default)
|
||||
if type == :binary || type == :text
|
||||
if sql_type =~ /blob/i || type == :text
|
||||
if default.blank?
|
||||
return null ? nil : ''
|
||||
else
|
||||
|
@ -94,7 +89,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def has_default?
|
||||
return false if type == :binary || type == :text #mysql forbids defaults on blob and text columns
|
||||
return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
|
||||
super
|
||||
end
|
||||
|
||||
|
@ -212,6 +207,10 @@ module ActiveRecord
|
|||
true
|
||||
end
|
||||
|
||||
def supports_primary_key? #:nodoc:
|
||||
true
|
||||
end
|
||||
|
||||
def supports_savepoints? #:nodoc:
|
||||
true
|
||||
end
|
||||
|
@ -554,6 +553,12 @@ module ActiveRecord
|
|||
keys.length == 1 ? [keys.first, nil] : nil
|
||||
end
|
||||
|
||||
# Returns just a table's primary key
|
||||
def primary_key(table)
|
||||
pk_and_sequence = pk_and_sequence_for(table)
|
||||
pk_and_sequence && pk_and_sequence.first
|
||||
end
|
||||
|
||||
def case_sensitive_equality_operator
|
||||
"= BINARY"
|
||||
end
|
||||
|
@ -573,6 +578,10 @@ module ActiveRecord
|
|||
@connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
|
||||
end
|
||||
|
||||
@connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
|
||||
@connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
|
||||
@connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
|
||||
|
||||
@connection.real_connect(*@connection_options)
|
||||
|
||||
# reconnect must be set after real_connect is called, because real_connect sets it to false internally
|
||||
|
|
|
@ -39,6 +39,12 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
module ConnectionAdapters
|
||||
class TableDefinition
|
||||
def xml(*args)
|
||||
options = args.extract_options!
|
||||
column(args[0], 'xml', options)
|
||||
end
|
||||
end
|
||||
# PostgreSQL-specific extensions to column definitions in a table.
|
||||
class PostgreSQLColumn < Column #:nodoc:
|
||||
# Instantiates a new PostgreSQL column definition in a table.
|
||||
|
@ -99,7 +105,7 @@ module ActiveRecord
|
|||
:string
|
||||
# XML type
|
||||
when /^xml$/
|
||||
:string
|
||||
:xml
|
||||
# Arrays
|
||||
when /^\D+\[\]$/
|
||||
:string
|
||||
|
@ -194,7 +200,8 @@ module ActiveRecord
|
|||
:time => { :name => "time" },
|
||||
:date => { :name => "date" },
|
||||
:binary => { :name => "bytea" },
|
||||
:boolean => { :name => "boolean" }
|
||||
:boolean => { :name => "boolean" },
|
||||
:xml => { :name => "xml" }
|
||||
}
|
||||
|
||||
# Returns 'PostgreSQL' as adapter name for identification purposes.
|
||||
|
@ -249,6 +256,11 @@ module ActiveRecord
|
|||
true
|
||||
end
|
||||
|
||||
# Does PostgreSQL support finding primary key on non-ActiveRecord tables?
|
||||
def supports_primary_key? #:nodoc:
|
||||
true
|
||||
end
|
||||
|
||||
# Does PostgreSQL support standard conforming strings?
|
||||
def supports_standard_conforming_strings?
|
||||
# Temporarily set the client message level above error to prevent unintentional
|
||||
|
@ -364,7 +376,7 @@ module ActiveRecord
|
|||
if value.kind_of?(String) && column && column.type == :binary
|
||||
"#{quoted_string_prefix}'#{escape_bytea(value)}'"
|
||||
elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
|
||||
"xml '#{quote_string(value)}'"
|
||||
"xml E'#{quote_string(value)}'"
|
||||
elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
|
||||
# Not truly string input, so doesn't require (or allow) escape string syntax.
|
||||
"'#{value.to_s}'"
|
||||
|
@ -810,6 +822,12 @@ module ActiveRecord
|
|||
nil
|
||||
end
|
||||
|
||||
# Returns just a table's primary key
|
||||
def primary_key(table)
|
||||
pk_and_sequence = pk_and_sequence_for(table)
|
||||
pk_and_sequence && pk_and_sequence.first
|
||||
end
|
||||
|
||||
# Renames a table.
|
||||
def rename_table(name, new_name)
|
||||
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
|
||||
|
@ -1092,3 +1110,4 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -16,6 +16,10 @@ module ActiveRecord
|
|||
db.results_as_hash = true if defined? SQLite::Version
|
||||
db.type_translation = false
|
||||
|
||||
message = "Support for SQLite2Adapter and DeprecatedSQLiteAdapter has been removed from Rails 3. "
|
||||
message << "You should migrate to SQLite 3+ or use the plugin from git://github.com/rails/sqlite2_adapter.git with Rails 3."
|
||||
ActiveSupport::Deprecation.warn(message)
|
||||
|
||||
# "Downgrade" deprecated sqlite API
|
||||
if SQLite.const_defined?(:Version)
|
||||
ConnectionAdapters::SQLite2Adapter.new(db, logger, config)
|
||||
|
@ -27,6 +31,10 @@ module ActiveRecord
|
|||
|
||||
private
|
||||
def parse_sqlite_config!(config)
|
||||
if config.include?(:dbfile)
|
||||
ActiveSupport::Deprecation.warn "Please update config/database.yml to use 'database' instead of 'dbfile'"
|
||||
end
|
||||
|
||||
config[:database] ||= config[:dbfile]
|
||||
# Require database.
|
||||
unless config[:database]
|
||||
|
@ -104,6 +112,10 @@ module ActiveRecord
|
|||
true
|
||||
end
|
||||
|
||||
def supports_primary_key? #:nodoc:
|
||||
true
|
||||
end
|
||||
|
||||
def requires_reloading?
|
||||
true
|
||||
end
|
||||
|
|
|
@ -143,7 +143,7 @@ module ActiveRecord
|
|||
if partial_updates?
|
||||
# Serialized attributes should always be written in case they've been
|
||||
# changed in place.
|
||||
update_without_dirty(changed | self.class.serialized_attributes.keys)
|
||||
update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
|
||||
else
|
||||
update_without_dirty
|
||||
end
|
||||
|
|
|
@ -621,7 +621,8 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
|
|||
targets.each do |target|
|
||||
join_fixtures["#{label}_#{target}"] = Fixture.new(
|
||||
{ association.primary_key_name => row[primary_key_name],
|
||||
association.association_foreign_key => Fixtures.identify(target) }, nil)
|
||||
association.association_foreign_key => Fixtures.identify(target) },
|
||||
nil, @connection)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -710,7 +711,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
|
|||
raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
|
||||
end
|
||||
|
||||
self[name] = Fixture.new(data, model_class)
|
||||
self[name] = Fixture.new(data, model_class, @connection)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -723,7 +724,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
|
|||
reader.each do |row|
|
||||
data = {}
|
||||
row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
|
||||
self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class)
|
||||
self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class, @connection)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -761,7 +762,8 @@ class Fixture #:nodoc:
|
|||
|
||||
attr_reader :model_class
|
||||
|
||||
def initialize(fixture, model_class)
|
||||
def initialize(fixture, model_class, connection = ActiveRecord::Base.connection)
|
||||
@connection = connection
|
||||
@fixture = fixture
|
||||
@model_class = model_class.is_a?(Class) ? model_class : model_class.constantize rescue nil
|
||||
end
|
||||
|
@ -783,14 +785,14 @@ class Fixture #:nodoc:
|
|||
end
|
||||
|
||||
def key_list
|
||||
columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) }
|
||||
columns = @fixture.keys.collect{ |column_name| @connection.quote_column_name(column_name) }
|
||||
columns.join(", ")
|
||||
end
|
||||
|
||||
def value_list
|
||||
list = @fixture.inject([]) do |fixtures, (key, value)|
|
||||
col = model_class.columns_hash[key] if model_class.respond_to?(:ancestors) && model_class.ancestors.include?(ActiveRecord::Base)
|
||||
fixtures << ActiveRecord::Base.connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
|
||||
fixtures << @connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
|
||||
end
|
||||
list * ', '
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ module I18n
|
|||
|
||||
protected
|
||||
def interpolate_with_deprecated_syntax(locale, string, values = {})
|
||||
return string unless string.is_a?(String)
|
||||
return string unless string.is_a?(String) && !values.empty?
|
||||
|
||||
string = string.gsub(/%d|%s/) do |s|
|
||||
instead = DEPRECATED_INTERPOLATORS[s]
|
||||
|
|
|
@ -23,8 +23,12 @@ en:
|
|||
less_than_or_equal_to: "must be less than or equal to {{count}}"
|
||||
odd: "must be odd"
|
||||
even: "must be even"
|
||||
record_invalid: "Validation failed: {{errors}}"
|
||||
# Append your own errors here or at the model/attributes scope.
|
||||
|
||||
full_messages:
|
||||
format: "{{attribute}} {{message}}"
|
||||
|
||||
# You can define own errors for models or model attributes.
|
||||
# The values :model, :attribute and :value are always available for interpolation.
|
||||
#
|
||||
|
|
|
@ -89,12 +89,7 @@ module ActiveRecord
|
|||
when Hash
|
||||
options
|
||||
when Proc
|
||||
case parent_scope
|
||||
when Scope
|
||||
with_scope(:find => parent_scope.proxy_options) { options.call(*args) }
|
||||
else
|
||||
options.call(*args)
|
||||
end
|
||||
end, &block)
|
||||
end
|
||||
(class << self; self end).instance_eval do
|
||||
|
|
|
@ -297,7 +297,7 @@ module ActiveRecord
|
|||
raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
|
||||
end
|
||||
|
||||
unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
|
||||
unless [:belongs_to, :has_many, :has_one].include?(source_reflection.macro) && source_reflection.options[:through].nil?
|
||||
raise HasManyThroughSourceAssociationMacroError.new(self)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -84,7 +84,6 @@ HEADER
|
|||
elsif @connection.respond_to?(:primary_key)
|
||||
pk = @connection.primary_key(table)
|
||||
end
|
||||
pk ||= 'id'
|
||||
|
||||
tbl.print " create_table #{table.inspect}"
|
||||
if columns.detect { |c| c.name == pk }
|
||||
|
|
|
@ -74,12 +74,14 @@ module ActiveRecord #:nodoc:
|
|||
# {"comments": [{"body": "Don't think too hard"}],
|
||||
# "title": "So I was thinking"}]}
|
||||
def to_json(options = {})
|
||||
hash = Serializer.new(self, options).serializable_record
|
||||
hash = { self.class.model_name.element => hash } if include_root_in_json
|
||||
ActiveSupport::JSON.encode(hash)
|
||||
super
|
||||
end
|
||||
|
||||
def as_json(options = nil) self end #:nodoc:
|
||||
def as_json(options = nil) #:nodoc:
|
||||
hash = Serializer.new(self, options).serializable_record
|
||||
hash = { self.class.model_name.element => hash } if include_root_in_json
|
||||
hash
|
||||
end
|
||||
|
||||
def from_json(json)
|
||||
self.attributes = ActiveSupport::JSON.decode(json)
|
||||
|
|
|
@ -178,7 +178,7 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
def root
|
||||
root = (options[:root] || @record.class.to_s.underscore).to_s
|
||||
root = (options[:root] || @record.class.model_name.singular).to_s
|
||||
reformat_name(root)
|
||||
end
|
||||
|
||||
|
@ -320,7 +320,11 @@ module ActiveRecord #:nodoc:
|
|||
|
||||
protected
|
||||
def compute_type
|
||||
type = @record.class.serialized_attributes.has_key?(name) ? :yaml : @record.class.columns_hash[name].type
|
||||
type = if @record.class.serialized_attributes.has_key?(name)
|
||||
:yaml
|
||||
else
|
||||
@record.class.columns_hash[name].try(:type)
|
||||
end
|
||||
|
||||
case type
|
||||
when :text
|
||||
|
|
|
@ -10,7 +10,114 @@ module ActiveRecord
|
|||
attr_reader :record
|
||||
def initialize(record)
|
||||
@record = record
|
||||
super("Validation failed: #{@record.errors.full_messages.join(", ")}")
|
||||
errors = @record.errors.full_messages.join(I18n.t('support.array.words_connector', :default => ', '))
|
||||
super(I18n.t('activerecord.errors.messages.record_invalid', :errors => errors))
|
||||
end
|
||||
end
|
||||
|
||||
class Error
|
||||
attr_accessor :base, :attribute, :type, :message, :options
|
||||
|
||||
def initialize(base, attribute, type = nil, options = {})
|
||||
self.base = base
|
||||
self.attribute = attribute
|
||||
self.type = type || :invalid
|
||||
self.options = options
|
||||
self.message = options.delete(:message) || self.type
|
||||
end
|
||||
|
||||
def message
|
||||
generate_message(@message, options.dup)
|
||||
end
|
||||
|
||||
def full_message
|
||||
attribute.to_s == 'base' ? message : generate_full_message(message, options.dup)
|
||||
end
|
||||
|
||||
alias :to_s :message
|
||||
|
||||
def value
|
||||
@base.respond_to?(attribute) ? @base.send(attribute) : nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Translates an error message in it's default scope (<tt>activerecord.errrors.messages</tt>).
|
||||
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there,
|
||||
# it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the
|
||||
# default message (e.g. <tt>activerecord.errors.messages.MESSAGE</tt>). The translated model name,
|
||||
# translated attribute name and the value are available for interpolation.
|
||||
#
|
||||
# When using inheritence in your models, it will check all the inherited models too, but only if the model itself
|
||||
# hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt>
|
||||
# error +message+ for the <tt>title</tt> +attribute+, it looks for these translations:
|
||||
#
|
||||
# <ol>
|
||||
# <li><tt>activerecord.errors.models.admin.attributes.title.blank</tt></li>
|
||||
# <li><tt>activerecord.errors.models.admin.blank</tt></li>
|
||||
# <li><tt>activerecord.errors.models.user.attributes.title.blank</tt></li>
|
||||
# <li><tt>activerecord.errors.models.user.blank</tt></li>
|
||||
# <li><tt>activerecord.errors.messages.blank</tt></li>
|
||||
# <li>any default you provided through the +options+ hash (in the activerecord.errors scope)</li>
|
||||
# </ol>
|
||||
def generate_message(message, options = {})
|
||||
keys = @base.class.self_and_descendants_from_active_record.map do |klass|
|
||||
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
|
||||
:"models.#{klass.name.underscore}.#{message}" ]
|
||||
end.flatten
|
||||
|
||||
keys << options.delete(:default)
|
||||
keys << :"messages.#{message}"
|
||||
keys << message if message.is_a?(String)
|
||||
keys << @type unless @type == message
|
||||
keys.compact!
|
||||
|
||||
options.reverse_merge! :default => keys,
|
||||
:scope => [:activerecord, :errors],
|
||||
:model => @base.class.human_name,
|
||||
:attribute => @base.class.human_attribute_name(attribute.to_s),
|
||||
:value => value
|
||||
|
||||
I18n.translate(keys.shift, options)
|
||||
end
|
||||
|
||||
# Wraps an error message into a full_message format.
|
||||
#
|
||||
# The default full_message format for any locale is <tt>"{{attribute}} {{message}}"</tt>.
|
||||
# One can specify locale specific default full_message format by storing it as a
|
||||
# translation for the key <tt>:"activerecord.errors.full_messages.format"</tt>.
|
||||
#
|
||||
# Additionally one can specify a validation specific error message format by
|
||||
# storing a translation for <tt>:"activerecord.errors.full_messages.[message_key]"</tt>.
|
||||
# E.g. the full_message format for any validation that uses :blank as a message
|
||||
# key (such as validates_presence_of) can be stored to <tt>:"activerecord.errors.full_messages.blank".</tt>
|
||||
#
|
||||
# Because the message key used by a validation can be overwritten on the
|
||||
# <tt>validates_*</tt> class macro level one can customize the full_message format for
|
||||
# any particular validation:
|
||||
#
|
||||
# # app/models/article.rb
|
||||
# class Article < ActiveRecord::Base
|
||||
# validates_presence_of :title, :message => :"title.blank"
|
||||
# end
|
||||
#
|
||||
# # config/locales/en.yml
|
||||
# en:
|
||||
# activerecord:
|
||||
# errors:
|
||||
# full_messages:
|
||||
# title:
|
||||
# blank: This title is screwed!
|
||||
def generate_full_message(message, options = {})
|
||||
options.reverse_merge! :message => self.message,
|
||||
:model => @base.class.human_name,
|
||||
:attribute => @base.class.human_attribute_name(attribute.to_s),
|
||||
:value => value
|
||||
|
||||
key = :"full_messages.#{@message}"
|
||||
defaults = [:'full_messages.format', '{{attribute}} {{message}}']
|
||||
|
||||
I18n.t(key, options.merge(:default => defaults, :scope => [:activerecord, :errors]))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -43,11 +150,19 @@ module ActiveRecord
|
|||
# error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
|
||||
# If no +messsage+ is supplied, :invalid is assumed.
|
||||
# If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
|
||||
def add(attribute, message = nil, options = {})
|
||||
message ||= :invalid
|
||||
message = generate_message(attribute, message, options) if message.is_a?(Symbol)
|
||||
# def add(attribute, message = nil, options = {})
|
||||
# message ||= :invalid
|
||||
# message = generate_message(attribute, message, options)) if message.is_a?(Symbol)
|
||||
# @errors[attribute.to_s] ||= []
|
||||
# @errors[attribute.to_s] << message
|
||||
# end
|
||||
|
||||
def add(error_or_attr, message = nil, options = {})
|
||||
error, attribute = error_or_attr.is_a?(Error) ? [error_or_attr, error_or_attr.attribute] : [nil, error_or_attr]
|
||||
options[:message] = options.delete(:default) if options.has_key?(:default)
|
||||
|
||||
@errors[attribute.to_s] ||= []
|
||||
@errors[attribute.to_s] << message
|
||||
@errors[attribute.to_s] << (error || Error.new(@base, attribute, message, options))
|
||||
end
|
||||
|
||||
# Will add an error message to each of the attributes in +attributes+ that is empty.
|
||||
|
@ -67,49 +182,6 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
# Translates an error message in it's default scope (<tt>activerecord.errrors.messages</tt>).
|
||||
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there,
|
||||
# it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the
|
||||
# default message (e.g. <tt>activerecord.errors.messages.MESSAGE</tt>). The translated model name,
|
||||
# translated attribute name and the value are available for interpolation.
|
||||
#
|
||||
# When using inheritence in your models, it will check all the inherited models too, but only if the model itself
|
||||
# hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt>
|
||||
# error +message+ for the <tt>title</tt> +attribute+, it looks for these translations:
|
||||
#
|
||||
# <ol>
|
||||
# <li><tt>activerecord.errors.models.admin.attributes.title.blank</tt></li>
|
||||
# <li><tt>activerecord.errors.models.admin.blank</tt></li>
|
||||
# <li><tt>activerecord.errors.models.user.attributes.title.blank</tt></li>
|
||||
# <li><tt>activerecord.errors.models.user.blank</tt></li>
|
||||
# <li><tt>activerecord.errors.messages.blank</tt></li>
|
||||
# <li>any default you provided through the +options+ hash (in the activerecord.errors scope)</li>
|
||||
# </ol>
|
||||
def generate_message(attribute, message = :invalid, options = {})
|
||||
|
||||
message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
|
||||
|
||||
defaults = @base.class.self_and_descendants_from_active_record.map do |klass|
|
||||
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
|
||||
:"models.#{klass.name.underscore}.#{message}" ]
|
||||
end
|
||||
|
||||
defaults << options.delete(:default)
|
||||
defaults = defaults.compact.flatten << :"messages.#{message}"
|
||||
|
||||
key = defaults.shift
|
||||
value = @base.respond_to?(attribute) ? @base.send(attribute) : nil
|
||||
|
||||
options = { :default => defaults,
|
||||
:model => @base.class.human_name,
|
||||
:attribute => @base.class.human_attribute_name(attribute.to_s),
|
||||
:value => value,
|
||||
:scope => [:activerecord, :errors]
|
||||
}.merge(options)
|
||||
|
||||
I18n.translate(key, options)
|
||||
end
|
||||
|
||||
# Returns true if the specified +attribute+ has errors associated with it.
|
||||
#
|
||||
# class Company < ActiveRecord::Base
|
||||
|
@ -138,8 +210,9 @@ module ActiveRecord
|
|||
# company.errors.on(:email) # => "can't be blank"
|
||||
# company.errors.on(:address) # => nil
|
||||
def on(attribute)
|
||||
errors = @errors[attribute.to_s]
|
||||
return nil if errors.nil?
|
||||
attribute = attribute.to_s
|
||||
return nil unless @errors.has_key?(attribute)
|
||||
errors = @errors[attribute].map(&:to_s)
|
||||
errors.size == 1 ? errors.first : errors
|
||||
end
|
||||
|
||||
|
@ -163,7 +236,11 @@ module ActiveRecord
|
|||
# # name - can't be blank
|
||||
# # address - can't be blank
|
||||
def each
|
||||
@errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
|
||||
@errors.each_key { |attr| @errors[attr].each { |error| yield attr, error.message } }
|
||||
end
|
||||
|
||||
def each_error
|
||||
@errors.each_key { |attr| @errors[attr].each { |error| yield attr, error } }
|
||||
end
|
||||
|
||||
# Yields each full error message added. So <tt>Person.errors.add("first_name", "can't be empty")</tt> will be returned
|
||||
|
@ -194,22 +271,10 @@ module ActiveRecord
|
|||
# company.errors.full_messages # =>
|
||||
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
|
||||
def full_messages(options = {})
|
||||
full_messages = []
|
||||
|
||||
@errors.each_key do |attr|
|
||||
@errors[attr].each do |message|
|
||||
next unless message
|
||||
|
||||
if attr == "base"
|
||||
full_messages << message
|
||||
else
|
||||
attr_name = @base.class.human_attribute_name(attr)
|
||||
full_messages << attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') + message
|
||||
@errors.values.inject([]) do |full_messages, errors|
|
||||
full_messages + errors.map { |error| error.full_message }
|
||||
end
|
||||
end
|
||||
end
|
||||
full_messages
|
||||
end
|
||||
|
||||
# Returns true if no errors have been added.
|
||||
def empty?
|
||||
|
@ -255,6 +320,10 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def generate_message(attribute, message = :invalid, options = {})
|
||||
ActiveSupport::Deprecation.warn("ActiveRecord::Errors#generate_message has been deprecated. Please use ActiveRecord::Error#generate_message.")
|
||||
Error.new(@base, attribute, message, options).to_s
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -726,7 +795,7 @@ module ActiveRecord
|
|||
comparison_operator = "IS ?"
|
||||
elsif column.text?
|
||||
comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
|
||||
value = column.limit ? value.to_s[0, column.limit] : value.to_s
|
||||
value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s
|
||||
else
|
||||
comparison_operator = "= ?"
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@ module ActiveRecord
|
|||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 3
|
||||
TINY = 3
|
||||
TINY = 4
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
|
|
@ -63,6 +63,18 @@ class AdapterTest < ActiveRecord::TestCase
|
|||
def test_show_nonexistent_variable_returns_nil
|
||||
assert_nil @connection.show_variable('foo_bar_baz')
|
||||
end
|
||||
|
||||
def test_not_specifying_database_name_for_cross_database_selects
|
||||
assert_nothing_raised do
|
||||
ActiveRecord::Base.establish_connection({
|
||||
:adapter => 'mysql',
|
||||
:username => 'rails'
|
||||
})
|
||||
ActiveRecord::Base.connection.execute "SELECT activerecord_unittest.pirates.*, activerecord_unittest2.courses.* FROM activerecord_unittest.pirates, activerecord_unittest2.courses"
|
||||
end
|
||||
|
||||
ActiveRecord::Base.establish_connection 'arunit'
|
||||
end
|
||||
end
|
||||
|
||||
if current_adapter?(:PostgreSQLAdapter)
|
||||
|
|
|
@ -249,24 +249,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal 1, Topic.find(topic.id)[:replies_count]
|
||||
end
|
||||
|
||||
def test_belongs_to_counter_after_save
|
||||
topic = Topic.create("title" => "monday night")
|
||||
topic.replies.create("title" => "re: monday night", "content" => "football")
|
||||
assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
|
||||
|
||||
topic.save
|
||||
assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
|
||||
end
|
||||
|
||||
def test_belongs_to_counter_after_update_attributes
|
||||
topic = Topic.create("title" => "37s")
|
||||
topic.replies.create("title" => "re: 37s", "content" => "rails")
|
||||
assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
|
||||
|
||||
topic.update_attributes("title" => "37signals")
|
||||
assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
|
||||
end
|
||||
|
||||
def test_assignment_before_child_saved
|
||||
final_cut = Client.new("name" => "Final Cut")
|
||||
firm = Firm.find(1)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'cases/helper'
|
||||
require 'models/author'
|
||||
require 'models/post'
|
||||
require 'models/author'
|
||||
require 'models/comment'
|
||||
require 'models/category'
|
||||
require 'models/categorization'
|
||||
|
|
56
vendor/rails/activerecord/test/cases/associations/habtm_join_table_test.rb
vendored
Normal file
56
vendor/rails/activerecord/test/cases/associations/habtm_join_table_test.rb
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
require 'cases/helper'
|
||||
|
||||
class MyReader < ActiveRecord::Base
|
||||
has_and_belongs_to_many :my_books
|
||||
end
|
||||
|
||||
class MyBook < ActiveRecord::Base
|
||||
has_and_belongs_to_many :my_readers
|
||||
end
|
||||
|
||||
class HabtmJoinTableTest < ActiveRecord::TestCase
|
||||
def setup
|
||||
ActiveRecord::Base.connection.create_table :my_books, :force => true do |t|
|
||||
t.string :name
|
||||
end
|
||||
assert ActiveRecord::Base.connection.table_exists?(:my_books)
|
||||
|
||||
ActiveRecord::Base.connection.create_table :my_readers, :force => true do |t|
|
||||
t.string :name
|
||||
end
|
||||
assert ActiveRecord::Base.connection.table_exists?(:my_readers)
|
||||
|
||||
ActiveRecord::Base.connection.create_table :my_books_my_readers, :force => true do |t|
|
||||
t.integer :my_book_id
|
||||
t.integer :my_reader_id
|
||||
end
|
||||
assert ActiveRecord::Base.connection.table_exists?(:my_books_my_readers)
|
||||
end
|
||||
|
||||
def teardown
|
||||
ActiveRecord::Base.connection.drop_table :my_books
|
||||
ActiveRecord::Base.connection.drop_table :my_readers
|
||||
ActiveRecord::Base.connection.drop_table :my_books_my_readers
|
||||
end
|
||||
|
||||
uses_transaction :test_should_raise_exception_when_join_table_has_a_primary_key
|
||||
def test_should_raise_exception_when_join_table_has_a_primary_key
|
||||
if ActiveRecord::Base.connection.supports_primary_key?
|
||||
assert_raise ActiveRecord::ConfigurationError do
|
||||
jaime = MyReader.create(:name=>"Jaime")
|
||||
jaime.my_books << MyBook.create(:name=>'Great Expectations')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
uses_transaction :test_should_cache_result_of_primary_key_check
|
||||
def test_should_cache_result_of_primary_key_check
|
||||
if ActiveRecord::Base.connection.supports_primary_key?
|
||||
ActiveRecord::Base.connection.stubs(:primary_key).with('my_books_my_readers').returns(false).once
|
||||
weaz = MyReader.create(:name=>'Weaz')
|
||||
|
||||
weaz.my_books << MyBook.create(:name=>'Great Expectations')
|
||||
weaz.my_books << MyBook.create(:name=>'Greater Expectations')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,11 +10,12 @@ require 'models/author'
|
|||
require 'models/comment'
|
||||
require 'models/person'
|
||||
require 'models/reader'
|
||||
require 'models/tagging'
|
||||
|
||||
class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||
fixtures :accounts, :categories, :companies, :developers, :projects,
|
||||
:developers_projects, :topics, :authors, :comments, :author_addresses,
|
||||
:people, :posts, :readers
|
||||
:people, :posts, :readers, :taggings
|
||||
|
||||
def setup
|
||||
Client.destroyed_client_ids.clear
|
||||
|
@ -279,6 +280,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }])
|
||||
end
|
||||
|
||||
def test_find_all_with_include_and_conditions
|
||||
assert_nothing_raised do
|
||||
Developer.find(:all, :joins => :audit_logs, :conditions => {'audit_logs.message' => nil, :name => 'Smith'})
|
||||
end
|
||||
end
|
||||
|
||||
def test_find_in_collection
|
||||
assert_equal Client.find(2).name, companies(:first_firm).clients.find(2).name
|
||||
assert_raise(ActiveRecord::RecordNotFound) { companies(:first_firm).clients.find(6) }
|
||||
|
@ -502,6 +509,23 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal 0, new_firm.clients_of_firm.size
|
||||
end
|
||||
|
||||
def test_deleting_updates_counter_cache
|
||||
topic = Topic.first
|
||||
assert_equal topic.replies.to_a.size, topic.replies_count
|
||||
|
||||
topic.replies.delete(topic.replies.first)
|
||||
topic.reload
|
||||
assert_equal topic.replies.to_a.size, topic.replies_count
|
||||
end
|
||||
|
||||
def test_deleting_updates_counter_cache_without_dependent_destroy
|
||||
post = posts(:welcome)
|
||||
|
||||
assert_difference "post.reload.taggings_count", -1 do
|
||||
post.taggings.delete(post.taggings.first)
|
||||
end
|
||||
end
|
||||
|
||||
def test_deleting_a_collection
|
||||
force_signal37_to_load_all_clients_of_firm
|
||||
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
|
||||
|
@ -547,6 +571,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_clearing_updates_counter_cache
|
||||
topic = Topic.first
|
||||
|
||||
topic.replies.clear
|
||||
topic.reload
|
||||
assert_equal 0, topic.replies_count
|
||||
end
|
||||
|
||||
def test_clearing_a_dependent_association_collection
|
||||
firm = companies(:first_firm)
|
||||
client_id = firm.dependent_clients_of_firm.first.id
|
||||
|
@ -691,6 +723,28 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
assert_equal 0, companies(:first_firm).clients_of_firm(true).size
|
||||
end
|
||||
|
||||
def test_destroying_by_fixnum_id
|
||||
force_signal37_to_load_all_clients_of_firm
|
||||
|
||||
assert_difference "Client.count", -1 do
|
||||
companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id)
|
||||
end
|
||||
|
||||
assert_equal 0, companies(:first_firm).reload.clients_of_firm.size
|
||||
assert_equal 0, companies(:first_firm).clients_of_firm(true).size
|
||||
end
|
||||
|
||||
def test_destroying_by_string_id
|
||||
force_signal37_to_load_all_clients_of_firm
|
||||
|
||||
assert_difference "Client.count", -1 do
|
||||
companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id.to_s)
|
||||
end
|
||||
|
||||
assert_equal 0, companies(:first_firm).reload.clients_of_firm.size
|
||||
assert_equal 0, companies(:first_firm).clients_of_firm(true).size
|
||||
end
|
||||
|
||||
def test_destroying_a_collection
|
||||
force_signal37_to_load_all_clients_of_firm
|
||||
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
|
||||
|
@ -861,7 +915,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
|||
lambda { authors(:mary).comments = [comments(:greetings), comments(:more_greetings)] },
|
||||
lambda { authors(:mary).comments << Comment.create!(:body => "Yay", :post_id => 424242) },
|
||||
lambda { authors(:mary).comments.delete(authors(:mary).comments.first) },
|
||||
].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection, &block) }
|
||||
].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
|
||||
end
|
||||
|
||||
def test_dynamic_find_should_respect_association_order_for_through
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue