Merge branch 'bzr/golem' of /Users/distler/Sites/code/instiki

This commit is contained in:
Jacques Distler 2010-09-05 15:26:17 -05:00
commit a5aa1b1fa8
200 changed files with 3065 additions and 1204 deletions

View file

@ -229,9 +229,9 @@ END_THM
assert_markup_parsed_as( assert_markup_parsed_as(
%{<p>equation <math class='maruku-mathml' displa} + %{<p>equation <math class='maruku-mathml' displa} +
%{y='inline' xmlns='http://www.w3.org/1998/Math/} + %{y='inline' xmlns='http://www.w3.org/1998/Math/} +
%{MathML'><mi>A</mi><mo>\342\253\275</mo><mi>B</} + %{MathML'><mi>A</mi><mi>\342\200\246</mi><mo>\342\253\275</mo><mi>B</} +
%{mi></math></p>}, %{mi></math></p>},
"equation $A\\sslash B$") "equation $A\\dots\\sslash B$")
assert_markup_parsed_as( assert_markup_parsed_as(
%{<p>boxed equation <math class='maruku-mathml' } + %{<p>boxed equation <math class='maruku-mathml' } +

View file

@ -4,7 +4,7 @@ module ActionView
module TagHelper module TagHelper
# Now that form_tag accepts blocks, it was easier to alias tag when name == :form # Now that form_tag accepts blocks, it was easier to alias tag when name == :form
def tag_with_form_spam_protection(name, *args) def tag_with_form_spam_protection(name, *args)
returning tag_without_form_spam_protection(name, *args) do |out| tag_without_form_spam_protection(name, *args).tap do |out|
if name == :form && @protect_form_from_spam if name == :form && @protect_form_from_spam
session[:form_keys] ||= {} session[:form_keys] ||= {}
form_key = Digest::SHA1.hexdigest(self.object_id.to_s + rand.to_s) form_key = Digest::SHA1.hexdigest(self.object_id.to_s + rand.to_s)

View file

@ -1,8 +1,5 @@
*2.3.9 (September 4, 2010)*
*2.3.8 (May 24, 2010)* *2.3.8 (May 24, 2010)*
* Version bump.
*2.3.7 (May 24, 2010)* *2.3.7 (May 24, 2010)*
* Version bump. * Version bump.

View file

@ -54,7 +54,7 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "actionmailer" s.rubyforge_project = "actionmailer"
s.homepage = "http://www.rubyonrails.org" s.homepage = "http://www.rubyonrails.org"
s.add_dependency('actionpack', '= 2.3.8' + PKG_BUILD) s.add_dependency('actionpack', '= 2.3.9' + PKG_BUILD)
s.has_rdoc = true s.has_rdoc = true
s.requirements << 'none' s.requirements << 'none'

View file

@ -278,7 +278,7 @@ module ActionMailer #:nodoc:
@@raise_delivery_errors = true @@raise_delivery_errors = true
cattr_accessor :raise_delivery_errors cattr_accessor :raise_delivery_errors
superclass_delegating_accessor :delivery_method class_attribute :delivery_method
self.delivery_method = :smtp self.delivery_method = :smtp
@@perform_deliveries = true @@perform_deliveries = true

View file

@ -105,7 +105,7 @@ module ActionMailer
private private
# Extend the template class instance with our controller's helper module. # Extend the template class instance with our controller's helper module.
def initialize_template_class_with_helper(assigns) def initialize_template_class_with_helper(assigns)
returning(template = initialize_template_class_without_helper(assigns)) do initialize_template_class_without_helper(assigns).tap do |template|
template.extend self.class.master_helper_module template.extend self.class.master_helper_module
end end
end end

View file

@ -2,7 +2,7 @@ module ActionMailer
module VERSION #:nodoc: module VERSION #:nodoc:
MAJOR = 2 MAJOR = 2
MINOR = 3 MINOR = 3
TINY = 8 TINY = 9
STRING = [MAJOR, MINOR, TINY].join('.') STRING = [MAJOR, MINOR, TINY].join('.')
end end

View file

@ -1,3 +1,8 @@
*2.3.9 (September 4, 2010)*
* Version bump.
*2.3.8 (May 24, 2010)* *2.3.8 (May 24, 2010)*
* HTML safety: fix compatibility *without* the optional rails_xss plugin. * HTML safety: fix compatibility *without* the optional rails_xss plugin.

View file

@ -79,7 +79,7 @@ spec = Gem::Specification.new do |s|
s.has_rdoc = true s.has_rdoc = true
s.requirements << 'none' s.requirements << 'none'
s.add_dependency('activesupport', '= 2.3.8' + PKG_BUILD) s.add_dependency('activesupport', '= 2.3.9' + PKG_BUILD)
s.add_dependency('rack', '~> 1.1.0') s.add_dependency('rack', '~> 1.1.0')
s.require_path = 'lib' s.require_path = 'lib'

View file

@ -65,8 +65,8 @@ module ActionController #:nodoc:
def read_fragment(key, options = nil) def read_fragment(key, options = nil)
return unless cache_configured? return unless cache_configured?
self.class.benchmark "Cached fragment hit: #{key}" do
key = fragment_cache_key(key) key = fragment_cache_key(key)
self.class.benchmark "Cached fragment hit: #{key}" do
result = cache_store.read(key, options) result = cache_store.read(key, options)
result.respond_to?(:html_safe) ? result.html_safe : result result.respond_to?(:html_safe) ? result.html_safe : result
end end

View file

@ -414,15 +414,25 @@ module ActionController
end end
def multipart_requestify(params, first=true) def multipart_requestify(params, first=true)
returning Hash.new do |p| Array.new.tap do |p|
params.each do |key, value| params.each do |key, value|
k = first ? key.to_s : "[#{key.to_s}]" k = first ? key.to_s : "[#{key.to_s}]"
if Hash === value if Hash === value
multipart_requestify(value, false).each do |subkey, subvalue| multipart_requestify(value, false).each do |subkey, subvalue|
p[k + subkey] = subvalue p << [k + subkey, subvalue]
end
elsif Array === value
value.each do |element|
if Hash === element || Array === element
multipart_requestify(element, false).each do |subkey, subvalue|
p << ["#{k}[]#{subkey}", subvalue]
end end
else else
p[k] = value p << ["#{k}[]", element]
end
end
else
p << [k, value]
end end
end end
end end
@ -453,6 +463,7 @@ EOF
end end
end.join("")+"--#{boundary}--\r" end.join("")+"--#{boundary}--\r"
end end
end end
# A module used to extend ActionController::Base, so that integration tests # A module used to extend ActionController::Base, so that integration tests
@ -500,7 +511,7 @@ EOF
reset! unless @integration_session reset! unless @integration_session
# reset the html_document variable, but only for new get/post calls # reset the html_document variable, but only for new get/post calls
@html_document = nil unless %w(cookies assigns).include?(method) @html_document = nil unless %w(cookies assigns).include?(method)
returning @integration_session.__send__(method, *args) do @integration_session.__send__(method, *args).tap do
copy_session_variables! copy_session_variables!
end end
end end
@ -556,7 +567,7 @@ EOF
def method_missing(sym, *args, &block) def method_missing(sym, *args, &block)
reset! unless @integration_session reset! unless @integration_session
if @integration_session.respond_to?(sym) if @integration_session.respond_to?(sym)
returning @integration_session.__send__(sym, *args, &block) do @integration_session.__send__(sym, *args, &block).tap do
copy_session_variables! copy_session_variables!
end end
else else

View file

@ -446,8 +446,8 @@ EOM
end end
def reset_session def reset_session
@env['rack.session.options'].delete(:id) session.destroy if session
@env['rack.session'] = {} self.session = {}
end end
def session_options def session_options

View file

@ -15,7 +15,7 @@ module ActionController #:nodoc:
# behavior is achieved by overriding the <tt>rescue_action_in_public</tt> # behavior is achieved by overriding the <tt>rescue_action_in_public</tt>
# and <tt>rescue_action_locally</tt> methods. # and <tt>rescue_action_locally</tt> methods.
module Rescue module Rescue
LOCALHOST = ['127.0.0.1', '::1'].freeze LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/].freeze
DEFAULT_RESCUE_RESPONSE = :internal_server_error DEFAULT_RESCUE_RESPONSE = :internal_server_error
DEFAULT_RESCUE_RESPONSES = { DEFAULT_RESCUE_RESPONSES = {
@ -122,7 +122,7 @@ module ActionController #:nodoc:
# method if you wish to redefine the meaning of a local request to # method if you wish to redefine the meaning of a local request to
# include remote IP addresses or other criteria. # include remote IP addresses or other criteria.
def local_request? #:doc: def local_request? #:doc:
LOCALHOST.any?{ |local_ip| request.remote_addr == local_ip && request.remote_ip == local_ip } LOCALHOST.any?{ |local_ip| request.remote_addr =~ local_ip && request.remote_ip =~ local_ip }
end end
# Render detailed diagnostics for unhandled exceptions rescued from # Render detailed diagnostics for unhandled exceptions rescued from

View file

@ -659,7 +659,7 @@ module ActionController
end end
def add_conditions_for(conditions, method) def add_conditions_for(conditions, method)
returning({:conditions => conditions.dup}) do |options| ({:conditions => conditions.dup}).tap do |options|
options[:conditions][:method] = method unless method == :any options[:conditions][:method] = method unless method == :any
end end
end end

View file

@ -377,7 +377,7 @@ module ActionController
ActiveSupport::Inflector.module_eval do ActiveSupport::Inflector.module_eval do
# Ensures that routes are reloaded when Rails inflections are updated. # Ensures that routes are reloaded when Rails inflections are updated.
def inflections_with_route_reloading(&block) def inflections_with_route_reloading(&block)
returning(inflections_without_route_reloading(&block)) { (inflections_without_route_reloading(&block)).tap {
ActionController::Routing::Routes.reload! if block_given? ActionController::Routing::Routes.reload! if block_given?
} }
end end

View file

@ -65,7 +65,7 @@ module ActionController
# map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/ # map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
# #
def parameter_shell def parameter_shell
@parameter_shell ||= returning({}) do |shell| @parameter_shell ||= {}.tap do |shell|
requirements.each do |key, requirement| requirements.each do |key, requirement|
shell[key] = requirement unless requirement.is_a? Regexp shell[key] = requirement unless requirement.is_a? Regexp
end end
@ -76,7 +76,7 @@ module ActionController
# includes keys that appear inside the path, and keys that have requirements # includes keys that appear inside the path, and keys that have requirements
# placed upon them. # placed upon them.
def significant_keys def significant_keys
@significant_keys ||= returning([]) do |sk| @significant_keys ||= [].tap do |sk|
segments.each { |segment| sk << segment.key if segment.respond_to? :key } segments.each { |segment| sk << segment.key if segment.respond_to? :key }
sk.concat requirements.keys sk.concat requirements.keys
sk.uniq! sk.uniq!
@ -86,7 +86,7 @@ module ActionController
# Return a hash of key/value pairs representing the keys in the route that # Return a hash of key/value pairs representing the keys in the route that
# have defaults, or which are specified by non-regexp requirements. # have defaults, or which are specified by non-regexp requirements.
def defaults def defaults
@defaults ||= returning({}) do |hash| @defaults ||= {}.tap do |hash|
segments.each do |segment| segments.each do |segment|
next unless segment.respond_to? :default next unless segment.respond_to? :default
hash[segment.key] = segment.default unless segment.default.nil? hash[segment.key] = segment.default unless segment.default.nil?

View file

@ -9,6 +9,35 @@ module ActionController
HTTP_COOKIE = 'HTTP_COOKIE'.freeze HTTP_COOKIE = 'HTTP_COOKIE'.freeze
SET_COOKIE = 'Set-Cookie'.freeze SET_COOKIE = 'Set-Cookie'.freeze
# thin wrapper around Hash that allows us to lazily
# load session id into session_options
class OptionsHash < Hash
def initialize(by, env, default_options)
@by = by
@env = env
@session_id_loaded = false
merge!(default_options)
end
def [](key)
if key == :id
load_session_id! unless super(:id) || has_session_id?
end
super(key)
end
private
def has_session_id?
@session_id_loaded
end
def load_session_id!
self[:id] = @by.send(:extract_session_id, @env)
@session_id_loaded = true
end
end
class SessionHash < Hash class SessionHash < Hash
def initialize(by, env) def initialize(by, env)
super() super()
@ -25,21 +54,42 @@ module ActionController
end end
def [](key) def [](key)
load! unless @loaded load_for_read!
super
end
def has_key?(key)
load_for_read!
super super
end end
def []=(key, value) def []=(key, value)
load! unless @loaded load_for_write!
super
end
def clear
load_for_write!
super super
end end
def to_hash def to_hash
load_for_read!
h = {}.replace(self) h = {}.replace(self)
h.delete_if { |k,v| v.nil? } h.delete_if { |k,v| v.nil? }
h h
end end
def update(hash)
load_for_write!
super
end
def delete(key)
load_for_write!
super
end
def data def data
ActiveSupport::Deprecation.warn( ActiveSupport::Deprecation.warn(
"ActionController::Session::AbstractStore::SessionHash#data " + "ActionController::Session::AbstractStore::SessionHash#data " +
@ -48,40 +98,43 @@ module ActionController
end end
def inspect def inspect
load! unless @loaded load_for_read!
super super
end end
private def exists?
return @exists if instance_variable_defined?(:@exists)
@exists = @by.send(:exists?, @env)
end
def loaded? def loaded?
@loaded @loaded
end end
def destroy
clear
@by.send(:destroy, @env) if @by
@env[ENV_SESSION_OPTIONS_KEY][:id] = nil if @env && @env[ENV_SESSION_OPTIONS_KEY]
@loaded = false
end
private
def load_for_read!
load! if !loaded? && exists?
end
def load_for_write!
load! unless loaded?
end
def load! def load!
stale_session_check! do
id, session = @by.send(:load_session, @env) id, session = @by.send(:load_session, @env)
(@env[ENV_SESSION_OPTIONS_KEY] ||= {})[:id] = id @env[ENV_SESSION_OPTIONS_KEY][:id] = id
replace(session) replace(session)
@loaded = true @loaded = true
end end
end
def stale_session_check!
yield
rescue ArgumentError => argument_error
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
begin
# Note that the regexp does not allow $1 to end with a ':'
$1.constantize
rescue LoadError, NameError => const_error
raise ActionController::SessionRestoreError, "Session contains objects whose class definition isn\\'t available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: \#{const_error.message} [\#{const_error.class}])\n"
end
retry
else
raise
end
end
end end
DEFAULT_OPTIONS = { DEFAULT_OPTIONS = {
@ -120,18 +173,14 @@ module ActionController
end end
def call(env) def call(env)
session = SessionHash.new(self, env) prepare!(env)
env[ENV_SESSION_KEY] = session
env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
response = @app.call(env) response = @app.call(env)
session_data = env[ENV_SESSION_KEY] session_data = env[ENV_SESSION_KEY]
options = env[ENV_SESSION_OPTIONS_KEY] options = env[ENV_SESSION_OPTIONS_KEY]
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after] if !session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after]
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?) session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
sid = options[:id] || generate_sid sid = options[:id] || generate_sid
@ -139,6 +188,7 @@ module ActionController
return response return response
end end
if (env["rack.request.cookie_hash"] && env["rack.request.cookie_hash"][@key] != sid) || options[:expire_after]
cookie = Rack::Utils.escape(@key) + '=' + Rack::Utils.escape(sid) cookie = Rack::Utils.escape(@key) + '=' + Rack::Utils.escape(sid)
cookie << "; domain=#{options[:domain]}" if options[:domain] cookie << "; domain=#{options[:domain]}" if options[:domain]
cookie << "; path=#{options[:path]}" if options[:path] cookie << "; path=#{options[:path]}" if options[:path]
@ -156,24 +206,46 @@ module ActionController
headers[SET_COOKIE] = cookie headers[SET_COOKIE] = cookie
end end
end end
end
response response
end end
private private
def prepare!(env)
env[ENV_SESSION_KEY] = SessionHash.new(self, env)
env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
end
def generate_sid def generate_sid
ActiveSupport::SecureRandom.hex(16) ActiveSupport::SecureRandom.hex(16)
end end
def load_session(env) def load_session(env)
request = Rack::Request.new(env) stale_session_check! do
sid = request.cookies[@key] sid = current_session_id(env)
unless @cookie_only
sid ||= request.params[@key]
end
sid, session = get_session(env, sid) sid, session = get_session(env, sid)
[sid, session] [sid, session]
end end
end
def extract_session_id(env)
stale_session_check! do
request = Rack::Request.new(env)
sid = request.cookies[@key]
sid ||= request.params[@key] unless @cookie_only
sid
end
end
def current_session_id(env)
env[ENV_SESSION_OPTIONS_KEY][:id]
end
def exists?(env)
current_session_id(env).present?
end
def get_session(env, sid) def get_session(env, sid)
raise '#get_session needs to be implemented.' raise '#get_session needs to be implemented.'
@ -182,6 +254,30 @@ module ActionController
def set_session(env, sid, session_data) def set_session(env, sid, session_data)
raise '#set_session needs to be implemented.' raise '#set_session needs to be implemented.'
end end
def destroy(env)
raise '#destroy needs to be implemented.'
end
module SessionUtils
private
def stale_session_check!
yield
rescue ArgumentError => argument_error
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
begin
# Note that the regexp does not allow $1 to end with a ':'
$1.constantize
rescue LoadError, NameError => const_error
raise ActionController::SessionRestoreError, "Session contains objects whose class definition isn\\'t available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: \#{const_error.message} [\#{const_error.class}])\n"
end
retry
else
raise
end
end
end
include SessionUtils
end end
end end
end end

View file

@ -36,6 +36,8 @@ module ActionController
# #
# Note that changing digest or secret invalidates all existing sessions! # Note that changing digest or secret invalidates all existing sessions!
class CookieStore class CookieStore
include AbstractStore::SessionUtils
# Cookies can typically store 4096 bytes. # Cookies can typically store 4096 bytes.
MAX = 4096 MAX = 4096
SECRET_MIN_LENGTH = 30 # characters SECRET_MIN_LENGTH = 30 # characters
@ -93,20 +95,20 @@ module ActionController
end end
def call(env) def call(env)
env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env) prepare!(env)
env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
status, headers, body = @app.call(env) status, headers, body = @app.call(env)
session_data = env[ENV_SESSION_KEY] session_data = env[ENV_SESSION_KEY]
options = env[ENV_SESSION_OPTIONS_KEY] options = env[ENV_SESSION_OPTIONS_KEY]
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after] if !session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after]
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?) session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
persistent_session_id!(session_data)
session_data = marshal(session_data.to_hash) session_data = marshal(session_data.to_hash)
raise CookieOverflow if session_data.size > MAX raise CookieOverflow if session_data.size > MAX
cookie = Hash.new cookie = Hash.new
cookie[:value] = session_data cookie[:value] = session_data
unless options[:expire_after].nil? unless options[:expire_after].nil?
@ -114,17 +116,20 @@ module ActionController
end end
cookie = build_cookie(@key, cookie.merge(options)) cookie = build_cookie(@key, cookie.merge(options))
unless headers[HTTP_SET_COOKIE].blank? headers[HTTP_SET_COOKIE] = [] if headers[HTTP_SET_COOKIE].blank?
headers[HTTP_SET_COOKIE] << "\n#{cookie}" headers[HTTP_SET_COOKIE] << cookie
else
headers[HTTP_SET_COOKIE] = cookie
end
end end
[status, headers, body] [status, headers, body]
end end
private private
def prepare!(env)
env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env)
env[ENV_SESSION_OPTIONS_KEY] = AbstractStore::OptionsHash.new(self, env, @default_options)
end
# Should be in Rack::Utils soon # Should be in Rack::Utils soon
def build_cookie(key, value) def build_cookie(key, value)
case value case value
@ -146,20 +151,46 @@ module ActionController
end end
def load_session(env) def load_session(env)
data = unpacked_cookie_data(env)
data = persistent_session_id!(data)
[data[:session_id], data]
end
def extract_session_id(env)
if data = unpacked_cookie_data(env)
persistent_session_id!(data) unless data.empty?
data[:session_id]
else
nil
end
end
def current_session_id(env)
env[ENV_SESSION_OPTIONS_KEY][:id]
end
def exists?(env)
current_session_id(env).present?
end
def unpacked_cookie_data(env)
env["action_dispatch.request.unsigned_session_cookie"] ||= begin
stale_session_check! do
request = Rack::Request.new(env) request = Rack::Request.new(env)
session_data = request.cookies[@key] session_data = request.cookies[@key]
data = unmarshal(session_data) || persistent_session_id!({}) unmarshal(session_data) || {}
[data[:session_id], data] end
end
end end
# Marshal a session hash into safe cookie data. Include an integrity hash. # Marshal a session hash into safe cookie data. Include an integrity hash.
def marshal(session) def marshal(session)
@verifier.generate(persistent_session_id!(session)) @verifier.generate(session)
end end
# Unmarshal cookie data to a hash and verify its integrity. # Unmarshal cookie data to a hash and verify its integrity.
def unmarshal(cookie) def unmarshal(cookie)
persistent_session_id!(@verifier.verify(cookie)) if cookie @verifier.verify(cookie) if cookie
rescue ActiveSupport::MessageVerifier::InvalidSignature rescue ActiveSupport::MessageVerifier::InvalidSignature
nil nil
end end
@ -207,6 +238,10 @@ module ActionController
ActiveSupport::SecureRandom.hex(16) ActiveSupport::SecureRandom.hex(16)
end end
def destroy(env)
# session data is stored on client; nothing to do here
end
def persistent_session_id!(data) def persistent_session_id!(data)
(data ||= {}).merge!(inject_persistent_session_id(data)) (data ||= {}).merge!(inject_persistent_session_id(data))
end end

