Compare commits

...

159 Commits

Author SHA1 Message Date
Michael Koziarski
83a21f75cf merge sybase changes to 1.2
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5840 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2007-01-05 00:16:54 +00:00
Rick Olson
e9a7b233f6 rollback [5833] and [5835]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5837 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2007-01-04 21:42:32 +00:00
Rick Olson
21e4825596 apply [5835] to 1.2 RC branch
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5836 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2007-01-04 21:31:48 +00:00
Rick Olson
622d70a11e apply [5833] to 1.2 RC branch
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5834 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2007-01-04 21:19:42 +00:00
Jamis Buck
c26cca3f45 Make sure html_document is reset between integration test requests
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5829 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2007-01-01 03:04:23 +00:00
Jeremy Kemper
b0303e1ac2 Merge [5826] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5827 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2007-01-01 01:36:48 +00:00
Jeremy Kemper
bdd09f50f8 Merge [5824] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5825 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2007-01-01 01:32:59 +00:00
Jeremy Kemper
6dc5756d39 Tighten new_session option check.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5822 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2007-01-01 01:27:55 +00:00
Jeremy Kemper
0120b226ba Merge [5820] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5821 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2007-01-01 01:14:02 +00:00
Jeremy Kemper
b6af22ec82 Missed additions from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5819 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2007-01-01 00:32:33 +00:00
Jeremy Kemper
4870becaeb Missed additions from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5818 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2007-01-01 00:30:20 +00:00
Jeremy Kemper
a68d1d35f8 Merge [5816] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5817 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2007-01-01 00:25:58 +00:00
Jeremy Kemper
a4e70d3e91 Merge [5814] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5815 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-31 23:54:28 +00:00
David Heinemeier Hansson
ba29222851 Make RDoc not spew errors on install because of HTML comments in code comments
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5813 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-31 19:28:28 +00:00
Nicholas Seckar
0ff4ede7ca Apply [5811] to RC
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5812 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-31 18:59:58 +00:00
Rick Olson
7269ee2da9 apply [5801] to 1.2 stable branch
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5802 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-27 22:19:36 +00:00
Michael Koziarski
59be73a1e7 Merge nil fix for error_message_on to RC, closes #6884
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5800 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-27 20:47:20 +00:00
Michael Koziarski
ce8e54e236 Apply doc fix to RC, closes #6522
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5798 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-27 20:42:51 +00:00
Michael Koziarski
155dcff431 Ensure dynamic finders are anchored to the beginning of the method name to prevent them from firing accidentally. [bradediger]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5796 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-27 20:39:48 +00:00
Rick Olson
df27fb873c apply [5790] to 1.2 stable branch
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5791 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-26 19:41:08 +00:00
David Heinemeier Hansson
4aa93358fa Apply [5740] from trunk
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5789 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-26 18:00:43 +00:00
Rick Olson
3054a742df apply [5785] to 1.2 branch
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5788 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-25 09:41:16 +00:00
Rick Olson
1fb2f21d1a apply [5784] to 1.2 pre-release
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5787 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-25 09:37:06 +00:00
Nicholas Seckar
da8a896d86 Apply [5781] and [5782] to RC
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5783 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-24 15:19:04 +00:00
Jeremy Kemper
debd9ea2ce Merge [5746] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5765 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-21 19:30:27 +00:00
Jeremy Kemper
8ec9f4d2a0 Merge [5763] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5764 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-20 21:29:43 +00:00
Jeremy Kemper
4dcdf0834e Merge [5761] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5762 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-20 21:15:19 +00:00
Jeremy Kemper
a451fd1fbf Merge [5759] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5760 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-20 18:40:46 +00:00
Jeremy Kemper
c7e2b03424 Merge [5757] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5758 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-19 23:17:43 +00:00
Jeremy Kemper
6ccbef5862 Merge [5755] from trunk. Closes #6514, closes #6743.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5756 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-19 20:27:35 +00:00
Jeremy Kemper
498bca8ae8 Merge [5753] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5754 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-19 19:50:19 +00:00
Jeremy Kemper
c3c7648d20 Merge [5749],[5750],[5751] from trunk. References #6840.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5752 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-19 19:28:19 +00:00
Rick Olson
fe2e51b047 apply [5744] to 1.2 pre-release
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5745 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-19 03:12:38 +00:00
Jeremy Kemper
67cda1f54e Rollback accidental commit from [5686].
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5743 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-18 23:19:24 +00:00
Michael Koziarski
cda3d89983 Merge plugin changes back to 1.2
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5739 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-18 20:30:27 +00:00
Jeremy Kemper
0ae462a82d Merge [5730] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5731 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-17 08:31:27 +00:00
Jeremy Kemper
d5ca9a5814 Merge [5728] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5729 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-17 08:24:06 +00:00
Michael Koziarski
72d556a391 Merge [5722] to release
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5723 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-16 02:26:01 +00:00
Michael Koziarski
34bc074441 merge plugin loading changes to 1.2 release
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5721 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-15 23:51:13 +00:00
Jeremy Kemper
6df67f5607 Merge [5715] from trunk. Closes #6699.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5716 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-12 22:28:21 +00:00
Nicholas Seckar
590b192e02 Apply [5710] to RC
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5712 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-10 21:14:21 +00:00
Jeremy Kemper
67ac59afc8 Merge [5694]-[5698] from trunk: respond_to json and render :json => ...
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5699 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-06 23:26:18 +00:00
Jeremy Kemper
3d5fb40bb0 Partially merge [5691] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5693 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-06 22:11:30 +00:00
Jeremy Kemper
0c483d0c8d Merge [5689] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5690 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-06 20:29:16 +00:00
Jeremy Kemper
8804b7aeb7 Include svn revision in changelogs.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5687 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-06 19:30:04 +00:00
Jeremy Kemper
2876efb784 Merge [5685] from trunk. Closes #3811.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5686 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-06 19:27:05 +00:00
Jeremy Kemper
762fc5447f Merge [5682] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5683 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-05 22:09:18 +00:00
David Heinemeier Hansson
bd261ffd1f Fixed script/process/spawner to work properly with Mongrel including in -r (daemonize mode) [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5672 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-04 03:50:57 +00:00
David Heinemeier Hansson
ffb17e89ef Dropped the idea of automatically routing :format for the vanilla routes -- that will be a treat for map.resources. Deprecated the name route root as it'll be used as a shortcut for map.connect '' in Rails 2.0 (Rails 1.2). Added map.root as an alias for map.connect '' (Rails 2.0)
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5671 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-04 00:12:00 +00:00
David Heinemeier Hansson
2a08c4547a Documentation for generators (closes #6671) [topfunky]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5669 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-03 16:53:16 +00:00
David Heinemeier Hansson
2b68762f5a Fixed Array#to_xml when it contains a series of hashes (each piece would get its own XML declaration) (closes #6610) [thkarcher/cyu]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5668 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-03 16:47:53 +00:00
David Heinemeier Hansson
de3ec6c008 Wups
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5662 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-01 23:02:48 +00:00
Jeremy Kemper
2890b9648e Merge [5660] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5661 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-01 21:34:22 +00:00
David Heinemeier Hansson
98a0440a56 If only life was that simple (it didnt help)
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5658 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-01 05:56:51 +00:00
David Heinemeier Hansson
f09c8c4529 Replace the elaborate reloading connection checking scheme, just fix the Ruby-based MySQL adapter, ye?
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5656 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-12-01 05:37:56 +00:00
David Heinemeier Hansson
25d7ea8ad2 Refactored to use same option setup
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5654 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-30 22:22:02 +00:00
David Heinemeier Hansson
91cd8890c7 Fixed that script/server running against Mongrel should tail the proper log regardless of the environment [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5652 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-30 21:45:31 +00:00
Jeremy Kemper
87ef365a49 Merge [5435] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5651 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-29 22:58:21 +00:00
Jeremy Kemper
d6c28bfaed Use head. Stop last @response.redirect_url from bleeding through (should be recycled like request is).
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5645 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-28 23:38:29 +00:00
Jeremy Kemper
b2ae346484 Merge [5643] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5644 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-28 23:26:25 +00:00
Michael Koziarski
fb6cd55de7 ensure follow_redirect works with normalized headers
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5641 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-27 22:52:58 +00:00
Michael Koziarski
c4d4660de3 Ensure integration tests get the correct value of fixtures_path. Closes #6672
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5639 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-27 05:14:05 +00:00
David Heinemeier Hansson
205ae50d3f Only reload connections in development mode that supports (and requires that) -- in other words, only do it for SQLite (closes #6687, #6700) [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5637 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-26 22:10:55 +00:00
Jeremy Kemper
5b8bfc40e0 Merge [5635] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5636 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-26 05:37:09 +00:00
David Heinemeier Hansson
f83eedd440 Fix that redirects should set "Location" header, not "location", and remove dead CGI.redirect
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5634 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-26 05:04:57 +00:00
David Heinemeier Hansson
691c439129 redirect_to is the one place where _url should be used, not _path [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5633 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-26 04:42:13 +00:00
Jeremy Kemper
8f3e81ed53 Merge [5631] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5632 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-26 01:24:57 +00:00
Jeremy Kemper
fd8ee0a253 Merge [5629] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5630 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-26 01:02:45 +00:00
David Heinemeier Hansson
509c920d35 Tried delaying database disconnect until after dependency resolution (references #6687, #6700) [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5627 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-25 19:59:00 +00:00
David Heinemeier Hansson
cef81e71eb Dont set default charset if the response is sending a file. Closes #6689 [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5626 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-25 19:29:10 +00:00
Nicholas Seckar
3ee809655e Apply [5624] to RC. References #6698.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5625 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-25 17:16:52 +00:00
David Heinemeier Hansson
c3cbd6b806 Fixed that HEAD should return the proper Content-Length header (that is, actually use @body.size, not just 0) [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5622 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-23 23:52:25 +00:00
David Heinemeier Hansson
fe1e771222 * Added GET-masquarading for HEAD, so request.method will return :get even for HEADs. This will help anyone relying on case request.method to automatically work with HEAD and map.resources will also allow HEADs to all GET actions. Rails automatically throws away the response content in a reply to HEAD, so you dont even need to worry about that. If you, for whatever reason, still need to distinguish between GET and HEAD in some edge case, you can use Request#head? and even Request.headers["REQUEST_METHOD"] for get the "real" answer. Closes #6694 [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5621 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-23 23:24:47 +00:00
David Heinemeier Hansson
694957ce5a Added text/csv as a default mime type and included example on how to make your own in config/environment.rb [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5620 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-23 17:25:52 +00:00
David Heinemeier Hansson
12949bbc13 Added ActiveRecord::Base.clear_active_connections! in development mode so the database connection is not carried over from request to request. Some databases won't reread the schema if that doesn't happen (I'm looking at you SQLite), so you have to restart the server after each migration (= no fun) [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5617 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-23 05:16:44 +00:00
David Heinemeier Hansson
82e5ff7c6e Actually require the gem found
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5615 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-23 04:49:04 +00:00
David Heinemeier Hansson
783c16bd61 Made RAILS_GEM_VERSION work for beta gems too, so specifying 1.1.6 will give you 1.1.6.4520 if available [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5613 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-23 04:40:38 +00:00
David Heinemeier Hansson
34a3d04af1 Dont include the mime.yml anyway, Mongrel will just ship with more defaults instead
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5611 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-23 02:41:41 +00:00
David Heinemeier Hansson
766ca17c91 Update release date for RC
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5610 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-23 02:32:50 +00:00
Nicholas Seckar
ffa2c5fda5 Apply [5607] to RC. References #6669.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5608 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-22 16:38:06 +00:00
Thomas Fuchs
87ecb782e6 Merge [5602] from trunk
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5603 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-20 23:45:47 +00:00
Jeremy Kemper
2043513f4c Merge [5586], [5596] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5599 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-20 12:20:16 +00:00
Jeremy Kemper
ef6c3c4289 Merge [5597] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5598 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-20 12:08:05 +00:00
Jeremy Kemper
af43e87f38 Merge [5591] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5592 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-20 11:05:12 +00:00
Jeremy Kemper
f2fd22c2d4 Merge [5589] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5590 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-20 10:55:41 +00:00
David Heinemeier Hansson
17921bafcb Proper dates
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5584 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-20 00:02:42 +00:00
Thomas Fuchs
36b3cb2c36 Merge 1-2-pre-release with [5581]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5582 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-19 23:08:27 +00:00
David Heinemeier Hansson
b93e4692b2 Made script/server work with -e and -d when using Mongrel [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5578 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-19 22:18:48 +00:00
Rick Olson
30e5436a3e merge [5571] and [5572] from trunk
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5573 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-19 21:16:30 +00:00
David Heinemeier Hansson
9ba96771c3 Scaffold resource should have both a layout and a stylesheet [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5566 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-19 16:57:45 +00:00
Jeremy Kemper
97d9dca26f Merge [5561] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5562 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-19 09:59:55 +00:00
David Heinemeier Hansson
2a70f6f0a2 Update changelogs in preparation for RC
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5560 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-19 01:07:08 +00:00
David Heinemeier Hansson
1e532f6606 Merged [5503] from trunk
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5559 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-18 19:17:13 +00:00
David Heinemeier Hansson
fb60a469c0 Merged [5485] from trunk
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5558 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-18 19:12:22 +00:00
David Heinemeier Hansson
ac2295137c Merged [5482] from trunk
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5557 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-18 19:10:18 +00:00
David Heinemeier Hansson
acfe119cb5 Merged [5473] from trunk
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5556 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-18 19:05:13 +00:00
David Heinemeier Hansson
004a28de81 Merged [5546] and [5547] from trunk
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5555 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-18 18:49:29 +00:00
David Heinemeier Hansson
0497868531 Active Resource will not ship with Rails 1.2
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5554 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-18 18:46:20 +00:00
Thomas Fuchs
23569d83b2 Update Prototype in RC to [5550]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5552 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-17 23:55:04 +00:00
Nicholas Seckar
bef626e2b1 Apply [5548] to RC
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5549 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-17 22:31:47 +00:00
Jeremy Kemper
b7f094e65e Merge [5543] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5545 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-17 13:11:59 +00:00
Jeremy Kemper
6baf9489d1 Merge [5541] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5542 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-16 22:51:17 +00:00
Jeremy Kemper
af33784698 Merge [5531] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5538 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-16 17:26:39 +00:00
Jeremy Kemper
a5631a5674 Merge [5535], [5536] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5537 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-16 06:45:06 +00:00
Jeremy Kemper
629b2ac36c Merge [5533] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5534 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-16 01:43:59 +00:00
Jeremy Kemper
dd1dc5e819 Merge [5521] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5522 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-14 09:23:17 +00:00
Jeremy Kemper
b8ff216f49 Merge [5518] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5519 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-14 02:52:14 +00:00
Tobias Lütke
924f5cdb61 Merge from [5516]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5517 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-13 20:30:18 +00:00
Jeremy Kemper
86bd0da7d0 Merge [5514] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5515 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-13 19:24:33 +00:00
Jeremy Kemper
35240ba66e Merge [5512] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5513 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-13 19:03:08 +00:00
Jeremy Kemper
b8a5d398b9 Merge [5509] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5510 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-13 07:40:20 +00:00
Jeremy Kemper
85c45051be Merge [5506] from trunk. Set Rails::VERSION::STRING to 1.1.6 pending 1.2 release.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5508 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-13 07:17:11 +00:00
Jeremy Kemper
f7c94e977b Merge [5501] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5502 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-13 01:10:13 +00:00
Sam Stephenson
69ada0e26e Merge [5497] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5500 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-12 03:48:45 +00:00
Sam Stephenson
9eaad14e8e Merge [5487] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5499 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-12 03:48:09 +00:00
Sam Stephenson
9f8c805f5d Merge [5486] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5498 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-12 03:47:27 +00:00
Jamis Buck
46b845e784 Add grep-based fallback to reaper, to work in pidless setups
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5494 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-12 02:51:43 +00:00
Jeremy Kemper
118764e47d Merge [5480] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5481 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-10 19:19:50 +00:00
Rick Olson
bcd50f1a37 merge [5476] from trunk
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5479 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-10 18:35:24 +00:00
Jeremy Kemper
5aced86de6 Merge [5474] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5475 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-09 19:36:39 +00:00
Nicholas Seckar
792780a80c Apply [5471] to RC
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5472 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-09 18:38:04 +00:00
Jeremy Kemper
5e677b67b5 Merge [5469] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5470 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-09 08:10:14 +00:00
Jeremy Kemper
dfa070aab9 Merge [5466] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5467 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-07 21:47:01 +00:00
Jeremy Kemper
ba086a4fc0 Merge [5440], [5444], [5463], [5464] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5465 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-07 21:13:27 +00:00
Jeremy Kemper
cc299d1b94 Merge [5459] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5460 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-07 20:14:09 +00:00
Nicholas Seckar
cbffafb497 Remove temporary crutch to help ApplicationController be unloaded. Closes #6496.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5458 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-07 19:54:43 +00:00
Jamis Buck
84668af330 make add_order a tad faster
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5453 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-07 19:11:39 +00:00
Jeremy Kemper
013004b592 Merge [5445] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5446 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-07 00:20:07 +00:00
Jeremy Kemper
5f5d8c224b Merge [5442] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5443 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-06 21:49:35 +00:00
Thomas Fuchs
5887b183ce Merge [5438] from trunk
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5439 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-06 09:45:39 +00:00
Jeremy Kemper
320875ad77 Merge [5432] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5433 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-05 19:05:00 +00:00
Jeremy Kemper
4aa358dd86 Merge [5429] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5430 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-05 02:42:42 +00:00
Jeremy Kemper
6439d0cd3e Merge [5427] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5428 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-05 02:16:34 +00:00
Scott Barron
7953eb170d merge [5424] from trunk
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5425 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-03 18:21:23 +00:00
Jeremy Kemper
5350fda95d Merge [5422] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5423 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-03 04:18:52 +00:00
Jeremy Kemper
5fac326020 Merge [5420] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5421 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-03 00:41:48 +00:00
Jeremy Kemper
17e445813b Merge [5418] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5419 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-02 20:37:32 +00:00
Jeremy Kemper
a2edd4381e Merge [5416] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5417 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-02 20:22:28 +00:00
Michael Koziarski
2029b8a8f5 merge xmlsimple update to release branch
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5415 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-02 20:13:27 +00:00
Jeremy Kemper
53c49036ea Merge [5412] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5413 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-02 19:32:16 +00:00
Scott Barron
855e0f6f09 merge [5408] from trunk
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5409 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-02 18:39:37 +00:00
Jeremy Kemper
e4e1d2afa3 Merge [5405] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5406 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-02 18:13:49 +00:00
Jeremy Kemper
526afd3ae2 Merge [5403] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5404 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-02 10:12:29 +00:00
Jeremy Kemper
31b901aa65 Merge [5401] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5402 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-02 09:47:36 +00:00
Jeremy Kemper
a746e39584 Merge [5399] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5400 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-02 08:49:47 +00:00
Jeremy Kemper
f4d88039fd Merge [5397] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5398 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-02 08:38:44 +00:00
Jeremy Kemper
9f26164d37 Merge [5395] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5396 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-02 07:57:44 +00:00
Scott Barron
c7f28e01c8 Merge in [5392] and [5393] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5394 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-02 05:02:09 +00:00
Michael Koziarski
3311953d3f merge rakefile changes to prerelease
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5391 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-02 03:38:36 +00:00
Jeremy Kemper
ce0653b1c6 Merge [5388] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5389 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-02 02:23:45 +00:00
Nicholas Seckar
229e197ac7 Update dependencies to allow constants to be defined alongside their siblings. (Applying changeset [5386] to RC.)
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5387 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-01 23:24:17 +00:00
Jeremy Kemper
503c7c0afd Merge [5384] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5385 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-01 20:47:27 +00:00
Jeremy Kemper
8e01efee02 Merge [5379] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5383 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-01 19:41:01 +00:00
Jeremy Kemper
e09f224a5c Merge [5378] from trunk.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5382 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-01 19:38:47 +00:00
Scott Barron
6eb941ee6c Merge breakpoint/1.8.5 fix from [5380]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5381 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-11-01 17:50:02 +00:00
Michael Koziarski
7e8dd0322c Revert environment.rb changes for autoloading
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5377 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-10-31 01:33:33 +00:00
Thomas Fuchs
159411f580 Update to latest Prototype, which doesnt serialize disabled form elements, adds clone() to arrays, empty/non-string Element.update() and adds a fixes excessive error reporting in WebKit beta versions [Thomas Fuchs]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5372 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-10-28 16:56:09 +00:00
Michael Koziarski
746cfb3ded Merge Statistics cleanup to release branch
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5370 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-10-27 01:42:47 +00:00
David Heinemeier Hansson
2227a178ee Branches so we can just get the bug fixes out of the way before shipping 1.2
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5368 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-10-26 17:05:00 +00:00
178 changed files with 6264 additions and 3454 deletions

View File

@@ -1,4 +1,4 @@
*SVN*
*1.3.0 RC1* (r5619, November 22nd, 2006)
* Make mime version default to 1.0. closes #2323 [ror@andreas-s.net]
@@ -14,8 +14,6 @@
* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
* Mailer template root applies to a class and its subclasses rather than acting globally. #5555 [somekool@gmail.com]
* Resolve action naming collision. #5520 [ssinghi@kreeti.com]
* ActionMailer::Base documentation rewrite. Closes #4991 [Kevin Clark, Marcel Molina Jr.]
@@ -24,8 +22,30 @@
* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
*1.2.5* (August 10th, 2006)
* Depend on Action Pack 1.12.5
*1.2.4* (August 8th, 2006)
* Backport of documentation enhancements. [Kevin Clark, Marcel Molina Jr]
* Correct spurious documentation example code which results in a SyntaxError. [Marcel Molina Jr.]
* Mailer template root applies to a class and its subclasses rather than acting globally. #5555 [somekool@gmail.com]
*1.2.3* (June 29th, 2006)
* Depend on Action Pack 1.12.3
*1.2.2* (June 27th, 2006)
* Depend on Action Pack 1.12.2
*1.2.1* (April 6th, 2006)

View File

@@ -1,4 +1,84 @@
*SVN*
*1.13.0 RC2*
* Make sure html_document is reset between integration test requests. [ctm]
* Set session to an empty hash if :new_session => false and no session cookie or param is present. CGI::Session was raising an unrescued ArgumentError. [Josh Susser]
* Fix assert_redirected_to bug where redirecting from a nested to to a top-level controller incorrectly added the current controller's nesting. Closes #6128. [Rick Olson]
* Ensure render :json => ... skips the layout. #6808 [Josh Peek]
* Silence log_error deprecation warnings from inspecting deprecated instance variables. [Nate Wiger]
* Only cache GET requests with a 200 OK response. #6514, #6743 [RSL, anamba]
* Correctly report which filter halted the chain. #6699 [Martin Emde]
* respond_to recognizes JSON. render :json => @person.to_json automatically sets the content type and takes a :callback option to specify a client-side function to call using the rendered JSON as an argument. #4185 [Scott Raymond, eventualbuddha]
# application/json response with body 'Element.show({:name: "David"})'
respond_to do |format|
format.json { render :json => { :name => "David" }.to_json, :callback => 'Element.show' }
end
* Makes :discard_year work without breaking multi-attribute parsing in AR. #1260, #3800 [sean@ardismg.com, jmartin@desertflood.com, stephen@touset.org, Bob Silva]
* Adds html id attribute to date helper elements. #1050, #1382 [mortonda@dgrmm.net, David North, Bob Silva]
* Add :index and @auto_index capability to model driven date/time selects. #847, #2655 [moriq, Doug Fales, Bob Silva]
* Add :order to datetime_select, select_datetime, and select_date. #1427 [Timothee Peignier, patrick@lenz.sh, Bob Silva]
* Added time_select to work with time values in models. Update scaffolding. #2489, #2833 [Justin Palmer, Andre Caum, Bob Silva]
* Added :include_seconds to select_datetime, datetime_select and time_select. #2998 [csn, Bob Silva]
* All date/datetime selects can now accept an array of month names with :use_month_names. Allows for localization. #363 [tomasj, Bob Silva]
* Adds :time_separator to select_time and :date_separator to select_datetime. Preserves BC. #3811 [Bob Silva]
* @response.redirect_url works with 201 Created responses: just return headers['Location'] rather than checking the response status. [Jeremy Kemper]
* Fixed that HEAD should return the proper Content-Length header (that is, actually use @body.size, not just 0) [DHH]
* Added GET-masquarading for HEAD, so request.method will return :get even for HEADs. This will help anyone relying on case request.method to automatically work with HEAD and map.resources will also allow HEADs to all GET actions. Rails automatically throws away the response content in a reply to HEAD, so you don't even need to worry about that. If you, for whatever reason, still need to distinguish between GET and HEAD in some edge case, you can use Request#head? and even Request.headers["REQUEST_METHOD"] for get the "real" answer. Closes #6694 [DHH]
*1.13.0 RC1* (r5619, November 22nd, 2006)
* Update Routing to complain when :controller is not specified by a route. Closes #6669. [Nicholas Seckar]
* Ensure render_to_string cleans up after itself when an exception is raised. #6658 [rsanheim]
* Update to Prototype and script.aculo.us [5579]. [Sam Stephenson, Thomas Fuchs]
* simple_format helper doesn't choke on nil. #6644 [jerry426]
* Reuse named route helper module between Routing reloads. Use remove_method to delete named route methods after each load. Since the module is never collected, this fixes a significant memory leak. [Nicholas Seckar]
* Deprecate standalone components. [Jeremy Kemper]
* Always clear model associations from session. #4795 [sd@notso.net, andylien@gmail.com]
* Remove JavaScriptLiteral in favor of ActiveSupport::JSON::Variable. [Sam Stephenson]
* Sync ActionController::StatusCodes::STATUS_CODES with http://www.iana.org/assignments/http-status-codes. #6586 [dkubb]
* Multipart form values may have a content type without being treated as uploaded files if they do not provide a filename. #6401 [Andreas Schwarz, Jeremy Kemper]
* assert_response supports symbolic status codes. #6569 [Kevin Clark]
assert_response :ok
assert_response :not_found
assert_response :forbidden
* Cache parsed query parameters. #6559 [Stefan Kaes]
* Deprecate JavaScriptHelper#update_element_function, which is superseeded by RJS [Thomas Fuchs]
* Fix invalid test fixture exposed by stricter Ruby 1.8.5 multipart parsing. #6524 [Bob Silva]
* Set ActionView::Base.default_form_builder once rather than passing the :builder option to every form or overriding the form helper methods. [Jeremy Kemper]
* Deprecate expire_matched_fragments. Use expire_fragment instead. #6535 [Bob Silva]
* Deprecate start_form_tag and end_form_tag. Use form_tag / '</form>' from now on. [Rick]
@@ -35,8 +115,6 @@
* Fix double-escaped entities, such as &amp;amp;, &amp;#123;, etc. [Rick]
* Fix deprecation warnings when rendering the template error template. [Nicholas Seckar]
* Fix routing to correctly determine when generation fails. Closes #6300. [psross].
* Fix broken assert_generates when extra keys are being checked. [Jamis Buck]
@@ -47,10 +125,6 @@
* Use String#chars in TextHelper::excerpt. Closes #6386 [Manfred Stienstra]
* Install named routes into ActionView::Base instead of proxying them to the view via helper_method. Closes #5932. [Nicholas Seckar]
* Update to latest Prototype and script.aculo.us trunk versions [Thomas Fuchs]
* Fix relative URL root matching problems. [Mark Imbriaco]
* Fix filter skipping in controller subclasses. #5949, #6297, #6299 [Martin Emde]
@@ -106,8 +180,6 @@
* Filters overhaul including meantime filter support using around filters + blocks. #5949 [Martin Emde, Roman Le Negrate, Stefan Kaes, Jeremy Kemper]
* Update RJS render tests. [sam]
* Update CGI process to allow sessions to contain namespaced models. Closes #4638. [dfelstead@site5.com]
* Fix routing to respect user provided requirements and defaults when assigning default routing options (such as :action => 'index'). Closes #5950. [Nicholas Seckar]
@@ -133,12 +205,6 @@
response.content_type = Mime::ATOM
response.charset = "utf-8"
* Updated prototype.js to 1.5.0_rc1 with latest fixes. [Rick Olson]
- XPATH support
- Make Form.getElements() return elements in the correct order
- fix broken Form.serialize return
* Declare file extensions exempt from layouts. #6219 [brandon]
Example: ActionController::Base.exempt_from_layout 'rpdf'
@@ -163,8 +229,6 @@
* Fix assert_tag so that :content => "foo" does not match substrings, but only exact strings. Use :content => /foo/ to match substrings. #2799 [Eric Hodel]
* Add descriptive messages to the exceptions thrown by cgi_methods. #6091, #6103 [Nicholas Seckar, Bob Silva]
* Update JavaScriptGenerator#show/hide/toggle/remove to new Prototype syntax for multiple ids, #6068 [petermichaux@gmail.com]
* Update UrlWriter to support :only_path. [Nicholas Seckar, Dave Thomas]
@@ -177,10 +241,6 @@
link_to("Hider", :class => "hider_link") { |p| p[:something].hide }
* Update to script.aculo.us 1.6.3 [Thomas Fuchs]
* Update to Prototype 1.5.0_rc1 [sam]
* Added access to nested attributes in RJS #4548 [richcollins@gmail.com]. Examples:
page['foo']['style'] # => $('foo').style;
@@ -202,9 +262,6 @@
* Changed that uncaught exceptions raised any where in the application will cause RAILS_ROOT/public/500.html to be read and shown instead of just the static "Application error (Rails)" [DHH]
* Integration tests: thoroughly test ActionController::Integration::Session. #6022 [Kevin Clark]
(tests skipped unless you `gem install mocha`)
* Added deprecation language for pagination which will become a plugin by Rails 2.0 [DHH]
* Added deprecation language for in_place_editor and auto_complete_field that both pieces will become plugins by Rails 2.0 [DHH]
@@ -215,7 +272,7 @@
* radio_button_tag generates unique id attributes. #3353 [Bob Silva, somekool@gmail.com]
* strip_tags returns nil for a blank arg such as nil or "". #2229 [duncan@whomwah.com]
* strip_tags passes through blank args such as nil or "". #2229, #6702 [duncan@whomwah.com, dharana]
* Cleanup assert_tag :children counting. #2181 [jamie@bravenet.com]
@@ -223,20 +280,12 @@
* Update sanitize text helper to strip plaintext tags, and <img src="javascript:bang">. [Rick Olson]
* Update routing documentation. Closes #6017 [Nathan Witmer]
* Add routing tests to assert that RoutingError is raised when conditions aren't met. Closes #6016 [Nathan Witmer]
* Deprecation: update docs. #5998 [jakob@mentalized.net, Kevin Clark]
* Make auto_link parse a greater subset of valid url formats. [Jamis Buck]
* Integration tests: headers beginning with X aren't excluded from the HTTP_ prefix, so X-Requested-With becomes HTTP_X_REQUESTED_WITH as expected. [Mike Clark]
* Tighten rescue clauses. #5985 [james@grayproductions.net]
* Fix send_data documentation typo. #5982 [brad@madriska.com]
* Switch to using FormEncodedPairParser for parsing request parameters. [Nicholas Seckar, DHH]
* respond_to .html now always renders #{action_name}.rhtml so that registered custom template handlers do not override it in priority. Custom mime types require a block and throw proper error now. [Tobias Luetke]
@@ -245,12 +294,6 @@
* Add UrlWriter to allow writing urls from Mailers and scripts. [Nicholas Seckar]
* Clean up and run the Active Record integration tests by default. #5854 [kevin.clark@gmail.com, Jeremy Kemper]
* Correct example in cookies docs. #5832 [jessemerriman@warpmail.net]
* Updated to script.aculo.us 1.6.2 [Thomas Fuchs]
* Relax Routing's anchor pattern warning; it was preventing use of [^/] inside restrictions. [Nicholas Seckar]
* Add controller_paths variable to Routing. [Nicholas Seckar]
@@ -261,8 +304,6 @@
* Invoke method_missing directly on hidden actions. Closes #3030. [Nicholas Seckar]
* Require Tempfile explicitly for TestUploadedFile due to changes in class auto loading. [Rick Olson]
* Add RoutingError exception when RouteSet fails to generate a path from a Named Route. [Rick Olson]
* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
@@ -277,8 +318,6 @@
* Added months and years to the resolution of DateHelper#distance_of_time_in_words, such that "60 days ago" becomes "2 months ago" #5611 [pjhyett@gmail.com]
* Short documentation to mention use of Mime::Type.register. #5710 [choonkeat@gmail.com]
* Make controller_path available as an instance method. #5724 [jmckible@gmail.com]
* Update query parser to support adjacent hashes. [Nicholas Seckar]
@@ -287,10 +326,6 @@
* Restrict Request Method hacking with ?_method to POST requests. [Rick Olson]
* Fix bug when passing multiple options to SimplyRestful, like :new => { :preview => :get, :draft => :get }. [Rick Olson, Josh Susser, Lars Pind]
* Dup the options passed to map.resources so that multiple resources get the same options. [Rick Olson]
* Fixed the new_#{resource}_url route and added named route tests for Simply Restful. [Rick Olson]
* Added map.resources from the Simply Restful plugin [DHH]. Examples (the API has changed to use plurals!):
@@ -318,14 +353,10 @@
* Provide support for decimal columns to form helpers. Closes #5672. [dave@pragprog.com]
* Update documentation for erb trim syntax. #5651 [matt@mattmargolis.net]
* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com, sebastien@goetzilla.info]
* Reset @html_document between requests so assert_tag works. #4810 [jarkko@jlaine.net, easleydp@gmail.com]
* Update render :partial documentation. #5646 [matt@mattmargolis.net]
* Integration tests behave well with render_component. #4632 [edward.frederick@revolution.com, dev.rubyonrails@maxdunn.com]
* Added exception handling of missing layouts #5373 [chris@ozmm.org]
@@ -360,8 +391,6 @@
* Make sure :id and friends are unescaped properly. #5275 [me@julik.nl]
* Fix documentation for with_routing to reflect new reality. #5281 [rramdas@gmail.com]
* Rewind readable CGI params so others may reread them (such as CGI::Session when passing the session id in a multipart form). #210 [mklame@atxeu.com, matthew@walker.wattle.id.au]
* Added Mime::TEXT (text/plain) and Mime::ICS (text/calendar) as new default types [DHH]
@@ -425,8 +454,6 @@
* Fix NoMethodError when parsing params like &&. [Adam Greenfield]
* Fix flip flopped logic in docs for url_for's :only_path option. Closes #4998. [esad@esse.at]
* form.text_area handles the :size option just like the original text_area (:size => '60x10' becomes cols="60" rows="10"). [Jeremy Kemper]
* Excise ingrown code from FormOptionsHelper#options_for_select. #5008 [anonymous]
@@ -435,43 +462,17 @@
map.connect '*path', :controller => 'files', :action => 'show'
* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.]
* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
* Use #flush between switching from #write to #syswrite. Closes #4907. [Blair Zajac <blair@orcaware.com>]
* Documentation fix: integration test scripts don't require integration_test. Closes #4914. [Frederick Ros <sl33p3r@free.fr>]
* ActionController::Base Summary documentation rewrite. Closes #4900. [kevin.clark@gmail.com]
* Fix text_helper.rb documentation rendering. Closes #4725. [Frederick Ros]
* Fixes bad rendering of JavaScriptMacrosHelper rdoc (closes #4910) [Frederick Ros]
* Allow error_messages_for to report errors for multiple objects, as well as support for customizing the name of the object in the error summary header. Closes #4186. [andrew@redlinesoftware.com, Marcel Molina Jr.]
error_messages_for :account, :user, :subscription, :object_name => :account
* Enhance documentation for setting headers in integration tests. Skip auto HTTP prepending when its already there. Closes #4079. [Rick Olson]
* Documentation for AbstractRequest. Closes #4895. [kevin.clark@gmail.com]
* Refactor various InstanceTag instance method to class methods. Closes #4800. [skaes@web.de]
* Remove all remaining references to @params in the documentation. [Marcel Molina Jr.]
* Add documentation for redirect_to :back's RedirectBackError exception. [Marcel Molina Jr.]
* Update layout and content_for documentation to use yield rather than magic @content_for instance variables. [Marcel Molina Jr.]
* Fix assert_redirected_to tests according to real-world usage. Also, don't fail if you add an extra :controller option: [Rick]
redirect_to :action => 'new'
assert_redirected_to :controller => 'monkeys', :action => 'new'
* Cache CgiRequest#request_parameters so that multiple calls don't re-parse multipart data. [Rick]
* Diff compared routing options. Allow #assert_recognizes to take a second arg as a hash to specify optional request method [Rick]
assert_recognizes({:controller => 'users', :action => 'index'}, 'users')
@@ -483,8 +484,6 @@
* Change link_to_function and button_to_function to (optionally) take an update_page block instead of a JavaScript string. Closes #4804. [zraii@comcast.net, Sam Stephenson]
* Fixed that remote_form_for can leave out the object parameter and default to the instance variable of the object_name, just like form_for [DHH]
* Modify routing so that you can say :require => { :method => :post } for a route, and the route will never be selected unless the request method is POST. Only works for route recognition, not for route generation. [Jamis Buck]
* Added :add_headers option to verify which merges a hash of name/value pairs into the response's headers hash if the prerequisites cannot be satisfied. [Sam Stephenson]
@@ -492,7 +491,43 @@
:render => { :status => 405, :text => "Must be post" },
:add_headers => { "Allow" => "POST" }
* Added ActionController.filter_parameter_logging that makes it easy to remove passwords, credit card numbers, and other sensitive information from being logged when a request is handled #1897 [jeremye@bsa.ca.gov]
*1.12.5* (August 10th, 2006)
* Updated security fix
*1.12.4* (August 8th, 2006)
* Cache CgiRequest#request_parameters so that multiple calls don't re-parse multipart data. [Rick]
* Fixed that remote_form_for can leave out the object parameter and default to the instance variable of the object_name, just like form_for [DHH]
* Added ActionController.filter_parameter_logging that makes it easy to remove passwords, credit card numbers, and other sensitive information from being logged when a request is handled. #1897 [jeremye@bsa.ca.gov]
* Fixed that real files and symlinks should be treated the same when compiling templates. #5438 [zachary@panandscan.com]
* Add :status option to send_data and send_file. Defaults to '200 OK'. #5243 [Manfred Stienstra <m.stienstra@fngtps.com>]
* Update documentation for erb trim syntax. #5651 [matt@mattmargolis.net]
* Short documentation to mention use of Mime::Type.register. #5710 [choonkeat@gmail.com]
*1.12.3* (June 28th, 2006)
* Fix broken traverse_to_controller. We now:
Look for a _controller.rb file under RAILS_ROOT to load.
If we find it, we require_dependency it and return the controller it defined. (If none was defined we stop looking.)
If we don't find it, we look for a .rb file under RAILS_ROOT to load. If we find it, and it loads a constant we keep looking.
Otherwise we check to see if a directory of the same name exists, and if it does we create a module for it.
*1.12.2* (June 27th, 2006)
* Refinement to avoid exceptions in traverse_to_controller.
* (Hackish) Fix loading of arbitrary files in Ruby's load path by traverse_to_controller. [Nicholas Seckar]
*1.12.1* (April 6th, 2006)
@@ -2727,9 +2762,9 @@ Default YAML web services were retired, ActionController::Base.param_parsers car
* Added pluralize method to the TextHelper that makes it easy to get strings like "1 message", "3 messages"
* Added proper escaping for the rescues [Andreas Schwartz]
* Added proper escaping for the rescues [Andreas Schwarz]
* Added proper escaping for the option and collection tags [Andreas Schwartz]
* Added proper escaping for the option and collection tags [Andreas Schwarz]
* Fixed NaN errors on benchmarking [Jim Weirich]

View File

@@ -5,27 +5,31 @@ module ActionController
module Assertions
module ResponseAssertions
# Asserts that the response is one of the following types:
#
#
# * <tt>:success</tt>: Status code was 200
# * <tt>:redirect</tt>: Status code was in the 300-399 range
# * <tt>:missing</tt>: Status code was 404
# * <tt>:error</tt>: Status code was in the 500-599 range
#
# You can also pass an explicit status code number as the type, like assert_response(501)
# You can also pass an explicit status number like assert_response(501)
# or its symbolic equivalent assert_response(:not_implemented).
# See ActionController::StatusCodes for a full list.
def assert_response(type, message = nil)
clean_backtrace do
if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?")
assert_block("") { true } # to count the assertion
elsif type.is_a?(Fixnum) && @response.response_code == type
assert_block("") { true } # to count the assertion
elsif type.is_a?(Symbol) && @response.response_code == ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE[type]
assert_block("") { true } # to count the assertion
else
assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
end
end
end
end
# Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial,
# such that assert_redirected_to(:controller => "weblog") will also match the redirection of
# such that assert_redirected_to(:controller => "weblog") will also match the redirection of
# redirect_to(:controller => "weblog", :action => "show") and so on.
def assert_redirected_to(options = {}, message=nil)
clean_backtrace do
@@ -66,18 +70,19 @@ module ActionController
if value.respond_to?(:[]) && value['controller']
if key == :actual && value['controller'].first != '/' && !value['controller'].include?('/')
value['controller'] = ActionController::Routing.controller_relative_to(value['controller'], @controller.class.controller_path)
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)
end
value['controller'] = value['controller'][1..-1] if value['controller'].first == '/' # strip leading hash
end
url[key] = value
end
@response_diff = url[:expected].diff(url[:actual]) if url[:actual]
msg = build_message(message, "response is not a redirection to all of the options supplied (redirection is <?>), difference: <?>",
msg = build_message(message, "response is not a redirection to all of the options supplied (redirection is <?>), difference: <?>",
url[:actual], @response_diff)
assert_block(msg) do
url[:expected].keys.all? do |k|
if k == :controller then url[:expected][k] == ActionController::Routing.controller_relative_to(url[:actual][k], @controller.class.controller_path)
@@ -94,7 +99,7 @@ module ActionController
end.flatten
assert_equal(eurl, url, msg) if eurl && url
assert_equal(epath, path, msg) if epath && path
assert_equal(epath, path, msg) if epath && path
end
end
end
@@ -110,7 +115,7 @@ module ActionController
else
expected == rendered
end
end
end
end
end
@@ -132,4 +137,4 @@ module ActionController
end
end
end
end
end

View File

@@ -650,6 +650,20 @@ module ActionController #:nodoc:
#
# _Deprecation_ _notice_: This used to have the signature <tt>render_text("text", status = 200)</tt>
#
# === Rendering JSON
#
# Rendering JSON sets the content type to text/x-json and optionally wraps the JSON in a callback. It is expected
# that the response will be eval'd for use as a data structure.
#
# # Renders '{name: "David"}'
# render :json => {:name => "David"}.to_json
#
# Sometimes the result isn't handled directly by a script (such as when the request comes from a SCRIPT tag),
# so the callback option is provided for these cases.
#
# # Renders 'show({name: "David"})'
# render :json => {:name => "David"}.to_json, :callback => 'show'
#
# === Rendering an inline template
#
# Rendering of an inline template works as a cross between text and action rendering where the source for the template
@@ -734,6 +748,9 @@ module ActionController #:nodoc:
elsif xml = options[:xml]
render_xml(xml, options[:status])
elsif json = options[:json]
render_json(json, options[:callback], options[:status])
elsif partial = options[:partial]
partial = default_template_name if partial == true
if collection = options[:collection]
@@ -763,13 +780,11 @@ module ActionController #:nodoc:
# Renders according to the same rules as <tt>render</tt>, but returns the result in a string instead
# of sending it as the response body to the browser.
def render_to_string(options = nil, &block) #:doc:
result = ActiveSupport::Deprecation.silence { render(options, &block) }
ActiveSupport::Deprecation.silence { render(options, &block) }
ensure
erase_render_results
forget_variables_added_to_assigns
reset_variables_added_to_assigns
result
end
def render_action(action_name, status = nil, with_layout = true) #:nodoc:
@@ -816,6 +831,13 @@ module ActionController #:nodoc:
render_text(xml, status)
end
def render_json(json, callback = nil, status = nil) #:nodoc:
json = "#{callback}(#{json})" unless callback.blank?
response.content_type = Mime::JSON
render_text(json, status)
end
def render_nothing(status = nil) #:nodoc:
render_text(' ', status)
end
@@ -893,7 +915,7 @@ module ActionController #:nodoc:
response.redirected_to = nil
response.redirected_to_method_params = nil
response.headers['Status'] = DEFAULT_RENDER_STATUS_CODE
response.headers.delete('location')
response.headers.delete('Location')
end
# Erase both render and redirect results
@@ -1089,7 +1111,11 @@ module ActionController #:nodoc:
def assign_default_content_type_and_charset
response.content_type ||= Mime::HTML
response.charset ||= self.class.default_charset
response.charset ||= self.class.default_charset unless sending_file?
end
def sending_file?
response.headers["Content-Transfer-Encoding"] == "binary"
end
def action_methods

View File

@@ -135,7 +135,7 @@ module ActionController #:nodoc:
private
def caching_allowed
!request.post? && response.headers['Status'] && response.headers['Status'].to_i < 400
request.get? && response.headers['Status'].to_i == 200
end
end
@@ -363,7 +363,12 @@ module ActionController #:nodoc:
# Name can take one of three forms:
# * String: This would normally take the form of a path like "pages/45/notes"
# * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
# * Regexp: Will destroy all the matched fragments, example: %r{pages/\d*/notes} Ensure you do not specify start and finish in the regex (^$) because the actual filename matched looks like ./cache/filename/path.cache
# * Regexp: Will destroy all the matched fragments, example:
# %r{pages/\d*/notes}
# Ensure you do not specify start and finish in the regex (^$) because
# the actual filename matched looks like ./cache/filename/path.cache
# Regexp expiration is not supported on caches which can't iterate over
# all keys, such as memcached.
def expire_fragment(name, options = nil)
return unless perform_caching
@@ -384,6 +389,7 @@ module ActionController #:nodoc:
def expire_matched_fragments(matcher = /.*/, options = nil) #:nodoc:
expire_fragment(matcher, options)
end
deprecate :expire_matched_fragments => :expire_fragment
class UnthreadedMemoryStore #:nodoc:

View File

@@ -27,13 +27,6 @@ class CGI #:nodoc:
def request_parameters
CGIMethods.parse_request_parameters(params, env_table)
end
def redirect(where)
header({
"Status" => "302 Moved",
"location" => "#{where}"
})
end
def session(parameters = nil)
parameters = {} if parameters.nil?

View File

@@ -63,43 +63,50 @@ class CGIMethods #:nodoc:
private
def get_typed_value(value)
# test most frequent case first
if value.is_a?(String)
value
elsif value.respond_to?(:content_type) && ! value.content_type.blank?
# Uploaded file
unless value.respond_to?(:full_original_filename)
class << value
alias_method :full_original_filename, :original_filename
case value
when String
value
when NilClass
''
when Array
value.map { |v| get_typed_value(v) }
else
# Uploaded file provides content type and filename.
if value.respond_to?(:content_type) &&
!value.content_type.blank? &&
!value.original_filename.blank?
unless value.respond_to?(:full_original_filename)
class << value
alias_method :full_original_filename, :original_filename
# Take the basename of the upload's original filename.
# This handles the full Windows paths given by Internet Explorer
# (and perhaps other broken user agents) without affecting
# those which give the lone filename.
# The Windows regexp is adapted from Perl's File::Basename.
def original_filename
if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
md.captures.first
else
File.basename full_original_filename
# Take the basename of the upload's original filename.
# This handles the full Windows paths given by Internet Explorer
# (and perhaps other broken user agents) without affecting
# those which give the lone filename.
# The Windows regexp is adapted from Perl's File::Basename.
def original_filename
if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
md.captures.first
else
File.basename full_original_filename
end
end
end
end
# Return the same value after overriding original_filename.
value
# Multipart values may have content type, but no filename.
elsif value.respond_to?(:read)
result = value.read
value.rewind
result
# Unknown value, neither string nor multipart.
else
raise "Unknown form value: #{value.inspect}"
end
end
# Return the same value after overriding original_filename.
value
elsif value.respond_to?(:read)
# Value as part of a multipart request
result = value.read
value.rewind
result
elsif value.class == Array
value.collect { |v| get_typed_value(v) }
else
# other value (neither string nor a multipart request)
value.to_s
end
end
end

View File

@@ -8,13 +8,13 @@ module ActionController #:nodoc:
# sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
#
# * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
# (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
# (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
# lib/action_controller/session.
# * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
# * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ parameter
# of the request, or automatically generated for a new session.
# * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
# exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
# exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
# an ArgumentError is raised.
# * <tt>:session_expires</tt> - the time the current session expires, as a +Time+ object. If not set, the session will continue
# indefinitely.
@@ -22,10 +22,10 @@ module ActionController #:nodoc:
# server.
# * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
# * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
def self.process_cgi(cgi = CGI.new, session_options = {})
def self.process_cgi(cgi = CGI.new, session_options = {})
new.process_cgi(cgi, session_options)
end
def process_cgi(cgi, session_options = {}) #:nodoc:
process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
end
@@ -51,7 +51,7 @@ module ActionController #:nodoc:
if (qs = @cgi.query_string) && !qs.empty?
qs
elsif uri = @env['REQUEST_URI']
parts = uri.split('?')
parts = uri.split('?')
parts.shift
parts.join('?')
else
@@ -60,7 +60,8 @@ module ActionController #:nodoc:
end
def query_parameters
(qs = self.query_string).empty? ? {} : CGIMethods.parse_query_parameters(qs)
@query_parameters ||=
(qs = self.query_string).empty? ? {} : CGIMethods.parse_query_parameters(qs)
end
def request_parameters
@@ -71,7 +72,7 @@ module ActionController #:nodoc:
CGIMethods.parse_request_parameters(@cgi.params)
end
end
def cookies
@cgi.cookies.freeze
end
@@ -106,10 +107,21 @@ module ActionController #:nodoc:
@session = Hash.new
else
stale_session_check! do
if session_options_with_string_keys['new_session'] == true
@session = new_session
else
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
case value = session_options_with_string_keys['new_session']
when true
@session = new_session
when false
begin
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
# CGI::Session raises ArgumentError if 'new_session' == false
# and no session cookie or query param is present.
rescue ArgumentError
@session = Hash.new
end
when nil
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
else
raise ArgumentError, "Invalid new_session option: #{value}"
end
@session['__valid_session']
end
@@ -159,7 +171,7 @@ end_msg
end
def session_options_with_string_keys
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).inject({}) { |options, (k,v)| options[k.to_s] = v; options }
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
end
end
@@ -170,7 +182,9 @@ end_msg
end
def out(output = $stdout)
convert_content_type!(@headers)
convert_content_type!
set_content_length!
output.binmode if output.respond_to?(:binmode)
output.sync = false if output.respond_to?(:sync=)
@@ -195,16 +209,22 @@ end_msg
end
private
def convert_content_type!(headers)
if header = headers.delete("Content-Type")
headers["type"] = header
def convert_content_type!
if content_type = @headers.delete("Content-Type")
@headers["type"] = content_type
end
if header = headers.delete("Content-type")
headers["type"] = header
if content_type = @headers.delete("Content-type")
@headers["type"] = content_type
end
if header = headers.delete("content-type")
headers["type"] = header
if content_type = @headers.delete("content-type")
@headers["type"] = content_type
end
end
# Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice
# for, say, a 2GB streaming file.
def set_content_length!
@headers["Content-Length"] = @body.size unless @body.respond_to?(:call)
end
end
end

View File

@@ -75,11 +75,13 @@ module ActionController #:nodoc:
# will also use /code/weblog/components as template root
# and find templates in /code/weblog/components/admin/parties/users/
def uses_component_template_root
path_of_calling_controller = File.dirname(caller[0].split(/:\d+:/, 2).first)
path_of_calling_controller = File.dirname(caller[1].split(/:\d+:/, 2).first)
path_of_controller_root = path_of_calling_controller.sub(/#{Regexp.escape(File.dirname(controller_path))}$/, "")
self.template_root = path_of_controller_root
end
deprecate :uses_component_template_root => 'Components are deprecated and will be removed in Rails 2.0.'
end
module InstanceMethods

View File

@@ -616,10 +616,7 @@ module ActionController #:nodoc:
end
def perform_action_with_filters
#result = perform_filters do
# perform_action_without_filters unless performed?
#end
@before_filter_chain_aborted = (call_filter(self.class.filter_chain, 0) == false)
call_filter(self.class.filter_chain, 0)
end
def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
@@ -640,7 +637,7 @@ module ActionController #:nodoc:
filter.call(self) do
halted = call_filter(chain, index.next)
end
halt_filter_chain(filter.filter, :no_yield) if halted == false
halt_filter_chain(filter.filter, :no_yield) if halted == false unless @before_filter_chain_aborted
halted
end
@@ -653,6 +650,7 @@ module ActionController #:nodoc:
logger.info "Filter chain halted as [#{filter.inspect}] returned false."
end
end
@before_filter_chain_aborted = true
return false
end

View File

@@ -113,7 +113,7 @@ module ActionController
# performed on the location header.
def follow_redirect!
raise "not a redirect! #{@status} #{@status_message}" unless redirect?
get(interpret_uri(headers["location"].first))
get(interpret_uri(headers['location'].first))
status
end
@@ -493,6 +493,8 @@ module ActionController
%w(get post cookies assigns xml_http_request).each do |method|
define_method(method) do |*args|
reset! unless @integration_session
# reset the html_document variable, but only for new get/post calls
@html_document = nil unless %w(cookies assigns).include?(method)
returning @integration_session.send(method, *args) do
copy_session_variables!
end

View File

@@ -27,9 +27,9 @@ module ActionController #:nodoc:
# With layouts, you can flip it around and have the common structure know where to insert changing content. This means
# that the header and footer are only mentioned in one place, like this:
#
# <!-- The header part of this layout -->
# // The header part of this layout
# <%= yield %>
# <!-- The footer part of this layout -->
# // The footer part of this layout -->
#
# And then you have content pages that look like this:
#
@@ -38,9 +38,9 @@ module ActionController #:nodoc:
# Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout,
# like this:
#
# <!-- The header part of this layout -->
# // The header part of this layout
# hello world
# <!-- The footer part of this layout -->
# // The footer part of this layout -->
#
# == Accessing shared variables
#
@@ -266,7 +266,7 @@ module ActionController #:nodoc:
def candidate_for_layout?(options)
(options.has_key?(:layout) && options[:layout] != false) ||
options.values_at(:text, :xml, :file, :inline, :partial, :nothing).compact.empty? &&
options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing).compact.empty? &&
!template_exempt_from_layout?(default_template_name(options[:action] || options[:template]))
end

View File

@@ -135,12 +135,14 @@ module Mime
HTML = Type.new "text/html", :html, %w( application/xhtml+xml )
JS = Type.new "text/javascript", :js, %w( application/javascript application/x-javascript )
ICS = Type.new "text/calendar", :ics
CSV = Type.new "text/csv", :csv
XML = Type.new "application/xml", :xml, %w( text/xml application/x-xml )
RSS = Type.new "application/rss+xml", :rss
ATOM = Type.new "application/atom+xml", :atom
YAML = Type.new "application/x-yaml", :yaml, %w( text/yaml )
JSON = Type.new "application/json", :json, %w( text/x-json )
SET = [ ALL, TEXT, HTML, JS, ICS, XML, RSS, ATOM, YAML ]
SET = [ ALL, TEXT, HTML, JS, ICS, XML, RSS, ATOM, YAML, JSON ]
LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k == "" }
@@ -157,6 +159,8 @@ module Mime
LOOKUP["text/calendar"] = ICS
LOOKUP["text/csv"] = CSV
LOOKUP["application/xml"] = XML
LOOKUP["text/xml"] = XML
LOOKUP["application/x-xml"] = XML
@@ -167,7 +171,10 @@ module Mime
LOOKUP["application/rss+xml"] = RSS
LOOKUP["application/atom+xml"] = ATOM
LOOKUP["application/json"] = JSON
LOOKUP["text/x-json"] = JSON
EXTENSION_LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k == "" }
EXTENSION_LOOKUP["html"] = HTML
@@ -181,9 +188,13 @@ module Mime
EXTENSION_LOOKUP["ics"] = ICS
EXTENSION_LOOKUP["csv"] = CSV
EXTENSION_LOOKUP["yml"] = YAML
EXTENSION_LOOKUP["yaml"] = YAML
EXTENSION_LOOKUP["rss"] = RSS
EXTENSION_LOOKUP["atom"] = ATOM
end
EXTENSION_LOOKUP["json"] = JSON
end

View File

@@ -13,14 +13,18 @@ module ActionController
@parameters ||= request_parameters.update(query_parameters).update(path_parameters).with_indifferent_access
end
# Returns the HTTP request method as a lowercase symbol (:get, for example)
# Returns the HTTP request method as a lowercase symbol (:get, for example). Note, HEAD is returned as :get
# since the two are supposedly to be functionaly equivilent for all purposes except that HEAD won't return a response
# body (which Rails also takes care of elsewhere).
def method
@request_method ||= (!parameters[:_method].blank? && @env['REQUEST_METHOD'] == 'POST') ?
parameters[:_method].to_s.downcase.to_sym :
@env['REQUEST_METHOD'].downcase.to_sym
@request_method == :head ? :get : @request_method
end
# Is this a GET request? Equivalent to request.method == :get
# Is this a GET (or HEAD) request? Equivalent to request.method == :get
def get?
method == :get
end
@@ -40,9 +44,10 @@ module ActionController
method == :delete
end
# Is this a HEAD request? Equivalent to request.method == :head
# Is this a HEAD request? HEAD is mapped as :get for request.method, so here we ask the
# REQUEST_METHOD header directly. Thus, for head, both get? and head? will return true.
def head?
method == :head
@env['REQUEST_METHOD'].downcase.to_sym == :head
end
# Determine whether the body of a HTTP call is URL-encoded (default)

View File

@@ -34,14 +34,16 @@ module ActionController #:nodoc:
# Overwrite to implement custom logging of errors. By default logs as fatal.
def log_error(exception) #:doc:
if ActionView::TemplateError === exception
logger.fatal(exception.to_s)
else
logger.fatal(
"\n\n#{exception.class} (#{exception.message}):\n " +
clean_backtrace(exception).join("\n ") +
"\n\n"
)
ActiveSupport::Deprecation.silence do
if ActionView::TemplateError === exception
logger.fatal(exception.to_s)
else
logger.fatal(
"\n\n#{exception.class} (#{exception.message}):\n " +
clean_backtrace(exception).join("\n ") +
"\n\n"
)
end
end
end

View File

@@ -27,7 +27,7 @@ module ActionController
def redirect(to_url, permanently = false)
@headers["Status"] = "302 Found" unless @headers["Status"] == "301 Moved Permanently"
@headers["location"] = to_url
@headers["Location"] = to_url
@body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
end

View File

@@ -950,6 +950,10 @@ module ActionController
route.significant_keys << :action
end
if !route.significant_keys.include?(:controller)
raise ArgumentError, "Illegal route: the :controller must be specified!"
end
route
end
end
@@ -975,6 +979,14 @@ module ActionController
@set.add_named_route(name, path, options)
end
# Added deprecation notice for anyone who already added a named route called "root".
# It'll be used as a shortcut for map.connect '' in Rails 2.0.
def root(*args, &proc)
super unless args.length >= 1 && proc.nil?
@set.add_named_route("root", *args)
end
deprecate :root => "(as the the label for a named route) will become a shortcut for map.connect '', so find another name"
def method_missing(route_name, *args, &proc)
super unless args.length >= 1 && proc.nil?
@set.add_named_route(route_name, *args)
@@ -996,7 +1008,11 @@ module ActionController
def clear!
@routes = {}
@helpers = []
@module = Module.new
@module ||= Module.new
@module.instance_methods.each do |selector|
@module.send :remove_method, selector
end
end
def add(name, route)

View File

@@ -5,6 +5,8 @@ require 'base64'
class CGI
class Session
attr_reader :data
# Return this session's underlying Session instance. Useful for the DB-backed session stores.
def model
@dbman.model if @dbman

View File

@@ -26,6 +26,10 @@ class CGI #:nodoc:all
def delete
@@session_data.delete(@session_id)
end
def data
@@session_data[@session_id]
end
end
end
end

View File

@@ -93,6 +93,10 @@ begin
end
@session_data = {}
end
def data
@session_data
end
end
end
end

View File

@@ -120,16 +120,16 @@ module ActionController #:nodoc:
end
def process_cleanup_with_session_management_support
process_cleanup_without_session_management_support
clear_persistent_model_associations
process_cleanup_without_session_management_support
end
# Clear cached associations in session data so they don't overflow
# the database field. Only applies to ActiveRecordStore since there
# is not a standard way to iterate over session data.
def clear_persistent_model_associations #:doc:
if defined?(@_session) && @_session.instance_variables.include?('@data')
session_data = @_session.instance_variable_get('@data')
if defined?(@_session) && @_session.respond_to?(:data)
session_data = @_session.data
if session_data && session_data.respond_to?(:each_value)
session_data.each_value do |obj|

View File

@@ -3,9 +3,11 @@ module ActionController
# Defines the standard HTTP status codes, by integer, with their
# corresponding default message texts.
# Source: http://www.iana.org/assignments/http-status-codes
STATUS_CODES = {
100 => "Continue",
101 => "Switching Protocols",
102 => "Processing",
200 => "OK",
201 => "Created",
@@ -14,6 +16,8 @@ module ActionController
204 => "No Content",
205 => "Reset Content",
206 => "Partial Content",
207 => "Multi-Status",
226 => "IM Used",
300 => "Multiple Choices",
301 => "Moved Permanently",
@@ -41,13 +45,19 @@ module ActionController
415 => "Unsupported Media Type",
416 => "Requested Range Not Satisfiable",
417 => "Expectation Failed",
422 => "Unprocessable Entity",
423 => "Locked",
424 => "Failed Dependency",
426 => "Upgrade Required",
500 => "Internal Server Error",
501 => "Not Implemented",
502 => "Bad Gateway",
503 => "Service Unavailable",
504 => "Gateway Timeout",
505 => "HTTP Version Not Supported"
505 => "HTTP Version Not Supported",
507 => "Insufficient Storage",
510 => "Not Extended"
}
# Provides a symbol-to-fixnum lookup for converting a symbol (like

View File

@@ -38,7 +38,7 @@ module ActionController #:nodoc:
def reset_session
@session = TestSession.new
end
end
def raw_post
if raw_post = env['RAW_POST_DATA']
@@ -179,7 +179,7 @@ module ActionController #:nodoc:
# returns the redirection location or nil
def redirect_url
redirect? ? headers['location'] : nil
headers['Location']
end
# does the redirect location match this regexp pattern?
@@ -275,27 +275,40 @@ module ActionController #:nodoc:
end
class TestSession #:nodoc:
def initialize(attributes = {})
attr_accessor :session_id
def initialize(attributes = nil)
@session_id = ''
@attributes = attributes
@saved_attributes = nil
end
def data
@attributes ||= @saved_attributes || {}
end
def [](key)
@attributes[key]
data[key]
end
def []=(key, value)
@attributes[key] = value
data[key] = value
end
def session_id
""
def update
@saved_attributes = @attributes
end
def update() end
def close() end
def delete() @attributes = {} end
def delete
@attributes = nil
end
def close
update
delete
end
end
# Essentially generates a modified Tempfile object similar to the object
# you'd get from the standard library CGI module in a multipart
# request. This means you can use an ActionController::TestUploadedFile

View File

@@ -85,8 +85,10 @@ module ActionView
# <%= error_message_on "post", "title", "Title simply ", " (or it won't work)", "inputError" %> =>
# <div class="inputError">Title simply can't be empty (or it won't work)</div>
def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError")
if errors = instance_variable_get("@#{object}").errors.on(method)
if object = instance_variable_get("@#{object}") && errors = object.errors.on(method)
content_tag("div", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", :class => css_class)
else
''
end
end
@@ -167,6 +169,8 @@ module ActionView
to_date_select_tag(options)
when :datetime, :timestamp
to_datetime_select_tag(options)
when :time
to_time_select_tag(options)
when :boolean
to_boolean_select_tag(options)
end
@@ -208,6 +212,15 @@ module ActionView
end
end
alias_method :to_time_select_tag_without_error_wrapping, :to_time_select_tag
def to_time_select_tag(options = {})
if object.respond_to?("errors") && object.errors.respond_to?("on")
error_wrapping(to_time_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
else
to_time_select_tag_without_error_wrapping(options)
end
end
def error_wrapping(html_tag, has_error)
has_error ? Base.field_error_proc.call(html_tag, self) : html_tag
end

View File

@@ -62,7 +62,7 @@ module ActionView
when 40..59 then 'less than a minute'
else '1 minute'
end
when 2..44 then "#{distance_in_minutes} minutes"
when 45..89 then 'about 1 hour'
when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
@@ -74,12 +74,12 @@ module ActionView
else "over #{(distance_in_minutes / 525960).round} years"
end
end
# Like distance_of_time_in_words, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
def time_ago_in_words(from_time, include_seconds = false)
distance_of_time_in_words(from_time, Time.now, include_seconds)
end
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
# Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by
@@ -108,6 +108,19 @@ module ActionView
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(options)
end
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified
# time-based attribute (identified by +method+) on an object assigned to the template (identified by +object+).
# You can include the seconds with <tt>:include_seconds</tt>.
# Examples:
#
# time_select("post", "sunrise")
# time_select("post", "start_time", :include_seconds => true)
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
def time_select(object_name, method, options = {})
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_time_select_tag(options)
end
# Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based
# attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples:
#
@@ -119,36 +132,55 @@ module ActionView
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datetime_select_tag(options)
end
# Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
def select_date(date = Date.today, options = {})
select_year(date, options) + select_month(date, options) + select_day(date, options)
end
# Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
def select_datetime(datetime = Time.now, options = {})
select_year(datetime, options) + select_month(datetime, options) + select_day(datetime, options) +
select_hour(datetime, options) + select_minute(datetime, options)
# It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
# will be appened onto the <tt>:order</tt> passed in. You can also add <tt>:date_separator</tt> and <tt>:time_separator</tt>
# keys to the +options+ to control visual display of the elements.
def select_datetime(datetime = Time.now, options = {})
separator = options[:datetime_separator] || ''
select_date(datetime, options) + separator + select_time(datetime, options)
end
# Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
# It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
# will be appened onto the <tt>:order</tt> passed in.
def select_date(date = Date.today, options = {})
options[:order] ||= []
[:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) }
select_date = ''
options[:order].each do |o|
select_date << self.send("select_#{o}", date, options)
end
select_date
end
# Returns a set of html select-tags (one for hour and minute)
# You can set <tt>:add_separator</tt> key to format the output.
def select_time(datetime = Time.now, options = {})
h = select_hour(datetime, options) + select_minute(datetime, options) + (options[:include_seconds] ? select_second(datetime, options) : '')
separator = options[:time_separator] || ''
select_hour(datetime, options) + separator + select_minute(datetime, options) + (options[:include_seconds] ? separator + select_second(datetime, options) : '')
end
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
# The <tt>second</tt> can also be substituted for a second number.
# Override the field name using the <tt>:field_name</tt> option, 'second' by default.
def select_second(datetime, options = {})
second_options = []
0.upto(59) do |second|
second_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) == second) ?
%(<option value="#{leading_zero_on_single_digits(second)}" selected="selected">#{leading_zero_on_single_digits(second)}</option>\n) :
%(<option value="#{leading_zero_on_single_digits(second)}">#{leading_zero_on_single_digits(second)}</option>\n)
)
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) : ''
if options[:use_hidden]
options[:include_seconds] ? hidden_html(options[:field_name] || 'second', val, options) : ''
else
second_options = []
0.upto(59) do |second|
second_options << ((val == second) ?
%(<option value="#{leading_zero_on_single_digits(second)}" selected="selected">#{leading_zero_on_single_digits(second)}</option>\n) :
%(<option value="#{leading_zero_on_single_digits(second)}">#{leading_zero_on_single_digits(second)}</option>\n)
)
end
select_html(options[:field_name] || 'second', second_options, options)
end
select_html(options[:field_name] || 'second', second_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
end
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
@@ -156,84 +188,100 @@ module ActionView
# The <tt>minute</tt> can also be substituted for a minute number.
# Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
def select_minute(datetime, options = {})
minute_options = []
0.step(59, options[:minute_step] || 1) do |minute|
minute_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.min) == minute) ?
%(<option value="#{leading_zero_on_single_digits(minute)}" selected="selected">#{leading_zero_on_single_digits(minute)}</option>\n) :
%(<option value="#{leading_zero_on_single_digits(minute)}">#{leading_zero_on_single_digits(minute)}</option>\n)
)
end
select_html(options[:field_name] || 'minute', minute_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.min) : ''
if options[:use_hidden]
hidden_html(options[:field_name] || 'minute', val, options)
else
minute_options = []
0.step(59, options[:minute_step] || 1) do |minute|
minute_options << ((val == minute) ?
%(<option value="#{leading_zero_on_single_digits(minute)}" selected="selected">#{leading_zero_on_single_digits(minute)}</option>\n) :
%(<option value="#{leading_zero_on_single_digits(minute)}">#{leading_zero_on_single_digits(minute)}</option>\n)
)
end
select_html(options[:field_name] || 'minute', minute_options, options)
end
end
# Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
# The <tt>hour</tt> can also be substituted for a hour number.
# Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
def select_hour(datetime, options = {})
hour_options = []
0.upto(23) do |hour|
hour_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) == hour) ?
%(<option value="#{leading_zero_on_single_digits(hour)}" selected="selected">#{leading_zero_on_single_digits(hour)}</option>\n) :
%(<option value="#{leading_zero_on_single_digits(hour)}">#{leading_zero_on_single_digits(hour)}</option>\n)
)
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) : ''
if options[:use_hidden]
hidden_html(options[:field_name] || 'hour', val, options)
else
hour_options = []
0.upto(23) do |hour|
hour_options << ((val == hour) ?
%(<option value="#{leading_zero_on_single_digits(hour)}" selected="selected">#{leading_zero_on_single_digits(hour)}</option>\n) :
%(<option value="#{leading_zero_on_single_digits(hour)}">#{leading_zero_on_single_digits(hour)}</option>\n)
)
end
select_html(options[:field_name] || 'hour', hour_options, options)
end
select_html(options[:field_name] || 'hour', hour_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
end
# Returns a select tag with options for each of the days 1 through 31 with the current day selected.
# The <tt>date</tt> can also be substituted for a hour number.
# Override the field name using the <tt>:field_name</tt> option, 'day' by default.
def select_day(date, options = {})
day_options = []
1.upto(31) do |day|
day_options << ((date && (date.kind_of?(Fixnum) ? date : date.day) == day) ?
%(<option value="#{day}" selected="selected">#{day}</option>\n) :
%(<option value="#{day}">#{day}</option>\n)
)
val = date ? (date.kind_of?(Fixnum) ? date : date.day) : ''
if options[:use_hidden]
hidden_html(options[:field_name] || 'day', val, options)
else
day_options = []
1.upto(31) do |day|
day_options << ((val == day) ?
%(<option value="#{day}" selected="selected">#{day}</option>\n) :
%(<option value="#{day}">#{day}</option>\n)
)
end
select_html(options[:field_name] || 'day', day_options, options)
end
select_html(options[:field_name] || 'day', day_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
end
# Returns a select tag with options for each of the months January through December with the current month selected.
# The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values
# (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names --
# set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you want both numbers and names,
# set the <tt>:add_month_numbers</tt> key in +options+ to true. Examples:
# set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer to show month names as abbreviations,
# set the <tt>:use_short_month</tt> key in +options+ to true. If you want to use your own month names, set the
# <tt>:use_month_names</tt> key in +options+ to an array of 12 month names.
#
# Examples:
#
# select_month(Date.today) # Will use keys like "January", "March"
# select_month(Date.today, :use_month_numbers => true) # Will use keys like "1", "3"
# select_month(Date.today, :add_month_numbers => true) # Will use keys like "1 - January", "3 - March"
# select_month(Date.today, :use_short_month => true) # Will use keys like "Jan", "Mar"
# select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...)) # Will use keys like "Januar", "Marts"
#
# Override the field name using the <tt>:field_name</tt> option, 'month' by default.
#
# If you would prefer to show month names as abbreviations, set the
# <tt>:use_short_month</tt> key in +options+ to true.
def select_month(date, options = {})
month_options = []
month_names = options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES
val = date ? (date.kind_of?(Fixnum) ? date : date.month) : ''
if options[:use_hidden]
hidden_html(options[:field_name] || 'month', val, options)
else
month_options = []
month_names = options[:use_month_names] || (options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES)
month_names.unshift(nil) if month_names.size < 13
1.upto(12) do |month_number|
month_name = if options[:use_month_numbers]
month_number
elsif options[:add_month_numbers]
month_number.to_s + ' - ' + month_names[month_number]
else
month_names[month_number]
end
1.upto(12) do |month_number|
month_name = if options[:use_month_numbers]
month_number
elsif options[:add_month_numbers]
month_number.to_s + ' - ' + month_names[month_number]
else
month_names[month_number]
month_options << ((val == month_number) ?
%(<option value="#{month_number}" selected="selected">#{month_name}</option>\n) :
%(<option value="#{month_number}">#{month_name}</option>\n)
)
end
month_options << ((date && (date.kind_of?(Fixnum) ? date : date.month) == month_number) ?
%(<option value="#{month_number}" selected="selected">#{month_name}</option>\n) :
%(<option value="#{month_number}">#{month_name}</option>\n)
)
select_html(options[:field_name] || 'month', month_options, options)
end
select_html(options[:field_name] || 'month', month_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
end
# Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius
@@ -243,37 +291,51 @@ module ActionView
#
# select_year(Date.today, :start_year => 1992, :end_year => 2007) # ascending year values
# select_year(Date.today, :start_year => 2005, :end_year => 1900) # descending year values
# select_year(2006, :start_year => 2000, :end_year => 2010)
#
# Override the field name using the <tt>:field_name</tt> option, 'year' by default.
def select_year(date, options = {})
year_options = []
y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
val = date ? (date.kind_of?(Fixnum) ? date : date.year) : ''
if options[:use_hidden]
hidden_html(options[:field_name] || 'year', val, options)
else
year_options = []
y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
step_val = start_year < end_year ? 1 : -1
start_year.step(end_year, step_val) do |year|
year_options << ((date && (date.kind_of?(Fixnum) ? date : date.year) == year) ?
%(<option value="#{year}" selected="selected">#{year}</option>\n) :
%(<option value="#{year}">#{year}</option>\n)
)
start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
step_val = start_year < end_year ? 1 : -1
start_year.step(end_year, step_val) do |year|
year_options << ((val == year) ?
%(<option value="#{year}" selected="selected">#{year}</option>\n) :
%(<option value="#{year}">#{year}</option>\n)
)
end
select_html(options[:field_name] || 'year', year_options, options)
end
select_html(options[:field_name] || 'year', year_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
end
private
def select_html(type, options, prefix = nil, include_blank = false, discard_type = false, disabled = false)
select_html = %(<select name="#{prefix || DEFAULT_PREFIX})
select_html << "[#{type}]" unless discard_type
select_html << %(")
select_html << %( disabled="disabled") if disabled
def select_html(type, html_options, options)
name_and_id_from_options(options, type)
select_html = %(<select id="#{options[:id]}" name="#{options[:name]}")
select_html << %( disabled="disabled") if options[:disabled]
select_html << %(>\n)
select_html << %(<option value=""></option>\n) if include_blank
select_html << options.to_s
select_html << %(<option value=""></option>\n) if options[:include_blank]
select_html << html_options.to_s
select_html << "</select>\n"
end
def hidden_html(type, value, options)
name_and_id_from_options(options, type)
hidden_html = %(<input type="hidden" id="#{options[:id]}" name="#{options[:name]}" value="#{value}" />\n)
end
def name_and_id_from_options(options, type)
options[:name] = (options[:prefix] || DEFAULT_PREFIX) + (options[:discard_type] ? '' : "[#{type}]")
options[:id] = options[:name].gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
end
def leading_zero_on_single_digits(number)
number > 9 ? number : "0#{number}"
end
@@ -283,45 +345,71 @@ module ActionView
include DateHelper
def to_date_select_tag(options = {})
defaults = { :discard_type => true }
options = defaults.merge(options)
options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
value = value(object)
date = options[:include_blank] ? (value || 0) : (value || Date.today)
date_or_time_select options.merge(:discard_hour => true)
end
date_select = ''
options[:order] = [:month, :year, :day] if options[:month_before_year] # For backwards compatibility
options[:order] ||= [:year, :month, :day]
position = {:year => 1, :month => 2, :day => 3}
discard = {}
discard[:year] = true if options[:discard_year]
discard[:month] = true if options[:discard_month]
discard[:day] = true if options[:discard_day] or options[:discard_month]
options[:order].each do |param|
date_select << self.send("select_#{param}", date, options_with_prefix.call(position[param])) unless discard[param]
end
date_select
def to_time_select_tag(options = {})
date_or_time_select options.merge(:discard_year => true, :discard_month => true)
end
def to_datetime_select_tag(options = {})
defaults = { :discard_type => true }
options = defaults.merge(options)
options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
value = value(object)
datetime = options[:include_blank] ? (value || nil) : (value || Time.now)
datetime_select = select_year(datetime, options_with_prefix.call(1))
datetime_select << select_month(datetime, options_with_prefix.call(2)) unless options[:discard_month]
datetime_select << select_day(datetime, options_with_prefix.call(3)) unless options[:discard_day] || options[:discard_month]
datetime_select << ' &mdash; ' + select_hour(datetime, options_with_prefix.call(4)) unless options[:discard_hour]
datetime_select << ' : ' + select_minute(datetime, options_with_prefix.call(5)) unless options[:discard_minute] || options[:discard_hour]
datetime_select
date_or_time_select options
end
private
def date_or_time_select(options)
defaults = { :discard_type => true }
options = defaults.merge(options)
datetime = value(object)
datetime ||= Time.now unless options[:include_blank]
position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
order = (options[:order] ||= [:year, :month, :day])
# Discard explicit and implicit by not being included in the :order
discard = {}
discard[:year] = true if options[:discard_year] or !order.include?(:year)
discard[:month] = true if options[:discard_month] or !order.include?(:month)
discard[:day] = true if options[:discard_day] or discard[:month] or !order.include?(:day)
discard[:hour] = true if options[:discard_hour]
discard[:minute] = true if options[:discard_minute] or discard[:hour]
discard[:second] = true unless options[:include_seconds] && !discard[:minute]
# Maintain valid dates by including hidden fields for discarded elements
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
# Ensure proper ordering of :hour, :minute and :second
[:hour, :minute, :second].each { |o| order.delete(o); order.push(o) }
date_or_time_select = ''
order.reverse.each do |param|
# Send hidden fields for discarded elements once output has started
# This ensures AR can reconstruct valid dates using ParseDate
next if discard[param] && date_or_time_select.empty?
date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param]))))
date_or_time_select.insert(0,
case param
when :hour then (discard[:year] && discard[:day] ? "" : " &mdash; ")
when :minute then " : "
when :second then options[:include_seconds] ? " : " : ""
else ""
end)
end
date_or_time_select
end
def options_with_prefix(position, options)
prefix = "#{@object_name}"
if options[:index]
prefix << "[#{options[:index]}]"
elsif @auto_index
prefix << "[#{@auto_index}]"
end
options.merge(:prefix => "#{prefix}[#{@method_name}(#{position}i)]")
end
end
class FormBuilder
@@ -329,6 +417,10 @@ module ActionView
@template.date_select(@object_name, method, options.merge(:object => @object))
end
def time_select(method, options = {})
@template.time_select(@object_name, method, options.merge(:object => @object))
end
def datetime_select(method, options = {})
@template.datetime_select(@object_name, method, options.merge(:object => @object))
end

View File

@@ -0,0 +1,34 @@
module ActionView
module Helpers
module PrototypeHelper
def update_element_function(element_id, options = {}, &block)
content = escape_javascript(options[:content] || '')
content = escape_javascript(capture(&block)) if block
javascript_function = case (options[:action] || :update)
when :update
if options[:position]
"new Insertion.#{options[:position].to_s.camelize}('#{element_id}','#{content}')"
else
"$('#{element_id}').innerHTML = '#{content}'"
end
when :empty
"$('#{element_id}').innerHTML = ''"
when :remove
"Element.remove('#{element_id}')"
else
raise ArgumentError, "Invalid action, choose one of :update, :remove, :empty"
end
javascript_function << ";\n"
options[:binding] ? concat(javascript_function, options[:binding]) : javascript_function
end
deprecate :update_element_function => "use RJS instead"
end
end
end

View File

@@ -142,11 +142,13 @@ module ActionView
#
# Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base.
# Like collection_select and datetime_select.
def fields_for(object_name, *args, &proc)
def fields_for(object_name, *args, &block)
raise ArgumentError, "Missing block" unless block_given?
options = args.last.is_a?(Hash) ? args.pop : {}
object = args.first
yield((options[:builder] || FormBuilder).new(object_name, object, self, options, proc))
builder = options[:builder] || ActionView::Base.default_form_builder
yield builder.new(object_name, object, self, options, block)
end
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -436,4 +438,9 @@ module ActionView
end
end
end
class Base
cattr_accessor :default_form_builder
self.default_form_builder = ::ActionView::Helpers::FormBuilder
end
end

View File

@@ -1,6 +1,6 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
// (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com)
// Contributors:
// Richard Livsey
// Rahul Bhargava
@@ -473,6 +473,7 @@ Ajax.InPlaceEditor.prototype = {
this.element = $(element);
this.options = Object.extend({
paramName: "value",
okButton: true,
okText: "ok",
cancelLink: true,
@@ -604,7 +605,7 @@ Ajax.InPlaceEditor.prototype = {
var textField = document.createElement("input");
textField.obj = this;
textField.type = "text";
textField.name = "value";
textField.name = this.options.paramName;
textField.value = text;
textField.style.backgroundColor = this.options.highlightcolor;
textField.className = 'editor_field';
@@ -617,7 +618,7 @@ Ajax.InPlaceEditor.prototype = {
this.options.textarea = true;
var textArea = document.createElement("textarea");
textArea.obj = this;
textArea.name = "value";
textArea.name = this.options.paramName;
textArea.value = this.convertHTMLLineBreaks(text);
textArea.rows = this.options.rows;
textArea.cols = this.options.cols || 40;

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
// Justin Palmer (http://encytemedia.com/)
// Mark Pilgrim (http://diveintomark.org/)
@@ -10,7 +10,7 @@
// converts rgb() and #xxx to #xxxxxx format,
// returns self (or first argument) if not convertable
String.prototype.parseColor = function() {
var color = '#';
var color = '#';
if(this.slice(0,4) == 'rgb(') {
var cols = this.slice(4,this.length-1).split(',');
var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
@@ -939,8 +939,142 @@ Effect.Fold = function(element) {
}}, arguments[1] || {}));
};
Effect.Morph = Class.create();
Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
initialize: function(element) {
this.element = $(element);
if(!this.element) throw(Effect._elementDoesNotExistError);
var options = Object.extend({
style: ''
}, arguments[1] || {});
this.start(options);
},
setup: function(){
function parseColor(color){
if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
color = color.parseColor();
return $R(0,2).map(function(i){
return parseInt( color.slice(i*2+1,i*2+3), 16 )
});
}
this.transforms = this.options.style.parseStyle().map(function(property){
var originalValue = this.element.getStyle(property[0]);
return $H({
style: property[0],
originalValue: property[1].unit=='color' ?
parseColor(originalValue) : parseFloat(originalValue || 0),
targetValue: property[1].unit=='color' ?
parseColor(property[1].value) : property[1].value,
unit: property[1].unit
});
}.bind(this)).reject(function(transform){
return (
(transform.originalValue == transform.targetValue) ||
(
transform.unit != 'color' &&
(isNaN(transform.originalValue) || isNaN(transform.targetValue))
)
)
});
},
update: function(position) {
var style = $H(), value = null;
this.transforms.each(function(transform){
value = transform.unit=='color' ?
$R(0,2).inject('#',function(m,v,i){
return m+(Math.round(transform.originalValue[i]+
(transform.targetValue[i] - transform.originalValue[i])*position)).toColorPart() }) :
transform.originalValue + Math.round(
((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
style[transform.style] = value;
});
this.element.setStyle(style);
}
});
Effect.Transform = Class.create();
Object.extend(Effect.Transform.prototype, {
initialize: function(tracks){
this.tracks = [];
this.options = arguments[1] || {};
this.addTracks(tracks);
},
addTracks: function(tracks){
tracks.each(function(track){
var data = $H(track).values().first();
this.tracks.push($H({
ids: $H(track).keys().first(),
effect: Effect.Morph,
options: { style: data }
}));
}.bind(this));
return this;
},
play: function(){
return new Effect.Parallel(
this.tracks.map(function(track){
var elements = [$(track.ids) || $$(track.ids)].flatten();
return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
}).flatten(),
this.options
);
}
});
Element.CSS_PROPERTIES = ['azimuth', 'backgroundAttachment', 'backgroundColor', 'backgroundImage',
'backgroundPosition', 'backgroundRepeat', 'borderBottomColor', 'borderBottomStyle',
'borderBottomWidth', 'borderCollapse', 'borderLeftColor', 'borderLeftStyle', 'borderLeftWidth',
'borderRightColor', 'borderRightStyle', 'borderRightWidth', 'borderSpacing', 'borderTopColor',
'borderTopStyle', 'borderTopWidth', 'bottom', 'captionSide', 'clear', 'clip', 'color', 'content',
'counterIncrement', 'counterReset', 'cssFloat', 'cueAfter', 'cueBefore', 'cursor', 'direction',
'display', 'elevation', 'emptyCells', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch',
'fontStyle', 'fontVariant', 'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight',
'listStyleImage', 'listStylePosition', 'listStyleType', 'marginBottom', 'marginLeft', 'marginRight',
'marginTop', 'markerOffset', 'marks', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'opacity',
'orphans', 'outlineColor', 'outlineOffset', 'outlineStyle', 'outlineWidth', 'overflowX', 'overflowY',
'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'page', 'pageBreakAfter', 'pageBreakBefore',
'pageBreakInside', 'pauseAfter', 'pauseBefore', 'pitch', 'pitchRange', 'position', 'quotes',
'richness', 'right', 'size', 'speakHeader', 'speakNumeral', 'speakPunctuation', 'speechRate', 'stress',
'tableLayout', 'textAlign', 'textDecoration', 'textIndent', 'textShadow', 'textTransform', 'top',
'unicodeBidi', 'verticalAlign', 'visibility', 'voiceFamily', 'volume', 'whiteSpace', 'widows',
'width', 'wordSpacing', 'zIndex'];
Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
String.prototype.parseStyle = function(){
var element = Element.extend(document.createElement('div'));
element.innerHTML = '<div style="' + this + '"></div>';
var style = element.down().style, styleRules = $H();
Element.CSS_PROPERTIES.each(function(property){
if(style[property]) styleRules[property] = style[property];
});
var result = $H();
styleRules.each(function(pair){
var property = pair[0], value = pair[1], unit = null;
if(value.parseColor('#zzzzzz') != '#zzzzzz') {
value = value.parseColor();
unit = 'color';
} else if(Element.CSS_LENGTH.test(value))
var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/),
value = parseFloat(components[1]), unit = (components.length == 3) ? components[2] : null;
result[property.underscore().dasherize()] = $H({ value:value, unit:unit });
}.bind(this));
return result;
};
Element.morph = function(element, style) {
new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
return element;
};
['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
'collectTextNodes','collectTextNodesIgnoreClass'].each(
'collectTextNodes','collectTextNodesIgnoreClass','morph'].each(
function(f) { Element.Methods[f] = Element[f]; }
);

View File

@@ -1,5 +1,5 @@
/* Prototype JavaScript framework, version 1.5.0_rc1
* (c) 2005 Sam Stephenson <sam@conio.net>
/* Prototype JavaScript framework, version 1.5.0_rc2
* (c) 2005, 2006 Sam Stephenson <sam@conio.net>
*
* Prototype is freely distributable under the terms of an MIT-style license.
* For details, see the Prototype web site: http://prototype.conio.net/
@@ -7,7 +7,7 @@
/*--------------------------------------------------------------------------*/
var Prototype = {
Version: '1.5.0_rc1',
Version: '1.5.0_rc2',
BrowserFeatures: {
XPath: !!document.evaluate
},
@@ -37,8 +37,8 @@ Object.extend = function(destination, source) {
Object.extend(Object, {
inspect: function(object) {
try {
if (object == undefined) return 'undefined';
if (object == null) return 'null';
if (object === undefined) return 'undefined';
if (object === null) return 'null';
return object.inspect ? object.inspect() : object.toString();
} catch (e) {
if (e instanceof RangeError) return '...';
@@ -100,7 +100,7 @@ var Try = {
these: function() {
var returnValue;
for (var i = 0; i < arguments.length; i++) {
for (var i = 0, length = arguments.length; i < length; i++) {
var lambda = arguments[i];
try {
returnValue = lambda();
@@ -218,16 +218,28 @@ Object.extend(String.prototype, {
unescapeHTML: function() {
var div = document.createElement('div');
div.innerHTML = this.stripTags();
return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
return div.childNodes[0] ? (div.childNodes.length > 1 ?
$A(div.childNodes).inject('',function(memo,node){ return memo+node.nodeValue }) :
div.childNodes[0].nodeValue) : '';
},
toQueryParams: function() {
var pairs = this.match(/^\??(.*)$/)[1].split('&');
return pairs.inject({}, function(params, pairString) {
var pair = pairString.split('=');
var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;
params[decodeURIComponent(pair[0])] = value;
return params;
toQueryParams: function(separator) {
var match = this.strip().match(/([^?#]*)(#.*)?$/);
if (!match) return {};
return match[1].split(separator || '&').inject({}, function(hash, pair) {
if ((pair = pair.split('='))[0]) {
var name = decodeURIComponent(pair[0]);
var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;
if (hash[name] !== undefined) {
if (hash[name].constructor != Array)
hash[name] = [hash[name]];
if (value) hash[name].push(value);
}
else hash[name] = value;
}
return hash;
});
},
@@ -243,7 +255,7 @@ Object.extend(String.prototype, {
? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
: oStringList[0];
for (var i = 1, len = oStringList.length; i < len; i++) {
for (var i = 1, length = oStringList.length; i < length; i++) {
var s = oStringList[i];
camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
}
@@ -251,6 +263,14 @@ Object.extend(String.prototype, {
return camelizedString;
},
underscore: function() {
return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'-').toLowerCase();
},
dasherize: function() {
return this.gsub(/_/,'-');
},
inspect: function(useDoubleQuotes) {
var escapedString = this.replace(/\\/g, '\\\\');
if (useDoubleQuotes)
@@ -338,7 +358,7 @@ var Enumerable = {
return results;
},
detect: function (iterator) {
detect: function(iterator) {
var result;
this.each(function(value, index) {
if (iterator(value, index)) {
@@ -490,7 +510,7 @@ var $A = Array.from = function(iterable) {
return iterable.toArray();
} else {
var results = [];
for (var i = 0; i < iterable.length; i++)
for (var i = 0, length = iterable.length; i < length; i++)
results.push(iterable[i]);
return results;
}
@@ -503,7 +523,7 @@ if (!Array.prototype._reverse)
Object.extend(Array.prototype, {
_each: function(iterator) {
for (var i = 0; i < this.length; i++)
for (var i = 0, length = this.length; i < length; i++)
iterator(this[i]);
},
@@ -541,7 +561,7 @@ Object.extend(Array.prototype, {
},
indexOf: function(object) {
for (var i = 0; i < this.length; i++)
for (var i = 0, length = this.length; i < length; i++)
if (this[i] == object) return i;
return -1;
},
@@ -560,10 +580,32 @@ Object.extend(Array.prototype, {
});
},
clone: function() {
return [].concat(this);
},
inspect: function() {
return '[' + this.map(Object.inspect).join(', ') + ']';
}
});
Array.prototype.toArray = Array.prototype.clone;
if(window.opera){
Array.prototype.concat = function(){
var array = [];
for(var i = 0, length = this.length; i < length; i++) array.push(this[i]);
for(var i = 0, length = arguments.length; i < length; i++) {
if(arguments[i].constructor == Array) {
for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
array.push(arguments[i][j]);
} else {
array.push(arguments[i]);
}
}
return array;
}
}
var Hash = {
_each: function(iterator) {
for (var key in this) {
@@ -586,7 +628,7 @@ var Hash = {
},
merge: function(hash) {
return $H(hash).inject($H(this), function(mergedHash, pair) {
return $H(hash).inject(this, function(mergedHash, pair) {
mergedHash[pair.key] = pair.value;
return mergedHash;
});
@@ -594,6 +636,22 @@ var Hash = {
toQueryString: function() {
return this.map(function(pair) {
if (!pair.key) return null;
if (pair.value && pair.value.constructor == Array) {
pair.value = pair.value.compact();
if (pair.value.length < 2) {
pair.value = pair.value.reduce();
} else {
var key = encodeURIComponent(pair.key);
return pair.value.map(function(value) {
return key + '=' + encodeURIComponent(value);
}).join('&');
}
}
if (pair.value == undefined) pair[1] = '';
return pair.map(encodeURIComponent).join('=');
}).join('&');
},
@@ -660,18 +718,18 @@ Ajax.Responders = {
this.responders._each(iterator);
},
register: function(responderToAdd) {
if (!this.include(responderToAdd))
this.responders.push(responderToAdd);
register: function(responder) {
if (!this.include(responder))
this.responders.push(responder);
},
unregister: function(responderToRemove) {
this.responders = this.responders.without(responderToRemove);
unregister: function(responder) {
this.responders = this.responders.without(responder);
},
dispatch: function(callback, request, transport, json) {
this.each(function(responder) {
if (responder[callback] && typeof responder[callback] == 'function') {
if (typeof responder[callback] == 'function') {
try {
responder[callback].apply(responder, [request, transport, json]);
} catch (e) {}
@@ -686,7 +744,6 @@ Ajax.Responders.register({
onCreate: function() {
Ajax.activeRequestCount++;
},
onComplete: function() {
Ajax.activeRequestCount--;
}
@@ -699,19 +756,14 @@ Ajax.Base.prototype = {
method: 'post',
asynchronous: true,
contentType: 'application/x-www-form-urlencoded',
encoding: 'UTF-8',
parameters: ''
}
Object.extend(this.options, options || {});
},
responseIsSuccess: function() {
return this.transport.status == undefined
|| this.transport.status == 0
|| (this.transport.status >= 200 && this.transport.status < 300);
},
responseIsFailure: function() {
return !this.responseIsSuccess();
this.options.method = this.options.method.toLowerCase();
this.options.parameters = $H(typeof this.options.parameters == 'string' ?
this.options.parameters.toQueryParams() : this.options.parameters);
}
}
@@ -720,6 +772,8 @@ Ajax.Request.Events =
['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
_complete: false,
initialize: function(url, options) {
this.transport = Ajax.getTransport();
this.setOptions(options);
@@ -727,24 +781,28 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
},
request: function(url) {
var parameters = this.options.parameters || '';
if (parameters.length > 0) parameters += '&_=';
var params = this.options.parameters;
if (params.any()) params['_'] = '';
/* Simulate other verbs over post */
if (this.options.method != 'get' && this.options.method != 'post') {
parameters += (parameters.length > 0 ? '&' : '') + '_method=' + this.options.method;
if (!['get', 'post'].include(this.options.method)) {
// simulate other verbs over post
params['_method'] = this.options.method;
this.options.method = 'post';
}
try {
this.url = url;
if (this.options.method == 'get' && parameters.length > 0)
this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
this.url = url;
// when GET, append parameters to URL
if (this.options.method == 'get' && params.any())
this.url += (this.url.indexOf('?') >= 0 ? '&' : '?') +
params.toQueryString();
try {
Ajax.Responders.dispatch('onCreate', this, this.transport);
this.transport.open(this.options.method, this.url,
this.options.asynchronous);
this.transport.open(this.options.method.toUpperCase(), this.url,
this.options.asynchronous, this.options.username,
this.options.password);
if (this.options.asynchronous)
setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);
@@ -752,58 +810,110 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
this.transport.onreadystatechange = this.onStateChange.bind(this);
this.setRequestHeaders();
var body = this.options.postBody ? this.options.postBody : parameters;
this.transport.send(this.options.method == 'post' ? body : null);
var body = this.options.method == 'post' ?
(this.options.postBody || params.toQueryString()) : null;
this.transport.send(body);
/* Force Firefox to handle ready state 4 for synchronous requests */
if (!this.options.asynchronous && this.transport.overrideMimeType)
this.onStateChange();
} catch (e) {
}
catch (e) {
this.dispatchException(e);
}
},
setRequestHeaders: function() {
var requestHeaders =
['X-Requested-With', 'XMLHttpRequest',
'X-Prototype-Version', Prototype.Version,
'Accept', 'text/javascript, text/html, application/xml, text/xml, */*'];
if (this.options.method == 'post') {
requestHeaders.push('Content-type', this.options.contentType);
/* Force "Connection: close" for Mozilla browsers to work around
* a bug where XMLHttpReqeuest sends an incorrect Content-length
* header. See Mozilla Bugzilla #246651.
*/
if (this.transport.overrideMimeType)
requestHeaders.push('Connection', 'close');
}
if (this.options.requestHeaders)
requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
for (var i = 0; i < requestHeaders.length; i += 2)
this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
},
onStateChange: function() {
var readyState = this.transport.readyState;
if (readyState != 1)
if (readyState > 1 && !((readyState == 4) && this._complete))
this.respondToReadyState(this.transport.readyState);
},
header: function(name) {
setRequestHeaders: function() {
var headers = {
'X-Requested-With': 'XMLHttpRequest',
'X-Prototype-Version': Prototype.Version,
'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
};
if (this.options.method == 'post') {
headers['Content-type'] = this.options.contentType +
(this.options.encoding ? '; charset=' + this.options.encoding : '');
/* Force "Connection: close" for older Mozilla browsers to work
* around a bug where XMLHttpRequest sends an incorrect
* Content-length header. See Mozilla Bugzilla #246651.
*/
if (this.transport.overrideMimeType &&
(navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
headers['Connection'] = 'close';
}
// user-defined headers
if (typeof this.options.requestHeaders == 'object') {
var extras = this.options.requestHeaders;
if (typeof extras.push == 'function')
for (var i = 0, length = extras.length; i < length; i += 2)
headers[extras[i]] = extras[i+1];
else
$H(extras).each(function(pair) { headers[pair.key] = pair.value });
}
for (var name in headers)
this.transport.setRequestHeader(name, headers[name]);
},
success: function() {
return !this.transport.status
|| (this.transport.status >= 200 && this.transport.status < 300);
},
respondToReadyState: function(readyState) {
var state = Ajax.Request.Events[readyState];
var transport = this.transport, json = this.evalJSON();
if (state == 'Complete') {
try {
this._complete = true;
(this.options['on' + this.transport.status]
|| this.options['on' + (this.success() ? 'Success' : 'Failure')]
|| Prototype.emptyFunction)(transport, json);
} catch (e) {
this.dispatchException(e);
}
}
try {
(this.options['on' + state] || Prototype.emptyFunction)(transport, json);
Ajax.Responders.dispatch('on' + state, this, transport, json);
} catch (e) {
this.dispatchException(e);
}
if (state == 'Complete') {
if ((this.getHeader('Content-type') || '').strip().
match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
this.evalResponse();
// avoid memory leak in MSIE: clean up
this.transport.onreadystatechange = Prototype.emptyFunction;
}
},
getHeader: function(name) {
try {
return this.transport.getResponseHeader(name);
} catch (e) {}
} catch (e) { return null }
},
evalJSON: function() {
try {
return eval('(' + this.header('X-JSON') + ')');
} catch (e) {}
var json = this.getHeader('X-JSON');
return json ? eval('(' + json + ')') : null;
} catch (e) { return null }
},
evalResponse: function() {
@@ -814,35 +924,6 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
}
},
respondToReadyState: function(readyState) {
var event = Ajax.Request.Events[readyState];
var transport = this.transport, json = this.evalJSON();
if (event == 'Complete') {
try {
(this.options['on' + this.transport.status]
|| this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
|| Prototype.emptyFunction)(transport, json);
} catch (e) {
this.dispatchException(e);
}
if ((this.header('Content-type') || '').match(/^text\/javascript/i))
this.evalResponse();
}
try {
(this.options['on' + event] || Prototype.emptyFunction)(transport, json);
Ajax.Responders.dispatch('on' + event, this, transport, json);
} catch (e) {
this.dispatchException(e);
}
/* Avoid memory leak in MSIE: clean up the oncomplete event handler */
if (event == 'Complete')
this.transport.onreadystatechange = Prototype.emptyFunction;
},
dispatchException: function(exception) {
(this.options.onException || Prototype.emptyFunction)(this, exception);
Ajax.Responders.dispatch('onException', this, exception);
@@ -853,41 +934,37 @@ Ajax.Updater = Class.create();
Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
initialize: function(container, url, options) {
this.containers = {
success: container.success ? $(container.success) : $(container),
failure: container.failure ? $(container.failure) :
(container.success ? null : $(container))
this.container = {
success: (container.success || container),
failure: (container.failure || (container.success ? null : container))
}
this.transport = Ajax.getTransport();
this.setOptions(options);
var onComplete = this.options.onComplete || Prototype.emptyFunction;
this.options.onComplete = (function(transport, object) {
this.options.onComplete = (function(transport, param) {
this.updateContent();
onComplete(transport, object);
onComplete(transport, param);
}).bind(this);
this.request(url);
},
updateContent: function() {
var receiver = this.responseIsSuccess() ?
this.containers.success : this.containers.failure;
var receiver = this.container[this.success() ? 'success' : 'failure'];
var response = this.transport.responseText;
if (!this.options.evalScripts)
response = response.stripScripts();
if (!this.options.evalScripts) response = response.stripScripts();
if (receiver) {
if (this.options.insertion) {
if (receiver = $(receiver)) {
if (this.options.insertion)
new this.options.insertion(receiver, response);
} else {
Element.update(receiver, response);
}
else
receiver.update(response);
}
if (this.responseIsSuccess()) {
if (this.success()) {
if (this.onComplete)
setTimeout(this.onComplete.bind(this), 10);
}
@@ -936,15 +1013,15 @@ Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
this.updater = new Ajax.Updater(this.container, this.url, this.options);
}
});
function $() {
var results = [], element;
for (var i = 0; i < arguments.length; i++) {
element = arguments[i];
if (typeof element == 'string')
element = document.getElementById(element);
results.push(Element.extend(element));
function $(element) {
if (arguments.length > 1) {
for (var i = 0, elements = [], length = arguments.length; i < length; i++)
elements.push($(arguments[i]));
return elements;
}
return results.reduce();
if (typeof element == 'string')
element = document.getElementById(element);
return Element.extend(element);
}
if (Prototype.BrowserFeatures.XPath) {
@@ -952,7 +1029,7 @@ if (Prototype.BrowserFeatures.XPath) {
var results = [];
var query = document.evaluate(expression, $(parentElement) || document,
null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = 0, len = query.snapshotLength; i < len; i++)
for (var i = 0, length = query.snapshotLength; i < length; i++)
results.push(query.snapshotItem(i));
return results;
}
@@ -965,11 +1042,9 @@ document.getElementsByClassName = function(className, parentElement) {
} else {
var children = ($(parentElement) || document.body).getElementsByTagName('*');
var elements = [], child;
for (var i = 0, len = children.length; i < len; i++) {
for (var i = 0, length = children.length; i < length; i++) {
child = children[i];
if (child.className.length == 0) continue;
if (child.className == className ||
child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
if (Element.hasClassName(child, className))
elements.push(Element.extend(child));
}
return elements;
@@ -993,9 +1068,11 @@ Element.extend = function(element) {
if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName))
Object.extend(methods, Form.Element.Methods);
Object.extend(methods, Element.Methods.Simulated);
for (var property in methods) {
var value = methods[property];
if (typeof value == 'function')
if (typeof value == 'function' && !(property in element))
element[property] = cache.findOrStore(value);
}
}
@@ -1040,6 +1117,7 @@ Element.Methods = {
},
update: function(element, html) {
html = typeof html == 'undefined' ? '' : html.toString();
$(element).innerHTML = html.stripScripts();
setTimeout(function() {html.evalScripts()}, 10);
return element;
@@ -1088,6 +1166,13 @@ Element.Methods = {
return $A(element.getElementsByTagName('*'));
},
immediateDescendants: function(element) {
if (!(element = $(element).firstChild)) return [];
while (element && element.nodeType != 1) element = element.nextSibling;
if (element) return [element].concat($(element).nextSiblings());
return [];
},
previousSiblings: function(element) {
return $(element).recursivelyCollect('previousSibling');
},
@@ -1134,6 +1219,10 @@ Element.Methods = {
return document.getElementsByClassName(className, element);
},
readAttribute: function(element, name) {
return $(element).getAttribute(name);
},
getHeight: function(element) {
element = $(element);
return element.offsetHeight;
@@ -1145,7 +1234,12 @@ Element.Methods = {
hasClassName: function(element, className) {
if (!(element = $(element))) return;
return Element.classNames(element).include(className);
var elementClassName = element.className;
if (elementClassName.length == 0) return false;
if (elementClassName == className ||
elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
return true;
return false;
},
addClassName: function(element, className) {
@@ -1204,16 +1298,21 @@ Element.Methods = {
getStyle: function(element, style) {
element = $(element);
var value = element.style[style.camelize()];
var inline = (style == 'float' ?
(typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat') : style);
var value = element.style[inline.camelize()];
if (!value) {
if (document.defaultView && document.defaultView.getComputedStyle) {
var css = document.defaultView.getComputedStyle(element, null);
value = css ? css.getPropertyValue(style) : null;
} else if (element.currentStyle) {
value = element.currentStyle[style.camelize()];
value = element.currentStyle[inline.camelize()];
}
}
if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none'))
value = element['offset'+style.charAt(0).toUpperCase()+style.substring(1)] + 'px';
if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
if (Element.getStyle(element, 'position') == 'static') value = 'auto';
@@ -1223,7 +1322,9 @@ Element.Methods = {
setStyle: function(element, style) {
element = $(element);
for (var name in style)
element.style[name.camelize()] = style[name];
element.style[ (name == 'float' ?
((typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat') : name).camelize()
] = style[name];
return element;
},
@@ -1295,12 +1396,19 @@ Element.Methods = {
}
}
Element.Methods.Simulated = {
hasAttribute: function(element, attribute) {
return $(element).getAttributeNode(attribute).specified;
}
}
// IE is missing .innerHTML support for TABLE-related elements
if(document.all){
Element.Methods.update = function(element, html) {
element = $(element);
html = typeof html == 'undefined' ? '' : html.toString();
var tagName = element.tagName.toUpperCase();
if (['THEAD','TBODY','TR','TD'].indexOf(tagName) > -1) {
if (['THEAD','TBODY','TR','TD'].include(tagName)) {
var div = document.createElement('div');
switch (tagName) {
case 'THEAD':
@@ -1335,28 +1443,30 @@ Object.extend(Element, Element.Methods);
var _nativeExtensions = false;
if (!window.HTMLElement && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
/* Emulate HTMLElement, HTMLFormElement, HTMLInputElement, HTMLTextAreaElement,
and HTMLSelectElement in Safari */
if(/Konqueror|Safari|KHTML/.test(navigator.userAgent))
['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) {
var klass = window['HTML' + tag + 'Element'] = {};
var className = 'HTML' + tag + 'Element';
if(window[className]) return;
var klass = window[className] = {};
klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__;
});
}
Element.addMethods = function(methods) {
Object.extend(Element.Methods, methods || {});
function copy(methods, destination) {
function copy(methods, destination, onlyIfAbsent) {
onlyIfAbsent = onlyIfAbsent || false;
var cache = Element.extend.cache;
for (var property in methods) {
var value = methods[property];
destination[property] = cache.findOrStore(value);
if (!onlyIfAbsent || !(property in destination))
destination[property] = cache.findOrStore(value);
}
}
if (typeof HTMLElement != 'undefined') {
copy(Element.Methods, HTMLElement.prototype);
copy(Element.Methods.Simulated, HTMLElement.prototype, true);
copy(Form.Methods, HTMLFormElement.prototype);
[HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) {
copy(Form.Element.Methods, klass.prototype);
@@ -1383,8 +1493,8 @@ Abstract.Insertion.prototype = {
try {
this.element.insertAdjacentHTML(this.adjacency, this.content);
} catch (e) {
var tagName = this.element.tagName.toLowerCase();
if (tagName == 'tbody' || tagName == 'tr') {
var tagName = this.element.tagName.toUpperCase();
if (['TBODY', 'TR'].include(tagName)) {
this.insertContent(this.contentFromAnonymousTable());
} else {
throw e;
@@ -1483,18 +1593,16 @@ Element.ClassNames.prototype = {
add: function(classNameToAdd) {
if (this.include(classNameToAdd)) return;
this.set(this.toArray().concat(classNameToAdd).join(' '));
this.set($A(this).concat(classNameToAdd).join(' '));
},
remove: function(classNameToRemove) {
if (!this.include(classNameToRemove)) return;
this.set(this.select(function(className) {
return className != classNameToRemove;
}).join(' '));
this.set($A(this).without(classNameToRemove).join(' '));
},
toString: function() {
return this.toArray().join(' ');
return $A(this).join(' ');
}
}
@@ -1547,7 +1655,7 @@ Selector.prototype = {
if (clause = params.tagName)
conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
if ((clause = params.classNames).length > 0)
for (var i = 0; i < clause.length; i++)
for (var i = 0, length = clause.length; i < length; i++)
conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')');
if (clause = params.attributes) {
clause.each(function(attribute) {
@@ -1589,7 +1697,7 @@ Selector.prototype = {
scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
var results = [];
for (var i = 0; i < scope.length; i++)
for (var i = 0, length = scope.length; i < length; i++)
if (this.match(element = scope[i]))
results.push(Element.extend(element));
@@ -1631,32 +1739,30 @@ var Form = {
reset: function(form) {
$(form).reset();
return form;
},
serializeElements: function(elements) {
return elements.inject([], function(queryComponents, element) {
var queryComponent = Form.Element.serialize(element);
if (queryComponent) queryComponents.push(queryComponent);
return queryComponents;
}).join('&');
}
};
Form.Methods = {
serialize: function(form) {
return this.serializeElements(Form.getElements($(form)));
},
serializeElements: function(elements) {
var queryComponents = new Array();
for (var i = 0; i < elements.length; i++) {
var queryComponent = Form.Element.serialize(elements[i]);
if (queryComponent)
queryComponents.push(queryComponent);
}
return queryComponents.join('&');
return Form.serializeElements($(form).getElements());
},
getElements: function(form) {
return $A($(form).getElementsByTagName('*')).inject([], function(elements, child) {
if (Form.Element.Serializers[child.tagName.toLowerCase()])
elements.push(Element.extend(child));
return elements;
});
return $A($(form).getElementsByTagName('*')).inject([],
function(elements, child) {
if (Form.Element.Serializers[child.tagName.toLowerCase()])
elements.push(Element.extend(child));
return elements;
}
);
},
getInputs: function(form, typeName, name) {
@@ -1667,12 +1773,12 @@ Form.Methods = {
return inputs;
var matchingInputs = new Array();
for (var i = 0; i < inputs.length; i++) {
for (var i = 0, length = inputs.length; i < length; i++) {
var input = inputs[i];
if ((typeName && input.type != typeName) ||
(name && input.name != name))
continue;
matchingInputs.push(input);
matchingInputs.push(Element.extend(input));
}
return matchingInputs;
@@ -1680,27 +1786,23 @@ Form.Methods = {
disable: function(form) {
form = $(form);
var elements = Form.getElements(form);
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
form.getElements().each(function(element) {
element.blur();
element.disabled = 'true';
}
});
return form;
},
enable: function(form) {
form = $(form);
var elements = Form.getElements(form);
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
form.getElements().each(function(element) {
element.disabled = '';
}
});
return form;
},
findFirstElement: function(form) {
return Form.getElements(form).find(function(element) {
return $(form).getElements().find(function(element) {
return element.type != 'hidden' && !element.disabled &&
['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
});
@@ -1708,7 +1810,7 @@ Form.Methods = {
focusFirstElement: function(form) {
form = $(form);
Field.activate(Form.findFirstElement(form));
form.findFirstElement().activate();
return form;
}
}
@@ -1732,6 +1834,7 @@ Form.Element = {
Form.Element.Methods = {
serialize: function(element) {
element = $(element);
if (element.disabled) return '';
var method = element.tagName.toLowerCase();
var parameter = Form.Element.Serializers[method](element);
@@ -1769,7 +1872,8 @@ Form.Element.Methods = {
activate: function(element) {
element = $(element);
element.focus();
if (element.select)
if (element.select && ( element.tagName.toLowerCase() != 'input' ||
!['button', 'reset', 'submit'].include(element.type) ) )
element.select();
return element;
},
@@ -1822,18 +1926,20 @@ Form.Element.Serializers = {
selectOne: function(element) {
var value = '', opt, index = element.selectedIndex;
if (index >= 0) {
opt = element.options[index];
value = opt.value || opt.text;
opt = Element.extend(element.options[index]);
// Uses the new potential extension if hasAttribute isn't native.
value = opt.hasAttribute('value') ? opt.value : opt.text;
}
return [element.name, value];
},
selectMany: function(element) {
var value = [];
for (var i = 0; i < element.length; i++) {
var opt = element.options[i];
for (var i = 0, length = element.length; i < length; i++) {
var opt = Element.extend(element.options[i]);
if (opt.selected)
value.push(opt.value || opt.text);
// Uses the new potential extension if hasAttribute isn't native.
value.push(opt.hasAttribute('value') ? opt.value : opt.text);
}
return [element.name, value];
}
@@ -1907,9 +2013,7 @@ Abstract.EventObserver.prototype = {
},
registerFormCallbacks: function() {
var elements = Form.getElements(this.element);
for (var i = 0; i < elements.length; i++)
this.registerCallback(elements[i]);
Form.getElements(this.element).each(this.registerCallback.bind(this));
},
registerCallback: function(element) {
@@ -2013,7 +2117,7 @@ Object.extend(Event, {
unloadCache: function() {
if (!Event.observers) return;
for (var i = 0; i < Event.observers.length; i++) {
for (var i = 0, length = Event.observers.length; i < length; i++) {
Event.stopObserving.apply(this, Event.observers[i]);
Event.observers[i][0] = null;
}

View File

@@ -204,81 +204,6 @@ module ActionView
tag("input", options[:html], false)
end
# Returns a JavaScript function (or expression) that'll update a DOM
# element according to the options passed.
#
# * <tt>:content</tt>: The content to use for updating. Can be left out
# if using block, see example.
# * <tt>:action</tt>: Valid options are :update (assumed by default),
# :empty, :remove
# * <tt>:position</tt> If the :action is :update, you can optionally
# specify one of the following positions: :before, :top, :bottom,
# :after.
#
# Examples:
# <%= javascript_tag(update_element_function("products",
# :position => :bottom, :content => "<p>New product!</p>")) %>
#
# <% replacement_function = update_element_function("products") do %>
# <p>Product 1</p>
# <p>Product 2</p>
# <% end %>
# <%= javascript_tag(replacement_function) %>
#
# This method can also be used in combination with remote method call
# where the result is evaluated afterwards to cause multiple updates on
# a page. Example:
#
# # Calling view
# <%= form_remote_tag :url => { :action => "buy" },
# :complete => evaluate_remote_response %>
# all the inputs here...
#
# # Controller action
# def buy
# @product = Product.find(1)
# end
#
# # Returning view
# <%= update_element_function(
# "cart", :action => :update, :position => :bottom,
# :content => "<p>New Product: #{@product.name}</p>")) %>
# <% update_element_function("status", :binding => binding) do %>
# You've bought a new product!
# <% end %>
#
# Notice how the second call doesn't need to be in an ERb output block
# since it uses a block and passes in the binding to render directly.
# This trick will however only work in ERb (not Builder or other
# template forms).
#
# See also JavaScriptGenerator and update_page.
def update_element_function(element_id, options = {}, &block)
content = escape_javascript(options[:content] || '')
content = escape_javascript(capture(&block)) if block
javascript_function = case (options[:action] || :update)
when :update
if options[:position]
"new Insertion.#{options[:position].to_s.camelize}('#{element_id}','#{content}')"
else
"$('#{element_id}').innerHTML = '#{content}'"
end
when :empty
"$('#{element_id}').innerHTML = ''"
when :remove
"Element.remove('#{element_id}')"
else
raise ArgumentError, "Invalid action, choose one of :update, :remove, :empty"
end
javascript_function << ";\n"
options[:binding] ? concat(javascript_function, options[:binding]) : javascript_function
end
# Returns 'eval(request.responseText)' which is the JavaScript function
# that form_remote_tag can call in :complete to evaluate a multiple
# update return document using update_element_function calls.
@@ -466,7 +391,7 @@ module ActionView
# Returns an object whose <tt>#to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
# expression as an argument to another JavaScriptGenerator method.
def literal(code)
JavaScriptLiteral.new(code)
ActiveSupport::JSON::Variable.new(code.to_s)
end
# Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
@@ -763,13 +688,6 @@ module ActionView
end
end
# Bypasses string escaping so you can pass around raw JavaScript
class JavaScriptLiteral < String #:nodoc:
def to_json
to_s
end
end
# Converts chained method calls on DOM proxy elements into JavaScript chains
class JavaScriptProxy < Builder::BlankSlate #:nodoc:
def initialize(generator, root = nil)

View File

@@ -154,12 +154,10 @@ module ActionView
# considered as a linebreak and a <tt><br /></tt> tag is appended. This
# method does not remove the newlines from the +text+.
def simple_format(text)
text = text.gsub(/(\r\n|\n|\r)/, "\n") # lets make them newlines crossplatform
text.gsub!(/\n\n+/, "\n\n") # zap dupes
text.gsub!(/\n\n/, '</p>\0<p>') # turn two newlines into paragraph
text.gsub!(/([^\n])(\n)(?=[^\n])/, '\1\2<br />') # turn single newline into <br />
content_tag("p", text)
content_tag 'p', text.to_s.
gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
gsub(/\n\n+/, "</p>\n\n<p>"). # 2+ newline -> paragraph
gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
end
# Turns all urls and email addresses into clickable links. The +link+ parameter
@@ -256,7 +254,7 @@ module ActionView
# html-scanner tokenizer and so its HTML parsing ability is limited by
# that of html-scanner.
def strip_tags(html)
return nil if html.blank?
return html if html.blank?
if html.index("<")
text = ""
tokenizer = HTML::Tokenizer.new(html)

View File

@@ -6,16 +6,22 @@ module ActionView
attr_reader :original_exception
def initialize(base_path, file_name, assigns, source, original_exception)
@base_path, @assigns, @source, @original_exception =
base_path, assigns, source, original_exception
@file_name = file_name
def initialize(base_path, file_path, assigns, source, original_exception)
@base_path, @assigns, @source, @original_exception =
base_path, assigns.dup, source, original_exception
@file_path = file_path
remove_deprecated_assigns!
end
def message
original_exception.message
ActiveSupport::Deprecation.silence { original_exception.message }
end
def clean_backtrace
original_exception.clean_backtrace
end
def sub_template_message
if @sub_templates
"Trace of template inclusion: " +
@@ -24,63 +30,81 @@ module ActionView
""
end
end
def source_extract(indention = 0)
source_code = IO.readlines(@file_name)
start_on_line = [ line_number - SOURCE_CODE_RADIUS - 1, 0 ].max
end_on_line = [ line_number + SOURCE_CODE_RADIUS - 1, source_code.length].min
def source_extract(indentation = 0)
return unless num = line_number
num = num.to_i
source_code = IO.readlines(@file_path)
start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max
end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
indent = ' ' * indentation
line_counter = start_on_line
extract = source_code[start_on_line..end_on_line].collect do |line|
source_code[start_on_line..end_on_line].sum do |line|
line_counter += 1
"#{' ' * indention}#{line_counter}: " + line
"#{indent}#{line_counter}: #{line}"
end
extract.join
end
def sub_template_of(file_name)
def sub_template_of(template_path)
@sub_templates ||= []
@sub_templates << file_name
@sub_templates << template_path
end
def line_number
if file_name
regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
[@original_exception.message, @original_exception.clean_backtrace].flatten.each do |line|
return $1.to_i if regexp =~ line
@line_number ||=
if file_name
regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
$1 if message =~ regexp or clean_backtrace.find { |line| line =~ regexp }
end
end
0
end
def file_name
stripped = strip_base_path(@file_name)
stripped[0] == ?/ ? stripped[1..-1] : stripped
stripped = strip_base_path(@file_path)
stripped.slice!(0,1) if stripped[0] == ?/
stripped
end
def to_s
"\n\n#{self.class} (#{message}) on line ##{line_number} of #{file_name}:\n" +
source_extract + "\n " +
original_exception.clean_backtrace.join("\n ") +
"\n\n"
"\n\n#{self.class} (#{message}) #{source_location}:\n" +
"#{source_extract}\n #{clean_backtrace.join("\n ")}\n\n"
end
def backtrace
[
"On line ##{line_number} of #{file_name}\n\n#{source_extract(4)}\n " +
original_exception.clean_backtrace.join("\n ")
[
"#{source_location.capitalize}\n\n#{source_extract(4)}\n " +
clean_backtrace.join("\n ")
]
end
private
def strip_base_path(file_name)
file_name = File.expand_path(file_name).gsub(/^#{Regexp.escape File.expand_path(RAILS_ROOT)}/, '')
file_name.gsub(@base_path, "")
def remove_deprecated_assigns!
ActionController::Base::DEPRECATED_INSTANCE_VARIABLES.each do |ivar|
@assigns.delete(ivar)
end
end
def strip_base_path(path)
File.expand_path(path).
gsub(/^#{Regexp.escape File.expand_path(RAILS_ROOT)}/, '').
gsub(@base_path, "")
end
def source_location
if line_number
"on line ##{line_number} of "
else
'in '
end + file_name
end
end
end
Exception::TraceSubstitutions << [/:in\s+`_run_(html|xml).*'\s*$/, ''] if defined?(Exception::TraceSubstitutions)
Exception::TraceSubstitutions << [%r{^\s*#{Regexp.escape RAILS_ROOT}}, '#{RAILS_ROOT}'] if defined?(RAILS_ROOT)
if defined?(Exception::TraceSubstitutions)
Exception::TraceSubstitutions << [/:in\s+`_run_(html|xml).*'\s*$/, '']
Exception::TraceSubstitutions << [%r{^\s*#{Regexp.escape RAILS_ROOT}}, '#{RAILS_ROOT}'] if defined?(RAILS_ROOT)
end

View File

@@ -4,7 +4,7 @@ require File.dirname(__FILE__) + '/../abstract_unit'
class ActionPackAssertionsController < ActionController::Base
# this does absolutely nothing
def nothing() render_text ""; end
def nothing() head :ok end
# a standard template
def hello_world() render "test/hello_world"; end
@@ -27,13 +27,13 @@ class ActionPackAssertionsController < ActionController::Base
def redirect_external() redirect_to_url "http://www.rubyonrails.org"; end
# a 404
def response404() render_text "", "404 AWOL"; end
def response404() head '404 AWOL' end
# a 500
def response500() render_text "", "500 Sorry"; end
def response500() head '500 Sorry' end
# a fictional 599
def response599() render_text "", "599 Whoah!"; end
def response599() head '599 Whoah!' end
# putting stuff in the flash
def flash_me
@@ -139,6 +139,10 @@ module Admin
def redirect_to_fellow_controller
redirect_to :controller => 'user'
end
def redirect_to_top_level_named_route
redirect_to top_level_url(:id => "foo")
end
end
end
@@ -155,10 +159,16 @@ ActionPackAssertionsController.template_root = File.dirname(__FILE__) + "/../fix
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))
@controller = ActionPackAssertionsController.new
@request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
end
def teardown
ActionController::Routing::Routes.reload
end
# -- assertion-based testing ------------------------------------------------
def test_assert_tag_and_url_for
@@ -295,6 +305,19 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
end
end
def test_assert_redirected_to_top_level_named_route_from_nested_controller
with_routing do |set|
set.draw do |map|
map.top_level '/action_pack_assertions/:id', :controller => 'action_pack_assertions', :action => 'index'
map.connect ':controller/:action/:id'
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 "/action_pack_assertions/foo"
end
end
# test the flash-based assertions with something is in the flash
def test_flash_assertions_full
process :flash_me
@@ -402,7 +425,9 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
process :redirect_external
assert_equal 'http://www.rubyonrails.org', @response.redirect_url
end
def test_no_redirect_url
process :nothing
assert_nil @response.redirect_url
end

View File

@@ -5,8 +5,29 @@ CACHE_DIR = 'test_cache'
# Don't change '/../temp/' cavalierly or you might hoze something you don't want hozed
FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR)
ActionController::Base.perform_caching = true
ActionController::Base.page_cache_directory = FILE_STORE_PATH
ActionController::Base.fragment_cache_store = :file_store, FILE_STORE_PATH
class PageCachingTestController < ActionController::Base
caches_page :ok, :no_content, :found, :not_found
def ok
head :ok
end
def no_content
head :no_content
end
def found
redirect_to :action => 'ok'
end
def not_found
head :not_found
end
end
class PageCachingTest < Test::Unit::TestCase
def setup
ActionController::Routing::Routes.draw do |map|
@@ -14,11 +35,23 @@ class PageCachingTest < Test::Unit::TestCase
map.resources :posts
map.connect ':controller/:action/:id'
end
@request = ActionController::TestRequest.new
@request.host = 'hostname.com'
@response = ActionController::TestResponse.new
@controller = PageCachingTestController.new
@params = {:controller => 'posts', :action => 'index', :only_path => true, :skip_relative_url_root => true}
@rewriter = ActionController::UrlRewriter.new(@request, @params)
end
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
FileUtils.mkdir_p(FILE_STORE_PATH)
end
def teardown
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
end
def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route
@params[:format] = 'rss'
@@ -26,35 +59,67 @@ class PageCachingTest < Test::Unit::TestCase
@params[:format] = nil
assert_equal '/', @rewriter.rewrite(@params)
end
def test_should_cache_get_with_ok_status
get :ok
assert_response :ok
assert_page_cached :ok, "get with ok status should have been cached"
end
[:ok, :no_content, :found, :not_found].each do |status|
[:get, :post, :put, :delete].each do |method|
unless method == :get and status == :ok
define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
@request.env['REQUEST_METHOD'] = method.to_s.upcase
process status
assert_response status
assert_page_not_cached status, "#{method} with #{status} status shouldn't have been cached"
end
end
end
end
private
def assert_page_cached(action, message = "#{action} should have been cached")
assert page_cached?(action), message
end
def assert_page_not_cached(action, message = "#{action} shouldn't have been cached")
assert !page_cached?(action), message
end
def page_cached?(action)
File.exist? "#{FILE_STORE_PATH}/page_caching_test/#{action}.html"
end
end
class ActionCachingTestController < ActionController::Base
caches_action :index
def index
@cache_this = Time.now.to_f.to_s
render :text => @cache_this
end
def expire
expire_action :controller => 'action_caching_test', :action => 'index'
render :nothing => true
end
end
class ActionCachingMockController
attr_accessor :mock_url_for
attr_accessor :mock_path
def initialize
yield self if block_given?
end
def url_for(*args)
@mock_url_for
end
def request
mocked_path = @mock_path
Object.new.instance_eval(<<-EVAL)
@@ -71,62 +136,62 @@ class ActionCacheTest < Test::Unit::TestCase
@path_class = ActionController::Caching::Actions::ActionCachePath
@mock_controller = ActionCachingMockController.new
end
def teardown
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
end
def test_simple_action_cache
get :index
cached_time = content_to_cache
assert_equal cached_time, @response.body
reset!
get :index
assert_equal cached_time, @response.body
end
def test_cache_expiration
get :index
cached_time = content_to_cache
reset!
get :index
assert_equal cached_time, @response.body
reset!
get :expire
reset!
get :index
new_cached_time = content_to_cache
assert_not_equal cached_time, @response.body
reset!
get :index
assert_response :success
assert_equal new_cached_time, @response.body
end
def test_cache_is_scoped_by_subdomain
@request.host = 'jamis.hostname.com'
get :index
jamis_cache = content_to_cache
@request.host = 'david.hostname.com'
get :index
david_cache = content_to_cache
assert_not_equal jamis_cache, @response.body
@request.host = 'jamis.hostname.com'
get :index
assert_equal jamis_cache, @response.body
@request.host = 'david.hostname.com'
get :index
assert_equal david_cache, @response.body
end
def test_xml_version_of_resource_is_treated_as_different_cache
@mock_controller.mock_url_for = 'http://example.org/posts/'
@mock_controller.mock_path = '/posts/index.xml'
@@ -134,32 +199,30 @@ class ActionCacheTest < Test::Unit::TestCase
assert_equal 'xml', path_object.extension
assert_equal 'example.org/posts/index.xml', path_object.path
end
def test_empty_path_is_normalized
@mock_controller.mock_url_for = 'http://example.org/'
@mock_controller.mock_path = '/'
assert_equal 'example.org/index', @path_class.path_for(@mock_controller)
end
def test_file_extensions
get :index, :id => 'kitten.jpg'
get :index, :id => 'kitten.jpg'
get :index, :id => 'kitten.jpg'
assert_response :success
end
private
def content_to_cache
assigns(:cache_this)
end
def reset!
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@controller = ActionCachingTestController.new
@request.host = 'hostname.com'
end
end
end

View File

@@ -7,15 +7,15 @@ class CaptureController < ActionController::Base
def content_for
render :layout => "talk_from_action"
end
def erb_content_for
render :layout => "talk_from_action"
end
def block_content_for
render :layout => "talk_from_action"
end
def non_erb_block_content_for
render :layout => "talk_from_action"
end
@@ -43,36 +43,38 @@ class CaptureTest < Test::Unit::TestCase
get :capturing
assert_equal "Dreamy days", @response.body.strip
end
def test_content_for
get :content_for
assert_equal expected_content_for_output, @response.body
end
def test_erb_content_for
get :content_for
assert_equal expected_content_for_output, @response.body
end
def test_block_content_for
get :block_content_for
assert_equal expected_content_for_output, @response.body
end
def test_non_erb_block_content_for
get :non_erb_block_content_for
assert_equal expected_content_for_output, @response.body
end
def test_update_element_with_capture
get :update_element_with_capture
assert_deprecated 'update_element_function' do
get :update_element_with_capture
end
assert_equal(
"<script type=\"text/javascript\">\n//<![CDATA[\n$('products').innerHTML = '\\n <p>Product 1</p>\\n <p>Product 2</p>\\n';\n\n//]]>\n</script>" +
"\n\n$('status').innerHTML = '\\n <b>You bought something!</b>\\n';",
"\n\n$('status').innerHTML = '\\n <b>You bought something!</b>\\n';",
@response.body.strip
)
end
private
def expected_content_for_output
"<title>Putting stuff in the title!</title>\n\nGreat stuff!"

View File

@@ -155,9 +155,10 @@ class CGITest < Test::Unit::TestCase
end
def test_parse_params_from_multipart_upload
mockup = Struct.new(:content_type, :original_filename)
mockup = Struct.new(:content_type, :original_filename, :read, :rewind)
file = mockup.new('img/jpeg', 'foo.jpg')
ie_file = mockup.new('img/jpeg', 'c:\\Documents and Settings\\foo\\Desktop\\bar.jpg')
non_file_text_part = mockup.new('text/plain', '', 'abc')
input = {
"something" => [ StringIO.new("") ],
@@ -168,9 +169,10 @@ class CGITest < Test::Unit::TestCase
"products[string]" => [ StringIO.new("Apple Computer") ],
"products[file]" => [ file ],
"ie_products[string]" => [ StringIO.new("Microsoft") ],
"ie_products[file]" => [ ie_file ]
"ie_products[file]" => [ ie_file ],
"text_part" => [non_file_text_part]
}
expected_output = {
"something" => "",
"array_of_stringios" => ["One", "Two"],
@@ -192,7 +194,8 @@ class CGITest < Test::Unit::TestCase
"ie_products" => {
"string" => "Microsoft",
"file" => ie_file
}
},
"text_part" => "abc"
}
params = CGIMethods.parse_request_parameters(input)
@@ -338,7 +341,7 @@ class MultipartCGITest < Test::Unit::TestCase
assert_equal 'bar', params['foo']
# Ruby CGI doesn't handle multipart/mixed for us.
assert_kind_of StringIO, params['files']
assert_kind_of String, params['files']
assert_equal 19756, params['files'].size
end

View File

@@ -134,7 +134,8 @@ module A
class NestedController < ActionController::Base
# Stub for uses_component_template_root
def self.caller
['./test/fixtures/a/b/c/nested_controller.rb']
[ '/path/to/active_support/deprecation.rb:93:in `uses_component_template_root',
'./test/fixtures/a/b/c/nested_controller.rb' ]
end
end
end
@@ -143,6 +144,8 @@ end
class UsesComponentTemplateRootTest < Test::Unit::TestCase
def test_uses_component_template_root
assert_equal './test/fixtures/', A::B::C::NestedController.uses_component_template_root
assert_deprecated 'uses_component_template_root' do
assert_equal './test/fixtures/', A::B::C::NestedController.uses_component_template_root
end
end
end

View File

@@ -14,6 +14,10 @@ class DeprecatedBaseMethodsTest < Test::Unit::TestCase
"http://example.com/#{greeting}"
end
def raises_name_error
this_method_doesnt_exist
end
def rescue_action(e) raise e end
end
@@ -40,4 +44,17 @@ class DeprecatedBaseMethodsTest < Test::Unit::TestCase
assert_equal "Living in a nested world", @response.body
end
def test_log_error_silences_deprecation_warnings
get :raises_name_error
rescue => e
assert_not_deprecated { @controller.send :log_error, e }
end
def test_assertion_failed_error_silences_deprecation_warnings
get :raises_name_error
rescue => e
error = Test::Unit::Error.new('testing ur doodz', e)
assert_not_deprecated { error.message }
end
end

View File

@@ -20,6 +20,13 @@ class RespondToController < ActionController::Base
end
end
def json_or_yaml
respond_to do |type|
type.json { render :text => "JSON" }
type.yaml { render :text => "YAML" }
end
end
def html_or_xml
respond_to do |type|
type.html { render :text => "HTML" }
@@ -164,6 +171,27 @@ class MimeControllerTest < Test::Unit::TestCase
assert_response 406
end
def test_json_or_yaml
get :json_or_yaml
assert_equal 'JSON', @response.body
get :json_or_yaml, :format => 'json'
assert_equal 'JSON', @response.body
get :json_or_yaml, :format => 'yaml'
assert_equal 'YAML', @response.body
{ 'YAML' => %w(text/yaml),
'JSON' => %w(application/json text/x-json)
}.each do |body, content_types|
content_types.each do |content_type|
@request.env['HTTP_ACCEPT'] = content_type
get :json_or_yaml
assert_equal body, @response.body
end
end
end
def test_js_or_anything
@request.env["HTTP_ACCEPT"] = "text/javascript, */*"
get :js_or_html

View File

@@ -160,6 +160,27 @@ class NewRenderTestController < ActionController::Base
@customers = [ Customer.new("david"), Customer.new("mary") ]
render :text => "How's there? #{render_to_string("test/list")}"
end
def render_to_string_with_assigns
@before = "i'm before the render"
render_to_string :text => "foo"
@after = "i'm after the render"
render :action => "test/hello_world"
end
def render_to_string_with_exception
render_to_string :file => "exception that will not be caught - this will certainly not work", :use_full_path => true
end
def render_to_string_with_caught_exception
@before = "i'm before the render"
begin
render_to_string :file => "exception that will be caught- hope my future instance vars still work!", :use_full_path => true
rescue
end
@after = "i'm after the render"
render :action => "test/hello_world"
end
def accessing_params_in_template
render :inline => "Hello: <%= params[:name] %>"
@@ -187,6 +208,11 @@ class NewRenderTestController < ActionController::Base
render :text => "hello"
redirect_to :action => "double_render"
end
def render_to_string_and_render
@stuff = render_to_string :text => "here is some cached stuff"
render :text => "Hi web users! #{@stuff}"
end
def rendering_with_conflicting_local_vars
@name = "David"
@@ -523,6 +549,22 @@ EOS
assert_not_deprecated { get :hello_in_a_string }
assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body
end
def test_render_to_string_doesnt_break_assigns
get :render_to_string_with_assigns
assert_equal "i'm before the render", assigns(:before)
assert_equal "i'm after the render", assigns(:after)
end
def test_bad_render_to_string_still_throws_exception
assert_raises(ActionController::MissingTemplate) { get :render_to_string_with_exception }
end
def test_render_to_string_that_throws_caught_exception_doesnt_break_assigns
assert_nothing_raised { get :render_to_string_with_caught_exception }
assert_equal "i'm before the render", assigns(:before)
assert_equal "i'm after the render", assigns(:after)
end
def test_nested_rendering
get :hello_world
@@ -555,6 +597,12 @@ EOS
def test_render_and_redirect
assert_raises(ActionController::DoubleRenderError) { get :render_and_redirect }
end
# specify the one exception to double render rule - render_to_string followed by render
def test_render_to_string_and_render
get :render_to_string_and_render
assert_equal("Hi web users! here is some cached stuff", @response.body)
end
def test_rendering_with_conflicting_local_vars
get :rendering_with_conflicting_local_vars
@@ -640,24 +688,29 @@ EOS
get :head_with_location_header
assert @response.body.blank?
assert_equal "/foo", @response.headers["Location"]
assert_response :ok
end
def test_head_with_custom_header
get :head_with_custom_header
assert @response.body.blank?
assert_equal "something", @response.headers["X-Custom-Header"]
assert_response :ok
end
def test_head_with_symbolic_status
get :head_with_symbolic_status, :status => "ok"
assert_equal "200 OK", @response.headers["Status"]
assert_response :ok
get :head_with_symbolic_status, :status => "not_found"
assert_equal "404 Not Found", @response.headers["Status"]
assert_response :not_found
ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE.each do |status, code|
get :head_with_symbolic_status, :status => status.to_s
assert_equal code, @response.response_code
assert_response status
end
end
@@ -672,6 +725,7 @@ EOS
get :head_with_string_status, :status => "404 Eat Dirt"
assert_equal 404, @response.response_code
assert_equal "Eat Dirt", @response.message
assert_response :not_found
end
def test_head_with_status_code_first
@@ -679,5 +733,6 @@ EOS
assert_equal 403, @response.response_code
assert_equal "Forbidden", @response.message
assert_equal "something", @response.headers["X-Custom-Header"]
assert_response :forbidden
end
end

View File

@@ -39,6 +39,18 @@ class TestController < ActionController::Base
render_text "hello world"
end
def render_json_hello_world
render_json({:hello => 'world'}.to_json)
end
def render_json_hello_world_with_callback
render_json({:hello => 'world'}.to_json, 'alert')
end
def render_symbol_json
render :json => {:hello => 'world'}.to_json
end
def render_custom_code
render_text "hello world", "404 Moved"
end
@@ -117,6 +129,7 @@ class TestController < ActionController::Base
case action_name
when "layout_test": "layouts/standard"
when "builder_layout_test": "layouts/builder"
when "render_symbol_json": "layouts/standard" # to make sure layouts don't interfere
end
end
end
@@ -164,6 +177,24 @@ class RenderTest < Test::Unit::TestCase
assert_equal "hello world", @response.body
end
def test_do_with_render_json
get :render_json_hello_world
assert_equal '{hello: "world"}', @response.body
assert_equal 'application/json', @response.content_type
end
def test_do_with_render_json_with_callback
get :render_json_hello_world_with_callback
assert_equal 'alert({hello: "world"})', @response.body
assert_equal 'application/json', @response.content_type
end
def test_do_with_render_symbol_json
get :render_symbol_json
assert_equal '{hello: "world"}', @response.body
assert_equal 'application/json', @response.content_type
end
def test_do_with_render_custom_code
get :render_custom_code
assert_response 404

View File

@@ -274,7 +274,7 @@ class RequestTest < Test::Unit::TestCase
end
def test_symbolized_request_methods
[:head, :get, :post, :put, :delete].each do |method|
[:get, :post, :put, :delete].each do |method|
set_request_method_to method
assert_equal method, @request.method
end
@@ -282,7 +282,7 @@ class RequestTest < Test::Unit::TestCase
def test_allow_method_hacking_on_post
set_request_method_to :post
[:head, :get, :put, :delete].each do |method|
[:get, :put, :delete].each do |method|
@request.instance_eval { @parameters = { :_method => method } ; @request_method = nil }
assert_equal method, @request.method
end
@@ -290,11 +290,18 @@ class RequestTest < Test::Unit::TestCase
def test_restrict_method_hacking
@request.instance_eval { @parameters = { :_method => 'put' } }
[:head, :get, :put, :delete].each do |method|
[:get, :put, :delete].each do |method|
set_request_method_to method
assert_equal method, @request.method
end
end
def test_head_masquarading_as_get
set_request_method_to :head
assert_equal :get, @request.method
assert @request.get?
assert @request.head?
end
protected
def set_request_method_to(method)

View File

@@ -376,7 +376,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_named_url_with_no_action_specified
rs.draw do |map|
map.root '', :controller => 'content'
map.home '', :controller => 'content'
map.connect ':controller/:action/:id'
end
@@ -384,14 +384,14 @@ class LegacyRouteSetTests < Test::Unit::TestCase
assert_equal '/', rs.generate(:controller => 'content')
x = setup_for_named_route.new
assert_equal({:controller => 'content', :action => 'index', :use_route => :root, :only_path => false},
x.send(:root_url))
assert_equal({:controller => 'content', :action => 'index', :use_route => :home, :only_path => false},
x.send(:home_url))
end
def test_url_generated_when_forgetting_action
[{:controller => 'content', :action => 'index'}, {:controller => 'content'}].each do |hash|
rs.draw do |map|
map.root '', hash
map.home '', hash
map.connect ':controller/:action/:id'
end
assert_equal '/', rs.generate({:action => nil}, {:controller => 'content', :action => 'hello'})
@@ -853,6 +853,12 @@ class RouteTest < Test::Unit::TestCase
{ :controller => "users", :action => "show", :format => "html" },
route.defaults)
end
def test_builder_complains_without_controller
assert_raises(ArgumentError) do
ROUTING::RouteBuilder.new.build '/contact', :contoller => "contact", :action => "index"
end
end
def test_significant_keys_for_default_route
keys = default_route.significant_keys.sort_by {|k| k.to_s }
@@ -1576,6 +1582,18 @@ class RouteSetTest < Test::Unit::TestCase
Object.send(:remove_const, :PeopleController)
end
def test_deprecation_warning_for_root_route
Object.const_set(:PeopleController, Class.new)
set.draw do |map|
assert_deprecated do
map.root('', :controller => "people")
end
end
ensure
Object.send(:remove_const, :PeopleController)
end
def test_generate_with_default_action
set.draw do |map|
map.connect "/people", :controller => "people"
@@ -1673,10 +1691,13 @@ class RoutingTest < Test::Unit::TestCase
def test_possible_controllers
true_controller_paths = ActionController::Routing.controller_paths
ActionController::Routing.use_controllers! nil
Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + '/controller_fixtures')
silence_warnings do
Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + '/controller_fixtures')
end
ActionController::Routing.controller_paths = [
RAILS_ROOT, RAILS_ROOT + '/app/controllers', RAILS_ROOT + '/vendor/plugins/bad_plugin/lib'
]
@@ -1734,4 +1755,4 @@ class RoutingTest < Test::Unit::TestCase
assert_equal %w(vendor\\rails\\railties\\builtin\\rails_info vendor\\rails\\actionpack\\lib app\\controllers app\\helpers app\\models lib .), paths
end
end
end

View File

@@ -63,6 +63,14 @@ class SendFileTest < Test::Unit::TestCase
assert_equal file_data, response.body
end
def test_headers_after_send_shouldnt_include_charset
response = process('data')
assert_equal "application/octet-stream", response.headers["Content-Type"]
response = process('file')
assert_equal "application/octet-stream", response.headers["Content-Type"]
end
# Test that send_file_headers! is setting the correct HTTP headers.
def test_send_file_headers!
options = {

View File

@@ -44,6 +44,49 @@ class SessionManagementTest < Test::Unit::TestCase
end
end
class AssociationCachingTestController < ActionController::Base
class ObjectWithAssociationCache
def initialize
@cached_associations = false
end
def fetch_associations
@cached_associations = true
end
def clear_association_cache
@cached_associations = false
end
def has_cached_associations?
@cached_associations
end
end
def show
session[:object] = ObjectWithAssociationCache.new
session[:object].fetch_associations
if session[:object].has_cached_associations?
render :text => "has cached associations"
else
render :text => "does not have cached associations"
end
end
def tell
if session[:object]
if session[:object].has_cached_associations?
render :text => "has cached associations"
else
render :text => "does not have cached associations"
end
else
render :text => "there is no object"
end
end
end
def setup
@request, @response = ActionController::TestRequest.new,
ActionController::TestResponse.new
@@ -91,4 +134,12 @@ class SessionManagementTest < Test::Unit::TestCase
assert_equal CGI::Session::ActiveRecordStore, ActionController::Base.session_store
end
end
def test_process_cleanup_with_session_management_support
@controller = AssociationCachingTestController.new
get :show
assert_equal "has cached associations", @response.body
get :tell
assert_equal "does not have cached associations", @response.body
end
end

View File

@@ -66,6 +66,11 @@ HTML
redirect_to :controller => 'fail', :id => 5
end
def create
headers['Location'] = 'created resource'
head :created
end
private
def rescue_action(e)
raise e
@@ -81,7 +86,7 @@ HTML
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
ActionController::Routing::Routes.reload
ActionController::Routing.use_controllers! %w(content admin/user)
ActionController::Routing.use_controllers! %w(content admin/user test_test/test)
end
def teardown
@@ -463,6 +468,20 @@ HTML
end
end
def test_redirect_url_only_cares_about_location_header
get :create
assert_response :created
# Redirect url doesn't care that it wasn't a :redirect response.
assert_equal 'created resource', @response.redirect_url
assert_equal @response.redirect_url, redirect_to_url
# Must be a :redirect response.
assert_raise(Test::Unit::AssertionFailedError) do
assert_redirected_to 'created resource'
end
end
protected
def with_foo_routing
with_routing do |set|

View File

@@ -78,7 +78,7 @@ class UrlWriterTests < Test::Unit::TestCase
def test_named_route
ActionController::Routing::Routes.draw do |map|
map.home '/home/sweet/home/:user'
map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index'
map.connect ':controller/:action/:id'
end
@@ -96,7 +96,7 @@ class UrlWriterTests < Test::Unit::TestCase
def test_only_path
ActionController::Routing::Routes.draw do |map|
map.home '/home/sweet/home/:user'
map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index'
map.connect ':controller/:action/:id'
end

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
require File.dirname(__FILE__) + '/../abstract_unit'
class DeprecatedHelperTest < Test::Unit::TestCase
include ActionView::Helpers::JavaScriptHelper
include ActionView::Helpers::CaptureHelper
def test_update_element_function
assert_deprecated 'update_element_function' do
assert_equal %($('myelement').innerHTML = 'blub';\n),
update_element_function('myelement', :content => 'blub')
assert_equal %($('myelement').innerHTML = 'blub';\n),
update_element_function('myelement', :action => :update, :content => 'blub')
assert_equal %($('myelement').innerHTML = '';\n),
update_element_function('myelement', :action => :empty)
assert_equal %(Element.remove('myelement');\n),
update_element_function('myelement', :action => :remove)
assert_equal %(new Insertion.Bottom('myelement','blub');\n),
update_element_function('myelement', :position => 'bottom', :content => 'blub')
assert_equal %(new Insertion.Bottom('myelement','blub');\n),
update_element_function('myelement', :action => :update, :position => :bottom, :content => 'blub')
_erbout = ""
assert_equal %($('myelement').innerHTML = 'test';\n),
update_element_function('myelement') { _erbout << "test" }
_erbout = ""
assert_equal %($('myelement').innerHTML = 'blockstuff';\n),
update_element_function('myelement', :content => 'paramstuff') { _erbout << "blockstuff" }
end
end
end

View File

@@ -396,7 +396,31 @@ class FormHelperTest < Test::Unit::TestCase
assert_dom_equal expected, _erbout
end
def test_default_form_builder
old_default_form_builder, ActionView::Base.default_form_builder =
ActionView::Base.default_form_builder, LabelledFormBuilder
_erbout = ''
form_for(:post, @post) do |f|
_erbout.concat f.text_field(:title)
_erbout.concat f.text_area(:body)
_erbout.concat f.check_box(:secret)
end
expected =
"<form action='http://www.example.com' method='post'>" +
"<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
"<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
"<label for='secret'>Secret:</label> <input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
"<input name='post[secret]' type='hidden' value='0' /><br/>" +
"</form>"
assert_dom_equal expected, _erbout
ensure
ActionView::Base.default_form_builder = old_default_form_builder
end
# Perhaps this test should be moved to prototype helper tests.
def test_remote_form_for_with_labelled_builder
self.extend ActionView::Helpers::PrototypeHelper

View File

@@ -143,30 +143,6 @@ class PrototypeHelperTest < Test::Unit::TestCase
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {alert('Form changed')})\n//]]>\n</script>),
observe_form("cart", :frequency => 2, :function => "alert('Form changed')")
end
def test_update_element_function
assert_equal %($('myelement').innerHTML = 'blub';\n),
update_element_function('myelement', :content => 'blub')
assert_equal %($('myelement').innerHTML = 'blub';\n),
update_element_function('myelement', :action => :update, :content => 'blub')
assert_equal %($('myelement').innerHTML = '';\n),
update_element_function('myelement', :action => :empty)
assert_equal %(Element.remove('myelement');\n),
update_element_function('myelement', :action => :remove)
assert_equal %(new Insertion.Bottom('myelement','blub');\n),
update_element_function('myelement', :position => 'bottom', :content => 'blub')
assert_equal %(new Insertion.Bottom('myelement','blub');\n),
update_element_function('myelement', :action => :update, :position => :bottom, :content => 'blub')
_erbout = ""
assert_equal %($('myelement').innerHTML = 'test';\n),
update_element_function('myelement') { _erbout << "test" }
_erbout = ""
assert_equal %($('myelement').innerHTML = 'blockstuff';\n),
update_element_function('myelement', :content => 'paramstuff') { _erbout << "blockstuff" }
end
def test_update_page
block = Proc.new { |page| page.replace_html('foo', 'bar') }

View File

@@ -11,17 +11,21 @@ class TextHelperTest < Test::Unit::TestCase
# a view is rendered. The cycle helper depends on this behavior.
@_cycles = nil if (defined? @_cycles)
end
def test_simple_format
assert_equal "<p></p>", simple_format(nil)
assert_equal "<p>crazy\n<br /> cross\n<br /> platform linebreaks</p>", simple_format("crazy\r\n cross\r platform linebreaks")
assert_equal "<p>A paragraph</p>\n\n<p>and another one!</p>", simple_format("A paragraph\n\nand another one!")
assert_equal "<p>A paragraph\n<br /> With a newline</p>", simple_format("A paragraph\n With a newline")
text = "A\nB\nC\nD"
text = "A\nB\nC\nD".freeze
assert_equal "<p>A\n<br />B\n<br />C\n<br />D</p>", simple_format(text)
assert_equal text, "A\nB\nC\nD"
text = "A\r\n \nB\n\n\r\n\t\nC\nD".freeze
assert_equal "<p>A\n<br /> \n<br />B</p>\n\n<p>\t\n<br />C\n<br />D</p>", simple_format(text)
end
def test_truncate
assert_equal "Hello World!", truncate("Hello World!", 12)
assert_equal "Hello Wor...", truncate("Hello World!!", 12)
@@ -316,6 +320,6 @@ class TextHelperTest < Test::Unit::TestCase
%{This is a test.\n\n\nIt no longer contains any HTML.\n}, strip_tags(
%{<title>This is <b>a <a href="" target="_blank">test</a></b>.</title>\n\n<!-- it has a comment -->\n\n<p>It no <b>longer <strong>contains <em>any <strike>HTML</strike></em>.</strong></b></p>\n}))
assert_equal "This has a here.", strip_tags("This has a <!-- comment --> here.")
[nil, '', ' '].each { |blank| assert_nil strip_tags(blank) }
[nil, '', ' '].each { |blank| assert_equal blank, strip_tags(blank) }
end
end

View File

@@ -1,28 +1,37 @@
*SVN*
* Removed deprecated @request and @response usages. [Kent Sibilev]
*1.2.0 RC1* (r5619, November 22nd, 2006)
* Removed invocation of deprecated before_action and around_action filter methods. Corresponding before_invocation and after_invocation methods should be used instead. #6275 [Kent Sibilev]
* Provide access to the underlying SOAP driver. #6212 [bmilekic, Kent Sibilev]
* Deprecation: update docs. #5998 [jakob@mentalized.net, Kevin Clark]
* ActionWebService WSDL generation ignores HTTP_X_FORWARDED_HOST [Paul Butcher <paul@paulbutcher.com>]
* Tighten rescue clauses. #5985 [james@grayproductions.net]
* Fixed XMLRPC multicall when one of the called methods returns a struct object. [Kent Sibilev]
* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
* Fix invoke_layered since api_method didn't declare :expects. Closes #4720. [Kevin Ballard <kevin@sb.org>, Kent Sibilev]
* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.]
* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
*1.1.6* (August 10th, 2006)
* Rely on Action Pack 1.12.5
*1.1.5* (August 8th, 2006)
* Rely on Action Pack 1.12.4 and Active Record 1.14.4
*1.1.4* (June 29th, 2006)
* Rely on Action Pack 1.12.3
*1.1.3* (June 27th, 2006)
* Rely on Action Pack 1.12.2 and Active Record 1.14.3
* Fix test database name typo. [Marcel Molina Jr.]
*1.1.2* (April 9th, 2006)

View File

@@ -1,6 +1,6 @@
<h4>Method Invocation Details for <em><%= @scaffold_service %>#<%= @scaffold_method.public_name %></em></h4>
<%= form_tag :action => @scaffold_action_name + '_submit' %>
<% form_tag(:action => @scaffold_action_name + '_submit') do -%>
<%= hidden_field_tag "service", @scaffold_service.name %>
<%= hidden_field_tag "method", @scaffold_method.public_name %>
@@ -22,7 +22,7 @@
<% end %>
<%= submit_tag "Invoke" %>
<%= end_form_tag %>
<% end -%>
<p>
<%= link_to "Back", :action => @scaffold_action_name %>

View File

@@ -84,6 +84,7 @@ class ScaffoldedControllerTest < Test::Unit::TestCase
def test_scaffold_invoke_method_params_with_struct
get :scaffold_invoke_method_params, :service => 'scaffolded', :method => 'HelloStructParam'
assert_template 'parameters.rhtml'
assert_tag :tag => 'form'
assert_tag :tag => 'input', :attributes => {:name => "method_params[0][name]"}
end

View File

@@ -1,10 +1,48 @@
*SVN*
*1.15.0 RC2*
* Bring the sybase adapter up to scratch for 1.2 release. [jsheets]
* Oracle: fix connection reset failure. #6846 [leonlleslie]
* Subclass instantiation doesn't try to explicitly require the corresponding subclass. #6840 [leei, Jeremy Kemper]
* fix faulty inheritance tests and that eager loading grabs the wrong inheritance column when the class of your association is an STI subclass. Closes #6859 [protocool]
* find supports :lock with :include. Check whether your database allows SELECT ... FOR UPDATE with outer joins before using. #6764 [vitaly, Jeremy Kemper]
* Support nil and Array in :conditions => { attr => value } hashes. #6548 [Assaf, Jeremy Kemper]
find(:all, :conditions => { :topic_id => [1, 2, 3], :last_read => nil }
*1.15.0 RC1* (r5619, November 22nd, 2006)
* Quote ActiveSupport::Multibyte::Chars. #6653 [Julian Tarkhanov]
* MySQL: detect when a NOT NULL column without a default value is misreported as default ''. Can't detect for string, text, and binary columns since '' is a legitimate default. #6156 [simon@redhillconsulting.com.au, obrie, Jonathan Viney, Jeremy Kemper]
* validates_numericality_of uses \A \Z to ensure the entire string matches rather than ^ $ which may match one valid line of a multiline string. #5716 [Andreas Schwarz]
* Oracle: automatically detect the primary key. #6594 [vesaria, Michael Schoen]
* Oracle: to increase performance, prefetch 100 rows and enable similar cursor sharing. Both are configurable in database.yml. #6607 [philbogle@gmail.com, ray.fortna@jobster.com, Michael Schoen]
* Firebird: decimal/numeric support. #6408 [macrnic]
* Find with :include respects scoped :order. #5850
* Dynamically generate reader methods for serialized attributes. #6362 [Stefan Kaes]
* Deprecation: object transactions warning. [Jeremy Kemper]
* has_one :dependent => :nullify ignores nil associates. #6528 [janovetz, Jeremy Kemper]
* Oracle: resolve test failures, use prefetched primary key for inserts, check for null defaults, fix limited id selection for eager loading. Factor out some common methods from all adapters. #6515 [Michael Schoen]
* Make add_column use the options hash with the Sqlite Adapter. Closes #6464 [obrie]
* Document other options available to migration's add_column. #6419 [grg]
* MySQL: all_hashes compatibility with old MysqlRes class. #6429 [Jeremy Kemper]
* MySQL: all_hashes compatibility with old MysqlRes class. #6429, #6601 [Jeremy Kemper]
* Fix has_many :through to add the appropriate conditions when going through an association using STI. Closes #5783. [Jonathan Viney]
@@ -58,20 +96,12 @@
* Deprecation: use :dependent => :delete_all rather than :exclusively_dependent => true. #6024 [Josh Susser]
* Document validates_presences_of behavior with booleans: you probably want validates_inclusion_of :attr, :in => [true, false]. #2253 [Bob Silva]
* Optimistic locking: gracefully handle nil versions, treat as zero. #5908 [Tom Ward]
* to_xml: the :methods option works on arrays of records. #5845 [Josh Starcher]
* Deprecation: update docs. #5998 [jakob@mentalized.net, Kevin Clark]
* Add some XmlSerialization tests for ActiveRecord [Rick Olson]
* has_many :through conditions are sanitized by the associating class. #5971 [martin.emde@gmail.com]
* Tighten rescue clauses. #5985 [james@grayproductions.net]
* Fix spurious newlines and spaces in AR::Base#to_xml output [Jamis Buck]
* has_one supports the :dependent => :delete option which skips the typical callback chain and deletes the associated object directly from the database. #5927 [Chris Mear, Jonathan Viney]
@@ -80,8 +110,6 @@
* SQLServer: work around bug where some unambiguous date formats are not correctly identified if the session language is set to german. #5894 [Tom Ward, kruth@bfpi]
* SQLServer: fix eager association test. #5901 [Tom Ward]
* Clashing type columns due to a sloppy join shouldn't wreck single-table inheritance. #5838 [Kevin Clark]
* Fixtures: correct escaping of \n and \r. #5859 [evgeny.zislis@gmail.com]
@@ -132,16 +160,12 @@
* Remove ActiveRecord::Base.reset since Dispatcher doesn't use it anymore. [Rick Olson]
* Document find's :from option. Closes #5762. [andrew@redlinesoftware.com]
* PostgreSQL: autodetected sequences work correctly with multiple schemas. Rely on the schema search_path instead of explicitly qualifying the sequence name with its schema. #5280 [guy.naor@famundo.com]
* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
* Cache nil results for has_one associations so multiple calls don't call the database. Closes #5757. [Michael A. Schoen]
* Add documentation for how to disable timestamps on a per model basis. Closes #5684. [matt@mattmargolis.net Marcel Molina Jr.]
* Don't save has_one associations unnecessarily. #5735 [Jonathan Viney]
* Refactor ActiveRecord::Base.reset_subclasses to #reset, and add global observer resetting. [Rick Olson]
@@ -190,8 +214,6 @@
* PostgreSQL: create/drop as postgres user. #4790 [mail@matthewpainter.co.uk, mlaster@metavillage.com]
* Update callbacks documentation. #3970 [Robby Russell <robby@planetargon.com>]
* PostgreSQL: correctly quote the ' in pk_and_sequence_for. #5462 [tietew@tietew.net]
* PostgreSQL: correctly quote microseconds in timestamps. #5641 [rick@rickbradley.com]
@@ -204,8 +226,6 @@
* Added :group to available options for finds done on associations #5516 [mike@michaeldewey.org]
* Minor tweak to improve performance of ActiveRecord::Base#to_param.
* Observers also watch subclasses created after they are declared. #5535 [daniels@pronto.com.au]
* Removed deprecated timestamps_gmt class methods. [Jeremy Kemper]
@@ -305,22 +325,10 @@
* Allow AR::Base#to_xml to include methods too. Closes #4921. [johan@textdrive.com]
* Replace superfluous name_to_class_name variant with camelize. [Marcel Molina Jr.]
* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.]
* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
* Remove duplicate fixture entry in comments.yml. Closes #4923. [Blair Zajac <blair@orcaware.com>]
* Update FrontBase adapter to check binding version. Closes #4920. [mlaster@metavillage.com]
* New Frontbase connections don't start in auto-commit mode. Closes #4922. [mlaster@metavillage.com]
* When grouping, use the appropriate option key. [Marcel Molina Jr.]
* Only modify the sequence name in the FrontBase adapter if the FrontBase adapter is actually being used. [Marcel Molina Jr.]
* Add support for FrontBase (http://www.frontbase.com/) with a new adapter thanks to the hard work of one Mike Laster. Closes #4093. [mlaster@metavillage.com]
* Add warning about the proper way to validate the presence of a foreign key. Closes #4147. [Francois Beausoleil <francois.beausoleil@gmail.com>]
@@ -348,8 +356,6 @@
* Allow all calculations to take the :include option, not just COUNT (closes #4840) [Rick]
* Update inconsistent migrations documentation. #4683 [machomagna@gmail.com]
* Add ActiveRecord::Errors#to_xml [Jamis Buck]
* Properly quote index names in migrations (closes #4764) [John Long]
@@ -360,10 +366,6 @@
* Associations#select_limited_ids_list adds the ORDER BY columns to the SELECT DISTINCT List for postgresql. [Rick]
* DRY up association collection reader method generation. [Marcel Molina Jr.]
* DRY up and tweak style of the validation error object. [Marcel Molina Jr.]
* Add :case_sensitive option to validates_uniqueness_of (closes #3090) [Rick]
class Account < ActiveRecord::Base
@@ -377,7 +379,29 @@
end
*1.14.2* (April 9th, 2005)
*1.14.4* (August 8th, 2006)
* Add warning about the proper way to validate the presence of a foreign key. #4147 [Francois Beausoleil <francois.beausoleil@gmail.com>]
* Fix syntax error in documentation. #4679 [mislav@nippur.irb.hr]
* Update inconsistent migrations documentation. #4683 [machomagna@gmail.com]
*1.14.3* (June 27th, 2006)
* Fix announcement of very long migration names. #5722 [blake@near-time.com]
* Update callbacks documentation. #3970 [Robby Russell <robby@planetargon.com>]
* Properly quote index names in migrations (closes #4764) [John Long]
* Ensure that Associations#include_eager_conditions? checks both scoped and explicit conditions [Rick]
* Associations#select_limited_ids_list adds the ORDER BY columns to the SELECT DISTINCT List for postgresql. [Rick]
*1.14.2* (April 9th, 2006)
* Fixed calculations for the Oracle Adapter (closes #4626) [Michael Schoen]

View File

@@ -947,10 +947,6 @@ module ActiveRecord
end
end
def require_association_class(class_name)
require_association(Inflector.underscore(class_name)) if class_name
end
def add_multiple_associated_save_callbacks(association_name)
method_name = "validate_associated_records_for_#{association_name}".to_sym
define_method(method_name) do
@@ -1060,7 +1056,7 @@ module ActiveRecord
when :delete
module_eval "before_destroy '#{reflection.class_name}.delete(#{reflection.name}.id) unless #{reflection.name}.nil?'"
when :nullify
module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil)'"
module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil) unless #{reflection.name}.nil?'"
when nil, false
# pass
else
@@ -1169,9 +1165,10 @@ module ActiveRecord
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
sql << "GROUP BY #{options[:group]} " if options[:group]
sql << "ORDER BY #{options[:order]} " if options[:order]
add_order!(sql, options[:order], scope)
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
add_lock!(sql, options, scope)
return sanitize_sql(sql)
end
@@ -1190,25 +1187,23 @@ module ActiveRecord
"#{name} Load IDs For Limited Eager Loading"
).collect { |row| connection.quote(row[primary_key]) }.join(", ")
end
def construct_finder_sql_for_association_limiting(options, join_dependency)
scope = scope(:find)
is_distinct = include_eager_conditions?(options) || include_eager_order?(options)
sql = "SELECT "
if is_distinct
ordered_columns = options[:order].to_s.split(',').collect! { |s| s.split.first }
options[:order] = "#{table_name}.#{primary_key}, #{options[:order]}" if options[:order] && connection.requires_order_columns_in_distinct_clause?
sql << connection.distinct("#{table_name}.#{primary_key}", ordered_columns)
sql << connection.distinct("#{table_name}.#{primary_key}", options[:order])
else
sql << primary_key
end
sql << " FROM #{table_name} "
if is_distinct
sql << join_dependency.join_associations.collect(&:association_join).join
add_joins!(sql, options, scope)
end
add_conditions!(sql, options[:conditions], scope)
sql << "ORDER BY #{options[:order]} " if options[:order]
add_limit!(sql, options, scope)
@@ -1570,7 +1565,7 @@ module ActiveRecord
end || ''
join << %(AND %s.%s = %s ) % [
aliased_table_name,
reflection.active_record.connection.quote_column_name(reflection.active_record.inheritance_column),
reflection.active_record.connection.quote_column_name(klass.inheritance_column),
klass.quote_value(klass.name.demodulize)] unless klass.descends_from_active_record?
[through_reflection, reflection].each do |ref|

View File

@@ -648,9 +648,10 @@ module ActiveRecord #:nodoc:
key
end
# Defines the column name for use with single table inheritance -- can be overridden in subclasses.
# Defines the column name for use with single table inheritance
# -- can be set in subclasses like so: self.inheritance_column = "type_id"
def inheritance_column
"type"
@inheritance_column ||= "type".freeze
end
# Lazy-set the sequence name to the connection's default. This method
@@ -762,7 +763,7 @@ module ActiveRecord #:nodoc:
@columns
end
# Returns an array of column objects for the table associated with this class.
# Returns a hash of column objects for the table associated with this class.
def columns_hash
@columns_hash ||= columns.inject({}) { |hash, column| hash[column.name] = column; hash }
end
@@ -800,7 +801,7 @@ module ActiveRecord #:nodoc:
# Resets all the cached information about columns, which will cause them to be reloaded on the next request.
def reset_column_information
read_methods.each { |name| undef_method(name) }
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @read_methods = nil
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @read_methods = @inheritance_column = nil
end
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
@@ -986,7 +987,7 @@ module ActiveRecord #:nodoc:
options.update(:limit => 1) unless options[:include]
find_every(options).first
end
def find_every(options)
records = scoped?(:find, :include) || options[:include] ?
find_with_associations(options) :
@@ -996,11 +997,11 @@ module ActiveRecord #:nodoc:
records
end
def find_from_ids(ids, options)
expects_array = ids.first.kind_of?(Array)
expects_array = ids.first.kind_of?(Array)
return ids.first if expects_array && ids.first.empty?
ids = ids.flatten.compact.uniq
case ids.size
@@ -1053,11 +1054,9 @@ module ActiveRecord #:nodoc:
allocate
else
require_association_class(subclass_name)
# Ignore type if no column is present since it was probably
# pulled in from a sloppy join.
unless self.columns_hash.include?(inheritance_column)
unless columns_hash.include?(inheritance_column)
allocate
else
@@ -1096,7 +1095,7 @@ module ActiveRecord #:nodoc:
sql << " GROUP BY #{options[:group]} " if options[:group]
add_order!(sql, options[:order])
add_order!(sql, options[:order], scope)
add_limit!(sql, options, scope)
add_lock!(sql, options, scope)
@@ -1120,12 +1119,14 @@ module ActiveRecord #:nodoc:
end
end
def add_order!(sql, order)
def add_order!(sql, order, scope = :auto)
scope = scope(:find) if :auto == scope
scoped_order = scope[:order] if scope
if order
sql << " ORDER BY #{order}"
sql << ", #{scope(:find, :order)}" if scoped?(:find, :order)
sql << ", #{scoped_order}" if scoped_order
else
sql << " ORDER BY #{scope(:find, :order)}" if scoped?(:find, :order)
sql << " ORDER BY #{scoped_order}" if scoped_order
end
end
@@ -1158,7 +1159,7 @@ module ActiveRecord #:nodoc:
segments = []
segments << sanitize_sql(scope[:conditions]) if scope && scope[:conditions]
segments << sanitize_sql(conditions) unless conditions.nil?
segments << type_condition unless descends_from_active_record?
segments << type_condition unless descends_from_active_record?
segments.compact!
sql << "WHERE (#{segments.join(") AND (")}) " unless segments.empty?
end
@@ -1186,22 +1187,22 @@ module ActiveRecord #:nodoc:
# It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount
# is actually find_all_by_amount(amount, options).
def method_missing(method_id, *arguments)
if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s)
if match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
finder, deprecated_finder = determine_finder(match), determine_deprecated_finder(match)
attribute_names = extract_attribute_names_from_match(match)
super unless all_attributes_exists?(attribute_names)
conditions = construct_conditions_from_arguments(attribute_names, arguments)
attributes = construct_attributes_from_arguments(attribute_names, arguments)
case extra_options = arguments[attribute_names.size]
when nil
options = { :conditions => conditions }
options = { :conditions => attributes }
set_readonly_option!(options)
ActiveSupport::Deprecation.silence { send(finder, options) }
when Hash
finder_options = extra_options.merge(:conditions => conditions)
finder_options = extra_options.merge(:conditions => attributes)
validate_find_options(finder_options)
set_readonly_option!(finder_options)
@@ -1215,17 +1216,19 @@ module ActiveRecord #:nodoc:
else
ActiveSupport::Deprecation.silence do
send(deprecated_finder, conditions, *arguments[attribute_names.length..-1])
send(deprecated_finder, sanitize_sql(attributes), *arguments[attribute_names.length..-1])
end
end
elsif match = /find_or_(initialize|create)_by_([_a-zA-Z]\w*)/.match(method_id.to_s)
elsif match = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s)
instantiator = determine_instantiator(match)
attribute_names = extract_attribute_names_from_match(match)
super unless all_attributes_exists?(attribute_names)
options = { :conditions => construct_conditions_from_arguments(attribute_names, arguments) }
attributes = construct_attributes_from_arguments(attribute_names, arguments)
options = { :conditions => attributes }
set_readonly_option!(options)
find_initial(options) || send(instantiator, construct_attributes_from_arguments(attribute_names, arguments))
find_initial(options) || send(instantiator, attributes)
else
super
end
@@ -1247,12 +1250,6 @@ module ActiveRecord #:nodoc:
match.captures.last.split('_and_')
end
def construct_conditions_from_arguments(attribute_names, arguments)
conditions = []
attribute_names.each_with_index { |name, idx| conditions << "#{table_name}.#{connection.quote_column_name(name)} #{attribute_condition(arguments[idx])} " }
[ conditions.join(" AND "), *arguments[0...attribute_names.length] ]
end
def construct_attributes_from_arguments(attribute_names, arguments)
attributes = {}
attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
@@ -1275,7 +1272,7 @@ module ActiveRecord #:nodoc:
def expand_id_conditions(id_or_conditions)
case id_or_conditions
when Array, Hash then id_or_conditions
else construct_conditions_from_arguments([primary_key], [id_or_conditions])
else sanitize_sql(primary_key => id_or_conditions)
end
end
@@ -1355,9 +1352,9 @@ module ActiveRecord #:nodoc:
def compute_type(type_name)
modularized_name = type_name_with_module(type_name)
begin
instance_eval(modularized_name)
rescue NameError => e
instance_eval(type_name)
class_eval(modularized_name, __FILE__, __LINE__)
rescue NameError
class_eval(type_name, __FILE__, __LINE__)
end
end
@@ -1377,26 +1374,32 @@ module ActiveRecord #:nodoc:
klass.base_class.name
end
#Accepts an array, hash, or string of sql conditions and
#deals with them accordingly
# Accepts an array, hash, or string of sql conditions and sanitizes
# them into a valid SQL fragment.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
# { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
def sanitize_sql(condition)
return sanitize_sql_array(condition) if condition.is_a?(Array)
return sanitize_sql_hash(condition) if condition.is_a?(Hash)
condition
case condition
when Array; sanitize_sql_array(condition)
when Hash; sanitize_sql_hash(condition)
else condition
end
end
# Accepts a hash of conditions. The hash has each key/value or attribute/value pair
# sanitized and interpolated into the sql statement.
# { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id= 4"
def sanitize_sql_hash(hash)
hash.collect { |attrib, value|
"#{table_name}.#{connection.quote_column_name(attrib)} = #{quote_value(value)}"
}.join(" AND ")
# Sanitizes a hash of attribute/value pairs into SQL conditions.
# { :name => "foo'bar", :group_id => 4 }
# # => "name='foo''bar' and group_id= 4"
# { :status => nil, :group_id => [1,2,3] }
# # => "status IS NULL and group_id IN (1,2,3)"
def sanitize_sql_hash(attrs)
conditions = attrs.map do |attr, value|
"#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
end.join(' AND ')
replace_bind_variables(conditions, attrs.values)
end
# Accepts an array of conditions. The array has each value
# sanitized and interpolated into the sql statement.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
@@ -1874,9 +1877,16 @@ module ActiveRecord #:nodoc:
# ActiveRecord::Base.generate_read_methods is set to true.
def define_read_methods
self.class.columns_hash.each do |name, column|
unless self.class.serialized_attributes[name]
define_read_method(name.to_sym, name, column) unless respond_to_without_attributes?(name)
define_question_method(name) unless respond_to_without_attributes?("#{name}?")
unless respond_to_without_attributes?(name)
if self.class.serialized_attributes[name]
define_read_method_for_serialized_attribute(name)
else
define_read_method(name.to_sym, name, column)
end
end
unless respond_to_without_attributes?("#{name}?")
define_question_method(name)
end
end
end
@@ -1894,6 +1904,15 @@ module ActiveRecord #:nodoc:
evaluate_read_method attr_name, "def #{symbol}; #{access_code}; end"
end
# Define read method for serialized attribute.
def define_read_method_for_serialized_attribute(attr_name)
unless attr_name.to_s == self.class.primary_key.to_s
self.class.read_methods << attr_name
end
evaluate_read_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end"
end
# Define an attribute ? method.
def define_question_method(attr_name)
unless attr_name.to_s == self.class.primary_key.to_s

View File

@@ -86,6 +86,14 @@ module ActiveRecord
conn.disconnect!
end
end
# Clears the cache which maps classes
def clear_reloadable_connections!
@@active_connections.each do |name, conn|
conn.disconnect! if conn.supports_reloading?
@@active_connections.delete(name)
end
end
# Verify active connections.
def verify_active_connections! #:nodoc:

View File

@@ -4,11 +4,14 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select_all(sql, name = nil)
select(sql, name)
end
# Returns a record hash with the column names as keys and column values
# as values.
def select_one(sql, name = nil)
result = select(sql, name)
result.first if result
end
# Returns a single value from a record
@@ -25,19 +28,24 @@ module ActiveRecord
end
# Executes the SQL statement in the context of this connection.
# This abstract method raises a NotImplementedError.
def execute(sql, name = nil)
raise NotImplementedError, "execute is an abstract method"
end
# Returns the last auto-generated ID from the affected table.
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) end
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
raise NotImplementedError, "insert is an abstract method"
end
# Executes the update statement and returns the number of rows affected.
def update(sql, name = nil) end
def update(sql, name = nil)
execute(sql, name)
end
# Executes the delete statement and returns the number of rows affected.
def delete(sql, name = nil) end
def delete(sql, name = nil)
update(sql, name)
end
# Wrap a block in a transaction. Returns result of block.
def transaction(start_db_transaction = true)
@@ -110,6 +118,13 @@ module ActiveRecord
def reset_sequence!(table, column, sequence = nil)
# Do nothing by default. Implement for PostgreSQL, Oracle, ...
end
protected
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select(sql, name = nil)
raise NotImplementedError, "select is an abstract method"
end
end
end
end

View File

@@ -8,7 +8,8 @@ module ActiveRecord
return value.quoted_id if value.respond_to?(:quoted_id)
case value
when String
when String, ActiveSupport::Multibyte::Chars
value = value.to_s
if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
"'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode)
elsif column && [:integer, :float].include?(column.type)

View File

@@ -280,8 +280,11 @@ module ActiveRecord
sql << " NOT NULL" if options[:null] == false
end
# SELECT DISTINCT clause for a given set of columns. PostgreSQL overrides this for custom DISTINCT syntax.
def distinct(columns, *order_columns)
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
# Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax.
#
# distinct("posts.id", "posts.created_at desc")
def distinct(columns, order_by)
"DISTINCT #{columns}"
end
end

View File

@@ -56,12 +56,6 @@ module ActiveRecord
false
end
# Does this adapter require the order columns to be in the select clause
# of a DISTINCT query? This is +false+ in all adapters except postgresql.
def requires_order_columns_in_distinct_clause?
false
end
def reset_runtime #:nodoc:
rt, @runtime = @runtime, 0
rt
@@ -85,6 +79,12 @@ module ActiveRecord
@active = false
end
# Returns true if its safe to reload the connection between requests for development mode.
# This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
def supports_reloading?
false
end
# Lazily verify this connection, calling +active?+ only if it hasn't
# been called for +timeout+ seconds.
def verify!(timeout)

View File

@@ -47,14 +47,6 @@ begin
end
end
end
def select_all(sql, name = nil)
select(sql, name)
end
def select_one(sql, name = nil)
select(sql, name).first
end
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
execute(sql, name = nil)
@@ -72,9 +64,6 @@ begin
rows_affected
end
alias_method :update, :execute
alias_method :delete, :execute
def begin_db_transaction
@connection.set_auto_commit_off
end

View File

@@ -50,7 +50,7 @@ module ActiveRecord
@default = parse_default(default_source) if default_source
@limit = decide_limit(length)
@domain, @sub_type, @precision, @scale = domain, sub_type, precision, scale
@domain, @sub_type, @precision, @scale = domain, sub_type, precision, scale.abs
end
def type
@@ -293,6 +293,8 @@ module ActiveRecord
:string => { :name => "varchar", :limit => 255 },
:text => { :name => "blob sub_type text" },
:integer => { :name => "bigint" },
:decimal => { :name => "decimal" },
:numeric => { :name => "numeric" },
:float => { :name => "float" },
:datetime => { :name => "timestamp" },
:timestamp => { :name => "timestamp" },
@@ -534,12 +536,7 @@ module ActiveRecord
end
def remove_index(table_name, options) #:nodoc:
if Hash === options
index_name = options[:name]
else
index_name = "#{table_name}_#{options}_index"
end
execute "DROP INDEX #{index_name}"
execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
end
def rename_table(name, new_name) # :nodoc:
@@ -568,12 +565,12 @@ module ActiveRecord
super << ";\n"
end
def type_to_sql(type, limit = nil) # :nodoc:
def type_to_sql(type, limit = nil, precision = nil, scale = nil) # :nodoc:
case type
when :integer then integer_sql_type(limit)
when :float then float_sql_type(limit)
when :string then super
else super(type)
when :string then super(type, limit, precision, scale)
else super(type, limit, precision, scale)
end
end

View File

@@ -1,34 +1,29 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'set'
module MysqlCompat
# add all_hashes method to standard mysql-c bindings or pure ruby version
def self.define_all_hashes_method!
raise 'Mysql not loaded' unless defined?(::Mysql)
# for compatibility
Object.const_set(:MysqlRes, Mysql::Result) unless defined?(::MysqlRes)
Object.const_set(:MysqlField, Mysql::Field) unless defined?(::MysqlField)
Object.const_set(:MysqlError, Mysql::Error) unless defined?(::MysqlError)
return if ::MysqlRes.instance_methods.include?('all_hashes')
target = defined?(Mysql::Result) ? Mysql::Result : MysqlRes
return if target.instance_methods.include?('all_hashes')
# Ruby driver has a version string and returns null values in each_hash
# C driver >= 2.7 returns null values in each_hash
if Mysql.const_defined?(:VERSION)
if Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700
::MysqlRes.class_eval <<-'end_eval'
def all_hashes
rows = []
each_hash { |row| rows << row }
rows
end
end_eval
if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700)
target.class_eval <<-'end_eval'
def all_hashes
rows = []
each_hash { |row| rows << row }
rows
end
end_eval
# adapters before 2.7 don't have a version constant
# and don't return null values in each_hash
else
::MysqlRes.class_eval <<-'end_eval'
target.class_eval <<-'end_eval'
def all_hashes
rows = []
all_fields = fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
@@ -37,19 +32,22 @@ module MysqlCompat
end
end_eval
end
unless target.instance_methods.include?('all_hashes')
raise "Failed to defined #{target.name}#all_hashes method. Mysql::VERSION = #{Mysql::VERSION.inspect}"
end
end
end
module ActiveRecord
class Base
# Establishes a connection to the database that's used by all Active Record objects.
def self.mysql_connection(config) # :nodoc:
# Only include the MySQL driver if one hasn't already been loaded
def self.require_mysql
# Include the MySQL driver if one hasn't already been loaded
unless defined? Mysql
begin
require_library_or_gem 'mysql'
rescue LoadError => cannot_require_mysql
# Only use the supplied backup Ruby/MySQL driver if no driver is already in place
# Use the bundled Ruby/MySQL driver if no driver is already in place
begin
require 'active_record/vendor/mysql'
rescue LoadError
@@ -60,7 +58,10 @@ module ActiveRecord
# Define Mysql::Result.all_hashes
MysqlCompat.define_all_hashes_method!
end
# Establishes a connection to the database that's used by all Active Record objects.
def self.mysql_connection(config) # :nodoc:
config = config.symbolize_keys
host = config[:host]
port = config[:port]
@@ -74,20 +75,41 @@ module ActiveRecord
raise ArgumentError, "No database specified. Missing argument: database."
end
require_mysql
mysql = Mysql.init
mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
end
end
module ConnectionAdapters
class MysqlColumn < Column #:nodoc:
TYPES_ALLOWING_EMPTY_STRING_DEFAULT = Set.new([:binary, :string, :text])
def initialize(name, default, sql_type = nil, null = true)
@original_default = default
super
@default = nil if missing_default_forged_as_empty_string?
end
private
def simplified_type(field_type)
return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
return :string if field_type =~ /enum/i
super
end
# MySQL misreports NOT NULL column default when none is given.
# We can't detect this for columns which may have a legitimate ''
# default (string, text, binary) but we can for others (integer,
# datetime, boolean, and the rest).
#
# Test whether the column has default '', is not null, and is not
# a type allowing default ''.
def missing_default_forged_as_empty_string?
!null && @original_default == '' && !TYPES_ALLOWING_EMPTY_STRING_DEFAULT.include?(type)
end
end
# The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
@@ -217,16 +239,7 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
def select_all(sql, name = nil) #:nodoc:
select(sql, name)
end
def select_one(sql, name = nil) #:nodoc:
result = select(sql, name)
result.nil? ? nil : result.first
end
def execute(sql, name = nil, retries = 2) #:nodoc:
def execute(sql, name = nil) #:nodoc:
log(sql, name) { @connection.query(sql) }
rescue ActiveRecord::StatementInvalid => exception
if exception.message.split(":").first =~ /Packets out of order/
@@ -246,9 +259,6 @@ module ActiveRecord
@connection.affected_rows
end
alias_method :delete, :update #:nodoc:
def begin_db_transaction #:nodoc:
execute "BEGIN"
rescue Exception

View File

@@ -41,28 +41,16 @@ begin
self.oracle_connection(config)
end
# Enable the id column to be bound into the sql later, by the adapter's insert method.
# This is preferable to inserting the hard-coded value here, because the insert method
# needs to know the id value explicitly.
alias :attributes_with_quotes_pre_oracle :attributes_with_quotes
def attributes_with_quotes(include_primary_key = true) #:nodoc:
aq = attributes_with_quotes_pre_oracle(include_primary_key)
if connection.class == ConnectionAdapters::OracleAdapter
aq[self.class.primary_key] = ":id" if include_primary_key && aq[self.class.primary_key].nil?
end
aq
end
# After setting large objects to empty, select the OCI8::LOB
# and write back the data.
after_save :write_lobs
after_save :write_lobs
def write_lobs() #:nodoc:
if connection.is_a?(ConnectionAdapters::OracleAdapter)
self.class.columns.select { |c| c.sql_type =~ /LOB$/i }.each { |c|
value = self[c.name]
next if value.nil? || (value == '')
lob = connection.select_one(
"SELECT #{c.name} FROM #{self.class.table_name} WHERE #{self.class.primary_key} = #{quote(id)}",
"SELECT #{c.name} FROM #{self.class.table_name} WHERE #{self.class.primary_key} = #{quote_value(id)}",
'Writable Large Object')[c.name]
lob.write value
}
@@ -145,7 +133,7 @@ begin
def supports_migrations? #:nodoc:
true
end
def native_database_types #:nodoc
{
:primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
@@ -192,7 +180,7 @@ begin
def quoted_true
"1"
end
def quoted_false
"0"
end
@@ -204,7 +192,7 @@ begin
# Returns true if the connection is active.
def active?
# Pings the connection to check if it's still good. Note that an
# #active? method is also available, but that simply returns the
# #active? method is also available, but that simply returns the
# last known state, which isn't good enough if the connection has
# gone stale since the last use.
@connection.ping
@@ -230,34 +218,23 @@ begin
#
# see: abstract/database_statements.rb
def select_all(sql, name = nil) #:nodoc:
select(sql, name)
end
def select_one(sql, name = nil) #:nodoc:
result = select_all(sql, name)
result.size > 0 ? result.first : nil
end
def execute(sql, name = nil) #:nodoc:
log(sql, name) { @connection.exec sql }
end
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
if pk.nil? # Who called us? What does the sql look like? No idea!
execute sql, name
elsif id_value # Pre-assigned id
log(sql, name) { @connection.exec sql }
else # Assume the sql contains a bind-variable for the id
id_value = select_one("select #{sequence_name}.nextval id from dual")['id'].to_i
log(sql.sub(/\B:id\b/, id_value.to_s), name) { @connection.exec sql, id_value }
end
id_value
# Returns the next sequence value from a sequence generator. Not generally
# called directly; used by ActiveRecord to get the next primary key value
# when inserting a new database record (see #prefetch_primary_key?).
def next_sequence_value(sequence_name)
id = 0
@connection.exec("select #{sequence_name}.nextval id from dual") { |r| id = r[0].to_i }
id
end
alias :update :execute #:nodoc:
alias :delete :execute #:nodoc:
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
execute(sql, name)
id_value
end
def begin_db_transaction #:nodoc:
@connection.autocommit = false
@@ -285,6 +262,12 @@ begin
end
end
# Returns true for Oracle adapter (since Oracle requires primary key
# values to be pre-fetched before insert). See also #next_sequence_value.
def prefetch_primary_key?(table_name = nil)
true
end
def default_sequence_name(table, column) #:nodoc:
"#{table}_seq"
end
@@ -332,7 +315,7 @@ begin
def columns(table_name, name = nil) #:nodoc:
(owner, table_name) = @connection.describe(table_name)
table_cols = %Q{
table_cols = <<-SQL
select column_name as name, data_type as sql_type, data_default, nullable,
decode(data_type, 'NUMBER', data_precision,
'FLOAT', data_precision,
@@ -343,7 +326,7 @@ begin
where owner = '#{owner}'
and table_name = '#{table_name}'
order by column_id
}
SQL
select_all(table_cols, name).map do |row|
limit, scale = row['limit'], row['scale']
@@ -355,6 +338,7 @@ begin
if row['data_default']
row['data_default'].sub!(/^(.*?)\s*$/, '\1')
row['data_default'].sub!(/^'(.*)'$/, '\1')
row['data_default'] = nil if row['data_default'] =~ /^null$/i
end
OracleColumn.new(oracle_downcase(row['name']),
@@ -372,7 +356,7 @@ begin
def rename_table(name, new_name) #:nodoc:
execute "RENAME #{name} TO #{new_name}"
execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
end
end
def drop_table(name) #:nodoc:
super(name)
@@ -401,26 +385,45 @@ begin
execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
end
# Find a table's primary key and sequence.
# *Note*: Only primary key is implemented - sequence will be nil.
def pk_and_sequence_for(table_name)
(owner, table_name) = @connection.describe(table_name)
pks = select_values(<<-SQL, 'Primary Key')
select cc.column_name
from all_constraints c, all_cons_columns cc
where c.owner = '#{owner}'
and c.table_name = '#{table_name}'
and c.constraint_type = 'P'
and cc.owner = c.owner
and cc.constraint_name = c.constraint_name
SQL
# only support single column keys
pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
end
def structure_dump #:nodoc:
s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
structure << "create sequence #{seq.to_a.first.last};\n\n"
end
select_all("select table_name from user_tables").inject(s) do |structure, table|
ddl = "create table #{table.to_a.first.last} (\n "
ddl = "create table #{table.to_a.first.last} (\n "
cols = select_all(%Q{
select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
from user_tab_columns
where table_name = '#{table.to_a.first.last}'
order by column_id
}).map do |row|
col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
}).map do |row|
col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
col << "(#{row['data_precision'].to_i}"
col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
col << ')'
elsif row['data_type'].include?('CHAR')
col << "(#{row['data_length'].to_i})"
col << "(#{row['data_length'].to_i})"
end
col << " default #{row['data_default']}" if !row['data_default'].nil?
col << ' not null' if row['nullable'] == 'N'
@@ -442,6 +445,34 @@ begin
end
end
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
#
# Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
# queries. However, with those columns included in the SELECT DISTINCT list, you
# won't actually get a distinct list of the column you want (presuming the column
# has duplicates with multiple values for the ordered-by columns. So we use the
# FIRST_VALUE function to get a single (first) value for each column, effectively
# making every row the same.
#
# distinct("posts.id", "posts.created_at desc")
def distinct(columns, order_by)
return "DISTINCT #{columns}" if order_by.blank?
# construct a clean list of column names from the ORDER BY clause, removing
# any asc/desc modifiers
order_columns = order_by.split(',').collect! { |s| s.split.first }
order_columns.delete_if &:blank?
# simplify the ORDER BY to just use positional syntax, to avoid the complexity of
# having to create valid column aliases for the FIRST_VALUE columns
order_by.replace(((offset=columns.count(',')+2) .. offset+order_by.count(',')).to_a * ", ")
# construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
# FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
order_columns.map! { |c| "FIRST_VALUE(#{c}) OVER (PARTITION BY #{columns} ORDER BY #{c})" }
sql = "DISTINCT #{columns}, "
sql << order_columns * ", "
end
private
@@ -518,7 +549,7 @@ begin
def describe(name)
@desc ||= @@env.alloc(OCIDescribe)
@desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
@desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK)
@desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK) rescue raise %Q{"DESC #{name}" failed; does it exist?}
info = @desc.attrGet(OCI_ATTR_PARAM)
case info.attrGet(OCI_ATTR_PTYPE)
@@ -530,6 +561,7 @@ begin
schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
name = info.attrGet(OCI_ATTR_NAME)
describe(schema + '.' + name)
else raise %Q{"DESC #{name}" failed; not a table or view.}
end
end
@@ -539,12 +571,14 @@ begin
# The OracleConnectionFactory factors out the code necessary to connect and
# configure an Oracle/OCI connection.
class OracleConnectionFactory #:nodoc:
def new_connection(username, password, database, async)
def new_connection(username, password, database, async, prefetch_rows, cursor_sharing)
conn = OCI8.new username, password, database
conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
conn.autocommit = true
conn.non_blocking = true if async
conn.prefetch_rows = prefetch_rows
conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil
conn
end
end
@@ -552,10 +586,10 @@ begin
# The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
# reset functionality. If a call to #exec fails, and autocommit is turned on
# (ie., we're not in the middle of a longer transaction), it will
# (ie., we're not in the middle of a longer transaction), it will
# automatically reconnect and try again. If autocommit is turned off,
# this would be dangerous (as the earlier part of the implied transaction
# may have failed silently if the connection died) -- so instead the
# may have failed silently if the connection died) -- so instead the
# connection is marked as dead, to be reconnected on it's next use.
class OCI8AutoRecover < DelegateClass(OCI8) #:nodoc:
attr_accessor :active
@@ -571,8 +605,10 @@ begin
@active = true
@username, @password, @database, = config[:username], config[:password], config[:database]
@async = config[:allow_concurrency]
@prefetch_rows = config[:prefetch_rows] || 100
@cursor_sharing = config[:cursor_sharing] || 'similar'
@factory = factory
@connection = @factory.new_connection @username, @password, @database, @async
@connection = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing
super @connection
end
@@ -591,7 +627,7 @@ begin
def reset!
logoff rescue nil
begin
@connection = @factory.new_connection @username, @password, @database, @async
@connection = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing
__setobj__ @connection
@active = true
rescue
@@ -601,7 +637,7 @@ begin
end
# ORA-00028: your session has been killed
# ORA-01012: not logged on
# ORA-01012: not logged on
# ORA-03113: end-of-file on communication channel
# ORA-03114: not connected to ORACLE
LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]

View File

@@ -111,10 +111,6 @@ module ActiveRecord
63
end
def requires_order_columns_in_distinct_clause?
true
end
# QUOTING ==================================================
def quote(value, column = nil)
@@ -136,15 +132,6 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
def select_all(sql, name = nil) #:nodoc:
select(sql, name)
end
def select_one(sql, name = nil) #:nodoc:
result = select(sql, name)
result.first if result
end
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
execute(sql, name)
table = sql.split(" ", 4)[2]
@@ -175,9 +162,6 @@ module ActiveRecord
execute(sql, name).cmdtuples
end
alias_method :delete, :update #:nodoc:
def begin_db_transaction #:nodoc:
execute "BEGIN"
end
@@ -388,14 +372,27 @@ module ActiveRecord
end
end
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries.
# If you select distinct by a column though, you must pass that column in the order by clause too:
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
#
# distinct("posts.id", 'posts.id', 'posts.created_at')
def distinct(columns, *order_columns)
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
# requires that the ORDER BY include the distinct column.
#
# distinct("posts.id", "posts.created_at desc")
def distinct(columns, order_by)
return "DISTINCT #{columns}" if order_by.blank?
# construct a clean list of column names from the ORDER BY clause, removing
# any asc/desc modifiers
order_columns = order_by.split(',').collect! { |s| s.split.first }
order_columns.delete_if &:blank?
sql = "DISTINCT ON (#{columns}) #{columns}"
sql << (order_columns.any? ? ", #{order_columns * ', '}" : '')
# add the DISTINCT columns to the start of the ORDER BY clause
order_by.replace "#{columns}, #{order_by}"
# return a DISTINCT ON() clause that's distinct on the columns we want but includes
# all the required columns for the ORDER BY to work properly
sql = "DISTINCT ON (#{columns}) #{columns}, "
sql << order_columns * ', '
end
private

View File

@@ -101,6 +101,10 @@ module ActiveRecord
def supports_migrations? #:nodoc:
true
end
def supports_reloading?
true
end
def supports_count_distinct? #:nodoc:
false

View File

@@ -253,16 +253,6 @@ module ActiveRecord
@connection.disconnect rescue nil
end
def select_all(sql, name = nil)
select(sql, name)
end
def select_one(sql, name = nil)
add_limit!(sql, :limit => 1)
result = select(sql, name)
result.nil? ? nil : result.first
end
def columns(table_name, name = nil)
return [] if table_name.blank?
table_name = table_name.to_s if table_name.is_a?(Symbol)
@@ -331,7 +321,7 @@ module ActiveRecord
end
end
end
def begin_db_transaction
@connection["AutoCommit"] = false
rescue Exception => e

View File

@@ -1,13 +1,20 @@
# sybase_adaptor.rb
# Author: John Sheets <dev@metacasa.net>
# Author: John R. Sheets
#
# 01 Mar 2006: Initial version. Based on code from Will Sobel
# (http://dev.rubyonrails.org/ticket/2030)
#
# 01 Mar 2006: Initial version.
# 17 Mar 2006: Added support for migrations; fixed issues with :boolean columns.
#
# 13 Apr 2006: Improved column type support to properly handle dates and user-defined
# types; fixed quoting of integer columns.
#
# Based on code from Will Sobel (http://dev.rubyonrails.org/ticket/2030)
#
#
# 05 Jan 2007: Updated for Rails 1.2 release:
# restricted Fixtures#insert_fixtures monkeypatch to Sybase adapter;
# removed SQL type precision from TEXT type to fix broken
# ActiveRecordStore (jburks, #6878); refactored select() to use execute();
# fixed leaked exception for no-op change_column(); removed verbose SQL dump
# from columns(); added missing scale parameter in normalize_type().
require 'active_record/connection_adapters/abstract_adapter'
@@ -77,7 +84,7 @@ module ActiveRecord
# 2> go
class SybaseAdapter < AbstractAdapter # :nodoc:
class ColumnWithIdentity < Column
attr_reader :identity, :primary
attr_reader :identity
def initialize(name, default, sql_type = nil, nullable = nil, identity = nil, primary = nil)
super(name, default, sql_type, nullable)
@@ -98,16 +105,6 @@ module ActiveRecord
end
end
def self.string_to_time(string)
return string unless string.is_a?(String)
# Since Sybase doesn't handle DATE or TIME, handle it here.
# Populate nil year/month/day with string_to_dummy_time() values.
time_array = ParseDate.parsedate(string)[0..5]
time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1;
Time.send(Base.default_timezone, *time_array) rescue nil
end
def self.string_to_binary(value)
"0x#{value.unpack("H*")[0]}"
end
@@ -148,6 +145,15 @@ module ActiveRecord
}
end
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
return super unless type.to_s == 'integer'
if !limit.nil? && limit < 4
'smallint'
else
'integer'
end
end
def adapter_name
'Sybase'
end
@@ -170,23 +176,6 @@ module ActiveRecord
30
end
def select_all(sql, name = nil)
select(sql, name)
end
def select_one(sql, name = nil)
result = select(sql, name)
result.nil? ? nil : result.first
end
def columns(table_name, name = nil)
table_structure(table_name).inject([]) do |columns, column|
name, default, type, nullable, identity, primary = column
columns << ColumnWithIdentity.new(name, default, type, nullable, identity, primary)
columns
end
end
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
begin
table_name = get_table_name(sql)
@@ -221,49 +210,62 @@ module ActiveRecord
end
def execute(sql, name = nil)
log(sql, name) do
@connection.context.reset
@connection.set_rowcount(@limit || 0)
@limit = @offset = nil
@connection.sql_norow(sql)
if @connection.cmd_fail? or @connection.context.failed?
raise "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}"
end
end
# Return rows affected
raw_execute(sql, name)
@connection.results[0].row_count
end
alias_method :update, :execute
alias_method :delete, :execute
def begin_db_transaction() execute "BEGIN TRAN" end
def commit_db_transaction() execute "COMMIT TRAN" end
def rollback_db_transaction() execute "ROLLBACK TRAN" end
def begin_db_transaction() raw_execute "BEGIN TRAN" end
def commit_db_transaction() raw_execute "COMMIT TRAN" end
def rollback_db_transaction() raw_execute "ROLLBACK TRAN" end
def current_database
select_one("select DB_NAME() as name")["name"]
end
def tables(name = nil)
tables = []
select("select name from sysobjects where type='U'", name).each do |row|
tables << row['name']
end
tables
select("select name from sysobjects where type='U'", name).map { |row| row['name'] }
end
def indexes(table_name, name = nil)
indexes = []
select("exec sp_helpindex #{table_name}", name).each do |index|
select("exec sp_helpindex #{table_name}", name).map do |index|
unique = index["index_description"] =~ /unique/
primary = index["index_description"] =~ /^clustered/
if !primary
cols = index["index_keys"].split(", ").each { |col| col.strip! }
indexes << IndexDefinition.new(table_name, index["index_name"], unique, cols)
IndexDefinition.new(table_name, index["index_name"], unique, cols)
end
end.compact
end
def columns(table_name, name = nil)
sql = <<SQLTEXT
SELECT col.name AS name, type.name AS type, col.prec, col.scale,
col.length, col.status, obj.sysstat2, def.text
FROM sysobjects obj, syscolumns col, systypes type, syscomments def
WHERE obj.id = col.id AND col.usertype = type.usertype AND col.cdefault *= def.id
AND obj.type = 'U' AND obj.name = '#{table_name}' ORDER BY col.colid
SQLTEXT
@logger.debug "Get Column Info for table '#{table_name}'" if @logger
@connection.set_rowcount(0)
@connection.sql(sql)
raise "SQL Command for table_structure for #{table_name} failed\nMessage: #{@connection.context.message}" if @connection.context.failed?
return nil if @connection.cmd_fail?
@connection.top_row_result.rows.map do |row|
name, type, prec, scale, length, status, sysstat2, default = row
name.sub!(/_$/o, '')
type = normalize_type(type, prec, scale, length)
default_value = nil
if default =~ /DEFAULT\s+(.+)/o
default_value = $1.strip
default_value = default_value[1...-1] if default_value =~ /^['"]/o
end
nullable = (status & 8) == 8
identity = status >= 128
primary = (sysstat2 & 8) == 8
ColumnWithIdentity.new(name, default_value, type, nullable, identity, primary)
end
indexes
end
def quoted_true
@@ -314,7 +316,7 @@ module ActiveRecord
def add_limit_offset!(sql, options) # :nodoc:
@limit = options[:limit]
@offset = options[:offset]
if !normal_select?
if use_temp_table?
# Use temp table to hack offset with Sybase
sql.sub!(/ FROM /i, ' INTO #artemp FROM ')
elsif zero_limit?
@@ -330,6 +332,11 @@ module ActiveRecord
end
end
def add_lock!(sql, options) #:nodoc:
@logger.info "Warning: Sybase :lock option '#{options[:lock].inspect}' not supported" if @logger && options.has_key?(:lock)
sql
end
def supports_migrations? #:nodoc:
true
end
@@ -346,13 +353,14 @@ module ActiveRecord
begin
execute "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"
rescue StatementInvalid => e
# Swallow exception if no-op.
# Swallow exception and reset context if no-op.
raise e unless e.message =~ /no columns to drop, add or modify/
@connection.context.reset
end
if options[:default]
if options.has_key?(:default)
remove_default_constraint(table_name, column_name)
execute "ALTER TABLE #{table_name} REPLACE #{column_name} DEFAULT #{options[:default]}"
execute "ALTER TABLE #{table_name} REPLACE #{column_name} DEFAULT #{quote options[:default]}"
end
end
@@ -362,10 +370,10 @@ module ActiveRecord
end
def remove_default_constraint(table_name, column_name)
defaults = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
defaults.each {|constraint|
sql = "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
select(sql).each do |constraint|
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
}
end
end
def remove_index(table_name, options = {})
@@ -430,34 +438,44 @@ module ActiveRecord
end
end
def normal_select?
# If limit is not set at all, we can ignore offset;
# if limit *is* set but offset is zero, use normal select
# with simple SET ROWCOUNT. Thus, only use the temp table
# if limit is set and offset > 0.
has_limit = !@limit.nil?
has_offset = !@offset.nil? && @offset > 0
!has_limit || !has_offset
# If limit is not set at all, we can ignore offset;
# if limit *is* set but offset is zero, use normal select
# with simple SET ROWCOUNT. Thus, only use the temp table
# if limit is set and offset > 0.
def use_temp_table?
!@limit.nil? && !@offset.nil? && @offset > 0
end
def zero_limit?
!@limit.nil? && @limit == 0
end
# Select limit number of rows starting at optional offset.
def select(sql, name = nil)
@connection.context.reset
def raw_execute(sql, name = nil)
log(sql, name) do
if normal_select?
# If limit is not explicitly set, return all results.
@logger.debug "Setting row count to (#{@limit || 'off'})" if @logger
# Run a normal select
@connection.set_rowcount(@limit || 0)
@connection.context.reset
@logger.debug "Setting row count to (#{@limit})" if @logger && @limit
@connection.set_rowcount(@limit || 0)
if sql =~ /^\s*SELECT/i
@connection.sql(sql)
else
@connection.sql_norow(sql)
end
@limit = @offset = nil
if @connection.cmd_fail? or @connection.context.failed?
raise "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}"
end
end
end
# Select limit number of rows starting at optional offset.
def select(sql, name = nil)
if !use_temp_table?
execute(sql, name)
else
log(sql, name) do
# Select into a temp table and prune results
@logger.debug "Selecting #{@limit + (@offset || 0)} or fewer rows into #artemp" if @logger
@connection.context.reset
@connection.set_rowcount(@limit + (@offset || 0))
@connection.sql_norow(sql) # Select into temp table
@logger.debug "Deleting #{@offset || 0} or fewer rows from #artemp" if @logger
@@ -468,23 +486,21 @@ module ActiveRecord
end
end
raise StatementInvalid, "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}" if @connection.context.failed? or @connection.cmd_fail?
rows = []
if @connection.context.failed? or @connection.cmd_fail?
raise StatementInvalid, "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}"
else
results = @connection.top_row_result
if results && results.rows.length > 0
fields = results.columns.map { |column| column.sub(/_$/, '') }
results.rows.each do |row|
hashed_row = {}
row.zip(fields) { |cell, column| hashed_row[column] = cell }
rows << hashed_row
end
results = @connection.top_row_result
if results && results.rows.length > 0
fields = results.columns.map { |column| column.sub(/_$/, '') }
results.rows.each do |row|
hashed_row = {}
row.zip(fields) { |cell, column| hashed_row[column] = cell }
rows << hashed_row
end
end
@connection.sql_norow("drop table #artemp") if !normal_select?
@connection.sql_norow("drop table #artemp") if use_temp_table?
@limit = @offset = nil
return rows
rows
end
def get_table_name(sql)
@@ -502,79 +518,42 @@ module ActiveRecord
end
def get_identity_column(table_name)
@table_columns ||= {}
@table_columns[table_name] ||= columns(table_name)
@table_columns[table_name].each do |col|
return col.name if col.identity
@id_columns ||= {}
if !@id_columns.has_key?(table_name)
@logger.debug "Looking up identity column for table '#{table_name}'" if @logger
col = columns(table_name).detect { |col| col.identity }
@id_columns[table_name] = col.nil? ? nil : col.name
end
nil
@id_columns[table_name]
end
def query_contains_identity_column(sql, col)
sql =~ /\[#{col}\]/
end
def table_structure(table_name)
sql = <<SQLTEXT
SELECT col.name AS name, type.name AS type, col.prec, col.scale, col.length,
col.status, obj.sysstat2, def.text
FROM sysobjects obj, syscolumns col, systypes type, syscomments def
WHERE obj.id = col.id AND col.usertype = type.usertype AND col.cdefault *= def.id
AND obj.type = 'U' AND obj.name = '#{table_name}' ORDER BY col.colid
SQLTEXT
log(sql, "Get Column Info ") do
@connection.set_rowcount(0)
@connection.sql(sql)
end
if @connection.context.failed?
raise "SQL Command for table_structure for #{table_name} failed\nMessage: #{@connection.context.message}"
elsif !@connection.cmd_fail?
columns = []
results = @connection.top_row_result
results.rows.each do |row|
name, type, prec, scale, length, status, sysstat2, default = row
type = normalize_type(type, prec, scale, length)
default_value = nil
name.sub!(/_$/o, '')
if default =~ /DEFAULT\s+(.+)/o
default_value = $1.strip
default_value = default_value[1...-1] if default_value =~ /^['"]/o
end
nullable = (status & 8) == 8
identity = status >= 128
primary = (sysstat2 & 8) == 8
columns << [name, default_value, type, nullable, identity, primary]
end
columns
else
nil
end
end
# Resolve all user-defined types (udt) to their fundamental types.
def resolve_type(field_type)
(@udts ||= {})[field_type] ||= select_one("sp_help #{field_type}")["Storage_type"].strip
end
def normalize_type(field_type, prec, scale, length)
if field_type =~ /numeric/i and (scale.nil? or scale == 0)
type = 'int'
has_scale = (!scale.nil? && scale > 0)
type = if field_type =~ /numeric/i and !has_scale
'int'
elsif field_type =~ /money/i
type = 'numeric'
'numeric'
else
type = resolve_type(field_type.strip)
resolve_type(field_type.strip)
end
size = ''
if prec
size = "(#{prec})"
elsif length && !(type =~ /date|time/)
size = "(#{length})"
end
return type + size
end
def default_value(value)
spec = if prec
has_scale ? "(#{prec},#{scale})" : "(#{prec})"
elsif length && !(type =~ /date|time|text/)
"(#{length})"
else
''
end
"#{type}#{spec}"
end
end # class SybaseAdapter
@@ -666,10 +645,14 @@ class Fixtures
alias :original_insert_fixtures :insert_fixtures
def insert_fixtures
values.each do |fixture|
@connection.enable_identity_insert(table_name, true)
@connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
@connection.enable_identity_insert(table_name, false)
if @connection.instance_of?(ActiveRecord::ConnectionAdapters::SybaseAdapter)
values.each do |fixture|
@connection.enable_identity_insert(table_name, true)
@connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
@connection.enable_identity_insert(table_name, false)
end
else
original_insert_fixtures
end
end
end

View File

@@ -55,7 +55,7 @@ module ActiveRecord
# will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction
# depend on or you can raise exceptions in the callbacks to rollback.
#
# == Object-level transactions
# == Object-level transactions (deprecated)
#
# You can enable object-level transactions for Active Record objects, though. You do this by naming each of the Active Records
# that you want to enable object-level transactions for, like this:
@@ -65,8 +65,14 @@ module ActiveRecord
# mary.deposit(100)
# end
#
# If the transaction fails, David and Mary will be returned to their pre-transactional state. No money will have changed hands in
# neither object nor database.
# If the transaction fails, David and Mary will be returned to their
# pre-transactional state. No money will have changed hands in neither
# object nor database.
#
# However, useful state such as validation errors are also rolled back,
# limiting the usefulness of this feature. As such it is deprecated in
# Rails 1.2 and will be removed in the next release. Install the
# object_transactions plugin if you wish to continue using it.
#
# == Exception handling
#
@@ -80,8 +86,11 @@ module ActiveRecord
increment_open_transactions
begin
objects.each { |o| o.extend(Transaction::Simple) }
objects.each { |o| o.start_transaction }
unless objects.empty?
ActiveSupport::Deprecation.warn "Object transactions are deprecated and will be removed from Rails 2.0. See http://www.rubyonrails.org/deprecation for details.", caller
objects.each { |o| o.extend(Transaction::Simple) }
objects.each { |o| o.start_transaction }
end
result = connection.transaction(Thread.current['start_db_transaction'], &block)

View File

@@ -559,9 +559,11 @@ module ActiveRecord
# provided.
#
# class Person < ActiveRecord::Base
# validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :on => :create
# validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
# end
#
# Note: use \A and \Z to match the start and end of the string, ^ and $ match the start/end of a line.
#
# A regular expression must be provided or else an exception will be raised.
#
# Configuration options:
@@ -675,7 +677,7 @@ module ActiveRecord
# Validates whether the value of the specified attribute is numeric by trying to convert it to
# a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
# <tt>/^[\+\-]?\d+$/</tt> (if <tt>integer</tt> is set to true).
# <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>integer</tt> is set to true).
#
# class Person < ActiveRecord::Base
# validates_numericality_of :value, :on => :create
@@ -696,7 +698,7 @@ module ActiveRecord
if configuration[:only_integer]
validates_each(attr_names,configuration) do |record, attr_name,value|
record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_before_type_cast").to_s =~ /^[+-]?\d+$/
record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_before_type_cast").to_s =~ /\A[+-]?\d+\Z/
end
else
validates_each(attr_names,configuration) do |record, attr_name,value|

View File

@@ -121,10 +121,11 @@ class EagerAssociationTest < Test::Unit::TestCase
author = Author.find(:first, :include => :special_nonexistant_post_comments, :order => 'authors.id')
assert_equal [], author.special_nonexistant_post_comments
end
def test_eager_with_has_many_through_join_model_with_conditions
assert_equal Author.find(:first, :include => :hello_post_comments).hello_post_comments,
Author.find(:first).hello_post_comments
assert_equal Author.find(:first, :include => :hello_post_comments,
:order => 'authors.id').hello_post_comments.sort_by(&:id),
Author.find(:first, :order => 'authors.id').hello_post_comments.sort_by(&:id)
end
def test_eager_with_has_many_and_limit
@@ -373,7 +374,7 @@ class EagerAssociationTest < Test::Unit::TestCase
end
def test_count_with_include
if current_adapter?(:SQLServerAdapter)
if current_adapter?(:SQLServerAdapter, :SybaseAdapter)
assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15")
else
assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15")

View File

@@ -184,11 +184,13 @@ class HasOneAssociationsTest < Test::Unit::TestCase
def test_dependence
num_accounts = Account.count
firm = Firm.find(1)
assert !firm.account.nil?
account_id = firm.account.id
assert_equal [], Account.destroyed_account_ids[firm.id]
firm.destroy
firm.destroy
assert_equal num_accounts - 1, Account.count
assert_equal [account_id], Account.destroyed_account_ids[firm.id]
end
@@ -201,15 +203,23 @@ class HasOneAssociationsTest < Test::Unit::TestCase
def test_exclusive_dependence
num_accounts = Account.count
firm = ExclusivelyDependentFirm.find(9)
assert !firm.account.nil?
account_id = firm.account.id
assert_equal [], Account.destroyed_account_ids[firm.id]
firm.destroy
assert_equal num_accounts - 1, Account.count
assert_equal [], Account.destroyed_account_ids[firm.id]
end
def test_dependence_with_nil_associate
firm = DependentFirm.new(:name => 'nullify')
firm.save!
assert_nothing_raised { firm.destroy }
end
def test_succesful_build_association
firm = Firm.new("name" => "GlobalMegaCorp")
firm.save
@@ -1782,7 +1792,7 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
assert_equal projects(:active_record), developer.projects[0]
assert_equal projects(:action_controller), developer.projects[1]
end
def test_select_limited_ids_list
# Set timestamps
Developer.transaction do
@@ -1790,10 +1800,10 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
record.update_attributes(:created_at => 5.years.ago + (i * 5.minutes))
end
end
join_base = ActiveRecord::Associations::ClassMethods::JoinDependency::JoinBase.new(Project)
join_dep = ActiveRecord::Associations::ClassMethods::JoinDependency.new(join_base, :developers, nil)
projects = Project.send(:select_limited_ids_list, {:order => 'developers.created_at'}, join_dep)
assert_equal "'1', '2'", projects
assert_equal %w(1 2), projects.scan(/\d/).sort
end
end

View File

@@ -10,6 +10,7 @@ require 'fixtures/auto_id'
require 'fixtures/column_name'
require 'fixtures/subscriber'
require 'fixtures/keyboard'
require 'fixtures/post'
class Category < ActiveRecord::Base; end
class Smarts < ActiveRecord::Base; end
@@ -28,8 +29,9 @@ class NonExistentTable < ActiveRecord::Base; end
class TestOracleDefault < ActiveRecord::Base; end
class LoosePerson < ActiveRecord::Base
attr_protected :credit_rating, :administrator
self.table_name = 'people'
self.abstract_class = true
attr_protected :credit_rating, :administrator
end
class LooseDescendant < LoosePerson
@@ -37,6 +39,7 @@ class LooseDescendant < LoosePerson
end
class TightPerson < ActiveRecord::Base
self.table_name = 'people'
attr_accessible :name, :address
end
@@ -571,8 +574,8 @@ class BasicsTest < Test::Unit::TestCase
end
end
# Oracle and SQLServer do not have a TIME datatype.
unless current_adapter?(:SQLServerAdapter, :OracleAdapter)
# Oracle, SQLServer, and Sybase do not have a TIME datatype.
unless current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter)
def test_utc_as_time_zone
Topic.default_timezone = :utc
attributes = { "bonus_time" => "5:42:00AM" }
@@ -801,8 +804,8 @@ class BasicsTest < Test::Unit::TestCase
end
def test_attributes_on_dummy_time
# Oracle and SQL Server do not have a TIME datatype.
return true if current_adapter?(:SQLServerAdapter, :OracleAdapter)
# Oracle, SQL Server, and Sybase do not have a TIME datatype.
return true if current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter)
attributes = {
"bonus_time" => "5:42:00AM"
@@ -1024,7 +1027,7 @@ class BasicsTest < Test::Unit::TestCase
end
def test_sql_injection_via_find
assert_raises(ActiveRecord::RecordNotFound) do
assert_raises(ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid) do
Topic.find("123456 OR id > 0")
end
end
@@ -1075,6 +1078,18 @@ class BasicsTest < Test::Unit::TestCase
assert_equal author_name, Topic.find(topic.id).author_name
end
def test_quote_chars
str = 'The Narrator'
topic = Topic.create(:author_name => str)
assert_equal str, topic.author_name
assert_kind_of ActiveSupport::Multibyte::Chars, str.chars
topic = Topic.find_by_author_name(str.chars)
assert_kind_of Topic, topic
assert_equal str, topic.author_name, "The right topic should have been found by name even with name passed as Chars"
end
def test_class_level_destroy
should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
Topic.find(1).replies << should_be_destroyed_reply
@@ -1310,13 +1325,73 @@ class BasicsTest < Test::Unit::TestCase
assert_equal 2, topics.first.id
end
def test_base_class
def test_scoped_find_order_including_has_many_association
developers = Developer.with_scope(:find => { :order => 'developers.salary DESC', :include => :projects }) do
Developer.find(:all)
end
assert developers.size >= 2
for i in 1...developers.size
assert developers[i-1].salary >= developers[i].salary
end
end
def test_abstract_class
assert !ActiveRecord::Base.abstract_class?
assert LoosePerson.abstract_class?
assert !LooseDescendant.abstract_class?
end
def test_base_class
assert_equal LoosePerson, LoosePerson.base_class
assert_equal LooseDescendant, LooseDescendant.base_class
assert_equal TightPerson, TightPerson.base_class
assert_equal TightPerson, TightDescendant.base_class
assert_equal Post, Post.base_class
assert_equal Post, SpecialPost.base_class
assert_equal Post, StiPost.base_class
assert_equal SubStiPost, SubStiPost.base_class
end
def test_descends_from_active_record
# Tries to call Object.abstract_class?
assert_raise(NoMethodError) do
ActiveRecord::Base.descends_from_active_record?
end
# Abstract subclass of AR::Base.
assert LoosePerson.descends_from_active_record?
# Concrete subclass of an abstract class.
assert LooseDescendant.descends_from_active_record?
# Concrete subclass of AR::Base.
assert TightPerson.descends_from_active_record?
# Concrete subclass of a concrete class but has no type column.
assert TightDescendant.descends_from_active_record?
# Concrete subclass of AR::Base.
assert Post.descends_from_active_record?
# Abstract subclass of a concrete class which has a type column.
# This is pathological, as you'll never have Sub < Abstract < Concrete.
assert !StiPost.descends_from_active_record?
# Concrete subclasses an abstract class which has a type column.
assert !SubStiPost.descends_from_active_record?
end
def test_find_on_abstract_base_class_doesnt_use_type_condition
old_class = LooseDescendant
Object.send :remove_const, :LooseDescendant
descendant = old_class.create!
assert_not_nil LoosePerson.find(descendant.id), "Should have found instance of LooseDescendant when finding abstract LoosePerson: #{descendant.inspect}"
ensure
unless Object.const_defined?(:LooseDescendant)
Object.const_set :LooseDescendant, old_class
end
end
def test_assert_queries
@@ -1476,7 +1551,7 @@ class BasicsTest < Test::Unit::TestCase
private
def assert_readers(model, exceptions)
expected_readers = Set.new(model.column_names - (model.serialized_attributes.keys + ['id']))
expected_readers = Set.new(model.column_names - ['id'])
expected_readers += expected_readers.map { |col| "#{col}?" }
expected_readers -= exceptions
assert_equal expected_readers, model.read_methods

View File

@@ -2,7 +2,9 @@ print "Using native MySQL\n"
require_dependency 'fixtures/course'
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
RAILS_DEFAULT_LOGGER = Logger.new('debug.log')
RAILS_DEFAULT_LOGGER.level = Logger::DEBUG
ActiveRecord::Base.logger = RAILS_DEFAULT_LOGGER
ActiveRecord::Base.configurations = {
'arunit' => {

View File

@@ -1,8 +1,24 @@
require 'abstract_unit'
require 'fixtures/default'
require 'fixtures/entrant'
if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter)
class DefaultsTest < Test::Unit::TestCase
class DefaultTest < Test::Unit::TestCase
def test_nil_defaults_for_not_null_columns
column_defaults =
if current_adapter?(:MysqlAdapter)
{ 'id' => nil, 'name' => '', 'course_id' => nil }
else
{ 'id' => nil, 'name' => nil, 'course_id' => nil }
end
column_defaults.each do |name, default|
column = Entrant.columns_hash[name]
assert !column.null, "#{name} column should be NOT NULL"
assert_equal default, column.default, "#{name} column should be DEFAULT #{default.inspect}"
end
end
if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter, :FirebirdAdapter, :OpenBaseAdapter)
def test_default_integers
default = Default.new
assert_instance_of Fixnum, default.positive_integer

View File

@@ -161,10 +161,22 @@ class FinderTest < Test::Unit::TestCase
Company.find(:first, :conditions => { :id => 2, :dhh => true })
}
end
def test_hash_condition_find_with_escaped_characters
Company.create("name" => "Ain't noth'n like' \#stuff")
assert Company.find(:first, :conditions => { :name => "Ain't noth'n like' \#stuff"})
assert Company.find(:first, :conditions => { :name => "Ain't noth'n like' \#stuff" })
end
def test_hash_condition_find_with_array
p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc')
assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2] }, :order => 'id asc')
assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2.id] }, :order => 'id asc')
end
def test_hash_condition_find_with_nil
topic = Topic.find(:first, :conditions => { :last_read => nil } )
assert_not_nil topic
assert_nil topic.last_read
end
def test_bind_variables
@@ -281,6 +293,13 @@ class FinderTest < Test::Unit::TestCase
def test_find_by_one_missing_attribute
assert_raises(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
end
def test_find_by_invalid_method_syntax
assert_raises(NoMethodError) { Topic.fail_to_find_by_title("The First Topic") }
assert_raises(NoMethodError) { Topic.find_by_title?("The First Topic") }
assert_raises(NoMethodError) { Topic.fail_to_find_or_create_by_title("Nonexistent Title") }
assert_raises(NoMethodError) { Topic.find_or_create_by_title?("Nonexistent Title") }
end
def test_find_by_two_attributes
assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")

View File

@@ -37,7 +37,7 @@ CREATE TABLE `topics` (
`parent_id` int(11) default NULL,
`type` varchar(50) default NULL,
PRIMARY KEY (`id`)
) TYPE=InnoDB;
) TYPE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `developers` (
`id` int(11) NOT NULL auto_increment,
@@ -159,14 +159,14 @@ CREATE TABLE `posts` (
`author_id` INTEGER,
`title` VARCHAR(255) NOT NULL,
`body` TEXT NOT NULL,
`type` VARCHAR(255) NOT NULL
`type` VARCHAR(255) default NULL
) TYPE=InnoDB;
CREATE TABLE `comments` (
`id` INTEGER NOT NULL auto_increment PRIMARY KEY,
`post_id` INTEGER NOT NULL,
`body` TEXT NOT NULL,
`type` VARCHAR(255) NOT NULL
`type` VARCHAR(255) default NULL
) TYPE=InnoDB;
CREATE TABLE `authors` (
@@ -184,7 +184,7 @@ CREATE TABLE `tasks` (
CREATE TABLE `categories` (
`id` int(11) NOT NULL auto_increment,
`name` VARCHAR(255) NOT NULL,
`type` VARCHAR(255) NOT NULL,
`type` VARCHAR(255) default NULL,
PRIMARY KEY (`id`)
) TYPE=InnoDB;

View File

@@ -130,8 +130,8 @@ CREATE TABLE auto_id_tests (
CREATE TABLE entrants (
id serial,
name text,
course_id integer
name text not null,
course_id integer not null
);
CREATE TABLE colnametests (

View File

@@ -54,7 +54,7 @@ ActiveRecord::Schema.define do
t.column :lock_version, :integer
end
create_table :lock_with_custom_column_without_defaults, :force => true do |t|
create_table :lock_without_defaults_cust, :force => true do |t|
t.column :custom_lock_version, :integer
end
end

View File

@@ -144,14 +144,14 @@ CREATE TABLE 'posts' (
'id' INTEGER NOT NULL PRIMARY KEY,
'author_id' INTEGER,
'title' VARCHAR(255) NOT NULL,
'type' VARCHAR(255) NOT NULL,
'type' VARCHAR(255) DEFAULT NULL,
'body' TEXT NOT NULL
);
CREATE TABLE 'comments' (
'id' INTEGER NOT NULL PRIMARY KEY,
'post_id' INTEGER NOT NULL,
'type' VARCHAR(255) NOT NULL,
'type' VARCHAR(255) DEFAULT NULL,
'body' TEXT NOT NULL
);

View File

@@ -29,4 +29,5 @@ DROP TABLE fk_test_has_pk
DROP TABLE keyboards
DROP TABLE legacy_things
DROP TABLE numeric_data
DROP TABLE schema_info
go

View File

@@ -197,12 +197,12 @@ CREATE TABLE keyboards (
CREATE TABLE legacy_things (
id numeric(9,0) IDENTITY PRIMARY KEY,
tps_report_number int default NULL,
version int default 0,
version int default 0
)
CREATE TABLE numeric_data (
id numeric((9,0) IDENTITY PRIMARY KEY,
id numeric(9,0) IDENTITY PRIMARY KEY,
bank_balance numeric(10,2),
big_bank_balance numeric(15,2),
world_population numeric(10),

View File

@@ -46,7 +46,7 @@ class Post < ActiveRecord::Base
end
end
class SpecialPost < Post; end;
class SpecialPost < Post; end
class StiPost < Post
self.abstract_class = true
@@ -54,4 +54,5 @@ class StiPost < Post
end
class SubStiPost < StiPost
self.table_name = Post.table_name
end

View File

@@ -4,7 +4,7 @@ require 'fixtures/project'
require 'fixtures/subscriber'
class InheritanceTest < Test::Unit::TestCase
fixtures :companies, :projects, :subscribers
fixtures :companies, :projects, :subscribers, :accounts
def test_a_bad_type_column
#SQLServer need to turn Identity Insert On before manually inserting into the Identity column
@@ -30,6 +30,7 @@ class InheritanceTest < Test::Unit::TestCase
def test_alt_inheritance_find
switch_to_alt_inheritance_column
test_inheritance_find
switch_to_default_inheritance_column
end
def test_inheritance_find_all
@@ -41,6 +42,7 @@ class InheritanceTest < Test::Unit::TestCase
def test_alt_inheritance_find_all
switch_to_alt_inheritance_column
test_inheritance_find_all
switch_to_default_inheritance_column
end
def test_inheritance_save
@@ -55,6 +57,7 @@ class InheritanceTest < Test::Unit::TestCase
def test_alt_inheritance_save
switch_to_alt_inheritance_column
test_inheritance_save
switch_to_default_inheritance_column
end
def test_inheritance_condition
@@ -66,6 +69,7 @@ class InheritanceTest < Test::Unit::TestCase
def test_alt_inheritance_condition
switch_to_alt_inheritance_column
test_inheritance_condition
switch_to_default_inheritance_column
end
def test_finding_incorrect_type_data
@@ -76,6 +80,7 @@ class InheritanceTest < Test::Unit::TestCase
def test_alt_finding_incorrect_type_data
switch_to_alt_inheritance_column
test_finding_incorrect_type_data
switch_to_default_inheritance_column
end
def test_update_all_within_inheritance
@@ -87,6 +92,7 @@ class InheritanceTest < Test::Unit::TestCase
def test_alt_update_all_within_inheritance
switch_to_alt_inheritance_column
test_update_all_within_inheritance
switch_to_default_inheritance_column
end
def test_destroy_all_within_inheritance
@@ -98,6 +104,7 @@ class InheritanceTest < Test::Unit::TestCase
def test_alt_destroy_all_within_inheritance
switch_to_alt_inheritance_column
test_destroy_all_within_inheritance
switch_to_default_inheritance_column
end
def test_find_first_within_inheritance
@@ -109,6 +116,7 @@ class InheritanceTest < Test::Unit::TestCase
def test_alt_find_first_within_inheritance
switch_to_alt_inheritance_column
test_find_first_within_inheritance
switch_to_default_inheritance_column
end
def test_complex_inheritance
@@ -124,6 +132,19 @@ class InheritanceTest < Test::Unit::TestCase
def test_alt_complex_inheritance
switch_to_alt_inheritance_column
test_complex_inheritance
switch_to_default_inheritance_column
end
def test_eager_load_belongs_to_something_inherited
account = Account.find(1, :include => :firm)
assert_not_nil account.instance_variable_get("@firm"), "nil proves eager load failed"
end
def test_alt_eager_loading
switch_to_alt_inheritance_column
test_eager_load_belongs_to_something_inherited
switch_to_default_inheritance_column
ActiveRecord::Base.logger.debug "cocksucker"
end
def test_inheritance_without_mapping
@@ -138,7 +159,47 @@ class InheritanceTest < Test::Unit::TestCase
c['type'] = nil
c.save
end
def Company.inheritance_column() "ruby_type" end
[ Company, Firm, Client].each { |klass| klass.reset_column_information }
Company.set_inheritance_column('ruby_type')
end
def switch_to_default_inheritance_column
[ Company, Firm, Client].each { |klass| klass.reset_column_information }
Company.set_inheritance_column('type')
end
end
class InheritanceComputeTypeTest < Test::Unit::TestCase
fixtures :companies
def setup
Dependencies.log_activity = true
end
def teardown
Dependencies.log_activity = false
self.class.const_remove :FirmOnTheFly rescue nil
Firm.const_remove :FirmOnTheFly rescue nil
end
def test_instantiation_doesnt_try_to_require_corresponding_file
foo = Firm.find(:first).clone
foo.ruby_type = foo.type = 'FirmOnTheFly'
foo.save!
# Should fail without FirmOnTheFly in the type condition.
assert_raise(ActiveRecord::RecordNotFound) { Firm.find(foo.id) }
# Nest FirmOnTheFly in the test case where Dependencies won't see it.
self.class.const_set :FirmOnTheFly, Class.new(Firm)
assert_raise(ActiveRecord::SubclassNotFound) { Firm.find(foo.id) }
# Nest FirmOnTheFly in Firm where Dependencies will see it.
# This is analogous to nesting models in a migration.
Firm.const_set :FirmOnTheFly, Class.new(Firm)
# And instantiate will find the existing constant rather than trying
# to require firm_on_the_fly.
assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find(foo.id) }
end
end

View File

@@ -5,6 +5,7 @@ require 'fixtures/legacy_thing'
class LockWithoutDefault < ActiveRecord::Base; end
class LockWithCustomColumnWithoutDefault < ActiveRecord::Base
set_table_name :lock_without_defaults_cust
set_locking_column :custom_lock_version
end
@@ -62,12 +63,12 @@ class OptimisticLockingTest < Test::Unit::TestCase
assert_equal 1, p1.lock_version
assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
end
def test_lock_without_default_sets_version_to_zero
t1 = LockWithoutDefault.new
assert_equal 0, t1.lock_version
end
def test_lock_with_custom_column_without_default_sets_version_to_zero
t1 = LockWithCustomColumnWithoutDefault.new
assert_equal 0, t1.custom_lock_version
@@ -80,23 +81,26 @@ end
# blocks, so separate script called by Kernel#system is needed.
# (See exec vs. async_exec in the PostgreSQL adapter.)
# TODO: The SQL Server adapter currently has no support for pessimistic locking
# TODO: The SQL Server and Sybase adapters currently have no support for pessimistic locking
unless current_adapter?(:SQLServerAdapter)
unless current_adapter?(:SQLServerAdapter, :SybaseAdapter)
class PessimisticLockingTest < Test::Unit::TestCase
self.use_transactional_fixtures = false
fixtures :people
fixtures :people, :readers
def setup
# Avoid introspection queries during tests.
Person.columns; Reader.columns
@allow_concurrency = ActiveRecord::Base.allow_concurrency
ActiveRecord::Base.allow_concurrency = true
end
def teardown
ActiveRecord::Base.allow_concurrency = @allow_concurrency
end
# Test that the adapter doesn't blow up on add_lock!
# Test typical find.
def test_sane_find_with_lock
assert_nothing_raised do
Person.transaction do
@@ -104,9 +108,9 @@ unless current_adapter?(:SQLServerAdapter)
end
end
end
# Test no-blowup for scoped lock.
def test_sane_find_with_lock
# Test scoped lock.
def test_sane_find_with_scoped_lock
assert_nothing_raised do
Person.transaction do
Person.with_scope(:find => { :lock => true }) do
@@ -115,7 +119,19 @@ unless current_adapter?(:SQLServerAdapter)
end
end
end
# PostgreSQL protests SELECT ... FOR UPDATE on an outer join.
unless current_adapter?(:PostgreSQLAdapter)
# Test locked eager find.
def test_eager_find_with_lock
assert_nothing_raised do
Person.transaction do
Person.find 1, :include => :readers, :lock => true
end
end
end
end
# Locking a record reloads it.
def test_sane_lock_method
assert_nothing_raised do
@@ -127,24 +143,24 @@ unless current_adapter?(:SQLServerAdapter)
end
end
end
if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
def test_no_locks_no_wait
first, second = duel { Person.find 1 }
assert first.end > second.end
end
def test_second_lock_waits
assert [0.2, 1, 5].any? { |zzz|
first, second = duel(zzz) { Person.find 1, :lock => true }
second.end > first.end
}
end
protected
def duel(zzz = 5)
t0, t1, t2, t3 = nil, nil, nil, nil
a = Thread.new do
t0 = Time.now
Person.transaction do
@@ -153,17 +169,17 @@ unless current_adapter?(:SQLServerAdapter)
end
t1 = Time.now
end
b = Thread.new do
sleep zzz / 2.0 # ensure thread 1 tx starts first
t2 = Time.now
Person.transaction { yield }
t3 = Time.now
end
a.join
b.join
assert t1 > t0 + zzz
assert t2 > t0
assert t3 > t2

View File

@@ -58,8 +58,8 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_nothing_raised { Person.connection.add_index("people", "last_name") }
assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
# Orcl nds shrt indx nms.
unless current_adapter?(:OracleAdapter)
# Orcl nds shrt indx nms. Sybs 2.
unless current_adapter?(:OracleAdapter, :SybaseAdapter)
assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
assert_nothing_raised { Person.connection.remove_index("people", :column => ["last_name", "first_name"]) }
assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
@@ -189,7 +189,9 @@ if ActiveRecord::Base.connection.supports_migrations?
end
con = Person.connection
Person.connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter)
Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')"
Person.connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter)
assert_nothing_raised {Person.connection.add_column :testings, :bar, :string, :null => false, :default => "default" }
assert_raises(ActiveRecord::StatementInvalid) do
@@ -368,7 +370,9 @@ if ActiveRecord::Base.connection.supports_migrations?
# Using explicit id in insert for compatibility across all databases
con = ActiveRecord::Base.connection
con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
assert_nothing_raised { con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" }
con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1")
@@ -389,7 +393,9 @@ if ActiveRecord::Base.connection.supports_migrations?
# Using explicit id in insert for compatibility across all databases
con = ActiveRecord::Base.connection
con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
assert_nothing_raised { con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" }
con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1")
assert ActiveRecord::Base.connection.indexes(:octopi).first.columns.include?("url")

View File

@@ -91,7 +91,7 @@ class FirebirdMigrationTest < Test::Unit::TestCase
assert_nothing_raised { @connection.rename_table :foo, :bar }
assert !@connection.tables.include?("foo")
assert @connection.tables.include?("bar")
assert_equal "bar_baz_index", @connection.indexes("bar").first.name
assert_equal "index_bar_on_baz", @connection.indexes("bar").first.name
assert_equal 100, FireRuby::Generator.new("bar_seq", @fireruby_connection).last
assert_equal 100, @connection.select_one("SELECT COUNT(*) FROM bar")["count"]
ensure

View File

@@ -46,7 +46,7 @@ if ActiveRecord::Base.connection.respond_to?(:tables)
def test_schema_dump_includes_not_null_columns
stream = StringIO.new
ActiveRecord::SchemaDumper.ignore_tables = [/^[^s]/]
ActiveRecord::SchemaDumper.ignore_tables = [/^[^r]/]
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
output = stream.string
assert_match %r{:null => false}, output

View File

@@ -90,12 +90,14 @@ class TransactionTest < Test::Unit::TestCase
assert !@first.approved?, "First should be unapproved initially"
begin
Topic.transaction(@first, @second) do
@first.approved = true
@second.approved = false
@first.save
@second.save
raise "Bad things!"
assert_deprecated /Object transactions/ do
Topic.transaction(@first, @second) do
@first.approved = true
@second.approved = false
@first.save
@second.save
raise "Bad things!"
end
end
rescue
# caught it

View File

@@ -1028,15 +1028,16 @@ class ValidationsTest < Test::Unit::TestCase
end
class ValidatesNumericalityTest
NIL = [nil, "", " ", " \t \r \n"]
class ValidatesNumericalityTest < Test::Unit::TestCase
NIL = [nil]
BLANK = ["", " ", " \t \r \n"]
BIGDECIMAL_STRINGS = %w(12345678901234567890.1234567890) # 30 significent digits
FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1 90.1e1 -90.1e5 -90.1e-5 90e-5)
INTEGER_STRINGS = %w(0 +0 -0 10 +10 -10 0090 -090)
FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001] + FLOAT_STRINGS
INTEGERS = [0, 10, -10] + INTEGER_STRINGS
BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) }
JUNK = ["not a number", "42 not a number", "0xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12"]
JUNK = ["not a number", "42 not a number", "0xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"]
def setup
Topic.write_inheritable_attribute(:validate, nil)
@@ -1047,44 +1048,50 @@ class ValidatesNumericalityTest
def test_default_validates_numericality_of
Topic.validates_numericality_of :approved
invalid!(NIL + JUNK)
invalid!(NIL + BLANK + JUNK)
valid!(FLOATS + INTEGERS + BIGDECIMAL)
end
def test_validates_numericality_of_with_nil_allowed
Topic.validates_numericality_of :approved, :allow_nil => true
invalid!(JUNK)
invalid!(BLANK + JUNK)
valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL)
end
def test_validates_numericality_of_with_integer_only
Topic.validates_numericality_of :approved, :only_integer => true
invalid!(NIL + JUNK + FLOATS + BIGDECIMAL)
invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL)
valid!(INTEGERS)
end
def test_validates_numericality_of_with_integer_only_and_nil_allowed
Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true
invalid!(JUNK + FLOATS + BIGDECIMAL)
invalid!(BLANK + JUNK + FLOATS + BIGDECIMAL)
valid!(NIL + INTEGERS)
end
private
def invalid!(values)
values.each do |value|
topic = Topic.create("title" => "numeric test", "content" => "whatever", "approved" => value)
assert !topic.valid?, "#{value} not rejected as a number"
with_each_topic_approved_value(values) do |topic, value|
assert !topic.valid?, "#{value.inspect} not rejected as a number"
assert topic.errors.on(:approved)
end
end
def valid!(values)
with_each_topic_approved_value(values) do |topic, value|
assert topic.valid?, "#{value.inspect} not accepted as a number"
end
end
def with_each_topic_approved_value(values)
topic = Topic.new("title" => "numeric test", "content" => "whatever")
values.each do |value|
topic = Topic.create("title" => "numeric test", "content" => "whatever", "approved" => value)
assert topic.valid?, "#{value} not accepted as a number"
topic.approved = value
yield topic, value
end
end
end

View File

@@ -1,67 +0,0 @@
*SVN*
* Add basic logging support for logging outgoing requests. [Jamis Buck]
* Add Base.delete for deleting resources without having to instantiate them first. [Jamis Buck]
* Make #save behavior mimic AR::Base#save (true on success, false on failure). [Jamis Buck]
* Add Basic HTTP Authentication to ActiveResource (closes #6305). [jonathan]
* Extracted #id_from_response as an entry point for customizing how a created resource gets its own ID.
By default, it extracts from the Location response header.
* Optimistic locking: raise ActiveResource::ResourceConflict on 409 Conflict response. [Jeremy Kemper]
# Example controller action
def update
@person.save!
rescue ActiveRecord::StaleObjectError
render :xml => @person.reload.to_xml, :status => '409 Conflict'
end
* Basic validation support [Rick Olson]
Parses the xml response of ActiveRecord::Errors#to_xml with a similar interface to ActiveRecord::Errors.
render :xml => @person.errors.to_xml, :status => '400 Validation Error'
* Deep hashes are converted into collections of resources. [Jeremy Kemper]
Person.new :name => 'Bob',
:address => { :id => 1, :city => 'Portland' },
:contacts => [{ :id => 1 }, { :id => 2 }]
Looks for Address and Contact resources and creates them if unavailable.
So clients can fetch a complex resource in a single request if you e.g.
render :xml => @person.to_xml(:include => [:address, :contacts])
in your controller action.
* Major updates [Rick Olson]
* Add full support for find/create/update/destroy
* Add support for specifying prefixes.
* Allow overriding of element_name, collection_name, and primary key
* Provide simpler HTTP mock interface for testing
# rails routing code
map.resources :posts do |post|
post.resources :comments
end
# ActiveResources
class Post < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000/"
end
class Comment < ActiveResource::Base
self.site = "http://37s.sunrise.i:3000/posts/:post_id/"
end
@post = Post.find 5
@comments = Comment.find :all, :post_id => @post.id
@comment = Comment.new({:body => 'hello world'}, {:post_id => @post.id})
@comment.save
* Base.site= accepts URIs. 200...400 are valid response codes. PUT and POST request bodies default to ''. [Jeremy Kemper]
* Initial checkin: object-oriented client for restful HTTP resources which follow the Rails convention. [DHH]

View File

@@ -1,20 +0,0 @@
Copyright (c) 2006 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1 +0,0 @@
= Active Resource -- Object-oriented REST services

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