Compare commits

...

120 Commits

Author SHA1 Message Date
Jeremy Kemper
a60779f7e6 Ruby 1.9: fix Time#beginning_of_day inaccuracy due to subtracting a Float 2009-09-13 05:07:42 -07:00
Jeremy Kemper
63f9426d45 Ruby 1.9 compat: no . in load path 2009-09-13 04:53:29 -07:00
Jeremy Kemper
e9a5ef4755 Silence warning for Encoding.default_external= 2009-09-13 04:51:51 -07:00
Jeremy Kemper
8d354bc792 Use Encoding.default_external, not _internal 2009-09-13 04:51:43 -07:00
Michael Koziarski
76e971ecfb Dup the arguments to string compare so we can use force_encoding. 2009-09-13 10:38:57 +12:00
Beau Harrington
095cf9135b Remove redundant checks for valid character regexp in ActiveSupport::Multibyte#clean and #verify.
[#3181 state:committed]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2009-09-10 17:37:27 -07:00
Michael Koziarski
26306f9292 1.9 compatible secure_compare 2009-09-09 10:47:01 +12:00
Michael Koziarski
896475c192 Revert "Ruby 1.9: fix MessageVerifier#secure_compare"
This reverts commit 91f65b714b.

MessageVerifier was never in 2.2
2009-09-09 10:27:19 +12:00
Jeremy Kemper
91f65b714b Ruby 1.9: fix MessageVerifier#secure_compare 2009-09-08 14:22:38 +09:00
Jeremy Kemper
7a48cd6462 Fix AS test breakage 2009-09-08 14:20:06 +09:00
rick
6363822f18 Prepare for Rails 2.2.3 release. 2009-09-03 23:11:12 -07:00
Michael Koziarski
31678df212 Clean tag attributes before passing through the escape_once logic.
Addresses CVE-2009-3009
2009-08-31 12:20:55 -07:00
Michael Koziarski
9c61eb32c5 Add verify and clean methods to ActiveSupport::Multibyte.
When accepting character input from outside of your application you can't
blindly trust that all strings are properly encoded. With these methods
you can check incoming strings and clean them up if necessary.

Signed-off-by: Michael Koziarski <michael@koziarski.com>

Conflicts:

	activesupport/lib/active_support/multibyte/chars.rb
2009-08-31 12:20:46 -07:00
Michael Koziarski
674f780d59 Fix timing attack vulnerability in the Cookie Store
Use a constant-time comparison algorithm to compare the candidate HMAC with the calculated HMAC to prevent leaking information about the calculated HMAC
2009-08-23 17:26:46 +12:00
Pratik Naik
3cb89257b4 Ensure JoinAssociation uses aliased table name when multiple associations have hash conditions on the same table 2009-04-20 13:56:28 +01:00
Frederick Cheung
2dab082d0e Don't use the transaction instance method so that people with has_one/belongs_to :transaction aren't fubared
[#1551 state:committed]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2009-04-01 15:51:10 +01:00
Jeremy Kemper
5f95347af5 Don't duplicate :order from scope and options, it makes mysql do extra work 2009-03-10 23:11:51 -07:00
Sam Granieri
b6ad4bd0a2 Ruby 1.9 compat: silence a warning about regexp languages
[#2050 state:committed]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2009-02-25 09:39:10 -08:00
rick
763effcb6d Merge branch '2-2-stable' of git@github.com:rails/rails into 2-2-stable 2009-02-25 09:11:58 -08:00
Diego Algorta
a9aa18fdcd Fixed bug that makes named_scopes _forgot_ current scope
Signed-off-by: rick <technoweenie@gmail.com>
[#1960 #1677 state:resolved]
2009-02-25 09:10:40 -08:00
Andrew White
51eb04c84c Remove hardcoded number_of_capturesin ControllerSegment to allow regexp requirements with capturing parentheses 2009-02-22 15:41:16 +13:00
Andrew White
ce41582d27 Fix requirements regexp for path segments
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2009-02-22 15:38:05 +13:00
Jeremy Kemper
0f93f8c4c8 Update changelog for URI.unescape fix
[#2033 state:committed]
2009-02-20 18:42:48 -08:00
Jeremy Kemper
518389d1ef Broaden URI.unescape fix to all affected 1.9.x by checking for broken behavior instead of specific patchlevel 2009-02-20 18:38:32 -08:00
moro
faf7986bae fix test data, should specify encoding to use multibyte chars on Ruby 1.9
Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2009-02-20 18:31:48 -08:00
Jeremy Kemper
99341a2d80 URI.unescape fix removes the old unescape method 2009-02-20 18:26:32 -08:00
moro
a8f32e1c86 Ruby 1.9.1p0's URI.decode() bug fix
backport to fix Ruby 1.9.1p0 bug on [ruby-dev:38005].

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2009-02-20 18:26:22 -08:00
Bruno Duyé
3d15e1a7b5 Make atomic_write() puts the check_file in the cache dir, not in application
root [#1962 state:resolved]
Signed-off-by: Joshua Peek <josh@joshpeek.com>
2009-02-19 21:01:55 -06:00
Akira Matsuda
13bf5c5a6a Ruby 1.9 compat: fix JSON decoding to work properly with multibyte values
[#1969 state:committed]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2009-02-17 11:59:49 -08:00
Joshua Sierles
b0792a3e7b Allow memcache-client versions > 1.5.x to override bundled version
Signed-off-by: Joshua Peek <josh@joshpeek.com>
2009-02-12 13:24:36 -06:00
Jeremy Kemper
96687ad8a9 Add missing test for parsing a multivalued query string 2009-02-10 11:10:12 -08:00
David Heinemeier Hansson
30560593e7 Fixed syntax error [#1894 state:committed] 2009-02-06 14:38:41 +01:00
Michael Koziarski
3c006d428b Handle every error that can come out of the Iconv branch by rescuing and returning nil
[#1195 state:committed]

Conflicts:

	activesupport/lib/active_support/inflector.rb
2009-02-06 13:34:16 +13:00
Daniel Guettler
be4ecc2e64 check for template with specified extension but without template handler extension [#1798 state:resolved]
Signed-off-by: Joshua Peek <josh@joshpeek.com>
2009-02-05 16:10:10 -06:00
Michael Koziarski
0c4a126b16 Move to use pg instead of postgres on the CI server 2009-01-29 17:08:08 +13:00
Dov Murik
e5c211c5eb Mysql#reconnect is set according to the 'reconnect' key in the connection spec.
The 'reconenct' boolean option is read from the connection specification
and is used to set the reconnect attribute of Mysql.  The default is
false in order not to change existing application behaviour.

Also, reconnect is set AFTER real_connect is called, so its value sticks
(the mysql gem sets reconnect to false inside real_connect).

Signed-off-by: Michael Koziarski <michael@koziarski.com>
[#1797 state:committed]
2009-01-27 09:56:31 +13:00
Stephen Bannasch
b081059913 Adding AR tests for JDBC connections
New connections:

  jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb jdbcpostgresql

To test you will need the native database installed (if one is required),
activerecord-jdbc-adapter and the specific activerecord-jdbc<database>-adapter
for the database you are testing.

Run the tests like this:

  jruby -S rake test_jdbcmysql

Signed-off-by: Michael Koziarski <michael@koziarski.com>
[#1685 state:committed]
2009-01-26 16:09:38 +13:00
Michael Koziarski
827efe1a08 Bring back relative_url_root but deprecate it 2009-01-22 13:44:33 +13:00
Michael Koziarski
b75e2f3321 Rationalise the session options to one hash, prevents rack or integration tests from seeing incorrect defaults 2009-01-21 14:59:17 +13:00
Michael Koziarski
97bc170a79 Fix has_and_belongs_to_many_associations tests. #1738
Conflicts:

	activerecord/test/cases/helper.rb
2009-01-18 14:30:51 +13:00
lukeludwig
7c147e94e6 Cache columns for has_and_belongs_to_many associations
This avoids repeatedly calling SHOW COLUMNS when the association is queried
[#1738 state:committed]
2009-01-17 18:10:32 +13:00
Carlos Kozuszko
9606bc8832 Fixing bug on ActiveRecord::Dirty#field_changed? for nullable numeric columns, NULL gets stored in database for blank (i.e. '') values. Only integer columns were considered.
Signed-off-by: Michael Koziarski <michael@koziarski.com>
[#1692 state:committed]
2009-01-16 10:01:18 +13:00
Nicholas Dainty
5664f24973 TimeWithZone#xmlschema accepts optional fraction_digits argument [#1725 state:resolved] 2009-01-11 13:57:57 -06:00
Michael Koziarski
b0091097bc Spam people with commit rights on test failures. 2009-01-07 18:17:49 +13:00
Michael Koziarski
e7e34a47ac Don't expand_path the tests in 2-2-stable as the target code doesn't. 2009-01-07 17:29:16 +13:00
Michael Koziarski
b3ece62a0d Explicitly require action_view to bring in its i18n load path 2009-01-07 17:11:44 +13:00
Michael Koziarski
4b858a55f5 Remove errant debugger statement 2009-01-07 17:09:19 +13:00
Mike Gunderloy
8101675cf2 Update CI config to reflect current gem requirements. 2009-01-07 16:43:29 +13:00
Joshua Peek
255c6564ef Cache AssetTag timestamps 2009-01-05 12:37:48 +01:00
Joshua Peek
e14909d8ca Revert to the good old days when AssetTag didn't cause anyone problems 2009-01-05 12:37:30 +01:00
gbuesing
3d87f0fb4e TimeWithZone#- gives correct result with wrapped DateTime, and with DateTime argument 2009-01-04 14:02:41 -06:00
David Heinemeier Hansson
bc36b07d14 Make sure #compute_public_path caching allows to return different results
for different given sources [#1471 state:resolved]
2009-01-02 12:14:54 +01:00
David Heinemeier Hansson
ee701e0672 Fixed the AssetTagHelper cache to use the computed asset host as part of the cache key instead of just assuming the its a string [#1299 state:fixed] 2009-01-01 19:20:13 +01:00
Jeremy Kemper
d7b7ff0556 Revert "Make constantize look into ancestors"
[#410 state:open]

This reverts commit 87790e00ec.
2008-12-15 18:19:56 -08:00
Jeremy Kemper
75f55960eb Fix Array#forty_two test case spelling 2008-12-15 11:03:44 -08:00
Frederick Cheung
87790e00ec Make constantize look into ancestors
[#410 state:resolved]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-12-15 11:02:54 -08:00
Frederick Cheung
bf0a8eb777 Fixed session related memory leak [#1558 state:resolved]
Signed-off-by: Joshua Peek <josh@joshpeek.com>
2008-12-15 11:51:58 -06:00
Frederick Cheung
8da5c5597a Squash memory leak when calling flush with an empty buffer
[#1552 state:committed]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-12-10 15:07:46 -08:00
Jeremy Kemper
8fcd9cfb9f Revert "Fix: counter_cache should decrement on deleting associated records."
[#1196 state:open]

This reverts commit c9e176de78.
2008-12-10 14:48:46 -08:00
Emilio Tagua
08394014f3 Fix test names collision.
[#1549 state:committed]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-12-10 11:10:55 -08:00
Emilio Tagua
c9e176de78 Fix: counter_cache should decrement on deleting associated records.
[#1195 state:committed]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-12-10 11:01:16 -08:00
Jason Cheow
032902146a Add ActiveSupport::Multibyte::Chars#ord method so that it returns correct Unicode value instead of falling back on String#ord in CoreExtensions, which is not multibyte compatible
[#1483 state:committed]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-12-08 16:10:10 -08:00
Ben Symonds
fc037beedf Change field_changed? method to handle the case where a nullable integer column is changed from 0 to '0'
[#1530 state:committed]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-12-08 15:42:43 -08:00
Joshua Peek
5919b626bb ActionView::Base.register_template_extension doesn't exist either 2008-12-02 20:25:38 -06:00
miloops
701abb927c Make new_record? an alias of new? in ActiveResource to fix problem with route generation in forms.
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-12-02 15:27:45 +01:00
Matt Jones
d6f4072990 handle missing dependecies in gem loading
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-12-01 20:43:19 +01:00
Jeremy Kemper
8b10c3ecb0 Extract named_helper module_eval so it's easier to override 2008-11-29 19:33:58 -08:00
Geoff Garside
b546886138 Test default singleton resource route to ensure it uses GET. This is important if using map.root :resource instead of map.root :resources for some reason.
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-11-24 19:08:13 +01:00
Geoff Garside
43a06d07a8 Reorder the way in which map.resource routes are added to the set. This prevents the singular named route from hitting :create instead of :show.
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-11-24 19:08:13 +01:00
Jeremy Kemper
41abbe4593 Changelog for #1448. Mention updating old translations with storage_units key. 2008-11-23 13:23:43 -08:00
Yaroslav Markin
68d2130a6b Add i18n for number_to_human_size() helper storage units. Translation key is number.human.storage_units.
[#1448 state:committed]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-11-23 13:12:26 -08:00
Joshua Peek
24fea8049d A back support for legacy TemplateHandler#render API 2008-11-23 14:00:17 -06:00
Tom Lea
b696f047b5 Changed the fallback String#each_char to use valid 1.9 syntax.
Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-11-22 22:48:03 -08:00
David Heinemeier Hansson
ff56137073 Doc updates 2008-11-21 17:25:26 +01:00
David Heinemeier Hansson
4adf56b153 Prepped for release 2008-11-21 16:16:14 +01:00
David Heinemeier Hansson
ca29de5d1f Its forty, not fourty, dummy 2008-11-21 09:58:50 +01:00
David Heinemeier Hansson
9d8cc60ec3 Reduced the number of literal aliases to the range that has actually seen personal use. With the massive savings in overhead, I was able to fit Array#fourty_two 2008-11-21 09:06:46 +01:00
Colin Curtin
5b72c9b697 ActionMailer should respect content type when choosing layouts
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-11-21 04:08:43 +05:30
David Heinemeier Hansson
3639c0bfa7 Merge branch '2-2-stable' of git@github.com:rails/rails into 2-2-stable 2008-11-20 21:15:46 +01:00
David Heinemeier Hansson
5e55ed1951 Added back special case for ApplicationController 2008-11-20 21:13:31 +01:00
Daniel Schierbeck
2f7c073f19 Ensure only delegations to methods can have an automatic prefix. [#1235 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-11-21 01:19:17 +05:30
David Heinemeier Hansson
8be7d960f2 Cleaned up deprecation notices 2008-11-20 20:19:49 +01:00
David Heinemeier Hansson
56220ab607 Next release will be 2.2.2, might as well prepare for that 2008-11-20 10:40:04 +01:00
David Heinemeier Hansson
24ead54f2b alternative resolution to vendor load problem 2008-11-20 08:55:24 +01:00
Aaron Batalion
085ebf0368 need to make sure the asset type is cached with it in Cache.. name is sufficient, not self
Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-11-19 15:57:29 -08:00
Aaron Batalion
24dbd4b958 Fixed asset host to not cache objects [#1419 state:resolved]
Signed-off-by: Joshua Peek <josh@joshpeek.com>
2008-11-19 17:28:51 -06:00
Jeremy Kemper
dff4ab9ca5 Reflect default locale change from en-US to en 2008-11-19 12:22:45 -08:00
Akira Matsuda
011a525de0 Require active_support/secure_random for Ruby 1.9.
[#1326 state:committed]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-11-19 10:17:42 -08:00
Ken Collins
7a9c48c1cb Remove SQL Server cases from tests for latest adapter work to pass rails expected behavior.
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-11-19 18:01:01 +01:00
Pratik Naik
4f20a15e9c Remove reset! as a connection#checkout callback 2008-11-19 21:27:31 +05:30
David Heinemeier Hansson
9c42d1945c Deprecated the :file default for ActionView#render to prepare for 2.3's new :partial default [DHH] 2008-11-19 14:04:46 +01:00
David Heinemeier Hansson
49797f77f6 Another piece of markup removed from environment.rb 2008-11-19 12:36:53 +01:00
David Heinemeier Hansson
12c7fef5f1 The docs in environment.rb are not going to be rendered so it doesnt make sense to mark them up 2008-11-19 12:31:50 +01:00
Hiroshi Saito
591560c641 Let polymorphic_path treat an array contains single name as without array [#1386 state:committed]
Signed-off-by: David Heinemeier Hansson <david@loudthinking.com>
2008-11-19 12:07:31 +01:00
Gabe da Silveira
af57ccb468 Make optimized named routes respect all reserved options and tie it into UrlRewriter::RESERVED_OPTIONS so it's DRY
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-11-18 22:45:11 +01:00
Luke Melia
afc7fceefc Fix rendering html partial via inline render when with :js format [#1399 state:resolved]
Signed-off-by: Joshua Peek <josh@joshpeek.com>
2008-11-18 15:18:34 -06:00
Hongli Lai (Phusion)
0f89ed5636 Register 'checked' as an HTML boolean attribute.
This way, 'tag :foo, :type => "checkbox", :checked => false' would output
the expected

  <input type="checkbox" />

instead of the old

  <input type="checkbox" checked="false" />

The latter would result in a checkbox that's initially checked.

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-11-18 11:50:54 -08:00
Michael Koziarski
7d3efe72b4 Remove duplicate distribution of prototype and scriptaculous.
This was previously needed by define_javascript_functions which has been removed for a while.
2008-11-18 20:09:59 +01:00
Michael Koziarski
80e6aaed83 Remove mention of long-dead define_javascript_functions 2008-11-18 20:08:20 +01:00
Thomas Fuchs
9fb3c8412e Update Prototype to 1.6.0.3 and update script.aculo.us to 1.8.2 2008-11-18 19:53:37 +01:00
gbuesing
b88d394ce7 TimeZone offset tests: use current_period, to ensure TimeZone#utc_offset is up-to-date 2008-11-18 09:27:52 -06:00
gbuesing
98199eb3bc Update bundled TZInfo to 0.3.12 2008-11-18 09:09:08 -06:00
Matt Jones
453931cac5 add vendor/ back to load paths; catch errors in constant loading
Signed-off-by: David Heinemeier Hansson <david@loudthinking.com>
2008-11-18 14:31:32 +01:00
Sven Fuchs
5d3a14a936 use :en as a default locale (in favor of :en-US)
Signed-off-by: David Heinemeier Hansson <david@loudthinking.com>
2008-11-18 14:28:43 +01:00
David Heinemeier Hansson
42c9d37273 Added config.i18n settings gatherer to config/environment, auto-loading of all locales in config/locales/*.rb,yml, and config/locales/en.yml as a sample locale [DHH] 2008-11-18 14:28:31 +01:00
Luke Melia
f7a8e39400 Prevent assert_template failures when a render :inline is called before rendering a file-based template [#1383 state:resolved]
Signed-off-by: Joshua Peek <josh@joshpeek.com>
2008-11-17 22:10:47 -06:00
Jeremy Kemper
76b54c5eae Wrap straggling mocha user with uses_mocha block 2008-11-17 11:49:13 -08:00
Jeremy Kemper
fa7151cc0d Ruby 1.9 compat: rescue Exception since minitest's assertion doesn't subclass StandardError 2008-11-17 11:48:33 -08:00
Jeremy Kemper
b55fc0fc08 Ruby 1.9 compat: CGI switched back to Tempfile 2008-11-17 11:48:25 -08:00
Jeremy Kemper
8789394f0e Workaround lack of Mocha on 1.9 (hasn't been updated for minitest yet) 2008-11-17 11:44:33 -08:00
Jeremy Kemper
3eb0d2973a Explicitly require AS::Duration 2008-11-17 11:38:44 -08:00
Jeremy Kemper
6bfd0ac48b Prefer a feature check to a version check 2008-11-17 11:22:15 -08:00
Jeremy Kemper
78e374d4ec Remove deprecated Gem.manage_gems 2008-11-17 11:22:07 -08:00
Carlos Paramio
9fab882b91 Change usage of defined? to check the rubygems constant existance by a rescue block on boot.rb for Ruby 1.9 compatibility
Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-11-17 10:53:40 -08:00
David Heinemeier Hansson
3e79dbd3f9 The inflector is meant to work on words not phrases -- dont confuse people with a phrase example 2008-11-16 21:30:33 +01:00
Michael Koziarski
b401c281ac Add text/plain to the browser_generated_types array as webkit and gecko can submit them.
For more information see:

http://pseudo-flaw.net/content/web-browsers/form-data-encoding-roundup/
2008-11-16 20:25:09 +01:00
Will Bryant
5140bbd0d3 Moved the * strings out of construct_finder_sql to a new default_select method so it can be overridden by plugins cleanly
Signed-off-by: Michael Koziarski <michael@koziarski.com>
[#1371 state:resolved]
2008-11-15 18:27:18 +01:00
Matt Jones
a5609cd3cf fix assignment to has_one :through associations.
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-11-15 18:23:58 +01:00
Michael Koziarski
0b424da08e Merge branch 'master' into 2-2-stable 2008-11-14 16:12:19 +01:00
Pratik Naik
f58b0b2bd7 Rails now requires rubygems 1.3.1 of higher. 2008-11-14 17:10:52 +05:30
306 changed files with 2519 additions and 9011 deletions

View File

@@ -1,10 +1,11 @@
*2.2.1 [RC2] (November 14th, 2008)*
*2.2.3 (September 4th, 2009)*
Version bump.
*2.2 (November 21st, 2008)*
* Turn on STARTTLS if it is available in Net::SMTP (added in Ruby 1.8.7) and the SMTP server supports it (This is required for Gmail's SMTP server) #1336 [Grant Hollingworth]
*2.2.0 [RC1] (October 24th, 2008)*
* Add layout functionality to mailers [Pratik]
Mailer layouts behaves just like controller layouts, except layout names need to

View File

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

View File

@@ -429,7 +429,7 @@ module ActionMailer #:nodoc:
def register_template_extension(extension)
ActiveSupport::Deprecation.warn(
"ActionMailer::Base.register_template_extension has been deprecated." +
"Use ActionView::Base.register_template_extension instead", caller)
"Use ActionView::Template.register_template_handler instead", caller)
end
def template_root
@@ -549,7 +549,12 @@ module ActionMailer #:nodoc:
end
def render_message(method_name, body)
if method_name.respond_to?(:content_type)
@current_template_content_type = method_name.content_type
end
render :file => method_name, :body => body
ensure
@current_template_content_type = nil
end
def render(opts)
@@ -568,7 +573,11 @@ module ActionMailer #:nodoc:
end
def default_template_format
:html
if @current_template_content_type
Mime::Type.lookup(@current_template_content_type).to_sym
else
:html
end
end
def candidate_for_layout?(options)
@@ -588,7 +597,9 @@ module ActionMailer #:nodoc:
end
def initialize_template_class(assigns)
ActionView::Base.new(view_paths, assigns, self)
template = ActionView::Base.new(view_paths, assigns, self)
template.template_format = default_template_format
template
end
def sort_parts(parts, order = [])

View File

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

View File

@@ -0,0 +1 @@
text/html multipart

View File

@@ -0,0 +1 @@
text/plain multipart

View File

@@ -0,0 +1 @@
text/plain layout - <%= yield %>

View File

@@ -20,6 +20,12 @@ class AutoLayoutMailer < ActionMailer::Base
from "tester@example.com"
body render(:inline => "Hello, <%= @world %>", :layout => false, :body => { :world => "Earth" })
end
def multipart(recipient)
recipients recipient
subject "You have a mail"
from "tester@example.com"
end
end
class ExplicitLayoutMailer < ActionMailer::Base
@@ -56,6 +62,17 @@ class LayoutMailerTest < Test::Unit::TestCase
assert_equal "Hello from layout Inside", mail.body.strip
end
def test_should_pickup_multipart_layout
mail = AutoLayoutMailer.create_multipart(@recipient)
assert_equal 2, mail.parts.size
assert_equal 'text/plain', mail.parts.first.content_type
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
assert_equal 'text/html', mail.parts.last.content_type
assert_equal "Hello from layout text/html multipart", mail.parts.last.body
end
def test_should_pickup_layout_given_to_render
mail = AutoLayoutMailer.create_spam(@recipient)
assert_equal "Spammer layout Hello, Earth", mail.body.strip

View File

@@ -1,4 +1,72 @@
*2.2.1 [RC2] (November 14th, 2008)*
*2.2.3 (September 4th, 2009)*
* Sanitize multibyte strings before escaping them with escape_once. CVE-2009-3009
* Backwards Compatibility: bring back Request#relative_url_root but deprecate it.
* Rationalise the session options to one hash, prevents rack or integration tests from seeing in correct defaults. [Koz]
* I18n: translate number_to_human_size. Add storage_units: [Bytes, KB, MB, GB, TB] to your translations. #1448 [Yaroslav Markin]
* Fixed the AssetTagHelper cache to use the computed asset host as part of the cache key instead of just assuming the its a string #1299 [DHH]
* Make ActionController#render(string) work as a shortcut for render :file/:template/:action => string. [#1435] [Pratik Naik] Examples:
# Instead of render(:action => 'other_action')
render('other_action') # argument has no '/'
render(:other_action)
# Instead of render(:template => 'controller/action')
render('controller/action') # argument must not begin with a '/', but contain a '/'
# Instead of render(:file => '/Users/lifo/home.html.erb')
render('/Users/lifo/home.html.erb') # argument must begin with a '/'
* Add :prompt option to date/time select helpers. #561 [Sam Oliver]
* Fixed that send_file shouldn't set an etag #1578 [Hongli Lai]
* Allow users to opt out of the spoofing checks in Request#remote_ip. Useful for sites whose traffic regularly triggers false positives. [Darren Boyd]
* Deprecated formatted_polymorphic_url. [Jeremy Kemper]
* Added the option to declare an asset_host as an object that responds to call (see http://github.com/dhh/asset-hosting-with-minimum-ssl for an example) [David Heinemeier Hansson]
* Added support for multiple routes.rb files (useful for plugin engines). This also means that draw will no longer clear the route set, you have to do that by hand (shouldn't make a difference to you unless you're doing some funky stuff) [David Heinemeier Hansson]
* Dropped formatted_* routes in favor of just passing in :format as an option. This cuts resource routes generation in half #1359 [aaronbatalion]
* Remove support for old double-encoded cookies from the cookie store. These values haven't been generated since before 2.1.0, and any users who have visited the app in the intervening 6 months will have had their cookie upgraded. [Michael Koziarski]
* Allow helpers directory to be overridden via ActionController::Base.helpers_dir #1424 [Sam Pohlenz]
* Remove deprecated ActionController::Base#assign_default_content_type_and_charset
* Changed the default of ActionView#render to assume partials instead of files when not given an options hash [David Heinemeier Hansson]. Examples:
# Instead of <%= render :partial => "account" %>
<%= render "account" %>
# Instead of <%= render :partial => "account", :locals => { :account => @buyer } %>
<%= render "account", :account => @buyer %>
# @account is an Account instance, so it uses the RecordIdentifier to replace
# <%= render :partial => "accounts/account", :locals => { :account => @account } %>
<%= render(@account) %>
# @posts is an array of Post instances, so it uses the RecordIdentifier to replace
# <%= render :partial => "posts/post", :collection => @posts %>
<%= render(@posts) %>
* Remove deprecated render_component. Please use the plugin from http://github.com/rails/render_component/tree/master [Pratik Naik]
* Fixed RedCloth and BlueCloth shouldn't preload. Instead just assume that they're available if you want to use textilize and markdown and let autoload require them [David Heinemeier Hansson]
>>>>>>> 49a055d... Fixed the AssetTagHelper cache to use the computed asset host as part of the cache key instead of just assuming the its a string [#1299 state:committed]:actionpack/CHANGELOG
*2.2.2 (November 21st, 2008)*
* Deprecated the :file default for ActionView#render to prepare for 2.3's new :partial default [DHH]
* Restore backwards compatible functionality for setting relative_url_root. Include deprecation
@@ -28,9 +96,6 @@
* Fixed bug with asset timestamping when using relative_url_root #1265 [Joe Goldwasser]
*2.2.0 [RC1] (October 24th, 2008)*
* Fix incorrect closing CDATA delimiter and that HTML::Node.parse would blow up on unclosed CDATA sections [packagethief]
* Added stale? and fresh_when methods to provide a layer of abstraction above request.fresh? and friends [DHH]. Example:

View File

@@ -80,7 +80,7 @@ spec = Gem::Specification.new do |s|
s.has_rdoc = true
s.requirements << 'none'
s.add_dependency('activesupport', '= 2.2.1' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.2.3' + PKG_BUILD)
s.require_path = 'lib'
s.autorequire = 'action_controller'

View File

@@ -1164,6 +1164,9 @@ module ActionController #:nodoc:
def reset_session #:doc:
request.reset_session
@_session = request.session
#http://rails.lighthouseapp.com/projects/8994/tickets/1558-memory-problem-on-reset_session-in-around_filter#ticket-1558-1
#MRI appears to have a GC related memory leak to do with the finalizer that is defined on CGI::Session
ObjectSpace.undefine_finalizer(@_session)
response.session = @_session
end

View File

@@ -10,6 +10,8 @@ module ActionController
# Development mode callbacks
before_dispatch :reload_application
after_dispatch :cleanup_application
ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
end
# Common callbacks
@@ -147,7 +149,6 @@ module ActionController
Routing::Routes.reload
ActionController::Base.view_paths.reload!
ActionView::Helpers::AssetTagHelper::AssetTag::Cache.clear
end
# Cleanup the application by clearing out loaded classes so they can

View File

@@ -25,7 +25,7 @@ module Mime
# These are the content types which browsers can generate without using ajax, flash, etc
# i.e. following a link, getting an image or posting a form. CSRF protection
# only needs to protect against these types.
@@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form]
@@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form, :text]
cattr_reader :browser_generated_types
@@ -177,7 +177,7 @@ module Mime
end
# Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See
# ActionController::RequestForgerProtection.
# ActionController::RequestForgeryProtection.
def verify_request?
browser_generated?
end

View File

@@ -74,6 +74,7 @@ module ActionController
def polymorphic_url(record_or_hash_or_array, options = {})
if record_or_hash_or_array.kind_of?(Array)
record_or_hash_or_array = record_or_hash_or_array.compact
record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
end
record = extract_record(record_or_hash_or_array)

View File

@@ -1,5 +1,6 @@
require 'action_controller/cgi_ext'
require 'action_controller/session/cookie_store'
require 'action_controller/cgi_process'
module ActionController #:nodoc:
class RackRequest < AbstractRequest #:nodoc:
@@ -9,14 +10,7 @@ module ActionController #:nodoc:
class SessionFixationAttempt < StandardError #:nodoc:
end
DEFAULT_SESSION_OPTIONS = {
:database_manager => CGI::Session::CookieStore, # store data in cookie
:prefix => "ruby_sess.", # prefix session file names
:session_path => "/", # available to all paths in app
:session_key => "_session_id",
:cookie_only => true,
:session_http_only=> true
}
DEFAULT_SESSION_OPTIONS = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
@session_options = session_options

View File

@@ -305,6 +305,15 @@ EOM
else 80
end
end
# Returns the value of ActionController::Base.relative_url_root. This method is
# deprecated as the value is an application wide setting, not something which
# changes per request.
def relative_url_root
ActiveSupport::Deprecation.warn(
"relative_url_root is now set application-wide, use ActionController::Base.relative_url_root instead.", caller)
ActionController::Base.relative_url_root
end
# Returns a \port suffix like ":8080" if the \port number of this request
# is not the default HTTP \port 80 or HTTPS \port 443.

View File

@@ -535,9 +535,9 @@ module ActionController
with_options :controller => resource.controller do |map|
map_collection_actions(map, resource)
map_default_singleton_actions(map, resource)
map_new_actions(map, resource)
map_member_actions(map, resource)
map_default_singleton_actions(map, resource)
map_associations(resource, options)

View File

@@ -106,12 +106,8 @@ module ActionController
# argument
class PositionalArgumentsWithAdditionalParams < PositionalArguments
def guard_conditions
[
"args.size == #{route.segment_keys.size + 1}",
"!args.last.has_key?(:anchor)",
"!args.last.has_key?(:port)",
"!args.last.has_key?(:host)"
]
["args.size == #{route.segment_keys.size + 1}"] +
UrlRewriter::RESERVED_OPTIONS.collect{ |key| "!args.last.has_key?(:#{key})" }
end
# This case uses almost the same code as positional arguments,

View File

@@ -136,9 +136,13 @@ module ActionController
end
end
def named_helper_module_eval(code, *args)
@module.module_eval(code, *args)
end
def define_hash_access(route, name, kind, options)
selector = hash_access_name(name, kind)
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
def #{selector}(options = nil)
options ? #{options.inspect}.merge(options) : #{options.inspect}
end
@@ -166,8 +170,9 @@ module ActionController
#
# foo_url(bar, baz, bang, :sort_by => 'baz')
#
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
def #{selector}(*args)
#{generate_optimisation_block(route, kind)}
opts = if args.empty? || Hash === args.first

View File

@@ -3,7 +3,11 @@ module ActionController
class Segment #:nodoc:
RESERVED_PCHAR = ':@&=+$,;'
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
if RUBY_VERSION >= '1.9'
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze
else
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
end
# TODO: Convert :is_optional accessor to read only
attr_accessor :is_optional
@@ -191,23 +195,19 @@ module ActionController
end
def regexp_chunk
if regexp
if regexp_has_modifiers?
"(#{regexp.to_s})"
else
"(#{regexp.source})"
end
else
"([^#{Routing::SEPARATORS.join}]+)"
end
regexp ? regexp_string : default_regexp_chunk
end
def regexp_string
regexp_has_modifiers? ? "(#{regexp.to_s})" : "(#{regexp.source})"
end
def default_regexp_chunk
"([^#{Routing::SEPARATORS.join}]+)"
end
def number_of_captures
if regexp
regexp.number_of_captures + 1
else
1
end
regexp ? regexp.number_of_captures + 1 : 1
end
def build_pattern(pattern)
@@ -244,10 +244,6 @@ module ActionController
"(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
end
def number_of_captures
1
end
# Don't URI.escape the controller name since it may contain slashes.
def interpolation_chunk(value_code = local_name)
"\#{#{value_code}.to_s}"
@@ -289,8 +285,8 @@ module ActionController
"params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}"
end
def regexp_chunk
regexp || "(.*)"
def default_regexp_chunk
"(.*)"
end
def number_of_captures

View File

@@ -140,7 +140,7 @@ class CGI::Session::CookieStore
data, digest = cookie.split('--')
# Do two checks to transparently support old double-escaped data.
unless digest == generate_digest(data) || digest == generate_digest(data = CGI.unescape(data))
unless secure_compare(digest, generate_digest(data)) || secure_compare(digest, generate_digest(data = CGI.unescape(data)))
delete
raise TamperedWithCookie
end
@@ -164,4 +164,36 @@ class CGI::Session::CookieStore
def clear_old_cookie_value
@session.cgi.cookies[@cookie_options['name']].clear
end
if "foo".respond_to?(:force_encoding)
# constant-time comparison algorithm to prevent timing attacks
def secure_compare(a, b)
a = a.dup.force_encoding(Encoding::BINARY)
b = b.dup.force_encoding(Encoding::BINARY)
if a.length == b.length
result = 0
for i in 0..(a.length - 1)
result |= a[i].ord ^ b[i].ord
end
result == 0
else
false
end
end
else
# For 1.8
def secure_compare(a, b)
if a.length == b.length
result = 0
for i in 0..(a.length - 1)
result |= a[i] ^ b[i]
end
result == 0
else
false
end
end
end
end

View File

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

View File

@@ -43,7 +43,7 @@ require 'action_view/base'
require 'action_view/partials'
require 'action_view/template_error'
I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en-US.yml"
I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml"
require 'action_view/helpers'

View File

@@ -240,6 +240,11 @@ module ActionView #:nodoc:
local_assigns ||= {}
if options.is_a?(String)
ActiveSupport::Deprecation.warn(
"Calling render with a string will render a partial from Rails 2.3. " +
"Change this call to render(:file => '#{options}', :locals => locals_hash)."
)
render(:file => options, :locals => local_assigns)
elsif options == :update
update_page(&block)
@@ -315,9 +320,12 @@ module ActionView #:nodoc:
# OPTIMIZE: Checks to lookup template in view path
if template = self.view_paths["#{template_file_name}.#{template_format}"]
template
elsif template_file_extension && template = self.view_paths["#{template_file_name}.#{template_file_extension}"]
template
elsif template = self.view_paths[template_file_name]
template
elsif @_render_stack.first && template = self.view_paths["#{template_file_name}.#{@_render_stack.first.format_and_extension}"]
elsif (first_render = @_render_stack.first) && first_render.respond_to?(:format_and_extension) &&
(template = self.view_paths["#{template_file_name}.#{first_render.format_and_extension}"])
template
elsif template_format == :js && template = self.view_paths["#{template_file_name}.html"]
@template_format = :html

View File

@@ -151,7 +151,7 @@ module ActionView
# javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr.js
# javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js
def javascript_path(source)
JavaScriptTag.create(self, @controller, source).public_path
compute_public_path(source, 'javascripts', 'js')
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
@@ -249,17 +249,15 @@ module ActionView
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name)
unless File.exists?(joined_javascript_path)
JavaScriptSources.create(self, @controller, sources, recursive).write_asset_file_contents(joined_javascript_path)
end
write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) unless File.exists?(joined_javascript_path)
javascript_src_tag(joined_javascript_name, options)
else
JavaScriptSources.create(self, @controller, sources, recursive).expand_sources.collect { |source|
javascript_src_tag(source, options)
}.join("\n")
expand_javascript_sources(sources, recursive).collect { |source| javascript_src_tag(source, options) }.join("\n")
end
end
@@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
# Register one or more javascript files to be included when <tt>symbol</tt>
# is passed to <tt>javascript_include_tag</tt>. This method is typically intended
# to be called from plugin initialization to register javascript files
@@ -272,9 +270,11 @@ module ActionView
# <script type="text/javascript" src="/javascripts/body.js"></script>
# <script type="text/javascript" src="/javascripts/tail.js"></script>
def self.register_javascript_expansion(expansions)
JavaScriptSources.expansions.merge!(expansions)
@@javascript_expansions.merge!(expansions)
end
@@stylesheet_expansions = {}
# Register one or more stylesheet files to be included when <tt>symbol</tt>
# is passed to <tt>stylesheet_link_tag</tt>. This method is typically intended
# to be called from plugin initialization to register stylesheet files
@@ -287,7 +287,7 @@ module ActionView
# <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" />
# <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" />
def self.register_stylesheet_expansion(expansions)
StylesheetSources.expansions.merge!(expansions)
@@stylesheet_expansions.merge!(expansions)
end
# Register one or more additional JavaScript files to be included when
@@ -295,11 +295,11 @@ module ActionView
# typically intended to be called from plugin initialization to register additional
# .js files that the plugin installed in <tt>public/javascripts</tt>.
def self.register_javascript_include_default(*sources)
JavaScriptSources.expansions[:defaults].concat(sources)
@@javascript_expansions[:defaults].concat(sources)
end
def self.reset_javascript_include_default #:nodoc:
JavaScriptSources.expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup
@@javascript_expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup
end
# Computes the path to a stylesheet asset in the public stylesheets directory.
@@ -314,7 +314,7 @@ module ActionView
# stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style.css
# stylesheet_path "http://www.railsapplication.com/css/style.js" # => http://www.railsapplication.com/css/style.css
def stylesheet_path(source)
StylesheetTag.create(self, @controller, source).public_path
compute_public_path(source, 'stylesheets', 'css')
end
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
@@ -389,14 +389,10 @@ module ActionView
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name)
unless File.exists?(joined_stylesheet_path)
StylesheetSources.create(self, @controller, sources, recursive).write_asset_file_contents(joined_stylesheet_path)
end
write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) unless File.exists?(joined_stylesheet_path)
stylesheet_tag(joined_stylesheet_name, options)
else
StylesheetSources.create(self, @controller, sources, recursive).expand_sources.collect { |source|
stylesheet_tag(source, options)
}.join("\n")
expand_stylesheet_sources(sources, recursive).collect { |source| stylesheet_tag(source, options) }.join("\n")
end
end
@@ -411,7 +407,7 @@ module ActionView
# image_path("/icons/edit.png") # => /icons/edit.png
# image_path("http://www.railsapplication.com/img/edit.png") # => http://www.railsapplication.com/img/edit.png
def image_path(source)
ImageTag.create(self, @controller, source).public_path
compute_public_path(source, 'images')
end
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
@@ -466,7 +462,117 @@ module ActionView
tag("img", options)
end
def self.cache_asset_timestamps
@@cache_asset_timestamps
end
# You can enable or disable the asset tag timestamps cache.
# With the cache enabled, the asset tag helper methods will make fewer
# expense file system calls. However this prevents you from modifying
# any asset files while the server is running.
#
# ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
def self.cache_asset_timestamps=(value)
@@cache_asset_timestamps = value
end
@@cache_asset_timestamps = true
private
# Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
# Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
# roots. Rewrite the asset path for cache-busting asset ids. Include
# asset host, if configured, with the correct request protocol.
def compute_public_path(source, dir, ext = nil, include_host = true)
has_request = @controller.respond_to?(:request)
if ext && (File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}")))
source += ".#{ext}"
end
unless source =~ %r{^[-a-z]+://}
source = "/#{dir}/#{source}" unless source[0] == ?/
source = rewrite_asset_path(source)
if has_request && include_host
unless source =~ %r{^#{ActionController::Base.relative_url_root}/}
source = "#{ActionController::Base.relative_url_root}#{source}"
end
end
end
if include_host && source !~ %r{^[-a-z]+://}
host = compute_asset_host(source)
if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
host = "#{@controller.request.protocol}#{host}"
end
"#{host}#{source}"
else
source
end
end
# Pick an asset host for this source. Returns +nil+ if no host is set,
# the host if no wildcard is set, the host interpolated with the
# numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
# or the value returned from invoking the proc if it's a proc or the value from
# invoking call if it's an object responding to call.
def compute_asset_host(source)
if host = ActionController::Base.asset_host
if host.is_a?(Proc) || host.respond_to?(:call)
case host.is_a?(Proc) ? host.arity : host.method(:call).arity
when 2
request = @controller.respond_to?(:request) && @controller.request
host.call(source, request)
else
host.call(source)
end
else
(host =~ /%d/) ? host % (source.hash % 4) : host
end
end
end
@@asset_timestamps_cache = {}
@@asset_timestamps_cache_guard = Mutex.new
# Use the RAILS_ASSET_ID environment variable or the source's
# modification time as its cache-busting asset id.
def rails_asset_id(source)
if asset_id = ENV["RAILS_ASSET_ID"]
asset_id
else
if @@cache_asset_timestamps && (asset_id = @@asset_timestamps_cache[source])
asset_id
else
path = File.join(ASSETS_DIR, source)
asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : ''
if @@cache_asset_timestamps
@@asset_timestamps_cache_guard.synchronize do
@@asset_timestamps_cache[source] = asset_id
end
end
asset_id
end
end
end
# Break out the asset path rewrite in case plugins wish to put the asset id
# someplace other than the query string.
def rewrite_asset_path(source)
asset_id = rails_asset_id(source)
if asset_id.blank?
source
else
source + "?#{asset_id}"
end
end
def javascript_src_tag(source, options)
content_tag("script", "", { "type" => Mime::JS, "src" => path_to_javascript(source) }.merge(options))
end
@@ -475,337 +581,71 @@ module ActionView
tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
end
module ImageAsset
DIRECTORY = 'images'.freeze
def compute_javascript_paths(*args)
expand_javascript_sources(*args).collect { |source| compute_public_path(source, 'javascripts', 'js', false) }
end
def directory
DIRECTORY
end
def compute_stylesheet_paths(*args)
expand_stylesheet_sources(*args).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) }
end
def extension
nil
def expand_javascript_sources(sources, recursive = false)
if sources.include?(:all)
all_javascript_files = collect_asset_files(JAVASCRIPTS_DIR, ('**' if recursive), '*.js')
((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq
else
expanded_sources = sources.collect do |source|
determine_source(source, @@javascript_expansions)
end.flatten
expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
expanded_sources
end
end
module JavaScriptAsset
DIRECTORY = 'javascripts'.freeze
EXTENSION = 'js'.freeze
def public_directory
JAVASCRIPTS_DIR
end
def directory
DIRECTORY
end
def extension
EXTENSION
def expand_stylesheet_sources(sources, recursive)
if sources.first == :all
collect_asset_files(STYLESHEETS_DIR, ('**' if recursive), '*.css')
else
sources.collect do |source|
determine_source(source, @@stylesheet_expansions)
end.flatten
end
end
module StylesheetAsset
DIRECTORY = 'stylesheets'.freeze
EXTENSION = 'css'.freeze
def public_directory
STYLESHEETS_DIR
end
def directory
DIRECTORY
end
def extension
EXTENSION
def determine_source(source, collection)
case source
when Symbol
collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
else
source
end
end
class AssetTag
extend ActiveSupport::Memoizable
Cache = {}
CacheGuard = Mutex.new
def self.create(template, controller, source, include_host = true)
CacheGuard.synchronize do
key = if controller.respond_to?(:request)
[self, controller.request.protocol,
ActionController::Base.asset_host,
ActionController::Base.relative_url_root,
source, include_host]
else
[self, ActionController::Base.asset_host, source, include_host]
end
Cache[key] ||= new(template, controller, source, include_host).freeze
end
end
ProtocolRegexp = %r{^[-a-z]+://}.freeze
def initialize(template, controller, source, include_host = true)
# NOTE: The template arg is temporarily needed for a legacy plugin
# hook that is expected to call rewrite_asset_path on the
# template. This should eventually be removed.
@template = template
@controller = controller
@source = source
@include_host = include_host
end
def public_path
compute_public_path(@source)
end
memoize :public_path
def asset_file_path
File.join(ASSETS_DIR, public_path.split('?').first)
end
memoize :asset_file_path
def contents
File.read(asset_file_path)
end
def mtime
File.mtime(asset_file_path)
end
private
def request
@controller.request
end
def request?
@controller.respond_to?(:request)
end
# Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
# Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
# roots. Rewrite the asset path for cache-busting asset ids. Include
# asset host, if configured, with the correct request protocol.
def compute_public_path(source)
source += ".#{extension}" if missing_extension?(source)
unless source =~ ProtocolRegexp
source = "/#{directory}/#{source}" unless source[0] == ?/
source = rewrite_asset_path(source)
source = prepend_relative_url_root(source)
end
source = prepend_asset_host(source)
source
end
def missing_extension?(source)
extension && (File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, directory, "#{source}.#{extension}")))
end
def prepend_relative_url_root(source)
relative_url_root = ActionController::Base.relative_url_root
if request? && @include_host && source !~ %r{^#{relative_url_root}/}
"#{relative_url_root}#{source}"
else
source
end
end
def prepend_asset_host(source)
if @include_host && source !~ ProtocolRegexp
host = compute_asset_host(source)
if request? && !host.blank? && host !~ ProtocolRegexp
host = "#{request.protocol}#{host}"
end
"#{host}#{source}"
else
source
end
end
# Pick an asset host for this source. Returns +nil+ if no host is set,
# the host if no wildcard is set, the host interpolated with the
# numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
# or the value returned from invoking the proc if it's a proc.
def compute_asset_host(source)
if host = ActionController::Base.asset_host
if host.is_a?(Proc)
case host.arity
when 2
host.call(source, request)
else
host.call(source)
end
else
(host =~ /%d/) ? host % (source.hash % 4) : host
end
end
end
# Use the RAILS_ASSET_ID environment variable or the source's
# modification time as its cache-busting asset id.
def rails_asset_id(source)
if asset_id = ENV["RAILS_ASSET_ID"]
asset_id
else
path = File.join(ASSETS_DIR, source)
if File.exist?(path)
File.mtime(path).to_i.to_s
else
''
end
end
end
# Break out the asset path rewrite in case plugins wish to put the asset id
# someplace other than the query string.
def rewrite_asset_path(source)
if @template.respond_to?(:rewrite_asset_path)
# DEPRECATE: This way to override rewrite_asset_path
@template.send(:rewrite_asset_path, source)
else
asset_id = rails_asset_id(source)
if asset_id.blank?
source
else
"#{source}?#{asset_id}"
end
end
end
def join_asset_file_contents(paths)
paths.collect { |path| File.read(asset_file_path(path)) }.join("\n\n")
end
class ImageTag < AssetTag
include ImageAsset
def write_asset_file_contents(joined_asset_path, asset_paths)
FileUtils.mkdir_p(File.dirname(joined_asset_path))
File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
# Set mtime to the latest of the combined files to allow for
# consistent ETag without a shared filesystem.
mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max
File.utime(mt, mt, joined_asset_path)
end
class JavaScriptTag < AssetTag
include JavaScriptAsset
def asset_file_path(path)
File.join(ASSETS_DIR, path.split('?').first)
end
class StylesheetTag < AssetTag
include StylesheetAsset
end
def collect_asset_files(*path)
dir = path.first
class AssetCollection
extend ActiveSupport::Memoizable
Cache = {}
CacheGuard = Mutex.new
def self.create(template, controller, sources, recursive)
CacheGuard.synchronize do
key = [self, sources, recursive]
Cache[key] ||= new(template, controller, sources, recursive).freeze
end
end
def initialize(template, controller, sources, recursive)
# NOTE: The template arg is temporarily needed for a legacy plugin
# hook. See NOTE under AssetTag#initialize for more details
@template = template
@controller = controller
@sources = sources
@recursive = recursive
end
def write_asset_file_contents(joined_asset_path)
FileUtils.mkdir_p(File.dirname(joined_asset_path))
File.open(joined_asset_path, "w+") { |cache| cache.write(joined_contents) }
mt = latest_mtime
File.utime(mt, mt, joined_asset_path)
end
private
def determine_source(source, collection)
case source
when Symbol
collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
else
source
end
end
def validate_sources!
@sources.collect { |source| determine_source(source, self.class.expansions) }.flatten
end
def all_asset_files
path = [public_directory, ('**' if @recursive), "*.#{extension}"].compact
Dir[File.join(*path)].collect { |file|
file[-(file.size - public_directory.size - 1)..-1].sub(/\.\w+$/, '')
}.sort
end
def tag_sources
expand_sources.collect { |source| tag_class.create(@template, @controller, source, false) }
end
def joined_contents
tag_sources.collect { |source| source.contents }.join("\n\n")
end
# Set mtime to the latest of the combined files to allow for
# consistent ETag without a shared filesystem.
def latest_mtime
tag_sources.map { |source| source.mtime }.max
end
end
class JavaScriptSources < AssetCollection
include JavaScriptAsset
EXPANSIONS = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
def self.expansions
EXPANSIONS
end
APPLICATION_JS = "application".freeze
APPLICATION_FILE = "application.js".freeze
def expand_sources
if @sources.include?(:all)
assets = all_asset_files
((defaults.dup & assets) + assets).uniq!
else
expanded_sources = validate_sources!
expanded_sources << APPLICATION_JS if include_application?
expanded_sources
end
end
memoize :expand_sources
private
def tag_class
JavaScriptTag
end
def defaults
determine_source(:defaults, self.class.expansions)
end
def include_application?
@sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, APPLICATION_FILE))
end
end
class StylesheetSources < AssetCollection
include StylesheetAsset
EXPANSIONS = {}
def self.expansions
EXPANSIONS
end
def expand_sources
@sources.first == :all ? all_asset_files : validate_sources!
end
memoize :expand_sources
private
def tag_class
StylesheetTag
end
Dir[File.join(*path.compact)].collect do |file|
file[-(file.size - dir.size - 1)..-1].sub(/\.\w+$/, '')
end.sort
end
end
end
end
end

View File

@@ -131,7 +131,7 @@ module ActionView
# * <tt>:order</tt> - Set to an array containing <tt>:day</tt>, <tt>:month</tt> and <tt>:year</tt> do
# customize the order in which the select fields are shown. If you leave out any of the symbols, the respective
# select will not be shown (like when you set <tt>:discard_xxx => true</tt>. Defaults to the order defined in
# the respective locale (e.g. [:year, :month, :day] in the en-US locale that ships with Rails).
# the respective locale (e.g. [:year, :month, :day] in the en locale that ships with Rails).
# * <tt>:include_blank</tt> - Include a blank option in every select field so it's possible to set empty
# dates.
# * <tt>:default</tt> - Set a default date if the affected date isn't set or is nil.

View File

@@ -31,9 +31,6 @@ module ActionView
# to use all basic AJAX functionality. For the Scriptaculous-based
# JavaScript helpers, like visual effects, autocompletion, drag and drop
# and so on, you should use the method described above.
# * Use <tt><%= define_javascript_functions %></tt>: this will copy all the
# JavaScript support functions within a single script block. Not
# recommended.
#
# For documentation on +javascript_include_tag+ see
# ActionView::Helpers::AssetTagHelper.

View File

@@ -1,963 +0,0 @@
// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
// Contributors:
// Richard Livsey
// Rahul Bhargava
// Rob Wills
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
// Autocompleter.Base handles all the autocompletion functionality
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least,
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most
// useful when one of the tokens is \n (a newline), as it
// allows smart autocompletion after linebreaks.
if(typeof Effect == 'undefined')
throw("controls.js requires including script.aculo.us' effects.js library");
var Autocompleter = { }
Autocompleter.Base = Class.create({
baseInitialize: function(element, update, options) {
element = $(element)
this.element = element;
this.update = $(update);
this.hasFocus = false;
this.changed = false;
this.active = false;
this.index = 0;
this.entryCount = 0;
this.oldElementValue = this.element.value;
if(this.setOptions)
this.setOptions(options);
else
this.options = options || { };
this.options.paramName = this.options.paramName || this.element.name;
this.options.tokens = this.options.tokens || [];
this.options.frequency = this.options.frequency || 0.4;
this.options.minChars = this.options.minChars || 1;
this.options.onShow = this.options.onShow ||
function(element, update){
if(!update.style.position || update.style.position=='absolute') {
update.style.position = 'absolute';
Position.clone(element, update, {
setHeight: false,
offsetTop: element.offsetHeight
});
}
Effect.Appear(update,{duration:0.15});
};
this.options.onHide = this.options.onHide ||
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
if(typeof(this.options.tokens) == 'string')
this.options.tokens = new Array(this.options.tokens);
// Force carriage returns as token delimiters anyway
if (!this.options.tokens.include('\n'))
this.options.tokens.push('\n');
this.observer = null;
this.element.setAttribute('autocomplete','off');
Element.hide(this.update);
Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
},
show: function() {
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
if(!this.iefix &&
(Prototype.Browser.IE) &&
(Element.getStyle(this.update, 'position')=='absolute')) {
new Insertion.After(this.update,
'<iframe id="' + this.update.id + '_iefix" '+
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
this.iefix = $(this.update.id+'_iefix');
}
if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
},
fixIEOverlapping: function() {
Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
this.iefix.style.zIndex = 1;
this.update.style.zIndex = 2;
Element.show(this.iefix);
},
hide: function() {
this.stopIndicator();
if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
if(this.iefix) Element.hide(this.iefix);
},
startIndicator: function() {
if(this.options.indicator) Element.show(this.options.indicator);
},
stopIndicator: function() {
if(this.options.indicator) Element.hide(this.options.indicator);
},
onKeyPress: function(event) {
if(this.active)
switch(event.keyCode) {
case Event.KEY_TAB:
case Event.KEY_RETURN:
this.selectEntry();
Event.stop(event);
case Event.KEY_ESC:
this.hide();
this.active = false;
Event.stop(event);
return;
case Event.KEY_LEFT:
case Event.KEY_RIGHT:
return;
case Event.KEY_UP:
this.markPrevious();
this.render();
Event.stop(event);
return;
case Event.KEY_DOWN:
this.markNext();
this.render();
Event.stop(event);
return;
}
else
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
(Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
this.changed = true;
this.hasFocus = true;
if(this.observer) clearTimeout(this.observer);
this.observer =
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
},
activate: function() {
this.changed = false;
this.hasFocus = true;
this.getUpdatedChoices();
},
onHover: function(event) {
var element = Event.findElement(event, 'LI');
if(this.index != element.autocompleteIndex)
{
this.index = element.autocompleteIndex;
this.render();
}
Event.stop(event);
},
onClick: function(event) {
var element = Event.findElement(event, 'LI');
this.index = element.autocompleteIndex;
this.selectEntry();
this.hide();
},
onBlur: function(event) {
// needed to make click events working
setTimeout(this.hide.bind(this), 250);
this.hasFocus = false;
this.active = false;
},
render: function() {
if(this.entryCount > 0) {
for (var i = 0; i < this.entryCount; i++)
this.index==i ?
Element.addClassName(this.getEntry(i),"selected") :
Element.removeClassName(this.getEntry(i),"selected");
if(this.hasFocus) {
this.show();
this.active = true;
}
} else {
this.active = false;
this.hide();
}
},
markPrevious: function() {
if(this.index > 0) this.index--
else this.index = this.entryCount-1;
this.getEntry(this.index).scrollIntoView(true);
},
markNext: function() {
if(this.index < this.entryCount-1) this.index++
else this.index = 0;
this.getEntry(this.index).scrollIntoView(false);
},
getEntry: function(index) {
return this.update.firstChild.childNodes[index];
},
getCurrentEntry: function() {
return this.getEntry(this.index);
},
selectEntry: function() {
this.active = false;
this.updateElement(this.getCurrentEntry());
},
updateElement: function(selectedElement) {
if (this.options.updateElement) {
this.options.updateElement(selectedElement);
return;
}
var value = '';
if (this.options.select) {
var nodes = $(selectedElement).select('.' + this.options.select) || [];
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
} else
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
var bounds = this.getTokenBounds();
if (bounds[0] != -1) {
var newValue = this.element.value.substr(0, bounds[0]);
var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
if (whitespace)
newValue += whitespace[0];
this.element.value = newValue + value + this.element.value.substr(bounds[1]);
} else {
this.element.value = value;
}
this.oldElementValue = this.element.value;
this.element.focus();
if (this.options.afterUpdateElement)
this.options.afterUpdateElement(this.element, selectedElement);
},
updateChoices: function(choices) {
if(!this.changed && this.hasFocus) {
this.update.innerHTML = choices;
Element.cleanWhitespace(this.update);
Element.cleanWhitespace(this.update.down());
if(this.update.firstChild && this.update.down().childNodes) {
this.entryCount =
this.update.down().childNodes.length;
for (var i = 0; i < this.entryCount; i++) {
var entry = this.getEntry(i);
entry.autocompleteIndex = i;
this.addObservers(entry);
}
} else {
this.entryCount = 0;
}
this.stopIndicator();
this.index = 0;
if(this.entryCount==1 && this.options.autoSelect) {
this.selectEntry();
this.hide();
} else {
this.render();
}
}
},
addObservers: function(element) {
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
Event.observe(element, "click", this.onClick.bindAsEventListener(this));
},
onObserverEvent: function() {
this.changed = false;
this.tokenBounds = null;
if(this.getToken().length>=this.options.minChars) {
this.getUpdatedChoices();
} else {
this.active = false;
this.hide();
}
this.oldElementValue = this.element.value;
},
getToken: function() {
var bounds = this.getTokenBounds();
return this.element.value.substring(bounds[0], bounds[1]).strip();
},
getTokenBounds: function() {
if (null != this.tokenBounds) return this.tokenBounds;
var value = this.element.value;
if (value.strip().empty()) return [-1, 0];
var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
var offset = (diff == this.oldElementValue.length ? 1 : 0);
var prevTokenPos = -1, nextTokenPos = value.length;
var tp;
for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
if (tp > prevTokenPos) prevTokenPos = tp;
tp = value.indexOf(this.options.tokens[index], diff + offset);
if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
}
return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
}
});
Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
var boundary = Math.min(newS.length, oldS.length);
for (var index = 0; index < boundary; ++index)
if (newS[index] != oldS[index])
return index;
return boundary;
};
Ajax.Autocompleter = Class.create(Autocompleter.Base, {
initialize: function(element, update, url, options) {
this.baseInitialize(element, update, options);
this.options.asynchronous = true;
this.options.onComplete = this.onComplete.bind(this);
this.options.defaultParams = this.options.parameters || null;
this.url = url;
},
getUpdatedChoices: function() {
this.startIndicator();
var entry = encodeURIComponent(this.options.paramName) + '=' +
encodeURIComponent(this.getToken());
this.options.parameters = this.options.callback ?
this.options.callback(this.element, entry) : entry;
if(this.options.defaultParams)
this.options.parameters += '&' + this.options.defaultParams;
new Ajax.Request(this.url, this.options);
},
onComplete: function(request) {
this.updateChoices(request.responseText);
}
});
// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partialSearch - If false, the autocompleter will match entered
// text only at the beginning of strings in the
// autocomplete array. Defaults to true, which will
// match text at the beginning of any *word* in the
// strings in the autocomplete array. If you want to
// search anywhere in the string, additionally set
// the option fullSearch to true (default: off).
//
// - fullSsearch - Search anywhere in autocomplete array strings.
//
// - partialChars - How many characters to enter before triggering
// a partial match (unlike minChars, which defines
// how many characters are required to do any match
// at all). Defaults to 2.
//
// - ignoreCase - Whether to ignore case when autocompleting.
// Defaults to true.
//
// It's possible to pass in a custom function as the 'selector'
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.
Autocompleter.Local = Class.create(Autocompleter.Base, {
initialize: function(element, update, array, options) {
this.baseInitialize(element, update, options);
this.options.array = array;
},
getUpdatedChoices: function() {
this.updateChoices(this.options.selector(this));
},
setOptions: function(options) {
this.options = Object.extend({
choices: 10,
partialSearch: true,
partialChars: 2,
ignoreCase: true,
fullSearch: false,
selector: function(instance) {
var ret = []; // Beginning matches
var partial = []; // Inside matches
var entry = instance.getToken();
var count = 0;
for (var i = 0; i < instance.options.array.length &&
ret.length < instance.options.choices ; i++) {
var elem = instance.options.array[i];
var foundPos = instance.options.ignoreCase ?
elem.toLowerCase().indexOf(entry.toLowerCase()) :
elem.indexOf(entry);
while (foundPos != -1) {
if (foundPos == 0 && elem.length != entry.length) {
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
elem.substr(entry.length) + "</li>");
break;
} else if (entry.length >= instance.options.partialChars &&
instance.options.partialSearch && foundPos != -1) {
if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
foundPos + entry.length) + "</li>");
break;
}
}
foundPos = instance.options.ignoreCase ?
elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
elem.indexOf(entry, foundPos + 1);
}
}
if (partial.length)
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
return "<ul>" + ret.join('') + "</ul>";
}
}, options || { });
}
});
// AJAX in-place editor and collection editor
// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
// Use this if you notice weird scrolling problems on some browsers,
// the DOM might be a bit confused when this gets called so do this
// waits 1 ms (with setTimeout) until it does the activation
Field.scrollFreeActivate = function(field) {
setTimeout(function() {
Field.activate(field);
}, 1);
}
Ajax.InPlaceEditor = Class.create({
initialize: function(element, url, options) {
this.url = url;
this.element = element = $(element);
this.prepareOptions();
this._controls = { };
arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
Object.extend(this.options, options || { });
if (!this.options.formId && this.element.id) {
this.options.formId = this.element.id + '-inplaceeditor';
if ($(this.options.formId))
this.options.formId = '';
}
if (this.options.externalControl)
this.options.externalControl = $(this.options.externalControl);
if (!this.options.externalControl)
this.options.externalControlOnly = false;
this._originalBackground = this.element.getStyle('background-color') || 'transparent';
this.element.title = this.options.clickToEditText;
this._boundCancelHandler = this.handleFormCancellation.bind(this);
this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
this._boundFailureHandler = this.handleAJAXFailure.bind(this);
this._boundSubmitHandler = this.handleFormSubmission.bind(this);
this._boundWrapperHandler = this.wrapUp.bind(this);
this.registerListeners();
},
checkForEscapeOrReturn: function(e) {
if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
if (Event.KEY_ESC == e.keyCode)
this.handleFormCancellation(e);
else if (Event.KEY_RETURN == e.keyCode)
this.handleFormSubmission(e);
},
createControl: function(mode, handler, extraClasses) {
var control = this.options[mode + 'Control'];
var text = this.options[mode + 'Text'];
if ('button' == control) {
var btn = document.createElement('input');
btn.type = 'submit';
btn.value = text;
btn.className = 'editor_' + mode + '_button';
if ('cancel' == mode)
btn.onclick = this._boundCancelHandler;
this._form.appendChild(btn);
this._controls[mode] = btn;
} else if ('link' == control) {
var link = document.createElement('a');
link.href = '#';
link.appendChild(document.createTextNode(text));
link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
link.className = 'editor_' + mode + '_link';
if (extraClasses)
link.className += ' ' + extraClasses;
this._form.appendChild(link);
this._controls[mode] = link;
}
},
createEditField: function() {
var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
var fld;
if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
fld = document.createElement('input');
fld.type = 'text';
var size = this.options.size || this.options.cols || 0;
if (0 < size) fld.size = size;
} else {
fld = document.createElement('textarea');
fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
fld.cols = this.options.cols || 40;
}
fld.name = this.options.paramName;
fld.value = text; // No HTML breaks conversion anymore
fld.className = 'editor_field';
if (this.options.submitOnBlur)
fld.onblur = this._boundSubmitHandler;
this._controls.editor = fld;
if (this.options.loadTextURL)
this.loadExternalText();
this._form.appendChild(this._controls.editor);
},
createForm: function() {
var ipe = this;
function addText(mode, condition) {
var text = ipe.options['text' + mode + 'Controls'];
if (!text || condition === false) return;
ipe._form.appendChild(document.createTextNode(text));
};
this._form = $(document.createElement('form'));
this._form.id = this.options.formId;
this._form.addClassName(this.options.formClassName);
this._form.onsubmit = this._boundSubmitHandler;
this.createEditField();
if ('textarea' == this._controls.editor.tagName.toLowerCase())
this._form.appendChild(document.createElement('br'));
if (this.options.onFormCustomization)
this.options.onFormCustomization(this, this._form);
addText('Before', this.options.okControl || this.options.cancelControl);
this.createControl('ok', this._boundSubmitHandler);
addText('Between', this.options.okControl && this.options.cancelControl);
this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
addText('After', this.options.okControl || this.options.cancelControl);
},
destroy: function() {
if (this._oldInnerHTML)
this.element.innerHTML = this._oldInnerHTML;
this.leaveEditMode();
this.unregisterListeners();
},
enterEditMode: function(e) {
if (this._saving || this._editing) return;
this._editing = true;
this.triggerCallback('onEnterEditMode');
if (this.options.externalControl)
this.options.externalControl.hide();
this.element.hide();
this.createForm();
this.element.parentNode.insertBefore(this._form, this.element);
if (!this.options.loadTextURL)
this.postProcessEditField();
if (e) Event.stop(e);
},
enterHover: function(e) {
if (this.options.hoverClassName)
this.element.addClassName(this.options.hoverClassName);
if (this._saving) return;
this.triggerCallback('onEnterHover');
},
getText: function() {
return this.element.innerHTML;
},
handleAJAXFailure: function(transport) {
this.triggerCallback('onFailure', transport);
if (this._oldInnerHTML) {
this.element.innerHTML = this._oldInnerHTML;
this._oldInnerHTML = null;
}
},
handleFormCancellation: function(e) {
this.wrapUp();
if (e) Event.stop(e);
},
handleFormSubmission: function(e) {
var form = this._form;
var value = $F(this._controls.editor);
this.prepareSubmission();
var params = this.options.callback(form, value) || '';
if (Object.isString(params))
params = params.toQueryParams();
params.editorId = this.element.id;
if (this.options.htmlResponse) {
var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
Object.extend(options, {
parameters: params,
onComplete: this._boundWrapperHandler,
onFailure: this._boundFailureHandler
});
new Ajax.Updater({ success: this.element }, this.url, options);
} else {
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: params,
onComplete: this._boundWrapperHandler,
onFailure: this._boundFailureHandler
});
new Ajax.Request(this.url, options);
}
if (e) Event.stop(e);
},
leaveEditMode: function() {
this.element.removeClassName(this.options.savingClassName);
this.removeForm();
this.leaveHover();
this.element.style.backgroundColor = this._originalBackground;
this.element.show();
if (this.options.externalControl)
this.options.externalControl.show();
this._saving = false;
this._editing = false;
this._oldInnerHTML = null;
this.triggerCallback('onLeaveEditMode');
},
leaveHover: function(e) {
if (this.options.hoverClassName)
this.element.removeClassName(this.options.hoverClassName);
if (this._saving) return;
this.triggerCallback('onLeaveHover');
},
loadExternalText: function() {
this._form.addClassName(this.options.loadingClassName);
this._controls.editor.disabled = true;
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: 'editorId=' + encodeURIComponent(this.element.id),
onComplete: Prototype.emptyFunction,
onSuccess: function(transport) {
this._form.removeClassName(this.options.loadingClassName);
var text = transport.responseText;
if (this.options.stripLoadedTextTags)
text = text.stripTags();
this._controls.editor.value = text;
this._controls.editor.disabled = false;
this.postProcessEditField();
}.bind(this),
onFailure: this._boundFailureHandler
});
new Ajax.Request(this.options.loadTextURL, options);
},
postProcessEditField: function() {
var fpc = this.options.fieldPostCreation;
if (fpc)
$(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
},
prepareOptions: function() {
this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
[this._extraDefaultOptions].flatten().compact().each(function(defs) {
Object.extend(this.options, defs);
}.bind(this));
},
prepareSubmission: function() {
this._saving = true;
this.removeForm();
this.leaveHover();
this.showSaving();
},
registerListeners: function() {
this._listeners = { };
var listener;
$H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
listener = this[pair.value].bind(this);
this._listeners[pair.key] = listener;
if (!this.options.externalControlOnly)
this.element.observe(pair.key, listener);
if (this.options.externalControl)
this.options.externalControl.observe(pair.key, listener);
}.bind(this));
},
removeForm: function() {
if (!this._form) return;
this._form.remove();
this._form = null;
this._controls = { };
},
showSaving: function() {
this._oldInnerHTML = this.element.innerHTML;
this.element.innerHTML = this.options.savingText;
this.element.addClassName(this.options.savingClassName);
this.element.style.backgroundColor = this._originalBackground;
this.element.show();
},
triggerCallback: function(cbName, arg) {
if ('function' == typeof this.options[cbName]) {
this.options[cbName](this, arg);
}
},
unregisterListeners: function() {
$H(this._listeners).each(function(pair) {
if (!this.options.externalControlOnly)
this.element.stopObserving(pair.key, pair.value);
if (this.options.externalControl)
this.options.externalControl.stopObserving(pair.key, pair.value);
}.bind(this));
},
wrapUp: function(transport) {
this.leaveEditMode();
// Can't use triggerCallback due to backward compatibility: requires
// binding + direct element
this._boundComplete(transport, this.element);
}
});
Object.extend(Ajax.InPlaceEditor.prototype, {
dispose: Ajax.InPlaceEditor.prototype.destroy
});
Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
initialize: function($super, element, url, options) {
this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
$super(element, url, options);
},
createEditField: function() {
var list = document.createElement('select');
list.name = this.options.paramName;
list.size = 1;
this._controls.editor = list;
this._collection = this.options.collection || [];
if (this.options.loadCollectionURL)
this.loadCollection();
else
this.checkForExternalText();
this._form.appendChild(this._controls.editor);
},
loadCollection: function() {
this._form.addClassName(this.options.loadingClassName);
this.showLoadingText(this.options.loadingCollectionText);
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: 'editorId=' + encodeURIComponent(this.element.id),
onComplete: Prototype.emptyFunction,
onSuccess: function(transport) {
var js = transport.responseText.strip();
if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
throw 'Server returned an invalid collection representation.';
this._collection = eval(js);
this.checkForExternalText();
}.bind(this),
onFailure: this.onFailure
});
new Ajax.Request(this.options.loadCollectionURL, options);
},
showLoadingText: function(text) {
this._controls.editor.disabled = true;
var tempOption = this._controls.editor.firstChild;
if (!tempOption) {
tempOption = document.createElement('option');
tempOption.value = '';
this._controls.editor.appendChild(tempOption);
tempOption.selected = true;
}
tempOption.update((text || '').stripScripts().stripTags());
},
checkForExternalText: function() {
this._text = this.getText();
if (this.options.loadTextURL)
this.loadExternalText();
else
this.buildOptionList();
},
loadExternalText: function() {
this.showLoadingText(this.options.loadingText);
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: 'editorId=' + encodeURIComponent(this.element.id),
onComplete: Prototype.emptyFunction,
onSuccess: function(transport) {
this._text = transport.responseText.strip();
this.buildOptionList();
}.bind(this),
onFailure: this.onFailure
});
new Ajax.Request(this.options.loadTextURL, options);
},
buildOptionList: function() {
this._form.removeClassName(this.options.loadingClassName);
this._collection = this._collection.map(function(entry) {
return 2 === entry.length ? entry : [entry, entry].flatten();
});
var marker = ('value' in this.options) ? this.options.value : this._text;
var textFound = this._collection.any(function(entry) {
return entry[0] == marker;
}.bind(this));
this._controls.editor.update('');
var option;
this._collection.each(function(entry, index) {
option = document.createElement('option');
option.value = entry[0];
option.selected = textFound ? entry[0] == marker : 0 == index;
option.appendChild(document.createTextNode(entry[1]));
this._controls.editor.appendChild(option);
}.bind(this));
this._controls.editor.disabled = false;
Field.scrollFreeActivate(this._controls.editor);
}
});
//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
//**** This only exists for a while, in order to let ****
//**** users adapt to the new API. Read up on the new ****
//**** API and convert your code to it ASAP! ****
Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
if (!options) return;
function fallback(name, expr) {
if (name in options || expr === undefined) return;
options[name] = expr;
};
fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
options.cancelLink == options.cancelButton == false ? false : undefined)));
fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
options.okLink == options.okButton == false ? false : undefined)));
fallback('highlightColor', options.highlightcolor);
fallback('highlightEndColor', options.highlightendcolor);
};
Object.extend(Ajax.InPlaceEditor, {
DefaultOptions: {
ajaxOptions: { },
autoRows: 3, // Use when multi-line w/ rows == 1
cancelControl: 'link', // 'link'|'button'|false
cancelText: 'cancel',
clickToEditText: 'Click to edit',
externalControl: null, // id|elt
externalControlOnly: false,
fieldPostCreation: 'activate', // 'activate'|'focus'|false
formClassName: 'inplaceeditor-form',
formId: null, // id|elt
highlightColor: '#ffff99',
highlightEndColor: '#ffffff',
hoverClassName: '',
htmlResponse: true,
loadingClassName: 'inplaceeditor-loading',
loadingText: 'Loading...',
okControl: 'button', // 'link'|'button'|false
okText: 'ok',
paramName: 'value',
rows: 1, // If 1 and multi-line, uses autoRows
savingClassName: 'inplaceeditor-saving',
savingText: 'Saving...',
size: 0,
stripLoadedTextTags: false,
submitOnBlur: false,
textAfterControls: '',
textBeforeControls: '',
textBetweenControls: ''
},
DefaultCallbacks: {
callback: function(form) {
return Form.serialize(form);
},
onComplete: function(transport, element) {
// For backward compatibility, this one is bound to the IPE, and passes
// the element directly. It was too often customized, so we don't break it.
new Effect.Highlight(element, {
startcolor: this.options.highlightColor, keepBackgroundImage: true });
},
onEnterEditMode: null,
onEnterHover: function(ipe) {
ipe.element.style.backgroundColor = ipe.options.highlightColor;
if (ipe._effect)
ipe._effect.cancel();
},
onFailure: function(transport, ipe) {
alert('Error communication with the server: ' + transport.responseText.stripTags());
},
onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
onLeaveEditMode: null,
onLeaveHover: function(ipe) {
ipe._effect = new Effect.Highlight(ipe.element, {
startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
restorecolor: ipe._originalBackground, keepBackgroundImage: true
});
}
},
Listeners: {
click: 'enterEditMode',
keydown: 'checkForEscapeOrReturn',
mouseover: 'enterHover',
mouseout: 'leaveHover'
}
});
Ajax.InPlaceCollectionEditor.DefaultOptions = {
loadingCollectionText: 'Loading options...'
};
// Delayed observer, like Form.Element.Observer,
// but waits for delay after last key input
// Ideal for live-search fields
Form.Element.DelayedObserver = Class.create({
initialize: function(element, delay, callback) {
this.delay = delay || 0.5;
this.element = $(element);
this.callback = callback;
this.timer = null;
this.lastValue = $F(this.element);
Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
},
delayedListener: function(event) {
if(this.lastValue == $F(this.element)) return;
if(this.timer) clearTimeout(this.timer);
this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
this.lastValue = $F(this.element);
},
onTimerEvent: function() {
this.timer = null;
this.callback(this.element, $F(this.element));
}
});

View File

@@ -1,972 +0,0 @@
// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
if(Object.isUndefined(Effect))
throw("dragdrop.js requires including script.aculo.us' effects.js library");
var Droppables = {
drops: [],
remove: function(element) {
this.drops = this.drops.reject(function(d) { return d.element==$(element) });
},
add: function(element) {
element = $(element);
var options = Object.extend({
greedy: true,
hoverclass: null,
tree: false
}, arguments[1] || { });
// cache containers
if(options.containment) {
options._containers = [];
var containment = options.containment;
if(Object.isArray(containment)) {
containment.each( function(c) { options._containers.push($(c)) });
} else {
options._containers.push($(containment));
}
}
if(options.accept) options.accept = [options.accept].flatten();
Element.makePositioned(element); // fix IE
options.element = element;
this.drops.push(options);
},
findDeepestChild: function(drops) {
deepest = drops[0];
for (i = 1; i < drops.length; ++i)
if (Element.isParent(drops[i].element, deepest.element))
deepest = drops[i];
return deepest;
},
isContained: function(element, drop) {
var containmentNode;
if(drop.tree) {
containmentNode = element.treeNode;
} else {
containmentNode = element.parentNode;
}
return drop._containers.detect(function(c) { return containmentNode == c });
},
isAffected: function(point, element, drop) {
return (
(drop.element!=element) &&
((!drop._containers) ||
this.isContained(element, drop)) &&
((!drop.accept) ||
(Element.classNames(element).detect(
function(v) { return drop.accept.include(v) } ) )) &&
Position.within(drop.element, point[0], point[1]) );
},
deactivate: function(drop) {
if(drop.hoverclass)
Element.removeClassName(drop.element, drop.hoverclass);
this.last_active = null;
},
activate: function(drop) {
if(drop.hoverclass)
Element.addClassName(drop.element, drop.hoverclass);
this.last_active = drop;
},
show: function(point, element) {
if(!this.drops.length) return;
var drop, affected = [];
this.drops.each( function(drop) {
if(Droppables.isAffected(point, element, drop))
affected.push(drop);
});
if(affected.length>0)
drop = Droppables.findDeepestChild(affected);
if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
if (drop) {
Position.within(drop.element, point[0], point[1]);
if(drop.onHover)
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
if (drop != this.last_active) Droppables.activate(drop);
}
},
fire: function(event, element) {
if(!this.last_active) return;
Position.prepare();
if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
if (this.last_active.onDrop) {
this.last_active.onDrop(element, this.last_active.element, event);
return true;
}
},
reset: function() {
if(this.last_active)
this.deactivate(this.last_active);
}
}
var Draggables = {
drags: [],
observers: [],
register: function(draggable) {
if(this.drags.length == 0) {
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
this.eventKeypress = this.keyPress.bindAsEventListener(this);
Event.observe(document, "mouseup", this.eventMouseUp);
Event.observe(document, "mousemove", this.eventMouseMove);
Event.observe(document, "keypress", this.eventKeypress);
}
this.drags.push(draggable);
},
unregister: function(draggable) {
this.drags = this.drags.reject(function(d) { return d==draggable });
if(this.drags.length == 0) {
Event.stopObserving(document, "mouseup", this.eventMouseUp);
Event.stopObserving(document, "mousemove", this.eventMouseMove);
Event.stopObserving(document, "keypress", this.eventKeypress);
}
},
activate: function(draggable) {
if(draggable.options.delay) {
this._timeout = setTimeout(function() {
Draggables._timeout = null;
window.focus();
Draggables.activeDraggable = draggable;
}.bind(this), draggable.options.delay);
} else {
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
this.activeDraggable = draggable;
}
},
deactivate: function() {
this.activeDraggable = null;
},
updateDrag: function(event) {
if(!this.activeDraggable) return;
var pointer = [Event.pointerX(event), Event.pointerY(event)];
// Mozilla-based browsers fire successive mousemove events with
// the same coordinates, prevent needless redrawing (moz bug?)
if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
this._lastPointer = pointer;
this.activeDraggable.updateDrag(event, pointer);
},
endDrag: function(event) {
if(this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}
if(!this.activeDraggable) return;
this._lastPointer = null;
this.activeDraggable.endDrag(event);
this.activeDraggable = null;
},
keyPress: function(event) {
if(this.activeDraggable)
this.activeDraggable.keyPress(event);
},
addObserver: function(observer) {
this.observers.push(observer);
this._cacheObserverCallbacks();
},
removeObserver: function(element) { // element instead of observer fixes mem leaks
this.observers = this.observers.reject( function(o) { return o.element==element });
this._cacheObserverCallbacks();
},
notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
if(this[eventName+'Count'] > 0)
this.observers.each( function(o) {
if(o[eventName]) o[eventName](eventName, draggable, event);
});
if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
},
_cacheObserverCallbacks: function() {
['onStart','onEnd','onDrag'].each( function(eventName) {
Draggables[eventName+'Count'] = Draggables.observers.select(
function(o) { return o[eventName]; }
).length;
});
}
}
/*--------------------------------------------------------------------------*/
var Draggable = Class.create({
initialize: function(element) {
var defaults = {
handle: false,
reverteffect: function(element, top_offset, left_offset) {
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
queue: {scope:'_draggable', position:'end'}
});
},
endeffect: function(element) {
var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
queue: {scope:'_draggable', position:'end'},
afterFinish: function(){
Draggable._dragging[element] = false
}
});
},
zindex: 1000,
revert: false,
quiet: false,
scroll: false,
scrollSensitivity: 20,
scrollSpeed: 15,
snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
delay: 0
};
if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
Object.extend(defaults, {
starteffect: function(element) {
element._opacity = Element.getOpacity(element);
Draggable._dragging[element] = true;
new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
}
});
var options = Object.extend(defaults, arguments[1] || { });
this.element = $(element);
if(options.handle && Object.isString(options.handle))
this.handle = this.element.down('.'+options.handle, 0);
if(!this.handle) this.handle = $(options.handle);
if(!this.handle) this.handle = this.element;
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
options.scroll = $(options.scroll);
this._isScrollChild = Element.childOf(this.element, options.scroll);
}
Element.makePositioned(this.element); // fix IE
this.options = options;
this.dragging = false;
this.eventMouseDown = this.initDrag.bindAsEventListener(this);
Event.observe(this.handle, "mousedown", this.eventMouseDown);
Draggables.register(this);
},
destroy: function() {
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
Draggables.unregister(this);
},
currentDelta: function() {
return([
parseInt(Element.getStyle(this.element,'left') || '0'),
parseInt(Element.getStyle(this.element,'top') || '0')]);
},
initDrag: function(event) {
if(!Object.isUndefined(Draggable._dragging[this.element]) &&
Draggable._dragging[this.element]) return;
if(Event.isLeftClick(event)) {
// abort on form elements, fixes a Firefox issue
var src = Event.element(event);
if((tag_name = src.tagName.toUpperCase()) && (
tag_name=='INPUT' ||
tag_name=='SELECT' ||
tag_name=='OPTION' ||
tag_name=='BUTTON' ||
tag_name=='TEXTAREA')) return;
var pointer = [Event.pointerX(event), Event.pointerY(event)];
var pos = Position.cumulativeOffset(this.element);
this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
Draggables.activate(this);
Event.stop(event);
}
},
startDrag: function(event) {
this.dragging = true;
if(!this.delta)
this.delta = this.currentDelta();
if(this.options.zindex) {
this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
this.element.style.zIndex = this.options.zindex;
}
if(this.options.ghosting) {
this._clone = this.element.cloneNode(true);
this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
if (!this.element._originallyAbsolute)
Position.absolutize(this.element);
this.element.parentNode.insertBefore(this._clone, this.element);
}
if(this.options.scroll) {
if (this.options.scroll == window) {
var where = this._getWindowScroll(this.options.scroll);
this.originalScrollLeft = where.left;
this.originalScrollTop = where.top;
} else {
this.originalScrollLeft = this.options.scroll.scrollLeft;
this.originalScrollTop = this.options.scroll.scrollTop;
}
}
Draggables.notify('onStart', this, event);
if(this.options.starteffect) this.options.starteffect(this.element);
},
updateDrag: function(event, pointer) {
if(!this.dragging) this.startDrag(event);
if(!this.options.quiet){
Position.prepare();
Droppables.show(pointer, this.element);
}
Draggables.notify('onDrag', this, event);
this.draw(pointer);
if(this.options.change) this.options.change(this);
if(this.options.scroll) {
this.stopScrolling();
var p;
if (this.options.scroll == window) {
with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
} else {
p = Position.page(this.options.scroll);
p[0] += this.options.scroll.scrollLeft + Position.deltaX;
p[1] += this.options.scroll.scrollTop + Position.deltaY;
p.push(p[0]+this.options.scroll.offsetWidth);
p.push(p[1]+this.options.scroll.offsetHeight);
}
var speed = [0,0];
if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
this.startScrolling(speed);
}
// fix AppleWebKit rendering
if(Prototype.Browser.WebKit) window.scrollBy(0,0);
Event.stop(event);
},
finishDrag: function(event, success) {
this.dragging = false;
if(this.options.quiet){
Position.prepare();
var pointer = [Event.pointerX(event), Event.pointerY(event)];
Droppables.show(pointer, this.element);
}
if(this.options.ghosting) {
if (!this.element._originallyAbsolute)
Position.relativize(this.element);
delete this.element._originallyAbsolute;
Element.remove(this._clone);
this._clone = null;
}
var dropped = false;
if(success) {
dropped = Droppables.fire(event, this.element);
if (!dropped) dropped = false;
}
if(dropped && this.options.onDropped) this.options.onDropped(this.element);
Draggables.notify('onEnd', this, event);
var revert = this.options.revert;
if(revert && Object.isFunction(revert)) revert = revert(this.element);
var d = this.currentDelta();
if(revert && this.options.reverteffect) {
if (dropped == 0 || revert != 'failure')
this.options.reverteffect(this.element,
d[1]-this.delta[1], d[0]-this.delta[0]);
} else {
this.delta = d;
}
if(this.options.zindex)
this.element.style.zIndex = this.originalZ;
if(this.options.endeffect)
this.options.endeffect(this.element);
Draggables.deactivate(this);
Droppables.reset();
},
keyPress: function(event) {
if(event.keyCode!=Event.KEY_ESC) return;
this.finishDrag(event, false);
Event.stop(event);
},
endDrag: function(event) {
if(!this.dragging) return;
this.stopScrolling();
this.finishDrag(event, true);
Event.stop(event);
},
draw: function(point) {
var pos = Position.cumulativeOffset(this.element);
if(this.options.ghosting) {
var r = Position.realOffset(this.element);
pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
}
var d = this.currentDelta();
pos[0] -= d[0]; pos[1] -= d[1];
if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
}
var p = [0,1].map(function(i){
return (point[i]-pos[i]-this.offset[i])
}.bind(this));
if(this.options.snap) {
if(Object.isFunction(this.options.snap)) {
p = this.options.snap(p[0],p[1],this);
} else {
if(Object.isArray(this.options.snap)) {
p = p.map( function(v, i) {
return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
} else {
p = p.map( function(v) {
return (v/this.options.snap).round()*this.options.snap }.bind(this))
}
}}
var style = this.element.style;
if((!this.options.constraint) || (this.options.constraint=='horizontal'))
style.left = p[0] + "px";
if((!this.options.constraint) || (this.options.constraint=='vertical'))
style.top = p[1] + "px";
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
},
stopScrolling: function() {
if(this.scrollInterval) {
clearInterval(this.scrollInterval);
this.scrollInterval = null;
Draggables._lastScrollPointer = null;
}
},
startScrolling: function(speed) {
if(!(speed[0] || speed[1])) return;
this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
this.lastScrolled = new Date();
this.scrollInterval = setInterval(this.scroll.bind(this), 10);
},
scroll: function() {
var current = new Date();
var delta = current - this.lastScrolled;
this.lastScrolled = current;
if(this.options.scroll == window) {
with (this._getWindowScroll(this.options.scroll)) {
if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
var d = delta / 1000;
this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
}
}
} else {
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
}
Position.prepare();
Droppables.show(Draggables._lastPointer, this.element);
Draggables.notify('onDrag', this);
if (this._isScrollChild) {
Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
if (Draggables._lastScrollPointer[0] < 0)
Draggables._lastScrollPointer[0] = 0;
if (Draggables._lastScrollPointer[1] < 0)
Draggables._lastScrollPointer[1] = 0;
this.draw(Draggables._lastScrollPointer);
}
if(this.options.change) this.options.change(this);
},
_getWindowScroll: function(w) {
var T, L, W, H;
with (w.document) {
if (w.document.documentElement && documentElement.scrollTop) {
T = documentElement.scrollTop;
L = documentElement.scrollLeft;
} else if (w.document.body) {
T = body.scrollTop;
L = body.scrollLeft;
}
if (w.innerWidth) {
W = w.innerWidth;
H = w.innerHeight;
} else if (w.document.documentElement && documentElement.clientWidth) {
W = documentElement.clientWidth;
H = documentElement.clientHeight;
} else {
W = body.offsetWidth;
H = body.offsetHeight
}
}
return { top: T, left: L, width: W, height: H };
}
});
Draggable._dragging = { };
/*--------------------------------------------------------------------------*/
var SortableObserver = Class.create({
initialize: function(element, observer) {
this.element = $(element);
this.observer = observer;
this.lastValue = Sortable.serialize(this.element);
},
onStart: function() {
this.lastValue = Sortable.serialize(this.element);
},
onEnd: function() {
Sortable.unmark();
if(this.lastValue != Sortable.serialize(this.element))
this.observer(this.element)
}
});
var Sortable = {
SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
sortables: { },
_findRootElement: function(element) {
while (element.tagName.toUpperCase() != "BODY") {
if(element.id && Sortable.sortables[element.id]) return element;
element = element.parentNode;
}
},
options: function(element) {
element = Sortable._findRootElement($(element));
if(!element) return;
return Sortable.sortables[element.id];
},
destroy: function(element){
var s = Sortable.options(element);
if(s) {
Draggables.removeObserver(s.element);
s.droppables.each(function(d){ Droppables.remove(d) });
s.draggables.invoke('destroy');
delete Sortable.sortables[s.element.id];
}
},
create: function(element) {
element = $(element);
var options = Object.extend({
element: element,
tag: 'li', // assumes li children, override with tag: 'tagname'
dropOnEmpty: false,
tree: false,
treeTag: 'ul',
overlap: 'vertical', // one of 'vertical', 'horizontal'
constraint: 'vertical', // one of 'vertical', 'horizontal', false
containment: element, // also takes array of elements (or id's); or false
handle: false, // or a CSS class
only: false,
delay: 0,
hoverclass: null,
ghosting: false,
quiet: false,
scroll: false,
scrollSensitivity: 20,
scrollSpeed: 15,
format: this.SERIALIZE_RULE,
// these take arrays of elements or ids and can be
// used for better initialization performance
elements: false,
handles: false,
onChange: Prototype.emptyFunction,
onUpdate: Prototype.emptyFunction
}, arguments[1] || { });
// clear any old sortable with same element
this.destroy(element);
// build options for the draggables
var options_for_draggable = {
revert: true,
quiet: options.quiet,
scroll: options.scroll,
scrollSpeed: options.scrollSpeed,
scrollSensitivity: options.scrollSensitivity,
delay: options.delay,
ghosting: options.ghosting,
constraint: options.constraint,
handle: options.handle };
if(options.starteffect)
options_for_draggable.starteffect = options.starteffect;
if(options.reverteffect)
options_for_draggable.reverteffect = options.reverteffect;
else
if(options.ghosting) options_for_draggable.reverteffect = function(element) {
element.style.top = 0;
element.style.left = 0;
};
if(options.endeffect)
options_for_draggable.endeffect = options.endeffect;
if(options.zindex)
options_for_draggable.zindex = options.zindex;
// build options for the droppables
var options_for_droppable = {
overlap: options.overlap,
containment: options.containment,
tree: options.tree,
hoverclass: options.hoverclass,
onHover: Sortable.onHover
}
var options_for_tree = {
onHover: Sortable.onEmptyHover,
overlap: options.overlap,
containment: options.containment,
hoverclass: options.hoverclass
}
// fix for gecko engine
Element.cleanWhitespace(element);
options.draggables = [];
options.droppables = [];
// drop on empty handling
if(options.dropOnEmpty || options.tree) {
Droppables.add(element, options_for_tree);
options.droppables.push(element);
}
(options.elements || this.findElements(element, options) || []).each( function(e,i) {
var handle = options.handles ? $(options.handles[i]) :
(options.handle ? $(e).select('.' + options.handle)[0] : e);
options.draggables.push(
new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
Droppables.add(e, options_for_droppable);
if(options.tree) e.treeNode = element;
options.droppables.push(e);
});
if(options.tree) {
(Sortable.findTreeElements(element, options) || []).each( function(e) {
Droppables.add(e, options_for_tree);
e.treeNode = element;
options.droppables.push(e);
});
}
// keep reference
this.sortables[element.id] = options;
// for onupdate
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
},
// return all suitable-for-sortable elements in a guaranteed order
findElements: function(element, options) {
return Element.findChildren(
element, options.only, options.tree ? true : false, options.tag);
},
findTreeElements: function(element, options) {
return Element.findChildren(
element, options.only, options.tree ? true : false, options.treeTag);
},
onHover: function(element, dropon, overlap) {
if(Element.isParent(dropon, element)) return;
if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
return;
} else if(overlap>0.5) {
Sortable.mark(dropon, 'before');
if(dropon.previousSibling != element) {
var oldParentNode = element.parentNode;
element.style.visibility = "hidden"; // fix gecko rendering
dropon.parentNode.insertBefore(element, dropon);
if(dropon.parentNode!=oldParentNode)
Sortable.options(oldParentNode).onChange(element);
Sortable.options(dropon.parentNode).onChange(element);
}
} else {
Sortable.mark(dropon, 'after');
var nextElement = dropon.nextSibling || null;
if(nextElement != element) {
var oldParentNode = element.parentNode;
element.style.visibility = "hidden"; // fix gecko rendering
dropon.parentNode.insertBefore(element, nextElement);
if(dropon.parentNode!=oldParentNode)
Sortable.options(oldParentNode).onChange(element);
Sortable.options(dropon.parentNode).onChange(element);
}
}
},
onEmptyHover: function(element, dropon, overlap) {
var oldParentNode = element.parentNode;
var droponOptions = Sortable.options(dropon);
if(!Element.isParent(dropon, element)) {
var index;
var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
var child = null;
if(children) {
var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
for (index = 0; index < children.length; index += 1) {
if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
offset -= Element.offsetSize (children[index], droponOptions.overlap);
} else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
child = index + 1 < children.length ? children[index + 1] : null;
break;
} else {
child = children[index];
break;
}
}
}
dropon.insertBefore(element, child);
Sortable.options(oldParentNode).onChange(element);
droponOptions.onChange(element);
}
},
unmark: function() {
if(Sortable._marker) Sortable._marker.hide();
},
mark: function(dropon, position) {
// mark on ghosting only
var sortable = Sortable.options(dropon.parentNode);
if(sortable && !sortable.ghosting) return;
if(!Sortable._marker) {
Sortable._marker =
($('dropmarker') || Element.extend(document.createElement('DIV'))).
hide().addClassName('dropmarker').setStyle({position:'absolute'});
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
}
var offsets = Position.cumulativeOffset(dropon);
Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
if(position=='after')
if(sortable.overlap == 'horizontal')
Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
else
Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
Sortable._marker.show();
},
_tree: function(element, options, parent) {
var children = Sortable.findElements(element, options) || [];
for (var i = 0; i < children.length; ++i) {
var match = children[i].id.match(options.format);
if (!match) continue;
var child = {
id: encodeURIComponent(match ? match[1] : null),
element: element,
parent: parent,
children: [],
position: parent.children.length,
container: $(children[i]).down(options.treeTag)
}
/* Get the element containing the children and recurse over it */
if (child.container)
this._tree(child.container, options, child)
parent.children.push (child);
}
return parent;
},
tree: function(element) {
element = $(element);
var sortableOptions = this.options(element);
var options = Object.extend({
tag: sortableOptions.tag,
treeTag: sortableOptions.treeTag,
only: sortableOptions.only,
name: element.id,
format: sortableOptions.format
}, arguments[1] || { });
var root = {
id: null,
parent: null,
children: [],
container: element,
position: 0
}
return Sortable._tree(element, options, root);
},
/* Construct a [i] index for a particular node */
_constructIndex: function(node) {
var index = '';
do {
if (node.id) index = '[' + node.position + ']' + index;
} while ((node = node.parent) != null);
return index;
},
sequence: function(element) {
element = $(element);
var options = Object.extend(this.options(element), arguments[1] || { });
return $(this.findElements(element, options) || []).map( function(item) {
return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
});
},
setSequence: function(element, new_sequence) {
element = $(element);
var options = Object.extend(this.options(element), arguments[2] || { });
var nodeMap = { };
this.findElements(element, options).each( function(n) {
if (n.id.match(options.format))
nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
n.parentNode.removeChild(n);
});
new_sequence.each(function(ident) {
var n = nodeMap[ident];
if (n) {
n[1].appendChild(n[0]);
delete nodeMap[ident];
}
});
},
serialize: function(element) {
element = $(element);
var options = Object.extend(Sortable.options(element), arguments[1] || { });
var name = encodeURIComponent(
(arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
if (options.tree) {
return Sortable.tree(element, arguments[1]).children.map( function (item) {
return [name + Sortable._constructIndex(item) + "[id]=" +
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
}).flatten().join('&');
} else {
return Sortable.sequence(element, arguments[1]).map( function(item) {
return name + "[]=" + encodeURIComponent(item);
}).join('&');
}
}
}
// Returns true if child is contained within element
Element.isParent = function(child, element) {
if (!child.parentNode || child == element) return false;
if (child.parentNode == element) return true;
return Element.isParent(child.parentNode, element);
}
Element.findChildren = function(element, only, recursive, tagName) {
if(!element.hasChildNodes()) return null;
tagName = tagName.toUpperCase();
if(only) only = [only].flatten();
var elements = [];
$A(element.childNodes).each( function(e) {
if(e.tagName && e.tagName.toUpperCase()==tagName &&
(!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
elements.push(e);
if(recursive) {
var grandchildren = Element.findChildren(e, only, recursive, tagName);
if(grandchildren) elements.push(grandchildren);
}
});
return (elements.length>0 ? elements.flatten() : []);
}
Element.offsetSize = function (element, type) {
return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -220,8 +220,6 @@ module ActionView
end
end
STORAGE_UNITS = %w( Bytes KB MB GB TB ).freeze
# Formats the bytes in +size+ into a more understandable representation
# (e.g., giving it 1500 yields 1.5 KB). This method is useful for
# reporting file sizes to users. This method returns nil if
@@ -257,6 +255,7 @@ module ActionView
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
human = I18n.translate(:'number.human.format', :locale => options[:locale], :raise => true) rescue {}
defaults = defaults.merge(human)
storage_units = I18n.translate(:'number.human.storage_units', :locale => options[:locale], :raise => true)
unless args.empty?
ActiveSupport::Deprecation.warn('number_to_human_size takes an option hash ' +
@@ -268,12 +267,12 @@ module ActionView
separator ||= (options[:separator] || defaults[:separator])
delimiter ||= (options[:delimiter] || defaults[:delimiter])
max_exp = STORAGE_UNITS.size - 1
max_exp = storage_units.size - 1
number = Float(number)
exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024
exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
number /= 1024 ** exponent
unit = STORAGE_UNITS[exponent]
unit = storage_units[exponent]
begin
escaped_separator = Regexp.escape(separator)

View File

@@ -9,7 +9,7 @@ module ActionView
module TagHelper
include ERB::Util
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple).to_set
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked).to_set
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
# Returns an empty HTML tag of type +name+ which by default is XHTML
@@ -104,7 +104,7 @@ module ActionView
# escape_once("&lt;&lt; Accept & Checkout")
# # => "&lt;&lt; Accept &amp; Checkout"
def escape_once(html)
html.to_s.gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] }
ActiveSupport::Multibyte.clean(html.to_s).gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] }
end
private

View File

@@ -1,4 +1,4 @@
"en-US":
"en":
number:
# Used in number_with_delimiter()
# These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
@@ -44,6 +44,7 @@
# separator:
delimiter: ""
precision: 1
storage_units: [Bytes, KB, MB, GB, TB]
# Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()
datetime:

View File

@@ -29,7 +29,9 @@ module ActionView
stack.push(self)
# This is only used for TestResponse to set rendered_template
view.instance_variable_set(:@_first_render, self) unless view.instance_variable_get(:@_first_render)
unless is_a?(InlineTemplate) || view.instance_variable_get(:@_first_render)
view.instance_variable_set(:@_first_render, self)
end
view.send(:_evaluate_assigns_and_ivars)
view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type)

View File

@@ -1,14 +1,34 @@
# Legacy TemplateHandler stub
module ActionView
module TemplateHandlers
module TemplateHandlers #:nodoc:
module Compilable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def call(template)
new.compile(template)
end
end
def compile(template)
raise "Need to implement #{self.class.name}#compile(template)"
end
end
end
class TemplateHandler
class TemplateHandler #:nodoc:
def self.call(template)
new.compile(template)
"#{name}.new(self).render(template, local_assigns)"
end
def initialize(view = nil)
@view = view
end
def render(template, local_assigns)
raise "Need to implement #{self.class.name}#render(template, local_assigns)"
end
end
end

View File

@@ -291,11 +291,13 @@ class ActionCacheTest < Test::Unit::TestCase
ActionController::Base.use_accept_header = old_use_accept_header
end
def test_action_cache_with_store_options
MockTime.expects(:now).returns(12345).once
@controller.expects(:read_fragment).with('hostname.com/action_caching_test', :expires_in => 1.hour).once
@controller.expects(:write_fragment).with('hostname.com/action_caching_test', '12345.0', :expires_in => 1.hour).once
get :index
uses_mocha 'test action cache' do
def test_action_cache_with_store_options
MockTime.expects(:now).returns(12345).once
@controller.expects(:read_fragment).with('hostname.com/action_caching_test', :expires_in => 1.hour).once
@controller.expects(:write_fragment).with('hostname.com/action_caching_test', '12345.0', :expires_in => 1.hour).once
get :index
end
end
def test_action_cache_with_custom_cache_path

View File

@@ -48,7 +48,8 @@ class BaseCgiTest < Test::Unit::TestCase
# some developers have grown accustomed to using comma in cookie values.
@alt_cookie_fmt_request_hash = {"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"}
@cgi = CGI.new
@cgi.stubs(:env_table).returns(@request_hash)
class << @cgi; attr_accessor :env_table end
@cgi.env_table = @request_hash
@request = ActionController::CgiRequest.new(@cgi)
end
@@ -160,6 +161,12 @@ class CgiRequestTest < BaseCgiTest
assert_equal ["c84ace847,96670c052c6ceb2451fb0f2"], alt_cookies["_session_id"], alt_cookies.inspect
assert_equal ["yes"], alt_cookies["is_admin"], alt_cookies.inspect
end
def test_relative_url_root
assert_deprecated("ActionController::Base.relative_url_root") do
assert_equal ActionController::Base.relative_url_root, @request.relative_url_root
end
end
end
class CgiRequestParamsParsingTest < BaseCgiTest

View File

@@ -35,11 +35,6 @@ class DispatcherTest < Test::Unit::TestCase
dispatch(@output, false)
end
def test_clears_asset_tag_cache_before_dispatch_if_in_loading_mode
ActionView::Helpers::AssetTagHelper::AssetTag::Cache.expects(:clear).once
dispatch(@output, false)
end
def test_leaves_dependencies_after_dispatch_if_not_in_loading_mode
ActionController::Routing::Routes.expects(:reload).never
ActiveSupport::Dependencies.expects(:clear).never

View File

@@ -180,6 +180,12 @@ uses_mocha 'polymorphic URL helpers' do
polymorphic_url([nil, @article])
end
def test_with_array_containing_single_name
@article.save
expects(:articles_url)
polymorphic_url([:articles])
end
# TODO: Needs to be updated to correctly know about whether the object is in a hash or not
def xtest_with_hash
expects(:article_url).with(@article)

View File

@@ -39,7 +39,7 @@ class TestController < ActionController::Base
render :action => 'hello_world'
end
before_filter :handle_last_modified_and_etags, :only=>:conditional_hello_with_bangs
def handle_last_modified_and_etags
fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ])
end
@@ -246,6 +246,15 @@ class TestController < ActionController::Base
:locals => { :local_name => name }
end
def helper_method_to_render_to_string(*args)
render_to_string(*args)
end
helper_method :helper_method_to_render_to_string
def render_html_only_partial_within_inline
render :inline => "Hello world <%= helper_method_to_render_to_string :partial => 'test/partial_with_only_html_version' %>"
end
def formatted_html_erb
end
@@ -337,6 +346,11 @@ class TestController < ActionController::Base
render :text => "Hi web users! #{@stuff}"
end
def render_to_string_with_inline_and_render
render_to_string :inline => "<%= 'dlrow olleh'.reverse %>"
render :template => "test/hello_world"
end
def rendering_with_conflicting_local_vars
@name = "David"
def @template.name() nil end
@@ -855,7 +869,10 @@ class RenderTest < Test::Unit::TestCase
end
def test_render_xml
get :render_xml_hello
assert_deprecated do
get :render_xml_hello
end
assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body
assert_equal "application/xml", @response.content_type
end
@@ -889,7 +906,10 @@ class RenderTest < Test::Unit::TestCase
end
def test_render_xml_with_layouts
get :builder_layout_test
assert_deprecated do
get :builder_layout_test
end
assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
end
@@ -908,6 +928,11 @@ class RenderTest < Test::Unit::TestCase
assert_equal "The value of foo is: ::this is a test::\n", @response.body
end
def test_render_to_string_inline
get :render_to_string_with_inline_and_render
assert_template "test/hello_world"
end
def test_nested_rendering
@controller = Fun::GamesController.new
get :hello_world
@@ -924,6 +949,11 @@ class RenderTest < Test::Unit::TestCase
assert_equal "Goodbye, Local David", @response.body
end
def test_rendering_html_only_partial_within_inline_with_js
get :render_html_only_partial_within_inline, :format => :js
assert_equal "Hello world partial with only html version", @response.body
end
def test_should_render_formatted_template
get :formatted_html_erb
assert_equal 'formatted html erb', @response.body
@@ -1368,7 +1398,7 @@ class EtagRenderTest < Test::Unit::TestCase
assert_equal "200 OK", @response.status
assert !@response.body.empty?
end
def test_render_should_not_set_etag_when_last_modified_has_been_specified
get :render_hello_world_with_last_modified_set
assert_equal "200 OK", @response.status
@@ -1382,7 +1412,7 @@ class EtagRenderTest < Test::Unit::TestCase
expected_etag = etag_for('hello david')
assert_equal expected_etag, @response.headers['ETag']
@response = ActionController::TestResponse.new
@request.if_none_match = expected_etag
get :render_hello_world_from_variable
assert_equal "304 Not Modified", @response.status
@@ -1403,28 +1433,31 @@ class EtagRenderTest < Test::Unit::TestCase
end
def test_etag_should_govern_renders_with_layouts_too
get :builder_layout_test
assert_deprecated do
get :builder_layout_test
end
assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
assert_equal etag_for("<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n"), @response.headers['ETag']
end
def test_etag_with_bang_should_set_etag
get :conditional_hello_with_bangs
assert_equal @expected_bang_etag, @response.headers["ETag"]
assert_response :success
end
def test_etag_with_bang_should_obey_if_none_match
@request.if_none_match = @expected_bang_etag
get :conditional_hello_with_bangs
assert_response :not_modified
end
protected
def etag_for(text)
%("#{Digest::MD5.hexdigest(text)}")
end
def expand_key(args)
ActiveSupport::Cache.expand_cache_key(args)
end
@@ -1467,13 +1500,13 @@ class LastModifiedRenderTest < Test::Unit::TestCase
assert !@response.body.blank?
assert_equal @last_modified, @response.headers['Last-Modified']
end
def test_request_with_bang_gets_last_modified
get :conditional_hello_with_bangs
assert_equal @last_modified, @response.headers['Last-Modified']
assert_response :success
end
def test_request_with_bang_obeys_last_modified
@request.if_modified_since = @last_modified
get :conditional_hello_with_bangs

View File

@@ -471,6 +471,13 @@ class UrlEncodedRequestParameterParsingTest < Test::Unit::TestCase
)
end
def test_query_string_with_multiple_of_same_name
assert_equal(
{ "action" => "update_order", "full_name" => "Lau Taarnskov", "products" => "4" },
ActionController::AbstractRequest.parse_query_parameters(@query_string_with_multiple_of_same_name)
)
end
def test_query_string_with_many_equal
assert_equal(
{ "action" => "create_customer", "full_name" => "abc=def=ghi"},
@@ -735,11 +742,7 @@ class MultipartRequestParameterParsingTest < Test::Unit::TestCase
file = params['file']
foo = params['foo']
if RUBY_VERSION > '1.9'
assert_kind_of File, file
else
assert_kind_of Tempfile, file
end
assert_kind_of Tempfile, file
assert_equal 'file.txt', file.original_filename
assert_equal "text/plain", file.content_type
@@ -753,11 +756,9 @@ class MultipartRequestParameterParsingTest < Test::Unit::TestCase
assert_equal 'bar', params['foo']
file = params['file']
if RUBY_VERSION > '1.9'
assert_kind_of File, file
else
assert_kind_of Tempfile, file
end
assert_kind_of Tempfile, file
assert_equal 'file.txt', file.original_filename
assert_equal "text/plain", file.content_type
assert ('a' * 20480) == file.read

View File

@@ -997,6 +997,16 @@ class ResourcesTest < Test::Unit::TestCase
end
end
def test_default_singleton_restful_route_uses_get
with_routing do |set|
set.draw do |map|
map.resource :product
end
assert_equal :get, set.named_routes.routes[:product].conditions[:method]
end
end
protected
def with_restful_routing(*args)
with_routing do |set|

View File

@@ -341,6 +341,30 @@ class ControllerSegmentTest < Test::Unit::TestCase
end
end
class PathSegmentTest < Test::Unit::TestCase
def segment(options = {})
unless @segment
@segment = ROUTING::PathSegment.new(:path, options)
end
@segment
end
def test_regexp_chunk_should_return_string
segment = segment(:regexp => /[a-z]+/)
assert_kind_of String, segment.regexp_chunk
end
def test_regexp_chunk_should_be_wrapped_with_parenthesis
segment = segment(:regexp => /[a-z]+/)
assert_equal "([a-z]+)", segment.regexp_chunk
end
def test_regexp_chunk_should_respect_options
segment = segment(:regexp => /[a-z]+/i)
assert_equal "((?i-mx:[a-z]+))", segment.regexp_chunk
end
end
class RouteBuilderTest < Test::Unit::TestCase
def builder
@builder ||= ROUTING::RouteBuilder.new
@@ -706,12 +730,13 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
port = options.delete(:port) || 80
port_string = port == 80 ? '' : ":#{port}"
host = options.delete(:host) || "named.route.test"
anchor = "##{options.delete(:anchor)}" if options.key?(:anchor)
protocol = options.delete(:protocol) || "http"
host = options.delete(:host) || "named.route.test"
anchor = "##{options.delete(:anchor)}" if options.key?(:anchor)
path = routes.generate(options)
only_path ? "#{path}#{anchor}" : "http://#{host}#{port_string}#{path}#{anchor}"
only_path ? "#{path}#{anchor}" : "#{protocol}://#{host}#{port_string}#{path}#{anchor}"
end
def request
@@ -868,6 +893,16 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
assert_equal '/content/foo', rs.generate(:controller => "content", :action => "foo")
end
def test_route_with_regexp_and_captures_for_controller
rs.draw do |map|
map.connect ':controller/:action/:id', :controller => /admin\/(accounts|users)/
end
assert_equal({:controller => "admin/accounts", :action => "index"}, rs.recognize_path("/admin/accounts"))
assert_equal({:controller => "admin/users", :action => "index"}, rs.recognize_path("/admin/users"))
assert_raises(ActionController::RoutingError) { rs.recognize_path("/admin/products") }
end
def test_route_with_regexp_and_dot
rs.draw do |map|
map.connect ':controller/:action/:file',
@@ -1148,6 +1183,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
assert_equal({:controller => "content", :action => 'show_page', :id => 'foo'}, rs.recognize_path("/page/foo"))
token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in russian
token.force_encoding("UTF-8") if token.respond_to?(:force_encoding)
escaped_token = CGI::escape(token)
assert_equal '/page/' + escaped_token, rs.generate(:controller => 'content', :action => 'show_page', :id => token)
@@ -1726,6 +1762,11 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
assert_equal "http://some.example.com/people/5", controller.send(:show_url, 5, :host=>"some.example.com")
end
def test_named_route_url_method_with_protocol
controller = setup_named_route_test
assert_equal "https://named.route.test/people/5", controller.send(:show_url, 5, :protocol => "https")
end
def test_named_route_url_method_with_ordered_parameters
controller = setup_named_route_test
assert_equal "http://named.route.test/people/go/7/hello/joe/5",

View File

@@ -606,7 +606,7 @@ class CleanBacktraceTest < Test::Unit::TestCase
def test_should_reraise_the_same_object
exception = Test::Unit::AssertionFailedError.new('message')
clean_backtrace { raise exception }
rescue => caught
rescue Exception => caught
assert_equal exception.object_id, caught.object_id
assert_equal exception.message, caught.message
end
@@ -616,7 +616,7 @@ class CleanBacktraceTest < Test::Unit::TestCase
exception = Test::Unit::AssertionFailedError.new('message')
exception.set_backtrace ["#{path}/abc", "#{path}/assertions/def"]
clean_backtrace { raise exception }
rescue => caught
rescue Exception => caught
assert_equal ["#{path}/abc"], caught.backtrace
end

View File

@@ -0,0 +1 @@
partial with only html version

View File

@@ -0,0 +1 @@
var greeting = 'Hallo World!';

View File

@@ -10,37 +10,37 @@ class ActiveRecordHelperI18nTest < Test::Unit::TestCase
@object_name = 'book'
stubs(:content_tag).returns 'content_tag'
I18n.stubs(:t).with(:'header', :locale => 'en-US', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved"
I18n.stubs(:t).with(:'body', :locale => 'en-US', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:'
I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved"
I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:'
end
def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message
I18n.expects(:translate).with(:'header', :locale => 'en-US', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').never
error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en-US')
I18n.expects(:translate).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').never
error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en')
end
def test_error_messages_for_given_no_header_option_it_translates_header_message
I18n.expects(:t).with(:'header', :locale => 'en-US', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns 'header message'
I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns 'header message'
I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns ''
error_messages_for(:object => @object, :locale => 'en-US')
error_messages_for(:object => @object, :locale => 'en')
end
def test_error_messages_for_given_a_message_option_it_does_not_translate_message
I18n.expects(:t).with(:'body', :locale => 'en-US', :scope => [:activerecord, :errors, :template]).never
I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).never
I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns ''
error_messages_for(:object => @object, :message => 'message', :locale => 'en-US')
error_messages_for(:object => @object, :message => 'message', :locale => 'en')
end
def test_error_messages_for_given_no_message_option_it_translates_message
I18n.expects(:t).with(:'body', :locale => 'en-US', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:'
I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:'
I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns ''
error_messages_for(:object => @object, :locale => 'en-US')
error_messages_for(:object => @object, :locale => 'en')
end
def test_error_messages_for_given_object_name_it_translates_object_name
I18n.expects(:t).with(:header, :locale => 'en-US', :scope => [:activerecord, :errors, :template], :count => 1, :model => @object_name).returns "1 error prohibited this #{@object_name} from being saved"
I18n.expects(:t).with(:header, :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => @object_name).returns "1 error prohibited this #{@object_name} from being saved"
I18n.expects(:t).with(@object_name, :default => @object_name, :count => 1, :scope => [:activerecord, :models]).once.returns @object_name
error_messages_for(:object => @object, :locale => 'en-US', :object_name => @object_name)
error_messages_for(:object => @object, :locale => 'en', :object_name => @object_name)
end
end
end

View File

@@ -38,8 +38,6 @@ class AssetTagHelperTest < ActionView::TestCase
@controller.request = @request
ActionView::Helpers::AssetTagHelper::reset_javascript_include_default
AssetTag::Cache.clear
AssetCollection::Cache.clear
end
def teardown
@@ -281,6 +279,26 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal copy, source
end
def test_caching_image_path_with_caching_and_proc_asset_host_using_request
ENV['RAILS_ASSET_ID'] = ''
ActionController::Base.asset_host = Proc.new do |source, request|
if request.ssl?
"#{request.protocol}#{request.host_with_port}"
else
"#{request.protocol}assets#{source.length}.example.com"
end
end
ActionController::Base.perform_caching = true
@controller.request.stubs(:ssl?).returns(false)
assert_equal "http://assets15.example.com/images/xml.png", image_path("xml.png")
@controller.request.stubs(:ssl?).returns(true)
assert_equal "http://localhost/images/xml.png", image_path("xml.png")
end
def test_caching_javascript_include_tag_when_caching_on
ENV["RAILS_ASSET_ID"] = ""
ActionController::Base.asset_host = 'http://a0.example.com'
@@ -648,4 +666,10 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
ensure
ActionController::Base.asset_host = nil
end
def test_assert_css_and_js_of_the_same_name_return_correct_extension
assert_dom_equal(%(/collaboration/hieraki/javascripts/foo.js), javascript_path("foo"))
assert_dom_equal(%(/collaboration/hieraki/stylesheets/foo.css), stylesheet_path("foo"))
end
end

View File

@@ -12,30 +12,42 @@ uses_mocha 'TestTemplateRecompilation' do
def test_template_gets_compiled
assert_equal 0, @compiled_templates.instance_methods.size
assert_equal "Hello world!", render("test/hello_world.erb")
assert_deprecated do
assert_equal "Hello world!", render("test/hello_world.erb")
end
assert_equal 1, @compiled_templates.instance_methods.size
end
def test_template_gets_recompiled_when_using_different_keys_in_local_assigns
assert_equal 0, @compiled_templates.instance_methods.size
assert_equal "Hello world!", render("test/hello_world.erb")
assert_equal "Hello world!", render("test/hello_world.erb", {:foo => "bar"})
assert_deprecated do
assert_equal "Hello world!", render("test/hello_world.erb")
assert_equal "Hello world!", render("test/hello_world.erb", {:foo => "bar"})
end
assert_equal 2, @compiled_templates.instance_methods.size
end
def test_compiled_template_will_not_be_recompiled_when_rendered_with_identical_local_assigns
assert_equal 0, @compiled_templates.instance_methods.size
assert_equal "Hello world!", render("test/hello_world.erb")
ActionView::Template.any_instance.expects(:compile!).never
assert_equal "Hello world!", render("test/hello_world.erb")
assert_deprecated do
assert_equal "Hello world!", render("test/hello_world.erb")
ActionView::Template.any_instance.expects(:compile!).never
assert_equal "Hello world!", render("test/hello_world.erb")
end
end
def test_compiled_template_will_always_be_recompiled_when_eager_loaded_templates_is_off
ActionView::PathSet::Path.expects(:eager_load_templates?).times(4).returns(false)
assert_equal 0, @compiled_templates.instance_methods.size
assert_equal "Hello world!", render("#{FIXTURE_LOAD_PATH}/test/hello_world.erb")
assert_deprecated do
assert_equal "Hello world!", render("#{FIXTURE_LOAD_PATH}/test/hello_world.erb")
end
ActionView::Template.any_instance.expects(:compile!).times(3)
3.times { assert_equal "Hello world!", render("#{FIXTURE_LOAD_PATH}/test/hello_world.erb") }
assert_deprecated do
3.times { assert_equal "Hello world!", render("#{FIXTURE_LOAD_PATH}/test/hello_world.erb") }
end
assert_equal 1, @compiled_templates.instance_methods.size
end

View File

@@ -41,11 +41,11 @@ class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase
key, count = *expected
to = @from + diff
options = {:locale => 'en-US', :scope => :'datetime.distance_in_words'}
options = {:locale => 'en', :scope => :'datetime.distance_in_words'}
options[:count] = count if count
I18n.expects(:t).with(key, options)
distance_of_time_in_words(@from, to, include_seconds, :locale => 'en-US')
distance_of_time_in_words(@from, to, include_seconds, :locale => 'en')
end
def test_distance_of_time_pluralizations
@@ -78,36 +78,36 @@ class DateHelperSelectTagsI18nTests < Test::Unit::TestCase
uses_mocha 'date_helper_select_tags_i18n_tests' do
def setup
I18n.stubs(:translate).with(:'date.month_names', :locale => 'en-US').returns Date::MONTHNAMES
I18n.stubs(:translate).with(:'date.month_names', :locale => 'en').returns Date::MONTHNAMES
end
# select_month
def test_select_month_given_use_month_names_option_does_not_translate_monthnames
I18n.expects(:translate).never
select_month(8, :locale => 'en-US', :use_month_names => Date::MONTHNAMES)
select_month(8, :locale => 'en', :use_month_names => Date::MONTHNAMES)
end
def test_select_month_translates_monthnames
I18n.expects(:translate).with(:'date.month_names', :locale => 'en-US').returns Date::MONTHNAMES
select_month(8, :locale => 'en-US')
I18n.expects(:translate).with(:'date.month_names', :locale => 'en').returns Date::MONTHNAMES
select_month(8, :locale => 'en')
end
def test_select_month_given_use_short_month_option_translates_abbr_monthnames
I18n.expects(:translate).with(:'date.abbr_month_names', :locale => 'en-US').returns Date::ABBR_MONTHNAMES
select_month(8, :locale => 'en-US', :use_short_month => true)
I18n.expects(:translate).with(:'date.abbr_month_names', :locale => 'en').returns Date::ABBR_MONTHNAMES
select_month(8, :locale => 'en', :use_short_month => true)
end
# date_or_time_select
def test_date_or_time_select_given_an_order_options_does_not_translate_order
I18n.expects(:translate).never
datetime_select('post', 'updated_at', :order => [:year, :month, :day], :locale => 'en-US')
datetime_select('post', 'updated_at', :order => [:year, :month, :day], :locale => 'en')
end
def test_date_or_time_select_given_no_order_options_translates_order
I18n.expects(:translate).with(:'date.order', :locale => 'en-US').returns [:year, :month, :day]
datetime_select('post', 'updated_at', :locale => 'en-US')
I18n.expects(:translate).with(:'date.order', :locale => 'en').returns [:year, :month, :day]
datetime_select('post', 'updated_at', :locale => 'en')
end
end
end

View File

@@ -238,6 +238,7 @@ class FormTagHelperTest < ActionView::TestCase
def test_boolean_options
assert_dom_equal %(<input checked="checked" disabled="disabled" id="admin" name="admin" readonly="readonly" type="checkbox" value="1" />), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes")
assert_dom_equal %(<input checked="checked" id="admin" name="admin" type="checkbox" value="1" />), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil)
assert_dom_equal %(<input type="checkbox" />), tag(:input, :type => "checkbox", :checked => false)
assert_dom_equal %(<select id="people" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people", "<option>david</option>", :multiple => true)
assert_dom_equal %(<select id="people_" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people[]", "<option>david</option>", :multiple => true)
assert_dom_equal %(<select id="people" name="people"><option>david</option></select>), select_tag("people", "<option>david</option>", :multiple => nil)

View File

@@ -10,45 +10,48 @@ class NumberHelperI18nTests < Test::Unit::TestCase
@number_defaults = { :precision => 3, :delimiter => ',', :separator => '.' }
@currency_defaults = { :unit => '$', :format => '%u%n', :precision => 2 }
@human_defaults = { :precision => 1 }
@human_storage_units_defaults = %w(Bytes KB MB GB TB)
@percentage_defaults = { :delimiter => '' }
@precision_defaults = { :delimiter => '' }
I18n.backend.store_translations 'en-US', :number => { :format => @number_defaults,
I18n.backend.store_translations 'en', :number => { :format => @number_defaults,
:currency => { :format => @currency_defaults }, :human => @human_defaults }
end
def test_number_to_currency_translates_currency_formats
I18n.expects(:translate).with(:'number.format', :locale => 'en-US', :raise => true).returns(@number_defaults)
I18n.expects(:translate).with(:'number.currency.format', :locale => 'en-US',
I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults)
I18n.expects(:translate).with(:'number.currency.format', :locale => 'en',
:raise => true).returns(@currency_defaults)
number_to_currency(1, :locale => 'en-US')
number_to_currency(1, :locale => 'en')
end
def test_number_with_precision_translates_number_formats
I18n.expects(:translate).with(:'number.format', :locale => 'en-US', :raise => true).returns(@number_defaults)
I18n.expects(:translate).with(:'number.precision.format', :locale => 'en-US',
I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults)
I18n.expects(:translate).with(:'number.precision.format', :locale => 'en',
:raise => true).returns(@precision_defaults)
number_with_precision(1, :locale => 'en-US')
number_with_precision(1, :locale => 'en')
end
def test_number_with_delimiter_translates_number_formats
I18n.expects(:translate).with(:'number.format', :locale => 'en-US', :raise => true).returns(@number_defaults)
number_with_delimiter(1, :locale => 'en-US')
I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults)
number_with_delimiter(1, :locale => 'en')
end
def test_number_to_percentage_translates_number_formats
I18n.expects(:translate).with(:'number.format', :locale => 'en-US', :raise => true).returns(@number_defaults)
I18n.expects(:translate).with(:'number.percentage.format', :locale => 'en-US',
I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults)
I18n.expects(:translate).with(:'number.percentage.format', :locale => 'en',
:raise => true).returns(@percentage_defaults)
number_to_percentage(1, :locale => 'en-US')
number_to_percentage(1, :locale => 'en')
end
def test_number_to_human_size_translates_human_formats
I18n.expects(:translate).with(:'number.format', :locale => 'en-US', :raise => true).returns(@number_defaults)
I18n.expects(:translate).with(:'number.human.format', :locale => 'en-US',
I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults)
I18n.expects(:translate).with(:'number.human.format', :locale => 'en',
:raise => true).returns(@human_defaults)
I18n.expects(:translate).with(:'number.human.storage_units', :locale => 'en',
:raise => true).returns(@human_storage_units_defaults)
# can't be called with 1 because this directly returns without calling I18n.translate
number_to_human_size(1025, :locale => 'en-US')
number_to_human_size(1025, :locale => 'en')
end
end
end

View File

@@ -8,7 +8,9 @@ class ViewRenderTest < Test::Unit::TestCase
end
def test_render_file
assert_equal "Hello world!", @view.render("test/hello_world.erb")
assert_deprecated do
assert_equal "Hello world!", @view.render("test/hello_world.erb")
end
end
def test_render_file_not_using_full_path
@@ -16,11 +18,15 @@ class ViewRenderTest < Test::Unit::TestCase
end
def test_render_file_without_specific_extension
assert_equal "Hello world!", @view.render("test/hello_world")
assert_deprecated do
assert_equal "Hello world!", @view.render("test/hello_world")
end
end
def test_render_file_at_top_level
assert_equal 'Elastica', @view.render('/shared')
assert_deprecated do
assert_equal 'Elastica', @view.render('/shared')
end
end
def test_render_file_with_full_path
@@ -28,21 +34,33 @@ class ViewRenderTest < Test::Unit::TestCase
assert_equal "Hello world!", @view.render(:file => template_path)
end
def test_render_file_not_using_template_handler_extension
assert_equal "var greeting = 'Hallo World!';", @view.render(:file => 'test/hello_world.js')
end
def test_render_file_with_instance_variables
assert_equal "The secret is in the sauce\n", @view.render("test/render_file_with_ivar.erb")
assert_deprecated do
assert_equal "The secret is in the sauce\n", @view.render("test/render_file_with_ivar.erb")
end
end
def test_render_file_with_locals
locals = { :secret => 'in the sauce' }
assert_equal "The secret is in the sauce\n", @view.render("test/render_file_with_locals.erb", locals)
assert_deprecated do
assert_equal "The secret is in the sauce\n", @view.render("test/render_file_with_locals.erb", locals)
end
end
def test_render_file_not_using_full_path_with_dot_in_path
assert_equal "The secret is in the sauce\n", @view.render("test/dot.directory/render_file_with_ivar")
assert_deprecated do
assert_equal "The secret is in the sauce\n", @view.render("test/dot.directory/render_file_with_ivar")
end
end
def test_render_has_access_current_template
assert_equal "test/template.erb", @view.render("test/template.erb")
assert_deprecated do
assert_equal "test/template.erb", @view.render("test/template.erb")
end
end
def test_render_update
@@ -167,6 +185,17 @@ class ViewRenderTest < Test::Unit::TestCase
assert_equal 'source: "Hello, <%= name %>!"', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo)
end
class LegacyHandler < ActionView::TemplateHandler
def render(template, local_assigns)
"source: #{template.source}; locals: #{local_assigns.inspect}"
end
end
def test_render_legacy_handler_with_custom_type
ActionView::Template.register_template_handler :foo, LegacyHandler
assert_equal 'source: Hello, <%= name %>!; locals: {:name=>"Josh"}', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo)
end
def test_render_with_layout
assert_equal %(<title></title>\nHello world!\n),
@view.render(:file => "test/hello_world.erb", :layout => "layouts/yield")

View File

@@ -10,12 +10,12 @@ class TranslationHelperTest < Test::Unit::TestCase
end
def test_delegates_to_i18n_setting_the_raise_option
I18n.expects(:translate).with(:foo, :locale => 'en-US', :raise => true)
translate :foo, :locale => 'en-US'
I18n.expects(:translate).with(:foo, :locale => 'en', :raise => true)
translate :foo, :locale => 'en'
end
def test_returns_missing_translation_message_wrapped_into_span
expected = '<span class="translation_missing">en-US, foo</span>'
expected = '<span class="translation_missing">en, foo</span>'
assert_equal expected, translate(:foo)
end

View File

@@ -1,12 +1,13 @@
*2.2.1 [RC2] (November 14th, 2008)*
*2.2.3 (September 4th, 2009)*
Version bump.
*2.2 (November 21st, 2008)*
* Ensure indices don't flip order in schema.rb #1266 [Jordi Bunster]
* Fixed that serialized strings should never be type-casted (i.e. turning "Yes" to a boolean) #857 [Andreas Korth]
*2.2.0 [RC1] (October 24th, 2008)*
* Skip collection ids reader optimization if using :finder_sql [Jeremy Kemper]
* Add Model#delete instance method, similar to Model.delete class method. #1086 [Hongli Lai]

View File

@@ -32,9 +32,13 @@ task :default => :test
desc 'Run mysql, sqlite, and postgresql tests'
task :test => %w(test_mysql test_sqlite3 test_postgresql)
for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase )
for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb )
Rake::TestTask.new("test_#{adapter}") { |t|
t.libs << "test" << "test/connections/native_#{adapter}"
if adapter =~ /jdbc/
t.libs << "test" << "test/connections/jdbc_#{adapter}"
else
t.libs << "test" << "test/connections/native_#{adapter}"
end
adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
t.test_files=Dir.glob( "test/cases/**/*_test{,_#{adapter_short}}.rb" ).sort
t.verbose = true
@@ -171,7 +175,7 @@ spec = Gem::Specification.new do |s|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
s.add_dependency('activesupport', '= 2.2.1' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.2.3' + PKG_BUILD)
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"

View File

@@ -78,4 +78,4 @@ require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/schema_dumper'
require 'active_record/i18n_interpolation_deprecation'
I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en-US.yml'
I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'

View File

@@ -2159,7 +2159,7 @@ module ActiveRecord
klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record?
[through_reflection, reflection].each do |ref|
join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions]))} " if ref && ref.options[:conditions]
join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))} " if ref && ref.options[:conditions]
end
join

View File

@@ -169,8 +169,8 @@ module ActiveRecord
end
# Forwards the call to the reflection class.
def sanitize_sql(sql)
@reflection.klass.send(:sanitize_sql, sql)
def sanitize_sql(sql, table_name = @reflection.klass.quoted_table_name)
@reflection.klass.send(:sanitize_sql, sql, table_name)
end
# Assigns the ID of the owner to the corresponding foreign key in +record+.

View File

@@ -9,6 +9,14 @@ module ActiveRecord
create_record(attributes) { |record| insert_record(record, true) }
end
def columns
@reflection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
end
def reset_column_information
@reflection.reset_column_information
end
protected
def construct_find_options!(options)
options[:joins] = @join_sql
@@ -32,8 +40,6 @@ module ActiveRecord
if @reflection.options[:insert_sql]
@owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
else
columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
attributes = columns.inject({}) do |attrs, column|
case column.name.to_s
when @reflection.primary_key_name.to_s
@@ -103,7 +109,7 @@ module ActiveRecord
# clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
# an id column. This will then overwrite the id column of the records coming back.
def finding_with_ambiguous_select?(select_clause)
!select_clause && @owner.connection.columns(@reflection.options[:join_table], "Join Table Columns").size != 2
!select_clause && columns.size != 2
end
private

View File

@@ -8,11 +8,10 @@ module ActiveRecord
current_object = @owner.send(@reflection.through_reflection.name)
if current_object
klass.destroy(current_object)
@owner.clear_association_cache
current_object.update_attributes(construct_join_attributes(new_value))
else
@owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value)))
end
@owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value)))
end
private

View File

@@ -1612,9 +1612,17 @@ module ActiveRecord #:nodoc:
end
end
def default_select(qualified)
if qualified
quoted_table_name + '.*'
else
'*'
end
end
def construct_finder_sql(options)
scope = scope(:find)
sql = "SELECT #{options[:select] || (scope && scope[:select]) || ((options[:joins] || (scope && scope[:joins])) && quoted_table_name + '.*') || '*'} "
sql = "SELECT #{options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))} "
sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
add_joins!(sql, options[:joins], scope)
@@ -1670,7 +1678,9 @@ module ActiveRecord #:nodoc:
scoped_order = scope[:order] if scope
if order
sql << " ORDER BY #{order}"
sql << ", #{scoped_order}" if scoped_order
if scoped_order && scoped_order != order
sql << ", #{scoped_order}"
end
else
sql << " ORDER BY #{scoped_order}" if scoped_order
end
@@ -2064,12 +2074,12 @@ module ActiveRecord #:nodoc:
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
# { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
def sanitize_sql_for_conditions(condition)
def sanitize_sql_for_conditions(condition, table_name = quoted_table_name)
return nil if condition.blank?
case condition
when Array; sanitize_sql_array(condition)
when Hash; sanitize_sql_hash_for_conditions(condition)
when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
else condition
end
end

View File

@@ -31,7 +31,7 @@ module ActiveRecord
include QueryCache
include ActiveSupport::Callbacks
define_callbacks :checkout, :checkin
checkout :reset!
@@row_even = true
def initialize(connection, logger = nil) #:nodoc:

View File

@@ -150,6 +150,7 @@ module ActiveRecord
# * <tt>:password</tt> - Defaults to nothing.
# * <tt>:database</tt> - The name of the database. No default, must be provided.
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
# * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
@@ -534,8 +535,6 @@ module ActiveRecord
private
def connect
@connection.reconnect = true if @connection.respond_to?(:reconnect=)
encoding = @config[:encoding]
if encoding
@connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
@@ -546,6 +545,10 @@ module ActiveRecord
end
@connection.real_connect(*@connection_options)
# reconnect must be set after real_connect is called, because real_connect sets it to false internally
@connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
configure_connection
end

View File

@@ -151,12 +151,12 @@ module ActiveRecord
def field_changed?(attr, old, value)
if column = column_for_attribute(attr)
if column.type == :integer && column.null && (old.nil? || old == 0)
# For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
if column.number? && column.null && (old.nil? || old == 0) && value.blank?
# For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
# Hence we don't record it as a change if the value changes from nil to ''.
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
# be typecast back to 0 (''.to_i => 0)
value = nil if value.blank?
value = nil
else
value = column.type_cast(value)
end

View File

@@ -1,4 +1,4 @@
en-US:
en:
activerecord:
errors:
# The values :model, :attribute and :value are always available for interpolation

View File

@@ -100,7 +100,7 @@ module ActiveRecord
end
class Scope
attr_reader :proxy_scope, :proxy_options
attr_reader :proxy_scope, :proxy_options, :current_scoped_methods_when_defined
NON_DELEGATE_METHODS = %w(nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? respond_to?).to_set
[].methods.each do |m|
unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s)
@@ -113,6 +113,9 @@ module ActiveRecord
def initialize(proxy_scope, options, &block)
[options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
extend Module.new(&block) if block_given?
unless Scope === proxy_scope
@current_scoped_methods_when_defined = proxy_scope.send(:current_scoped_methods)
end
@proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
end
@@ -168,7 +171,13 @@ module ActiveRecord
else
with_scope :find => proxy_options, :create => proxy_options[:conditions].is_a?(Hash) ? proxy_options[:conditions] : {} do
method = :new if method == :build
proxy_scope.send(method, *args, &block)
if current_scoped_methods_when_defined
with_scope current_scoped_methods_when_defined do
proxy_scope.send(method, *args, &block)
end
else
proxy_scope.send(method, *args, &block)
end
end
end
end

View File

@@ -198,6 +198,14 @@ module ActiveRecord
end
end
def columns(tbl_name, log_msg)
@columns ||= klass.connection.columns(tbl_name, log_msg)
end
def reset_column_information
@columns = nil
end
def check_validity!
end

View File

@@ -11,11 +11,9 @@ module ActiveRecord
end
def assert_date_from_db(expected, actual, message = nil)
# SQL Server doesn't have a separate column type just for dates,
# SybaseAdapter doesn't have a separate column type just for dates,
# so the time is in the string and incorrectly formatted
if current_adapter?(:SQLServerAdapter)
assert_equal expected.strftime("%Y/%m/%d 00:00:00"), actual.strftime("%Y/%m/%d 00:00:00")
elsif current_adapter?(:SybaseAdapter)
if current_adapter?(:SybaseAdapter)
assert_equal expected.to_s, actual.to_date.to_s, message
else
assert_equal expected.to_s, actual.to_s, message

View File

@@ -147,7 +147,7 @@ module ActiveRecord
end
def save_with_transactions! #:nodoc:
rollback_active_record_state! { transaction { save_without_transactions! } }
rollback_active_record_state! { self.class.transaction { save_without_transactions! } }
end
# Reset id and @new_record if the transaction rolls back.
@@ -175,7 +175,7 @@ module ActiveRecord
# instance.
def with_transaction_returning_status(method, *args)
status = nil
transaction do
self.class.transaction do
status = send(method, *args)
raise ActiveRecord::Rollback unless status
end

View File

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

View File

@@ -1,95 +0,0 @@
require "cases/helper"
require 'models/default'
require 'models/post'
require 'models/task'
class SqlServerAdapterTest < ActiveRecord::TestCase
class TableWithRealColumn < ActiveRecord::Base; end
fixtures :posts, :tasks
def setup
@connection = ActiveRecord::Base.connection
end
def teardown
@connection.execute("SET LANGUAGE us_english") rescue nil
end
def test_real_column_has_float_type
assert_equal :float, TableWithRealColumn.columns_hash["real_number"].type
end
# SQL Server 2000 has a bug where some unambiguous date formats are not
# correctly identified if the session language is set to german
def test_date_insertion_when_language_is_german
@connection.execute("SET LANGUAGE deutsch")
assert_nothing_raised do
Task.create(:starting => Time.utc(2000, 1, 31, 5, 42, 0), :ending => Date.new(2006, 12, 31))
end
end
def test_indexes_with_descending_order
# Make sure we have an index with descending order
@connection.execute "CREATE INDEX idx_credit_limit ON accounts (credit_limit DESC)" rescue nil
assert_equal ["credit_limit"], @connection.indexes('accounts').first.columns
ensure
@connection.execute "DROP INDEX accounts.idx_credit_limit"
end
def test_execute_without_block_closes_statement
assert_all_statements_used_are_closed do
@connection.execute("SELECT 1")
end
end
def test_execute_with_block_closes_statement
assert_all_statements_used_are_closed do
@connection.execute("SELECT 1") do |sth|
assert !sth.finished?, "Statement should still be alive within block"
end
end
end
def test_insert_with_identity_closes_statement
assert_all_statements_used_are_closed do
@connection.insert("INSERT INTO accounts ([id], [firm_id],[credit_limit]) values (999, 1, 50)")
end
end
def test_insert_without_identity_closes_statement
assert_all_statements_used_are_closed do
@connection.insert("INSERT INTO accounts ([firm_id],[credit_limit]) values (1, 50)")
end
end
def test_active_closes_statement
assert_all_statements_used_are_closed do
@connection.active?
end
end
def assert_all_statements_used_are_closed(&block)
existing_handles = []
ObjectSpace.each_object(DBI::StatementHandle) {|handle| existing_handles << handle}
GC.disable
yield
used_handles = []
ObjectSpace.each_object(DBI::StatementHandle) {|handle| used_handles << handle unless existing_handles.include? handle}
assert_block "No statements were used within given block" do
used_handles.size > 0
end
ObjectSpace.each_object(DBI::StatementHandle) do |handle|
assert_block "Statement should have been closed within given block" do
handle.finished?
end
end
ensure
GC.enable
end
end

View File

@@ -667,7 +667,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_count_with_include
if current_adapter?(:SQLServerAdapter, :SybaseAdapter)
if current_adapter?(:SybaseAdapter)
assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15")
elsif current_adapter?(:OpenBaseAdapter)
assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(FETCHBLOB(comments.body)) > 15")

View File

@@ -770,4 +770,15 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
end
end
def test_caching_of_columns
david = Developer.find(1)
# clear cache possibly created by other tests
david.projects.reset_column_information
assert_queries(0) { david.projects.columns; david.projects.columns }
# and again to verify that reset_column_information clears the cache correctly
david.projects.reset_column_information
assert_queries(0) { david.projects.columns; david.projects.columns }
end
end

View File

@@ -3,9 +3,11 @@ require 'models/club'
require 'models/member'
require 'models/membership'
require 'models/sponsor'
require 'models/organization'
require 'models/member_detail'
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
fixtures :members, :clubs, :memberships, :sponsors
fixtures :members, :clubs, :memberships, :sponsors, :organizations
def setup
@member = members(:groucho)
@@ -120,4 +122,40 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
clubs(:moustache_club).send(:private_method)
@member.club.send(:private_method)
end
def test_assigning_to_has_one_through_preserves_decorated_join_record
@organization = organizations(:nsa)
assert_difference 'MemberDetail.count', 1 do
@member_detail = MemberDetail.new(:extra_data => 'Extra')
@member.member_detail = @member_detail
@member.organization = @organization
end
assert_equal @organization, @member.organization
assert @organization.members.include?(@member)
assert_equal 'Extra', @member.member_detail.extra_data
end
def test_reassigning_has_one_through
@organization = organizations(:nsa)
@new_organization = organizations(:discordians)
assert_difference 'MemberDetail.count', 1 do
@member_detail = MemberDetail.new(:extra_data => 'Extra')
@member.member_detail = @member_detail
@member.organization = @organization
end
assert_equal @organization, @member.organization
assert_equal 'Extra', @member.member_detail.extra_data
assert @organization.members.include?(@member)
assert !@new_organization.members.include?(@member)
assert_no_difference 'MemberDetail.count' do
@member.organization = @new_organization
end
assert_equal @new_organization, @member.organization
assert_equal 'Extra', @member.member_detail.extra_data
assert !@organization.members.include?(@member)
assert @new_organization.members.include?(@member)
end
end

View File

@@ -29,6 +29,11 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
assert_match /INNER JOIN .?categories.? ON.*AND.*.?General.?.*TERMINATING_MARKER/, sql
end
def test_construct_finder_sql_applies_aliases_tables_on_association_conditions
result = Author.find(:all, :joins => [:thinking_posts, :welcome_posts])
assert_equal authors(:david), result.first
end
def test_construct_finder_sql_unpacks_nested_joins
sql = Author.send(:construct_finder_sql, :joins => {:posts => [[:comments]]})
assert_no_match /inner join.*inner join.*inner join/i, sql, "only two join clauses should be present"

View File

@@ -428,9 +428,6 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_preserving_date_objects
# SQL Server doesn't have a separate column type just for dates, so all are returned as time
return true if current_adapter?(:SQLServerAdapter)
if current_adapter?(:SybaseAdapter, :OracleAdapter)
# Sybase ctlib does not (yet?) support the date type; use datetime instead.
# Oracle treats all dates/times as Time.
@@ -777,8 +774,8 @@ class BasicsTest < ActiveRecord::TestCase
end
end
# Oracle, SQLServer, and Sybase do not have a TIME datatype.
unless current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter)
# Oracle, and Sybase do not have a TIME datatype.
unless current_adapter?(:OracleAdapter, :SybaseAdapter)
def test_utc_as_time_zone
Topic.default_timezone = :utc
attributes = { "bonus_time" => "5:42:00AM" }
@@ -1157,8 +1154,8 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_attributes_on_dummy_time
# Oracle, SQL Server, and Sybase do not have a TIME datatype.
return true if current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter)
# Oracle, and Sybase do not have a TIME datatype.
return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
attributes = {
"bonus_time" => "5:42:00AM"
@@ -1874,7 +1871,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "integer", xml.elements["//parent-id"].attributes['type']
assert_equal "true", xml.elements["//parent-id"].attributes['nil']
if current_adapter?(:SybaseAdapter, :SQLServerAdapter, :OracleAdapter)
if current_adapter?(:SybaseAdapter, :OracleAdapter)
assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text
assert_equal "datetime" , xml.elements["//last-read"].attributes['type']
else

View File

@@ -1,13 +1,9 @@
require "cases/helper"
# Without using prepared statements, it makes no sense to test
# BLOB data with SQL Server, because the length of a statement is
# limited to 8KB.
#
# Without using prepared statements, it makes no sense to test
# BLOB data with DB2 or Firebird, because the length of a statement
# is limited to 32KB.
unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :DB2Adapter, :FirebirdAdapter)
unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter)
require 'models/binary'
class BinaryTest < ActiveRecord::TestCase

View File

@@ -2,9 +2,24 @@ require "cases/helper"
class MysqlConnectionTest < ActiveRecord::TestCase
def setup
super
@connection = ActiveRecord::Base.connection
end
def test_mysql_reconnect_attribute_after_connection_with_reconnect_true
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => true}))
assert ActiveRecord::Base.connection.raw_connection.reconnect
end
end
def test_mysql_reconnect_attribute_after_connection_with_reconnect_false
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => false}))
assert !ActiveRecord::Base.connection.raw_connection.reconnect
end
end
def test_no_automatic_reconnection_after_timeout
assert @connection.active?
@connection.update('set @@wait_timeout=1')
@@ -27,4 +42,15 @@ class MysqlConnectionTest < ActiveRecord::TestCase
@connection.verify!
assert @connection.active?
end
private
def run_without_connection
original_connection = ActiveRecord::Base.remove_connection
begin
yield original_connection
ensure
ActiveRecord::Base.establish_connection(original_connection)
end
end
end

View File

@@ -78,7 +78,7 @@ class DefaultTest < ActiveRecord::TestCase
end
end
if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter, :FirebirdAdapter, :OpenBaseAdapter, :OracleAdapter)
if current_adapter?(:PostgreSQLAdapter, :FirebirdAdapter, :OpenBaseAdapter, :OracleAdapter)
def test_default_integers
default = Default.new
assert_instance_of Fixnum, default.positive_integer

View File

@@ -58,7 +58,7 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal parrot.name_change, parrot.title_change
end
def test_nullable_integer_not_marked_as_changed_if_new_value_is_blank
def test_nullable_number_not_marked_as_changed_if_new_value_is_blank
pirate = Pirate.new
["", nil].each do |value|
@@ -68,6 +68,18 @@ class DirtyTest < ActiveRecord::TestCase
end
end
def test_nullable_integer_zero_to_string_zero_not_marked_as_changed
pirate = Pirate.new
pirate.parrot_id = 0
pirate.catchphrase = 'arrr'
assert pirate.save!
assert !pirate.changed?
pirate.parrot_id = '0'
assert !pirate.changed?
end
def test_zero_to_blank_marked_as_changed
pirate = Pirate.new
pirate.catchphrase = "Yarrrr, me hearties"

View File

@@ -31,7 +31,7 @@ rescue LoadError
end
ActiveRecord::Base.connection.class.class_eval do
IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/]
IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /SHOW FIELDS/]
def execute_with_query_record(sql, name = nil, &block)
$queries_executed ||= []
@@ -59,4 +59,4 @@ unless ENV['FIXTURE_DEBUG']
end
end
end
end
end