View file

@ -43,6 +43,15 @@ begin
rescue MemCache::MemCacheError, Errno::ECONNREFUSED rescue MemCache::MemCacheError, Errno::ECONNREFUSED
return false return false
end end
def destroy(env)
if sid = current_session_id(env)
@pool.delete(sid)
end
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
false
end
end end
end end
end end

View file

@ -450,7 +450,7 @@ module ActionController #:nodoc:
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@request.env['HTTP_ACCEPT'] = [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') @request.env['HTTP_ACCEPT'] = [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
returning __send__(request_method, action, parameters, session, flash) do __send__(request_method, action, parameters, session, flash).tap do
@request.env.delete 'HTTP_X_REQUESTED_WITH' @request.env.delete 'HTTP_X_REQUESTED_WITH'
@request.env.delete 'HTTP_ACCEPT' @request.env.delete 'HTTP_ACCEPT'
end end

View file

@ -92,6 +92,14 @@ module ActionController
# end # end
# end # end
module UrlWriter module UrlWriter
RESERVED_PCHAR = ':@&=+$,;%'
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
if RUBY_VERSION >= '1.9'
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze
else
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
end
def self.included(base) #:nodoc: def self.included(base) #:nodoc:
ActionController::Routing::Routes.install_helpers(base) ActionController::Routing::Routes.install_helpers(base)
base.mattr_accessor :default_url_options base.mattr_accessor :default_url_options
@ -142,7 +150,7 @@ module ActionController
end end
trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash) trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root] url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor] anchor = "##{URI.escape(options.delete(:anchor).to_param.to_s, UNSAFE_PCHAR)}" if options[:anchor]
generated = Routing::Routes.generate(options, {}) generated = Routing::Routes.generate(options, {})
url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated) url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
url << anchor if anchor url << anchor if anchor

View file

@ -2,7 +2,7 @@ module ActionPack #:nodoc:
module VERSION #:nodoc: module VERSION #:nodoc:
MAJOR = 2 MAJOR = 2
MINOR = 3 MINOR = 3
TINY = 8 TINY = 9
STRING = [MAJOR, MINOR, TINY].join('.') STRING = [MAJOR, MINOR, TINY].join('.')
end end

View file

@ -877,9 +877,9 @@ module ActionView
def value_before_type_cast(object, method_name) def value_before_type_cast(object, method_name)
unless object.nil? unless object.nil?
object.respond_to?(method_name + "_before_type_cast") ? object.respond_to?(method_name) ?
object.send(method_name + "_before_type_cast") : object.send(method_name) :
object.send(method_name) object.send(method_name + "_before_type_cast")
end end
end end

View file

@ -481,7 +481,7 @@ module ActionView
end end
zone_options += options_for_select(convert_zones[zones], selected) zone_options += options_for_select(convert_zones[zones], selected)
zone_options zone_options.html_safe
end end
private private

View file

@ -440,7 +440,7 @@ module ActionView
private private
def html_options_for_form(url_for_options, options, *parameters_for_url) def html_options_for_form(url_for_options, options, *parameters_for_url)
returning options.stringify_keys do |html_options| options.stringify_keys.tap do |html_options|
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart") html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
html_options["action"] = url_for(url_for_options, *parameters_for_url) html_options["action"] = url_for(url_for_options, *parameters_for_url)
end end

View file

@ -653,7 +653,7 @@ module ActionView
# <script> tag. # <script> tag.
module GeneratorMethods module GeneratorMethods
def to_s #:nodoc: def to_s #:nodoc:
returning javascript = @lines * $/ do (@lines * $/).tap do |javascript|
if ActionView::Base.debug_rjs if ActionView::Base.debug_rjs
source = javascript.dup source = javascript.dup
javascript.replace "try {\n#{source}\n} catch (e) " javascript.replace "try {\n#{source}\n} catch (e) "
@ -981,8 +981,8 @@ module ActionView
end end
def record(line) def record(line)
returning line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" do "#{line.to_s.chomp.gsub(/\;\z/, '')};".tap do |_line|
self << line self << _line
end end
end end

View file

