- https://gist.github.com/DeepNeuralAI/efd2f56b798fef9d38ad90a277d7cbfb
- https://guides.rubyonrails.org/active_record_migrations.html
- https://guides.rubyonrails.org/active_record_basics.html
- https://guides.rubyonrails.org/v5.2/command_line.html
Objective
- fix list of food types for filtering
- add ability to add reviews to restaurants
Create a git repo for the rails-01-restaurants app on github and clone it locally
Create the rails app
rails new rails-01-restaurants --database=postgresql
cd rails-01-restaurants
rails db:create
rails server
Creating the controller
rails generate controller restaurants
Ok now we know the basic functionality we are going to need lets setup our routes file.
config/routes.rb
Rails.application.routes.draw do
get "/restaurants", to: "restaurants#index", as: "restaurants"
post "/restaurants", to: "restaurants#create"
get "/restaurants/new", to: "restaurants#new", as: "new_restaurant"
get "/restaurants/:id", to: "restaurants#show", as: "restaurant"
root 'restaurants#index'
end
Ok the routes file is setup so lets create and fill out our controller.
app/controllers/restaurant_controller.rb
class RestaurantsController < ApplicationController
def index
end
def create
end
def new
end
def show
end
end
Generate the model
rails generate model Restaurant title:string address:string description:string food_type:string
Run the created migration
rake db:migrate
And now lets just setup some empty views for index, new and show. Create will not have a view since its responsible for creating a new movie and then redirecting to one of the other routes.
Create these files
- app/views/restaurants/index.html.erb
- app/views/restaurants/new.html.erb
- app/views/restaurants/show.html.erb
Alright so if we go to "localhost:3000/restaurants/new" we don’t get an error but there isn’t anything on our screen yet. Let’s fix that by adding in some HTML.
Use of for
in labels
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label
app/views/restaurants/new.html.erb
<h1>Create A New Restaurant</h1>
<form action="<%= restaurants_path %>" method="POST" >
<input type="hidden" value="<%= form_authenticity_token %>" name="authenticity_token" />
<label for="title">Title</label>
<input type="text" name="title" id="title"/>
<label for="address">Address</label>
<input type="text" name="address" id="address"/>
<label>Description</label>
<input type="textarea" name="description" id="description" />
<label for="food_type">Food type</label>
<input type="text" name="food_type" id="food_type" />
<input type="submit" value="Create" />
</form>
Lets first print out our params variable and make sure we are receiving data first.
def create
puts params
end
Look at the server output to see the data is coming through. Let’s fill out our controller methods.
class RestaurantsController < ApplicationController
def index
@restaurants = Restaurant.all
end
def create
@restaurant = Restaurant.new(restaurant_params)
if @restaurant.save
redirect_to @restaurant
else
render 'new'
end
end
def new
@restaurant = Restaurant.new
end
def show
@restaurant = Restaurant.find(params[:id])
end
private
def restaurant_params
params.permit(:title, :address, :description, :food_type)
end
end
And now our other views.
app/views/restaurants/index.html.erb
<h1>Restaurants</h1>
<ul>
<% @restaurants.each_with_index do |restaurant, index| %>
<li>
<%= restaurant.title %> - <%= restaurant.food_type %> <%= link_to "show", restaurant_path(restaurant.id) %>
</li>
<% end %>
</ul>
app/views/restaurants/show.html.erb
<h2><%= @restaurant.title %></h2>
<h3>Food Type</h3>
<p><%= @restaurant.food_type %></p>
<h3>Address</h3>
<p><%= @restaurant.address %></p>
<h3>Description</h3>
<p><%= @restaurant.description %></p>
<%= link_to "Back", :back %>
Awesome now all that’s left is to add in a way to update.
Ok first we need to setup our routes, controller methods and a view with a form to update our movie.
config/routes.rb
put "/restaurants/:id", to: "restaurants#update"
patch "/restaurants/:id", to: "restaurants#update"
get "/restaurants/:id/edit", to: "restaurants#edit", as: "edit_restaurant"
app/controllers/restaurants_controller.rb
def edit
@restaurant = Restaurant.find(params[:id])
end
def update
@restaurant = Restaurant.find(params[:id])
if @restaurant.update(restaurant_params)
redirect_to @restaurant
else
render 'edit'
end
end
app/views/restaurants/edit.html.erb
<h1>Edit Restaurant</h1>
<form action="<%= restaurant_path(params[:id]) %>" method="POST" >
<input type="hidden" value="<%= form_authenticity_token %>" name="authenticity_token" />
<input type="hidden" name="_method" value="patch" />
<label for="title">Title</label>
<input type="text" name="title" id="title" value="<%= @restaurant.title %>"/>
<label for="address">Address</label>
<input type="text" name="address" name="address" id="address" value="<%= @restaurant.address %>"/>
<label>Description</label>
<input type="textarea" name="description" id="description" value="<%= @restaurant.description %>" />
<label for="food_type">Food type</label>
<input type="text" name="food_type" id="food_type" value="<%= @restaurant.food_type %>"/>
<input type="submit" value="Update" />
</form>
/app/models/restaurant.rb
class Restaurant < ApplicationRecord
validates :title, presence: true
validates :address, presence: true
validates :description, presence: true
validates :food_type, presence: true
end
Add the error display code to the create and edit forms
/app/views/restaurants/new.html.erb
<h1>Create A New Restaurant</h1>
<form action="<%= restaurants_path %>" method="POST" >
<input type="hidden" value="<%= form_authenticity_token %>" name="authenticity_token" />
<% if @restaurant.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(@restaurant.errors.count, "error") %> prohibited
this article from being saved:
</h2>
<ul>
<% @restaurant.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<label for="title">Title</label>
<input type="text" name="title" id="title"/>
<label for="address">Address</label>
<input type="text" name="address" name="address" id="address"/>
<label>Description</label>
<input type="textarea" name="description" id="description" />
<label for="food_type">Food type</label>
<input type="text" name="food_type" id="food_type" />
<input type="submit" value="Create" />
</form>
Reviews will
- belong to restaurants
- restaurants will have many reviews (We will add user sign-in later)
Generate the model
rails generate model Review title:string content:string rating:integer
Update the migration to create the association by adding the belongs_to
/db/migrate/20190417044201_create_reviews.rb
- your file name will have a different time stamp
class CreateReviews < ActiveRecord::Migration[5.2]
def change
create_table :reviews do |t|
t.string :title
t.string :content
t.integer :rating
t.belongs_to :restaurant, index: true
t.timestamps
end
end
end
Add fake gem by adding the following line to the end of the gemfile
gem "faker", "~> 1.9"
Then at the command line run
bundle install
Add some seed data
/db/seeds.rb
puts "Start of Seeding..."
Restaurant.destroy_all
10.times do
params = {
title: Faker::Restaurant.unique.name,
address: Faker::Address.unique.full_address,
food_type: Faker::Restaurant.type,
description: Faker::Lorem.paragraph
}
puts "Creating Restaurant: #{params[:title]}"
restaurant = Restaurant.new(params)
restaurant.save
reviews = rand(6)
reviews.times do
params = {
title: Faker::Restaurant.unique.name,
rating: rand(5),
content: Faker::Lorem.paragraph,
restaurant_id: restaurant.id
}
puts "Creating review: #{params[:title]}"
review = Review.new(params)
review.save
end
end
puts "Seeding Over"
/app/models/restaurant.rb
class Restaurant < ApplicationRecord
validates :title, presence: true
validates :address, presence: true
validates :description, presence: true
validates :food_type, presence: true
has_many :reviews
end
The dependent: :destroy
argument says, when a Restaurant gets deleted, all reviews belonging to that restaurant will be deleted too.
app/views/restaurants/index.html.erb
<h1>Restaurants</h1>
<ul>
<% @restaurants.each_with_index do |restaurant, index| %>
<li>
<p><%= restaurant.title %> - <%= restaurant.food_type %> <%= link_to "show", restaurant_path(restaurant.id) %></p>
<p>Reviews: <%= restaurant.reviews.count %></p>
</li>
<% end %>
</ul>
app/views/restaurants/show.html.erb
<h2><%= @restaurant.title %></h2>
<h3>Food Type</h3>
<p><%= @restaurant.food_type %></p>
<h3>Address</h3>
<p><%= @restaurant.address %></p>
<h3>Description</h3>
<p><%= @restaurant.description %></p>
<h3>Reviews</h3>
<ul>
<% @restaurant.reviews.each do |review| %>
<li>
<p><%= review.title %> - <%= review.rating %> </p>
<p><%= review.content %></p>
</li>
<% end %>
</ul>
<%= link_to "Back", :back %>
Add review edit Add review show Add review destroy Add restaurant reference for review
config/routes.rb
post "/reviews", to: "reviews#create", as: "review"
Creating the controller
rails generate controller reviews
Add create to the review controller
/app/controllers/reviews_controller.rb
class ReviewsController < ApplicationController
def create
@restaurant = Restaurant.find(params[:restaurant_id])
@review = @restaurant.reviews.create(review_params)
redirect_to restaurant_path(@restaurant.id)
end
private
def review_params
params.permit(:title, :content, :rating)
end
end
Edit the show details page to include a add review form below the description
app/views/restaurant/show.html.erb
<<h3>Add a Review</h3>
<form action="<%= review_path %>" method="POST" >
<input type="hidden" value="<%= form_authenticity_token %>" name="authenticity_token" />
<input type="hidden" value="<%= @restaurant.id %>" name="restaurant_id" />
<label for="title">Title</label>
<input type="text" name="title" id="title"/>
<label for="content">Content</label>
<input type="text" name="content" id="content"/>
<label for="rating">Rating</label>
<input type="text" name="rating" id="rating" />
<input type="submit" value="Create" />
</form>
- add navigation with a link to new restaurant and list restaurants
Restaurant Show erb
- Add code to only display the Reviews heading if there are any reviews
- Change the review layout from list to use headings and make the rating more obvious
- Add an overall rating for the restaurant
Restaurant Index erb
-
Display an overall rating for the restaurant
-
add an edit link
-
Add a destroy method for restaurants
- use the tutorial https://guides.rubyonrails.org/getting_started.html as a guide
- Add a link to Delete restaurant on the restaurant list page
-
Add a destroy method for reviews
- Add a link to Delete review on the restaurant show page
-
Add flash messages to CRUD operations
-
Add next to the show page to show the next restaurant