Compare commits

...

58 Commits

Author SHA1 Message Date
Ryan Tomayko
fe11782158 Merge remote-tracking branch 'github/rack-1.x' into 2-3-github 2011-11-17 12:57:09 -08:00
Ryan Tomayko
899e99a025 pin to rack ~> 1.1 instead of ~> 1.1.0
Some pretty gnarly bugs and security issues are present in the
latest rack 1.1.x release. There are 1.2.x and 1.3.x releases that
correct these.

This changes the gem dependencies to allow for rack versions > 1.1.
At GitHub we're on 1.2.4 (latest 1.2.x release at present) and
should have some results from real world testing soon.
2011-11-17 12:51:32 -08:00
Aaron Patterson
e0774e4730 fixing utf8 escape vulerability 2011-08-16 14:58:39 -07:00
Aaron Patterson
60f783d9ce fixing strip tags vulnerability 2011-08-16 14:58:13 -07:00
Aaron Patterson
6b46d65597 fixing sql injection problem 2011-08-16 14:57:48 -07:00
Aaron Patterson
fb1588c5ff 2.3.14. yay. :'( 2011-08-16 14:57:05 -07:00
Aaron Patterson
dea5a10f71 bumping to 2.3.13 2011-08-16 14:34:14 -07:00
Aaron Patterson
11dafeaa75 fixing response splitting problem 2011-08-16 14:25:45 -07:00
Aaron Patterson
bb99aa1149 adding notification for rdoc 2011-08-16 14:24:44 -07:00
Aaron Patterson
b132992978 we should not ignore all gems in here 2011-08-04 16:34:20 -07:00
Xavier Noria
78a1fda7c8 contrib app minor tweak 2011-07-27 13:23:42 -07:00
José Valim
8d02083f23 Merge pull request #1740 from Antiarchitect/2-3-stable
Fix OrderedHash merging with block given.
2011-06-17 06:25:39 -07:00
Andrey Voronkov
b1c36b7088 Added tests for OrderedHash merging with block. 2011-06-16 23:56:39 -07:00
Andrey Voronkov
b2d4142fb7 Fix OrderedHash merging with block given. 2011-06-16 16:47:29 -07:00
Brian Cardarella
1aae5e70ef Remove deprecation warning for ActiveRecord::Errors#generate_message. This is the same API that ActiveModel ended up using and that won't be changing. 2011-06-09 14:59:33 -07:00
Aaron Patterson
a2a34133d8 find the spec from the source index, then activate it 2011-06-06 20:22:47 -07:00
Ryan Davis
79aa54d0c7 + Switched to newer rdoc and gem package tasks (and their requires).
+ Fixed deprecated usage in gemspecs.

