Compare commits

...

246 Commits

Author SHA1 Message Date
David Heinemeier Hansson
d4eb3c0b7d Forgot to resolve one file 2008-09-04 16:31:40 +02:00
David Heinemeier Hansson
02d610b1b7 Prepare for release of 2.1.1 2008-09-04 16:23:11 +02:00
David Heinemeier Hansson
7398874696 Push to temporary gem server until Wrath is back in shape 2008-09-04 16:13:21 +02:00
Michael Koziarski
cf51b173b6 Handle the case where there is no ivar set.
This happens on jruby due to a bug, but also on historically marshalled data.
2008-09-04 15:08:46 +02:00
Hongli Lai (Phusion
011cbbc1da Plugin locator: sort directory listing because we can't assume that the OS will do it for us. This fixes some unit test failures. 2008-09-04 11:21:57 +02:00
Joshua Peek
ae378b925f Stub out timestamped_migrations in generator tests 2008-09-04 10:54:03 +02:00
rick
d278d6d1bf use mocha for TimeZone mocking in Form Options helper tests
Signed-off-by: Tarmo Tänav <tarmo@itech.ee>
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-09-04 10:40:26 +02:00
Tarmo Tänav
40990e2467 Don't run 32bit dependant assertions in 64bit environments
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-09-04 10:11:43 +02:00
Nigel Ramsay
81d1c29bf6 Inline help text was incorrectly telling user to uncomment line to use default local time. User should comment the line to use default local time.
Signed-off-by: Michael Koziarski <michael@koziarski.com>
[#960 state:committed]
2008-09-03 09:02:26 +02:00
Michael Koziarski
5a56dbb7e1 Merge rexml-expansion-fix gem into activesupport.
Addresses the security issue documented at:
* http://www.ruby-lang.org/en/news/2008/08/23/dos-vulnerability-in-rexml/
2008-09-02 16:24:15 +02:00
miloops
c364c1f27d Allow prototype functions to receive position parameter as a symbol.
[#887 state:resolved]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-30 16:36:45 -07:00
Nathaniel Bibler
c87fea3094 Added optional rake doc:app TITLE environment parameter
[#939 state:resolved]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-29 21:04:32 -07:00
Rasik Pandey
caabe228bc Format related patches to support serializing data out in the correct format with correct http request headers per http method type [#450 state:resolved]
Signed-off-by: Tarmo Tänav <tarmo@itech.ee>
Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-29 18:46:39 -07:00
Jeremy Kemper
e7df4ce001 Fix bad merge from e21ed3e 2008-08-29 18:00:44 -07:00
Tarmo Tänav
184ae2ccb4 Make case insensitive validates_uniqueness_of use unicode aware downcase method. [#932 state:resolved]
Signed-off-by: Michael Koziarski <michael@koziarski.com>

Conflicts:

	activerecord/lib/active_record/validations.rb

Signed-off-by: Tarmo Tänav <tarmo@itech.ee>
2008-08-29 21:52:10 +03:00
Michael Koziarski
08b0c8da7f Fix parentheses warnings 2008-08-29 15:23:28 +02:00
Michael Koziarski
c37900138c Fix NamedScope regex so methods containing "an" get delegated to proxy_found
Signed-off-by: Michael Koziarski <michael@koziarski.com>
[#901 state:committed]

Conflicts:

	activerecord/lib/active_record/named_scope.rb
2008-08-29 15:22:06 +02:00
Jeremy Kemper
e27e1f0308 Date#freeze bug doesn't affect Ruby 1.9 2008-08-28 22:37:36 -07:00
Jeremy Kemper
d4e668b965 Work around frozen Date memoization 2008-08-28 22:28:50 -07:00
Bradford Folkens
b0907153da Reinstate Range#step default argument.
[#595 state:resolved]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-27 23:49:55 -07:00
Tarmo Tänav
367942d93b Implement count limit/offset support for has_many associations
[#348 state:resolved]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-27 23:32:43 -07:00
Tarmo Tänav
0ed29df6fa Alias included associations if needed when doing a count
[#302 state:resolved]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-27 23:30:25 -07:00
Tom Lea
a3a3067adb Dirty: treat two changes resulting in the original value as being unchanged.
[#798 state:resolved]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-27 23:20:36 -07:00
Michael S. Klishin
e21ed3e454 Request#remote_ip handles the uncommon case that REMOTE_ADDR is a comma-separated list.
[#523 state:resolved]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-27 23:03:27 -07:00
Lars Kanis
b23b191090 PostgreSQL: fix quote_string for certain old pg drivers. [#94 state:resolved] 2008-08-27 22:51:36 -07:00
Jeremy Kemper
9aa3c59b21 respond_to? passes along splat args to avoid introducing the second arg if it was omitted 2008-08-27 21:33:46 -07:00
Tim Haines
dabd8c8282 Add TestUploadFile.content_type= to match Request.UploadedFile
[#920 state:resolved]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-27 18:50:57 -07:00
Luca Guidi
4d71e99d1f Fix Ruby's Time marshaling bug in pre-1.9 versions of Ruby: utc instances are now correctly unmarshaled with a utc zone instead of the system local zone [#900 state:resolved] 2008-08-27 09:12:24 -05:00
pivotal
e710902f26 Fix two has_one :through errors
* Set the association target on assignment;
* Reset target to nil on reset, rather than empty array.

Signed-off-by: Michael Koziarski <michael@koziarski.com>
[#895 state:committed]
2008-08-27 11:24:45 +02:00
Joshua Peek
b4d13a97cd Updated bundled TZInfo gem to version 0.3.9 for Ruby 1.9 compat 2008-08-26 15:14:04 +03:00
Tarmo Tänav
d92e4613da Just look at sql_type when testing that the correct database-specific type was used
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-08-26 14:44:58 +03:00
Tarmo Tänav
3a59bf0f28 Added missing fixtures for tests which fail to run independently if run after schema reset
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-08-26 14:41:13 +03:00
Tarmo Tänav
946067ef93 Back to fetching all versions in ruby instead of letting SQL do it as it's difficult to get all databases to convert the text value to a number with the same SQL
Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-26 03:04:01 -07:00
Jeremy Kemper
ad51406d75 Include people and readers fixtures to fix test isolation error 2008-08-26 02:39:00 -07:00
Jeremy Kemper
eab7611618 fix tests relying on implicit ordering 2008-08-26 02:17:47 -07:00
Tarmo Tänav
eae903aecf Create mysql binary_fields table with latin1 character set as with utf8 all the limits would have to be divided by 3 to get the expected text types
Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-26 01:50:14 -07:00
Jeremy Kemper
d093e90f28 PostgreSQL: pg driver expects nil instead of empty string for missing user/pass 2008-08-26 01:50:11 -07:00
Tarmo Tänav
ac7a0209bb Include mysql older than 5.1.23 in the 5.1 series in the list of those that can't handle NULL defaults
Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-26 00:53:56 -07:00
Jeremy Kemper
813290d871 um.. yeah 2008-08-26 00:10:52 -07:00
Jeremy Kemper
8f03357966 typo 2008-08-26 00:02:59 -07:00
Jeremy Kemper
71c53f6433 fix another ordering failure 2008-08-26 00:02:50 -07:00
Jeremy Kemper
698357b9e4 fix tests relying on implicit ordering 2008-08-25 23:53:46 -07:00
Tarmo Tänav
84c10b0f1e Use DECIMAL instead of INTEGER when casting as mysql doesn't work with just "INTEGER" and other databases don't like "UNSIGNED" which mysql requires
And don't mask exceptions.

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-25 23:25:08 -07:00
Tarmo Tänav
7d2b72f3ae Cache migrated versions list in Migrator and use it to fetch the latest migrated version name [#845 state:resolved]
Also optimized Migrator#current_version class method to fetch
only the latest version number and not all of them.

With this change no matter how many migrations there are the
schema_migrations table is only SELECTed from once.

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-25 22:04:11 -07:00
Tarmo Tänav
6ae0a0557d Load the first and not the last has_one result when doing join-based eager loading
This matters when the has_one is defined with an order in which case
there is an expectation that the first one will be loaded.

[#904 state:resolved]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-25 21:25:27 -07:00
Frederick Cheung
bff0f5fb6d Implement old-skool eagerloading for has_one :through
Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-25 21:25:24 -07:00
Frederick Cheung
fdeeeaea61 Fix preloading of has_one through associations
[#903 state:resolved]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-08-25 20:50:39 -07:00
Michael Koziarski
f477676964 Merge commit 'tarmo/2-1-stable' into upgrade 2008-08-25 10:03:53 +02:00
Tarmo Tänav
052875800e Fixed merge mistake for 38a0d5c0c7 2008-08-25 08:40:20 +03:00
Tarmo Tänav
11d9669038 Merge branch '2-1-unsure' into tarmo_2-1-unsure 2008-08-24 19:50:19 +03:00
Joshua Peek
482f6aa59a Update uses_mocha in ActionMailer and ActiveResource 2008-08-24 19:45:22 +03:00
Tarmo Tänav
4e5e0b7eed Clear prefix_parameters cache when setting prefix
Conflicts:

	activeresource/test/base_test.rb
2008-08-24 19:41:51 +03:00
Patrick Reagan
9c11b96c72 Ensure t.timestamps respects options. [#828 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:41:51 +03:00
Tarmo Tänav
8e7505756b Fixed ordering in test_find_in_association_with_custom_finder_sql_and_multiple_interpolations 2008-08-24 19:41:51 +03:00
Tarmo Tänav
a6619865d0 Properly quote CREATE DATABASE parameters in postgresql [#771 state:resolved] 2008-08-24 19:41:51 +03:00
Peter Wagenet
104220ed2c Don't interpret decimals as table names in ActiveRecord::Associations::ClassMethods#references_eager_loaded_tables? [#532 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:41:51 +03:00
Tom Lea
76886789ac Fix incorrect signature for NamedScope#respond_to? [#852 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:41:51 +03:00
Philip Hallstrom
2092f26edb Fix generated WHERE IN query for named scopes. [#583 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:41:51 +03:00
Xavier Noria
0048f558e6 Fix has_many#count_records. [#865 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:41:51 +03:00
Jakub Kuźma
2242d3faf1 Fix that has_one natural assignment to already associated record. [#854 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:41:51 +03:00
Ryan Bates
38a0d5c0c7 Support find_all on named scopes. [#730 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:41:47 +03:00
Miles Georgi
6e71a35529 PostgreSQL: fix transaction bug that can occur if you call change_column with invalid parameters
[#861 state:resolved]
2008-08-24 19:38:31 +03:00
Tarmo Tänav
893d76251f Test for eager loading of STI subclasses from htm associations
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:38:30 +03:00
Nathan Witmer
4c071bc796 Updated has_and_belongs_to_many association to fix :finder_sql interpolation. [#848 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:38:30 +03:00
Tarmo Tänav
fcc5a6e1b0 Fixed STI type condition for eager loading of associations
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:38:30 +03:00
Eloy Duran
56dc039138 Fix ActiveRecord::NamedScope::Scope#respond_to? [#818 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:38:30 +03:00
Michalis Polakis
b6ad9a75cc Alias subquery used in calculations, to provide better compatibility with databases such as MonetDB
Signed-off-by: Michael Koziarski <michael@koziarski.com>
Signed-off-by: Tom Ward <tom@popdog.net>
[#796 state:committed]
2008-08-24 19:38:30 +03:00
Ernie Miller
e7b00c113a Fixed AssociationCollection#<< resulting in unexpected values in @target when :uniq => true
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-08-24 19:38:30 +03:00
Ben Sandofsky
3b767000b7 Make requiring gems optional.
Signed-off-by: Michael Koziarski <michael@koziarski.com>
[#743 state:resolved]
2008-08-24 19:38:20 +03:00
miloops
4b2826a8e8 In javascript helpers option[:type] = :synchronous should work as described in docs.
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-08-24 19:36:31 +03:00
Tarmo Tänav
b999bb8478 Fixed negative default integer parsing for Postgresql 8.3.3
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-08-24 19:36:31 +03:00
Tarmo Tänav
6d8d77e3ce Cast value to string in validates_uniqueness_of if the column is of text type
This fixes an error for postgresql where "text_column = 100" fails in version 8.3

Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-08-24 19:36:31 +03:00
Tarmo Tänav
e6bc5c67c0 Fixed test_joins_with_namespaced_model_should_use_correct_type for postgresql
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-08-24 19:36:31 +03:00
Jan De Poorter
2752cebb8b Fix that label_tag doesn't take a symbol for a name. [#719 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:36:31 +03:00
George Ogata
473d8d0462 Make observers define #after_find in the model only if needed.
[#676 state:resolved]
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-08-24 19:36:20 +03:00
Miles Georgi
5f732b93ac Make script/plugin work with svn+ssh urls. [#662 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:26:58 +03:00
Daniel Guettler
c2f1918990 Use klass.sti_name to make sure associations take store_full_sti_class into account. [#671 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:26:57 +03:00
Joachim Garth
c3aad22332 Make sure association preloading works with full STI class name [#465 state:Resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:26:57 +03:00
David Heinemeier Hansson
82e6e48af1 Fixed that AssetTagHelper#compute_public_path shouldn't cache the asset_host along with the source or per-request proc's won't run [DHH] 2008-08-24 19:26:57 +03:00
Tapajós
8c8399f192 Use full path in database tasks so commands will work outside of Rails root [#612 state:resolved]
Signed-off-by: Joshua Peek <josh@joshpeek.com>
2008-08-24 19:26:57 +03:00
Daniel Guettler
84ceff6921 Ensure script/generate finds generators from symlinked plugins. [#449 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:26:57 +03:00
Johan Sørensen
8d61eadcc6 Ensure mail_to label is obfuscated for javascript encoding. [#294 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:26:40 +03:00
Joshua Peek
0e10e93fcf All 2xx requests are considered successful [#217 state:resolved] 2008-08-24 19:26:40 +03:00
Ripta Pasay
6fbd6c0a00 Use fully-qualified controller name when logging. [#600 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:26:40 +03:00
Pratik Naik
50c73c2dfd Slightly faster DateTime#to_json. [#598 state:resolved] [Alex Zepeda] 2008-08-24 19:26:40 +03:00
Cheah Chu Yeow
4fa6615b15 Ensure url_for(nil) falls back to url_for({}). [#472 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-08-24 19:14:09 +03:00
Jeremy Kemper
99277f5155 link_to_function and button_to_function shouldn't modify their options hashes 2008-08-24 19:14:09 +03:00
Tarmo Tänav
69d9ec3909 Always require activesupport, even if its constant already exists
This is needed because the existance of the ActiveSupport
constant by itself does not guarantee that the whole library
has been loaded.

Also load the StringInquirer in the Rails#env method as
the it might be called inside the initializer block
before activesupport itself has been loaded.
2008-08-24 06:07:22 +03:00
Joshua Peek
9e65cbd3f4 Renamed StringQuestioneer to StringInquirer.
Signed-off-by: Tarmo Tänav <tarmo@itech.ee>
2008-08-24 06:06:38 +03:00
Joshua Peek
5b6fb7cfee Namespaced StringQuestioneer under ActiveSupport. 2008-08-24 06:01:01 +03:00
Tarmo Tänav
ddb8c9c92e Don't set "NULL" as a constraint on nullable columns [#398 state:resolved]
This is already the default and adding it breaks SQL standards compatibility.

Conflicts:

	activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
2008-08-24 05:57:33 +03:00
Jeremy Kemper
482e8fe62a Rely on quieter db:test:load task 2008-08-20 09:16:53 -07:00
Tarmo Tänav
6660206388 Use type_condition method for hmt STI condition 2008-08-15 16:01:55 -07:00
Jeffrey Hardy
762ee05fce Account for the possibility of a nil options argument to CompressedMemCacheStore#read/#write 2008-08-13 04:19:55 -07:00
Tarmo Tänav
dc5997ff42 Fixed Time/Date object serialization
Time/Date objects used to be converted to_s instead of to_uaml
which made them unserializable.
2008-08-12 20:29:24 -07:00
Tom Lea
decc973095 Serialized attributes will now always be saved even with partial_updates turned on.
Signed-off-by: Michael Koziarski <michael@koziarski.com>
[#788 state:committed]
2008-08-12 18:18:29 +02:00
Jeremy Kemper
88eec8327b JRuby: improve constantize performance. [#410 state:resolved] 2008-08-06 17:33:34 -07:00
Michael Koziarski
0498d32e5d Ensure dbconsole includes the -p parameter to mysql as intended 2008-07-31 09:47:25 +02:00
Michael Koziarski
af3f2aad7e Set config.active_record.timestamped_migrations = false to have migrations with numeric prefix instead of UTC timestamp. [#446 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-07-30 18:33:43 +02:00
José Valim
af92dc53a8 Initializer requires ERB explicitly instead of assuming Action Pack loaded it. [#722 state:resolved] 2008-07-30 01:50:08 -07:00
miloops
f93e73782e Prototype helpers should generate Element.insert instead of Insertion.new, which has been deprecated in Prototype 1.6. 2008-07-30 01:44:26 -07:00
José Valim
931d4629d2 Initializer skips prepare_dispatcher if Action Controller isn't in use. [#721 state:resolved] 2008-07-30 01:43:57 -07:00
Tarmo Tänav
8887f2076a Use :namespace instead of :path_prefix for finding controller. [#544 state:resolved]
:namespace is supposed to be the module where controller exists.
:path_prefix can contain anything, including variables, which
makes it unsuitable for determining the module for a controller.

Signed-off-by: Pratik Naik <pratiknaik@gmail.com>

Conflicts:

	actionpack/test/controller/routing_test.rb
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-07-24 12:32:50 +02:00
Joshua Peek
82f338635f Revert "Run callbacks from object's metaclass"
This reverts commit e0846c8417.
2008-07-16 18:03:34 -05:00
Joshua Peek
7a84681016 Revert "Added Object#metaclass"
This reverts commit 98dd7226fa.
2008-07-16 18:03:24 -05:00
Joshua Peek
e0846c8417 Run callbacks from object's metaclass 2008-07-15 21:58:52 -05:00
Joshua Peek
98dd7226fa Added Object#metaclass 2008-07-15 21:56:30 -05:00
David Lowenfels
97ac788e79 requiring rubygems version 1.1.1 2008-07-15 17:05:13 -07:00
Stefan Kaes
6caaa02516 Observers not longer add an after_find method to the observed class.
[#625 state:resolved]
2008-07-15 16:53:02 -07:00
miloops
f253e98d84 update_counters should update nil values.
This allows counter columns with default null instead of requiring default 0.

[#493 state:resolved]
2008-07-15 16:24:48 -07:00
Jason Dew
04f7ac59d2 Add block syntax to HasManyAssociation#build. [#502 state:resolve]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-07-15 15:54:21 -07:00
Tarmo Tänav
536400bfcf SQLite: rename_column raises if the column doesn't exist.
[#622 state:resolved]
2008-07-15 15:54:20 -07:00
Tarmo Tänav
aa99bd19e1 Fixed postgresql limited eager loading for the case where scoped :order was present 2008-07-15 15:54:20 -07:00
Tiago Macedo
84baada079 Fix integer quoting issues in association preload. [#602 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-07-15 15:54:20 -07:00
Gabe da Silveira
97fa8547b2 Add assert_sql helper method to check for specific SQL output in Active Record test suite. [#325 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-07-15 15:54:20 -07:00
Tarmo Tänav
8a548e4fa8 Fixed test_rename_nonexistent_column for PostgreSQL
Also fixed ability to run migration_test.rb alone

[#616 state:resolved]
2008-07-15 15:54:20 -07:00
Tarmo Tänav
275c3ab2a7 Fixed that create database statements would always include "DEFAULT NULL" (Nick Sieger) [#334 status:committed]
Conflicts:

	activerecord/CHANGELOG
2008-07-15 15:54:20 -07:00
Michael Koziarski
0826384a01 Use require_dependency 'application' not require in the console bootstraps to avoid requiring application.rb twice 2008-07-15 15:54:19 -07:00
Joshua Peek
dde5d26425 Fixed teardown method typo (plus whitespace) 2008-07-15 15:54:19 -07:00
Chris Cherry
d126600225 Allow Infinity (1.0/0.0) to pass validates_numericality_of. [#354 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-07-15 15:54:19 -07:00
Michael Koziarski
b32790c558 Tighten the rescue clause when dealing with invalid instance variable names in form_helper. 2008-07-15 15:54:19 -07:00
Michael Koziarski
0d96fcc784 Tighten the rescue clause here to prevent hiding strange mock related errors behind the line offset test 2008-07-15 15:54:19 -07:00
Ricardo Santos
b27e64f670 Ensure script/plugin unsource 'Usage' text is correct. [#526 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-07-15 15:54:19 -07:00
Carl Porth
85bd455713 Ensure Rails::Generator quotes file names while generating diff. [#264 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-07-15 15:54:18 -07:00
Tarmo Tänav
8477fce4c3 Oops, already had a postgresql_version method!
Conflicts:

	activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
2008-07-15 15:54:18 -07:00
Jeremy Kemper
b6d0eba579 Don't dump schema for every test run, just when migrations are run 2008-07-15 15:54:18 -07:00
Jeremy Kemper
4c6eed0537 Fix quoting in test_counting_with_single_conditions 2008-07-15 15:54:18 -07:00
Jeremy Kemper
44363ba7fc PostgreSQL: use 'INSERT ... RETURNING id' for 8.2 and later. 2008-07-15 15:54:18 -07:00
Luis Hurtado
70a34cd641 Fixes parsing deep nested resources from XML. [#380 state:resolved] 2008-07-15 15:54:18 -07:00
Jeremy Kemper
7df10781e8 Remove dead, unused vendor/db2.rb 2008-07-15 15:54:18 -07:00
Jeremy Kemper
0a11165cdc Performance: faster Object.subclasses_of 2008-07-15 15:54:17 -07:00
Joshua Peek
88ff9e1098 Wrap date part value method tests inside a uses mocha block. 2008-07-15 15:54:17 -07:00
Pratik Naik
ea1c1f2d28 Fix that Rails::InfoController tests 2008-07-15 15:54:17 -07:00
Ryan Kinderman
fc2fbe5eb5 Ensure plugins' rake tasks are loaded before application's rake tasks. [#259 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-07-15 15:54:17 -07:00
Jeremy Kemper
0dcc81a84d Give more info on missing gems and abort instead of printing a warning. App can begin in incomplete state otherwise. 2008-07-15 15:50:51 -07:00
Tarmo Tänav
3f48a97f68 Fixed mysql change_column_default to not make the column always nullable.
Also added change_column_null to both mysql and sqlite to keep the api features closer to postgresql.

[#617 state:resolved]
2008-07-15 13:25:31 -07:00
Jeremy Kemper
e8c5859a0d PostgreSQL: don't dump :limit => 4 for integers 2008-07-15 13:23:58 -07:00
gbuesing
b44be6a341 TimeWithZone: when crossing DST boundary, treat Durations of days, months or years as variable-length, and all other values as absolute length. A time + 24.hours will advance exactly 24 hours, but a time + 1.day will advance 23-25 hours, depending on the day. Ensure consistent behavior across all advancing methods 2008-07-15 00:12:08 -05:00
gbuesing
e6ad7ff466 Fix TimeWithZone unmarshaling: coerce unmarshaled Time instances to utc, because Ruby's marshaling of Time instances doesn't respect the zone 2008-07-14 23:26:48 -05:00
Clemens Kofler
6e58a25494 Added notes to Routing documentation and routes.rb regarding defaults routes opening the whole application for GET requests
Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-07-10 10:58:02 +02:00
Michael Koziarski
c6a4c1735a Deprecate the limited follow_redirect in functional tests. If you wish to follow redirects, use integration tests. 2008-07-09 13:43:02 +02:00
Jeremy Kemper
ef0bd72852 Fix rdoc for Filters::ClassMethods 2008-07-04 12:51:38 -07:00
Pratik Naik
9d8fdc92c0 Use ActiveSupport::TimeZone in time:zones rake tasks 2008-07-04 20:08:02 +01:00
David Lowenfels
dd8946231c Add :tokenizer option to validates_length_of. [#507 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-07-04 01:34:33 +01:00
Michael Koziarski
a78750f16d Deprecate define_javascript_functions in favour of javascript_include_tag 2008-07-03 19:34:51 +03:00
Tim Haines
6303ba0c12 Make sure render :template works with :locals. [#524 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-07-02 04:29:14 +01:00
Pratik Naik
25ce6886b2 Ensure AssociationCollection#size considers all unsaved record. [#305 state:resolved] [sds]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-07-02 03:18:55 +01:00
Scott Stewart
67d5ac9355 Ensure proper output when submit_tag is used with :disabled_with. [#388 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-07-02 01:38:37 +01:00
Pratik Naik
b416c6880b Ensure FormBuilder date helpers respects html_options. [#506 state:resolved] [Pascal Ehlert]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>

Conflicts:

	actionpack/test/template/date_helper_test.rb
2008-06-29 00:52:10 +01:00
Tim Chater
692e595477 Dirty: recognize when an integer changes from zero to blank. [#433 state:resolved] 2008-06-27 21:31:32 -07:00
Jeremy Kemper
a42599dfd8 Fix typo in apparently-dead will_unload? method. 2008-06-27 17:21:36 -07:00
Pratik Naik
0fd6371017 Ensure observer test inherits from ActiveSupport::TestCase 2008-06-27 18:20:42 +01:00
Cheah Chu Yeow
42612fe6d1 Allow single quote (the ' character) in the middle of URL when auto_link-ing. [#471 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-06-27 13:47:16 +01:00
Jeremy Kemper
a892af60cb MySQL: treat integer with :limit => 11 as a display width, not byte size, for backward-compatibility. 2008-06-27 01:07:32 -07:00
Jan De Poorter
be099c07ab Make sure associated has_many/habtm objects get saved even when :validate => false is used. [#486 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-06-27 03:03:35 +01:00
Jimmy Baker
02ffbc2763 Patched HTML::Document#initialize call to Node.parse so that it includes the strict argument. [#330 state:resolved] 2008-06-24 23:13:21 -07:00
Jeremy Kemper
6051249f4c Test for tinyint 2008-06-23 23:44:37 -07:00
Jeremy Kemper
fe81af79ab Treat any limit > 4 as bigint 2008-06-23 18:16:16 -07:00
Cheah Chu Yeow
c1ae8b92ac Allow script/about to run in production mode instead of failing with a cryptic const_missing error.
[#370 state:resolved]
2008-06-23 10:27:51 -07:00
Jeremy Kemper
642b0e9512 Revert "Check for mocha gem without requiring the lib. [#403 state:resolved]"
This reverts commit 8636df7b55.
2008-06-23 00:37:19 -07:00
Jeremy Kemper
8636df7b55 Check for mocha gem without requiring the lib. [#403 state:resolved] 2008-06-22 20:56:23 -07:00
Tarmo Tänav
a2eab629dc Always treat integer :limit as byte length. [#420 state:resolved] 2008-06-22 20:48:55 -07:00
Daniel Morrison
44b8907400 Partial updates don't update lock_version if nothing changed. [#426 state:resolved] 2008-06-22 20:36:52 -07:00
Mark Catley
3558a50a1c Fix column collision with named_scope and :joins. [#46 state:resolved] 2008-06-22 19:21:32 -07:00
Tammer Saleh
0304bb182a Fixed polymorphic_url to be able to handle singleton resources.
Example usage:
polymorphic_url([:admin, @user, :blog, @post]) # => admin_user_blog_post_url(@user, @post)

[#461 state:resolved]
2008-06-22 19:07:59 -07:00
ian
965848ec61 Only use DROP ... IF EXISTS for PostgreSQL 8.2 or later. [#400 state:resolved] 2008-06-22 18:32:19 -07:00
Michael Raidel
7839a83227 ActiveRecord::Migrator#run records version-state after migrating. [#369 state:resolved] 2008-06-22 18:16:43 -07:00
Tarmo Tänav
b31b6ef4da Fixed that scopes defined with a string name could not be composed 2008-06-22 17:52:28 -07:00
Jeremy Kemper
44656db5bc Changelog for 509374e 2008-06-22 16:21:56 -07:00
Tarmo Tänav
4ecc13b46b Named bind variables can now be used with postgresql-style typecasts
For example :conditions => ['stringcol::integer = :var', { :var => 10 }]
will no longer raise an exception about ':integer' having a missing value.
2008-06-22 16:16:44 -07:00
Diego Algorta
9855d0b080 MySQL: rename_column preserves default values. [#466 state:resolved] 2008-06-22 15:21:47 -07:00
Jeremy Kemper
4573b7b63d Remove incorrect master entries from 2-1-stable CHANGELOGs. Mark upcoming stuff as 2.1.1 (next release) instead of Edge. 2008-06-22 12:25:26 -07:00
Jeremy Kemper
3a05ba645b Horo rdoc template 2008-06-22 10:39:34 -07:00
Jeremy Kemper
5f52da442f Fall back to #to_s for cache key expansion 2008-06-20 00:26:01 -07:00
rick
e1bd75a922 Fix discrepancies with loading rails/init.rb from gems. [#324 state:resolved] 2008-06-19 10:11:45 -07:00
rick
924244bf5c Add the gem load paths before the framework is loaded, so certain gems like RedCloth and BlueCloth can be frozen. [#320 state:resolved] 2008-06-19 10:10:44 -07:00
Brandon Keepers
7827b0789f fix eager loading with dynamic finders 2008-06-19 10:06:36 -07:00
Jeremy Kemper
8c0ce21d29 Add toplevel doc to .gitignore 2008-06-18 23:15:47 -07:00
Jeremy Kemper
271b8341e4 fix toplevel pdoc task 2008-06-18 22:00:09 -07:00
Jeremy Kemper
1256bba926 Require ssh publisher in toplevel Rakefile 2008-06-18 20:51:03 -07:00
Jeremy Kemper
7084e88e61 Add toplevel rdoc and pdoc tasks 2008-06-18 20:49:31 -07:00
Jeremy Kemper
a46d09f803 Add dummy pdoc task to railties 2008-06-18 20:34:15 -07:00
Jeremy Kemper
edb48d6f55 Use rdoc exclude 2008-06-18 20:16:02 -07:00
Jeremy Kemper
a4752c6709 Add lib/rails/*.rb to rdoc 2008-06-18 20:14:44 -07:00
Jeremy Kemper
55bd351008 Use native include/exclude instead of doing it by hand 2008-06-18 20:13:21 -07:00
Jeremy Kemper
8c95c8e618 Exclude lib/activeresource.rb from rdoc 2008-06-18 20:12:25 -07:00
Jeremy Kemper
644e2cd769 Exclude lib/actionpack.rb from rdoc 2008-06-18 20:09:48 -07:00
Jeremy Kemper
99b429f476 Generate rdoc for all .rb files except those in vendor 2008-06-18 20:05:23 -07:00
Jeremy Kemper
4d45c094dc Generate rdoc for all .rb files except those in vendor 2008-06-18 20:03:07 -07:00
Jeremy Kemper
df98d4bbb3 Add pdoc task to toplevel Rakefile 2008-06-18 19:57:52 -07:00
Jeremy Kemper
6a0929d98f Update Rakefiles to connect to wrath as current user. Use ssh config to set a different user. 2008-06-18 19:56:22 -07:00
Luke Redpath
a83ac48501 Fix url_for with no arguments when default_url_options is not explicitly defined. [#339 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-06-17 21:00:28 +01:00
George Ogata
5c071a78be Fix observers that use after_find. [#375 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-06-17 19:45:44 +01:00
Amos King
b99c1c939c verify :redirect_to => :back should redirect to the referrer. [#280 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-06-17 18:31:27 +01:00
Andrew Kaspick
4d83e9d737 Correct code example in dom_id docs. [#437 state:resolved] 2008-06-17 00:34:00 -07:00
Pratik Naik
f1a1e551f5 Make rescue template xhtml compatible [Sam Ruby] [#415 state:resolved] 2008-06-13 15:46:03 -07:00
Ben Munat
67e8ec0e07 Add :from option to calculations. [#397 state:resolved] 2008-06-11 18:08:11 -07:00
Antonio Cangiano
4689496b52 Fixed non-standard SQL generated by preloading has_and_belongs_to_many association. [#394 state:resolved] 2008-06-11 17:36:16 -07:00
Jan De Poorter
c83a183946 Added some has_many tests
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-06-11 17:12:27 +01:00
Pratik Naik
20442acf8f Silence TimeZone warning 2008-06-11 17:11:54 +01:00
Jan De Poorter
25a4327637 Fix FormOptionsHelper tests.
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-06-11 17:02:15 +01:00
Pratik Naik
535b98859c Update docs to reflect 71bf75 2008-06-11 16:56:53 +01:00
Pratik Naik
e25e272fc5 Disable validations for associated belongs_to record by default 2008-06-11 16:56:46 +01:00
Jan De Poorter
23223ce415 Add :validate option to associations. [#301 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-06-11 16:56:30 +01:00
Ruy Asan
011b5eda0e TimeZone -> ActiveSupport::TimeZone. [#387 state:resolved] 2008-06-11 00:49:27 -07:00
Grant Hollingworth
a797375c99 Performance: speed up Hash#except. [#382 state:resolved] 2008-06-11 00:46:25 -07:00
Jeremy Kemper
a065144afb PostgreSQL: insert looks up pk and sequence name if not given. [#384 state:resolved] 2008-06-10 15:50:23 -07:00
Jeremy Kemper
319941e88b Inflector -> ActiveSupport::Inflector 2008-06-10 14:04:34 -07:00
Joshua Peek
8b65473d17 Fixed deprecated call to Dependencies in plugin loader test. 2008-06-09 16:41:25 -05:00
Joshua Peek
d2a3723b08 Fixed ambiguous first argument warning in ArrayExtTest. 2008-06-09 16:41:10 -05:00
Joshua Peek
1b38705c86 Namespace Inflector, Dependencies, OrderedOptions, and TimeZone under ActiveSupport [#238 state:resolved] 2008-06-09 16:40:45 -05:00
Jeremy Kemper
20b07f9a14 Deprecation warning for vendor/mysql.rb usage. Gone in 2.2, so gem install rails sooner than later. 2008-06-08 16:06:13 -07:00
Tiago Macedo
0aedc7aa96 Fix conditions and order on join tables with limited eager loading. [#372 state:resolved] 2008-06-08 13:02:36 -07:00
Jeremy Kemper
475527c083 Missed add: deprecated erb_variable test 2008-06-08 01:38:19 -07:00
Jeremy Kemper
fa875b9898 Fix changelog wording 2008-06-07 19:48:31 -07:00
Jeremy Kemper
b30604a90e Deprecate ActionView::Base.erb_variable. Append the concat helper method instead of appending to it directly. 2008-06-07 19:47:29 -07:00
Jeremy Kemper
b69de8c5fd Give a nice message if there are duplicate migrations instead of raising a strange insert error 2008-06-07 14:01:58 -07:00
Jeremy Kemper
faec7f5fd1 Move Class::ModelName to Active Support module core_ext 2008-06-07 14:01:03 -07:00
Jeremy Kemper
fc6385f6cb Cache RecordIdentifier methods in Class#model_name wrapper 2008-06-07 14:00:58 -07:00
Jeremy Kemper
782b054e9b Generate less garbage when expanding range bind variables in conditions 2008-06-07 14:00:27 -07:00
Jeremy Kemper
3f89b57791 Drop a string conversion from the often-called tag_options helper 2008-06-07 14:00:19 -07:00
Jeremy Kemper
78a0ccae88 Ensure we have an array to collect 2008-06-07 13:59:16 -07:00
Jeremy Kemper
6775caca7b Remove 1.9's String#chars also 2008-06-07 13:55:55 -07:00
Jeremy Kemper
3ffbc57357 GemDependency#specification should be public 2008-06-07 13:55:44 -07:00
Jeremy Kemper
a6e10ba7cd PostgreSQL: update create_database_with_encoding test also 2008-06-07 13:47:29 -07:00
Jeremy Kemper
87fa9db9e2 PostgreSQL: quote bare table names 2008-06-07 13:47:18 -07:00
Pratik Naik
27b68e35a7 Ensure render :file works inside templates 2008-06-05 23:35:20 +01:00
Frederick Cheung
a04bfacbf0 Make partial counter start from 0 (as in 2.0.x)
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-06-05 21:03:53 +01:00
David Heinemeier Hansson
cce30f7124 Dependencies move to ActiveSupport::Dependencies missed a few spots 2008-06-03 19:21:31 -05:00
David Heinemeier Hansson
e7947e00de Fixed Request#remote_ip to only raise hell if the HTTP_CLIENT_IP and HTTP_X_FORWARDED_FOR doesnt match (not just if theyre both present) [Mark Imbriaco, Bradford Folkens] 2008-06-03 18:16:47 -05:00
Gabe da Silveira
55e5219f99 Fix assert_redirected_to for nested controllers and named routes
[#308 state:resolved]

Signed-off-by: Michael Koziarski <michael@koziarski.com>
2008-06-04 11:09:43 +12:00
David Heinemeier Hansson
9a7a6960be Wrapped Rails.env in StringQuestioneer so you can do Rails.env.development? [DHH] 2008-06-03 17:42:40 -05:00
David Heinemeier Hansson
db1cac2f42 Fixed Base#exists? to check status code as integer [#299 state:resolved] (Wes Oldenbeuving) 2008-06-02 22:11:26 -05:00
David Heinemeier Hansson
27117920af Fixed Date#end_of_quarter to not blow up on May 31st [#289 state:resolved] (Danger) 2008-06-02 22:11:12 -05:00
David Heinemeier Hansson
fe4d75b894 Added tests [#279 state:resolved] (Nicholas Schlueter) 2008-06-02 22:11:03 -05:00
David Heinemeier Hansson
4741c983c7 Fixed that RailsInfoController wasnt considering all requests local in development mode (Edgard Castro) [#310 state:resolved] 2008-06-02 21:56:13 -05:00
Joshua Peek
6fb7de4688 In 9c4f003, gem installation quotes versions. Do the same for unpack and update tests to reflect the change. 2008-06-02 19:02:50 -05:00
Joshua Peek
9959f280cf Fixed initializer tests by stubbing out gems dependencies check. 2008-06-02 19:02:10 -05:00
David Heinemeier Hansson
7a315b7e8b Fixed the brokeness from 952ec79bec 2008-06-02 19:01:41 -05:00
Cheah Chu Yeow
7247ee6a49 Faster Hash#slice that doesn't use Enumerable#include?.
Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
2008-06-02 18:55:23 -05:00
Pratik Naik
604a58807e Ensure AR#sum result is typecasted properly 2008-06-02 18:55:05 -05:00
David Heinemeier Hansson
2af64890a1 Added a test for Gzip 2008-06-02 18:54:57 -05:00
Marcos Tapajos
be794d8046 Fixed changelog merge.
Signed-off-by: Joshua Peek <josh@joshpeek.com>
2008-06-02 18:53:40 -05:00
Jonathan Viney
f657371fc3 Ensure Associations#sum returns 0 when no rows are returned. [#295 state:resolved]
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2008-06-02 18:52:15 -05:00
David Heinemeier Hansson
4d850e43d7 Added SQL escaping for :limit and :offset in MySQL [Jonathan Wiess] 2008-06-02 18:51:53 -05:00
David Heinemeier Hansson
3d5bf096e8 AR can be disabled, new_rails_defaults.rb should check [#303 state:resolved] (Jesper Hvirring Henriksen) 2008-06-02 18:48:14 -05:00
359 changed files with 6032 additions and 3542 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
debug.log
doc/rdoc
activeresource/doc
activerecord/doc
actionpack/doc

View File

@@ -1,4 +1,6 @@
require 'rake'
require 'rake/rdoctask'
require 'rake/contrib/sshpublisher'
env = %(PKG_BUILD="#{ENV['PKG_BUILD']}") if ENV['PKG_BUILD']
@@ -11,7 +13,7 @@ end
desc 'Run all tests by default'
task :default => :test
%w(test rdoc package pgem release).each do |task_name|
%w(test rdoc pgem package release).each do |task_name|
desc "Run #{task_name} task for all projects"
task task_name do
PROJECTS.each do |project|
@@ -19,3 +21,61 @@ task :default => :test
end
end
end
desc "Generate documentation for the Rails framework"
Rake::RDocTask.new do |rdoc|
rdoc.rdoc_dir = 'doc/rdoc'
rdoc.title = "Ruby on Rails Documentation"
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.options << '-A cattr_accessor=object'
rdoc.options << '--charset' << 'utf-8'
rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : './doc/template/horo'
rdoc.rdoc_files.include('railties/CHANGELOG')
rdoc.rdoc_files.include('railties/MIT-LICENSE')
rdoc.rdoc_files.include('railties/README')
rdoc.rdoc_files.include('railties/lib/{*.rb,commands/*.rb,rails/*.rb,rails_generator/*.rb}')
rdoc.rdoc_files.include('activerecord/README')
rdoc.rdoc_files.include('activerecord/CHANGELOG')
rdoc.rdoc_files.include('activerecord/lib/active_record/**/*.rb')
rdoc.rdoc_files.exclude('activerecord/lib/active_record/vendor/*')
rdoc.rdoc_files.include('activeresource/README')
rdoc.rdoc_files.include('activeresource/CHANGELOG')
rdoc.rdoc_files.include('activeresource/lib/active_resource.rb')
rdoc.rdoc_files.include('activeresource/lib/active_resource/*')
rdoc.rdoc_files.include('actionpack/README')
rdoc.rdoc_files.include('actionpack/CHANGELOG')
rdoc.rdoc_files.include('actionpack/lib/action_controller/**/*.rb')
rdoc.rdoc_files.include('actionpack/lib/action_view/**/*.rb')
rdoc.rdoc_files.exclude('actionpack/lib/action_controller/vendor/*')
rdoc.rdoc_files.include('actionmailer/README')
rdoc.rdoc_files.include('actionmailer/CHANGELOG')
rdoc.rdoc_files.include('actionmailer/lib/action_mailer/base.rb')
rdoc.rdoc_files.exclude('actionmailer/lib/action_mailer/vendor/*')
rdoc.rdoc_files.include('activesupport/README')
rdoc.rdoc_files.include('activesupport/CHANGELOG')
rdoc.rdoc_files.include('activesupport/lib/active_support/**/*.rb')
rdoc.rdoc_files.exclude('activesupport/lib/active_support/vendor/*')
end
# Enhance rdoc task to copy referenced images also
task :rdoc do
FileUtils.mkdir_p "doc/rdoc/files/examples/"
FileUtils.copy "activerecord/examples/associations.png", "doc/rdoc/files/examples/associations.png"
end
desc "Publish API docs for Rails as a whole and for each component"
task :pdoc => :rdoc do
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/api", "doc/rdoc").upload
PROJECTS.each do |project|
system %(cd #{project} && #{env} #{$0} pdoc)
end
end

View File

@@ -1,3 +1,8 @@
*2.1.1 (September 4th, 2008)*
* Included in Rails 2.1.1
*2.1.0 (May 31st, 2008)*
* Fixed that a return-path header would be ignored #7572 [joost]

View File

@@ -5,6 +5,8 @@ require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rake/contrib/sshpublisher'
require 'rake/contrib/rubyforgepublisher'
require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version')
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
@@ -35,7 +37,7 @@ Rake::RDocTask.new { |rdoc|
rdoc.title = "Action Mailer -- Easy email delivery and testing"
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
rdoc.options << '--charset' << 'utf-8'
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
rdoc.rdoc_files.include('README', 'CHANGELOG')
rdoc.rdoc_files.include('lib/action_mailer.rb')
rdoc.rdoc_files.include('lib/action_mailer/*.rb')
@@ -55,7 +57,7 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "actionmailer"
s.homepage = "http://www.rubyonrails.org"
s.add_dependency('actionpack', '= 2.1.0' + PKG_BUILD)
s.add_dependency('actionpack', '= 2.1.1' + PKG_BUILD)
s.has_rdoc = true
s.requirements << 'none'
@@ -76,12 +78,13 @@ end
desc "Publish the API documentation"
task :pgem => [:package] do
Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
Rake::SshFilePublisher.new("david@greed.loudthinking.com", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
`ssh david@greed.loudthinking.com '/u/sites/gems/gemupdate.sh'`
end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/am", "doc").upload
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/am", "doc").upload
end
desc "Publish the release files to RubyForge."

View File

@@ -530,7 +530,7 @@ module ActionMailer #:nodoc:
end
def render_message(method_name, body)
render :file => method_name, :body => body
render :file => method_name, :body => body, :use_full_path => true
end
def render(opts)
@@ -538,6 +538,7 @@ module ActionMailer #:nodoc:
if opts[:file] && opts[:file] !~ /\//
opts[:file] = "#{mailer_name}/#{opts[:file]}"
end
opts[:use_full_path] = true
initialize_template_class(body).render(opts)
end

View File

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

View File

@@ -30,13 +30,20 @@ class Net::SMTP
end
end
# Wrap tests that use Mocha and skip if unavailable.
def uses_mocha(test_name)
gem 'mocha', ">=0.5"
require 'stubba'
def uses_gem(gem_name, test_name, version = '> 0')
require 'rubygems'
gem gem_name.to_s, version
require gem_name.to_s
yield
rescue Gem::LoadError
$stderr.puts "Skipping #{test_name} tests (Mocha >= 0.5 is required). `gem install mocha` and try again."
rescue LoadError
$stderr.puts "Skipping #{test_name} tests. `gem install #{gem_name}` and try again."
end
# Wrap tests that use Mocha and skip if unavailable.
unless defined? uses_mocha
def uses_mocha(test_name, &block)
uses_gem('mocha', test_name, '>= 0.5.5', &block)
end
end
def set_delivery_method(delivery_method)

View File

@@ -1,3 +1,20 @@
*2.1.1 (September 4th, 2008)*
* All 2xx requests are considered successful [Josh Peek]
* Deprecate the limited follow_redirect in functional tests. If you wish to follow redirects, use integration tests. [Michael Koziarski]
* Fixed that AssetTagHelper#compute_public_path shouldn't cache the asset_host along with the source or per-request proc's won't run [DHH]
* Deprecate define_javascript_functions, javascript_include_tag and friends are much better [Michael Koziarski]
* Fix polymorphic_url with singleton resources. #461 [Tammer Saleh]
* Deprecate ActionView::Base.erb_variable. Use the concat helper method instead of appending to it directly. [Jeremy Kemper]
* Fixed Request#remote_ip to only raise hell if the HTTP_CLIENT_IP and HTTP_X_FORWARDED_FOR doesn't match (not just if they're both present) [Mark Imbriaco, Bradford Folkens]
*2.1.0 (May 31st, 2008)*
* InstanceTag#default_time_from_options overflows to DateTime [Geoff Buesing]

View File

@@ -5,6 +5,8 @@ require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rake/contrib/sshpublisher'
require 'rake/contrib/rubyforgepublisher'
require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version')
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
@@ -49,12 +51,14 @@ Rake::RDocTask.new { |rdoc|
rdoc.title = "Action Pack -- On rails from request to response"
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.options << '--charset' << 'utf-8'
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
if ENV['DOC_FILES']
rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/))
else
rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
rdoc.rdoc_files.include('lib/**/*.rb')
rdoc.rdoc_files.include(Dir['lib/**/*.rb'] -
Dir['lib/*/vendor/**/*.rb'])
rdoc.rdoc_files.exclude('lib/actionpack.rb')
end
}
@@ -76,7 +80,7 @@ spec = Gem::Specification.new do |s|
s.has_rdoc = true
s.requirements << 'none'
s.add_dependency('activesupport', '= 2.1.0' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.1.1' + PKG_BUILD)
s.require_path = 'lib'
s.autorequire = 'action_controller'
@@ -132,13 +136,13 @@ task :update_js => [ :update_scriptaculous ]
desc "Publish the API documentation"
task :pgem => [:package] do
Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
`ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
Rake::SshFilePublisher.new("david@greed.loudthinking.com", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
`ssh david@greed.loudthinking.com '/u/sites/gems/gemupdate.sh'`
end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ap", "doc").upload
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload
end
desc "Publish the release files to RubyForge."

View File

@@ -97,7 +97,7 @@ module ActionController
value['controller'] = value['controller'].to_s
if key == :actual && value['controller'].first != '/' && !value['controller'].include?('/')
new_controller_path = ActionController::Routing.controller_relative_to(value['controller'], @controller.class.controller_path)
value['controller'] = new_controller_path if value['controller'] != new_controller_path && ActionController::Routing.possible_controllers.include?(new_controller_path)
value['controller'] = new_controller_path if value['controller'] != new_controller_path && ActionController::Routing.possible_controllers.include?(new_controller_path) && @response.redirected_to.is_a?(Hash)
end
value['controller'] = value['controller'][1..-1] if value['controller'].first == '/' # strip leading hash
end

View File

@@ -398,47 +398,31 @@ module ActionController
# # The same, but shorter.
# assert_select "ol>li", 4
def assert_select_rjs(*args, &block)
rjs_type = nil
arg = args.shift
rjs_type = args.first.is_a?(Symbol) ? args.shift : nil
id = args.first.is_a?(String) ? args.shift : nil
# If the first argument is a symbol, it's the type of RJS statement we're looking
# for (update, replace, insertion, etc). Otherwise, we're looking for just about
# any RJS statement.
if arg.is_a?(Symbol)
rjs_type = arg
if rjs_type
if rjs_type == :insert
arg = args.shift
insertion = "insert_#{arg}".to_sym
raise ArgumentError, "Unknown RJS insertion type #{arg}" unless RJS_STATEMENTS[insertion]
position = args.shift
insertion = "insert_#{position}".to_sym
raise ArgumentError, "Unknown RJS insertion type #{position}" unless RJS_STATEMENTS[insertion]
statement = "(#{RJS_STATEMENTS[insertion]})"
else
raise ArgumentError, "Unknown RJS statement type #{rjs_type}" unless RJS_STATEMENTS[rjs_type]
statement = "(#{RJS_STATEMENTS[rjs_type]})"
end
arg = args.shift
else
statement = "#{RJS_STATEMENTS[:any]}"
end
# Next argument we're looking for is the element identifier. If missing, we pick
# any element.
if arg.is_a?(String)
id = Regexp.quote(arg)
arg = args.shift
else
id = "[^\"]*"
end
pattern =
case rjs_type
when :chained_replace, :chained_replace_html
Regexp.new("\\$\\(\"#{id}\"\\)#{statement}\\(#{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE)
when :remove, :show, :hide, :toggle
Regexp.new("#{statement}\\(\"#{id}\"\\)")
else
Regexp.new("#{statement}\\(\"#{id}\", #{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE)
end
# any element, otherwise we replace it in the statement.
pattern = Regexp.new(
id ? statement.gsub(RJS_ANY_ID, "\"#{id}\"") : statement
)
# Duplicate the body since the next step involves destroying it.
matches = nil
@@ -447,7 +431,7 @@ module ActionController
matches = @response.body.match(pattern)
else
@response.body.gsub(pattern) do |match|
html = unescape_rjs($2)
html = unescape_rjs(match)
matches ||= []
matches.concat HTML::Document.new(html).root.children.select { |n| n.tag? }
""
@@ -577,27 +561,23 @@ module ActionController
protected
unless const_defined?(:RJS_STATEMENTS)
RJS_STATEMENTS = {
:replace => /Element\.replace/,
:replace_html => /Element\.update/,
:chained_replace => /\.replace/,
:chained_replace_html => /\.update/,
:remove => /Element\.remove/,
:show => /Element\.show/,
:hide => /Element\.hide/,
:toggle => /Element\.toggle/
RJS_PATTERN_HTML = "\"((\\\\\"|[^\"])*)\""
RJS_ANY_ID = "\"([^\"])*\""
RJS_STATEMENTS = {
:chained_replace => "\\$\\(#{RJS_ANY_ID}\\)\\.replace\\(#{RJS_PATTERN_HTML}\\)",
:chained_replace_html => "\\$\\(#{RJS_ANY_ID}\\)\\.update\\(#{RJS_PATTERN_HTML}\\)",
:replace_html => "Element\\.update\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)",
:replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)"
}
RJS_INSERTIONS = [:top, :bottom, :before, :after]
RJS_INSERTIONS.each do |insertion|
RJS_STATEMENTS["insert_#{insertion}".to_sym] = Regexp.new(Regexp.quote("new Insertion.#{insertion.to_s.camelize}"))
[:remove, :show, :hide, :toggle].each do |action|
RJS_STATEMENTS[action] = "Element\\.#{action}\\(#{RJS_ANY_ID}\\)"
end
RJS_INSERTIONS = ["top", "bottom", "before", "after"]
RJS_INSERTIONS.each do |insertion|
RJS_STATEMENTS["insert_#{insertion}".to_sym] = "Element.insert\\(#{RJS_ANY_ID}, \\{ #{insertion}: #{RJS_PATTERN_HTML} \\}\\)"
end
RJS_STATEMENTS[:insert_html] = "Element.insert\\(#{RJS_ANY_ID}, \\{ (#{RJS_INSERTIONS.join('|')}): #{RJS_PATTERN_HTML} \\}\\)"
RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})")
RJS_STATEMENTS[:insert_html] = Regexp.new(RJS_INSERTIONS.collect do |insertion|
Regexp.quote("new Insertion.#{insertion.to_s.camelize}")
end.join('|'))
RJS_PATTERN_HTML = /"((\\"|[^"])*)"/
RJS_PATTERN_EVERYTHING = Regexp.new("#{RJS_STATEMENTS[:any]}\\(\"([^\"]*)\", #{RJS_PATTERN_HTML}\\)",
Regexp::MULTILINE)
RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/
end
@@ -611,8 +591,8 @@ module ActionController
root = HTML::Node.new(nil)
while true
next if body.sub!(RJS_PATTERN_EVERYTHING) do |match|
html = unescape_rjs($3)
next if body.sub!(RJS_STATEMENTS[:any]) do |match|
html = unescape_rjs(match)
matches = HTML::Document.new(html).root.children.select { |n| n.tag? }
root.children.concat matches
""

View File

@@ -613,8 +613,9 @@ module ActionController #:nodoc:
#
# This takes the current URL as is and only exchanges the action. In contrast, <tt>url_for :action => 'print'</tt>
# would have slashed-off the path components after the changed action.
def url_for(options = nil) #:doc:
case options || {}
def url_for(options = {})
options ||= {}
case options
when String
options
when Hash
@@ -743,6 +744,9 @@ module ActionController #:nodoc:
# # Renders the template located in [TEMPLATE_ROOT]/weblog/show.r(html|xml) (in Rails, app/views/weblog/show.erb)
# render :template => "weblog/show"
#
# # Renders the template with a local variable
# render :template => "weblog/show", :locals => {:customer => Customer.new}
#
# === Rendering a file
#
# File rendering works just like action rendering except that it takes a filesystem path. By default, the path
@@ -865,7 +869,7 @@ module ActionController #:nodoc:
render_for_file(file, options[:status], options[:use_full_path], options[:locals] || {})
elsif template = options[:template]
render_for_file(template, options[:status], true)
render_for_file(template, options[:status], true, options[:locals] || {})
elsif inline = options[:inline]
add_variables_to_assigns
@@ -1147,7 +1151,7 @@ module ActionController #:nodoc:
def log_processing
if logger && logger.info?
logger.info "\n\nProcessing #{controller_class_name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]"
logger.info "\n\nProcessing #{self.class.name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]"
logger.info " Session ID: #{@_session.session_id}" if @_session and @_session.respond_to?(:session_id)
logger.info " Parameters: #{respond_to?(:filter_parameters) ? filter_parameters(params).inspect : params.inspect}"
end

View File

@@ -135,7 +135,7 @@ module ActionController
# be reloaded on the next request without restarting the server.
def cleanup_application
ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
Dependencies.clear
ActiveSupport::Dependencies.clear
ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
end

View File

@@ -7,6 +7,200 @@ module ActionController #:nodoc:
end
end
class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc:
def append_filter_to_chain(filters, filter_type, &block)
pos = find_filter_append_position(filters, filter_type)
update_filter_chain(filters, filter_type, pos, &block)
end
def prepend_filter_to_chain(filters, filter_type, &block)
pos = find_filter_prepend_position(filters, filter_type)
update_filter_chain(filters, filter_type, pos, &block)
end
def create_filters(filters, filter_type, &block)
filters, conditions = extract_options(filters, &block)
filters.map! { |filter| find_or_create_filter(filter, filter_type, conditions) }
filters
end
def skip_filter_in_chain(*filters, &test)
filters, conditions = extract_options(filters)
filters.each do |filter|
if callback = find(filter) then delete(callback) end
end if conditions.empty?
update_filter_in_chain(filters, :skip => conditions, &test)
end
private
def update_filter_chain(filters, filter_type, pos, &block)
new_filters = create_filters(filters, filter_type, &block)
insert(pos, new_filters).flatten!
end
def find_filter_append_position(filters, filter_type)
# appending an after filter puts it at the end of the call chain
# before and around filters go before the first after filter in the chain
unless filter_type == :after
each_with_index do |f,i|
return i if f.after?
end
end
return -1
end
def find_filter_prepend_position(filters, filter_type)
# prepending a before or around filter puts it at the front of the call chain
# after filters go before the first after filter in the chain
if filter_type == :after
each_with_index do |f,i|
return i if f.after?
end
return -1
end
return 0
end
def find_or_create_filter(filter, filter_type, options = {})
update_filter_in_chain([filter], options)
if found_filter = find(filter) { |f| f.type == filter_type }
found_filter
else
filter_kind = case
when filter.respond_to?(:before) && filter_type == :before
:before
when filter.respond_to?(:after) && filter_type == :after
:after
else
:filter
end
case filter_type
when :before
BeforeFilter.new(filter_kind, filter, options)
when :after
AfterFilter.new(filter_kind, filter, options)
else
AroundFilter.new(filter_kind, filter, options)
end
end
end
def update_filter_in_chain(filters, options, &test)
filters.map! { |f| block_given? ? find(f, &test) : find(f) }
filters.compact!
map! do |filter|
if filters.include?(filter)
new_filter = filter.dup
new_filter.options.merge!(options)
new_filter
else
filter
end
end
end
end
class Filter < ActiveSupport::Callbacks::Callback #:nodoc:
def before?
self.class == BeforeFilter
end
def after?
self.class == AfterFilter
end
def around?
self.class == AroundFilter
end
private
def should_not_skip?(controller)
if options[:skip]
!included_in_action?(controller, options[:skip])
else
true
end
end
def included_in_action?(controller, options)
if options[:only]
Array(options[:only]).map(&:to_s).include?(controller.action_name)
elsif options[:except]
!Array(options[:except]).map(&:to_s).include?(controller.action_name)
else
true
end
end
def should_run_callback?(controller)
should_not_skip?(controller) && included_in_action?(controller, options) && super
end
end
class AroundFilter < Filter #:nodoc:
def type
:around
end
def call(controller, &block)
if should_run_callback?(controller)
method = filter_responds_to_before_and_after? ? around_proc : self.method
# For around_filter do |controller, action|
if method.is_a?(Proc) && method.arity == 2
evaluate_method(method, controller, block)
else
evaluate_method(method, controller, &block)
end
else
block.call
end
end
private
def filter_responds_to_before_and_after?
method.respond_to?(:before) && method.respond_to?(:after)
end
def around_proc
Proc.new do |controller, action|
method.before(controller)
if controller.send!(:performed?)
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
else
begin
action.call
ensure
method.after(controller)
end
end
end
end
end
class BeforeFilter < Filter #:nodoc:
def type
:before
end
def call(controller, &block)
super
if controller.send!(:performed?)
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
end
end
end
class AfterFilter < Filter #:nodoc:
def type
:after
end
end
# Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do
# authentication, caching, or auditing before the intended action is performed. Or to do localization or output
# compression after the action has been performed. Filters have access to the request, response, and all the instance
@@ -245,201 +439,6 @@ module ActionController #:nodoc:
# filter and controller action will not be run. If +before+ renders or redirects,
# the second half of +around+ and will still run but +after+ and the
# action will not. If +around+ fails to yield, +after+ will not be run.
class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc:
def append_filter_to_chain(filters, filter_type, &block)
pos = find_filter_append_position(filters, filter_type)
update_filter_chain(filters, filter_type, pos, &block)
end
def prepend_filter_to_chain(filters, filter_type, &block)
pos = find_filter_prepend_position(filters, filter_type)
update_filter_chain(filters, filter_type, pos, &block)
end
def create_filters(filters, filter_type, &block)
filters, conditions = extract_options(filters, &block)
filters.map! { |filter| find_or_create_filter(filter, filter_type, conditions) }
filters
end
def skip_filter_in_chain(*filters, &test)
filters, conditions = extract_options(filters)
filters.each do |filter|
if callback = find(filter) then delete(callback) end
end if conditions.empty?
update_filter_in_chain(filters, :skip => conditions, &test)
end
private
def update_filter_chain(filters, filter_type, pos, &block)
new_filters = create_filters(filters, filter_type, &block)
insert(pos, new_filters).flatten!
end
def find_filter_append_position(filters, filter_type)
# appending an after filter puts it at the end of the call chain
# before and around filters go before the first after filter in the chain
unless filter_type == :after
each_with_index do |f,i|
return i if f.after?
end
end
return -1
end
def find_filter_prepend_position(filters, filter_type)
# prepending a before or around filter puts it at the front of the call chain
# after filters go before the first after filter in the chain
if filter_type == :after
each_with_index do |f,i|
return i if f.after?
end
return -1
end
return 0
end
def find_or_create_filter(filter, filter_type, options = {})
update_filter_in_chain([filter], options)
if found_filter = find(filter) { |f| f.type == filter_type }
found_filter
else
filter_kind = case
when filter.respond_to?(:before) && filter_type == :before
:before
when filter.respond_to?(:after) && filter_type == :after
:after
else
:filter
end
case filter_type
when :before
BeforeFilter.new(filter_kind, filter, options)
when :after
AfterFilter.new(filter_kind, filter, options)
else
AroundFilter.new(filter_kind, filter, options)
end
end
end
def update_filter_in_chain(filters, options, &test)
filters.map! { |f| block_given? ? find(f, &test) : find(f) }
filters.compact!
map! do |filter|
if filters.include?(filter)
new_filter = filter.dup
new_filter.options.merge!(options)
new_filter
else
filter
end
end
end
end
class Filter < ActiveSupport::Callbacks::Callback #:nodoc:
def before?
self.class == BeforeFilter
end
def after?
self.class == AfterFilter
end
def around?
self.class == AroundFilter
end
private
def should_not_skip?(controller)
if options[:skip]
!included_in_action?(controller, options[:skip])
else
true
end
end
def included_in_action?(controller, options)
if options[:only]
Array(options[:only]).map(&:to_s).include?(controller.action_name)
elsif options[:except]
!Array(options[:except]).map(&:to_s).include?(controller.action_name)
else
true
end
end
def should_run_callback?(controller)
should_not_skip?(controller) && included_in_action?(controller, options) && super
end
end
class AroundFilter < Filter #:nodoc:
def type
:around
end
def call(controller, &block)
if should_run_callback?(controller)
method = filter_responds_to_before_and_after? ? around_proc : self.method
# For around_filter do |controller, action|
if method.is_a?(Proc) && method.arity == 2
evaluate_method(method, controller, block)
else
evaluate_method(method, controller, &block)
end
else
block.call
end
end
private
def filter_responds_to_before_and_after?
method.respond_to?(:before) && method.respond_to?(:after)
end
def around_proc
Proc.new do |controller, action|
method.before(controller)
if controller.send!(:performed?)
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
else
begin
action.call
ensure
method.after(controller)
end
end
end
end
end
class BeforeFilter < Filter #:nodoc:
def type
:before
end
def call(controller, &block)
super
if controller.send!(:performed?)
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
end
end
end
class AfterFilter < Filter #:nodoc:
def type
:after
end
end
module ClassMethods
# The passed <tt>filters</tt> will be appended to the filter_chain and
# will execute before the action on this controller is performed.

View File

@@ -48,6 +48,9 @@ module ActionController
#
# # calls post_url(post)
# polymorphic_url(post) # => "http://example.com/posts/1"
# polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
# polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
# polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
#
# ==== Options
#
@@ -83,8 +86,6 @@ module ActionController
else [ record_or_hash_or_array ]
end
args << format if format
inflection =
case
when options[:action].to_s == "new"
@@ -96,6 +97,9 @@ module ActionController
else
:singular
end
args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
args << format if format
named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
send!(named_route, *args)
@@ -136,11 +140,19 @@ module ActionController
else
record = records.pop
route = records.inject("") do |string, parent|
string << "#{RecordIdentifier.send!("singular_class_name", parent)}_"
if parent.is_a?(Symbol) || parent.is_a?(String)
string << "#{parent}_"
else
string << "#{RecordIdentifier.send!("singular_class_name", parent)}_"
end
end
end
route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"
if record.is_a?(Symbol) || record.is_a?(String)
route << "#{record}_"
else
route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"
end
action_prefix(options) + namespace + route + routing_type(options).to_s
end
@@ -163,16 +175,17 @@ module ActionController
end
end
# Remove the first symbols from the array and return the url prefix
# implied by those symbols.
def extract_namespace(record_or_hash_or_array)
returning "" do |namespace|
if record_or_hash_or_array.is_a?(Array)
record_or_hash_or_array.delete_if do |record_or_namespace|
if record_or_namespace.is_a?(String) || record_or_namespace.is_a?(Symbol)
namespace << "#{record_or_namespace}_"
end
end
end
return "" unless record_or_hash_or_array.is_a?(Array)
namespace_keys = []
while (key = record_or_hash_or_array.first) && key.is_a?(String) || key.is_a?(Symbol)
namespace_keys << record_or_hash_or_array.shift
end
namespace_keys.map {|k| "#{k}_"}.join
end
end
end

View File

@@ -31,18 +31,21 @@ module ActionController
module RecordIdentifier
extend self
JOIN = '_'.freeze
NEW = 'new'.freeze
# Returns plural/singular for a record or class. Example:
#
# partial_path(post) # => "posts/post"
# partial_path(Person) # => "people/person"
# partial_path(Person, "admin/games") # => "admin/people/person"
def partial_path(record_or_class, controller_path = nil)
klass = class_from_record_or_class(record_or_class)
name = model_name_from_record_or_class(record_or_class)
if controller_path && controller_path.include?("/")
"#{File.dirname(controller_path)}/#{klass.name.tableize}/#{klass.name.demodulize.underscore}"
"#{File.dirname(controller_path)}/#{name.partial_path}"
else
"#{klass.name.tableize}/#{klass.name.demodulize.underscore}"
name.partial_path
end
end
@@ -56,21 +59,25 @@ module ActionController
# dom_class(post, :edit) # => "edit_post"
# dom_class(Person, :edit) # => "edit_person"
def dom_class(record_or_class, prefix = nil)
[ prefix, singular_class_name(record_or_class) ].compact * '_'
singular = singular_class_name(record_or_class)
prefix ? "#{prefix}#{JOIN}#{singular}" : singular
end
# The DOM id convention is to use the singular form of an object or class with the id following an underscore.
# If no id is found, prefix with "new_" instead. Examples:
#
# dom_id(Post.new(:id => 45)) # => "post_45"
# dom_id(Post.find(45)) # => "post_45"
# dom_id(Post.new) # => "new_post"
#
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
#
# dom_id(Post.new(:id => 45), :edit) # => "edit_post_45"
# dom_id(Post.find(45), :edit) # => "edit_post_45"
def dom_id(record, prefix = nil)
prefix ||= 'new' unless record.id
[ prefix, singular_class_name(record), record.id ].compact * '_'
if record_id = record.id
"#{dom_class(record, prefix)}#{JOIN}#{record_id}"
else
dom_class(record, prefix || NEW)
end
end
# Returns the plural class name of a record or class. Examples:
@@ -78,7 +85,7 @@ module ActionController
# plural_class_name(post) # => "posts"
# plural_class_name(Highrise::Person) # => "highrise_people"
def plural_class_name(record_or_class)
singular_class_name(record_or_class).pluralize
model_name_from_record_or_class(record_or_class).plural
end
# Returns the singular class name of a record or class. Examples:
@@ -86,12 +93,12 @@ module ActionController
# singular_class_name(post) # => "post"
# singular_class_name(Highrise::Person) # => "highrise_person"
def singular_class_name(record_or_class)
class_from_record_or_class(record_or_class).name.underscore.tr('/', '_')
model_name_from_record_or_class(record_or_class).singular
end
private
def class_from_record_or_class(record_or_class)
record_or_class.is_a?(Class) ? record_or_class : record_or_class.class
def model_name_from_record_or_class(record_or_class)
(record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name
end
end
end
end

View File

@@ -134,14 +134,17 @@ module ActionController
# REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
# delimited list in the case of multiple chained proxies; the last
# address which is not trusted is the originating IP.
def remote_ip
if TRUSTED_PROXIES !~ @env['REMOTE_ADDR']
return @env['REMOTE_ADDR']
remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].split(',').collect(&:strip)
unless remote_addr_list.blank?
not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES}
return not_trusted_addrs.first unless not_trusted_addrs.empty?
end
remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')
if @env.include? 'HTTP_CLIENT_IP'
if @env.include? 'HTTP_X_FORWARDED_FOR'
if remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
# We don't know which came from the proxy, and which from the user
raise ActionControllerError.new(<<EOM)
IP spoofing attack?!
@@ -149,11 +152,11 @@ HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}
HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}
EOM
end
return @env['HTTP_CLIENT_IP']
end
if @env.include? 'HTTP_X_FORWARDED_FOR' then
remote_ips = @env['HTTP_X_FORWARDED_FOR'].split(',')
if remote_ips
while remote_ips.size > 1 && TRUSTED_PROXIES =~ remote_ips.last.strip
remote_ips.pop
end

View File

@@ -88,6 +88,10 @@ module ActionController
#
# map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' }
#
# Note: The default routes, as provided by the Rails generator, make all actions in every
# controller accessible via GET requests. You should consider removing them or commenting
# them out if you're using named routes and resources.
#
# == Named routes
#
# Routes can be named with the syntax <tt>map.name_of_route options</tt>,
@@ -369,7 +373,7 @@ module ActionController
Routes = RouteSet.new
::Inflector.module_eval do
ActiveSupport::Inflector.module_eval do
# Ensures that routes are reloaded when Rails inflections are updated.
def inflections_with_route_reloading(&block)
returning(inflections_without_route_reloading(&block)) {

View File

@@ -67,10 +67,9 @@ module ActionController
options = options.dup
if options[:namespace]
options[:controller] = "#{options[:path_prefix]}/#{options[:controller]}"
options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}"
options.delete(:path_prefix)
options.delete(:name_prefix)
options.delete(:namespace)
end
requirements = (options.delete(:requirements) || {}).dup

View File

@@ -249,7 +249,7 @@ module ActionController
end
def extract_value
"#{local_name} = hash[:#{key}] && hash[:#{key}].collect { |path_component| URI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}"
"#{local_name} = hash[:#{key}] && Array(hash[:#{key}]).collect { |path_component| URI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}"
end
def default

View File

@@ -1,4 +1,4 @@
<html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Action Controller: Exception caught</title>
<style>

View File

@@ -171,7 +171,7 @@ module ActionController #:nodoc:
# Was the response successful?
def success?
response_code == 200
(200..299).include?(response_code)
end
# Was the URL not found?
@@ -333,7 +333,7 @@ module ActionController #:nodoc:
attr_reader :original_filename
# The content type of the "uploaded" file
attr_reader :content_type
attr_accessor :content_type
def initialize(path, content_type = Mime::TEXT, binary = false)
raise "#{path} file does not exist" unless File.exist?(path)
@@ -413,6 +413,8 @@ module ActionController #:nodoc:
get(@response.redirected_to.delete(:action), @response.redirected_to.stringify_keys)
end
deprecate :follow_redirect => "If you wish to follow redirects, you should use integration tests"
def assigns(key = nil)
if key.nil?
@response.template.assigns

View File

@@ -17,7 +17,7 @@ module HTML #:nodoc:
@root = Node.new(nil)
node_stack = [ @root ]
while token = tokenizer.next
node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token)
node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token, strict)
node_stack.last.children << node unless node.tag? && node.closing == :close
if node.tag?

View File

@@ -116,7 +116,7 @@ module ActionController #:nodoc:
end
def apply_redirect_to(redirect_to_option) # :nodoc:
redirect_to_option.is_a?(Symbol) ? self.send!(redirect_to_option) : redirect_to_option
(redirect_to_option.is_a?(Symbol) && redirect_to_option != :back) ? self.send!(redirect_to_option) : redirect_to_option
end
def apply_remaining_actions(options) # :nodoc:

View File

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

View File

@@ -178,10 +178,13 @@ module ActionView #:nodoc:
# that alert()s the caught exception (and then re-raises it).
@@debug_rjs = false
cattr_accessor :debug_rjs
@@erb_variable = '_erbout'
cattr_accessor :erb_variable
class << self
deprecate :erb_variable= => 'The erb variable will no longer be configurable. Use the concat helper method instead of appending to it directly.'
end
attr_internal :request
delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
@@ -253,6 +256,7 @@ If you are rendering a subtemplate, you must now use controller-like partial syn
elsif options == :update
update_page(&block)
elsif options.is_a?(Hash)
use_full_path = options[:use_full_path]
options = options.reverse_merge(:locals => {}, :use_full_path => true)
if partial_layout = options.delete(:layout)
@@ -266,7 +270,7 @@ If you are rendering a subtemplate, you must now use controller-like partial syn
end
end
elsif options[:file]
render_file(options[:file], options[:use_full_path], options[:locals])
render_file(options[:file], use_full_path || false, options[:locals])
elsif options[:partial] && options[:collection]
render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals])
elsif options[:partial]

View File

@@ -485,21 +485,24 @@ module ActionView
source = "#{@controller.request.relative_url_root}#{source}"
end
end
source = rewrite_asset_path(source)
if include_host
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
rewrite_asset_path(source)
end
end
source = ActionView::Base.computed_public_paths[cache_key]
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,

View File

@@ -696,15 +696,15 @@ module ActionView
class FormBuilder
def date_select(method, options = {}, html_options = {})
@template.date_select(@object_name, method, options.merge(:object => @object))
@template.date_select(@object_name, method, options.merge(:object => @object), html_options)
end
def time_select(method, options = {}, html_options = {})
@template.time_select(@object_name, method, options.merge(:object => @object))
@template.time_select(@object_name, method, options.merge(:object => @object), html_options)
end
def datetime_select(method, options = {}, html_options = {})
@template.datetime_select(@object_name, method, options.merge(:object => @object))
@template.datetime_select(@object_name, method, options.merge(:object => @object), html_options)
end
end
end

View File

@@ -601,7 +601,11 @@ module ActionView
end
def object
@object || (@template_object.instance_variable_get("@#{@object_name}") rescue nil)
@object || @template_object.instance_variable_get("@#{@object_name}")
rescue NameError
# As @object_name may contain the nested syntax (item[subobject]) we
# need to fallback to nil.
nil
end
def value(object)

View File

@@ -304,7 +304,7 @@ module ActionView
#
# NOTE: Only the option tags are returned, you have to wrap this call in
# a regular HTML select tag.
def time_zone_options_for_select(selected = nil, priority_zones = nil, model = TimeZone)
def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone)
zone_options = ""
zones = model.all
@@ -417,7 +417,7 @@ module ActionView
value = value(object)
content_tag("select",
add_options(
time_zone_options_for_select(value || options[:default], priority_zones, options[:model] || TimeZone),
time_zone_options_for_select(value || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone),
options, value
), html_options
)

View File

@@ -129,7 +129,7 @@ module ActionView
# label_tag 'name', nil, :class => 'small_label'
# # => <label for="name" class="small_label">Name</label>
def label_tag(name, text = nil, options = {})
content_tag :label, text || name.humanize, { "for" => name }.update(options.stringify_keys)
content_tag :label, text || name.to_s.humanize, { "for" => name }.update(options.stringify_keys)
end
# Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or
@@ -348,11 +348,13 @@ module ActionView
options.stringify_keys!
if disable_with = options.delete("disable_with")
disable_with = "this.value='#{disable_with}'"
disable_with << ";#{options.delete('onclick')}" if options['onclick']
options["onclick"] = [
"this.setAttribute('originalValue', this.value)",
"this.disabled=true",
"this.value='#{disable_with}'",
"#{options["onclick"]}",
disable_with,
"result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit())",
"if (result == false) { this.value = this.getAttribute('originalValue'); this.disabled = false }",
"return result;",

View File

@@ -80,10 +80,9 @@ module ActionView
# return false;">Show me more</a>
#
def link_to_function(name, *args, &block)
html_options = args.extract_options!
html_options = args.extract_options!.symbolize_keys
function = args[0] || ''
html_options.symbolize_keys!
function = update_page(&block) if block_given?
content_tag(
"a", name,
@@ -111,10 +110,9 @@ module ActionView
# page[:details].visual_effect :toggle_slide
# end
def button_to_function(name, *args, &block)
html_options = args.extract_options!
html_options = args.extract_options!.symbolize_keys
function = args[0] || ''
html_options.symbolize_keys!
function = update_page(&block) if block_given?
tag(:input, html_options.merge({
:type => "button", :value => name,
@@ -147,6 +145,8 @@ module ActionView
javascript << '</script>'
end
deprecate :define_javascript_functions=>"use javascript_include_tag instead"
# Escape carrier returns and single and double quotes for JavaScript segments.
def escape_javascript(javascript)
(javascript || '').gsub('\\','\0\0').gsub('</','<\/').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }

View File

@@ -111,7 +111,7 @@ module ActionView
(100..599).to_a)
AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url,
:asynchronous, :method, :insertion, :position,
:form, :with, :update, :script ]).merge(CALLBACKS)
:form, :with, :update, :script, :type ]).merge(CALLBACKS)
end
# Returns a link to a remote action defined by <tt>options[:url]</tt>
@@ -603,7 +603,7 @@ module ActionView
# Example:
#
# # Generates:
# # new Insertion.Bottom("list", "<li>Some item</li>");
# # new Element.insert("list", { bottom: <li>Some item</li>" });
# # new Effect.Highlight("list");
# # ["status-indicator", "cancel-link"].each(Element.hide);
# update_page do |page|
@@ -736,16 +736,16 @@ module ActionView
#
# # Insert the rendered 'navigation' partial just before the DOM
# # element with ID 'content'.
# # Generates: new Insertion.Before("content", "-- Contents of 'navigation' partial --");
# # Generates: Element.insert("content", { before: "-- Contents of 'navigation' partial --" });
# page.insert_html :before, 'content', :partial => 'navigation'
#
# # Add a list item to the bottom of the <ul> with ID 'list'.
# # Generates: new Insertion.Bottom("list", "<li>Last item</li>");
# # Generates: Element.insert("list", { bottom: "<li>Last item</li>" });
# page.insert_html :bottom, 'list', '<li>Last item</li>'
#
def insert_html(position, id, *options_for_render)
insertion = position.to_s.camelize
call "new Insertion.#{insertion}", id, render(*options_for_render)
content = javascript_object_for(render(*options_for_render))
record "Element.insert(\"#{id}\", { #{position.to_s.downcase}: #{content} });"
end
# Replaces the inner HTML of the DOM element with the given +id+.
@@ -1039,7 +1039,7 @@ module ActionView
js_options['asynchronous'] = options[:type] != :synchronous
js_options['method'] = method_option_to_s(options[:method]) if options[:method]
js_options['insertion'] = "Insertion.#{options[:position].to_s.camelize}" if options[:position]
js_options['insertion'] = "'#{options[:position].to_s.downcase}'" if options[:position]
js_options['evalScripts'] = options[:script].nil? || options[:script]
if options[:form]

View File

@@ -1,5 +1,6 @@
require 'cgi'
require 'erb'
require 'set'
module ActionView
module Helpers #:nodoc:
@@ -8,7 +9,8 @@ module ActionView
module TagHelper
include ERB::Util
BOOLEAN_ATTRIBUTES = Set.new(%w(disabled readonly multiple))
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple).to_set
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
# Returns an empty HTML tag of type +name+ which by default is XHTML
# compliant. Set +open+ to true to create an open tag compatible
@@ -37,7 +39,7 @@ module ActionView
# tag("img", { :src => "open &amp; shut.png" }, false, false)
# # => <img src="open &amp; shut.png" />
def tag(name, options = nil, open = false, escape = true)
"<#{name}#{tag_options(options, escape) if options}" + (open ? ">" : " />")
"<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}"
end
# Returns an HTML block tag of type +name+ surrounding the +content+. Add
@@ -114,7 +116,6 @@ module ActionView
if escape
options.each do |key, value|
next unless value
key = key.to_s
value = BOOLEAN_ATTRIBUTES.include?(key) ? key : escape_once(value)
attrs << %(#{key}="#{value}")
end

View File

@@ -464,7 +464,7 @@ module ActionView
[-\w]+ # subdomain or domain
(?:\.[-\w]+)* # remaining subdomains or domain
(?::\d+)? # port
(?:/(?:(?:[~\w\+@%=\(\)-]|(?:[,.;:][^\s$]))+)?)* # path
(?:/(?:(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$]))+)?)* # path
(?:\?[\w\+@%&=.;-]+)? # query string
(?:\#[\w\-]*)? # trailing anchor
)

View File

@@ -63,17 +63,15 @@ module ActionView
# # calls @workshop.to_s
# # => /workshops/5
def url_for(options = {})
options ||= {}
case options
when Hash
show_path = options[:host].nil? ? true : false
options = { :only_path => show_path }.update(options.symbolize_keys)
options = { :only_path => options[:host].nil? }.update(options.symbolize_keys)
escape = options.key?(:escape) ? options.delete(:escape) : true
url = @controller.send(:url_for, options)
when String
escape = true
url = options
when NilClass
url = @controller.send(:url_for, nil)
else
escape = false
url = polymorphic_path(options)
@@ -444,7 +442,7 @@ module ActionView
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")
if encode == "javascript"
"document.write('#{content_tag("a", name || email_address, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
"document.write('#{content_tag("a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
string << sprintf("%%%x", c)
end
"<script type=\"#{Mime::JS}\">eval(unescape('#{string}'))</script>"

View File

@@ -22,10 +22,10 @@ module ActionView #:nodoc:
end
def render_member(object)
@locals[@counter_name] += 1
@locals[:object] = @locals[@variable_name] = object
template = render_template
@locals[@counter_name] += 1
@locals.delete(@variable_name)
@locals.delete(:object)

View File

@@ -137,6 +137,9 @@ class AssertResponseWithUnexpectedErrorController < ActionController::Base
end
end
class UserController < ActionController::Base
end
module Admin
class InnerModuleController < ActionController::Base
def index
@@ -174,7 +177,7 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
# let's get this party started
def setup
ActionController::Routing::Routes.reload
ActionController::Routing.use_controllers!(%w(action_pack_assertions admin/inner_module content admin/user))
ActionController::Routing.use_controllers!(%w(action_pack_assertions admin/inner_module user content admin/user))
@controller = ActionPackAssertionsController.new
@request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
end
@@ -268,7 +271,7 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
assert_redirected_to admin_inner_module_path
end
end
def test_assert_redirected_to_top_level_named_route_from_nested_controller
with_routing do |set|
set.draw do |map|
@@ -277,11 +280,25 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
end
@controller = Admin::InnerModuleController.new
process :redirect_to_top_level_named_route
# passes -> assert_redirected_to "http://test.host/action_pack_assertions/foo"
# assert_redirected_to "http://test.host/action_pack_assertions/foo" would pass because of exact match early return
assert_redirected_to "/action_pack_assertions/foo"
end
end
def test_assert_redirected_to_top_level_named_route_with_same_controller_name_in_both_namespaces
with_routing do |set|
set.draw do |map|
# this controller exists in the admin namespace as well which is the only difference from previous test
map.top_level '/user/:id', :controller => 'user', :action => 'index'
map.connect ':controller/:action/:id'
end
@controller = Admin::InnerModuleController.new
process :redirect_to_top_level_named_route
# assert_redirected_to top_level_url('foo') would pass because of exact match early return
assert_redirected_to top_level_path('foo')
end
end
# -- standard request/response object testing --------------------------------
# make sure that the template objects exist
@@ -406,7 +423,7 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
process :redirect_to_action
assert_redirected_to :action => "flash_me"
follow_redirect
assert_deprecated { follow_redirect }
assert_equal 1, @request.parameters["id"].to_i
assert "Inconceivable!", @response.body
@@ -416,7 +433,9 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
process :redirect_to_controller
assert_redirected_to :controller => "elsewhere", :action => "flash_me"
assert_raises(RuntimeError, "Can't follow redirects outside of current controller (elsewhere)") { follow_redirect }
assert_raises(RuntimeError, "Can't follow redirects outside of current controller (elsewhere)") do
assert_deprecated { follow_redirect }
end
end
def test_assert_redirection_fails_with_incorrect_controller

View File

@@ -568,7 +568,12 @@ class AssertSelectTest < Test::Unit::TestCase
assert_select "div", 4
end
end
def test_assert_select_rjs_raise_errors
assert_raises(ArgumentError) { assert_select_rjs(:destroy) }
assert_raises(ArgumentError) { assert_select_rjs(:insert, :left) }
end
# Simple selection from a single result.
def test_nested_assert_select_rjs_with_single_result
render_rjs do |page|

View File

@@ -7,6 +7,7 @@ module Submodule
end
class ContainedNonEmptyController < ActionController::Base
def public_action
render :nothing => true
end
hide_action :hidden_action
@@ -105,6 +106,18 @@ end
class PerformActionTest < Test::Unit::TestCase
class MockLogger
attr_reader :logged
def initialize
@logged = []
end
def method_missing(method, *args)
@logged << args.first
end
end
def use_controller(controller_class)
@controller = controller_class.new
@@ -142,6 +155,13 @@ class PerformActionTest < Test::Unit::TestCase
get :another_hidden_action
assert_response 404
end
def test_namespaced_action_should_log_module_name
use_controller Submodule::ContainedNonEmptyController
@controller.logger = MockLogger.new
get :public_action
assert_match /Processing\sSubmodule::ContainedNonEmptyController#public_action/, @controller.logger.logged[1]
end
end
class DefaultUrlOptionsTest < Test::Unit::TestCase
@@ -169,6 +189,22 @@ class DefaultUrlOptionsTest < Test::Unit::TestCase
end
end
class EmptyUrlOptionsTest < Test::Unit::TestCase
def setup
@controller = NonEmptyController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@request.host = 'www.example.com'
end
def test_ensure_url_for_works_as_expected_when_called_with_no_options_if_default_url_options_is_not_set
get :public_action
assert_equal "http://www.example.com/non_empty/public_action", @controller.url_for
end
end
class EnsureNamedRoutesWorksTicket22BugTest < Test::Unit::TestCase
def test_named_routes_still_work
ActionController::Routing::Routes.draw do |map|
@@ -180,4 +216,4 @@ class EnsureNamedRoutesWorksTicket22BugTest < Test::Unit::TestCase
ensure
ActionController::Routing::Routes.load!
end
end
end

View File

@@ -27,14 +27,14 @@ class DispatcherTest < Test::Unit::TestCase
def test_clears_dependencies_after_dispatch_if_in_loading_mode
ActionController::Routing::Routes.expects(:reload).once
Dependencies.expects(:clear).once
ActiveSupport::Dependencies.expects(:clear).once
dispatch(@output, false)
end
def test_leaves_dependencies_after_dispatch_if_not_in_loading_mode
ActionController::Routing::Routes.expects(:reload).never
Dependencies.expects(:clear).never
ActiveSupport::Dependencies.expects(:clear).never
dispatch
end

View File

@@ -120,4 +120,29 @@ HTML
assert doc.find(:tag => "div", :attributes => { :id => "map" }, :content => "")
assert doc.find(:tag => "div", :attributes => { :id => "map" }, :content => nil)
end
def test_parse_invalid_document
assert_nothing_raised do
doc = HTML::Document.new("<html>
<table>
<tr>
<td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td>
</tr>
</table>
</html>")
end
end
def test_invalid_document_raises_exception_when_strict
assert_raises RuntimeError do
doc = HTML::Document.new("<html>
<table>
<tr>
<td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td>
</tr>
</table>
</html>", true)
end
end
end

View File

@@ -28,7 +28,7 @@ class SessionUploadTest < ActionController::IntegrationTest
# end
def test_post_with_upload
uses_mocha "test_post_with_upload" do
Dependencies.stubs(:load?).returns(false)
ActiveSupport::Dependencies.stubs(:load?).returns(false)
with_routing do |set|
set.draw do |map|
map.update 'update', :controller => "upload_test", :action => "update", :method => :post

View File

@@ -68,6 +68,11 @@ class NewRenderTestController < ActionController::Base
path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb')
render :file => path
end
def render_file_from_template
@secret = 'in the sauce'
@path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb'))
end
def render_file_with_locals
path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.erb')
@@ -259,6 +264,10 @@ class NewRenderTestController < ActionController::Base
render :template => "test/hello_world"
end
def render_with_explicit_template_with_locals
render :template => "test/render_file_with_locals", :locals => { :secret => 'area51' }
end
def double_render
render :text => "hello"
render :text => "world"
@@ -531,6 +540,11 @@ class NewRenderTest < Test::Unit::TestCase
get :render_file_with_locals
assert_equal "The secret is in the sauce\n", @response.body
end
def test_render_file_from_template
get :render_file_from_template
assert_equal "The secret is in the sauce\n", @response.body
end
def test_attempt_to_access_object_method
assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone }
@@ -742,7 +756,7 @@ EOS
def test_partial_collection_with_counter
get :partial_collection_with_counter
assert_equal "david1mary2", @response.body
assert_equal "david0mary1", @response.body
end
def test_partial_collection_with_locals
@@ -762,7 +776,7 @@ EOS
def test_partial_collection_shorthand_with_different_types_of_records
get :partial_collection_shorthand_with_different_types_of_records
assert_equal "Bonjour bad customer: mark1Bonjour good customer: craig2Bonjour bad customer: john3Bonjour good customer: zach4Bonjour good customer: brandon5Bonjour bad customer: dan6", @response.body
assert_equal "Bonjour bad customer: mark0Bonjour good customer: craig1Bonjour bad customer: john2Bonjour good customer: zach3Bonjour good customer: brandon4Bonjour bad customer: dan5", @response.body
end
def test_empty_partial_collection
@@ -800,7 +814,12 @@ EOS
get :render_text_with_assigns
assert_equal "world", assigns["hello"]
end
def test_template_with_locals
get :render_with_explicit_template_with_locals
assert_equal "The secret is area51\n", @response.body
end
def test_update_page
get :update_page
assert_template nil

View File

@@ -118,6 +118,39 @@ uses_mocha 'polymorphic URL helpers' do
polymorphic_url([:site, :admin, @article, @response, @tag])
end
def test_nesting_with_array_ending_in_singleton_resource
expects(:article_response_url).with(@article)
polymorphic_url([@article, :response])
end
def test_nesting_with_array_containing_singleton_resource
@tag = Tag.new
@tag.save
expects(:article_response_tag_url).with(@article, @tag)
polymorphic_url([@article, :response, @tag])
end
def test_nesting_with_array_containing_namespace_and_singleton_resource
@tag = Tag.new
@tag.save
expects(:admin_article_response_tag_url).with(@article, @tag)
polymorphic_url([:admin, @article, :response, @tag])
end
def test_nesting_with_array_containing_singleton_resource_and_format
@tag = Tag.new
@tag.save
expects(:formatted_article_response_tag_url).with(@article, @tag, :pdf)
formatted_polymorphic_url([@article, :response, @tag, :pdf])
end
def test_nesting_with_array_containing_singleton_resource_and_format_option
@tag = Tag.new
@tag.save
expects(:article_response_tag_url).with(@article, @tag, :pdf)
polymorphic_url([@article, :response, @tag], :format => :pdf)
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

@@ -103,7 +103,7 @@ class TestController < ActionController::Base
def render_line_offset
begin
render :inline => '<% raise %>', :locals => {:foo => 'bar'}
rescue => exc
rescue RuntimeError => exc
end
line = exc.backtrace.first
render :text => line

View File

@@ -12,6 +12,9 @@ class RequestTest < Test::Unit::TestCase
@request.remote_addr = '1.2.3.4'
assert_equal '1.2.3.4', @request.remote_ip
@request.remote_addr = '1.2.3.4,3.4.5.6'
assert_equal '1.2.3.4', @request.remote_ip
@request.env['HTTP_CLIENT_IP'] = '2.3.4.5'
assert_equal '1.2.3.4', @request.remote_ip
@@ -59,6 +62,9 @@ class RequestTest < Test::Unit::TestCase
assert_match /HTTP_X_FORWARDED_FOR="9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4"/, e.message
assert_match /HTTP_CLIENT_IP="8.8.8.8"/, e.message
@request.env['HTTP_X_FORWARDED_FOR'] = '8.8.8.8, 9.9.9.9'
assert_equal '8.8.8.8', @request.remote_ip
@request.env.delete 'HTTP_CLIENT_IP'
@request.env.delete 'HTTP_X_FORWARDED_FOR'
end

View File

@@ -28,18 +28,16 @@ module Backoffice
end
class ResourcesTest < Test::Unit::TestCase
# The assertions in these tests are incompatible with the hash method
# optimisation. This could indicate user level problems
def setup
ActionController::Base.optimise_named_routes = false
end
def tear_down
def teardown
ActionController::Base.optimise_named_routes = true
end
def test_should_arrange_actions
resource = ActionController::Resources::Resource.new(:messages,
:collection => { :rss => :get, :reorder => :post, :csv => :post },
@@ -159,14 +157,14 @@ class ResourcesTest < Test::Unit::TestCase
def test_with_collection_actions_and_name_prefix
actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
with_restful_routing :messages, :path_prefix => '/threads/:thread_id', :name_prefix => "thread_", :collection => actions do
assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
actions.each do |action, method|
assert_recognizes(options.merge(:action => action), :path => "/threads/1/messages/#{action}", :method => method)
end
end
assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
actions.keys.each do |action|
assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", :action => action
@@ -177,14 +175,14 @@ class ResourcesTest < Test::Unit::TestCase
def test_with_collection_action_and_name_prefix_and_formatted
actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
with_restful_routing :messages, :path_prefix => '/threads/:thread_id', :name_prefix => "thread_", :collection => actions do
assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
actions.each do |action, method|
assert_recognizes(options.merge(:action => action, :format => 'xml'), :path => "/threads/1/messages/#{action}.xml", :method => method)
end
end
assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
actions.keys.each do |action|
assert_named_route "/threads/1/messages/#{action}.xml", "formatted_#{action}_thread_messages_path", :action => action, :format => 'xml'
@@ -279,7 +277,7 @@ class ResourcesTest < Test::Unit::TestCase
end
end
end
def test_with_new_action_with_name_prefix
with_restful_routing :messages, :new => { :preview => :post }, :path_prefix => '/threads/:thread_id', :name_prefix => 'thread_' do
preview_options = {:action => 'preview', :thread_id => '1'}
@@ -293,7 +291,7 @@ class ResourcesTest < Test::Unit::TestCase
end
end
end
def test_with_formatted_new_action_with_name_prefix
with_restful_routing :messages, :new => { :preview => :post }, :path_prefix => '/threads/:thread_id', :name_prefix => 'thread_' do
preview_options = {:action => 'preview', :thread_id => '1', :format => 'xml'}
@@ -307,7 +305,7 @@ class ResourcesTest < Test::Unit::TestCase
end
end
end
def test_override_new_method
with_restful_routing :messages do
assert_restful_routes_for :messages do |options|
@@ -524,9 +522,9 @@ class ResourcesTest < Test::Unit::TestCase
map.resources :messages, :collection => {:search => :get}, :new => {:preview => :any}, :name_prefix => 'thread_', :path_prefix => '/threads/:thread_id'
map.resource :account, :member => {:login => :get}, :new => {:preview => :any}, :name_prefix => 'admin_', :path_prefix => '/admin'
end
action_separator = ActionController::Base.resource_action_separator
assert_simply_restful_for :messages, :name_prefix => 'thread_', :path_prefix => 'threads/1/', :options => { :thread_id => '1' }
assert_named_route "/threads/1/messages#{action_separator}search", "search_thread_messages_path", {}
assert_named_route "/threads/1/messages/new", "new_thread_message_path", {}
@@ -623,7 +621,7 @@ class ResourcesTest < Test::Unit::TestCase
assert_simply_restful_for :products, :controller => "backoffice/products"
end
end
def test_nested_resources_using_namespace
with_routing do |set|
set.draw do |map|
@@ -795,7 +793,7 @@ class ResourcesTest < Test::Unit::TestCase
yield options[:options] if block_given?
end
def assert_singleton_routes_for(singleton_name, options = {})
options[:options] ||= {}
options[:options][:controller] = options[:controller] || singleton_name.to_s.pluralize
@@ -855,7 +853,7 @@ class ResourcesTest < Test::Unit::TestCase
actual = @controller.send(route, options) rescue $!.class.name
assert_equal expected, actual, "Error on route: #{route}(#{options.inspect})"
end
def assert_resource_methods(expected, resource, action_method, method)
assert_equal expected.length, resource.send("#{action_method}_methods")[method].size, "#{resource.send("#{action_method}_methods")[method].inspect}"
expected.each do |action|

View File

@@ -1983,6 +1983,26 @@ class RouteSetTest < Test::Unit::TestCase
Object.send(:remove_const, :Api)
end
def test_namespace_with_path_prefix
Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) })
set.draw do |map|
map.namespace 'api', :path_prefix => 'prefix' do |api|
api.route 'inventory', :controller => "products", :action => 'inventory'
end
end
request.path = "/prefix/inventory"
request.method = :get
assert_nothing_raised { set.recognize(request) }
assert_equal("api/products", request.path_parameters[:controller])
assert_equal("inventory", request.path_parameters[:action])
ensure
Object.send(:remove_const, :Api)
end
def test_generate_finds_best_fit
set.draw do |map|
map.connect "/people", :controller => "people", :action => "index"
@@ -2392,10 +2412,10 @@ uses_mocha 'route loading' do
end
def test_adding_inflections_forces_reload
Inflector::Inflections.instance.expects(:uncountable).with('equipment')
ActiveSupport::Inflector::Inflections.instance.expects(:uncountable).with('equipment')
routes.expects(:reload!)
Inflector.inflections { |inflect| inflect.uncountable('equipment') }
ActiveSupport::Inflector.inflections { |inflect| inflect.uncountable('equipment') }
end
def test_load_with_configuration

View File

@@ -531,6 +531,11 @@ XML
assert_equal content_type, file.content_type
assert_equal file.path, file.local_path
assert_equal expected, file.read
new_content_type = "new content_type"
file.content_type = new_content_type
assert_equal new_content_type, file.content_type
end
def test_test_uploaded_file_with_binary
@@ -571,7 +576,9 @@ XML
get :redirect_to_same_controller
assert_response :redirect
assert_redirected_to :controller => 'test_test/test', :action => 'test_uri', :id => 5
assert_nothing_raised { follow_redirect }
assert_deprecated 'follow_redirect' do
assert_nothing_raised { follow_redirect }
end
end
end
@@ -580,7 +587,9 @@ XML
get :redirect_to_different_controller
assert_response :redirect
assert_redirected_to :controller => 'fail', :id => 5
assert_raise(RuntimeError) { follow_redirect }
assert_raise(RuntimeError) do
assert_deprecated { follow_redirect }
end
end
end

View File

@@ -21,10 +21,10 @@ class VerificationTest < Test::Unit::TestCase
verify :only => :guarded_by_method, :method => :post,
:redirect_to => { :action => "unguarded" }
verify :only => :guarded_by_xhr, :xhr => true,
:redirect_to => { :action => "unguarded" }
verify :only => :guarded_by_not_xhr, :xhr => false,
:redirect_to => { :action => "unguarded" }
@@ -39,10 +39,13 @@ class VerificationTest < Test::Unit::TestCase
verify :only => :no_default_action, :params => "santa"
verify :only => :guarded_with_back, :method => :post,
:redirect_to => :back
def guarded_one
render :text => "#{params[:one]}"
end
def guarded_one_for_named_route_test
render :text => "#{params[:one]}"
end
@@ -70,11 +73,11 @@ class VerificationTest < Test::Unit::TestCase
def guarded_by_method
render :text => "#{request.method}"
end
def guarded_by_xhr
render :text => "#{request.xhr?}"
end
def guarded_by_not_xhr
render :text => "#{request.xhr?}"
end
@@ -86,15 +89,19 @@ class VerificationTest < Test::Unit::TestCase
def two_redirects
render :nothing => true
end
def must_be_post
render :text => "Was a post!"
end
def guarded_with_back
render :text => "#{params[:one]}"
end
def no_default_action
# Will never run
end
protected
def rescue_action(e) raise end
@@ -109,7 +116,17 @@ class VerificationTest < Test::Unit::TestCase
@response = ActionController::TestResponse.new
ActionController::Routing::Routes.add_named_route :foo, '/foo', :controller => 'test', :action => 'foo'
end
def test_using_symbol_back_with_no_referrer
assert_raise(ActionController::RedirectBackError) { get :guarded_with_back }
end
def test_using_symbol_back_redirects_to_referrer
@request.env["HTTP_REFERER"] = "/foo"
get :guarded_with_back
assert_redirected_to '/foo'
end
def test_no_deprecation_warning_for_named_route
assert_not_deprecated do
get :guarded_one_for_named_route_test, :two => "not one"
@@ -209,44 +226,44 @@ class VerificationTest < Test::Unit::TestCase
get :guarded_by_method
assert_redirected_to :action => "unguarded"
end
def test_guarded_by_xhr_with_prereqs
xhr :post, :guarded_by_xhr
assert_equal "true", @response.body
end
def test_guarded_by_xhr_without_prereqs
get :guarded_by_xhr
assert_redirected_to :action => "unguarded"
end
def test_guarded_by_not_xhr_with_prereqs
get :guarded_by_not_xhr
assert_equal "false", @response.body
end
def test_guarded_by_not_xhr_without_prereqs
xhr :post, :guarded_by_not_xhr
assert_redirected_to :action => "unguarded"
end
def test_guarded_post_and_calls_render_succeeds
post :must_be_post
assert_equal "Was a post!", @response.body
end
def test_default_failure_should_be_a_bad_request
post :no_default_action
assert_response :bad_request
end
def test_guarded_post_and_calls_render_fails_and_sets_allow_header
get :must_be_post
assert_response 405
assert_equal "Must be post", @response.body
assert_equal "POST", @response.headers["Allow"]
end
def test_second_redirect
assert_nothing_raised { get :two_redirects }
end

View File

@@ -0,0 +1 @@
<%= render :file => @path %>

View File

@@ -1157,6 +1157,32 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, date_select("post", "written_on", {}, :class => 'selector')
end
def test_date_select_with_html_options_within_fields_for
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
_erbout = ''
fields_for :post, @post do |f|
_erbout.concat f.date_select(:written_on, {}, :class => 'selector')
end
expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="selector">\n}
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="selector">\n}
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
expected << "</select>\n"
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="selector">\n}
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
expected << "</select>\n"
assert_dom_equal expected, _erbout
end
def test_time_select
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
@@ -1218,6 +1244,31 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, time_select("post", "written_on", {}, :class => 'selector')
end
def test_time_select_with_html_options_within_fields_for
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
_erbout = ''
fields_for :post, @post do |f|
_erbout.concat f.time_select(:written_on, {}, :class => 'selector')
end
expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]" class="selector">\n)
0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) }
expected << "</select>\n"
expected << " : "
expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]" class="selector">\n)
0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) }
expected << "</select>\n"
assert_dom_equal expected, _erbout
end
def test_datetime_select
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 16, 35)
@@ -1283,23 +1334,23 @@ class DateHelperTest < ActionView::TestCase
end
end
def test_datetime_select_within_fields_for
def test_datetime_select_with_html_options_within_fields_for
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 16, 35)
_erbout = ''
fields_for :post, @post do |f|
_erbout.concat f.datetime_select(:updated_at)
_erbout.concat f.datetime_select(:updated_at, {}, :class => 'selector')
end
expected = "<select id='post_updated_at_1i' name='post[updated_at(1i)]'>\n<option value='1999'>1999</option>\n<option value='2000'>2000</option>\n<option value='2001'>2001</option>\n<option value='2002'>2002</option>\n<option value='2003'>2003</option>\n<option selected='selected' value='2004'>2004</option>\n<option value='2005'>2005</option>\n<option value='2006'>2006</option>\n<option value='2007'>2007</option>\n<option value='2008'>2008</option>\n<option value='2009'>2009</option>\n</select>\n"
expected << "<select id='post_updated_at_2i' name='post[updated_at(2i)]'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option selected='selected' value='6'>June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n"
expected << "<select id='post_updated_at_3i' name='post[updated_at(3i)]'>\n<option value='1'>1</option>\n<option value='2'>2</option>\n<option value='3'>3</option>\n<option value='4'>4</option>\n<option value='5'>5</option>\n<option value='6'>6</option>\n<option value='7'>7</option>\n<option value='8'>8</option>\n<option value='9'>9</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option selected='selected' value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n</select>\n"
expected << " &mdash; <select id='post_updated_at_4i' name='post[updated_at(4i)]'>\n<option value='00'>00</option>\n<option value='01'>01</option>\n<option value='02'>02</option>\n<option value='03'>03</option>\n<option value='04'>04</option>\n<option value='05'>05</option>\n<option value='06'>06</option>\n<option value='07'>07</option>\n<option value='08'>08</option>\n<option value='09'>09</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option value='15'>15</option>\n<option selected='selected' value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n</select>\n"
expected << " : <select id='post_updated_at_5i' name='post[updated_at(5i)]'>\n<option value='00'>00</option>\n<option value='01'>01</option>\n<option value='02'>02</option>\n<option value='03'>03</option>\n<option value='04'>04</option>\n<option value='05'>05</option>\n<option value='06'>06</option>\n<option value='07'>07</option>\n<option value='08'>08</option>\n<option value='09'>09</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n<option value='32'>32</option>\n<option value='33'>33</option>\n<option value='34'>34</option>\n<option selected='selected' value='35'>35</option>\n<option value='36'>36</option>\n<option value='37'>37</option>\n<option value='38'>38</option>\n<option value='39'>39</option>\n<option value='40'>40</option>\n<option value='41'>41</option>\n<option value='42'>42</option>\n<option value='43'>43</option>\n<option value='44'>44</option>\n<option value='45'>45</option>\n<option value='46'>46</option>\n<option value='47'>47</option>\n<option value='48'>48</option>\n<option value='49'>49</option>\n<option value='50'>50</option>\n<option value='51'>51</option>\n<option value='52'>52</option>\n<option value='53'>53</option>\n<option value='54'>54</option>\n<option value='55'>55</option>\n<option value='56'>56</option>\n<option value='57'>57</option>\n<option value='58'>58</option>\n<option value='59'>59</option>\n</select>\n"
expected = "<select id='post_updated_at_1i' name='post[updated_at(1i)]' class='selector'>\n<option value='1999'>1999</option>\n<option value='2000'>2000</option>\n<option value='2001'>2001</option>\n<option value='2002'>2002</option>\n<option value='2003'>2003</option>\n<option selected='selected' value='2004'>2004</option>\n<option value='2005'>2005</option>\n<option value='2006'>2006</option>\n<option value='2007'>2007</option>\n<option value='2008'>2008</option>\n<option value='2009'>2009</option>\n</select>\n"
expected << "<select id='post_updated_at_2i' name='post[updated_at(2i)]' class='selector'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option selected='selected' value='6'>June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n"
expected << "<select id='post_updated_at_3i' name='post[updated_at(3i)]' class='selector'>\n<option value='1'>1</option>\n<option value='2'>2</option>\n<option value='3'>3</option>\n<option value='4'>4</option>\n<option value='5'>5</option>\n<option value='6'>6</option>\n<option value='7'>7</option>\n<option value='8'>8</option>\n<option value='9'>9</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option selected='selected' value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n</select>\n"
expected << " &mdash; <select id='post_updated_at_4i' name='post[updated_at(4i)]' class='selector'>\n<option value='00'>00</option>\n<option value='01'>01</option>\n<option value='02'>02</option>\n<option value='03'>03</option>\n<option value='04'>04</option>\n<option value='05'>05</option>\n<option value='06'>06</option>\n<option value='07'>07</option>\n<option value='08'>08</option>\n<option value='09'>09</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option value='15'>15</option>\n<option selected='selected' value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n</select>\n"
expected << " : <select id='post_updated_at_5i' name='post[updated_at(5i)]' class='selector'>\n<option value='00'>00</option>\n<option value='01'>01</option>\n<option value='02'>02</option>\n<option value='03'>03</option>\n<option value='04'>04</option>\n<option value='05'>05</option>\n<option value='06'>06</option>\n<option value='07'>07</option>\n<option value='08'>08</option>\n<option value='09'>09</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n<option value='32'>32</option>\n<option value='33'>33</option>\n<option value='34'>34</option>\n<option selected='selected' value='35'>35</option>\n<option value='36'>36</option>\n<option value='37'>37</option>\n<option value='38'>38</option>\n<option value='39'>39</option>\n<option value='40'>40</option>\n<option value='41'>41</option>\n<option value='42'>42</option>\n<option value='43'>43</option>\n<option value='44'>44</option>\n<option value='45'>45</option>\n<option value='46'>46</option>\n<option value='47'>47</option>\n<option value='48'>48</option>\n<option value='49'>49</option>\n<option value='50'>50</option>\n<option value='51'>51</option>\n<option value='52'>52</option>\n<option value='53'>53</option>\n<option value='54'>54</option>\n<option value='55'>55</option>\n<option value='56'>56</option>\n<option value='57'>57</option>\n<option value='58'>58</option>\n<option value='59'>59</option>\n</select>\n"
assert_dom_equal(expected, _erbout)
assert_dom_equal expected, _erbout
end
def test_date_select_with_zero_value_and_no_start_year

View File

@@ -0,0 +1,9 @@
require 'abstract_unit'
class DeprecatedErbVariableTest < ActionView::TestCase
def test_setting_erb_variable_warns
assert_deprecated 'erb_variable' do
ActionView::Base.erb_variable = '_erbout'
end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -190,6 +190,12 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal expected, actual
end
def test_label_tag_with_symbol
actual = label_tag :title
expected = %(<label for="title">Title</label>)
assert_dom_equal expected, actual
end
def test_label_tag_with_text
actual = label_tag "title", "My Title"
expected = %(<label for="title">My Title</label>)
@@ -222,6 +228,13 @@ class FormTagHelperTest < ActionView::TestCase
)
end
def test_submit_tag_with_no_onclick_options
assert_dom_equal(
%(<input name='commit' type='submit' value='Save' onclick="this.setAttribute('originalValue', this.value);this.disabled=true;this.value='Saving...';result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());if (result == false) { this.value = this.getAttribute('originalValue'); this.disabled = false };return result;" />),
submit_tag("Save", :disable_with => "Saving...")
)
end
def test_submit_tag_with_confirmation
assert_dom_equal(
%(<input name='commit' type='submit' value='Save' onclick="return confirm('Are you sure?');"/>),

View File

@@ -4,11 +4,14 @@ class JavaScriptHelperTest < ActionView::TestCase
tests ActionView::Helpers::JavaScriptHelper
def test_define_javascript_functions
# check if prototype.js is included first
assert_not_nil define_javascript_functions.split("\n")[1].match(/Prototype JavaScript framework/)
assert_deprecated(/javascript_include_tag/) do
# check if prototype.js is included first
src = define_javascript_functions
assert_not_nil src.split("\n")[1].match(/Prototype JavaScript framework/)
# check that scriptaculous.js is not in here, only needed if loaded remotely
assert_nil define_javascript_functions.split("\n")[1].match(/var Scriptaculous = \{/)
# check that scriptaculous.js is not in here, only needed if loaded remotely
assert_nil src.split("\n")[1].match(/var Scriptaculous = \{/)
end
end
def test_escape_javascript

View File

@@ -77,6 +77,10 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
link_to_remote("Remote outauthor", :failure => "alert(request.responseText)", :url => { :action => "whatnot" })
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot?a=10&amp;b=20', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>),
link_to_remote("Remote outauthor", :failure => "alert(request.responseText)", :url => { :action => "whatnot", :a => '10', :b => '20' })
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:false, evalScripts:true}); return false;\">Remote outauthor</a>),
link_to_remote("Remote outauthor", :url => { :action => "whatnot" }, :type => :synchronous)
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, insertion:'bottom'}); return false;\">Remote outauthor</a>),
link_to_remote("Remote outauthor", :url => { :action => "whatnot" }, :position => :bottom)
end
def test_link_to_remote_html_options
@@ -288,13 +292,13 @@ class JavaScriptGeneratorTest < PrototypeHelperBaseTest
end
def test_insert_html_with_string
assert_equal 'new Insertion.Top("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");',
assert_equal 'Element.insert("element", { top: "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E" });',
@generator.insert_html(:top, 'element', '<p>This is a test</p>')
assert_equal 'new Insertion.Bottom("element", "\\u003Cp\u003EThis is a test\\u003C/p\u003E");',
assert_equal 'Element.insert("element", { bottom: "\\u003Cp\u003EThis is a test\\u003C/p\u003E" });',
@generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
assert_equal 'new Insertion.Before("element", "\\u003Cp\u003EThis is a test\\u003C/p\u003E");',
assert_equal 'Element.insert("element", { before: "\\u003Cp\u003EThis is a test\\u003C/p\u003E" });',
@generator.insert_html(:before, 'element', '<p>This is a test</p>')
assert_equal 'new Insertion.After("element", "\\u003Cp\u003EThis is a test\\u003C/p\u003E");',
assert_equal 'Element.insert("element", { after: "\\u003Cp\u003EThis is a test\\u003C/p\u003E" });',
@generator.insert_html(:after, 'element', '<p>This is a test</p>')
end
@@ -362,8 +366,8 @@ class JavaScriptGeneratorTest < PrototypeHelperBaseTest
@generator.replace_html('baz', '<p>This is a test</p>')
assert_equal <<-EOS.chomp, @generator.to_s
new Insertion.Top("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
new Insertion.Bottom("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
Element.insert("element", { top: "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E" });
Element.insert("element", { bottom: "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E" });
["foo", "bar"].each(Element.remove);
Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
EOS
@@ -425,6 +429,8 @@ Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
def test_sortable
assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("blah")})}});),
@generator.sortable('blah', :url => { :action => "order" })
assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:false, evalScripts:true, parameters:Sortable.serialize("blah")})}});),
@generator.sortable('blah', :url => { :action => "order" }, :type => :synchronous)
end
def test_draggable
@@ -435,6 +441,8 @@ Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
def test_drop_receiving
assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});),
@generator.drop_receiving('blah', :url => { :action => "order" })
assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:false, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});),
@generator.drop_receiving('blah', :url => { :action => "order" }, :type => :synchronous)
end
def test_collection_first_and_last

View File

@@ -187,6 +187,7 @@ class TextHelperTest < ActionView::TestCase
http://www.mail-archive.com/rails@lists.rubyonrails.org/
http://www.amazon.com/Testing-Equal-Sign-In-Path/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1198861734&sr=8-1
http://en.wikipedia.org/wiki/Sprite_(computer_graphics)
http://en.wikipedia.org/wiki/Texas_hold'em
)
urls.each do |url|

View File

@@ -284,6 +284,7 @@ class UrlHelperTest < ActionView::TestCase
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex", :replace_at => "(at)")
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#40;&#100;&#111;&#116;&#41;&#99;&#111;&#109;</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)")
assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
end
def protect_against_forgery?
@@ -305,6 +306,10 @@ class UrlHelperWithControllerTest < ActionView::TestCase
render :inline => "<%= show_named_route_#{params[:kind]} %>"
end
def nil_url_for
render :inline => '<%= url_for(nil) %>'
end
def rescue_action(e) raise e end
end
@@ -321,7 +326,7 @@ class UrlHelperWithControllerTest < ActionView::TestCase
assert_equal '/url_helper_with_controller/show_url_for', @response.body
end
def test_named_route_shows_host_and_path
def test_named_route_url_shows_host_and_path
with_url_helper_routing do
get :show_named_route, :kind => 'url'
assert_equal 'http://test.host/url_helper_with_controller/show_named_route', @response.body
@@ -335,6 +340,11 @@ class UrlHelperWithControllerTest < ActionView::TestCase
end
end
def test_url_for_nil_returns_current_path
get :nil_url_for
assert_equal '/url_helper_with_controller/nil_url_for', @response.body
end
protected
def with_url_helper_routing
with_routing do |set|

View File

@@ -10,7 +10,7 @@ Rake::RDocTask.new { |rdoc|
rdoc.title = "Active Model"
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
rdoc.options << '--charset' << 'utf-8'
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
rdoc.rdoc_files.include('README', 'CHANGES')
rdoc.rdoc_files.include('lib/**/*.rb')
}
}

View File

@@ -1,3 +1,37 @@
*2.1.1 (September 4th, 2008)*
* Set config.active_record.timestamped_migrations = false to have migrations with numeric prefix instead of UTC timestamp. #446. [Andrew Stone, Nik Wakelin]
* Fixed that create database statements would always include "DEFAULT NULL" (Nick Sieger) [#334]
* change_column_default preserves the not-null constraint. #617 [Tarmo Tänav]
* Add :tokenizer option to validates_length_of to specify how to split up the attribute string. #507. [David Lowenfels] Example :
# Ensure essay contains at least 100 words.
validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %d words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
* Always treat integer :limit as byte length. #420 [Tarmo Tänav]
* Partial updates don't update lock_version if nothing changed. #426 [Daniel Morrison]
* Fix column collision with named_scope and :joins. #46 [Duncan Beevers, Mark Catley]
* db:migrate:down and :up update schema_migrations. #369 [Michael Raidel, RaceCondition]
* PostgreSQL: support :conditions => [':foo::integer', { :foo => 1 }] without treating the ::integer typecast as a bind variable. [Tarmo Tänav]
* MySQL: rename_column preserves column defaults. #466 [Diego Algorta]
* Add :from option to calculations. #397 [Ben Munat]
* Add :validate option to associations to enable/disable the automatic validation of associated models. Resolves #301. [Jan De Poorter]
* PostgreSQL: use 'INSERT ... RETURNING id' for 8.2 and later. [Jeremy Kemper]
* Added SQL escaping for :limit and :offset in MySQL [Jonathan Wiess]
*2.1.0 (May 31st, 2008)*
* Add ActiveRecord::Base.sti_name that checks ActiveRecord::Base#store_full_sti_class? and returns either the full or demodulized name. [rick]

View File

@@ -5,6 +5,7 @@ require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rake/contrib/sshpublisher'
require 'rake/contrib/rubyforgepublisher'
require File.join(File.dirname(__FILE__), 'lib', 'active_record', 'version')
require File.expand_path(File.dirname(__FILE__)) + "/test/config"
@@ -141,7 +142,7 @@ Rake::RDocTask.new { |rdoc|
rdoc.title = "Active Record -- Object-relation mapping put on rails"
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
rdoc.options << '--charset' << 'utf-8'
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
rdoc.rdoc_files.include('lib/**/*.rb')
rdoc.rdoc_files.exclude('lib/active_record/vendor/*')
@@ -171,7 +172,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.1.0' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.1.1' + PKG_BUILD)
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
@@ -225,13 +226,13 @@ end
desc "Publish the beta gem"
task :pgem => [:package] do
Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
`ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
Rake::SshFilePublisher.new("david@greed.loudthinking.com", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
`ssh david@greed.loudthinking.com '/u/sites/gems/gemupdate.sh'`
end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ar", "doc").upload
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ar", "doc").upload
end
desc "Publish the release files to RubyForge."

View File

@@ -24,16 +24,14 @@
$:.unshift(File.dirname(__FILE__)) unless
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
unless defined? ActiveSupport
active_support_path = File.dirname(__FILE__) + "/../../activesupport/lib"
if File.exist?(active_support_path)
$:.unshift active_support_path
require 'active_support'
else
require 'rubygems'
gem 'activesupport'
require 'active_support'
end
active_support_path = File.dirname(__FILE__) + "/../../activesupport/lib"
if File.exist?(active_support_path)
$:.unshift active_support_path
require 'active_support'
else
require 'rubygems'
gem 'activesupport'
require 'active_support'
end
require 'active_record/base'

View File

@@ -51,9 +51,7 @@ module ActiveRecord
def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
parent_records.each do |parent_record|
association_proxy = parent_record.send(reflection_name)
association_proxy.loaded
association_proxy.target = associated_record
parent_record.send("set_#{reflection_name}_target", associated_record)
end
end
@@ -103,17 +101,17 @@ module ActiveRecord
associated_records = reflection.klass.find(:all, :conditions => [conditions, ids],
:include => options[:include],
:joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} as t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
:select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as _parent_record_id",
:select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
:order => options[:order])
set_association_collection_records(id_to_record_map, reflection.name, associated_records, '_parent_record_id')
set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id')
end
def preload_has_one_association(records, reflection, preload_options={})
id_to_record_map, ids = construct_id_map(records)
options = reflection.options
records.each {|record| record.send("set_#{reflection.name}_target", nil)}
if options[:through]
records.each {|record| record.send(reflection.name) && record.send(reflection.name).loaded}
through_records = preload_through_records(records, reflection, options[:through])
through_reflection = reflections[options[:through]]
through_primary_key = through_reflection.primary_key_name
@@ -126,8 +124,6 @@ module ActiveRecord
end
end
else
records.each {|record| record.send("set_#{reflection.name}_target", nil)}
set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.primary_key_name)
end
end
@@ -188,7 +184,6 @@ module ActiveRecord
through_records
end
# FIXME: quoting
def preload_belongs_to_association(records, reflection, preload_options={})
options = reflection.options
primary_key_name = reflection.primary_key_name
@@ -227,9 +222,19 @@ module ActiveRecord
table_name = klass.quoted_table_name
primary_key = klass.primary_key
conditions = "#{table_name}.#{primary_key} IN (?)"
conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} IN (?)"
conditions << append_conditions(options, preload_options)
associated_records = klass.find(:all, :conditions => [conditions, id_map.keys.uniq],
column_type = klass.columns.detect{|c| c.name == primary_key}.type
ids = id_map.keys.uniq.map do |id|
if column_type == :integer
id.to_i
elsif column_type == :float
id.to_f
else
id
end
end
associated_records = klass.find(:all, :conditions => [conditions, ids],
:include => options[:include],
:select => options[:select],
:joins => options[:joins],
@@ -243,7 +248,7 @@ module ActiveRecord
table_name = reflection.klass.quoted_table_name
if interface = reflection.options[:as]
conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} IN (?) and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.name.demodulize}'"
conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} IN (?) and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.sti_name}'"
else
foreign_key = reflection.primary_key_name
conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} IN (?)"

View File

@@ -690,6 +690,7 @@ module ActiveRecord
# association is a polymorphic +belongs_to+.
# * <tt>:uniq</tt> - If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
# * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
# * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. true by default.
#
# Option examples:
# has_many :comments, :order => "posted_on"
@@ -710,6 +711,7 @@ module ActiveRecord
configure_dependency_for_has_many(reflection)
add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
add_multiple_associated_save_callbacks(reflection.name)
add_association_callbacks(reflection.name, reflection.options)
@@ -769,6 +771,7 @@ module ActiveRecord
# * <tt>:source_type</tt> - Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
# association is a polymorphic +belongs_to+.
# * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
# * <tt>:validate</tt> - If false, don't validate the associated object when saving the parent object. +false+ by default.
#
# Option examples:
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
@@ -799,7 +802,7 @@ module ActiveRecord
end
after_save method_name
add_single_associated_save_callbacks(reflection.name)
add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
association_accessor_methods(reflection, HasOneAssociation)
association_constructor_method(:build, reflection, HasOneAssociation)
association_constructor_method(:create, reflection, HasOneAssociation)
@@ -857,6 +860,7 @@ module ActiveRecord
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
# * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
# * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +false+ by default.
#
# Option examples:
# belongs_to :firm, :foreign_key => "client_of"
@@ -937,6 +941,8 @@ module ActiveRecord
)
end
add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
configure_dependency_for_belongs_to(reflection)
end
@@ -1025,6 +1031,7 @@ module ActiveRecord
# * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
# but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
# * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
# * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +true+ by default.
#
# Option examples:
# has_and_belongs_to_many :projects
@@ -1037,6 +1044,7 @@ module ActiveRecord
def has_and_belongs_to_many(association_id, options = {}, &extension)
reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
add_multiple_associated_save_callbacks(reflection.name)
collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
@@ -1103,10 +1111,9 @@ module ActiveRecord
association.create_through_record(new_value)
self.send(reflection.name, new_value)
else
association.replace(new_value)
association.replace(new_value)
instance_variable_set(ivar, new_value.nil? ? nil : association)
end
instance_variable_set(ivar, new_value.nil? ? nil : association)
end
define_method("set_#{reflection.name}_target") do |target|
@@ -1157,7 +1164,7 @@ module ActiveRecord
end
end
def add_single_associated_save_callbacks(association_name)
def add_single_associated_validation_callbacks(association_name)
method_name = "validate_associated_records_for_#{association_name}".to_sym
define_method(method_name) do
association = instance_variable_get("@#{association_name}")
@@ -1169,7 +1176,7 @@ module ActiveRecord
validate method_name
end
def add_multiple_associated_save_callbacks(association_name)
def add_multiple_associated_validation_callbacks(association_name)
method_name = "validate_associated_records_for_#{association_name}".to_sym
ivar = "@#{association_name}"
@@ -1190,6 +1197,10 @@ module ActiveRecord
end
validate method_name
end
def add_multiple_associated_save_callbacks(association_name)
ivar = "@#{association_name}"
method_name = "before_save_associated_records_for_#{association_name}".to_sym
define_method(method_name) do
@@ -1211,7 +1222,6 @@ module ActiveRecord
else
[]
end
records_to_save.each { |record| association.send(:insert_record, record) } unless records_to_save.blank?
# reconstruct the SQL queries now that we know the owner's id
@@ -1343,7 +1353,8 @@ module ActiveRecord
:uniq,
:finder_sql, :counter_sql,
:before_add, :after_add, :before_remove, :after_remove,
:extend, :readonly
:extend, :readonly,
:validate
)
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
@@ -1353,7 +1364,7 @@ module ActiveRecord
def create_has_one_reflection(association_id, options)
options.assert_valid_keys(
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate
)
create_reflection(:has_one, association_id, options, self)
@@ -1361,7 +1372,7 @@ module ActiveRecord
def create_has_one_through_reflection(association_id, options)
options.assert_valid_keys(
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type
:class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate
)
create_reflection(:has_one, association_id, options, self)
end
@@ -1369,7 +1380,7 @@ module ActiveRecord
def create_belongs_to_reflection(association_id, options)
options.assert_valid_keys(
:class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent,
:counter_cache, :extend, :polymorphic, :readonly
:counter_cache, :extend, :polymorphic, :readonly, :validate
)
reflection = create_reflection(:belongs_to, association_id, options, self)
@@ -1388,7 +1399,8 @@ module ActiveRecord
:uniq,
:finder_sql, :delete_sql, :insert_sql,
:before_add, :after_add, :before_remove, :after_remove,
:extend, :readonly
:extend, :readonly,
:validate
)
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
@@ -1465,10 +1477,15 @@ module ActiveRecord
join_dependency.joins_for_table_name(table)
}.flatten.compact.uniq
order = options[:order]
if scoped_order = (scope && scope[:order])
order = order ? "#{order}, #{scoped_order}" : scoped_order
end
is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order)
sql = "SELECT "
if is_distinct
sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", options[:order])
sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", order)
else
sql << primary_key
end
@@ -1482,8 +1499,8 @@ module ActiveRecord
add_conditions!(sql, options[:conditions], scope)
add_group!(sql, options[:group], scope)
if options[:order] && is_distinct
connection.add_order_by_for_association_limiting!(sql, options)
if order && is_distinct
connection.add_order_by_for_association_limiting!(sql, :order => order)
else
add_order!(sql, options[:order], scope)
end
@@ -1502,19 +1519,19 @@ module ActiveRecord
else all << cond
end
end
conditions.join(' ').scan(/([\.\w]+).?\./).flatten
conditions.join(' ').scan(/([\.a-zA-Z_]+).?\./).flatten
end
def order_tables(options)
order = options[:order]
order = [options[:order], scope(:find, :order) ].join(", ")
return [] unless order && order.is_a?(String)
order.scan(/([\.\w]+).?\./).flatten
order.scan(/([\.a-zA-Z_]+).?\./).flatten
end
def selects_tables(options)
select = options[:select]
return [] unless select && select.is_a?(String)
select.scan(/"?([\.\w]+)"?.?\./).flatten
select.scan(/"?([\.a-zA-Z_]+)"?.?\./).flatten
end
# Checks if the conditions reference a table other than the current model table
@@ -1638,7 +1655,9 @@ module ActiveRecord
end
def join_for_table_name(table_name)
@joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil
join = (@joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first) rescue nil
return join unless join.nil?
@joins.select{|j|j.is_a?(JoinAssociation) && j.aliased_join_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil
end
def joins_for_table_name(table_name)
@@ -1714,6 +1733,7 @@ module ActiveRecord
collection.target.push(association)
when :has_one
return if record.id.to_s != join.parent.record_id(row).to_s
return if record.instance_variable_defined?("@#{join.reflection.name}")
association = join.instantiate(row) unless row[join.aliased_primary_key].nil?
record.send("set_#{join.reflection.name}_target", association)
when :belongs_to
@@ -1795,7 +1815,7 @@ module ActiveRecord
@aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
end
if reflection.macro == :has_many && reflection.options[:through]
if [:has_many, :has_one].include?(reflection.macro) && reflection.options[:through]
@aliased_join_table_name = aliased_table_name_for(reflection.through_reflection.klass.table_name, "_join")
end
end
@@ -1819,7 +1839,7 @@ module ActiveRecord
]
when :has_many, :has_one
case
when reflection.macro == :has_many && reflection.options[:through]
when reflection.options[:through]
through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
@@ -1855,7 +1875,7 @@ module ActiveRecord
jt_sti_extra = " AND %s.%s = %s" % [
connection.quote_table_name(aliased_join_table_name),
connection.quote_column_name(through_reflection.active_record.inheritance_column),
through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
through_reflection.klass.quote_value(through_reflection.klass.sti_name)]
end
when :belongs_to
first_key = primary_key
@@ -1920,10 +1940,8 @@ module ActiveRecord
else
""
end || ''
join << %(AND %s.%s = %s ) % [
connection.quote_table_name(aliased_table_name),
connection.quote_column_name(klass.inheritance_column),
klass.quote_value(klass.name.demodulize)] unless klass.descends_from_active_record?
join << %(AND %s) % [
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]

View File

@@ -78,11 +78,14 @@ module ActiveRecord
@loaded = false
end
def build(attributes = {})
def build(attributes = {}, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| build(attr) }
attributes.collect { |attr| build(attr, &block) }
else
build_record(attributes) { |record| set_belongs_to_association_for(record) }
build_record(attributes) do |record|
block.call(record) if block_given?
set_belongs_to_association_for(record)
end
end
end
@@ -187,7 +190,7 @@ module ActiveRecord
if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
@target.size
elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
unsaved_records = Array(@target.detect { |r| r.new_record? })
unsaved_records = @target.select { |r| r.new_record? }
unsaved_records.size + count_records
else
count_records
@@ -335,7 +338,7 @@ module ActiveRecord
callback(:before_add, record)
yield(record) if block_given?
@target ||= [] unless loaded?
@target << record
@target << record unless @reflection.options[:uniq] && @target.include?(record)
callback(:after_add, record)
record
end

View File

@@ -69,8 +69,8 @@ module ActiveRecord
@target
end
def respond_to?(symbol, include_priv = false)
proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
def respond_to?(*args)
proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args))
end
# Explicitly proxy === because the instance method removal above
@@ -131,10 +131,6 @@ module ActiveRecord
records.map { |record| record.quoted_id }.join(',')
end
def interpolate_sql_options!(options, *keys)
keys.each { |key| options[key] &&= interpolate_sql(options[key]) }
end
def interpolate_sql(sql, record = nil)
@owner.send(:interpolate_sql, sql, record)
end

View File

@@ -70,10 +70,8 @@ module ActiveRecord
end
def construct_sql
interpolate_sql_options!(@reflection.options, :finder_sql)
if @reflection.options[:finder_sql]
@finder_sql = @reflection.options[:finder_sql]
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
else
@finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
@finder_sql << " AND (#{conditions})" if conditions
@@ -87,6 +85,7 @@ module ActiveRecord
:joins => @join_sql,
:readonly => false,
:order => @reflection.options[:order],
:include => @reflection.options[:include],
:limit => @reflection.options[:limit] } }
end

View File

@@ -14,7 +14,16 @@ module ActiveRecord
@finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
options[:include] ||= @reflection.options[:include]
@reflection.klass.count(column_name, options)
value = @reflection.klass.count(column_name, options)
limit = @reflection.options[:limit]
offset = @reflection.options[:offset]
if limit || offset
[ [value - offset.to_i, 0].max, limit.to_i ].min
else
value
end
end
end
@@ -27,8 +36,11 @@ module ActiveRecord
else
@reflection.klass.count(:conditions => @counter_sql, :include => @reflection.options[:include])
end
@target = [] and loaded if count == 0
# If there's nothing in the database and @target has no new records
# we are certain the current target is an empty array. This is a
# documented side-effect of the method that may avoid an extra SELECT.
@target ||= [] and loaded if count == 0
if @reflection.options[:limit]
count = [ @reflection.options[:limit], count ].min
@@ -100,7 +112,7 @@ module ActiveRecord
create_scoping = {}
set_belongs_to_association_for(create_scoping)
{
:find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit] },
:find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]},
:create => create_scoping
}
end

View File

@@ -237,7 +237,7 @@ module ActiveRecord
end
def build_sti_condition
"#{@reflection.through_reflection.quoted_table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.sti_name)}"
@reflection.through_reflection.klass.send(:type_condition)
end
alias_method :sql_conditions, :conditions

View File

@@ -21,8 +21,8 @@ module ActiveRecord
def replace(obj, dont_save = false)
load_target
unless @target.nil?
if dependent? && !dont_save && @target != obj
unless @target.nil? || @target == obj
if dependent? && !dont_save
@target.destroy unless @target.new_record?
@owner.clear_association_cache
else

View File

@@ -22,6 +22,10 @@ module ActiveRecord
def find_target
super.first
end
def reset_target!
@target = nil
end
end
end

View File

@@ -372,7 +372,7 @@ module ActiveRecord #:nodoc:
def self.reset_subclasses #:nodoc:
nonreloadables = []
subclasses.each do |klass|
unless Dependencies.autoloaded? klass
unless ActiveSupport::Dependencies.autoloaded? klass
nonreloadables << klass
next
end
@@ -439,6 +439,10 @@ module ActiveRecord #:nodoc:
cattr_accessor :schema_format , :instance_writer => false
@@schema_format = :ruby
# Specify whether or not to use timestamps for migration numbers
cattr_accessor :timestamped_migrations , :instance_writer => false
@@timestamped_migrations = true
# Determine whether to store the full constant name including namespace when using STI
superclass_delegating_accessor :store_full_sti_class
self.store_full_sti_class = false
@@ -828,7 +832,7 @@ module ActiveRecord #:nodoc:
def update_counters(id, counters)
updates = counters.inject([]) { |list, (counter_name, increment)|
sign = increment < 0 ? "-" : "+"
list << "#{connection.quote_column_name(counter_name)} = #{connection.quote_column_name(counter_name)} #{sign} #{increment.abs}"
list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
}.join(", ")
update_all(updates, "#{connection.quote_column_name(primary_key)} = #{quote_value(id)}")
end
@@ -1465,7 +1469,7 @@ module ActiveRecord #:nodoc:
def construct_finder_sql(options)
scope = scope(:find)
sql = "SELECT #{options[:select] || (scope && scope[:select]) || (options[:joins] && quoted_table_name + '.*') || '*'} "
sql = "SELECT #{options[:select] || (scope && scope[:select]) || ((options[:joins] || (scope && scope[:joins])) && quoted_table_name + '.*') || '*'} "
sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
add_joins!(sql, options, scope)
@@ -1577,10 +1581,11 @@ module ActiveRecord #:nodoc:
sql << "WHERE #{merged_conditions} " unless merged_conditions.blank?
end
def type_condition
def type_condition(table_alias=nil)
quoted_table_alias = self.connection.quote_table_name(table_alias || table_name)
quoted_inheritance_column = connection.quote_column_name(inheritance_column)
type_condition = subclasses.inject("#{quoted_table_name}.#{quoted_inheritance_column} = '#{sti_name}' ") do |condition, subclass|
condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{subclass.sti_name}' "
type_condition = subclasses.inject("#{quoted_table_alias}.#{quoted_inheritance_column} = '#{sti_name}' ") do |condition, subclass|
condition << "OR #{quoted_table_alias}.#{quoted_inheritance_column} = '#{subclass.sti_name}' "
end
" (#{type_condition}) "
@@ -1717,7 +1722,7 @@ module ActiveRecord #:nodoc:
def attribute_condition(argument)
case argument
when nil then "IS ?"
when Array, ActiveRecord::Associations::AssociationCollection then "IN (?)"
when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope then "IN (?)"
when Range then "BETWEEN ? AND ?"
else "= ?"
end
@@ -2053,9 +2058,10 @@ module ActiveRecord #:nodoc:
end
def replace_named_bind_variables(statement, bind_vars) #:nodoc:
statement.gsub(/:([a-zA-Z]\w*)/) do
match = $1.to_sym
if bind_vars.include?(match)
statement.gsub(/(:?):([a-zA-Z]\w*)/) do
if $1 == ':' # skip postgresql casts
$& # return the whole match
elsif bind_vars.include?(match = $2.to_sym)
quote_bound_value(bind_vars[match])
else
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
@@ -2064,13 +2070,18 @@ module ActiveRecord #:nodoc:
end
def expand_range_bind_variables(bind_vars) #:nodoc:
bind_vars.sum do |var|
expanded = []
bind_vars.each do |var|
if var.is_a?(Range)
[var.first, var.last]
expanded << var.first
expanded << var.last
else
[var]
expanded << var
end
end
expanded
end
def quote_bound_value(value) #:nodoc:
@@ -2572,8 +2583,15 @@ module ActiveRecord #:nodoc:
quoted = {}
connection = self.class.connection
attribute_names.each do |name|
if column = column_for_attribute(name)
quoted[name] = connection.quote(read_attribute(name), column) unless !include_primary_key && column.primary
if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
value = read_attribute(name)
# We need explicit to_yaml because quote() does not properly convert Time/Date fields to YAML.
if value && self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))
value = value.to_yaml
end
quoted[name] = connection.quote(value, column)
end
end
include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)

View File

@@ -1,6 +1,6 @@
module ActiveRecord
module Calculations #:nodoc:
CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include]
CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include, :from]
def self.included(base)
base.extend(ClassMethods)
end
@@ -27,6 +27,8 @@ module ActiveRecord
# * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not
# include the joined columns.
# * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
# * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
# of a database view).
#
# Examples for counting all:
# Person.count # returns the total count of all people
@@ -71,7 +73,7 @@ module ActiveRecord
#
# Person.sum('age')
def sum(column_name, options = {})
calculate(:sum, column_name, options) || 0
calculate(:sum, column_name, options)
end
# This calculates aggregate values in the given column. Methods for count, sum, average, minimum, and maximum have been added as shortcuts.
@@ -178,13 +180,23 @@ module ActiveRecord
sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround
sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
sql << " FROM #{connection.quote_table_name(table_name)} "
if options[:from]
sql << " FROM #{options[:from]} "
else
sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
sql << " FROM #{connection.quote_table_name(table_name)} "
end
joins = ""
add_joins!(joins, options, scope)
if merged_includes.any?
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins])
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, joins)
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
end
add_joins!(sql, options, scope)
sql << joins unless joins.blank?
add_conditions!(sql, options[:conditions], scope)
add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
@@ -205,7 +217,7 @@ module ActiveRecord
sql << " ORDER BY #{options[:order]} " if options[:order]
add_limit!(sql, options, scope)
sql << ')' if use_workaround
sql << ') AS #{aggregate_alias}_subquery' if use_workaround
sql
end
@@ -266,6 +278,7 @@ module ActiveRecord
operation = operation.to_s.downcase
case operation
when 'count' then value.to_i
when 'sum' then value =~ /\./ ? value.to_f : value.to_i
when 'avg' then value && value.to_f
else column ? column.type_cast(value) : value
end

View File

@@ -257,7 +257,10 @@ module ActiveRecord
def to_sql
column_sql = "#{base.quote_column_name(name)} #{sql_type}"
add_column_options!(column_sql, :null => null, :default => default) unless type.to_sym == :primary_key
column_options = {}
column_options[:null] = null unless null.nil?
column_options[:default] = default unless default.nil?
add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key
column_sql
end
alias to_s :to_sql
@@ -304,8 +307,7 @@ module ActiveRecord
#
# Available options are (none of these exists by default):
# * <tt>:limit</tt> -
# Requests a maximum column length (<tt>:string</tt>, <tt>:text</tt>,
# <tt>:binary</tt> or <tt>:integer</tt> columns only)
# Requests a maximum column length. This is number of characters for <tt>:string</tt> and <tt>:text</tt> columns and number of bytes for :binary and :integer columns.
# * <tt>:default</tt> -
# The column's default value. Use nil for NULL.
# * <tt>:null</tt> -
@@ -442,9 +444,10 @@ module ActiveRecord
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
# <tt>:updated_at</tt> to the table.
def timestamps
column(:created_at, :datetime)
column(:updated_at, :datetime)
def timestamps(*args)
options = args.extract_options!
column(:created_at, :datetime, options)
column(:updated_at, :datetime, options)
end
def references(*args)

View File

@@ -331,15 +331,26 @@ module ActiveRecord
end
def assume_migrated_upto_version(version)
version = version.to_i
sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
versions = Dir['db/migrate/[0-9]*_*.rb'].map do |filename|
filename.split('/').last.split('_').first.to_i
end
execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" unless migrated.include?(version.to_i)
(versions - migrated).select { |v| v < version.to_i }.each do |v|
execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
unless migrated.include?(version)
execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
end
inserted = Set.new
(versions - migrated).each do |v|
if inserted.include?(v)
raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict."
elsif v < version
execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
inserted << v
end
end
end
@@ -372,13 +383,9 @@ module ActiveRecord
def add_column_options!(sql, options) #:nodoc:
sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
# must explcitly check for :null to allow change_column to work on migrations
if options.has_key? :null
if options[:null] == false
sql << " NOT NULL"
else
sql << " NULL"
end
# must explicitly check for :null to allow change_column to work on migrations
if options[:null] == false
sql << " NOT NULL"
end
end

View File

@@ -50,10 +50,7 @@ module ActiveRecord
rescue LoadError => cannot_require_mysql
# Use the bundled Ruby/MySQL driver if no driver is already in place
begin
ActiveRecord::Base.logger.info(
"WARNING: You're using the Ruby-based MySQL library that ships with Rails. This library is not suited for production. " +
"Please install the C-based MySQL library instead (gem install mysql)."
) if ActiveRecord::Base.logger
ActiveSupport::Deprecation.warn "You're using the Ruby-based MySQL library that ships with Rails. This library will be REMOVED FROM RAILS 2.2. Please switch to the offical mysql gem: `gem install mysql`", caller
require 'active_record/vendor/mysql'
rescue LoadError
@@ -113,7 +110,8 @@ module ActiveRecord
end
def extract_limit(sql_type)
if sql_type =~ /blob|text/i
case sql_type
when /blob|text/i
case sql_type
when /tiny/i
255
@@ -124,6 +122,11 @@ module ActiveRecord
else
super # we could return 65535 here, but we leave it undecorated by default
end
when /^bigint/i; 8
when /^int/i; 4
when /^mediumint/i; 3
when /^smallint/i; 2
when /^tinyint/i; 1
else
super
end
@@ -193,10 +196,10 @@ module ActiveRecord
def native_database_types #:nodoc:
{
:primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
:primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY".freeze,
:string => { :name => "varchar", :limit => 255 },
:text => { :name => "text" },
:integer => { :name => "int"},
:integer => { :name => "int", :limit => 4 },
:float => { :name => "float" },
:decimal => { :name => "decimal" },
:datetime => { :name => "datetime" },
@@ -336,10 +339,11 @@ module ActiveRecord
def add_limit_offset!(sql, options) #:nodoc:
if limit = options[:limit]
limit = sanitize_limit(limit)
unless offset = options[:offset]
sql << " LIMIT #{limit}"
else
sql << " LIMIT #{offset}, #{limit}"
sql << " LIMIT #{offset.to_i}, #{limit}"
end
end
end
@@ -439,18 +443,29 @@ module ActiveRecord
end
def change_column_default(table_name, column_name, default) #:nodoc:
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
column = column_for(table_name, column_name)
change_column table_name, column_name, column.sql_type, :default => default
end
execute("ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{current_type} DEFAULT #{quote(default)}")
def change_column_null(table_name, column_name, null, default = nil)
column = column_for(table_name, column_name)
unless null || default.nil?
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
end
change_column table_name, column_name, column.sql_type, :null => null
end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
column = column_for(table_name, column_name)
unless options_include_default?(options)
if column = columns(table_name).find { |c| c.name == column_name.to_s }
options[:default] = column.default
else
raise "No such column: #{table_name}.#{column_name}"
end
options[:default] = column.default
end
unless options.has_key?(:null)
options[:null] = column.null
end
change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
@@ -459,8 +474,17 @@ module ActiveRecord
end
def rename_column(table_name, column_name, new_column_name) #:nodoc:
options = {}
if column = columns(table_name).find { |c| c.name == column_name.to_s }
options[:default] = column.default
options[:null] = column.null
else
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
end
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
execute "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
add_column_options!(rename_column_sql, options)
execute(rename_column_sql)
end
# Maps logical Rails types to MySQL-specific data types.
@@ -468,14 +492,12 @@ module ActiveRecord
return super unless type.to_s == 'integer'
case limit
when 0..3
"smallint(#{limit})"
when 4..8
"int(#{limit})"
when 9..20
"bigint(#{limit})"
else
'int(11)'
when 1; 'tinyint'
when 2; 'smallint'
when 3; 'mediumint'
when nil, 4, 11; 'int(11)' # compatibility with MySQL default
when 5..8; 'bigint'
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
end
end
@@ -525,6 +547,13 @@ module ActiveRecord
def version
@version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
end
def column_for(table_name, column_name)
unless column = columns(table_name).find { |c| c.name == column_name.to_s }
raise "No such column: #{table_name}.#{column_name}"
end
column
end
end
end
end

View File

@@ -23,8 +23,8 @@ module ActiveRecord
config = config.symbolize_keys
host = config[:host]
port = config[:port] || 5432
username = config[:username].to_s
password = config[:password].to_s
username = config[:username].to_s if config[:username]
password = config[:password].to_s if config[:password]
if config.has_key?(:database)
database = config[:database]
@@ -47,6 +47,14 @@ module ActiveRecord
end
private
def extract_limit(sql_type)
case sql_type
when /^bigint/i; 8
when /^smallint/i; 2
else super
end
end
# Extracts the scale from PostgreSQL-specific data types.
def extract_scale(sql_type)
# Money type has a fixed scale of 2.
@@ -174,8 +182,8 @@ module ActiveRecord
def self.extract_value_from_default(default)
case default
# Numeric types
when /\A-?\d+(\.\d*)?\z/
default
when /\A\(?(-?\d+(\.\d*)?\)?)\z/
$1
# Character types
when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
$1
@@ -319,6 +327,10 @@ module ActiveRecord
has_support
end
def supports_insert_with_returning?
postgresql_version >= 80200
end
# Returns the configured supported identifier length supported by PostgreSQL,
# or report the default of 63 on PostgreSQL 7.x.
def table_alias_length
@@ -360,7 +372,7 @@ module ActiveRecord
# There are some incorrectly compiled postgres drivers out there
# that don't define PGconn.escape.
self.class.instance_eval do
undef_method(:quote_string)
remove_method(:quote_string)
end
end
quote_string(s)
@@ -411,8 +423,34 @@ module ActiveRecord
# Executes an INSERT query and returns the new record's ID
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
# Extract the table from the insert sql. Yuck.
table = sql.split(" ", 4)[2].gsub('"', '')
super || pk && last_insert_id(table, sequence_name || default_sequence_name(table, pk))
# Try an insert with 'returning id' if available (PG >= 8.2)
if supports_insert_with_returning?
pk, sequence_name = *pk_and_sequence_for(table) unless pk
if pk
id = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
clear_query_cache
return id
end
end
# Otherwise, insert then grab last_insert_id.
if insert_id = super
insert_id
else
# If neither pk nor sequence name is given, look them up.
unless pk || sequence_name
pk, sequence_name = *pk_and_sequence_for(table)
end
# If a pk is given, fallback to default sequence name.
# Don't fetch last insert id for a table without a pk.
if pk && sequence_name ||= default_sequence_name(table, pk)
last_insert_id(table, sequence_name)
end
end
end
# create a 2D array representing the result set
@@ -492,13 +530,13 @@ module ActiveRecord
option_string = options.symbolize_keys.sum do |key, value|
case key
when :owner
" OWNER = '#{value}'"
" OWNER = \"#{value}\""
when :template
" TEMPLATE = #{value}"
" TEMPLATE = \"#{value}\""
when :encoding
" ENCODING = '#{value}'"
when :tablespace
" TABLESPACE = #{value}"
" TABLESPACE = \"#{value}\""
when :connection_limit
" CONNECTION LIMIT = #{value}"
else
@@ -506,7 +544,7 @@ module ActiveRecord
end
end
execute "CREATE DATABASE #{name}#{option_string}"
execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
end
# Drops a PostgreSQL database
@@ -514,7 +552,15 @@ module ActiveRecord
# Example:
# drop_database 'matt_development'
def drop_database(name) #:nodoc:
execute "DROP DATABASE IF EXISTS #{name}"
if postgresql_version >= 80200
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
else
begin
execute "DROP DATABASE #{quote_table_name(name)}"
rescue ActiveRecord::StatementInvalid
@logger.warn "#{name} database doesn't exist." if @logger
end
end
end
@@ -676,7 +722,7 @@ module ActiveRecord
# Renames a table.
def rename_table(name, new_name)
execute "ALTER TABLE #{name} RENAME TO #{new_name}"
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
end
# Adds a new column to the named table.
@@ -698,7 +744,8 @@ module ActiveRecord
begin
execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
rescue ActiveRecord::StatementInvalid
rescue ActiveRecord::StatementInvalid => e
raise e if postgresql_version > 80000
# This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
begin
begin_db_transaction
@@ -743,15 +790,14 @@ module ActiveRecord
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
return super unless type.to_s == 'integer'
if limit.nil? || limit == 4
'integer'
elsif limit < 4
'smallint'
else
'bigint'
case limit
when 1..2; 'smallint'
when 3..4, nil; 'integer'
when 5..8; 'bigint'
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
end
end
# Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
#
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and

View File

@@ -238,6 +238,15 @@ module ActiveRecord
end
end
def change_column_null(table_name, column_name, null, default = nil)
unless null || default.nil?
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
end
alter_table(table_name) do |definition|
definition[column_name].null = null
end
end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
alter_table(table_name) do |definition|
include_default = options_include_default?(options)
@@ -251,6 +260,9 @@ module ActiveRecord
end
def rename_column(table_name, column_name, new_column_name) #:nodoc:
unless columns(table_name).detect{|c| c.name == column_name.to_s }
raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
end
alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
end

View File

@@ -123,7 +123,10 @@ module ActiveRecord
attr = attr.to_s
# The attribute already has an unsaved change.
unless changed_attributes.include?(attr)
if changed_attributes.include?(attr)
old = changed_attributes[attr]
changed_attributes.delete(attr) unless field_changed?(attr, old, value)
else
old = clone_attribute_value(:read_attribute, attr)
changed_attributes[attr] = old if field_changed?(attr, old, value)
end
@@ -134,7 +137,9 @@ module ActiveRecord
def update_with_dirty
if partial_updates?
update_without_dirty(changed)
# Serialized attributes should always be written in case they've been
# changed in place.
update_without_dirty(changed | self.class.serialized_attributes.keys)
else
update_without_dirty
end
@@ -142,9 +147,11 @@ module ActiveRecord
def field_changed?(attr, old, value)
if column = column_for_attribute(attr)
if column.type == :integer && column.null && old.nil?
if column.type == :integer && column.null && (old.nil? || old == 0)
# For nullable integer 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?
else
value = column.type_cast(value)

View File

@@ -68,6 +68,7 @@ module ActiveRecord
def update_with_lock(attribute_names = @attributes.keys) #:nodoc:
return update_without_lock(attribute_names) unless locking_enabled?
return 0 if attribute_names.empty?
lock_col = self.class.locking_column
previous_value = send(lock_col).to_i

View File

@@ -238,6 +238,22 @@ module ActiveRecord
# lower than the current schema version: when migrating up, those
# never-applied "interleaved" migrations will be automatically applied, and
# when migrating down, never-applied "interleaved" migrations will be skipped.
#
# == Timestamped Migrations
#
# By default, Rails generates migrations that look like:
#
# 20080717013526_your_migration_name.rb
#
# The prefix is a generation timestamp (in UTC).
#
# If you'd prefer to use numeric prefixes, you can turn timestamped migrations
# off by setting:
#
# config.active_record.timestamped_migrations = false
#
# In environment.rb.
#
class Migration
@@verbose = true
cattr_accessor :verbose
@@ -369,11 +385,17 @@ module ActiveRecord
Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
end
def get_all_versions
Base.connection.select_values("SELECT version FROM #{schema_migrations_table_name}").map(&:to_i).sort
end
def current_version
version = Base.connection.select_values(
"SELECT version FROM #{schema_migrations_table_name}"
).map(&:to_i).max rescue nil
version || 0
sm_table = schema_migrations_table_name
if Base.connection.table_exists?(sm_table)
get_all_versions.max || 0
else
0
end
end
def proper_table_name(name)
@@ -389,7 +411,7 @@ module ActiveRecord
end
def current_version
self.class.current_version
migrated.last || 0
end
def current_migration
@@ -399,7 +421,10 @@ module ActiveRecord
def run
target = migrations.detect { |m| m.version == @target_version }
raise UnknownMigrationVersionError.new(@target_version) if target.nil?
target.migrate(@direction)
unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
target.migrate(@direction)
record_version_state_after_migrating(target.version)
end
end
def migrate
@@ -470,17 +495,19 @@ module ActiveRecord
end
def migrated
sm_table = self.class.schema_migrations_table_name
Base.connection.select_values("SELECT version FROM #{sm_table}").map(&:to_i).sort
@migrated_versions ||= self.class.get_all_versions
end
private
def record_version_state_after_migrating(version)
sm_table = self.class.schema_migrations_table_name
@migrated_versions ||= []
if down?
@migrated_versions.delete(version.to_i)
Base.connection.update("DELETE FROM #{sm_table} WHERE version = '#{version}'")
else
@migrated_versions.push(version.to_i).sort!
Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')")
end
end

View File

@@ -82,6 +82,7 @@ module ActiveRecord
# expected_options = { :conditions => { :colored => 'red' } }
# assert_equal expected_options, Shirt.colored('red').proxy_options
def named_scope(name, options = {}, &block)
name = name.to_sym
scopes[name] = lambda do |parent_scope, *args|
Scope.new(parent_scope, case options
when Hash
@@ -102,7 +103,7 @@ module ActiveRecord
attr_reader :proxy_scope, :proxy_options
[].methods.each do |m|
unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?)/
unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|^find$|count|sum|average|maximum|minimum|paginate|first|last|empty\?|respond_to\?)/
delegate m, :to => :proxy_found
end
end
@@ -139,6 +140,10 @@ module ActiveRecord
@found ? @found.empty? : count.zero?
end
def respond_to?(method, include_private = false)
super || @proxy_scope.respond_to?(method, include_private)
end
protected
def proxy_found
@found || load_found

View File

@@ -20,7 +20,7 @@ module ActiveRecord
# ActiveRecord::Base.observers = Cacher, GarbageCollector
#
# Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is
# called during startup, and before each development request.
# called during startup, and before each development request.
def observers=(*observers)
@observers = observers.flatten
end
@@ -130,11 +130,11 @@ module ActiveRecord
# Observers register themselves in the model class they observe, since it is the class that
# notifies them of events when they occur. As a side-effect, when an observer is loaded its
# corresponding model class is loaded.
#
#
# Up to (and including) Rails 2.0.2 observers were instantiated between plugins and
# application initializers. Now observers are loaded after application initializers,
# application initializers. Now observers are loaded after application initializers,
# so observed models can make use of extensions.
#
#
# If by any chance you are using observed models in the initialization you can still
# load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are
# singletons and that call instantiates and registers them.
@@ -189,7 +189,9 @@ module ActiveRecord
def add_observer!(klass)
klass.add_observer(self)
klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find)
if respond_to?(:after_find) && !klass.method_defined?(:after_find)
klass.class_eval 'def after_find() end'
end
end
end
end

View File

@@ -22,11 +22,22 @@ module ActiveRecord
end
end
def assert_queries(num = 1)
$query_count = 0
def assert_sql(*patterns_to_match)
$queries_executed = []
yield
ensure
assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
failed_patterns = []
patterns_to_match.each do |pattern|
failed_patterns << pattern unless $queries_executed.any?{ |sql| pattern === sql }
end
assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found."
end
def assert_queries(num = 1)
$queries_executed = []
yield
ensure
assert_equal num, $queries_executed.size, "#{$queries_executed.size} instead of #{num} queries were executed."
end
def assert_no_queries(&block)

View File

@@ -480,8 +480,9 @@ module ActiveRecord
# validates_length_of :fax, :in => 7..32, :allow_nil => true
# validates_length_of :phone, :in => 7..32, :allow_blank => true
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
# validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
# validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
# validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least %d character"
# validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with %d characters... don't play me."
# validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %d words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
# end
#
# Configuration options:
@@ -492,7 +493,6 @@ module ActiveRecord
# * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
#
# * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)").
# * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)").
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)").
@@ -504,12 +504,16 @@ module ActiveRecord
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to
# count words as in above example.)
# Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
def validates_length_of(*attrs)
# Merge given options with defaults.
options = {
:too_long => ActiveRecord::Errors.default_error_messages[:too_long],
:too_short => ActiveRecord::Errors.default_error_messages[:too_short],
:wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
:wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length],
:tokenizer => lambda {|value| value.split(//)}
}.merge(DEFAULT_VALIDATION_OPTIONS)
options.update(attrs.extract_options!.symbolize_keys)
@@ -536,7 +540,7 @@ module ActiveRecord
too_long = options[:too_long] % option_value.end
validates_each(attrs, options) do |record, attr, value|
value = value.split(//) if value.kind_of?(String)
value = options[:tokenizer].call(value) if value.kind_of?(String)
if value.nil? or value.size < option_value.begin
record.errors.add(attr, too_short)
elsif value.size > option_value.end
@@ -553,7 +557,7 @@ module ActiveRecord
message = (options[:message] || options[message_options[option]]) % option_value
validates_each(attrs, options) do |record, attr, value|
value = value.split(//) if value.kind_of?(String)
value = options[:tokenizer].call(value) if value.kind_of?(String)
record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
end
end
@@ -614,14 +618,20 @@ module ActiveRecord
# class (which has a database table to query from).
finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }
if value.nil? || (configuration[:case_sensitive] || !finder_class.columns_hash[attr_name.to_s].text?)
is_text_column = finder_class.columns_hash[attr_name.to_s].text?
if !value.nil? && is_text_column
value = value.to_s
end
if value.nil? || (configuration[:case_sensitive] || !is_text_column)
condition_sql = "#{record.class.quoted_table_name}.#{attr_name} #{attribute_condition(value)}"
condition_params = [value]
else
# sqlite has case sensitive SELECT query, while MySQL/Postgresql don't.
# Hence, this is needed only for sqlite.
condition_sql = "LOWER(#{record.class.quoted_table_name}.#{attr_name}) #{attribute_condition(value)}"
condition_params = [value.downcase]
condition_params = [value.chars.downcase]
end
if scope = configuration[:scope]
@@ -851,7 +861,7 @@ module ActiveRecord
raw_value = raw_value.to_i
else
begin
raw_value = Kernel.Float(raw_value.to_s)
raw_value = Kernel.Float(raw_value)
rescue ArgumentError, TypeError
record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
next

View File

@@ -1,362 +0,0 @@
require 'db2/db2cli.rb'
module DB2
module DB2Util
include DB2CLI
def free() SQLFreeHandle(@handle_type, @handle); end
def handle() @handle; end
def check_rc(rc)
if ![SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_NO_DATA_FOUND].include?(rc)
rec = 1
msg = ''
loop do
a = SQLGetDiagRec(@handle_type, @handle, rec, 500)
break if a[0] != SQL_SUCCESS
msg << a[3] if !a[3].nil? and a[3] != '' # Create message.
rec += 1
end
raise "DB2 error: #{msg}"
end
end
end
class Environment
include DB2Util
def initialize
@handle_type = SQL_HANDLE_ENV
rc, @handle = SQLAllocHandle(@handle_type, SQL_NULL_HANDLE)
check_rc(rc)
end
def data_sources(buffer_length = 1024)
retval = []
max_buffer_length = buffer_length
a = SQLDataSources(@handle, SQL_FETCH_FIRST, SQL_MAX_DSN_LENGTH + 1, buffer_length)
retval << [a[1], a[3]]
max_buffer_length = [max_buffer_length, a[4]].max
loop do
a = SQLDataSources(@handle, SQL_FETCH_NEXT, SQL_MAX_DSN_LENGTH + 1, buffer_length)
break if a[0] == SQL_NO_DATA_FOUND
retval << [a[1], a[3]]
max_buffer_length = [max_buffer_length, a[4]].max
end
if max_buffer_length > buffer_length
get_data_sources(max_buffer_length)
else
retval
end
end
end
class Connection
include DB2Util
def initialize(environment)
@env = environment
@handle_type = SQL_HANDLE_DBC
rc, @handle = SQLAllocHandle(@handle_type, @env.handle)
check_rc(rc)
end
def connect(server_name, user_name = '', auth = '')
check_rc(SQLConnect(@handle, server_name, user_name.to_s, auth.to_s))
end
def set_connect_attr(attr, value)
value += "\0" if value.class == String
check_rc(SQLSetConnectAttr(@handle, attr, value))
end
def set_auto_commit_on
set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_ON)
end
def set_auto_commit_off
set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF)
end
def disconnect
check_rc(SQLDisconnect(@handle))
end
def rollback
check_rc(SQLEndTran(@handle_type, @handle, SQL_ROLLBACK))
end
def commit
check_rc(SQLEndTran(@handle_type, @handle, SQL_COMMIT))
end
end
class Statement
include DB2Util
def initialize(connection)
@conn = connection
@handle_type = SQL_HANDLE_STMT
@parms = [] #yun
@sql = '' #yun
@numParms = 0 #yun
@prepared = false #yun
@parmArray = [] #yun. attributes of the parameter markers
rc, @handle = SQLAllocHandle(@handle_type, @conn.handle)
check_rc(rc)
end
def columns(table_name, schema_name = '%')
check_rc(SQLColumns(@handle, '', schema_name.upcase, table_name.upcase, '%'))
fetch_all
end
def tables(schema_name = '%')
check_rc(SQLTables(@handle, '', schema_name.upcase, '%', 'TABLE'))
fetch_all
end
def indexes(table_name, schema_name = '')
check_rc(SQLStatistics(@handle, '', schema_name.upcase, table_name.upcase, SQL_INDEX_ALL, SQL_ENSURE))
fetch_all
end
def prepare(sql)
@sql = sql
check_rc(SQLPrepare(@handle, sql))
rc, @numParms = SQLNumParams(@handle) #number of question marks
check_rc(rc)
#--------------------------------------------------------------------------
# parameter attributes are stored in instance variable @parmArray so that
# they are available when execute method is called.
#--------------------------------------------------------------------------
if @numParms > 0 # get parameter marker attributes
1.upto(@numParms) do |i| # parameter number starts from 1
rc, type, size, decimalDigits = SQLDescribeParam(@handle, i)
check_rc(rc)
@parmArray << Parameter.new(type, size, decimalDigits)
end
end
@prepared = true
self
end
def execute(*parms)
raise "The statement was not prepared" if @prepared == false
if parms.size == 1 and parms[0].class == Array
parms = parms[0]
end
if @numParms != parms.size
raise "Number of parameters supplied does not match with the SQL statement"
end
if @numParms > 0 #need to bind parameters
#--------------------------------------------------------------------
#calling bindParms may not be safe. Look comment below.
#--------------------------------------------------------------------
#bindParms(parms)
valueArray = []
1.upto(@numParms) do |i| # parameter number starts from 1
type = @parmArray[i - 1].class
size = @parmArray[i - 1].size
decimalDigits = @parmArray[i - 1].decimalDigits
if parms[i - 1].class == String
valueArray << parms[i - 1]
else
valueArray << parms[i - 1].to_s
end
rc = SQLBindParameter(@handle, i, type, size, decimalDigits, valueArray[i - 1])
check_rc(rc)
end
end
check_rc(SQLExecute(@handle))
if @numParms != 0
check_rc(SQLFreeStmt(@handle, SQL_RESET_PARAMS)) # Reset parameters
end
self
end
#-------------------------------------------------------------------------------
# The last argument(value) to SQLBindParameter is a deferred argument, that is,
# it should be available when SQLExecute is called. Even though "value" is
# local to bindParms method, it seems that it is available when SQLExecute
# is called. I am not sure whether it would still work if garbage collection
# is done between bindParms call and SQLExecute call inside the execute method
# above.
#-------------------------------------------------------------------------------
def bindParms(parms) # This is the real thing. It uses SQLBindParms
1.upto(@numParms) do |i| # parameter number starts from 1
rc, dataType, parmSize, decimalDigits = SQLDescribeParam(@handle, i)
check_rc(rc)
if parms[i - 1].class == String
value = parms[i - 1]
else
value = parms[i - 1].to_s
end
rc = SQLBindParameter(@handle, i, dataType, parmSize, decimalDigits, value)
check_rc(rc)
end
end
#------------------------------------------------------------------------------
# bind method does not use DB2's SQLBindParams, but replaces "?" in the
# SQL statement with the value before passing the SQL statement to DB2.
# It is not efficient and can handle only strings since it puts everything in
# quotes.
#------------------------------------------------------------------------------
def bind(sql, args) #does not use SQLBindParams
arg_index = 0
result = ""
tokens(sql).each do |part|
case part
when '?'
result << "'" + (args[arg_index]) + "'" #put it into quotes
arg_index += 1
when '??'
result << "?"
else
result << part
end
end
if arg_index < args.size
raise "Too many SQL parameters"
elsif arg_index > args.size
raise "Not enough SQL parameters"
end
result
end
## Break the sql string into parts.
#
# This is NOT a full lexer for SQL. It just breaks up the SQL
# string enough so that question marks, double question marks and
# quoted strings are separated. This is used when binding
# arguments to "?" in the SQL string. Note: comments are not
# handled.
#
def tokens(sql)
toks = sql.scan(/('([^'\\]|''|\\.)*'|"([^"\\]|""|\\.)*"|\?\??|[^'"?]+)/)
toks.collect { |t| t[0] }
end
def exec_direct(sql)
check_rc(SQLExecDirect(@handle, sql))
self
end
def set_cursor_name(name)
check_rc(SQLSetCursorName(@handle, name))
self
end
def get_cursor_name
rc, name = SQLGetCursorName(@handle)
check_rc(rc)
name
end
def row_count
rc, rowcount = SQLRowCount(@handle)
check_rc(rc)
rowcount
end
def num_result_cols
rc, cols = SQLNumResultCols(@handle)
check_rc(rc)
cols
end
def fetch_all
if block_given?
while row = fetch do
yield row
end
else
res = []
while row = fetch do
res << row
end
res
end
end
def fetch
cols = get_col_desc
rc = SQLFetch(@handle)
if rc == SQL_NO_DATA_FOUND
SQLFreeStmt(@handle, SQL_CLOSE) # Close cursor
SQLFreeStmt(@handle, SQL_RESET_PARAMS) # Reset parameters
return nil
end
raise "ERROR" unless rc == SQL_SUCCESS
retval = []
cols.each_with_index do |c, i|
rc, content = SQLGetData(@handle, i + 1, c[1], c[2] + 1) #yun added 1 to c[2]
retval << adjust_content(content)
end
retval
end
def fetch_as_hash
cols = get_col_desc
rc = SQLFetch(@handle)
if rc == SQL_NO_DATA_FOUND
SQLFreeStmt(@handle, SQL_CLOSE) # Close cursor
SQLFreeStmt(@handle, SQL_RESET_PARAMS) # Reset parameters
return nil
end
raise "ERROR" unless rc == SQL_SUCCESS
retval = {}
cols.each_with_index do |c, i|
rc, content = SQLGetData(@handle, i + 1, c[1], c[2] + 1) #yun added 1 to c[2]
retval[c[0]] = adjust_content(content)
end
retval
end
def get_col_desc
rc, nr_cols = SQLNumResultCols(@handle)
cols = (1..nr_cols).collect do |c|
rc, name, bl, type, col_sz = SQLDescribeCol(@handle, c, 1024)
[name.downcase, type, col_sz]
end
end
def adjust_content(c)
case c.class.to_s
when 'DB2CLI::NullClass'
return nil
when 'DB2CLI::Time'
"%02d:%02d:%02d" % [c.hour, c.minute, c.second]
when 'DB2CLI::Date'
"%04d-%02d-%02d" % [c.year, c.month, c.day]
when 'DB2CLI::Timestamp'
"%04d-%02d-%02d %02d:%02d:%02d" % [c.year, c.month, c.day, c.hour, c.minute, c.second]
else
return c
end
end
end
class Parameter
attr_reader :type, :size, :decimalDigits
def initialize(type, size, decimalDigits)
@type, @size, @decimalDigits = type, size, decimalDigits
end
end
end

View File

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

View File

@@ -13,8 +13,8 @@ class PostgresqlActiveSchemaTest < Test::Unit::TestCase
end
def test_create_database_with_encoding
assert_equal "CREATE DATABASE matt ENCODING = 'utf8'", create_database(:matt)
assert_equal "CREATE DATABASE aimonetti ENCODING = 'latin1'", create_database(:aimonetti, :encoding => :latin1)
assert_equal %(CREATE DATABASE "matt" ENCODING = 'utf8'), create_database(:matt)
assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1)
end
private

View File

@@ -118,7 +118,7 @@ class AdapterTest < ActiveRecord::TestCase
sql_inject = "1, 7 procedure help()"
if current_adapter?(:MysqlAdapter)
assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject)
assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=> '1 ; DROP TABLE USERS', :offset=>7)
else
assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject)
assert_equal " LIMIT 1,7 OFFSET 7", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)

View File

@@ -409,4 +409,23 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
sponsor.sponsorable = new_member
assert_equal nil, sponsor.sponsorable_id
end
def test_save_fails_for_invalid_belongs_to
assert log = AuditLog.create(:developer_id=>0,:message=>"")
log.developer = Developer.new
assert !log.developer.valid?
assert !log.valid?
assert !log.save
assert_equal "is invalid", log.errors.on("developer")
end
def test_save_succeeds_for_invalid_belongs_to_with_validate_false
assert log = AuditLog.create(:developer_id=>0,:message=>"")
log.unvalidated_developer = Developer.new
assert !log.unvalidated_developer.valid?
assert log.valid?
assert log.save
end
end

View File

@@ -9,7 +9,7 @@ require 'models/topic'
require 'models/reply'
class CascadedEagerLoadingTest < ActiveRecord::TestCase
fixtures :authors, :mixins, :companies, :posts, :topics
fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments, :categorizations
def test_eager_association_loading_with_cascaded_two_levels
authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id")
@@ -68,6 +68,18 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
end
def test_eager_association_loading_with_has_many_sti_and_subclasses
silly = SillyReply.new(:title => "gaga", :content => "boo-boo", :parent_id => 1)
silly.parent_id = 1
assert silly.save
topics = Topic.find(:all, :include => :replies, :order => 'topics.id, replies_topics.id')
assert_no_queries do
assert_equal 2, topics[0].replies.size
assert_equal 0, topics[1].replies.size
end
end
def test_eager_association_loading_with_belongs_to_sti
replies = Reply.find(:all, :include => :topic, :order => 'topics.id')
assert replies.include?(topics(:second))

View File

@@ -0,0 +1,36 @@
require 'cases/helper'
require 'models/post'
require 'models/tagging'
module Namespaced
class Post < ActiveRecord::Base
set_table_name 'posts'
has_one :tagging, :as => :taggable, :class_name => 'Tagging'
end
end
class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase
def setup
generate_test_objects
end
def generate_test_objects
post = Namespaced::Post.create( :title => 'Great stuff', :body => 'This is not', :author_id => 1 )
tagging = Tagging.create( :taggable => post )
end
def test_class_names
old = ActiveRecord::Base.store_full_sti_class
ActiveRecord::Base.store_full_sti_class = false
post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging )
assert_nil post.tagging
ActiveRecord::Base.store_full_sti_class = true
post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging )
assert_equal 'Tagging', post.tagging.class.name
ensure
ActiveRecord::Base.store_full_sti_class = old
end
end

View File

@@ -14,11 +14,14 @@ require 'models/job'
require 'models/subscriber'
require 'models/subscription'
require 'models/book'
require 'models/developer'
require 'models/project'
class EagerAssociationTest < ActiveRecord::TestCase
fixtures :posts, :comments, :authors, :categories, :categories_posts,
:companies, :accounts, :tags, :taggings, :people, :readers,
:owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books
:owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books,
:developers, :projects, :developers_projects
def test_loading_with_one_association
posts = Post.find(:all, :include => :comments)
@@ -35,6 +38,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal Post.find(1).last_comment, post.last_comment
end
def test_loading_with_one_association_with_non_preload
posts = Post.find(:all, :include => :last_comment, :order => 'comments.id DESC')
post = posts.find { |p| p.id == 1 }
assert_equal Post.find(1).last_comment, post.last_comment
end
def test_loading_conditions_with_or
posts = authors(:david).posts.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'")
assert_nil posts.detect { |p| p.author_id != authors(:david).id },
@@ -556,6 +565,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_nothing_raised { Post.find(:all, :include => 'comments') }
end
def test_eager_with_floating_point_numbers
assert_queries(2) do
# Before changes, the floating point numbers will be interpreted as table names and will cause this to run in one query
Comment.find :all, :conditions => "123.456 = 123.456", :include => :post
end
end
def test_preconfigured_includes_with_belongs_to
author = posts(:welcome).author_with_posts
assert_no_queries {assert_equal 5, author.posts.size}
@@ -609,4 +625,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
Comment.find :all, :include => :post
end
end
def test_conditions_on_join_table_with_include_and_limit
assert_equal 3, Developer.find(:all, :include => 'projects', :conditions => 'developers_projects.access_level = 1', :limit => 5).size
end
def test_order_on_join_table_with_include_and_limit
assert_equal 5, Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size
end
end

View File

@@ -70,7 +70,7 @@ end
class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,
:parrots, :pirates, :treasures, :price_estimates
:parrots, :pirates, :treasures, :price_estimates, :tags, :taggings
def test_has_and_belongs_to_many
david = Developer.find(1)
@@ -299,6 +299,17 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 3, projects(:active_record, :reload).developers.size
end
def test_uniq_option_prevents_duplicate_push
project = projects(:active_record)
project.developers << developers(:jamis)
project.developers << developers(:david)
assert_equal 3, project.developers.size
project.developers << developers(:david)
project.developers << developers(:jamis)
assert_equal 3, project.developers.size
end
def test_deleting
david = Developer.find(1)
active_record = Project.find(1)
@@ -439,6 +450,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal developers(:david), active_record.developers_with_finder_sql.find(developers(:david).id), "Ruby find"
end
def test_find_in_association_with_custom_finder_sql_and_multiple_interpolations
# interpolate once:
assert_equal [developers(:david), developers(:jamis), developers(:poor_jamis)], projects(:active_record).developers_with_finder_sql, "first interpolation"
# interpolate again, for a different project id
assert_equal [developers(:david)], projects(:action_controller).developers_with_finder_sql, "second interpolation"
end
def test_find_in_association_with_custom_finder_sql_and_string_id
assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id.to_s), "SQL find"
end
@@ -629,8 +647,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
developer.save
developer.reload
assert_equal 2, developer.projects.length
assert_equal projects(:active_record), developer.projects[0]
assert_equal projects(:action_controller), developer.projects[1]
assert_equal [projects(:active_record), projects(:action_controller)].map(&:id).sort, developer.project_ids.sort
end
def test_assign_ids_ignoring_blanks
@@ -639,8 +656,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
developer.save
developer.reload
assert_equal 2, developer.projects.length
assert_equal projects(:active_record), developer.projects[0]
assert_equal projects(:action_controller), developer.projects[1]
assert_equal [projects(:active_record), projects(:action_controller)].map(&:id).sort, developer.project_ids.sort
end
def test_select_limited_ids_list
@@ -681,4 +697,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal developer, project.developers.find(:first)
assert_equal project, developer.projects.find(:first)
end
def test_dynamic_find_should_respect_association_include
# SQL error in sort clause if :include is not included
# due to Unknown column 'authors.id'
assert Category.find(1).posts_with_authors_sorted_by_author_id.find_by_title('Welcome to the weblog')
end
end

View File

@@ -14,7 +14,7 @@ require 'models/reader'
class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :comments, :author_addresses,
:people, :posts
:people, :posts, :readers
def setup
Client.destroyed_client_ids.clear
@@ -37,15 +37,21 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_counting_with_single_conditions
assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => '1=1')
assert_equal 1, Firm.find(:first).plain_clients.count(:conditions => ['name=?', "Microsoft"])
end
def test_counting_with_single_hash
assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => '1=1')
assert_equal 1, Firm.find(:first).plain_clients.count(:conditions => {:name => "Microsoft"})
end
def test_counting_with_column_name_and_hash
assert_equal 2, Firm.find(:first).plain_clients.count(:all, :conditions => '1=1')
assert_equal 2, Firm.find(:first).plain_clients.count(:name)
end
def test_counting_with_association_limit
firm = companies(:first_firm)
assert_equal firm.limited_clients.length, firm.limited_clients.size
assert_equal firm.limited_clients.length, firm.limited_clients.count
end
def test_finding
@@ -342,6 +348,34 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert new_firm.new_record?
end
def test_invalid_adding_with_validate_false
firm = Firm.find(:first)
client = Client.new
firm.unvalidated_clients_of_firm << client
assert firm.valid?
assert !client.valid?
assert firm.save
assert client.new_record?
end
def test_valid_adding_with_validate_false
no_of_clients = Client.count
firm = Firm.find(:first)
client = Client.new("name" => "Apple")
assert firm.valid?
assert client.valid?
assert client.new_record?
firm.unvalidated_clients_of_firm << client
assert firm.save
assert !client.new_record?
assert_equal no_of_clients+1, Client.count
end
def test_build
company = companies(:first_firm)
new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
@@ -356,6 +390,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, company.clients_of_firm(true).size
end
def test_collection_size_after_building
company = companies(:first_firm) # company already has one client
company.clients_of_firm.build("name" => "Another Client")
company.clients_of_firm.build("name" => "Yet Another Client")
assert_equal 3, company.clients_of_firm.size
end
def test_collection_size_twice_for_regressions
post = posts(:thinking)
assert_equal 0, post.readers.size
# This test needs a post that has no readers, we assert it to ensure it holds,
# but need to reload the post because the very call to #size hides the bug.
post.reload
post.readers.build
size1 = post.readers.size
size2 = post.readers.size
assert_equal size1, size2
end
def test_build_many
company = companies(:first_firm)
new_clients = assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
@@ -386,6 +439,37 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, first_topic.replies.to_ary.size
end
def test_build_via_block
company = companies(:first_firm)
new_client = assert_no_queries { company.clients_of_firm.build {|client| client.name = "Another Client" } }
assert !company.clients_of_firm.loaded?
assert_equal "Another Client", new_client.name
assert new_client.new_record?
assert_equal new_client, company.clients_of_firm.last
company.name += '-changed'
assert_queries(2) { assert company.save }
assert !new_client.new_record?
assert_equal 2, company.clients_of_firm(true).size
end
def test_build_many_via_block
company = companies(:first_firm)
new_clients = assert_no_queries do
company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client|
client.name = "changed"
end
end
assert_equal 2, new_clients.size
assert_equal "changed", new_clients.first.name
assert_equal "changed", new_clients.last.name
company.name += '-changed'
assert_queries(3) { assert company.save }
assert_equal 3, company.clients_of_firm(true).size
end
def test_create_without_loading_association
first_firm = companies(:first_firm)
Firm.column_names
@@ -929,4 +1013,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert firm.clients.loaded?
end
def test_joins_with_namespaced_model_should_use_correct_type
old = ActiveRecord::Base.store_full_sti_class
ActiveRecord::Base.store_full_sti_class = true
firm = Namespaced::Firm.create({ :name => 'Some Company' })
firm.clients.create({ :name => 'Some Client' })
stats = Namespaced::Firm.find(firm.id, {
:select => "#{Namespaced::Firm.table_name}.id, COUNT(#{Namespaced::Client.table_name}.id) AS num_clients",
:joins => :clients,
:group => "#{Namespaced::Firm.table_name}.id"
})
assert_equal 1, stats.num_clients.to_i
ensure
ActiveRecord::Base.store_full_sti_class = old
end
end

View File

@@ -187,4 +187,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
post.people_with_callbacks.clear
assert_equal (%w(Michael David Julian Roger) * 2).sort, log.last(8).collect(&:last).sort
end
def test_dynamic_find_should_respect_association_include
# SQL error in sort clause if :include is not included
# due to Unknown column 'comments.id'
assert Person.find(1).posts_with_comments_sorted_by_comment_id.find_by_title('Welcome to the weblog')
end
def test_count_with_include_should_alias_join_table
assert_equal 2, people(:michael).posts.count(:include => :readers)
end
end

View File

@@ -72,6 +72,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordNotFound) { Account.find(old_account_id) }
end
def test_natural_assignment_to_already_associated_record
company = companies(:first_firm)
account = accounts(:signals37)
assert_equal company.account, account
company.account = account
company.reload
account.reload
assert_equal company.account, account
end
def test_assignment_without_replacement
apple = Firm.create("name" => "Apple")
citibank = Account.create("credit_limit" => 10)
@@ -275,6 +285,18 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal "is invalid", firm.errors.on("account")
end
def test_save_succeeds_for_invalid_has_one_with_validate_false
firm = Firm.find(:first)
assert firm.valid?
firm.unvalidated_account = Account.new
assert !firm.unvalidated_account.valid?
assert firm.valid?
assert firm.save
end
def test_assignment_before_either_saved
firm = Firm.new("name" => "GlobalMegaCorp")
firm.account = a = Account.new("credit_limit" => 1000)

View File

@@ -44,19 +44,23 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_polymorphic
assert_equal clubs(:moustache_club), @member.sponsor_club
end
def has_one_through_to_has_many
assert_equal 2, @member.fellow_members.size
end
def test_has_one_through_eager_loading
members = Member.find(:all, :include => :club, :conditions => ["name = ?", "Groucho Marx"])
members = assert_queries(3) do #base table, through table, clubs table
Member.find(:all, :include => :club, :conditions => ["name = ?", "Groucho Marx"])
end
assert_equal 1, members.size
assert_not_nil assert_no_queries {members[0].club}
end
def test_has_one_through_eager_loading_through_polymorphic
members = Member.find(:all, :include => :sponsor_club, :conditions => ["name = ?", "Groucho Marx"])
members = assert_queries(3) do #base table, through table, clubs table
Member.find(:all, :include => :sponsor_club, :conditions => ["name = ?", "Groucho Marx"])
end
assert_equal 1, members.size
assert_not_nil assert_no_queries {members[0].sponsor_club}
end
@@ -71,4 +75,39 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_not_nil assert_no_queries {clubs[0].sponsored_member}
end
def test_has_one_through_nonpreload_eagerloading
members = assert_queries(1) do
Member.find(:all, :include => :club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries {members[0].club}
end
def test_has_one_through_nonpreload_eager_loading_through_polymorphic
members = assert_queries(1) do
Member.find(:all, :include => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries {members[0].sponsor_club}
end
def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record
Sponsor.new(:sponsor_club => clubs(:crazy_club), :sponsorable => members(:groucho)).save!
members = assert_queries(1) do
Member.find(:all, :include => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name DESC') #force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries { members[0].sponsor_club }
assert_equal clubs(:crazy_club), members[0].sponsor_club
end
def test_uninitialized_has_one_through_should_return_nil_for_unsaved_record
assert_nil Member.new.club
end
def test_assigning_association_correctly_assigns_target
new_member = Member.create(:name => "Chris")
new_member.club = new_club = Club.create(:name => "LRUG")
assert_equal new_club, new_member.club.target
end
end

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