Skip to content

Latest commit

 

History

History
562 lines (458 loc) · 13.3 KB

07.md

File metadata and controls

562 lines (458 loc) · 13.3 KB

Logo do Curso

<< Voltar

Material da aula 07

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

Passo a passo

Models e Seeds

Gerar models

$ 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

Models

Cart

# cart.rb
class Cart < ApplicationRecord
  include Priceable

  belongs_to :user
  has_many :order_items
end

Order

# order.rb
class Order < ApplicationRecord
  include Priceable

  belongs_to :user
  has_many :order_items
end

OrderItem

# 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

# 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

# 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

Detalhe de exemplo para concerns

# 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 (cancancan)

# 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

Seeds

# 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})}

Controllers, rotas e Views

Gerar controllers

$ rails g controller cart index
$ rails g controller orders index show

Corrigir e adicionar rotas

# 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

Controllers

Cart

# 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

# 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

# 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 ...

Views

Cart

cart#index
<!-- # 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

orders#index
<!-- # 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
<!-- # 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
<!-- # 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

actors#show
<!-- # 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

directors#show
<!-- # 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
<!-- # movies/_form.html.erb:27 [price] -->
  <%= f.input :price %>
_movie.html.erb
<!--  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
<!-- # 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 (movies/add_to_cart.js.erb)
// 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.");