Bumped the version to 2.3.12 so I could test locally with actual
installs. If this is bad form for this project, please beat me up and
I'll split them out.
2011-05-25 01:49:15 -07:00
Ryan Davis
3ad5fd1879 Removed the bulk of the deprecations by simply not calling refresh.
This may cause problems. I dunno.
The real solution is to get rid of all of this mess and use gem paths properly.
2011-05-12 16:02:41 -07:00
Ryan Davis
4c3725723f Fixed buggy gem activation. Don't pass a dependency to gem, pass the
name and requirement. Better, just activate the spec for the
dependency (1.8 only)
2011-05-12 16:01:56 -07:00
Ryan Davis
c20a4d18e3 Removed buggy GemDependency#requirement override. Overrides should NEVER change the semantics of the parent (returning nil if default). 2011-05-12 16:01:10 -07:00
Ryan Davis
01a9fbbcca Fix broken GemDependency#==. You should ALWAYS check the class! 2011-05-12 16:00:28 -07:00
Ryan Davis
8d4ca9edc6 Fix stupid emacsisms. Just makes things more readable. 2011-05-12 16:00:03 -07:00
José Valim
d793a56121 Merged pull request #198 from robdimarco/2-3-stable.
Patch for issue 6440 - Session Reset undefined method `destroy' for {}:Hash
2011-04-28 00:37:53 -07:00
José Valim
f424efe97f Merged pull request #331 from daphonz/2-3-stable.
Dynamic find_or_create_by_x_and_y always creates new records in Rails 2.3.11
2011-04-28 00:20:15 -07:00
Casey Dreier
9f7ff621bd Fixing dynamic finders on associations to properly send arguments to the find_by_* method. Closes issue #330.
Commit fdfc8e3b9 introduced a bugfix to prevent additional values passed
to a dynamic find_or_create_by_x methods from confusing the finder.
This patch also broke the essential behavior of this method on an
association by incorrectly sending arguments to the find_by_x methods.
The finder method would always see its inputs as a single array of
values instead of individual arguments, almost guaranteeing that the
finder call would be incorrect, and that we'd always create a new
record instead.

This patch adds a splat operator to the parameter array we send along to
the dynamic finder so that it receives its inputs correctly, and
includes an additional test to ensure that repeated calls to
find_or_create_by_x only creates one new record.
2011-04-27 21:57:24 -04:00
gmarik
b0be721dd9 respect :expire_after option
- it was broken after
[commit](e0eb8e9c65)
- there's also
[issue](https://rails.lighthouseapp.com/projects/8994/tickets/6634-railsrack-inconsistency-about-expires_afterexpires-cookie-option)

- also: maybe it worth making Rack understand :expire_after as we
duplicate same logic in [cookie_store](https://github.com/gmarik/rails/blob/v2.3.11/actionpack/lib/action_controller/session/cookie_store.rb#L114)

Signed-off-by: José Valim <jose.valim@gmail.com>
2011-04-14 13:48:35 +02:00
Rob Di Marco
8ca8ac379d Fixed bug 6440 by checking that destroy exists on the session 2011-02-28 22:54:03 -05:00
Rob Di Marco
589ce09564 Unit test that shows calling reset session twice results in an exception 2011-02-28 22:53:36 -05:00
Vijay Dev
6c42c142e2 fix incorrect version in deprecation message
Signed-off-by: Santiago Pastorino <santiago@wyeworks.com>
2011-02-20 13:32:18 -02:00
Aaron Patterson
abc06a2f76 rubygems 1.5.0 compatibility. Thanks Tim Serong 2011-02-09 15:24:14 -08:00
Michael Koziarski
b0c3d451a2 Prepare for the 2.3.11 release 2011-02-09 09:30:53 +13:00
Michael Koziarski
7e86f9b4d2 Change the CSRF whitelisting to only apply to get requests
Unfortunately the previous method of browser detection and XHR whitelisting is unable to prevent requests issued from some Flash animations and Java applets.  To ease the work required to include the CSRF token in ajax requests rails now supports providing the token in a custom http header:

 X-CSRF-Token: ...

This fixes CVE-2011-0447
2011-02-09 09:20:17 +13:00
Michael Koziarski
abe97736b8 Be sure to javascript_escape the email address to prevent apostrophes inadvertently causing javascript errors.
This fixes CVE-2011-0446
2011-02-09 09:20:16 +13:00
Aaron Patterson
7e0f60d2ed fixing invalid yaml [#4418 state:resolved]
Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2011-02-01 13:30:58 -08:00
Jamis Buck
3afa5385c9 Revert "make TestCaseTest work for pre-1.9 rubies, too"
This reverts commit 8378a44ff9.
2011-01-19 15:57:16 -07:00
Jamis Buck
c545331f9e Revert "scrub instance variables from test cases on teardown"
This reverts commit b5cf2b4b82.
2011-01-19 15:57:16 -07:00
Jamis Buck
cd0ecff00b Revert "rein in GC during tests by making them run (at most) once per second"
This reverts commit a0c761dc6b.
2011-01-19 15:57:14 -07:00
Jamis Buck
a0c761dc6b rein in GC during tests by making them run (at most) once per second
this can provide a significant performance boost during testing, by
preventing the GC from running too frequently.
2011-01-19 10:27:53 -07:00
Jamis Buck
b5cf2b4b82 scrub instance variables from test cases on teardown
this prevents test state from accumulating, resulting in leaked
objects and slow tests due to overactive GC.
2011-01-19 10:12:18 -07:00
Jamis Buck
8378a44ff9 make TestCaseTest work for pre-1.9 rubies, too 2011-01-19 10:08:02 -07:00
Johnathan Ritzi
4f0c8ef9f1 Fix doc for #check_box [#6311 state:resolved]
Signed-off-by: Xavier Noria <fxn@hashref.com>
2011-01-19 08:47:19 +01:00
Jeremy Kemper
bc302f2aec Revert "use Object#class instead of Object#type"
This reverts commit 08d94d3f7e.
2011-01-10 14:14:25 -08:00
Tomasz Pajor
08d94d3f7e use Object#class instead of Object#type 2011-01-09 15:12:25 -08:00
Mikel Lindsaar
10ec012f58 Updating documentation on ActionMailer base to show a multipart email with attachments 2011-01-02 11:13:44 +11:00
Mikel Lindsaar
92fd824480 Correcting actionmailer guide for Rails 2.3 2011-01-02 11:08:44 +11:00
Michael Koziarski
6d916329b8 Require thread explicitly rather than relying on rubygems to do it. 2010-12-20 11:16:55 +13:00
Michael Koziarski
84465a2cc1 Revert "In nested_attributes when association is not loaded and association record is saved then in memory record attributes should be saved"
This reverts commit 12bbc34aca.

It caused errors when combined with attr_accessible, piggy back attributes fetched by :select, etc.  Leaving it in 3.0, but removing from 2.3
2010-12-08 09:48:54 +13:00
Will Bryant
0fee359278 Don't add non-new records back to the target array after loading targets on associations, as that makes destroy_all destroy any created records that don't match the scope destroy_all is called on
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2010-12-08 09:48:16 +13:00
Pascal Friederich
e0eb8e9c65 Let Rack::Utils.set_cookie_header! create the Set-Cookie header instead of manually fiddling with the response headers [#4941 state:resolved]
Signed-off-by: José Valim <jose.valim@gmail.com>
2010-12-01 11:58:45 +01:00
José Valim
2826324e56 Revert "Fix AbstractStore so that it preserves Set-Cookie header as an array, rather than as newline separated strings"
This reverts commit 36b91e34f4.

Conflicts:

	actionpack/test/activerecord/active_record_store_test.rb
2010-12-01 11:48:31 +01:00
Alexandru Catighera
1681ede605 Fix ActiveRecord calculations when grouped by multiple fields 2010-11-16 11:06:49 -08:00
Tom Stuart
44db47c63e Backport BlankSlate removal from ActiveSupport::BasicObject [#5911 state:resolved]
This is a backport of dd15a3fee0.

Signed-off-by: Andrew White <andyw@pixeltrix.co.uk>
2010-11-03 11:03:38 +00:00
Andrew White
25139ac92c Don't write out secure cookies unless the request is secure 2010-10-27 15:04:29 +01:00
Andrew White
0e52a609fd Don't create a deprecation proxy object if the variable was passed in local_assigns [#1671 state:resolved] 2010-10-26 12:57:21 +01:00
Aaron Patterson
df78de2bc8 removing space errors 2010-10-21 10:30:18 -07:00
Omar Qureshi
36b91e34f4 Fix AbstractStore so that it preserves Set-Cookie header as an array, rather than as newline separated strings 2010-10-21 10:28:54 -07:00
toby cabot
bdfddb09d7 bug 1108: yield to block provided to find_or_create_by_x
Starting in 2.3.8 we stopped yielding to blocks passed in to
find_or_create_by_x methods.  This patch restores that behavior and
adds a case to test it.
2010-10-20 17:23:54 -07:00
toby cabot
fdfc8e3b9c bug 1108: fix a bug with find_or_create_by and additional values
There was a bug with find_or_create_by_x introduced in 2.3.9 - if you
included extra parameters for the create() then those parameters would
confuse the find() so you'd never get to the create().  This patch
filters the parameters so we only pass to find() the subset that it's
interested in.  The code for the filtering was modelled on the code in
base.rb's method_missing().
2010-10-20 17:23:54 -07:00
72 changed files with 675 additions and 374 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,3 @@
*.gem
pkg
.bundle
debug.log

View File

@@ -1,5 +1,5 @@
require 'rake'
require 'rake/rdoctask'
require 'rdoc/task'
env = %(PKG_BUILD="#{ENV['PKG_BUILD']}") if ENV['PKG_BUILD']
@@ -23,7 +23,7 @@ end
desc "Generate documentation for the Rails framework"
Rake::RDocTask.new do |rdoc|
RDoc::Task.new do |rdoc|
rdoc.rdoc_dir = 'doc/rdoc'
rdoc.title = "Ruby on Rails Documentation"
rdoc.main = "railties/README"

View File

@@ -1,3 +1,4 @@
*2.3.11 (February 9, 2011)*
*2.3.10 (October 15, 2010)*
*2.3.9 (September 4, 2010)*
*2.3.8 (May 24, 2010)*

View File

@@ -1,9 +1,9 @@
require 'rubygems'
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rdoc/task'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rubygems/package_task'
require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version')
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
@@ -29,7 +29,7 @@ Rake::TestTask.new { |t|
# Generate the RDoc documentation
Rake::RDocTask.new { |rdoc|
RDoc::Task.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Action Mailer -- Easy email delivery and testing"
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
@@ -54,19 +54,17 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "actionmailer"
s.homepage = "http://www.rubyonrails.org"
s.add_dependency('actionpack', '= 2.3.10' + PKG_BUILD)
s.add_dependency('actionpack', '= 2.3.14' + PKG_BUILD)
s.has_rdoc = true
s.requirements << 'none'
s.require_path = 'lib'
s.autorequire = 'action_mailer'
s.files = [ "Rakefile", "install.rb", "README", "CHANGELOG", "MIT-LICENSE" ]
s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
Rake::GemPackageTask.new(spec) do |p|
Gem::PackageTask.new(spec) do |p|
p.gem_spec = spec
p.need_tar = true
p.need_zip = true

View File

@@ -195,6 +195,39 @@ module ActionMailer #:nodoc:
# end
# end
#
# = Multipart Emails with Attachments
#
# Multipart emails that also have attachments can be created by nesting a "multipart/alternative" part
# within an email that has its content type set to "multipart/mixed". This would also need two templates
# in place within +app/views/mailer+ called "welcome_email.text.html.erb" and "welcome_email.text.plain.erb"
#
# class ApplicationMailer < ActionMailer::Base
# def signup_notification(recipient)
# recipients recipient.email_address_with_name
# subject "New account information"
# from "system@example.com"
# content_type "multipart/mixed"
#
# part "multipart/alternative" do |alternative|
#
# alternative.part "text/html" do |html|
# html.body = render_message("welcome_email.text.html", :message => "<h1>HTML content</h1>")
# end
#
# alternative.part "text/plain" do |plain|
# plain.body = render_message("welcome_email.text.plain", :message => "text content")
# end
#
# end
#
# attachment :content_type => "image/png",
# :body => File.read(File.join(RAILS_ROOT, 'public/images/rails.png'))
#
# attachment "application/pdf" do |a|
# a.body = File.read('/Users/mikel/Code/mail/spec/fixtures/attachments/test.pdf')
# end
# end
# end
#
# = Configuration options
#

View File

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

View File

@@ -1,3 +1,7 @@
*2.3.11 (February 9, 2011)*
* Two security fixes. CVE-2011-0446, CVE-2011-0447
*2.3.10 (October 15, 2010)*
*2.3.9 (September 4, 2010)*
@@ -1931,7 +1935,7 @@ superclass' view_paths. [Rick Olson]
* Update documentation for erb trim syntax. #5651 [matt@mattmargolis.net]
* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com, sebastien@goetzilla.info]
* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com]
* Reset @html_document between requests so assert_tag works. #4810 [Jarkko Laine, easleydp@gmail.com]
@@ -2528,7 +2532,7 @@ superclass' view_paths. [Rick Olson]
* Provide support for decimal columns to form helpers. Closes #5672. [Dave Thomas]
* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com, sebastien@goetzilla.info]
* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com]
* Reset @html_document between requests so assert_tag works. #4810 [Jarkko Laine, easleydp@gmail.com]

View File

@@ -1,9 +1,9 @@
require 'rubygems'
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rdoc/task'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rubygems/package_task'
require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version')
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
@@ -45,7 +45,7 @@ end
# Genereate the RDoc documentation
Rake::RDocTask.new { |rdoc|
RDoc::Task.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Action Pack -- On rails from request to response"
rdoc.options << '--line-numbers' << '--inline-source'
@@ -76,14 +76,12 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "actionpack"
s.homepage = "http://www.rubyonrails.org"
s.has_rdoc = true
s.requirements << 'none'
s.add_dependency('activesupport', '= 2.3.10' + PKG_BUILD)
s.add_dependency('rack', '~> 1.1.0')
s.add_dependency('activesupport', '= 2.3.14' + PKG_BUILD)
s.add_dependency('rack', '~> 1.1')
s.require_path = 'lib'
s.autorequire = 'action_controller'
s.files = [ "Rakefile", "install.rb", "README", "RUNNING_UNIT_TESTS", "CHANGELOG", "MIT-LICENSE" ]
dist_dirs.each do |dir|
@@ -91,7 +89,7 @@ spec = Gem::Specification.new do |s|
end
end
Rake::GemPackageTask.new(spec) do |p|
Gem::PackageTask.new(spec) do |p|
p.gem_spec = spec
p.need_tar = true
p.need_zip = true

View File

@@ -31,7 +31,7 @@ rescue LoadError
end
end
gem 'rack', '~> 1.1.0'
gem 'rack', '~> 1.1'
require 'rack'
require 'action_controller/cgi_ext'

View File

@@ -60,7 +60,7 @@ module ActionController #:nodoc:
attr_reader :controller
def initialize(controller)
@controller, @cookies = controller, controller.request.cookies
@controller, @cookies, @secure = controller, controller.request.cookies, controller.request.ssl?
super()
update(@cookies)
end
@@ -81,7 +81,7 @@ module ActionController #:nodoc:
options[:path] = "/" unless options.has_key?(:path)
super(key.to_s, options[:value])
@controller.response.set_cookie(key, options)
@controller.response.set_cookie(key, options) if write_cookie?(options)
end
# Removes the cookie on the client machine by setting the value to an empty string
@@ -126,6 +126,12 @@ module ActionController #:nodoc:
def signed
@signed ||= SignedCookieJar.new(self)
end
private
def write_cookie?(cookie)
@secure || !cookie[:secure] || defined?(Rails.env) && Rails.env.development?
end
end
class PermanentCookieJar < CookieJar #:nodoc:

View File

@@ -446,7 +446,9 @@ EOM
end
def reset_session
session.destroy if session
# session may be a hash, if so, we do not want to call destroy
# fixes issue 6440
session.destroy if session and session.respond_to?(:destroy)
self.session = {}
end

View File

@@ -76,7 +76,11 @@ module ActionController #:nodoc:
protected
# The actual before_filter that is used. Modify this to change how you handle unverified requests.
def verify_authenticity_token
verified_request? || raise(ActionController::InvalidAuthenticityToken)
verified_request? || handle_unverified_request
end
def handle_unverified_request
reset_session
end
# Returns true or false if a request is verified. Checks:
@@ -85,11 +89,10 @@ module ActionController #:nodoc:
# * is it a GET request? Gets should be safe and idempotent
# * Does the form_authenticity_token match the given token value from the params?
def verified_request?
!protect_against_forgery? ||
request.method == :get ||
request.xhr? ||
!verifiable_request_format? ||
form_authenticity_token == form_authenticity_param
!protect_against_forgery? ||
request.get? ||
form_authenticity_token == form_authenticity_param ||
form_authenticity_token == request.headers['X-CSRF-Token']
end
def form_authenticity_param

View File

@@ -64,12 +64,13 @@ module ActionController # :nodoc:
# the character set information will also be included in the content type
# information.
def content_type=(mime_type)
self.headers["Content-Type"] =
new_content_type =
if mime_type =~ /charset/ || (c = charset).nil?
mime_type.to_s
else
"#{mime_type}; charset=#{c}"
end
self.headers["Content-Type"] = URI.escape(new_content_type, "\r\n")
end
# Returns the response's content MIME type, or nil if content type has been set.

View File

@@ -195,22 +195,9 @@ module ActionController
request_cookies = env["rack.request.cookie_hash"]
if (request_cookies.nil? || request_cookies[@key] != sid) || options[:expire_after]
cookie = Rack::Utils.escape(@key) + '=' + Rack::Utils.escape(sid)
cookie << "; domain=#{options[:domain]}" if options[:domain]
cookie << "; path=#{options[:path]}" if options[:path]
if options[:expire_after]
expiry = Time.now + options[:expire_after]
cookie << "; expires=#{expiry.httpdate}"
end
cookie << "; secure" if options[:secure]
cookie << "; HttpOnly" if options[:httponly]
headers = response[1]
unless headers[SET_COOKIE].blank?
headers[SET_COOKIE] << "\n#{cookie}"
else
headers[SET_COOKIE] = cookie
end
cookie = {:value => sid}
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
Rack::Utils.set_cookie_header!(response[1], @key, cookie.merge(options))
end
end

View File

@@ -52,7 +52,6 @@ module ActionController
ENV_SESSION_KEY = "rack.session".freeze
ENV_SESSION_OPTIONS_KEY = "rack.session.options".freeze
HTTP_SET_COOKIE = "Set-Cookie".freeze
# Raised when storing more than 4K of session data.
class CookieOverflow < StandardError; end
@@ -116,9 +115,7 @@ module ActionController
cookie[:expires] = Time.now + options[:expire_after]
end
cookie = build_cookie(@key, cookie.merge(options))
headers[HTTP_SET_COOKIE] = [] if headers[HTTP_SET_COOKIE].blank?
headers[HTTP_SET_COOKIE] << cookie
Rack::Utils.set_cookie_header!(headers, @key, cookie.merge(options))
end
[status, headers, body]
@@ -130,26 +127,6 @@ module ActionController
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
def build_cookie(key, value)
case value
when Hash
domain = "; domain=" + value[:domain] if value[:domain]
path = "; path=" + value[:path] if value[:path]
# According to RFC 2109, we need dashes here.
# N.B.: cgi.rb uses spaces...
expires = "; expires=" + value[:expires].clone.gmtime.
strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
secure = "; secure" if value[:secure]
httponly = "; HttpOnly" if value[:httponly]
value = value[:value]
end
value = [value] unless Array === value
cookie = Rack::Utils.escape(key) + "=" +
value.map { |v| Rack::Utils.escape(v) }.join("&") +
"#{domain}#{path}#{expires}#{secure}#{httponly}"
end
def load_session(env)
data = unpacked_cookie_data(env)

View File

@@ -1,6 +1,6 @@
begin
require_library_or_gem 'memcache'
require 'thread'
module ActionController
module Session
class MemCacheStore < AbstractStore

View File

@@ -162,7 +162,7 @@ module HTML #:nodoc:
end
closing = ( scanner.scan(/\//) ? :close : nil )
return Text.new(parent, line, pos, content) unless name = scanner.scan(/[-:\w\x00-\x09\x0b-\x0c\x0e-\x1f]+/)
return Text.new(parent, line, pos, content) unless name = scanner.scan(/[^\s!>\/]+/)
name.downcase!
unless closing

View File

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

View File

@@ -6,6 +6,7 @@ module ActionView #:nodoc:
autoload :BenchmarkHelper, 'action_view/helpers/benchmark_helper'
autoload :CacheHelper, 'action_view/helpers/cache_helper'
autoload :CaptureHelper, 'action_view/helpers/capture_helper'
autoload :CsrfHelper, 'action_view/helpers/csrf_helper'
autoload :DateHelper, 'action_view/helpers/date_helper'
autoload :DebugHelper, 'action_view/helpers/debug_helper'
autoload :FormHelper, 'action_view/helpers/form_helper'
@@ -38,6 +39,7 @@ module ActionView #:nodoc:
include BenchmarkHelper
include CacheHelper
include CaptureHelper
include CsrfHelper
include DateHelper
include DebugHelper
include FormHelper

View File

@@ -1,6 +1,7 @@
require 'cgi'
require 'action_view/helpers/url_helper'
require 'action_view/helpers/tag_helper'
require 'thread'
module ActionView
module Helpers #:nodoc:

View File

@@ -0,0 +1,14 @@
module ActionView
# = Action View CSRF Helper
module Helpers
module CsrfHelper
# Returns a meta tag with the cross-site request forgery protection token
# for forms to use. Place this in your head.
def csrf_meta_tag
if protect_against_forgery?
%(<meta name="csrf-param" content="#{h(request_forgery_protection_token)}"/>\n<meta name="csrf-token" content="#{h(form_authenticity_token)}"/>).html_safe
end
end
end
end
end

View File

@@ -665,7 +665,7 @@ module ActionView
#
# The HTML specification says unchecked check boxes are not successful, and
# thus web browsers do not send them. Unfortunately this introduces a gotcha:
# if an Invoice model has a +paid+ flag, and in the form that edits a paid
# if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
# invoice the user unchecks its check box, no +paid+ parameter is sent. So,
# any mass-assignment idiom like
#
@@ -673,12 +673,15 @@ module ActionView
#
# wouldn't update the flag.
#
# To prevent this the helper generates a hidden field with the same name as
# the checkbox after the very check box. So, the client either sends only the
# hidden field (representing the check box is unchecked), or both fields.
# Since the HTML specification says key/value pairs have to be sent in the
# same order they appear in the form and Rails parameters extraction always
# gets the first occurrence of any given key, that works in ordinary forms.
# To prevent this the helper generates an auxiliary hidden field before
# the very check box. The hidden field has the same name and its
# attributes mimick an unchecked check box.
#
# This way, the client either sends only the hidden field (representing
# the check box is unchecked), or both fields. Since the HTML specification
# says key/value pairs have to be sent in the same order they appear in the
# form, and parameters extraction gets the last occurrence of any repeated
# key in the query string, that works for ordinary forms.
#
# Unfortunately that workaround does not work when the check box goes
# within an array-like parameter, as in
@@ -689,22 +692,26 @@ module ActionView
# <% end %>
#
# because parameter name repetition is precisely what Rails seeks to distinguish
# the elements of the array.
# the elements of the array. For each item with a checked check box you
# get an extra ghost item with only that attribute, assigned to "0".
#
# In that case it is preferable to either use +check_box_tag+ or to use
# hashes instead of arrays.
#
# ==== Examples
# # Let's say that @post.validated? is 1:
# check_box("post", "validated")
# # => <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
# # <input name="post[validated]" type="hidden" value="0" />
# # => <input name="post[validated]" type="hidden" value="0" />
# # <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
#
# # Let's say that @puppy.gooddog is "no":
# check_box("puppy", "gooddog", {}, "yes", "no")
# # => <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
# # <input name="puppy[gooddog]" type="hidden" value="no" />
# # => <input name="puppy[gooddog]" type="hidden" value="no" />
# # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
#
# check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
# # => <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
# # <input name="eula[accepted]" type="hidden" value="no" />
# # => <input name="eula[accepted]" type="hidden" value="no" />
# # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
#
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)

View File

@@ -471,7 +471,8 @@ module ActionView
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")
if encode == "javascript"
"document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
html = content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:"+html_escape(email_address)+extras }))
"document.write('#{escape_javascript(html)}');".each_byte do |c|
string << sprintf("%%%x", c)
end
"<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>"

View File

@@ -27,7 +27,7 @@ module ActionView
def render_partial(view, object = nil, local_assigns = {}, as = nil)
object ||= local_assigns[:object] || local_assigns[variable_name]
if object.nil? && view.respond_to?(:controller)
if object.nil? && !local_assigns_key?(local_assigns) && view.respond_to?(:controller)
ivar = :"@#{variable_name}"
object =
if view.controller.instance_variable_defined?(ivar)
@@ -43,5 +43,11 @@ module ActionView
render_template(view, local_assigns)
end
private
def local_assigns_key?(local_assigns)
local_assigns.key?(:object) || local_assigns.key?(variable_name)
end
end
end

View File

@@ -46,6 +46,11 @@ class ContentTypeController < ActionController::Base
format.rss { render :text => "hello world!", :content_type => Mime::XML }
end
end
def render_content_type_from_user_input
response.content_type= params[:hello]
render :text=>"hello"
end
def rescue_action(e) raise end
end
@@ -129,6 +134,11 @@ class ContentTypeTest < ActionController::TestCase
assert_equal Mime::HTML, @response.content_type
assert_equal "utf-8", @response.charset
end
def test_user_supplied_value
get :render_content_type_from_user_input, :hello=>"hello/world\r\nAttack: true"
assert_equal "hello/world%0D%0AAttack: true", @response.content_type
end
end
class AcceptBasedContentTypeTest < ActionController::TestCase

View File

@@ -100,11 +100,26 @@ class CookieTest < ActionController::TestCase
end
def test_setting_cookie_with_secure
@request.env["HTTPS"] = "on"
get :authenticate_with_secure
assert_equal ["user_name=david; path=/; secure"], @response.headers["Set-Cookie"]
assert_equal({"user_name" => "david"}, @response.cookies)
end
def test_setting_cookie_with_secure_in_development
with_environment(:development) do
get :authenticate_with_secure
assert_equal ["user_name=david; path=/; secure"], @response.headers["Set-Cookie"]
assert_equal({"user_name" => "david"}, @response.cookies)
end
end
def test_not_setting_cookie_with_secure
get :authenticate_with_secure
assert_not_equal ["user_name=david; path=/; secure"], @response.headers["Set-Cookie"]
assert_not_equal({"user_name" => "david"}, @response.cookies)
end
def test_multiple_cookies
get :set_multiple_cookies
assert_equal 2, @response.cookies.size
@@ -177,4 +192,17 @@ class CookieTest < ActionController::TestCase
assert_match %r(#{20.years.from_now.year}), @response.headers["Set-Cookie"].first
assert_equal 100, @controller.send(:cookies).signed[:remember_me]
end
private
def with_environment(enviroment)
old_rails = Object.const_get(:Rails) rescue nil
mod = Object.const_set(:Rails, Module.new)
(class << mod; self; end).instance_eval do
define_method(:env) { @_env ||= ActiveSupport::StringInquirer.new(enviroment.to_s) }
end
yield
ensure
Object.module_eval { remove_const(:Rails) } if defined?(Rails)
Object.const_set(:Rails, old_rails) if old_rails
end
end

View File

@@ -5,6 +5,13 @@ class SanitizerTest < ActionController::TestCase
@sanitizer = nil # used by assert_sanitizer
end
def test_strip_tags_with_quote
sanitizer = HTML::FullSanitizer.new
string = '<" <img src="trollface.gif" onload="alert(1)"> hi'
assert_equal ' hi', sanitizer.sanitize(string)
end
def test_strip_tags
sanitizer = HTML::FullSanitizer.new
assert_equal("<<<bad html", sanitizer.sanitize("<<<bad html"))

View File

@@ -1,4 +1,5 @@
require 'abstract_unit'
require 'thread'
class ReloaderTests < ActiveSupport::TestCase
Reloader = ActionController::Reloader

View File

@@ -716,6 +716,11 @@ class TestController < ActionController::Base
render :partial => "customer"
end
def partial_with_implicit_local_assignment_and_nil_local
@customer = Customer.new("Marcel")
render :partial => "customer", :locals => { :customer => nil }
end
def render_call_to_partial_with_layout
render :action => "calling_partial_with_layout"
end
@@ -1543,6 +1548,13 @@ class RenderTest < ActionController::TestCase
end
end
def test_partial_with_implicit_local_assignment_and_nil_local
assert_not_deprecated do
get :partial_with_implicit_local_assignment_and_nil_local
assert_equal "Hello: Anonymous", @response.body
end
end
def test_render_missing_partial_template
assert_raise(ActionView::MissingTemplate) do
get :missing_partial

View File

@@ -23,6 +23,10 @@ module RequestForgeryProtectionActions
render :text => 'pwn'
end
def meta
render :inline => "<%= csrf_meta_tag %>"
end
def rescue_action(e) raise e end
end
@@ -32,6 +36,16 @@ class RequestForgeryProtectionController < ActionController::Base
protect_from_forgery :only => :index
end
class RequestForgeryProtectionControllerUsingOldBehaviour < ActionController::Base
include RequestForgeryProtectionActions
protect_from_forgery :only => %w(index meta)
def handle_unverified_request
raise(ActionController::InvalidAuthenticityToken)
end
end
class FreeCookieController < RequestForgeryProtectionController
self.allow_forgery_protection = false
@@ -54,158 +68,92 @@ end
# common test methods
module RequestForgeryProtectionTests
def teardown
ActionController::Base.request_forgery_protection_token = nil
def setup
@token = "cf50faa3fe97702ca1ae"
ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
ActionController::Base.request_forgery_protection_token = :authenticity_token
end
def test_should_render_form_with_token_tag
get :index
assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
end
def test_should_render_button_to_with_token_tag
get :show_button
assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
end
def test_should_render_remote_form_with_only_one_token_parameter
get :remote_form
assert_equal 1, @response.body.scan(@token).size
end
def test_should_allow_get
get :index
assert_response :success
end
def test_should_allow_post_without_token_on_unsafe_action
post :unsafe
assert_response :success
end
def test_should_not_allow_html_post_without_token
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
assert_raise(ActionController::InvalidAuthenticityToken) { post :index, :format => :html }
end
def test_should_not_allow_html_put_without_token
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
assert_raise(ActionController::InvalidAuthenticityToken) { put :index, :format => :html }
end
def test_should_not_allow_html_delete_without_token
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
assert_raise(ActionController::InvalidAuthenticityToken) { delete :index, :format => :html }
end
def test_should_allow_api_formatted_post_without_token
assert_nothing_raised do
post :index, :format => 'xml'
assert_not_blocked do
get :index
end
assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
end
def test_should_not_allow_api_formatted_put_without_token
assert_nothing_raised do
put :index, :format => 'xml'
def test_should_render_button_to_with_token_tag
assert_not_blocked do
get :show_button
end
assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
end
def test_should_allow_api_formatted_delete_without_token
assert_nothing_raised do
delete :index, :format => 'xml'
end
def test_should_allow_get
assert_not_blocked { get :index }
end
def test_should_not_allow_api_formatted_post_sent_as_url_encoded_form_without_token
assert_raise(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
post :index, :format => 'xml'
end
def test_should_allow_post_without_token_on_unsafe_action
assert_not_blocked { post :unsafe }
end
def test_should_not_allow_api_formatted_put_sent_as_url_encoded_form_without_token
assert_raise(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
put :index, :format => 'xml'
end
def test_should_not_allow_post_without_token
assert_blocked { post :index }
end
def test_should_not_allow_api_formatted_delete_sent_as_url_encoded_form_without_token
assert_raise(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
delete :index, :format => 'xml'
end
def test_should_not_allow_post_without_token_irrespective_of_format
assert_blocked { post :index, :format=>'xml' }
end
def test_should_not_allow_api_formatted_post_sent_as_multipart_form_without_token
assert_raise(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
post :index, :format => 'xml'
end
def test_should_not_allow_put_without_token
assert_blocked { put :index }
end
def test_should_not_allow_api_formatted_put_sent_as_multipart_form_without_token
assert_raise(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
put :index, :format => 'xml'
end
def test_should_not_allow_delete_without_token
assert_blocked { delete :index }
end
def test_should_not_allow_api_formatted_delete_sent_as_multipart_form_without_token
assert_raise(ActionController::InvalidAuthenticityToken) do
@request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
delete :index, :format => 'xml'
end
def test_should_not_allow_xhr_post_without_token
assert_blocked { xhr :post, :index }
end
def test_should_allow_xhr_post_without_token
assert_nothing_raised { xhr :post, :index }
end
def test_should_allow_xhr_put_without_token
assert_nothing_raised { xhr :put, :index }
end
def test_should_allow_xhr_delete_without_token
assert_nothing_raised { xhr :delete, :index }
end
def test_should_allow_xhr_post_with_encoded_form_content_type_without_token
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
assert_nothing_raised { xhr :post, :index }
end
def test_should_allow_post_with_token
post :index, :authenticity_token => @token
assert_response :success
assert_not_blocked { post :index, :authenticity_token => @token }
end
def test_should_allow_put_with_token
put :index, :authenticity_token => @token
assert_response :success
assert_not_blocked { put :index, :authenticity_token => @token }
end
def test_should_allow_delete_with_token
delete :index, :authenticity_token => @token
assert_not_blocked { delete :index, :authenticity_token => @token }
end
def test_should_allow_post_with_token_in_header
@request.env['HTTP_X_CSRF_TOKEN'] = @token
assert_not_blocked { post :index }
end
def test_should_allow_delete_with_token_in_header
@request.env['HTTP_X_CSRF_TOKEN'] = @token
assert_not_blocked { delete :index }
end
def test_should_allow_put_with_token_in_header
@request.env['HTTP_X_CSRF_TOKEN'] = @token
assert_not_blocked { put :index }
end
def assert_blocked
session[:something_like_user_id] = 1
yield
assert_nil session[:something_like_user_id], "session values are still present"
assert_response :success
end
def test_should_allow_post_with_xml
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
post :index, :format => 'xml'
assert_response :success
end
def test_should_allow_put_with_xml
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
put :index, :format => 'xml'
assert_response :success
end
def test_should_allow_delete_with_xml
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
delete :index, :format => 'xml'
def assert_not_blocked
assert_nothing_raised { yield }
assert_response :success
end
end
@@ -214,15 +162,20 @@ end
class RequestForgeryProtectionControllerTest < ActionController::TestCase
include RequestForgeryProtectionTests
def setup
@controller = RequestForgeryProtectionController.new
@request = ActionController::TestRequest.new
@request.format = :html
@response = ActionController::TestResponse.new
@token = "cf50faa3fe97702ca1ae"
ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
ActionController::Base.request_forgery_protection_token = :authenticity_token
test 'should emit a csrf-token meta tag' do
ActiveSupport::SecureRandom.stubs(:base64).returns(@token + '<=?')
get :meta
assert_equal %(<meta name="csrf-param" content="authenticity_token"/>\n<meta name="csrf-token" content="cf50faa3fe97702ca1ae&lt;=?"/>), @response.body
end
end
class RequestForgeryProtectionControllerUsingOldBehaviourTest < ActionController::TestCase
include RequestForgeryProtectionTests
def assert_blocked
assert_raises(ActionController::InvalidAuthenticityToken) do
yield
end
end
end
@@ -251,15 +204,30 @@ class FreeCookieControllerTest < ActionController::TestCase
assert_nothing_raised { send(method, :index)}
end
end
test 'should not emit a csrf-token meta tag' do
get :meta
assert_blank @response.body
end
end
class CustomAuthenticityParamControllerTest < ActionController::TestCase
def setup
ActionController::Base.request_forgery_protection_token = :custom_token_name
super
end
def teardown
ActionController::Base.request_forgery_protection_token = :authenticity_token
super
end
def test_should_allow_custom_token
post :index, :authenticity_token => 'foobar'
post :index, :custom_token_name => 'foobar'
assert_response :ok
end
end

View File

@@ -0,0 +1,64 @@
require 'abstract_unit'
# You need to start a memcached server inorder to run these tests
class AbstractStoreTest < ActionController::IntegrationTest
SessionKey = '_myapp_session'
DispatcherApp = ActionController::Dispatcher.new
class TestController < ActionController::Base
def get_session
session[:test] = 'test'
head :ok
end
end
def test_expiry_after
with_test_route_set(:expire_after => 5 * 60) do
get 'get_session'
assert_response :success
assert_match /expires=\S+/, headers['Set-Cookie']
end
end
protected
def with_test_route_set(options = {})
with_routing do |set|
set.draw do |map|
map.with_options :controller => "abstract_store_test/test" do |c|
c.connect "/:action"
end
end
options = { :key => SessionKey, :secret => 'SessionSecret' }.merge!(options)
@integration_session = open_session(TestStore.new(DispatcherApp, options))
yield
end
end
class TestStore < ActionController::Session::AbstractStore
def initialize(app, options = {})
super
@_store = Hash.new({})
end
private
def get_session(env, sid)
sid ||= generate_sid
session = @_store[sid]
[sid, session]
end
def set_session(env, sid, session_data)
@_store[sid] = session_data
end
def destroy(env)
@_store.delete(sid)
end
end
end

View File

@@ -42,6 +42,12 @@ class CookieStoreTest < ActionController::IntegrationTest
head :ok
end
def call_reset_session_twice
reset_session
reset_session
head :ok
end
def call_reset_session
reset_session
head :ok
@@ -106,7 +112,7 @@ class CookieStoreTest < ActionController::IntegrationTest
with_test_route_set do
get '/set_session_value'
assert_response :success
assert_equal ["_myapp_session=#{response.body}; path=/; HttpOnly"],
assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
headers['Set-Cookie']
end
end
@@ -159,7 +165,7 @@ class CookieStoreTest < ActionController::IntegrationTest
with_test_route_set(:secure => true) do
get '/set_session_value', nil, 'HTTPS' => 'on'
assert_response :success
assert_equal ["_myapp_session=#{response.body}; path=/; secure; HttpOnly"],
assert_equal "_myapp_session=#{response.body}; path=/; secure; HttpOnly",
headers['Set-Cookie']
end
end
@@ -190,17 +196,55 @@ class CookieStoreTest < ActionController::IntegrationTest
end
end
def test_calling_session_reset_twice
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_reset_session_twice'
assert_response :success
assert_not_equal "", headers['Set-Cookie']
assert_not_equal session_payload, cookies[SessionKey]
get '/get_session_value'
assert_response :success
assert_equal 'foo: nil', response.body
end
end
def test_setting_session_value_after_session_reset
with_test_route_set do
get '/set_session_value'
assert_response :success
session_payload = response.body
assert_equal ["_myapp_session=#{response.body}; path=/; HttpOnly"],
assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
headers['Set-Cookie']
get '/call_reset_session'
assert_response :success
assert_not_equal [], headers['Set-Cookie']
assert_not_equal "", headers['Set-Cookie']
assert_not_equal session_payload, cookies[SessionKey]
get '/get_session_value'
assert_response :success
assert_equal 'foo: nil', response.body
end
end
def test_setting_session_value_after_session_reset
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_reset_session'
assert_response :success
assert_not_equal "", headers['Set-Cookie']
assert_not_equal session_payload, cookies[SessionKey]
get '/get_session_value'
@@ -214,7 +258,7 @@ class CookieStoreTest < ActionController::IntegrationTest
get '/set_session_value'
assert_response :success
session_payload = response.body
assert_equal ["_myapp_session=#{response.body}; path=/; HttpOnly"],
assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
headers['Set-Cookie']
get '/call_session_clear'

View File

@@ -333,11 +333,11 @@ class UrlHelperTest < ActionView::TestCase
end
def test_mail_to_with_javascript
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript")
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript")
end
def test_mail_to_with_javascript_unicode
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%22%3e%c3%ba%6e%69%63%6f%64%65%3c%2f%61%3e%27%29%3b'))</script>", mail_to("unicode@example.com", "únicode", :encode => "javascript")
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%5c%22%3e%c3%ba%6e%69%63%6f%64%65%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("unicode@example.com", "únicode", :encode => "javascript")
end
def test_mail_with_options
@@ -361,8 +361,8 @@ class UrlHelperTest < ActionView::TestCase
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#46;&#99;&#111;&#109;</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)")
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex", :replace_at => "(at)")
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#40;&#100;&#111;&#116;&#41;&#99;&#111;&#109;</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)")
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
end
def protect_against_forgery?

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env ruby
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rdoc/task'
task :default => :test
@@ -13,7 +13,7 @@ Rake::TestTask.new do |t|
end
# Generate the RDoc documentation
Rake::RDocTask.new do |rdoc|
RDoc::Task.new do |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Active Model"
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'

View File

@@ -1,3 +1,5 @@
*2.3.11 (February 9, 2011)*
*2.3.10 (October 15, 2010)*
* Security Release to fix CVE-2010-3933

View File

@@ -1,9 +1,9 @@
require 'rubygems'
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rdoc/task'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rubygems/package_task'
require File.join(File.dirname(__FILE__), 'lib', 'active_record', 'version')
require File.expand_path(File.dirname(__FILE__)) + "/test/config"
@@ -157,7 +157,7 @@ task :rebuild_frontbase_databases => 'frontbase:rebuild_databases'
# Generate the RDoc documentation
Rake::RDocTask.new { |rdoc|
RDoc::Task.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Active Record -- Object-relation mapping put on rails"
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
@@ -192,16 +192,14 @@ spec = Gem::Specification.new do |s|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
s.add_dependency('activesupport', '= 2.3.10' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.3.14' + PKG_BUILD)
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite3"
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite3"
s.require_path = 'lib'
s.autorequire = 'active_record'
s.has_rdoc = true
s.extra_rdoc_files = %w( README )
s.rdoc_options.concat ['--main', 'README']
@@ -211,7 +209,7 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "activerecord"
end
Rake::GemPackageTask.new(spec) do |p|
Gem::PackageTask.new(spec) do |p|
p.gem_spec = spec
p.need_tar = true
p.need_zip = true

View File

@@ -353,15 +353,14 @@ module ActiveRecord
if @target.is_a?(Array) && @target.any?
@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
t = @target.delete_at(i) if i
if t && t.changed?
t
else
f.mark_for_destruction if t && t.marked_for_destruction?
f
end
end + @target
end + @target.find_all {|t| t.new_record?}
else
@target = find_target
end
@@ -375,16 +374,17 @@ module ActiveRecord
target
end
def method_missing(method, *args)
def method_missing(method, *args, &block)
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)
find_args = pull_finder_args_from(DynamicFinderMatch.match(method).attribute_names, *args)
return send("find_by_#{rest}", *find_args) ||
method_missing("create_by_#{rest}", *args, &block)
when /^create_by_(.*)$/
return create($1.split('_and_').zip(args).inject({}) { |h,kv| k,v=kv ; h[k] = v ; h })
return create($1.split('_and_').zip(args).inject({}) { |h,kv| k,v=kv ; h[k] = v ; h }, &block)
end
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
@@ -434,20 +434,32 @@ module ActiveRecord
callback(:before_add, record)
yield(record) if block_given?
@target ||= [] unless loaded?
index = @target.index(record)
unless @reflection.options[:uniq] && index
if index
@target[index] = record
else
@target << record
end
end
@target << record unless @reflection.options[:uniq] && @target.include?(record)
callback(:after_add, record)
set_inverse_instance(record, @owner)
record
end
private
# Separate the "finder" args from the "create" args given to a
# find_or_create_by_ call. Returns an array with the
# parameter values in the same order as the keys in the
# "names" array. This code was based on code in base.rb's
# method_missing method.
def pull_finder_args_from(names, *args)
attributes = names.collect { |name| name.intern }
attribute_hash = {}
args.each_with_index do |arg, i|
if arg.is_a?(Hash)
attribute_hash.merge! arg
else
attribute_hash[attributes[i]] = arg
end
end
attribute_hash = attribute_hash.with_indifferent_access
attributes.collect { |attr| attribute_hash[attr] }
end
def create_record(attrs)
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
ensure_owner_is_not_new

View File

@@ -1286,7 +1286,7 @@ module ActiveRecord #:nodoc:
# Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
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)
ActiveSupport::Deprecation.warn("ActiveRecord::Base#class_name is deprecated and will be removed in Rails 3.", caller)
# remove any prefix and/or suffix from the table name
class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize

View File

@@ -187,7 +187,7 @@ module ActiveRecord
# A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround
sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
options[:group_fields].each_index{|i| sql << ", #{options[:group_fields][i]} AS #{options[:group_aliases][i]}" } if options[:group]
if options[:from]
sql << " FROM #{options[:from]} "
elsif scope && scope[:from] && !use_workaround
@@ -211,8 +211,8 @@ module ActiveRecord
add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
if options[:group]
group_key = connection.adapter_name == 'FrontBase' ? :group_alias : :group_field
sql << " GROUP BY #{options[group_key]} "
group_key = connection.adapter_name == 'FrontBase' ? :group_aliases : :group_fields
sql << " GROUP BY #{options[group_key].join(',')} "
end
if options[:group] && options[:having]
@@ -239,24 +239,31 @@ module ActiveRecord
end
def execute_grouped_calculation(operation, column_name, column, options) #:nodoc:
group_attr = options[:group].to_s
association = reflect_on_association(group_attr.to_sym)
associated = association && association.macro == :belongs_to # only count belongs_to associations
group_field = associated ? association.primary_key_name : group_attr
group_alias = column_alias_for(group_field)
group_column = column_for group_field
sql = construct_calculation_sql(operation, column_name, options.merge(:group_field => group_field, :group_alias => group_alias))
group_attr = options[:group]
association = reflect_on_association(group_attr.to_s.to_sym)
associated = association && association.macro == :belongs_to # only count belongs_to associations
group_fields = Array(associated ? association.primary_key_name : group_attr)
group_aliases = []
group_columns = {}
group_fields.each do |field|
group_aliases << column_alias_for(field)
group_columns[column_alias_for(field)] = column_for(field)
end
sql = construct_calculation_sql(operation, column_name, options.merge(:group_fields => group_fields, :group_aliases => group_aliases))
calculated_data = connection.select_all(sql)
aggregate_alias = column_alias_for(operation, column_name)
if association
key_ids = calculated_data.collect { |row| row[group_alias] }
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
key_records = association.klass.base_class.find(key_ids)
key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
end
calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
key = type_cast_calculated_value(row[group_alias], group_column)
key = group_aliases.map{|group_alias| type_cast_calculated_value(row[group_alias], group_columns[group_alias])}
key = key.first if key.size == 1
key = key_records[key] if associated
value = row[aggregate_alias]
all[key] = type_cast_calculated_value(value, column, operation)

View File

@@ -238,7 +238,7 @@ module ActiveRecord
end
def quote_column_name(name) #:nodoc:
@quoted_column_names[name] ||= "`#{name}`"
@quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
end
def quote_table_name(name) #:nodoc:

View File

@@ -162,7 +162,7 @@ module ActiveRecord
end
def quote_column_name(name) #:nodoc:
%Q("#{name}")
%Q("#{name.to_s.gsub('"', '""')}")
end

View File

@@ -333,7 +333,6 @@ module ActiveRecord
end
def generate_message(attribute, message = :invalid, options = {})
ActiveSupport::Deprecation.warn("ActiveRecord::Errors#generate_message has been deprecated. Please use ActiveRecord::Error.new().to_s.")
Error.new(@base, attribute, message, options).to_s
end
end

View File

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

View File

@@ -65,6 +65,38 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal person, person.readers.first.person
end
def test_find_or_create_by_with_additional_parameters
post = Post.create! :title => 'test_find_or_create_by_with_additional_parameters', :body => 'this is the body'
comment = post.comments.create! :body => 'test comment body', :type => 'test'
assert_equal comment, post.comments.find_or_create_by_body('test comment body')
post.comments.find_or_create_by_body(:body => 'other test comment body', :type => 'test')
assert_equal 2, post.comments.count
assert_equal 2, post.comments.length
post.comments.find_or_create_by_body('other other test comment body', :type => 'test')
assert_equal 3, post.comments.count
assert_equal 3, post.comments.length
post.comments.find_or_create_by_body_and_type('3rd test comment body', 'test')
assert_equal 4, post.comments.count
assert_equal 4, post.comments.length
end
def test_find_or_create_by_with_same_parameters_creates_a_single_record
author = Author.first
assert_difference "Post.count", +1 do
2.times do
author.posts.find_or_create_by_body_and_title('one', 'two')
end
end
end
def test_find_or_create_by_with_block
post = Post.create! :title => 'test_find_or_create_by_with_additional_parameters', :body => 'this is the body'
comment = post.comments.find_or_create_by_body('other test comment body') { |comment| comment.type = 'test' }
assert_equal 'test', comment.type
end
def test_find_or_create
person = Person.create! :first_name => 'tenderlove'
post = Post.find :first
@@ -843,6 +875,17 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh"
end
def test_destroy_all_with_creates_and_scope_that_doesnt_match_created_records
company = companies(:first_firm)
unloaded_client_matching_scope = companies(:second_client)
created_client_matching_scope = company.clients_of_firm.create!(:name => "Somesoft")
created_client_not_matching_scope = company.clients_of_firm.create!(:name => "OtherCo")
destroyed = company.clients_of_firm.with_oft_in_name.destroy_all
assert destroyed.include?(unloaded_client_matching_scope), "unloaded clients matching the scope destroy_all on should have been destroyed"
assert destroyed.include?(created_client_matching_scope), "loaded clients matching the scope destroy_all on should have been destroyed"
assert !destroyed.include?(created_client_not_matching_scope), "loaded clients not matching the scope destroy_all on should not have been destroyed"
end
def test_dependence
firm = companies(:first_firm)
assert_equal 2, firm.clients.size

View File

@@ -18,8 +18,6 @@ require 'models/person'
require 'models/reader'
require 'models/parrot'
require 'models/pirate'
require 'models/ship'
require 'models/ship_part'
require 'models/treasure'
require 'models/price_estimate'
require 'models/club'
@@ -31,23 +29,6 @@ class AssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :developers_projects,
: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
assert_nothing_raised {Account.find(:first, :order => 'id', :include => :firm)}
assert_nothing_raised {Account.find(:first, :order => :id, :include => :firm)}

View File

@@ -79,6 +79,23 @@ end
class BasicsTest < ActiveRecord::TestCase
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts
def test_column_names_are_escaped
conn = ActiveRecord::Base.connection
classname = conn.class.name[/[^:]*$/]
badchar = {
'SQLite3Adapter' => '"',
'MysqlAdapter' => '`',
'Mysql2Adapter' => '`',
'PostgreSQLAdapter' => '"',
'OracleAdapter' => '"',
}.fetch(classname) {
raise "need a bad char for #{classname}"
}
quoted = conn.quote_column_name "foo#{badchar}bar"
assert_equal("#{badchar}foo#{badchar * 2}bar#{badchar}", quoted)
end
def test_table_exists
assert !NonExistentTable.table_exists?
assert Topic.table_exists?

View File

@@ -58,6 +58,19 @@ class CalculationsTest < ActiveRecord::TestCase
[1,6,2].each { |firm_id| assert c.keys.include?(firm_id) }
end
def test_should_group_by_multiple_fields
c = Account.count(:all, :group => ['firm_id', :credit_limit])
[ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) }
end
def test_should_group_by_multiple_fields_having_functions
c = Topic.count(:all, :group => [:author_name, 'COALESCE(type, title)'])
assert_equal 1, c[["Nick", "The Third Topic of the day"]]
assert_equal 1, c[["Mary", "Reply"]]
assert_equal 1, c[["David", "The First Topic"]]
assert_equal 1, c[["Carl", "Reply"]]
end
def test_should_group_by_summed_field
c = Account.sum(:credit_limit, :group => :firm_id)
assert_equal 50, c[1]