@ -532,9 +532,14 @@ module ActionView
end end
AUTO_LINK_RE = %r{ AUTO_LINK_RE = %r{
( https?:// | www\. ) (?: ([\w+.:-]+:)// | www\. )
[^\s<]+ [^\s<]+
}x unless const_defined?(:AUTO_LINK_RE) }x
# regexps for determining context, used high-volume
AUTO_LINK_CRE = [/<[^>]+$/, /^[^>]*>/, /<a\b.*?>/i, /<\/a>/i]
AUTO_EMAIL_RE = /[\w.!#\$%+-]+@[\w-]+(?:\.[\w-]+)+/
BRACKETS = { ']' => '[', ')' => '(', '}' => '{' } BRACKETS = { ']' => '[', ')' => '(', '}' => '{' }
@ -543,26 +548,26 @@ module ActionView
def auto_link_urls(text, html_options = {}) def auto_link_urls(text, html_options = {})
link_attributes = html_options.stringify_keys link_attributes = html_options.stringify_keys
text.gsub(AUTO_LINK_RE) do text.gsub(AUTO_LINK_RE) do
href = $& scheme, href = $1, $&
punctuation = '' punctuation = []
left, right = $`, $'
# detect already linked URLs and URLs in the middle of a tag if auto_linked?($`, $')
if left =~ /<[^>]+$/ && right =~ /^[^>]*>/
# do not change string; URL is already linked # do not change string; URL is already linked
href href
else else
# don't include trailing punctuation character as part of the URL # don't include trailing punctuation character as part of the URL
if href.sub!(/[^\w\/-]$/, '') and punctuation = $& and opening = BRACKETS[punctuation] while href.sub!(/[^\w\/-]$/, '')
if href.scan(opening).size > href.scan(punctuation).size punctuation.push $&
href << punctuation if opening = BRACKETS[punctuation.last] and href.scan(opening).size > href.scan(punctuation.last).size
punctuation = '' href << punctuation.pop
break
end end
end end
link_text = block_given?? yield(href) : href link_text = block_given?? yield(href) : href
href = 'http://' + href unless href =~ %r{^[a-z]+://}i href = 'http://' + href unless scheme
content_tag(:a, h(link_text), link_attributes.merge('href' => href)) + punctuation content_tag(:a, h(link_text), link_attributes.merge('href' => href)) + punctuation.reverse.join('')
end end
end end
end end
@ -570,11 +575,10 @@ module ActionView
# Turns all email addresses into clickable links. If a block is given, # Turns all email addresses into clickable links. If a block is given,
# each email is yielded and the result is used as the link text. # each email is yielded and the result is used as the link text.
def auto_link_email_addresses(text, html_options = {}) def auto_link_email_addresses(text, html_options = {})
body = text.dup text.gsub(AUTO_EMAIL_RE) do
text.gsub(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do text = $&
text = $1
if body.match(/<a\b[^>]*>(.*)(#{Regexp.escape(text)})(.*)<\/a>/) if auto_linked?($`, $')
text text
else else
display_text = (block_given?) ? yield(text) : text display_text = (block_given?) ? yield(text) : text
@ -582,6 +586,12 @@ module ActionView
end end
end end
end end
# Detects already linked context or position in the middle of a tag
def auto_linked?(left, right)
(left =~ AUTO_LINK_CRE[0] and right =~ AUTO_LINK_CRE[1]) or
(left.rindex(AUTO_LINK_CRE[2]) and $' !~ AUTO_LINK_CRE[3])
end
end end
end end
end end

View file

@ -63,37 +63,37 @@
half_a_minute: "half a minute" half_a_minute: "half a minute"
less_than_x_seconds: less_than_x_seconds:
one: "less than 1 second" one: "less than 1 second"
other: "less than {{count}} seconds" other: "less than %{count} seconds"
x_seconds: x_seconds:
one: "1 second" one: "1 second"
other: "{{count}} seconds" other: "%{count} seconds"
less_than_x_minutes: less_than_x_minutes:
one: "less than a minute" one: "less than a minute"
other: "less than {{count}} minutes" other: "less than %{count} minutes"
x_minutes: x_minutes:
one: "1 minute" one: "1 minute"
other: "{{count}} minutes" other: "%{count} minutes"
about_x_hours: about_x_hours:
one: "about 1 hour" one: "about 1 hour"
other: "about {{count}} hours" other: "about %{count} hours"
x_days: x_days:
one: "1 day" one: "1 day"
other: "{{count}} days" other: "%{count} days"
about_x_months: about_x_months:
one: "about 1 month" one: "about 1 month"
other: "about {{count}} months" other: "about %{count} months"
x_months: x_months:
one: "1 month" one: "1 month"
other: "{{count}} months" other: "%{count} months"
about_x_years: about_x_years:
one: "about 1 year" one: "about 1 year"
other: "about {{count}} years" other: "about %{count} years"
over_x_years: over_x_years:
one: "over 1 year" one: "over 1 year"
other: "over {{count}} years" other: "over %{count} years"
almost_x_years: almost_x_years:
one: "almost 1 year" one: "almost 1 year"
other: "almost {{count}} years" other: "almost %{count} years"
prompts: prompts:
year: "Year" year: "Year"
month: "Month" month: "Month"
@ -106,8 +106,8 @@
errors: errors:
template: template:
header: header:
one: "1 error prohibited this {{model}} from being saved" one: "1 error prohibited this %{model} from being saved"
other: "{{count}} errors prohibited this {{model}} from being saved" other: "%{count} errors prohibited this %{model} from being saved"
# The variable :count is also available # The variable :count is also available
body: "There were problems with the following fields:" body: "There were problems with the following fields:"

View file

@ -45,8 +45,8 @@ module ActionView #:nodoc:
end end
def self.new_and_loaded(path) def self.new_and_loaded(path)
returning new(path) do |path| new(path).tap do |_path|
path.load! _path.load!
end end
end end

View file

@ -58,4 +58,21 @@ class DummyMutex
end end
end end
class ActionController::IntegrationTest < ActiveSupport::TestCase
def with_autoload_path(path)
path = File.join(File.dirname(__FILE__), "fixtures", path)
if ActiveSupport::Dependencies.autoload_paths.include?(path)
yield
else
begin
ActiveSupport::Dependencies.autoload_paths << path
yield
ensure
ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path}
ActiveSupport::Dependencies.clear
end
end
end
end
ActionController::Reloader.default_lock = DummyMutex.new ActionController::Reloader.default_lock = DummyMutex.new

View file

@ -22,7 +22,6 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
end end
def get_session_id def get_session_id
session[:foo]
render :text => "#{request.session_options[:id]}" render :text => "#{request.session_options[:id]}"
end end
@ -45,7 +44,9 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
ActiveRecord::SessionStore.session_class.drop_table! ActiveRecord::SessionStore.session_class.drop_table!
end end
def test_setting_and_getting_session_value %w{ session sql_bypass }.each do |class_name|
define_method("test_setting_and_getting_session_value_with_#{class_name}_store") do
with_store class_name do
with_test_route_set do with_test_route_set do
get '/set_session_value' get '/set_session_value'
assert_response :success assert_response :success
@ -64,6 +65,8 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
assert_equal 'foo: "baz"', response.body assert_equal 'foo: "baz"', response.body
end end
end end
end
end
def test_getting_nil_session_value def test_getting_nil_session_value
with_test_route_set do with_test_route_set do
@ -107,6 +110,38 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
end end
end end
def test_getting_session_value
with_test_route_set do
get '/set_session_value'
assert_response :success
assert cookies['_session_id']
get '/get_session_value'
assert_response :success
assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
session_id = cookies["_session_id"]
get '/call_reset_session'
assert_response :success
assert_not_equal [], headers['Set-Cookie']
cookies["_session_id"] = session_id # replace our new session_id with our old, pre-reset session_id
get '/get_session_value'
assert_response :success
assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from the database"
end
end
def test_getting_from_nonexistent_session
with_test_route_set do
get '/get_session_value'
assert_response :success
assert_equal 'foo: nil', response.body
assert_nil cookies['_session_id'], "should only create session on write, not read"
end
end
def test_prevents_session_fixation def test_prevents_session_fixation
with_test_route_set do with_test_route_set do
get '/set_session_value' get '/set_session_value'
@ -171,4 +206,16 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
yield yield
end end
end end
def with_store(class_name)
begin
session_class = ActiveRecord::SessionStore.session_class
ActiveRecord::SessionStore.session_class = "ActiveRecord::SessionStore::#{class_name.camelize}".constantize
yield
rescue
ActiveRecord::SessionStore.session_class = session_class
raise
end
end
end end

View file

@ -266,6 +266,14 @@ class IntegrationProcessTest < ActionController::IntegrationTest
render :text => "foo(1i): #{params[:"foo(1i)"]}, foo(2i): #{params[:"foo(2i)"]}, filesize: #{params[:file].size}", :status => 200 render :text => "foo(1i): #{params[:"foo(1i)"]}, foo(2i): #{params[:"foo(2i)"]}, filesize: #{params[:file].size}", :status => 200
end end
def multipart_post_with_nested_params
render :text => "foo: #{params[:foo][0]}, #{params[:foo][1]}; [filesize: #{params[:file_list][0][:content].size}, filesize: #{params[:file_list][1][:content].size}]", :status => 200
end
def multipart_post_with_multiparameter_complex_params
render :text => "foo(1i): #{params[:"foo(1i)"]}, foo(2i): #{params[:"foo(2i)"]}, [filesize: #{params[:file_list][0][:content].size}, filesize: #{params[:file_list][1][:content].size}]", :status => 200
end
def post def post
render :text => "Created", :status => 201 render :text => "Created", :status => 201
end end
@ -405,6 +413,24 @@ class IntegrationProcessTest < ActionController::IntegrationTest
end end
end end
def test_multipart_post_with_nested_params
with_test_route_set do
post '/multipart_post_with_nested_params', :"foo" => ['a', 'b'], :file_list => [{:content => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")}, {:content => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")}]
assert_equal 200, status
assert_equal "foo: a, b; [filesize: 159528, filesize: 159528]", response.body
end
end
def test_multipart_post_with_multiparameter_complex_attribute_parameters
with_test_route_set do
post '/multipart_post_with_multiparameter_complex_params', :"foo(1i)" => "bar", :"foo(2i)" => "baz", :file_list => [{:content => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")}, {:content => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")}]
assert_equal 200, status
assert_equal "foo(1i): bar, foo(2i): baz, [filesize: 159528, filesize: 159528]", response.body
end
end
def test_head def test_head
with_test_route_set do with_test_route_set do
head '/get' head '/get'

View file

@ -14,6 +14,10 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest
def read def read
render :text => "File: #{params[:uploaded_data].read}" render :text => "File: #{params[:uploaded_data].read}"
end end
def read_complex
render :text => "File: #{params[:level0][:level1][0][:file_data].read}"
end
end end
FIXTURE_PATH = File.dirname(__FILE__) + '/../../fixtures/multipart' FIXTURE_PATH = File.dirname(__FILE__) + '/../../fixtures/multipart'
@ -133,6 +137,17 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest
end end
end end
test "uploads and reads file in complex parameter" do
with_test_routing do
post '/read_complex',
:level0 => {
:level1 => [ { :file_data => fixture_file_upload(FIXTURE_PATH + "/hello.txt", "text/plain") }
]
}
assert_equal "File: Hello", response.body
end
end
private private
def fixture(name) def fixture(name)
File.open(File.join(FIXTURE_PATH, name), 'rb') do |file| File.open(File.join(FIXTURE_PATH, name), 'rb') do |file|

View file

@ -281,12 +281,11 @@ class RescueControllerTest < ActionController::TestCase
end end
def test_local_request_when_remote_addr_is_localhost def test_local_request_when_remote_addr_is_localhost
@controller.expects(:request).returns(@request).at_least(4) @controller.expects(:request).returns(@request).at_least(10)
with_remote_addr '127.0.0.1' do ['127.0.0.1', '127.0.0.127', '::1', '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1%0'].each do |ip_address|
with_remote_addr ip_address do
assert @controller.send(:local_request?) assert @controller.send(:local_request?)
end end
with_remote_addr '::1' do
assert @controller.send(:local_request?)
end end
end end

View file

@ -34,6 +34,15 @@ class CookieStoreTest < ActionController::IntegrationTest
render :text => "foo: #{session[:foo].inspect}; id: #{request.session_options[:id]}" render :text => "foo: #{session[:foo].inspect}; id: #{request.session_options[:id]}"
end end
def get_session_id_only
render :text => "id: #{request.session_options[:id]}"
end
def call_session_clear
session.clear
head :ok
end
def call_reset_session def call_reset_session
reset_session reset_session
head :ok head :ok
@ -44,6 +53,12 @@ class CookieStoreTest < ActionController::IntegrationTest
head :ok head :ok
end end
def set_session_value_and_cookie
cookies["foo"] = "bar"
session[:foo] = "bar"
render :text => Rack::Utils.escape(Verifier.generate(session.to_hash))
end
def rescue_action(e) raise end def rescue_action(e) raise end
end end
@ -96,7 +111,7 @@ class CookieStoreTest < ActionController::IntegrationTest
with_test_route_set do with_test_route_set do
get '/set_session_value' get '/set_session_value'
assert_response :success assert_response :success
assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly", assert_equal ["_myapp_session=#{response.body}; path=/; HttpOnly"],
headers['Set-Cookie'] headers['Set-Cookie']
end end
end end
@ -121,6 +136,10 @@ class CookieStoreTest < ActionController::IntegrationTest
get '/get_session_id' get '/get_session_id'
assert_response :success assert_response :success
assert_equal "foo: \"bar\"; id: #{session_id}", response.body assert_equal "foo: \"bar\"; id: #{session_id}", response.body
get '/get_session_id_only'
assert_response :success
assert_equal "id: #{session_id}", response.body, "should be able to read session id without accessing the session hash"
end end
end end
@ -164,7 +183,7 @@ class CookieStoreTest < ActionController::IntegrationTest
get '/set_session_value' get '/set_session_value'
assert_response :success assert_response :success
session_payload = response.body session_payload = response.body
assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly", assert_equal ["_myapp_session=#{response.body}; path=/; HttpOnly"],
headers['Set-Cookie'] headers['Set-Cookie']
get '/call_reset_session' get '/call_reset_session'
@ -178,6 +197,57 @@ class CookieStoreTest < ActionController::IntegrationTest
end end
end end
def test_setting_session_value_after_session_clear
with_test_route_set do
get '/set_session_value'
assert_response :success
session_payload = response.body
assert_equal ["_myapp_session=#{response.body}; path=/; HttpOnly"],
headers['Set-Cookie']
get '/call_session_clear'
assert_response :success
get '/get_session_value'
assert_response :success
assert_equal 'foo: nil', response.body
end
end
def test_getting_from_nonexistent_session
with_test_route_set do
get '/get_session_value'
assert_response :success
assert_equal 'foo: nil', response.body
assert_nil headers['Set-Cookie'], "should only create session on write, not read"
end
end
# {:foo=>#<SessionAutoloadTest::Foo bar:"baz">, :session_id=>"ce8b0752a6ab7c7af3cdb8a80e6b9e46"}
SignedSerializedCookie = "BAh7BzoIZm9vbzodU2Vzc2lvbkF1dG9sb2FkVGVzdDo6Rm9vBjoJQGJhciIIYmF6Og9zZXNzaW9uX2lkIiVjZThiMDc1MmE2YWI3YzdhZjNjZGI4YTgwZTZiOWU0Ng==--2bf3af1ae8bd4e52b9ac2099258ace0c380e601c"
def test_deserializes_unloaded_classes_on_get_id
with_test_route_set do
with_autoload_path "session_autoload_test" do
cookies[SessionKey] = SignedSerializedCookie
get '/get_session_id_only'
assert_response :success
assert_equal 'id: ce8b0752a6ab7c7af3cdb8a80e6b9e46', response.body, "should auto-load unloaded class"
end
end
end
def test_deserializes_unloaded_classes_on_get_value
with_test_route_set do
with_autoload_path "session_autoload_test" do
cookies[SessionKey] = SignedSerializedCookie
get '/get_session_value'
assert_response :success
assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class"
end
end
end
def test_persistent_session_id def test_persistent_session_id
with_test_route_set do with_test_route_set do
cookies[SessionKey] = SignedBar cookies[SessionKey] = SignedBar
@ -193,6 +263,14 @@ class CookieStoreTest < ActionController::IntegrationTest
end end
end end
def test_setting_session_value_and_cookie
with_test_route_set do
get '/set_session_value_and_cookie'
assert_response :success
assert_equal({"_myapp_session" => response.body, "foo" => "bar"}, cookies)
end
end
private private
def with_test_route_set def with_test_route_set
with_routing do |set| with_routing do |set|

View file

@ -12,12 +12,16 @@ class MemCacheStoreTest < ActionController::IntegrationTest
head :ok head :ok
end end
def set_serialized_session_value
session[:foo] = SessionAutoloadTest::Foo.new
head :ok
end
def get_session_value def get_session_value
render :text => "foo: #{session[:foo].inspect}" render :text => "foo: #{session[:foo].inspect}"
end end
def get_session_id def get_session_id
session[:foo]
render :text => "#{request.session_options[:id]}" render :text => "#{request.session_options[:id]}"
end end
@ -82,6 +86,34 @@ class MemCacheStoreTest < ActionController::IntegrationTest
end end
end end
def test_getting_session_value_after_session_reset
with_test_route_set do
get '/set_session_value'
assert_response :success
assert cookies['_session_id']
session_id = cookies["_session_id"]
get '/call_reset_session'
assert_response :success
assert_not_equal [], headers['Set-Cookie']
cookies["_session_id"] = session_id # replace our new session_id with our old, pre-reset session_id
get '/get_session_value'
assert_response :success
assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from memcached"
end
end
def test_getting_from_nonexistent_session
with_test_route_set do
get '/get_session_value'
assert_response :success
assert_equal 'foo: nil', response.body
assert_nil cookies['_session_id'], "should only create session on write, not read"
end
end
def test_getting_session_id def test_getting_session_id
with_test_route_set do with_test_route_set do
get '/set_session_value' get '/set_session_value'
@ -91,7 +123,38 @@ class MemCacheStoreTest < ActionController::IntegrationTest
get '/get_session_id' get '/get_session_id'
assert_response :success assert_response :success
assert_equal session_id, response.body assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
end
end
def test_doesnt_write_session_cookie_if_session_id_is_already_exists
with_test_route_set do
get '/set_session_value'
assert_response :success
assert cookies['_session_id']
get '/get_session_value'
assert_response :success
assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
end
end
def test_deserializes_unloaded_class
with_test_route_set do
with_autoload_path "session_autoload_test" do
get '/set_serialized_session_value'
assert_response :success
assert cookies['_session_id']
end
with_autoload_path "session_autoload_test" do
get '/get_session_id'
assert_response :success
end
with_autoload_path "session_autoload_test" do
get '/get_session_value'
assert_response :success
assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class"
end
end end
end end

View file

@ -134,9 +134,15 @@ class UrlWriterTests < ActionController::TestCase
) )
end end
def test_anchor_should_be_cgi_escaped def test_anchor_should_escape_unsafe_pchar
assert_equal('/c/a#anc%2Fhor', assert_equal('/c/a#%23anchor',
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anc/hor')) W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('#anchor'))
)
end
def test_anchor_should_not_escape_safe_pchar
assert_equal('/c/a#name=user&email=user@domain.com',
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('name=user&email=user@domain.com'))
) )
end end

View file

@ -0,0 +1,10 @@
module SessionAutoloadTest
class Foo
def initialize(bar='baz')
@bar = bar
end
def inspect
"#<#{self.class} bar:#{@bar.inspect}>"
end
end
end

View file

@ -91,6 +91,16 @@ end
class FormHelperTest < ActionView::TestCase class FormHelperTest < ActionView::TestCase
tests ActionView::Helpers::FormHelper tests ActionView::Helpers::FormHelper
class Developer
def name_before_type_cast
"David"
end
def name
"Santiago"
end
end
def setup def setup
super super
@ -256,6 +266,13 @@ class FormHelperTest < ActionView::TestCase
assert_equal object_name, "post[]" assert_equal object_name, "post[]"
end end
def test_text_field_from_a_user_defined_method
@developer = Developer.new
assert_dom_equal(
'<input id="developer_name" name="developer[name]" size="30" type="text" value="Santiago" />', text_field("developer", "name")
)
end
def test_hidden_field def test_hidden_field
assert_dom_equal '<input id="post_title" name="post[title]" type="hidden" value="Hello World" />', assert_dom_equal '<input id="post_title" name="post[title]" type="hidden" value="Hello World" />',
hidden_field("post", "title") hidden_field("post", "title")

View file

@ -280,6 +280,10 @@ class FormOptionsHelperTest < ActionView::TestCase
opts opts
end end
def test_time_zone_options_returns_html_safe_string
assert time_zone_options_for_select.html_safe?
end
def test_select def test_select
@post = Post.new @post = Post.new
@post.category = "<mus>" @post.category = "<mus>"

View file

@ -296,6 +296,7 @@ class TextHelperTest < ActionView::TestCase
assert_equal %(<p>Link #{link_result_with_options}</p>), auto_link("<p>Link #{link_raw}</p>", :all, {:target => "_blank"}) assert_equal %(<p>Link #{link_result_with_options}</p>), auto_link("<p>Link #{link_raw}</p>", :all, {:target => "_blank"})
assert_equal %(Go to #{link_result}.), auto_link(%(Go to #{link_raw}.)) assert_equal %(Go to #{link_result}.), auto_link(%(Go to #{link_raw}.))
assert_equal %(<p>Go to #{link_result}, then say hello to #{email_result}.</p>), auto_link(%(<p>Go to #{link_raw}, then say hello to #{email_raw}.</p>)) assert_equal %(<p>Go to #{link_result}, then say hello to #{email_result}.</p>), auto_link(%(<p>Go to #{link_raw}, then say hello to #{email_raw}.</p>))
assert_equal %(#{link_result} #{link_result}), auto_link(%(#{link_result} #{link_raw}))
email2_raw = '+david@loudthinking.com' email2_raw = '+david@loudthinking.com'
email2_result = %{<a href="mailto:#{email2_raw}">#{email2_raw}</a>} email2_result = %{<a href="mailto:#{email2_raw}">#{email2_raw}</a>}
@ -368,24 +369,38 @@ class TextHelperTest < ActionView::TestCase
end end
def test_auto_link_other_protocols def test_auto_link_other_protocols
silence_warnings do ftp_raw = 'ftp://example.com/file.txt'
begin assert_equal %(Download #{generate_result(ftp_raw)}), auto_link("Download #{ftp_raw}")
old_re_value = ActionView::Helpers::TextHelper::AUTO_LINK_RE
ActionView::Helpers::TextHelper.const_set :AUTO_LINK_RE, %r{(ftp://)[^\s<]+} file_scheme = 'file:///home/username/RomeoAndJuliet.pdf'
link_raw = 'ftp://example.com/file.txt' z39_scheme = 'z39.50r://host:696/db'
link_result = generate_result(link_raw) chrome_scheme = 'chrome://package/section/path'
assert_equal %(Download #{link_result}), auto_link("Download #{link_raw}") view_source = 'view-source:http://en.wikipedia.org/wiki/URI_scheme'
ensure assert_equal generate_result(z39_scheme), auto_link(z39_scheme)
ActionView::Helpers::TextHelper.const_set :AUTO_LINK_RE, old_re_value assert_equal generate_result(chrome_scheme), auto_link(chrome_scheme)
end assert_equal generate_result(view_source), auto_link(view_source)
end
end end
def test_auto_link_already_linked def test_auto_link_already_linked
linked1 = generate_result('Ruby On Rails', 'http://www.rubyonrails.com') linked1 = generate_result('Ruby On Rails', 'http://www.rubyonrails.com')
linked2 = generate_result('www.rubyonrails.com', 'http://www.rubyonrails.com') linked2 = %('<a href="http://www.example.com">www.example.com</a>')
linked3 = %('<a href="http://www.example.com" rel="nofollow">www.example.com</a>')
linked4 = %('<a href="http://www.example.com"><b>www.example.com</b></a>')
linked5 = %('<a href="#close">close</a> <a href="http://www.example.com"><b>www.example.com</b></a>')
assert_equal linked1, auto_link(linked1) assert_equal linked1, auto_link(linked1)
assert_equal linked2, auto_link(linked2) assert_equal linked2, auto_link(linked2)
assert_equal linked3, auto_link(linked3)
assert_equal linked4, auto_link(linked4)
assert_equal linked5, auto_link(linked5)
linked_email = %Q(<a href="mailto:david@loudthinking.com">Mail me</a>)
assert_equal linked_email, auto_link(linked_email)
end
def test_auto_link_within_tags
link_raw = 'http://www.rubyonrails.org/images/rails.png'
link_result = %Q(<img src="#{link_raw}" />)
assert_equal link_result, auto_link(link_result)
end end
def test_auto_link_with_brackets def test_auto_link_with_brackets
@ -405,12 +420,6 @@ class TextHelperTest < ActionView::TestCase
assert_equal "{link: #{link3_result}}", auto_link("{link: #{link3_raw}}") assert_equal "{link: #{link3_result}}", auto_link("{link: #{link3_raw}}")
end end
def test_auto_link_in_tags
link_raw = 'http://www.rubyonrails.org/images/rails.png'
link_result = %Q(<img src="#{link_raw}" />)
assert_equal link_result, auto_link(link_result)
end
def test_auto_link_at_eol def test_auto_link_at_eol
url1 = "http://api.rubyonrails.com/Foo.html" url1 = "http://api.rubyonrails.com/Foo.html"
url2 = "http://www.ruby-doc.org/core/Bar.html" url2 = "http://www.ruby-doc.org/core/Bar.html"
@ -425,12 +434,32 @@ class TextHelperTest < ActionView::TestCase
assert_equal %(<p><a href="#{url}">#{url[0...7]}...</a><br /><a href="mailto:#{email}">#{email[0...7]}...</a><br /></p>), auto_link("<p>#{url}<br />#{email}<br /></p>") { |url| truncate(url, :length => 10) } assert_equal %(<p><a href="#{url}">#{url[0...7]}...</a><br /><a href="mailto:#{email}">#{email[0...7]}...</a><br /></p>), auto_link("<p>#{url}<br />#{email}<br /></p>") { |url| truncate(url, :length => 10) }
end end
def test_auto_link_with_block_with_html
pic = "http://example.com/pic.png"
url = "http://example.com/album?a&b=c"
assert_equal %(My pic: <a href="#{pic}"><img src="#{pic}" width="160px"></a> -- full album here #{generate_result(url)}), auto_link("My pic: #{pic} -- full album here #{url}") { |link|
if link =~ /\.(jpg|gif|png|bmp|tif)$/i
raw %(<img src="#{link}" width="160px">)
else
link
end
}
end
def test_auto_link_with_options_hash def test_auto_link_with_options_hash
assert_dom_equal 'Welcome to my new blog at <a href="http://www.myblog.com/" class="menu" target="_blank">http://www.myblog.com/</a>. Please e-mail me at <a href="mailto:me@email.com" class="menu" target="_blank">me@email.com</a>.', assert_dom_equal 'Welcome to my new blog at <a href="http://www.myblog.com/" class="menu" target="_blank">http://www.myblog.com/</a>. Please e-mail me at <a href="mailto:me@email.com" class="menu" target="_blank">me@email.com</a>.',
auto_link("Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com.", auto_link("Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com.",
:link => :all, :html => { :class => "menu", :target => "_blank" }) :link => :all, :html => { :class => "menu", :target => "_blank" })
end end
def test_auto_link_with_multiple_trailing_punctuations
url = "http://youtube.com"
url_result = generate_result(url)
assert_equal url_result, auto_link(url)
assert_equal "(link: #{url_result}).", auto_link("(link: #{url}).")
end
def test_cycle_class def test_cycle_class
value = Cycle.new("one", 2, "3") value = Cycle.new("one", 2, "3")
assert_equal("one", value.to_s) assert_equal("one", value.to_s)

View file

@ -1,8 +1,5 @@
*2.3.9 (September 4, 2010)*
*2.3.8 (May 24, 2010)* *2.3.8 (May 24, 2010)*
* Version bump.
*2.3.7 (May 24, 2010)* *2.3.7 (May 24, 2010)*
* Version bump. * Version bump.

View file

@ -192,7 +192,7 @@ spec = Gem::Specification.new do |s|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) } s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end end
s.add_dependency('activesupport', '= 2.3.8' + PKG_BUILD) s.add_dependency('activesupport', '= 2.3.9' + PKG_BUILD)
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite" s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite" s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"

View file

@ -12,6 +12,8 @@ require 'rbench'
__DIR__ = File.dirname(__FILE__) __DIR__ = File.dirname(__FILE__)
$:.unshift "#{__DIR__}/../lib" $:.unshift "#{__DIR__}/../lib"
$:.unshift "#{__DIR__}/../../activesupport/lib"
require 'active_record' require 'active_record'
conn = { :adapter => 'mysql', conn = { :adapter => 'mysql',

View file

@ -282,7 +282,11 @@ module ActiveRecord
end end
through_records.flatten! through_records.flatten!
else else
records.first.class.preload_associations(records, through_association) options = {}
options[:include] = reflection.options[:include] || reflection.options[:source] if reflection.options[:conditions]
options[:order] = reflection.options[:order]
options[:conditions] = reflection.options[:conditions]
records.first.class.preload_associations(records, through_association, options)
through_records = records.map {|record| record.send(through_association)}.flatten through_records = records.map {|record| record.send(through_association)}.flatten
end end
through_records.compact! through_records.compact!
@ -357,7 +361,13 @@ module ActiveRecord
table_name = reflection.klass.quoted_table_name table_name = reflection.klass.quoted_table_name
if interface = reflection.options[:as] if interface = reflection.options[:as]
conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.sti_name}'" parent_type = if reflection.active_record.abstract_class?
self.base_class.sti_name
else
reflection.active_record.sti_name
end
conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{parent_type}'"
else else
foreign_key = reflection.primary_key_name foreign_key = reflection.primary_key_name
conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} #{in_or_equals_for_ids(ids)}" conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} #{in_or_equals_for_ids(ids)}"

View file

@ -1782,7 +1782,7 @@ module ActiveRecord
end end
def using_limitable_reflections?(reflections) def using_limitable_reflections?(reflections)
reflections.collect(&:collection?).length.zero? reflections.none?(&:collection?)
end end
def column_aliases(join_dependency) def column_aliases(join_dependency)

View file

@ -234,9 +234,10 @@ module ActiveRecord
# See destroy for more info. # See destroy for more info.
def destroy_all def destroy_all
load_target load_target
destroy(@target) destroy(@target).tap do
reset_target! reset_target!
end end
end
def create(attrs = {}) def create(attrs = {})
if attrs.is_a?(Array) if attrs.is_a?(Array)
@ -349,7 +350,17 @@ module ActiveRecord
begin begin
if !loaded? if !loaded?
if @target.is_a?(Array) && @target.any? if @target.is_a?(Array) && @target.any?
@target = find_target + @target.find_all {|t| t.new_record? } @target = find_target.map do |f|
i = @target.index(f)
if i
@target.delete_at(i).tap do |t|
keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
t.attributes = f.attributes.except(*keys)
end
else
f
end
end + @target
else else
@target = find_target @target = find_target
end end
@ -364,6 +375,17 @@ module ActiveRecord
end end
def method_missing(method, *args) def method_missing(method, *args)
case method.to_s
when 'find_or_create'
return find(:first, :conditions => args.first) || create(args.first)
when /^find_or_create_by_(.*)$/
rest = $1
return send("find_by_#{rest}", *args) ||
method_missing("create_by_#{rest}", *args)
when /^create_by_(.*)$/
return create($1.split('_and_').zip(args).inject({}) { |h,kv| k,v=kv ; h[k] = v ; h })
end
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
if block_given? if block_given?
super { |*block_args| yield(*block_args) } super { |*block_args| yield(*block_args) }
@ -411,7 +433,14 @@ module ActiveRecord
callback(:before_add, record) callback(:before_add, record)
yield(record) if block_given? yield(record) if block_given?
@target ||= [] unless loaded? @target ||= [] unless loaded?
@target << record unless @reflection.options[:uniq] && @target.include?(record) index = @target.index(record)
unless @reflection.options[:uniq] && index
if index
@target[index] = record
else
@target << record
end
end
callback(:after_add, record) callback(:after_add, record)
set_inverse_instance(record, @owner) set_inverse_instance(record, @owner)
record record

View file

@ -230,10 +230,6 @@ module ActiveRecord
# It's also possible to instantiate related objects, so a Client class belonging to the clients # It's also possible to instantiate related objects, so a Client class belonging to the clients
# table with a +master_id+ foreign key can instantiate master through Client#master. # table with a +master_id+ foreign key can instantiate master through Client#master.
def method_missing(method_id, *args, &block) def method_missing(method_id, *args, &block)
if method_id == :to_ary || method_id == :to_str
raise NoMethodError, "undefined method `#{method_id}' for #{inspect}:#{self.class}"
end
method_name = method_id.to_s method_name = method_id.to_s
if self.class.private_method_defined?(method_name) if self.class.private_method_defined?(method_name)

View file

@ -146,12 +146,12 @@ module ActiveRecord
# add_autosave_association_callbacks(reflect_on_association(name)) # add_autosave_association_callbacks(reflect_on_association(name))
# end # end
ASSOCIATION_TYPES.each do |type| ASSOCIATION_TYPES.each do |type|
module_eval %{ module_eval <<-CODE, __FILE__, __LINE__ + 1
def #{type}(name, options = {}) def #{type}(name, options = {})
super super
add_autosave_association_callbacks(reflect_on_association(name)) add_autosave_association_callbacks(reflect_on_association(name))
end end
} CODE
end end
# Adds a validate and save callback for the association as specified by # Adds a validate and save callback for the association as specified by

View file

@ -1,5 +1,6 @@
require 'yaml' require 'yaml'
require 'set' require 'set'
require 'active_support/core_ext/class/attribute'
module ActiveRecord #:nodoc: module ActiveRecord #:nodoc:
# Generic Active Record exception class. # Generic Active Record exception class.
@ -515,7 +516,7 @@ module ActiveRecord #:nodoc:
@@timestamped_migrations = true @@timestamped_migrations = true
# Determine whether to store the full constant name including namespace when using STI # Determine whether to store the full constant name including namespace when using STI
superclass_delegating_accessor :store_full_sti_class class_attribute :store_full_sti_class
self.store_full_sti_class = false self.store_full_sti_class = false
# Stores the default scope for the class # Stores the default scope for the class
@ -935,11 +936,18 @@ module ActiveRecord #:nodoc:
def reset_counters(id, *counters) def reset_counters(id, *counters)
object = find(id) object = find(id)
counters.each do |association| counters.each do |association|
child_class = reflect_on_association(association).klass child_class = reflect_on_association(association.to_sym).klass
counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column belongs_name = self.name.demodulize.underscore.to_sym
counter_name = child_class.reflect_on_association(belongs_name).counter_cache_column
value = object.send(association).count
connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE") connection.update(<<-CMD, "#{name} UPDATE")
UPDATE #{quoted_table_name}
SET #{connection.quote_column_name(counter_name)} = #{value}
WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}
CMD
end end
return true
end end
# A generic "counter updater" implementation, intended primarily to be # A generic "counter updater" implementation, intended primarily to be
@ -972,19 +980,13 @@ module ActiveRecord #:nodoc:
# # SET comment_count = comment_count + 1, # # SET comment_count = comment_count + 1,
# # WHERE id IN (10, 15) # # WHERE id IN (10, 15)
def update_counters(id, counters) def update_counters(id, counters)
updates = counters.inject([]) { |list, (counter_name, increment)| updates = counters.map do |counter_name, value|
sign = increment < 0 ? "-" : "+" operator = value < 0 ? '-' : '+'
list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}" quoted_column = connection.quote_column_name(counter_name)
}.join(", ") "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
if id.is_a?(Array)
ids_list = id.map {|i| quote_value(i)}.join(', ')
condition = "IN (#{ids_list})"
else
condition = "= #{quote_value(id)}"
end end
update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}") update_all(updates.join(', '), primary_key => id )
end end
# Increment a number field by one, usually representing a count. # Increment a number field by one, usually representing a count.
@ -1284,6 +1286,8 @@ module ActiveRecord #:nodoc:
# Turns the +table_name+ back into a class name following the reverse rules of +table_name+. # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
def class_name(table_name = table_name) # :nodoc: def class_name(table_name = table_name) # :nodoc:
ActiveSupport::Deprecation.warn("ActiveRecord::Base#class_name is deprecated and will be removed in Rails 2.3.9.", caller)
# remove any prefix and/or suffix from the table name # remove any prefix and/or suffix from the table name
class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
class_name = class_name.singularize if pluralize_table_names class_name = class_name.singularize if pluralize_table_names
@ -2642,7 +2646,7 @@ module ActiveRecord #:nodoc:
# Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either # Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
# instance will affect the other. # instance will affect the other.
def becomes(klass) def becomes(klass)
returning klass.new do |became| klass.new.tap do |became|
became.instance_variable_set("@attributes", @attributes) became.instance_variable_set("@attributes", @attributes)
became.instance_variable_set("@attributes_cache", @attributes_cache) became.instance_variable_set("@attributes_cache", @attributes_cache)
became.instance_variable_set("@new_record", new_record?) became.instance_variable_set("@new_record", new_record?)
@ -2660,12 +2664,20 @@ module ActiveRecord #:nodoc:
# Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
# fail and false will be returned. # fail and false will be returned.
def update_attributes(attributes) def update_attributes(attributes)
with_transaction_returning_status(:update_attributes_inside_transaction, attributes)
end
def update_attributes_inside_transaction(attributes) #:nodoc:
self.attributes = attributes self.attributes = attributes
save save
end end
# Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid. # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
def update_attributes!(attributes) def update_attributes!(attributes)
with_transaction_returning_status(:update_attributes_inside_transaction!, attributes)
end
def update_attributes_inside_transaction!(attributes) #:nodoc:
self.attributes = attributes self.attributes = attributes
save! save!
end end

View file

@ -10,7 +10,7 @@ module ActiveRecord
## ##
# :singleton-method: # :singleton-method:
# The connection handler # The connection handler
superclass_delegating_accessor :connection_handler class_attribute :connection_handler
self.connection_handler = ConnectionAdapters::ConnectionHandler.new self.connection_handler = ConnectionAdapters::ConnectionHandler.new
# Returns the connection currently associated with the class. This can # Returns the connection currently associated with the class. This can

View file

@ -195,6 +195,7 @@ module ActiveRecord
# remove_column(:suppliers, :qualification) # remove_column(:suppliers, :qualification)
# remove_columns(:suppliers, :qualification, :experience) # remove_columns(:suppliers, :qualification, :experience)
def remove_column(table_name, *column_names) def remove_column(table_name, *column_names)
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
column_names.flatten.each do |column_name| column_names.flatten.each do |column_name|
execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}" execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
end end

View file

@ -211,6 +211,12 @@ module ActiveRecord
log_info(sql, name, 0) log_info(sql, name, 0)
nil nil
end end
rescue SystemExit, SignalException, NoMemoryError => e
# Don't re-wrap these exceptions. They are probably not being caused by invalid
# sql, but rather some external stimulus beyond the responsibilty of this code.
# Additionaly, wrapping these exceptions with StatementInvalid would lead to
# meaningful loss of data, such as losing SystemExit#status.
raise e
rescue Exception => e rescue Exception => e
# Log message and raise exception. # Log message and raise exception.
# Set last_verification to 0, so that connection gets verified # Set last_verification to 0, so that connection gets verified

View file

@ -315,6 +315,7 @@ module ActiveRecord
rows = [] rows = []
result.each { |row| rows << row } result.each { |row| rows << row }
result.free result.free
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
rows rows
end end
@ -638,6 +639,7 @@ module ActiveRecord
result = execute(sql, name) result = execute(sql, name)
rows = result.all_hashes rows = result.all_hashes
result.free result.free
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
rows rows
end end

View file

@ -25,7 +25,7 @@ module ActiveRecord
module ConnectionAdapters #:nodoc: module ConnectionAdapters #:nodoc:
class SQLite3Adapter < SQLiteAdapter # :nodoc: class SQLite3Adapter < SQLiteAdapter # :nodoc:
def table_structure(table_name) def table_structure(table_name)
returning structure = @connection.table_info(quote_table_name(table_name)) do @connection.table_info(quote_table_name(table_name)).tap do |structure|
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
end end
end end

View file

@ -269,6 +269,7 @@ module ActiveRecord
end end
def remove_column(table_name, *column_names) #:nodoc: def remove_column(table_name, *column_names) #:nodoc:
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
column_names.flatten.each do |column_name| column_names.flatten.each do |column_name|
alter_table(table_name) do |definition| alter_table(table_name) do |definition|
definition.columns.delete(definition[column_name]) definition.columns.delete(definition[column_name])
@ -329,7 +330,7 @@ module ActiveRecord
end end
def table_structure(table_name) def table_structure(table_name)
returning structure = execute("PRAGMA table_info(#{quote_table_name(table_name)})") do execute("PRAGMA table_info(#{quote_table_name(table_name)})").tap do |structure|
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
end end
end end

View file

@ -44,7 +44,7 @@ module ActiveRecord
base.alias_method_chain :update, :dirty base.alias_method_chain :update, :dirty
base.alias_method_chain :reload, :dirty base.alias_method_chain :reload, :dirty
base.superclass_delegating_accessor :partial_updates base.class_attribute :partial_updates
base.partial_updates = true base.partial_updates = true
base.send(:extend, ClassMethods) base.send(:extend, ClassMethods)

View file

@ -11,23 +11,23 @@ en:
accepted: "must be accepted" accepted: "must be accepted"
empty: "can't be empty" empty: "can't be empty"
blank: "can't be blank" blank: "can't be blank"
too_long: "is too long (maximum is {{count}} characters)" too_long: "is too long (maximum is %{count} characters)"
too_short: "is too short (minimum is {{count}} characters)" too_short: "is too short (minimum is %{count} characters)"
wrong_length: "is the wrong length (should be {{count}} characters)" wrong_length: "is the wrong length (should be %{count} characters)"
taken: "has already been taken" taken: "has already been taken"
not_a_number: "is not a number" not_a_number: "is not a number"
greater_than: "must be greater than {{count}}" greater_than: "must be greater than %{count}"
greater_than_or_equal_to: "must be greater than or equal to {{count}}" greater_than_or_equal_to: "must be greater than or equal to %{count}"
equal_to: "must be equal to {{count}}" equal_to: "must be equal to %{count}"
less_than: "must be less than {{count}}" less_than: "must be less than %{count}"
less_than_or_equal_to: "must be less than or equal to {{count}}" less_than_or_equal_to: "must be less than or equal to %{count}"
odd: "must be odd" odd: "must be odd"
even: "must be even" even: "must be even"
record_invalid: "Validation failed: {{errors}}" record_invalid: "Validation failed: %{errors}"
# Append your own errors here or at the model/attributes scope. # Append your own errors here or at the model/attributes scope.
full_messages: full_messages:
format: "{{attribute}} {{message}}" format: "%{attribute} %{message}"
# You can define own errors for models or model attributes. # You can define own errors for models or model attributes.
# The values :model, :attribute and :value are always available for interpolation. # The values :model, :attribute and :value are always available for interpolation.
@ -35,7 +35,7 @@ en:
# For example, # For example,
# models: # models:
# user: # user:
# blank: "This is a custom blank message for {{model}}: {{attribute}}" # blank: "This is a custom blank message for %{model}: %{attribute}"
# attributes: # attributes:
# login: # login:
# blank: "This is a custom blank message for User login" # blank: "This is a custom blank message for User login"

View file

@ -128,6 +128,7 @@ module ActiveRecord
end end
end end
@destroyed = true
freeze freeze
end end

View file

@ -516,7 +516,7 @@ module ActiveRecord
raise DuplicateMigrationNameError.new(name.camelize) raise DuplicateMigrationNameError.new(name.camelize)
end end
klasses << returning(MigrationProxy.new) do |migration| klasses << (MigrationProxy.new).tap do |migration|
migration.name = name.camelize migration.name = name.camelize
migration.version = version migration.version = version
migration.filename = file migration.filename = file

View file

@ -8,10 +8,7 @@ module ActiveRecord
# #
# You can define a scope that applies to all finders using ActiveRecord::Base.default_scope. # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope.
def self.included(base) def self.included(base)
base.class_eval do base.extend ClassMethods
extend ClassMethods
named_scope :scoped, lambda { |scope| scope }
end
end end
module ClassMethods module ClassMethods
@ -19,6 +16,10 @@ module ActiveRecord
read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {}) read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
end end
def scoped(scope, &block)
Scope.new(self, scope, &block)
end
# Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query, # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
# such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>. # such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
# #
@ -84,21 +85,25 @@ module ActiveRecord
# assert_equal expected_options, Shirt.colored('red').proxy_options # assert_equal expected_options, Shirt.colored('red').proxy_options
def named_scope(name, options = {}, &block) def named_scope(name, options = {}, &block)
name = name.to_sym name = name.to_sym
scopes[name] = lambda do |parent_scope, *args| scopes[name] = lambda do |parent_scope, *args|
Scope.new(parent_scope, case options Scope.new(parent_scope, case options
when Hash when Hash
options options
when Proc when Proc
if self.model_name != parent_scope.model_name
options.bind(parent_scope).call(*args)
else
options.call(*args) options.call(*args)
end
end, &block) end, &block)
end end
(class << self; self end).instance_eval do
define_method name do |*args| singleton_class.send :define_method, name do |*args|
scopes[name].call(self, *args) scopes[name].call(self, *args)
end end
end end
end end
end
class Scope class Scope
attr_reader :proxy_scope, :proxy_options, :current_scoped_methods_when_defined attr_reader :proxy_scope, :proxy_options, :current_scoped_methods_when_defined

View file

@ -286,7 +286,9 @@ module ActiveRecord
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy])
elsif attributes['id'] elsif attributes['id']
raise_nested_attributes_record_not_found(association_name, attributes['id']) existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
self.send(association_name.to_s+'=', existing_record)
elsif !reject_new_record?(association_name, attributes) elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}" method = "build_#{association_name}"
@ -356,11 +358,16 @@ module ActiveRecord
unless reject_new_record?(association_name, attributes) unless reject_new_record?(association_name, attributes)
association.build(attributes.except(*UNASSIGNABLE_KEYS)) association.build(attributes.except(*UNASSIGNABLE_KEYS))
end end
elsif existing_records.size == 0 # Existing record but not yet associated
existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s } elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded? association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
else
raise_nested_attributes_record_not_found(association_name, attributes['id'])
end end
end end
end end
@ -380,7 +387,7 @@ module ActiveRecord
ConnectionAdapters::Column.value_to_boolean(hash['_destroy']) ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
end end
# Determines if a new record should be build by checking for # Determines if a new record should be built by checking for
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
# association and evaluates to +true+. # association and evaluates to +true+.
def reject_new_record?(association_name, attributes) def reject_new_record?(association_name, attributes)
@ -396,9 +403,5 @@ module ActiveRecord
end end
end end
def raise_nested_attributes_record_not_found(association_name, record_id)
reflection = self.class.reflect_on_association(association_name)
raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
end
end end
end end