View File

@@ -9,32 +9,32 @@ class ActiveRecordI18nTests < Test::Unit::TestCase
end
def test_translated_model_attributes
I18n.backend.store_translations 'en-US', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } }
I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } }
assert_equal 'topic title attribute', Topic.human_attribute_name('title')
end
def test_translated_model_attributes_with_sti
I18n.backend.store_translations 'en-US', :activerecord => {:attributes => {:reply => {:title => 'reply title attribute'} } }
I18n.backend.store_translations 'en', :activerecord => {:attributes => {:reply => {:title => 'reply title attribute'} } }
assert_equal 'reply title attribute', Reply.human_attribute_name('title')
end
def test_translated_model_attributes_with_sti_fallback
I18n.backend.store_translations 'en-US', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } }
I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } }
assert_equal 'topic title attribute', Reply.human_attribute_name('title')
end
def test_translated_model_names
I18n.backend.store_translations 'en-US', :activerecord => {:models => {:topic => 'topic model'} }
I18n.backend.store_translations 'en', :activerecord => {:models => {:topic => 'topic model'} }
assert_equal 'topic model', Topic.human_name
end
def test_translated_model_names_with_sti
I18n.backend.store_translations 'en-US', :activerecord => {:models => {:reply => 'reply model'} }
I18n.backend.store_translations 'en', :activerecord => {:models => {:reply => 'reply model'} }
assert_equal 'reply model', Reply.human_name
end
def test_translated_model_names_with_sti_fallback
I18n.backend.store_translations 'en-US', :activerecord => {:models => {:topic => 'topic model'} }
I18n.backend.store_translations 'en', :activerecord => {:models => {:topic => 'topic model'} }
assert_equal 'topic model', Reply.human_name
end
end