View File

@@ -808,13 +808,7 @@ class TestHasManyAutosaveAssoictaionWhichItselfHasAutosaveAssociations < ActiveR
@part = @ship.parts.create!(:name => "Mast")
@trinket = @part.trinkets.create!(:name => "Necklace")
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
@trinket.name = "changed"
@ship.save

View File

@@ -12,6 +12,8 @@ class Company < AbstractCompany
has_many :contracts
has_many :developers, :through => :contracts
named_scope :with_oft_in_name, :conditions => "name LIKE '%oft%'"
def arbitrary_method
"I am Jack's profound disappointment"
end

View File

@@ -1,3 +1,4 @@
*2.3.11 (February 9, 2011)*
*2.3.10 (October 15, 2010)*
*2.3.9 (September 4, 2010)*
*2.3.8 (May 24, 2010)*

View File

@@ -1,9 +1,9 @@
require 'rubygems'
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rdoc/task'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rubygems/package_task'
require File.join(File.dirname(__FILE__), 'lib', 'active_resource', 'version')
@@ -38,7 +38,7 @@ Rake::TestTask.new { |t|
# Generate the RDoc documentation
Rake::RDocTask.new { |rdoc|
RDoc::Task.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Active Resource -- Object-oriented REST services"
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
@@ -66,12 +66,10 @@ spec = Gem::Specification.new do |s|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
s.add_dependency('activesupport', '= 2.3.10' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.3.14' + PKG_BUILD)
s.require_path = 'lib'
s.autorequire = 'active_resource'
s.has_rdoc = true
s.extra_rdoc_files = %w( README )
s.rdoc_options.concat ['--main', 'README']
@@ -81,7 +79,7 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "activeresource"
end
Rake::GemPackageTask.new(spec) do |p|
Gem::PackageTask.new(spec) do |p|
p.gem_spec = spec
p.need_tar = true
p.need_zip = true

View File

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

View File

@@ -1,3 +1,5 @@
*2.3.11 (February 9, 2011)*
*2.3.10 (October 15, 2010)*

View File

@@ -1,6 +1,6 @@
require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/gempackagetask'
require 'rdoc/task'
require 'rubygems/package_task'
require File.join(File.dirname(__FILE__), 'lib', 'active_support', 'version')
@@ -27,7 +27,7 @@ dist_dirs = [ "lib", "test"]
# Genereate the RDoc documentation
Rake::RDocTask.new { |rdoc|
RDoc::Task.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Active Support -- Utility classes and standard library extensions from Rails"
rdoc.options << '--line-numbers' << '--inline-source'
@@ -48,7 +48,6 @@ spec = Gem::Specification.new do |s|
s.files = [ "CHANGELOG", "README" ] + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
s.require_path = 'lib'
s.has_rdoc = true
s.author = "David Heinemeier Hansson"
s.email = "david@loudthinking.com"
@@ -56,7 +55,7 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "activesupport"
end
Rake::GemPackageTask.new(spec) do |p|
Gem::PackageTask.new(spec) do |p|
p.gem_spec = spec
p.need_tar = true
p.need_zip = true

View File

@@ -1,13 +1,7 @@
# A base class with no predefined methods that tries to behave like Builder's
# BlankSlate in Ruby 1.9. In Ruby pre-1.9, this is actually the
# Builder::BlankSlate class.
#
# Ruby 1.9 introduces BasicObject which differs slightly from Builder's
# BlankSlate that has been used so far. ActiveSupport::BasicObject provides a
# barebones base class that emulates Builder::BlankSlate while still relying on
# Ruby 1.9's BasicObject in Ruby 1.9.
module ActiveSupport
if defined? ::BasicObject
# A class with no predefined methods that behaves similarly to Builder's
# BlankSlate. Used for proxy classes.
class BasicObject < ::BasicObject
undef_method :==
undef_method :equal?
@@ -18,7 +12,10 @@ module ActiveSupport
end
end
else
require 'blankslate'
BasicObject = BlankSlate
class BasicObject #:nodoc:
instance_methods.each do |m|
undef_method(m) if m.to_s !~ /(?:^__|^nil\?$|^send$|^object_id$)/
end
end
end
end

View File

@@ -1,3 +1,5 @@
require 'thread'
module ActiveSupport
# Inspired by the buffered logger idea by Ezra
class BufferedLogger

View File

@@ -19,7 +19,7 @@ class ERB
if s.html_safe?
s
else
s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }.html_safe
s.to_s.gsub(/&/, "&amp;").gsub(/\"/, "&quot;").gsub(/>/, "&gt;").gsub(/</, "&lt;").html_safe
end
end

View File

@@ -15,7 +15,10 @@ en:
month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
# Used in date_select and datime_select.
order: [ :year, :month, :day ]
order:
- :year
- :month
- :day
time:
formats:

View File

@@ -130,14 +130,18 @@ module ActiveSupport
end
def merge!(other_hash)
other_hash.each {|k,v| self[k] = v }
if block_given?
other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
else
other_hash.each { |k, v| self[k] = v }
end
self
end
alias_method :update, :merge!
def merge(other_hash)
dup.merge!(other_hash)
def merge(other_hash, &block)
dup.merge!(other_hash, &block)
end
# When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.

View File

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

View File

@@ -1,4 +1,5 @@
require 'abstract_unit'
require 'thread'
class SynchronizationTest < Test::Unit::TestCase
def setup

View File

@@ -2,10 +2,17 @@
require 'date'
require 'abstract_unit'
require 'inflector_test_cases'
require 'active_support/core_ext/string/output_safety'
class StringInflectionsTest < Test::Unit::TestCase
include InflectorTestCases
def test_erb_escape
string = [192, 60].pack('CC')
expected = 192.chr + "&lt;"
assert_equal expected, ERB::Util.html_escape(string)
end
def test_pluralize
SingularToPlural.each do |singular, plural|
assert_equal(plural, singular.pluralize)

View File

@@ -147,6 +147,32 @@ class OrderedHashTest < Test::Unit::TestCase
assert_equal @ordered_hash.keys, merged.keys
end
def test_merge_with_block
hash = ActiveSupport::OrderedHash.new
hash[:a] = 0
hash[:b] = 0
merged = hash.merge(:b => 2, :c => 7) do |key, old_value, new_value|
new_value + 1
end
assert_equal 0, merged[:a]
assert_equal 3, merged[:b]
assert_equal 7, merged[:c]
end
def test_merge_bang_with_block
hash = ActiveSupport::OrderedHash.new
hash[:a] = 0
hash[:b] = 0
hash.merge!(:a => 1, :c => 7) do |key, old_value, new_value|
new_value + 3
end
assert_equal 4, hash[:a]
assert_equal 0, hash[:b]
assert_equal 7, hash[:c]
end
def test_shift
pair = @ordered_hash.shift
assert_equal [@keys.first, @values.first], pair

View File

@@ -1,3 +1,5 @@
*2.3.11 (February 9, 2011)*
*2.3.10 (October 15, 2010)*
*2.3.9 (September 4, 2010)*

View File

@@ -1,7 +1,7 @@
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/gempackagetask'
require 'rdoc/task'
require 'rubygems/package_task'
require 'date'
require 'rbconfig'
@@ -267,7 +267,7 @@ task :generate_app_doc do
system %{cd #{PKG_DESTINATION}; rake doc:app}
end
Rake::RDocTask.new { |rdoc|
RDoc::Task.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Railties -- Gluing the Engine to the Rails"
rdoc.options << '--line-numbers' << '--inline-source' << '--accessor' << 'cattr_accessor=object'
@@ -313,20 +313,18 @@ spec = Gem::Specification.new do |s|
EOF
s.add_dependency('rake', '>= 0.8.3')
s.add_dependency('activesupport', '= 2.3.10' + PKG_BUILD)
s.add_dependency('activerecord', '= 2.3.10' + PKG_BUILD)
s.add_dependency('actionpack', '= 2.3.10' + PKG_BUILD)
s.add_dependency('actionmailer', '= 2.3.10' + PKG_BUILD)
s.add_dependency('activeresource', '= 2.3.10' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.3.14' + PKG_BUILD)
s.add_dependency('activerecord', '= 2.3.14' + PKG_BUILD)
s.add_dependency('actionpack', '= 2.3.14' + PKG_BUILD)
s.add_dependency('actionmailer', '= 2.3.14' + PKG_BUILD)
s.add_dependency('activeresource', '= 2.3.14' + PKG_BUILD)
s.rdoc_options << '--exclude' << '.'
s.has_rdoc = false
s.files = PKG_FILES
s.require_path = 'lib'
s.bindir = "bin" # Use these for applications.
s.executables = ["rails"]
s.default_executable = "rails"
s.author = "David Heinemeier Hansson"
s.email = "david@loudthinking.com"
@@ -334,7 +332,7 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "rails"
end
Rake::GemPackageTask.new(spec) do |pkg|
Gem::PackageTask.new(spec) do |pkg|
pkg.gem_spec = spec
end

View File

@@ -218,7 +218,9 @@ If you set a default +:host+ for your mailers you need to pass +:only_path => fa
h4. Sending Multipart Emails
Action Mailer will automatically send multipart emails if you have different templates for the same action. So, for our UserMailer example, if you have +welcome_email.text.plain.erb+ and +welcome_email.text.html.erb+ in +app/views/user_mailer+, Action Mailer will automatically send a multipart email with the HTML and text versions setup as different parts.
Action Mailer will automatically send multipart emails if you have different templates for the same action. So, for our UserMailer example, if you have +welcome_email.text.plain.erb+ and +welcome_email.text.html.erb+ in +app/views/user_mailer+, Action Mailer will automatically send a "multipart/alternative" email with the HTML and text versions setup as different parts.
A "multipart/alternative" content type tells your email client that there are several representations of the same content available and that the email client is free to choose any one to display to the user. In this case we are giving a plain text and HTML version of the same message. But this could also be a text version, and a recording of someone speaking the the same message. It is important to use "multipart/alternative" only when each part has the same content.
To explicitly specify multipart messages, you can do something like:
@@ -242,7 +244,11 @@ end
h4. Sending Emails with Attachments
Attachments can be added by using the +attachment+ method:
Attachments can be added by using the +attachment+ method. The +attachment+ method has two variations, you can either pass the body in as an option, or create it within a block.
Usually you will use the variation shown below for the "image/jpeg" attachment, here you just pass in the content type and body as a options hash to the attachment method. However, if you need to do some processing to create the attachment, such as with the PDF below, then the block variation can be used.
This email uses the "multipart/mixed" content type because each part is a different block of content. This indicates to the email client that it must show all the parts that it can display to the end user.
<ruby>
class UserMailer < ActionMailer::Base
@@ -250,13 +256,14 @@ class UserMailer < ActionMailer::Base
recipients user.email_address
subject "New account information"
from "system@example.com"
content_type "multipart/alternative"
content_type "multipart/mixed"
attachment :content_type => "image/jpeg",
:body => File.read("an-image.jpg")
attachment "application/pdf" do |a|
a.body = generate_your_pdf_here()
pdf = generate_your_pdf_here(:name => user)
a.body = pdf
end
end
end
@@ -266,7 +273,11 @@ h4. Sending Multipart Emails with Attachments
Once you use the +attachment+ method, ActionMailer will no longer automagically use the correct template based on the filename. You must declare which template you are using for each content type via the +part+ method.
In the following example, there would be two template files, +welcome_email_html.erb+ and +welcome_email_plain.erb+ in the +app/views/user_mailer+ folder.
Here we are making the email "multipart/mixed" with three top level parts, a "multipart/alternative", an "image/jpeg" and an "application/pdf". Within the "multipart/alternative" we are nesting a "text/html" and "text/plain" version of the same message.
This tells the email client that each of the top level parts should be shown to the end user, however, the first part has the content type "multipart/alternative" and provides two versions of the same message, a plain text and HTML version.
In the following example, there would be two template files, +welcome_email.text.html.erb+ and +welcome_email.text.plain.erb+ in the +app/views/user_mailer+ folder.
<ruby>
class UserMailer < ActionMailer::Base
@@ -274,22 +285,28 @@ class UserMailer < ActionMailer::Base
recipients user.email_address
subject "New account information"
from "system@example.com"
content_type "multipart/alternative"
content_type "multipart/mixed"
part "text/html" do |p|
p.body = render_message("welcome_email_html", :message => "<h1>HTML content</h1>")
end
part "multipart/alternative" do |alternative|
alternative.part "text/html" do |html|
html.body = render_message("welcome_email.text.html", :message => "<h1>HTML content</h1>")
end
alternative.part "text/plain" do |plain|
plain.body = render_message("welcome_email.text.plain", :message => "text content")
end
part "text/plain" do |p|
p.body = render_message("welcome_email_plain", :message => "text content")
end
attachment :content_type => "image/jpeg",
:body => File.read("an-image.jpg")
attachment "application/pdf" do |a|
a.body = generate_your_pdf_here()
pdf = generate_your_pdf_here(:name => user)
a.body = pdf
end
end
end
</ruby>

View File

@@ -38,7 +38,7 @@ module Rails
result = self.new(name, :version => version)
spec_filename = File.join(directory_name, '.specification')
if load_spec
raise "Missing specification file in #{File.dirname(spec_filename)}. Perhaps you need to do a 'rake gems:refresh_specs'?" unless File.exists?(spec_filename)
raise "Missing specification file in #{File.dirname(spec_filename)}. Perhaps you need to do a 'rake gems:refresh_specs'\?" unless File.exists?(spec_filename)
spec = YAML::load_file(spec_filename)
result.specification = spec
end
@@ -72,7 +72,15 @@ module Rails
@load_paths_added = @loaded = @frozen = true
return
end
gem self
begin
dep = Gem::Dependency.new(name, requirement)
spec = Gem.source_index.find { |_,s| s.satisfies_requirement?(dep) }.last
spec.activate # a way that exists
rescue
gem self.name, self.requirement # < 1.8 unhappy way
end
@spec = Gem.loaded_specs[name]
@frozen = @spec.loaded_from.include?(self.class.unpacked_path) if @spec
@load_paths_added = true
@@ -117,18 +125,6 @@ module Rails
@spec = s
end
if method_defined?(:requirement)
def requirement
req = super
req unless req == Gem::Requirement.default
end
else
def requirement
req = version_requirements
req unless req == Gem::Requirement.default
end
end
def built?
return false unless frozen?
@@ -274,9 +270,10 @@ module Rails
end
def ==(other)
self.name == other.name && self.requirement == other.requirement
Gem::Dependency === other.class &&
self.name == other.name && self.requirement == other.requirement
end
alias_method :"eql?", :"=="
alias_method :eql?, :"=="
private

View File

@@ -31,7 +31,7 @@ module Rails
def refresh!
# reload the installed gems
@installed_source_index.refresh!
# HACK: I don't think this is needed: @installed_source_index.refresh!
vendor_gems = {}
# handle vendor Rails gems - they are identified by having loaded_from set to ""

View File

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

View File

@@ -1,6 +1,6 @@
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rdoc/task'
desc 'Default: run unit tests.'
task :default => :test
@@ -14,7 +14,7 @@ Rake::TestTask.new(:test) do |t|
end
desc 'Generate documentation for the <%= file_name %> plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = '<%= class_name %>'
rdoc.options << '--line-numbers' << '--inline-source'

View File

@@ -1,6 +1,8 @@
begin
require 'rdoc/task'
namespace :doc do
desc "Generate documentation for the application. Set custom template with TEMPLATE=/path/to/rdoc/template.rb or title with TITLE=\"Custom Title\""
Rake::RDocTask.new("app") { |rdoc|
RDoc::Task.new("app") { |rdoc|
rdoc.rdoc_dir = 'doc/app'
rdoc.template = ENV['template'] if ENV['template']
rdoc.title = ENV['title'] || "Rails Application Documentation"
@@ -12,7 +14,7 @@ namespace :doc do
}
desc "Generate documentation for the Rails framework"
Rake::RDocTask.new("rails") { |rdoc|
RDoc::Task.new("rails") { |rdoc|
rdoc.rdoc_dir = 'doc/api'
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
rdoc.title = "Rails Framework Documentation"
@@ -86,3 +88,6 @@ namespace :doc do
end
end
end
rescue LoadError
$stderr.puts 'Please install RDoc 2.4.2+ to generate documentation.'
end

View File

@@ -24,7 +24,7 @@ namespace :rails do
begin
chdir("vendor/rails") do
rails.dependencies.select { |g| deps.include? g.name }.each do |g|
Gem::GemRunner.new.run(["unpack", g.name, "--version", g.version_requirements.to_s])
Gem::GemRunner.new.run(["unpack", g.name, "--version", g.respond_to?(:requirement) ? g.requirement.to_s : g.version_requirements.to_s])
mv(Dir.glob("#{g.name}*").first, g.name)
end