View file

@ -74,7 +74,7 @@ module ActiveRecord #:nodoc:
end end
def serializable_record def serializable_record
returning(serializable_record = {}) do {}.tap do |serializable_record|
serializable_names.each { |name| serializable_record[name] = @record.send(name) } serializable_names.each { |name| serializable_record[name] = @record.send(name) }
add_includes do |association, records, opts| add_includes do |association, records, opts|
if records.is_a?(Enumerable) if records.is_a?(Enumerable)

View file

@ -184,7 +184,7 @@ module ActiveRecord
# Look up a session by id and unmarshal its data if found. # Look up a session by id and unmarshal its data if found.
def find_by_session_id(session_id) def find_by_session_id(session_id)
if record = @@connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{@@connection.quote(session_id)}") if record = connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{connection.quote(session_id)}")
new(:session_id => session_id, :marshaled_data => record['data']) new(:session_id => session_id, :marshaled_data => record['data'])
end end
end end
@ -310,6 +310,14 @@ module ActiveRecord
return true return true
end end
def destroy(env)
if sid = current_session_id(env)
Base.silence do
get_session_model(env, sid).destroy
end
end
end
def get_session_model(env, sid) def get_session_model(env, sid)
if env[ENV_SESSION_OPTIONS_KEY][:id].nil? if env[ENV_SESSION_OPTIONS_KEY][:id].nil?
env[SESSION_RECORD_KEY] = find_session(sid) env[SESSION_RECORD_KEY] = find_session(sid)