View File

@@ -59,13 +59,13 @@ class InheritanceTest < ActiveRecord::TestCase
def test_a_bad_type_column
#SQLServer need to turn Identity Insert On before manually inserting into the Identity column
if current_adapter?(:SQLServerAdapter, :SybaseAdapter)
if current_adapter?(:SybaseAdapter)
Company.connection.execute "SET IDENTITY_INSERT companies ON"
end
Company.connection.insert "INSERT INTO companies (id, #{QUOTED_TYPE}, name) VALUES(100, 'bad_class!', 'Not happening')"
#We then need to turn it back Off before continuing.
if current_adapter?(:SQLServerAdapter, :SybaseAdapter)
if current_adapter?(:SybaseAdapter)
Company.connection.execute "SET IDENTITY_INSERT companies OFF"
end
assert_raises(ActiveRecord::SubclassNotFound) { Company.find(100) }

View File

@@ -200,9 +200,9 @@ end
# blocks, so separate script called by Kernel#system is needed.
# (See exec vs. async_exec in the PostgreSQL adapter.)
# TODO: The SQL Server, Sybase, and OpenBase adapters currently have no support for pessimistic locking
# TODO: The Sybase, and OpenBase adapters currently have no support for pessimistic locking
unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :OpenBaseAdapter)
unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
class PessimisticLockingTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
fixtures :people, :readers

