From dfdaf7af908dc71aa0bdae49f1a51a082df22350 Mon Sep 17 00:00:00 2001 From: IsabelDePapel Date: Mon, 6 Nov 2017 11:05:15 -0800 Subject: [PATCH 01/32] set up rails files --- .gitignore | 16 ++ Gemfile | 55 ++++++ Gemfile.lock | 187 ++++++++++++++++++ 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/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 + lib/tasks/.keep | 0 log/.keep | 0 public/robots.txt | 1 + test/controllers/.keep | 0 test/fixtures/.keep | 0 test/fixtures/files/.keep | 0 test/integration/.keep | 0 test/mailers/.keep | 0 test/models/.keep | 0 test/test_helper.rb | 17 ++ tmp/.keep | 0 vendor/.keep | 0 53 files changed, 936 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/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 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/files/.keep create mode 100644 test/integration/.keep create mode 100644 test/mailers/.keep create mode 100644 test/models/.keep 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..367e93da6 --- /dev/null +++ b/Gemfile @@ -0,0 +1,55 @@ +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' +gem 'foundation-rails', '6.4.1.2' +group :development do + gem 'better_errors' + gem 'pry-rails' + gem 'binding_of_caller' + gem 'awesome_print' +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..51c9f6736 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,187 @@ +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) + awesome_print (1.8.0) + babel-source (5.8.35) + babel-transpiler (0.7.0) + babel-source (>= 4.0, < 6) + execjs (~> 2.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) + execjs (2.7.0) + ffi (1.9.18) + foundation-rails (6.4.1.2) + railties (>= 3.1.0) + sass (>= 3.3.0, < 3.5) + sprockets-es6 (>= 0.9.0) + 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) + sass (3.4.25) + 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-es6 (0.9.2) + babel-source (>= 5.8.11) + babel-transpiler + sprockets (>= 3.0.0) + 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 + awesome_print + better_errors + binding_of_caller + byebug + foundation-rails (= 6.4.1.2) + 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/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..5974bf3dc --- /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..aa6f5b221 --- /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: 938f30040172b1cb3388325f06f8f59459d4a8045943c999c24ca1f97c8a37889961bfd322cb419a76e5321582ab5032b6824f87b99dc4323568c4a305f3691e + +test: + secret_key_base: 160ab880504b896218fa1df2d5c2bbb1cb4fd3acd1a81f223141313a0efe8b5bf7d82684f1869975afddcb03d6dc8f6aa7635749c2bf26529fed3c2ebf7f06f0 + +# 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/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/files/.keep b/test/fixtures/files/.keep new file mode 100644 index 000000000..e69de29bb 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/test_helper.rb b/test/test_helper.rb new file mode 100644 index 000000000..7c0fbfd6f --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,17 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path("../../config/environment", __FILE__) +require "rails/test_help" +require "minitest/rails" + +# 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 b710b07b4ac795ed21da51984d0293001bbe11fc Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Mon, 6 Nov 2017 12:54:07 -0800 Subject: [PATCH 02/32] IS/LC: Generate models for Rental, Customer, and Movie. Create database and migrate. Add ERD and simplecov gems. --- Gemfile | 3 ++ Gemfile.lock | 16 ++++++ app/models/customer.rb | 2 + app/models/movie.rb | 2 + app/models/rental.rb | 4 ++ db/migrate/20171106204242_create_movies.rb | 12 +++++ db/migrate/20171106204350_create_customers.rb | 14 +++++ db/migrate/20171106204620_create_rentals.rb | 11 ++++ db/schema.rb | 50 ++++++++++++++++++ erd.pdf | Bin 0 -> 25703 bytes test/fixtures/customers.yml | 17 ++++++ test/fixtures/movies.yml | 13 +++++ test/fixtures/rentals.yml | 11 ++++ test/models/customer_test.rb | 9 ++++ test/models/movie_test.rb | 9 ++++ test/models/rental_test.rb | 9 ++++ 16 files changed, 182 insertions(+) create mode 100644 app/models/customer.rb create mode 100644 app/models/movie.rb create mode 100644 app/models/rental.rb create mode 100644 db/migrate/20171106204242_create_movies.rb create mode 100644 db/migrate/20171106204350_create_customers.rb create mode 100644 db/migrate/20171106204620_create_rentals.rb create mode 100644 db/schema.rb create mode 100644 erd.pdf create mode 100644 test/fixtures/customers.yml create mode 100644 test/fixtures/movies.yml create mode 100644 test/fixtures/rentals.yml create mode 100644 test/models/customer_test.rb create mode 100644 test/models/movie_test.rb create mode 100644 test/models/rental_test.rb diff --git a/Gemfile b/Gemfile index 367e93da6..8e1adb0fc 100644 --- a/Gemfile +++ b/Gemfile @@ -28,6 +28,7 @@ gem 'puma', '~> 3.7' 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] + gem 'simplecov', require: false end group :development do @@ -42,11 +43,13 @@ gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'jquery-turbolinks' gem 'foundation-rails', '6.4.1.2' + group :development do gem 'better_errors' gem 'pry-rails' gem 'binding_of_caller' gem 'awesome_print' + gem 'rails-erd' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 51c9f6736..540d67160 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -53,10 +53,12 @@ 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) debug_inspector (0.0.3) + docile (1.1.5) erubi (1.7.0) execjs (2.7.0) ffi (1.9.18) @@ -71,6 +73,7 @@ GEM jquery-turbolinks (2.1.0) railties (>= 3.1.0) turbolinks + json (2.0.2) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -120,6 +123,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) @@ -132,9 +140,15 @@ 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) sass (3.4.25) + simplecov (0.15.0) + docile (~> 1.1.0) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.2) spring (2.0.2) activesupport (>= 4.2) spring-watcher-listen (2.0.1) @@ -179,6 +193,8 @@ DEPENDENCIES pry-rails puma (~> 3.7) rails (~> 5.1.4) + rails-erd + simplecov spring spring-watcher-listen (~> 2.0.0) tzinfo-data diff --git a/app/models/customer.rb b/app/models/customer.rb new file mode 100644 index 000000000..0b5277335 --- /dev/null +++ b/app/models/customer.rb @@ -0,0 +1,2 @@ +class Customer < ApplicationRecord +end diff --git a/app/models/movie.rb b/app/models/movie.rb new file mode 100644 index 000000000..dc614df15 --- /dev/null +++ b/app/models/movie.rb @@ -0,0 +1,2 @@ +class Movie < ApplicationRecord +end diff --git a/app/models/rental.rb b/app/models/rental.rb new file mode 100644 index 000000000..34d3f4df8 --- /dev/null +++ b/app/models/rental.rb @@ -0,0 +1,4 @@ +class Rental < ApplicationRecord + belongs_to :movie + belongs_to :customer +end diff --git a/db/migrate/20171106204242_create_movies.rb b/db/migrate/20171106204242_create_movies.rb new file mode 100644 index 000000000..674ab9ae8 --- /dev/null +++ b/db/migrate/20171106204242_create_movies.rb @@ -0,0 +1,12 @@ +class CreateMovies < ActiveRecord::Migration[5.1] + def change + create_table :movies do |t| + t.string :title + t.text :overview + t.date :release_date + t.integer :inventory + + t.timestamps + end + end +end diff --git a/db/migrate/20171106204350_create_customers.rb b/db/migrate/20171106204350_create_customers.rb new file mode 100644 index 000000000..02edcfab8 --- /dev/null +++ b/db/migrate/20171106204350_create_customers.rb @@ -0,0 +1,14 @@ +class CreateCustomers < ActiveRecord::Migration[5.1] + def change + create_table :customers do |t| + t.string :name + t.string :phone + t.string :address + t.string :city + t.string :state + t.string :postal_code + + t.timestamps + end + end +end diff --git a/db/migrate/20171106204620_create_rentals.rb b/db/migrate/20171106204620_create_rentals.rb new file mode 100644 index 000000000..eedfa5020 --- /dev/null +++ b/db/migrate/20171106204620_create_rentals.rb @@ -0,0 +1,11 @@ +class CreateRentals < ActiveRecord::Migration[5.1] + def change + create_table :rentals do |t| + t.references :movie, foreign_key: true + t.references :customer, foreign_key: true + t.date :due_date + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 000000000..3ee15d1fa --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,50 @@ +# 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: 20171106204620) 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 "phone" + t.string "address" + t.string "city" + t.string "state" + t.string "postal_code" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "movies", force: :cascade do |t| + t.string "title" + t.text "overview" + t.date "release_date" + t.integer "inventory" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "rentals", force: :cascade do |t| + t.bigint "movie_id" + t.bigint "customer_id" + t.date "due_date" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["customer_id"], name: "index_rentals_on_customer_id" + t.index ["movie_id"], name: "index_rentals_on_movie_id" + end + + add_foreign_key "rentals", "customers" + add_foreign_key "rentals", "movies" +end diff --git a/erd.pdf b/erd.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cccc49b24f15c5e5bf52a37893d463a8cc0da37f GIT binary patch literal 25703 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 literal 0 HcmV?d00001 diff --git a/test/fixtures/customers.yml b/test/fixtures/customers.yml new file mode 100644 index 000000000..1600a2eec --- /dev/null +++ b/test/fixtures/customers.yml @@ -0,0 +1,17 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + phone: MyString + address: MyString + city: MyString + state: MyString + postal_code: MyString + +two: + name: MyString + phone: MyString + address: MyString + city: MyString + state: MyString + postal_code: MyString diff --git a/test/fixtures/movies.yml b/test/fixtures/movies.yml new file mode 100644 index 000000000..916309f20 --- /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: MyText + release_date: 2017-11-06 + inventory: 1 + +two: + title: MyString + overview: MyText + release_date: 2017-11-06 + inventory: 1 diff --git a/test/fixtures/rentals.yml b/test/fixtures/rentals.yml new file mode 100644 index 000000000..cc8ec75ed --- /dev/null +++ b/test/fixtures/rentals.yml @@ -0,0 +1,11 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + movie: one + customer: one + due_date: 2017-11-06 + +two: + movie: two + customer: two + due_date: 2017-11-06 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 From 80be712beadaa30ba8a574e6bf0415b84d12be3c Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Mon, 6 Nov 2017 13:06:31 -0800 Subject: [PATCH 03/32] IS/LC: Write model validations tests. --- test/fixtures/movies.yml | 16 ++++++++-------- test/models/movie_test.rb | 21 ++++++++++++++++++--- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/test/fixtures/movies.yml b/test/fixtures/movies.yml index 916309f20..def239a9d 100644 --- a/test/fixtures/movies.yml +++ b/test/fixtures/movies.yml @@ -1,13 +1,13 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -one: - title: MyString +dune: + title: Dune overview: MyText - release_date: 2017-11-06 - inventory: 1 + release_date: 1983-01-07 + inventory: 3 -two: - title: MyString +gremlins: + title: Gremlins overview: MyText - release_date: 2017-11-06 - inventory: 1 + release_date: 1982-10-31 + inventory: 2 diff --git a/test/models/movie_test.rb b/test/models/movie_test.rb index 34d1d30a5..ee9f92324 100644 --- a/test/models/movie_test.rb +++ b/test/models/movie_test.rb @@ -1,9 +1,24 @@ require "test_helper" describe Movie do - let(:movie) { Movie.new } + let(:dune) { movies(:dune) } + let(:gremlins) { movies(:gremlins) } + describe "validations" do + it "must have a title" do + dune.title = nil + dune.valid?.must_equal false + end - it "must be valid" do - value(movie).must_be :valid? + it "must have an inventory that is greater than 0" do + gremlins.inventory = nil + gremlins.valid?.must_equal false + gremlins.inventory = 0 + gremlins.valid?.must_equal false + end + + it "must have a release date" do + dune.release_date = nil + dune.valid?.must_equal false + end end end From 899cffc4b94fd3d3476a5bc7d0f95bae37607818 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Mon, 6 Nov 2017 13:17:39 -0800 Subject: [PATCH 04/32] IS/LC: Add Rental and Customer valdiations tests. --- test/fixtures/customers.yml | 28 ++++++++++++++-------------- test/fixtures/rentals.yml | 10 +++++----- test/models/customer_test.rb | 15 ++++++++++++--- test/models/rental_test.rb | 10 ++++++---- 4 files changed, 37 insertions(+), 26 deletions(-) diff --git a/test/fixtures/customers.yml b/test/fixtures/customers.yml index 1600a2eec..2944f4acb 100644 --- a/test/fixtures/customers.yml +++ b/test/fixtures/customers.yml @@ -1,17 +1,17 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -one: - name: MyString - phone: MyString - address: MyString - city: MyString - state: MyString - postal_code: MyString +harry: + name: Harry Henderson + phone: (999) 999-9999 + address: 123 Main St + city: Seattle + state: WA + postal_code: 98144 -two: - name: MyString - phone: MyString - address: MyString - city: MyString - state: MyString - postal_code: MyString +sally: + name: Sally Field + phone: (123) 456-7890 + address: 224 Peach St + city: Seattle + state: WA + postal_code: 98122 diff --git a/test/fixtures/rentals.yml b/test/fixtures/rentals.yml index cc8ec75ed..1e08ecc43 100644 --- a/test/fixtures/rentals.yml +++ b/test/fixtures/rentals.yml @@ -1,11 +1,11 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: - movie: one - customer: one + movie: dune + customer: sally due_date: 2017-11-06 two: - movie: two - customer: two - due_date: 2017-11-06 + movie: gremlins + customer: harry + due_date: 2017-11-08 diff --git a/test/models/customer_test.rb b/test/models/customer_test.rb index 5ebc5c850..96ce39805 100644 --- a/test/models/customer_test.rb +++ b/test/models/customer_test.rb @@ -1,9 +1,18 @@ require "test_helper" describe Customer do - let(:customer) { Customer.new } + describe "validations" do + let(:harry) { customers(:harry) } + let(:sally) { customers(:sally) } - it "must be valid" do - value(customer).must_be :valid? + it "must have a name" do + harry.name = nil + harry.valid?.must_equal false + end + + it "must have a phone num" do + sally.phone = nil + sally.valid?.must_equal false + end end end diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb index 6ea53d94f..5b6ddf868 100644 --- a/test/models/rental_test.rb +++ b/test/models/rental_test.rb @@ -1,9 +1,11 @@ require "test_helper" describe Rental do - let(:rental) { Rental.new } - - it "must be valid" do - value(rental).must_be :valid? + describe "validations" do + it "must have a due date" do + rental = rentals(:one) + rental.due_date = nil + rental.valid?.must_equal false + end end end From e0b7b33b049aed173fa38b6607b143063d8ac8c6 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Mon, 6 Nov 2017 13:44:36 -0800 Subject: [PATCH 05/32] IS/LC: Add dependent clauses to model relations. Build relationships tests. --- app/models/customer.rb | 3 +++ app/models/movie.rb | 3 +++ app/models/rental.rb | 1 + test/models/customer_test.rb | 19 ++++++++++++++++--- test/models/movie_test.rb | 13 +++++++++++++ test/models/rental_test.rb | 18 ++++++++++++++++++ 6 files changed, 54 insertions(+), 3 deletions(-) diff --git a/app/models/customer.rb b/app/models/customer.rb index 0b5277335..07e69b693 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -1,2 +1,5 @@ class Customer < ApplicationRecord + has_many :rentals, dependent: :nullify + has_many :movies, through: :rentals + end diff --git a/app/models/movie.rb b/app/models/movie.rb index dc614df15..ee7d88999 100644 --- a/app/models/movie.rb +++ b/app/models/movie.rb @@ -1,2 +1,5 @@ class Movie < ApplicationRecord + has_many :rentals, dependent: :nullify + has_many :customers, through: :rentals + end diff --git a/app/models/rental.rb b/app/models/rental.rb index 34d3f4df8..09e05ad13 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -1,4 +1,5 @@ class Rental < ApplicationRecord belongs_to :movie belongs_to :customer + end diff --git a/test/models/customer_test.rb b/test/models/customer_test.rb index 96ce39805..c2e2858cf 100644 --- a/test/models/customer_test.rb +++ b/test/models/customer_test.rb @@ -1,10 +1,10 @@ require "test_helper" describe Customer do - describe "validations" do - let(:harry) { customers(:harry) } - let(:sally) { customers(:sally) } + let(:harry) { customers(:harry) } + let(:sally) { customers(:sally) } + describe "validations" do it "must have a name" do harry.name = nil harry.valid?.must_equal false @@ -15,4 +15,17 @@ sally.valid?.must_equal false end end + + describe "relationships" do + it "must have a collection of rentals" do + harry.must_respond_to :rentals + harry.rentals.length.must_equal 1 + harry.rentals[0].must_be_kind_of Rental + end + + it "must have a collection of movies" do + sally.must_respond_to :movies + sally.movies[0].must_be_instance_of Movie + end + end end diff --git a/test/models/movie_test.rb b/test/models/movie_test.rb index ee9f92324..75a423cb4 100644 --- a/test/models/movie_test.rb +++ b/test/models/movie_test.rb @@ -21,4 +21,17 @@ dune.valid?.must_equal false end end + + describe "relationships" do + it "must have a collection of rentals" do + dune.must_respond_to :rentals + dune.rentals.length.must_equal 1 + dune.rentals[0].must_be_instance_of Rental + end + + it "must have a collection of customers" do + gremlins.must_respond_to :customers + gremlins.customers[0].must_be_instance_of Customer + end + end end diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb index 5b6ddf868..f82aa9827 100644 --- a/test/models/rental_test.rb +++ b/test/models/rental_test.rb @@ -8,4 +8,22 @@ rental.valid?.must_equal false end end + + describe "record persistence" do + it "must persist a rental even if movie is deleted" do + start_count = Rental.count + rental = rentals(:one) + Movie.destroy_all + Rental.count.must_equal start_count + rental.movie.must_equal nil + end + + it "must persist a rental even if a customer is deleted" do + start_count = Rental.count + rental = rentals(:two) + Customer.destroy_all + Rental.count.must_equal start_count + rental.customer.must_equal nil + end + end end From 8de8036f944af9f8b7410d588e915ab869ca05d7 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Mon, 6 Nov 2017 14:12:30 -0800 Subject: [PATCH 06/32] IS/LC: Create migration to add credit column and rename registered_at column in Customers table. Seed database. --- .../20171106215500_change_add_columns_to_match_seed_data.rb | 6 ++++++ db/schema.rb | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20171106215500_change_add_columns_to_match_seed_data.rb diff --git a/db/migrate/20171106215500_change_add_columns_to_match_seed_data.rb b/db/migrate/20171106215500_change_add_columns_to_match_seed_data.rb new file mode 100644 index 000000000..ecbd367bd --- /dev/null +++ b/db/migrate/20171106215500_change_add_columns_to_match_seed_data.rb @@ -0,0 +1,6 @@ +class ChangeAddColumnsToMatchSeedData < ActiveRecord::Migration[5.1] + def change + rename_column :customers, :created_at, :registered_at + add_column :customers, :account_credit, :decimal + end +end diff --git a/db/schema.rb b/db/schema.rb index 3ee15d1fa..328fb4122 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: 20171106204620) do +ActiveRecord::Schema.define(version: 20171106215500) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -22,8 +22,9 @@ t.string "city" t.string "state" t.string "postal_code" - t.datetime "created_at", null: false + t.datetime "registered_at", null: false t.datetime "updated_at", null: false + t.decimal "account_credit" end create_table "movies", force: :cascade do |t| From ee9e302963a734f9770deb89b92644db7a8e67a0 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Mon, 6 Nov 2017 14:17:15 -0800 Subject: [PATCH 07/32] IS/LC: Add simplcove require statement to test_helper. Add overage folder to gitignore. --- .gitignore | 2 ++ test/test_helper.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 68ac019ec..f0157e449 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ !/tmp/.keep .byebug_history + +coverage/* diff --git a/test/test_helper.rb b/test/test_helper.rb index 7c0fbfd6f..a1aa131b2 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,3 +1,5 @@ +require 'simplecov' +SimpleCov.start 'rails' ENV["RAILS_ENV"] = "test" require File.expand_path("../../config/environment", __FILE__) require "rails/test_help" From 2b0b4afa43e7e34bb1fcb7c6e0f24fd82708677a Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Mon, 6 Nov 2017 14:38:21 -0800 Subject: [PATCH 08/32] Add validation to models. All tests passing. Wave 1 complete. --- app/models/customer.rb | 3 +++ app/models/movie.rb | 3 +++ app/models/rental.rb | 1 + test/fixtures/customers.yml | 2 ++ 4 files changed, 9 insertions(+) diff --git a/app/models/customer.rb b/app/models/customer.rb index 07e69b693..297e2cd82 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -2,4 +2,7 @@ class Customer < ApplicationRecord has_many :rentals, dependent: :nullify has_many :movies, through: :rentals + validates :name, presence: true + validates :phone, presence: true + end diff --git a/app/models/movie.rb b/app/models/movie.rb index ee7d88999..3185225c4 100644 --- a/app/models/movie.rb +++ b/app/models/movie.rb @@ -2,4 +2,7 @@ class Movie < ApplicationRecord has_many :rentals, dependent: :nullify has_many :customers, through: :rentals + validates :title, presence: true + validates :release_date, presence: true + validates :inventory, numericality: {only_integer: true, greater_than: 0 } end diff --git a/app/models/rental.rb b/app/models/rental.rb index 09e05ad13..74e919fe4 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -2,4 +2,5 @@ class Rental < ApplicationRecord belongs_to :movie belongs_to :customer + validates :due_date, presence: true end diff --git a/test/fixtures/customers.yml b/test/fixtures/customers.yml index 2944f4acb..d44fdd5b0 100644 --- a/test/fixtures/customers.yml +++ b/test/fixtures/customers.yml @@ -7,6 +7,7 @@ harry: city: Seattle state: WA postal_code: 98144 + registered_at: 2017-11-05 22:19:39.093509 sally: name: Sally Field @@ -15,3 +16,4 @@ sally: city: Seattle state: WA postal_code: 98122 + registered_at: 2017-11-06 22:19:39.102487 From d707ef11cd0c8a8a198c4b1c9b5fcd39afef4aa1 Mon Sep 17 00:00:00 2001 From: IsabelDePapel Date: Mon, 6 Nov 2017 14:47:19 -0800 Subject: [PATCH 09/32] add minitest reporters to test helper --- test/test_helper.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/test_helper.rb b/test/test_helper.rb index a1aa131b2..cd536c79c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -4,6 +4,14 @@ 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: From 8a146c3af0ce7524f2ac8672eddb8a3a08f7a3b0 Mon Sep 17 00:00:00 2001 From: IsabelDePapel Date: Mon, 6 Nov 2017 15:10:40 -0800 Subject: [PATCH 10/32] IS/LS add index route and index action and tests for customers controller. All tests passing. --- app/controllers/customers_controller.rb | 6 +++ config/routes.rb | 2 + test/controllers/customers_controller_test.rb | 50 +++++++++++++++++++ test/test_helper.rb | 3 +- 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 app/controllers/customers_controller.rb create mode 100644 test/controllers/customers_controller_test.rb diff --git a/app/controllers/customers_controller.rb b/app/controllers/customers_controller.rb new file mode 100644 index 000000000..c3d9564cf --- /dev/null +++ b/app/controllers/customers_controller.rb @@ -0,0 +1,6 @@ +class CustomersController < ApplicationController + def index + customers = Customer.all + render json: customers.as_json(except: :updated_at), status: :ok + end +end diff --git a/config/routes.rb b/config/routes.rb index 787824f88..db9db0fb1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,5 @@ Rails.application.routes.draw do + get 'customers/', to: 'customers#index', as: 'customers' + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html end diff --git a/test/controllers/customers_controller_test.rb b/test/controllers/customers_controller_test.rb new file mode 100644 index 000000000..688ffc6de --- /dev/null +++ b/test/controllers/customers_controller_test.rb @@ -0,0 +1,50 @@ +require "test_helper" + +describe CustomersController do + describe "index" do + it "is a real working route - must return success" 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 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_instance_of Array + body.must_be :empty? + end + + it "returns customers with the required fields" do + keys = %w(account_credit address city id name phone postal_code registered_at state) + + get customers_path + + body = JSON.parse(response.body) + body.each do |customer| + customer.keys.sort.must_equal keys + end + end + end + +end diff --git a/test/test_helper.rb b/test/test_helper.rb index cd536c79c..ca07e5dcb 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,10 +1,11 @@ require 'simplecov' SimpleCov.start 'rails' ENV["RAILS_ENV"] = "test" + require File.expand_path("../../config/environment", __FILE__) require "rails/test_help" -require "minitest/rails" require "minitest/reporters" # for Colorized output +require "minitest/rails" # For colorful output! Minitest::Reporters.use!( From 7021a34c2a6e896715a321d507690f91374670be Mon Sep 17 00:00:00 2001 From: IsabelDePapel Date: Mon, 6 Nov 2017 15:44:30 -0800 Subject: [PATCH 11/32] add routes and index, show, create actions and tests for Movies controller. All tests passing --- app/controllers/movies_controller.rb | 32 ++++++ config/routes.rb | 2 + test/controllers/movies_controller_test.rb | 107 +++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 app/controllers/movies_controller.rb create mode 100644 test/controllers/movies_controller_test.rb diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb new file mode 100644 index 000000000..8a4b71be1 --- /dev/null +++ b/app/controllers/movies_controller.rb @@ -0,0 +1,32 @@ +class MoviesController < ApplicationController + def index + movies = Movie.all + render json: movies.as_json(except: [:created_at, :updated_at]), status: :ok + end + + def show + movie = Movie.find_by(id: params[:id]) + + if movie + render json: movie.as_json(except: [:created_at, :udpated_at]), status: :ok + else + render json: { ok: false }, status: :not_found + end + end + + def create + movie = Movie.create(movies_params) + + if movie.valid? + render json: movie.as_json(except: [:created_at, :updated_at]), status: :created + else + render json: { errors: movie.errors.messages }, status: :bad_request + end + end + + private + + def movies_params + return params.require(:movie).permit(:title, :overview, :release_date, :inventory) + end +end diff --git a/config/routes.rb b/config/routes.rb index db9db0fb1..df7ecbb02 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,6 @@ Rails.application.routes.draw do + resources :movies, only: [:index, :show, :create] + get 'customers/', to: 'customers#index', as: 'customers' # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb new file mode 100644 index 000000000..f68f089ff --- /dev/null +++ b/test/controllers/movies_controller_test.rb @@ -0,0 +1,107 @@ +require "test_helper" + +describe MoviesController do + describe "index" do + it "is a real working route - must return success" 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 the movies" do + get movies_path + body = JSON.parse(response.body) + + body.length.must_equal Movie.count + end + + it "returns an empty array if there are no movies" do + Movie.destroy_all + get movies_path + + must_respond_with :success + body = JSON.parse(response.body) + body.must_be_instance_of Array + body.must_be :empty? + end + + it "returns movies with the required fields" do + keys = %w(id inventory overview release_date title) + + get movies_path + body = JSON.parse(response.body) + body.each do |movie| + movie.keys.sort.must_equal keys + end + end + end + + describe "show" do + let(:dune) { movies(:dune) } + + it "can get a movie" do + get movie_path(dune.id) + must_respond_with :success + + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "id" + + Movie.find(body["id"]).title.must_equal dune.title + end + + it "returns not found if movie id is invalid" do + dune.destroy + get movie_path(dune.id) + must_respond_with :not_found + end + end + + describe "create" do + let(:movie_data) { + { title: "Nightamre before Xmas", + overview: "Xmas movie", + release_date: Date.new(1997-12-25), + inventory: 12 + } + } + + it "creates a new movie" do + proc { post movies_path, params: { movie: movie_data } }.must_change 'Movie.count', 1 + + must_respond_with :success + + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "id" + + Movie.find(body["id"]).title.must_equal movie_data[:title] + end + + it "returns bad request for an invalid movie" do + bad_data = movie_data.clone() + bad_data.delete(:title) + + proc { post movies_path, params: { movie: bad_data } }.wont_change 'Movie.count' + + 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 741e33caa7cf0f402bcf0fd77b607bfb2c89d827 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Tue, 7 Nov 2017 09:49:58 -0800 Subject: [PATCH 12/32] Add available_inventory and movies_checked_out_count method skeleton to movie and customer models. --- app/models/customer.rb | 6 +++++- app/models/movie.rb | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/models/customer.rb b/app/models/customer.rb index 297e2cd82..fc94fc3c2 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -4,5 +4,9 @@ class Customer < ApplicationRecord validates :name, presence: true validates :phone, presence: true - + + def movies_checked_out_count + return 0 + end + end diff --git a/app/models/movie.rb b/app/models/movie.rb index 3185225c4..f1309fbe3 100644 --- a/app/models/movie.rb +++ b/app/models/movie.rb @@ -4,5 +4,9 @@ class Movie < ApplicationRecord validates :title, presence: true validates :release_date, presence: true - validates :inventory, numericality: {only_integer: true, greater_than: 0 } + validates :inventory, numericality: {only_integer: true, greater_than: 0 } + + def available_inventory + return 0 + end end From 016104fffd1ed8152f5e1a3682e8000d783fdf09 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Tue, 7 Nov 2017 09:56:23 -0800 Subject: [PATCH 13/32] Add test plugs for movies_checked_out_count and available_inventory methods. --- test/models/customer_test.rb | 7 +++++++ test/models/movie_test.rb | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/test/models/customer_test.rb b/test/models/customer_test.rb index c2e2858cf..1ede49562 100644 --- a/test/models/customer_test.rb +++ b/test/models/customer_test.rb @@ -28,4 +28,11 @@ sally.movies[0].must_be_instance_of Movie end end + + describe "movies_checked_out_count" do + # edit once this method is built out + it "must return 0" do + sally.movies_checked_out_count.must_equal 0 + end + end end diff --git a/test/models/movie_test.rb b/test/models/movie_test.rb index 75a423cb4..9f9c57af7 100644 --- a/test/models/movie_test.rb +++ b/test/models/movie_test.rb @@ -34,4 +34,18 @@ gremlins.customers[0].must_be_instance_of Customer end end + + describe "available_inventory" do + it "should be decremented when a copy is checked out (rental creation)" do + + end + + it "should increment when a copy is checked back in" do + + end + + it "should not allow a rental to be created when available_inventory is 0" do + + end + end end From 8882e5990277fcabfd99cf60c84a486f45381393 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Tue, 7 Nov 2017 09:58:37 -0800 Subject: [PATCH 14/32] Add serializer gem. Generate serializers for customer, movie, and rental. --- Gemfile | 2 +- Gemfile.lock | 9 +++++++++ app/serializers/customer_serializer.rb | 3 +++ app/serializers/movie_serializer.rb | 3 +++ app/serializers/rental_serializer.rb | 3 +++ 5 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 app/serializers/customer_serializer.rb create mode 100644 app/serializers/movie_serializer.rb create mode 100644 app/serializers/rental_serializer.rb diff --git a/Gemfile b/Gemfile index 8e1adb0fc..cb935fb4c 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +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' # Use postgresql as the database for Active Record diff --git a/Gemfile.lock b/Gemfile.lock index 540d67160..3fbae9573 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) @@ -53,6 +58,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) @@ -74,6 +81,7 @@ GEM railties (>= 3.1.0) turbolinks json (2.0.2) + jsonapi-renderer (0.1.3) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -180,6 +188,7 @@ PLATFORMS ruby DEPENDENCIES + active_model_serializers awesome_print better_errors binding_of_caller diff --git a/app/serializers/customer_serializer.rb b/app/serializers/customer_serializer.rb new file mode 100644 index 000000000..367477232 --- /dev/null +++ b/app/serializers/customer_serializer.rb @@ -0,0 +1,3 @@ +class CustomerSerializer < ActiveModel::Serializer + attributes :id +end diff --git a/app/serializers/movie_serializer.rb b/app/serializers/movie_serializer.rb new file mode 100644 index 000000000..73224018d --- /dev/null +++ b/app/serializers/movie_serializer.rb @@ -0,0 +1,3 @@ +class MovieSerializer < ActiveModel::Serializer + attributes :id +end diff --git a/app/serializers/rental_serializer.rb b/app/serializers/rental_serializer.rb new file mode 100644 index 000000000..5585f1ea2 --- /dev/null +++ b/app/serializers/rental_serializer.rb @@ -0,0 +1,3 @@ +class RentalSerializer < ActiveModel::Serializer + attributes :id +end From 0854f9fb642775de380462f392d234a78bd749f7 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Tue, 7 Nov 2017 10:03:21 -0800 Subject: [PATCH 15/32] Add attributes to serializer files. --- app/serializers/customer_serializer.rb | 2 +- app/serializers/movie_serializer.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/serializers/customer_serializer.rb b/app/serializers/customer_serializer.rb index 367477232..645cbd8d5 100644 --- a/app/serializers/customer_serializer.rb +++ b/app/serializers/customer_serializer.rb @@ -1,3 +1,3 @@ class CustomerSerializer < ActiveModel::Serializer - attributes :id + attributes :id, :name, :phone, :registered_at, :postal_code, :movies_checked_out_count end diff --git a/app/serializers/movie_serializer.rb b/app/serializers/movie_serializer.rb index 73224018d..510aad5d6 100644 --- a/app/serializers/movie_serializer.rb +++ b/app/serializers/movie_serializer.rb @@ -1,3 +1,3 @@ class MovieSerializer < ActiveModel::Serializer - attributes :id + attributes :id, :title, :overview, :release_date, :inventory, :available_inventory end From 3a82d5093506316c40ff30ac722dc5918ed928f8 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Tue, 7 Nov 2017 10:05:36 -0800 Subject: [PATCH 16/32] Remove as_json code from controllers where needed. All Postman smoke tests passing. Wave 2 complete. --- app/controllers/customers_controller.rb | 2 +- app/controllers/movies_controller.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/customers_controller.rb b/app/controllers/customers_controller.rb index c3d9564cf..9b57781d6 100644 --- a/app/controllers/customers_controller.rb +++ b/app/controllers/customers_controller.rb @@ -1,6 +1,6 @@ class CustomersController < ApplicationController def index customers = Customer.all - render json: customers.as_json(except: :updated_at), status: :ok + render json: customers, status: :ok end end diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index 8a4b71be1..6f94eb061 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -8,7 +8,7 @@ def show movie = Movie.find_by(id: params[:id]) if movie - render json: movie.as_json(except: [:created_at, :udpated_at]), status: :ok + render json: movie, status: :ok else render json: { ok: false }, status: :not_found end @@ -18,7 +18,7 @@ def create movie = Movie.create(movies_params) if movie.valid? - render json: movie.as_json(except: [:created_at, :updated_at]), status: :created + render json: movie, status: :created else render json: { errors: movie.errors.messages }, status: :bad_request end From bca860f66fed4b5317095041854bd5fcf9056413 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Tue, 7 Nov 2017 10:28:36 -0800 Subject: [PATCH 17/32] Add returned column to rentals table. --- db/migrate/20171107182621_add_returned_column_to_rentals.rb | 5 +++++ db/schema.rb | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20171107182621_add_returned_column_to_rentals.rb diff --git a/db/migrate/20171107182621_add_returned_column_to_rentals.rb b/db/migrate/20171107182621_add_returned_column_to_rentals.rb new file mode 100644 index 000000000..b625e3298 --- /dev/null +++ b/db/migrate/20171107182621_add_returned_column_to_rentals.rb @@ -0,0 +1,5 @@ +class AddReturnedColumnToRentals < ActiveRecord::Migration[5.1] + def change + add_column :rentals, :returned, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 328fb4122..e23176bf3 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: 20171106215500) do +ActiveRecord::Schema.define(version: 20171107182621) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -42,6 +42,7 @@ t.date "due_date" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.boolean "returned", default: false t.index ["customer_id"], name: "index_rentals_on_customer_id" t.index ["movie_id"], name: "index_rentals_on_movie_id" end From 6624110bb5f875ec3109a05c690b58c34e6ca83f Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Tue, 7 Nov 2017 12:54:19 -0800 Subject: [PATCH 18/32] Generate rentals controller. Add set_due_date method to model. --- app/controllers/rentals_controller.rb | 2 ++ app/models/rental.rb | 9 +++++++++ 2 files 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..58c72b791 --- /dev/null +++ b/app/controllers/rentals_controller.rb @@ -0,0 +1,2 @@ +class RentalsController < ApplicationController +end diff --git a/app/models/rental.rb b/app/models/rental.rb index 74e919fe4..6248cdc5a 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -1,6 +1,15 @@ class Rental < ApplicationRecord + RENTAL_PERIOD = 5 belongs_to :movie belongs_to :customer + before_create :set_due_date + validates :due_date, presence: true + + private + + def set_due_date + self.due_date = Date.today + RENTAL_PERIOD + end end From 6b93ed2a3ed77c342d1269504eb05289eef5215d Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Tue, 7 Nov 2017 12:55:02 -0800 Subject: [PATCH 19/32] Rollback due_date default setting from previous migration. --- db/migrate/20171107185720_set_default_due_date.rb | 5 +++++ db/migrate/20171107191532_rollback_due_date_default.rb | 5 +++++ db/schema.rb | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20171107185720_set_default_due_date.rb create mode 100644 db/migrate/20171107191532_rollback_due_date_default.rb diff --git a/db/migrate/20171107185720_set_default_due_date.rb b/db/migrate/20171107185720_set_default_due_date.rb new file mode 100644 index 000000000..ce4e8df9e --- /dev/null +++ b/db/migrate/20171107185720_set_default_due_date.rb @@ -0,0 +1,5 @@ +class SetDefaultDueDate < ActiveRecord::Migration[5.1] + def change + change_column :rentals, :due_date, :date, :default => Date.today + 5 + end +end diff --git a/db/migrate/20171107191532_rollback_due_date_default.rb b/db/migrate/20171107191532_rollback_due_date_default.rb new file mode 100644 index 000000000..c2aae874c --- /dev/null +++ b/db/migrate/20171107191532_rollback_due_date_default.rb @@ -0,0 +1,5 @@ +class RollbackDueDateDefault < ActiveRecord::Migration[5.1] + def change + change_column_default(:rentals, :due_date, nil) + end +end diff --git a/db/schema.rb b/db/schema.rb index e23176bf3..b1983b47e 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: 20171107182621) do +ActiveRecord::Schema.define(version: 20171107191532) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" From c099f19feff6229a3ea2ab3c7ec460f5eb0a2896 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Tue, 7 Nov 2017 12:57:00 -0800 Subject: [PATCH 20/32] Add rentals controller and model tests. Adjust rental and movie fixtures. --- test/controllers/rentals_controller_test.rb | 47 +++++++++++++++++++++ test/fixtures/movies.yml | 6 +++ test/fixtures/rentals.yml | 8 ++++ test/models/rental_test.rb | 15 +++++++ 4 files changed, 76 insertions(+) create mode 100644 test/controllers/rentals_controller_test.rb diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb new file mode 100644 index 000000000..7d90cf9fd --- /dev/null +++ b/test/controllers/rentals_controller_test.rb @@ -0,0 +1,47 @@ +require "test_helper" + +describe RentalsController do + describe "checkout" do + it "must create a rental record" do + proc { + post checkout_path, params: {movie: movies(:dune), customer: customers(:harry)} + }.must_change 'Rental.count', 1 + end + + it "must set a due_date 5 days in the future" do + post checkout_path, params: {movie: movies(:gremlins), customer: customers(:sally)} + rental = Rental.last + + rental.due_date.must_equal Date.now + 5 + end + + it "must decrement the available_inventory for the movie" do + movie = movies(:gremlins) + start_inventory = movie.available_inventory + post checkout_path params: {movie: movie, customer: customers(:harry)} + movie.available_inventory.must_equal start_inventory - 1 + end + + it "will not create a rental record and will return an error message if the available_inventory is 0" do + movie = movies(:dune) + movie.available_inventory = 0 + post checkout_path params: {movie: movie, customer: customers(:sally)} + end + end + + describe "checkin" do + it "must change the value of returned field to true" do + rental = rentals(:one) + rental.returned.must_be false + put checkin_path(rental.id) + rental.returned.must_be true + end + + it "must increment the available_inventory of the movie" do + movie = movies(:gremlins) + start_inventory = movie.available_inventory + put checkin_path(rentals(:two).id) + movie.available_inventory.must_equal start_inventory + 1 + end + end +end diff --git a/test/fixtures/movies.yml b/test/fixtures/movies.yml index def239a9d..7762b0900 100644 --- a/test/fixtures/movies.yml +++ b/test/fixtures/movies.yml @@ -11,3 +11,9 @@ gremlins: overview: MyText release_date: 1982-10-31 inventory: 2 + +tnes: + title: The Neverending Story + overview: Fantastical + release_date: 1985-04-30 + inventory: 3 diff --git a/test/fixtures/rentals.yml b/test/fixtures/rentals.yml index 1e08ecc43..0215004ec 100644 --- a/test/fixtures/rentals.yml +++ b/test/fixtures/rentals.yml @@ -4,8 +4,16 @@ one: movie: dune customer: sally due_date: 2017-11-06 + returned: false two: movie: gremlins customer: harry due_date: 2017-11-08 + returned: false + +returned: + movie: the neverending story + customer: harry + due_date: 2017-11-02 + returned: true diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb index f82aa9827..336c2e1df 100644 --- a/test/models/rental_test.rb +++ b/test/models/rental_test.rb @@ -26,4 +26,19 @@ rental.customer.must_equal nil end end + + describe "checkin" do + it "must turn the record to read-only after update" do + rental = rentals(:one) + rental.readonly?.must_equal false + put checkin_path(rental.id) + rental.readonly?.must_equal true + end + + it "must return an error message when attempting to update a returned rental" do + rental = rentals(:returned) + put checkin_path(rental.id) + must_respond_with :bad_request + end + end end From 12b47b2299215edd959c00723ff7238bfeef5a18 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Tue, 7 Nov 2017 13:41:16 -0800 Subject: [PATCH 21/32] Adjust rentals controller and model tests. No errors at run time.: --- test/controllers/customers_controller_test.rb | 2 +- test/controllers/rentals_controller_test.rb | 14 ++++++++++---- test/models/rental_test.rb | 6 ------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/test/controllers/customers_controller_test.rb b/test/controllers/customers_controller_test.rb index 688ffc6de..7a057a40f 100644 --- a/test/controllers/customers_controller_test.rb +++ b/test/controllers/customers_controller_test.rb @@ -36,7 +36,7 @@ end it "returns customers with the required fields" do - keys = %w(account_credit address city id name phone postal_code registered_at state) + keys = %w(id movies_checked_out_count name phone postal_code registered_at) get customers_path diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 7d90cf9fd..0d76d90fe 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -12,7 +12,7 @@ post checkout_path, params: {movie: movies(:gremlins), customer: customers(:sally)} rental = Rental.last - rental.due_date.must_equal Date.now + 5 + rental.due_date.must_equal Date.today + 5 end it "must decrement the available_inventory for the movie" do @@ -24,7 +24,7 @@ it "will not create a rental record and will return an error message if the available_inventory is 0" do movie = movies(:dune) - movie.available_inventory = 0 + # movie.available_inventory = 0 post checkout_path params: {movie: movie, customer: customers(:sally)} end end @@ -32,9 +32,9 @@ describe "checkin" do it "must change the value of returned field to true" do rental = rentals(:one) - rental.returned.must_be false + rental.returned.must_equal false put checkin_path(rental.id) - rental.returned.must_be true + rental.returned.must_equal true end it "must increment the available_inventory of the movie" do @@ -43,5 +43,11 @@ put checkin_path(rentals(:two).id) movie.available_inventory.must_equal start_inventory + 1 end + + it "must return an error message when attempting to update a returned rental" do + rental = rentals(:returned) + put checkin_path(rental.id) + must_respond_with :bad_request + end end end diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb index 336c2e1df..c458d33db 100644 --- a/test/models/rental_test.rb +++ b/test/models/rental_test.rb @@ -34,11 +34,5 @@ put checkin_path(rental.id) rental.readonly?.must_equal true end - - it "must return an error message when attempting to update a returned rental" do - rental = rentals(:returned) - put checkin_path(rental.id) - must_respond_with :bad_request - end end end From 549a8b3bffa469a84ab9a86fb7dd4658f9ec776f Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Tue, 7 Nov 2017 13:41:54 -0800 Subject: [PATCH 22/32] Add rentals routes for checkin, checkout, and index. --- config/routes.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/routes.rb b/config/routes.rb index df7ecbb02..b47978900 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,5 +3,9 @@ get 'customers/', to: 'customers#index', as: 'customers' + post 'rentals/checkout', to: 'rentals#create', as: 'checkout' + put 'rentals/:id/checkin', to: 'rentals#update', as: 'checkin' + get 'rentals/overdues', to: 'rentals#index', as: 'rentals' + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html end From 17f286e8e02dbaa8f7fcdb0abea286634542db89 Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Tue, 7 Nov 2017 15:20:19 -0800 Subject: [PATCH 23/32] Add controller methods to rentals controller. Move availabile? method to movie model. All tests passing, 82% coverage in rentals controller. --- app/controllers/rentals_controller.rb | 34 +++++++++++++++++++++ app/models/movie.rb | 10 +++++- app/models/rental.rb | 21 +++++++++++-- test/controllers/rentals_controller_test.rb | 10 +++--- 4 files changed, 67 insertions(+), 8 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 58c72b791..589b9c476 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -1,2 +1,36 @@ class RentalsController < ApplicationController + def index + rentals = Rental.all + end + + def create + rental = Rental.new(movie_id: params[:movie], customer_id: params[:customer]) + movie = Movie.find_by(id: params[:movie]) + + if movie.available? + if rental.save + render json: rental.as_json(only: [:id, :movie, :customer, :due_date]), status: :created + else + render json: {errors: rental.errors.messages}, status: :bad_request + end + else + render json: {ok: false}, status: :bad_request + end + end + + def update + rental = Rental.find_by(id: params[:id]) + + if rental.checkin + render json: rental.as_json(except: [:created_at]), status: :ok + else + render json: {errors: rental.errors.messages}, status: :bad_request + end + end + + private + + # def rentals_params + # params.require(:rental).permit(:movie, :customer) + # end end diff --git a/app/models/movie.rb b/app/models/movie.rb index f1309fbe3..bdc992e14 100644 --- a/app/models/movie.rb +++ b/app/models/movie.rb @@ -7,6 +7,14 @@ class Movie < ApplicationRecord validates :inventory, numericality: {only_integer: true, greater_than: 0 } def available_inventory - return 0 + unavailable = Rental.where(movie_id: self.id, returned: false).count + + available = self.inventory - unavailable + + return available + end + + def available? + return self.available_inventory > 0 end end diff --git a/app/models/rental.rb b/app/models/rental.rb index 6248cdc5a..ca63850b1 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -3,13 +3,30 @@ class Rental < ApplicationRecord belongs_to :movie belongs_to :customer - before_create :set_due_date + before_validation :set_due_date validates :due_date, presence: true + def checkin + unless self.returned + self.returned = true + if self.save + self.readonly! + return true + else + return false + end + else + errors.add(:returned, "Rental has already been returned. Cannot update.") + return false + end + end + private def set_due_date - self.due_date = Date.today + RENTAL_PERIOD + if self.due_date.nil? + self.due_date = Date.today + RENTAL_PERIOD + end end end diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 0d76d90fe..a2b376c37 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -4,12 +4,12 @@ describe "checkout" do it "must create a rental record" do proc { - post checkout_path, params: {movie: movies(:dune), customer: customers(:harry)} + post checkout_path, params: {movie: movies(:dune).id, customer: customers(:harry).id} }.must_change 'Rental.count', 1 end it "must set a due_date 5 days in the future" do - post checkout_path, params: {movie: movies(:gremlins), customer: customers(:sally)} + post checkout_path, params: {movie: movies(:gremlins).id, customer: customers(:sally).id} rental = Rental.last rental.due_date.must_equal Date.today + 5 @@ -18,14 +18,14 @@ it "must decrement the available_inventory for the movie" do movie = movies(:gremlins) start_inventory = movie.available_inventory - post checkout_path params: {movie: movie, customer: customers(:harry)} + post checkout_path params: {movie: movie.id, customer: customers(:harry).id} movie.available_inventory.must_equal start_inventory - 1 end it "will not create a rental record and will return an error message if the available_inventory is 0" do movie = movies(:dune) # movie.available_inventory = 0 - post checkout_path params: {movie: movie, customer: customers(:sally)} + post checkout_path params: {movie: movie.id, customer: customers(:sally).id} end end @@ -34,7 +34,7 @@ rental = rentals(:one) rental.returned.must_equal false put checkin_path(rental.id) - rental.returned.must_equal true + rental.reload.returned.must_equal true end it "must increment the available_inventory of the movie" do From 7ececb271f0c3e6134a9ea613b0dc6ec873925ce Mon Sep 17 00:00:00 2001 From: IsabelDePapel Date: Tue, 7 Nov 2017 15:58:25 -0800 Subject: [PATCH 24/32] fix wrong num in Customer test for rentals collection. Still have issues w params in Movies controller --- app/controllers/movies_controller.rb | 7 ++++--- test/models/customer_test.rb | 2 +- test/models/rental_test.rb | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index 6f94eb061..b4c74796d 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -15,10 +15,11 @@ def show end def create - movie = Movie.create(movies_params) + byebug + movie = Movie.create(params[:movie]) if movie.valid? - render json: movie, status: :created + render json: movie, status: :ok else render json: { errors: movie.errors.messages }, status: :bad_request end @@ -27,6 +28,6 @@ def create private def movies_params - return params.require(:movie).permit(:title, :overview, :release_date, :inventory) + return params.permit(:title, :overview, :release_date, :inventory) end end diff --git a/test/models/customer_test.rb b/test/models/customer_test.rb index 1ede49562..b8bafcdc4 100644 --- a/test/models/customer_test.rb +++ b/test/models/customer_test.rb @@ -19,7 +19,7 @@ describe "relationships" do it "must have a collection of rentals" do harry.must_respond_to :rentals - harry.rentals.length.must_equal 1 + harry.rentals.length.must_equal 2 harry.rentals[0].must_be_kind_of Rental end diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb index c458d33db..d2d89eb39 100644 --- a/test/models/rental_test.rb +++ b/test/models/rental_test.rb @@ -31,7 +31,7 @@ it "must turn the record to read-only after update" do rental = rentals(:one) rental.readonly?.must_equal false - put checkin_path(rental.id) + rental.checkin.must_equal true rental.readonly?.must_equal true end end From 9da0080eb3a20b5a24f5a644057d341e7fb1a31b Mon Sep 17 00:00:00 2001 From: enigmagnetic Date: Tue, 7 Nov 2017 16:03:53 -0800 Subject: [PATCH 25/32] Edit movies controller strong params method to access object correctly. All tests passing. --- app/controllers/movies_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index 6f94eb061..3116675bc 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -27,6 +27,6 @@ def create private def movies_params - return params.require(:movie).permit(:title, :overview, :release_date, :inventory) + return params[:movie].permit(:title, :overview, :release_date, :inventory) end end From f5d02f2226d0754e62e0853bb8bcb3184d700455 Mon Sep 17 00:00:00 2001 From: IsabelDePapel Date: Wed, 8 Nov 2017 11:10:28 -0800 Subject: [PATCH 26/32] add checks if movie and customer exist to rental create (checkin) --- app/controllers/rentals_controller.rb | 29 ++++++- test/controllers/rentals_controller_test.rb | 83 ++++++++++++++++++++- 2 files changed, 107 insertions(+), 5 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 589b9c476..870866fe9 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -1,26 +1,47 @@ class RentalsController < ApplicationController def index - rentals = Rental.all + rentals = Rental.where('due_date < ?', Date.today).where(returned: false) + + render json: rentals, status: :ok end def create rental = Rental.new(movie_id: params[:movie], customer_id: params[:customer]) movie = Movie.find_by(id: params[:movie]) + customer = Customer.find_by(id: params[:customer]) + + # if movie doesn't exist + unless movie + render json: { errors: { id: "Movie id #{params[:movie]} not found" } }, status: :not_found + return + end - if movie.available? + # if customer doesn't exist + unless customer + render json: { errors: { id: "Customer id #{params[:customer]} not found" } }, status: :not_found + return + end + + # check if movie is available + if movie.available? if rental.save render json: rental.as_json(only: [:id, :movie, :customer, :due_date]), status: :created else - render json: {errors: rental.errors.messages}, status: :bad_request + render json: { errors: rental.errors.messages }, status: :bad_request end else - render json: {ok: false}, status: :bad_request + render json: { errors: { inventory: "Movie #{movie.title} isn't available" } }, status: :bad_request end end def update rental = Rental.find_by(id: params[:id]) + unless rental + render json: { errors: { id: "Rental id #{params[:id]} not found" } }, status: :not_found + return + end + if rental.checkin render json: rental.as_json(except: [:created_at]), status: :ok else diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index a2b376c37..f07184ee4 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -1,4 +1,5 @@ require "test_helper" +require 'awesome_print' describe RentalsController do describe "checkout" do @@ -24,8 +25,47 @@ it "will not create a rental record and will return an error message if the available_inventory is 0" do movie = movies(:dune) - # movie.available_inventory = 0 + + movie.inventory = 1 + movie.save + post checkout_path params: {movie: movie.id, customer: customers(:sally).id} + + must_respond_with :bad_request + + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "errors" + body["errors"].must_include "inventory" + end + + it "will respond with not found if trying to rent a movie that doesn't exist" do + movie = movies(:gremlins) + movie.destroy + + post checkout_path params: { movie: movie.id, customer: customers(:sally).id } + + must_respond_with :not_found + + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "errors" + body["errors"].must_include "id" + end + + it "will respond with not found if trying to rent a movie for a customer that doesn't exist" do + + sally = customers(:sally) + sally.destroy + + post checkout_path params: { movie: movies(:gremlins).id, customer: sally.id } + + must_respond_with :not_found + + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "errors" + body["errors"].must_include "id" end end @@ -48,6 +88,47 @@ rental = rentals(:returned) put checkin_path(rental.id) must_respond_with :bad_request + + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "errors" end + + it "must return an error message if given invalid rental id" do + rental = rentals(:one) + rental.destroy + + put checkin_path(rental.id) + must_respond_with :not_found + + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "errors" + body["errors"].must_include "id" + end + end + + describe "overdue" do + it "returns the list of overdue movies" do + get rentals_path + + must_respond_with :success + + body = JSON.parse(response.body) + body.must_be_kind_of Array + end + + it "returns empty array if there are no overdue movies" do + Rental.destroy_all + + get rentals_path + + must_respond_with :success + + body = JSON.parse(response.body) + body.must_be_kind_of Array + body.must_be :empty? + end + end end From 8f01d9283c8fd8fc79923904720e19400f815ce1 Mon Sep 17 00:00:00 2001 From: IsabelDePapel Date: Wed, 8 Nov 2017 11:10:51 -0800 Subject: [PATCH 27/32] implement movies_checked_out_count method in customer model --- app/models/customer.rb | 2 +- test/models/customer_test.rb | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/models/customer.rb b/app/models/customer.rb index fc94fc3c2..29b951311 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -6,7 +6,7 @@ class Customer < ApplicationRecord validates :phone, presence: true def movies_checked_out_count - return 0 + return Rental.where(customer_id: self.id, returned: false).count end end diff --git a/test/models/customer_test.rb b/test/models/customer_test.rb index b8bafcdc4..9a4a53bc8 100644 --- a/test/models/customer_test.rb +++ b/test/models/customer_test.rb @@ -31,8 +31,14 @@ describe "movies_checked_out_count" do # edit once this method is built out - it "must return 0" do - sally.movies_checked_out_count.must_equal 0 + it "must return the number of movies currently checked out" do + sally.movies_checked_out_count.must_equal 1 + end + + it "will return 0 if no movies are checked out" do + rentals(:two).checkin + + harry.movies_checked_out_count.must_equal 0 end end end From a9cf5ff3fd12d396b5afd58e0f0bdf72f527e69e Mon Sep 17 00:00:00 2001 From: IsabelDePapel Date: Wed, 8 Nov 2017 11:11:49 -0800 Subject: [PATCH 28/32] fix error messages and status in movies controller --- app/controllers/movies_controller.rb | 6 +++--- test/controllers/movies_controller_test.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index b4c74796d..22e341533 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -15,8 +15,8 @@ def show end def create - byebug - movie = Movie.create(params[:movie]) + # byebug + movie = Movie.create(movies_params) if movie.valid? render json: movie, status: :ok @@ -28,6 +28,6 @@ def create private def movies_params - return params.permit(:title, :overview, :release_date, :inventory) + params.permit(:title, :overview, :release_date, :inventory) end end diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb index f68f089ff..9f81361d6 100644 --- a/test/controllers/movies_controller_test.rb +++ b/test/controllers/movies_controller_test.rb @@ -70,7 +70,7 @@ describe "create" do let(:movie_data) { - { title: "Nightamre before Xmas", + { title: "Nightmare before Xmas", overview: "Xmas movie", release_date: Date.new(1997-12-25), inventory: 12 @@ -78,7 +78,7 @@ } it "creates a new movie" do - proc { post movies_path, params: { movie: movie_data } }.must_change 'Movie.count', 1 + proc { post movies_path, params: movie_data }.must_change 'Movie.count', 1 must_respond_with :success From cf56d314b1af55efdaf216063a91f7b839cbc346 Mon Sep 17 00:00:00 2001 From: IsabelDePapel Date: Wed, 8 Nov 2017 11:12:11 -0800 Subject: [PATCH 29/32] implement rentals#overdue (index) and add fields to rental serializer --- app/models/rental.rb | 22 +++++++++++++++++++--- app/serializers/rental_serializer.rb | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/models/rental.rb b/app/models/rental.rb index ca63850b1..ce61d0f41 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -3,7 +3,7 @@ class Rental < ApplicationRecord belongs_to :movie belongs_to :customer - before_validation :set_due_date + before_validation :set_due_date, on: :create validates :due_date, presence: true @@ -25,8 +25,24 @@ def checkin private def set_due_date - if self.due_date.nil? + # if self.due_date.nil? self.due_date = Date.today + RENTAL_PERIOD - end + # end + end + + def movie_title + return self.movie.title + end + + def customer_name + return self.customer.name + end + + def customer_postal_code + return self.customer.postal_code + end + + def checkout_date + return self.created_at end end diff --git a/app/serializers/rental_serializer.rb b/app/serializers/rental_serializer.rb index 5585f1ea2..f8e121847 100644 --- a/app/serializers/rental_serializer.rb +++ b/app/serializers/rental_serializer.rb @@ -1,3 +1,3 @@ class RentalSerializer < ActiveModel::Serializer - attributes :id + attributes :id, :movie_title, :customer_id, :customer_name, :customer_postal_code, :checkout_date, :due_date end From 8b765ad6f836eed59ee949d336ea196e3c09b791 Mon Sep 17 00:00:00 2001 From: IsabelDePapel Date: Wed, 8 Nov 2017 11:15:43 -0800 Subject: [PATCH 30/32] add filters to simple cov to filter out mailers, jobs, and channels files --- test/test_helper.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index ca07e5dcb..dee61fde0 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,5 +1,9 @@ require 'simplecov' -SimpleCov.start 'rails' +SimpleCov.start 'rails' do + add_filter "/mailers/" + add_filter "/jobs/" + add_filter "/channels/" +end ENV["RAILS_ENV"] = "test" require File.expand_path("../../config/environment", __FILE__) From 55d6111257aab8bf21bc1d90f3dcd74ee61dffb9 Mon Sep 17 00:00:00 2001 From: IsabelDePapel Date: Wed, 8 Nov 2017 14:10:50 -0800 Subject: [PATCH 31/32] add test for available_inventory in movie model --- test/models/movie_test.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/models/movie_test.rb b/test/models/movie_test.rb index 9f9c57af7..44cbed2fc 100644 --- a/test/models/movie_test.rb +++ b/test/models/movie_test.rb @@ -37,15 +37,22 @@ describe "available_inventory" do it "should be decremented when a copy is checked out (rental creation)" do + rented = movies(:tnes) + start_count = rented.available_inventory + Rental.create!(movie: rented, customer: (customers(:sally))) + + rented.available_inventory.must_equal start_count - 1 end it "should increment when a copy is checked back in" do + rented = movies(:gremlins) + start_count = rented.available_inventory - end - - it "should not allow a rental to be created when available_inventory is 0" do + Rental.find_by(movie_id: rented.id).checkin + rented.available_inventory.must_equal start_count + 1 end + end end From f3660f9fd54a89919c38918c179f65cf93bfe919 Mon Sep 17 00:00:00 2001 From: IsabelDePapel Date: Wed, 8 Nov 2017 14:20:04 -0800 Subject: [PATCH 32/32] update erd diagram --- erd.pdf | Bin 25703 -> 32147 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/erd.pdf b/erd.pdf index cccc49b24f15c5e5bf52a37893d463a8cc0da37f..caf3973da555fb2613f1592bd3df3ae4046f6a6f 100644 GIT binary patch delta 27284 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 delta 21225 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