Allows you to send messages back to user like Book was succesfully created. Renders regardless of which page you refresh to.
Two different types of messages
- :notice - success messages
- :alert - error messages
Steps
-
Update spec/features/book_management_spec.rb to test for flash message
require "spec_helper" feature "Book management" do scenario "User manages books" do visit root_path click_link "Books" click_link "Add book" fill_in "Title", with: "Garfield" fill_in "Pages", with: "250" click_button "Save" expect(page).to have_content "successfully created" end end
-
Run the spec
$ rspec spec/features/book_management_spec.rb F Failures: 1) Book management User manages books Failure/Error: expect(page).to have_content "successfully created" expected to find text "successfully created" in "Books Garfield" # ./spec/features/book_management_spec.rb:15:in `block (2 levels) in <top (required)>' Finished in 0.24735 seconds 1 example, 1 failure Failed examples: rspec ./spec/features/book_management_spec.rb:4 # Book management User manages books Randomized with seed 36959 -
Update the create method app/controllers/books_controller.rb
def create @book = Book.new book_params if @book.save flash[:notice] = "Book successfully created." redirect_to @book else render "new" end end
-
Update the app/views/layouts/application.html.erb to render flash mesages
<!DOCTYPE html> <html> <head> <title>Readathon App</title> <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> <%= javascript_include_tag "application", "data-turbolinks-track" => true %> <%= csrf_meta_tags %> </head> <body> <%= link_to "Books", books_path %> <% flash.each do |name, msg| %> <div class="flash <%= name %>"><%= msg %></div> <% end %> <%= yield %> </body> </html> -
Run the specs
$ rspec spec/features/book_management_spec.rb . Finished in 0.76564 seconds 1 example, 0 failures Randomized with seed 61984 -
Commit
$ git add . $ git commit -m "Added flash messages" -
Fire up the server and check it out
-
Update spec to include edit and delete
require "spec_helper" feature "Book management" do scenario "User manages books" do visit root_path click_link "Books" click_link "Add book" fill_in "Title", with: "Garfield" fill_in "Pages", with: "250" click_button "Save" expect(page).to have_content "successfully created" click_link "Edit" fill_in "Title", with: "Calvin and Hobbes" click_button "Save" expect(page).to have_content "successfully updated" expect(page).to have_content "Calvin and Hobbes" click_link "Delete" expect(page).to have_content "successfully deleted" end end -
Run the spec
$ rspec spec/features/book_management_spec.rb F Failures: 1) Book management User manages books Failure/Error: click_link "Edit" Capybara::ElementNotFound: Unable to find link "Edit" # ./spec/features/book_management_spec.rb:17:in `block (2 levels) in <top (required)>' Finished in 0.24665 seconds 1 example, 1 failure Failed examples: rspec ./spec/features/book_management_spec.rb:4 # Book management User manages books Randomized with seed 48927 -
Add edit link in app/views/books/show.html.erb
<%= link_to "Edit", edit_book_path(@book) %> <h1><%= @book.title %></h1> -
Run spec
$ rspec spec/features/book_management_spec.rb F Failures: 1) Book management User manages books Failure/Error: click_button "Save" ActionView::Template::Error: undefined method `edit_book_path' for #<#<Class:0x00000003aa1da8>:0x0000000481d090> # ./app/views/books/show.html.erb:1:in `_app_views_books_show_html_erb__4335723529987197110_37835000' # ./spec/features/book_management_spec.rb:13:in `block (2 levels) in <top (required)>' Finished in 0.7835 seconds 1 example, 1 failure Failed examples: rspec ./spec/features/book_management_spec.rb:4 # Book management User manages books Randomized with seed 14255 -
Update config/routes.rb
Readathon::Application.routes.draw do root "pages#index" resources :books, only: [:index, :new, :create, :show, :edit] end -
Run the spec
$ rspec spec/features/book_management_spec.rb F Failures: 1) Book management User manages books Failure/Error: click_link "Edit" AbstractController::ActionNotFound: The action 'edit' could not be found for BooksController # ./spec/features/book_management_spec.rb:17:in `block (2 levels) in <top (required)>' Finished in 0.26976 seconds 1 example, 1 failure Failed examples: rspec ./spec/features/book_management_spec.rb:4 # Book management User manages books Randomized with seed 59630 -
Add the edit action to the books controller
class BooksController < ApplicationController def index end def new @book = Book.new end def create @book = Book.new book_params if @book.save flash[:notice] = "Book successfully created." redirect_to @book else render "new" end end def show @book = Book.find params[:id] end def edit @book = Book.find params[:id] end private def book_params params.require(:book).permit(:title, :pages) end end
-
Run the spec
$ rspec spec/features/book_management_spec.rb F Failures: 1) Book management User manages books Failure/Error: click_link "Edit" ActionView::MissingTemplate: Missing template books/edit, application/edit with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :jbuilder]}. Searched in: * "/home/action/workspace/readathon/app/views" # ./spec/features/book_management_spec.rb:17:in `block (2 levels) in <top (required)>' Finished in 0.76224 seconds 1 example, 1 failure Failed examples: rspec ./spec/features/book_management_spec.rb:4 # Book management User manages books Randomized with seed 46924 -
Create app/views/books/edit.html.erb
<%= simple_form_for @book do |f| %> <%= f.input :title %> <%= f.input :pages %> <%= f.submit "Save" %> <% end %> -
Run the spec
$ rspec spec/features/book_management_spec.rb F Failures: 1) Book management User manages books Failure/Error: click_button "Save" ActionController::RoutingError: No route matches [PATCH] "/books/8" # ./spec/features/book_management_spec.rb:19:in `block (2 levels) in <top (required)>' Finished in 0.76454 seconds 1 example, 1 failure Failed examples: rspec ./spec/features/book_management_spec.rb:4 # Book management User manages books Randomized with seed 13097 -
Add the update route
Readathon::Application.routes.draw do root "pages#index" resources :books, only: [:index, :new, :create, :show, :edit, :update] end
-
Run the spec
$ rspec spec/features/book_management_spec.rb F Failures: 1) Book management User manages books Failure/Error: click_button "Save" AbstractController::ActionNotFound: The action 'update' could not be found for BooksController # ./spec/features/book_management_spec.rb:19:in `block (2 levels) in <top (required)>' Finished in 0.29706 seconds 1 example, 1 failure Failed examples: rspec ./spec/features/book_management_spec.rb:4 # Book management User manages books Randomized with seed 61146 -
Create update action in app/controllers/books_controller.rb
class BooksController < ApplicationController # Other actions omitted def update @book = Book.find params[:id] if @book.update_attributes book_params flash[:notice] = "Book successfully updated." redirect_to @book else render "edit" end end end
-
Run the spec
$ rspec spec/features/book_management_spec.rb F Failures: 1) Book management User manages books Failure/Error: click_link "Delete" Capybara::ElementNotFound: Unable to find link "Delete" # ./spec/features/book_management_spec.rb:24:in `block (2 levels) in <top (required)>' Finished in 0.79845 seconds 1 example, 1 failure Failed examples: rspec ./spec/features/book_management_spec.rb:4 # Book management User manages books Randomized with seed 15875
-
Add a delete link to app/views/books/show.html.erb
<%= link_to "Edit", edit_book_path(@book) %> | <%= link_to "Delete", @book, method: :delete %> <h1><%= @book.title %></h1> -
Run the spec
$ rspec spec/features/book_management_spec.rb F Failures: 1) Book management User manages books Failure/Error: click_link "Delete" ActionController::RoutingError: No route matches [DELETE] "/books/15" # ./spec/features/book_management_spec.rb:24:in `block (2 levels) in <top (required)>' Finished in 0.78999 seconds 1 example, 1 failure Failed examples: rspec ./spec/features/book_management_spec.rb:4 # Book management User manages books Randomized with seed 23182 -
Add the destroy route
Readathon::Application.routes.draw do root "pages#index" resources :books, only: [:index, :new, :create, :show, :edit, :update, :destroy] end -
Run the spec
$ rspec spec/features/book_management_spec.rb F Failures: 1) Book management User manages books Failure/Error: click_link "Delete" AbstractController::ActionNotFound: The action 'destroy' could not be found for BooksController # ./spec/features/book_management_spec.rb:24:in `block (2 levels) in <top (required)>' Finished in 0.3065 seconds 1 example, 1 failure Failed examples: rspec ./spec/features/book_management_spec.rb:4 # Book management User manages books Randomized with seed 54552 -
Add the destroy action
class BooksController < ApplicationController # Other actions omitted... def destroy @book = Book.find params[:id] if @book.destroy flash[:notice] = "Book successfully deleted." end redirect_to books_path end end
-
Run the spec
$ rspec spec/features/book_management_spec.rb . Finished in 0.80757 seconds 1 example, 0 failures Randomized with seed 3856 -
Commit
$ git add . $ git commit -m "Finished book crud"
-
Let's start by making the spec more readable. spec/features/book_management_spec.rb
require "spec_helper" feature "Book management" do scenario "User manages books" do visit root_path click_link "Books" click_link "Add book" fill_in "Title", with: "Garfield" fill_in "Pages", with: "250" click_button "Save" user_sees_flash_message "successfully created" click_link "Edit" fill_in "Title", with: "Calvin and Hobbes" click_button "Save" user_sees_flash_message "successfully updated" expect(page).to have_content "Calvin and Hobbes" click_link "Delete" user_sees_flash_message "successfully deleted" end def user_sees_flash_message msg expect(page).to have_content msg end end
-
Run the spec and make sure things still work
$ rspec spec/features/book_management_spec.rb . Finished in 0.81859 seconds 1 example, 0 failures Randomized with seed 14977 -
Extract button saving
require "spec_helper" feature "Book management" do scenario "User manages books" do visit root_path click_link "Books" click_link "Add book" fill_in "Title", with: "Garfield" fill_in "Pages", with: "250" save user_sees_flash_message "successfully created" click_link "Edit" fill_in "Title", with: "Calvin and Hobbes" save user_sees_flash_message "successfully updated" expect(page).to have_content "Calvin and Hobbes" click_link "Delete" user_sees_flash_message "successfully deleted" end def user_sees_flash_message msg expect(page).to have_content msg end def save click_button "Save" end end -
Run the spec and make sure things still work
$ rspec spec/features/book_management_spec.rb . Finished in 0.80727 seconds 1 example, 0 failures Randomized with seed 37814 -
Extract book adding
require "spec_helper" feature "Book management" do scenario "User manages books" do visit root_path click_link "Books" add_book_with_title "Garfield" user_sees_flash_message "successfully created" click_link "Edit" fill_in "Title", with: "Calvin and Hobbes" save user_sees_flash_message "successfully updated" expect(page).to have_content "Calvin and Hobbes" click_link "Delete" user_sees_flash_message "successfully deleted" end def add_book_with_title title click_link "Add book" fill_in "Title", with: title fill_in "Pages", with: "250" save end def user_sees_flash_message msg expect(page).to have_content msg end def save click_button "Save" end end
-
Run the spec and make sure it still works
-
Extract editing book title
require "spec_helper" feature "Book management" do scenario "User manages books" do visit root_path click_link "Books" add_book_with_title "Garfield" user_sees_flash_message "successfully created" edit_book_title "Calvin and Hobbes" user_sees_flash_message "successfully updated" expect(page).to have_content "Calvin and Hobbes" click_link "Delete" user_sees_flash_message "successfully deleted" end def add_book_with_title title click_link "Add book" fill_in "Title", with: title fill_in "Pages", with: "250" save end def edit_book_title title click_link "Edit" fill_in "Title", with: title save end def user_sees_flash_message msg expect(page).to have_content msg end def save click_button "Save" end end
-
Run the spec and make sure things still work
-
Extract the book title to helper
require "spec_helper" feature "Book management" do scenario "User manages books" do visit root_path click_link "Books" add_book_with_title "Garfield" user_sees_flash_message "successfully created" edit_book_title "Calvin and Hobbes" user_sees_flash_message "successfully updated" user_sees_book_title "Calvin and Hobbes" click_link "Delete" user_sees_flash_message "successfully deleted" end def add_book_with_title title click_link "Add book" fill_in "Title", with: title fill_in "Pages", with: "250" save end def edit_book_title title click_link "Edit" fill_in "Title", with: title save end def user_sees_book_title title expect(page).to have_content title end def user_sees_flash_message msg expect(page).to have_content msg end def save click_button "Save" end end
-
Run the spec to make sure it still passes
-
Commit
$ git add . $ git commit -m "Refactored test to help readability" -
Extract book loading into before filter in app/controllers/books_controller.rb
class BooksController < ApplicationController before_filter :find_book, only: [ :show, :edit, :update, :destroy] def index end def new @book = Book.new end def create @book = Book.new book_params if @book.save flash[:notice] = "Book successfully created." redirect_to @book else render "new" end end def show end def edit end def update if @book.update_attributes book_params flash[:notice] = "Book successfully updated." redirect_to @book else render "edit" end end def destroy if @book.destroy flash[:notice] = "Book successfully deleted." end redirect_to books_path end private def find_book @book = Book.find params[:id] end def book_params params.require(:book).permit(:title, :pages) end end
-
Run your spec and make sure it still works.
-
Commit
$ git add . $ git commit -m "Extracted out book loading into before filter"
-
Extract new and edit html into app/views/books/_form.html.erb
<%= simple_form_for @book do |f| %> <%= f.input :title %> <%= f.input :pages %> <%= f.submit "Save" %> <% end %> -
Update app/views/books/edit.html.erb and app/views/books/new.html.erb to use the partial
<%= render partial: "form" %> -
Run the spec and make sure it still works
-
Commit
$ git add . $ git commit -m "Extracted book form into partial"
-
Update the form so you don't explicitly name the submit button "Save"
<%= simple_form_for @book do |f| %> <%= f.input :title %> <%= f.input :pages %> <%= f.submit %> <% end %> -
Run the spec
Failures: 1) Book management User manages books Failure/Error: click_button "Save" Capybara::ElementNotFound: Unable to find button "Save" # ./spec/features/book_management_spec.rb:44:in `save' # ./spec/features/book_management_spec.rb:26:in `add_book_with_title' # ./spec/features/book_management_spec.rb:8:in `block (2 levels) in <top (required)>' Finished in 0.73467 seconds 1 example, 1 failure Failed examples: rspec ./spec/features/book_management_spec.rb:4 # Book management User manages books Randomized with seed 40644 -
Start up the server and see what else you could reference the submit button by
-
Modify your test to use the css selector "input[type=submit]"
require "spec_helper" feature "Book management" do scenario "User manages books" do visit root_path click_link "Books" add_book_with_title "Garfield" user_sees_flash_message "successfully created" edit_book_title "Calvin and Hobbes" user_sees_flash_message "successfully updated" user_sees_book_title "Calvin and Hobbes" click_link "Delete" user_sees_flash_message "successfully deleted" end def add_book_with_title title click_link "Add book" fill_in "Title", with: title fill_in "Pages", with: "250" save end def edit_book_title title click_link "Edit" fill_in "Title", with: title save end def user_sees_book_title title expect(page).to have_content title end def user_sees_flash_message msg expect(page).to have_content msg end def save find("input[type=submit]").click end end
-
Run the spec to make sure it still works
-
Commit
$ git add . $ git commit -m "Changed submit buttons to use default text" -
Update config/routes.rb to just use the resources call, since we are defining all 7 routes
Readathon::Application.routes.draw do root "pages#index" resources :books end -
Run rake and make sure everything still works
-
Commit
$ git add . $ git commit -m "Cleaned up routes"
-
Create another scenario in spec/features/book_management_spec.rb
scenario 'user tries to save invalid book' do visit new_book_path fill_in "Pages", with: "200" save expect(page).to_not have_content "successfully created" fill_in "Title", with: "Star Wars" save user_sees_flash_message "successfully created" end
-
Run the spec
$ rspec spec/features/book_management_spec.rb .F Failures: 1) Book management user tries to save invalid book Failure/Error: expect(page).to_not have_content "successfully created" expected not to find text "successfully created" in "Books Book successfully created. Edit | Delete" # ./spec/features/book_management_spec.rb:28:in `block (2 levels) in <top (required)>' Finished in 0.87525 seconds 2 examples, 1 failure Failed examples: rspec ./spec/features/book_management_spec.rb:21 # Book management user tries to save invalid book Randomized with seed 8897 -
Let's create a unit test to just test the user validation. spec/models/user_spec.rb
require 'spec_helper' describe Book, "validations" do it "is valid when it has a title attribute" do book = Book.new title: "Something" expect(book.valid?).to be_true end it "is not valid when it is missing a title attribute" do book = Book.new expect(book.valid?).to be_false end end -
Run your new spec
$ rspec spec/models/book_spec.rb .F Failures: 1) Book validations is not valid when it is missing a title attribute Failure/Error: expect(book.valid?).to be_false expected: false value got: true # ./spec/models/book_spec.rb:11:in `block (2 levels) in <top (required)>' Finished in 0.01662 seconds 2 examples, 1 failure Failed examples: rspec ./spec/models/book_spec.rb:9 # Book validations is not valid when is missing a title attribute Randomized with seed 16767 -
Add validates_presence_of to your book model
class Book < ActiveRecord::Base validates_presence_of :title end
-
Run your spec
$ rspec spec/models/book_spec.rb .. Finished in 0.02011 seconds 2 examples, 0 failures Randomized with seed 1631 -
Now add tests to make sure pages are required
require 'spec_helper' describe Book, "validations" do it "is valid when it has a title attribute" do book = Book.new title: "Something" expect(book.valid?).to be_true end it "is not valid when it is missing a title attribute" do book = Book.new expect(book.valid?).to be_false end it "is valid when it has a pages attribute" do book = Book.new pages: "Something" expect(book.valid?).to be_true end it "is not valid when it is missing a pages attribute" do book = Book.new expect(book.valid?).to be_false end end -
Run the spec
$ rspec spec/models/book_spec.rb .F.. Failures: 1) Book validations is valid when it has a pages attribute Failure/Error: expect(book.valid?).to be_true expected: true value got: false # ./spec/models/book_spec.rb:16:in `block (2 levels) in <top (required)>' Finished in 0.02594 seconds 4 examples, 1 failure Failed examples: rspec ./spec/models/book_spec.rb:14 # Book validations is valid when it has a pages attribute Randomized with seed 402 -
Make pages required.
class Book < ActiveRecord::Base validates_presence_of :title, :pages end -
Run the spec.
$ rspec spec/models/book_spec.rb F.F. Failures: 1) Book validations is valid when it has a pages attribute Failure/Error: expect(book.valid?).to be_true expected: true value got: false # ./spec/models/book_spec.rb:16:in `block (2 levels) in <top (required)>' 2) Book validations is valid when it has a title attribute Failure/Error: expect(book.valid?).to be_true expected: true value got: false # ./spec/models/book_spec.rb:6:in `block (2 levels) in <top (required)>' Finished in 0.06586 seconds 4 examples, 2 failures Failed examples: rspec ./spec/models/book_spec.rb:14 # Book validations is valid when it has a pages attribute rspec ./spec/models/book_spec.rb:4 # Book validations is valid when it has a title attribute Randomized with seed 4768 -
What just happened? How can we fix this?
-
Switch to test by which fields have errors
require 'spec_helper' describe Book, "validations" do it "is valid when it has a title attribute" do book = Book.new title: "Something" book.valid? expect(book.errors[:title].empty?).to be_true end it "is not valid when it is missing a title attribute" do book = Book.new book.valid? expect(book.errors[:title].empty?).to_not be_true end it "is valid when it has a pages attribute" do book = Book.new pages: "Something" book.valid? expect(book.errors[:pages]).to be_empty end it "is not valid when it is missing a pages attribute" do book = Book.new book.valid? expect(book.errors[:pages]).to_not be_empty end end
-
Run the spec
$ rspec spec/models/book_spec.rb .... Finished in 0.06578 seconds 4 examples, 0 failures Randomized with seed 53245 -
Comment out the validates_presence_of line in app/models/book.rb and make sure it fails
-
Comment that line back in and run the tests again and make sure it passes
-
Run rake and see if all of the tests pass
$ rake /home/action/.rvm/rubies/ruby-2.0.0-p247/bin/ruby -S rspec ./spec/features/book_management_spec.rb ./spec/features/view_homepage_spec.rb ./spec/models/book_spec.rb ....... Finished in 0.92894 seconds 7 examples, 0 failures Randomized with seed 7570 -
Commit
$ git add . $ git commit -m "Made book title and pages required"
Let's clean up the duplication between tests in spec/models/book_spec.rb
-
Install shoulda-matchers in your test group of your Gemfile.
group :test do gem 'shoulda-matchers' end -
Bundle install
-
Shoulda matchers allows you to change your tests in spec/models/book_spec.rb to read like this
require 'spec_helper' describe Book, "validations" do it{should validate_presence_of(:title)} it{should validate_presence_of(:pages)} end
-
Run your specs
$ rspec spec/models/book_spec.rb .. Finished in 0.03258 seconds 2 examples, 0 failures Randomized with seed 54297 -
Comment out your validates_presence_of lines in app/models/book.rb, run the tests and make sure it fails
-
Comment back in those lines and run rake, to make sure everything still passes
-
Commit
$ git add . $ git commit -m "Switched book specs to use shoulda-matchers"
-
Create another scenario in spec/features/book_management_spec.rb
scenario "user looks for all books" do # Create a few books # Look at the page # Make sure the books show up end -
Add factory girl rails to your gemfile in the development,test group
# Other gems omitted group :development, :test do gem 'rspec-rails' gem 'capybara' gem 'factory_girl_rails' end
-
Bundle install
-
Create your book factory spec/factories/books.rb
FactoryGirl.define do factory :book do title "My Book" pages 250 end end -
Update the your spec to create some books and make sure they show up
scenario "user looks for all books" do # Create a few books FactoryGirl.create_list :book, 3 # Look at the page visit books_path # Make sure the books show up expect(page).to have_css ".book", count: 3 end -
Run your spec
$ rspec spec/features/book_management_spec.rb F.. Failures: 1) Book management user looks for all books Failure/Error: expect(page).to have_css ".book", count: 3 Capybara::ExpectationNotMet: expected to find css ".book" 3 times but there were no matches # ./spec/features/book_management_spec.rb:41:in `block (2 levels) in <top (required)>' Finished in 0.90766 seconds 3 examples, 1 failure Failed examples: rspec ./spec/features/book_management_spec.rb:35 # Book management user looks for all books Randomized with seed 34425 -
Update app/views/books/index.html
<% @books.each do |book| %> <div class="book"> <%= book.title %> </div> <% end %> -
Run the spec
Failures: 1) Book management User manages books Failure/Error: click_link "Books" ActionView::Template::Error: undefined method `each' for nil:NilClass # ./app/views/books/index.html.erb:3:in `_app_views_books_index_html_erb___3101251044140856188_22759520' # ./spec/features/book_management_spec.rb:6:in `block (2 levels) in <top (required)>' 2) Book management user looks for all books Failure/Error: visit books_path ActionView::Template::Error: undefined method `each' for nil:NilClass # ./app/views/books/index.html.erb:3:in `_app_views_books_index_html_erb___3101251044140856188_22759520' # ./spec/features/book_management_spec.rb:39:in `block (2 levels) in <top (required)>' Finished in 0.80397 seconds 3 examples, 2 failures Failed examples: rspec ./spec/features/book_management_spec.rb:4 # Book management User manages books rspec ./spec/features/book_management_spec.rb:35 # Book management user looks for all books Randomized with seed 19423 -
Update the controller to assign the @books variable
class BooksController < ApplicationController before_filter :find_book, only: [ :show, :edit, :update, :destroy] def index @books = Book.all end # Other actions are omitted end -
Run the spec
$ rspec spec/features/book_management_spec.rb ... Finished in 0.91608 seconds 3 examples, 0 failures Randomized with seed 1596 -
Commit
$ git add . $ git commit -m "Book index page" -
Refactor app/views/books/index.html.erb to use div_for helper
<%= link_to "Add book", new_book_path %> <% @books.each do |book| %> <%= div_for book do %> <%= book.title %> <% end %> <% end %> -
Run specs to make sure it still passes
-
Commit
git add . git commit -m "Refactored to use div_for"