View file

@ -80,7 +80,7 @@ module ActiveRecord
# Wraps an error message into a full_message format. # Wraps an error message into a full_message format.
# #
# The default full_message format for any locale is <tt>"{{attribute}} {{message}}"</tt>. # 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 # 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>. # translation for the key <tt>:"activerecord.errors.full_messages.format"</tt>.
# #
@ -109,7 +109,7 @@ module ActiveRecord
keys = [ keys = [
:"full_messages.#{@message}", :"full_messages.#{@message}",
:'full_messages.format', :'full_messages.format',
'{{attribute}} {{message}}' '%{attribute} %{message}'
] ]
options.merge!(:default => keys, :message => self.message) options.merge!(:default => keys, :message => self.message)
@ -604,13 +604,13 @@ module ActiveRecord
# #
# class Person < ActiveRecord::Base # class Person < ActiveRecord::Base
# validates_length_of :first_name, :maximum=>30 # validates_length_of :first_name, :maximum=>30
# validates_length_of :last_name, :maximum=>30, :message=>"less than {{count}} if you don't mind" # validates_length_of :last_name, :maximum=>30, :message=>"less than %{count} if you don't mind"
# validates_length_of :fax, :in => 7..32, :allow_nil => true # validates_length_of :fax, :in => 7..32, :allow_nil => true
# validates_length_of :phone, :in => 7..32, :allow_blank => true # validates_length_of :phone, :in => 7..32, :allow_blank => true
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name" # validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
# validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least {{count}} character" # validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least %{count} character"
# validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with {{count}} characters... don't play me." # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with %{count} characters... don't play me."
# validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least {{count}} words."), :tokenizer => lambda {|str| str.scan(/\w+/) } # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %{count} words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
# end # end
# #
# Configuration options: # Configuration options:
@ -621,9 +621,9 @@ module ActiveRecord
# * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>. # * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation. # * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation. # * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
# * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is {{count}} characters)"). # * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %{count} characters)").
# * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is {{count}} characters)"). # * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %{count} characters)").
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be {{count}} characters)"). # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be %{count} characters)").
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>, <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message. # * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>, <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>). # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
@ -825,7 +825,7 @@ module ActiveRecord
if scope = configuration[:scope] if scope = configuration[:scope]
Array(scope).map do |scope_item| Array(scope).map do |scope_item|
scope_value = record.send(scope_item) scope_value = record.send(scope_item)
condition_sql << " AND " << attribute_condition("#{record.class.quoted_table_name}.#{scope_item}", scope_value) condition_sql << " AND " << attribute_condition("#{record.class.quoted_table_name}.#{connection.quote_column_name(scope_item)}", scope_value)
condition_params << scope_value condition_params << scope_value
end end
end end
@ -885,7 +885,7 @@ module ActiveRecord
# class Person < ActiveRecord::Base # class Person < ActiveRecord::Base
# validates_inclusion_of :gender, :in => %w( m f ) # validates_inclusion_of :gender, :in => %w( m f )
# validates_inclusion_of :age, :in => 0..99 # validates_inclusion_of :age, :in => 0..99
# validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension {{value}} is not included in the list" # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %{value} is not included in the list"
# end # end
# #
# Configuration options: # Configuration options:
@ -919,7 +919,7 @@ module ActiveRecord
# class Person < ActiveRecord::Base # class Person < ActiveRecord::Base
# validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here" # validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
# validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60" # validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
# validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension {{value}} is not allowed" # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %{value} is not allowed"
# end # end
# #
# Configuration options: # Configuration options:

View file

@ -2,7 +2,7 @@ module ActiveRecord
module VERSION #:nodoc: module VERSION #:nodoc:
MAJOR = 2 MAJOR = 2
MINOR = 3 MINOR = 3
TINY = 8 TINY = 9
STRING = [MAJOR, MINOR, TINY].join('.') STRING = [MAJOR, MINOR, TINY].join('.')
end end

View file

@ -65,17 +65,16 @@ class AdapterTest < ActiveRecord::TestCase
end end
def test_not_specifying_database_name_for_cross_database_selects def test_not_specifying_database_name_for_cross_database_selects
begin
assert_nothing_raised do assert_nothing_raised do
ActiveRecord::Base.establish_connection({ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['arunit'].except(:database))
:adapter => 'mysql',
:username => 'rails'
})
ActiveRecord::Base.connection.execute "SELECT activerecord_unittest.pirates.*, activerecord_unittest2.courses.* FROM activerecord_unittest.pirates, activerecord_unittest2.courses" ActiveRecord::Base.connection.execute "SELECT activerecord_unittest.pirates.*, activerecord_unittest2.courses.* FROM activerecord_unittest.pirates, activerecord_unittest2.courses"
end end
ensure
ActiveRecord::Base.establish_connection 'arunit' ActiveRecord::Base.establish_connection 'arunit'
end end
end end
end
if current_adapter?(:PostgreSQLAdapter) if current_adapter?(:PostgreSQLAdapter)
def test_encoding def test_encoding

View file

@ -18,7 +18,7 @@ module Remembered
module ClassMethods module ClassMethods
def remembered; @@remembered ||= []; end def remembered; @@remembered ||= []; end
def random_element; @@remembered.random_element; end def sample; @@remembered.sample; end
end end
end end
@ -82,14 +82,14 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase
[Circle, Square, Triangle, NonPolyOne, NonPolyTwo].map(&:create!) [Circle, Square, Triangle, NonPolyOne, NonPolyTwo].map(&:create!)
end end
1.upto(NUM_SIMPLE_OBJS) do 1.upto(NUM_SIMPLE_OBJS) do
PaintColor.create!(:non_poly_one_id => NonPolyOne.random_element.id) PaintColor.create!(:non_poly_one_id => NonPolyOne.sample.id)
PaintTexture.create!(:non_poly_two_id => NonPolyTwo.random_element.id) PaintTexture.create!(:non_poly_two_id => NonPolyTwo.sample.id)
end end
1.upto(NUM_SHAPE_EXPRESSIONS) do 1.upto(NUM_SHAPE_EXPRESSIONS) do
shape_type = [Circle, Square, Triangle].random_element shape_type = [Circle, Square, Triangle].sample
paint_type = [PaintColor, PaintTexture].random_element paint_type = [PaintColor, PaintTexture].sample
ShapeExpression.create!(:shape_type => shape_type.to_s, :shape_id => shape_type.random_element.id, ShapeExpression.create!(:shape_type => shape_type.to_s, :shape_id => shape_type.sample.id,
:paint_type => paint_type.to_s, :paint_id => paint_type.random_element.id) :paint_type => paint_type.to_s, :paint_id => paint_type.sample.id)
end end
end end

View file

@ -0,0 +1,19 @@
require 'cases/helper'
require 'models/tee'
require 'models/tie'
require 'models/polymorphic_design'
require 'models/polymorphic_price'
class EagerLoadNestedPolymorphicIncludeTest < ActiveRecord::TestCase
fixtures :tees, :ties, :polymorphic_designs, :polymorphic_prices
def test_eager_load_polymorphic_has_one_nested_under_polymorphic_belongs_to
designs = PolymorphicDesign.scoped(:include => {:designable => :polymorphic_price})
associated_price_ids = designs.map{|design| design.designable.polymorphic_price.id}
expected_price_ids = [1, 2, 3, 4]
assert expected_price_ids.all?{|expected_id| associated_price_ids.include?(expected_id)},
"Expected associated prices to be #{expected_price_ids.inspect} but they were #{associated_price_ids.sort.inspect}"
end
end

View file

@ -357,6 +357,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal comments(:more_greetings), Author.find(authors(:david).id, :include => :comments_with_order_and_conditions).comments_with_order_and_conditions.first assert_equal comments(:more_greetings), Author.find(authors(:david).id, :include => :comments_with_order_and_conditions).comments_with_order_and_conditions.first
end end
def test_eager_with_has_many_through_with_conditions_join_model_with_include
post_tags = Post.find(posts(:welcome).id).misc_tags
eager_post_tags = Post.find(1, :include => :misc_tags).misc_tags
assert_equal post_tags, eager_post_tags
end
def test_eager_with_has_many_through_join_model_with_include def test_eager_with_has_many_through_join_model_with_include
author_comments = Author.find(authors(:david).id, :include => :comments_with_include).comments_with_include.to_a author_comments = Author.find(authors(:david).id, :include => :comments_with_include).comments_with_include.to_a
assert_no_queries do assert_no_queries do

View file