View File

@@ -341,8 +341,10 @@ class NestedScopingTest < ActiveRecord::TestCase
def test_merged_scoped_find
poor_jamis = developers(:poor_jamis)
Developer.with_scope(:find => { :conditions => "salary < 100000" }) do
Developer.with_scope(:find => { :offset => 1 }) do
assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc'))
Developer.with_scope(:find => { :offset => 1, :order => 'id asc' }) do
assert_sql /ORDER BY id asc / do
assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc'))
end
end
end
end

View File

@@ -271,9 +271,9 @@ if ActiveRecord::Base.connection.supports_migrations?
Person.connection.drop_table table_name rescue nil
end
# SQL Server, Sybase, and SQLite3 will not allow you to add a NOT NULL
# Sybase, and SQLite3 will not allow you to add a NOT NULL
# column to a table without a default value.
unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :SQLiteAdapter)
unless current_adapter?(:SybaseAdapter, :SQLiteAdapter)
def test_add_column_not_null_without_default
Person.connection.create_table :testings do |t|
t.column :foo, :string
@@ -410,7 +410,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal Fixnum, bob.age.class
assert_equal Time, bob.birthday.class
if current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter)
if current_adapter?(:OracleAdapter, :SybaseAdapter)
# Sybase, and Oracle don't differentiate between date/time
assert_equal Time, bob.favorite_day.class
else
@@ -851,10 +851,6 @@ if ActiveRecord::Base.connection.supports_migrations?
# - SQLite3 stores a float, in violation of SQL
assert_kind_of BigDecimal, b.value_of_e
assert_equal BigDecimal("2.71828182845905"), b.value_of_e
elsif current_adapter?(:SQLServer)
# - SQL Server rounds instead of truncating
assert_kind_of Fixnum, b.value_of_e
assert_equal 3, b.value_of_e
else
# - SQL standard is an integer
assert_kind_of Fixnum, b.value_of_e

