-
Add a new review link on app/views/reviews/show.html.erb
<h1><%= @book.name %></h1> <%= link_to "Edit", edit_book_path(@book) %> | <%= link_to "Delete", @book, method: :delete, data:{confirm: "Are you sure?"} %> <p>Pages: <%= @book.pages %></p> <p><%= simple_format @book.summary %></p> <h2>Reviews</h2> <!-- Add this line ---> <%= link_to "Add Review", new_book_review_path(@book) %> <% @reviews.each do |review| %> <div> <%= simple_format review.body %> <%= link_to [@book, review] do %> <p style="font-style: italic"><%= review.name %> gave this book a <%= review.rating %> star rating.</p> <% end %> </div> <% end %> <%= link_to "All Books", books_path %> -
Add the new route in config/routes.rb
Bookstore::Application.routes.draw do resources :books do resources :reviews, only: [:show, :new] end end
-
Create the new action in app/controllers/reviews_controller.rb
class ReviewsController < ApplicationController def show @book = Book.find params[:book_id] @review = @book.reviews.find params[:id] end def new @book = Book.find params[:book_id] @review = @book.reviews.new end end
-
Create app/views/reviews/new.html.erb
<%= form_for [@book, @review] do |f| %> <div> <%= f.label :name %> <%= f.text_field :name %> </div> <div> <%= f.label :rating %> <%= f.text_field :rating %> </div> <div> <%= f.label :body %> <%= f.text_area :body %> </div> <%= f.submit %> <% end %> -
Add create route in config/routes.rb
Bookstore::Application.routes.draw do resources :books do resources :reviews, only: [:show, :new, :create] end end
-
Add the create action to app/controllers/reviews_controller.rb
class ReviewsController < ApplicationController def show @book = Book.find params[:book_id] @review = @book.reviews.find params[:id] end def new @book = Book.find params[:book_id] @review = @book.reviews.new end def create @book = Book.find params[:book_id] @review = @book.reviews.new params[:review] @review.save redirect_to [@book, @review] end end
-
Commit
git add . git commit -m "New review functionality"
-
Refactor app_controllers/reviews_controller.rb to use a before_filter
class ReviewsController < ApplicationController before_filter :find_book def show @review = @book.reviews.find params[:id] end def new @review = @book.reviews.new end def create @review = @book.reviews.create params[:book] @review.save redirect_to [@book, @review] end private def find_book @book = Book.find params[:book_id] end end
-
Commit
git add . git commit -m "Extract book loading to before filter"
-
Create an edit link in app/views/reviews/show.html.erb
<h1>Review #<%= @review.id %> for <%= link_to @book.name, @book %></h1> <p><%= @review.name %> gave this book <%= @review.rating %> stars</p> <%= simple_format @review.body %> <%= link_to "Edit", edit_book_review_path(@book, @review) %> -
Add edit route to config/routes.rb
Bookstore::Application.routes.draw do resources :books do resources :reviews, only: [:show, :new, :create, :edit] end end
-
Add edit action to app/controllers/reviews_controller.rb
def edit @review = @book.reviews.find params[:id] end
-
Add app/views/edit.html.erb
<%= render "form" %> -
Move contents of app/views/reviews/new.html into app/views/reviews/_form.html.erb
<%= form_for [@book, @review] do |f| %> <div> <%= f.label :name %> <%= f.text_field :name %> </div> <div> <%= f.label :rating %> <%= f.text_field :rating %> </div> <div> <%= f.label :body %> <%= f.text_area :body %> </div> <%= f.submit %> <% end %> -
Edit app/views/new.html.erb
<%= render "form" %> -
Create update route in config/routes.rb
Bookstore::Application.routes.draw do resources :books do resources :reviews, only: [:show, :new, :create, :edit, :update] end end
-
Create update action in app/controllers/reviews_controller.rb
def update @review = @book.reviews.find params[:id] @review.update_attributes params[:review] redirect_to [@book, @review] end
-
Commit
git add . git commit -m "Edit reviews"
-
Add a delete link to app/views/reviews/show.html.erb
<h1>Review #<%= @review.id %> for <%= link_to @book.name, @book %></h1> <p><%= @review.name %> gave this book <%= @review.rating %> stars</p> <%= simple_format @review.body %> <%= link_to "Edit", edit_book_review_path(@book, @review) %> | <%= link_to "Delete", book_review_path(@book, @review), method: :delete, data: {confirm: "Are you sure?"} %> -
Add the destroy route to config/routes.rb
Bookstore::Application.routes.draw do resources :books do resources :reviews, only: [:show, :new, :create, :edit, :update, :destroy] end end
-
Add the destroy action to app/controllers/reviews_controller.rb
def destroy @review = @book.reviews.find params[:id] @review.destroy redirect_to @book end
-
Commit
git add . git commit -m "Delete reviews"
-
Require book name is set. In app/models/book.rb
class Book < ActiveRecord::Base has_many :reviews attr_accessible :name, :pages, :summary validates_presence_of :name end
-
Try to create new book without a title in the console
$ rails c Loading development environment (Rails 3.2.17) 2.0.0-p247 :001 > book = Book.new => #<Book id: nil, name: nil, pages: nil, created_at: nil, updated_at: nil, summary: nil> 2.0.0-p247 :002 > book.save (0.1ms) begin transaction [deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = f alse to avoid this message. (0.1ms) rollback transaction => false 2.0.0-p247 :003 > book.errors.full_messages => ["Name can't be blank"] 2.0.0-p247 :004 > book.name = "War and Peace" => "War and Peace" 2.0.0-p247 :005 > book.valid? => true 2.0.0-p247 :006 > book.save (0.1ms) begin transaction SQL (4.9ms) INSERT INTO "books" ("created_at", "name", "pages", "summary", "updated_at") VALUES (?, ?, ?, ?, ?) [["created_at", Mon, 17 Mar 2014 00:46:12 UTC +00:00], ["name", "War and Peace"], ["pages", nil], ["summary", nil], ["updated_at", Mon, 17 Mar 2014 00:46:12 UTC +00:00]] (30.5ms) commit transaction => true 2.0.0-p247 :007 > -
Update app/controllers/books_controller.rb to re-display the form if the create action fails
def create @book = Book.new params[:book] if @book.save redirect_to @book else render 'new' end end
def update @book = Book.find params[:id] if @book.update_attributes params[:book] redirect_to @book else render "edit" end end
-
Show the errors on the book form (app/views/books/_form.html.erb)
<%= form_for @book do |f| %> <% if @book.errors.any? %> <div class="errors"> The form contains <%= pluralize(@book.errors.count, "error") %>. <ul> <% @book.errors.full_messages.each do |msg| %> <li> <%= msg %></li> <% end %> </ul> </div> <% end %> <div> <%= f.label :name %> <%= f.text_field :name %> </div> <div> <%= f.label :pages %> <%= f.text_field :pages %> </div> <div> <%= f.label :summary %> <%= f.text_area :summary %> </div> <%= f.submit %> <% end %> -
Ensure pages greater than 0. (app/models/book.rb)
class Book < ActiveRecord::Base has_many :reviews attr_accessible :name, :pages, :summary validates_presence_of :name validates_numericality_of :pages, greater_than: 0, allow_blank: true end
-
Commit
git add . git commit -m "Added book validations"
-
Add simple_form to the Gemfile.
gem 'simple_form', '~> 2.1.1'
-
Install gem
bundle install
-
Run simple form install generator
rails generate simple_form:install
-
Update app/views/books/_form.html.erb
<%= simple_form_for @book do |f| %> <%= f.input :name %> <%= f.input :pages %> <%= f.input :summary %> <%= f.submit %> <% end %> -
Commit
git add . git commit -m "Added simple form"
-
Add the bootstrap-sass gem to the Gemfile (in the assets group)
gem 'bootstrap-sass', '~> 2.1.1.0'
-
Bundle install
bundle install
-
Rename app/assets/stylesheets/application.css to app/assets/stylesheets/application.css.scss
git mv app/assets/stylesheets/application.css app/assets/stylesheets/application.css.scss -
Import bootstrap in app/assets/stylesheets/application.css.scss
@import "bootstrap";
-
Reinstall simple form with bootstrap support
rails generate simple_form:install --bootstrap
-
Style the form buttons app/views/books/_form.html.erb
<%= simple_form_for @book do |f| %> <%= f.input :name %> <%= f.input :pages %> <%= f.input :summary %> <%= f.submit class: "btn btn-primary" %> <% end %> -
Commit
git add . git commit -m "Added bootstrap"
-
Update app/controllers/reviews_controller.rb to handle invalid data
def create @review = @book.reviews.new params[:review] if @review.save redirect_to [@book, @review] else render "new" end end
def update @review = @book.reviews.find params[:id] if @review.update_attributes params[:review] redirect_to [@book, @review] else render "edit" end end
-
Make review name, rating, and body required (app/models/review.rb)
class Review < ActiveRecord::Base belongs_to :book attr_accessible :body, :name, :rating validates_presence_of :name, :rating, :body end
-
Convert app/views/reviews/_form.html.erb to use simple form
<%= simple_form_for [@book, @review] do |f| %> <%= f.input :name %> <%= f.input :rating %> <%= f.input :body %> <%= f.submit, class: "btn btn-primary"%> <% end %> -
Commit
git add . git commit -m "Reviews simple form and validations"
-
Update review model to only allow ratings as integers between 1 and 5
class Review < ActiveRecord::Base belongs_to :book attr_accessible :body, :name, :rating validates_presence_of :name, :rating, :body validates_numericality_of :rating, only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 5 end
-
Update review form to render ratings as a select menu
<%= simple_form_for [@book, @review] do |f| %> <%= f.input :name %> <%= f.input :rating, as: :select, collection: 1..5 %> <%= f.input :body %> <%= f.submit, class: "btn btn-primary" %> <% end %> -
Commit
git add . git commit -m "Rating is now a dropdown"
- Update app/views/book.rb
class Book < ActiveRecord::Base
has_many :reviews, dependent: :destroy
attr_accessible :name, :pages, :summary
validates_presence_of :name
validates_numericality_of :pages, greater_than: 0, allow_blank: true
end