@ -21,6 +21,68 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
Client.destroyed_client_ids.clear Client.destroyed_client_ids.clear
end end
def test_create_by
person = Person.create! :first_name => 'tenderlove'
post = Post.find :first
assert_equal [], person.readers
assert_nil person.readers.find_by_post_id(post.id)
reader = person.readers.create_by_post_id post.id
assert_equal 1, person.readers.count
assert_equal 1, person.readers.length
assert_equal post, person.readers.first.post
assert_equal person, person.readers.first.person
end
def test_create_by_multi
person = Person.create! :first_name => 'tenderlove'
post = Post.find :first
assert_equal [], person.readers
reader = person.readers.create_by_post_id_and_skimmer post.id, false
assert_equal 1, person.readers.count
assert_equal 1, person.readers.length
assert_equal post, person.readers.first.post
assert_equal person, person.readers.first.person
end
def test_find_or_create_by
person = Person.create! :first_name => 'tenderlove'
post = Post.find :first
assert_equal [], person.readers
assert_nil person.readers.find_by_post_id(post.id)
reader = person.readers.find_or_create_by_post_id post.id
assert_equal 1, person.readers.count
assert_equal 1, person.readers.length
assert_equal post, person.readers.first.post
assert_equal person, person.readers.first.person
end
def test_find_or_create
person = Person.create! :first_name => 'tenderlove'
post = Post.find :first
assert_equal [], person.readers
assert_nil person.readers.find(:first, :conditions => {
:post_id => post.id
})
reader = person.readers.find_or_create :post_id => post.id
assert_equal 1, person.readers.count
assert_equal 1, person.readers.length
assert_equal post, person.readers.first.post
assert_equal person, person.readers.first.person
end
def force_signal37_to_load_all_clients_of_firm def force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.each {|f| } companies(:first_firm).clients_of_firm.each {|f| }
end end
@ -486,7 +548,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert the_client.new_record? assert the_client.new_record?
end end
def test_find_or_create def test_find_or_create_updates_size
number_of_clients = companies(:first_firm).clients.size number_of_clients = companies(:first_firm).clients.size
the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client") the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client")
assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
@ -772,8 +834,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_destroy_all def test_destroy_all
force_signal37_to_load_all_clients_of_firm force_signal37_to_load_all_clients_of_firm
assert !companies(:first_firm).clients_of_firm.empty?, "37signals has clients after load" clients = companies(:first_firm).clients_of_firm.to_a
companies(:first_firm).clients_of_firm.destroy_all assert !clients.empty?, "37signals has clients after load"
destroyed = companies(:first_firm).clients_of_firm.destroy_all
assert_equal clients.sort_by(&:id), destroyed.sort_by(&:id)
assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen"
assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all" assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all"
assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh" assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh"
end end

View file

@ -18,6 +18,8 @@ require 'models/person'
require 'models/reader' require 'models/reader'
require 'models/parrot' require 'models/parrot'
require 'models/pirate' require 'models/pirate'
require 'models/ship'
require 'models/ship_part'
require 'models/treasure' require 'models/treasure'
require 'models/price_estimate' require 'models/price_estimate'
require 'models/club' require 'models/club'
@ -29,6 +31,23 @@ class AssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :developers_projects, fixtures :accounts, :companies, :developers, :projects, :developers_projects,
:computers, :people, :readers :computers, :people, :readers
def test_loading_the_association_target_should_keep_child_records_marked_for_destruction
ship = Ship.create!(:name => "The good ship Dollypop")
part = ship.parts.create!(:name => "Mast")
part.mark_for_destruction
ship.parts.send(:load_target)
assert ship.parts[0].marked_for_destruction?
end
def test_loading_the_association_target_should_load_most_recent_attributes_for_child_records_marked_for_destruction
ship = Ship.create!(:name => "The good ship Dollypop")
part = ship.parts.create!(:name => "Mast")
part.mark_for_destruction
ShipPart.find(part.id).update_attribute(:name, 'Deck')
ship.parts.send(:load_target)
assert_equal 'Deck', ship.parts[0].name
end
def test_include_with_order_works def test_include_with_order_works
assert_nothing_raised {Account.find(:first, :order => 'id', :include => :firm)} assert_nothing_raised {Account.find(:first, :order => 'id', :include => :firm)}
assert_nothing_raised {Account.find(:first, :order => :id, :include => :firm)} assert_nothing_raised {Account.find(:first, :order => :id, :include => :firm)}
@ -75,6 +94,16 @@ class AssociationsTest < ActiveRecord::TestCase
end end
end end
def test_using_limitable_reflections_helper
using_limitable_reflections = lambda { |reflections| ActiveRecord::Base.send :using_limitable_reflections?, reflections }
belongs_to_reflections = [Tagging.reflect_on_association(:tag), Tagging.reflect_on_association(:super_tag)]
has_many_reflections = [Tag.reflect_on_association(:taggings), Developer.reflect_on_association(:projects)]
mixed_reflections = (belongs_to_reflections + has_many_reflections).uniq
assert using_limitable_reflections.call(belongs_to_reflections), "Belong to associations are limitable"
assert !using_limitable_reflections.call(has_many_reflections), "All has many style associations are not limitable"
assert !using_limitable_reflections.call(mixed_reflections), "No collection associations (has many style) should pass"
end
def test_storing_in_pstore def test_storing_in_pstore
require "tmpdir" require "tmpdir"
store_filename = File.join(Dir.tmpdir, "ar-pstore-association-test") store_filename = File.join(Dir.tmpdir, "ar-pstore-association-test")

View file

@ -1,6 +1,5 @@
require "cases/helper" require "cases/helper"
require 'models/post' require 'models/post'
require 'models/author'
require 'models/event_author' require 'models/event_author'
require 'models/topic' require 'models/topic'
require 'models/reply' require 'models/reply'
@ -588,17 +587,25 @@ class BasicsTest < ActiveRecord::TestCase
end end
def test_destroy_all def test_destroy_all
original_count = Topic.count conditions = "author_name = 'Mary'"
topics_by_mary = Topic.count(:conditions => mary = "author_name = 'Mary'") topics_by_mary = Topic.all(:conditions => conditions, :order => 'id')
assert ! topics_by_mary.empty?
Topic.destroy_all mary assert_difference('Topic.count', -topics_by_mary.size) do
assert_equal original_count - topics_by_mary, Topic.count destroyed = Topic.destroy_all(conditions).sort_by(&:id)
assert_equal topics_by_mary, destroyed
assert destroyed.all? { |topic| topic.frozen? }
end
end end
def test_destroy_many def test_destroy_many
assert_equal 3, Client.count clients = Client.find([2, 3], :order => 'id')
Client.destroy([2, 3])
assert_equal 1, Client.count assert_difference('Client.count', -2) do
destroyed = Client.destroy([2, 3]).sort_by(&:id)
assert_equal clients, destroyed
assert destroyed.all? { |client| client.frozen? }
end
end end
def test_delete_many def test_delete_many
@ -612,55 +619,6 @@ class BasicsTest < ActiveRecord::TestCase
assert Topic.find(2).approved? assert Topic.find(2).approved?
end end
def test_increment_counter
Topic.increment_counter("replies_count", 1)
assert_equal 2, Topic.find(1).replies_count
Topic.increment_counter("replies_count", 1)
assert_equal 3, Topic.find(1).replies_count
end
def test_decrement_counter
Topic.decrement_counter("replies_count", 2)
assert_equal -1, Topic.find(2).replies_count
Topic.decrement_counter("replies_count", 2)
assert_equal -2, Topic.find(2).replies_count
end
def test_reset_counters
assert_equal 1, Topic.find(1).replies_count
Topic.increment_counter("replies_count", 1)
assert_equal 2, Topic.find(1).replies_count
Topic.reset_counters(1, :replies)
assert_equal 1, Topic.find(1).replies_count
end
def test_update_counter
category = categories(:general)
assert_nil category.categorizations_count
assert_equal 2, category.categorizations.count
Category.update_counters(category.id, "categorizations_count" => category.categorizations.count)
category.reload
assert_not_nil category.categorizations_count
assert_equal 2, category.categorizations_count
Category.update_counters(category.id, "categorizations_count" => category.categorizations.count)
category.reload
assert_not_nil category.categorizations_count
assert_equal 4, category.categorizations_count
category_2 = categories(:technology)
count_1, count_2 = (category.categorizations_count || 0), (category_2.categorizations_count || 0)
Category.update_counters([category.id, category_2.id], "categorizations_count" => 2)
category.reload; category_2.reload
assert_equal count_1 + 2, category.categorizations_count
assert_equal count_2 + 2, category_2.categorizations_count
end
def test_update_all def test_update_all
assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'") assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'")
assert_equal "bulk updated!", Topic.find(1).content assert_equal "bulk updated!", Topic.find(1).content
@ -749,6 +707,7 @@ class BasicsTest < ActiveRecord::TestCase
end end
def test_class_name def test_class_name
ActiveSupport::Deprecation.silence do
assert_equal "Firm", ActiveRecord::Base.class_name("firms") assert_equal "Firm", ActiveRecord::Base.class_name("firms")
assert_equal "Category", ActiveRecord::Base.class_name("categories") assert_equal "Category", ActiveRecord::Base.class_name("categories")
assert_equal "AccountHolder", ActiveRecord::Base.class_name("account_holder") assert_equal "AccountHolder", ActiveRecord::Base.class_name("account_holder")
@ -766,6 +725,7 @@ class BasicsTest < ActiveRecord::TestCase
ActiveRecord::Base.table_name_suffix = "" ActiveRecord::Base.table_name_suffix = ""
assert_equal "Firm", ActiveRecord::Base.class_name( "firms" ) assert_equal "Firm", ActiveRecord::Base.class_name( "firms" )
end end
end
def test_null_fields def test_null_fields
assert_nil Topic.find(1).parent_id assert_nil Topic.find(1).parent_id
@ -2090,7 +2050,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_inspect_instance def test_inspect_instance
topic = topics(:first) topic = topics(:first)
assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil>), topic.inspect assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil>), topic.inspect
end end
def test_inspect_new_instance def test_inspect_new_instance

View file

@ -48,6 +48,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
def test_multi_results def test_multi_results
rows = ActiveRecord::Base.connection.select_rows('CALL ten();') rows = ActiveRecord::Base.connection.select_rows('CALL ten();')
assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}" assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}"
assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'"
end end
end end

View file

@ -0,0 +1,84 @@
require 'cases/helper'
require 'models/topic'
require 'models/reply'
require 'models/category'
require 'models/categorization'
class CounterCacheTest < ActiveRecord::TestCase
fixtures :topics, :categories, :categorizations
class SpecialTopic < ::Topic
has_many :special_replies, :foreign_key => 'parent_id'
end
class SpecialReply < ::Reply
belongs_to :special_topic, :foreign_key => 'parent_id', :counter_cache => 'replies_count'
end
test "increment counter" do
topic = Topic.find(1)
assert_difference 'topic.reload.replies_count' do
Topic.increment_counter(:replies_count, topic.id)
end
end
test "decrement counter" do
topic = Topic.find(1)
assert_difference 'topic.reload.replies_count', -1 do
Topic.decrement_counter(:replies_count, topic.id)
end
end
test "reset counters" do
topic = Topic.find(1)
# throw the count off by 1
Topic.increment_counter(:replies_count, topic.id)
# check that it gets reset
assert_difference 'topic.reload.replies_count', -1 do
Topic.reset_counters(topic.id, :replies)
end
end
test "reset counters with string argument" do
topic = Topic.find(1)
Topic.increment_counter('replies_count', topic.id)
assert_difference 'topic.reload.replies_count', -1 do
Topic.reset_counters(topic.id, 'replies')
end
end
test "reset counters with modularized and camelized classnames" do
special = SpecialTopic.create!(:title => 'Special')
SpecialTopic.increment_counter(:replies_count, special.id)
assert_difference 'special.reload.replies_count', -1 do
SpecialTopic.reset_counters(special.id, :special_replies)
end
end
test "update counter with initial null value" do
category = categories(:general)
assert_equal 2, category.categorizations.count
assert_nil category.categorizations_count
Category.update_counters(category.id, :categorizations_count => category.categorizations.count)
assert_equal 2, category.reload.categorizations_count
end
test "update counter for decrement" do
topic = Topic.find(1)
assert_difference 'topic.reload.replies_count', -3 do
Topic.update_counters(topic.id, :replies_count => -3)
end
end
test "update counters of multiple records" do
t1, t2 = topics(:first, :second)
assert_difference ['t1.reload.replies_count', 't2.reload.replies_count'], 2 do
Topic.update_counters([t1.id, t2.id], :replies_count => 2)
end
end
end

View file

@ -53,7 +53,8 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::StaleObjectError) { p2.destroy } assert_raises(ActiveRecord::StaleObjectError) { p2.destroy }
assert p1.destroy assert p1.destroy
assert_equal true, p1.frozen? assert p1.frozen?
assert p1.destroyed?
assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) } assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) }
end end

View file

@ -739,6 +739,10 @@ if ActiveRecord::Base.connection.supports_migrations?
ActiveRecord::Base.connection.drop_table(:hats) ActiveRecord::Base.connection.drop_table(:hats)
end end
def test_remove_column_no_second_parameter_raises_exception
assert_raise(ArgumentError) { Person.connection.remove_column("funny") }
end
def test_change_type_of_not_null_column def test_change_type_of_not_null_column
assert_nothing_raised do assert_nothing_raised do
Topic.connection.change_column "topics", "written_on", :datetime, :null => false Topic.connection.change_column "topics", "written_on", :datetime, :null => false

View file

@ -9,6 +9,11 @@ require 'models/developer'
class NamedScopeTest < ActiveRecord::TestCase class NamedScopeTest < ActiveRecord::TestCase
fixtures :posts, :authors, :topics, :comments, :author_addresses fixtures :posts, :authors, :topics, :comments, :author_addresses
def test_named_scope_with_STI
assert_equal 5,Post.with_type_self.count
assert_equal 1,SpecialPost.with_type_self.count
end
def test_implements_enumerable def test_implements_enumerable
assert !Topic.find(:all).empty? assert !Topic.find(:all).empty?
@ -265,7 +270,7 @@ class NamedScopeTest < ActiveRecord::TestCase
end end
def test_rand_should_select_a_random_object_from_proxy def test_rand_should_select_a_random_object_from_proxy
assert Topic.approved.random_element.is_a?(Topic) assert Topic.approved.sample.is_a?(Topic)
end end
def test_should_use_where_in_query_for_named_scope def test_should_use_where_in_query_for_named_scope

View file

@ -175,12 +175,6 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
end end
def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
@pirate.ship_attributes = { :id => 1234567890 }
end
end
def test_should_take_a_hash_with_string_keys_and_update_the_associated_model def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
@pirate.reload.ship_attributes = { 'id' => @ship.id, 'name' => 'Davy Jones Gold Dagger' } @pirate.reload.ship_attributes = { 'id' => @ship.id, 'name' => 'Davy Jones Gold Dagger' }
@ -330,10 +324,13 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
assert_equal 'Arr', @ship.pirate.catchphrase assert_equal 'Arr', @ship.pirate.catchphrase
end end
def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record def test_should_associate_with_record_if_parent_record_is_not_saved
assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}" do @ship.destroy
@ship.pirate_attributes = { :id => 1234567890 } @pirate = Pirate.create(:catchphrase => 'Arr')
end @ship = Ship.new(:name => 'Nights Dirty Lightning', :pirate_attributes => { :id => @pirate.id, :catchphrase => @pirate.catchphrase})
assert_equal @ship.name, 'Nights Dirty Lightning'
assert_equal @pirate, @ship.pirate
end end
def test_should_take_a_hash_with_string_keys_and_update_the_associated_model def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
@ -437,6 +434,11 @@ module NestedAttributesOnACollectionAssociationTests
assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name] assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name]
end end
def test_should_assign_existing_children_if_parent_is_new
@pirate = Pirate.new({:catchphrase => "Don' botharr talkin' like one, savvy?"}.merge(@alternate_params))
assert_equal ['Grace OMalley', 'Privateers Greed'], [@pirate.send(@association_name)[0].name, @pirate.send(@association_name)[1].name]
end
def test_should_take_an_array_and_assign_the_attributes_to_the_associated_models def test_should_take_an_array_and_assign_the_attributes_to_the_associated_models
@pirate.send(association_setter, @alternate_params[association_getter].values) @pirate.send(association_setter, @alternate_params[association_getter].values)
@pirate.save @pirate.save
@ -465,6 +467,33 @@ module NestedAttributesOnACollectionAssociationTests
assert_equal 'Grace OMalley', @child_1.reload.name assert_equal 'Grace OMalley', @child_1.reload.name
end end
def test_should_not_overwrite_unsaved_updates_when_loading_association
@pirate.reload
@pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
assert_equal 'Grace OMalley', @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.name
end
def test_should_preserve_order_when_not_overwriting_unsaved_updates
@pirate.reload
@pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
assert_equal @child_1.id, @pirate.send(@association_name).send(:load_target).first.id
end
def test_should_refresh_saved_records_when_not_overwriting_unsaved_updates
@pirate.reload
record = @pirate.class.reflect_on_association(@association_name).klass.new(:name => 'Grace OMalley')
@pirate.send(@association_name) << record
record.save!
@pirate.send(@association_name).last.update_attributes!(:name => 'Polly')
assert_equal 'Polly', @pirate.send(@association_name).send(:load_target).last.name
end
def test_should_not_remove_scheduled_destroys_when_loading_association
@pirate.reload
@pirate.send(association_setter, [{ :id => @child_1.id, :_destroy => '1' }])
assert @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.marked_for_destruction?
end
def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models
@child_1.stubs(:id).returns('ABC1X') @child_1.stubs(:id).returns('ABC1X')
@child_2.stubs(:id).returns('ABC2X') @child_2.stubs(:id).returns('ABC2X')
@ -479,8 +508,8 @@ module NestedAttributesOnACollectionAssociationTests
assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name] assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name]
end end
def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record def test_should_not_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}" do assert_nothing_raised ActiveRecord::RecordNotFound do
@pirate.attributes = { association_getter => [{ :id => 1234567890 }] } @pirate.attributes = { association_getter => [{ :id => 1234567890 }] }
end end
end end
@ -782,6 +811,12 @@ class TestHasManyAutosaveAssoictaionWhichItselfHasAutosaveAssociations < ActiveR
@trinket = @part.trinkets.create!(:name => "Necklace") @trinket = @part.trinkets.create!(:name => "Necklace")
end end
test "if association is not loaded and association record is saved and then in memory record attributes should be saved" do
@ship.parts_attributes=[{:id => @part.id,:name =>'Deck'}]
assert_equal 1, @ship.parts.proxy_target.size
assert_equal 'Deck', @ship.parts[0].name
end
test "when grandchild changed in memory, saving parent should save grandchild" do test "when grandchild changed in memory, saving parent should save grandchild" do
@trinket.name = "changed" @trinket.name = "changed"
@ship.save @ship.save

