aboutsummaryrefslogtreecommitdiffstats
path: root/railties
diff options
context:
space:
mode:
authorMike Gunderloy <MikeG1@larkfarm.com>2009-03-24 20:06:59 -0500
committerMike Gunderloy <MikeG1@larkfarm.com>2009-03-24 20:06:59 -0500
commitfab9d3b59dc87faec96aa01ce89402a9c3e57df8 (patch)
treefc40fe92aac64a926ba5edd868a031b28f320c61 /railties
parent4e27ca4c28432cd735a8ccb82bbaff37941a9d3b (diff)
parent0e6b9695aff6500ad48c4dd9ab61343d7090b030 (diff)
downloadrails-fab9d3b59dc87faec96aa01ce89402a9c3e57df8.tar.gz
rails-fab9d3b59dc87faec96aa01ce89402a9c3e57df8.tar.bz2
rails-fab9d3b59dc87faec96aa01ce89402a9c3e57df8.zip
Merge branch 'master' of git@github.com:lifo/docrails
Conflicts: railties/guides/source/2_3_release_notes.textile
Diffstat (limited to 'railties')
-rw-r--r--railties/CHANGELOG7
-rw-r--r--railties/Rakefile11
-rw-r--r--railties/builtin/rails_info/rails/info.rb4
-rw-r--r--railties/guides/images/fxn.jpgbin17868 -> 17773 bytes
-rw-r--r--railties/guides/rails_guides.rb28
-rw-r--r--railties/guides/rails_guides/generator.rb28
-rw-r--r--railties/guides/rails_guides/indexer.rb2
-rw-r--r--railties/guides/rails_guides/levenshtein.rb29
-rw-r--r--railties/guides/source/2_3_release_notes.textile32
-rw-r--r--railties/guides/source/action_controller_overview.textile2
-rw-r--r--railties/guides/source/active_record_querying.textile33
-rw-r--r--railties/guides/source/activerecord_validations_callbacks.textile2
-rw-r--r--railties/guides/source/association_basics.textile14
-rw-r--r--railties/guides/source/contribute.textile4
-rw-r--r--railties/guides/source/credits.erb.textile6
-rw-r--r--railties/guides/source/form_helpers.textile2
-rw-r--r--railties/guides/source/getting_started.textile12
-rw-r--r--railties/guides/source/i18n.textile8
-rw-r--r--railties/guides/source/layout.html.erb18
-rw-r--r--railties/guides/source/layouts_and_rendering.textile2
-rw-r--r--railties/guides/source/migrations.textile8
-rw-r--r--railties/guides/source/nested_model_forms.textile222
-rw-r--r--railties/guides/source/performance_testing.textile12
-rw-r--r--railties/guides/source/rails_on_rack.textile2
-rw-r--r--railties/guides/source/routing.textile2
-rw-r--r--railties/guides/source/security.textile17
-rw-r--r--railties/guides/source/testing.textile4
-rw-r--r--railties/lib/commands/plugin.rb446
-rw-r--r--railties/lib/rails/rack/metal.rb2
-rw-r--r--railties/lib/rails/version.rb2
-rw-r--r--railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb2
-rw-r--r--railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb2
-rw-r--r--railties/test/fixtures/metal/pluralmetal/app/metal/legacy_routes.rb5
-rw-r--r--railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb2
-rw-r--r--railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb2
-rw-r--r--railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb2
-rw-r--r--railties/test/metal_test.rb6
37 files changed, 425 insertions, 557 deletions
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index de506dfbbb..98e3a861e8 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,9 +1,8 @@
-*2.3.1 [RC2] (March 5, 2009)*
-
-* Allow metal to live in plugins #2045 [Matthew Rudy]
+*2.3.2 [Final] (March 15, 2009)*
+* Remove outdated script/plugin options [Pratik Naik]
-*2.3.0 [RC1] (February 1st, 2009)*
+* Allow metal to live in plugins #2045 [Matthew Rudy]
* Added metal [Josh Peek]
diff --git a/railties/Rakefile b/railties/Rakefile
index 4b524f1c6f..a9adbda0b5 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -247,6 +247,7 @@ end
desc 'Generate guides (for authors), use ONLY=foo to process just "foo.textile"'
task :guides do
+ ENV["WARN_BROKEN_LINKS"] = "1" # authors can't disable this
ruby "guides/rails_guides.rb"
end
@@ -311,11 +312,11 @@ spec = Gem::Specification.new do |s|
EOF
s.add_dependency('rake', '>= 0.8.3')
- s.add_dependency('activesupport', '= 2.3.1' + PKG_BUILD)
- s.add_dependency('activerecord', '= 2.3.1' + PKG_BUILD)
- s.add_dependency('actionpack', '= 2.3.1' + PKG_BUILD)
- s.add_dependency('actionmailer', '= 2.3.1' + PKG_BUILD)
- s.add_dependency('activeresource', '= 2.3.1' + PKG_BUILD)
+ s.add_dependency('activesupport', '= 2.3.2' + PKG_BUILD)
+ s.add_dependency('activerecord', '= 2.3.2' + PKG_BUILD)
+ s.add_dependency('actionpack', '= 2.3.2' + PKG_BUILD)
+ s.add_dependency('actionmailer', '= 2.3.2' + PKG_BUILD)
+ s.add_dependency('activeresource', '= 2.3.2' + PKG_BUILD)
s.rdoc_options << '--exclude' << '.'
s.has_rdoc = false
diff --git a/railties/builtin/rails_info/rails/info.rb b/railties/builtin/rails_info/rails/info.rb
index 7b6f09ac69..a20d9bfe62 100644
--- a/railties/builtin/rails_info/rails/info.rb
+++ b/railties/builtin/rails_info/rails/info.rb
@@ -85,6 +85,10 @@ module Rails
Gem::RubyGemsVersion
end
+ property 'Rack version' do
+ ::Rack.release
+ end
+
# The Rails version.
property 'Rails version' do
Rails::VERSION::STRING
diff --git a/railties/guides/images/fxn.jpg b/railties/guides/images/fxn.jpg
index b661a0e402..81999341f1 100644
--- a/railties/guides/images/fxn.jpg
+++ b/railties/guides/images/fxn.jpg
Binary files differ
diff --git a/railties/guides/rails_guides.rb b/railties/guides/rails_guides.rb
index 6da7de890e..e0532812e4 100644
--- a/railties/guides/rails_guides.rb
+++ b/railties/guides/rails_guides.rb
@@ -1,17 +1,28 @@
pwd = File.dirname(__FILE__)
$: << pwd
-$: << File.join(pwd, "../../activesupport/lib")
-$: << File.join(pwd, "../../actionpack/lib")
-require "action_controller"
-require "action_view"
+begin
+ as_lib = File.join(pwd, "../../activesupport/lib")
+ ap_lib = File.join(pwd, "../../actionpack/lib")
+
+ $: << as_lib if File.directory?(as_lib)
+ $: << ap_lib if File.directory?(ap_lib)
+
+ require "action_controller"
+ require "action_view"
+rescue LoadError
+ require 'rubygems'
+ gem "actionpack", '>= 2.3'
+
+ require "action_controller"
+ require "action_view"
+end
-# Require rubygems after loading Action View
-require 'rubygems'
begin
- gem 'RedCloth', '>= 4.1.1'# Need exactly 4.1.1
+ require 'rubygems'
+ gem 'RedCloth', '>= 4.1.1'
rescue Gem::LoadError
- $stderr.puts %(Missing the RedCloth 4.1.1 gem.\nPlease `gem install -v=4.1.1 RedCloth` to generate the guides.)
+ $stderr.puts %(Generating Guides requires RedCloth 4.1.1+)
exit 1
end
@@ -22,6 +33,7 @@ module RailsGuides
autoload :Indexer, "rails_guides/indexer"
autoload :Helpers, "rails_guides/helpers"
autoload :TextileExtensions, "rails_guides/textile_extensions"
+ autoload :Levenshtein, "rails_guides/levenshtein"
end
RedCloth.send(:include, RailsGuides::TextileExtensions)
diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb
index dd147c4d5f..f93282db2e 100644
--- a/railties/guides/rails_guides/generator.rb
+++ b/railties/guides/rails_guides/generator.rb
@@ -57,7 +57,7 @@ module RailsGuides
result = view.render(:layout => 'layout', :text => textile(body))
f.write result
- warn_about_broken_links(result)
+ warn_about_broken_links(result) if ENV.key?("WARN_BROKEN_LINKS")
end
end
end
@@ -137,16 +137,34 @@ module RailsGuides
end
def warn_about_broken_links(html)
+ anchors = extract_anchors(html)
+ check_fragment_identifiers(html, anchors)
+ end
+
+ def extract_anchors(html)
# Textile generates headers with IDs computed from titles.
- anchors = Set.new(html.scan(/<h\d\s+id="([^"]+)/).flatten)
+ anchors = Set.new
+ html.scan(/<h\d\s+id="([^"]+)/).flatten.each do |anchor|
+ if anchors.member?(anchor)
+ puts "*** DUPLICATE HEADER ID: #{anchor}, please consider rewording" if ENV.key?("WARN_DUPLICATE_HEADERS")
+ else
+ anchors << anchor
+ end
+ end
+
# Also, footnotes are rendered as paragraphs this way.
anchors += Set.new(html.scan(/<p\s+class="footnote"\s+id="([^"]+)/).flatten)
-
- # Check fragment identifiers.
+ return anchors
+ end
+
+ def check_fragment_identifiers(html, anchors)
html.scan(/<a\s+href="#([^"]+)/).flatten.each do |fragment_identifier|
next if fragment_identifier == 'mainCol' # in layout, jumps to some DIV
unless anchors.member?(fragment_identifier)
- puts "BROKEN LINK: ##{fragment_identifier}"
+ guess = anchors.min { |a, b|
+ Levenshtein.distance(fragment_identifier, a) <=> Levenshtein.distance(fragment_identifier, b)
+ }
+ puts "*** BROKEN LINK: ##{fragment_identifier}, perhaps you meant ##{guess}."
end
end
end
diff --git a/railties/guides/rails_guides/indexer.rb b/railties/guides/rails_guides/indexer.rb
index 7cb254d0b0..5b5ad3fee1 100644
--- a/railties/guides/rails_guides/indexer.rb
+++ b/railties/guides/rails_guides/indexer.rb
@@ -29,7 +29,7 @@ module RailsGuides
return level_hash
elsif level == current_level
index = counters.join(".")
- bookmark = '#' + title.gsub(/[^a-z0-9\-_]+/i, '').underscore.dasherize
+ bookmark = '#' + title.strip.downcase.gsub(/\s+|_/, '-').delete('^a-z0-9-')
raise "Parsing Fail" unless @result.sub!(matched, "h#{level}(#{bookmark}). #{index}#{title}")
diff --git a/railties/guides/rails_guides/levenshtein.rb b/railties/guides/rails_guides/levenshtein.rb
new file mode 100644
index 0000000000..4010b61e26
--- /dev/null
+++ b/railties/guides/rails_guides/levenshtein.rb
@@ -0,0 +1,29 @@
+module Levenshtein
+ # Based on the pseudocode in http://en.wikipedia.org/wiki/Levenshtein_distance.
+ def self.distance(s1, s2)
+ s = s1.unpack('U*')
+ t = s2.unpack('U*')
+ m = s.length
+ n = t.length
+
+ # matrix initialization
+ d = []
+ 0.upto(m) { |i| d << [i] }
+ 0.upto(n) { |j| d[0][j] = j }
+
+ # distance computation
+ 1.upto(m) do |i|
+ 1.upto(n) do |j|
+ cost = s[i] == t[j] ? 0 : 1
+ d[i][j] = [
+ d[i-1][j] + 1, # deletion
+ d[i][j-1] + 1, # insertion
+ d[i-1][j-1] + cost, # substitution
+ ].min
+ end
+ end
+
+ # all done
+ return d[m][n]
+ end
+end
diff --git a/railties/guides/source/2_3_release_notes.textile b/railties/guides/source/2_3_release_notes.textile
index eaf83d7da8..6a97fd2cd1 100644
--- a/railties/guides/source/2_3_release_notes.textile
+++ b/railties/guides/source/2_3_release_notes.textile
@@ -20,25 +20,25 @@ Rails has now broken with its CGI past, and uses Rack everywhere. This required
Here's a summary of the rack-related changes:
* +script/server+ has been switched to use Rack, which means it supports any Rack compatible server. +script/server+ will also pick up a rackup configuration file if one exists. By default, it will look for a +config.ru+ file, but you can override this with the +-c+ switch.
-* The FCGI handler goes through Rack
-* +ActionController::Dispatcher+ maintains its own default middleware stack. Middlewares can be injected in, reordered, and removed. The stack is compiled into a chain on boot. You can configure the middleware stack in +environment.rb+
+* The FCGI handler goes through Rack.
+* +ActionController::Dispatcher+ maintains its own default middleware stack. Middlewares can be injected in, reordered, and removed. The stack is compiled into a chain on boot. You can configure the middleware stack in +environment.rb+.
* The +rake middleware+ task has been added to inspect the middleware stack. This is useful for debugging the order of the middleware stack.
* The integration test runner has been modified to execute the entire middleware and application stack. This makes integration tests perfect for testing Rack middleware.
* +ActionController::CGIHandler+ is a backwards compatible CGI wrapper around Rack. The +CGIHandler+ is meant to take an old CGI object and convert its environment information into a Rack compatible form.
-* +CgiRequest+ and +CgiResponse+ have been removed
+* +CgiRequest+ and +CgiResponse+ have been removed.
* Session stores are now lazy loaded. If you never access the session object during a request, it will never attempt to load the session data (parse the cookie, load the data from memcache, or lookup an Active Record object).
* You no longer need to use +CGI::Cookie.new+ in your tests for setting a cookie value. Assigning a +String+ value to request.cookies["foo"] now sets the cookie as expected.
-* +CGI::Session::CookieStore+ has been replaced by +ActionController::Session::CookieStore+
-* +CGI::Session::MemCacheStore+ has been replaced by +ActionController::Session::MemCacheStore+
-* +CGI::Session::ActiveRecordStore+ has been replaced by +ActiveRecord::SessionStore+
-* You can still change your session store with +ActionController::Base.session_store = :active_record_store+
-* Default session options are still set with +ActionController::Base.session = { :key => "..." }+. However, the +:session_domain+ option has been renamed to +:domain+.
-* The mutex that normally wraps your entire request has been moved into middleware, +ActionController::Lock+
+* +CGI::Session::CookieStore+ has been replaced by +ActionController::Session::CookieStore+.
+* +CGI::Session::MemCacheStore+ has been replaced by +ActionController::Session::MemCacheStore+.
+* +CGI::Session::ActiveRecordStore+ has been replaced by +ActiveRecord::SessionStore+.
+* You can still change your session store with +ActionController::Base.session_store = :active_record_store+.
+* Default sessions options are still set with +ActionController::Base.session = { :key => "..." }+. However, the +:session_domain+ option has been renamed to +:domain+.
+* The mutex that normally wraps your entire request has been moved into middleware, +ActionController::Lock+.
* +ActionController::AbstractRequest+ and +ActionController::Request+ have been unified. The new +ActionController::Request+ inherits from +Rack::Request+. This affects access to +response.headers['type']+ in test requests. Use +response.content_type+ instead.
* +ActiveRecord::QueryCache+ middleware is automatically inserted onto the middleware stack if +ActiveRecord+ has been loaded. This middleware sets up and flushes the per-request Active Record query cache.
* The Rails router and controller classes follow the Rack spec. You can call a controller directly with +SomeController.call(env)+. The router stores the routing parameters in +rack.routing_args+.
-* +ActionController::Request+ inherits from +Rack::Request+
-* Instead of +config.action_controller.session = { :session_key => 'foo', ...+ use +config.action_controller.session = { :key => 'foo', ...+
+* +ActionController::Request+ inherits from +Rack::Request+.
+* Instead of +config.action_controller.session = { :session_key => 'foo', ...+ use +config.action_controller.session = { :key => 'foo', ...+.
* Using the +ParamsParser+ middleware preprocesses any XML, JSON, or YAML requests so they can be read normally with any +Rack::Request+ object after it.
h4. Renewed Support for Rails Engines
@@ -49,7 +49,7 @@ h3. Documentation
The "Ruby on Rails guides":http://guides.rubyonrails.org/ project has published several additional guides for Rails 2.3. In addition, a "separate site":http://guides.rails.info/ maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the "Rails wiki":http://newwiki.rubyonrails.org/ and early planning for a Rails Book.
-* More Information: "Rails Documentation Projects":http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects
+* More Information: "Rails Documentation Projects":http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects.
h3. Ruby 1.9.1 Support
@@ -185,16 +185,16 @@ MySQL supports a reconnect flag in its connections - if set to true, then the cl
h4. Other Active Record Changes
-* An extra +AS+ was removed from the generated SQL for has_and_belongs_to_many preloading, making it work better for some databases.
+* An extra +AS+ was removed from the generated SQL for +has_and_belongs_to_many+ preloading, making it work better for some databases.
* +ActiveRecord::Base#new_record?+ now returns +false+ rather than +nil+ when confronted with an existing record.
* A bug in quoting table names in some +has_many :through+ associations was fixed.
* You can now specify a particular timestamp for +updated_at+ timestamps: +cust = Customer.create(:name => "ABC Industries", :updated_at => 1.day.ago)+
* Better error messages on failed +find_by_attribute!+ calls.
* Active Record's +to_xml+ support gets just a little bit more flexible with the addition of a +:camelize+ option.
-* A bug in canceling callbacks from +before_update+ or +before_create_ was fixed.
+* A bug in canceling callbacks from +before_update+ or +before_create+ was fixed.
* Rake tasks for testing databases via JDBC have been added.
-* +validates_length_of+ will use a custom error message with the +:in+ or +:within+ options (if one is supplied)
-* Counts on scoped selects now work properly, so you can do things like +Account.scoped(:select => "DISTINCT credit_limit").count+
+* +validates_length_of+ will use a custom error message with the +:in+ or +:within+ options (if one is supplied).
+* Counts on scoped selects now work properly, so you can do things like +Account.scoped(:select => "DISTINCT credit_limit").count+.
* +ActiveRecord::Base#invalid?+ now works as the opposite of +ActiveRecord::Base#valid?+.
h3. Action Controller
diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile
index 3ce6720815..054ca99985 100644
--- a/railties/guides/source/action_controller_overview.textile
+++ b/railties/guides/source/action_controller_overview.textile
@@ -20,7 +20,7 @@ For most conventional RESTful applications, the controller will receive the requ
A controller can thus be thought of as a middle man between models and views. It makes the model data available to the view so it can display that data to the user, and it saves or updates data from the user to the model.
-NOTE: For more details on the routing process, see "Rails Routing from the Outside In":routing_outside_in.html.
+NOTE: For more details on the routing process, see "Rails Routing from the Outside In":routing.html.
h3. Methods and Actions
diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile
index 986d8344d3..b112c4f5fb 100644
--- a/railties/guides/source/active_record_querying.textile
+++ b/railties/guides/source/active_record_querying.textile
@@ -111,7 +111,6 @@ h5. +last+
<tt>Model.last(options = nil)</tt> finds the last record matched by the supplied options. If no +options+ are supplied, the last matching record is returned. For example:
<ruby>
-# Find the client with primary key (id) 10.
client = Client.last
=> #<Client id: 221, name: => "Russel">
</ruby>
@@ -236,7 +235,7 @@ The above will yield the supplied block with +1000+ invoices every time.
h3. Conditions
-The +find+ method allows you to specify conditions to limit the records returned, representing the WHERE-part of the SQL statement. Conditions can either be specified as a string, array, or hash.
+The +find+ method allows you to specify conditions to limit the records returned, representing the +WHERE+-part of the SQL statement. Conditions can either be specified as a string, array, or hash.
h4. Pure String Conditions
@@ -276,11 +275,11 @@ Client.first(:conditions => "orders_count = #{params[:orders]}")
is because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database *as-is*. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string.
-TIP: For more information on the dangers of SQL injection, see the "Ruby on Rails Security Guide":../security.html#_sql_injection.
+TIP: For more information on the dangers of SQL injection, see the "Ruby on Rails Security Guide":security.html#sql-injection.
h5. Placeholder Conditions
-Similar to the +(?)+ replacement style of params, you can also specify keys/values hash in your Array conditions:
+Similar to the +(?)+ replacement style of params, you can also specify keys/values hash in your array conditions:
<ruby>
Client.all(:conditions =>
@@ -348,7 +347,7 @@ Client.all(:conditions =>
["created_at >= ? AND created_at <= ?", params[:start_date], params[:end_date]])
</ruby>
-Just like in Ruby. If you want a shorter syntax be sure to check out the "Hash Conditions":hash-conditions section later on in the guide.
+Just like in Ruby. If you want a shorter syntax be sure to check out the "Hash Conditions":#hash-conditions section later on in the guide.
h4. Hash Conditions
@@ -382,7 +381,7 @@ This will find all clients created yesterday by using a +BETWEEN+ SQL statement:
SELECT * FROM clients WHERE (clients.created_at BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00')
</sql>
-This demonstrates a shorter syntax for the examples in "Array Conditions":#arrayconditions
+This demonstrates a shorter syntax for the examples in "Array Conditions":#array-conditions
h5. Subset Conditions
@@ -444,7 +443,7 @@ By default, <tt>Model.find</tt> selects all the fields from the result set using
To select only a subset of fields from the result set, you can specify the subset via +:select+ option on the +find+.
-NOTE: If the +:select+ option is used, all the returning objects will be "read only":#readonlyobjects.
+NOTE: If the +:select+ option is used, all the returning objects will be "read only":#readonly-objects.
<br />
@@ -639,13 +638,13 @@ h4. Using a String SQL Fragment
You can just supply the raw SQL specifying the +JOIN+ clause to the +:joins+ option. For example:
<ruby>
-Client.all(:joins => 'LEFT OUTER JOIN addresses ON addresses.client_id = client.id')
+Client.all(:joins => 'LEFT OUTER JOIN addresses ON addresses.client_id = clients.id')
</ruby>
This will result in the following SQL:
<sql>
-SELECT clients.* FROM clients INNER JOIN addresses ON addresses.client_id = clients.id
+SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = clients.id
</sql>
h4. Using Array/Hash of Named Associations
@@ -654,7 +653,7 @@ WARNING: This method only works with +INNER JOIN+,
<br />
-Active Record lets you use the names of the "associations":association_basics.html defined on the Model, as a shortcut for specifying the +:joins+ option.
+Active Record lets you use the names of the "associations":association_basics.html defined on the model as a shortcut for specifying the +:joins+ option.
For example, consider the following +Category+, +Post+, +Comments+ and +Guest+ models:
@@ -722,7 +721,7 @@ Category.all :joins => {:posts => [{:comments => :guest}, :tags]}
h4. Specifying Conditions on the Joined Tables
-You can specify conditions on the joined tables using the regular "Array":#arrayconditions and "String":#purestringconditions conditions. "Hash conditions":#hashconditions provides a special syntax for specifying conditions for the joined tables:
+You can specify conditions on the joined tables using the regular "Array":#array-conditions and "String":#pure-string-conditions conditions. "Hash conditions":#hash-conditions provides a special syntax for specifying conditions for the joined tables:
<ruby>
time_range = (Time.now.midnight - 1.day)..Time.now.midnight
@@ -780,7 +779,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 Array, Hash or a nested Hash of Array/Hash with +:include+ find option.
+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 +:include+ option.
h5. Array of Multiple Associations
@@ -800,15 +799,15 @@ The above code finds the category with id 1 and eager loads all the posts associ
h4. Specifying Conditions on Eager Loaded Associations
-Even though Active Record lets you specify conditions on the eager loaded associations just like +:joins+, the recommended way is to use ":joins":#joiningtables instead.
+Even though Active Record lets you specify conditions on the eager loaded associations just like +:joins+, the recommended way is to use ":joins":#joining-tables instead.
h3. Dynamic Finders
-For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +name+ on your Client model for example, you get +find_by_name+ and +find_all_by_name+ for free from Active Record. If you have also have a +locked+ field on the Client model, you also get +find_by_locked+ and +find_all_by_locked+.
+For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +name+ on your +Client+ model for example, you get +find_by_name+ and +find_all_by_name+ for free from Active Record. If you have also have a +locked+ field on the +Client+ model, you also get +find_by_locked+ and +find_all_by_locked+.
You can do +find_last_by_*+ methods too which will find the last record matching your argument.
-You can specify an exclamation point (!) on the end of the dynamic finders to get them to raise an ActiveRecord::RecordNotFound error if they do not return any records, like +Client.find_by_name!("Ryan")+
+You can specify an exclamation point (!) on the end of the dynamic finders to get them to raise an +ActiveRecord::RecordNotFound+ error if they do not return any records, like +Client.find_by_name!("Ryan")+
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_name_and_locked("Ryan", true)+.
@@ -829,9 +828,9 @@ COMMIT
client = Client.find_or_initialize_by_name('Ryan')
</ruby>
-will either assign an existing client object with the name 'Ryan' to the client local variable, or initialize a new object similar to calling +Client.new(:name => 'Ryan')+. From here, you can modify other fields in client by calling the attribute setters on it: +client.locked = true+ and when you want to write it to the database just call +save+ on it.
+will either assign an existing client object with the name "Ryan" to the client local variable, or initialize a new object similar to calling +Client.new(:name => 'Ryan')+. From here, you can modify other fields in client by calling the attribute setters on it: +client.locked = true+ and when you want to write it to the database just call +save+ on it.
-h3. Finding By SQL
+h3. Finding by SQL
If you'd like to use your own SQL to find records in a table you can use +find_by_sql+. The +find_by_sql+ method will return an array of objects even the underlying query returns just a single record. For example you could run this query:
diff --git a/railties/guides/source/activerecord_validations_callbacks.textile b/railties/guides/source/activerecord_validations_callbacks.textile
index 3f62c76d81..5ae4884297 100644
--- a/railties/guides/source/activerecord_validations_callbacks.textile
+++ b/railties/guides/source/activerecord_validations_callbacks.textile
@@ -152,7 +152,7 @@ end
>> Person.create.errors.invalid?(:name) # => true
</ruby>
-We'll cover validation errors in greater depth in the "Working with Validation Errors":#workingwith-validation-errors section. For now, let's turn to the built-in validation helpers that Rails provides by default.
+We'll cover validation errors in greater depth in the "Working with Validation Errors":#working-with-validation-errors section. For now, let's turn to the built-in validation helpers that Rails provides by default.
h3. Validation Helpers
diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile
index 772df83d6a..03e22bd6fe 100644
--- a/railties/guides/source/association_basics.textile
+++ b/railties/guides/source/association_basics.textile
@@ -724,7 +724,7 @@ NOTE: There's no need to use +:include+ for immediate associations - that is, if
h6. +:polymorphic+
-Passing +true+ to the +:polymorphic+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphicassociations">earlier in this guide</a>.
+Passing +true+ to the +:polymorphic+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphic-associations">earlier in this guide</a>.
h6. +:readonly+
@@ -848,7 +848,7 @@ The +has_one+ association supports these options:
h6. +:as+
-Setting the +:as+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphicassociations">earlier in this guide</a>.
+Setting the +:as+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphic-associations">earlier in this guide</a>.
h6. +:autosave+
@@ -952,7 +952,7 @@ The +:source_type+ option specifies the source association type for a +has_one :
h6. :through
-The +:through+ option specifies a join model through which to perform the query. +has_one :through+ associations were discussed in detail <a href="#thehas-onethroughassociation">earlier in this guide</a>.
+The +:through+ option specifies a join model through which to perform the query. +has_one :through+ associations were discussed in detail <a href="#the-has-one-through-association">earlier in this guide</a>.
h6. +:validate+
@@ -1158,7 +1158,7 @@ The +has_many+ association supports these options:
h6. +:as+
-Setting the +:as+ option indicates that this is a polymorphic association, as discussed <a href="#polymorphicassociations">earlier in this guide</a>.
+Setting the +:as+ option indicates that this is a polymorphic association, as discussed <a href="#polymorphic-associations">earlier in this guide</a>.
h6. +:autosave+
@@ -1210,7 +1210,7 @@ NOTE: This option is ignored when you use the +:through+ option on the associati
h6. +:extend+
-The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#associationextensions">later in this guide</a>.
+The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#association-extensions">later in this guide</a>.
h6. +:finder_sql+
@@ -1323,7 +1323,7 @@ The +:source_type+ option specifies the source association type for a +has_many
h6. +:through+
-The +:through+ option specifies a join model through which to perform the query. +has_many :through+ associations provide a way to implement many-to-many relationships, as discussed <a href="#thehas-manythroughassociation">earlier in this guide</a>.
+The +:through+ option specifies a join model through which to perform the query. +has_many :through+ associations provide a way to implement many-to-many relationships, as discussed <a href="#the-has-many-through-association">earlier in this guide</a>.
h6. +:uniq+
@@ -1589,7 +1589,7 @@ Normally Rails automatically generates the proper SQL to remove links between th
h6. +:extend+
-The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#associationextensions">later in this guide</a>.
+The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#association-extensions">later in this guide</a>.
h6. +:finder_sql+
diff --git a/railties/guides/source/contribute.textile b/railties/guides/source/contribute.textile
index c8e3b9c202..650004bd09 100644
--- a/railties/guides/source/contribute.textile
+++ b/railties/guides/source/contribute.textile
@@ -7,12 +7,12 @@ endprologue.
h3. How to Contribute?
* We have an open commit policy: anyone is welcome to contribute, but you'll need to ask for commit access.
-* PM lifo at "GitHub":http://github.om asking for "docrails":http://github.com/lifo/docrails commit access.
+* PM lifo at "GitHub":http://github.com asking for "docrails":http://github.com/lifo/docrails/tree/master commit access.
* Guides are written in Textile, and reside at railties/guides/source in the docrails project.
* All images are in the railties/guides/images directory.
* Sample format : "Active Record Associations":http://github.com/lifo/docrails/blob/3e56a3832415476fdd1cb963980d0ae390ac1ed3/railties/guides/source/association_basics.textile
* Sample output : "Active Record Associations":http://guides.rails.info/association_basics.html
-* You can build the Guides during testing by running railties/guides/rails_guides.rb.
+* You can build the Guides during testing by running +rake guides+ in the +railties+ directory.
h3. What to Contribute?
diff --git a/railties/guides/source/credits.erb.textile b/railties/guides/source/credits.erb.textile
index 2b1c02b9a9..b09a931fd6 100644
--- a/railties/guides/source/credits.erb.textile
+++ b/railties/guides/source/credits.erb.textile
@@ -12,7 +12,7 @@ p. We'd like to thank the following people for their tireless contributions to t
<% end %>
<% author('Pratik Naik', 'lifo') do %>
- Pratik Naik is a Ruby on Rails consultant with "ActionRails":http://www.actionrails.com and also a member of the "Rails core team":http://rubyonrails.com/core. He maintains a blog at "has_many :bugs, :through => :rails":http://m.onkey.org and has an active "twitter account":http://twitter.com/lifo.
+ Pratik Naik is a Ruby on Rails consultant with "ActionRails":http://www.actionrails.com and also a member of the "Rails core team":http://rubyonrails.org/core. He maintains a blog at "has_many :bugs, :through => :rails":http://m.onkey.org and has an active "twitter account":http://twitter.com/lifo.
<% end %>
<% author('Xavier Noria', 'fxn', 'fxn.jpg') do %>
@@ -22,7 +22,7 @@ p. We'd like to thank the following people for their tireless contributions to t
<h3 class="section">Rails Guides Designers</h3>
<% author('Jason Zimdars', 'jz') do %>
- Jason Zimdars is an experienced creative director and web designer who has lead UI and UX design for numerous websites and web applications. You can see more of his design and writing at <a href="http://www.thinkcage.com/">Thinkcage.com</a> or follow him on <a href="http://www.twitter.com/JZ">Twitter</a>.
+ Jason Zimdars is an experienced creative director and web designer who has lead UI and UX design for numerous websites and web applications. You can see more of his design and writing at <a href="http://www.thinkcage.com/">Thinkcage.com</a> or follow him on <a href="http://twitter.com/JZ">Twitter</a>.
<% end %>
<h3 class="section">Rails Guides Authors</h3>
@@ -44,7 +44,7 @@ p. We'd like to thank the following people for their tireless contributions to t
<% end %>
<% author('Emilio Tagua', 'miloops') do %>
- Emilio Tagua -- a.k.a. miloops -- is an Argentinian entrepreneur, developer, open source contributor and Rails evangelist. Cofounder of "Eventioz":http://www.eventioz.com. He has been using Rails since 2006 and contributing since early 2008. Can be found at gmail, twitter, freenode, everywhere as miloops.
+ Emilio Tagua -- a.k.a. miloops -- is an Argentinian entrepreneur, developer, open source contributor and Rails evangelist. Cofounder of "Eventioz":http://eventioz.com. He has been using Rails since 2006 and contributing since early 2008. Can be found at gmail, twitter, freenode, everywhere as miloops.
<% end %>
<% author('Heiko Webers', 'hawe') do %>
diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile
index 9ab4deff4e..22d24b0903 100644
--- a/railties/guides/source/form_helpers.textile
+++ b/railties/guides/source/form_helpers.textile
@@ -683,7 +683,7 @@ This would result in +params[:addresses]+ being an array of hashes with keys +li
There's a restriction, however, while hashes can be nested arbitrarily, only one level of "arrayness" is allowed. Arrays can be usually replaced by hashes, for example instead of having an array of model objects one can have a hash of model objects keyed by their id, an array index or some other parameter.
-WARNING: Array parameters do not play well with the +check_box+ helper. According to the HTML specification unchecked checkboxes submit no value. However it is often convenient for a checkbox to always submit a value. The +check_box+ helper fakes this by creating a second hidden input with the same name. If the checkbox is unchecked only the hidden input is submitted and if it is checked then both are submitted but the value submitted by the checkbox takes precedence. When working with array parameters this duplicate submission will confuse Rails since duplicate input names are how it decides when to start a new array element. It is preferable to either use +check_box_tag+ or to use hashes instead of arrays.
+WARNING: Array parameters do not play well with the +check_box+ helper. According to the HTML specification unchecked checkboxes submit no value. However it is often convenient for a checkbox to always submit a value. The +check_box+ helper fakes this by creating an auxiliary hidden input with the same name. If the checkbox is unchecked only the hidden input is submitted and if it is checked then both are submitted but the value submitted by the checkbox takes precedence. When working with array parameters this duplicate submission will confuse Rails since duplicate input names are how it decides when to start a new array element. It is preferable to either use +check_box_tag+ or to use hashes instead of arrays.
h4. Using Form Helpers
diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile
index 6e02cfe1bd..97f141b5e9 100644
--- a/railties/guides/source/getting_started.textile
+++ b/railties/guides/source/getting_started.textile
@@ -1,4 +1,4 @@
-h2. Getting Started With Rails
+h2. Getting Started with Rails
This guide covers getting up and running with Ruby on Rails. After reading it, you should be familiar with:
@@ -23,7 +23,7 @@ It is highly recommended that you *familiarize yourself with Ruby before diving
* "Mr. Neighborly’s Humble Little Ruby Book":http://www.humblelittlerubybook.com
* "Programming Ruby":http://www.rubycentral.com/book
-* "Why’s (Poignant) Guide to Ruby":http://poignantguide.net/ruby
+* "Why’s (Poignant) Guide to Ruby":http://poignantguide.net/ruby/
h3. What is Rails?
@@ -175,7 +175,7 @@ In any case, Rails will create a folder in your working directory called <tt>blo
|log/|Application log files.|
|public/|The only folder seen to the world as-is. This is where your images, javascript, stylesheets (CSS), and other static files go.|
|script/|Scripts provided by Rails to do recurring tasks, such as benchmarking, plugin installation, and starting the console or the web server.|
-|test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing_rails_applications.html|
+|test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing.html|
|tmp/|Temporary files|
|vendor/|A place for third-party code. In a typical Rails application, this includes Ruby Gems, the Rails source code (if you install it into your project) and plugins containing additional prepackaged functionality.|
@@ -310,7 +310,7 @@ This line illustrates one tiny bit of the "convention over configuration" approa
Now if you navigate to +http://localhost:3000+ in your browser, you'll see the +home/index+ view.
-NOTE. For more information about routing, refer to "Rails Routing from the Outside In":routing_outside_in.html.
+NOTE. For more information about routing, refer to "Rails Routing from the Outside In":routing.html.
h3. Getting Up and Running Quickly With Scaffolding
@@ -472,7 +472,7 @@ end
This code sets the +@posts+ instance variable to an array of all posts in the database. +Post.find(:all)+ or +Post.all+ calls the +Post+ model to return all of the posts that are currently in the database, with no limiting conditions.
-TIP: For more information on finding records with Active Record, see "Active Record Finders":finders.html.
+TIP: For more information on finding records with Active Record, see "Active Record Query Interface":active_record_querying.html.
The +respond_to+ block handles both HTML and XML calls to this action. If you browse to +http://localhost:3000/posts.xml+, you'll see all of the posts in XML format. The HTML format looks for a view in +app/views/posts/+ with a name that corresponds to the action name. Rails makes all of the instance variables from the action available to the view. Here's +app/view/posts/index.html.erb+:
@@ -846,7 +846,7 @@ end
Rails runs _before filters_ before any action in the controller. You can use the +:only+ clause to limit a before filter to only certain actions, or an +:except+ clause to specifically skip a before filter for certain actions. Rails also allows you to define _after filters_ that run after processing an action, as well as _around filters_ that surround the processing of actions. Filters can also be defined in external classes to make it easy to share them between controllers.
-For more information on filters, see the "Action Controller Basics":actioncontroller_basics.html guide.
+For more information on filters, see the "Action Controller Overview":action_controller_overview.html guide.
h3. Adding a Second Model
diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile
index 0b5f872a72..103ccb1c7a 100644
--- a/railties/guides/source/i18n.textile
+++ b/railties/guides/source/i18n.textile
@@ -40,7 +40,7 @@ Thus, the Ruby I18n gem is split into two parts:
As a user you should always only access the public methods on the I18n module, but it is useful to know about the capabilities of the backend.
-NOTE: It is possible (or even desirable) to swap the shipped Simple backend with a more powerful one, which would store translation data in a relational database, GetText dictionary, or similar. See section "Using different backends":#usingdifferentbackends below.
+NOTE: It is possible (or even desirable) to swap the shipped Simple backend with a more powerful one, which would store translation data in a relational database, GetText dictionary, or similar. See section "Using different backends":#using-different-backends below.
h4. The Public I18n API
@@ -91,7 +91,7 @@ This means, that in the +:en+ locale, the key _hello_ will map to the _Hello wor
The I18n library will use *English* as a *default locale*, i.e. if you don't set a different locale, +:en+ will be used for looking up translations.
-NOTE: The i18n library takes a *pragmatic approach* to locale keys (after "some discussion":http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), including only the _locale_ ("language") part, like +:en+, +:pl+, not the _region_ part, like +:en-US+ or +:en-UK+, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as +:cz+, +:th+ or +:es+ (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the +:en-US+ locale you would have $ as a currency symbol, while in +:en-UK+, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a +:en-UK+ dictionary. Various "Rails I18n plugins":http://rails-i18n.org/wiki such as "Globalize2":http://github.com/joshmh/globalize2 may help you implement it.
+NOTE: The i18n library takes a *pragmatic approach* to locale keys (after "some discussion":http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), including only the _locale_ ("language") part, like +:en+, +:pl+, not the _region_ part, like +:en-US+ or +:en-UK+, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as +:cz+, +:th+ or +:es+ (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the +:en-US+ locale you would have $ as a currency symbol, while in +:en-UK+, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a +:en-UK+ dictionary. Various "Rails I18n plugins":http://rails-i18n.org/wiki such as "Globalize2":http://github.com/joshmh/globalize2/tree/master may help you implement it.
The *translations load path* (+I18n.load_path+) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you.
@@ -146,7 +146,7 @@ This requires you to pass the locale as a URL query parameter as in +http://exam
Of course, you probably don't want to manually include the locale in every URL all over your application, or want the URLs look differently, e.g. the usual +http://example.com/pt/books+ versus +http://example.com/en/books+. Let's discuss the different options you have.
-IMPORTANT: The following examples rely on having available locales loaded into your application as an array of strings like +["en", "es", "gr"]+. This is not included in the current version of Rails 2.2 -- the forthcoming Rails version 2.3 will contain the easy accessor +available_locales+. (See "this commit":http://github.com/svenfuchs/i18n/commit/411f8fe7 and background at "Rails I18n Wiki":http://rails-i18n.org/wiki/pages/i18n-available_locales.)
+IMPORTANT: The following examples rely on having available locales loaded into your application as an array of strings like +["en", "es", "gr"]+. This is not included in the current version of Rails 2.2 -- the forthcoming Rails version 2.3 will contain the easy accessor +available_locales+. (See "this commit":http://github.com/svenfuchs/i18n/commit/411f8fe7c8f3f89e9b6b921fa62ed66cb92f3af4 and background at "Rails I18n Wiki":http://rails-i18n.org/wiki/pages/i18n-available_locales.)
So, for having available locales easily accessible in Rails 2.2, we have to include this support manually in an initializer, like this:
@@ -301,7 +301,7 @@ def extract_locale_from_accept_language_header
end
</ruby>
-Of course, in a production environment you would need much more robust code, and could use a plugin such as Iain Hecker's "http_accept_language":http://github.com/iain/http_accept_language or even Rack middleware such as Ryan Tomayko's "locale":http://github.com/rtomayko/rack-contrib/blob/master/lib/rack/locale.rb.
+Of course, in a production environment you would need much more robust code, and could use a plugin such as Iain Hecker's "http_accept_language":http://github.com/iain/http_accept_language/tree/master or even Rack middleware such as Ryan Tomayko's "locale":http://github.com/rtomayko/rack-contrib/blob/master/lib/rack/locale.rb.
h5. Using GeoIP (or Similar) Database
diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb
index 606f6a6cd1..eb66366d07 100644
--- a/railties/guides/source/layout.html.erb
+++ b/railties/guides/source/layout.html.erb
@@ -16,15 +16,15 @@
<body class="guide">
<div id="topNav">
<div class="wrapper">
- <strong>More at <a href="http://www.rubyonrails.org/">rubyonrails.org:</a> </strong>
- <a href="http://www.rubyonrails.org/">Overview</a> |
- <a href="http://www.rubyonrails.org/download">Download</a> |
- <a href="http://www.rubyonrails.org/deploy">Deploy</a> |
+ <strong>More at <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong>
+ <a href="http://rubyonrails.org/">Overview</a> |
+ <a href="http://rubyonrails.org/download">Download</a> |
+ <a href="http://rubyonrails.org/deploy">Deploy</a> |
<a href="http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/overview">Code</a> |
- <a href="http://www.rubyonrails.org/screencasts">Screencasts</a> |
- <a href="http://www.rubyonrails.org/documentation">Documentation</a> |
- <a href="http://www.rubyonrails.org/ecosystem">Ecosystem</a> |
- <a href="http://www.rubyonrails.org/community">Community</a> |
+ <a href="http://rubyonrails.org/screencasts">Screencasts</a> |
+ <a href="http://rubyonrails.org/documentation">Documentation</a> |
+ <a href="http://rubyonrails.org/ecosystem">Ecosystem</a> |
+ <a href="http://rubyonrails.org/community">Community</a> |
<a href="http://weblog.rubyonrails.org/">Blog</a>
</div>
</div>
@@ -95,7 +95,7 @@
<hr class="hide" />
<div id="footer">
<div class="wrapper">
- <p>This work is licensed under a <a href="http://creativecommons.org/licenses/by-sa/3.0">Creative Commons Attribution-Share Alike 3.0</a> License</a></p>
+ <p>This work is licensed under a <a href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-Share Alike 3.0</a> License</a></p>
<p>"Rails", "Ruby on Rails", and the Rails logo are trademarks of David Heinemeier Hansson. All rights reserved.</p>
</div>
</div>
diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile
index 69faa1b449..809d2b2172 100644
--- a/railties/guides/source/layouts_and_rendering.textile
+++ b/railties/guides/source/layouts_and_rendering.textile
@@ -959,7 +959,7 @@ On pages generated by +NewsController+, you want to hide the top menu and add a
<div id="right_menu">Right menu items here</div>
<%= yield(:news_content) or yield %>
<% end -%>
-<% render :file => 'layouts/application' %>
+<%= render :file => 'layouts/application' %>
</erb>
That's it. The News views will use the new layout, hiding the top menu and adding a new right menu inside the "content" div.
diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile
index 3a20e84ddb..5ed94c30b7 100644
--- a/railties/guides/source/migrations.textile
+++ b/railties/guides/source/migrations.textile
@@ -76,7 +76,7 @@ Active Record provides methods that perform common data definition tasks in a da
* +add_index+
* +remove_index+
-If you need to perform tasks specific to your database (for example create a "foreign key":#active-recordand-referential-integrity constraint) then the +execute+ function allows you to execute arbitrary SQL. A migration is just a regular Ruby class so you're not limited to these functions. For example after adding a column you could write code to set the value of that column for existing records (if necessary using your models).
+If you need to perform tasks specific to your database (for example create a "foreign key":#active-record-and-referential-integrity constraint) then the +execute+ function allows you to execute arbitrary SQL. A migration is just a regular Ruby class so you're not limited to these functions. For example after adding a column you could write code to set the value of that column for existing records (if necessary using your models).
On databases that support transactions with statements that change the schema (such as PostgreSQL), migrations are wrapped in a transaction. If the database does not support this (for example MySQL and SQLite) then when a migration fails the parts of it that succeeded will not be rolled back. You will have to unpick the changes that were made by hand.
@@ -327,11 +327,11 @@ end
</ruby>
will add an +attachment_id+ column and a string +attachment_type+ column with a default value of 'Photo'.
-NOTE: The +references+ helper does not actually create foreign key constraints for you. You will need to use +execute+ for that or a plugin that adds "foreign key support":#active-recordand-referential-integrity.
+NOTE: The +references+ helper does not actually create foreign key constraints for you. You will need to use +execute+ for that or a plugin that adds "foreign key support":#active-record-and-referential-integrity.
If the helpers provided by Active Record aren't enough you can use the +execute+ function to execute arbitrary SQL.
-For more details and examples of individual methods check the API documentation, in particular the documentation for "<tt>ActiveRecord::ConnectionAdapters::SchemaStatements</tt>":http://api.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html (which provides the methods available in the +up+ and +down+ methods), "<tt>ActiveRecord::ConnectionAdapters::TableDefinition</tt>":http://api.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html (which provides the methods available on the object yielded by +create_table+) and "<tt>ActiveRecord::ConnectionAdapters::Table</tt>":http://api.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/Table.html (which provides the methods available on the object yielded by +change_table+).
+For more details and examples of individual methods check the API documentation, in particular the documentation for "<tt>ActiveRecord::ConnectionAdapters::SchemaStatements</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html (which provides the methods available in the +up+ and +down+ methods), "<tt>ActiveRecord::ConnectionAdapters::TableDefinition</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html (which provides the methods available on the object yielded by +create_table+) and "<tt>ActiveRecord::ConnectionAdapters::Table</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html (which provides the methods available on the object yielded by +change_table+).
h4. Writing Your +down+ Method
@@ -410,7 +410,7 @@ Neither of these Rake tasks do anything you could not do with +db:migrate+, they
Lastly, the +db:reset+ task will drop the database, recreate it and load the current schema into it.
-NOTE: This is not the same as running all the migrations - see the section on "schema.rb":#schemadumpingandyou.
+NOTE: This is not the same as running all the migrations - see the section on "schema.rb":#schema-dumping-and-you.
h4. Being Specific
diff --git a/railties/guides/source/nested_model_forms.textile b/railties/guides/source/nested_model_forms.textile
new file mode 100644
index 0000000000..4b685b214e
--- /dev/null
+++ b/railties/guides/source/nested_model_forms.textile
@@ -0,0 +1,222 @@
+h2. Rails nested model forms
+
+Creating a form for a model _and_ its associations can become quite tedious. Therefor Rails provides helpers to assist in dealing with the complexities of generating these forms _and_ the required CRUD operations to create, update, and destroy associations.
+
+In this guide you will:
+
+* do stuff
+
+endprologue.
+
+NOTE: This guide assumes the user knows how to use the "Rails form helpers":form_helpers.html in general. Also, it’s *not* an API reference. For a complete reference please visit "the Rails API documentation":http://api.rubyonrails.org/.
+
+
+h3. Model setup
+
+To be able to use the nested model functionality in your forms, the model will need to support some basic operations.
+
+First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The +fields_for+ form helper will look for this method to decide whether or not a nested model form should be build.
+
+If the associated object is an array a form builder will be yielded for each object, else only a single form builder will be yielded.
+
+Consider a Person model with an associated Address. When asked to yield a nested FormBuilder for the +:address+ attribute, the +fields_for+ form helper will look for a method on the Person instance named +address_attributes=+.
+
+h4. ActiveRecord::Base model
+
+For an ActiveRecord::Base model and association this writer method is commonly defined with the +accepts_nested_attributes_for+ class method:
+
+h5. has_one
+
+<ruby>
+class Person < ActiveRecord::Base
+ has_one :address
+ accepts_nested_attributes_for :address
+end
+</ruby>
+
+h5. belongs_to
+
+<ruby>
+class Person < ActiveRecord::Base
+ belongs_to :firm
+ accepts_nested_attributes_for :firm
+end
+</ruby>
+
+h5. has_many / has_and_belongs_to_many
+
+<ruby>
+class Person < ActiveRecord::Base
+ has_many :projects
+ accepts_nested_attributes_for :projects
+end
+</ruby>
+
+h4. Custom model
+
+As you might have inflected from this explanation, you _don’t_ necessarily need an ActiveRecord::Base model to use this functionality. The following examples are sufficient to enable the nested model form behaviour:
+
+h5. Single associated object
+
+<ruby>
+class Person
+ def address
+ Address.new
+ end
+
+ def address_attributes=(attributes)
+ # ...
+ end
+end
+</ruby>
+
+h5. Association collection
+
+<ruby>
+class Person
+ def projects
+ [Project.new, Project.new]
+ end
+
+ def projects_attributes=(attributes)
+ # ...
+ end
+end
+</ruby>
+
+NOTE: See (TODO) in the advanced section for more information on how to deal with the CRUD operations in your custom model.
+
+h3. Views
+
+h4. Controller code
+
+A nested model form will _only_ be build if the associated object(s) exist. This means that for a new model instance you would probably want to build the associated object(s) first.
+
+Consider the following typical RESTful controller which will prepare a new Person instance and its +address+ and +projects+ associations before rendering the +new+ template:
+
+<ruby>
+class PeopleController < ActionController:Base
+ def new
+ @person = Person.new
+ @person.built_address
+ 2.times { @person.projects.build }
+ end
+
+ def create
+ @person = Person.new(params[:person])
+ if @person.save
+ # ...
+ end
+ end
+end
+</ruby>
+
+NOTE: Obviously the instantiation of the associated object(s) can become tedious and not DRY, so you might want to move that into the model itself. ActiveRecord::Base provides an +after_initialize+ callback which is a good way to refactor this.
+
+h4. Form code
+
+Now that you have a model instance, with the appropriate methods and associated object(s), you can start building the nested model form.
+
+h5. Standard form
+
+Start out with a regular RESTful form:
+
+<erb>
+<% form_for @person do |f| %>
+ <%= f.text_field :name %>
+<% end %>
+</erb>
+
+This will generate the following html:
+
+<html>
+<form action="/people" class="new_person" id="new_person" method="post">
+ <input id="person_name" name="person[name]" size="30" type="text" />
+</form>
+</html>
+
+h5. Nested form for a single associated object
+
+Now add a nested form for the +address+ association:
+
+<erb>
+<% form_for @person do |f| %>
+ <%= f.text_field :name %>
+
+ <% f.fields_for :address do |af| %>
+ <%= f.text_field :street %>
+ <% end %>
+<% end %>
+</erb>
+
+This generates:
+
+<html>
+<form action="/people" class="new_person" id="new_person" method="post">
+ <input id="person_name" name="person[name]" size="30" type="text" />
+
+ <input id="person_address_attributes_street" name="person[address_attributes][street]" size="30" type="text" />
+</form>
+</html>
+
+Notice that +fields_for+ recognized the +address+ as an association for which a nested model form should be build by the way it has namespaced the +name+ attribute.
+
+When this form is posted the Rails parameter parser will construct a hash like the following:
+
+<ruby>
+{
+ "person" => {
+ "name" => "Eloy Duran",
+ "address_attributes" => {
+ "street" => "Nieuwe Prinsengracht"
+ }
+ }
+}
+</ruby>
+
+That’s it. The controller will simply pass this hash on to the model from the +create+ action. The model will then handle building the +address+ association for you and automatically save it when the parent (+person+) is saved.
+
+h5. Nested form for a collection of associated objects
+
+The form code for an association collection is pretty similar to that of a single associated object:
+
+<erb>
+<% form_for @person do |f| %>
+ <%= f.text_field :name %>
+
+ <% f.fields_for :projects do |pf| %>
+ <%= f.text_field :name %>
+ <% end %>
+<% end %>
+</erb>
+
+Which generates:
+
+<html>
+<form action="/people" class="new_person" id="new_person" method="post">
+ <input id="person_name" name="person[name]" size="30" type="text" />
+
+ <input id="person_projects_attributes_0_name" name="person[projects_attributes][0][name]" size="30" type="text" />
+ <input id="person_projects_attributes_1_name" name="person[projects_attributes][1][name]" size="30" type="text" />
+</form>
+</html>
+
+As you can see it has generated 2 +project name+ inputs, one for each new +project+ that’s build in the controllers +new+ action. Only this time the +name+ attribute of the input contains a digit as an extra namespace. This will be parsed by the Rails parameter parser as:
+
+<ruby>
+{
+ "person" => {
+ "name" => "Eloy Duran",
+ "projects_attributes" => {
+ "0" => { "name" => "Project 1" },
+ "1" => { "name" => "Project 2" }
+ }
+ }
+}
+</ruby>
+
+You can basically see the +projects_attributes+ hash as an array of attribute hashes. One for each model instance.
+
+NOTE: The reason that +fields_for+ constructed a form which would result in a hash instead of an array is that it won't work for any forms nested deeper than one level deep.
+
+TIP: You _can_ however pass an array to the writer method generated by +accepts_nested_attributes_for+ if you're using plain Ruby or some other API access. See (TODO) for more info and example. \ No newline at end of file
diff --git a/railties/guides/source/performance_testing.textile b/railties/guides/source/performance_testing.textile
index 8e36c2419a..320a5b8472 100644
--- a/railties/guides/source/performance_testing.textile
+++ b/railties/guides/source/performance_testing.textile
@@ -446,11 +446,11 @@ This benchmarks the code enclosed in the +Project.benchmark("Creating project")
Creating project (185.3ms)
</ruby>
-Please refer to the "API docs":http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M001336 for additional options to +benchmark()+
+Please refer to the "API docs":http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M001336 for additional options to +benchmark()+
h4. Controller
-Similarly, you could use this helper method inside "controllers":http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715
+Similarly, you could use this helper method inside "controllers":http://api.rubyonrails.org/classes/ActionController/Benchmarking/ClassMethods.html#M000715
<ruby>
def process_projects
@@ -465,7 +465,7 @@ NOTE: +benchmark+ is a class method inside controllers
h4. View
-And in "views":http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715:
+And in "views":http://api.rubyonrails.org/classes/ActionController/Benchmarking/ClassMethods.html#M000715:
<erb>
<% benchmark("Showing projects partial") do %>
@@ -499,15 +499,15 @@ h3. Useful Links
h4. Rails Plugins and Gems
* "Rails Analyzer":http://rails-analyzer.rubyforge.org
-* "Palmist":http://www.flyingmachinestudios.com/projects
+* "Palmist":http://www.flyingmachinestudios.com/projects/
* "Rails Footnotes":http://github.com/josevalim/rails-footnotes/tree/master
* "Query Reviewer":http://github.com/dsboulder/query_reviewer/tree/master
h4. Generic Tools
-* "httperf":http://www.hpl.hp.com/research/linux/httperf
+* "httperf":http://www.hpl.hp.com/research/linux/httperf/
* "ab":http://httpd.apache.org/docs/2.2/programs/ab.html
-* "JMeter":http://jakarta.apache.org/jmeter
+* "JMeter":http://jakarta.apache.org/jmeter/
* "kcachegrind":http://kcachegrind.sourceforge.net/html/Home.html
h4. Tutorials and Documentation
diff --git a/railties/guides/source/rails_on_rack.textile b/railties/guides/source/rails_on_rack.textile
index c323467fcd..07ca1624f4 100644
--- a/railties/guides/source/rails_on_rack.textile
+++ b/railties/guides/source/rails_on_rack.textile
@@ -15,7 +15,7 @@ h3. Introduction to Rack
bq. Rack provides a minimal, modular and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call.
-- "Rack API Documentation":http://rack.rubyforge.org/doc
+- "Rack API Documentation":http://rack.rubyforge.org/doc/
Explaining Rack is not really in the scope of this guide. In case you are not familiar with Rack's basics, you should check out the following links:
diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile
index 1f1c7b1cc3..a4d9e140d5 100644
--- a/railties/guides/source/routing.textile
+++ b/railties/guides/source/routing.textile
@@ -851,7 +851,7 @@ TIP: You'll find that the output from +rake routes+ is much more readable if you
h4. Testing Routes
-Routes should be included in your testing strategy (just like the rest of your application). Rails offers three "built-in assertions":http://api.rubyonrails.com/classes/ActionController/Assertions/RoutingAssertions.html designed to make testing routes simpler:
+Routes should be included in your testing strategy (just like the rest of your application). Rails offers three "built-in assertions":http://api.rubyonrails.org/classes/ActionController/Assertions/RoutingAssertions.html designed to make testing routes simpler:
* +assert_generates+
* +assert_recognizes+
diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile
index adb96fa19c..1b64cc1be7 100644
--- a/railties/guides/source/security.textile
+++ b/railties/guides/source/security.textile
@@ -23,7 +23,7 @@ The Gartner Group however estimates that 75% of attacks are at the web applicati
The threats against web applications include user account hijacking, bypass of access control, reading or modifying sensitive data, or presenting fraudulent content. Or an attacker might be able to install a Trojan horse program or unsolicited e-mail sending software, aim at financial enrichment or cause brand name damage by modifying company resources. In order to prevent attacks, minimize their impact and remove points of attack, first of all, you have to fully understand the attack methods in order to find the correct countermeasures. That is what this guide aims at.
-In order to develop secure web applications you have to keep up to date on all layers and know your enemies. To keep up to date subscribe to security mailing lists, read security blogs and make updating and security checks a habit (check the <a href="#additionalresources">Additional Resources</a> chapter). I do it manually because that‘s how you find the nasty logical security problems.
+In order to develop secure web applications you have to keep up to date on all layers and know your enemies. To keep up to date subscribe to security mailing lists, read security blogs and make updating and security checks a habit (check the <a href="#additional-resources">Additional Resources</a> chapter). I do it manually because that‘s how you find the nasty logical security problems.
h3. Sessions
@@ -337,7 +337,7 @@ h3. Intranet and Admin Security
-- _Intranet and administration interfaces are popular attack targets, because they allow privileged access. Although this would require several extra-security measures, the opposite is the case in the real world._
-In 2007 there was the first tailor-made "Trojan":http://www.symantec.com/enterprise/security_response/weblog/2007/08/a_monster_trojan.html which stole information from an Intranet, namely the "Monster for employers" web site of Monster.com, an online recruitment web application. Tailor-made Trojans are very rare, so far, and the risk is quite low, but it is certainly a possibility and an example of how the security of the client host is important, too. However, the highest threat to Intranet and Admin applications are XSS and CSRF.

+In 2007 there was the first tailor-made trojan which stole information from an Intranet, namely the "Monster for employers" web site of Monster.com, an online recruitment web application. Tailor-made Trojans are very rare, so far, and the risk is quite low, but it is certainly a possibility and an example of how the security of the client host is important, too. However, the highest threat to Intranet and Admin applications are XSS and CSRF.

*XSS* If your application re-displays malicious user input from the extranet, the application will be vulnerable to XSS. User names, comments, spam reports, order addresses are just a few uncommon examples, where there can be XSS.
@@ -347,9 +347,9 @@ Refer to the Injection section for countermeasures against XSS. It is _(highligh
*CSRF* Cross-Site Reference Forgery (CSRF) is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface.
-A real-world example is a "router reconfiguration by CSRF":http://www.symantec.com/enterprise/security_response/weblog/2008/01/driveby_pharming_in_the_
wild.html. The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had his credentials stolen.
+A real-world example is a "router reconfiguration by CSRF":http://www.h-online.com/security/Symantec-reports-first-active-attack-on-a-DSL-router--/news/102352. The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had his credentials stolen.
-Another example changed Google Adsense's e-mail address and password by "CSRF":http://www.0x000000.com/index.php?i=213&bin=11010101. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change his credentials.

+Another example changed Google Adsense's e-mail address and password by. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change his credentials.

Another popular attack is to spam your web application, your blog or forum to propagate malicious XSS. Of course, the attacker has to know the URL structure, but most Rails URLs are quite straightforward or they will be easy to find out, if it is an open-source application's admin interface. The attacker may even do 1,000 lucky guesses by just including malicious IMG-tags which try every possible combination.
@@ -700,7 +700,7 @@ The most common entry points are message posts, user comments, and guest books,
XSS attacks work like this: An attacker injects some code, the web application saves it and displays it on a page, later presented to a victim. Most XSS examples simply display an alert box, but it is more powerful than that. XSS can steal the cookie, hijack the session, redirect the victim to a fake website, display advertisements for the benefit of the attacker, change elements on the web site to get confidential information or install malicious software through security holes in the web browser.
-During the second half of 2007, there were 88 vulnerabilities reported in Mozilla browsers, 22 in Safari, 18 in IE, and 12 in Opera. The "Symantec Global Internet Security threat report":http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf also documented 239 browser plug-in vulnerabilities in the last six months of 2007. "Mpack":http://pandalabs.pandasecurity.com/archive/MPack-uncovered_2100_.aspx is a very active and up-to-date attack framework which exploits these vulnerabilities. For criminal hackers, it is very attractive to exploit an SQL-Injection vulnerability in a web application framework and insert malicious code in every textual table column. In April 2008 more than 510,000 sites "were hacked":http://www.0x000000.com/?i=556 like this, among them the British government, United Nations, and many more high targets.
+During the second half of 2007, there were 88 vulnerabilities reported in Mozilla browsers, 22 in Safari, 18 in IE, and 12 in Opera. The "Symantec Global Internet Security threat report":http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf also documented 239 browser plug-in vulnerabilities in the last six months of 2007. "Mpack":http://pandalabs.pandasecurity.com/archive/MPack-uncovered_2100_.aspx is a very active and up-to-date attack framework which exploits these vulnerabilities. For criminal hackers, it is very attractive to exploit an SQL-Injection vulnerability in a web application framework and insert malicious code in every textual table column. In April 2008 more than 510,000 sites were hacked like this, among them the British government, United Nations, and many more high targets.
A relatively new, and unusual, form of entry points are banner advertisements. In earlier 2008, malicious code appeared in banner ads on popular sites, such as MySpace and Excite, according to "Trend Micro":http://blog.trendmicro.com/myspace-excite-and-blick-serve-up-malicious-banner-ads/.
@@ -751,7 +751,7 @@ With web page defacement an attacker can do a lot of things, for example, presen
<iframe name=”StatPage” src="http://58.xx.xxx.xxx" width=5 height=5 style=”display:none”></iframe>
</html>
-This loads arbitrary HTML and/or JavaScript from an external source and embeds it as part of the site. This iFrame is taken from an "actual attack":http://www.symantec.com/enterprise/security_response/weblog/2007/06/italy_under_attack_mpack_gang.html on legitimate Italian sites using the "Mpack attack framework":http://isc.sans.org/diary.html?storyid=3015. Mpack tries to install malicious software through security holes in the web browser – very successfully, 50% of the attacks succeed.
+This loads arbitrary HTML and/or JavaScript from an external source and embeds it as part of the site. This iframe is taken from an actual attack on legitimate Italian sites using the "Mpack attack framework":http://isc.sans.org/diary.html?storyid=3015. Mpack tries to install malicious software through security holes in the web browser – very successfully, 50% of the attacks succeed.
A more specialized attack could overlap the entire web site or display a login form, which looks the same as the site's original, but transmits the user name and password to the attackers site. Or it could use CSS and/or JavaScript to hide a legitimate link in the web application, and display another one at its place which redirects to a fake web site.
@@ -810,7 +810,7 @@ The following is an excerpt from the "Js.Yamanner@m":http://www.symantec.com/sec
The worms exploits a hole in Yahoo's HTML/JavaScript filter, which usually filters all target and onload attributes from tags (because there can be JavaScript). The filter is applied only once, however, so the onload attribute with the worm code stays in place. This is a good example why blacklist filters are never complete and why it is hard to allow HTML/JavaScript in a web application.
-Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details and a video demonstration on "Rosario Valotta's website":http://rosario.valotta.googlepages.com/home. Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with.
+Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details on "Rosario Valotta's paper":http://www.xssed.com/article/9/Paper_A_PoC_of_a_cross_webmail_worm_XWW_called_Njuda_connection/. Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with.
In December 2006, 34,000 actual user names and passwords were stolen in a "MySpace phishing attack":http://news.netcraft.com/archives/2006/10/27/myspace_accounts_compromised_by_phishers.html. The idea of the attack was to create a profile page named “login_home_index_html”, so the URL looked very convincing. Specially-crafted HTML and CSS was used to hide the genuine MySpace content from the page and instead display its own login form.
@@ -858,7 +858,7 @@ This example, again, showed that a blacklist filter is never complete. However,
h4. Textile Injection
--- _If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. "RedCloth":http://whytheluckystiff.net/ruby/redcloth/ is such a language for Ruby, but without precautions, it is also vulnerable to XSS._
+-- _If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. "RedCloth":http://redcloth.org/ is such a language for Ruby, but without precautions, it is also vulnerable to XSS._
For example, RedCloth translates +_test_+ to &lt;em&gt;test&lt;em&gt;, which makes the text italic. However, up to the current version 3.0.4, it is still vulnerable to XSS. Get the "all-new version 4":http://www.redcloth.org that removed serious bugs. However, even that version has "some security bugs":http://www.rorsecurity.info/journal/2008/10/13/new-redcloth-security.html, so the countermeasures still apply. Here is an example for version 3.0.4:
@@ -978,7 +978,6 @@ The security landscape shifts and it is important to keep up to date, because mi
* Subscribe to the Rails security "mailing list":http://groups.google.com/group/rubyonrails-security
* "Keep up to date on the other application layers":http://secunia.com/ (they have a weekly newsletter, too)
* A "good security blog":http://ha.ckers.org/blog/ including the "Cross-Site scripting Cheat Sheet":http://ha.ckers.org/xss.html
-* Another "good security blog":http://www.0x000000.com/ with some Cheat Sheets, too
h3. Changelog
diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile
index 7959b767f6..12fc836edf 100644
--- a/railties/guides/source/testing.textile
+++ b/railties/guides/source/testing.textile
@@ -142,7 +142,7 @@ In Rails, unit tests are what you write to test your models.
For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practices. I will be using examples from this generated code and would be supplementing it with additional examples where necessary.
-NOTE: For more information on Rails _scaffolding_, refer to "Getting Started with Rails":../getting_started_with_rails.html
+NOTE: For more information on Rails _scaffolding_, refer to "Getting Started with Rails":getting_started.html
When you use +script/generate scaffold+, for a resource among other things it creates a test stub in the +test/unit+ folder:
@@ -582,7 +582,7 @@ assert_select "ol" do
end
</ruby>
-The +assert_select+ assertion is quite powerful. For more advanced usage, refer to its "documentation":http://api.rubyonrails.com/classes/ActionController/Assertions/SelectorAssertions.html.
+The +assert_select+ assertion is quite powerful. For more advanced usage, refer to its "documentation":http://api.rubyonrails.org/classes/ActionController/Assertions/SelectorAssertions.html.
h5. Additional View-Based Assertions
diff --git a/railties/lib/commands/plugin.rb b/railties/lib/commands/plugin.rb
index 8589b1698d..3d76bcc73f 100644
--- a/railties/lib/commands/plugin.rb
+++ b/railties/lib/commands/plugin.rb
@@ -1,47 +1,8 @@
# Rails Plugin Manager.
-#
-# Listing available plugins:
-#
-# $ ./script/plugin list
-# continuous_builder http://dev.rubyonrails.com/svn/rails/plugins/continuous_builder
-# asset_timestamping http://svn.aviditybytes.com/rails/plugins/asset_timestamping
-# enumerations_mixin http://svn.protocool.com/rails/plugins/enumerations_mixin/trunk
-# calculations http://techno-weenie.net/svn/projects/calculations/
-# ...
#
# Installing plugins:
#
# $ ./script/plugin install continuous_builder asset_timestamping
-#
-# Finding Repositories:
-#
-# $ ./script/plugin discover
-#
-# Adding Repositories:
-#
-# $ ./script/plugin source http://svn.protocool.com/rails/plugins/
-#
-# How it works:
-#
-# * Maintains a list of subversion repositories that are assumed to have
-# a plugin directory structure. Manage them with the (source, unsource,
-# and sources commands)
-#
-# * The discover command scrapes the following page for things that
-# look like subversion repositories with plugins:
-# http://wiki.rubyonrails.org/rails/pages/Plugins
-#
-# * Unless you specify that you want to use svn, script/plugin uses plain old
-# HTTP for downloads. The following bullets are true if you specify
-# that you want to use svn.
-#
-# * If `vendor/plugins` is under subversion control, the script will
-# modify the svn:externals property and perform an update. You can
-# use normal subversion commands to keep the plugins up to date.
-#
-# * Or, if `vendor/plugins` is not under subversion control, the
-# plugin is pulled via `svn checkout` or `svn export` but looks
-# exactly the same.
#
# Specifying revisions:
#
@@ -156,13 +117,13 @@ end
class Plugin
attr_reader :name, :uri
- def initialize(uri, name=nil)
+ def initialize(uri, name = nil)
@uri = uri
guess_name(uri)
end
def self.find(name)
- name =~ /\// ? new(name) : Repositories.instance.find_plugin(name)
+ new(name)
end
def to_s
@@ -208,10 +169,13 @@ class Plugin
else
puts "Plugin doesn't exist: #{path}"
end
- # clean up svn:externals
- externals = rails_env.externals
- externals.reject!{|n,u| name == n or name == u}
- rails_env.externals = externals
+
+ if rails_env.use_externals?
+ # clean up svn:externals
+ externals = rails_env.externals
+ externals.reject!{|n,u| name == n or name == u}
+ rails_env.externals = externals
+ end
end
def info
@@ -310,129 +274,6 @@ class Plugin
end
end
-class Repositories
- include Enumerable
-
- def initialize(cache_file = File.join(find_home, ".rails-plugin-sources"))
- @cache_file = File.expand_path(cache_file)
- load!
- end
-
- def each(&block)
- @repositories.each(&block)
- end
-
- def add(uri)
- unless find{|repo| repo.uri == uri }
- @repositories.push(Repository.new(uri)).last
- end
- end
-
- def remove(uri)
- @repositories.reject!{|repo| repo.uri == uri}
- end
-
- def exist?(uri)
- @repositories.detect{|repo| repo.uri == uri }
- end
-
- def all
- @repositories
- end
-
- def find_plugin(name)
- @repositories.each do |repo|
- repo.each do |plugin|
- return plugin if plugin.name == name
- end
- end
- return nil
- end
-
- def load!
- contents = File.exist?(@cache_file) ? File.read(@cache_file) : defaults
- contents = defaults if contents.empty?
- @repositories = contents.split(/\n/).reject do |line|
- line =~ /^\s*#/ or line =~ /^\s*$/
- end.map { |source| Repository.new(source.strip) }
- end
-
- def save
- File.open(@cache_file, 'w') do |f|
- each do |repo|
- f.write(repo.uri)
- f.write("\n")
- end
- end
- end
-
- def defaults
- <<-DEFAULTS
- http://dev.rubyonrails.com/svn/rails/plugins/
- DEFAULTS
- end
-
- def find_home
- ['HOME', 'USERPROFILE'].each do |homekey|
- return ENV[homekey] if ENV[homekey]
- end
- if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
- return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}"
- end
- begin
- File.expand_path("~")
- rescue StandardError => ex
- if File::ALT_SEPARATOR
- "C:/"
- else
- "/"
- end
- end
- end
-
- def self.instance
- @instance ||= Repositories.new
- end
-
- def self.each(&block)
- self.instance.each(&block)
- end
-end
-
-class Repository
- include Enumerable
- attr_reader :uri, :plugins
-
- def initialize(uri)
- @uri = uri.chomp('/') << "/"
- @plugins = nil
- end
-
- def plugins
- unless @plugins
- if $verbose
- puts "Discovering plugins in #{@uri}"
- puts index
- end
-
- @plugins = index.reject{ |line| line !~ /\/$/ }
- @plugins.map! { |name| Plugin.new(File.join(@uri, name), name) }
- end
-
- @plugins
- end
-
- def each(&block)
- plugins.each(&block)
- end
-
- private
- def index
- @index ||= RecursiveHTTPFetcher.new(@uri).ls
- end
-end
-
-
# load default environment and parse arguments
require 'optparse'
module Commands
@@ -472,14 +313,8 @@ module Commands
o.separator ""
o.separator "COMMANDS"
- o.separator " discover Discover plugin repositories."
- o.separator " list List available plugins."
o.separator " install Install plugin(s) from known repositories or URLs."
- o.separator " update Update installed plugins."
o.separator " remove Uninstall plugins."
- o.separator " source Add a plugin source repository."
- o.separator " unsource Remove a plugin repository."
- o.separator " sources List currently configured plugin repositories."
o.separator ""
o.separator "EXAMPLES"
@@ -491,20 +326,6 @@ module Commands
o.separator " #{@script_name} install git://github.com/SomeGuy/my_awesome_plugin.git\n"
o.separator " Install a plugin and add a svn:externals entry to vendor/plugins"
o.separator " #{@script_name} install -x continuous_builder\n"
- o.separator " List all available plugins:"
- o.separator " #{@script_name} list\n"
- o.separator " List plugins in the specified repository:"
- o.separator " #{@script_name} list --source=http://dev.rubyonrails.com/svn/rails/plugins/\n"
- o.separator " Discover and prompt to add new repositories:"
- o.separator " #{@script_name} discover\n"
- o.separator " Discover new repositories but just list them, don't add anything:"
- o.separator " #{@script_name} discover -l\n"
- o.separator " Add a new repository to the source list:"
- o.separator " #{@script_name} source http://dev.rubyonrails.com/svn/rails/plugins/\n"
- o.separator " Remove a repository from the source list:"
- o.separator " #{@script_name} unsource http://dev.rubyonrails.com/svn/rails/plugins/\n"
- o.separator " Show currently configured repositories:"
- o.separator " #{@script_name} sources\n"
end
end
@@ -513,7 +334,7 @@ module Commands
options.parse!(general)
command = general.shift
- if command =~ /^(list|discover|install|source|unsource|sources|remove|update|info)$/
+ if command =~ /^(install|remove)$/
command = Commands.const_get(command.capitalize).new(self)
command.parse!(sub)
else
@@ -535,218 +356,6 @@ module Commands
end
end
-
- class List
- def initialize(base_command)
- @base_command = base_command
- @sources = []
- @local = false
- @remote = true
- end
-
- def options
- OptionParser.new do |o|
- o.set_summary_indent(' ')
- o.banner = "Usage: #{@base_command.script_name} list [OPTIONS] [PATTERN]"
- o.define_head "List available plugins."
- o.separator ""
- o.separator "Options:"
- o.separator ""
- o.on( "-s", "--source=URL1,URL2", Array,
- "Use the specified plugin repositories.") {|sources| @sources = sources}
- o.on( "--local",
- "List locally installed plugins.") {|local| @local, @remote = local, false}
- o.on( "--remote",
- "List remotely available plugins. This is the default behavior",
- "unless --local is provided.") {|remote| @remote = remote}
- end
- end
-
- def parse!(args)
- options.order!(args)
- unless @sources.empty?
- @sources.map!{ |uri| Repository.new(uri) }
- else
- @sources = Repositories.instance.all
- end
- if @remote
- @sources.map{|r| r.plugins}.flatten.each do |plugin|
- if @local or !plugin.installed?
- puts plugin.to_s
- end
- end
- else
- cd "#{@base_command.environment.root}/vendor/plugins"
- Dir["*"].select{|p| File.directory?(p)}.each do |name|
- puts name
- end
- end
- end
- end
-
-
- class Sources
- def initialize(base_command)
- @base_command = base_command
- end
-
- def options
- OptionParser.new do |o|
- o.set_summary_indent(' ')
- o.banner = "Usage: #{@base_command.script_name} sources [OPTIONS] [PATTERN]"
- o.define_head "List configured plugin repositories."
- o.separator ""
- o.separator "Options:"
- o.separator ""
- o.on( "-c", "--check",
- "Report status of repository.") { |sources| @sources = sources}
- end
- end
-
- def parse!(args)
- options.parse!(args)
- Repositories.each do |repo|
- puts repo.uri
- end
- end
- end
-
-
- class Source
- def initialize(base_command)
- @base_command = base_command
- end
-
- def options
- OptionParser.new do |o|
- o.set_summary_indent(' ')
- o.banner = "Usage: #{@base_command.script_name} source REPOSITORY [REPOSITORY [REPOSITORY]...]"
- o.define_head "Add new repositories to the default search list."
- end
- end
-
- def parse!(args)
- options.parse!(args)
- count = 0
- args.each do |uri|
- if Repositories.instance.add(uri)
- puts "added: #{uri.ljust(50)}" if $verbose
- count += 1
- else
- puts "failed: #{uri.ljust(50)}"
- end
- end
- Repositories.instance.save
- puts "Added #{count} repositories."
- end
- end
-
-
- class Unsource
- def initialize(base_command)
- @base_command = base_command
- end
-
- def options
- OptionParser.new do |o|
- o.set_summary_indent(' ')
- o.banner = "Usage: #{@base_command.script_name} unsource URI [URI [URI]...]"
- o.define_head "Remove repositories from the default search list."
- o.separator ""
- o.on_tail("-h", "--help", "Show this help message.") { puts o; exit }
- end
- end
-
- def parse!(args)
- options.parse!(args)
- count = 0
- args.each do |uri|
- if Repositories.instance.remove(uri)
- count += 1
- puts "removed: #{uri.ljust(50)}"
- else
- puts "failed: #{uri.ljust(50)}"
- end
- end
- Repositories.instance.save
- puts "Removed #{count} repositories."
- end
- end
-
-
- class Discover
- def initialize(base_command)
- @base_command = base_command
- @list = false
- @prompt = true
- end
-
- def options
- OptionParser.new do |o|
- o.set_summary_indent(' ')
- o.banner = "Usage: #{@base_command.script_name} discover URI [URI [URI]...]"
- o.define_head "Discover repositories referenced on a page."
- o.separator ""
- o.separator "Options:"
- o.separator ""
- o.on( "-l", "--list",
- "List but don't prompt or add discovered repositories.") { |list| @list, @prompt = list, !@list }
- o.on( "-n", "--no-prompt",
- "Add all new repositories without prompting.") { |v| @prompt = !v }
- end
- end
-
- def parse!(args)
- options.parse!(args)
- args = ['http://wiki.rubyonrails.org/rails/pages/Plugins'] if args.empty?
- args.each do |uri|
- scrape(uri) do |repo_uri|
- catch(:next_uri) do
- if @prompt
- begin
- $stdout.print "Add #{repo_uri}? [Y/n] "
- throw :next_uri if $stdin.gets !~ /^y?$/i
- rescue Interrupt
- $stdout.puts
- exit 1
- end
- elsif @list
- puts repo_uri
- throw :next_uri
- end
- Repositories.instance.add(repo_uri)
- puts "discovered: #{repo_uri}" if $verbose or !@prompt
- end
- end
- end
- Repositories.instance.save
- end
-
- def scrape(uri)
- require 'open-uri'
- puts "Scraping #{uri}" if $verbose
- dupes = []
- content = open(uri).each do |line|
- begin
- if line =~ /<a[^>]*href=['"]([^'"]*)['"]/ || line =~ /(svn:\/\/[^<|\n]*)/
- uri = $1
- if uri =~ /^\w+:\/\// && uri =~ /\/plugins\// && uri !~ /\/browser\// && uri !~ /^http:\/\/wiki\.rubyonrails/ && uri !~ /http:\/\/instiki/
- uri = extract_repository_uri(uri)
- yield uri unless dupes.include?(uri) || Repositories.instance.exist?(uri)
- dupes << uri
- end
- end
- rescue
- puts "Problems scraping '#{uri}': #{$!.to_s}"
- end
- end
- end
-
- def extract_repository_uri(uri)
- uri.match(/(svn|https?):.*\/plugins\//i)[0]
- end
- end
-
class Install
def initialize(base_command)
@base_command = base_command
@@ -817,41 +426,6 @@ module Commands
end
end
- class Update
- def initialize(base_command)
- @base_command = base_command
- end
-
- def options
- OptionParser.new do |o|
- o.set_summary_indent(' ')
- o.banner = "Usage: #{@base_command.script_name} update [name [name]...]"
- o.on( "-r REVISION", "--revision REVISION",
- "Checks out the given revision from subversion.",
- "Ignored if subversion is not used.") { |v| @revision = v }
- o.define_head "Update plugins."
- end
- end
-
- def parse!(args)
- options.parse!(args)
- root = @base_command.environment.root
- cd root
- args = Dir["vendor/plugins/*"].map do |f|
- File.directory?("#{f}/.svn") ? File.basename(f) : nil
- end.compact if args.empty?
- cd "vendor/plugins"
- args.each do |name|
- if File.directory?(name)
- puts "Updating plugin: #{name}"
- system("svn #{$verbose ? '' : '-q'} up \"#{name}\" #{@revision ? "-r #{@revision}" : ''}")
- else
- puts "Plugin doesn't exist: #{name}"
- end
- end
- end
- end
-
class Remove
def initialize(base_command)
@base_command = base_command
diff --git a/railties/lib/rails/rack/metal.rb b/railties/lib/rails/rack/metal.rb
index 78b8a01449..adc43da864 100644
--- a/railties/lib/rails/rack/metal.rb
+++ b/railties/lib/rails/rack/metal.rb
@@ -18,7 +18,7 @@ module Rails
metal_glob.each do |glob|
Dir[glob].sort.map do |file|
file = file.match(matcher)[1]
- all_metals[file.classify] = file
+ all_metals[file.camelize] = file
end
end
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index fd38705e75..99c7516a65 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -2,7 +2,7 @@ module Rails
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
- TINY = 1
+ TINY = 2
STRING = [MAJOR, MINOR, TINY].join('.')
end
diff --git a/railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb b/railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb
index b8e7001351..2d373ce422 100644
--- a/railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb
+++ b/railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb
@@ -1,5 +1,5 @@
class MetalA < Rails::Rack::Metal
def self.call(env)
- [200, { "Content-Type" => "text/html"}, "Hi"]
+ [200, { "Content-Type" => "text/html"}, ["Hi"]]
end
end
diff --git a/railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb b/railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb
index adc2f45fcf..a8bbf3fd60 100644
--- a/railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb
+++ b/railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb
@@ -1,5 +1,5 @@
class MetalB < Rails::Rack::Metal
def self.call(env)
- [200, { "Content-Type" => "text/html"}, "Hi"]
+ [200, { "Content-Type" => "text/html"}, ["Hi"]]
end
end
diff --git a/railties/test/fixtures/metal/pluralmetal/app/metal/legacy_routes.rb b/railties/test/fixtures/metal/pluralmetal/app/metal/legacy_routes.rb
new file mode 100644
index 0000000000..0cd3737c32
--- /dev/null
+++ b/railties/test/fixtures/metal/pluralmetal/app/metal/legacy_routes.rb
@@ -0,0 +1,5 @@
+class LegacyRoutes < Rails::Rack::Metal
+ def self.call(env)
+ [301, { "Location" => "http://example.com"}, []]
+ end
+end
diff --git a/railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb b/railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb
index 9ade2ce8e7..5f5b087592 100644
--- a/railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb
+++ b/railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb
@@ -1,5 +1,5 @@
class FooMetal < Rails::Rack::Metal
def self.call(env)
- [200, { "Content-Type" => "text/html"}, "Hi"]
+ [200, { "Content-Type" => "text/html"}, ["Hi"]]
end
end
diff --git a/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb b/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb
index 71a5a62eb8..25b3bb0abc 100644
--- a/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb
+++ b/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb
@@ -1,7 +1,7 @@
module Folder
class MetalA < Rails::Rack::Metal
def self.call(env)
- [200, { "Content-Type" => "text/html"}, "Hi"]
+ [200, { "Content-Type" => "text/html"}, ["Hi"]]
end
end
end
diff --git a/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb b/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb
index 430d7bfed6..7583363f71 100644
--- a/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb
+++ b/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb
@@ -1,7 +1,7 @@
module Folder
class MetalB < Rails::Rack::Metal
def self.call(env)
- [200, { "Content-Type" => "text/html"}, "Hi"]
+ [200, { "Content-Type" => "text/html"}, ["Hi"]]
end
end
end
diff --git a/railties/test/metal_test.rb b/railties/test/metal_test.rb
index 143efdda11..d3d231132b 100644
--- a/railties/test/metal_test.rb
+++ b/railties/test/metal_test.rb
@@ -8,6 +8,12 @@ class MetalTest < Test::Unit::TestCase
end
end
+ def test_metals_should_respect_class_name_conventions
+ use_appdir("pluralmetal") do
+ assert_equal(["LegacyRoutes"], found_metals_as_string_array)
+ end
+ end
+
def test_metals_should_return_alphabetical_list_of_found_metal_apps
use_appdir("multiplemetals") do
assert_equal(["MetalA", "MetalB"], found_metals_as_string_array)