aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activemodel/lib/active_model/errors.rb2
-rw-r--r--railties/guides/source/active_support_overview.textile120
-rw-r--r--railties/guides/source/activerecord_validations_callbacks.textile41
-rw-r--r--railties/guides/source/contributing_to_rails.textile25
-rw-r--r--railties/guides/source/testing.textile81
5 files changed, 210 insertions, 59 deletions
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 7a3001174f..b31ab0b837 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -113,7 +113,7 @@ module ActiveModel
full_messages
end
- # Translates an error message in it's default scope (<tt>activemodel.errrors.messages</tt>).
+ # Translates an error message in it's default scope (<tt>activemodel.errors.messages</tt>).
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there,
# it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the
# default message (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model name,
diff --git a/railties/guides/source/active_support_overview.textile b/railties/guides/source/active_support_overview.textile
index aea77c8d4e..10e098b029 100644
--- a/railties/guides/source/active_support_overview.textile
+++ b/railties/guides/source/active_support_overview.textile
@@ -596,21 +596,25 @@ C # => NameError: uninitialized constant C
See also +Object#remove_subclasses_of+ in "Extensions to All Objects FIX THIS LINK":FIXME.
-h3. Extensions to +NilClass+
+h3. Extensions to +Symbol+
-...
+h4. +to_proc+
-h3. Extensions to +TrueClass+
+The method +to_proc+ turns a symbol into a Proc object so that for example
-...
+<ruby>
+emails = users.map {|u| u.email}
+</ruby>
-h3. Extensions to +FalseClass+
+can be written as
-...
+<ruby>
+emails = users.map(&:email)
+</ruby>
-h3. Extensions to +Symbol+
+TIP: If the method that receives the Proc yields more than one value to it the rest are considered to be arguments of the method call.
-...
+Symbols from Ruby 1.8.7 on respond to +to_proc+, and Active Support defines it for previous versions.
h3. Extensions to +String+
@@ -622,7 +626,40 @@ h3. Extensions to +Numeric+
h3. Extensions to +Integer+
-...
+h4. +multiple_of?+
+
+The method +multiple_of?+ tests whether an integer is multiple of the argument:
+
+<ruby>
+2.multiple_of?(1) # => true
+1.multiple_of?(2) # => false
+</ruby>
+
+WARNING: Due the way it is implemented the argument must be nonzero, otherwise +ZeroDivisionError+ is raised.
+
+h4. +even?+ and +odd?+
+
+Integers in Ruby 1.8.7 and above respond to +even?+ and +odd?+, Active Support defines them for older versions:
+
+<ruby>
+-1.even? # => false
+-1.odd? # => true
+ 0.even? # => true
+ 0.odd? # => false
+ 2.even? # => true
+ 2.odd? # => false
+</ruby>
+
+h4. +ordinalize+
+
+The method +ordinalize+ returns the ordinal string corresponding to the receiver integer:
+
+<ruby>
+1.ordinalize # => "1st"
+2.ordinalize # => "2nd"
+53.ordinalize # => "53rd"
+2009.ordinalize # => "2009th"
+</ruby>
h3. Extensions to +Float+
@@ -789,7 +826,25 @@ h3. Extensions to +Pathname+
h3. Extensions to +File+
-...
+h4. +atomic_write+
+
+With the class method +File.atomic_write+ you can write to a file in a way that will prevent any reader from seeing half-written content.
+
+The name of the file is passed as an argument, and the method yields a file handle opened for writing. Once the block is done +atomic_write+ closes the file handle and completes its job.
+
+For example, Action Pack uses this method to write asset cache files like +all.css+:
+
+<ruby>
+File.atomic_write(joined_asset_path) do |cache|
+ cache.write(join_asset_file_contents(asset_paths))
+end
+</ruby>
+
+To accomplish this +atomic_write+ creates a temporary file. That's the file the code in the block actually writes to. On completion, the temporary file is renamed. If the target file exists +atomic_write+ overwrites it and keeps owners and permissions.
+
+WARNING. Note you can't append with +atomic_write+.
+
+The auxiliary file is written in a standard directory for temporary files, but you can pass a directory of your choice as second argument.
h3. Extensions to +Exception+
@@ -797,11 +852,54 @@ h3. Extensions to +Exception+
h3. Extensions to +NameError+
-...
+Active Support adds +missing_name?+ to +NameError+, which tests whether the exception was raised because of the name passed as argument.
+
+The name may be given as a symbol or string. A symbol is tested against the bare constant name, a string is against the fully-qualified constant name.
+TIP: A symbol can represent a fully-qualified constant name as in +:"ActiveRecord::Base"+, so the behaviour for symbols is defined for convenience, not because it has to be that way technically.
+
+For example, when an action of +PostsController+ is called Rails tries optimistically to use +PostsHelper+. It is OK that the helper module does not exist, so if an exception for that constant name is raised it should be silenced. But it could be the case that +posts_helper.rb+ raises a +NameError+ due to an actual unknown constant. That should be reraised. The method +missing_name?+ provides a way to distinguish both cases:
+
+<ruby>
+def default_helper_module!
+ module_name = name.sub(/Controller$/, '')
+ module_path = module_name.underscore
+ helper module_path
+rescue MissingSourceFile => e
+ raise e unless e.is_missing? "#{module_path}_helper"
+rescue NameError => e
+ raise e unless e.missing_name? "#{module_name}Helper"
+end
+</ruby>
+
h3. Extensions to +LoadError+
+Rails hijacks +LoadError.new+ to return a +MissingSourceFile+ exception:
+
+<shell>
+$ ruby -e 'require "nonexistent"'
+...: no such file to load -- nonexistent (LoadError)
+...
+$ script/runner 'require "nonexistent"'
+...: no such file to load -- nonexistent (MissingSourceFile)
...
+</shell>
+
+The class +MissingSourceFile+ is a subclass of +LoadError+, so any code that rescues +LoadError+ as usual still works as expected. Point is these exception objects respond to +is_missing?+, which given a path name tests whether the exception was raised due to that particular file (except perhaps for the ".rb" extension).
+
+For example, when an action of +PostsController+ is called Rails tries to load +posts_helper.rb+, but that file may not exist. That's fine, the helper module is not mandatory so Rails silences a load error. But it could be the case that the helper module does exist, but it in turn requires another library that is missing. In that case Rails must reraise the exception. The method +is_missing?+ provides a way to distinguish both cases:
+
+<ruby>
+def default_helper_module!
+ module_name = name.sub(/Controller$/, '')
+ module_path = module_name.underscore
+ helper module_path
+rescue MissingSourceFile => e
+ raise e unless e.is_missing? "#{module_path}_helper"
+rescue NameError => e
+ raise e unless e.missing_name? "#{module_name}Helper"
+end
+</ruby>
h3. Extensions to +CGI+
diff --git a/railties/guides/source/activerecord_validations_callbacks.textile b/railties/guides/source/activerecord_validations_callbacks.textile
index 03d521ea1f..9d0ee29ff2 100644
--- a/railties/guides/source/activerecord_validations_callbacks.textile
+++ b/railties/guides/source/activerecord_validations_callbacks.textile
@@ -403,6 +403,47 @@ WARNING. Note that some databases are configured to perform case-insensitive sea
The default error message for +validates_uniqueness_of+ is "_has already been taken_".
+h4. +validates_with+
+
+This helper passes the record to a separate class for validation.
+
+<ruby>
+class Person < ActiveRecord::Base
+ validates_with GoodnessValidator
+end
+
+class GoodnessValidator < ActiveRecord::Validator
+ def validate
+ if record.first_name == "Evil"
+ record.errors[:base] << "This person is evil"
+ end
+ end
+end
+</ruby>
+
+The +validates_with+ helper takes a class, or a list of classes to use for validation. There is no default error message for +validates_with+. You must manually add errors to the record's errors collection in the validator class.
+
+The validator class has two attributes by default:
+
+* +record+ - the record to be validated
+* +options+ - the extra options that were passed to +validates_with+
+
+Like all other validations, +validates_with+ takes the +:if+, +:unless+ and +:on+ options. If you pass any other options, it will send those options to the validator class as +options+:
+
+<ruby>
+class Person < ActiveRecord::Base
+ validates_with GoodnessValidator, :fields => [:first_name, :last_name]
+end
+
+class GoodnessValidator < ActiveRecord::Validator
+ def validate
+ if options[:fields].any?{|field| record.send(field) == "Evil" }
+ record.errors[:base] << "This person is evil"
+ end
+ end
+end
+</ruby>
+
h4. +validates_each+
This helper validates attributes against a block. It doesn't have a predefined validation function. You should create one using a block, and every attribute passed to +validates_each+ will be tested against it. In the following example, we don't want names and surnames to begin with lower case.
diff --git a/railties/guides/source/contributing_to_rails.textile b/railties/guides/source/contributing_to_rails.textile
index a5912643c0..eae89e0b7e 100644
--- a/railties/guides/source/contributing_to_rails.textile
+++ b/railties/guides/source/contributing_to_rails.textile
@@ -14,7 +14,7 @@ endprologue.
h3. Reporting a Rails Issue
-Rails uses a "Lighthouse project":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/ to track issues (primarily bugs and contributions of new code). If you've found a bug in Rails, this is the place to start.
+Rails uses a "Lighthouse project":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/ to track issues (primarily bugs and contributions of new code). If you've found a bug in Rails, this is the place to start. You'll need to create a (free) Lighthouse account in order to comment on issues or to upload tests or patches.
NOTE: Bugs in the most recent released version of Rails are likely to get the most attention. Also, the Rails core team is always interested in feedback from those who can take the time to test _edge Rails_ (the code for the version of Rails that is currently under development). Later in this Guide you'll find out how to get edge Rails for testing.
@@ -32,6 +32,10 @@ h4. Special Treatment for Security Issues
If you've found a security vulnerability in Rails, please do *not* report it via a Lighthouse ticket. Lighthouse tickets are public as soon as they are entered. Instead, you should use the dedicated email address "security@rubyonrails.org":mailto:security@rubyonrails.org to report any vulnerabilities. This alias is monitored and the core team will work with you to quickly and completely address any such vulnerabilities.
+WARNING: Just to emphasize the point, _please do not report security vulnerabilities on public Lighthouse tickets_. This will only expose your fellow Rails developers to needless risks.
+
+You should receive an acknowledgement and detailed response to any reported security issue within 48 hours. If you don't think you're getting adequate response from the security alias, refer to the "Rails security policy page":http://rubyonrails.org/security for direct emails for the current Rails security coordinators.
+
h4. What About Feature Requests?
Please don't put "feature request" tickets into Lighthouse. If there's a new feature that you want to see added to Rails, you'll need to write the code yourself - or convince someone else to partner with you to write the code. Later in this guide you'll find detailed instructions for proposing a patch to Rails. If you enter a wishlist item in Lighthouse with no code, you can expect it to be marked "invalid" as soon as it's reviewed.
@@ -47,6 +51,7 @@ Rails uses git for source code control. You won’t be able to do anything witho
* "Everyday Git":http://www.kernel.org/pub/software/scm/git/docs/everyday.html will teach you just enough about git to get by.
* The "PeepCode screencast":https://peepcode.com/products/git on git ($9) is easier to follow.
* "GitHub":http://github.com/guides/home offers links to a variety of git resources.
+* "Pro Git":http://progit.org/book/ is an entire book about git with a Creative Commons license.
h4. Get the Rails Source Code
@@ -57,6 +62,17 @@ git clone git://github.com/rails/rails.git
cd rails
</shell>
+h4. Pick a Branch
+
+Currently, there is active work being done on both the 2-3-stable branch of Rails and on the master branch (which will become Rails 3.0). If you want to work with the master branch, you're all set. To work with 2.3, you'll need to set up and switch to your own local tracking branch:
+
+<shell>
+git branch --track 2-3-stable origin/2-3-stable
+git checkout 2-3-stable
+</shell>
+
+TIP: You may want to "put your git branch name in your shell prompt":http://github.com/guides/put-your-git-branch-name-in-your-shell-prompt to make it easier to remember which version of the code you're working with.
+
h4. Set up and Run the Tests
All of the Rails tests must pass with any code you submit, otherwise you have no chance of getting code accepted. This means you need to be able to run the tests. For the tests that touch the database, this means creating the databases. If you're using MySQL:
@@ -79,7 +95,7 @@ rake test_sqlite3
rake test_sqlite3 TEST=test/cases/validations_test.rb
</shell>
-You can change +sqlite3+ with +jdbcmysql+, +jdbcsqlite3+, +jdbcpostgresql+, +mysql+ or +postgresql+. Check out the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/ci_build.rb+ to see the test suite that the Rails continuous integration server runs.
+You can replace +sqlite3+ with +jdbcmysql+, +jdbcsqlite3+, +jdbcpostgresql+, +mysql+ or +postgresql+. Check out the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/ci_build.rb+ to see the test suite that the Rails continuous integration server runs.
@@ -132,7 +148,7 @@ h3. Contributing to the Rails Documentation
Another area where you can help out if you're not yet ready to take the plunge to writing Rails core code is with Rails documentation. You can help with the Rails Guides or the Rails API documentation.
-TIP: "docrails":http://github.com/lifo/docrails/tree/master is the documentation branch for Rails with an *open commit policy*. You can simply PM "lifo":http://github.com/lifo on Github and ask for the commit rights. Documentation changes made as part of the "docrails":http://github.com/lifo/docrails/tree/master project, are merged back to the Rails master code from time to time. Check out the "original announcement":http://weblog.rubyonrails.org/2008/5/2/help-improve-rails-documentation-on-git-branch for more details.
+TIP: "docrails":http://github.com/lifo/docrails/tree/master is the documentation branch for Rails with an *open commit policy*. You can simply PM "lifo":http://github.com/lifo on Github and ask for the commit rights. Documentation changes made as part of the "docrails":http://github.com/lifo/docrails/tree/master project are merged back to the Rails master code from time to time. Check out the "original announcement":http://weblog.rubyonrails.org/2008/5/2/help-improve-rails-documentation-on-git-branch for more details.
h4. The Rails Guides
@@ -188,6 +204,8 @@ h4. Sanity Check
You should not be the only person who looks at the code before you submit it. You know at least one other Rails developer, right? Show them what you’re doing and ask for feedback. Doing this in private before you push a patch out publicly is the “smoke test” for a patch: if you can’t convince one other developer of the beauty of your code, you’re unlikely to convince the core team either.
+You might also want to check out the "RailsBridge BugMash":http://wiki.railsbridge.org/projects/railsbridge/wiki/BugMash as a way to get involved in a group effort to improve Rails. This can help you get started and help check your code when you're writing your first patches.
+
h4. Commit Your Changes
When you're happy with the code on your computer, you need to commit the changes to git:
@@ -243,6 +261,7 @@ h3. Changelog
"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/64
+* August 1, 2009: Updates/amplifications by "Mike Gunderloy":credits.html#mgunderloy
* March 2, 2009: Initial draft by "Mike Gunderloy":credits.html#mgunderloy
diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile
index 8318146ed3..c7b475899f 100644
--- a/railties/guides/source/testing.textile
+++ b/railties/guides/source/testing.textile
@@ -162,7 +162,7 @@ require 'test_helper'
class PostTest < ActiveSupport::TestCase
# Replace this with your real tests.
- def test_truth
+ test "the truth" do
assert true
end
end
@@ -186,7 +186,17 @@ The +PostTest+ class defines a _test case_ because it inherits from +ActiveSuppo
def test_truth
</ruby>
-Any method defined within a test case that begins with +test+ (case sensitive) is simply called a test. So, +test_password+, +test_valid_password+ and +testValidPassword+ all are legal test names and are run automatically when the test case is run.
+Any method defined within a +Test::Unit+ test case that begins with +test+ (case sensitive) is simply called a test. So, +test_password+, +test_valid_password+ and +testValidPassword+ all are legal test names and are run automatically when the test case is run.
+
+Rails adds a +test+ method that takes a test name and a block. It generates a normal +Test::Unit+ test with method names prefixed with +test_+.
+
+<ruby>
+test "the truth" do
+ # ...
+end
+</ruby>
+
+This makes test names more readable by replacing underscores with regular language.
<ruby>
assert true
@@ -262,7 +272,7 @@ The +.+ (dot) above indicates a passing test. When a test fails you see an +F+;
To see how a test failure is reported, you can add a failing test to the +post_test.rb+ test case.
<ruby>
-def test_should_not_save_post_without_title
+test "should not save post without title" do
post = Post.new
assert !post.save
end
@@ -272,16 +282,13 @@ Let us run this newly added test.
<pre>
$ ruby unit/post_test.rb -n test_should_not_save_post_without_title
-Loaded suite unit/post_test
+Loaded suite -e
Started
F
-Finished in 0.197094 seconds.
+Finished in 0.102072 seconds.
1) Failure:
-test_should_not_save_post_without_title(PostTest)
- [unit/post_test.rb:11:in `test_should_not_save_post_without_title'
- /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__'
- /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run']:
+test_should_not_save_post_without_title(PostTest) [/test/unit/post_test.rb:6]:
<false> is not true.
1 tests, 1 assertions, 1 failures, 0 errors
@@ -290,7 +297,7 @@ test_should_not_save_post_without_title(PostTest)
In the output, +F+ denotes a failure. You can see the corresponding trace shown under +1)+ along with the name of the failing test. The next few lines contain the stack trace followed by a message which mentions the actual value and the expected value by the assertion. The default assertion messages provide just enough information to help pinpoint the error. To make the assertion failure message more readable every assertion provides an optional message parameter, as shown here:
<ruby>
-def test_should_not_save_post_without_title
+test "should not save post without title" do
post = Post.new
assert !post.save, "Saved the post without a title"
end
@@ -299,21 +306,10 @@ end
Running this test shows the friendlier assertion message:
<pre>
-$ ruby unit/post_test.rb -n test_should_not_save_post_without_title
-Loaded suite unit/post_test
-Started
-F
-Finished in 0.198093 seconds.
-
1) Failure:
-test_should_not_save_post_without_title(PostTest)
- [unit/post_test.rb:11:in `test_should_not_save_post_without_title'
- /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__'
- /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run']:
+test_should_not_save_post_without_title(PostTest) [/test/unit/post_test.rb:6]:
Saved the post without a title.
<false> is not true.
-
-1 tests, 1 assertions, 1 failures, 0 errors
</pre>
Now to get this test to pass we can add a model level validation for the _title_ field.
@@ -343,7 +339,7 @@ TIP: Many Rails developers practice _Test-Driven Development_ (TDD). This is an
To see how an error gets reported, here's a test containing an error:
<ruby>
-def test_should_report_error
+test "should report error" do
# some_undefined_variable is not defined elsewhere in the test case
some_undefined_variable
assert true
@@ -354,18 +350,15 @@ Now you can see even more output in the console from running the tests:
<pre>
$ ruby unit/post_test.rb -n test_should_report_error
-Loaded suite unit/post_test
+Loaded suite -e
Started
E
-Finished in 0.195757 seconds.
+Finished in 0.082603 seconds.
1) Error:
test_should_report_error(PostTest):
-NameError: undefined local variable or method `some_undefined_variable' for #<PostTest:0x2cc9de8>
- /opt/local/lib/ruby/gems/1.8/gems/actionpack-2.1.1/lib/action_controller/test_process.rb:467:in `method_missing'
- unit/post_test.rb:16:in `test_should_report_error'
- /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__'
- /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run'
+NameError: undefined local variable or method `some_undefined_variable' for #<PostTest:0x249d354>
+ /test/unit/post_test.rb:6:in `test_should_report_error'
1 tests, 0 assertions, 0 failures, 1 errors
</pre>
@@ -446,7 +439,7 @@ Now that we have used Rails scaffold generator for our +Post+ resource, it has a
Let me take you through one such test, +test_should_get_index+ from the file +posts_controller_test.rb+.
<ruby>
-def test_should_get_index
+test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:posts)
@@ -479,7 +472,7 @@ NOTE: If you try running +test_should_create_post+ test from +posts_controller_t
Let us modify +test_should_create_post+ test in +posts_controller_test.rb+ so that all our test pass:
<ruby>
-def test_should_create_post
+test "should create post" do
assert_difference('Post.count') do
post :create, :post => { :title => 'Some title'}
end
@@ -535,7 +528,7 @@ h4. A Fuller Functional Test Example
Here's another example that uses +flash+, +assert_redirected_to+, and +assert_difference+:
<ruby>
-def test_should_create_post
+test "should create post" do
assert_difference('Post.count') do
post :create, :post => { :title => 'Hi', :body => 'This is my first post.'}
end
@@ -625,7 +618,7 @@ class UserFlowsTest < ActionController::IntegrationTest
# fixtures :your, :models
# Replace this with your real tests.
- def test_truth
+ test "the truth" do
assert true
end
end
@@ -660,7 +653,7 @@ require 'test_helper'
class UserFlowsTest < ActionController::IntegrationTest
fixtures :users
- def test_login_and_browse_site
+ test "login and browse site" do
# login via https
https!
get "/login"
@@ -688,7 +681,7 @@ require 'test_helper'
class UserFlowsTest < ActionController::IntegrationTest
fixtures :users
- def test_login_and_browse_site
+ test "login and browse site" do
# User avs logs in
avs = login(:avs)
@@ -771,12 +764,12 @@ class PostsControllerTest < ActionController::TestCase
@post = nil
end
- def test_should_show_post
+ test "should show post" do
get :show, :id => @post.id
assert_response :success
end
- def test_should_destroy_post
+ test "should destroy post" do
assert_difference('Post.count', -1) do
delete :destroy, :id => @post.id
end
@@ -809,17 +802,17 @@ class PostsControllerTest < ActionController::TestCase
@post = nil
end
- def test_should_show_post
+ test "should show post" do
get :show, :id => @post.id
assert_response :success
end
- def test_should_update_post
+ test "should update post" do
put :update, :id => @post.id, :post => { }
assert_redirected_to post_path(assigns(:post))
end
- def test_should_destroy_post
+ test "should destroy post" do
assert_difference('Post.count', -1) do
delete :destroy, :id => @post.id
end
@@ -841,7 +834,7 @@ h3. Testing Routes
Like everything else in your Rails application, it is recommended that you test your routes. An example test for a route in the default +show+ action of +Posts+ controller above should look like:
<ruby>
-def test_should_route_to_post
+test "should route to post" do
assert_routing '/posts/1', { :controller => "posts", :action => "show", :id => "1" }
end
</ruby>
@@ -883,7 +876,7 @@ require 'test_helper'
class UserMailerTest < ActionMailer::TestCase
tests UserMailer
- def test_invite
+ test "invite" do
@expected.from = 'me@example.com'
@expected.to = 'friend@example.com'
@expected.subject = "You have been invited by #{@expected.from}"
@@ -920,7 +913,7 @@ Functional testing for mailers involves more than just checking that the email b
require 'test_helper'
class UserControllerTest < ActionController::TestCase
- def test_invite_friend
+ test "invite friend" do
assert_difference 'ActionMailer::Base.deliveries.size', +1 do
post :invite_friend, :email => 'friend@example.com'
end