View file

@ -24,25 +24,25 @@ class ReflectionTest < ActiveRecord::TestCase
def test_read_attribute_names def test_read_attribute_names
assert_equal( assert_equal(
%w( id title author_name author_email_address bonus_time written_on last_read content approved replies_count parent_id parent_title type ).sort, %w( id title author_name author_email_address bonus_time written_on last_read content group approved replies_count parent_id parent_title type ).sort,
@first.attribute_names @first.attribute_names
) )
end end
def test_columns def test_columns
assert_equal 13, Topic.columns.length assert_equal 14, Topic.columns.length
end end
def test_columns_are_returned_in_the_order_they_were_declared def test_columns_are_returned_in_the_order_they_were_declared
column_names = Topic.columns.map { |column| column.name } column_names = Topic.columns.map { |column| column.name }
assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id parent_title type), column_names assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id parent_title type group), column_names
end end
def test_content_columns def test_content_columns
content_columns = Topic.content_columns content_columns = Topic.content_columns
content_column_names = content_columns.map {|column| column.name} content_column_names = content_columns.map {|column| column.name}
assert_equal 9, content_columns.length assert_equal 10, content_columns.length
assert_equal %w(title author_name author_email_address written_on bonus_time last_read content approved parent_title).sort, content_column_names.sort assert_equal %w(title author_name author_email_address written_on bonus_time last_read content group approved parent_title).sort, content_column_names.sort
end end
def test_column_string_type_and_limit def test_column_string_type_and_limit

View file

@ -0,0 +1,16 @@
require "cases/helper"
require 'models/topic'
require 'models/minimalistic'
class StoredProcedureTest < ActiveRecord::TestCase
fixtures :topics
# Test that MySQL allows multiple results for stored procedures
if Mysql.const_defined?(:CLIENT_MULTI_RESULTS)
def test_multi_results_from_find_by_sql
topics = Topic.find_by_sql 'CALL topics();'
assert_equal 1, topics.size
assert ActiveRecord::Base.connection.active?, "Bad connection use by 'MysqlAdapter.select'"
end
end
end

View file

@ -3,10 +3,12 @@ require 'models/topic'
require 'models/reply' require 'models/reply'
require 'models/developer' require 'models/developer'
require 'models/book' require 'models/book'
require 'models/author'
require 'models/post'
class TransactionTest < ActiveRecord::TestCase class TransactionTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false self.use_transactional_fixtures = false
fixtures :topics, :developers fixtures :topics, :developers, :authors, :posts
def setup def setup
@first, @second = Topic.find(1, 2).sort_by { |t| t.id } @first, @second = Topic.find(1, 2).sort_by { |t| t.id }
@ -34,6 +36,25 @@ class TransactionTest < ActiveRecord::TestCase
end end
end end
def test_update_attributes_should_rollback_on_failure
author = Author.find(1)
posts_count = author.posts.size
assert posts_count > 0
status = author.update_attributes(:name => nil, :post_ids => [])
assert !status
assert_equal posts_count, author.posts(true).size
end
def test_update_attributes_should_rollback_on_failure!
author = Author.find(1)
posts_count = author.posts.size
assert posts_count > 0
assert_raise(ActiveRecord::RecordInvalid) do
author.update_attributes!(:name => nil, :post_ids => [])
end
assert_equal posts_count, author.posts(true).size
end
def test_successful_with_return def test_successful_with_return
class << Topic.connection class << Topic.connection
alias :real_commit_db_transaction :commit_db_transaction alias :real_commit_db_transaction :commit_db_transaction

View file

@ -486,20 +486,20 @@ class ActiveRecordErrorI18nTests < ActiveSupport::TestCase
end end
test ":default is only given to message if a symbol is supplied" do test ":default is only given to message if a symbol is supplied" do
store_translations(:errors => { :messages => { :"foo bar" => "You fooed: {{value}}." } }) store_translations(:errors => { :messages => { :"foo bar" => "You fooed: %{value}." } })
@reply.errors.add(:title, :inexistent, :default => "foo bar") @reply.errors.add(:title, :inexistent, :default => "foo bar")
assert_equal "foo bar", @reply.errors[:title] assert_equal "foo bar", @reply.errors[:title]
end end
test "#generate_message passes the model attribute value for interpolation" do test "#generate_message passes the model attribute value for interpolation" do
store_translations(:errors => { :messages => { :foo => "You fooed: {{value}}." } }) store_translations(:errors => { :messages => { :foo => "You fooed: %{value}." } })
@reply.title = "da title" @reply.title = "da title"
assert_error_message 'You fooed: da title.', :title, :foo assert_error_message 'You fooed: da title.', :title, :foo
end end
test "#generate_message passes the human_name of the model for interpolation" do test "#generate_message passes the human_name of the model for interpolation" do
store_translations( store_translations(
:errors => { :messages => { :foo => "You fooed: {{model}}." } }, :errors => { :messages => { :foo => "You fooed: %{model}." } },
:models => { :topic => 'da topic' } :models => { :topic => 'da topic' }
) )
assert_error_message 'You fooed: da topic.', :title, :foo assert_error_message 'You fooed: da topic.', :title, :foo
@ -507,7 +507,7 @@ class ActiveRecordErrorI18nTests < ActiveSupport::TestCase
test "#generate_message passes the human_name of the attribute for interpolation" do test "#generate_message passes the human_name of the attribute for interpolation" do
store_translations( store_translations(
:errors => { :messages => { :foo => "You fooed: {{attribute}}." } }, :errors => { :messages => { :foo => "You fooed: %{attribute}." } },
:attributes => { :topic => { :title => 'da topic title' } } :attributes => { :topic => { :title => 'da topic title' } }
) )
assert_error_message 'You fooed: da topic title.', :title, :foo assert_error_message 'You fooed: da topic title.', :title, :foo
@ -607,17 +607,17 @@ class ActiveRecordErrorI18nTests < ActiveSupport::TestCase
end end
test "#full_message with a format present" do test "#full_message with a format present" do
store_translations(:errors => { :messages => { :kaputt => 'is kaputt' }, :full_messages => { :format => '{{attribute}}: {{message}}' } }) store_translations(:errors => { :messages => { :kaputt => 'is kaputt' }, :full_messages => { :format => '%{attribute}: %{message}' } })
assert_full_message 'Title: is kaputt', :title, :kaputt assert_full_message 'Title: is kaputt', :title, :kaputt
end end
test "#full_message with a type specific format present" do test "#full_message with a type specific format present" do
store_translations(:errors => { :messages => { :kaputt => 'is kaputt' }, :full_messages => { :kaputt => '{{attribute}} {{message}}!' } }) store_translations(:errors => { :messages => { :kaputt => 'is kaputt' }, :full_messages => { :kaputt => '%{attribute} %{message}!' } })
assert_full_message 'Title is kaputt!', :title, :kaputt assert_full_message 'Title is kaputt!', :title, :kaputt
end end
test "#full_message with class-level specified custom message" do test "#full_message with class-level specified custom message" do
store_translations(:errors => { :messages => { :broken => 'is kaputt' }, :full_messages => { :broken => '{{attribute}} {{message}}?!' } }) store_translations(:errors => { :messages => { :broken => 'is kaputt' }, :full_messages => { :broken => '%{attribute} %{message}?!' } })
assert_full_message 'Title is kaputt?!', :title, :kaputt, :message => :broken assert_full_message 'Title is kaputt?!', :title, :kaputt, :message => :broken
end end
@ -625,7 +625,7 @@ class ActiveRecordErrorI18nTests < ActiveSupport::TestCase
store_translations(:my_errors => { :messages => { :kaputt => 'is kaputt' } }) store_translations(:my_errors => { :messages => { :kaputt => 'is kaputt' } })
assert_full_message 'Title is kaputt', :title, :kaputt, :scope => [:activerecord, :my_errors] assert_full_message 'Title is kaputt', :title, :kaputt, :scope => [:activerecord, :my_errors]
store_translations(:my_errors => { :full_messages => { :kaputt => '{{attribute}} {{message}}!' } }) store_translations(:my_errors => { :full_messages => { :kaputt => '%{attribute} %{message}!' } })
assert_full_message 'Title is kaputt!', :title, :kaputt, :scope => [:activerecord, :my_errors] assert_full_message 'Title is kaputt!', :title, :kaputt, :scope => [:activerecord, :my_errors]
end end
@ -763,7 +763,7 @@ class ActiveRecordDefaultErrorMessagesI18nTests < ActiveSupport::TestCase
end end
test "custom message string interpolation" do test "custom message string interpolation" do
assert_equal 'custom message title', error_message(:invalid, :default => 'custom message {{value}}', :value => 'title') assert_equal 'custom message title', error_message(:invalid, :default => 'custom message %{value}', :value => 'title')
end end
end end
@ -897,14 +897,14 @@ class ActiveRecordValidationsI18nFullMessagesFullStackTests < ActiveSupport::Tes
test "full_message format stored per custom error message key" do test "full_message format stored per custom error message key" do
assert_full_message("Name is broken!") do assert_full_message("Name is broken!") do
store_translations :errors => { :messages => { :broken => 'is broken' }, :full_messages => { :broken => '{{attribute}} {{message}}!' } } store_translations :errors => { :messages => { :broken => 'is broken' }, :full_messages => { :broken => '%{attribute} %{message}!' } }
I18nPerson.validates_presence_of :name, :message => :broken I18nPerson.validates_presence_of :name, :message => :broken
end end
end end
test "full_message format stored per error type" do test "full_message format stored per error type" do
assert_full_message("Name can't be blank!") do assert_full_message("Name can't be blank!") do
store_translations :errors => { :full_messages => { :blank => '{{attribute}} {{message}}!' } } store_translations :errors => { :full_messages => { :blank => '%{attribute} %{message}!' } }
I18nPerson.validates_presence_of :name I18nPerson.validates_presence_of :name
end end
end end
@ -912,7 +912,7 @@ class ActiveRecordValidationsI18nFullMessagesFullStackTests < ActiveSupport::Tes
test "full_message format stored as default" do test "full_message format stored as default" do
assert_full_message("Name: can't be blank") do assert_full_message("Name: can't be blank") do
store_translations :errors => { :full_messages => { :format => '{{attribute}}: {{message}}' } } store_translations :errors => { :full_messages => { :format => '%{attribute}: %{message}' } }
I18nPerson.validates_presence_of :name I18nPerson.validates_presence_of :name
end end
end end

View file

