aboutsummaryrefslogtreecommitdiffstats
path: root/railties
diff options
context:
space:
mode:
authorGonçalo Silva <goncalossilva@gmail.com>2010-08-10 18:15:12 +0100
committerGonçalo Silva <goncalossilva@gmail.com>2010-08-10 18:15:12 +0100
commit62658500049fbb7a5e7d75537dd6f6a374204207 (patch)
tree8892d8305ced43866068a6c1c66548e465e45b38 /railties
parentcd2bbed9846d84a1230a1b9e52843eedca17b28d (diff)
parente86cced311539932420f9cda49d736606d106c28 (diff)
downloadrails-62658500049fbb7a5e7d75537dd6f6a374204207.tar.gz
rails-62658500049fbb7a5e7d75537dd6f6a374204207.tar.bz2
rails-62658500049fbb7a5e7d75537dd6f6a374204207.zip
Merge branch 'master' of http://github.com/rails/rails
Diffstat (limited to 'railties')
-rw-r--r--railties/CHANGELOG19
-rw-r--r--railties/README281
-rw-r--r--railties/README.rdoc25
-rw-r--r--railties/Rakefile14
-rw-r--r--railties/guides/rails_guides.rb21
-rw-r--r--railties/guides/source/3_0_release_notes.textile7
-rw-r--r--railties/guides/source/action_controller_overview.textile35
-rw-r--r--railties/guides/source/active_record_querying.textile48
-rw-r--r--railties/guides/source/active_record_validations_callbacks.textile27
-rw-r--r--railties/guides/source/active_support_core_extensions.textile563
-rw-r--r--railties/guides/source/api_documentation_guidelines.textile187
-rw-r--r--railties/guides/source/association_basics.textile36
-rw-r--r--railties/guides/source/command_line.textile12
-rw-r--r--railties/guides/source/configuring.textile2
-rw-r--r--railties/guides/source/contributing_to_rails.textile78
-rw-r--r--railties/guides/source/form_helpers.textile2
-rw-r--r--railties/guides/source/getting_started.textile20
-rw-r--r--railties/guides/source/i18n.textile2
-rw-r--r--railties/guides/source/index.html.erb20
-rw-r--r--railties/guides/source/initialization.textile34
-rw-r--r--railties/guides/source/layout.html.erb6
-rw-r--r--railties/guides/source/layouts_and_rendering.textile2
-rw-r--r--railties/guides/source/migrations.textile7
-rw-r--r--railties/guides/source/plugins.textile2
-rw-r--r--railties/guides/source/routing.textile90
-rw-r--r--railties/guides/source/security.textile2
-rw-r--r--railties/lib/rails/application.rb14
-rw-r--r--railties/lib/rails/commands.rb2
-rw-r--r--railties/lib/rails/commands/console.rb5
-rw-r--r--railties/lib/rails/configuration.rb5
-rw-r--r--railties/lib/rails/engine/configuration.rb2
-rw-r--r--railties/lib/rails/generators/actions.rb6
-rw-r--r--railties/lib/rails/generators/active_model.rb6
-rw-r--r--railties/lib/rails/generators/base.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb28
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb16
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/index.html23
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js2907
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt2
-rw-r--r--railties/lib/rails/generators/test_case.rb8
-rw-r--r--railties/lib/rails/info.rb3
-rw-r--r--railties/lib/rails/paths.rb40
-rw-r--r--railties/lib/rails/rack/log_tailer.rb7
-rw-r--r--railties/lib/rails/railtie.rb62
-rw-r--r--railties/lib/rails/script_rails_loader.rb3
-rw-r--r--railties/lib/rails/tasks/documentation.rake14
-rw-r--r--railties/lib/rails/tasks/framework.rake2
-rw-r--r--railties/lib/rails/test_unit/testing.rake2
-rw-r--r--railties/lib/rails/version.rb2
-rw-r--r--railties/railties.gemspec4
-rw-r--r--railties/test/application/console_test.rb21
-rw-r--r--railties/test/application/generators_test.rb15
-rw-r--r--railties/test/application/initializers/frameworks_test.rb12
-rw-r--r--railties/test/application/initializers/notifications_test.rb19
-rw-r--r--railties/test/application/middleware_test.rb6
-rw-r--r--railties/test/application/routing_test.rb97
-rw-r--r--railties/test/generators/app_generator_test.rb57
-rw-r--r--railties/test/generators/scaffold_generator_test.rb15
-rw-r--r--railties/test/generators_test.rb2
-rw-r--r--railties/test/paths_test.rb24
-rw-r--r--railties/test/railties/railtie_test.rb16
64 files changed, 3274 insertions, 1725 deletions
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 8e2bac7f00..fc1ec340c7 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,4 +1,9 @@
-*Rails 3.0.0 [Release Candidate] (unreleased)*
+*Rails 3.0.0 [release candidate] (July 26th, 2010)*
+
+* Application generation: --skip-testunit and --skip-activerecord become --skip-test-unit
+ and --skip-active-record respectively. [fxn]
+
+* Added console to Rails::Railtie as a hook called just after console starts. [José Valim]
* Rails no longer autoload code in lib for application. You need to explicitly require it. [José Valim]
@@ -13,26 +18,26 @@
*Rails 3.0.0 [beta 4] (June 8th, 2010)*
-* Version bump
-* Removed Rails Metal [YK & JV].
+* Removed Rails Metal [Yehuda Katz, José Valim].
+
*Rails 3.0.0 [beta 3] (April 13th, 2010)*
-* Renamed config.cookie_secret to config.secret_token and pass it as env key. [JV]
+* Renamed config.cookie_secret to config.secret_token and pass it as env key. [José Valim]
*Rails 3.0.0 [beta 2] (April 1st, 2010)*
-* Session store configuration has changed [YK & CL]
+* Session store configuration has changed [Yehuda Katz, Carl Lerche]
config.session_store :cookie_store, {:key => "..."}
config.cookie_secret = "fdsfhisdghfidugnfdlg"
* railtie_name and engine_name are deprecated. You can now add any object to
- the configuration object: config.your_plugin = {} [JV]
+ the configuration object: config.your_plugin = {} [José Valim]
* Added config.generators.templates to provide alternative paths for the generators
- to look for templates [JV]
+ to look for templates [José Valim]
*Rails 3.0.0 [beta 1] (February 4, 2010)*
diff --git a/railties/README b/railties/README
deleted file mode 100644
index d8be15e346..0000000000
--- a/railties/README
+++ /dev/null
@@ -1,281 +0,0 @@
-== Welcome to Rails
-
-Rails is a web-application framework that includes everything needed to create
-database-backed web applications according to the Model-View-Control pattern.
-
-This pattern splits the view (also called the presentation) into "dumb"
-templates that are primarily responsible for inserting pre-built data in between
-HTML tags. The model contains the "smart" domain objects (such as Account,
-Product, Person, Post) that holds all the business logic and knows how to
-persist themselves to a database. The controller handles the incoming requests
-(such as Save New Account, Update Product, Show Post) by manipulating the model
-and directing data to the view.
-
-In Rails, the model is handled by what's called an object-relational mapping
-layer entitled Active Record. This layer allows you to present the data from
-database rows as objects and embellish these data objects with business logic
-methods. You can read more about Active Record in
-link:files/vendor/rails/activerecord/README.html.
-
-The controller and view are handled by the Action Pack, which handles both
-layers by its two parts: Action View and Action Controller. These two layers
-are bundled in a single package due to their heavy interdependence. This is
-unlike the relationship between the Active Record and Action Pack that is much
-more separate. Each of these packages can be used independently outside of
-Rails. You can read more about Action Pack in
-link:files/vendor/rails/actionpack/README.html.
-
-
-== Getting Started
-
-1. At the command prompt, create a new Rails application:
- <tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name)
-
-2. Change directory to <tt>myapp</tt> and start the web server:
- <tt>cd myapp; rails server</tt> (run with --help for options)
-
-3. Go to http://localhost:3000/ and you'll see:
- "Welcome aboard: You're riding Ruby on Rails!"
-
-4. Follow the guidelines to start developing your application. You can find
-the following resources handy:
-
-* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
-* Ruby on Rails Tutorial Book: http://www.railstutorial.org/
-
-
-== Web Servers
-
-By default, Rails will try to use Mongrel if it's installed when started with
-<tt>rails server</tt>, otherwise Rails will use WEBrick, the web server that
-ships with Ruby.
-
-Mongrel is a Ruby-based web server with a C component (which requires
-compilation) that is suitable for development. If you have Ruby Gems installed,
-getting up and running with mongrel is as easy as:
- <tt>sudo gem install mongrel</tt>.
-
-You can find more info at: http://mongrel.rubyforge.org
-
-You can alternatively run Rails applications with other Ruby web servers, e.g.,
-{Thin}[http://code.macournoyer.com/thin/], {Ebb}[http://ebb.rubyforge.org/], and
-Apache with {mod_rails}[http://www.modrails.com/]. However, <tt>rails server</tt>
-doesn't search for or start them.
-
-For production use, often a web/proxy server, e.g., {Apache}[http://apache.org],
-{Nginx}[http://nginx.net/], {LiteSpeed}[http://litespeedtech.com/],
-{Lighttpd}[http://www.lighttpd.net/], or {IIS}[http://www.iis.net/], is deployed
-as the front end server with the chosen Ruby web server running in the back end
-and receiving the proxied requests via one of several protocols (HTTP, CGI, FCGI).
-
-
-== Debugging Rails
-
-Sometimes your application goes wrong. Fortunately there are a lot of tools that
-will help you debug it and get it back on the rails.
-
-First area to check is the application log files. Have "tail -f" commands
-running on the server.log and development.log. Rails will automatically display
-debugging and runtime information to these files. Debugging info will also be
-shown in the browser on requests from 127.0.0.1.
-
-You can also log your own messages directly into the log file from your code
-using the Ruby logger class from inside your controllers. Example:
-
- class WeblogController < ActionController::Base
- def destroy
- @weblog = Weblog.find(params[:id])
- @weblog.destroy
- logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
- end
- end
-
-The result will be a message in your log file along the lines of:
-
- Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1!
-
-More information on how to use the logger is at http://www.ruby-doc.org/core/
-
-Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
-several books available online as well:
-
-* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
-* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
-
-These two books will bring you up to speed on the Ruby language and also on
-programming in general.
-
-
-== Debugger
-
-Debugger support is available through the debugger command when you start your
-Mongrel or WEBrick server with --debugger. This means that you can break out of
-execution at any point in the code, investigate and change the model, and then,
-resume execution! You need to install ruby-debug to run the server in debugging
-mode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example:
-
- class WeblogController < ActionController::Base
- def index
- @posts = Post.find(:all)
- debugger
- end
- end
-
-So the controller will accept the action, run the first line, then present you
-with a IRB prompt in the server window. Here you can do things like:
-
- >> @posts.inspect
- => "[#<Post:0x14a6be8
- @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>,
- #<Post:0x14a6620
- @attributes={"title"=>"Rails", "body"=>"Only ten..", "id"=>"2"}>]"
- >> @posts.first.title = "hello from a debugger"
- => "hello from a debugger"
-
-...and even better, you can examine how your runtime objects actually work:
-
- >> f = @posts.first
- => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
- >> f.
- Display all 152 possibilities? (y or n)
-
-Finally, when you're ready to resume execution, you can enter "cont".
-
-
-== Console
-
-The console is a Ruby shell, which allows you to interact with your
-application's domain model. Here you'll have all parts of the application
-configured, just like it is when the application is running. You can inspect
-domain models, change values, and save to the database. Starting the script
-without arguments will launch it in the development environment.
-
-To start the console, run <tt>rails console</tt> from the application
-directory.
-
-Options:
-
-* Passing the <tt>-s, --sandbox</tt> argument will rollback any modifications
- made to the database.
-* Passing an environment name as an argument will load the corresponding
- environment. Example: <tt>rails console production</tt>.
-
-To reload your controllers and models after launching the console run
-<tt>reload!</tt>
-
-More information about irb can be found at:
-link:http://www.rubycentral.com/pickaxe/irb.html
-
-
-== dbconsole
-
-You can go to the command line of your database directly through <tt>rails
-dbconsole</tt>. You would be connected to the database with the credentials
-defined in database.yml. Starting the script without arguments will connect you
-to the development database. Passing an argument will connect you to a different
-database, like <tt>rails dbconsole production</tt>. Currently works for MySQL,
-PostgreSQL and SQLite 3.
-
-== Description of Contents
-
-The default directory structure of a generated Ruby on Rails application:
-
- |-- app
- | |-- controllers
- | |-- helpers
- | |-- models
- | `-- views
- | `-- layouts
- |-- config
- | |-- environments
- | |-- initializers
- | `-- locales
- |-- db
- |-- doc
- |-- lib
- | `-- tasks
- |-- log
- |-- public
- | |-- images
- | |-- javascripts
- | `-- stylesheets
- |-- script
- | `-- performance
- |-- test
- | |-- fixtures
- | |-- functional
- | |-- integration
- | |-- performance
- | `-- unit
- |-- tmp
- | |-- cache
- | |-- pids
- | |-- sessions
- | `-- sockets
- `-- vendor
- `-- plugins
-
-app
- Holds all the code that's specific to this particular application.
-
-app/controllers
- Holds controllers that should be named like weblogs_controller.rb for
- automated URL mapping. All controllers should descend from
- ApplicationController which itself descends from ActionController::Base.
-
-app/models
- Holds models that should be named like post.rb. Models descend from
- ActiveRecord::Base by default.
-
-app/views
- Holds the template files for the view that should be named like
- weblogs/index.html.erb for the WeblogsController#index action. All views use
- eRuby syntax by default.
-
-app/views/layouts
- Holds the template files for layouts to be used with views. This models the
- common header/footer method of wrapping views. In your views, define a layout
- using the <tt>layout :default</tt> and create a file named default.html.erb.
- Inside default.html.erb, call <% yield %> to render the view using this
- layout.
-
-app/helpers
- Holds view helpers that should be named like weblogs_helper.rb. These are
- generated for you automatically when using generators for controllers.
- Helpers can be used to wrap functionality for your views into methods.
-
-config
- Configuration files for the Rails environment, the routing map, the database,
- and other dependencies.
-
-db
- Contains the database schema in schema.rb. db/migrate contains all the
- sequence of Migrations for your schema.
-
-doc
- This directory is where your application documentation will be stored when
- generated using <tt>rake doc:app</tt>
-
-lib
- Application specific libraries. Basically, any kind of custom code that
- doesn't belong under controllers, models, or helpers. This directory is in
- the load path.
-
-public
- The directory available for the web server. Contains subdirectories for
- images, stylesheets, and javascripts. Also contains the dispatchers and the
- default HTML files. This should be set as the DOCUMENT_ROOT of your web
- server.
-
-script
- Helper scripts for automation and generation.
-
-test
- Unit and functional tests along with fixtures. When using the rails generate
- command, template test files will be generated for you and placed in this
- directory.
-
-vendor
- External libraries that the application depends on. Also includes the plugins
- subdirectory. If the app has frozen rails, those gems also go here, under
- vendor/rails/. This directory is in the load path.
diff --git a/railties/README.rdoc b/railties/README.rdoc
new file mode 100644
index 0000000000..a1718a7d96
--- /dev/null
+++ b/railties/README.rdoc
@@ -0,0 +1,25 @@
+= Railties -- Gluing the Engine to the Rails
+
+Railties is responsible to glue all frameworks together. Overall, it:
+
+* handles all the bootstrapping process for a Rails application;
+
+* manager rails command line interface;
+
+* provides Rails generators core;
+
+
+== Download
+
+The latest version of Railties can be installed with Rubygems:
+
+* gem install railties
+
+Documentation can be found at
+
+* http://api.rubyonrails.org
+
+
+== License
+
+Railties is released under the MIT license.
diff --git a/railties/Rakefile b/railties/Rakefile
index ddc872e18b..8e78d2ff4a 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -1,8 +1,8 @@
-gem 'rdoc', '= 2.2'
+gem 'rdoc', '>= 2.5.9'
require 'rdoc'
require 'rake'
require 'rake/testtask'
-require 'rake/rdoctask'
+require 'rdoc/task'
require 'rake/gempackagetask'
require 'date'
@@ -39,7 +39,7 @@ desc "Updates application README to the latest version Railties README"
task :update_readme do
readme = "lib/rails/generators/rails/app/templates/README"
rm readme
- cp "./README", readme
+ cp "./README.rdoc", readme
end
desc 'Generate guides (for authors), use ONLY=foo to process just "foo.textile"'
@@ -60,13 +60,13 @@ end
# Generate documentation ------------------------------------------------------------------
-Rake::RDocTask.new { |rdoc|
+RDoc::Task.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Railties -- Gluing the Engine to the Rails"
- rdoc.options << '--line-numbers' << '--inline-source' << '--accessor' << 'cattr_accessor=object'
+ rdoc.options << '-f' << 'horo'
+ rdoc.options << '--main' << 'README.rdoc'
rdoc.options << '--charset' << 'utf-8'
- rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
- rdoc.rdoc_files.include('README', 'CHANGELOG')
+ rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG')
rdoc.rdoc_files.include('lib/**/*.rb')
rdoc.rdoc_files.exclude('lib/rails/generators/**/templates/*')
}
diff --git a/railties/guides/rails_guides.rb b/railties/guides/rails_guides.rb
index e6f0b694a6..dfbb06cc76 100644
--- a/railties/guides/rails_guides.rb
+++ b/railties/guides/rails_guides.rb
@@ -1,6 +1,13 @@
pwd = File.dirname(__FILE__)
$:.unshift pwd
+# This is a predicate useful for the doc:guides task of applications.
+def bundler?
+ # Note that rake sets the cwd to the one that contains the Rakefile
+ # being executed.
+ File.exists?('Gemfile')
+end
+
# Loading Action Pack requires rack and erubis.
require 'rubygems'
@@ -20,7 +27,19 @@ begin
gem 'RedCloth', '>= 4.1.1'
require 'redcloth'
rescue Gem::LoadError
- $stderr.puts %(Generating Guides requires RedCloth 4.1.1+)
+ $stderr.puts('Generating guides requires RedCloth 4.1.1+.')
+ $stderr.puts(<<ERROR) if bundler?
+Please add
+
+ gem 'RedCloth', '>= 4.1.1'
+
+to the Gemfile, run
+
+ bundle install
+
+and try again.
+ERROR
+
exit 1
end
diff --git a/railties/guides/source/3_0_release_notes.textile b/railties/guides/source/3_0_release_notes.textile
index 7dcaf508c6..14da650e83 100644
--- a/railties/guides/source/3_0_release_notes.textile
+++ b/railties/guides/source/3_0_release_notes.textile
@@ -1,6 +1,6 @@
h2. Ruby on Rails 3.0 Release Notes
-Rails 3.0 is ponies and rainbows! It's going to cook you dinner and fold your laundry. You're going to wonder how life was ever possible before it arrived. It's the Best Version of Rails We've Ever Done!
+Rails 3.0 is ponies and rainbows! It's going to cook you dinner and fold your laundry. You're going to wonder how life was ever possible before it arrived. It's the Best Version of Rails We've Ever Done!
But seriously now, it's really good stuff. There are all the good ideas brought over from when the Merb team joined the party and brought a focus on framework agnosticism, slimmer and faster internals, and a handful of tasty APIs. If you're coming to Rails 3.0 from Merb 1.x, you should recognize lots. If you're coming from Rails 2.x, you're going to love it too.
@@ -12,7 +12,7 @@ Even if you don't give a hoot about any of our internal cleanups, Rails 3.0 is g
* Unobtrusive JavaScript helpers with drivers for Prototype, jQuery, and more coming (end of inline JS)
* Explicit dependency management with Bundler
-On top of all that, we've tried our best to deprecate the old APIs with nice warnings. That means that you can move your existing application to Rails 3 without immediately rewriting all your old code to the latest best practices.
+On top of all that, we've tried our best to deprecate the old APIs with nice warnings. That means that you can move your existing application to Rails 3 without immediately rewriting all your old code to the latest best practices.
These release notes cover the major upgrades, but don't include every little bug fix and change. Rails 3.0 consists of almost 4,000 commits by more than 250 authors! If you want to see everything, check out the "list of commits":http://github.com/rails/rails/commits/master in the main Rails repository on GitHub.
@@ -66,7 +66,7 @@ To help with the upgrade process, a plugin named "Rails Upgrade":http://github.c
Simply install the plugin, then run +rake rails:upgrade:check+ to check your app for pieces that need to be updated (with links to information on how to update them). It also offers a task to generate a +Gemfile+ based on your current +config.gem+ calls and a task to generate a new routes file from your current one. To get the plugin, simply run the following:
<shell>
-rails plugin install git://github.com/rails/rails_upgrade.git
+ruby script/plugin install git://github.com/rails/rails_upgrade.git
</shell>
You can see an example of how that works at "Rails Upgrade is now an Official Plugin":http://omgbloglol.com/post/364624593/rails-upgrade-is-now-an-official-plugin
@@ -596,3 +596,4 @@ h3. Credits
See the "full list of contributors to Rails":http://contributors.rubyonrails.org/ for the many people who spent many hours making Rails 3. Kudos to all of them.
Rails 3.0 Release Notes were compiled by "Mikel Lindsaar":http://lindsaar.net.
+
diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile
index 038ca903c1..ec2d5b2787 100644
--- a/railties/guides/source/action_controller_overview.textile
+++ b/railties/guides/source/action_controller_overview.textile
@@ -162,23 +162,38 @@ If you need a different session storage mechanism, you can change it in the +con
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
# (create the session table with "rake db:sessions:create")
-# ActionController::Base.session_store = :active_record_store
+# YourApp::Application.config.session_store :active_record_store
</ruby>
-Rails sets up a session key (the name of the cookie) and (for the CookieStore) a secret key used when signing the session data. These can also be changed in +config/initializers/session_store.rb+:
+Rails sets up a session key (the name of the cookie) when signing the session data. These can also be changed in +config/initializers/session_store.rb+:
<ruby>
-# Your secret key for verifying cookie session data integrity.
-# If you change this key, all old sessions will become invalid!
-# Make sure the secret is at least 30 characters and all random,
+# Be sure to restart your server when you modify this file.
+
+YourApp::Application.config.session_store :cookie_store, :key => '_your_app_session'
+</ruby>
+
+You can also pass a +:domain+ key and specify the domain name for the cookie:
+
+<ruby>
+# Be sure to restart your server when you modify this file.
+
+YourApp::Application.config.session_store :cookie_store, :key => '_your_app_session', :domain => ".example.com"
+</ruby>
+
+Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in +config/initializers/secret_token.rb+
+
+<ruby>
+# Be sure to restart your server when you modify this file.
+
+# Your secret key for verifying the integrity of signed cookies.
+# If you change this key, all old signed cookies will become invalid!
+# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
-ActionController::Base.session = {
- :key => '_yourappname_session',
- :secret => '4f50711b8f0f49572...'
-}
+YourApp::Application.config.secret_token = '49d3f3de9ed86c74b94ad6bd0...'
</ruby>
-NOTE: Changing the secret when using the CookieStore will invalidate all existing sessions.
+NOTE: Changing the secret when using the +CookieStore+ will invalidate all existing sessions.
h4. Accessing the Session
diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile
index f5e70aef41..53095a2bd3 100644
--- a/railties/guides/source/active_record_querying.textile
+++ b/railties/guides/source/active_record_querying.textile
@@ -337,7 +337,7 @@ Just like in Ruby. If you want a shorter syntax be sure to check out the "Hash C
h4. Hash Conditions
-Active Record also allows you to pass in a hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them:
+Active Record also allows you to pass in hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them:
NOTE: Only equality, range and subset checking are possible with Hash conditions.
@@ -347,7 +347,7 @@ h5. Equality Conditions
Client.where({ :locked => true })
</ruby>
-The field name does not have to be a symbol it can also be a string:
+The field name can also be a string:
<ruby>
Client.where({ 'locked' => true })
@@ -447,33 +447,33 @@ h4. Limit and Offset
To apply +LIMIT+ to the SQL fired by the +Model.find+, you can specify the +LIMIT+ using +limit+ and +offset+ methods on the relation.
-If you want to limit the amount of records to a certain subset of all the records retrieved you usually use +limit+ for this, sometimes coupled with +offset+. Limit is the maximum number of records that will be retrieved from a query, and offset is the number of records it will start reading from from the first record of the set. For example:
+You can use +limit+ to specify the number of records to be retrieved, and use +offset+ to specify the number of records to skip before starting to return the records. For example
<ruby>
Client.limit(5)
</ruby>
-This code will return a maximum of 5 clients and because it specifies no offset it will return the first 5 clients in the table. The SQL it executes will look like this:
+will return a maximum of 5 clients and because it specifies no offset it will return the first 5 in the table. The SQL it executes looks like this:
<sql>
SELECT * FROM clients LIMIT 5
</sql>
-Or chaining both +limit+ and +offset+:
+Adding +offset+ to that
<ruby>
-Client.limit(5).offset(5)
+Client.limit(5).offset(30)
</ruby>
-This code will return a maximum of 5 clients and because it specifies an offset this time, it will return these records starting from the 5th client in the clients table. The SQL looks like:
+will return instead a maximum of 5 clients beginning with the 31st. The SQL looks like:
<sql>
-SELECT * FROM clients LIMIT 5, 5
+SELECT * FROM clients LIMIT 5, 30
</sql>
h4. Group
-To apply +GROUP BY+ clause to the SQL fired by the finder, you can specify the +group+ method on the find.
+To apply a +GROUP BY+ clause to the SQL fired by the finder, you can specify the +group+ method on the find.
For example, if you want to find a collection of the dates orders were created on:
@@ -491,7 +491,7 @@ SELECT * FROM orders GROUP BY date(created_at)
h4. Having
-SQL uses +HAVING+ clause to specify conditions on the +GROUP BY+ fields. You can specify the +HAVING+ clause to the SQL fired by the +Model.find+ using +:having+ option on the find.
+SQL uses the +HAVING+ clause to specify conditions on the +GROUP BY+ fields. You can add the +HAVING+ clause to the SQL fired by the +Model.find+ by adding the +:having+ option to the find.
For example:
@@ -517,7 +517,7 @@ Any attempt to alter or destroy the readonly records will not succeed, raising a
Client.first.readonly(true)
</ruby>
-If you assign this record to a variable client, calling the following code will raise an +ActiveRecord::ReadOnlyRecord+ exception:
+For example, calling the following code will raise an +ActiveRecord::ReadOnlyRecord+ exception:
<ruby>
client = Client.first.readonly(true)
@@ -527,7 +527,9 @@ client.save
h4. Locking Records for Update
-Locking is helpful for preventing the race conditions when updating records in the database and ensuring atomic updated. Active Record provides two locking mechanism:
+Locking is helpful for preventing race conditions when updating records in the database and ensuring atomic updates.
+
+Active Record provides two locking mechanisms:
* Optimistic Locking
* Pessimistic Locking
@@ -538,7 +540,7 @@ Optimistic locking allows multiple users to access the same record for edits, an
<strong>Optimistic locking column</strong>
-In order to use optimistic locking, the table needs to have a column called +lock_version+. Each time the record is updated, Active Record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice will let the last one saved raise an +ActiveRecord::StaleObjectError+ exception if the first was also updated. Example:
+In order to use optimistic locking, the table needs to have a column called +lock_version+. Each time the record is updated, Active Record increments the +lock_version+ column. If an update request is made with a lower value in the +lock_version+ field than is currently in the +lock_version+ column in the database, the update request will fail with an +ActiveRecord::StaleObjectError+. Example:
<ruby>
c1 = Client.find(1)
@@ -569,7 +571,7 @@ end
h5. Pessimistic Locking
-Pessimistic locking uses locking mechanism provided by the underlying database. Passing +:lock => true+ to +Model.find+ obtains an exclusive lock on the selected rows. +Model.find+ using +:lock+ are usually wrapped inside a transaction for preventing deadlock conditions.
+Pessimistic locking uses a locking mechanism provided by the underlying database. Passing +:lock => true+ to +Model.find+ obtains an exclusive lock on the selected rows. +Model.find+ using +:lock+ are usually wrapped inside a transaction for preventing deadlock conditions.
For example:
@@ -601,7 +603,7 @@ end
h3. Joining Tables
-<tt>Model.find</tt> provides a +:joins+ option for specifying +JOIN+ clauses on the resulting SQL. There multiple different ways to specify the +:joins+ option:
+<tt>Model.find</tt> provides a +:joins+ option for specifying +JOIN+ clauses on the resulting SQL. There are multiple ways to specify the +:joins+ option:
h4. Using a String SQL Fragment
@@ -698,7 +700,7 @@ time_range = (Time.now.midnight - 1.day)..Time.now.midnight
Client.joins(:orders).where('orders.created_at' => time_range)
</ruby>
-An alternative and cleaner syntax to this is to nest the hash conditions:
+An alternative and cleaner syntax is to nest the hash conditions:
<ruby>
time_range = (Time.now.midnight - 1.day)..Time.now.midnight
@@ -727,7 +729,7 @@ This code looks fine at the first sight. But the problem lies within the total n
<strong>Solution to N <plus> 1 queries problem</strong>
-Active Record lets you specify all the associations in advanced that are going to be loaded. This is possible by specifying the +includes+ method of the +Model.find+ call. With +includes+, Active Record ensures that all the specified associations are loaded using minimum possible number of queries.
+Active Record lets you specify in advance all the associations that are going to be loaded. This is possible by specifying the +includes+ method of the +Model.find+ call. With +includes+, Active Record ensures that all of the specified associations are loaded using the minimum possible number of queries.
Revisiting the above case, we could rewrite +Client.all+ to use eager load addresses:
@@ -749,7 +751,7 @@ SELECT addresses.* FROM addresses
h4. Eager Loading Multiple Associations
-Active Record lets you eager load any possible number of associations with a single +Model.find+ call by using an array, hash, or a nested hash of array/hash with the +includes+ method.
+Active Record lets you eager load any number of associations with a single +Model.find+ call by using an array, hash, or a nested hash of array/hash with the +includes+ method.
h5. Array of Multiple Associations
@@ -762,10 +764,10 @@ This loads all the posts and the associated category and comments for each post.
h5. Nested Associations Hash
<ruby>
-Category.find(1).includes(:posts => [{:comments => :guest}, :tags])
+Category.includes(:posts => [{:comments => :guest}, :tags]).find(1)
</ruby>
-The above code finds the category with id 1 and eager loads all the posts associated with the found category. Additionally, it will also eager load every posts' tags and comments. Every comment's guest association will get eager loaded as well.
+This will find the category with id 1 and eager load all of the associated posts, the associated posts' tags and comments, and every comment's guest association.
h4. Specifying Conditions on Eager Loaded Associations
@@ -782,7 +784,7 @@ You can specify an exclamation point (<tt>!</tt>) on the end of the dynamic find
If you want to find both by name and locked, you can chain these finders together by simply typing +and+ between the fields for example +Client.find_by_first_name_and_locked("Ryan", true)+.
-There's another set of dynamic finders that let you find or create/initialize objects if they aren't found. These work in a similar fashion to the other finders and can be used like +find_or_create_by_first_name(params[:first_name])+. Using this will firstly perform a find and then create if the find returns +nil+. The SQL looks like this for +Client.find_or_create_by_first_name("Ryan")+:
+There's another set of dynamic finders that let you find or create/initialize objects if they aren't found. These work in a similar fashion to the other finders and can be used like +find_or_create_by_first_name(params[:first_name])+. Using this will first perform a find and then create if the find returns +nil+. The SQL looks like this for +Client.find_or_create_by_first_name("Ryan")+:
<sql>
SELECT * FROM clients WHERE (clients.first_name = 'Ryan') LIMIT 1
@@ -792,7 +794,7 @@ INSERT INTO clients (first_name, updated_at, created_at, orders_count, locked)
COMMIT
</sql>
-+find_or_create+'s sibling, +find_or_initialize+, will find an object and if it does not exist will act similar to calling +new+ with the arguments you passed in. For example:
++find_or_create+'s sibling, +find_or_initialize+, will find an object and if it does not exist will act similarly to calling +new+ with the arguments you passed in. For example:
<ruby>
client = Client.find_or_initialize_by_first_name('Ryan')
@@ -836,7 +838,7 @@ Client.exists?(1,2,3)
Client.exists?([1,2,3])
</ruby>
-Further more, +exists+ takes a +conditions+ option much like find:
+The +exists+ method may also take a +conditions+ option much like find:
<ruby>
Client.exists?(:conditions => "first_name = 'Ryan'")
diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile
index 84c33e34f9..37a65d211e 100644
--- a/railties/guides/source/active_record_validations_callbacks.textile
+++ b/railties/guides/source/active_record_validations_callbacks.textile
@@ -1,22 +1,22 @@
h2. Active Record Validations and Callbacks
-This guide teaches you how to hook into the lifecycle of your Active Record objects. You will learn how to validate the state of objects before they go into the database, and how to perform custom operations at certain points in the object lifecycle.
+This guide teaches you how to hook into the life cycle of your Active Record objects. You will learn how to validate the state of objects before they go into the database, and how to perform custom operations at certain points in the object life cycle.
After reading this guide and trying out the presented concepts, we hope that you'll be able to:
-* Understand the lifecycle of Active Record objects
+* Understand the life cycle of Active Record objects
* Use the built-in Active Record validation helpers
* Create your own custom validation methods
* Work with the error messages generated by the validation process
-* Create callback methods that respond to events in the object lifecycle
+* Create callback methods that respond to events in the object life cycle
* Create special classes that encapsulate common behavior for your callbacks
-* Create Observers that respond to lifecycle events outside of the original class
+* Create Observers that respond to life cycle events outside of the original class
endprologue.
-h3. The Object Lifecycle
+h3. The Object Life Cycle
-During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this <em>object lifecycle</em> so that you can control your application and its data.
+During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this <em>object life cycle</em> so that you can control your application and its data.
Validations allow you to ensure that only valid data is stored in your database. Callbacks and observers allow you to trigger logic before or after an alteration of an object's state.
@@ -57,7 +57,7 @@ We can see how it works by looking at some +rails console+ output:
=> false
</shell>
-Creating and saving a new record will send an SQL +INSERT+ operation to the database. Updating an existing record will send an SQL +UPDATE+ operation instead. Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not perform the +INSERT+ or +UPDATE+ operation. This helps to avoid storing an invalid object in the database. You can choose to have specific validations run when an object is created, saved, or updated.
+Creating and saving a new record will send a SQL +INSERT+ operation to the database. Updating an existing record will send a SQL +UPDATE+ operation instead. Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not perform the +INSERT+ or +UPDATE+ operation. This helps to avoid storing an invalid object in the database. You can choose to have specific validations run when an object is created, saved, or updated.
CAUTION: There are many ways to change the state of an object in the database. Some methods will trigger validations, but some will not. This means that it's possible to save an object in the database in an invalid state if you aren't careful.
@@ -799,7 +799,7 @@ h4. Customizing the Error Messages CSS
The selectors to customize the style of error messages are:
-* +.fieldWithErrors+ - Style for the form fields and labels with errors.
+* +.field_with_errors+ - Style for the form fields and labels with errors.
* +#errorExplanation+ - Style for the +div+ element with the error messages.
* +#errorExplanation h2+ - Style for the header of the +div+ element.
* +#errorExplanation p+ - Style for the paragraph that holds the message that appears right below the header of the +div+ element.
@@ -811,7 +811,7 @@ The name of the class and the id can be changed with the +:class+ and +:id+ opti
h4. Customizing the Error Messages HTML
-By default, form fields with errors are displayed enclosed by a +div+ element with the +fieldWithErrors+ CSS class. However, it's possible to override that.
+By default, form fields with errors are displayed enclosed by a +div+ element with the +field_with_errors+ CSS class. However, it's possible to override that.
The way form fields with errors are treated is defined by +ActionView::Base.field_error_proc+. This is a +Proc+ that receives two parameters:
@@ -838,7 +838,7 @@ This will result in something like the following:
h3. Callbacks Overview
-Callbacks are methods that get called at certain moments of an object's lifecycle. With callbacks it's possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database.
+Callbacks are methods that get called at certain moments of an object's life cycle. With callbacks it's possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database.
h4. Callback Registration
@@ -984,7 +984,7 @@ h3. Halting Execution
As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks, and the database operation to be executed.
-The whole callback chain is wrapped in a transaction. If any before callback method returns exactly +false+ or raises an exception the execution chain gets halted and a ROLLBACK is issued. After callbacks can only accomplish that by raising an exception.
+The whole callback chain is wrapped in a transaction. If any <em>before</em> callback method returns exactly +false+ or raises an exception the execution chain gets halted and a ROLLBACK is issued; <em>after</em> callbacks can only accomplish that by raising an exception.
WARNING. Raising an arbitrary exception may break code that expects +save+ and friends not to fail like that. The +ActiveRecord::Rollback+ exception is thought precisely to tell Active Record a rollback is going on. That one is internally captured but not reraised.
@@ -1020,7 +1020,7 @@ Like in validations, we can also make our callbacks conditional, calling them on
h4. Using +:if+ and +:unless+ with a Symbol
-You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. If this method returns +false+ the callback won't be executed. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check if the callback should be executed.
+You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. When using the +:if+ option, the callback won't be executed if the method returns false; when using the +:unless+ option, the callback won't be executed if the method returns true. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check if the callback should be executed.
<ruby>
class Order < ActiveRecord::Base
@@ -1135,7 +1135,7 @@ Observers are conventionally placed inside of your +app/models+ directory and re
config.active_record.observers = :user_observer
</ruby>
-As usual, settings in +config/environments+ take precedence over those in +config/environment.rb+. So, if you prefer that an observer not run in all environments, you can simply register it in a specific environment instead.
+As usual, settings in +config/environments+ take precedence over those in +config/environment.rb+. So, if you prefer that an observer doesn't run in all environments, you can simply register it in a specific environment instead.
h4. Sharing Observers
@@ -1162,6 +1162,7 @@ h3. Changelog
"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213/tickets/26-active-record-validations-and-callbacks
+* July 20, 2010: Fixed typos and rephrased some paragraphs for clarity. "Jaime Iniesta":http://jaimeiniesta.com
* May 24, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com
* May 15, 2010: Validation Errors section updated by "Emili Parreño":http://www.eparreno.com
* March 7, 2009: Callbacks revision by Trevor Turk
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index 58824d7aeb..54f766fffd 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -157,21 +157,6 @@ WARNING. Using +duplicable?+ is discouraged because it depends on a hard-coded l
NOTE: Defined in +active_support/core_ext/object/duplicable.rb+.
-h4. +returning+
-
-The method +returning+ yields its argument to a block and returns it. You typically use it with a mutable object that gets modified in the block:
-
-<ruby>
-def html_options_for_form(url_for_options, options, *parameters_for_url)
- returning options.stringify_keys do |html_options|
- html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
- html_options["action"] = url_for(url_for_options, *parameters_for_url)
- end
-end
-</ruby>
-
-NOTE: Defined in +active_support/core_ext/object/returning.rb+.
-
h4. +try+
Sometimes you want to call a method provided the receiver object is not +nil+, which is something you usually check first.
@@ -682,107 +667,6 @@ end
NOTE: Defined in +active_support/core_ext/module/attribute_accessors.rb+.
-h4. Method Delegation
-
-The class method +delegate+ offers an easy way to forward methods.
-
-For example, if +User+ has some details like the age factored out to +Profile+, it could be handy to still be able to access such attributes directly, <tt>user.age</tt>, instead of having to explicit the chain <tt>user.profile.age</tt>.
-
-That can be accomplished by hand:
-
-<ruby>
-class User
- has_one :profile
-
- def age
- profile.age
- end
-end
-</ruby>
-
-But with +delegate+ you can make that shorter and the intention even more obvious:
-
-<ruby>
-class User
- has_one :profile
-
- delegate :age, to => :profile
-end
-</ruby>
-
-The macro accepts more than one method:
-
-<ruby>
-class User
- has_one :profile
-
- delegate :age, :avatar, :twitter_username, to => :profile
-end
-</ruby>
-
-Methods can be delegated to objects returned by methods, as in the examples above, but also to instance variables, class variables, and constants. Just pass their names as symbols or strings, including the at signs in the last cases.
-
-For example, +ActionView::Base+ delegates +erb_trim_mode=+:
-
-<ruby>
-module ActionView
- class Base
- delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB'
- end
-end
-</ruby>
-
-In fact, you can delegate to any expression passed as a string. It will be evaluated in the context of the receiver. Controllers for example delegate alerts and notices to the current flash:
-
-<ruby>
-delegate :alert, :notice, :to => "request.flash"
-</ruby>
-
-If the target is +nil+ calling any delegated method will raise an exception even if +nil+ responds to such method. You can override this behavior setting the option +:allow_nil+ to true, in which case the forwarded call will simply return +nil+.
-
-If the target is a method, the name of delegated methods can also be prefixed. If the +:prefix+ option is set to (exactly) the +true+ object, the value of the +:to+ option is prefixed:
-
-<ruby>
-class Invoice
- belongs_to :customer
-
- # defines a method called customer_name
- delegate :name, :to => :customer, :prefix => true
-end
-</ruby>
-
-And a custom prefix can be set as well, in that case it does not matter wheter the target is a method or not:
-
-<ruby>
-class Account
- belongs_to :user
-
- # defines a method called admin_email
- delegate :email, :to => :user, :prefix => 'admin'
-end
-</ruby>
-
-NOTE: Defined in +active_support/core_ext/module/delegation.rb+.
-
-h4. Method Removal
-
-h5. +remove_possible_method+
-
-The method +remove_possible_method+ is like the standard +remove_method+, except it silently returns on failure:
-
-<ruby>
-class A; end
-
-A.class_eval do
- remove_method(:nonexistent) # raises NameError
- remove_possible_method(:nonexistent) # no problem, continue
-end
-</ruby>
-
-This may come in handy if you need to define a method that may already exist, since redefining a method issues a warning "method redefined; discarding old redefined_method_name".
-
-NOTE: Defined in +active_support/core_ext/module/remove_method.rb+.
-
h4. Parents
h5. +parent+
@@ -977,11 +861,128 @@ though an anonymous module is unreachable by definition.
NOTE: Defined in +active_support/core_ext/module/anonymous.rb+.
+h4. Method Delegation
+
+The macro +delegate+ offers an easy way to forward methods.
+
+Let's imagine that users in some application have login information in the +User+ model but name and other data in a separate +Profile+ model:
+
+<ruby>
+class User < ActiveRecord::Base
+ has_one :profile
+end
+</ruby>
+
+With that configuration you get a user's name via his profile, +user.profile.name+, but it could be handy to still be able to access such attribute directly:
+
+<ruby>
+class User < ActiveRecord::Base
+ has_one :profile
+
+ def name
+ profile.name
+ end
+end
+</ruby>
+
+That is what +delegate+ does for you:
+
+<ruby>
+class User < ActiveRecord::Base
+ has_one :profile
+
+ delegate :name, :to => :profile
+end
+</ruby>
+
+It is shorter, and the intention more obvious.
+
+The macro accepts several methods:
+
+<ruby>
+delegate :name, :age, :address, :twitter, :to => :profile
+</ruby>
+
+When interpolated into a string, the +:to+ option should become an expression that evaluates to the object the method is delegated to. Typically a string or symbol. Such a expression is evaluated in the context of the receiver:
+
+<ruby>
+# delegates to the Rails constant
+delegate :logger, :to => :Rails
+
+# delegates to the receiver's class
+delegate :table_name, :to => 'self.class'
+</ruby>
+
+WARNING: If the +:prefix+ option is +true+ this is less generic, see below.
+
+By default, if the delegation raises +NoMethodError+ and the target is +nil+ the exception is propagated. You can ask that +nil+ is returned instead with the +:allow_nil+ option:
+
+<ruby>
+delegate :name, :to => :profile, :allow_nil => true
+</ruby>
+
+With +:allow_nil+ the call +user.name+ returns +nil+ if the user has no profile.
+
+The option +:prefix+ adds a prefix to the name of the generated method. This may be handy for example to get a better name:
+
+<ruby>
+delegate :street, :to => :address, :prefix => true
+</ruby>
+
+The previous example generates +address_street+ rather than +street+.
+
+WARNING: Since in this case the name of the generated method is composed of the target object and target method names, the +:to+ option must be a method name.
+
+A custom prefix may also be configured:
+
+<ruby>
+delegate :size, :to => :attachment, :prefix => :avatar
+</ruby>
+
+In the previous example the macro generates +avatar_size+ rather than +size+.
+
+NOTE: Defined in +active_support/core_ext/module/delegation.rb+
+
+h4. Method Names
+
+The builtin methods +instance_methods+ and +methods+ return method names as strings or symbols depending on the Ruby version. Active Support defines +instance_method_names+ and +method_names+ to be equivalent to them, respectively, but always getting strings back.
+
+For example, +ActionView::Helpers::FormBuilder+ knows this array difference is going to work no matter the Ruby version:
+
+<ruby>
+self.field_helpers = (FormHelper.instance_method_names - ['form_for'])
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/module/method_names.rb+
+
+h4. Redefining Methods
+
+There are cases where you need to define a method with +define_method+, but don't know whether a method with that name already exists. If it does, a warning is issued if they are enabled. No big deal, but not clean either.
+
+The method +redefine_method+ prevents such a potential warning, removing the existing method before if needed. Rails uses it in a few places, for instance when it generates an association's API:
+
+<ruby>
+redefine_method("#{reflection.name}=") do |new_value|
+ association = association_instance_get(reflection.name)
+
+ if association.nil? || association.target != new_value
+ association = association_proxy_class.new(self, reflection)
+ end
+
+ association.replace(new_value)
+ association_instance_set(reflection.name, new_value.nil? ? nil : association)
+end
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/module/remove_method.rb+
+
h3. Extensions to +Class+
h4. Class Attributes
-The method +Class#class_attribute+ declares one or more inheritable class attributes that can be overridden at any level down the hierarchy:
+h5. +class_attribute+
+
+The method +class_attribute+ declares one or more inheritable class attributes that can be overridden at any level down the hierarchy:
<ruby>
class A
@@ -1005,17 +1006,50 @@ A.x # => :a
B.x # => :b
</ruby>
-For example that's the way the +allow_forgery_protection+ flag is implemented for controllers:
+For example +ActionMailer::Base+ defines:
+
+<ruby>
+class_attribute :default_params
+self.default_params = {
+ :mime_version => "1.0",
+ :charset => "UTF-8",
+ :content_type => "text/plain",
+ :parts_order => [ "text/plain", "text/enriched", "text/html" ]
+}.freeze
+</ruby>
+
+They can be also accessed and overridden at the instance level:
<ruby>
-class_attribute :allow_forgery_protection
-self.allow_forgery_protection = true
+A.x = 1
+
+a1 = A.new
+a2 = A.new
+a2.x = 2
+
+a1.x # => 1, comes from A
+a2.x # => 2, overridden in a2
</ruby>
-For convenience +class_attribute+ defines also a predicate, so that declaration also generates +allow_forgery_protection?+. Such predicate returns the double boolean negation of the value.
+The generation of the writer instance method can be prevented by setting the option +:instance_writer+ to false, as in
+
+<ruby>
+module AcitveRecord
+ class Base
+ class_attribute :table_name_prefix, :instance_writer => false
+ self.table_name_prefix = ""
+ end
+end
+</ruby>
+
+A model may find that option useful as a way to prevent mass-assignment from setting the attribute.
+
+For convenience +class_attribute+ defines also an instance predicate which is the double negation of what the instance reader returns. In the examples above it would be called +x?+.
NOTE: Defined in +active_support/core_ext/class/attribute.rb+
+h5. +cattr_reader+, +cattr_writer+, and +cattr_accessor+
+
The macros +cattr_reader+, +cattr_writer+, and +cattr_accessor+ are analogous to their +attr_*+ counterparts but for classes. They initialize a class variable to +nil+ unless it already exists, and generate the corresponding class methods to access it:
<ruby>
@@ -1026,17 +1060,18 @@ class MysqlAdapter < AbstractAdapter
end
</ruby>
-Instance methods are created as well for convenience. For example given
+Instance methods are created as well for convenience, they are just proxies to the class attribute. So, instances can change the class attribute, but cannot override it as it happens with +class_attribute+ (see above). For example given
<ruby>
-module ActionController
+module ActionView
class Base
- cattr_accessor :logger
+ cattr_accessor :field_error_proc
+ @@field_error_proc = Proc.new{ ... }
end
end
</ruby>
-we can access +logger+ in actions. The generation of the writer instance method can be prevented setting +:instance_writer+ to +false+ (not any false value, but exactly +false+):
+we can access +field_error_proc+ in views. The generation of the writer instance method can be prevented by setting +:instance_writer+ to +false+ (not any false value, but exactly +false+):
<ruby>
module ActiveRecord
@@ -1047,7 +1082,7 @@ module ActiveRecord
end
</ruby>
-A model may find that option useful as a way to prevent mass-assignment from setting the attribute.
+A model may find that option useful as a way to prevent mass-assignment from setting the attribute.
NOTE: Defined in +active_support/core_ext/class/attribute_accessors.rb+.
@@ -1092,21 +1127,6 @@ Since values are copied when a subclass is defined, if the base class changes th
NOTE: Defined in +active_support/core_ext/class/inheritable_attributes.rb+.
-There's a related macro called +superclass_delegating_accessor+, however, that does not copy the value when the base class is subclassed. Instead, it delegates reading to the superclass as long as the attribute is not set via its own writer. For example, +ActionMailer::Base+ defines +delivery_method+ this way:
-
-<ruby>
-module ActionMailer
- class Base
- superclass_delegating_accessor :delivery_method
- self.delivery_method = :smtp
- end
-end
-</ruby>
-
-If for whatever reason an application loads the definition of a mailer class and after that sets +ActionMailer::Base.delivery_method+, the mailer class will still see the new value. In addition, the mailer class is able to change the +delivery_method+ without affecting the value in the parent using its own inherited class attribute writer.
-
-NOTE: Defined in +active_support/core_ext/class/delegating_attributes.rb+.
-
h4. Subclasses & Descendants
h5. +subclasses+
@@ -1433,13 +1453,15 @@ end
That may be handy to compute method names in a language that follows that convention, for example JavaScript.
+INFO: As a rule of thumb you can think of +camelize+ as the inverse of +underscore+, though there are cases where that does not hold: <tt>"SSLError".underscore.camelize</tt> gives back <tt>"SslError"</tt>.
+
+camelize+ is aliased to +camelcase+.
NOTE: Defined in +active_support/core_ext/string/inflections.rb+.
h5. +underscore+
-The method +underscore+ is the inverse of +camelize+, explained above:
+The method +underscore+ goes the other way around, from camel case to paths:
<ruby>
"Product".underscore # => "product"
@@ -1472,6 +1494,8 @@ def load_missing_constant(from_mod, const_name)
end
</ruby>
+INFO: As a rule of thumb you can think of +underscore+ as the inverse of +camelize+, though there are cases where that does not hold. For example, <tt>"SSLError".underscore.camelize</tt> gives back <tt>"SslError"</tt>.
+
NOTE: Defined in +active_support/core_ext/string/inflections.rb+.
h5. +titleize+
@@ -1840,9 +1864,7 @@ h3. Extensions to +Enumerable+
h4. +group_by+
-Ruby 1.8.7 and up define +group_by+, and Active Support does it for previous versions.
-
-This iterator takes a block and builds an ordered hash with its return values as keys. Each key is mapped to the array of elements for which the block returned that value:
+Active Support redefines +group_by+ in Ruby 1.8.7 so that it returns an ordered hash as in 1.9:
<ruby>
entries_by_surname_initial = address_book.group_by do |entry|
@@ -1850,7 +1872,7 @@ entries_by_surname_initial = address_book.group_by do |entry|
end
</ruby>
-WARNING. Active Support redefines +group_by+ in Ruby 1.8.7 so that it still returns an ordered hash.
+Distinct block return values are added to the hash as they come, so that's the resulting order.
NOTE: Defined in +active_support/core_ext/enumerable.rb+.
@@ -2184,7 +2206,27 @@ NOTE: Defined in +active_support/core_ext/array/conversions.rb+.
h4. Wrapping
-The class method +Array.wrap+ behaves like the function +Array()+ except that it does not try to call +to_a+ on its argument. That changes the behavior for enumerables:
+The method +Array.wrap+ wraps its argument in an array unless it is already an array (or array-like).
+
+Specifically:
+
+* If the argument is +nil+ an empty list is returned.
+* Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned.
+* Otherwise, returns an array with the argument as its single element.
+
+<ruby>
+Array.wrap(nil) # => []
+Array.wrap([1, 2, 3]) # => [1, 2, 3]
+Array.wrap(0) # => [0]
+</ruby>
+
+This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences:
+
+* If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt> moves on to try +to_a+ if the returned value is +nil+, but <tt>Arraw.wrap</tt> returns such a +nil+ right away.
+* If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt> raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
+* It does not call +to_a+ on the argument, though special-cases +nil+ to return an empty array.
+
+The last point is particularly worth comparing for some enumerables:
<ruby>
Array.wrap(:foo => :bar) # => [{:foo => :bar}]
@@ -2194,6 +2236,16 @@ Array.wrap("foo\nbar") # => ["foo\nbar"]
Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8
</ruby>
+There's also a related idiom that uses the splat operator:
+
+<ruby>
+[*object]
+</ruby>
+
+which returns +[nil]+ for +nil+, and calls to <tt>Array(object)</tt> otherwise
+
+Thus, in this case the behavior is different for +nil+, and the differences with <tt>Kernel#Array</tt> explained above apply to the rest of +object+s.
+
NOTE: Defined in +active_support/core_ext/array/wrap.rb+.
h4. Grouping
@@ -2919,11 +2971,11 @@ Note in the previous example that increments may be negative.
To perform the computation the method first increments years, then months, then weeks, and finally days. This order is important towards the end of months. Say for example we are at the end of February of 2010, and we want to move one month and one day forward.
-The method +advance+ advances first one month, and the one day, the result is:
+The method +advance+ advances first one month, and then one day, the result is:
<ruby>
-Date.new(2010, 2, 28).advance(:months => 1, :day => 1)
-# => Sun, 28 Mar 2010
+Date.new(2010, 2, 28).advance(:months => 1, :days => 1)
+# => Sun, 29 Mar 2010
</ruby>
While if it did it the other way around the result would be different:
@@ -2949,6 +3001,26 @@ Date.new(2010, 1, 31).change(:month => 2)
# => ArgumentError: invalid date
</ruby>
+h5. Durations
+
+Durations can be added and substracted to dates:
+
+<ruby>
+d = Date.current
+# => Mon, 09 Aug 2010
+d + 1.year
+# => Tue, 09 Aug 2011
+d - 3.hours
+# => Sun, 08 Aug 2010 21:00:00 UTC +00:00
+</ruby>
+
+They translate to calls to +since+ or +advance+. For example here we get the correct jump in the calendar reform:
+
+<ruby>
+Date.new(1582, 10, 4) + 1.day
+# => Fri, 15 Oct 1582
+</ruby>
+
h5. Timestamps
INFO: The following methods return a +Time+ object if possible, otherwise a +DateTime+. If set, they honor the user time zone.
@@ -2993,30 +3065,30 @@ h4(#date-conversions). Conversions
h3. Extensions to +DateTime+
-NOTE: All the following methods are defined in +active_support/core_ext/date_time/calculations.rb+.
-
WARNING: +DateTime+ is not aware of DST rules and so some of these methods have edge cases when a DST change is going on. For example +seconds_since_midnight+ might not return the real amount in such a day.
h4(#calculations-datetime). Calculations
+NOTE: All the following methods are defined in +active_support/core_ext/date_time/calculations.rb+.
+
The class +DateTime+ is a subclass of +Date+ so by loading +active_support/core_ext/date/calculations.rb+ you inherit these methods and their aliases, except that they will always return datetimes:
<ruby>
yesterday
tomorrow
-beginning_of_week
-end_on_week
+beginning_of_week (monday, at_beginning_of_week)
+end_on_week (at_end_of_week)
next_week
months_ago
months_since
-beginning_of_month
-end_of_month
+beginning_of_month (at_beginning_of_month)
+end_of_month (at_end_of_month)
prev_month
next_month
-beginning_of_quarter
-end_of_quarter
-beginning_of_year
-end_of_year
+beginning_of_quarter (at_beginning_of_quarter)
+end_of_quarter (at_end_of_quarter)
+beginning_of_year (at_beginning_of_year)
+end_of_year (at_end_of_year)
years_ago
years_since
prev_year
@@ -3026,10 +3098,10 @@ next_year
The following methods are reimplemented so you do *not* need to load +active_support/core_ext/date/calculations.rb+ for these ones:
<ruby>
-beginning_of_day
+beginning_of_day (midnight, at_midnight, at_beginning_of_day)
end_of_day
ago
-since
+since (in)
</ruby>
On the other hand, +advance+ and +change+ are also defined and support more options, they are documented below.
@@ -3072,6 +3144,37 @@ now.utc? # => false
now.utc.utc? # => true
</ruby>
+h6(#datetime-advance). +advance+
+
+The most generic way to jump to another datetime is +advance+. This method receives a hash with keys +:years+, +:months+, +:weeks+, +:days+, +:hours+, +:minutes+, and +:seconds+, and returns a datetime advanced as much as the present keys indicate.
+
+<ruby>
+d = DateTime.current
+# => Thu, 05 Aug 2010 11:33:31 +0000
+d.advance(:years => 1, :months => 1, :days => 1, :hours => 1, :minutes => 1, :seconds => 1)
+# => Tue, 06 Sep 2011 12:34:32 +0000
+</ruby>
+
+This method first computes the destination date passing +:years+, +:months+, +:weeks+, and +:days+ to +Date#advance+ documented above. After that, it adjusts the time calling +since+ with the number of seconds to advance. This order is relevant, a different ordering would give different datetimes in some edge-cases. The example in +Date#advance+ applies, and we can extend it to show order relevance related to the time bits.
+
+If we first move the date bits (that have also a relative order of processing, as documented before), and then the time bits we get for example the following computation:
+
+<ruby>
+d = DateTime.new(2010, 2, 28, 23, 59, 59)
+# => Sun, 28 Feb 2010 23:59:59 +0000
+d.advance(:months => 1, :seconds => 1)
+# => Mon, 29 Mar 2010 00:00:00 +0000
+</ruby>
+
+but if we computed them the other way around, the result would be different:
+
+<ruby>
+d.advance(:seconds => 1).advance(:months => 1)
+# => Thu, 01 Apr 2010 00:00:00 +0000
+</ruby>
+
+WARNING: Since +DateTime+ is not DST-aware you can end up in a non-existing point in time with no warning or error telling you so.
+
h5(#datetime-changing-components). Changing Components
The method +change+ allows you to get a new datetime which is the same as the receiver except for the given options, which may include +:year+, +:month+, +:day+, +:hour+, +:min+, +:sec+, +:offset+, +:start+:
@@ -3104,15 +3207,144 @@ DateTime.current.change(:month => 2, :day => 30)
# => ArgumentError: invalid date
</ruby>
-h4(#datetime-conversions). Conversions
+h5. Durations
+
+Durations can be added and substracted to datetimes:
+
+<ruby>
+now = DateTime.current
+# => Mon, 09 Aug 2010 23:15:17 +0000
+now + 1.year
+# => Tue, 09 Aug 2011 23:15:17 +0000
+now - 1.week
+# => Mon, 02 Aug 2010 23:15:17 +0000
+</ruby>
+
+They translate to calls to +since+ or +advance+. For example here we get the correct jump in the calendar reform:
+
+<ruby>
+DateTime.new(1582, 10, 4, 23) + 1.hour
+# => Fri, 15 Oct 1582 00:00:00 +0000
+</ruby>
h3. Extensions to +Time+
-...
+h4(#time-calculations). Calculations
+
+NOTE: All the following methods are defined in +active_support/core_ext/time/calculations.rb+.
+
+Active Support adds to +Time+ many of the methods available for +DateTime+:
+
+<ruby>
+past?
+today?
+future?
+yesterday
+tomorrow
+seconds_since_midnight
+change
+advance
+ago
+since (in)
+beginning_of_day (midnight, at_midnight, at_beginning_of_day)
+end_of_day
+beginning_of_week (monday, at_beginning_of_week)
+end_on_week (at_end_of_week)
+next_week
+months_ago
+months_since
+beginning_of_month (at_beginning_of_month)
+end_of_month (at_end_of_month)
+prev_month
+next_month
+beginning_of_quarter (at_beginning_of_quarter)
+end_of_quarter (at_end_of_quarter)
+beginning_of_year (at_beginning_of_year)
+end_of_year (at_end_of_year)
+years_ago
+years_since
+prev_year
+next_year
+</ruby>
+
+They are analogous. Please refer to their documentation above and take into account the following differences:
+
+* +change+ accepts an additional +:usec+ option.
+* +Time+ understands DST, so you get correct DST calculations as in
+
+<ruby>
+Time.zone_default
+# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
+
+# In Barcelona, 2010/03/28 02:00 +0100 becomes 2010/03/28 03:00 +0200 due to DST.
+t = Time.local_time(2010, 3, 28, 1, 59, 59)
+# => Sun Mar 28 01:59:59 +0100 2010
+t.advance(:seconds => 1)
+# => Sun Mar 28 03:00:00 +0200 2010
+</ruby>
+
+* If +since+ or +ago+ jump to a time that can't be expressed with +Time+ a +DateTime+ object is returned instead.
+
+h4. Time Constructors
+
+Active Support defines +Time.current+ to be +Time.zone.now+ if there's a user time zone defined, with fallback to +Time.now+:
+
+<ruby>
+Time.zone_default
+# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
+Time.current
+# => Fri, 06 Aug 2010 17:11:58 CEST +02:00
+</ruby>
+
+Analogously to +DateTime+, the predicates +past?+, and +future?+ are relative to +Time.current+.
+
+Use the +local_time+ class method to create time objects honoring the user time zone:
+
+<ruby>
+Time.zone_default
+# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
+Time.local_time(2010, 8, 15)
+# => Sun Aug 15 00:00:00 +0200 2010
+</ruby>
+
+The +utc_time+ class method returns a time in UTC:
+
+<ruby>
+Time.zone_default
+# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
+Time.utc_time(2010, 8, 15)
+# => Sun Aug 15 00:00:00 UTC 2010
+</ruby>
+
+Both +local_time+ and +utc_time+ accept up to seven positional arguments: year, month, day, hour, min, sec, usec. Year is mandatory, month and day default to 1, and the rest default to 0.
+
+If the time to be constructed lies beyond the range supported by +Time+ in the runtime platform, usecs are discarded and a +DateTime+ object is returned instead.
+
+h5. Durations
+
+Durations can be added and substracted to time objects:
+
+<ruby>
+now = Time.current
+# => Mon, 09 Aug 2010 23:20:05 UTC +00:00
+now + 1.year
+# => Tue, 09 Aug 2011 23:21:11 UTC +00:00
+now - 1.week
+# => Mon, 02 Aug 2010 23:21:11 UTC +00:00
+</ruby>
+
+They translate to calls to +since+ or +advance+. For example here we get the correct jump in the calendar reform:
+
+<ruby>
+Time.utc_time(1582, 10, 3) + 5.days
+# => Mon Oct 18 00:00:00 UTC 1582
+</ruby>
h3. Extensions to +Process+
-...
+h4. +daemon+
+
+Ruby 1.9 provides +Process.daemon+, and Active Support defines it for previous versions. It accepts the same two arguments, whether it should chdir to the root directory (default, true), and whether it should inherit the standard file descriptors from the parent (default, false).
h3. Extensions to +File+
@@ -3188,4 +3420,5 @@ h3. Changelog
"Lighthouse ticket":https://rails.lighthouseapp.com/projects/16213/tickets/67
+* August 10, 2010: Starts to take shape, added to the index.
* April 18, 2009: Initial version by "Xavier Noria":credits.html#fxn
diff --git a/railties/guides/source/api_documentation_guidelines.textile b/railties/guides/source/api_documentation_guidelines.textile
new file mode 100644
index 0000000000..9f201de49b
--- /dev/null
+++ b/railties/guides/source/api_documentation_guidelines.textile
@@ -0,0 +1,187 @@
+h2. API Documentation Guidelines
+
+This guide documents the Ruby on Rails API documentation guidelines.
+
+endprologue.
+
+h3. RDoc
+
+The Rails API documentation is generated with RDoc 2.5. Please consult the "RDoc documentation":http://rdoc.rubyforge.org/RDoc.htmlFor for help with its markup.
+
+h3. Wording
+
+Write simple, declarative sentences. Brevity is a plus: get to the point.
+
+Write in present tense: "Returns a hash that...", rather than "Returned a hash that..." or "Will return a hash that...".
+
+Start comments in upper case, follow regular punctuation rules:
+
+<ruby>
+# Declares an attribute reader backed by an internally-named instance variable.
+def attr_internal_reader(*attrs)
+ ...
+end
+</ruby>
+
+Communicate to the reader the current way of doing things, both explicitly and implicitly. Use the recommended idioms in edge, reorder sections to emphasize favored approaches if needed, etc. The documentation should be a model for best practices and canonical, modern Rails usage.
+
+Documentation has to be concise but comprehensive. Explore and document edge cases. What happens if a module is anonymous? What if a collection is empty? What if an argument is nil?
+
+The proper names of Rails components have a space in between the words, like "Active Support". +ActiveRecord+ is a Ruby module, whereas Active Record is an ORM. Historically there has been lack of consistency regarding this, but we checked with David when docrails started. All Rails documentation consistently refer to Rails components by their proper name, and if in your next blog post or presentation you remember this tidbit and take it into account that'd be fenomenal :).
+
+Spell names correctly: HTML, MySQL, JavaScript, ERb. Use the article "an" for "SQL", as in "an SQL statement". Also "an SQLite database".
+
+h3. Example Code
+
+Choose meaningful examples that depict and cover the basics as well as interesting points or gotchas.
+
+Use two spaces to indent chunks of code.—that is two spaces with respect to the left margin; the examples
+themselves should use "Rails code conventions":http://rails.lighthouseapp.com/projects/8994/source-style.
+
+Short docs do not need an explicit "Examples" label to introduce snippets, they just follow paragraphs:
+
+<ruby>
+# Converts a collection of elements into a formatted string by calling
+# <tt>to_s</tt> on all elements and joining them.
+#
+# Blog.find(:all).to_formatted_s # => "First PostSecond PostThird Post"
+</ruby>
+
+On the other hand big chunks of structured documentation may have a separate "Examples" section:
+
+<ruby>
+# ==== Examples
+#
+# Person.exists?(5)
+# Person.exists?('5')
+# Person.exists?(:name => "David")
+# Person.exists?(['name LIKE ?', "%#{query}%"])
+</ruby>
+
+The result of expressions follow them and are introduced by "# => ", vertically aligned:
+
+<ruby>
+# For checking if a fixnum is even or odd.
+#
+# 1.even? # => false
+# 1.odd? # => true
+# 2.even? # => true
+# 2.odd? # => false
+</ruby>
+
+If a line is too long, the comment may be placed on the next line:
+
+<ruby>
+ # label(:post, :title)
+ # # => <label for="post_title">Title</label>
+ #
+ # label(:post, :title, "A short title")
+ # # => <label for="post_title">A short title</label>
+ #
+ # label(:post, :title, "A short title", :class => "title_label")
+ # # => <label for="post_title" class="title_label">A short title</label>
+</ruby>
+
+Avoid using any printing methods like +puts+ or +p+ for that purpose.
+
+On the other hand, regular comments do not use an arrow:
+
+<ruby>
+# polymorphic_url(record) # same as comment_url(record)
+</ruby>
+
+h3. Filenames
+
+As a rule of thumb use filenames relative to the application root:
+
+<plain>
+config/routes.rb # YES
+routes.rb # NO
+RAILS_ROOT/config/routes.rb # NO
+</plain>
+
+
+h3. Fonts
+
+h4. Fixed-width Font
+
+Use fixed-width fonts for:
+* constants, in particular class and module names
+* method names
+* literals like +nil+, +false+, +true+, +self+
+* symbols
+* method parameters
+* file names
+
+<ruby>
+# Copies the instance variables of +object+ into +self+.
+#
+# Instance variable names in the +exclude+ array are ignored. If +object+
+# responds to <tt>protected_instance_variables</tt> the ones returned are
+# also ignored. For example, Rails controllers implement that method.
+# ...
+def copy_instance_variables_from(object, exclude = [])
+ ...
+end
+</ruby>
+
+WARNING: Using a pair of +&#43;...&#43;+ for fixed-width font only works with *words*; that is: anything matching <tt>\A\w&#43;\z</tt>. For anything else use +&lt;tt&gt;...&lt;/tt&gt;+, notably symbols, setters, inline snippets, etc:
+
+h4. Regular Font
+
+When "true" and "false" are English words rather than Ruby keywords use a regular font:
+
+<ruby>
+# If <tt>reload_plugins?</tt> is false, add this to your plugin's <tt>init.rb</tt>
+# to make it reloadable:
+#
+# Dependencies.load_once_paths.delete lib_path
+</ruby>
+
+h3. Description Lists
+
+In lists of options, parameters, etc. use a hyphen between the item and its description (reads better than a colon because normally options are symbols):
+
+<ruby>
+# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
+</ruby>
+
+The description starts in upper case and ends with a full stop—it's standard English.
+
+h3. Dynamically Generated Methods
+
+Methods created with +(module|class)_eval(STRING)+ have a comment by their side with an instance of the generated code. That comment is 2 spaces apart from the template:
+
+<ruby>
+for severity in Severity.constants
+ class_eval <<-EOT, __FILE__, __LINE__
+ def #{severity.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block)
+ add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block)
+ end # end
+ #
+ def #{severity.downcase}? # def debug?
+ #{severity} >= @level # DEBUG >= @level
+ end # end
+ EOT
+end
+</ruby>
+
+If the resulting lines are too wide, say 200 columns or more, we put the comment above the call:
+
+<ruby>
+# def self.find_by_login_and_activated(*args)
+# options = args.extract_options!
+# ...
+# end
+self.class_eval %{
+ def self.#{method_id}(*args)
+ options = args.extract_options!
+ ...
+ end
+}
+</ruby>
+
+h3. Changelog
+
+* July 17, 2010: ported from the docrails wiki and revised by "Xavier Noria":credits.html#fxn
+
diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile
index c69f2ae8c9..b1ee4b8be4 100644
--- a/railties/guides/source/association_basics.textile
+++ b/railties/guides/source/association_basics.textile
@@ -1371,7 +1371,41 @@ The +:through+ option specifies a join model through which to perform the query.
h6(#has_many-uniq). +:uniq+
-Specify the +:uniq => true+ option to remove duplicates from the collection. This is most useful in conjunction with the +:through+ option.
+Set the +:uniq+ option to true to keep the collection free of duplicates. This is mostly useful together with the +:through+ option.
+
+<ruby>
+class Person < ActiveRecord::Base
+ has_many :readings
+ has_many :posts, :through => :readings
+end
+
+person = Person.create(:name => 'john')
+post = Post.create(:name => 'a1')
+person.posts << post
+person.posts << post
+person.posts.inspect # => [#<Post id: 5, name: "a1">, #<Post id: 5, name: "a1">]
+Reading.all.inspect # => [#<Reading id: 12, person_id: 5, post_id: 5>, #<Reading id: 13, person_id: 5, post_id: 5>]
+</ruby>
+
+In the above case there are two readings and +person.posts+ brings out both of them even though these records are pointing to the same post.
+
+Now let's set +:uniq+ to true:
+
+<ruby>
+class Person
+ has_many :readings
+ has_many :posts, :through => :readings, :uniq => true
+end
+
+person = Person.create(:name => 'honda')
+post = Post.create(:name => 'a1')
+person.posts << post
+person.posts << post
+person.posts.inspect # => [#<Post id: 7, name: "a1">]
+Reading.all.inspect # => [#<Reading id: 16, person_id: 7, post_id: 7>, #<Reading id: 17, person_id: 7, post_id: 7>]
+</ruby>
+
+In the above case there are still two readings. However +person.posts+ shows only one post because the collection loads only unique records.
h6(#has_many-validate). +:validate+
diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile
index 983c04c3d9..fb625f7a44 100644
--- a/railties/guides/source/command_line.textile
+++ b/railties/guides/source/command_line.textile
@@ -23,18 +23,18 @@ There are a few commands that are absolutely critical to your everyday usage of
* <tt>rake</tt>
* <tt>rails generate</tt>
* <tt>rails dbconsole</tt>
-* <tt>rails app_name</tt>
+* <tt>rails new app_name</tt>
Let's create a simple Rails application to step through each of these commands in context.
-h4. +rails+
+h4. +rails new+
-The first thing we'll want to do is create a new Rails application by running the +rails+ command after installing Rails.
+The first thing we'll want to do is create a new Rails application by running the +rails new+ command after installing Rails.
WARNING: You know you need the rails gem installed by typing +gem install rails+ first, if you don't have this installed, follow the instructions in the "Rails 3 Release Notes":/3_0_release_notes.html
<shell>
-$ rails commandsapp
+$ rails new commandsapp
create
create README
create .gitignore
@@ -352,7 +352,7 @@ $ mkdir gitapp
$ cd gitapp
$ git init
Initialized empty Git repository in .git/
-$ rails . --git --database=postgresql
+$ rails new . --git --database=postgresql
exists
create app/controllers
create app/helpers
@@ -397,7 +397,7 @@ development:
...
</shell>
-It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the +rails+ command to generate the basis of your app.
+It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the +rails new+ command to generate the basis of your app.
h4. +server+ with Different Backends
diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile
index 86655746e4..9e0c7cd060 100644
--- a/railties/guides/source/configuring.textile
+++ b/railties/guides/source/configuring.textile
@@ -181,7 +181,7 @@ There are only a few configuration options for Action View, starting with four o
* +config.action_view.warn_cache_misses+ tells Rails to display a warning whenever an action results in a cache miss on your view paths. The default is +false+.
-* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is <tt>Proc.new{ |html_tag, instance| "&lt;div class=\"fieldWithErrors\"&gt;#{html_tag}&lt;/div&gt;" }</tt>
+* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is <tt>Proc.new{ |html_tag, instance| %Q(%&lt;div class=&quot;field_with_errors&quot;&gt;#{html_tag}&lt;/div&gt;).html_safe }</tt>
* +config.action_view.default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+.
diff --git a/railties/guides/source/contributing_to_rails.textile b/railties/guides/source/contributing_to_rails.textile
index 5590895508..fb81bab98d 100644
--- a/railties/guides/source/contributing_to_rails.textile
+++ b/railties/guides/source/contributing_to_rails.textile
@@ -48,7 +48,7 @@ h4. Install git
Rails uses git for source code control. You won’t be able to do anything without the Rails source code, and this is a prerequisite. The "git homepage":http://git-scm.com/ has installation instructions. If you’re on OS X, use the "Git for OS X":http://code.google.com/p/git-osx-installer/ installer. If you're unfamiliar with git, there are a variety of resources on the net that will help you learn more:
-* "Everyday Git":http://www.kernel.org/pub/software/scm/git/docs/everyday.html will teach you just enough about git to get by.
+* "Everyday Git":http://www.kernel.org/pub/software/scm/git/docs/everyday.html will teach you just enough about git to get by.
* The "PeepCode screencast":https://peepcode.com/products/git on git ($9) is easier to follow.
* "GitHub":http://github.com/guides/home offers links to a variety of git resources.
* "Pro Git":http://progit.org/book/ is an entire book about git with a Creative Commons license.
@@ -58,30 +58,45 @@ h4. Get the Rails Source Code
Don’t fork the main Rails repository. Instead, you want to clone it to your own computer. Navigate to the folder where you want the source code (it will create its own /rails subdirectory) and run:
<shell>
-git clone git://github.com/rails/rails.git
+git clone git://github.com/rails/rails.git
cd rails
</shell>
-h4. Pick a Branch
+h4. Set up and Run the Tests
+
+All of the Rails tests must pass with any code you submit, otherwise you have no chance of getting code accepted. This means you need to be able to run the tests. First, you need to install all Rails dependencies with bundler:
-Currently, there is active work being done on both the 2-3-stable branch of Rails and on the master branch (which will become Rails 3.0). If you want to work with the master branch, you're all set. To work with 2.3, you'll need to set up and switch to your own local tracking branch:
+NOTE: Ensure you install bundler v1.0
<shell>
-git branch --track 2-3-stable origin/2-3-stable
-git checkout 2-3-stable
+gem install -v=1.0.0.rc.2 bundler
+bundle install --without db
</shell>
-TIP: You may want to "put your git branch name in your shell prompt":http://github.com/guides/put-your-git-branch-name-in-your-shell-prompt to make it easier to remember which version of the code you're working with.
+The second command will install all dependencies, except MySQL and PostgreSQL. We will come back at these soon. With dependencies installed, you can run the whole Rails test suite with:
-h4. Set up and Run the Tests
+<shell>
+rake test
+</shell>
-All of the Rails tests must pass with any code you submit, otherwise you have no chance of getting code accepted. This means you need to be able to run the tests. Rails needs the +mocha+ gem for running some tests, so install it with:
+You can also run tests for an specific framework, like Action Pack, by going into its directory and executing the same command:
<shell>
-gem install mocha
+cd actionpack
+rake test
</shell>
-For the tests that touch the database, this means creating test databases. If you're using MySQL, create a user named +rails+ with privileges on the test databases.
+h4. Testing Active Record
+
+By default, when you run Active Record tests, it will execute the test suite three times, one for each of the main databases: SQLite3, MySQL and PostgreSQL. If you are adding a feature that is not specific to the database, you can run the test suite (or just one file) for just one of them. Here is an example for SQLite3:
+
+<shell>
+cd activerecord
+rake test_sqlite3
+rake test_sqlite3 TEST=test/cases/validations_test.rb
+</shell>
+
+If you want to use another database, as MySQL, you need to create a user named +rails+ with privileges on the test databases.
<shell>
mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.*
@@ -90,7 +105,13 @@ mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.*
to 'rails'@'localhost';
</shell>
-Enter this from the +activerecord+ directory to create the test databases:
+Then ensure you run bundle install without the +--without db+ option:
+
+<shell>
+bundle install
+</shell>
+
+Finally, enter this from the +activerecord+ directory to create the test databases:
<shell>
rake mysql:build_databases
@@ -100,18 +121,26 @@ NOTE: Using the rake task to create the test databases ensures they have the cor
If you’re using another database, check the files under +activerecord/test/connections+ in the Rails source code for default connection information. You can edit these files if you _must_ on your machine to provide different credentials, but obviously you should not push any such changes back to Rails.
-Now if you go back to the root of the Rails source on your machine and run +rake+ with no parameters, you should see every test in all of the Rails components pass. If you want to run the all ActiveRecord tests (or just a single one) with another database adapter, enter this from the +activerecord+ directory:
+You can now run tests as you did for +sqlite3+:
<shell>
-rake test_sqlite3
-rake test_sqlite3 TEST=test/cases/validations_test.rb
+rake test_mysql
</shell>
-You can replace +sqlite3+ with +jdbcmysql+, +jdbcsqlite3+, +jdbcpostgresql+, +mysql+ or +postgresql+. Check out the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/ci_build.rb+ to see the test suite that the Rails continuous integration server runs.
+You can also +myqsl+ with +postgresql+, +jdbcmysql+, +jdbcsqlite3+ or +jdbcpostgresql+. Check out the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/ci_build.rb+ to see the test suite that the Rails continuous integration server runs.
+NOTE: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, and SQLite 3. Subtle differences between the various Active Record database adapters have been behind the rejection of many patches that looked OK when tested only against MySQL.
+h4. Older versions of Rails
-NOTE: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, and SQLite 3. Subtle differences between the various Active Record database adapters have been behind the rejection of many patches that looked OK when tested only against MySQL.
+If you want to work add a fix to older versions of Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to Rails 2.3 branch:
+
+<shell>
+git branch --track 2-3-stable origin/2-3-stable
+git checkout 2-3-stable
+</shell>
+
+TIP: You may want to "put your git branch name in your shell prompt":http://github.com/guides/put-your-git-branch-name-in-your-shell-prompt to make it easier to remember which version of the code you're working with.
h3. Helping to Resolve Existing Issues
@@ -231,15 +260,15 @@ h4. Update Rails
Update your copy of Rails. It’s pretty likely that other changes to core Rails have happened while you were working. Go get them:
<shell>
-git checkout master
-git pull
+git checkout master
+git pull
</shell>
Now reapply your patch on top of the latest changes:
<shell>
-git checkout my_new_branch
-git rebase master
+git checkout my_new_branch
+git rebase master
</shell>
No conflicts? Tests still pass? Change still seems reasonable to you? Then move on.
@@ -249,8 +278,8 @@ h4. Create a Patch
Now you can create a patch file to share with other developers (and with the Rails core team). Still in your branch, run
<shell>
-git commit -a
-git format-patch master --stdout > my_new_patch.diff
+git commit -a
+git format-patch master --stdout > my_new_patch.diff
</shell>
Sanity check the results of this operation: open the diff file in your text editor of choice and make sure that no unintended changes crept in.
@@ -275,4 +304,5 @@ h3. Changelog
* April 6, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com
* August 1, 2009: Updates/amplifications by "Mike Gunderloy":credits.html#mgunderloy
-* March 2, 2009: Initial draft by "Mike Gunderloy":credits.html#mgunderloy \ No newline at end of file
+* March 2, 2009: Initial draft by "Mike Gunderloy":credits.html#mgunderloy
+
diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile
index 1f1b7d076e..146b75da3f 100644
--- a/railties/guides/source/form_helpers.textile
+++ b/railties/guides/source/form_helpers.textile
@@ -647,7 +647,7 @@ the +params+ hash will contain
{'person' => {'name' => 'Henry'}}
</erb>
-and +params["name"]+ will retrieve the submitted value in the controller.
+and +params[:person][:name]+ will retrieve the submitted value in the controller.
Hashes can be nested as many levels as required, for example
diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile
index f547f29087..ffb0310816 100644
--- a/railties/guides/source/getting_started.textile
+++ b/railties/guides/source/getting_started.textile
@@ -213,9 +213,9 @@ If you open this file in a new Rails application, you'll see a default database
* The +test+ environment is used to run automated tests
* The +production+ environment is used when you deploy your application for the world to use.
-h5. Configuring a SQLite3 Database
+h5. Configuring an SQLite3 Database
-Rails comes with built-in support for "SQLite3":http://www.sqlite.org, which is a lightweight serverless database application. While a busy production environment may overload SQLite, it works well for development and testing. Rails defaults to using a SQLite database when creating a new project, but you can always change it later.
+Rails comes with built-in support for "SQLite3":http://www.sqlite.org, which is a lightweight serverless database application. While a busy production environment may overload SQLite, it works well for development and testing. Rails defaults to using an SQLite database when creating a new project, but you can always change it later.
Here's the section of the default configuration file (<tt>config/database.yml</tt>) with connection information for the development environment:
@@ -322,16 +322,15 @@ $ rm public/index.html
We need to do this as Rails will deliver any static file in the +public+ directory in preference to any dynamic contact we generate from the controllers.
-Now, you have to tell Rails where your actual home page is located. Open the file +config/routes.rb+ in your editor. This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. There are only comments in this file, so we need to add at the top the following:
+Now, you have to tell Rails where your actual home page is located. Open the file +config/routes.rb+ in your editor. This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with +:root to+, uncomment it and change it like the following:
<ruby>
-Blog::Application.routes.draw do |map|
+Blog::Application.routes.draw do
- root :to => "home#index"
-
- # The priority is based upon order of creation:
- # first created -> highest priority.
#...
+ # You can have the root of your site routed with "root"
+ # just remember to delete public/index.html.
+ root :to => "home#index"
</ruby>
The +root :to => "home#index"+ tells Rails to map the root action to the home controller's index action.
@@ -475,7 +474,7 @@ $ rails console
After the console loads, you can use it to work with your application's models:
<shell>
->> p = Post.create(:content => "A new post")
+>> p = Post.new(:content => "A new post")
=> #<Post id: nil, name: nil, title: nil,
content: "A new post", created_at: nil,
updated_at: nil>
@@ -1194,7 +1193,7 @@ The +destroy+ action will find the post we are looking at, locate the comment wi
h4. Deleting Associated Objects
-If you delete a post then it's associated comments will also need to be deleted. Otherwise they would simply occupy space in the database. Rails allows you to use the +dependent+ option of an association to achieve this. Modify the Post model, +app/models/post.rb+, as follows:
+If you delete a post then its associated comments will also need to be deleted. Otherwise they would simply occupy space in the database. Rails allows you to use the +dependent+ option of an association to achieve this. Modify the Post model, +app/models/post.rb+, as follows:
<ruby>
class Post < ActiveRecord::Base
@@ -1486,6 +1485,7 @@ h3. Changelog
"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/2
+* July 12, 2010: Fixes, editing and updating of code samples by "Jaime Iniesta":http://jaimeiniesta.com
* May 16, 2010: Added a section on configuration gotchas to address common encoding problems that people might have by "Yehuda Katz":http://www.yehudakatz.com
* April 30, 2010: Fixes, editing and updating of code samples by "Rohit Arondekar":http://rohitarondekar.com
* April 25, 2010: Couple of more minor fixups "Mikel Lindsaar":credits.html#raasdnil
diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile
index b09bb470ee..63d22db485 100644
--- a/railties/guides/source/i18n.textile
+++ b/railties/guides/source/i18n.textile
@@ -287,7 +287,7 @@ You most probably have something like this in one of your applications:
<ruby>
# config/routes.rb
-Yourapp::Application.routes.draw do |map|
+Yourapp::Application.routes.draw do
root :to => "home#index"
end
diff --git a/railties/guides/source/index.html.erb b/railties/guides/source/index.html.erb
index a930db0f1d..a0db87c188 100644
--- a/railties/guides/source/index.html.erb
+++ b/railties/guides/source/index.html.erb
@@ -88,6 +88,10 @@ Ruby on Rails Guides
<dl>
+<%= guide("Active Support Core Extensions", 'active_support_core_extensions.html') do %>
+ <p>This guide documents the Ruby core extensions defined in Active Support.</p>
+<% end %>
+
<%= guide("Rails Internationalization API", 'i18n.html') do %>
<p>This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country and so on.</p>
<% end %>
@@ -123,10 +127,6 @@ Ruby on Rails Guides
<%= guide("Caching with Rails", 'caching_with_rails.html', :ticket => 10) do %>
<p>Various caching techniques provided by Rails.</p>
<% end %>
-
-<%= guide("Contributing to Rails", 'contributing_to_rails.html') do %>
- <p>Rails is not &quot;somebody else's framework.&quot; This guide covers a variety of ways that you can get involved in the ongoing development of Rails.</p>
-<% end %>
</dl>
<h3>Extending Rails</h3>
@@ -147,6 +147,18 @@ Ruby on Rails Guides
<% end %>
</dl>
+<h3>Contributing to Rails</h3>
+
+<dl>
+ <%= guide("Contributing to Rails", 'contributing_to_rails.html') do %>
+ <p>Rails is not &quot;somebody else's framework.&quot; This guide covers a variety of ways that you can get involved in the ongoing development of Rails.</p>
+ <% end %>
+
+ <%= guide('API Documentation Guidelines', 'api_documentation_guidelines.html') do %>
+ <p>This guide documents the Ruby on Rails API documentation guidelines.</p>
+ <% end %>
+</dl>
+
<h3>Release Notes</h3>
<dl>
diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile
index 7a44ef7c77..f80c00b280 100644
--- a/railties/guides/source/initialization.textile
+++ b/railties/guides/source/initialization.textile
@@ -118,7 +118,7 @@ Now with Rails 3 we have a Gemfile which defines the basics our application need
# Bundle the extra gems:
# gem 'bj'
- # gem 'nokogiri', '1.4.1'
+ # gem 'nokogiri'
# gem 'sqlite3-ruby', :require => 'sqlite3'
# gem 'aws-s3', :require => 'aws/s3'
@@ -141,20 +141,21 @@ Here the only two gems we need are +rails+ and +sqlite3-ruby+, so it seems. This
* activesupport-3.0.0.beta4.gem
* arel-0.4.0.gem
* builder-2.1.2.gem
-* bundler-1.0.0.beta.2.gem
+* bundler-1.0.0.rc.2.gem
* erubis-2.6.6.gem
* i18n-0.4.1.gem
-* mail-2.2.4.gem
-* memcache-client-1.8.3.gem
+* mail-2.2.5.gem
+* memcache-client-1.8.5.gem
* mime-types-1.16.gem
+* nokogiri-1.4.3.1.gem
* polyglot-0.3.1.gem
-* rack-1.1.0.gem
-* rack-mount-0.6.5.gem
+* rack-1.2.1.gem
+* rack-mount-0.6.9.gem
* rack-test-0.5.4.gem
* rails-3.0.0.beta4.gem
* railties-3.0.0.beta4.gem
* rake-0.8.7.gem
-* sqlite3-ruby-1.3.0.gem
+* sqlite3-ruby-1.3.1.gem
* text-format-1.0.0.gem
* text-hyphen-1.0.0.gem
* thor-0.13.7.gem
@@ -257,28 +258,23 @@ This file goes on to define some classes that will be automatically loaded using
h4. Lazy Hooks
-At the top if the +ActiveSupport::Autoload+ module is the +def self.extended+ method:
-
-<ruby>
- def self.extended(base)
- base.extend(LazyLoadHooks)
- end
-</ruby>
-
-This is called when we extend this module into one of our classes or modules, such is the case later on when we call +extend ActiveSupport::LazyLoadHooks+ not only in the +ActiveSupport+ module, but in all of the Railtie modules (+ActiveRecord+ and so on), as well as in a couple of places.
-
+ActiveSupport::LazyLoadHooks+ is responsible for defining methods used for running hooks that are defined during the initialization process, such as the one defined inside the +active_record.initialize_timezone+ initializer:
<ruby>
initializer "active_record.initialize_timezone" do
- ActiveRecord.base_hook do
+ ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
self.default_timezone = :utc
end
end
</ruby>
-When the initializer is ran it defines a +base_hook+ for +ActiveRecord+ and will only run it when +run_base_hooks+ is called, which in the case of Active Record, is ran after the entirety of +activerecord/lib/active_record/base.rb+ has been evaluated.
+When the initializer runs it invokes method +on_load+ for +ActiveRecord+ and the block passed to it would be called only when +run_load_hooks+ is called.
+When the entirety of +activerecord/lib/active_record/base.rb+ has been evaluated then +run_load_hooks+ is invoked. The very last line of +activerecord/lib/active_record/base.rb+ is:
+
+<ruby>
+ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
+</ruby>
h4. +require 'active_support'+ cont'd.
diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb
index 501d8fef6d..cc7d54c256 100644
--- a/railties/guides/source/layout.html.erb
+++ b/railties/guides/source/layout.html.erb
@@ -62,6 +62,7 @@
</dl>
<dl class="R">
<dt>Digging Deeper</dt>
+ <dd><a href="active_support_core_extensions.html">Active Support Core Extensions</a></dd>
<dd><a href="i18n.html">Rails Internationalization API</a></dd>
<dd><a href="action_mailer_basics.html">Action Mailer Basics</a></dd>
<dd><a href="testing.html">Testing Rails Applications</a></dd>
@@ -71,13 +72,16 @@
<dd><a href="configuring.html">Configuring Rails Applications</a></dd>
<dd><a href="command_line.html">Rails Command Line Tools and Rake Tasks</a></dd>
<dd><a href="caching_with_rails.html">Caching with Rails</a></dd>
- <dd><a href="contributing_to_rails.html">Contributing to Rails</a></dd>
<dt>Extending Rails</dt>
<dd><a href="plugins.html">The Basics of Creating Rails Plugins</a></dd>
<dd><a href="rails_on_rack.html">Rails on Rack</a></dd>
<dd><a href="generators.html">Adding a Generator to Your Plugin</a></dd>
+ <dt>Contributing to Rails</dt>
+ <dd><a href="contributing_to_rails.html">Contributing to Rails</a></dd>
+ <dd><a href="api_documentation_guidelines.html">API Documentation Guidelines</a></dd>
+
<dt>Release Notes</dt>
<dd><a href="3_0_release_notes.html">Ruby on Rails 3.0 Release Notes</a></dd>
<dd><a href="2_3_release_notes.html">Ruby on Rails 2.3 Release Notes</a></dd>
diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile
index f4ba6dd53b..b9a201e5f0 100644
--- a/railties/guides/source/layouts_and_rendering.textile
+++ b/railties/guides/source/layouts_and_rendering.textile
@@ -1168,7 +1168,7 @@ Suppose you have the following +ApplicationController+ layout:
<body>
<div id="top_menu">Top menu items here</div>
<div id="menu">Menu items here</div>
- <div id="content"><%= yield(:content) or yield %></div>
+ <div id="content"><%= content_for?(:content) ? yield(:content) : yield %></div>
</body>
</html>
</erb>
diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile
index f05c0589b5..16f616a5bc 100644
--- a/railties/guides/source/migrations.textile
+++ b/railties/guides/source/migrations.textile
@@ -1,6 +1,6 @@
h2. Migrations
-Migrations are a convenient way for you to alter your database in a structured and organized manner. You could edit fragments of SQL by hand but you would then be responsible for telling other developers that they need to go and run it. You'd also have to keep track of which changes need to be run against the production machines next time you deploy.
+Migrations are a convenient way for you to alter your database in a structured and organized manner. You could edit fragments of SQL by hand but you would then be responsible for telling other developers that they need to go and run them. You'd also have to keep track of which changes need to be run against the production machines next time you deploy.
Active Record tracks which migrations have already been run so all you have to do is update your source and run +rake db:migrate+. Active Record will work out which migrations should be run. It will also update your +db/schema.rb+ file to match the structure of your database.
@@ -234,7 +234,7 @@ end
which creates a +products+ table with a column called +name+ (and as discussed below, an implicit +id+ column).
-The object yielded to the block allows you create columns on the table. There are two ways of doing this: The first (traditional) form looks like
+The object yielded to the block allows you to create columns on the table. There are two ways of doing this: The first (traditional) form looks like
<ruby>
create_table :products do |t|
@@ -250,7 +250,7 @@ create_table :products do |t|
end
</ruby>
-By default +create_table+ will create a primary key called +id+. You can change the name of the primary key with the +:primary_key+ option (don't forget to update the corresponding model) or if you don't want a primary key at all (for example for a HABTM join table) you can pass +:id => false+. If you need to pass database specific options you can place an SQL fragment in the +:options+ option. For example
+By default +create_table+ will create a primary key called +id+. You can change the name of the primary key with the +:primary_key+ option (don't forget to update the corresponding model) or if you don't want a primary key at all (for example for a HABTM join table) you can pass +:id => false+. If you need to pass database specific options you can place a SQL fragment in the +:options+ option. For example
<ruby>
create_table :products, :options => "ENGINE=BLACKHOLE" do |t|
@@ -592,4 +592,5 @@ h3. Changelog
"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/6
+* July 15, 2010: minor typos corrected by "Jaime Iniesta":http://jaimeiniesta.com
* September 14, 2008: initial version by "Frederick Cheung":credits.html#fcheung
diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile
index e853ba79e9..82f2276153 100644
--- a/railties/guides/source/plugins.textile
+++ b/railties/guides/source/plugins.textile
@@ -1274,7 +1274,7 @@ class YaffleMigrationGenerator < Rails::Generator::NamedBase
end
def yaffle_local_assigns
- returning(assigns = {}) do
+ {}.tap do |assigns|
assigns[:migration_action] = "add"
assigns[:class_name] = "add_yaffle_fields_to_#{custom_file_name}"
assigns[:table_name] = custom_file_name
diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile
index 72a76e25bb..625941ba31 100644
--- a/railties/guides/source/routing.textile
+++ b/railties/guides/source/routing.textile
@@ -3,16 +3,16 @@ h2. Rails Routing from the Outside In
This guide covers the user-facing features of Rails routing. By referring to this guide, you will be able to:
* Understand the code in +routes.rb+
-* Construct your own routes, using either the preferred resourceful style or with the @match@ method
+* Construct your own routes, using either the preferred resourceful style or with the <tt>match</tt> method
* Identify what parameters to expect an action to receive
-* Automatically create URLs using route helpers
+* Automatically create paths and URLs using route helpers
* Use advanced techniques such as constraints and Rack endpoints
endprologue.
h3. The Purpose of the Rails Router
-The Rails router recognizes URLs and dispatches them to a controller's action. It can also generate URLs, avoiding the need to hardcode URL strings in your views.
+The Rails router recognizes URLs and dispatches them to a controller's action. It can also generate paths and URLs, avoiding the need to hardcode strings in your views.
h4. Connecting URLs to Code
@@ -30,9 +30,9 @@ match "/patients/:id" => "patients#show"
the request is dispatched to the +patients+ controller's +show+ action with <tt>{ :id => "17" }</tt> in +params+.
-h4. Generating URLs from Code
+h4. Generating Paths and URLs from Code
-You can also generate URLs. If your application contains this code:
+You can also generate paths and URLs. If your application contains this code:
<ruby>
@patient = Patient.find(17)
@@ -76,7 +76,7 @@ resources :photos
creates seven different routes in your application, all mapping to the +Photos+ controller:
-|_. Verb |_.URL |_.action |_.used for|
+|_. Verb |_.Path |_.action |_.used for|
|GET |/photos |index |display a list of all photos|
|GET |/photos/new |new |return an HTML form for creating a new photo|
|POST |/photos |create |create a new photo|
@@ -85,7 +85,7 @@ creates seven different routes in your application, all mapping to the +Photos+
|PUT |/photos/:id |update |update a specific photo|
|DELETE |/photos/:id |destroy |delete a specific photo|
-h4. URLs and Paths
+h4. Paths and URLs
Creating a resourceful route will also expose a number of helpers to the controllers in your application. In the case of +resources :photos+:
@@ -130,7 +130,7 @@ resource :geocoder
creates six different routes in your application, all mapping to the +Geocoders+ controller:
-|_. Verb |_.URL |_.action |_.used for|
+|_. Verb |_.Path |_.action |_.used for|
|GET |/geocoder/new |new |return an HTML form for creating the geocoder|
|POST |/geocoder |create |create the new geocoder|
|GET |/geocoder |show |display the one and only geocoder resource|
@@ -160,7 +160,7 @@ end
This will create a number of routes for each of the +posts+ and +comments+ controller. For +Admin::PostsController+, Rails will create:
-|_. Verb |_.URL |_.action |_. helper |
+|_. Verb |_.Path |_.action |_. helper |
|GET |/admin/photos |index | admin_photos_path |
|GET |/admin/photos/new |new | new_admin_photos_path |
|POST |/admin/photos |create | admin_photos_path |
@@ -197,16 +197,16 @@ or, for a single case
resources :posts, :path => "/admin"
</ruby>
-In each of these cases, the named routes remain the same as if you did not use +scope+. In the last case, the following URLs map to +PostsController+:
+In each of these cases, the named routes remain the same as if you did not use +scope+. In the last case, the following paths map to +PostsController+:
-|_. Verb |_.URL |_.action |_. helper |
-|GET |photos |index | photos_path |
-|GET |photos/new |new | photos_path |
-|POST |photos |create | photos_path |
-|GET |photos/1 |show | photo_path(id) |
-|GET |photos/1/edit |edit | edit_photo_path(id) |
-|PUT |photos/1 |update | photo_path(id) |
-|DELETE |photos/1 |destroy | photo_path(id) |
+|_. Verb |_.Path |_.action |_. helper |
+|GET |/admin/photos |index | photos_path |
+|GET |/admin/photos/new |new | photos_path |
+|POST |/admin/photos |create | photos_path |
+|GET |/admin/photos/1 |show | photo_path(id) |
+|GET |/admin/photos/1/edit |edit | edit_photo_path(id) |
+|PUT |/admin/photos/1 |update | photo_path(id) |
+|DELETE |/admin/photos/1 |destroy | photo_path(id) |
h4. Nested Resources
@@ -232,7 +232,7 @@ end
In addition to the routes for magazines, this declaration will also route ads to an +AdsController+. The ad URLs require a magazine:
-|_.Verb |_.URL |_.action |_.used for|
+|_.Verb |_.Path |_.action |_.used for|
|GET |/magazines/1/ads |index |display a list of all ads for a specific magazine|
|GET |/magazines/1/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine|
|POST |/magazines/1/ads |create |create a new ad belonging to a specific magazine|
@@ -256,7 +256,7 @@ resources :publishers do
end
</ruby>
-Deeply-nested resources quickly become cumbersome. In this case, for example, the application would recognize URLs such as
+Deeply-nested resources quickly become cumbersome. In this case, for example, the application would recognize paths such as
<pre>
/publishers/1/magazines/2/photos/3
@@ -266,9 +266,9 @@ The corresponding route helper would be +publisher_magazine_photo_url+, requirin
TIP: _Resources should never be nested more than 1 level deep._
-h4. Creating URLs From Objects
+h4. Creating Paths and URLs From Objects
-In addition to using the routing helpers, Rails can also create URLs from an array of parameters. For example, suppose you have this set of routes:
+In addition to using the routing helpers, Rails can also create paths and URLs from an array of parameters. For example, suppose you have this set of routes:
<ruby>
resources :magazines do
@@ -340,7 +340,7 @@ resources :photos do
end
</ruby>
-This will enable Rails to recognize URLs such as +/photos/search+ with GET, and route to the +search+ action of +PhotosController+. It will also create the +search_photos_url+ and +search_photos_path+ route helpers.
+This will enable Rails to recognize paths such as +/photos/search+ with GET, and route to the +search+ action of +PhotosController+. It will also create the +search_photos_url+ and +search_photos_path+ route helpers.
Just as with member routes, you can pass +:on+ to a route:
@@ -380,7 +380,7 @@ You can set up as many dynamic segments within a regular route as you like. Anyt
match ':controller/:action/:id/:user_id'
</ruby>
-An incoming URL of +/photos/show/1/2+ will be dispatched to the +show+ action of the +PhotosController+. +params[:id]+ will be +"1"+, and +params[:user_id]+ will be +"2"+.
+An incoming path of +/photos/show/1/2+ will be dispatched to the +show+ action of the +PhotosController+. +params[:id]+ will be +"1"+, and +params[:user_id]+ will be +"2"+.
NOTE: You can't use +namespace+ or +:module+ with a +:controller+ path segment. If you need to do this then use a constraint on :controller that matches the namespace you require. e.g:
@@ -396,7 +396,7 @@ You can specify static segments when creating a route:
match ':controller/:action/:id/with_user/:user_id'
</ruby>
-This route would respond to URLs such as +/photos/show/1/with_user/2+. In this case, +params+ would be <tt>{ :controller => "photos", :action => "show", :id => "1", :user_id => "2" }</tt>.
+This route would respond to paths such as +/photos/show/1/with_user/2+. In this case, +params+ would be <tt>{ :controller => "photos", :action => "show", :id => "1", :user_id => "2" }</tt>.
h4. The Query String
@@ -406,7 +406,7 @@ The +params+ will also include any parameters from the query string. For example
match ':controller/:action/:id'
</ruby>
-An incoming URL of +/photos/show/1?user_id=2+ will be dispatched to the +show+ action of the +Photos+ controller. +params+ will be <tt>{ :controller => "photos", :action => "show", :id => "1", :user_id => "2" }</tt>.
+An incoming path of +/photos/show/1?user_id=2+ will be dispatched to the +show+ action of the +Photos+ controller. +params+ will be <tt>{ :controller => "photos", :action => "show", :id => "1", :user_id => "2" }</tt>.
h4. Defining Defaults
@@ -416,7 +416,7 @@ You do not need to explicitly use the +:controller+ and +:action+ symbols within
match 'photos/:id' => 'photos#show'
</ruby>
-With this route, Rails will match an incoming URL of +/photos/12+ to the +show+ action of +PhotosController+.
+With this route, Rails will match an incoming path of +/photos/12+ to the +show+ action of +PhotosController+.
You can also define other defaults in a route by supplying a hash for the +:defaults+ option. This even applies to parameters that you do not specify as dynamic segments. For example:
@@ -441,13 +441,13 @@ h4. Segment Constraints
You can use the +:constraints+ option to enforce a format for a dynamic segment:
<ruby>
-match 'photo/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ }
+match 'photos/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ }
</ruby>
-This route would match URLs such as +/photo/A12345+. You can more succinctly express the same route this way:
+This route would match paths such as +/photos/A12345+. You can more succinctly express the same route this way:
<ruby>
-match 'photo/:id' => 'photos#show', :id => /[A-Z]\d{5}/
+match 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
</ruby>
+:constraints+ takes regular expression. However note that regexp anchors can't be used within constraints. For example following route will not work:
@@ -472,7 +472,7 @@ You can also constrain a route based on any method on the <a href="action_contro
You specify a request-based constraint the same way that you specify a segment constraint:
<ruby>
-match "photo", :constraints => {:subdomain => "admin"}
+match "photos", :constraints => {:subdomain => "admin"}
</ruby>
You can also specify constrains in a block form:
@@ -511,10 +511,10 @@ h4. Route Globbing
Route globbing is a way to specify that a particular parameter should be matched to all the remaining parts of a route. For example
<ruby>
-match 'photo/*other' => 'photos#unknown'
+match 'photos/*other' => 'photos#unknown'
</ruby>
-This route would match +photo/12+ or +/photo/long/path/to/12+, setting +params[:other]+ to +"12"+ or +"long/path/to/12"+.
+This route would match +photos/12+ or +/photos/long/path/to/12+, setting +params[:other]+ to +"12"+ or +"long/path/to/12"+.
h4. Redirection
@@ -573,9 +573,9 @@ The +:controller+ option lets you explicitly specify a controller to use for the
resources :photos, :controller => "images"
</ruby>
-will recognize incoming URLs beginning with +/photo+ but route to the +Images+ controller:
+will recognize incoming paths beginning with +/photo+ but route to the +Images+ controller:
-|_. Verb |_.URL |_.action |
+|_. Verb |_.Path |_.action |
|GET |/photos |index |
|GET |/photos/new |new |
|POST |/photos |create |
@@ -584,7 +584,7 @@ will recognize incoming URLs beginning with +/photo+ but route to the +Images+ c
|PUT |/photos/1 |update |
|DELETE |/photos/1 |destroy |
-NOTE: Use +photos_path+, +new_photos_path+, etc. to generate URLs for this resource.
+NOTE: Use +photos_path+, +new_photos_path+, etc. to generate paths for this resource.
h4. Specifying Constraints
@@ -615,9 +615,9 @@ The +:as+ option lets you override the normal naming for the named route helpers
resources :photos, :as => "images"
</ruby>
-will recognize incoming URLs beginning with +/photos+ and route the requests to +PhotosController+:
+will recognize incoming paths beginning with +/photos+ and route the requests to +PhotosController+:
-|_.HTTP verb|_.URL |_.action |_.named helper |
+|_.HTTP verb|_.Path |_.action |_.named helper |
|GET |/photos |index | images_path |
|GET |/photos/new |new | new_image_path |
|POST |/photos |create | images_path |
@@ -628,20 +628,20 @@ will recognize incoming URLs beginning with +/photos+ and route the requests to
h4. Overriding the +new+ and +edit+ Segments
-The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in URLs:
+The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in paths:
<ruby>
resources :photos, :path_names => { :new => 'make', :edit => 'change' }
</ruby>
-This would cause the routing to recognize URLs such as
+This would cause the routing to recognize paths such as
<plain>
/photos/make
/photos/1/change
</plain>
-NOTE: The actual action names aren't changed by this option. The two URLs shown would still route to the new and edit actions.
+NOTE: The actual action names aren't changed by this option. The two paths shown would still route to the +new+ and +edit+ actions.
TIP: If you find yourself wanting to change this option uniformly for all of your routes, you can use a scope:
@@ -709,7 +709,7 @@ end
Rails now creates routes to the +CategoriesControlleR+.
-|_.HTTP verb|_.URL |_.action |
+|_.HTTP verb|_.Path |_.action |
|GET |/kategorien |index |
|GET |/kategorien/neu |new |
|POST |/kategorien |create |
@@ -762,6 +762,12 @@ formatted_users GET /users.:format {:controller=>"users", :action=>"index"}
POST /users.:format {:controller=>"users", :action=>"create"}
</pre>
+You may restrict the listing to the routes that map to a particular controller setting the +CONTROLLER+ environment variable:
+
+<shell>
+$ CONTROLLER=users rake routes
+</shell>
+
TIP: You'll find that the output from +rake routes+ is much more readable if you widen your terminal window until the output lines don't wrap.
h4. Testing Routes
diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile
index 60108d5ab3..8ce0001080 100644
--- a/railties/guides/source/security.textile
+++ b/railties/guides/source/security.textile
@@ -286,7 +286,7 @@ When filtering user input file names, _(highlight)don't try to remove malicious
<ruby>
def sanitize_filename(filename)
- returning filename.strip do |name|
+ filename.strip.tap do |name|
# NOTE: File.basename doesn't work right with Windows paths on Unix
# get only the filename, not the whole path
name.sub! /\A.*(\\|\/)/, ''
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 458177b954..5b26333486 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -149,6 +149,13 @@ module Rails
self
end
+ def load_console(sandbox=false)
+ initialize_console(sandbox)
+ railties.all { |r| r.load_console }
+ super()
+ self
+ end
+
def app
@app ||= begin
config.middleware = config.middleware.merge_into(default_middleware_stack)
@@ -198,6 +205,7 @@ module Rails
middleware.use ::ActionDispatch::ParamsParser
middleware.use ::Rack::MethodOverride
middleware.use ::ActionDispatch::Head
+ middleware.use ::ActionDispatch::BestStandardsSupport, config.action_dispatch.best_standards_support if config.action_dispatch.best_standards_support
end
end
@@ -212,5 +220,11 @@ module Rails
def initialize_generators
require "rails/generators"
end
+
+ def initialize_console(sandbox=false)
+ require "rails/console/app"
+ require "rails/console/sandbox" if sandbox
+ require "rails/console/helpers"
+ end
end
end
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index b9353ba336..60a93c9848 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -70,4 +70,4 @@ In addition to those, there are:
All commands can be run with -h for more information.
EOT
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index 50df6ba405..834a120c01 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -23,10 +23,7 @@ module Rails
opt.parse!(ARGV)
end
- @app.initialize!
- require "rails/console/app"
- require "rails/console/sandbox" if options[:sandbox]
- require "rails/console/helpers"
+ @app.load_console(options[:sandbox])
if options[:debugger]
begin
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index 0becb780de..e5af12b901 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -56,12 +56,11 @@ module Rails
return @options[method] if args.empty?
- if method == :rails
- namespace, configuration = :rails, args.shift
- elsif args.first.is_a?(Hash)
+ if method == :rails || args.first.is_a?(Hash)
namespace, configuration = method, args.shift
else
namespace, configuration = args.shift, args.shift
+ namespace = namespace.to_sym if namespace.respond_to?(:to_sym)
@options[:rails][method] = namespace
end
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 2f465670cf..521ed95447 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -50,4 +50,4 @@ module Rails
end
end
end
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index a27d38e23a..668ef48892 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -40,7 +40,7 @@ module Rails
end
end
- # Adds an entry into config/environment.rb for the supplied gem. If env
+ # Adds an entry into Gemfile for the supplied gem. If env
# is specified, add the gem to the given environment.
#
# ==== Example
@@ -100,7 +100,7 @@ module Rails
end
end
- # Adds a line inside the Initializer block for config/environment.rb.
+ # Adds a line inside the Application class for config/application.rb.
#
# If options :env is specified, the line is appended to the corresponding
# file in config/environments.
@@ -275,7 +275,7 @@ module Rails
#
def route(routing_code)
log :route, routing_code
- sentinel = /\.routes\.draw do(\s*\|map\|)?\s*$/
+ sentinel = /\.routes\.draw do(?:\s*\|map\|)?\s*$/
in_root do
inject_into_file 'config/routes.rb', "\n #{routing_code}\n", { :after => sentinel, :verbose => false }
diff --git a/railties/lib/rails/generators/active_model.rb b/railties/lib/rails/generators/active_model.rb
index fe6321af30..4b828340d2 100644
--- a/railties/lib/rails/generators/active_model.rb
+++ b/railties/lib/rails/generators/active_model.rb
@@ -9,16 +9,16 @@ module Rails
# For example:
#
# ActiveRecord::Generators::ActiveModel.find(Foo, "params[:id]")
- # #=> "Foo.find(params[:id])"
+ # # => "Foo.find(params[:id])"
#
# Datamapper::Generators::ActiveModel.find(Foo, "params[:id]")
- # #=> "Foo.get(params[:id])"
+ # # => "Foo.get(params[:id])"
#
# On initialization, the ActiveModel accepts the instance name that will
# receive the calls:
#
# builder = ActiveRecord::Generators::ActiveModel.new "@foo"
- # builder.save #=> "@foo.save"
+ # builder.save # => "@foo.save"
#
# The only exception in ActiveModel for ActiveRecord is the use of self.build
# instead of self.new.
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 67a9a6030d..fdfceb14ce 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -3,7 +3,7 @@ begin
rescue LoadError
puts "Thor is not available.\nIf you ran this command from a git checkout " \
"of Rails, please make sure thor is installed,\nand run this command " \
- "as `ruby /path/to/rails new myapp --dev`"
+ "as `ruby #{$0} #{ARGV.join(" ")} --dev`"
exit
end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 7d50e7da67..a90f109844 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -115,6 +115,7 @@ module Rails
directory "public/javascripts"
else
empty_directory_with_gitkeep "public/javascripts"
+ create_file "public/javascripts/application.js"
end
end
@@ -167,7 +168,7 @@ module Rails
:desc => "Path to an application builder (can be a filesystem path or URL)"
class_option :template, :type => :string, :aliases => "-m",
- :desc => "Path to an application template (can be a filesystem path or URL)."
+ :desc => "Path to an application template (can be a filesystem path or URL)"
class_option :dev, :type => :boolean, :default => false,
:desc => "Setup the application with Gemfile pointing to your Rails checkout"
@@ -178,11 +179,11 @@ module Rails
class_option :skip_gemfile, :type => :boolean, :default => false,
:desc => "Don't create a Gemfile"
- class_option :skip_activerecord, :type => :boolean, :aliases => "-O", :default => false,
- :desc => "Skip ActiveRecord files"
+ class_option :skip_active_record, :type => :boolean, :aliases => "-O", :default => false,
+ :desc => "Skip Active Record files"
- class_option :skip_testunit, :type => :boolean, :aliases => "-T", :default => false,
- :desc => "Skip TestUnit files"
+ class_option :skip_test_unit, :type => :boolean, :aliases => "-T", :default => false,
+ :desc => "Skip Test::Unit files"
class_option :skip_prototype, :type => :boolean, :aliases => "-J", :default => false,
:desc => "Skip Prototype files"
@@ -204,7 +205,7 @@ module Rails
super
- if !options[:skip_activerecord] && !DATABASES.include?(options[:database])
+ if !options[:skip_active_record] && !DATABASES.include?(options[:database])
raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}."
end
end
@@ -215,7 +216,7 @@ module Rails
empty_directory '.'
set_default_accessors!
- FileUtils.cd(destination_root)
+ FileUtils.cd(destination_root) unless options[:pretend]
end
def create_root_files
@@ -238,8 +239,8 @@ module Rails
template "config/boot.rb"
end
- def create_activerecord_files
- return if options[:skip_activerecord]
+ def create_active_record_files
+ return if options[:skip_active_record]
build(:database_yml)
end
@@ -280,7 +281,7 @@ module Rails
end
def create_test_files
- build(:test) unless options[:skip_testunit]
+ build(:test) unless options[:skip_test_unit]
end
def create_tmp_files
@@ -355,8 +356,13 @@ module Rails
@app_name ||= File.basename(destination_root)
end
+ def defined_app_const_base
+ Rails.respond_to?(:application) && defined?(Rails::Application) &&
+ Rails.application.is_a?(Rails::Application) && Rails.application.class.name.sub(/::Application$/, "")
+ end
+
def app_const_base
- @app_const_base ||= app_name.gsub(/\W/, '_').squeeze('_').camelize
+ @app_const_base ||= defined_app_const_base || app_name.gsub(/\W/, '_').squeeze('_').camelize
end
def app_const
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index a108968b97..1980684a94 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -28,7 +28,7 @@ gem '<%= gem_for_database %>'<% if require_for_database %>, :require => '<%= req
# Bundle the extra gems:
# gem 'bj'
-# gem 'nokogiri', '1.4.1'
+# gem 'nokogiri'
# gem 'sqlite3-ruby', :require => 'sqlite3'
# gem 'aws-s3', :require => 'aws/s3'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index 67a38ea1d5..7d63e99e05 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -1,6 +1,6 @@
require File.expand_path('../boot', __FILE__)
-<% unless options[:skip_activerecord] -%>
+<% unless options[:skip_active_record] -%>
require 'rails/all'
<% else -%>
# Pick the frameworks you want:
@@ -22,7 +22,7 @@ module <%= app_const_base %>
# -- all .rb files in that directory are automatically loaded.
# Custom directories with classes and modules you want to be autoloadable.
- # config.autoload_paths += %W( #{config.root}/extras )
+ # config.autoload_paths += %W(#{config.root}/extras)
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
@@ -39,12 +39,12 @@ module <%= app_const_base %>
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
- # Configure generators values. Many other options are available, be sure to check the documentation.
- # config.generators do |g|
- # g.orm :active_record
- # g.template_engine :erb
- # g.test_framework :test_unit, :fixture => true
- # end
+ # JavaScript files you want as :defaults (application.js is always included).
+<% if options[:skip_prototype] -%>
+ config.action_view.javascript_expansions[:defaults] = %w()
+<% else -%>
+ # config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
+<% end -%>
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
index f99ee937f3..fddf8b8144 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
@@ -4,7 +4,7 @@
# http://rubyforge.org/projects/ruby-oci8/
#
# Specify your database using any valid connection syntax, such as a
-# tnsnames.ora service name, or a SQL connect url string of the form:
+# tnsnames.ora service name, or an SQL connect string of the form:
#
# //host:[port][/service name]
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index 99758dfcf7..7616614aff 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -19,4 +19,8 @@
# Print deprecation notices to the Rails logger
config.active_support.deprecation = :log
+
+ # Only use best-standards-support built into browsers
+ config.action_dispatch.best_standards_support = :builtin
end
+
diff --git a/railties/lib/rails/generators/rails/app/templates/public/index.html b/railties/lib/rails/generators/rails/app/templates/public/index.html
index c65593e8bc..75d5edd06d 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/index.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/index.html
@@ -151,19 +151,6 @@
}
- #search {
- margin: 0;
- padding-top: 10px;
- padding-bottom: 10px;
- font-size: 11px;
- }
- #search input {
- font-size: 11px;
- margin: 2px;
- }
- #search-text {width: 170px}
-
-
#sidebar ul {
margin-left: 0;
padding-left: 0;
@@ -194,16 +181,6 @@
info.innerHTML = xhr.responseText;
info.style.display = 'block'
}
-
- function prepend() {
- search = document.getElementById('search-text');
- text = search.value;
- search.value = 'site:rubyonrails.org ' + text;
- }
-
- window.onload = function() {
- document.getElementById('search-text').value = '';
- }
</script>
</head>
<body>
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js
index 9fe6e1243b..06249a6ae3 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js
+++ b/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js
@@ -1,5 +1,5 @@
-/* Prototype JavaScript framework, version 1.6.1
- * (c) 2005-2009 Sam Stephenson
+/* Prototype JavaScript framework, version 1.7_rc2
+ * (c) 2005-2010 Sam Stephenson
*
* Prototype is freely distributable under the terms of an MIT-style license.
* For details, see the Prototype web site: http://www.prototypejs.org/
@@ -7,7 +7,8 @@
*--------------------------------------------------------------------------*/
var Prototype = {
- Version: '1.6.1',
+
+ Version: '1.7_rc2',
Browser: (function(){
var ua = navigator.userAgent;
@@ -17,13 +18,15 @@ var Prototype = {
Opera: isOpera,
WebKit: ua.indexOf('AppleWebKit/') > -1,
Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
- MobileSafari: /Apple.*Mobile.*Safari/.test(ua)
+ MobileSafari: /Apple.*Mobile/.test(ua)
}
})(),
BrowserFeatures: {
XPath: !!document.evaluate,
+
SelectorsAPI: !!document.querySelector,
+
ElementExtensions: (function() {
var constructor = window.Element || window.HTMLElement;
return !!(constructor && constructor.prototype);
@@ -32,9 +35,9 @@ var Prototype = {
if (typeof window.HTMLDivElement !== 'undefined')
return true;
- var div = document.createElement('div');
- var form = document.createElement('form');
- var isSupported = false;
+ var div = document.createElement('div'),
+ form = document.createElement('form'),
+ isSupported = false;
if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
isSupported = true;
@@ -50,6 +53,7 @@ var Prototype = {
JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
emptyFunction: function() { },
+
K: function(x) { return x }
};
@@ -79,6 +83,14 @@ var Try = {
/* Based on Alex Arnell's inheritance implementation. */
var Class = (function() {
+
+ var IS_DONTENUM_BUGGY = (function(){
+ for (var p in { toString: 1 }) {
+ if (p === 'toString') return false;
+ }
+ return true;
+ })();
+
function subclass() {};
function create() {
var parent = null, properties = $A(arguments);
@@ -99,7 +111,7 @@ var Class = (function() {
parent.subclasses.push(klass);
}
- for (var i = 0; i < properties.length; i++)
+ for (var i = 0, length = properties.length; i < length; i++)
klass.addMethods(properties[i]);
if (!klass.prototype.initialize)
@@ -110,10 +122,10 @@ var Class = (function() {
}
function addMethods(source) {
- var ancestor = this.superclass && this.superclass.prototype;
- var properties = Object.keys(source);
+ var ancestor = this.superclass && this.superclass.prototype,
+ properties = Object.keys(source);
- if (!Object.keys({ toString: true }).length) {
+ if (IS_DONTENUM_BUGGY) {
if (source.toString != Object.prototype.toString)
properties.push("toString");
if (source.valueOf != Object.prototype.valueOf)
@@ -123,7 +135,7 @@ var Class = (function() {
for (var i = 0, length = properties.length; i < length; i++) {
var property = properties[i], value = source[property];
if (ancestor && Object.isFunction(value) &&
- value.argumentNames().first() == "$super") {
+ value.argumentNames()[0] == "$super") {
var method = value;
value = (function(m) {
return function() { return ancestor[m].apply(this, arguments); };
@@ -147,7 +159,35 @@ var Class = (function() {
})();
(function() {
- var _toString = Object.prototype.toString;
+ var _toString = Object.prototype.toString,
+ NULL_TYPE = 'Null',
+ UNDEFINED_TYPE = 'Undefined',
+ BOOLEAN_TYPE = 'Boolean',
+ NUMBER_TYPE = 'Number',
+ STRING_TYPE = 'String',
+ OBJECT_TYPE = 'Object',
+ BOOLEAN_CLASS = '[object Boolean]',
+ NUMBER_CLASS = '[object Number]',
+ STRING_CLASS = '[object String]',
+ ARRAY_CLASS = '[object Array]',
+ NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON &&
+ typeof JSON.stringify === 'function' &&
+ JSON.stringify(0) === '0' &&
+ typeof JSON.stringify(Prototype.K) === 'undefined';
+
+ function Type(o) {
+ switch(o) {
+ case null: return NULL_TYPE;
+ case (void 0): return UNDEFINED_TYPE;
+ }
+ var type = typeof o;
+ switch(type) {
+ case 'boolean': return BOOLEAN_TYPE;
+ case 'number': return NUMBER_TYPE;
+ case 'string': return STRING_TYPE;
+ }
+ return OBJECT_TYPE;
+ }
function extend(destination, source) {
for (var property in source)
@@ -166,27 +206,70 @@ var Class = (function() {
}
}
- function toJSON(object) {
- var type = typeof object;
- switch (type) {
- case 'undefined':
- case 'function':
- case 'unknown': return;
- case 'boolean': return object.toString();
+ function toJSON(value) {
+ return Str('', { '': value }, []);
+ }
+
+ function Str(key, holder, stack) {
+ var value = holder[key],
+ type = typeof value;
+
+ if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
}
- if (object === null) return 'null';
- if (object.toJSON) return object.toJSON();
- if (isElement(object)) return;
+ var _class = _toString.call(value);
- var results = [];
- for (var property in object) {
- var value = toJSON(object[property]);
- if (!isUndefined(value))
- results.push(property.toJSON() + ': ' + value);
+ switch (_class) {
+ case NUMBER_CLASS:
+ case BOOLEAN_CLASS:
+ case STRING_CLASS:
+ value = value.valueOf();
+ }
+
+ switch (value) {
+ case null: return 'null';
+ case true: return 'true';
+ case false: return 'false';
}
- return '{' + results.join(', ') + '}';
+ type = typeof value;
+ switch (type) {
+ case 'string':
+ return value.inspect(true);
+ case 'number':
+ return isFinite(value) ? String(value) : 'null';
+ case 'object':
+
+ for (var i = 0, length = stack.length; i < length; i++) {
+ if (stack[i] === value) { throw new TypeError(); }
+ }
+ stack.push(value);
+
+ var partial = [];
+ if (_class === ARRAY_CLASS) {
+ for (var i = 0, length = value.length; i < length; i++) {
+ var str = Str(i, value, stack);
+ partial.push(typeof str === 'undefined' ? 'null' : str);
+ }
+ partial = '[' + partial.join(',') + ']';
+ } else {
+ var keys = Object.keys(value);
+ for (var i = 0, length = keys.length; i < length; i++) {
+ var key = keys[i], str = Str(key, value, stack);
+ if (typeof str !== "undefined") {
+ partial.push(key.inspect(true)+ ':' + str);
+ }
+ }
+ partial = '{' + partial.join(',') + '}';
+ }
+ stack.pop();
+ return partial;
+ }
+ }
+
+ function stringify(object) {
+ return JSON.stringify(object);
}
function toQueryString(object) {
@@ -198,9 +281,13 @@ var Class = (function() {
}
function keys(object) {
+ if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); }
var results = [];
- for (var property in object)
- results.push(property);
+ for (var property in object) {
+ if (object.hasOwnProperty(property)) {
+ results.push(property);
+ }
+ }
return results;
}
@@ -220,9 +307,15 @@ var Class = (function() {
}
function isArray(object) {
- return _toString.call(object) == "[object Array]";
+ return _toString.call(object) === ARRAY_CLASS;
}
+ var hasNativeIsArray = (typeof Array.isArray == 'function')
+ && Array.isArray([]) && !Array.isArray({});
+
+ if (hasNativeIsArray) {
+ isArray = Array.isArray;
+ }
function isHash(object) {
return object instanceof Hash;
@@ -233,11 +326,11 @@ var Class = (function() {
}
function isString(object) {
- return _toString.call(object) == "[object String]";
+ return _toString.call(object) === STRING_CLASS;
}
function isNumber(object) {
- return _toString.call(object) == "[object Number]";
+ return _toString.call(object) === NUMBER_CLASS;
}
function isUndefined(object) {
@@ -247,10 +340,10 @@ var Class = (function() {
extend(Object, {
extend: extend,
inspect: inspect,
- toJSON: toJSON,
+ toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON,
toQueryString: toQueryString,
toHTML: toHTML,
- keys: keys,
+ keys: Object.keys || keys,
values: values,
clone: clone,
isElement: isElement,
@@ -311,7 +404,7 @@ Object.extend(Function.prototype, (function() {
function delay(timeout) {
var __method = this, args = slice.call(arguments, 1);
- timeout = timeout * 1000
+ timeout = timeout * 1000;
return window.setTimeout(function() {
return __method.apply(__method, args);
}, timeout);
@@ -352,14 +445,28 @@ Object.extend(Function.prototype, (function() {
})());
-Date.prototype.toJSON = function() {
- return '"' + this.getUTCFullYear() + '-' +
- (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
- this.getUTCDate().toPaddedString(2) + 'T' +
- this.getUTCHours().toPaddedString(2) + ':' +
- this.getUTCMinutes().toPaddedString(2) + ':' +
- this.getUTCSeconds().toPaddedString(2) + 'Z"';
-};
+
+(function(proto) {
+
+
+ function toISOString() {
+ return this.getUTCFullYear() + '-' +
+ (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+ this.getUTCDate().toPaddedString(2) + 'T' +
+ this.getUTCHours().toPaddedString(2) + ':' +
+ this.getUTCMinutes().toPaddedString(2) + ':' +
+ this.getUTCSeconds().toPaddedString(2) + 'Z';
+ }
+
+
+ function toJSON() {
+ return this.toISOString();
+ }
+
+ if (!proto.toISOString) proto.toISOString = toISOString;
+ if (!proto.toJSON) proto.toJSON = toJSON;
+
+})(Date.prototype);
RegExp.prototype.match = RegExp.prototype.test;
@@ -418,6 +525,9 @@ Object.extend(String, {
});
Object.extend(String.prototype, (function() {
+ var NATIVE_JSON_PARSE_SUPPORT = window.JSON &&
+ typeof JSON.parse === 'function' &&
+ JSON.parse('{"test": true}').test;
function prepareReplacement(replacement) {
if (Object.isFunction(replacement)) return replacement;
@@ -484,8 +594,8 @@ Object.extend(String.prototype, (function() {
}
function extractScripts() {
- var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
- var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+ var matchAll = new RegExp(Prototype.ScriptFragment, 'img'),
+ matchOne = new RegExp(Prototype.ScriptFragment, 'im');
return (this.match(matchAll) || []).map(function(scriptTag) {
return (scriptTag.match(matchOne) || ['', ''])[1];
});
@@ -510,8 +620,9 @@ Object.extend(String.prototype, (function() {
return match[1].split(separator || '&').inject({ }, function(hash, pair) {
if ((pair = pair.split('='))[0]) {
- var key = decodeURIComponent(pair.shift());
- var value = pair.length > 1 ? pair.join('=') : pair[0];
+ var key = decodeURIComponent(pair.shift()),
+ value = pair.length > 1 ? pair.join('=') : pair[0];
+
if (value != undefined) value = decodeURIComponent(value);
if (key in hash) {
@@ -538,17 +649,9 @@ Object.extend(String.prototype, (function() {
}
function camelize() {
- var parts = this.split('-'), len = parts.length;
- if (len == 1) return parts[0];
-
- var camelized = this.charAt(0) == '-'
- ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
- : parts[0];
-
- for (var i = 1; i < len; i++)
- camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
-
- return camelized;
+ return this.replace(/-+(.)?/g, function(match, chr) {
+ return chr ? chr.toUpperCase() : '';
+ });
}
function capitalize() {
@@ -578,10 +681,6 @@ Object.extend(String.prototype, (function() {
return "'" + escapedString.replace(/'/g, '\\\'') + "'";
}
- function toJSON() {
- return this.inspect(true);
- }
-
function unfilterJSON(filter) {
return this.replace(filter || Prototype.JSONFilter, '$1');
}
@@ -589,29 +688,42 @@ Object.extend(String.prototype, (function() {
function isJSON() {
var str = this;
if (str.blank()) return false;
- str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
- return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+ str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@');
+ str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
+ str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+ return (/^[\],:{}\s]*$/).test(str);
}
function evalJSON(sanitize) {
- var json = this.unfilterJSON();
+ var json = this.unfilterJSON(),
+ cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
+ if (cx.test(json)) {
+ json = json.replace(cx, function (a) {
+ return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
try {
if (!sanitize || json.isJSON()) return eval('(' + json + ')');
} catch (e) { }
throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
}
+ function parseJSON() {
+ var json = this.unfilterJSON();
+ return JSON.parse(json);
+ }
+
function include(pattern) {
return this.indexOf(pattern) > -1;
}
function startsWith(pattern) {
- return this.indexOf(pattern) === 0;
+ return this.lastIndexOf(pattern, 0) === 0;
}
function endsWith(pattern) {
var d = this.length - pattern.length;
- return d >= 0 && this.lastIndexOf(pattern) === d;
+ return d >= 0 && this.indexOf(pattern, d) === d;
}
function empty() {
@@ -631,7 +743,7 @@ Object.extend(String.prototype, (function() {
sub: sub,
scan: scan,
truncate: truncate,
- strip: String.prototype.trim ? String.prototype.trim : strip,
+ strip: String.prototype.trim || strip,
stripTags: stripTags,
stripScripts: stripScripts,
extractScripts: extractScripts,
@@ -648,10 +760,9 @@ Object.extend(String.prototype, (function() {
underscore: underscore,
dasherize: dasherize,
inspect: inspect,
- toJSON: toJSON,
unfilterJSON: unfilterJSON,
isJSON: isJSON,
- evalJSON: evalJSON,
+ evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON,
include: include,
startsWith: startsWith,
endsWith: endsWith,
@@ -677,8 +788,9 @@ var Template = Class.create({
var before = match[1] || '';
if (before == '\\') return match[2];
- var ctx = object, expr = match[3];
- var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+ var ctx = object, expr = match[3],
+ pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+
match = pattern.exec(expr);
if (match == null) return before;
@@ -943,6 +1055,7 @@ var Enumerable = (function() {
find: detect
};
})();
+
function $A(iterable) {
if (!iterable) return [];
if ('toArray' in Object(iterable)) return iterable.toArray();
@@ -951,6 +1064,7 @@ function $A(iterable) {
return results;
}
+
function $w(string) {
if (!Object.isString(string)) return [];
string = string.strip();
@@ -1007,7 +1121,7 @@ Array.from = $A;
}
function reverse(inline) {
- return (inline !== false ? this : this.toArray())._reverse();
+ return (inline === false ? this.toArray() : this)._reverse();
}
function uniq(sorted) {
@@ -1037,15 +1151,6 @@ Array.from = $A;
return '[' + this.map(Object.inspect).join(', ') + ']';
}
- function toJSON() {
- var results = [];
- this.each(function(object) {
- var value = Object.toJSON(object);
- if (!Object.isUndefined(value)) results.push(value);
- });
- return '[' + results.join(', ') + ']';
- }
-
function indexOf(item, i) {
i || (i = 0);
var length = this.length;
@@ -1094,8 +1199,7 @@ Array.from = $A;
clone: clone,
toArray: clone,
size: size,
- inspect: inspect,
- toJSON: toJSON
+ inspect: inspect
});
var CONCAT_ARGUMENTS_BUGGY = (function() {
@@ -1116,6 +1220,7 @@ var Hash = Class.create(Enumerable, (function() {
this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
}
+
function _each(iterator) {
for (var key in this._object) {
var value = this._object[key], pair = [key, value];
@@ -1144,6 +1249,8 @@ var Hash = Class.create(Enumerable, (function() {
return Object.clone(this._object);
}
+
+
function keys() {
return this.pluck('key');
}
@@ -1193,10 +1300,6 @@ var Hash = Class.create(Enumerable, (function() {
}).join(', ') + '}>';
}
- function toJSON() {
- return Object.toJSON(this.toObject());
- }
-
function clone() {
return new Hash(this);
}
@@ -1216,7 +1319,7 @@ var Hash = Class.create(Enumerable, (function() {
update: update,
toQueryString: toQueryString,
inspect: inspect,
- toJSON: toJSON,
+ toJSON: toObject,
clone: clone
};
})());
@@ -1241,10 +1344,6 @@ Object.extend(Number.prototype, (function() {
return '0'.times(length - string.length) + string;
}
- function toJSON() {
- return isFinite(this) ? this.toString() : 'null';
- }
-
function abs() {
return Math.abs(this);
}
@@ -1266,7 +1365,6 @@ Object.extend(Number.prototype, (function() {
succ: succ,
times: times,
toPaddedString: toPaddedString,
- toJSON: toJSON,
abs: abs,
round: round,
ceil: ceil,
@@ -1558,14 +1656,14 @@ Ajax.Response = Class.create({
var transport = this.transport = request.transport,
readyState = this.readyState = transport.readyState;
- if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+ if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
this.status = this.getStatus();
this.statusText = this.getStatusText();
this.responseText = String.interpret(transport.responseText);
this.headerJSON = this._getHeaderJSON();
}
- if(readyState == 4) {
+ if (readyState == 4) {
var xml = transport.responseXML;
this.responseXML = Object.isUndefined(xml) ? null : xml;
this.responseJSON = this._getResponseJSON();
@@ -1705,7 +1803,6 @@ Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
});
-
function $(element) {
if (arguments.length > 1) {
for (var i = 0, elements = [], length = arguments.length; i < length; i++)
@@ -1730,7 +1827,7 @@ if (Prototype.BrowserFeatures.XPath) {
/*--------------------------------------------------------------------------*/
-if (!window.Node) var Node = { };
+if (!Node) var Node = { };
if (!Node.ELEMENT_NODE) {
Object.extend(Node, {
@@ -1750,29 +1847,26 @@ if (!Node.ELEMENT_NODE) {
}
+
(function(global) {
- var SETATTRIBUTE_IGNORES_NAME = (function(){
- var elForm = document.createElement("form");
- var elInput = document.createElement("input");
- var root = document.documentElement;
- elInput.setAttribute("name", "test");
- elForm.appendChild(elInput);
- root.appendChild(elForm);
- var isBuggy = elForm.elements
- ? (typeof elForm.elements.test == "undefined")
- : null;
- root.removeChild(elForm);
- elForm = elInput = null;
- return isBuggy;
+ var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){
+ try {
+ var el = document.createElement('<input name="x">');
+ return el.tagName.toLowerCase() === 'input' && el.name === 'x';
+ }
+ catch(err) {
+ return false;
+ }
})();
var element = global.Element;
+
global.Element = function(tagName, attributes) {
attributes = attributes || { };
tagName = tagName.toLowerCase();
var cache = Element.cache;
- if (SETATTRIBUTE_IGNORES_NAME && attributes.name) {
+ if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) {
tagName = '<' + tagName + ' name="' + attributes.name + '">';
delete attributes.name;
return Element.writeAttribute(document.createElement(tagName), attributes);
@@ -1780,12 +1874,23 @@ if (!Node.ELEMENT_NODE) {
if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
};
+
Object.extend(global.Element, element || { });
if (element) global.Element.prototype = element.prototype;
+
})(this);
-Element.cache = { };
Element.idCounter = 1;
+Element.cache = { };
+
+function purgeElement(element) {
+ var uid = element._prototypeUID;
+ if (uid) {
+ Element.stopObserving(element);
+ element._prototypeUID = void 0;
+ delete Element.Storage[uid];
+ }
+}
Element.Methods = {
visible: function(element) {
@@ -1798,7 +1903,6 @@ Element.Methods = {
return element;
},
-
hide: function(element) {
element = $(element);
element.style.display = 'none';
@@ -1861,6 +1965,10 @@ Element.Methods = {
function update(element, content) {
element = $(element);
+ var descendants = element.getElementsByTagName('*'),
+ i = descendants.length;
+ while (i--) purgeElement(descendants[i]);
+
if (content && content.toElement)
content = content.toElement();
@@ -1967,19 +2075,26 @@ Element.Methods = {
element = $(element);
var result = '<' + element.tagName.toLowerCase();
$H({'id': 'id', 'className': 'class'}).each(function(pair) {
- var property = pair.first(), attribute = pair.last();
- var value = (element[property] || '').toString();
+ var property = pair.first(),
+ attribute = pair.last(),
+ value = (element[property] || '').toString();
if (value) result += ' ' + attribute + '=' + value.inspect(true);
});
return result + '>';
},
- recursivelyCollect: function(element, property) {
+ recursivelyCollect: function(element, property, maximumLength) {
element = $(element);
+ maximumLength = maximumLength || -1;
var elements = [];
- while (element = element[property])
+
+ while (element = element[property]) {
if (element.nodeType == 1)
elements.push(Element.extend(element));
+ if (elements.length == maximumLength)
+ break;
+ }
+
return elements;
},
@@ -1998,13 +2113,17 @@ Element.Methods = {
},
immediateDescendants: function(element) {
- if (!(element = $(element).firstChild)) return [];
- while (element && element.nodeType != 1) element = element.nextSibling;
- if (element) return [element].concat($(element).nextSiblings());
- return [];
+ var results = [], child = $(element).firstChild;
+ while (child) {
+ if (child.nodeType === 1) {
+ results.push(Element.extend(child));
+ }
+ child = child.nextSibling;
+ }
+ return results;
},
- previousSiblings: function(element) {
+ previousSiblings: function(element, maximumLength) {
return Element.recursivelyCollect(element, 'previousSibling');
},
@@ -2019,9 +2138,10 @@ Element.Methods = {
},
match: function(element, selector) {
+ element = $(element);
if (Object.isString(selector))
- selector = new Selector(selector);
- return selector.match($(element));
+ return Prototype.Selector.match(element, selector);
+ return selector.match(element);
},
up: function(element, expression, index) {
@@ -2029,7 +2149,7 @@ Element.Methods = {
if (arguments.length == 1) return $(element.parentNode);
var ancestors = Element.ancestors(element);
return Object.isNumber(expression) ? ancestors[expression] :
- Selector.findElement(ancestors, expression, index);
+ Prototype.Selector.find(ancestors, expression, index);
},
down: function(element, expression, index) {
@@ -2041,29 +2161,40 @@ Element.Methods = {
previous: function(element, expression, index) {
element = $(element);
- if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
- var previousSiblings = Element.previousSiblings(element);
- return Object.isNumber(expression) ? previousSiblings[expression] :
- Selector.findElement(previousSiblings, expression, index);
+ if (Object.isNumber(expression)) index = expression, expression = false;
+ if (!Object.isNumber(index)) index = 0;
+
+ if (expression) {
+ return Prototype.Selector.find(element.previousSiblings(), expression, index);
+ } else {
+ return element.recursivelyCollect("previousSibling", index + 1)[index];
+ }
},
next: function(element, expression, index) {
element = $(element);
- if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
- var nextSiblings = Element.nextSiblings(element);
- return Object.isNumber(expression) ? nextSiblings[expression] :
- Selector.findElement(nextSiblings, expression, index);
+ if (Object.isNumber(expression)) index = expression, expression = false;
+ if (!Object.isNumber(index)) index = 0;
+
+ if (expression) {
+ return Prototype.Selector.find(element.nextSiblings(), expression, index);
+ } else {
+ var maximumLength = Object.isNumber(index) ? index + 1 : 1;
+ return element.recursivelyCollect("nextSibling", index + 1)[index];
+ }
},
select: function(element) {
- var args = Array.prototype.slice.call(arguments, 1);
- return Selector.findChildElements(element, args);
+ element = $(element);
+ var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
+ return Prototype.Selector.select(expressions, element);
},
adjacent: function(element) {
- var args = Array.prototype.slice.call(arguments, 1);
- return Selector.findChildElements(element.parentNode, args).without(element);
+ element = $(element);
+ var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
+ return Prototype.Selector.select(expressions, element.parentNode).without(element);
},
identify: function(element) {
@@ -2227,28 +2358,6 @@ Element.Methods = {
return element;
},
- getDimensions: function(element) {
- element = $(element);
- var display = Element.getStyle(element, 'display');
- if (display != 'none' && display != null) // Safari bug
- return {width: element.offsetWidth, height: element.offsetHeight};
-
- var els = element.style;
- var originalVisibility = els.visibility;
- var originalPosition = els.position;
- var originalDisplay = els.display;
- els.visibility = 'hidden';
- if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari
- els.position = 'absolute';
- els.display = 'block';
- var originalWidth = element.clientWidth;
- var originalHeight = element.clientHeight;
- els.display = originalDisplay;
- els.position = originalPosition;
- els.visibility = originalVisibility;
- return {width: originalWidth, height: originalHeight};
- },
-
makePositioned: function(element) {
element = $(element);
var pos = Element.getStyle(element, 'position');
@@ -2295,11 +2404,13 @@ Element.Methods = {
cumulativeOffset: function(element) {
var valueT = 0, valueL = 0;
- do {
- valueT += element.offsetTop || 0;
- valueL += element.offsetLeft || 0;
- element = element.offsetParent;
- } while (element);
+ if (element.parentNode) {
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ } while (element);
+ }
return Element._returnOffset(valueL, valueT);
},
@@ -2322,11 +2433,11 @@ Element.Methods = {
element = $(element);
if (Element.getStyle(element, 'position') == 'absolute') return element;
- var offsets = Element.positionedOffset(element);
- var top = offsets[1];
- var left = offsets[0];
- var width = element.clientWidth;
- var height = element.clientHeight;
+ var offsets = Element.positionedOffset(element),
+ top = offsets[1],
+ left = offsets[0],
+ width = element.clientWidth,
+ height = element.clientHeight;
element._originalLeft = left - parseFloat(element.style.left || 0);
element._originalTop = top - parseFloat(element.style.top || 0);
@@ -2346,8 +2457,8 @@ Element.Methods = {
if (Element.getStyle(element, 'position') == 'relative') return element;
element.style.position = 'relative';
- var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
- var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+ var top = parseFloat(element.style.top || 0) - (element._originalTop || 0),
+ left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
element.style.top = top + 'px';
element.style.left = left + 'px';
@@ -2378,9 +2489,10 @@ Element.Methods = {
},
viewportOffset: function(forElement) {
- var valueT = 0, valueL = 0;
+ var valueT = 0,
+ valueL = 0,
+ element = forElement;
- var element = forElement;
do {
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
@@ -2412,11 +2524,10 @@ Element.Methods = {
}, arguments[2] || { });
source = $(source);
- var p = Element.viewportOffset(source);
+ var p = Element.viewportOffset(source), delta = [0, 0], parent = null;
element = $(element);
- var delta = [0, 0];
- var parent = null;
+
if (Element.getStyle(element, 'position') == 'absolute') {
parent = Element.getOffsetParent(element);
delta = Element.viewportOffset(parent);
@@ -2495,8 +2606,7 @@ else if (Prototype.Browser.IE) {
Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
function(proceed, element) {
element = $(element);
- try { element.offsetParent }
- catch(e) { return $(document.body) }
+ if (!element.parentNode) return $(document.body);
var position = element.getStyle('position');
if (position !== 'static') return proceed(element);
element.setStyle({ position: 'relative' });
@@ -2510,8 +2620,7 @@ else if (Prototype.Browser.IE) {
Element.Methods[method] = Element.Methods[method].wrap(
function(proceed, element) {
element = $(element);
- try { element.offsetParent }
- catch(e) { return Element._returnOffset(0,0) }
+ if (!element.parentNode) return Element._returnOffset(0, 0);
var position = element.getStyle('position');
if (position !== 'static') return proceed(element);
var offsetParent = element.getOffsetParent();
@@ -2525,14 +2634,6 @@ else if (Prototype.Browser.IE) {
);
});
- Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
- function(proceed, element) {
- try { element.offsetParent }
- catch(e) { return Element._returnOffset(0,0) }
- return proceed(element);
- }
- );
-
Element.Methods.getStyle = function(element, style) {
element = $(element);
style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
@@ -2576,10 +2677,9 @@ else if (Prototype.Browser.IE) {
Element._attributeTranslations = (function(){
- var classProp = 'className';
- var forProp = 'for';
-
- var el = document.createElement('div');
+ var classProp = 'className',
+ forProp = 'for',
+ el = document.createElement('div');
el.setAttribute(classProp, 'x');
@@ -2622,10 +2722,9 @@ else if (Prototype.Browser.IE) {
},
_getEv: (function(){
- var el = document.createElement('div');
+ var el = document.createElement('div'), f;
el.onclick = Prototype.emptyFunction;
var value = el.getAttribute('onclick');
- var f;
if (String(value).indexOf('{') > -1) {
f = function(element, attribute) {
@@ -2753,7 +2852,7 @@ else if (Prototype.Browser.WebKit) {
(value < 0.00001) ? 0 : value;
if (value == 1)
- if(element.tagName.toUpperCase() == 'IMG' && element.width) {
+ if (element.tagName.toUpperCase() == 'IMG' && element.width) {
element.width++; element.width--;
} else try {
var n = document.createTextNode(' ');
@@ -2793,8 +2892,8 @@ if ('outerHTML' in document.documentElement) {
var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
if (Element._insertionTranslations.tags[tagName]) {
- var nextSibling = element.next();
- var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+ var nextSibling = element.next(),
+ fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
parent.removeChild(element);
if (nextSibling)
fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
@@ -2816,11 +2915,17 @@ Element._returnOffset = function(l, t) {
};
Element._getContentFromAnonymousElement = function(tagName, html) {
- var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
+ var div = new Element('div'),
+ t = Element._insertionTranslations.tags[tagName];
if (t) {
div.innerHTML = t[0] + html + t[1];
- t[2].times(function() { div = div.firstChild });
- } else div.innerHTML = html;
+ for (var i = t[2]; i--; ) {
+ div = div.firstChild;
+ }
+ }
+ else {
+ div.innerHTML = html;
+ }
return $A(div.childNodes);
};
@@ -2877,7 +2982,7 @@ Object.extend(Element, Element.Methods);
div = null;
-})(document.createElement('div'))
+})(document.createElement('div'));
Element.extend = (function() {
@@ -2885,8 +2990,8 @@ Element.extend = (function() {
if (typeof window.Element != 'undefined') {
var proto = window.Element.prototype;
if (proto) {
- var id = '_' + (Math.random()+'').slice(2);
- var el = document.createElement(tagName);
+ var id = '_' + (Math.random()+'').slice(2),
+ el = document.createElement(tagName);
proto[id] = 'x';
var isBuggy = (el[id] !== 'x');
delete proto[id];
@@ -2953,10 +3058,14 @@ Element.extend = (function() {
return extend;
})();
-Element.hasAttribute = function(element, attribute) {
- if (element.hasAttribute) return element.hasAttribute(attribute);
- return Element.Methods.Simulated.hasAttribute(element, attribute);
-};
+if (document.documentElement.hasAttribute) {
+ Element.hasAttribute = function(element, attribute) {
+ return element.hasAttribute(attribute);
+ };
+}
+else {
+ Element.hasAttribute = Element.Methods.Simulated.hasAttribute;
+}
Element.addMethods = function(methods) {
var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
@@ -3020,8 +3129,9 @@ Element.addMethods = function(methods) {
klass = 'HTML' + tagName.capitalize() + 'Element';
if (window[klass]) return window[klass];
- var element = document.createElement(tagName);
- var proto = element['__proto__'] || element.constructor.prototype;
+ var element = document.createElement(tagName),
+ proto = element['__proto__'] || element.constructor.prototype;
+
element = null;
return proto;
}
@@ -3104,8 +3214,8 @@ Element.addMethods({
uid = 0;
} else {
if (typeof element._prototypeUID === "undefined")
- element._prototypeUID = [Element.Storage.UID++];
- uid = element._prototypeUID[0];
+ element._prototypeUID = Element.Storage.UID++;
+ uid = element._prototypeUID;
}
if (!Element.Storage[uid])
@@ -3150,770 +3260,1698 @@ Element.addMethods({
}
}
return Element.extend(clone);
+ },
+
+ purge: function(element) {
+ if (!(element = $(element))) return;
+ purgeElement(element);
+
+ var descendants = element.getElementsByTagName('*'),
+ i = descendants.length;
+
+ while (i--) purgeElement(descendants[i]);
+
+ return null;
}
});
-/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
- * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
- * license. Please see http://www.yui-ext.com/ for more information. */
-
-var Selector = Class.create({
- initialize: function(expression) {
- this.expression = expression.strip();
-
- if (this.shouldUseSelectorsAPI()) {
- this.mode = 'selectorsAPI';
- } else if (this.shouldUseXPath()) {
- this.mode = 'xpath';
- this.compileXPathMatcher();
- } else {
- this.mode = "normal";
- this.compileMatcher();
- }
- },
+(function() {
- shouldUseXPath: (function() {
+ function toDecimal(pctString) {
+ var match = pctString.match(/^(\d+)%?$/i);
+ if (!match) return null;
+ return (Number(match[1]) / 100);
+ }
- var IS_DESCENDANT_SELECTOR_BUGGY = (function(){
- var isBuggy = false;
- if (document.evaluate && window.XPathResult) {
- var el = document.createElement('div');
- el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>';
+ function getPixelValue(value, property) {
+ if (Object.isElement(value)) {
+ element = value;
+ value = element.getStyle(property);
+ }
+ if (value === null) {
+ return null;
+ }
- var xpath = ".//*[local-name()='ul' or local-name()='UL']" +
- "//*[local-name()='li' or local-name()='LI']";
+ if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) {
+ return window.parseFloat(value);
+ }
- var result = document.evaluate(xpath, el, null,
- XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+ if (/\d/.test(value) && element.runtimeStyle) {
+ var style = element.style.left, rStyle = element.runtimeStyle.left;
+ element.runtimeStyle.left = element.currentStyle.left;
+ element.style.left = value || 0;
+ value = element.style.pixelLeft;
+ element.style.left = style;
+ element.runtimeStyle.left = rStyle;
- isBuggy = (result.snapshotLength !== 2);
- el = null;
+ return value;
+ }
+
+ if (value.include('%')) {
+ var decimal = toDecimal(value);
+ var whole;
+ if (property.include('left') || property.include('right') ||
+ property.include('width')) {
+ whole = $(element.parentNode).measure('width');
+ } else if (property.include('top') || property.include('bottom') ||
+ property.include('height')) {
+ whole = $(element.parentNode).measure('height');
}
- return isBuggy;
- })();
- return function() {
- if (!Prototype.BrowserFeatures.XPath) return false;
+ return whole * decimal;
+ }
- var e = this.expression;
+ return 0;
+ }
- if (Prototype.Browser.WebKit &&
- (e.include("-of-type") || e.include(":empty")))
- return false;
+ function toCSSPixels(number) {
+ if (Object.isString(number) && number.endsWith('px')) {
+ return number;
+ }
+ return number + 'px';
+ }
- if ((/(\[[\w-]*?:|:checked)/).test(e))
+ function isDisplayed(element) {
+ var originalElement = element;
+ while (element && element.parentNode) {
+ var display = element.getStyle('display');
+ if (display === 'none') {
return false;
+ }
+ element = $(element.parentNode);
+ }
+ return true;
+ }
- if (IS_DESCENDANT_SELECTOR_BUGGY) return false;
+ var hasLayout = Prototype.K;
+ if ('currentStyle' in document.documentElement) {
+ hasLayout = function(element) {
+ if (!element.currentStyle.hasLayout) {
+ element.style.zoom = 1;
+ }
+ return element;
+ };
+ }
- return true;
- }
+ function cssNameFor(key) {
+ if (key.include('border')) key = key + '-width';
+ return key.camelize();
+ }
- })(),
+ Element.Layout = Class.create(Hash, {
+ initialize: function($super, element, preCompute) {
+ $super();
+ this.element = $(element);
- shouldUseSelectorsAPI: function() {
- if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
+ Element.Layout.PROPERTIES.each( function(property) {
+ this._set(property, null);
+ }, this);
- if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false;
+ if (preCompute) {
+ this._preComputing = true;
+ this._begin();
+ Element.Layout.PROPERTIES.each( this._compute, this );
+ this._end();
+ this._preComputing = false;
+ }
+ },
- if (!Selector._div) Selector._div = new Element('div');
+ _set: function(property, value) {
+ return Hash.prototype.set.call(this, property, value);
+ },
- try {
- Selector._div.querySelector(this.expression);
- } catch(e) {
- return false;
- }
+ set: function(property, value) {
+ throw "Properties of Element.Layout are read-only.";
+ },
- return true;
- },
+ get: function($super, property) {
+ var value = $super(property);
+ return value === null ? this._compute(property) : value;
+ },
- compileMatcher: function() {
- var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
- c = Selector.criteria, le, p, m, len = ps.length, name;
+ _begin: function() {
+ if (this._prepared) return;
- if (Selector._cache[e]) {
- this.matcher = Selector._cache[e];
- return;
- }
+ var element = this.element;
+ if (isDisplayed(element)) {
+ this._prepared = true;
+ return;
+ }
- this.matcher = ["this.matcher = function(root) {",
- "var r = root, h = Selector.handlers, c = false, n;"];
+ var originalStyles = {
+ position: element.style.position || '',
+ width: element.style.width || '',
+ visibility: element.style.visibility || '',
+ display: element.style.display || ''
+ };
- while (e && le != e && (/\S/).test(e)) {
- le = e;
- for (var i = 0; i<len; i++) {
- p = ps[i].re;
- name = ps[i].name;
- if (m = e.match(p)) {
- this.matcher.push(Object.isFunction(c[name]) ? c[name](m) :
- new Template(c[name]).evaluate(m));
- e = e.replace(m[0], '');
- break;
- }
+ element.store('prototype_original_styles', originalStyles);
+
+ var position = element.getStyle('position'),
+ width = element.getStyle('width');
+
+ element.setStyle({
+ position: 'absolute',
+ visibility: 'hidden',
+ display: 'block'
+ });
+
+ var positionedWidth = element.getStyle('width');
+
+ var newWidth;
+ if (width && (positionedWidth === width)) {
+ newWidth = getPixelValue(width);
+ } else if (width && (position === 'absolute' || position === 'fixed')) {
+ newWidth = getPixelValue(width);
+ } else {
+ var parent = element.parentNode, pLayout = $(parent).getLayout();
+
+ newWidth = pLayout.get('width') -
+ this.get('margin-left') -
+ this.get('border-left') -
+ this.get('padding-left') -
+ this.get('padding-right') -
+ this.get('border-right') -
+ this.get('margin-right');
}
- }
- this.matcher.push("return h.unique(n);\n}");
- eval(this.matcher.join('\n'));
- Selector._cache[this.expression] = this.matcher;
- },
+ element.setStyle({ width: newWidth + 'px' });
- compileXPathMatcher: function() {
- var e = this.expression, ps = Selector.patterns,
- x = Selector.xpath, le, m, len = ps.length, name;
+ this._prepared = true;
+ },
- if (Selector._cache[e]) {
- this.xpath = Selector._cache[e]; return;
- }
+ _end: function() {
+ var element = this.element;
+ var originalStyles = element.retrieve('prototype_original_styles');
+ element.store('prototype_original_styles', null);
+ element.setStyle(originalStyles);
+ this._prepared = false;
+ },
- this.matcher = ['.//*'];
- while (e && le != e && (/\S/).test(e)) {
- le = e;
- for (var i = 0; i<len; i++) {
- name = ps[i].name;
- if (m = e.match(ps[i].re)) {
- this.matcher.push(Object.isFunction(x[name]) ? x[name](m) :
- new Template(x[name]).evaluate(m));
- e = e.replace(m[0], '');
- break;
- }
+ _compute: function(property) {
+ var COMPUTATIONS = Element.Layout.COMPUTATIONS;
+ if (!(property in COMPUTATIONS)) {
+ throw "Property not found.";
}
- }
+ return this._set(property, COMPUTATIONS[property].call(this, this.element));
+ },
- this.xpath = this.matcher.join('');
- Selector._cache[this.expression] = this.xpath;
- },
+ toObject: function() {
+ var args = $A(arguments);
+ var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
+ args.join(' ').split(' ');
+ var obj = {};
+ keys.each( function(key) {
+ if (!Element.Layout.PROPERTIES.include(key)) return;
+ var value = this.get(key);
+ if (value != null) obj[key] = value;
+ }, this);
+ return obj;
+ },
- findElements: function(root) {
- root = root || document;
- var e = this.expression, results;
+ toHash: function() {
+ var obj = this.toObject.apply(this, arguments);
+ return new Hash(obj);
+ },
- switch (this.mode) {
- case 'selectorsAPI':
- if (root !== document) {
- var oldId = root.id, id = $(root).identify();
- id = id.replace(/([\.:])/g, "\\$1");
- e = "#" + id + " " + e;
- }
+ toCSS: function() {
+ var args = $A(arguments);
+ var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
+ args.join(' ').split(' ');
+ var css = {};
- results = $A(root.querySelectorAll(e)).map(Element.extend);
- root.id = oldId;
+ keys.each( function(key) {
+ if (!Element.Layout.PROPERTIES.include(key)) return;
+ if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return;
- return results;
- case 'xpath':
- return document._getElementsByXPath(this.xpath, root);
- default:
- return this.matcher(root);
+ var value = this.get(key);
+ if (value != null) css[cssNameFor(key)] = value + 'px';
+ }, this);
+ return css;
+ },
+
+ inspect: function() {
+ return "#<Element.Layout>";
}
- },
+ });
- match: function(element) {
- this.tokens = [];
+ Object.extend(Element.Layout, {
+ PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'),
- var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
- var le, p, m, len = ps.length, name;
+ COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'),
- while (e && le !== e && (/\S/).test(e)) {
- le = e;
- for (var i = 0; i<len; i++) {
- p = ps[i].re;
- name = ps[i].name;
- if (m = e.match(p)) {
- if (as[name]) {
- this.tokens.push([name, Object.clone(m)]);
- e = e.replace(m[0], '');
- } else {
- return this.findElements(document).include(element);
- }
- }
- }
- }
+ COMPUTATIONS: {
+ 'height': function(element) {
+ if (!this._preComputing) this._begin();
- var match = true, name, matches;
- for (var i = 0, token; token = this.tokens[i]; i++) {
- name = token[0], matches = token[1];
- if (!Selector.assertions[name](element, matches)) {
- match = false; break;
- }
- }
+ var bHeight = this.get('border-box-height');
+ if (bHeight <= 0) return 0;
- return match;
- },
+ var bTop = this.get('border-top'),
+ bBottom = this.get('border-bottom');
- toString: function() {
- return this.expression;
- },
+ var pTop = this.get('padding-top'),
+ pBottom = this.get('padding-bottom');
- inspect: function() {
- return "#<Selector:" + this.expression.inspect() + ">";
- }
-});
+ if (!this._preComputing) this._end();
-if (Prototype.BrowserFeatures.SelectorsAPI &&
- document.compatMode === 'BackCompat') {
- Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){
- var div = document.createElement('div'),
- span = document.createElement('span');
-
- div.id = "prototype_test_id";
- span.className = 'Test';
- div.appendChild(span);
- var isIgnored = (div.querySelector('#prototype_test_id .test') !== null);
- div = span = null;
- return isIgnored;
- })();
-}
+ return bHeight - bTop - bBottom - pTop - pBottom;
+ },
-Object.extend(Selector, {
- _cache: { },
-
- xpath: {
- descendant: "//*",
- child: "/*",
- adjacent: "/following-sibling::*[1]",
- laterSibling: '/following-sibling::*',
- tagName: function(m) {
- if (m[1] == '*') return '';
- return "[local-name()='" + m[1].toLowerCase() +
- "' or local-name()='" + m[1].toUpperCase() + "']";
- },
- className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
- id: "[@id='#{1}']",
- attrPresence: function(m) {
- m[1] = m[1].toLowerCase();
- return new Template("[@#{1}]").evaluate(m);
- },
- attr: function(m) {
- m[1] = m[1].toLowerCase();
- m[3] = m[5] || m[6];
- return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
- },
- pseudo: function(m) {
- var h = Selector.xpath.pseudos[m[1]];
- if (!h) return '';
- if (Object.isFunction(h)) return h(m);
- return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
- },
- operators: {
- '=': "[@#{1}='#{3}']",
- '!=': "[@#{1}!='#{3}']",
- '^=': "[starts-with(@#{1}, '#{3}')]",
- '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
- '*=': "[contains(@#{1}, '#{3}')]",
- '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
- '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
- },
- pseudos: {
- 'first-child': '[not(preceding-sibling::*)]',
- 'last-child': '[not(following-sibling::*)]',
- 'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
- 'empty': "[count(*) = 0 and (count(text()) = 0)]",
- 'checked': "[@checked]",
- 'disabled': "[(@disabled) and (@type!='hidden')]",
- 'enabled': "[not(@disabled) and (@type!='hidden')]",
- 'not': function(m) {
- var e = m[6], p = Selector.patterns,
- x = Selector.xpath, le, v, len = p.length, name;
-
- var exclusion = [];
- while (e && le != e && (/\S/).test(e)) {
- le = e;
- for (var i = 0; i<len; i++) {
- name = p[i].name
- if (m = e.match(p[i].re)) {
- v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m);
- exclusion.push("(" + v.substring(1, v.length - 1) + ")");
- e = e.replace(m[0], '');
- break;
- }
- }
- }
- return "[not(" + exclusion.join(" and ") + ")]";
+ 'width': function(element) {
+ if (!this._preComputing) this._begin();
+
+ var bWidth = this.get('border-box-width');
+ if (bWidth <= 0) return 0;
+
+ var bLeft = this.get('border-left'),
+ bRight = this.get('border-right');
+
+ var pLeft = this.get('padding-left'),
+ pRight = this.get('padding-right');
+
+ if (!this._preComputing) this._end();
+
+ return bWidth - bLeft - bRight - pLeft - pRight;
+ },
+
+ 'padding-box-height': function(element) {
+ var height = this.get('height'),
+ pTop = this.get('padding-top'),
+ pBottom = this.get('padding-bottom');
+
+ return height + pTop + pBottom;
+ },
+
+ 'padding-box-width': function(element) {
+ var width = this.get('width'),
+ pLeft = this.get('padding-left'),
+ pRight = this.get('padding-right');
+
+ return width + pLeft + pRight;
},
- 'nth-child': function(m) {
- return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
+
+ 'border-box-height': function(element) {
+ return element.offsetHeight;
},
- 'nth-last-child': function(m) {
- return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
+
+ 'border-box-width': function(element) {
+ return element.offsetWidth;
},
- 'nth-of-type': function(m) {
- return Selector.xpath.pseudos.nth("position() ", m);
+
+ 'margin-box-height': function(element) {
+ var bHeight = this.get('border-box-height'),
+ mTop = this.get('margin-top'),
+ mBottom = this.get('margin-bottom');
+
+ if (bHeight <= 0) return 0;
+
+ return bHeight + mTop + mBottom;
},
- 'nth-last-of-type': function(m) {
- return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
+
+ 'margin-box-width': function(element) {
+ var bWidth = this.get('border-box-width'),
+ mLeft = this.get('margin-left'),
+ mRight = this.get('margin-right');
+
+ if (bWidth <= 0) return 0;
+
+ return bWidth + mLeft + mRight;
},
- 'first-of-type': function(m) {
- m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
+
+ 'top': function(element) {
+ var offset = element.positionedOffset();
+ return offset.top;
},
- 'last-of-type': function(m) {
- m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
+
+ 'bottom': function(element) {
+ var offset = element.positionedOffset(),
+ parent = element.getOffsetParent(),
+ pHeight = parent.measure('height');
+
+ var mHeight = this.get('border-box-height');
+
+ return pHeight - mHeight - offset.top;
},
- 'only-of-type': function(m) {
- var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+
+ 'left': function(element) {
+ var offset = element.positionedOffset();
+ return offset.left;
},
- nth: function(fragment, m) {
- var mm, formula = m[6], predicate;
- if (formula == 'even') formula = '2n+0';
- if (formula == 'odd') formula = '2n+1';
- if (mm = formula.match(/^(\d+)$/)) // digit only
- return '[' + fragment + "= " + mm[1] + ']';
- if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
- if (mm[1] == "-") mm[1] = -1;
- var a = mm[1] ? Number(mm[1]) : 1;
- var b = mm[2] ? Number(mm[2]) : 0;
- predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
- "((#{fragment} - #{b}) div #{a} >= 0)]";
- return new Template(predicate).evaluate({
- fragment: fragment, a: a, b: b });
- }
+
+ 'right': function(element) {
+ var offset = element.positionedOffset(),
+ parent = element.getOffsetParent(),
+ pWidth = parent.measure('width');
+
+ var mWidth = this.get('border-box-width');
+
+ return pWidth - mWidth - offset.left;
+ },
+
+ 'padding-top': function(element) {
+ return getPixelValue(element, 'paddingTop');
+ },
+
+ 'padding-bottom': function(element) {
+ return getPixelValue(element, 'paddingBottom');
+ },
+
+ 'padding-left': function(element) {
+ return getPixelValue(element, 'paddingLeft');
+ },
+
+ 'padding-right': function(element) {
+ return getPixelValue(element, 'paddingRight');
+ },
+
+ 'border-top': function(element) {
+ return Object.isNumber(element.clientTop) ? element.clientTop :
+ getPixelValue(element, 'borderTopWidth');
+ },
+
+ 'border-bottom': function(element) {
+ return Object.isNumber(element.clientBottom) ? element.clientBottom :
+ getPixelValue(element, 'borderBottomWidth');
+ },
+
+ 'border-left': function(element) {
+ return Object.isNumber(element.clientLeft) ? element.clientLeft :
+ getPixelValue(element, 'borderLeftWidth');
+ },
+
+ 'border-right': function(element) {
+ return Object.isNumber(element.clientRight) ? element.clientRight :
+ getPixelValue(element, 'borderRightWidth');
+ },
+
+ 'margin-top': function(element) {
+ return getPixelValue(element, 'marginTop');
+ },
+
+ 'margin-bottom': function(element) {
+ return getPixelValue(element, 'marginBottom');
+ },
+
+ 'margin-left': function(element) {
+ return getPixelValue(element, 'marginLeft');
+ },
+
+ 'margin-right': function(element) {
+ return getPixelValue(element, 'marginRight');
}
}
- },
+ });
- criteria: {
- tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
- className: 'n = h.className(n, r, "#{1}", c); c = false;',
- id: 'n = h.id(n, r, "#{1}", c); c = false;',
- attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
- attr: function(m) {
- m[3] = (m[5] || m[6]);
- return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
- },
- pseudo: function(m) {
- if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
- return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
- },
- descendant: 'c = "descendant";',
- child: 'c = "child";',
- adjacent: 'c = "adjacent";',
- laterSibling: 'c = "laterSibling";'
- },
-
- patterns: [
- { name: 'laterSibling', re: /^\s*~\s*/ },
- { name: 'child', re: /^\s*>\s*/ },
- { name: 'adjacent', re: /^\s*\+\s*/ },
- { name: 'descendant', re: /^\s/ },
-
- { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ },
- { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ },
- { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ },
- { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ },
- { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ },
- { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }
- ],
-
- assertions: {
- tagName: function(element, matches) {
- return matches[1].toUpperCase() == element.tagName.toUpperCase();
+ if ('getBoundingClientRect' in document.documentElement) {
+ Object.extend(Element.Layout.COMPUTATIONS, {
+ 'right': function(element) {
+ var parent = hasLayout(element.getOffsetParent());
+ var rect = element.getBoundingClientRect(),
+ pRect = parent.getBoundingClientRect();
+
+ return (pRect.right - rect.right).round();
+ },
+
+ 'bottom': function(element) {
+ var parent = hasLayout(element.getOffsetParent());
+ var rect = element.getBoundingClientRect(),
+ pRect = parent.getBoundingClientRect();
+
+ return (pRect.bottom - rect.bottom).round();
+ }
+ });
+ }
+
+ Element.Offset = Class.create({
+ initialize: function(left, top) {
+ this.left = left.round();
+ this.top = top.round();
+
+ this[0] = this.left;
+ this[1] = this.top;
},
- className: function(element, matches) {
- return Element.hasClassName(element, matches[1]);
+ relativeTo: function(offset) {
+ return new Element.Offset(
+ this.left - offset.left,
+ this.top - offset.top
+ );
},
- id: function(element, matches) {
- return element.id === matches[1];
+ inspect: function() {
+ return "#<Element.Offset left: #{left} top: #{top}>".interpolate(this);
},
- attrPresence: function(element, matches) {
- return Element.hasAttribute(element, matches[1]);
+ toString: function() {
+ return "[#{left}, #{top}]".interpolate(this);
},
- attr: function(element, matches) {
- var nodeValue = Element.readAttribute(element, matches[1]);
- return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
+ toArray: function() {
+ return [this.left, this.top];
}
- },
+ });
- handlers: {
- concat: function(a, b) {
- for (var i = 0, node; node = b[i]; i++)
- a.push(node);
- return a;
- },
+ function getLayout(element, preCompute) {
+ return new Element.Layout(element, preCompute);
+ }
- mark: function(nodes) {
- var _true = Prototype.emptyFunction;
- for (var i = 0, node; node = nodes[i]; i++)
- node._countedByPrototype = _true;
- return nodes;
- },
+ function measure(element, property) {
+ return $(element).getLayout().get(property);
+ }
- unmark: (function(){
+ function getDimensions(element) {
+ var layout = $(element).getLayout();
+ return {
+ width: layout.get('width'),
+ height: layout.get('height')
+ };
+ }
- var PROPERTIES_ATTRIBUTES_MAP = (function(){
- var el = document.createElement('div'),
- isBuggy = false,
- propName = '_countedByPrototype',
- value = 'x'
- el[propName] = value;
- isBuggy = (el.getAttribute(propName) === value);
- el = null;
- return isBuggy;
- })();
-
- return PROPERTIES_ATTRIBUTES_MAP ?
- function(nodes) {
- for (var i = 0, node; node = nodes[i]; i++)
- node.removeAttribute('_countedByPrototype');
- return nodes;
- } :
- function(nodes) {
- for (var i = 0, node; node = nodes[i]; i++)
- node._countedByPrototype = void 0;
- return nodes;
- }
- })(),
+ function getOffsetParent(element) {
+ if (isDetached(element)) return $(document.body);
- index: function(parentNode, reverse, ofType) {
- parentNode._countedByPrototype = Prototype.emptyFunction;
- if (reverse) {
- for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
- var node = nodes[i];
- if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
- }
- } else {
- for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
- if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+ var isInline = (Element.getStyle(element, 'display') === 'inline');
+ if (!isInline && element.offsetParent) return $(element.offsetParent);
+ if (element === document.body) return $(element);
+
+ while ((element = element.parentNode) && element !== document.body) {
+ if (Element.getStyle(element, 'position') !== 'static') {
+ return (element.nodeName === 'HTML') ? $(document.body) : $(element);
}
- },
+ }
- unique: function(nodes) {
- if (nodes.length == 0) return nodes;
- var results = [], n;
- for (var i = 0, l = nodes.length; i < l; i++)
- if (typeof (n = nodes[i])._countedByPrototype == 'undefined') {
- n._countedByPrototype = Prototype.emptyFunction;
- results.push(Element.extend(n));
- }
- return Selector.handlers.unmark(results);
- },
+ return $(document.body);
+ }
- descendant: function(nodes) {
- var h = Selector.handlers;
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- h.concat(results, node.getElementsByTagName('*'));
- return results;
- },
- child: function(nodes) {
- var h = Selector.handlers;
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
- for (var j = 0, child; child = node.childNodes[j]; j++)
- if (child.nodeType == 1 && child.tagName != '!') results.push(child);
- }
- return results;
- },
+ function cumulativeOffset(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ } while (element);
+ return new Element.Offset(valueL, valueT);
+ }
+
+ function positionedOffset(element) {
+ var layout = element.getLayout();
- adjacent: function(nodes) {
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
- var next = this.nextElementSibling(node);
- if (next) results.push(next);
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ if (element) {
+ if (isBody(element)) break;
+ var p = Element.getStyle(element, 'position');
+ if (p !== 'static') break;
}
- return results;
- },
+ } while (element);
- laterSibling: function(nodes) {
- var h = Selector.handlers;
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- h.concat(results, Element.nextSiblings(node));
- return results;
- },
+ valueL -= layout.get('margin-top');
+ valueT -= layout.get('margin-left');
- nextElementSibling: function(node) {
- while (node = node.nextSibling)
- if (node.nodeType == 1) return node;
- return null;
- },
+ return new Element.Offset(valueL, valueT);
+ }
- previousElementSibling: function(node) {
- while (node = node.previousSibling)
- if (node.nodeType == 1) return node;
- return null;
- },
+ function cumulativeScrollOffset(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.scrollTop || 0;
+ valueL += element.scrollLeft || 0;
+ element = element.parentNode;
+ } while (element);
+ return new Element.Offset(valueL, valueT);
+ }
- tagName: function(nodes, root, tagName, combinator) {
- var uTagName = tagName.toUpperCase();
- var results = [], h = Selector.handlers;
- if (nodes) {
- if (combinator) {
- if (combinator == "descendant") {
- for (var i = 0, node; node = nodes[i]; i++)
- h.concat(results, node.getElementsByTagName(tagName));
- return results;
- } else nodes = this[combinator](nodes);
- if (tagName == "*") return nodes;
- }
- for (var i = 0, node; node = nodes[i]; i++)
- if (node.tagName.toUpperCase() === uTagName) results.push(node);
- return results;
- } else return root.getElementsByTagName(tagName);
- },
+ function viewportOffset(forElement) {
+ var valueT = 0, valueL = 0, docBody = document.body;
- id: function(nodes, root, id, combinator) {
- var targetNode = $(id), h = Selector.handlers;
+ var element = forElement;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ if (element.offsetParent == docBody &&
+ Element.getStyle(element, 'position') == 'absolute') break;
+ } while (element = element.offsetParent);
- if (root == document) {
- if (!targetNode) return [];
- if (!nodes) return [targetNode];
- } else {
- if (!root.sourceIndex || root.sourceIndex < 1) {
- var nodes = root.getElementsByTagName('*');
- for (var j = 0, node; node = nodes[j]; j++) {
- if (node.id === id) return [node];
- }
- }
+ element = forElement;
+ do {
+ if (element != docBody) {
+ valueT -= element.scrollTop || 0;
+ valueL -= element.scrollLeft || 0;
}
+ } while (element = element.parentNode);
+ return new Element.Offset(valueL, valueT);
+ }
- if (nodes) {
- if (combinator) {
- if (combinator == 'child') {
- for (var i = 0, node; node = nodes[i]; i++)
- if (targetNode.parentNode == node) return [targetNode];
- } else if (combinator == 'descendant') {
- for (var i = 0, node; node = nodes[i]; i++)
- if (Element.descendantOf(targetNode, node)) return [targetNode];
- } else if (combinator == 'adjacent') {
- for (var i = 0, node; node = nodes[i]; i++)
- if (Selector.handlers.previousElementSibling(targetNode) == node)
- return [targetNode];
- } else nodes = h[combinator](nodes);
- }
- for (var i = 0, node; node = nodes[i]; i++)
- if (node == targetNode) return [targetNode];
- return [];
- }
- return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
- },
+ function absolutize(element) {
+ element = $(element);
- className: function(nodes, root, className, combinator) {
- if (nodes && combinator) nodes = this[combinator](nodes);
- return Selector.handlers.byClassName(nodes, root, className);
- },
+ if (Element.getStyle(element, 'position') === 'absolute') {
+ return element;
+ }
- byClassName: function(nodes, root, className) {
- if (!nodes) nodes = Selector.handlers.descendant([root]);
- var needle = ' ' + className + ' ';
- for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
- nodeClassName = node.className;
- if (nodeClassName.length == 0) continue;
- if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
- results.push(node);
- }
- return results;
- },
+ var offsetParent = getOffsetParent(element);
+ var eOffset = element.viewportOffset(),
+ pOffset = offsetParent.viewportOffset();
- attrPresence: function(nodes, root, attr, combinator) {
- if (!nodes) nodes = root.getElementsByTagName("*");
- if (nodes && combinator) nodes = this[combinator](nodes);
- var results = [];
- for (var i = 0, node; node = nodes[i]; i++)
- if (Element.hasAttribute(node, attr)) results.push(node);
- return results;
- },
+ var offset = eOffset.relativeTo(pOffset);
+ var layout = element.getLayout();
- attr: function(nodes, root, attr, value, operator, combinator) {
- if (!nodes) nodes = root.getElementsByTagName("*");
- if (nodes && combinator) nodes = this[combinator](nodes);
- var handler = Selector.operators[operator], results = [];
- for (var i = 0, node; node = nodes[i]; i++) {
- var nodeValue = Element.readAttribute(node, attr);
- if (nodeValue === null) continue;
- if (handler(nodeValue, value)) results.push(node);
- }
- return results;
- },
+ element.store('prototype_absolutize_original_styles', {
+ left: element.getStyle('left'),
+ top: element.getStyle('top'),
+ width: element.getStyle('width'),
+ height: element.getStyle('height')
+ });
+
+ element.setStyle({
+ position: 'absolute',
+ top: offset.top + 'px',
+ left: offset.left + 'px',
+ width: layout.get('width') + 'px',
+ height: layout.get('height') + 'px'
+ });
+
+ return element;
+ }
- pseudo: function(nodes, name, value, root, combinator) {
- if (nodes && combinator) nodes = this[combinator](nodes);
- if (!nodes) nodes = root.getElementsByTagName("*");
- return Selector.pseudos[name](nodes, value, root);
+ function relativize(element) {
+ element = $(element);
+ if (Element.getStyle(element, 'position') === 'relative') {
+ return element;
}
- },
- pseudos: {
- 'first-child': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
- if (Selector.handlers.previousElementSibling(node)) continue;
- results.push(node);
- }
- return results;
- },
- 'last-child': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
- if (Selector.handlers.nextElementSibling(node)) continue;
- results.push(node);
- }
- return results;
- },
- 'only-child': function(nodes, value, root) {
- var h = Selector.handlers;
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
- results.push(node);
- return results;
- },
- 'nth-child': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, formula, root);
- },
- 'nth-last-child': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, formula, root, true);
- },
- 'nth-of-type': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, formula, root, false, true);
- },
- 'nth-last-of-type': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, formula, root, true, true);
- },
- 'first-of-type': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, "1", root, false, true);
- },
- 'last-of-type': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, "1", root, true, true);
- },
- 'only-of-type': function(nodes, formula, root) {
- var p = Selector.pseudos;
- return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
- },
+ var originalStyles =
+ element.retrieve('prototype_absolutize_original_styles');
- getIndices: function(a, b, total) {
- if (a == 0) return b > 0 ? [b] : [];
- return $R(1, total).inject([], function(memo, i) {
- if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
- return memo;
- });
- },
+ if (originalStyles) element.setStyle(originalStyles);
+ return element;
+ }
- nth: function(nodes, formula, root, reverse, ofType) {
- if (nodes.length == 0) return [];
- if (formula == 'even') formula = '2n+0';
- if (formula == 'odd') formula = '2n+1';
- var h = Selector.handlers, results = [], indexed = [], m;
- h.mark(nodes);
- for (var i = 0, node; node = nodes[i]; i++) {
- if (!node.parentNode._countedByPrototype) {
- h.index(node.parentNode, reverse, ofType);
- indexed.push(node.parentNode);
- }
- }
- if (formula.match(/^\d+$/)) { // just a number
- formula = Number(formula);
- for (var i = 0, node; node = nodes[i]; i++)
- if (node.nodeIndex == formula) results.push(node);
- } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
- if (m[1] == "-") m[1] = -1;
- var a = m[1] ? Number(m[1]) : 1;
- var b = m[2] ? Number(m[2]) : 0;
- var indices = Selector.pseudos.getIndices(a, b, nodes.length);
- for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
- for (var j = 0; j < l; j++)
- if (node.nodeIndex == indices[j]) results.push(node);
- }
- }
- h.unmark(nodes);
- h.unmark(indexed);
- return results;
- },
+ Element.addMethods({
+ getLayout: getLayout,
+ measure: measure,
+ getDimensions: getDimensions,
+ getOffsetParent: getOffsetParent,
+ cumulativeOffset: cumulativeOffset,
+ positionedOffset: positionedOffset,
+ cumulativeScrollOffset: cumulativeScrollOffset,
+ viewportOffset: viewportOffset,
+ absolutize: absolutize,
+ relativize: relativize
+ });
- 'empty': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
- if (node.tagName == '!' || node.firstChild) continue;
- results.push(node);
- }
- return results;
- },
+ function isBody(element) {
+ return element.nodeName.toUpperCase() === 'BODY';
+ }
- 'not': function(nodes, selector, root) {
- var h = Selector.handlers, selectorType, m;
- var exclusions = new Selector(selector).findElements(root);
- h.mark(exclusions);
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (!node._countedByPrototype) results.push(node);
- h.unmark(exclusions);
- return results;
- },
+ function isDetached(element) {
+ return element !== document.body &&
+ !Element.descendantOf(element, document.body);
+ }
- 'enabled': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (!node.disabled && (!node.type || node.type !== 'hidden'))
- results.push(node);
- return results;
- },
+ if ('getBoundingClientRect' in document.documentElement) {
+ Element.addMethods({
+ viewportOffset: function(element) {
+ element = $(element);
+ if (isDetached(element)) return new Element.Offset(0, 0);
- 'disabled': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (node.disabled) results.push(node);
- return results;
- },
+ var rect = element.getBoundingClientRect(),
+ docEl = document.documentElement;
+ return new Element.Offset(rect.left - docEl.clientLeft,
+ rect.top - docEl.clientTop);
+ },
- 'checked': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (node.checked) results.push(node);
- return results;
- }
- },
+ positionedOffset: function(element) {
+ element = $(element);
+ var parent = element.getOffsetParent();
+ if (isDetached(element)) return new Element.Offset(0, 0);
- operators: {
- '=': function(nv, v) { return nv == v; },
- '!=': function(nv, v) { return nv != v; },
- '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
- '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
- '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
- '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
- '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
- '-').include('-' + (v || "").toUpperCase() + '-'); }
- },
+ if (element.offsetParent &&
+ element.offsetParent.nodeName.toUpperCase() === 'HTML') {
+ return positionedOffset(element);
+ }
+
+ var eOffset = element.viewportOffset(),
+ pOffset = isBody(parent) ? viewportOffset(parent) :
+ parent.viewportOffset();
+ var retOffset = eOffset.relativeTo(pOffset);
- split: function(expression) {
- var expressions = [];
- expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
- expressions.push(m[1].strip());
+ var layout = element.getLayout();
+ var top = retOffset.top - layout.get('margin-top');
+ var left = retOffset.left - layout.get('margin-left');
+
+ return new Element.Offset(left, top);
+ }
});
- return expressions;
- },
+ }
+})();
+window.$$ = function() {
+ var expression = $A(arguments).join(', ');
+ return Prototype.Selector.select(expression, document);
+};
- matchElements: function(elements, expression) {
- var matches = $$(expression), h = Selector.handlers;
- h.mark(matches);
- for (var i = 0, results = [], element; element = elements[i]; i++)
- if (element._countedByPrototype) results.push(element);
- h.unmark(matches);
- return results;
- },
+Prototype.Selector = (function() {
+
+ function select() {
+ throw new Error('Method "Prototype.Selector.select" must be defined.');
+ }
+
+ function match() {
+ throw new Error('Method "Prototype.Selector.match" must be defined.');
+ }
- findElement: function(elements, expression, index) {
- if (Object.isNumber(expression)) {
- index = expression; expression = false;
+ function find(elements, expression, index) {
+ index = index || 0;
+ var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i;
+
+ for (i = 0; i < length; i++) {
+ if (match(elements[i], expression) && index == matchIndex++) {
+ return Element.extend(elements[i]);
+ }
}
- return Selector.matchElements(elements, expression || '*')[index || 0];
- },
+ }
- findChildElements: function(element, expressions) {
- expressions = Selector.split(expressions.join(','));
- var results = [], h = Selector.handlers;
- for (var i = 0, l = expressions.length, selector; i < l; i++) {
- selector = new Selector(expressions[i].strip());
- h.concat(results, selector.findElements(element));
+ function extendElements(elements) {
+ for (var i = 0, length = elements.length; i < length; i++) {
+ Element.extend(elements[i]);
}
- return (l > 1) ? h.unique(results) : results;
+ return elements;
}
+
+
+ var K = Prototype.K;
+
+ return {
+ select: select,
+ match: match,
+ find: find,
+ extendElements: (Element.extend === K) ? K : extendElements,
+ extendElement: Element.extend
+ };
+})();
+Prototype._original_property = window.Sizzle;
+/*!
+ * Sizzle CSS Selector Engine - v1.0
+ * Copyright 2009, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true;
+
+[0, 0].sort(function(){
+ baseHasDuplicate = false;
+ return 0;
});
-if (Prototype.Browser.IE) {
- Object.extend(Selector.handlers, {
- concat: function(a, b) {
- for (var i = 0, node; node = b[i]; i++)
- if (node.tagName !== "!") a.push(node);
- return a;
- }
- });
+var Sizzle = function(selector, context, results, seed) {
+ results = results || [];
+ var origContext = context = context || document;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context),
+ soFar = selector;
+
+ while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context );
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] )
+ selector += parts.shift();
+
+ set = posProcess( selector, set );
+ }
+ }
+ } else {
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+ var ret = Sizzle.find( parts.shift(), context, contextXML );
+ context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
+ }
+
+ if ( context ) {
+ var ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+ set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray(set);
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ var cur = parts.pop(), pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXML );
+ }
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ throw "Syntax error, unrecognized expression: " + (cur || selector);
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+ } else if ( context && context.nodeType === 1 ) {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+ } else {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function(results){
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort(sortOrder);
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[i-1] ) {
+ results.splice(i--, 1);
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function(expr, set){
+ return Sizzle(expr, null, null, set);
+};
+
+Sizzle.find = function(expr, context, isXML){
+ var set, match;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+ var type = Expr.order[i], match;
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ var left = match[1];
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace(/\\/g, "");
+ set = Expr.find[ type ]( match, context, isXML );
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = context.getElementsByTagName("*");
+ }
+
+ return {set: set, expr: expr};
+};
+
+Sizzle.filter = function(expr, set, inplace, not){
+ var old = expr, result = [], curLoop = set, match, anyFound,
+ isXMLFilter = set && set[0] && isXML(set[0]);
+
+ while ( expr && set.length ) {
+ for ( var type in Expr.filter ) {
+ if ( (match = Expr.match[ type ].exec( expr )) != null ) {
+ var filter = Expr.filter[ type ], found, item;
+ anyFound = false;
+
+ if ( curLoop == result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ var pass = not ^ !!found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+ } else {
+ curLoop[i] = false;
+ }
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ if ( expr == old ) {
+ if ( anyFound == null ) {
+ throw "Syntax error, unrecognized expression: " + expr;
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
+ },
+ leftMatch: {},
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+ attrHandle: {
+ href: function(elem){
+ return elem.getAttribute("href");
+ }
+ },
+ relative: {
+ "+": function(checkSet, part, isXML){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !/\W/.test(part),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag && !isXML ) {
+ part = part.toUpperCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+ ">": function(checkSet, part, isXML){
+ var isPartStr = typeof part === "string";
+
+ if ( isPartStr && !/\W/.test(part) ) {
+ part = isXML ? part : part.toUpperCase();
+
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName === part ? parent : false;
+ }
+ }
+ } else {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+ "": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( !/\W/.test(part) ) {
+ var nodeCheck = part = isXML ? part : part.toUpperCase();
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
+ },
+ "~": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( typeof part === "string" && !/\W/.test(part) ) {
+ var nodeCheck = part = isXML ? part : part.toUpperCase();
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
+ }
+ },
+ find: {
+ ID: function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ return m ? [m] : [];
+ }
+ },
+ NAME: function(match, context, isXML){
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [], results = context.getElementsByName(match[1]);
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+ TAG: function(match, context){
+ return context.getElementsByTagName(match[1]);
+ }
+ },
+ preFilter: {
+ CLASS: function(match, curLoop, inplace, result, not, isXML){
+ match = " " + match[1].replace(/\\/g, "") + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
+ if ( !inplace )
+ result.push( elem );
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+ ID: function(match){
+ return match[1].replace(/\\/g, "");
+ },
+ TAG: function(match, curLoop){
+ for ( var i = 0; curLoop[i] === false; i++ ){}
+ return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
+ },
+ CHILD: function(match){
+ if ( match[1] == "nth" ) {
+ var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+ match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+
+ match[0] = done++;
+
+ return match;
+ },
+ ATTR: function(match, curLoop, inplace, result, not, isXML){
+ var name = match[1].replace(/\\/g, "");
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+ PSEUDO: function(match, curLoop, inplace, result, not){
+ if ( match[1] === "not" ) {
+ if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+ return false;
+ }
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+ POS: function(match){
+ match.unshift( true );
+ return match;
+ }
+ },
+ filters: {
+ enabled: function(elem){
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+ disabled: function(elem){
+ return elem.disabled === true;
+ },
+ checked: function(elem){
+ return elem.checked === true;
+ },
+ selected: function(elem){
+ elem.parentNode.selectedIndex;
+ return elem.selected === true;
+ },
+ parent: function(elem){
+ return !!elem.firstChild;
+ },
+ empty: function(elem){
+ return !elem.firstChild;
+ },
+ has: function(elem, i, match){
+ return !!Sizzle( match[3], elem ).length;
+ },
+ header: function(elem){
+ return /h\d/i.test( elem.nodeName );
+ },
+ text: function(elem){
+ return "text" === elem.type;
+ },
+ radio: function(elem){
+ return "radio" === elem.type;
+ },
+ checkbox: function(elem){
+ return "checkbox" === elem.type;
+ },
+ file: function(elem){
+ return "file" === elem.type;
+ },
+ password: function(elem){
+ return "password" === elem.type;
+ },
+ submit: function(elem){
+ return "submit" === elem.type;
+ },
+ image: function(elem){
+ return "image" === elem.type;
+ },
+ reset: function(elem){
+ return "reset" === elem.type;
+ },
+ button: function(elem){
+ return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
+ },
+ input: function(elem){
+ return /input|select|textarea|button/i.test(elem.nodeName);
+ }
+ },
+ setFilters: {
+ first: function(elem, i){
+ return i === 0;
+ },
+ last: function(elem, i, match, array){
+ return i === array.length - 1;
+ },
+ even: function(elem, i){
+ return i % 2 === 0;
+ },
+ odd: function(elem, i){
+ return i % 2 === 1;
+ },
+ lt: function(elem, i, match){
+ return i < match[3] - 0;
+ },
+ gt: function(elem, i, match){
+ return i > match[3] - 0;
+ },
+ nth: function(elem, i, match){
+ return match[3] - 0 == i;
+ },
+ eq: function(elem, i, match){
+ return match[3] - 0 == i;
+ }
+ },
+ filter: {
+ PSEUDO: function(elem, match, i, array){
+ var name = match[1], filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var i = 0, l = not.length; i < l; i++ ) {
+ if ( not[i] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ },
+ CHILD: function(elem, match){
+ var type = match[1], node = elem;
+ switch (type) {
+ case 'only':
+ case 'first':
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) return false;
+ }
+ if ( type == 'first') return true;
+ node = elem;
+ case 'last':
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) return false;
+ }
+ return true;
+ case 'nth':
+ var first = match[2], last = match[3];
+
+ if ( first == 1 && last == 0 ) {
+ return true;
+ }
+
+ var doneName = match[0],
+ parent = elem.parentNode;
+
+ if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+ var count = 0;
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+ parent.sizcache = doneName;
+ }
+
+ var diff = elem.nodeIndex - last;
+ if ( first == 0 ) {
+ return diff == 0;
+ } else {
+ return ( diff % first == 0 && diff / first >= 0 );
+ }
+ }
+ },
+ ID: function(elem, match){
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+ TAG: function(elem, match){
+ return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
+ },
+ CLASS: function(elem, match){
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+ ATTR: function(elem, match){
+ var name = match[1],
+ result = Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value != check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+ POS: function(elem, match, i, array){
+ var name = match[2], filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS;
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source );
+}
+
+var makeArray = function(array, results) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 );
+
+} catch(e){
+ makeArray = function(array, results) {
+ var ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var i = 0, l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+ } else {
+ for ( var i = 0; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ }
+
+ var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( "sourceIndex" in document.documentElement ) {
+ sortOrder = function( a, b ) {
+ if ( !a.sourceIndex || !b.sourceIndex ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ }
+
+ var ret = a.sourceIndex - b.sourceIndex;
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( document.createRange ) {
+ sortOrder = function( a, b ) {
+ if ( !a.ownerDocument || !b.ownerDocument ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ }
+
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+}
+
+(function(){
+ var form = document.createElement("div"),
+ id = "script" + (new Date).getTime();
+ form.innerHTML = "<a name='" + id + "'/>";
+
+ var root = document.documentElement;
+ root.insertBefore( form, root.firstChild );
+
+ if ( !!document.getElementById( id ) ) {
+ Expr.find.ID = function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
+ }
+ };
+
+ Expr.filter.ID = function(elem, match){
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+ root = form = null; // release memory in IE
+})();
+
+(function(){
+
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function(match, context){
+ var results = context.getElementsByTagName(match[1]);
+
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ div.innerHTML = "<a href='#'></a>";
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+ Expr.attrHandle.href = function(elem){
+ return elem.getAttribute("href", 2);
+ };
+ }
+
+ div = null; // release memory in IE
+})();
+
+if ( document.querySelectorAll ) (function(){
+ var oldSizzle = Sizzle, div = document.createElement("div");
+ div.innerHTML = "<p class='TEST'></p>";
+
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+ return;
+ }
+
+ Sizzle = function(query, context, extra, seed){
+ context = context || document;
+
+ if ( !seed && context.nodeType === 9 && !isXML(context) ) {
+ try {
+ return makeArray( context.querySelectorAll(query), extra );
+ } catch(e){}
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ for ( var prop in oldSizzle ) {
+ Sizzle[ prop ] = oldSizzle[ prop ];
+ }
+
+ div = null; // release memory in IE
+})();
+
+if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
+ var div = document.createElement("div");
+ div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+ if ( div.getElementsByClassName("e").length === 0 )
+ return;
+
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 )
+ return;
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function(match, context, isXML) {
+ if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+
+ div = null; // release memory in IE
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ var sibDir = dir == "previousSibling" && !isXML;
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ if ( sibDir && elem.nodeType === 1 ){
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ elem = elem[dir];
+ var match = false;
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
}
-function $$() {
- return Selector.findChildElements(document, $A(arguments));
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ var sibDir = dir == "previousSibling" && !isXML;
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ if ( sibDir && elem.nodeType === 1 ) {
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ elem = elem[dir];
+ var match = false;
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
}
+var contains = document.compareDocumentPosition ? function(a, b){
+ return a.compareDocumentPosition(b) & 16;
+} : function(a, b){
+ return a !== b && (a.contains ? a.contains(b) : true);
+};
+
+var isXML = function(elem){
+ return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
+ !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML";
+};
+
+var posProcess = function(selector, context){
+ var tmpSet = [], later = "", match,
+ root = context.nodeType ? [context] : context;
+
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+
+window.Sizzle = Sizzle;
+
+})();
+
+;(function(engine) {
+ var extendElements = Prototype.Selector.extendElements;
+
+ function select(selector, scope) {
+ return extendElements(engine(selector, scope || document));
+ }
+
+ function match(element, selector) {
+ return engine.matches(selector, [element]).length == 1;
+ }
+
+ Prototype.Selector.engine = engine;
+ Prototype.Selector.select = select;
+ Prototype.Selector.match = match;
+})(Sizzle);
+
+window.Sizzle = Prototype._original_property;
+delete Prototype._original_property;
+
var Form = {
reset: function(form) {
form = $(form);
@@ -4334,8 +5372,12 @@ Form.EventObserver = Class.create(Abstract.EventObserver, {
function findElement(event, expression) {
var element = Event.element(event);
if (!expression) return element;
- var elements = [element].concat(element.ancestors());
- return Selector.findElement(elements, expression, 0);
+ while (element) {
+ if (Object.isElement(element) && Prototype.Selector.match(element, expression)) {
+ return Element.extend(element);
+ }
+ element = element.parentNode;
+ }
}
function pointer(event) {
@@ -4504,12 +5546,12 @@ Form.EventObserver = Class.create(Abstract.EventObserver, {
window.addEventListener('unload', Prototype.emptyFunction, false);
- var _getDOMEventName = Prototype.K;
+ var _getDOMEventName = Prototype.K,
+ translations = { mouseenter: "mouseover", mouseleave: "mouseout" };
if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) {
_getDOMEventName = function(eventName) {
- var translations = { mouseenter: "mouseover", mouseleave: "mouseout" };
- return eventName in translations ? translations[eventName] : eventName;
+ return (translations[eventName] || eventName);
};
}
@@ -4543,38 +5585,29 @@ Form.EventObserver = Class.create(Abstract.EventObserver, {
element = $(element);
var registry = Element.retrieve(element, 'prototype_event_registry');
+ if (!registry) return element;
- if (Object.isUndefined(registry)) return element;
-
- if (eventName && !handler) {
- var responders = registry.get(eventName);
-
- if (Object.isUndefined(responders)) return element;
-
- responders.each( function(r) {
- Element.stopObserving(element, eventName, r.handler);
- });
- return element;
- } else if (!eventName) {
+ if (!eventName) {
registry.each( function(pair) {
- var eventName = pair.key, responders = pair.value;
-
- responders.each( function(r) {
- Element.stopObserving(element, eventName, r.handler);
- });
+ var eventName = pair.key;
+ stopObserving(element, eventName);
});
return element;
}
var responders = registry.get(eventName);
+ if (!responders) return element;
- if (!responders) return;
+ if (!handler) {
+ responders.each(function(r) {
+ stopObserving(element, eventName, r.handler);
+ });
+ return element;
+ }
var responder = responders.find( function(r) { return r.handler === handler; });
if (!responder) return element;
- var actualEventName = _getDOMEventName(eventName);
-
if (eventName.include(':')) {
if (element.removeEventListener)
element.removeEventListener("dataavailable", responder, false);
@@ -4583,6 +5616,7 @@ Form.EventObserver = Class.create(Abstract.EventObserver, {
element.detachEvent("onfilterchange", responder);
}
} else {
+ var actualEventName = _getDOMEventName(eventName);
if (element.removeEventListener)
element.removeEventListener(actualEventName, responder, false);
else
@@ -4623,13 +5657,47 @@ Form.EventObserver = Class.create(Abstract.EventObserver, {
return Event.extend(event);
}
+ Event.Handler = Class.create({
+ initialize: function(element, eventName, selector, callback) {
+ this.element = $(element);
+ this.eventName = eventName;
+ this.selector = selector;
+ this.callback = callback;
+ this.handler = this.handleEvent.bind(this);
+ },
+
+ start: function() {
+ Event.observe(this.element, this.eventName, this.handler);
+ return this;
+ },
+
+ stop: function() {
+ Event.stopObserving(this.element, this.eventName, this.handler);
+ return this;
+ },
+
+ handleEvent: function(event) {
+ var element = event.findElement(this.selector);
+ if (element) this.callback.call(this.element, event, element);
+ }
+ });
+
+ function on(element, eventName, selector, callback) {
+ element = $(element);
+ if (Object.isFunction(selector) && Object.isUndefined(callback)) {
+ callback = selector, selector = null;
+ }
+
+ return new Event.Handler(element, eventName, selector, callback).start();
+ }
Object.extend(Event, Event.Methods);
Object.extend(Event, {
fire: fire,
observe: observe,
- stopObserving: stopObserving
+ stopObserving: stopObserving,
+ on: on
});
Element.addMethods({
@@ -4637,7 +5705,9 @@ Form.EventObserver = Class.create(Abstract.EventObserver, {
observe: observe,
- stopObserving: stopObserving
+ stopObserving: stopObserving,
+
+ on: on
});
Object.extend(document, {
@@ -4647,6 +5717,8 @@ Form.EventObserver = Class.create(Abstract.EventObserver, {
stopObserving: stopObserving.methodize(),
+ on: on.methodize(),
+
loaded: false
});
@@ -4872,3 +5944,58 @@ Element.ClassNames.prototype = {
Object.extend(Element.ClassNames.prototype, Enumerable);
/*--------------------------------------------------------------------------*/
+
+(function() {
+ window.Selector = Class.create({
+ initialize: function(expression) {
+ this.expression = expression.strip();
+ },
+
+ findElements: function(rootElement) {
+ return Prototype.Selector.select(this.expression, rootElement);
+ },
+
+ match: function(element) {
+ return Prototype.Selector.match(element, this.expression);
+ },
+
+ toString: function() {
+ return this.expression;
+ },
+
+ inspect: function() {
+ return "#<Selector: " + this.expression + ">";
+ }
+ });
+
+ Object.extend(Selector, {
+ matchElements: function(elements, expression) {
+ var match = Prototype.Selector.match,
+ results = [];
+
+ for (var i = 0, length = elements.length; i < length; i++) {
+ var element = elements[i];
+ if (match(element, expression)) {
+ results.push(Element.extend(element));
+ }
+ }
+ return results;
+ },
+
+ findElement: function(elements, expression, index) {
+ index = index || 0;
+ var matchIndex = 0, element;
+ for (var i = 0, length = elements.length; i < length; i++) {
+ element = elements[i];
+ if (Prototype.Selector.match(element, expression) && index === matchIndex++) {
+ return Element.extend(element);
+ }
+ }
+ },
+
+ findChildElements: function(element, expressions) {
+ var selector = expressions.toArray().join(', ');
+ return Prototype.Selector.select(selector, element || document);
+ }
+ });
+})();
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
index 86564031f5..a8f7aeac7d 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
@@ -3,7 +3,7 @@ require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
class ActiveSupport::TestCase
-<% unless options[:skip_activerecord] -%>
+<% unless options[:skip_active_record] -%>
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb
index 0dfb5cd1c9..3376b422cb 100644
--- a/railties/lib/rails/generators/test_case.rb
+++ b/railties/lib/rails/generators/test_case.rb
@@ -51,7 +51,7 @@ module Rails
# Sets default arguments on generator invocation. This can be overwritten when
# invoking it.
#
- # arguments %w(app_name --skip-activerecord)
+ # arguments %w(app_name --skip-active-record)
#
def self.arguments(array)
self.default_arguments = array
@@ -68,7 +68,7 @@ module Rails
# Captures the given stream and returns it:
#
# stream = capture(:stdout){ puts "Cool" }
- # stream #=> "Cool\n"
+ # stream # => "Cool\n"
#
def capture(stream)
begin
@@ -214,8 +214,8 @@ module Rails
# destination File.expand_path("../tmp", File.dirname(__FILE__))
# teardown :cleanup_destination_root
#
- # test "database.yml is not created when skipping activerecord" do
- # run_generator %w(myapp --skip-activerecord)
+ # test "database.yml is not created when skipping Active Record" do
+ # run_generator %w(myapp --skip-active-record)
# assert_no_file "config/database.yml"
# end
# end
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index e9c3ebe685..6cbd1f21c0 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -1,4 +1,3 @@
-require "active_support/core_ext/object/misc"
require "cgi"
require "active_support/core_ext/cgi"
@@ -89,7 +88,7 @@ module Rails
end
property 'Middleware' do
- Rails.configuration.middleware.active.map(&:inspect)
+ Rails.configuration.middleware.map(&:inspect)
end
# The application's location on the filesystem.
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index 7a65188a9a..d303212f52 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -116,36 +116,20 @@ module Rails
@paths.concat paths
end
- def autoload_once!
- @autoload_once = true
- end
-
- def autoload_once?
- @autoload_once
- end
-
- def eager_load!
- @eager_load = true
- end
-
- def eager_load?
- @eager_load
- end
-
- def autoload!
- @autoload = true
- end
-
- def autoload?
- @autoload
- end
+ %w(autoload_once eager_load autoload load_path).each do |m|
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{m}!
+ @#{m} = true
+ end
- def load_path!
- @load_path = true
- end
+ def skip_#{m}!
+ @#{m} = false
+ end
- def load_path?
- @load_path
+ def #{m}?
+ @#{m}
+ end
+ RUBY
end
def paths
diff --git a/railties/lib/rails/rack/log_tailer.rb b/railties/lib/rails/rack/log_tailer.rb
index 3fa45156c3..011ac6cecc 100644
--- a/railties/lib/rails/rack/log_tailer.rb
+++ b/railties/lib/rails/rack/log_tailer.rb
@@ -6,7 +6,6 @@ module Rails
path = Pathname.new(log || "#{File.expand_path(Rails.root)}/log/#{Rails.env}.log").cleanpath
@cursor = ::File.size(path)
- @last_checked = Time.now.to_f
@file = ::File.open(path, 'r')
end
@@ -20,11 +19,9 @@ module Rails
def tail!
@file.seek @cursor
- mod = @file.mtime.to_f
- if mod > @last_checked
+ if !@file.eof?
contents = @file.read
- @last_checked = mod
- @cursor += contents.size
+ @cursor = @file.tell
$stdout.print contents
end
end
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index dbdbfea509..8a6e2716dc 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -6,53 +6,53 @@ require 'active_support/deprecation'
module Rails
# Railtie is the core of the Rails Framework and provides several hooks to extend
# Rails and/or modify the initialization process.
- #
+ #
# Every major component of Rails (Action Mailer, Action Controller,
# Action View, Active Record and Active Resource) are all Railties, so each of
# them is responsible to set their own initialization. This makes, for example,
# Rails absent of any Active Record hook, allowing any other ORM framework to hook in.
- #
+ #
# Developing a Rails extension does _not_ require any implementation of
# Railtie, but if you need to interact with the Rails framework during
# or after boot, then Railtie is what you need to do that interaction.
- #
+ #
# For example, the following would need you to implement Railtie in your
# plugin:
- #
+ #
# * creating initializers
# * configuring a Rails framework or the Application, like setting a generator
# * adding Rails config.* keys to the environment
# * setting up a subscriber to the Rails +ActiveSupport::Notifications+
# * adding rake tasks into rails
- #
+ #
# == Creating your Railtie
#
# Implementing Railtie in your Rails extension is done by creating a class
# Railtie that has your extension name and making sure that this gets loaded
# during boot time of the Rails stack.
- #
+ #
# You can do this however you wish, but here is an example if you want to provide
# it for a gem that can be used with or without Rails:
- #
+ #
# * Create a file (say, lib/my_gem/railtie.rb) which contains class Railtie inheriting from
# Rails::Railtie and is namespaced to your gem:
#
- # # lib/my_gem/railtie.rb
- # module MyGem
- # class Railtie < Rails::Railtie
+ # # lib/my_gem/railtie.rb
+ # module MyGem
+ # class Railtie < Rails::Railtie
+ # end
# end
- # end
- #
+ #
# * Require your own gem as well as rails in this file:
- #
- # # lib/my_gem/railtie.rb
- # require 'my_gem'
- # require 'rails'
- #
- # module MyGem
- # class Railtie < Rails::Railtie
+ #
+ # # lib/my_gem/railtie.rb
+ # require 'my_gem'
+ # require 'rails'
+ #
+ # module MyGem
+ # class Railtie < Rails::Railtie
+ # end
# end
- # end
#
# == Initializers
#
@@ -65,12 +65,12 @@ module Rails
# end
# end
#
- # If specified, the block can also receive the application object, in case you
+ # If specified, the block can also receive the application object, in case you
# need to access some application specific configuration, like middleware:
#
# class MyRailtie < Rails::Railtie
# initializer "my_railtie.configure_rails_initialization" do |app|
- # app.middlewares.use MyRailtie::Middleware
+ # app.middleware.use MyRailtie::Middleware
# end
# end
#
@@ -156,6 +156,12 @@ module Rails
@rake_tasks
end
+ def console(&blk)
+ @load_console ||= []
+ @load_console << blk if blk
+ @load_console
+ end
+
def generators(&blk)
@generators ||= []
@generators << blk if blk
@@ -170,20 +176,16 @@ module Rails
def eager_load!
end
- def rake_tasks
- self.class.rake_tasks
- end
-
- def generators
- self.class.generators
+ def load_console
+ self.class.console.each(&:call)
end
def load_tasks
- rake_tasks.each { |blk| blk.call }
+ self.class.rake_tasks.each(&:call)
end
def load_generators
- generators.each { |blk| blk.call }
+ self.class.generators.each(&:call)
end
end
end
diff --git a/railties/lib/rails/script_rails_loader.rb b/railties/lib/rails/script_rails_loader.rb
index 8fbd3bf492..91672e5d81 100644
--- a/railties/lib/rails/script_rails_loader.rb
+++ b/railties/lib/rails/script_rails_loader.rb
@@ -7,6 +7,7 @@ module Rails
def self.exec_script_rails!
cwd = Dir.pwd
+ return unless in_rails_application? || in_rails_application_subdirectory?
exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application?
Dir.chdir("..") do
# Recurse in a chdir block: if the search fails we want to be sure
@@ -18,7 +19,7 @@ module Rails
end
def self.in_rails_application?
- File.exists?(SCRIPT_RAILS) || in_rails_application_subdirectory?
+ File.exists?(SCRIPT_RAILS)
end
def self.in_rails_application_subdirectory?(path = Pathname.new(Dir.pwd))
diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake
index 492f05e3cc..c1b1a41d48 100644
--- a/railties/lib/rails/tasks/documentation.rake
+++ b/railties/lib/rails/tasks/documentation.rake
@@ -58,43 +58,43 @@ namespace :doc do
rdoc.rdoc_files.include('README')
gem_path('actionmailer') do |actionmailer|
- %w(README CHANGELOG MIT-LICENSE lib/action_mailer/base.rb).each do |file|
+ %w(README.rdoc CHANGELOG MIT-LICENSE lib/action_mailer/base.rb).each do |file|
rdoc.rdoc_files.include("#{actionmailer}/#{file}")
end
end
gem_path('actionpack') do |actionpack|
- %w(README CHANGELOG MIT-LICENSE lib/action_controller/**/*.rb lib/action_view/**/*.rb).each do |file|
+ %w(README.rdoc CHANGELOG MIT-LICENSE lib/action_controller/**/*.rb lib/action_view/**/*.rb).each do |file|
rdoc.rdoc_files.include("#{actionpack}/#{file}")
end
end
gem_path('activemodel') do |activemodel|
- %w(README CHANGELOG MIT-LICENSE lib/active_model/**/*.rb).each do |file|
+ %w(README.rdoc CHANGELOG MIT-LICENSE lib/active_model/**/*.rb).each do |file|
rdoc.rdoc_files.include("#{activemodel}/#{file}")
end
end
gem_path('activerecord') do |activerecord|
- %w(README CHANGELOG lib/active_record/**/*.rb).each do |file|
+ %w(README.rdoc CHANGELOG lib/active_record/**/*.rb).each do |file|
rdoc.rdoc_files.include("#{activerecord}/#{file}")
end
end
gem_path('activeresource') do |activeresource|
- %w(README CHANGELOG lib/active_resource.rb lib/active_resource/*).each do |file|
+ %w(README.rdoc CHANGELOG lib/active_resource.rb lib/active_resource/*).each do |file|
rdoc.rdoc_files.include("#{activeresource}/#{file}")
end
end
gem_path('activesupport') do |activesupport|
- %w(README CHANGELOG lib/active_support/**/*.rb).each do |file|
+ %w(README.rdoc CHANGELOG lib/active_support/**/*.rb).each do |file|
rdoc.rdoc_files.include("#{activesupport}/#{file}")
end
end
gem_path('railties') do |railties|
- %w(README CHANGELOG lib/{*.rb,commands/*.rb,generators/*.rb}).each do |file|
+ %w(README.rdoc CHANGELOG lib/{*.rb,commands/*.rb,generators/*.rb}).each do |file|
rdoc.rdoc_files.include("#{railties}/#{file}")
end
end
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index e7bd0c38dc..443dacd739 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -54,7 +54,7 @@ namespace :rails do
namespace :update do
def invoke_from_app_generator(method)
- app_generator.invoke(method)
+ app_generator.send(method)
end
def app_generator
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index ecd513c2c8..38c14fcd6b 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -70,7 +70,7 @@ module Kernel
end
end
-desc 'Runs test:unit, test:functional, test:integration together (also available: test:benchmark, test:profile, test:plugins)'
+desc 'Runs test:units, test:functionals, test:integration together (also available: test:benchmark, test:profile, test:plugins)'
task :test do
errors = %w(test:units test:functionals test:integration).collect do |task|
begin
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index 34d96fd0f3..c5d1d02bc1 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -3,7 +3,7 @@ module Rails
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta4"
+ BUILD = "rc"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index 247b926af9..d76b74190b 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -13,14 +13,14 @@ Gem::Specification.new do |s|
s.homepage = 'http://www.rubyonrails.org'
s.rubyforge_project = 'rails'
- s.files = Dir['CHANGELOG', 'README', 'bin/**/*', 'guides/**/*', 'lib/**/{*,.[a-z]*}']
+ s.files = Dir['CHANGELOG', 'README.rdoc', 'bin/**/*', 'guides/**/*', 'lib/**/{*,.[a-z]*}']
s.require_path = 'lib'
s.rdoc_options << '--exclude' << '.'
s.has_rdoc = false
s.add_dependency('rake', '>= 0.8.3')
- s.add_dependency('thor', '~> 0.13.7')
+ s.add_dependency('thor', '~> 0.14.0')
s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version)
end
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index 8ff69f0208..a72e6916dd 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -9,10 +9,8 @@ class ConsoleTest < Test::Unit::TestCase
end
def load_environment
- # Load steps taken from rails/commands/console.rb
require "#{rails_root}/config/environment"
- require 'rails/console/app'
- require 'rails/console/helpers'
+ Rails.application.load_console
end
def test_app_method_should_return_integration_session
@@ -75,4 +73,21 @@ class ConsoleTest < Test::Unit::TestCase
assert_equal 'Once upon a time in a world...',
helper.truncate('Once upon a time in a world far far away')
end
+
+ def test_active_record_does_not_panic_when_referencing_an_observed_constant
+ add_to_config "config.active_record.observers = :user_observer"
+
+ app_file "app/models/user.rb", <<-MODEL
+ class User < ActiveRecord::Base
+ end
+ MODEL
+
+ app_file "app/models/user_observer.rb", <<-MODEL
+ class UserObserver < ActiveRecord::Observer
+ end
+ MODEL
+
+ load_environment
+ assert_nothing_raised { User }
+ end
end
diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb
index cbf0decd07..d258625f42 100644
--- a/railties/test/application/generators_test.rb
+++ b/railties/test/application/generators_test.rb
@@ -103,5 +103,20 @@ module ApplicationTests
assert_equal({ :plugin => { :generator => "-g" } }, c.generators.aliases)
end
end
+
+ test "generators with string and hash for options should generate symbol keys" do
+ with_bare_config do |c|
+ c.generators do |g|
+ g.orm 'datamapper', :migration => false
+ end
+
+ expected = {
+ :rails => { :orm => :datamapper },
+ :datamapper => { :migration => false }
+ }
+
+ assert_equal expected, c.generators.options
+ end
+ end
end
end
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 4ff10091b1..d8e916f45f 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -98,7 +98,17 @@ module ApplicationTests
require "#{app_path}/config/environment"
- expects = [ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActiveRecord::SessionStore]
+ expects = [ActiveRecord::QueryCache, ActiveRecord::SessionStore]
+ middleware = Rails.application.config.middleware.map { |m| m.klass }
+ assert_equal expects, middleware & expects
+ end
+
+ test "database middleware initializes when allow concurrency is true" do
+ add_to_config "config.threadsafe!"
+
+ require "#{app_path}/config/environment"
+
+ expects = [ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache]
middleware = Rails.application.config.middleware.map { |m| m.klass }
assert_equal expects, middleware & expects
end
diff --git a/railties/test/application/initializers/notifications_test.rb b/railties/test/application/initializers/notifications_test.rb
index fc8548af1f..7e035be764 100644
--- a/railties/test/application/initializers/notifications_test.rb
+++ b/railties/test/application/initializers/notifications_test.rb
@@ -1,17 +1,6 @@
require "isolation/abstract_unit"
module ApplicationTests
- class MockLogger
- def method_missing(*args)
- @logged ||= []
- @logged << args.last
- end
-
- def logged
- @logged.compact.map { |l| l.to_s.strip }
- end
- end
-
class NotificationsTest < Test::Unit::TestCase
include ActiveSupport::Testing::Isolation
@@ -34,15 +23,17 @@ module ApplicationTests
RUBY
require "#{app_path}/config/environment"
+ require "active_support/log_subscriber/test_helper"
- ActiveRecord::Base.logger = logger = MockLogger.new
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ ActiveRecord::Base.logger = logger
# Mimic Active Record notifications
instrument "sql.active_record", :name => "SQL", :sql => "SHOW tables"
wait
- assert_equal 1, logger.logged.size
- assert_match /SHOW tables/, logger.logged.last
+ assert_equal 1, logger.logged(:debug).size
+ assert_match /SHOW tables/, logger.logged(:debug).last
end
end
end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index e66e81ea2c..1f127cee78 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -28,18 +28,18 @@ module ApplicationTests
"ActionDispatch::RemoteIp",
"Rack::Sendfile",
"ActionDispatch::Callbacks",
- "ActiveRecord::ConnectionAdapters::ConnectionManagement",
"ActiveRecord::QueryCache",
"ActionDispatch::Cookies",
"ActionDispatch::Session::CookieStore",
"ActionDispatch::Flash",
"ActionDispatch::ParamsParser",
"Rack::MethodOverride",
- "ActionDispatch::Head"
+ "ActionDispatch::Head",
+ "ActionDispatch::BestStandardsSupport"
], middleware
end
- test "removing activerecord omits its middleware" do
+ test "removing Active Record omits its middleware" do
use_frameworks []
boot!
assert !middleware.include?("ActiveRecord::ConnectionAdapters::ConnectionManagement")
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index f0268164d0..febc53bac9 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -11,19 +11,19 @@ module ApplicationTests
extend Rack::Test::Methods
end
- def app
+ def app(env = "production")
+ old_env = ENV["RAILS_ENV"]
+
@app ||= begin
+ ENV["RAILS_ENV"] = env
require "#{app_path}/config/environment"
Rails.application
end
+ ensure
+ ENV["RAILS_ENV"] = old_env
end
- test "rails/info/properties" do
- get "/rails/info/properties"
- assert_equal 200, last_response.status
- end
-
- test "simple controller" do
+ def simple_controller
controller :foo, <<-RUBY
class FooController < ApplicationController
def index
@@ -37,11 +37,42 @@ module ApplicationTests
match ':controller(/:action)'
end
RUBY
+ end
+
+ test "rails/info/properties in development" do
+ app("development")
+ get "/rails/info/properties"
+ assert_equal 200, last_response.status
+ end
+
+ test "rails/info/properties in production" do
+ app("production")
+ get "/rails/info/properties"
+ assert_equal 404, last_response.status
+ end
+
+ test "simple controller" do
+ simple_controller
get '/foo'
assert_equal 'foo', last_response.body
end
+ test "simple controller in production mode returns best standards" do
+ simple_controller
+
+ get '/foo'
+ assert_equal "IE=Edge,chrome=1", last_response.headers["X-UA-Compatible"]
+ end
+
+ test "simple controller in development mode leaves out Chrome" do
+ simple_controller
+ app("development")
+
+ get "/foo"
+ assert_equal "IE=Edge", last_response.headers["X-UA-Compatible"]
+ end
+
test "simple controller with helper" do
controller :foo, <<-RUBY
class FooController < ApplicationController
@@ -146,38 +177,42 @@ module ApplicationTests
assert_equal 'admin::foo', last_response.body
end
- test "reloads routes when configuration is changed" do
- controller :foo, <<-RUBY
- class FooController < ApplicationController
- def bar
- render :text => "bar"
+ {"development" => "baz", "production" => "bar"}.each do |mode, expected|
+ test "reloads routes when configuration is changed in #{mode}" do
+ controller :foo, <<-RUBY
+ class FooController < ApplicationController
+ def bar
+ render :text => "bar"
+ end
+
+ def baz
+ render :text => "baz"
+ end
end
+ RUBY
- def baz
- render :text => "baz"
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do |map|
+ match 'foo', :to => 'foo#bar'
end
- end
- RUBY
+ RUBY
- app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do |map|
- match 'foo', :to => 'foo#bar'
- end
- RUBY
+ app(mode)
- get '/foo'
- assert_equal 'bar', last_response.body
+ get '/foo'
+ assert_equal 'bar', last_response.body
- app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do |map|
- match 'foo', :to => 'foo#baz'
- end
- RUBY
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do |map|
+ match 'foo', :to => 'foo#baz'
+ end
+ RUBY
- sleep 0.1
+ sleep 0.1
- get '/foo'
- assert_equal 'baz', last_response.body
+ get '/foo'
+ assert_equal expected, last_response.body
+ end
end
test 'resource routing with irrigular inflection' do
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 6a3b5de9de..08cfb585f9 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -58,6 +58,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
DEFAULT_APP_FILES.each{ |path| assert_file path }
end
+ def test_application_generate_pretend
+ run_generator ["testapp", "--pretend"]
+
+ DEFAULT_APP_FILES.each{ |path| assert_no_file path }
+ end
+
def test_application_controller_and_layout_files
run_generator
assert_file "app/views/layouts/application.html.erb", /stylesheet_link_tag :all/
@@ -65,7 +71,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_options_before_application_name_raises_an_error
- content = capture(:stderr){ run_generator(["--skip-activerecord", destination_root]) }
+ content = capture(:stderr){ run_generator(["--skip-active-record", destination_root]) }
assert_equal "Options should be given after the application name. For details run: rails --help\n", content
end
@@ -100,6 +106,30 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "things-43/config/application.rb", /^module Things43$/
end
+ def test_application_name_is_detected_if_it_exists_and_app_folder_renamed
+ app_root = File.join(destination_root, "myapp")
+ app_moved_root = File.join(destination_root, "myapp_moved")
+
+ run_generator [app_root]
+
+ Rails.application.config.root = app_moved_root
+ Rails.application.class.stubs(:name).returns("Myapp")
+ Rails.application.stubs(:is_a?).returns(Rails::Application)
+
+ FileUtils.mv(app_root, app_moved_root)
+
+ # forces the shell to automatically overwrite all files
+ Thor::Base.shell.send(:attr_accessor, :always_force)
+ shell = Thor::Base.shell.new
+ shell.send(:always_force=, true)
+
+ generator = Rails::Generators::AppGenerator.new ["rails"], { :with_dispatchers => true },
+ :destination_root => app_moved_root, :shell => shell
+ generator.send(:app_const)
+ silence(:stdout){ generator.send(:create_config_files) }
+ assert_file "myapp_moved/config/environment.rb", /Myapp::Application\.initialize!/
+ end
+
def test_application_names_are_not_singularized
run_generator [File.join(destination_root, "hats")]
assert_file "hats/config/environment.rb", /Hats::Application\.initialize!/
@@ -117,26 +147,31 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile", /^gem\s+["']mysql["']$/
end
- def test_config_database_is_not_added_if_skip_activerecord_is_given
- run_generator [destination_root, "--skip-activerecord"]
+ def test_config_database_is_not_added_if_skip_active_record_is_given
+ run_generator [destination_root, "--skip-active-record"]
assert_no_file "config/database.yml"
end
- def test_activerecord_is_removed_from_frameworks_if_skip_activerecord_is_given
- run_generator [destination_root, "--skip-activerecord"]
+ def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given
+ run_generator [destination_root, "--skip-active-record"]
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
end
def test_prototype_and_test_unit_are_added_by_default
run_generator
+ assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(jquery rails\)/
+ assert_file "public/javascripts/application.js"
assert_file "public/javascripts/prototype.js"
+ assert_file "public/javascripts/rails.js"
assert_file "test"
end
def test_prototype_and_test_unit_are_skipped_if_required
- run_generator [destination_root, "--skip-prototype", "--skip-testunit"]
+ run_generator [destination_root, "--skip-prototype", "--skip-test-unit"]
+ assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(\)/
+ assert_file "public/javascripts/application.js"
assert_no_file "public/javascripts/prototype.js"
- assert_file "public/javascripts"
+ assert_no_file "public/javascripts/rails.js"
assert_no_file "test"
end
@@ -167,7 +202,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
template.instance_eval "def read; self; end" # Make the string respond to read
generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- assert_match /It works!/, silence(:stdout){ generator.invoke }
+ assert_match /It works!/, silence(:stdout){ generator.invoke_all }
end
def test_usage_read_from_file
@@ -191,14 +226,14 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_dev_option
generator([destination_root], :dev => true).expects(:run).with("#{@bundle_command} install")
- silence(:stdout){ generator.invoke }
+ silence(:stdout){ generator.invoke_all }
rails_path = File.expand_path('../../..', Rails.root)
assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/
end
def test_edge_option
generator([destination_root], :edge => true).expects(:run).with("#{@bundle_command} install")
- silence(:stdout){ generator.invoke }
+ silence(:stdout){ generator.invoke_all }
assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$/
end
@@ -262,7 +297,7 @@ class CustomAppGeneratorTest < Rails::Generators::TestCase
template.instance_eval "def read; self; end" # Make the string respond to read
generator([destination_root], :builder => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- capture(:stdout) { generator.invoke }
+ capture(:stdout) { generator.invoke_all }
DEFAULT_APP_FILES.each{ |path| assert_no_file path }
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index ea469cb3c8..f12445ae35 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -216,4 +216,19 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
# Stylesheets (should not be removed)
assert_file "public/stylesheets/scaffold.css"
end
+
+ def test_scaffold_generator_on_revoke_does_not_mutilate_legacy_map_parameter
+ run_generator
+
+ # Add a |map| parameter to the routes block manually
+ route_path = File.expand_path("config/routes.rb", destination_root)
+ content = File.read(route_path).gsub(/\.routes\.draw do/) do |match|
+ "#{match} |map|"
+ end
+ File.open(route_path, "wb") { |file| file.write(content) }
+
+ run_generator ["product_line"], :behavior => :revoke
+
+ assert_file "config/routes.rb", /\.routes\.draw do\s*\|map\|\s*$/
+ end
end
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 74a09d4bde..f93800a5ae 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -108,7 +108,7 @@ class GeneratorsTest < Rails::Generators::TestCase
assert_match /^ fixjour$/, output
end
- def test_rails_generators_does_not_show_activerecord_hooks
+ def test_rails_generators_does_not_show_active_record_hooks
output = capture(:stdout){ Rails::Generators.help }
assert_match /ActiveRecord:/, output
assert_match /^ active_record:fixjour$/, output
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index 008247cb1a..80fae8c543 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -118,13 +118,23 @@ class PathsTest < ActiveSupport::TestCase
assert_raise(RuntimeError) { @root << "/biz" }
end
- test "it is possible to add a path that should be loaded only once" do
+ test "it is possible to add a path that should be autoloaded only once" do
@root.app = "/app"
@root.app.autoload_once!
assert @root.app.autoload_once?
assert @root.autoload_once.include?(@root.app.paths.first)
end
+ test "it is possible to remove a path that should be autoloaded only once" do
+ @root.app = "/app"
+ @root.app.autoload_once!
+ assert @root.app.autoload_once?
+
+ @root.app.skip_autoload_once!
+ assert !@root.app.autoload_once?
+ assert !@root.autoload_once.include?(@root.app.paths.first)
+ end
+
test "it is possible to add a path without assignment and specify it should be loaded only once" do
@root.app "/app", :autoload_once => true
assert @root.app.autoload_once?
@@ -152,13 +162,23 @@ class PathsTest < ActiveSupport::TestCase
assert_equal 2, @root.autoload_once.size
end
- test "it is possible to mark a path as eager" do
+ test "it is possible to mark a path as eager loaded" do
@root.app = "/app"
@root.app.eager_load!
assert @root.app.eager_load?
assert @root.eager_load.include?(@root.app.paths.first)
end
+ test "it is possible to skip a path from eager loading" do
+ @root.app = "/app"
+ @root.app.eager_load!
+ assert @root.app.eager_load?
+
+ @root.app.skip_eager_load!
+ assert !@root.app.eager_load?
+ assert !@root.eager_load.include?(@root.app.paths.first)
+ end
+
test "it is possible to add a path without assignment and mark it as eager" do
@root.app "/app", :eager_load => true
assert @root.app.eager_load?
diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb
index c74cc01dc1..db0fd87491 100644
--- a/railties/test/railties/railtie_test.rb
+++ b/railties/test/railties/railtie_test.rb
@@ -103,6 +103,22 @@ module RailtiesTest
assert $ran_block
end
+ test "console block is executed when MyApp.load_console is called" do
+ $ran_block = false
+
+ class MyTie < Rails::Railtie
+ console do
+ $ran_block = true
+ end
+ end
+
+ require "#{app_path}/config/environment"
+
+ assert !$ran_block
+ AppTemplate::Application.load_console
+ assert $ran_block
+ end
+
test "railtie can add initializers" do
$ran_block = false