View File

@@ -142,6 +142,15 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal authors(:david).comments & Comment.containing_the_letter_e, authors(:david).comments.containing_the_letter_e
end
def test_named_scopes_honor_current_scopes_from_when_defined
assert !Post.ranked_by_comments.limit(5).empty?
assert !authors(:david).posts.ranked_by_comments.limit(5).empty?
assert_not_equal Post.ranked_by_comments.limit(5), authors(:david).posts.ranked_by_comments.limit(5)
assert_not_equal Post.top(5), authors(:david).posts.top(5)
assert_equal authors(:david).posts.ranked_by_comments.limit(5), authors(:david).posts.top(5)
assert_equal Post.ranked_by_comments.limit(5), Post.top(5)
end
def test_active_records_have_scope_named__all__
assert !Topic.find(:all).empty?

View File

@@ -1,23 +0,0 @@
require "cases/helper"
require 'active_record/schema'
if ActiveRecord::Base.connection.supports_migrations?
class Order < ActiveRecord::Base
self.table_name = '[order]'
end
class TableNameTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
# Ensures Model.columns works when using SQLServer escape characters.
# Enables legacy schemas using SQL reserved words as table names.
# Should work with table names with spaces as well ('table name').
def test_escaped_table_name
assert_nothing_raised do
ActiveRecord::Base.connection.select_all 'SELECT * FROM [order]'
end
assert_equal '[order]', Order.table_name
assert_equal 5, Order.columns.length
end
end
end

