mirror of
https://github.com/github/rails.git
synced 2026-01-13 00:28:26 -05:00
Compare commits
85 Commits
github28
...
v1.2.0_RC1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12949bbc13 | ||
|
|
82e5ff7c6e | ||
|
|
783c16bd61 | ||
|
|
34a3d04af1 | ||
|
|
766ca17c91 | ||
|
|
ffa2c5fda5 | ||
|
|
87ecb782e6 | ||
|
|
2043513f4c | ||
|
|
ef6c3c4289 | ||
|
|
af43e87f38 | ||
|
|
f2fd22c2d4 | ||
|
|
17921bafcb | ||
|
|
36b3cb2c36 | ||
|
|
b93e4692b2 | ||
|
|
30e5436a3e | ||
|
|
9ba96771c3 | ||
|
|
97d9dca26f | ||
|
|
2a70f6f0a2 | ||
|
|
1e532f6606 | ||
|
|
fb60a469c0 | ||
|
|
ac2295137c | ||
|
|
acfe119cb5 | ||
|
|
004a28de81 | ||
|
|
0497868531 | ||
|
|
23569d83b2 | ||
|
|
bef626e2b1 | ||
|
|
b7f094e65e | ||
|
|
6baf9489d1 | ||
|
|
af33784698 | ||
|
|
a5631a5674 | ||
|
|
629b2ac36c | ||
|
|
dd1dc5e819 | ||
|
|
b8ff216f49 | ||
|
|
924f5cdb61 | ||
|
|
86bd0da7d0 | ||
|
|
35240ba66e | ||
|
|
b8a5d398b9 | ||
|
|
85c45051be | ||
|
|
f7c94e977b | ||
|
|
69ada0e26e | ||
|
|
9eaad14e8e | ||
|
|
9f8c805f5d | ||
|
|
46b845e784 | ||
|
|
118764e47d | ||
|
|
bcd50f1a37 | ||
|
|
5aced86de6 | ||
|
|
792780a80c | ||
|
|
5e677b67b5 | ||
|
|
dfa070aab9 | ||
|
|
ba086a4fc0 | ||
|
|
cc299d1b94 | ||
|
|
cbffafb497 | ||
|
|
84668af330 | ||
|
|
013004b592 | ||
|
|
5f5d8c224b | ||
|
|
5887b183ce | ||
|
|
320875ad77 | ||
|
|
4aa358dd86 | ||
|
|
6439d0cd3e | ||
|
|
7953eb170d | ||
|
|
5350fda95d | ||
|
|
5fac326020 | ||
|
|
17e445813b | ||
|
|
a2edd4381e | ||
|
|
2029b8a8f5 | ||
|
|
53c49036ea | ||
|
|
855e0f6f09 | ||
|
|
e4e1d2afa3 | ||
|
|
526afd3ae2 | ||
|
|
31b901aa65 | ||
|
|
a746e39584 | ||
|
|
f4d88039fd | ||
|
|
9f26164d37 | ||
|
|
c7f28e01c8 | ||
|
|
3311953d3f | ||
|
|
ce0653b1c6 | ||
|
|
229e197ac7 | ||
|
|
503c7c0afd | ||
|
|
8e01efee02 | ||
|
|
e09f224a5c | ||
|
|
6eb941ee6c | ||
|
|
7e8dd0322c | ||
|
|
159411f580 | ||
|
|
746cfb3ded | ||
|
|
2227a178ee |
@@ -1,4 +1,4 @@
|
||||
*SVN*
|
||||
*1.3.0 RC1* (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)
|
||||
|
||||
|
||||
@@ -1,4 +1,39 @@
|
||||
*SVN*
|
||||
*1.13.0 RC1* (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 +70,6 @@
|
||||
|
||||
* Fix double-escaped entities, such as &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 +80,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 +135,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 +160,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 +184,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 +196,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 +217,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]
|
||||
@@ -223,20 +235,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 +249,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 +259,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 +273,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 +281,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 +308,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 +346,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 +409,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 +417,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 +439,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 +446,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 +2717,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]
|
||||
|
||||
|
||||
@@ -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,18 @@ 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)
|
||||
value['controller'] = ActionController::Routing.controller_relative_to(value['controller'], @controller.class.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 +98,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 +114,7 @@ module ActionController
|
||||
else
|
||||
expected == rendered
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -132,4 +136,4 @@ module ActionController
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -763,13 +763,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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -80,6 +80,8 @@ module ActionController #:nodoc:
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -996,7 +1000,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -93,6 +93,10 @@ begin
|
||||
end
|
||||
@session_data = {}
|
||||
end
|
||||
|
||||
def data
|
||||
@session_data
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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|
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']
|
||||
@@ -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
|
||||
|
||||
34
actionpack/lib/action_view/helpers/deprecated_helper.rb
Normal file
34
actionpack/lib/action_view/helpers/deprecated_helper.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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]; }
|
||||
);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -143,6 +143,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
@@ -1673,10 +1679,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 +1743,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
BIN
actionpack/test/fixtures/multipart/binary_file
vendored
BIN
actionpack/test/fixtures/multipart/binary_file
vendored
Binary file not shown.
36
actionpack/test/template/deprecated_helper_test.rb
Normal file
36
actionpack/test/template/deprecated_helper_test.rb
Normal 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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') }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,28 +1,37 @@
|
||||
*SVN*
|
||||
|
||||
* Removed deprecated @request and @response usages. [Kent Sibilev]
|
||||
*1.2.0 RC1* (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)
|
||||
|
||||
|
||||
@@ -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 %>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,10 +1,32 @@
|
||||
*SVN*
|
||||
*1.15.0 RC1* (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 +80,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 +94,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 +144,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 +198,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 +210,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 +309,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 +340,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 +350,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 +363,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]
|
||||
|
||||
|
||||
@@ -1060,7 +1060,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,8 +1169,8 @@ 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)
|
||||
|
||||
return sanitize_sql(sql)
|
||||
@@ -1190,25 +1190,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)
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
@@ -1057,7 +1058,7 @@ module ActiveRecord #:nodoc:
|
||||
|
||||
# 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 +1097,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 +1121,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -170,15 +170,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
|
||||
@@ -234,9 +225,6 @@ module ActiveRecord
|
||||
@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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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|
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1075,6 +1075,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,6 +1322,16 @@ class BasicsTest < Test::Unit::TestCase
|
||||
assert_equal 2, topics.first.id
|
||||
end
|
||||
|
||||
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_base_class
|
||||
assert LoosePerson.abstract_class?
|
||||
assert !LooseDescendant.abstract_class?
|
||||
@@ -1476,7 +1498,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,7 @@ 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_inheritance_without_mapping
|
||||
@@ -138,7 +147,10 @@ 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 }
|
||||
def Company.inheritance_column; @inheritance_column ||= "ruby_type"; end
|
||||
end
|
||||
def switch_to_default_inheritance_column
|
||||
[ Company, Firm, Client].each { |klass| klass.reset_column_information }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
@@ -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.
|
||||
@@ -1 +0,0 @@
|
||||
= Active Resource -- Object-oriented REST services
|
||||
@@ -1,138 +0,0 @@
|
||||
require 'rubygems'
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
require 'rake/packagetask'
|
||||
require 'rake/gempackagetask'
|
||||
require 'rake/contrib/rubyforgepublisher'
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'active_resource', 'version')
|
||||
|
||||
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
||||
PKG_NAME = 'activeresource'
|
||||
PKG_VERSION = ActiveResource::VERSION::STRING + PKG_BUILD
|
||||
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
||||
|
||||
RELEASE_NAME = "REL #{PKG_VERSION}"
|
||||
|
||||
RUBY_FORGE_PROJECT = "activerecord"
|
||||
RUBY_FORGE_USER = "webster132"
|
||||
|
||||
PKG_FILES = FileList[
|
||||
"lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "Rakefile"
|
||||
].exclude(/\bCVS\b|~$/)
|
||||
|
||||
desc "Default Task"
|
||||
task :default => [ :test ]
|
||||
|
||||
# Run the unit tests
|
||||
|
||||
Rake::TestTask.new { |t|
|
||||
t.libs << "test"
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
t.warning = true
|
||||
}
|
||||
|
||||
|
||||
# Generate the RDoc documentation
|
||||
|
||||
Rake::RDocTask.new { |rdoc|
|
||||
rdoc.rdoc_dir = 'doc'
|
||||
rdoc.title = "Active Resource -- Object-oriented REST services"
|
||||
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
||||
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
|
||||
rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
rdoc.rdoc_files.exclude('lib/active_record/vendor/*')
|
||||
rdoc.rdoc_files.include('dev-utils/*.rb')
|
||||
}
|
||||
|
||||
|
||||
# Create compressed packages
|
||||
|
||||
dist_dirs = [ "lib", "test", "examples", "dev-utils" ]
|
||||
|
||||
spec = Gem::Specification.new do |s|
|
||||
s.name = PKG_NAME
|
||||
s.version = PKG_VERSION
|
||||
s.summary = "Implements the ActiveRecord pattern for ORM."
|
||||
s.description = %q{Implements the ActiveRecord pattern (Fowler, PoEAA) for ORM. It ties database tables and classes together for business objects, like Customer or Subscription, that can find, save, and destroy themselves without resorting to manual SQL.}
|
||||
|
||||
s.files = [ "Rakefile", "install.rb", "README", "RUNNING_UNIT_TESTS", "CHANGELOG" ]
|
||||
dist_dirs.each do |dir|
|
||||
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
||||
end
|
||||
|
||||
s.add_dependency('activesupport', '= 1.3.1' + PKG_BUILD)
|
||||
|
||||
s.files.delete "test/fixtures/fixture_database.sqlite"
|
||||
s.files.delete "test/fixtures/fixture_database_2.sqlite"
|
||||
s.files.delete "test/fixtures/fixture_database.sqlite3"
|
||||
s.files.delete "test/fixtures/fixture_database_2.sqlite3"
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'active_record'
|
||||
|
||||
s.has_rdoc = true
|
||||
s.extra_rdoc_files = %w( README )
|
||||
s.rdoc_options.concat ['--main', 'README']
|
||||
|
||||
s.author = "David Heinemeier Hansson"
|
||||
s.email = "david@loudthinking.com"
|
||||
s.homepage = "http://www.rubyonrails.org"
|
||||
s.rubyforge_project = "activerecord"
|
||||
end
|
||||
|
||||
Rake::GemPackageTask.new(spec) do |p|
|
||||
p.gem_spec = spec
|
||||
p.need_tar = true
|
||||
p.need_zip = true
|
||||
end
|
||||
|
||||
task :lines do
|
||||
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
|
||||
|
||||
for file_name in FileList["lib/active_record/**/*.rb"]
|
||||
next if file_name =~ /vendor/
|
||||
f = File.open(file_name)
|
||||
|
||||
while line = f.gets
|
||||
lines += 1
|
||||
next if line =~ /^\s*$/
|
||||
next if line =~ /^\s*#/
|
||||
codelines += 1
|
||||
end
|
||||
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
|
||||
|
||||
total_lines += lines
|
||||
total_codelines += codelines
|
||||
|
||||
lines, codelines = 0, 0
|
||||
end
|
||||
|
||||
puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
|
||||
end
|
||||
|
||||
|
||||
# Publishing ------------------------------------------------------
|
||||
|
||||
desc "Publish the beta gem"
|
||||
task :pgem => [:package] do
|
||||
Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||
`ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
|
||||
end
|
||||
|
||||
desc "Publish the API documentation"
|
||||
task :pdoc => [:rdoc] do
|
||||
Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ar", "doc").upload
|
||||
end
|
||||
|
||||
desc "Publish the release files to RubyForge."
|
||||
task :release => [ :package ] do
|
||||
`rubyforge login`
|
||||
|
||||
for ext in %w( gem tgz zip )
|
||||
release_command = "rubyforge add_release #{PKG_NAME} #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}"
|
||||
puts release_command
|
||||
system(release_command)
|
||||
end
|
||||
end
|
||||
@@ -1,45 +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.
|
||||
#++
|
||||
|
||||
$:.unshift(File.dirname(__FILE__)) unless
|
||||
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
||||
|
||||
unless defined?(ActiveSupport)
|
||||
begin
|
||||
$:.unshift(File.dirname(__FILE__) + "/../../activesupport/lib")
|
||||
require 'active_support'
|
||||
rescue LoadError
|
||||
require 'rubygems'
|
||||
require_gem 'activesupport'
|
||||
end
|
||||
end
|
||||
|
||||
require 'active_resource/base'
|
||||
require 'active_resource/struct'
|
||||
require 'active_resource/validations'
|
||||
|
||||
module ActiveResource
|
||||
Base.class_eval do
|
||||
include Validations
|
||||
end
|
||||
end
|
||||
@@ -1,222 +0,0 @@
|
||||
require 'active_resource/connection'
|
||||
|
||||
module ActiveResource
|
||||
class Base
|
||||
# The logger for logging diagnostic and trace information during ARes
|
||||
# calls.
|
||||
cattr_accessor :logger
|
||||
|
||||
class << self
|
||||
attr_reader :site
|
||||
|
||||
def site=(site)
|
||||
@site = site.is_a?(URI) ? site : URI.parse(site)
|
||||
@connection = nil
|
||||
@site
|
||||
end
|
||||
|
||||
def connection(refresh = false)
|
||||
@connection = Connection.new(site) if refresh || @connection.nil?
|
||||
@connection
|
||||
end
|
||||
|
||||
def element_name
|
||||
self.to_s.underscore
|
||||
end
|
||||
|
||||
def collection_name
|
||||
element_name.pluralize
|
||||
end
|
||||
|
||||
def prefix(options={})
|
||||
default = site.path
|
||||
default << '/' unless default[-1..-1] == '/'
|
||||
self.prefix = default
|
||||
prefix(options)
|
||||
end
|
||||
|
||||
def prefix=(value = '/')
|
||||
prefix_call = value.gsub(/:\w+/) { |s| "\#{options[#{s}]}" }
|
||||
method_decl = %(def self.prefix(options={}) "#{prefix_call}" end)
|
||||
eval method_decl
|
||||
end
|
||||
alias_method :set_prefix, :prefix=
|
||||
|
||||
def element_name=(value)
|
||||
class << self ; attr_reader :element_name ; end
|
||||
@element_name = value
|
||||
end
|
||||
alias_method :set_element_name, :element_name=
|
||||
|
||||
def collection_name=(value)
|
||||
class << self ; attr_reader :collection_name ; end
|
||||
@collection_name = value
|
||||
end
|
||||
alias_method :set_collection_name, :collection_name=
|
||||
|
||||
def element_path(id, options = {})
|
||||
"#{prefix(options)}#{collection_name}/#{id}.xml"
|
||||
end
|
||||
|
||||
def collection_path(options = {})
|
||||
"#{prefix(options)}#{collection_name}.xml"
|
||||
end
|
||||
|
||||
def primary_key
|
||||
self.primary_key = 'id'
|
||||
end
|
||||
|
||||
def primary_key=(value)
|
||||
class << self ; attr_reader :primary_key ; end
|
||||
@primary_key = value
|
||||
end
|
||||
alias_method :set_primary_key, :primary_key=
|
||||
|
||||
# Person.find(1) # => GET /people/1.xml
|
||||
# StreetAddress.find(1, :person_id => 1) # => GET /people/1/street_addresses/1.xml
|
||||
def find(*arguments)
|
||||
scope = arguments.slice!(0)
|
||||
options = arguments.slice!(0) || {}
|
||||
|
||||
case scope
|
||||
when :all then find_every(options)
|
||||
when :first then find_every(options).first
|
||||
else find_single(scope, options)
|
||||
end
|
||||
end
|
||||
|
||||
def delete(id)
|
||||
connection.delete(element_path(id))
|
||||
end
|
||||
|
||||
private
|
||||
# { :people => { :person => [ person1, person2 ] } }
|
||||
def find_every(options)
|
||||
connection.get(collection_path(options)).values.first.values.first.collect { |element| new(element, options) }
|
||||
end
|
||||
|
||||
# { :person => person1 }
|
||||
def find_single(scope, options)
|
||||
new(connection.get(element_path(scope, options)).values.first, options)
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :attributes
|
||||
attr_accessor :prefix_options
|
||||
|
||||
def initialize(attributes = {}, prefix_options = {})
|
||||
@attributes = {}
|
||||
self.load attributes
|
||||
@prefix_options = prefix_options
|
||||
end
|
||||
|
||||
def new?
|
||||
id.nil?
|
||||
end
|
||||
|
||||
def id
|
||||
attributes[self.class.primary_key]
|
||||
end
|
||||
|
||||
def id=(id)
|
||||
attributes[self.class.primary_key] = id
|
||||
end
|
||||
|
||||
def save
|
||||
new? ? create : update
|
||||
end
|
||||
|
||||
def destroy
|
||||
connection.delete(self.class.element_path(id, prefix_options))
|
||||
end
|
||||
|
||||
def to_xml(options={})
|
||||
attributes.to_xml({:root => self.class.element_name}.merge(options))
|
||||
end
|
||||
|
||||
# Reloads the attributes of this object from the remote web service.
|
||||
def reload
|
||||
self.load self.class.find(id, @prefix_options).attributes
|
||||
end
|
||||
|
||||
# Manually load attributes from a hash. Recursively loads collections of
|
||||
# resources.
|
||||
def load(attributes)
|
||||
raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
|
||||
attributes.each do |key, value|
|
||||
@attributes[key.to_s] =
|
||||
case value
|
||||
when Array
|
||||
resource = find_or_create_resource_for_collection(key)
|
||||
value.map { |attrs| resource.new(attrs) }
|
||||
when Hash
|
||||
# Workaround collections loaded as Hash
|
||||
# :persons => { :person => [
|
||||
# { :id => 1, :name => 'a' },
|
||||
# { :id => 2, :name => 'b' } ]}
|
||||
if value.keys.size == 1 and value.values.first.is_a?(Array)
|
||||
resource = find_or_create_resource_for(value.keys.first)
|
||||
value.values.first.map { |attrs| resource.new(attrs) }
|
||||
else
|
||||
resource = find_or_create_resource_for(key)
|
||||
resource.new(value)
|
||||
end
|
||||
when ActiveResource::Base
|
||||
value.class.new(value.attributes)
|
||||
else
|
||||
value.dup rescue value
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
protected
|
||||
def connection(refresh = false)
|
||||
self.class.connection(refresh)
|
||||
end
|
||||
|
||||
def update
|
||||
connection.put(self.class.element_path(id, prefix_options), to_xml)
|
||||
true
|
||||
end
|
||||
|
||||
def create
|
||||
resp = connection.post(self.class.collection_path(prefix_options), to_xml)
|
||||
self.id = id_from_response(resp)
|
||||
true
|
||||
end
|
||||
|
||||
# takes a response from a typical create post and pulls the ID out
|
||||
def id_from_response(response)
|
||||
response['Location'][/\/([^\/]*?)(\.\w+)?$/, 1]
|
||||
end
|
||||
|
||||
private
|
||||
def find_or_create_resource_for_collection(name)
|
||||
find_or_create_resource_for(name.to_s.singularize)
|
||||
end
|
||||
|
||||
def find_or_create_resource_for(name)
|
||||
resource_name = name.to_s.camelize
|
||||
resource_name.constantize
|
||||
rescue NameError
|
||||
resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
|
||||
resource.prefix = self.class.prefix
|
||||
resource.site = self.class.site
|
||||
resource
|
||||
end
|
||||
|
||||
def method_missing(method_symbol, *arguments)
|
||||
method_name = method_symbol.to_s
|
||||
|
||||
case method_name.last
|
||||
when "="
|
||||
attributes[method_name.first(-1)] = arguments.first
|
||||
when "?"
|
||||
attributes[method_name.first(-1)] == true
|
||||
else
|
||||
attributes.has_key?(method_name) ? attributes[method_name] : super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,116 +0,0 @@
|
||||
require 'net/https'
|
||||
require 'date'
|
||||
require 'time'
|
||||
require 'uri'
|
||||
require 'benchmark'
|
||||
|
||||
module ActiveResource
|
||||
class ConnectionError < StandardError
|
||||
attr_reader :response
|
||||
|
||||
def initialize(response, message = nil)
|
||||
@response = response
|
||||
@message = message
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Failed with #{response.code}"
|
||||
end
|
||||
end
|
||||
|
||||
class ClientError < ConnectionError; end # 4xx Client Error
|
||||
class ResourceNotFound < ClientError; end # 404 Not Found
|
||||
class ResourceConflict < ClientError; end # 409 Conflict
|
||||
|
||||
class ServerError < ConnectionError; end # 5xx Server Error
|
||||
|
||||
|
||||
class Connection
|
||||
attr_reader :site
|
||||
|
||||
class << self
|
||||
def requests
|
||||
@@requests ||= []
|
||||
end
|
||||
|
||||
def default_header
|
||||
class << self ; attr_reader :default_header end
|
||||
@default_header = { 'Content-Type' => 'application/xml' }
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(site)
|
||||
self.site = site.is_a?(URI) ? site : URI.parse(site)
|
||||
end
|
||||
|
||||
def site=(site)
|
||||
@site = site.is_a?(URI) ? site : URI.parse(site)
|
||||
end
|
||||
|
||||
def get(path)
|
||||
Hash.from_xml(request(:get, path, build_request_headers).body)
|
||||
end
|
||||
|
||||
def delete(path)
|
||||
request(:delete, path, build_request_headers)
|
||||
end
|
||||
|
||||
def put(path, body = '')
|
||||
request(:put, path, body, build_request_headers)
|
||||
end
|
||||
|
||||
def post(path, body = '')
|
||||
request(:post, path, body, build_request_headers)
|
||||
end
|
||||
|
||||
private
|
||||
def request(method, path, *arguments)
|
||||
logger.info "requesting #{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}" if logger
|
||||
result = nil
|
||||
time = Benchmark.realtime { result = http.send(method, path, *arguments) }
|
||||
logger.info "--> #{result.code} #{result.message} (#{result.body.length}b %.2fs)" % time if logger
|
||||
handle_response(result)
|
||||
end
|
||||
|
||||
def handle_response(response)
|
||||
case response.code.to_i
|
||||
when 200...400
|
||||
response
|
||||
when 404
|
||||
raise(ResourceNotFound.new(response))
|
||||
when 400
|
||||
raise(ResourceInvalid.new(response))
|
||||
when 409
|
||||
raise(ResourceConflict.new(response))
|
||||
when 401...500
|
||||
raise(ClientError.new(response))
|
||||
when 500...600
|
||||
raise(ServerError.new(response))
|
||||
else
|
||||
raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
|
||||
end
|
||||
end
|
||||
|
||||
def http
|
||||
unless @http
|
||||
@http = Net::HTTP.new(@site.host, @site.port)
|
||||
@http.use_ssl = @site.is_a?(URI::HTTPS)
|
||||
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @http.use_ssl
|
||||
end
|
||||
|
||||
@http
|
||||
end
|
||||
|
||||
def build_request_headers
|
||||
authorization_header.update(self.class.default_header)
|
||||
end
|
||||
|
||||
def authorization_header
|
||||
(@site.user || @site.password ? { 'Authorization' => 'Basic ' + ["#{@site.user}:#{ @site.password}"].pack('m').delete("\r\n") } : {})
|
||||
end
|
||||
|
||||
def logger
|
||||
ActiveResource::Base.logger
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
module ActiveResource
|
||||
class Struct
|
||||
def self.create
|
||||
Class.new(Base)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,126 +0,0 @@
|
||||
module ActiveResource
|
||||
class ResourceInvalid < ClientError
|
||||
end
|
||||
|
||||
class Errors
|
||||
include Enumerable
|
||||
attr_reader :errors
|
||||
|
||||
delegate :empty?, :to => :errors
|
||||
|
||||
def initialize(base) # :nodoc:
|
||||
@base, @errors = base, {}
|
||||
end
|
||||
|
||||
def add_to_base(msg)
|
||||
add(:base, msg)
|
||||
end
|
||||
|
||||
def add(attribute, msg)
|
||||
@errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
|
||||
@errors[attribute.to_s] << msg
|
||||
end
|
||||
|
||||
# Returns true if the specified +attribute+ has errors associated with it.
|
||||
def invalid?(attribute)
|
||||
!@errors[attribute.to_s].nil?
|
||||
end
|
||||
|
||||
# * Returns nil, if no errors are associated with the specified +attribute+.
|
||||
# * Returns the error message, if one error is associated with the specified +attribute+.
|
||||
# * Returns an array of error messages, if more than one error is associated with the specified +attribute+.
|
||||
def on(attribute)
|
||||
errors = @errors[attribute.to_s]
|
||||
return nil if errors.nil?
|
||||
errors.size == 1 ? errors.first : errors
|
||||
end
|
||||
|
||||
alias :[] :on
|
||||
|
||||
# Returns errors assigned to base object through add_to_base according to the normal rules of on(attribute).
|
||||
def on_base
|
||||
on(:base)
|
||||
end
|
||||
|
||||
# Yields each attribute and associated message per error added.
|
||||
def each
|
||||
@errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
|
||||
end
|
||||
|
||||
# Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
|
||||
# through iteration as "First name can't be empty".
|
||||
def each_full
|
||||
full_messages.each { |msg| yield msg }
|
||||
end
|
||||
|
||||
# Returns all the full error messages in an array.
|
||||
def full_messages
|
||||
full_messages = []
|
||||
|
||||
@errors.each_key do |attr|
|
||||
@errors[attr].each do |msg|
|
||||
next if msg.nil?
|
||||
|
||||
if attr == "base"
|
||||
full_messages << msg
|
||||
else
|
||||
full_messages << [attr.humanize, msg].join(' ')
|
||||
end
|
||||
end
|
||||
end
|
||||
full_messages
|
||||
end
|
||||
|
||||
def clear
|
||||
@errors = {}
|
||||
end
|
||||
|
||||
# Returns the total number of errors added. Two errors added to the same attribute will be counted as such
|
||||
# with this as well.
|
||||
def size
|
||||
@errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
|
||||
end
|
||||
|
||||
alias_method :count, :size
|
||||
alias_method :length, :size
|
||||
|
||||
def from_xml(xml)
|
||||
clear
|
||||
humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) }
|
||||
messages = Hash.from_xml(xml)['errors']['error'] rescue []
|
||||
messages.each do |message|
|
||||
attr_message = humanized_attributes.keys.detect do |attr_name|
|
||||
if message[0, attr_name.size + 1] == "#{attr_name} "
|
||||
add humanized_attributes[attr_name], message[(attr_name.size + 1)..-1]
|
||||
end
|
||||
end
|
||||
|
||||
add_to_base message if attr_message.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Validations
|
||||
def self.included(base) # :nodoc:
|
||||
base.class_eval do
|
||||
alias_method_chain :save, :validation
|
||||
end
|
||||
end
|
||||
|
||||
def save_with_validation
|
||||
save_without_validation
|
||||
rescue ResourceInvalid => error
|
||||
errors.from_xml(error.response.body)
|
||||
return false
|
||||
end
|
||||
|
||||
def valid?
|
||||
errors.empty?
|
||||
end
|
||||
|
||||
# Returns the Errors object that holds all information about attribute error messages.
|
||||
def errors
|
||||
@errors ||= Errors.new(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,9 +0,0 @@
|
||||
module ActiveResource
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 0
|
||||
MINOR = 5
|
||||
TINY = 0
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||
require 'active_resource'
|
||||
require 'test/unit'
|
||||
require 'active_support/breakpoint'
|
||||
|
||||
$:.unshift(File.dirname(__FILE__) + '/.')
|
||||
require 'http_mock'
|
||||
@@ -1,82 +0,0 @@
|
||||
require "#{File.dirname(__FILE__)}/abstract_unit"
|
||||
require 'base64'
|
||||
|
||||
class AuthorizationTest < Test::Unit::TestCase
|
||||
Response = Struct.new(:code)
|
||||
|
||||
def setup
|
||||
@conn = ActiveResource::Connection.new('http://localhost')
|
||||
@matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person')
|
||||
@david = { :id => 2, :name => 'David' }.to_xml(:root => 'person')
|
||||
@authenticated_conn = ActiveResource::Connection.new("http://david:test123@localhost")
|
||||
@authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' }
|
||||
|
||||
ActiveResource::HttpMock.respond_to do |mock|
|
||||
mock.get "/people/2.xml", @authorization_request_header, @david
|
||||
mock.put "/people/2.xml", @authorization_request_header, nil, 204
|
||||
mock.delete "/people/2.xml", @authorization_request_header, nil, 200
|
||||
mock.post "/people/2/addresses.xml", @authorization_request_header, nil, 201, 'Location' => '/people/1/addresses/5'
|
||||
end
|
||||
end
|
||||
|
||||
def test_authorization_header
|
||||
authorization_header = @authenticated_conn.send(:authorization_header)
|
||||
assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization']
|
||||
authorization = authorization_header["Authorization"].to_s.split
|
||||
|
||||
assert_equal "Basic", authorization[0]
|
||||
assert_equal ["david", "test123"], Base64.decode64(authorization[1]).split(":")[0..1]
|
||||
end
|
||||
|
||||
def test_authorization_header_with_username_but_no_password
|
||||
@conn = ActiveResource::Connection.new("http://david:@localhost")
|
||||
authorization_header = @conn.send(:authorization_header)
|
||||
authorization = authorization_header["Authorization"].to_s.split
|
||||
|
||||
assert_equal "Basic", authorization[0]
|
||||
assert_equal ["david"], Base64.decode64(authorization[1]).split(":")[0..1]
|
||||
end
|
||||
|
||||
def test_authorization_header_with_password_but_no_username
|
||||
@conn = ActiveResource::Connection.new("http://:test123@localhost")
|
||||
authorization_header = @conn.send(:authorization_header)
|
||||
authorization = authorization_header["Authorization"].to_s.split
|
||||
|
||||
assert_equal "Basic", authorization[0]
|
||||
assert_equal ["", "test123"], Base64.decode64(authorization[1]).split(":")[0..1]
|
||||
end
|
||||
|
||||
def test_get
|
||||
david = @authenticated_conn.get("/people/2.xml")
|
||||
assert_equal "David", david["person"]["name"]
|
||||
end
|
||||
|
||||
def test_post
|
||||
response = @authenticated_conn.post("/people/2/addresses.xml")
|
||||
assert_equal "/people/1/addresses/5", response["Location"]
|
||||
end
|
||||
|
||||
def test_put
|
||||
response = @authenticated_conn.put("/people/2.xml")
|
||||
assert_equal 204, response.code
|
||||
end
|
||||
|
||||
def test_delete
|
||||
response = @authenticated_conn.delete("/people/2.xml")
|
||||
assert_equal 200, response.code
|
||||
end
|
||||
|
||||
def test_raises_invalid_request_on_unauthorized_requests
|
||||
assert_raises(ActiveResource::InvalidRequestError) { @conn.post("/people/2.xml") }
|
||||
assert_raises(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.xml") }
|
||||
assert_raises(ActiveResource::InvalidRequestError) { @conn.put("/people/2.xml") }
|
||||
assert_raises(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.xml") }
|
||||
end
|
||||
|
||||
protected
|
||||
def assert_response_raises(klass, code)
|
||||
assert_raise(klass, "Expected response code #{code} to raise #{klass}") do
|
||||
@conn.send(:handle_response, Response.new(code))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,77 +0,0 @@
|
||||
require "#{File.dirname(__FILE__)}/../abstract_unit"
|
||||
require "fixtures/person"
|
||||
require "fixtures/street_address"
|
||||
|
||||
class BaseLoadTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@matz = { :id => 1, :name => 'Matz' }
|
||||
|
||||
@first_address = { :id => 1, :street => '12345 Street' }
|
||||
@addresses = [@first_address, { :id => 2, :street => '67890 Street' }]
|
||||
@addresses_from_xml = { :street_addresses => { :street_address => @addresses }}
|
||||
|
||||
@deep = { :id => 1, :street => {
|
||||
:id => 1, :state => { :id => 1, :name => 'Oregon',
|
||||
:notable_rivers => { :notable_river => [
|
||||
{ :id => 1, :name => 'Willamette' },
|
||||
{ :id => 2, :name => 'Columbia', :rafted_by => @matz }] }}}}
|
||||
|
||||
@person = Person.new
|
||||
end
|
||||
|
||||
def test_load_expects_hash
|
||||
assert_raise(ArgumentError) { @person.load nil }
|
||||
assert_raise(ArgumentError) { @person.load '<person id="1"/>' }
|
||||
end
|
||||
|
||||
def test_load_simple_hash
|
||||
assert_equal Hash.new, @person.attributes
|
||||
assert_equal @matz.stringify_keys, @person.load(@matz).attributes
|
||||
end
|
||||
|
||||
def test_load_one_with_existing_resource
|
||||
address = @person.load(:street_address => @first_address).street_address
|
||||
assert_kind_of StreetAddress, address
|
||||
assert_equal @first_address.stringify_keys, address.attributes
|
||||
end
|
||||
|
||||
def test_load_one_with_unknown_resource
|
||||
address = silence_warnings { @person.load(:address => @first_address).address }
|
||||
assert_kind_of Person::Address, address
|
||||
assert_equal @first_address.stringify_keys, address.attributes
|
||||
end
|
||||
|
||||
def test_load_collection_with_existing_resource
|
||||
addresses = @person.load(@addresses_from_xml).street_addresses
|
||||
assert_kind_of Array, addresses
|
||||
addresses.each { |address| assert_kind_of StreetAddress, address }
|
||||
assert_equal @addresses.map(&:stringify_keys), addresses.map(&:attributes)
|
||||
end
|
||||
|
||||
def test_load_collection_with_unknown_resource
|
||||
assert !Person.const_defined?(:Address), "Address shouldn't exist until autocreated"
|
||||
addresses = silence_warnings { @person.load(:addresses => @addresses).addresses }
|
||||
assert Person.const_defined?(:Address), "Address should have been autocreated"
|
||||
addresses.each { |address| assert_kind_of Person::Address, address }
|
||||
assert_equal @addresses.map(&:stringify_keys), addresses.map(&:attributes)
|
||||
end
|
||||
|
||||
def test_recursively_loaded_collections
|
||||
person = @person.load(@deep)
|
||||
assert_equal @deep[:id], person.id
|
||||
|
||||
street = person.street
|
||||
assert_kind_of Person::Street, street
|
||||
assert_equal @deep[:street][:id], street.id
|
||||
|
||||
state = street.state
|
||||
assert_kind_of Person::Street::State, state
|
||||
assert_equal @deep[:street][:state][:id], state.id
|
||||
|
||||
rivers = state.notable_rivers
|
||||
assert_kind_of Array, rivers
|
||||
assert_kind_of Person::Street::State::NotableRiver, rivers.first
|
||||
assert_equal @deep[:street][:state][:notable_rivers][:notable_river].first[:id], rivers.first.id
|
||||
assert_equal @matz[:id], rivers.last.rafted_by.id
|
||||
end
|
||||
end
|
||||
@@ -1,35 +0,0 @@
|
||||
require "#{File.dirname(__FILE__)}/abstract_unit"
|
||||
require "fixtures/person"
|
||||
|
||||
class BaseErrorsTest < Test::Unit::TestCase
|
||||
def setup
|
||||
ActiveResource::HttpMock.respond_to do |mock|
|
||||
mock.post "/people.xml", {}, "<?xml version=\"1.0\" encoding=\"UTF-8\"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>", 400
|
||||
end
|
||||
@person = Person.new(:name => '', :age => '')
|
||||
assert_equal @person.save, false
|
||||
end
|
||||
|
||||
def test_should_mark_as_invalid
|
||||
assert !@person.valid?
|
||||
end
|
||||
|
||||
def test_should_parse_xml_errors
|
||||
assert_kind_of ActiveResource::Errors, @person.errors
|
||||
assert_equal 4, @person.errors.size
|
||||
end
|
||||
|
||||
def test_should_parse_errors_to_individual_attributes
|
||||
assert_equal "can't be blank", @person.errors.on(:age)
|
||||
assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name]
|
||||
assert_equal "Person quota full for today.", @person.errors.on_base
|
||||
end
|
||||
|
||||
def test_should_format_full_errors
|
||||
full = @person.errors.full_messages
|
||||
assert full.include?("Age can't be blank")
|
||||
assert full.include?("Name can't be blank")
|
||||
assert full.include?("Name must start with a letter")
|
||||
assert full.include?("Person quota full for today.")
|
||||
end
|
||||
end
|
||||
@@ -1,174 +0,0 @@
|
||||
require "#{File.dirname(__FILE__)}/abstract_unit"
|
||||
require "fixtures/person"
|
||||
require "fixtures/street_address"
|
||||
|
||||
class BaseTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person')
|
||||
@david = { :id => 2, :name => 'David' }.to_xml(:root => 'person')
|
||||
@addy = { :id => 1, :street => '12345 Street' }.to_xml(:root => 'address')
|
||||
@default_request_headers = { 'Content-Type' => 'application/xml' }
|
||||
|
||||
ActiveResource::HttpMock.respond_to do |mock|
|
||||
mock.get "/people/1.xml", {}, @matz
|
||||
mock.get "/people/2.xml", {}, @david
|
||||
mock.put "/people/1.xml", {}, nil, 204
|
||||
mock.delete "/people/1.xml", {}, nil, 200
|
||||
mock.delete "/people/2.xml", {}, nil, 400
|
||||
mock.post "/people.xml", {}, nil, 201, 'Location' => '/people/5.xml'
|
||||
mock.get "/people/99.xml", {}, nil, 404
|
||||
mock.get "/people.xml", {}, "<people>#{@matz}#{@david}</people>"
|
||||
mock.get "/people/1/addresses.xml", {}, "<addresses>#{@addy}</addresses>"
|
||||
mock.get "/people/1/addresses/1.xml", {}, @addy
|
||||
mock.put "/people/1/addresses/1.xml", {}, nil, 204
|
||||
mock.delete "/people/1/addresses/1.xml", {}, nil, 200
|
||||
mock.post "/people/1/addresses.xml", {}, nil, 201, 'Location' => '/people/1/addresses/5'
|
||||
mock.get "/people//addresses.xml", {}, nil, 404
|
||||
mock.get "/people//addresses/1.xml", {}, nil, 404
|
||||
mock.put "/people//addresses/1.xml", {}, nil, 404
|
||||
mock.delete "/people//addresses/1.xml", {}, nil, 404
|
||||
mock.post "/people//addresses.xml", {}, nil, 404
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def test_site_accessor_accepts_uri_or_string_argument
|
||||
site = URI.parse('http://localhost')
|
||||
|
||||
assert_nothing_raised { Person.site = 'http://localhost' }
|
||||
assert_equal site, Person.site
|
||||
|
||||
assert_nothing_raised { Person.site = site }
|
||||
assert_equal site, Person.site
|
||||
end
|
||||
|
||||
|
||||
def test_collection_name
|
||||
assert_equal "people", Person.collection_name
|
||||
end
|
||||
|
||||
def test_collection_path
|
||||
assert_equal '/people.xml', Person.collection_path
|
||||
end
|
||||
|
||||
def test_custom_element_path
|
||||
assert_equal '/people/1/addresses/1.xml', StreetAddress.element_path(1, :person_id => 1)
|
||||
end
|
||||
|
||||
def test_custom_collection_path
|
||||
assert_equal '/people/1/addresses.xml', StreetAddress.collection_path(:person_id => 1)
|
||||
end
|
||||
|
||||
def test_custom_element_name
|
||||
assert_equal 'address', StreetAddress.element_name
|
||||
end
|
||||
|
||||
def test_custom_collection_name
|
||||
assert_equal 'addresses', StreetAddress.collection_name
|
||||
end
|
||||
|
||||
def test_prefix
|
||||
assert_equal "/", Person.prefix
|
||||
end
|
||||
|
||||
def test_custom_prefix
|
||||
assert_equal '/people//', StreetAddress.prefix
|
||||
assert_equal '/people/1/', StreetAddress.prefix(:person_id => 1)
|
||||
end
|
||||
|
||||
def test_find_by_id
|
||||
matz = Person.find(1)
|
||||
assert_kind_of Person, matz
|
||||
assert_equal "Matz", matz.name
|
||||
end
|
||||
|
||||
def test_find_by_id_with_custom_prefix
|
||||
addy = StreetAddress.find(1, :person_id => 1)
|
||||
assert_kind_of StreetAddress, addy
|
||||
assert_equal '12345 Street', addy.street
|
||||
end
|
||||
|
||||
def test_find_all
|
||||
all = Person.find(:all)
|
||||
assert_equal 2, all.size
|
||||
assert_kind_of Person, all.first
|
||||
assert_equal "Matz", all.first.name
|
||||
assert_equal "David", all.last.name
|
||||
end
|
||||
|
||||
def test_find_first
|
||||
matz = Person.find(:first)
|
||||
assert_kind_of Person, matz
|
||||
assert_equal "Matz", matz.name
|
||||
end
|
||||
|
||||
def test_find_by_id_not_found
|
||||
assert_raises(ActiveResource::ResourceNotFound) { Person.find(99) }
|
||||
assert_raises(ActiveResource::ResourceNotFound) { StreetAddress.find(1) }
|
||||
end
|
||||
|
||||
def test_create
|
||||
rick = Person.new
|
||||
assert_equal true, rick.save
|
||||
assert_equal '5', rick.id
|
||||
end
|
||||
|
||||
def test_id_from_response
|
||||
p = Person.new
|
||||
resp = {'Location' => '/foo/bar/1'}
|
||||
assert_equal '1', p.send(:id_from_response, resp)
|
||||
|
||||
resp['Location'] << '.xml'
|
||||
assert_equal '1', p.send(:id_from_response, resp)
|
||||
end
|
||||
|
||||
def test_create_with_custom_prefix
|
||||
matzs_house = StreetAddress.new({}, {:person_id => 1})
|
||||
matzs_house.save
|
||||
assert_equal '5', matzs_house.id
|
||||
end
|
||||
|
||||
def test_update
|
||||
matz = Person.find(:first)
|
||||
matz.name = "David"
|
||||
assert_kind_of Person, matz
|
||||
assert_equal "David", matz.name
|
||||
assert_equal true, matz.save
|
||||
end
|
||||
|
||||
def test_update_with_custom_prefix
|
||||
addy = StreetAddress.find(1, :person_id => 1)
|
||||
addy.street = "54321 Street"
|
||||
assert_kind_of StreetAddress, addy
|
||||
assert_equal "54321 Street", addy.street
|
||||
addy.save
|
||||
end
|
||||
|
||||
def test_update_conflict
|
||||
ActiveResource::HttpMock.respond_to do |mock|
|
||||
mock.get "/people/2.xml", {}, @david
|
||||
mock.put "/people/2.xml", @default_request_headers, nil, 409
|
||||
end
|
||||
assert_raises(ActiveResource::ResourceConflict) { Person.find(2).save }
|
||||
end
|
||||
|
||||
def test_destroy
|
||||
assert Person.find(1).destroy
|
||||
ActiveResource::HttpMock.respond_to do |mock|
|
||||
mock.get "/people/1.xml", {}, nil, 404
|
||||
end
|
||||
assert_raises(ActiveResource::ResourceNotFound) { Person.find(1).destroy }
|
||||
end
|
||||
|
||||
def test_destroy_with_custom_prefix
|
||||
assert StreetAddress.find(1, :person_id => 1).destroy
|
||||
ActiveResource::HttpMock.respond_to do |mock|
|
||||
mock.get "/people/1/addresses/1.xml", {}, nil, 404
|
||||
end
|
||||
assert_raises(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :person_id => 1).destroy }
|
||||
end
|
||||
|
||||
def test_delete
|
||||
assert Person.delete(1)
|
||||
end
|
||||
end
|
||||
@@ -1,88 +0,0 @@
|
||||
require "#{File.dirname(__FILE__)}/abstract_unit"
|
||||
require 'base64'
|
||||
|
||||
class ConnectionTest < Test::Unit::TestCase
|
||||
Response = Struct.new(:code)
|
||||
|
||||
def setup
|
||||
@conn = ActiveResource::Connection.new('http://localhost')
|
||||
@matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person')
|
||||
@david = { :id => 2, :name => 'David' }.to_xml(:root => 'person')
|
||||
@default_request_headers = { 'Content-Type' => 'application/xml' }
|
||||
ActiveResource::HttpMock.respond_to do |mock|
|
||||
mock.get "/people/1.xml", {}, @matz
|
||||
mock.put "/people/1.xml", {}, nil, 204
|
||||
mock.delete "/people/1.xml", {}, nil, 200
|
||||
mock.post "/people.xml", {}, nil, 201, 'Location' => '/people/5.xml'
|
||||
end
|
||||
end
|
||||
|
||||
def test_handle_response
|
||||
# 2xx and 3xx are valid responses.
|
||||
[200, 299, 300, 399].each do |code|
|
||||
expected = Response.new(code)
|
||||
assert_equal expected, @conn.send(:handle_response, expected)
|
||||
end
|
||||
|
||||
# 404 is a missing resource.
|
||||
assert_response_raises ActiveResource::ResourceNotFound, 404
|
||||
|
||||
# 400 is a validation error
|
||||
assert_response_raises ActiveResource::ResourceInvalid, 400
|
||||
|
||||
# 409 is an optimistic locking error
|
||||
assert_response_raises ActiveResource::ResourceConflict, 409
|
||||
|
||||
# 4xx are client errors.
|
||||
[401, 499].each do |code|
|
||||
assert_response_raises ActiveResource::ClientError, code
|
||||
end
|
||||
|
||||
# 5xx are server errors.
|
||||
[500, 599].each do |code|
|
||||
assert_response_raises ActiveResource::ServerError, code
|
||||
end
|
||||
|
||||
# Others are unknown.
|
||||
[199, 600].each do |code|
|
||||
assert_response_raises ActiveResource::ConnectionError, code
|
||||
end
|
||||
end
|
||||
|
||||
def test_site_accessor_accepts_uri_or_string_argument
|
||||
site = URI.parse("http://localhost")
|
||||
|
||||
assert_nothing_raised { @conn.site = "http://localhost" }
|
||||
assert_equal site, @conn.site
|
||||
|
||||
assert_nothing_raised { @conn.site = site }
|
||||
assert_equal site, @conn.site
|
||||
end
|
||||
|
||||
def test_get
|
||||
matz = @conn.get("/people/1.xml")
|
||||
assert_equal "Matz", matz["person"]["name"]
|
||||
end
|
||||
|
||||
def test_post
|
||||
response = @conn.post("/people.xml")
|
||||
assert_equal "/people/5.xml", response["Location"]
|
||||
end
|
||||
|
||||
def test_put
|
||||
response = @conn.put("/people/1.xml")
|
||||
assert_equal 204, response.code
|
||||
end
|
||||
|
||||
def test_delete
|
||||
response = @conn.delete("/people/1.xml")
|
||||
assert_equal 200, response.code
|
||||
end
|
||||
|
||||
protected
|
||||
def assert_response_raises(klass, code)
|
||||
assert_raise(klass, "Expected response code #{code} to raise #{klass}") do
|
||||
@conn.send(:handle_response, Response.new(code))
|
||||
end
|
||||
end
|
||||
end
|
||||
3
activeresource/test/fixtures/person.rb
vendored
3
activeresource/test/fixtures/person.rb
vendored
@@ -1,3 +0,0 @@
|
||||
class Person < ActiveResource::Base
|
||||
self.site = "http://37s.sunrise.i:3000"
|
||||
end
|
||||
@@ -1,4 +0,0 @@
|
||||
class StreetAddress < ActiveResource::Base
|
||||
self.site = "http://37s.sunrise.i:3000/people/:person_id/"
|
||||
self.element_name = 'address'
|
||||
end
|
||||
@@ -1,123 +0,0 @@
|
||||
require 'active_resource/connection'
|
||||
|
||||
module ActiveResource
|
||||
class InvalidRequestError < StandardError; end
|
||||
|
||||
class HttpMock
|
||||
class Responder
|
||||
def initialize(responses)
|
||||
@responses = responses
|
||||
end
|
||||
|
||||
for method in [ :post, :put, :get, :delete ]
|
||||
module_eval <<-EOE
|
||||
def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {})
|
||||
@responses[Request.new(:#{method}, path, nil, request_headers)] = Response.new(body || {}, status, response_headers)
|
||||
end
|
||||
EOE
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
def requests
|
||||
@@requests ||= []
|
||||
end
|
||||
|
||||
def responses
|
||||
@@responses ||= {}
|
||||
end
|
||||
|
||||
def respond_to(pairs = {})
|
||||
reset!
|
||||
pairs.each do |(path, response)|
|
||||
responses[path] = response
|
||||
end
|
||||
yield Responder.new(responses) if block_given?
|
||||
end
|
||||
|
||||
def reset!
|
||||
requests.clear
|
||||
responses.clear
|
||||
end
|
||||
end
|
||||
|
||||
for method in [ :post, :put ]
|
||||
module_eval <<-EOE
|
||||
def #{method}(path, body, headers)
|
||||
request = ActiveResource::Request.new(:#{method}, path, body, headers)
|
||||
self.class.requests << request
|
||||
self.class.responses[request] || raise(InvalidRequestError.new("No response recorded for: \#{request}"))
|
||||
end
|
||||
EOE
|
||||
end
|
||||
|
||||
for method in [ :get, :delete ]
|
||||
module_eval <<-EOE
|
||||
def #{method}(path, headers)
|
||||
request = ActiveResource::Request.new(:#{method}, path, nil, headers)
|
||||
self.class.requests << request
|
||||
self.class.responses[request] || raise(InvalidRequestError.new("No response recorded for: \#{request}"))
|
||||
end
|
||||
EOE
|
||||
end
|
||||
|
||||
def initialize(site)
|
||||
@site = site
|
||||
end
|
||||
end
|
||||
|
||||
class Request
|
||||
attr_accessor :path, :method, :body, :headers
|
||||
|
||||
def initialize(method, path, body = nil, headers = {})
|
||||
@method, @path, @body, @headers = method, path, body, headers
|
||||
@headers.update('Content-Type' => 'application/xml')
|
||||
end
|
||||
|
||||
def ==(other_request)
|
||||
other_request.hash == hash
|
||||
end
|
||||
|
||||
def eql?(other_request)
|
||||
self == other_request
|
||||
end
|
||||
|
||||
def to_s
|
||||
"<#{method.to_s.upcase}: #{path} [#{headers}] (#{body})>"
|
||||
end
|
||||
|
||||
def hash
|
||||
"#{path}#{method}#{headers}".hash
|
||||
end
|
||||
end
|
||||
|
||||
class Response
|
||||
attr_accessor :body, :code, :headers
|
||||
|
||||
def initialize(body, code = 200, headers = {})
|
||||
@body, @code, @headers = body, code, headers
|
||||
end
|
||||
|
||||
def success?
|
||||
(200..299).include?(code)
|
||||
end
|
||||
|
||||
def [](key)
|
||||
headers[key]
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
headers[key] = value
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Connection
|
||||
private
|
||||
silence_warnings do
|
||||
def http
|
||||
@http ||= HttpMock.new(@site)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,20 @@
|
||||
*SVN*
|
||||
*1.4.0 RC1* (November 22nd, 2006)
|
||||
|
||||
* next_week respects DST changes. #6483 [marclove]
|
||||
* Hash#to_xml handles keys with the same name as Kernel methods. #6613 [Catfish]
|
||||
|
||||
* Don't quote hash keys in Hash#to_json if they're valid JavaScript identifiers. Disable this with ActiveSupport::JSON.unquote_hash_key_identifiers = false if you need strict JSON compliance. [Sam Stephenson]
|
||||
|
||||
* Lazily load the Unicode Database in the UTF-8 Handler [Rick Olson]
|
||||
|
||||
* Update dependencies to delete partially loaded constants. [Nicholas Seckar]
|
||||
|
||||
* Fix unicode JSON regexp for Onigurama compatibility. #6494 [whitley]
|
||||
|
||||
* Update XmlSimple to 1.0.10. Closes #6532. [nicksieger]
|
||||
|
||||
* Update dependencies to allow constants to be defined alongside their siblings. A common case for this is AR model classes with STI; user.rb might define User, Administrator and Guest for example. [Nicholas Seckar]
|
||||
|
||||
* next_week respects DST changes. #6483, #5617, #2353, #2509, #4551 [marclove, rabiedenharn, rails@roetzel.de, jsolson@damogran.org, drbrain@segment7.net]
|
||||
|
||||
* Expose methods added to Enumerable in the documentation, such as group_by. Closes #6170. [sergeykojin@gmail.com, Marcel Molina Jr.]
|
||||
|
||||
@@ -32,8 +46,6 @@
|
||||
|
||||
* Equate Kernel.const_missing with Object.const_missing. Fixes #5988. [Nicholas Seckar]
|
||||
|
||||
* Add ApplicationController special case to Dependencies. [Nicholas Seckar]
|
||||
|
||||
* Don't pad remaining places with in_groups_of if specified padding value is false. [Marcel Molina Jr.]
|
||||
|
||||
* Fix cases where empty xml nodes weren't being translated to nil in Hash.create_from_xml [Rick Olso n]
|
||||
@@ -78,8 +90,6 @@
|
||||
|
||||
* Add extention to obtain the missing constant from NameError instances. [Nicholas Seckar]
|
||||
|
||||
* Thoroughly document inflections. #5700 [petermichaux@gmail.com]
|
||||
|
||||
* Added Module#alias_attribute [Jamis/DHH]. Example:
|
||||
|
||||
class Content < ActiveRecord::Base
|
||||
@@ -203,6 +213,7 @@
|
||||
|
||||
* Add Array#split for dividing arrays into one or more subarrays by value or block. [Sam Stephenson]
|
||||
|
||||
|
||||
*1.3.1* (April 6th, 2006)
|
||||
|
||||
* Clean paths inside of exception messages and traces. [Nicholas Seckar]
|
||||
|
||||
@@ -28,6 +28,6 @@ class Exception # :nodoc:
|
||||
end
|
||||
|
||||
def framework_backtrace
|
||||
clean_backtrace.select {|line| line =~ FrameworkRegexp}
|
||||
clean_backtrace.grep FrameworkRegexp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,7 +35,7 @@ module ActiveSupport #:nodoc:
|
||||
dasherize = !options.has_key?(:dasherize) || options[:dasherize]
|
||||
root = dasherize ? options[:root].to_s.dasherize : options[:root].to_s
|
||||
|
||||
options[:builder].__send__(root) do
|
||||
options[:builder].__send__(:method_missing, root) do
|
||||
each do |key, value|
|
||||
case value
|
||||
when ::Hash
|
||||
|
||||
@@ -25,7 +25,7 @@ module ActiveSupport #:nodoc:
|
||||
|
||||
# Seconds since midnight: Time.now.seconds_since_midnight
|
||||
def seconds_since_midnight
|
||||
self.hour.hours + self.min.minutes + self.sec + (self.usec/1.0e+6)
|
||||
self.to_i - self.change(:hour => 0).to_i + (self.usec/1.0e+6)
|
||||
end
|
||||
|
||||
# Returns a new Time where one or more of the elements have been changed according to the +options+ parameter. The time options
|
||||
@@ -56,13 +56,16 @@ module ActiveSupport #:nodoc:
|
||||
# Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension
|
||||
# Do not use this method in combination with x.months, use months_ago instead!
|
||||
def ago(seconds)
|
||||
seconds.until(self)
|
||||
self.since(-seconds)
|
||||
end
|
||||
|
||||
# Returns a new Time representing the time a number of seconds since the instance time, this is basically a wrapper around
|
||||
#the Numeric extension. Do not use this method in combination with x.months, use months_since instead!
|
||||
def since(seconds)
|
||||
seconds.since(self)
|
||||
initial_dst = self.dst? ? 1 : 0
|
||||
f = seconds.since(self)
|
||||
final_dst = f.dst? ? 1 : 0
|
||||
(seconds.abs >= 86400 && initial_dst != final_dst) ? f + (initial_dst - final_dst).hours : f
|
||||
end
|
||||
alias :in :since
|
||||
|
||||
@@ -135,9 +138,7 @@ module ActiveSupport #:nodoc:
|
||||
# Returns a new Time representing the start of the given day in next week (default is Monday).
|
||||
def next_week(day = :monday)
|
||||
days_into_week = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6}
|
||||
# Adjust in case of switches to or from daylight savings time
|
||||
week_from_today = self.since(1.week) + (self.since(1.week) <=> self).hour
|
||||
week_from_today.beginning_of_week.since(days_into_week[day].day).change(:hour => 0)
|
||||
since(1.week).beginning_of_week.since(days_into_week[day].day).change(:hour => 0)
|
||||
end
|
||||
|
||||
# Returns a new Time representing the start of the day (0:00)
|
||||
|
||||
@@ -47,6 +47,11 @@ module Dependencies #:nodoc:
|
||||
mattr_accessor :log_activity
|
||||
self.log_activity = false
|
||||
|
||||
# :nodoc:
|
||||
# An internal stack used to record which constants are loaded by any block.
|
||||
mattr_accessor :constant_watch_stack
|
||||
self.constant_watch_stack = []
|
||||
|
||||
def load?
|
||||
mechanism == :load
|
||||
end
|
||||
@@ -188,11 +193,13 @@ module Dependencies #:nodoc:
|
||||
def load_file(path, const_paths = loadable_constants_for_path(path))
|
||||
log_call path, const_paths
|
||||
const_paths = [const_paths].compact unless const_paths.is_a? Array
|
||||
undefined_before = const_paths.reject(&method(:qualified_const_defined?))
|
||||
parent_paths = const_paths.collect { |const_path| /(.*)::[^:]+\Z/ =~ const_path ? $1 : :Object }
|
||||
|
||||
result = load path
|
||||
result = nil
|
||||
newly_defined_paths = new_constants_in(*parent_paths) do
|
||||
result = load_without_new_constant_marking path
|
||||
end
|
||||
|
||||
newly_defined_paths = undefined_before.select(&method(:qualified_const_defined?))
|
||||
autoloaded_constants.concat newly_defined_paths
|
||||
autoloaded_constants.uniq!
|
||||
log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty?
|
||||
@@ -227,7 +234,7 @@ module Dependencies #:nodoc:
|
||||
raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
|
||||
end
|
||||
|
||||
raise ArgumentError, "Expected #{from_mod} is not missing constant #{const_name}!" if from_mod.const_defined?(const_name)
|
||||
raise ArgumentError, "#{from_mod} is not missing constant #{const_name}!" if from_mod.const_defined?(const_name)
|
||||
|
||||
qualified_name = qualified_name_for from_mod, const_name
|
||||
path_suffix = qualified_name.underscore
|
||||
@@ -290,6 +297,85 @@ module Dependencies #:nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
# Run the provided block and detect the new constants that were loaded during
|
||||
# its execution. Constants may only be regarded as 'new' once -- so if the
|
||||
# block calls +new_constants_in+ again, then the constants defined within the
|
||||
# inner call will not be reported in this one.
|
||||
#
|
||||
# If the provided block does not run to completion, and instead raises an
|
||||
# exception, any new constants are regarded as being only partially defined
|
||||
# and will be removed immediately.
|
||||
def new_constants_in(*descs)
|
||||
log_call(*descs)
|
||||
|
||||
# Build the watch frames. Each frame is a tuple of
|
||||
# [module_name_as_string, constants_defined_elsewhere]
|
||||
watch_frames = descs.collect do |desc|
|
||||
if desc.is_a? Module
|
||||
mod_name = desc.name
|
||||
initial_constants = desc.constants
|
||||
elsif desc.is_a?(String) || desc.is_a?(Symbol)
|
||||
mod_name = desc.to_s
|
||||
|
||||
# Handle the case where the module has yet to be defined.
|
||||
initial_constants = if qualified_const_defined?(mod_name)
|
||||
mod_name.constantize.constants
|
||||
else
|
||||
[]
|
||||
end
|
||||
else
|
||||
raise Argument, "#{desc.inspect} does not describe a module!"
|
||||
end
|
||||
|
||||
[mod_name, initial_constants]
|
||||
end
|
||||
|
||||
constant_watch_stack.concat watch_frames
|
||||
|
||||
aborting = true
|
||||
begin
|
||||
yield # Now yield to the code that is to define new constants.
|
||||
aborting = false
|
||||
ensure
|
||||
# Find the new constants.
|
||||
new_constants = watch_frames.collect do |mod_name, prior_constants|
|
||||
# Module still doesn't exist? Treat it as if it has no constants.
|
||||
next [] unless qualified_const_defined?(mod_name)
|
||||
|
||||
mod = mod_name.constantize
|
||||
next [] unless mod.is_a? Module
|
||||
new_constants = mod.constants - prior_constants
|
||||
|
||||
# Make sure no other frames takes credit for these constants.
|
||||
constant_watch_stack.each do |frame_name, constants|
|
||||
constants.concat new_constants if frame_name == mod_name
|
||||
end
|
||||
|
||||
new_constants.collect do |suffix|
|
||||
mod_name == "Object" ? suffix : "#{mod_name}::#{suffix}"
|
||||
end
|
||||
end.flatten
|
||||
|
||||
log "New constants: #{new_constants * ', '}"
|
||||
|
||||
if aborting
|
||||
log "Error during loading, removing partially loaded constants "
|
||||
new_constants.each { |name| remove_constant name }
|
||||
new_constants.clear
|
||||
end
|
||||
end
|
||||
|
||||
return new_constants
|
||||
ensure
|
||||
# Remove the stack frames that we added.
|
||||
if defined?(watch_frames) && ! watch_frames.empty?
|
||||
frame_ids = watch_frames.collect(&:object_id)
|
||||
constant_watch_stack.delete_if do |watch_frame|
|
||||
frame_ids.include? watch_frame.object_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class LoadingModule
|
||||
# Old style environment.rb referenced this method directly. Please note, it doesn't
|
||||
# actualy *do* anything any more.
|
||||
@@ -389,15 +475,18 @@ class Class
|
||||
end
|
||||
|
||||
class Object #:nodoc:
|
||||
|
||||
alias_method :load_without_new_constant_marking, :load
|
||||
|
||||
def load(file, *extras)
|
||||
super(file, *extras)
|
||||
Dependencies.new_constants_in(Object) { super(file, *extras) }
|
||||
rescue Exception => exception # errors from loading file
|
||||
exception.blame_file! file
|
||||
raise
|
||||
end
|
||||
|
||||
def require(file, *extras)
|
||||
super(file, *extras)
|
||||
Dependencies.new_constants_in(Object) { super(file, *extras) }
|
||||
rescue Exception => exception # errors from required file
|
||||
exception.blame_file! file
|
||||
raise
|
||||
|
||||
@@ -37,10 +37,9 @@ module ActiveSupport
|
||||
@silenced
|
||||
end
|
||||
|
||||
# Silence deprecations for the duration of the provided block. For internal
|
||||
# use only.
|
||||
# Silence deprecation warnings within the block.
|
||||
def silence
|
||||
old_silenced, @silenced = @silenced, true # We could have done behavior = nil...
|
||||
old_silenced, @silenced = @silenced, true
|
||||
yield
|
||||
ensure
|
||||
@silenced = old_silenced
|
||||
@@ -51,13 +50,27 @@ module ActiveSupport
|
||||
|
||||
private
|
||||
def deprecation_message(callstack, message = nil)
|
||||
file, line, method = extract_callstack(callstack)
|
||||
message ||= "You are using deprecated behavior which will be removed from Rails 2.0."
|
||||
"DEPRECATION WARNING: #{message} See http://www.rubyonrails.org/deprecation for details. (called from #{method} at #{file}:#{line})"
|
||||
"DEPRECATION WARNING: #{message} See http://www.rubyonrails.org/deprecation for details. #{deprecation_caller_message(callstack)}"
|
||||
end
|
||||
|
||||
def deprecation_caller_message(callstack)
|
||||
file, line, method = extract_callstack(callstack)
|
||||
if file
|
||||
if line && method
|
||||
"(called from #{method} at #{file}:#{line})"
|
||||
else
|
||||
"(called from #{file}:#{line})"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def extract_callstack(callstack)
|
||||
callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
|
||||
if md = callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/)
|
||||
md.captures
|
||||
else
|
||||
callstack.first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -139,13 +152,17 @@ module ActiveSupport
|
||||
end
|
||||
|
||||
private
|
||||
def warn(callstack, called, args)
|
||||
ActiveSupport::Deprecation.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack)
|
||||
end
|
||||
|
||||
def method_missing(called, *args, &block)
|
||||
warn caller, called, args
|
||||
@instance.__send__(@method).__send__(called, *args, &block)
|
||||
target.__send__(called, *args, &block)
|
||||
end
|
||||
|
||||
def target
|
||||
@instance.__send__(@method)
|
||||
end
|
||||
|
||||
def warn(callstack, called, args)
|
||||
ActiveSupport::Deprecation.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,14 +4,20 @@ module ActiveSupport
|
||||
module JSON #:nodoc:
|
||||
class CircularReferenceError < StandardError #:nodoc:
|
||||
end
|
||||
# returns the literal string as its JSON encoded form. Useful for passing javascript variables into functions.
|
||||
#
|
||||
# page.call 'Element.show', ActiveSupport::JSON::Variable.new("$$(#items li)")
|
||||
|
||||
# A string that returns itself as as its JSON-encoded form.
|
||||
class Variable < String #:nodoc:
|
||||
def to_json
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
# When +true+, Hash#to_json will omit quoting string or symbol keys
|
||||
# if the keys are valid JavaScript identifiers. Note that this is
|
||||
# technically improper JSON (all object keys must be quoted), so if
|
||||
# you need strict JSON compliance, set this option to +false+.
|
||||
mattr_accessor :unquote_hash_key_identifiers
|
||||
@@unquote_hash_key_identifiers = true
|
||||
|
||||
class << self
|
||||
REFERENCE_STACK_VARIABLE = :json_reference_stack
|
||||
@@ -22,6 +28,11 @@ module ActiveSupport
|
||||
end
|
||||
end
|
||||
|
||||
def can_unquote_identifier?(key)
|
||||
return false unless unquote_hash_key_identifiers
|
||||
key.to_s =~ /^[[:alpha:]_$][[:alnum:]_$]*$/
|
||||
end
|
||||
|
||||
protected
|
||||
def raise_on_circular_reference(value)
|
||||
stack = Thread.current[REFERENCE_STACK_VARIABLE] ||= []
|
||||
|
||||
@@ -32,7 +32,7 @@ module ActiveSupport
|
||||
ESCAPED_CHARS[s]
|
||||
}.gsub(/([\xC0-\xDF][\x80-\xBF]|
|
||||
[\xE0-\xEF][\x80-\xBF]{2}|
|
||||
[\xF0-\xF7][\x80-\xBF]{3})+/ux) { |s|
|
||||
[\xF0-\xF7][\x80-\xBF]{3})+/nx) { |s|
|
||||
s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/, '\\\\u\&')
|
||||
} + '"'
|
||||
end
|
||||
@@ -51,8 +51,10 @@ module ActiveSupport
|
||||
|
||||
define_encoder Hash do |hash|
|
||||
returning result = '{' do
|
||||
result << hash.map do |pair|
|
||||
pair.map { |value| value.to_json } * ': '
|
||||
result << hash.map do |key, value|
|
||||
key = ActiveSupport::JSON::Variable.new(key.to_s) if
|
||||
ActiveSupport::JSON.can_unquote_identifier?(key)
|
||||
"#{key.to_json}: #{value.to_json}"
|
||||
end * ', '
|
||||
result << '}'
|
||||
end
|
||||
|
||||
@@ -7,12 +7,36 @@ module ActiveSupport::Multibyte::Handlers
|
||||
end
|
||||
|
||||
class UnicodeDatabase #:nodoc:
|
||||
attr_accessor :codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252
|
||||
attr_writer :codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252
|
||||
|
||||
# Creates a new UnicodeDatabase instance and loads the database.
|
||||
def initialize
|
||||
# self-expiring methods that lazily load the Unicode database and then return the value.
|
||||
[:codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252].each do |attr_name|
|
||||
class_eval(<<-EOS, __FILE__, __LINE__)
|
||||
def #{attr_name}
|
||||
load
|
||||
@#{attr_name}
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
||||
# Shortcut to ucd.codepoints[]
|
||||
def [](index); codepoints[index]; end
|
||||
|
||||
# Returns the directory in which the data files are stored
|
||||
def self.dirname
|
||||
File.dirname(__FILE__) + '/../../values/'
|
||||
end
|
||||
|
||||
# Returns the filename for the data file for this version
|
||||
def self.filename
|
||||
File.expand_path File.join(dirname, "unicode_tables.dat")
|
||||
end
|
||||
|
||||
# Loads the unicode database and returns all the internal objects of UnicodeDatabase
|
||||
# Once the values have been loaded, define attr_reader methods for the instance variables.
|
||||
def load
|
||||
begin
|
||||
@codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = self.class.load
|
||||
@codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, 'rb') { |f| Marshal.load f.read }
|
||||
rescue Exception => e
|
||||
raise IOError.new("Couldn't load the unicode tables for UTF8Handler (#{e.message}), handler is unusable")
|
||||
end
|
||||
@@ -30,24 +54,11 @@ module ActiveSupport::Multibyte::Handlers
|
||||
end
|
||||
end if @boundary[k].kind_of?(Array)
|
||||
end
|
||||
end
|
||||
|
||||
# Shortcut to ucd.codepoints[]
|
||||
def [](index); @codepoints[index]; end
|
||||
|
||||
# Returns the directory in which the data files are stored
|
||||
def self.dirname
|
||||
File.dirname(__FILE__) + '/../../values/'
|
||||
end
|
||||
|
||||
# Returns the filename for the data file for this version
|
||||
def self.filename
|
||||
File.expand_path File.join(dirname, "unicode_tables.dat")
|
||||
end
|
||||
|
||||
# Loads the unicode database and returns all the internal objects of UnicodeDatabase
|
||||
def self.load
|
||||
File.open(self.filename, 'rb') { |f| Marshal.load f.read }
|
||||
|
||||
# define attr_reader methods for the instance variables
|
||||
class << self
|
||||
attr_reader :codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
1054
activesupport/lib/active_support/vendor/flexmock.rb
vendored
1054
activesupport/lib/active_support/vendor/flexmock.rb
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,21 @@
|
||||
# = XmlSimple
|
||||
#
|
||||
# Author:: Maik Schmidt <contact@maik-schmidt.de>
|
||||
# Copyright:: Copyright (c) 2003 Maik Schmidt
|
||||
# Copyright:: Copyright (c) 2003-2006 Maik Schmidt
|
||||
# License:: Distributes under the same terms as Ruby.
|
||||
#
|
||||
require 'rexml/document'
|
||||
require 'stringio'
|
||||
|
||||
# Easy API to maintain XML (especially configuration files).
|
||||
class XmlSimple #:nodoc:
|
||||
class XmlSimple
|
||||
include REXML
|
||||
|
||||
@@VERSION = '1.0.2'
|
||||
@@VERSION = '1.0.9'
|
||||
|
||||
# A simple cache for XML documents that were already transformed
|
||||
# by xml_in.
|
||||
class Cache #:nodoc:
|
||||
class Cache
|
||||
# Creates and initializes a new Cache object.
|
||||
def initialize
|
||||
@mem_share_cache = {}
|
||||
@@ -184,7 +185,7 @@ class XmlSimple #:nodoc:
|
||||
|
||||
@doc = load_xml_file(filename)
|
||||
end
|
||||
elsif string.kind_of?(IO)
|
||||
elsif string.kind_of?(IO) || string.kind_of?(StringIO)
|
||||
@doc = parse(string.readlines.to_s)
|
||||
else
|
||||
raise ArgumentError, "Could not parse object of type: <#{string.type}>."
|
||||
@@ -266,7 +267,7 @@ class XmlSimple #:nodoc:
|
||||
keyattr keeproot forcecontent contentkey noattr
|
||||
searchpath forcearray suppressempty anonymoustag
|
||||
cache grouptags normalisespace normalizespace
|
||||
variables varattr
|
||||
variables varattr keytosymbol
|
||||
),
|
||||
'out' => %w(
|
||||
keyattr keeproot contentkey noattr rootname
|
||||
@@ -283,6 +284,7 @@ class XmlSimple #:nodoc:
|
||||
DEF_ANONYMOUS_TAG = 'anon'
|
||||
DEF_FORCE_ARRAY = true
|
||||
DEF_INDENTATION = ' '
|
||||
DEF_KEY_TO_SYMBOL = false
|
||||
|
||||
# Normalizes option names in a hash, i.e., turns all
|
||||
# characters to lower case and removes all underscores.
|
||||
@@ -350,6 +352,8 @@ class XmlSimple #:nodoc:
|
||||
@options['xmldeclaration'] = DEF_XML_DECLARATION
|
||||
end
|
||||
|
||||
@options['keytosymbol'] = DEF_KEY_TO_SYMBOL unless @options.has_key?('keytosymbol')
|
||||
|
||||
if @options.has_key?('contentkey')
|
||||
if @options['contentkey'] =~ /^-(.*)$/
|
||||
@options['contentkey'] = $1
|
||||
@@ -654,6 +658,14 @@ class XmlSimple #:nodoc:
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#patch for converting keys to symbols
|
||||
if @options.has_key?('keytosymbol')
|
||||
if @options['keytosymbol'] == true
|
||||
key = key.to_s.downcase.to_sym
|
||||
end
|
||||
end
|
||||
|
||||
if hash.has_key?(key)
|
||||
if hash[key].instance_of?(Array)
|
||||
hash[key] << value
|
||||
@@ -879,14 +891,7 @@ class XmlSimple #:nodoc:
|
||||
# data::
|
||||
# The string to be escaped.
|
||||
def escape_value(data)
|
||||
return data if data.nil? || data == ''
|
||||
result = data.dup
|
||||
result.gsub!('&', '&')
|
||||
result.gsub!('<', '<')
|
||||
result.gsub!('>', '>')
|
||||
result.gsub!('"', '"')
|
||||
result.gsub!("'", ''')
|
||||
result
|
||||
Text::normalize(data)
|
||||
end
|
||||
|
||||
# Removes leading and trailing whitespace and sequences of
|
||||
@@ -895,10 +900,7 @@ class XmlSimple #:nodoc:
|
||||
# text::
|
||||
# String to be normalised.
|
||||
def normalise_space(text)
|
||||
text.sub!(/^\s+/, '')
|
||||
text.sub!(/\s+$/, '')
|
||||
text.gsub!(/\s\s+/, ' ')
|
||||
text
|
||||
text.strip.gsub(/\s\s+/, ' ')
|
||||
end
|
||||
|
||||
# Checks, if an object is nil, an empty String or an empty Hash.
|
||||
@@ -926,14 +928,14 @@ class XmlSimple #:nodoc:
|
||||
# default::
|
||||
# Value to be returned, if node could not be converted.
|
||||
def node_to_text(node, default = nil)
|
||||
if node.instance_of?(Element)
|
||||
return node.texts.join('')
|
||||
elsif node.instance_of?(Attribute)
|
||||
return node.value.nil? ? default : node.value.strip
|
||||
elsif node.instance_of?(Text)
|
||||
return node.to_s.strip
|
||||
if node.instance_of?(REXML::Element)
|
||||
node.texts.map { |t| t.value }.join('')
|
||||
elsif node.instance_of?(REXML::Attribute)
|
||||
node.value.nil? ? default : node.value.strip
|
||||
elsif node.instance_of?(REXML::Text)
|
||||
node.value.strip
|
||||
else
|
||||
return default
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
class ClassFolder
|
||||
ConstantInClassFolder = 'indeed'
|
||||
end
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
class ClassFolder::ClassFolderSubclass < ClassFolder
|
||||
ConstantInClassFolder
|
||||
end
|
||||
@@ -1,4 +1,7 @@
|
||||
class ClassFolder
|
||||
class NestedClass
|
||||
end
|
||||
|
||||
class SiblingClass
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user