diff --git a/app/controllers/customers_controller.rb b/app/controllers/customers_controller.rb index be25f1be..ddeaf12a 100644 --- a/app/controllers/customers_controller.rb +++ b/app/controllers/customers_controller.rb @@ -18,7 +18,36 @@ def index ) end -private + def show + @customer = Customer.find_by(id: params[:id]) + + unless @customer + render json: { + errors: ['Customer Not Found'], + }, status: :bad_request + + return + end + + rentals = @customer.rentals.map do |rental| + { + id: rental.id, + customer_id: rental.customer_id, + movie_id: rental.movie_id, + name: @customer.name, + title: rental.movie.title, + checkout_date: rental.checkout_date, + due_date: rental.due_date, + returned: rental.returned + } + end + + render json: rentals.as_json(), status: :ok + return + end + + private + def parse_query_args errors = {} @sort = params[:sort] diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index 362e2791..f64961ed 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -2,7 +2,7 @@ class MoviesController < ApplicationController before_action :require_movie, only: [:show] def index - if params[:query] + if params[:query] # maybe the user search input? data = MovieWrapper.search(params[:query]) else data = Movie.all @@ -11,7 +11,9 @@ def index render status: :ok, json: data end + # runs require_movie before this action def show + # if movie exists, status: ok and return movie info render( status: :ok, json: @movie.as_json( @@ -21,6 +23,32 @@ def show ) end + + def create + movie = Movie.new(movie_params) + + # if movie is already in db, don't add + if Movie.find_by(external_id: movie.external_id) + render json: { + errors: "This movie is already in the database" + }, status: :forbidden + return + end + + if movie.save + # render json: movie.as_json(only: [:id, :title, :overview, :release_date, :image_url, :external_id]), status: :created + # return + render json: movie.as_json(only: [:id]), status: :created + return + + else + render json: { + errors: movie.errors.messages + }, status: :bad_request + + end + end + private def require_movie @@ -29,4 +57,8 @@ def require_movie render status: :not_found, json: { errors: { title: ["No movie with title #{params["title"]}"] } } end end + + def movie_params + return params.permit(:title, :overview, :release_date, :image_url, :external_id, :inventory) + end end diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 67e77073..00f40968 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -2,7 +2,6 @@ class RentalsController < ApplicationController before_action :require_movie, only: [:check_out, :check_in] before_action :require_customer, only: [:check_out, :check_in] - # TODO: make sure that wave 2 works all the way def check_out rental = Rental.new(movie: @movie, customer: @customer, due_date: params[:due_date]) @@ -14,7 +13,7 @@ def check_out end def check_in - rental = Rental.first_outstanding(@movie, @customer) + rental = Rental.first_outstanding(@movie, @customer) # grabs first rental that matches movie & customer and is not returned unless rental return render status: :not_found, json: { errors: { @@ -31,6 +30,7 @@ def check_in end def overdue + # return all rentals where returned: false, and due date is before today rentals = Rental.overdue.map do |rental| { title: rental.movie.title, diff --git a/app/models/customer.rb b/app/models/customer.rb index 6fc89447..47061da9 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -2,6 +2,7 @@ class Customer < ApplicationRecord has_many :rentals has_many :movies, through: :rentals + # returns number of movies checked out def movies_checked_out_count self.rentals.where(returned: false).length end diff --git a/app/models/movie.rb b/app/models/movie.rb index fda94941..0239ff8b 100644 --- a/app/models/movie.rb +++ b/app/models/movie.rb @@ -2,6 +2,9 @@ class Movie < ApplicationRecord has_many :rentals has_many :customers, through: :rentals + validates :title, uniqueness: true, presence: true + # validates :inventory, presence: true + def available_inventory self.inventory - Rental.where(movie: self, returned: false).length end diff --git a/app/models/rental.rb b/app/models/rental.rb index 18654f04..905a3c9b 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -19,6 +19,7 @@ def self.overdue end private + # due_date must be after today's date def due_date_in_future return unless self.due_date unless due_date > Date.today diff --git a/config/routes.rb b/config/routes.rb index f4c99688..a183c63c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,9 +1,9 @@ Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html - resources :customers, only: [:index] + resources :customers, only: [:index, :show] - resources :movies, only: [:index, :show], param: :title + resources :movies, only: [:index, :show, :create], param: :title # show uses title instead of ID post "/rentals/:title/check-out", to: "rentals#check_out", as: "check_out" post "/rentals/:title/return", to: "rentals#check_in", as: "check_in" diff --git a/db/schema.rb b/db/schema.rb index ffb28f7e..8c8b42d4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -2,48 +2,51 @@ # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). +# This file is the source Rails uses to define your schema when running `rails +# db:schema:load`. When creating a new database, `rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180618042754) do +ActiveRecord::Schema.define(version: 2018_06_18_042754) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" create_table "customers", force: :cascade do |t| - t.string "name" + t.string "name" t.datetime "registered_at" - t.string "address" - t.string "city" - t.string "state" - t.string "postal_code" - t.string "phone" - t.float "account_credit" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "address" + t.string "city" + t.string "state" + t.string "postal_code" + t.string "phone" + t.float "account_credit" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "movies", force: :cascade do |t| - t.string "title" - t.text "overview" - t.date "release_date" - t.integer "inventory" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "image_url" - t.integer "external_id" + t.string "title" + t.text "overview" + t.date "release_date" + t.integer "inventory" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "image_url" + t.integer "external_id" end create_table "rentals", force: :cascade do |t| - t.integer "customer_id" - t.integer "movie_id" - t.date "checkout_date" - t.date "due_date" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "returned" + t.integer "customer_id" + t.integer "movie_id" + t.date "checkout_date" + t.date "due_date" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "returned" t.index ["customer_id"], name: "index_rentals_on_customer_id" t.index ["movie_id"], name: "index_rentals_on_movie_id" end diff --git a/lib/movie_wrapper.rb b/lib/movie_wrapper.rb index 1c808e8d..186d9231 100644 --- a/lib/movie_wrapper.rb +++ b/lib/movie_wrapper.rb @@ -4,18 +4,22 @@ class MovieWrapper BASE_IMG_URL = "https://image.tmdb.org/t/p/" DEFAULT_IMG_SIZE = "w185" - DEFAULT_IMG_URL = "http://lorempixel.com/185/278/" + # DEFAULT_IMG_URL = "http://lorempixel.com/185/278/" + DEFAULT_IMG_URL = "https://i.imgur.com/Uw59wWE.png?1" + def self.search(query, retries_left=3) raise ArgumentError.new("Can't search without a MOVIEDB_KEY. Please check your .env file!") unless KEY url = BASE_URL + "search/movie?api_key=" + KEY + "&query=" + query - response = HTTParty.get(url) + response = HTTParty.get(url) # MovieDB API call if response.success? + # if no results, return empty array if response["total_results"] == 0 return [] + # if results, create new Movie for each result else movies = response["results"].map do |result| self.construct_movie(result) @@ -25,7 +29,7 @@ def self.search(query, retries_left=3) elsif retries_left > 0 sleep(1.0 / (2 ** retries_left)) - return self.search(query, retries_left - 1) + return self.search(query, retries_left - 1) # recursively call search while there are more than 0 retries_left else raise "Request failed: #{url}" end diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb index 9172cf6e..d555d002 100644 --- a/test/controllers/movies_controller_test.rb +++ b/test/controllers/movies_controller_test.rb @@ -75,4 +75,59 @@ class MoviesControllerTest < ActionDispatch::IntegrationTest end end + + describe "create" do + let(:movie_params) { + { + title: "Parasite", + overview: "test!", + inventory: 10, + image_url: "image test", + external_id: 123976 + } + } + + it "creates a movie with valid data" do + count = Movie.count + + expect { + post movies_path, params: movie_params + }.must_differ "Movie.count", 1 + + expect(Movie.count).must_equal count + 1 + + must_respond_with :created + end + + it "will respond with bad_request for invalid data" do + movie_params[:title] = nil + + expect { + post movies_path, params: movie_params + }.wont_change "Movie.count" + + must_respond_with :bad_request + + expect(response.header['Content-Type']).must_include 'json' + body = JSON.parse(response.body) + + expect(body["errors"].keys).must_include "title" + end + + + it "cannot add the same movie twice" do + count = Movie.count + + post movies_path, params: movie_params + + expect { + post movies_path, params: movie_params + }.wont_differ "Movie.count" + + expect(Movie.count).must_equal count + 1 + + must_respond_with :forbidden + expect(body).must_include "This movie is already in the database" + end + end end