View File

@@ -9,7 +9,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
@old_load_path, @old_backend = I18n.load_path, I18n.backend
I18n.load_path.clear
I18n.backend = I18n::Backend::Simple.new
I18n.backend.store_translations('en-US', :activerecord => {:errors => {:messages => {:custom => nil}}})
I18n.backend.store_translations('en', :activerecord => {:errors => {:messages => {:custom => nil}}})
end
def teardown
@@ -165,7 +165,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
@topic.errors.instance_variable_set :@errors, { 'title' => ['empty'] }
I18n.expects(:translate).with(:"topic.title", :default => ['Title'], :scope => [:activerecord, :attributes], :count => 1).returns('Title')
@topic.errors.full_messages :locale => 'en-US'
@topic.errors.full_messages :locale => 'en'
end
end
@@ -429,8 +429,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_confirmation_of w/o mocha
def test_validates_confirmation_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:confirmation => 'custom message'}}}}}}
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:confirmation => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}}
Topic.validates_confirmation_of :title
@topic.title_confirmation = 'foo'
@@ -439,7 +439,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_confirmation_of_finds_global_default_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}}
Topic.validates_confirmation_of :title
@topic.title_confirmation = 'foo'
@@ -450,8 +450,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_acceptance_of w/o mocha
def test_validates_acceptance_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:accepted => 'custom message'}}}}}}
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:accepted => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}}
Topic.validates_acceptance_of :title, :allow_nil => false
@topic.valid?
@@ -459,7 +459,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_acceptance_of_finds_global_default_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}}
Topic.validates_acceptance_of :title, :allow_nil => false
@topic.valid?
@@ -469,8 +469,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_presence_of w/o mocha
def test_validates_presence_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:blank => 'custom message'}}}}}}
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:blank => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:blank => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:blank => 'global message'}}}
Topic.validates_presence_of :title
@topic.valid?
@@ -478,7 +478,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_presence_of_finds_global_default_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:blank => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:blank => 'global message'}}}
Topic.validates_presence_of :title
@topic.valid?
@@ -488,8 +488,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_length_of :within w/o mocha
def test_validates_length_of_within_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:too_short => 'custom message'}}}}}}
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:too_short => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}}
Topic.validates_length_of :title, :within => 3..5
@topic.valid?
@@ -497,7 +497,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_length_of_within_finds_global_default_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}}
Topic.validates_length_of :title, :within => 3..5
@topic.valid?
@@ -506,17 +506,17 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_length_of :is w/o mocha
def test_validates_length_of_within_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
def test_validates_length_of_is_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
end
def test_validates_length_of_within_finds_global_default_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
def test_validates_length_of_is_finds_global_default_translation
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
@@ -525,17 +525,17 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_uniqueness_of w/o mocha
def test_validates_length_of_within_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
def test_validates_length_of_is_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
end
def test_validates_length_of_within_finds_global_default_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
def test_validates_length_of_is_finds_global_default_translation
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
@@ -546,8 +546,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_format_of w/o mocha
def test_validates_format_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:invalid => 'custom message'}}}}}}
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:invalid => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
@topic.valid?
@@ -555,7 +555,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_format_of_finds_global_default_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
@topic.valid?
@@ -565,8 +565,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_inclusion_of w/o mocha
def test_validates_inclusion_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:inclusion => 'custom message'}}}}}}
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:inclusion => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}}
Topic.validates_inclusion_of :title, :in => %w(a b c)
@topic.valid?
@@ -574,7 +574,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_inclusion_of_finds_global_default_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}}
Topic.validates_inclusion_of :title, :in => %w(a b c)
@topic.valid?
@@ -584,8 +584,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_exclusion_of w/o mocha
def test_validates_exclusion_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:exclusion => 'custom message'}}}}}}
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:exclusion => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}}
Topic.validates_exclusion_of :title, :in => %w(a b c)
@topic.title = 'a'
@@ -594,7 +594,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_exclusion_of_finds_global_default_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}}
Topic.validates_exclusion_of :title, :in => %w(a b c)
@topic.title = 'a'
@@ -605,8 +605,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_numericality_of without :only_integer w/o mocha
def test_validates_numericality_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}}
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}}
Topic.validates_numericality_of :title
@topic.title = 'a'
@@ -615,7 +615,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_finds_global_default_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}}
Topic.validates_numericality_of :title, :only_integer => true
@topic.title = 'a'
@@ -626,8 +626,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_numericality_of with :only_integer w/o mocha
def test_validates_numericality_of_only_integer_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}}
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}}
Topic.validates_numericality_of :title, :only_integer => true
@topic.title = 'a'
@@ -636,7 +636,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_only_integer_finds_global_default_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}}
Topic.validates_numericality_of :title, :only_integer => true
@topic.title = 'a'
@@ -647,8 +647,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_numericality_of :odd w/o mocha
def test_validates_numericality_of_odd_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:odd => 'custom message'}}}}}}
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:odd => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:odd => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:odd => 'global message'}}}
Topic.validates_numericality_of :title, :only_integer => true, :odd => true
@topic.title = 0
@@ -657,7 +657,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_odd_finds_global_default_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:odd => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:odd => 'global message'}}}
Topic.validates_numericality_of :title, :only_integer => true, :odd => true
@topic.title = 0
@@ -668,8 +668,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_numericality_of :less_than w/o mocha
def test_validates_numericality_of_less_than_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:less_than => 'custom message'}}}}}}
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:less_than => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}}
Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
@topic.title = 1
@@ -678,7 +678,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_less_than_finds_global_default_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}}
Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
@topic.title = 1
@@ -690,8 +690,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_associated w/o mocha
def test_validates_associated_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}}
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
Topic.validates_associated :replies
replied_topic.valid?
@@ -699,7 +699,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_associated_finds_global_default_translation
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
Topic.validates_associated :replies
replied_topic.valid?
@@ -707,7 +707,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validations_with_message_symbol_must_translate
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:messages => {:custom_error => "I am a custom error"}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:custom_error => "I am a custom error"}}}
Topic.validates_presence_of :title, :message => :custom_error
@topic.title = nil
@topic.valid?
@@ -715,7 +715,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_with_message_symbol_must_translate_per_attribute
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}}
Topic.validates_presence_of :title, :message => :custom_error
@topic.title = nil
@topic.valid?
@@ -723,7 +723,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_with_message_symbol_must_translate_per_model
I18n.backend.store_translations 'en-US', :activerecord => {:errors => {:models => {:topic => {:custom_error => "I am a custom error"}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:custom_error => "I am a custom error"}}}}
Topic.validates_presence_of :title, :message => :custom_error
@topic.title = nil
@topic.valid?
@@ -743,7 +743,7 @@ class ActiveRecordValidationsGenerateMessageI18nTests < Test::Unit::TestCase
def setup
reset_callbacks Topic
@topic = Topic.new
I18n.backend.store_translations :'en-US', {
I18n.backend.store_translations :'en', {
:activerecord => {
:errors => {
:messages => {

View File

@@ -0,0 +1,18 @@
print "Using Derby via JRuby, activerecord-jdbc-adapter and activerecord-jdbcderby-adapter\n"
require_dependency 'models/course'
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
ActiveRecord::Base.configurations = {
'arunit' => {
:adapter => 'jdbcderby',
:database => 'activerecord_unittest'
},
'arunit2' => {
:adapter => 'jdbcderby',
:database => 'activerecord_unittest2'
}
}
ActiveRecord::Base.establish_connection 'arunit'
Course.establish_connection 'arunit2'

View File

@@ -0,0 +1,18 @@
print "Using H2 via JRuby, activerecord-jdbc-adapter and activerecord-jdbch2-adapter\n"
require_dependency 'models/course'
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
ActiveRecord::Base.configurations = {
'arunit' => {
:adapter => 'jdbch2',
:database => 'activerecord_unittest'
},
'arunit2' => {
:adapter => 'jdbch2',
:database => 'activerecord_unittest2'
}
}
ActiveRecord::Base.establish_connection 'arunit'
Course.establish_connection 'arunit2'

View File

@@ -0,0 +1,18 @@
print "Using HSQLDB via JRuby, activerecord-jdbc-adapter and activerecord-jdbchsqldb-adapter\n"
require_dependency 'models/course'
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
ActiveRecord::Base.configurations = {
'arunit' => {
:adapter => 'jdbchsqldb',
:database => 'activerecord_unittest'
},
'arunit2' => {
:adapter => 'jdbchsqldb',
:database => 'activerecord_unittest2'
}
}
ActiveRecord::Base.establish_connection 'arunit'
Course.establish_connection 'arunit2'

View File

@@ -0,0 +1,26 @@
print "Using MySQL via JRuby, activerecord-jdbc-adapter and activerecord-jdbcmysql-adapter\n"
require_dependency 'models/course'
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
# GRANT ALL PRIVILEGES ON activerecord_unittest.* to 'rails'@'localhost';
# GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost';
ActiveRecord::Base.configurations = {
'arunit' => {
:adapter => 'jdbcmysql',
:username => 'rails',
:encoding => 'utf8',
:database => 'activerecord_unittest',
},
'arunit2' => {
:adapter => 'jdbcmysql',
:username => 'rails',
:database => 'activerecord_unittest2'
}
}
ActiveRecord::Base.establish_connection 'arunit'
Course.establish_connection 'arunit2'

View File

@@ -0,0 +1,26 @@
print "Using Postgrsql via JRuby, activerecord-jdbc-adapter and activerecord-postgresql-adapter\n"
require_dependency 'models/course'
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
# createuser rails --createdb --no-superuser --no-createrole
# createdb -O rails activerecord_unittest
# createdb -O rails activerecord_unittest2
ActiveRecord::Base.configurations = {
'arunit' => {
:adapter => 'jdbcpostgresql',
:username => ENV['USER'] || 'rails',
:database => 'activerecord_unittest'
},
'arunit2' => {
:adapter => 'jdbcpostgresql',
:username => ENV['USER'] || 'rails',
:database => 'activerecord_unittest2'
}
}
ActiveRecord::Base.establish_connection 'arunit'
Course.establish_connection 'arunit2'

View File

@@ -0,0 +1,25 @@
print "Using SQLite3 via JRuby, activerecord-jdbc-adapter and activerecord-jdbcsqlite3-adapter\n"
require_dependency 'models/course'
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
class SqliteError < StandardError
end
BASE_DIR = FIXTURES_ROOT
sqlite_test_db = "#{BASE_DIR}/fixture_database.sqlite3"
sqlite_test_db2 = "#{BASE_DIR}/fixture_database_2.sqlite3"
def make_connection(clazz, db_file)
ActiveRecord::Base.configurations = { clazz.name => { :adapter => 'jdbcsqlite3', :database => db_file, :timeout => 5000 } }
unless File.exist?(db_file)
puts "SQLite3 database not found at #{db_file}. Rebuilding it."
sqlite_command = %Q{sqlite3 "#{db_file}" "create table a (a integer); drop table a;"}
puts "Executing '#{sqlite_command}'"
raise SqliteError.new("Seems that there is no sqlite3 executable available") unless system(sqlite_command)
end
clazz.establish_connection(clazz.name)
end
make_connection(ActiveRecord::Base, sqlite_test_db)
make_connection(Course, sqlite_test_db2)

View File

@@ -0,0 +1,5 @@
nsa:
name: No Such Agency
discordians:
name: Discordians

View File

@@ -24,6 +24,8 @@ class Author < ActiveRecord::Base
has_many :comments_with_order_and_conditions, :through => :posts, :source => :comments, :order => 'comments.body', :conditions => "comments.body like 'Thank%'"
has_many :comments_with_include, :through => :posts, :source => :comments, :include => :post
has_many :thinking_posts, :class_name => 'Post', :conditions => { :title => 'So I was thinking' }
has_many :welcome_posts, :class_name => 'Post', :conditions => { :title => 'Welcome to the weblog' }
has_many :comments_desc, :through => :posts, :source => :comments, :order => 'comments.id DESC'
has_many :limited_comments, :through => :posts, :source => :comments, :limit => 1

View File

@@ -6,4 +6,6 @@ class Member < ActiveRecord::Base
has_one :favourite_club, :through => :memberships, :conditions => ["memberships.favourite = ?", true], :source => :club
has_one :sponsor, :as => :sponsorable
has_one :sponsor_club, :through => :sponsor
has_one :member_detail
has_one :organization, :through => :member_detail
end

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