Temas:
- Término da aplicação Movie-store
Repositório git com a Aplicação Movie-Store com autorização e autenticação: https://github.com/alexkutzke/movie-store-auth
Repositório git com a Aplicação Movie-Store "completa": https://github.com/alexkutzke/movie-store-final
$ rails g model order_item movie:references quantity:integer cart:references order:references
$ rails g model order user:references
$ rails g model cart user:references
$ rails g migration AddPriceToMovies price:float
# cart.rb
class Cart < ApplicationRecord
include Priceable
belongs_to :user
has_many :order_items
end
# order.rb
class Order < ApplicationRecord
include Priceable
belongs_to :user
has_many :order_items
end
# order_item.rb
class OrderItem < ApplicationRecord
belongs_to :movie
belongs_to :cart
belongs_to :order
after_commit on: :update do |order_item|
order_item.destroy! if order_item.quantity <= 0
end
def price
self.movie.price
end
def total
self.movie.price * self.quantity
end
end
# movie.rb [has_many :order_items]
class Movie < ApplicationRecord
has_and_belongs_to_many :actors
has_and_belongs_to_many :directors
has_many :order_itens
def self.search(query)
self.where('title LIKE ?', "%#{query}%").order('id DESC')
end
def to_s
self.title
end
end
# user.rb [has_many :orders, has_one :cart, after_create cria cart]
class User < ApplicationRecord
require 'digest/md5'
has_one :cart
has_many :orders
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
after_create :init_cart
def gravatar
md5 = Digest::MD5.hexdigest(self.email)
"https://www.gravatar.com/avatar/#{md5}"
end
protected
def init_cart
Cart.create({user: self})
end
end
# concerns/priceable.rb [exemplo para calculo do total]
module Priceable
extend ActiveSupport::Concern
def total
total = 0.0
self.order_items.each do |order_item|
total += order_item.total
end
total
end
end
# ability.rb [só pode acessar seu cart e suas orders, adiciona ao cart]
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
cannot :manage, :all
can :show, :all
can :search, Movie
can :read, Order, user_id: user.id
can :manage, Cart, user_id: user.id
can :add_to_cart, Movie
end
# db/seeds.rb [melhoras]
require 'csv'
Movie.delete_all
Director.delete_all
Actor.delete_all
User.delete_all
Cart.delete_all
Order.delete_all
OrderItem.delete_all
CSV.foreach(Rails.root.join("db/seeds_data/movies.csv"), headers: false) do |m|
puts "#{m[0]}"
movie = Movie.new({
title: m[0],
year: m[1],
runtime: m[2],
genre: m[3],
plot: m[4],
language: m[5],
country: m[6],
awards: m[7],
poster_url: m[8],
imdb_id: m[9],
imdb_rating: m[10],
price: rand * 100.0
}
)
m[11].split(",").each do |director|
movie.directors << Director.find_or_create_by({name: director})
end
m[12].split(",").each do |actor|
movie.actors << Actor.find_or_create_by({name: actor})
end
movie.save!
end
User.create({name: "Alex Kutzke", email: "[email protected]", password: "123123", admin: true})
User.create({name: "Zé Lelé", email: "[email protected]", password: "123123", admin: false})
#Cart.create([{user: User.first},{user: User.last}])
10.times {OrderItem.create({quantity: (rand * 10).floor+1, movie: Movie.all.sample, cart: Cart.all.sample, order: nil})}
2.times {Order.create({user: User.all.sample})}
10.times {OrderItem.create({quantity: (rand * 10).floor+1, movie: Movie.all.sample, cart: nil, order: Order.all.sample})}
$ rails g controller cart index
$ rails g controller orders index show
# config/routes.rb [orders, cart[checkout,movies[plus,minus]],movies[add_to_cart]]
Rails.application.routes.draw do
resources :orders, only: [:index, :show]
resources :cart, only: [:index] do
collection do
get 'checkout'
end
resources :movies, only: [:destroy,:update], controller: :cart do
member do
get 'plus'
get 'minus'
end
end
end
devise_for :users
resources :users, except: [:new, :create]
resources :directors
resources :actors
resources :movies do
member do
get 'add-to-cart'
end
collection do
get 'search'
end
end
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
root 'store#index'
end
# cart_controller.rb
class CartController < ApplicationController
before_action :authenticate_user!
before_action :get_order_item, except: [:index, :checkout]
def index
@cart = current_user.cart
end
def plus
unless @order_item.nil?
@order_item.increment!(:quantity)
end
redirect_to cart_index_url
end
def minus
unless @order_item.nil?
# @order_item.decrement!(:quantity) was not triggering the after:commmit
# callback. See models/order_item.rb for more
@order_item.quantity -= 1
@order_item.save!
end
redirect_to cart_index_url
end
def checkout
cart = current_user.cart
Order.create({user: current_user, order_items: cart.order_items})
cart.order_items = []
cart.save!
redirect_to orders_url, notice: "Your order was sent!"
end
protected
def get_order_item
cart = current_user.cart
@order_item = cart.order_items.where({movie_id: params[:id]}).first
end
end
# orders_controller.rb
class OrdersController < ApplicationController
before_action :authenticate_user!
def index
@orders = current_user.orders.order(created_at: :desc)
end
def show
@order = Order.find(params[:id])
end
end
# movies_controller.rb [add_to_cart]
# ...
def add_to_cart
cart = current_user.cart
order_item = cart.order_items.find_or_create_by({movie: @movie})
order_item.increment!(:quantity)
respond_to do |format|
format.html {redirect_to cart_index_url}
format.js
end
end
# protected ...
<!-- # cart/index.html [lista de itens no cart] -->
<div class="container">
<h1><%= current_user.name %>'s cart</h1>
<% if @cart.order_items.empty? %>
<p class="text-center"><strong>Your cart is empty!</strong></p>
<% else %>
<table class="table table-striped table-hover">
<thead>
<th></th>
<th>Movie</th>
<th>Quantity</th>
<th>Price</th>
</thead>
<tbody>
<% @cart.order_items.each do |order_item| %>
<tr>
<td width="100px"><%= image_tag order_item.movie.poster_url, height: "100px" %></td>
<td width="100%"><%= order_item.movie.title %></td>
<td width="20px">
<div class="input-group mb-2 mr-sm-2 mb-sm-0">
<div class="input-group-addon" data-order-item-id="<%= order_item.id %>">
<%= link_to '<i class="fa fa-minus"></i>'.html_safe, minus_cart_movie_path(current_user.cart, order_item.movie) %>
</div>
<input disabled type="text" class="form-control quantity-control" data-order-item-id="<%= order_item.id %>" value="<%= order_item.quantity %>">
<div class="input-group-addon" data-order-item-id="<%= order_item.id %>">
<%= link_to '<i class="fa fa-plus"></i>'.html_safe, plus_cart_movie_path(current_user.cart, order_item.movie) %>
</div>
</div>
</td>
<td><%= number_to_currency order_item.total %></td>
</tr>
<% end %>
<tr>
<td></td>
<td></td>
<td class="text-right"><strong>Total</strong></td>
<td><strong><%= number_to_currency @cart.total %></strong></td>
</tr>
</tbody>
</table>
<p class="text-right">
<%= link_to '<i class="fa fa-credit-card"></i> Proceed to checkout'.html_safe, checkout_cart_index_path, class: "btn btn-success", data: { confirm: 'Are you sure?' } %>
</p>
<% end %>
</div>
<!-- # orders/index.html [lista de orders. Igual ao cart] -->
<div class="container">
<h1><%= current_user.name %>'s orders</h1>
<% if @orders.empty? %>
<p class="text-center"><strong>You do not have any orders!</strong></p>
<% else %>
<table class="table table-striped table-hover">
<thead>
<th>#</th>
<th>Movies</th>
<th>Total</th>
<th></th>
</thead>
<tbody>
<% @orders.each do |order| %>
<tr>
<td><%= order.id %></td>
<td width="100%">
<% order.order_items.each do |order_item| %>
<%= image_tag order_item.movie.poster_url, height: "100px" %>
<% end %>
</td>
<td><%= number_to_currency order.total %></td>
<td><%= link_to '<i class="fa fa-eye"></i>'.html_safe, order, class: "btn btn-small btn-secondary" %></td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
</div>
<!-- # orders/show.html [mostra order] -->
<div class="container">
<h1>Order #<%= @order.id %> - <%= current_user.name %></h1>
<table class="table table-striped table-hover">
<thead>
<th></th>
<th>Movie</th>
<th>Quantity</th>
<th>Price</th>
</thead>
<tbody>
<% @order.order_items.each do |order_item| %>
<tr>
<td width="100px"><%= image_tag order_item.movie.poster_url, height: "100px" %></td>
<td width="100%"><%= order_item.movie.title %></td>
<td>
<%= order_item.quantity %>
</td>
<td><%= number_to_currency order_item.total %></td>
</tr>
<% end %>
<tr>
<td></td>
<td></td>
<td class="text-right"><strong>Total</strong></td>
<td><strong><%= number_to_currency @order.total %></strong></td>
</tr>
</tbody>
</table>
</div>
<!-- # layouts/application.html.erb [link my orders, items do cart] -->
<!-- ... -->
<% if can? :read, Order %>
<li><%= link_to "My orders", orders_path %></li>
<% end %>
<!-- ... -->
<% if user_signed_in? %>
<%= link_to cart_index_path, class: "navbar-toggler" do %>
<i class="fa fa-shopping-cart"></i>
<span id="cart-count">(<%= current_user.cart.order_items.count %> <%= "item".pluralize(current_user.cart.order_items.count) %>)</span>
<% end %>
<% end %>
<!-- # actors/show.html.erb [admin] -->
<div class="container">
<h2><%= @actor %>'s movies</h2>
<%= render partial: "movies/movies", locals: {movies: @actor.movies} %>
<% if can? :manage, Actor %>
<%= link_to 'Edit', edit_actor_path(@actor), class: "btn btn-primary" %>
<%= link_to 'List of Actors', actors_path, class: "btn btn-secondary" %>
<% end %>
</div>
<!-- # directors/show.html.erb [admin] -->
<div class="container">
<h2><%= @director %>'s movies</h2>
<%= render partial: "movies/movies", locals: {movies: @director.movies} %>
<% if can? :manage, Director %>
<%= link_to 'Edit', edit_director_path(@director), class: "btn btn-primary" %>
<%= link_to 'List of Directors', directors_path, class: "btn btn-secondary" %>
<% end %>
</div>
<!-- # movies/_form.html.erb:27 [price] -->
<%= f.input :price %>
<!-- movies/_movie.html.erb [price, addo_to_cart] -->
<!-- 5: -->
<%= movie.title %> <span class="badge badge-default"><%= number_to_currency(movie.price) %></span>
<!-- 29: -->
<%= link_to '<i class="fa fa-cart-plus"></i> Add to Cart'.html_safe, add_to_cart_movie_path(movie.id), class: "btn btn-success bt-small", remote: user_signed_in? %>
<!-- # movies/show.html.erb [admin, add_to_cart] -->
<%= link_to '<i class="fa fa-cart-plus"></i> Add to Cart'.html_safe, add_to_cart_movie_path(@movie.id), class: "btn btn-success bt-small", remote: user_signed_in? %>
<% if can? :manage, Movie %>
<%= link_to 'Edit', edit_movie_path(@movie), class: "btn btn-primary" %>
<%= link_to 'List of Movies', movies_path, class: "btn btn-secondary" %>
<% end %>
// movies/add_to_cart.js.erb [ajax: atualiza items no cart (navbar)]
$("#cart-count").html("(<%= escape_javascript("#{current_user.cart.order_items.count} #{'item'.pluralize(current_user.cart.order_items.count)}") %>)");
alert("Movie added to your cart.");