@ -434,6 +434,18 @@ class ValidationsTest < ActiveRecord::TestCase
end end
end end
def test_validate_uniqueness_with_reserved_word_as_scope
repair_validations(Reply) do
Topic.validates_uniqueness_of(:content, :scope => "group")
t1 = Topic.create "title" => "t1", "content" => "hello world2"
assert t1.valid?
t2 = Topic.create "title" => "t2", "content" => "hello world2"
assert !t2.valid?
end
end
def test_validate_uniqueness_scoped_to_defining_class def test_validate_uniqueness_scoped_to_defining_class
t = Topic.create("title" => "What, me worry?") t = Topic.create("title" => "What, me worry?")
@ -678,7 +690,7 @@ class ValidationsTest < ActiveRecord::TestCase
end end
def test_validate_format_with_formatted_message def test_validate_format_with_formatted_message
Topic.validates_format_of(:title, :with => /^Valid Title$/, :message => "can't be {{value}}") Topic.validates_format_of(:title, :with => /^Valid Title$/, :message => "can't be %{value}")
t = Topic.create(:title => 'Invalid title') t = Topic.create(:title => 'Invalid title')
assert_equal "can't be Invalid title", t.errors.on(:title) assert_equal "can't be Invalid title", t.errors.on(:title)
end end
@ -741,7 +753,7 @@ class ValidationsTest < ActiveRecord::TestCase
end end
def test_validates_inclusion_of_with_formatted_message def test_validates_inclusion_of_with_formatted_message
Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :message => "option {{value}} is not in the list" ) Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :message => "option %{value} is not in the list" )
assert Topic.create("title" => "a", "content" => "abc").valid? assert Topic.create("title" => "a", "content" => "abc").valid?
@ -768,7 +780,7 @@ class ValidationsTest < ActiveRecord::TestCase
end end
def test_validates_exclusion_of_with_formatted_message def test_validates_exclusion_of_with_formatted_message
Topic.validates_exclusion_of( :title, :in => %w( abe monkey ), :message => "option {{value}} is restricted" ) Topic.validates_exclusion_of( :title, :in => %w( abe monkey ), :message => "option %{value} is restricted" )
assert Topic.create("title" => "something", "content" => "abc") assert Topic.create("title" => "something", "content" => "abc")
@ -868,7 +880,7 @@ class ValidationsTest < ActiveRecord::TestCase
end end
def test_optionally_validates_length_of_using_within_on_create def test_optionally_validates_length_of_using_within_on_create
Topic.validates_length_of :title, :content, :within => 5..10, :on => :create, :too_long => "my string is too long: {{count}}" Topic.validates_length_of :title, :content, :within => 5..10, :on => :create, :too_long => "my string is too long: %{count}"
t = Topic.create("title" => "thisisnotvalid", "content" => "whatever") t = Topic.create("title" => "thisisnotvalid", "content" => "whatever")
assert !t.save assert !t.save
@ -889,7 +901,7 @@ class ValidationsTest < ActiveRecord::TestCase
end end
def test_optionally_validates_length_of_using_within_on_update def test_optionally_validates_length_of_using_within_on_update
Topic.validates_length_of :title, :content, :within => 5..10, :on => :update, :too_short => "my string is too short: {{count}}" Topic.validates_length_of :title, :content, :within => 5..10, :on => :update, :too_short => "my string is too short: %{count}"
t = Topic.create("title" => "vali", "content" => "whatever") t = Topic.create("title" => "vali", "content" => "whatever")
assert !t.save assert !t.save
@ -953,7 +965,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_validates_length_with_globally_modified_error_message def test_validates_length_with_globally_modified_error_message
defaults = ActiveSupport::Deprecation.silence { ActiveRecord::Errors.default_error_messages } defaults = ActiveSupport::Deprecation.silence { ActiveRecord::Errors.default_error_messages }
original_message = defaults[:too_short] original_message = defaults[:too_short]
defaults[:too_short] = 'tu est trops petit hombre {{count}}' defaults[:too_short] = 'tu est trops petit hombre %{count}'
Topic.validates_length_of :title, :minimum => 10 Topic.validates_length_of :title, :minimum => 10
t = Topic.create(:title => 'too short') t = Topic.create(:title => 'too short')
@ -1004,7 +1016,7 @@ class ValidationsTest < ActiveRecord::TestCase
end end
def test_validates_length_of_custom_errors_for_minimum_with_message def test_validates_length_of_custom_errors_for_minimum_with_message
Topic.validates_length_of( :title, :minimum=>5, :message=>"boo {{count}}" ) Topic.validates_length_of( :title, :minimum=>5, :message=>"boo %{count}" )
t = Topic.create("title" => "uhoh", "content" => "whatever") t = Topic.create("title" => "uhoh", "content" => "whatever")
assert !t.valid? assert !t.valid?
assert t.errors.on(:title) assert t.errors.on(:title)
@ -1012,7 +1024,7 @@ class ValidationsTest < ActiveRecord::TestCase
end end
def test_validates_length_of_custom_errors_for_minimum_with_too_short def test_validates_length_of_custom_errors_for_minimum_with_too_short
Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo {{count}}" ) Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo %{count}" )
t = Topic.create("title" => "uhoh", "content" => "whatever") t = Topic.create("title" => "uhoh", "content" => "whatever")
assert !t.valid? assert !t.valid?
assert t.errors.on(:title) assert t.errors.on(:title)
@ -1020,7 +1032,7 @@ class ValidationsTest < ActiveRecord::TestCase
end end
def test_validates_length_of_custom_errors_for_maximum_with_message def test_validates_length_of_custom_errors_for_maximum_with_message
Topic.validates_length_of( :title, :maximum=>5, :message=>"boo {{count}}" ) Topic.validates_length_of( :title, :maximum=>5, :message=>"boo %{count}" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid? assert !t.valid?
assert t.errors.on(:title) assert t.errors.on(:title)
@ -1028,7 +1040,7 @@ class ValidationsTest < ActiveRecord::TestCase
end end
def test_validates_length_of_custom_errors_for_in def test_validates_length_of_custom_errors_for_in
Topic.validates_length_of(:title, :in => 10..20, :message => "hoo {{count}}") Topic.validates_length_of(:title, :in => 10..20, :message => "hoo %{count}")
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid? assert !t.valid?
assert t.errors.on(:title) assert t.errors.on(:title)
@ -1041,7 +1053,7 @@ class ValidationsTest < ActiveRecord::TestCase
end end
def test_validates_length_of_custom_errors_for_maximum_with_too_long def test_validates_length_of_custom_errors_for_maximum_with_too_long
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}" ) Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid? assert !t.valid?
assert t.errors.on(:title) assert t.errors.on(:title)
@ -1049,7 +1061,7 @@ class ValidationsTest < ActiveRecord::TestCase
end end
def test_validates_length_of_custom_errors_for_is_with_message def test_validates_length_of_custom_errors_for_is_with_message
Topic.validates_length_of( :title, :is=>5, :message=>"boo {{count}}" ) Topic.validates_length_of( :title, :is=>5, :message=>"boo %{count}" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid? assert !t.valid?
assert t.errors.on(:title) assert t.errors.on(:title)
@ -1057,7 +1069,7 @@ class ValidationsTest < ActiveRecord::TestCase
end end
def test_validates_length_of_custom_errors_for_is_with_wrong_length def test_validates_length_of_custom_errors_for_is_with_wrong_length
Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo {{count}}" ) Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo %{count}" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid? assert !t.valid?
assert t.errors.on(:title) assert t.errors.on(:title)
@ -1123,7 +1135,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_optionally_validates_length_of_using_within_on_create_utf8 def test_optionally_validates_length_of_using_within_on_create_utf8
with_kcode('UTF8') do with_kcode('UTF8') do
Topic.validates_length_of :title, :within => 5..10, :on => :create, :too_long => "長すぎます: {{count}}" Topic.validates_length_of :title, :within => 5..10, :on => :create, :too_long => "長すぎます: %{count}"
t = Topic.create("title" => "一二三四五六七八九十A", "content" => "whatever") t = Topic.create("title" => "一二三四五六七八九十A", "content" => "whatever")
assert !t.save assert !t.save
@ -1146,7 +1158,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_optionally_validates_length_of_using_within_on_update_utf8 def test_optionally_validates_length_of_using_within_on_update_utf8
with_kcode('UTF8') do with_kcode('UTF8') do
Topic.validates_length_of :title, :within => 5..10, :on => :update, :too_short => "短すぎます: {{count}}" Topic.validates_length_of :title, :within => 5..10, :on => :update, :too_short => "短すぎます: %{count}"
t = Topic.create("title" => "一二三4", "content" => "whatever") t = Topic.create("title" => "一二三4", "content" => "whatever")
assert !t.save assert !t.save
@ -1181,7 +1193,7 @@ class ValidationsTest < ActiveRecord::TestCase
end end
def test_validates_length_of_with_block def test_validates_length_of_with_block
Topic.validates_length_of :content, :minimum => 5, :too_short=>"Your essay must be at least {{count}} words.", Topic.validates_length_of :content, :minimum => 5, :too_short=>"Your essay must be at least %{count} words.",
:tokenizer => lambda {|str| str.scan(/\w+/) } :tokenizer => lambda {|str| str.scan(/\w+/) }
t = Topic.create!(:content => "this content should be long enough") t = Topic.create!(:content => "this content should be long enough")
assert t.valid? assert t.valid?
@ -1356,7 +1368,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_if_validation_using_method_true def test_if_validation_using_method_true
# When the method returns true # When the method returns true
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => :condition_is_true ) Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :if => :condition_is_true )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid? assert !t.valid?
assert t.errors.on(:title) assert t.errors.on(:title)
@ -1365,7 +1377,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_unless_validation_using_method_true def test_unless_validation_using_method_true
# When the method returns true # When the method returns true
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => :condition_is_true ) Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :unless => :condition_is_true )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid? assert t.valid?
assert !t.errors.on(:title) assert !t.errors.on(:title)
@ -1373,7 +1385,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_if_validation_using_method_false def test_if_validation_using_method_false
# When the method returns false # When the method returns false
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => :condition_is_true_but_its_not ) Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :if => :condition_is_true_but_its_not )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid? assert t.valid?
assert !t.errors.on(:title) assert !t.errors.on(:title)
@ -1381,7 +1393,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_unless_validation_using_method_false def test_unless_validation_using_method_false
# When the method returns false # When the method returns false
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => :condition_is_true_but_its_not ) Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :unless => :condition_is_true_but_its_not )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid? assert !t.valid?
assert t.errors.on(:title) assert t.errors.on(:title)
@ -1390,7 +1402,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_if_validation_using_string_true def test_if_validation_using_string_true
# When the evaluated string returns true # When the evaluated string returns true
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => "a = 1; a == 1" ) Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :if => "a = 1; a == 1" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid? assert !t.valid?
assert t.errors.on(:title) assert t.errors.on(:title)
@ -1399,7 +1411,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_unless_validation_using_string_true def test_unless_validation_using_string_true
# When the evaluated string returns true # When the evaluated string returns true
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => "a = 1; a == 1" ) Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :unless => "a = 1; a == 1" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid? assert t.valid?
assert !t.errors.on(:title) assert !t.errors.on(:title)
@ -1407,7 +1419,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_if_validation_using_string_false def test_if_validation_using_string_false
# When the evaluated string returns false # When the evaluated string returns false
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :if => "false") Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :if => "false")
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid? assert t.valid?
assert !t.errors.on(:title) assert !t.errors.on(:title)
@ -1415,7 +1427,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_unless_validation_using_string_false def test_unless_validation_using_string_false
# When the evaluated string returns false # When the evaluated string returns false
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", :unless => "false") Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :unless => "false")
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid? assert !t.valid?
assert t.errors.on(:title) assert t.errors.on(:title)
@ -1424,7 +1436,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_if_validation_using_block_true def test_if_validation_using_block_true
# When the block returns true # When the block returns true
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}",
:if => Proc.new { |r| r.content.size > 4 } ) :if => Proc.new { |r| r.content.size > 4 } )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid? assert !t.valid?
@ -1434,7 +1446,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_unless_validation_using_block_true def test_unless_validation_using_block_true
# When the block returns true # When the block returns true
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}",
:unless => Proc.new { |r| r.content.size > 4 } ) :unless => Proc.new { |r| r.content.size > 4 } )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid? assert t.valid?
@ -1443,7 +1455,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_if_validation_using_block_false def test_if_validation_using_block_false
# When the block returns false # When the block returns false
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}",
:if => Proc.new { |r| r.title != "uhohuhoh"} ) :if => Proc.new { |r| r.title != "uhohuhoh"} )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid? assert t.valid?
@ -1452,7 +1464,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_unless_validation_using_block_false def test_unless_validation_using_block_false
# When the block returns false # When the block returns false
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo {{count}}", Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}",
:unless => Proc.new { |r| r.title != "uhohuhoh"} ) :unless => Proc.new { |r| r.title != "uhohuhoh"} )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid? assert !t.valid?
@ -1634,13 +1646,13 @@ class ValidatesNumericalityTest < ActiveRecord::TestCase
end end
def test_validates_numericality_with_numeric_message def test_validates_numericality_with_numeric_message
Topic.validates_numericality_of :approved, :less_than => 4, :message => "smaller than {{count}}" Topic.validates_numericality_of :approved, :less_than => 4, :message => "smaller than %{count}"
topic = Topic.new("title" => "numeric test", "approved" => 10) topic = Topic.new("title" => "numeric test", "approved" => 10)
assert !topic.valid? assert !topic.valid?
assert_equal "smaller than 4", topic.errors.on(:approved) assert_equal "smaller than 4", topic.errors.on(:approved)
Topic.validates_numericality_of :approved, :greater_than => 4, :message => "greater than {{count}}" Topic.validates_numericality_of :approved, :greater_than => 4, :message => "greater than %{count}"
topic = Topic.new("title" => "numeric test", "approved" => 1) topic = Topic.new("title" => "numeric test", "approved" => 1)
assert !topic.valid? assert !topic.valid?

View file

@ -0,0 +1,19 @@
awesome_tee_design:
id: 1
designable_id: 1
designable_type: Tee
sucky_tee_design:
id: 2
designable_id: 2
designable_type: Tee
awesome_hat_design:
id: 3
designable_id: 1
designable_type: Tie
sucky_hat_design:
id: 4
designable_id: 2
designable_type: Tie

View file

@ -0,0 +1,19 @@
awesome_tee_price:
id: 1
sellable_id: 1
sellable_type: Tee
sucky_tee_price:
id: 2
sellable_id: 2
sellable_type: Tee
awesome_hat_price:
id: 3
sellable_id: 1
sellable_type: Tie
sucky_hat_price:
id: 4
sellable_id: 2
sellable_type: Tie

View file

@ -0,0 +1,4 @@
awesome_tee:
id: 1
sucky_tee:
id: 2

View file

@ -0,0 +1,4 @@
awesome_tie:
id: 1
sucky_tie:
id: 2

View file

@ -106,6 +106,8 @@ class Author < ActiveRecord::Base
"#{id}-#{name}" "#{id}-#{name}"
end end
validates_presence_of :name
private private
def log_before_adding(object) def log_before_adding(object)
@post_log << "before_adding#{object.id || '<new>'}" @post_log << "before_adding#{object.id || '<new>'}"

View file

@ -1,3 +1,6 @@
require 'models/author'
require 'models/event'
class EventAuthor < ActiveRecord::Base class EventAuthor < ActiveRecord::Base
belongs_to :author belongs_to :author
belongs_to :event belongs_to :event

View file

@ -1,7 +1,7 @@
class Pirate < ActiveRecord::Base class Pirate < ActiveRecord::Base
belongs_to :parrot, :validate => true belongs_to :parrot, :validate => true
belongs_to :non_validated_parrot, :class_name => 'Parrot' belongs_to :non_validated_parrot, :class_name => 'Parrot'
has_and_belongs_to_many :parrots, :validate => true has_and_belongs_to_many :parrots, :validate => true, :order => 'parrots.id ASC'
has_and_belongs_to_many :non_validated_parrots, :class_name => 'Parrot' has_and_belongs_to_many :non_validated_parrots, :class_name => 'Parrot'
has_and_belongs_to_many :parrots_with_method_callbacks, :class_name => "Parrot", has_and_belongs_to_many :parrots_with_method_callbacks, :class_name => "Parrot",
:before_add => :log_before_add, :before_add => :log_before_add,
@ -21,7 +21,7 @@ class Pirate < ActiveRecord::Base
has_one :ship has_one :ship
has_one :update_only_ship, :class_name => 'Ship' has_one :update_only_ship, :class_name => 'Ship'
has_one :non_validated_ship, :class_name => 'Ship' has_one :non_validated_ship, :class_name => 'Ship'
has_many :birds has_many :birds, :order => 'birds.id ASC'
has_many :birds_with_method_callbacks, :class_name => "Bird", has_many :birds_with_method_callbacks, :class_name => "Bird",
:before_add => :log_before_add, :before_add => :log_before_add,
:after_add => :log_after_add, :after_add => :log_after_add,

View file

@ -0,0 +1,3 @@
class PolymorphicDesign < ActiveRecord::Base
belongs_to :designable, :polymorphic => true
end

View file

@ -0,0 +1,3 @@
class PolymorphicPrice < ActiveRecord::Base
belongs_to :sellable, :polymorphic => true
end

View file

@ -1,4 +1,5 @@
class Post < ActiveRecord::Base class Post < ActiveRecord::Base
named_scope :with_type_self, lambda{{:conditions => ["type=?", self.name]}}
named_scope :containing_the_letter_a, :conditions => "body LIKE '%a%'" named_scope :containing_the_letter_a, :conditions => "body LIKE '%a%'"
named_scope :ranked_by_comments, :order => "comments_count DESC" named_scope :ranked_by_comments, :order => "comments_count DESC"
named_scope :limit, lambda {|limit| {:limit => limit} } named_scope :limit, lambda {|limit| {:limit => limit} }
@ -52,6 +53,7 @@ class Post < ActiveRecord::Base
end end
end end
has_many :misc_tags, :through => :taggings, :source => :tag, :conditions => "tags.name = 'Misc'"
has_many :funky_tags, :through => :taggings, :source => :tag has_many :funky_tags, :through => :taggings, :source => :tag
has_many :super_tags, :through => :taggings has_many :super_tags, :through => :taggings
has_one :tagging, :as => :taggable has_one :tagging, :as => :taggable

View file

@ -0,0 +1,4 @@
class Tee < ActiveRecord::Base
has_one :polymorphic_design, :as => :designable
has_one :polymorphic_price, :as => :sellable
end

View file

@ -0,0 +1,4 @@
class Tie < ActiveRecord::Base
has_one :polymorphic_design, :as => :designable
has_one :polymorphic_price, :as => :sellable
end

View file

@ -19,6 +19,13 @@ CREATE PROCEDURE ten() SQL SECURITY INVOKER
BEGIN BEGIN
select 10; select 10;
END END
SQL
ActiveRecord::Base.connection.execute <<-SQL
CREATE PROCEDURE topics() SQL SECURITY INVOKER
BEGIN
select * from topics limit 1;
END
SQL SQL
end end

View file

@ -367,6 +367,16 @@ ActiveRecord::Schema.define do
t.column :updated_on, :datetime t.column :updated_on, :datetime
end end
create_table :polymorphic_designs, :force => true do |t|
t.integer :designable_id
t.string :designable_type
end
create_table :polymorphic_prices, :force => true do |t|
t.integer :sellable_id
t.string :sellable_type
end
create_table :posts, :force => true do |t| create_table :posts, :force => true do |t|
t.integer :author_id t.integer :author_id
t.string :title, :null => false t.string :title, :null => false
@ -390,6 +400,7 @@ ActiveRecord::Schema.define do
create_table :readers, :force => true do |t| create_table :readers, :force => true do |t|
t.integer :post_id, :null => false t.integer :post_id, :null => false
t.integer :person_id, :null => false t.integer :person_id, :null => false
t.boolean :skimmer, :default => false
end end
create_table :shape_expressions, :force => true do |t| create_table :shape_expressions, :force => true do |t|
@ -435,6 +446,8 @@ ActiveRecord::Schema.define do
t.datetime :ending t.datetime :ending
end end
create_table :ties, :force => true
create_table :topics, :force => true do |t| create_table :topics, :force => true do |t|
t.string :title t.string :title
t.string :author_name t.string :author_name
@ -448,6 +461,7 @@ ActiveRecord::Schema.define do
t.integer :parent_id t.integer :parent_id
t.string :parent_title t.string :parent_title
t.string :type t.string :type
t.string :group
end end
create_table :taggings, :force => true do |t| create_table :taggings, :force => true do |t|
@ -462,6 +476,8 @@ ActiveRecord::Schema.define do
t.column :taggings_count, :integer, :default => 0 t.column :taggings_count, :integer, :default => 0
end end
create_table :tees, :force => true
create_table :toys, :primary_key => :toy_id ,:force => true do |t| create_table :toys, :primary_key => :toy_id ,:force => true do |t|
t.string :name t.string :name
t.integer :pet_id, :integer t.integer :pet_id, :integer

View file

@ -1,8 +1,5 @@
*2.3.9 (September 4, 2010)*
*2.3.8 (May 24, 2010)* *2.3.8 (May 24, 2010)*
* Version bump.
*2.3.7 (May 24, 2010)* *2.3.7 (May 24, 2010)*
* Version bump. * Version bump.

View file

@ -66,7 +66,7 @@ spec = Gem::Specification.new do |s|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) } s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end end
s.add_dependency('activesupport', '= 2.3.8' + PKG_BUILD) s.add_dependency('activesupport', '= 2.3.9' + PKG_BUILD)
s.require_path = 'lib' s.require_path = 'lib'
s.autorequire = 'active_resource' s.autorequire = 'active_resource'

Some files were not shown because too many files have changed in this diff Show more