From 3c196e3ac0d79edabead6b0b2f1aa9a7c91098fe Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 11:26:08 -0800 Subject: [PATCH 01/41] rails initial set up --- .gitignore | 16 ++ Gemfile | 56 ++++++ Gemfile.lock | 170 ++++++++++++++++++ Rakefile | 6 + app/channels/application_cable/channel.rb | 4 + app/channels/application_cable/connection.rb | 4 + app/controllers/application_controller.rb | 2 + app/controllers/concerns/.keep | 0 app/jobs/application_job.rb | 2 + app/mailers/application_mailer.rb | 4 + app/models/application_record.rb | 3 + app/models/concerns/.keep | 0 app/models/customer.rb | 5 + app/models/movie.rb | 3 + app/models/rental.rb | 4 + app/views/layouts/mailer.html.erb | 13 ++ app/views/layouts/mailer.text.erb | 1 + bin/bundle | 3 + bin/rails | 9 + bin/rake | 9 + bin/setup | 35 ++++ bin/spring | 17 ++ bin/update | 29 +++ config.ru | 5 + config/application.rb | 40 +++++ config/boot.rb | 3 + config/cable.yml | 10 ++ config/database.yml | 85 +++++++++ config/environment.rb | 5 + config/environments/development.rb | 47 +++++ config/environments/production.rb | 83 +++++++++ config/environments/test.rb | 42 +++++ .../application_controller_renderer.rb | 8 + config/initializers/backtrace_silencers.rb | 7 + config/initializers/cors.rb | 16 ++ .../initializers/filter_parameter_logging.rb | 4 + config/initializers/inflections.rb | 16 ++ config/initializers/mime_types.rb | 4 + config/initializers/wrap_parameters.rb | 14 ++ config/locales/en.yml | 33 ++++ config/puma.rb | 56 ++++++ config/routes.rb | 3 + config/secrets.yml | 32 ++++ config/spring.rb | 6 + db/migrate/20171106191328_create_movies.rb | 12 ++ db/migrate/20171106191623_create_customers.rb | 15 ++ db/migrate/20171106191734_create_rentals.rb | 9 + ...1106192016_adding_foreign_keysto_rental.rb | 7 + db/schema.rb | 49 +++++ lib/tasks/.keep | 0 log/.keep | 0 public/robots.txt | 1 + test/controllers/.keep | 0 test/fixtures/.keep | 0 test/fixtures/customers.yml | 19 ++ test/fixtures/files/.keep | 0 test/fixtures/movies.yml | 13 ++ test/fixtures/rentals.yml | 11 ++ test/integration/.keep | 0 test/mailers/.keep | 0 test/models/.keep | 0 test/models/customer_test.rb | 9 + test/models/movie_test.rb | 9 + test/models/rental_test.rb | 9 + test/test_helper.rb | 26 +++ tmp/.keep | 0 vendor/.keep | 0 67 files changed, 1103 insertions(+) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 Rakefile create mode 100644 app/channels/application_cable/channel.rb create mode 100644 app/channels/application_cable/connection.rb create mode 100644 app/controllers/application_controller.rb create mode 100644 app/controllers/concerns/.keep create mode 100644 app/jobs/application_job.rb create mode 100644 app/mailers/application_mailer.rb create mode 100644 app/models/application_record.rb create mode 100644 app/models/concerns/.keep create mode 100644 app/models/customer.rb create mode 100644 app/models/movie.rb create mode 100644 app/models/rental.rb create mode 100644 app/views/layouts/mailer.html.erb create mode 100644 app/views/layouts/mailer.text.erb create mode 100755 bin/bundle create mode 100755 bin/rails create mode 100755 bin/rake create mode 100755 bin/setup create mode 100755 bin/spring create mode 100755 bin/update create mode 100644 config.ru create mode 100644 config/application.rb create mode 100644 config/boot.rb create mode 100644 config/cable.yml create mode 100644 config/database.yml create mode 100644 config/environment.rb create mode 100644 config/environments/development.rb create mode 100644 config/environments/production.rb create mode 100644 config/environments/test.rb create mode 100644 config/initializers/application_controller_renderer.rb create mode 100644 config/initializers/backtrace_silencers.rb create mode 100644 config/initializers/cors.rb create mode 100644 config/initializers/filter_parameter_logging.rb create mode 100644 config/initializers/inflections.rb create mode 100644 config/initializers/mime_types.rb create mode 100644 config/initializers/wrap_parameters.rb create mode 100644 config/locales/en.yml create mode 100644 config/puma.rb create mode 100644 config/routes.rb create mode 100644 config/secrets.yml create mode 100644 config/spring.rb create mode 100644 db/migrate/20171106191328_create_movies.rb create mode 100644 db/migrate/20171106191623_create_customers.rb create mode 100644 db/migrate/20171106191734_create_rentals.rb create mode 100644 db/migrate/20171106192016_adding_foreign_keysto_rental.rb create mode 100644 db/schema.rb create mode 100644 lib/tasks/.keep create mode 100644 log/.keep create mode 100644 public/robots.txt create mode 100644 test/controllers/.keep create mode 100644 test/fixtures/.keep create mode 100644 test/fixtures/customers.yml create mode 100644 test/fixtures/files/.keep create mode 100644 test/fixtures/movies.yml create mode 100644 test/fixtures/rentals.yml create mode 100644 test/integration/.keep create mode 100644 test/mailers/.keep create mode 100644 test/models/.keep create mode 100644 test/models/customer_test.rb create mode 100644 test/models/movie_test.rb create mode 100644 test/models/rental_test.rb create mode 100644 test/test_helper.rb create mode 100644 tmp/.keep create mode 100644 vendor/.keep diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..68ac019ec --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +.byebug_history diff --git a/Gemfile b/Gemfile new file mode 100644 index 000000000..525865c60 --- /dev/null +++ b/Gemfile @@ -0,0 +1,56 @@ +source 'https://rubygems.org' + +git_source(:github) do |repo_name| + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") + "https://github.com/#{repo_name}.git" +end + + +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '~> 5.1.4' +# Use postgresql as the database for Active Record +gem 'pg', '~> 0.18' +# Use Puma as the app server +gem 'puma', '~> 3.7' +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +# gem 'jbuilder', '~> 2.5' +# Use Redis adapter to run Action Cable in production +# gem 'redis', '~> 3.0' +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# Use Capistrano for deployment +# gem 'capistrano-rails', group: :development + +# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible +# gem 'rack-cors' + +group :development, :test do + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] +end + +group :development do + gem 'listen', '>= 3.0.5', '< 3.2' + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' + gem 'spring-watcher-listen', '~> 2.0.0' +end + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] + +gem 'jquery-turbolinks' +group :development, :test do + gem 'pry-rails' +end + +group :development do + gem 'better_errors' + gem 'binding_of_caller' +end + +group :test do + gem 'minitest-rails' + gem 'minitest-reporters' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000..4a738acb5 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,170 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.1.4) + actionpack (= 5.1.4) + nio4r (~> 2.0) + websocket-driver (~> 0.6.1) + actionmailer (5.1.4) + actionpack (= 5.1.4) + actionview (= 5.1.4) + activejob (= 5.1.4) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.1.4) + actionview (= 5.1.4) + activesupport (= 5.1.4) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.1.4) + activesupport (= 5.1.4) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.1.4) + activesupport (= 5.1.4) + globalid (>= 0.3.6) + activemodel (5.1.4) + activesupport (= 5.1.4) + activerecord (5.1.4) + activemodel (= 5.1.4) + activesupport (= 5.1.4) + arel (~> 8.0) + activesupport (5.1.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (~> 0.7) + minitest (~> 5.1) + tzinfo (~> 1.1) + ansi (1.5.0) + arel (8.0.0) + better_errors (2.4.0) + coderay (>= 1.0.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) + binding_of_caller (0.7.3) + debug_inspector (>= 0.0.1) + builder (3.2.3) + byebug (9.1.0) + coderay (1.1.2) + concurrent-ruby (1.0.5) + crass (1.0.2) + debug_inspector (0.0.3) + erubi (1.7.0) + ffi (1.9.18) + globalid (0.4.1) + activesupport (>= 4.2.0) + i18n (0.9.1) + concurrent-ruby (~> 1.0) + jquery-turbolinks (2.1.0) + railties (>= 3.1.0) + turbolinks + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + loofah (2.1.1) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.0) + mini_mime (>= 0.1.1) + method_source (0.9.0) + mini_mime (0.1.4) + mini_portile2 (2.3.0) + minitest (5.10.3) + minitest-rails (3.0.0) + minitest (~> 5.8) + railties (~> 5.0) + minitest-reporters (1.1.18) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + nio4r (2.1.0) + nokogiri (1.8.1) + mini_portile2 (~> 2.3.0) + pg (0.21.0) + pry (0.11.2) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry-rails (0.3.6) + pry (>= 0.10.4) + puma (3.10.0) + rack (2.0.3) + rack-test (0.7.0) + rack (>= 1.0, < 3) + rails (5.1.4) + actioncable (= 5.1.4) + actionmailer (= 5.1.4) + actionpack (= 5.1.4) + actionview (= 5.1.4) + activejob (= 5.1.4) + activemodel (= 5.1.4) + activerecord (= 5.1.4) + activesupport (= 5.1.4) + bundler (>= 1.3.0) + railties (= 5.1.4) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.3) + loofah (~> 2.0) + railties (5.1.4) + actionpack (= 5.1.4) + activesupport (= 5.1.4) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (12.2.1) + rb-fsevent (0.10.2) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + ruby-progressbar (1.9.0) + ruby_dep (1.5.0) + spring (2.0.2) + activesupport (>= 4.2) + spring-watcher-listen (2.0.1) + listen (>= 2.7, < 4.0) + spring (>= 1.2, < 3.0) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + thor (0.20.0) + thread_safe (0.3.6) + turbolinks (5.0.1) + turbolinks-source (~> 5) + turbolinks-source (5.0.3) + tzinfo (1.2.4) + thread_safe (~> 0.1) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) + +PLATFORMS + ruby + +DEPENDENCIES + better_errors + binding_of_caller + byebug + jquery-turbolinks + listen (>= 3.0.5, < 3.2) + minitest-rails + minitest-reporters + pg (~> 0.18) + pry-rails + puma (~> 3.7) + rails (~> 5.1.4) + spring + spring-watcher-listen (~> 2.0.0) + tzinfo-data + +BUNDLED WITH + 1.16.0.pre.3 diff --git a/Rakefile b/Rakefile new file mode 100644 index 000000000..e85f91391 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative 'config/application' + +Rails.application.load_tasks diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100644 index 000000000..d67269728 --- /dev/null +++ b/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb new file mode 100644 index 000000000..0ff5442f4 --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 000000000..4ac8823b0 --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::API +end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 000000000..a009ace51 --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb new file mode 100644 index 000000000..286b2239d --- /dev/null +++ b/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: 'from@example.com' + layout 'mailer' +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb new file mode 100644 index 000000000..10a4cba84 --- /dev/null +++ b/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/app/models/customer.rb b/app/models/customer.rb new file mode 100644 index 000000000..5bcca7741 --- /dev/null +++ b/app/models/customer.rb @@ -0,0 +1,5 @@ +class Customer < ApplicationRecord + has_many :rentals + + validates :name, presence: true +end diff --git a/app/models/movie.rb b/app/models/movie.rb new file mode 100644 index 000000000..d34b59f22 --- /dev/null +++ b/app/models/movie.rb @@ -0,0 +1,3 @@ +class Movie < ApplicationRecord + validates :title, presence: true +end diff --git a/app/models/rental.rb b/app/models/rental.rb new file mode 100644 index 000000000..2db0f9522 --- /dev/null +++ b/app/models/rental.rb @@ -0,0 +1,4 @@ +class Rental < ApplicationRecord + belongs_to :movie + belongs_to :movie +end diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb new file mode 100644 index 000000000..cbd34d2e9 --- /dev/null +++ b/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb new file mode 100644 index 000000000..37f0bddbd --- /dev/null +++ b/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/bin/bundle b/bin/bundle new file mode 100755 index 000000000..66e9889e8 --- /dev/null +++ b/bin/bundle @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +load Gem.bin_path('bundler', 'bundle') diff --git a/bin/rails b/bin/rails new file mode 100755 index 000000000..5badb2fde --- /dev/null +++ b/bin/rails @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +APP_PATH = File.expand_path('../config/application', __dir__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/bin/rake b/bin/rake new file mode 100755 index 000000000..d87d5f578 --- /dev/null +++ b/bin/rake @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/bin/setup b/bin/setup new file mode 100755 index 000000000..104e40c1c --- /dev/null +++ b/bin/setup @@ -0,0 +1,35 @@ +#!/usr/bin/env ruby +require 'pathname' +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a starting point to setup your application. + # Add necessary setup steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + + # puts "\n== Copying sample files ==" + # unless File.exist?('config/database.yml') + # cp 'config/database.yml.sample', 'config/database.yml' + # end + + puts "\n== Preparing database ==" + system! 'bin/rails db:setup' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/bin/spring b/bin/spring new file mode 100755 index 000000000..fb2ec2ebb --- /dev/null +++ b/bin/spring @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +# This file loads spring without using Bundler, in order to be fast. +# It gets overwritten when you run the `spring binstub` command. + +unless defined?(Spring) + require 'rubygems' + require 'bundler' + + lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) + spring = lockfile.specs.detect { |spec| spec.name == "spring" } + if spring + Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path + gem 'spring', spring.version + require 'spring/binstub' + end +end diff --git a/bin/update b/bin/update new file mode 100755 index 000000000..a8e4462f2 --- /dev/null +++ b/bin/update @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +require 'pathname' +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a way to update your development environment automatically. + # Add necessary update steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + puts "\n== Updating database ==" + system! 'bin/rails db:migrate' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/config.ru b/config.ru new file mode 100644 index 000000000..f7ba0b527 --- /dev/null +++ b/config.ru @@ -0,0 +1,5 @@ +# This file is used by Rack-based servers to start the application. + +require_relative 'config/environment' + +run Rails.application diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 000000000..7d3c866ff --- /dev/null +++ b/config/application.rb @@ -0,0 +1,40 @@ +require_relative 'boot' + +require "rails" +# Pick the frameworks you want: +require "active_model/railtie" +require "active_job/railtie" +require "active_record/railtie" +require "action_controller/railtie" +require "action_mailer/railtie" +require "action_view/railtie" +require "action_cable/engine" +# require "sprockets/railtie" +require "rails/test_unit/railtie" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module VideoStoreAPI + class Application < Rails::Application + config.generators do |g| + # Force new test files to be generated in the minitest-spec style + g.test_framework :minitest, spec: true + + # Always use .js files, never .coffee + g.javascript_engine :js + end + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 5.1 + + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Only loads a smaller set of middleware suitable for API only apps. + # Middleware like session, flash, cookies can be added back manually. + # Skip views, helpers and assets when generating a new resource. + config.api_only = true + end +end diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 000000000..30f5120df --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,3 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/config/cable.yml b/config/cable.yml new file mode 100644 index 000000000..ad59bcd88 --- /dev/null +++ b/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: async + +production: + adapter: redis + url: redis://localhost:6379/1 + channel_prefix: VideoStoreAPI_production diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 000000000..720570700 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,85 @@ +# PostgreSQL. Versions 9.1 and up are supported. +# +# Install the pg driver: +# gem install pg +# On OS X with Homebrew: +# gem install pg -- --with-pg-config=/usr/local/bin/pg_config +# On OS X with MacPorts: +# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config +# On Windows: +# gem install pg +# Choose the win32 build. +# Install PostgreSQL and put its /bin directory on your path. +# +# Configure Using Gemfile +# gem 'pg' +# +default: &default + adapter: postgresql + encoding: unicode + # For details on connection pooling, see Rails configuration guide + # http://guides.rubyonrails.org/configuring.html#database-pooling + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + +development: + <<: *default + database: VideoStoreAPI_development + + # The specified database role being used to connect to postgres. + # To create additional roles in postgres see `$ createuser --help`. + # When left blank, postgres will use the default role. This is + # the same name as the operating system user that initialized the database. + #username: VideoStoreAPI + + # The password associated with the postgres role (username). + #password: + + # Connect on a TCP socket. Omitted by default since the client uses a + # domain socket that doesn't need configuration. Windows does not have + # domain sockets, so uncomment these lines. + #host: localhost + + # The TCP port the server listens on. Defaults to 5432. + # If your server runs on a different port number, change accordingly. + #port: 5432 + + # Schema search path. The server defaults to $user,public + #schema_search_path: myapp,sharedapp,public + + # Minimum log levels, in increasing order: + # debug5, debug4, debug3, debug2, debug1, + # log, notice, warning, error, fatal, and panic + # Defaults to warning. + #min_messages: notice + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: VideoStoreAPI_test + +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%= ENV['DATABASE_URL'] %> +# +production: + <<: *default + database: VideoStoreAPI_production + username: VideoStoreAPI + password: <%= ENV['VIDEOSTOREAPI_DATABASE_PASSWORD'] %> diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 000000000..426333bb4 --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative 'application' + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 000000000..abc82221c --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,47 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_caching = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + config.file_watcher = ActiveSupport::EventedFileUpdateChecker +end diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 000000000..3bd8115ea --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,83 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Attempt to read encrypted secrets from `config/secrets.yml.enc`. + # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or + # `config/secrets.yml.key`. + config.read_encrypted_secrets = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment) + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "VideoStoreAPI_#{Rails.env}" + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 000000000..8e5cbde53 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,42 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb new file mode 100644 index 000000000..89d2efab2 --- /dev/null +++ b/config/initializers/application_controller_renderer.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb new file mode 100644 index 000000000..59385cdf3 --- /dev/null +++ b/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb new file mode 100644 index 000000000..3b1c1b5ed --- /dev/null +++ b/config/initializers/cors.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Avoid CORS issues when API is called from the frontend app. +# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. + +# Read more: https://github.com/cyu/rack-cors + +# Rails.application.config.middleware.insert_before 0, Rack::Cors do +# allow do +# origins 'example.com' +# +# resource '*', +# headers: :any, +# methods: [:get, :post, :put, :patch, :delete, :options, :head] +# end +# end diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb new file mode 100644 index 000000000..4a994e1e7 --- /dev/null +++ b/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 000000000..ac033bf9d --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb new file mode 100644 index 000000000..dc1899682 --- /dev/null +++ b/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb new file mode 100644 index 000000000..bbfc3961b --- /dev/null +++ b/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 000000000..decc5a857 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# 'true': 'foo' +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/config/puma.rb b/config/puma.rb new file mode 100644 index 000000000..1e19380dc --- /dev/null +++ b/config/puma.rb @@ -0,0 +1,56 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +# +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked webserver processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. If you use this option +# you need to make sure to reconnect any threads in the `on_worker_boot` +# block. +# +# preload_app! + +# If you are preloading your application and using Active Record, it's +# recommended that you close any connections to the database before workers +# are forked to prevent connection leakage. +# +# before_fork do +# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) +# end + +# The code in the `on_worker_boot` will be called if you are using +# clustered mode by specifying a number of `workers`. After each worker +# process is booted, this block will be run. If you are using the `preload_app!` +# option, you will want to use this block to reconnect to any threads +# or connections that may have been created at application boot, as Ruby +# cannot share connections between processes. +# +# on_worker_boot do +# ActiveRecord::Base.establish_connection if defined?(ActiveRecord) +# end +# + +# Allow puma to be restarted by `rails restart` command. +plugin :tmp_restart diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 000000000..787824f88 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,3 @@ +Rails.application.routes.draw do + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html +end diff --git a/config/secrets.yml b/config/secrets.yml new file mode 100644 index 000000000..e5c88857d --- /dev/null +++ b/config/secrets.yml @@ -0,0 +1,32 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rails secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +# Shared secrets are available across all environments. + +# shared: +# api_key: a1B2c3D4e5F6 + +# Environmental secrets are only available for that specific environment. + +development: + secret_key_base: a11fd5e18760c3a5b8f29c9af806fef7f54a28c92914f202ad121878cbd5b54aada32d2c6478ba4e615f19528f5b387fa72fc1a5327792612f3dc94aaba839e1 + +test: + secret_key_base: 2d084faf0e881f30eab6d734b6e2edf5dfc6ac99ebf37bee0b3b9fc22a27dca610e68507ce3452f606faba553de3a1a2f5718b499955c333a74538e5d6633da6 + +# Do not keep production secrets in the unencrypted secrets file. +# Instead, either read values from the environment. +# Or, use `bin/rails secrets:setup` to configure encrypted secrets +# and move the `production:` environment over there. + +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/config/spring.rb b/config/spring.rb new file mode 100644 index 000000000..c9119b40c --- /dev/null +++ b/config/spring.rb @@ -0,0 +1,6 @@ +%w( + .ruby-version + .rbenv-vars + tmp/restart.txt + tmp/caching-dev.txt +).each { |path| Spring.watch(path) } diff --git a/db/migrate/20171106191328_create_movies.rb b/db/migrate/20171106191328_create_movies.rb new file mode 100644 index 000000000..84782a1b7 --- /dev/null +++ b/db/migrate/20171106191328_create_movies.rb @@ -0,0 +1,12 @@ +class CreateMovies < ActiveRecord::Migration[5.1] + def change + create_table :movies do |t| + t.string :title + t.string :overview + t.string :release_date + t.integer :inventory + + t.timestamps + end + end +end diff --git a/db/migrate/20171106191623_create_customers.rb b/db/migrate/20171106191623_create_customers.rb new file mode 100644 index 000000000..0a05c4bad --- /dev/null +++ b/db/migrate/20171106191623_create_customers.rb @@ -0,0 +1,15 @@ +class CreateCustomers < ActiveRecord::Migration[5.1] + def change + create_table :customers do |t| + t.string :name + t.string :registered_at + t.string :address + t.string :city + t.string :state + t.string :postal_code + t.string :phone + + t.timestamps + end + end +end diff --git a/db/migrate/20171106191734_create_rentals.rb b/db/migrate/20171106191734_create_rentals.rb new file mode 100644 index 000000000..733f733c1 --- /dev/null +++ b/db/migrate/20171106191734_create_rentals.rb @@ -0,0 +1,9 @@ +class CreateRentals < ActiveRecord::Migration[5.1] + def change + create_table :rentals do |t| + t.string :due_date + + t.timestamps + end + end +end diff --git a/db/migrate/20171106192016_adding_foreign_keysto_rental.rb b/db/migrate/20171106192016_adding_foreign_keysto_rental.rb new file mode 100644 index 000000000..e279ff95f --- /dev/null +++ b/db/migrate/20171106192016_adding_foreign_keysto_rental.rb @@ -0,0 +1,7 @@ +class AddingForeignKeystoRental < ActiveRecord::Migration[5.1] + def change + add_reference :rentals, :movie, foreign_keys: true + add_reference :rentals, :customer, foreign_keys: true + + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 000000000..df3b6a0f4 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,49 @@ +# This file is auto-generated from the current state of the database. Instead +# 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). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 20171106192016) 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 "registered_at" + t.string "address" + t.string "city" + t.string "state" + t.string "postal_code" + t.string "phone" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "movies", force: :cascade do |t| + t.string "title" + t.string "overview" + t.string "release_date" + t.integer "inventory" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "rentals", force: :cascade do |t| + t.string "due_date" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "movie_id" + t.bigint "customer_id" + t.index ["customer_id"], name: "index_rentals_on_customer_id" + t.index ["movie_id"], name: "index_rentals_on_movie_id" + end + +end diff --git a/lib/tasks/.keep b/lib/tasks/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/log/.keep b/log/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 000000000..37b576a4a --- /dev/null +++ b/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/test/controllers/.keep b/test/controllers/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/.keep b/test/fixtures/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/customers.yml b/test/fixtures/customers.yml new file mode 100644 index 000000000..bf442fa90 --- /dev/null +++ b/test/fixtures/customers.yml @@ -0,0 +1,19 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + registered_at: MyString + address: MyString + city: MyString + state: MyString + postal_code: MyString + phone: MyString + +two: + name: MyString + registered_at: MyString + address: MyString + city: MyString + state: MyString + postal_code: MyString + phone: MyString diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/movies.yml b/test/fixtures/movies.yml new file mode 100644 index 000000000..d774de5f1 --- /dev/null +++ b/test/fixtures/movies.yml @@ -0,0 +1,13 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + title: MyString + overview: MyString + release_date: MyString + inventory: 1 + +two: + title: MyString + overview: MyString + release_date: MyString + inventory: 1 diff --git a/test/fixtures/rentals.yml b/test/fixtures/rentals.yml new file mode 100644 index 000000000..dc3ee79b5 --- /dev/null +++ b/test/fixtures/rentals.yml @@ -0,0 +1,11 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +one: {} +# column: value +# +two: {} +# column: value diff --git a/test/integration/.keep b/test/integration/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/mailers/.keep b/test/mailers/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/models/.keep b/test/models/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/test/models/customer_test.rb b/test/models/customer_test.rb new file mode 100644 index 000000000..5ebc5c850 --- /dev/null +++ b/test/models/customer_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +describe Customer do + let(:customer) { Customer.new } + + it "must be valid" do + value(customer).must_be :valid? + end +end diff --git a/test/models/movie_test.rb b/test/models/movie_test.rb new file mode 100644 index 000000000..34d1d30a5 --- /dev/null +++ b/test/models/movie_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +describe Movie do + let(:movie) { Movie.new } + + it "must be valid" do + value(movie).must_be :valid? + end +end diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb new file mode 100644 index 000000000..6ea53d94f --- /dev/null +++ b/test/models/rental_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +describe Rental do + let(:rental) { Rental.new } + + it "must be valid" do + value(rental).must_be :valid? + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 000000000..10594a324 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,26 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path("../../config/environment", __FILE__) +require "rails/test_help" +require "minitest/rails" +require "minitest/reporters" # for Colorized output + +# For colorful output! +Minitest::Reporters.use!( + Minitest::Reporters::SpecReporter.new, + ENV, + Minitest.backtrace_filter +) + + +# To add Capybara feature tests add `gem "minitest-rails-capybara"` +# to the test group in the Gemfile and uncomment the following: +# require "minitest/rails/capybara" + +# Uncomment for awesome colorful output +# require "minitest/pride" + +class ActiveSupport::TestCase + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all + # Add more helper methods to be used by all tests here... +end diff --git a/tmp/.keep b/tmp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/vendor/.keep b/vendor/.keep new file mode 100644 index 000000000..e69de29bb From bc7e51726a125fd0d8a289cceef14b7d88de4a3a Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 11:30:24 -0800 Subject: [PATCH 02/41] Added rails gem rails-erd and generated erd pdf --- Gemfile | 1 + Gemfile.lock | 8 ++++++++ erd.pdf | Bin 0 -> 31400 bytes 3 files changed, 9 insertions(+) create mode 100644 erd.pdf diff --git a/Gemfile b/Gemfile index 525865c60..b542d41f4 100644 --- a/Gemfile +++ b/Gemfile @@ -48,6 +48,7 @@ end group :development do gem 'better_errors' gem 'binding_of_caller' + gem 'rails-erd', require: false end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 4a738acb5..90489948d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -48,6 +48,7 @@ GEM debug_inspector (>= 0.0.1) builder (3.2.3) byebug (9.1.0) + choice (0.2.0) coderay (1.1.2) concurrent-ruby (1.0.5) crass (1.0.2) @@ -110,6 +111,11 @@ GEM rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) + rails-erd (1.5.2) + activerecord (>= 3.2) + activesupport (>= 3.2) + choice (~> 0.2.0) + ruby-graphviz (~> 1.2) rails-html-sanitizer (1.0.3) loofah (~> 2.0) railties (5.1.4) @@ -122,6 +128,7 @@ GEM rb-fsevent (0.10.2) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) + ruby-graphviz (1.2.3) ruby-progressbar (1.9.0) ruby_dep (1.5.0) spring (2.0.2) @@ -162,6 +169,7 @@ DEPENDENCIES pry-rails puma (~> 3.7) rails (~> 5.1.4) + rails-erd spring spring-watcher-listen (~> 2.0.0) tzinfo-data diff --git a/erd.pdf b/erd.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cdc644797996cfa73d3178b0bb5e99c2d40b1b38 GIT binary patch literal 31400 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 literal 0 HcmV?d00001 From 9a72c9a607e57a2c84ab6e4a799730909d29d633 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 12:58:09 -0800 Subject: [PATCH 03/41] Seeded db and added account_credit to customer model migration --- app/models/rental.rb | 2 +- db/migrate/20171106205448_add_account_credit_to_customer.rb | 5 +++++ db/schema.rb | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20171106205448_add_account_credit_to_customer.rb diff --git a/app/models/rental.rb b/app/models/rental.rb index 2db0f9522..34d3f4df8 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -1,4 +1,4 @@ class Rental < ApplicationRecord belongs_to :movie - belongs_to :movie + belongs_to :customer end diff --git a/db/migrate/20171106205448_add_account_credit_to_customer.rb b/db/migrate/20171106205448_add_account_credit_to_customer.rb new file mode 100644 index 000000000..5a412dc15 --- /dev/null +++ b/db/migrate/20171106205448_add_account_credit_to_customer.rb @@ -0,0 +1,5 @@ +class AddAccountCreditToCustomer < ActiveRecord::Migration[5.1] + def change + add_column :customers, :account_credit, :decimal + end +end diff --git a/db/schema.rb b/db/schema.rb index df3b6a0f4..b162cec98 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171106192016) do +ActiveRecord::Schema.define(version: 20171106205448) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -25,6 +25,7 @@ t.string "phone" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.decimal "account_credit" end create_table "movies", force: :cascade do |t| From 49b51cb5927c41db4ddd29ec392338da8250f67b Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 13:09:42 -0800 Subject: [PATCH 04/41] Movie model test validation --- test/models/customer_test.rb | 4 ---- test/models/movie_test.rb | 24 +++++++++++++++++++++--- test/models/rental_test.rb | 4 ---- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/test/models/customer_test.rb b/test/models/customer_test.rb index 5ebc5c850..eee222f67 100644 --- a/test/models/customer_test.rb +++ b/test/models/customer_test.rb @@ -1,9 +1,5 @@ require "test_helper" describe Customer do - let(:customer) { Customer.new } - it "must be valid" do - value(customer).must_be :valid? - end end diff --git a/test/models/movie_test.rb b/test/models/movie_test.rb index 34d1d30a5..e3206feaf 100644 --- a/test/models/movie_test.rb +++ b/test/models/movie_test.rb @@ -1,9 +1,27 @@ require "test_helper" describe Movie do - let(:movie) { Movie.new } - it "must be valid" do - value(movie).must_be :valid? + it "a movie must have a title" do + start_count = Movie.count + + movie1 = Movie.new + movie1.save + movie1.valid?.must_equal false + movie1.errors.messages.must_include :title + + Movie.count.must_equal start_count + end + + it "a movie must have a title" do + start_count = Movie.count + + movie = Movie.new(title: "Sleeping B") + movie.save + movie.valid?.must_equal true + + Movie.count.must_equal start_count + 1 end + + end diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb index 6ea53d94f..0bea59f1c 100644 --- a/test/models/rental_test.rb +++ b/test/models/rental_test.rb @@ -1,9 +1,5 @@ require "test_helper" describe Rental do - let(:rental) { Rental.new } - it "must be valid" do - value(rental).must_be :valid? - end end From 11da74774f3b5d3bf2c6941ad4c0161b1c5ff870 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 13:14:35 -0800 Subject: [PATCH 05/41] Customer model test validation --- test/models/customer_test.rb | 20 ++++++++++++++++++++ test/models/movie_test.rb | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/test/models/customer_test.rb b/test/models/customer_test.rb index eee222f67..15b3fa6cf 100644 --- a/test/models/customer_test.rb +++ b/test/models/customer_test.rb @@ -1,5 +1,25 @@ require "test_helper" describe Customer do + it "a customer requires a name" do + start_count = Customer.count + + customer = Customer.new + customer.save + customer.valid?.must_equal false + customer.errors.messages.must_include :name + + Customer.count.must_equal start_count + end + + it "a customer is created with a name" do + start_count = Customer.count + + customer1 = Customer.new(name: "Professor X") + customer1.save + customer1.valid?.must_equal true + + Customer.count.must_equal start_count + 1 + end end diff --git a/test/models/movie_test.rb b/test/models/movie_test.rb index e3206feaf..971103b78 100644 --- a/test/models/movie_test.rb +++ b/test/models/movie_test.rb @@ -2,7 +2,7 @@ describe Movie do - it "a movie must have a title" do + it "a movie requires a title" do start_count = Movie.count movie1 = Movie.new @@ -13,7 +13,7 @@ Movie.count.must_equal start_count end - it "a movie must have a title" do + it "a movie is created with a title" do start_count = Movie.count movie = Movie.new(title: "Sleeping B") From f2363a6bd47e44341fd7079c2a7fa883fbdc6b28 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 13:20:43 -0800 Subject: [PATCH 06/41] Movie model relations test --- test/fixtures/customers.yml | 4 ++-- test/models/customer_test.rb | 42 ++++++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/test/fixtures/customers.yml b/test/fixtures/customers.yml index bf442fa90..0551c69d6 100644 --- a/test/fixtures/customers.yml +++ b/test/fixtures/customers.yml @@ -1,7 +1,7 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -one: - name: MyString +bill: + name: Bill Nye registered_at: MyString address: MyString city: MyString diff --git a/test/models/customer_test.rb b/test/models/customer_test.rb index 15b3fa6cf..384a5ab06 100644 --- a/test/models/customer_test.rb +++ b/test/models/customer_test.rb @@ -1,25 +1,39 @@ require "test_helper" describe Customer do - it "a customer requires a name" do - start_count = Customer.count + let(:customer) { customers(:bill) } - customer = Customer.new - customer.save - customer.valid?.must_equal false - customer.errors.messages.must_include :name + describe "validations" do + it "a customer requires a name" do + start_count = Customer.count - Customer.count.must_equal start_count - end + invalid_customer = Customer.new + invalid_customer.save + invalid_customer.valid?.must_equal false + invalid_customer.errors.messages.must_include :name + + Customer.count.must_equal start_count + end - it "a customer is created with a name" do - start_count = Customer.count + it "a customer is created with a name" do + start_count = Customer.count - customer1 = Customer.new(name: "Professor X") - customer1.save - customer1.valid?.must_equal true + customer1 = Customer.new(name: "Professor X") + customer1.save + customer1.valid?.must_equal true + + Customer.count.must_equal start_count + 1 + end + end - Customer.count.must_equal start_count + 1 + describe "relations" do + it "has many rentals" do + customer.must_respond_to :rentals + customer.must_be_kind_of Customer + customer.rentals.each do |item| + item.must_be_kind_of Rental + end + end end end From 120d6067eebe27b4ea1937edc5c40c0c3f3f9f85 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 13:29:10 -0800 Subject: [PATCH 07/41] Rental model relations test --- test/fixtures/movies.yml | 8 ++++---- test/fixtures/rentals.yml | 8 +++----- test/models/rental_test.rb | 20 ++++++++++++++++++++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/test/fixtures/movies.yml b/test/fixtures/movies.yml index d774de5f1..a859ad79f 100644 --- a/test/fixtures/movies.yml +++ b/test/fixtures/movies.yml @@ -1,12 +1,12 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -one: - title: MyString +movie1: + title: Sleeping Beauty overview: MyString release_date: MyString - inventory: 1 + inventory: 2 -two: +movie2: title: MyString overview: MyString release_date: MyString diff --git a/test/fixtures/rentals.yml b/test/fixtures/rentals.yml index dc3ee79b5..be1ed0bd3 100644 --- a/test/fixtures/rentals.yml +++ b/test/fixtures/rentals.yml @@ -4,8 +4,6 @@ # model remove the "{}" from the fixture names and add the columns immediately # below each fixture, per the syntax in the comments below # -one: {} -# column: value -# -two: {} -# column: value +rental1: + customer: bill + movie: movie1 diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb index 0bea59f1c..f7c41d05e 100644 --- a/test/models/rental_test.rb +++ b/test/models/rental_test.rb @@ -1,5 +1,25 @@ require "test_helper" describe Rental do + let(:rental) { rentals(:rental1)} + let(:movie1) { movies(:movie1)} + let(:bill) { customers(:bill)} + + describe 'relationships' do + it "has a customer" do + rental.must_respond_to :customer + rental.must_be_kind_of Rental + rental.customer.must_be_kind_of Customer + rental.customer.must_equal bill + end + + it "has an movie" do + rental.must_respond_to :movie + rental.must_be_kind_of Rental + rental.movie.must_be_kind_of Movie + rental.movie.must_equal movie1 + end + + end end From 95d4c13cd3ed4d335dd8fb4e71c88ca4c78880bf Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 13:33:26 -0800 Subject: [PATCH 08/41] Added rails routes (for customers and movies) --- config/routes.rb | 2 ++ test/models/movie_test.rb | 31 ++++++++++++++++--------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 787824f88..a4a7bb5ad 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,5 @@ 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 :movies, only: [:index, :show, :create] end diff --git a/test/models/movie_test.rb b/test/models/movie_test.rb index 971103b78..ec7018758 100644 --- a/test/models/movie_test.rb +++ b/test/models/movie_test.rb @@ -2,26 +2,27 @@ describe Movie do - it "a movie requires a title" do - start_count = Movie.count + describe "validations" do + it "a movie requires a title" do + start_count = Movie.count - movie1 = Movie.new - movie1.save - movie1.valid?.must_equal false - movie1.errors.messages.must_include :title + movie1 = Movie.new + movie1.save + movie1.valid?.must_equal false + movie1.errors.messages.must_include :title - Movie.count.must_equal start_count - end + Movie.count.must_equal start_count + end - it "a movie is created with a title" do - start_count = Movie.count + it "a movie is created with a title" do + start_count = Movie.count - movie = Movie.new(title: "Sleeping B") - movie.save - movie.valid?.must_equal true + movie = Movie.new(title: "Sleeping B") + movie.save + movie.valid?.must_equal true - Movie.count.must_equal start_count + 1 + Movie.count.must_equal start_count + 1 + end end - end From 4699a3c037851c3a96d2090e7b296bec8c11083c Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 13:37:39 -0800 Subject: [PATCH 09/41] Added available_inventory column to movies model & checked_out_count to customers model --- db/migrate/20171106213505_added_extra_columns.rb | 6 ++++++ db/schema.rb | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20171106213505_added_extra_columns.rb diff --git a/db/migrate/20171106213505_added_extra_columns.rb b/db/migrate/20171106213505_added_extra_columns.rb new file mode 100644 index 000000000..1faed4bdf --- /dev/null +++ b/db/migrate/20171106213505_added_extra_columns.rb @@ -0,0 +1,6 @@ +class AddedExtraColumns < ActiveRecord::Migration[5.1] + def change + add_column :customers, :movies_checked_out_count, :integer + add_column :movies, :available_inventory, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index b162cec98..9f9595c0f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171106205448) do +ActiveRecord::Schema.define(version: 20171106213505) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -26,6 +26,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.decimal "account_credit" + t.integer "movies_checked_out_count" end create_table "movies", force: :cascade do |t| @@ -35,6 +36,7 @@ t.integer "inventory" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "available_inventory" end create_table "rentals", force: :cascade do |t| From 811d5bf81c03b55731b3757be0b7338f0d83884c Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 13:41:28 -0800 Subject: [PATCH 10/41] Added controllers for movies & customers (with customer index method updated) --- app/controllers/customers_controller.rb | 10 ++++++++++ app/controllers/movies_controller.rb | 4 ++++ config/routes.rb | 4 ++++ test/controllers/customers_controller_test.rb | 9 +++++++++ test/controllers/movies_controller_test.rb | 9 +++++++++ 5 files changed, 36 insertions(+) create mode 100644 app/controllers/customers_controller.rb create mode 100644 app/controllers/movies_controller.rb create mode 100644 test/controllers/customers_controller_test.rb create mode 100644 test/controllers/movies_controller_test.rb diff --git a/app/controllers/customers_controller.rb b/app/controllers/customers_controller.rb new file mode 100644 index 000000000..96b5dbb37 --- /dev/null +++ b/app/controllers/customers_controller.rb @@ -0,0 +1,10 @@ +class CustomersController < ApplicationController + def index + customers = Customer.all + + render( + json: customers.as_json(only: [:id, :name, :registered_at, :postal_code, :phone, :movies_checked_out_count]), + status: :ok + ) + end +end diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb new file mode 100644 index 000000000..b5ecfec7c --- /dev/null +++ b/app/controllers/movies_controller.rb @@ -0,0 +1,4 @@ +class MoviesController < ApplicationController + def index + end +end diff --git a/config/routes.rb b/config/routes.rb index a4a7bb5ad..93e0029bb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,8 @@ Rails.application.routes.draw do + get 'customers/index' + + get 'movies/index' + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html resources :customers, only: [:index] resources :movies, only: [:index, :show, :create] diff --git a/test/controllers/customers_controller_test.rb b/test/controllers/customers_controller_test.rb new file mode 100644 index 000000000..c5ec6f20b --- /dev/null +++ b/test/controllers/customers_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +describe CustomersController do + it "should get index" do + get customers_index_url + value(response).must_be :success? + end + +end diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb new file mode 100644 index 000000000..998bbef3f --- /dev/null +++ b/test/controllers/movies_controller_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +describe MoviesController do + it "should get index" do + get movies_index_url + value(response).must_be :success? + end + +end From 599899733a427b24e15e5843e465cf207f9f5339 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 13:50:42 -0800 Subject: [PATCH 11/41] Customer Controller index tests added --- test/controllers/customers_controller_test.rb | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/test/controllers/customers_controller_test.rb b/test/controllers/customers_controller_test.rb index c5ec6f20b..464e6dff0 100644 --- a/test/controllers/customers_controller_test.rb +++ b/test/controllers/customers_controller_test.rb @@ -1,9 +1,50 @@ require "test_helper" describe CustomersController do - it "should get index" do - get customers_index_url - value(response).must_be :success? - end + describe "index" do + it "is a real working route" do + get customers_path + must_respond_with :success + end + + it "returns json" do + get customers_path + response.header['Content-Type'].must_include 'json' + end + + it "returns an Array" do + get customers_path + + body = JSON.parse(response.body) + body.must_be_kind_of Array + end + + it "returns all of the customers" do + get customers_path + + body = JSON.parse(response.body) + body.length.must_equal Customer.count + end + + it "returns customers with exactly the required fields" do + keys = %w(id movies_checked_out_count name phone postal_code registered_at) + + get customers_path + + body = JSON.parse(response.body) + body.each do |customer| + customer.keys.sort.must_equal keys + end + end + + it "returns an empty array if there are no customers" do + Customer.destroy_all + get customers_path + must_respond_with :success + body = JSON.parse(response.body) + body.must_be_kind_of Array + body.must_be :empty? + end + end end From 779d64da9f47da7abf8d02864f4610d59e698dca Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 15:16:06 -0800 Subject: [PATCH 12/41] MoviesController #index testing and method update --- app/controllers/movies_controller.rb | 17 ++++++++ test/controllers/movies_controller_test.rb | 49 ++++++++++++++++++++-- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index b5ecfec7c..6e3aa8e8b 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -1,4 +1,21 @@ class MoviesController < ApplicationController def index + movies = Movie.all + + render( + json: movies.as_json(only: [:id, :title, :release_date]), + status: :ok + ) end + + def show + end + + def create + end + + private + def movie_params + params.require(:movie).permit(:id, :title, :release_date, :overview, :inventory, :available_inventory) + end end diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb index 998bbef3f..54e79c167 100644 --- a/test/controllers/movies_controller_test.rb +++ b/test/controllers/movies_controller_test.rb @@ -1,9 +1,52 @@ require "test_helper" describe MoviesController do - it "should get index" do - get movies_index_url - value(response).must_be :success? + + describe "index" do + it "is a real working route" do + get movies_path + must_respond_with :success + end + + it "returns json" do + get movies_path + response.header['Content-Type'].must_include 'json' + end + + it "returns an Array" do + get movies_path + + body = JSON.parse(response.body) + body.must_be_kind_of Array + end + + it "returns all of the movies" do + get movies_path + + body = JSON.parse(response.body) + body.length.must_equal Movie.count + end + + it "returns movies with exactly the required fields" do + keys = %w(id release_date title) + + get movies_path + + body = JSON.parse(response.body) + body.each do |movie| + movie.keys.sort.must_equal keys + end + end + + it "returns an empty array if there are no movie" do + Movie.destroy_all + get movies_path + + must_respond_with :success + body = JSON.parse(response.body) + body.must_be_kind_of Array + body.must_be :empty? + end end end From e7ea0d7583649b978a0cf8f37a3a7af65558e62b Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 15:20:16 -0800 Subject: [PATCH 13/41] MovieController #show testing and method updated --- app/controllers/movies_controller.rb | 12 ++++++++++++ test/controllers/movies_controller_test.rb | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index 6e3aa8e8b..0be0115dc 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -9,6 +9,18 @@ def index end def show + movie = Movie.find_by(id: params[:id]) + + if movie + render( + json: movie.as_json(only: [:age, :id, :human, :name]),status: :ok + ) + else + render( + json: { ok: false }, + status: :not_found + ) + end end def create diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb index 54e79c167..5d1891882 100644 --- a/test/controllers/movies_controller_test.rb +++ b/test/controllers/movies_controller_test.rb @@ -49,4 +49,19 @@ end end + describe "show" do + let(:movie) { movies(:movie1) } + + it "can get a movie" do + get movie_path(movie.id) + must_respond_with :success + end + + it "returns an error for an invalid id" do + movie.destroy() + get movie_path(movie.id) + must_respond_with :not_found + end + end + end From 72ac29220a373add53bc50a5dffdc66a9491465a Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 15:53:23 -0800 Subject: [PATCH 14/41] Added MovieController #show and #create tests --- app/controllers/movies_controller.rb | 16 +++++++- test/controllers/movies_controller_test.rb | 47 ++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index 0be0115dc..cfaf56b80 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -13,7 +13,7 @@ def show if movie render( - json: movie.as_json(only: [:age, :id, :human, :name]),status: :ok + json: movie.as_json(only: [:title, :release_date, :overview, :inventory, :available_inventory]),status: :ok ) else render( @@ -24,6 +24,20 @@ def show end def create + movie = Movie.create(movie_params) + + if movie.valid? + render( + json: movie.as_json(only: [:id, :title, :release_date, :overview, :inventory, :available_inventory]), + status: :created + ) + else + render( + json: { errors: movie.errors.messages }, + status: :bad_request + ) + end + end private diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb index 5d1891882..d63a3cb16 100644 --- a/test/controllers/movies_controller_test.rb +++ b/test/controllers/movies_controller_test.rb @@ -62,6 +62,53 @@ get movie_path(movie.id) must_respond_with :not_found end + + it "returns a movie with exactly the required fields" do + keys = %w(available_inventory inventory overview release_date title) + + get movie_path(movie.id) + + body = JSON.parse(response.body) + body.keys.sort.must_equal keys + + end end + describe "create" do + let(:movie_data) { + { + title: "Jack & the BeanStalk", + } + } + + let(:invalid_movie_data) { + { + title: nil + } + } + + it "Creates a new movie" do + assert_difference "Movie.count", 1 do + post movies_path, params: { movie: movie_data } + assert_response :success + end + + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "id" + + # Check that the ID matches + Movie.find(body["id"]).title.must_equal movie_data[:title] + end + + it "Returns an error for an invalid movie" do + post movies_path, params: { movie: invalid_movie_data } + must_respond_with :bad_request + + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "errors" + body["errors"].must_include "title" + end + end end From 7f5572b26803b46f77bcc63a61d5300ecf2c0f8e Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 16:33:16 -0800 Subject: [PATCH 15/41] Updated private movie_params to not include id --- app/controllers/movies_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index cfaf56b80..51842f586 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -29,7 +29,7 @@ def create if movie.valid? render( json: movie.as_json(only: [:id, :title, :release_date, :overview, :inventory, :available_inventory]), - status: :created + status: :ok ) else render( @@ -42,6 +42,6 @@ def create private def movie_params - params.require(:movie).permit(:id, :title, :release_date, :overview, :inventory, :available_inventory) + params.permit(:title, :release_date, :overview, :inventory, :available_inventory) end end From 16bc7ee924988dfd3ac56a4c47c2b7c115d93380 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 16:40:42 -0800 Subject: [PATCH 16/41] Updated params stxr(removed movie: from params) for MovieController #create tests after updating movie_params private method --- test/controllers/movies_controller_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb index d63a3cb16..295a86348 100644 --- a/test/controllers/movies_controller_test.rb +++ b/test/controllers/movies_controller_test.rb @@ -89,7 +89,7 @@ it "Creates a new movie" do assert_difference "Movie.count", 1 do - post movies_path, params: { movie: movie_data } + post movies_path, params: movie_data assert_response :success end @@ -102,7 +102,7 @@ end it "Returns an error for an invalid movie" do - post movies_path, params: { movie: invalid_movie_data } + post movies_path, params: invalid_movie_data must_respond_with :bad_request body = JSON.parse(response.body) From f28d29332fb00937b09a501726a51c8554572c2e Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Mon, 6 Nov 2017 18:09:01 -0800 Subject: [PATCH 17/41] Added defualt value to customer model for movies_checked_out_count --- ...20171107004439_add_default_value_to_customer_model.rb | 9 +++++++++ db/schema.rb | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20171107004439_add_default_value_to_customer_model.rb diff --git a/db/migrate/20171107004439_add_default_value_to_customer_model.rb b/db/migrate/20171107004439_add_default_value_to_customer_model.rb new file mode 100644 index 000000000..43480e73c --- /dev/null +++ b/db/migrate/20171107004439_add_default_value_to_customer_model.rb @@ -0,0 +1,9 @@ +class AddDefaultValueToCustomerModel < ActiveRecord::Migration[5.1] + def up + change_column_default :customers, :movies_checked_out_count, 0 + end + + def down + change_column_default :customers, :movies_checked_out_count, nil + end +end diff --git a/db/schema.rb b/db/schema.rb index 9f9595c0f..8ddd4afb5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171106213505) do +ActiveRecord::Schema.define(version: 20171107004439) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -26,7 +26,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.decimal "account_credit" - t.integer "movies_checked_out_count" + t.integer "movies_checked_out_count", default: 0 end create_table "movies", force: :cascade do |t| From d6f05787ab757f908db73f13a266bfbd963aa8ed Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 09:39:30 -0800 Subject: [PATCH 18/41] Added gem for serialization and removed .json code (and added attribute readers in MovieSerializer) --- Gemfile | 1 + Gemfile.lock | 9 +++++++++ app/controllers/movies_controller.rb | 8 +++----- app/serializers/movie_serializer.rb | 5 +++++ 4 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 app/serializers/movie_serializer.rb diff --git a/Gemfile b/Gemfile index b542d41f4..1437875bd 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,7 @@ git_source(:github) do |repo_name| "https://github.com/#{repo_name}.git" end +gem 'active_model_serializers' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.1.4' diff --git a/Gemfile.lock b/Gemfile.lock index 90489948d..4fdb622c2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -24,6 +24,11 @@ GEM erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) + active_model_serializers (0.10.6) + actionpack (>= 4.1, < 6) + activemodel (>= 4.1, < 6) + case_transform (>= 0.2) + jsonapi-renderer (>= 0.1.1.beta1, < 0.2) activejob (5.1.4) activesupport (= 5.1.4) globalid (>= 0.3.6) @@ -48,6 +53,8 @@ GEM debug_inspector (>= 0.0.1) builder (3.2.3) byebug (9.1.0) + case_transform (0.2) + activesupport choice (0.2.0) coderay (1.1.2) concurrent-ruby (1.0.5) @@ -62,6 +69,7 @@ GEM jquery-turbolinks (2.1.0) railties (>= 3.1.0) turbolinks + jsonapi-renderer (0.1.3) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -158,6 +166,7 @@ PLATFORMS ruby DEPENDENCIES + active_model_serializers better_errors binding_of_caller byebug diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index 51842f586..b2812e34b 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -3,8 +3,7 @@ def index movies = Movie.all render( - json: movies.as_json(only: [:id, :title, :release_date]), - status: :ok + json: movies, status: :ok ) end @@ -13,7 +12,7 @@ def show if movie render( - json: movie.as_json(only: [:title, :release_date, :overview, :inventory, :available_inventory]),status: :ok + json: movie, status: :ok ) else render( @@ -28,8 +27,7 @@ def create if movie.valid? render( - json: movie.as_json(only: [:id, :title, :release_date, :overview, :inventory, :available_inventory]), - status: :ok + json: movie, status: :ok ) else render( diff --git a/app/serializers/movie_serializer.rb b/app/serializers/movie_serializer.rb new file mode 100644 index 000000000..f53c228d7 --- /dev/null +++ b/app/serializers/movie_serializer.rb @@ -0,0 +1,5 @@ +class MovieSerializer < ActiveModel::Serializer + attributes :id, :title, :release_date, :overview, :inventory, :available_inventory + + +end From ebc66be67ce272fd89bd7657785ff9fe4aa7b439 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 09:41:17 -0800 Subject: [PATCH 19/41] Removed .json code (and added attribute readers in CustomerSerializer) --- app/controllers/customers_controller.rb | 2 +- app/serializers/customer_serializer.rb | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 app/serializers/customer_serializer.rb diff --git a/app/controllers/customers_controller.rb b/app/controllers/customers_controller.rb index 96b5dbb37..3f8899932 100644 --- a/app/controllers/customers_controller.rb +++ b/app/controllers/customers_controller.rb @@ -3,7 +3,7 @@ def index customers = Customer.all render( - json: customers.as_json(only: [:id, :name, :registered_at, :postal_code, :phone, :movies_checked_out_count]), + json: customers, status: :ok ) end diff --git a/app/serializers/customer_serializer.rb b/app/serializers/customer_serializer.rb new file mode 100644 index 000000000..902e6fea9 --- /dev/null +++ b/app/serializers/customer_serializer.rb @@ -0,0 +1,3 @@ +class CustomerSerializer < ActiveModel::Serializer + attributes :id, :name, :registered_at, :postal_code, :phone, :movies_checked_out_count +end From e105191289e3794fa93192bab1ff19a29d3b9e66 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 09:49:04 -0800 Subject: [PATCH 20/41] Updated Serializers for Movies --- app/controllers/movies_controller.rb | 6 +++--- app/serializers/all_movie_serializer.rb | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 app/serializers/all_movie_serializer.rb diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index b2812e34b..1cc33b788 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -3,7 +3,7 @@ def index movies = Movie.all render( - json: movies, status: :ok + json: movies, each_serializer: AllMovieSerializer, status: :ok ) end @@ -12,7 +12,7 @@ def show if movie render( - json: movie, status: :ok + json: movie, each_serializer: MovieSerializer, status: :ok ) else render( @@ -27,7 +27,7 @@ def create if movie.valid? render( - json: movie, status: :ok + json: movie, each_serializer: MovieSerializer, status: :ok ) else render( diff --git a/app/serializers/all_movie_serializer.rb b/app/serializers/all_movie_serializer.rb new file mode 100644 index 000000000..e9b27ff92 --- /dev/null +++ b/app/serializers/all_movie_serializer.rb @@ -0,0 +1,3 @@ +class AllMovieSerializer < ActiveModel::Serializer + attributes :id, :title, :release_date +end From 48900569bf5ab250e1a133ad6469bcd598767b26 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 10:04:26 -0800 Subject: [PATCH 21/41] Added nested routes for movies (checkin and checkout) and customers (overdue) --- app/controllers/rentals_controller.rb | 10 ++++++++++ config/routes.rb | 19 +++++++++++++++++-- test/controllers/rentals_controller_test.rb | 19 +++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 app/controllers/rentals_controller.rb create mode 100644 test/controllers/rentals_controller_test.rb diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb new file mode 100644 index 000000000..a1ea77d8c --- /dev/null +++ b/app/controllers/rentals_controller.rb @@ -0,0 +1,10 @@ +class RentalsController < ApplicationController + def checkout + end + + def checkin + end + + def overdue + end +end diff --git a/config/routes.rb b/config/routes.rb index 93e0029bb..2dd62cbc2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,9 +1,24 @@ Rails.application.routes.draw do + # get 'rentals/checkout' + # + # get 'rentals/checkin' + # + # get 'rentals/overdue' + get 'customers/index' get 'movies/index' # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html - resources :customers, only: [:index] - resources :movies, only: [:index, :show, :create] + resources :customers, only: [:index] do + get '/rentals/overdue', to: 'rentals#overdue', as: 'rental_overdue' + end + + resources :movies, only: [:index, :show, :create] do + post '/rentals/checkout', to: 'rentals#checkout', as: 'rental_checkout' + post '/rentals/checkin', to: 'rentals#checkin', as: 'rental_checkin' + end + + + end diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb new file mode 100644 index 000000000..ca52ed6ba --- /dev/null +++ b/test/controllers/rentals_controller_test.rb @@ -0,0 +1,19 @@ +require "test_helper" + +describe RentalsController do + it "should get checkout" do + get rentals_checkout_url + value(response).must_be :success? + end + + it "should get checkin" do + get rentals_checkin_url + value(response).must_be :success? + end + + it "should get overdue" do + get rentals_overdue_url + value(response).must_be :success? + end + +end From 7179566628152ef149ecf3bc8523628a884b4193 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 10:22:09 -0800 Subject: [PATCH 22/41] Added checkin and checkout fields(default values nil) for rentals and updated default values of due_date to nil --- config/routes.rb | 2 +- db/migrate/20171107181406_update_rental_model.rb | 10 ++++++++++ db/schema.rb | 4 +++- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20171107181406_update_rental_model.rb diff --git a/config/routes.rb b/config/routes.rb index 2dd62cbc2..aa2dafa64 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -16,7 +16,7 @@ resources :movies, only: [:index, :show, :create] do post '/rentals/checkout', to: 'rentals#checkout', as: 'rental_checkout' - post '/rentals/checkin', to: 'rentals#checkin', as: 'rental_checkin' + patch '/rentals/checkin', to: 'rentals#checkin', as: 'rental_checkin' end diff --git a/db/migrate/20171107181406_update_rental_model.rb b/db/migrate/20171107181406_update_rental_model.rb new file mode 100644 index 000000000..4d7dafb33 --- /dev/null +++ b/db/migrate/20171107181406_update_rental_model.rb @@ -0,0 +1,10 @@ +class UpdateRentalModel < ActiveRecord::Migration[5.1] + def change + add_column :rentals, :checkin, :string, default: nil + add_column :rentals, :checkout, :string, default: nil + end + + def up + change_column_default :rentals, :due_date, nil + end +end diff --git a/db/schema.rb b/db/schema.rb index 8ddd4afb5..e6e0c17ce 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171107004439) do +ActiveRecord::Schema.define(version: 20171107181406) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -45,6 +45,8 @@ t.datetime "updated_at", null: false t.bigint "movie_id" t.bigint "customer_id" + t.string "checkin" + t.string "checkout" t.index ["customer_id"], name: "index_rentals_on_customer_id" t.index ["movie_id"], name: "index_rentals_on_movie_id" end From 5ed07ae8adbc06ae3aafbb77b2c3035f651ac67a Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 10:33:13 -0800 Subject: [PATCH 23/41] made rental controller method names (chckout, checkin, overdue) --- app/controllers/rentals_controller.rb | 2 ++ test/controllers/rentals_controller_test.rb | 30 ++++++++++++++------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index a1ea77d8c..05f83c2fc 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -1,5 +1,7 @@ class RentalsController < ApplicationController + def checkout + end def checkin diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index ca52ed6ba..07805bf80 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -1,19 +1,29 @@ require "test_helper" describe RentalsController do - it "should get checkout" do - get rentals_checkout_url - value(response).must_be :success? - end + # it "should get checkout" do + # get rentals_checkout_url + # value(response).must_be :success? + # end + # + # it "should get checkin" do + # get rentals_checkin_url + # value(response).must_be :success? + # end + # + # it "should get overdue" do + # get rentals_overdue_url + # value(response).must_be :success? + # end + describe "checkout" do - it "should get checkin" do - get rentals_checkin_url - value(response).must_be :success? end - it "should get overdue" do - get rentals_overdue_url - value(response).must_be :success? + describe "checkin" do + end + describe "overdue" do + + end end From 4eed6bb52b907c0b52bb9110635b1fa83bdfed20 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 10:33:30 -0800 Subject: [PATCH 24/41] made rental controller method names (chckout, checkin, overdue) --- app/controllers/rentals_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 05f83c2fc..2f09f0d23 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -1,7 +1,6 @@ class RentalsController < ApplicationController def checkout - end def checkin From 2c004e9407ae38266862576c20b17f8bd0221bf7 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 10:34:54 -0800 Subject: [PATCH 25/41] removed spacing in rentals controller --- app/controllers/rentals_controller.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 app/controllers/rentals_controller.rb diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb new file mode 100644 index 000000000..2f09f0d23 --- /dev/null +++ b/app/controllers/rentals_controller.rb @@ -0,0 +1,11 @@ +class RentalsController < ApplicationController + + def checkout + end + + def checkin + end + + def overdue + end +end From 61abe73e3328bdba876b0a848c80fc9b2c971b8e Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 10:38:49 -0800 Subject: [PATCH 26/41] Removed nested rental routes to comply with postman tests --- config/routes.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index aa2dafa64..71bd88158 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,14 +10,14 @@ get 'movies/index' # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html - resources :customers, only: [:index] do - get '/rentals/overdue', to: 'rentals#overdue', as: 'rental_overdue' - end - - resources :movies, only: [:index, :show, :create] do - post '/rentals/checkout', to: 'rentals#checkout', as: 'rental_checkout' - patch '/rentals/checkin', to: 'rentals#checkin', as: 'rental_checkin' - end + resources :customers, only: [:index] + + resources :movies, only: [:index, :show, :create] + + post '/rentals/check-out', to: 'rentals#checkout', as: 'rental_checkout' + post '/rentals/check-in', to: 'rentals#checkin', as: 'rental_checkin' + + get '/rentals/overdue', to: 'rentals#overdue', as: 'rental_overdue' From 9340f63cefd102703bd9e6a155f87a31bebe0528 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 11:24:24 -0800 Subject: [PATCH 27/41] Updated rental db(changed field checkout to checkout date) and added rental_serializer --- app/controllers/rentals_controller.rb | 21 +++++++++++++++ app/models/rental.rb | 9 +++++++ app/serializers/movie_serializer.rb | 4 +-- app/serializers/rental_serializer.rb | 3 +++ ...92001_change_rental_checkout_field_name.rb | 6 +++++ db/schema.rb | 4 +-- test/controllers/movies_controller_test.rb | 2 +- test/controllers/rentals_controller_test.rb | 26 +++++++++++++++++++ 8 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 app/serializers/rental_serializer.rb create mode 100644 db/migrate/20171107192001_change_rental_checkout_field_name.rb diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 2f09f0d23..8128ab00d 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -1,6 +1,22 @@ class RentalsController < ApplicationController def checkout + rental = Rental.create(rental_params) + + rental.checkout_date = @checkout_date + + rental.save + + if rental.valid? + render( + json: rental, status: :ok + ) + else + render( + json: { errors: rental.errors.messages }, + status: :bad_request + ) + end end def checkin @@ -8,4 +24,9 @@ def checkin def overdue end + + private + def rental_params + params.permit(:movie_id, :customer_id, :due_date) + end end diff --git a/app/models/rental.rb b/app/models/rental.rb index 34d3f4df8..41323f3c2 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -1,4 +1,13 @@ +require 'date' + class Rental < ApplicationRecord belongs_to :movie belongs_to :customer + + def checkout_date + @checkout_date = Date.today + + @checkout_date.strftime('%Y-%m-%d') + + end end diff --git a/app/serializers/movie_serializer.rb b/app/serializers/movie_serializer.rb index f53c228d7..acafcb631 100644 --- a/app/serializers/movie_serializer.rb +++ b/app/serializers/movie_serializer.rb @@ -1,5 +1,3 @@ class MovieSerializer < ActiveModel::Serializer - attributes :id, :title, :release_date, :overview, :inventory, :available_inventory - - + attributes :available_inventory, :id, :inventory, :overview, :release_date, :title end diff --git a/app/serializers/rental_serializer.rb b/app/serializers/rental_serializer.rb new file mode 100644 index 000000000..80a654f93 --- /dev/null +++ b/app/serializers/rental_serializer.rb @@ -0,0 +1,3 @@ +class RentalSerializer < ActiveModel::Serializer + attributes :checkout_date, :customer_id, :due_date, :id, :movie_id +end diff --git a/db/migrate/20171107192001_change_rental_checkout_field_name.rb b/db/migrate/20171107192001_change_rental_checkout_field_name.rb new file mode 100644 index 000000000..bfe28f8e5 --- /dev/null +++ b/db/migrate/20171107192001_change_rental_checkout_field_name.rb @@ -0,0 +1,6 @@ +class ChangeRentalCheckoutFieldName < ActiveRecord::Migration[5.1] + def change + remove_column :rentals, :checkout + add_column :rentals, :checkout_date, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index e6e0c17ce..2702ae44c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171107181406) do +ActiveRecord::Schema.define(version: 20171107192001) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -46,7 +46,7 @@ t.bigint "movie_id" t.bigint "customer_id" t.string "checkin" - t.string "checkout" + t.string "checkout_date" t.index ["customer_id"], name: "index_rentals_on_customer_id" t.index ["movie_id"], name: "index_rentals_on_movie_id" end diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb index 295a86348..a97e66e8d 100644 --- a/test/controllers/movies_controller_test.rb +++ b/test/controllers/movies_controller_test.rb @@ -64,7 +64,7 @@ end it "returns a movie with exactly the required fields" do - keys = %w(available_inventory inventory overview release_date title) + keys = %w(available_inventory id inventory overview release_date title) get movie_path(movie.id) diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 07805bf80..6b60f688a 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -15,7 +15,33 @@ # get rentals_overdue_url # value(response).must_be :success? # end + + let(:movie) { movies(:movie1) } + let(:customer) { customers(:bill) } + describe "checkout" do + let(:rental_data) { + { + movie_id: movie.id, + customer_id: customer.id, + due_date: "2017-12-24" + } + } + + it "can successfully checkout" do + assert_difference "Rental.count", 1 do + post rental_checkout_path, params: rental_data + assert_response :success + end + + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "due_date" + p body + + + end + end From 9f9c41bd1860e5565728f4abfb5645b31ca6bc97 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 11:29:26 -0800 Subject: [PATCH 28/41] completed rental checkout positive case tests --- test/controllers/rentals_controller_test.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 6b60f688a..8ea8f048b 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -1,4 +1,5 @@ require "test_helper" +require 'date' describe RentalsController do # it "should get checkout" do @@ -37,7 +38,10 @@ body = JSON.parse(response.body) body.must_be_kind_of Hash body.must_include "due_date" - p body + # p body + body["checkout_date"].must_equal (Date.today).strftime('%Y-%m-%d') + + Rental.find(body["id"]).customer_id.must_equal rental_data[:customer_id] end From 7083a38973c86412ba06e5df29838e6456a52f6f Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 11:34:23 -0800 Subject: [PATCH 29/41] Rental Controller #checkout negative testing complete --- test/controllers/rentals_controller_test.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 8ea8f048b..c94bbf209 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -29,6 +29,14 @@ } } + let(:bad_rental_data) { + { + movie_id: -2, + customer_id: -2, + due_date: "2017-12-24" + } + } + it "can successfully checkout" do assert_difference "Rental.count", 1 do post rental_checkout_path, params: rental_data @@ -42,8 +50,17 @@ body["checkout_date"].must_equal (Date.today).strftime('%Y-%m-%d') Rental.find(body["id"]).customer_id.must_equal rental_data[:customer_id] + end + it "returns an error for an invalid rental" do + post rental_checkout_path, params: bad_rental_data + assert_response :bad_request + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "errors" + body["errors"].must_include "customer" + body["errors"].must_include "movie" end From 5c1511783e4b5d31951e52e781a636050177b667 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 14:10:16 -0800 Subject: [PATCH 30/41] added checkin method and controller tests --- app/controllers/rentals_controller.rb | 39 +++++++++++++-- app/models/rental.rb | 8 ++-- app/serializers/rental_serializer.rb | 2 +- config/routes.rb | 2 +- ...3011_change_rental_model_checkin_column.rb | 6 +++ db/schema.rb | 4 +- test/controllers/rentals_controller_test.rb | 48 +++++++++++++------ 7 files changed, 82 insertions(+), 27 deletions(-) create mode 100644 db/migrate/20171107213011_change_rental_model_checkin_column.rb diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 8128ab00d..fc7515f23 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -3,7 +3,7 @@ class RentalsController < ApplicationController def checkout rental = Rental.create(rental_params) - rental.checkout_date = @checkout_date + rental.checkout_date = rental.today rental.save @@ -20,13 +20,44 @@ def checkout end def checkin + rental = Rental.find_by(id: params[:id]) + + unless rental + render( + json: { errors: rental.errors.messages }, + status: :not_found + ) + end + + if rental.checkout_date == nil + render( + json: { errors: rental.errors.messages }, + status: :not_found + ) + else + + rental.checkin_date = rental.today + + rental.save + + if rental.valid? + render( + json: rental, status: :ok + ) + else + render( + json: { errors: rental.errors.messages }, + status: :not_found + ) + end + end end def overdue end private - def rental_params - params.permit(:movie_id, :customer_id, :due_date) - end + def rental_params + params.permit(:movie_id, :customer_id, :due_date) + end end diff --git a/app/models/rental.rb b/app/models/rental.rb index 41323f3c2..d74be556b 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -4,10 +4,10 @@ class Rental < ApplicationRecord belongs_to :movie belongs_to :customer - def checkout_date - @checkout_date = Date.today - - @checkout_date.strftime('%Y-%m-%d') + def today + @today = Date.today + @today.strftime('%Y-%m-%d') end + end diff --git a/app/serializers/rental_serializer.rb b/app/serializers/rental_serializer.rb index 80a654f93..c5fc46547 100644 --- a/app/serializers/rental_serializer.rb +++ b/app/serializers/rental_serializer.rb @@ -1,3 +1,3 @@ class RentalSerializer < ActiveModel::Serializer - attributes :checkout_date, :customer_id, :due_date, :id, :movie_id + attributes :checkin_date, :checkout_date, :customer_id, :due_date, :id, :movie_id end diff --git a/config/routes.rb b/config/routes.rb index 71bd88158..1ca24ced4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -15,7 +15,7 @@ resources :movies, only: [:index, :show, :create] post '/rentals/check-out', to: 'rentals#checkout', as: 'rental_checkout' - post '/rentals/check-in', to: 'rentals#checkin', as: 'rental_checkin' + post '/rentals/:id/check-in', to: 'rentals#checkin', as: 'rental_checkin' get '/rentals/overdue', to: 'rentals#overdue', as: 'rental_overdue' diff --git a/db/migrate/20171107213011_change_rental_model_checkin_column.rb b/db/migrate/20171107213011_change_rental_model_checkin_column.rb new file mode 100644 index 000000000..2bf22a402 --- /dev/null +++ b/db/migrate/20171107213011_change_rental_model_checkin_column.rb @@ -0,0 +1,6 @@ +class ChangeRentalModelCheckinColumn < ActiveRecord::Migration[5.1] + def change + remove_column :rentals, :checkin + add_column :rentals, :checkin_date, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 2702ae44c..035486712 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171107192001) do +ActiveRecord::Schema.define(version: 20171107213011) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -45,8 +45,8 @@ t.datetime "updated_at", null: false t.bigint "movie_id" t.bigint "customer_id" - t.string "checkin" t.string "checkout_date" + t.string "checkin_date" t.index ["customer_id"], name: "index_rentals_on_customer_id" t.index ["movie_id"], name: "index_rentals_on_movie_id" end diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index c94bbf209..759ea47b0 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -2,20 +2,6 @@ require 'date' describe RentalsController do - # it "should get checkout" do - # get rentals_checkout_url - # value(response).must_be :success? - # end - # - # it "should get checkin" do - # get rentals_checkin_url - # value(response).must_be :success? - # end - # - # it "should get overdue" do - # get rentals_overdue_url - # value(response).must_be :success? - # end let(:movie) { movies(:movie1) } let(:customer) { customers(:bill) } @@ -56,7 +42,7 @@ post rental_checkout_path, params: bad_rental_data assert_response :bad_request - body = JSON.parse(response.body) + body = JSON.parse(response.body) body.must_be_kind_of Hash body.must_include "errors" body["errors"].must_include "customer" @@ -67,6 +53,38 @@ end describe "checkin" do + let(:rental_data) { + { + movie_id: movie.id, + customer_id: customer.id, + due_date: "2017-12-24" + } + } +# let(:rental1) {rentals(:rental1)} + it "can successfully checkin" do + post rental_checkout_path, params: rental_data + post rental_checkin_path(Rental.last.id) + assert_response :success + + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "checkin_date" + body["checkin_date"].must_equal (Date.today).strftime('%Y-%m-%d') + + Rental.find(body["id"]).customer_id.must_equal rental_data[:customer_id] + end + + it "returns non found if movie does not have checkout date" do + post rental_checkout_path, params: rental_data + rental = Rental.last + rental.checkout_date = nil + rental.save + post rental_checkin_path(rental.id) + + assert_response :not_found + body = JSON.parse(response.body) + body.must_be_kind_of Hash + end end From 44f7ea47654b48d6212842f6bea406f81bca6963 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 14:44:48 -0800 Subject: [PATCH 31/41] added inventory increment and decrement methods and revised tests --- app/controllers/rentals_controller.rb | 30 ++++++++++++++------- app/models/rental.rb | 19 +++++++++++++ test/controllers/rentals_controller_test.rb | 5 ++-- test/fixtures/movies.yml | 1 + 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index fc7515f23..8d9294569 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -1,22 +1,32 @@ class RentalsController < ApplicationController def checkout - rental = Rental.create(rental_params) + rental = Rental.new(rental_params) - rental.checkout_date = rental.today - - rental.save - - if rental.valid? - render( - json: rental, status: :ok - ) + if rental.remove_movie + # if sufficient inventory to remove the movie, set checkout date and save + rental.checkout_date = rental.today + rental.save + # if valid save, return ok status + if rental.valid? + render( + json: rental, status: :ok + ) + else + render( + json: { errors: rental.errors.messages }, + status: :bad_request + ) + end else render( - json: { errors: rental.errors.messages }, + json: { ok: false, message: "Insufficient inventory of select movie -- cannot checkout"}, status: :bad_request ) + return end + + end def checkin diff --git a/app/models/rental.rb b/app/models/rental.rb index d74be556b..13e69ace8 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -10,4 +10,23 @@ def today @today.strftime('%Y-%m-%d') end + def remove_movie + movie = Movie.find_by(id: self.movie_id) + if movie + if movie.available_inventory >= 1 + movie.available_inventory -= 1 + else + return false + end + else + return false + end + end + + def return_movie + movie = Movie.find_by(id: rental.movie_id) + movie.available_inventory += 1 + movie.save + end + end diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 759ea47b0..3ec79e994 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -44,9 +44,8 @@ body = JSON.parse(response.body) body.must_be_kind_of Hash - body.must_include "errors" - body["errors"].must_include "customer" - body["errors"].must_include "movie" + body.must_include "ok" + body["ok"].must_equal false end diff --git a/test/fixtures/movies.yml b/test/fixtures/movies.yml index a859ad79f..9ae333435 100644 --- a/test/fixtures/movies.yml +++ b/test/fixtures/movies.yml @@ -5,6 +5,7 @@ movie1: overview: MyString release_date: MyString inventory: 2 + available_inventory: 2 movie2: title: MyString From d787255508a38fab3ff68d60d3f5e678dec038ce Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 15:00:55 -0800 Subject: [PATCH 32/41] added tests for rentals custom model methods --- app/controllers/rentals_controller.rb | 1 - app/models/rental.rb | 13 +++++++---- test/fixtures/movies.yml | 3 ++- test/fixtures/rentals.yml | 5 +++++ test/models/rental_test.rb | 32 +++++++++++++++++++++++++++ 5 files changed, 48 insertions(+), 6 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 8d9294569..c0ab35063 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -23,7 +23,6 @@ def checkout json: { ok: false, message: "Insufficient inventory of select movie -- cannot checkout"}, status: :bad_request ) - return end diff --git a/app/models/rental.rb b/app/models/rental.rb index 13e69ace8..10d3f8968 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -15,18 +15,23 @@ def remove_movie if movie if movie.available_inventory >= 1 movie.available_inventory -= 1 + movie.save else return false end else return false - end + end end def return_movie - movie = Movie.find_by(id: rental.movie_id) - movie.available_inventory += 1 - movie.save + movie = Movie.find_by(id: self.movie_id) + if self.checkout_date != nil && movie + movie.available_inventory += 1 + movie.save + else + return false + end end end diff --git a/test/fixtures/movies.yml b/test/fixtures/movies.yml index 9ae333435..38c8e2aa0 100644 --- a/test/fixtures/movies.yml +++ b/test/fixtures/movies.yml @@ -11,4 +11,5 @@ movie2: title: MyString overview: MyString release_date: MyString - inventory: 1 + inventory: 0 + available_inventory: 0 diff --git a/test/fixtures/rentals.yml b/test/fixtures/rentals.yml index be1ed0bd3..8fd7f86fc 100644 --- a/test/fixtures/rentals.yml +++ b/test/fixtures/rentals.yml @@ -7,3 +7,8 @@ rental1: customer: bill movie: movie1 + checkout_date: 2017-11-11 + +rental2: + customer: bill + movie: movie2 diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb index f7c41d05e..95eaaeabc 100644 --- a/test/models/rental_test.rb +++ b/test/models/rental_test.rb @@ -2,7 +2,9 @@ describe Rental do let(:rental) { rentals(:rental1)} + let(:rental2) { rentals(:rental2)} let(:movie1) { movies(:movie1)} + let(:unavailable_movie) { movies(:movie2)} let(:bill) { customers(:bill)} @@ -14,12 +16,42 @@ rental.customer.must_equal bill end + it "has an movie" do rental.must_respond_to :movie rental.must_be_kind_of Rental rental.movie.must_be_kind_of Movie rental.movie.must_equal movie1 end + end + + describe 'custom model tests' do + + it "decreases the availabile inventory when a movie is checked out " do + rental.must_respond_to :remove_movie + movie_count = Movie.find_by(id: rental.movie_id).available_inventory + rental.remove_movie + Movie.find_by(id: rental.movie_id).available_inventory.must_equal movie_count - 1 + end + + it "does not decrease the available inventory if insufficient inventory to checkout " do + movie_count = Movie.find_by(id: rental2.movie_id).available_inventory + rental2.remove_movie + Movie.find_by(id: rental2.movie_id).available_inventory.must_equal movie_count + end + + it "increases the available inventory when a movie is checked back in" do + rental.must_respond_to :return_movie + movie_count = Movie.find_by(id: rental.movie_id).available_inventory + rental.return_movie + Movie.find_by(id: rental.movie_id).available_inventory.must_equal movie_count + 1 + end + + it "does not increase the available inventory if a rental does not have a checkout date" do + movie_count = Movie.find_by(id: rental2.movie_id).available_inventory + rental2.return_movie + Movie.find_by(id: rental2.movie_id).available_inventory.must_equal movie_count + end end end From 6220edd2cfd5b51c7b695ef1d7924c34fdbed22f Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 15:28:22 -0800 Subject: [PATCH 33/41] migration to add default value of zero for inventory and available_inventory --- app/controllers/rentals_controller.rb | 13 +++++++++++-- app/models/rental.rb | 6 ++++++ ...7232040_change_movie_inventory_default_values.rb | 8 ++++++++ db/schema.rb | 6 +++--- test/controllers/rentals_controller_test.rb | 2 +- 5 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 db/migrate/20171107232040_change_movie_inventory_default_values.rb diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index c0ab35063..fae7616f1 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -20,12 +20,11 @@ def checkout end else render( - json: { ok: false, message: "Insufficient inventory of select movie -- cannot checkout"}, + json: { ok: false, message: "Insufficient inventory of selected movie -- cannot checkout"}, status: :bad_request ) end - end def checkin @@ -63,6 +62,16 @@ def checkin end def overdue + rental = Rental.find_by(id: params[:id]) + + unless rental + render( + json: { errors: rental.errors.messages }, + status: :not_found + ) + end + + rental.due_date_check end private diff --git a/app/models/rental.rb b/app/models/rental.rb index 10d3f8968..b0adf3c15 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -34,4 +34,10 @@ def return_movie end end + + def due_date_check + + end + + end diff --git a/db/migrate/20171107232040_change_movie_inventory_default_values.rb b/db/migrate/20171107232040_change_movie_inventory_default_values.rb new file mode 100644 index 000000000..134be19c1 --- /dev/null +++ b/db/migrate/20171107232040_change_movie_inventory_default_values.rb @@ -0,0 +1,8 @@ +class ChangeMovieInventoryDefaultValues < ActiveRecord::Migration[5.1] + + def up + change_column_default :movies, :inventory, 0 + change_column_default :movies, :available_inventory, 0 + end + +end diff --git a/db/schema.rb b/db/schema.rb index 035486712..c2d6de114 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171107213011) do +ActiveRecord::Schema.define(version: 20171107232040) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -33,10 +33,10 @@ t.string "title" t.string "overview" t.string "release_date" - t.integer "inventory" + t.integer "inventory", default: 0 t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "available_inventory" + t.integer "available_inventory", default: 0 end create_table "rentals", force: :cascade do |t| diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 3ec79e994..7b1009bc2 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -73,7 +73,7 @@ Rental.find(body["id"]).customer_id.must_equal rental_data[:customer_id] end - it "returns non found if movie does not have checkout date" do + it "returns not found if movie does not have checkout date" do post rental_checkout_path, params: rental_data rental = Rental.last rental.checkout_date = nil From 05e4bbb7bc1dfe0cbce0736b3bf3310bc8dd2a36 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 15:50:17 -0800 Subject: [PATCH 34/41] Added customer.movies_checked_out_count += 1 --- app/models/rental.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/models/rental.rb b/app/models/rental.rb index b0adf3c15..e18bf1e76 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -12,10 +12,15 @@ def today def remove_movie movie = Movie.find_by(id: self.movie_id) - if movie + customer = Customer.find_by(id: self.customer_id) + + if movie && customer if movie.available_inventory >= 1 movie.available_inventory -= 1 movie.save + + customer.movies_checked_out_count += 1 + customer.save else return false end @@ -26,9 +31,14 @@ def remove_movie def return_movie movie = Movie.find_by(id: self.movie_id) - if self.checkout_date != nil && movie + customer = Customer.find_by(id: self.customer_id) + + if self.checkout_date != nil && movie && customer movie.available_inventory += 1 movie.save + + customer.movies_checked_out_count -= 1 + customer.save else return false end From 5ee769862177b6a620e1864947e17e8a9ff8bdac Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 15:56:01 -0800 Subject: [PATCH 35/41] added extra custom rental model method tests --- test/models/rental_test.rb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb index 95eaaeabc..8932328ff 100644 --- a/test/models/rental_test.rb +++ b/test/models/rental_test.rb @@ -34,12 +34,24 @@ Movie.find_by(id: rental.movie_id).available_inventory.must_equal movie_count - 1 end + it "increases the customers movies_checked_out_count" do + customer_count = Customer.find_by(id: rental.customer_id).movies_checked_out_count + rental.remove_movie + Customer.find_by(id: rental.customer_id).movies_checked_out_count.must_equal customer_count + 1 + end + it "does not decrease the available inventory if insufficient inventory to checkout " do movie_count = Movie.find_by(id: rental2.movie_id).available_inventory rental2.remove_movie Movie.find_by(id: rental2.movie_id).available_inventory.must_equal movie_count end + it "does not increase the customers movies_checked_out_count if insufficient inventory to checkout" do + customer_count = Customer.find_by(id: rental2.customer_id).movies_checked_out_count + rental2.remove_movie + Customer.find_by(id: rental2.customer_id).movies_checked_out_count.must_equal customer_count + end + it "increases the available inventory when a movie is checked back in" do rental.must_respond_to :return_movie movie_count = Movie.find_by(id: rental.movie_id).available_inventory @@ -47,11 +59,23 @@ Movie.find_by(id: rental.movie_id).available_inventory.must_equal movie_count + 1 end + it "decreases the customers movies_checked_out_count" do + customer_count = Customer.find_by(id: rental.customer_id).movies_checked_out_count + rental.return_movie + Customer.find_by(id: rental.customer_id).movies_checked_out_count.must_equal customer_count - 1 + end + it "does not increase the available inventory if a rental does not have a checkout date" do movie_count = Movie.find_by(id: rental2.movie_id).available_inventory rental2.return_movie Movie.find_by(id: rental2.movie_id).available_inventory.must_equal movie_count end + it "does not decrease the customers movies_checked_out_count if insufficient inventory to checkout" do + customer_count = Customer.find_by(id: rental2.customer_id).movies_checked_out_count + rental2.return_movie + Customer.find_by(id: rental2.customer_id).movies_checked_out_count.must_equal customer_count + end + end end From ec21827b1c2ebd9e84300ad372d0adc7516a2b3e Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Tue, 7 Nov 2017 16:32:11 -0800 Subject: [PATCH 36/41] Backup Commit(prior to merge) --- app/controllers/rentals_controller.rb | 16 +++++++--------- app/models/movie.rb | 6 ++++++ app/models/rental.rb | 9 +++++++-- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index fae7616f1..4dfe2e082 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -62,16 +62,14 @@ def checkin end def overdue - rental = Rental.find_by(id: params[:id]) - - unless rental - render( - json: { errors: rental.errors.messages }, - status: :not_found - ) + rentals = Rental.all + overdue_rentals = [] + rentals.each do |rental| + if rental.is_overdue? + overdue_rentals << rental + end end - - rental.due_date_check + return overdue_rentals end private diff --git a/app/models/movie.rb b/app/models/movie.rb index d34b59f22..892effb67 100644 --- a/app/models/movie.rb +++ b/app/models/movie.rb @@ -1,3 +1,9 @@ class Movie < ApplicationRecord + before_create :set_available_inventory validates :title, presence: true + + private + def set_available_inventory + self.available_inventory = self.inventory + end end diff --git a/app/models/rental.rb b/app/models/rental.rb index e18bf1e76..10129e342 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -45,8 +45,13 @@ def return_movie end - def due_date_check - + def is_overdue? + due_date_object = self.due_date.parse + if Date.today > due_date_object + return true + else + return false + end end From 9ebb3137a7651d8737e1485e39e55b8adcfaa884 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Wed, 8 Nov 2017 10:56:16 -0800 Subject: [PATCH 37/41] Refactored RentalController#checkin method and updated tests --- app/controllers/rentals_controller.rb | 16 +++++++++++++--- config/routes.rb | 2 +- test/controllers/rentals_controller_test.rb | 17 +++++++++++++++-- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 5ef42e609..9f7c2ac82 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -28,13 +28,23 @@ def checkout end def checkin - rental = Rental.find_by(id: params[:id]) + rental = Rental.where(["customer_id = ? and movie_id = ?", params[:customer_id], params[:movie_id]]) - unless rental + if rental.count == 0 render( - json: { errors: rental.errors.messages }, + json: { errors: "No rental found" }, status: :not_found ) + return + elsif rental.count > 1 + rental.each do |elem| + if elem.checkin_date == nil + rental = elem + break + else + rental = rental[0] + end + end end if rental.checkout_date == nil diff --git a/config/routes.rb b/config/routes.rb index 1ca24ced4..71bd88158 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -15,7 +15,7 @@ resources :movies, only: [:index, :show, :create] post '/rentals/check-out', to: 'rentals#checkout', as: 'rental_checkout' - post '/rentals/:id/check-in', to: 'rentals#checkin', as: 'rental_checkin' + post '/rentals/check-in', to: 'rentals#checkin', as: 'rental_checkin' get '/rentals/overdue', to: 'rentals#overdue', as: 'rental_overdue' diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 7b1009bc2..72fb84603 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -56,16 +56,29 @@ { movie_id: movie.id, customer_id: customer.id, - due_date: "2017-12-24" + due_date: "2017-12-24", + checkout_date: nil, + checkin_date: nil } } # let(:rental1) {rentals(:rental1)} it "can successfully checkin" do post rental_checkout_path, params: rental_data - post rental_checkin_path(Rental.last.id) + rental2 = Rental.last + + rental_data2 = { + movie_id: rental2.movie_id, + customer_id: rental2.customer_id, + due_date: rental2.due_date, + checkout_date: rental2.checkout_date, + checkin_date: rental2.checkin_date + } + + post rental_checkin_path, params: rental_data2 assert_response :success body = JSON.parse(response.body) + p body body.must_be_kind_of Hash body.must_include "checkin_date" body["checkin_date"].must_equal (Date.today).strftime('%Y-%m-%d') From 8521b6c9ce9d3adc22c4904965f5bf5edb4db01a Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Wed, 8 Nov 2017 11:14:39 -0800 Subject: [PATCH 38/41] revisions to pass smoke tests --- app/controllers/rentals_controller.rb | 10 +++++----- test/controllers/rentals_controller_test.rb | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 9f7c2ac82..d40a93238 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -41,12 +41,13 @@ def checkin if elem.checkin_date == nil rental = elem break - else - rental = rental[0] end end + else + rental = rental[0] end + p rental if rental.checkout_date == nil render( json: { errors: rental.errors.messages }, @@ -55,10 +56,9 @@ def checkin else rental.checkin_date = rental.today + rental.return_movie - rental.save - - if rental.valid? + if rental.save render( json: rental, status: :ok ) diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 72fb84603..a8683aec1 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -78,7 +78,6 @@ assert_response :success body = JSON.parse(response.body) - p body body.must_be_kind_of Hash body.must_include "checkin_date" body["checkin_date"].must_equal (Date.today).strftime('%Y-%m-%d') From 1aeebc70bc33783ff5fe52d52ec9d46dc9ce5ae2 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Wed, 8 Nov 2017 14:12:29 -0800 Subject: [PATCH 39/41] Added rental model (is_overdue?) tests --- app/controllers/rentals_controller.rb | 3 ++- app/models/rental.rb | 4 ++-- test/fixtures/rentals.yml | 7 +++++++ test/models/rental_test.rb | 12 ++++++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index d40a93238..795f99d92 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -47,7 +47,7 @@ def checkin rental = rental[0] end - p rental + #p rental if rental.checkout_date == nil render( json: { errors: rental.errors.messages }, @@ -74,6 +74,7 @@ def checkin def overdue rentals = Rental.all overdue_rentals = [] + rentals.each do |rental| if rental.is_overdue? overdue_rentals << rental diff --git a/app/models/rental.rb b/app/models/rental.rb index 10129e342..7ce2effd3 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -46,9 +46,9 @@ def return_movie def is_overdue? - due_date_object = self.due_date.parse + due_date_object = Date.parse(self.due_date) if Date.today > due_date_object - return true + return true else return false end diff --git a/test/fixtures/rentals.yml b/test/fixtures/rentals.yml index 8fd7f86fc..8e9222b99 100644 --- a/test/fixtures/rentals.yml +++ b/test/fixtures/rentals.yml @@ -12,3 +12,10 @@ rental1: rental2: customer: bill movie: movie2 + due_date: 2017-11-7 + +rental3: + customer: bill + movie: movie1 + due_date: 2017-11-30 + checkout_date: 2017-11-1 diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb index 8932328ff..2a6239cb9 100644 --- a/test/models/rental_test.rb +++ b/test/models/rental_test.rb @@ -3,6 +3,8 @@ describe Rental do let(:rental) { rentals(:rental1)} let(:rental2) { rentals(:rental2)} + let(:rental3) { rentals(:rental3)} + let(:movie1) { movies(:movie1)} let(:unavailable_movie) { movies(:movie2)} let(:bill) { customers(:bill)} @@ -77,5 +79,15 @@ Customer.find_by(id: rental2.customer_id).movies_checked_out_count.must_equal customer_count end + describe "is_overdue?" do + it "returns true if movie is overdue" do + rental2.is_overdue?.must_equal true + + end + + it "returns false if movie is not overdue" do + rental3.is_overdue?.must_equal false + end + end end end From 020a4d22ddca682b71e32409fe538c23f4fd5e5b Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Wed, 8 Nov 2017 14:48:41 -0800 Subject: [PATCH 40/41] Updates to RentalController#overdue method and tests and #is_overdue? model method and test --- app/controllers/rentals_controller.rb | 6 ++++-- app/models/rental.rb | 1 + test/controllers/rentals_controller_test.rb | 23 ++++++++++++++++++++- test/fixtures/rentals.yml | 6 ++++++ test/models/rental_test.rb | 4 ++-- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 795f99d92..f17f02e56 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -74,13 +74,15 @@ def checkin def overdue rentals = Rental.all overdue_rentals = [] - + rentals.each do |rental| if rental.is_overdue? overdue_rentals << rental end end - return overdue_rentals + render( + json: overdue_rentals, status: :ok + ) end private diff --git a/app/models/rental.rb b/app/models/rental.rb index 7ce2effd3..591bc9ba5 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -46,6 +46,7 @@ def return_movie def is_overdue? + return false if self.checkout_date == nil || self.due_date == nil due_date_object = Date.parse(self.due_date) if Date.today > due_date_object return true diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index a8683aec1..0c464e8ae 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -93,13 +93,34 @@ post rental_checkin_path(rental.id) assert_response :not_found - body = JSON.parse(response.body) + body = JSON.parse(response.body) body.must_be_kind_of Hash end end describe "overdue" do + let(:rental_data) { + { + movie_id: movie.id, + customer_id: customer.id, + due_date: "2017-11-7" + } + } + it "returns a list of rentals that are overdue" do + post rental_checkout_path, params: rental_data + get rental_overdue_path + assert_response :success + + body = JSON.parse(response.body) + p body + # body.must_be_kind_of Array + body.each do |rental| + rental.must_be_kind_of Hash + end + body.count.must_equal 2 + + end end end diff --git a/test/fixtures/rentals.yml b/test/fixtures/rentals.yml index 8e9222b99..66cfc1fe0 100644 --- a/test/fixtures/rentals.yml +++ b/test/fixtures/rentals.yml @@ -14,6 +14,12 @@ rental2: movie: movie2 due_date: 2017-11-7 +rental22: + customer: bill + movie: movie2 + checkout_date: 2017-11-1 + due_date: 2017-11-7 + rental3: customer: bill movie: movie1 diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb index 2a6239cb9..b060c157e 100644 --- a/test/models/rental_test.rb +++ b/test/models/rental_test.rb @@ -4,7 +4,7 @@ let(:rental) { rentals(:rental1)} let(:rental2) { rentals(:rental2)} let(:rental3) { rentals(:rental3)} - + let(:rental22) { rentals(:rental22)} let(:movie1) { movies(:movie1)} let(:unavailable_movie) { movies(:movie2)} let(:bill) { customers(:bill)} @@ -81,7 +81,7 @@ describe "is_overdue?" do it "returns true if movie is overdue" do - rental2.is_overdue?.must_equal true + rental22.is_overdue?.must_equal true end From b318fd9b4d2fdf00205059c293687c1c4090e414 Mon Sep 17 00:00:00 2001 From: Jan Edrozo Date: Wed, 8 Nov 2017 14:53:23 -0800 Subject: [PATCH 41/41] Updated ERD diagram file --- erd.pdf | Bin 31400 -> 32672 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/erd.pdf b/erd.pdf index cdc644797996cfa73d3178b0bb5e99c2d40b1b38..e2a9ed4b5102eb602bcdc6c5c3065ac6f352a816 100644 GIT binary patch delta 26462 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 z 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 delta 25123 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

DYtMP+6;2b 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 a?xt?;-fosp0wxX~5D1NyR$4^{?Y{sX+EM2K