From fb521d691e5a550b6cdc74e35530bddc1054c288 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Mon, 6 Nov 2017 11:05:19 -0800 Subject: [PATCH 01/42] initial commit --- .gitignore | 16 + Gemfile | 53 ++++ Gemfile.lock | 170 ++++++++++ README.md | 300 +----------------- 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 | 26 ++ tmp/.keep | 0 vendor/.keep | 0 54 files changed, 939 insertions(+), 287 deletions(-) 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..4ae55c93d --- /dev/null +++ b/Gemfile @@ -0,0 +1,53 @@ +source 'https://rubygems.org' + +git_source(:github) do |repo_name| + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") + "https://github.com/#{repo_name}.git" +end + + +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '~> 5.1.4' +# Use postgresql as the database for Active Record +gem 'pg', '~> 0.18' +# Use Puma as the app server +gem 'puma', '~> 3.7' +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +# gem 'jbuilder', '~> 2.5' +# Use Redis adapter to run Action Cable in production +# gem 'redis', '~> 3.0' +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# Use Capistrano for deployment +# gem 'capistrano-rails', group: :development + +# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible +# gem 'rack-cors' + +group :development, :test do + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] +end + +group :development do + gem 'listen', '>= 3.0.5', '< 3.2' + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' + gem 'spring-watcher-listen', '~> 2.0.0' +end + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] + +gem 'jquery-turbolinks' +group :development do + gem 'better_errors' + gem 'pry-rails' + gem 'binding_of_caller' +end + +group :test do + gem 'minitest-rails' + gem 'minitest-reporters' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000..9593b240a --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,170 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.1.4) + actionpack (= 5.1.4) + nio4r (~> 2.0) + websocket-driver (~> 0.6.1) + actionmailer (5.1.4) + actionpack (= 5.1.4) + actionview (= 5.1.4) + activejob (= 5.1.4) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.1.4) + actionview (= 5.1.4) + activesupport (= 5.1.4) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.1.4) + activesupport (= 5.1.4) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.1.4) + activesupport (= 5.1.4) + globalid (>= 0.3.6) + activemodel (5.1.4) + activesupport (= 5.1.4) + activerecord (5.1.4) + activemodel (= 5.1.4) + activesupport (= 5.1.4) + arel (~> 8.0) + activesupport (5.1.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (~> 0.7) + minitest (~> 5.1) + tzinfo (~> 1.1) + ansi (1.5.0) + arel (8.0.0) + better_errors (2.4.0) + coderay (>= 1.0.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) + binding_of_caller (0.7.3) + debug_inspector (>= 0.0.1) + builder (3.2.3) + byebug (9.1.0) + coderay (1.1.2) + concurrent-ruby (1.0.5) + crass (1.0.2) + debug_inspector (0.0.3) + erubi (1.7.0) + ffi (1.9.18) + globalid (0.4.1) + activesupport (>= 4.2.0) + i18n (0.9.1) + concurrent-ruby (~> 1.0) + jquery-turbolinks (2.1.0) + railties (>= 3.1.0) + turbolinks + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + loofah (2.1.1) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.0) + mini_mime (>= 0.1.1) + method_source (0.9.0) + mini_mime (0.1.4) + mini_portile2 (2.3.0) + minitest (5.10.3) + minitest-rails (3.0.0) + minitest (~> 5.8) + railties (~> 5.0) + minitest-reporters (1.1.18) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + nio4r (2.1.0) + nokogiri (1.8.1) + mini_portile2 (~> 2.3.0) + pg (0.21.0) + pry (0.11.2) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry-rails (0.3.6) + pry (>= 0.10.4) + puma (3.10.0) + rack (2.0.3) + rack-test (0.7.0) + rack (>= 1.0, < 3) + rails (5.1.4) + actioncable (= 5.1.4) + actionmailer (= 5.1.4) + actionpack (= 5.1.4) + actionview (= 5.1.4) + activejob (= 5.1.4) + activemodel (= 5.1.4) + activerecord (= 5.1.4) + activesupport (= 5.1.4) + bundler (>= 1.3.0) + railties (= 5.1.4) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.3) + loofah (~> 2.0) + railties (5.1.4) + actionpack (= 5.1.4) + activesupport (= 5.1.4) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (12.2.1) + rb-fsevent (0.10.2) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + ruby-progressbar (1.9.0) + ruby_dep (1.5.0) + spring (2.0.2) + activesupport (>= 4.2) + spring-watcher-listen (2.0.1) + listen (>= 2.7, < 4.0) + spring (>= 1.2, < 3.0) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + thor (0.20.0) + thread_safe (0.3.6) + turbolinks (5.0.1) + turbolinks-source (~> 5) + turbolinks-source (5.0.3) + tzinfo (1.2.4) + thread_safe (~> 0.1) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) + +PLATFORMS + ruby + +DEPENDENCIES + better_errors + binding_of_caller + byebug + jquery-turbolinks + listen (>= 3.0.5, < 3.2) + minitest-rails + minitest-reporters + pg (~> 0.18) + pry-rails + puma (~> 3.7) + rails (~> 5.1.4) + spring + spring-watcher-listen (~> 2.0.0) + tzinfo-data + +BUNDLED WITH + 1.16.0.pre.2 diff --git a/README.md b/README.md index f8467027a..7db80e4ca 100644 --- a/README.md +++ b/README.md @@ -1,298 +1,24 @@ -# Project: VideoStoreAPI -The goal of this project is to create a system that a video store (remember those?) could use to track their inventory of rental videos and their list of customers. +# README -We will use Rails to construct a RESTful API. The purpose of this API is to quickly serve information about the store's video collection, customer information, and to update rental status. This repository provides two JSON datafiles to serve as the initial seeds for this system. +This README would normally document whatever steps are necessary to get the +application up and running. -This is a pair project. You and your partner should use all the techniques we've learned so far to keep yourselves organized and on track, and ensure that no requirements slip through the cracks. +Things you may want to cover: -## Learning Goals -Upon completing this project, students should be able to: +* Ruby version -- Build an ERD and set up ActiveRecord models for a given dataset / use-case -- Expose database contents through a web API -- Respond reasonably to bad user data in the context of an API -- Verify the correctness of an API using controller tests +* System dependencies -This is a [stage 2](https://github.com/Ada-Developers-Academy/pedagogy/blob/master/rule-of-three.md) project. +* Configuration -## Success Criteria -Your project will be evaluated against the following requirements: +* Database creation -- API conformance - - The provided smoke tests should pass (see the subfolder) - - Bad data sent to the API should result in an appropriate status code and helpful error -- Test coverage - - Models: All relations, validations, and custom model methods should include at least one positive and one negative test case - - Controllers: Every API endpoint should include at least one positive and one negative test case -- Style and Organization - - Business logic should be live in models +* Database initialization -## Project Baseline -- Read the API Requirements below and create a pseudo-code "routes" file that specifies - - The _endpoints_ your API will need - - The _HTTP verbs_ each endpoint will use - - Any data that must be provided to the endpoint in order for it to do its work -- Read the Seed Data description below and, bearing in mind the API Requirements, create an ERD for your database that specifies - - The _models_ your database will require - - The _attributes_ for each model - - Any _relationships_ between models -- Create a new Rails app to serve as the API - - **Create the rails app with:** `$ rails new . --api` -- Create a route that responds to `/zomg` that serves a json-encoded "it works!" +* How to run the test suite -## Wave 1: Database Models, Tables, & Seeds -- Generate Rails models and associations to match your ERD -- Use the provided seed script `db/seeds.rb` to import the provided JSON data into your database +* Services (job queues, cache servers, search engines, etc.) -### Seed Data -`movies.json` contains information about the videos available to rent at the store. The data is presented as an array of objects, with each object having the following key-value pairs: +* Deployment instructions -| Field | Datatype | Description -|----------------|----------|------------ -| `title` | string | The title of the film -| `overview` | string | A short plot synopsis -| `release_date` | string | `YYYY-MM-DD`, Day the film was originally released -| `inventory` | integer | How many copies of the film the video store owns - -`customers.json` contains information about the customers that have rented with the store in the past. The data is presented as, you guessed it, an array of objects, with each object have the following key-value pairs: - -| Field | Datatype | Description -|------------------|----------|------------ -| `name` | string | The customer's name -| `registered_at` | string | `Wed, 29 Apr 2015 07:54:14 -0700`, When the customer first visited the store -| `address` | string | Street address -| `city` | string |   -| `state` | string |   -| `postal_code` | string |   -| `phone` | string | Primary contact phone number - -### Testing -As with all Rails projects, model testing is a requirement. You should have _at least_ one positive and one negative test case for each relation, validation, and custom function you add to your models. - -Use good TDD practices, and test _before_ you code. Remember: red-green-refactor. - -## Waves 2 Coding The API -In this wave, you will implement the API described below. The endpoints are described more-or-less in order of complexity, and we recommend you build them in that order. Every endpoint must serve JSON data, and must use HTTP response codes to indicate the status of the request. - -The schema of your database and the structure of your rails app are completely up to you, so long as the API conforms to the description and provided script. - -### Error Handling -If something goes wrong, your API should return an appropriate [HTTP status code](http://billpatrianakos.me/blog/2013/10/13/list-of-rails-status-code-symbols/), as well as a list of errors. The list should be formatted like this: - -```json -{ - "errors": { - "title": ["Movie 'Revenge of the Gnomes' not found"] - } -} -``` - -All errors your API can return should be covered by at least one test case. - -### Testing -Because APIs are often open to the public, thorough testing is essential. For a Rails API, that means controller testing. - -For each API endpoint, you should have _at least_: -- A basic test with no parameters, if applicable -- Positive and negative tests for any URI parameters (user ID, movie title) -- Testing around any data in the request body - -Use good TDD practices, and test _before_ you code. Remember: red-green-refactor. - -#### Smoke Tests -Because this API will be used as the backend for a future project, there are strict requirements about how it should be structured. To this end, we have provided a set of [smoke tests](http://softwaretestingfundamentals.com/smoke-testing/) written in Postman to exercise all the endpoints. - -The smoke tests will verify that your API looks correct to the outside world, by sending actual HTTP requests to your running server and checking the results. They test things like: - -- Did I get a success response for a valid request? -- Did the API return JSON? -- Does the JSON contain the expected property names? - -**The smoke tests are not a substitute for writing your own tests!!!!!** They do **not** check that the content is _correct_, nor do they cover any negative or edge cases. Verifying correctness in these cases is **your** responsibility. - -The smoke tests live in the file [`test/VideoStoreAPI_smoke_tests.postman_collection.json`](test/VideoStoreAPI_smoke_tests.postman_collection.json). To run them: - -1. Open Postman -1. Click `Import` in the top left -1. Drag-and-drop the file into the box -1. In the left sidebar, click on the `Collections` tab -1. There should now be an entry for the smoke tests. Hover over it and click the `>` icon for a detail view. You will notice they are in the format `{{url}}/movies`. `{{url}}` is a key which you can give a value on your computer. -1. To do so go to the Gearbox in the top-right and select `Manage Environments` -![Manage Environments](images/manage-environment.png) -1. Then Select `Add` -![add button](images/add-btn.png) -1. Lastly add a key `url` and value `http://localhost:3000` -![Key & Value](images/key-value.png) -1. Click the blue `Run` button. This will launch the collection runner. -1. In the collection runner, scroll down in the center pane and click the blue `Start Test` button - -## API Description - -#### `GET /customers` -List all customers - -Fields to return: -- `id` -- `name` -- `registered_at` -- `postal_code` -- `phone` -- `movies_checked_out_count` - - This will be 0 unless you've completed optional requirements - -#### `GET /movies` -List all movies - -Fields to return: -- `title` -- `release_date` - -#### `GET /movies/:id` -Look a movie up by `id` - -URI parameters: -- `id`: Movie identifier - -Fields to return: -- `title` -- `overview` -- `release_date` -- `inventory` (total) -- `available_inventory` (not currently checked-out to a customer) - - This will be the same as `inventory` unless you've completed the optional endpoints. - -#### `POST /movies` -Create a new movie in the video store inventory. - -Upon success, this request should return the `id` of the movie created. - -Request body: - -| Field | Datatype | Description -|---------------|---------------------|------------ -| `title` | string | Title of the movie -| `overview` | string | Descriptive summary of the movie -| `release_date` | string `YYYY-MM-DD` | Date the movie was released -| `inventory` | integer | Quantity available in the video store - -### Optional Rentals - -Wave 2 focused on working with customers and movies. With these endpoints you can extend the functionality of your API to allow managing the rental process. - -#### `POST /rentals/check-out` -Check out one of the movie's inventory to the customer. The rental's check-out date should be set to today. - -**Note:** Some of the fields from wave 2 should now have interesting values. Good thing you wrote tests for them, right... right? - -Request body: - -| Field | Datatype | Description -|---------------|---------------------|------------ -| `customer_id` | integer | ID of the customer checking out this film -| `movie_id` | integer | ID of the movie to be checked out -| `due_date` | string `YYYY-MM-DD` | When should this movie be checked back in? - -#### `POST /rentals/check-in` -Check in one of a customer's rentals - -Request body: - -| Field | Datatype | Description -|---------------|----------|------------ -| `customer_id` | integer | ID of the customer checking in this film -| `movie_id` | integer | ID of the movie to be checked in - -#### `GET /rentals/overdue` -List all customers with overdue movies - -Fields to return: -- `title` -- `customer_id` -- `name` -- `postal_code` -- `checkout_date` -- `due_date` - -## Going Further -These really are **optional** - if you've gotten here and you have time left, that means you're moving speedy fast! - -### Query Parameters -Any endpoint that returns a list should accept 3 _optional_ [query parameters](http://guides.rubyonrails.org/action_controller_overview.html#parameters): - -| Name | Value | Description -|--------|---------|------------ -| `sort` | string | Sort objects by this field, in ascending order -| `n` | integer | Number of responses to return per page -| `p` | integer | Page of responses to return - -So, for an API endpoint like `GET /customers`, the following requests should be valid: -- `GET /customers`: All customers, sorted by ID -- `GET /customers?sort=name`: All customers, sorted by name -- `GET /customers?n=10&p=2`: Customers 10-19, sorted by ID -- `GET /customers?sort=name&n=10&p=2`: Customers 10-19, sorted by name - -Of course, adding new features means you should be adding new controller tests to verify them. - -Things to note: -- Sorting by ID is the rails default -- Possible sort fields: - - Customers can be sorted by `name`, `registered_at` and `postal_code` - - Movies can be sorted by `title` and `release_date` - - Overdue rentals can be sorted by `title`, `name`, `checkout_date` and `due_date` -- If the client requests both sorting and pagination, pagination should be relative to the sorted order -- Check out the [will_paginate gem](https://github.com/mislav/will_paginate) - -### More Endpoints: Inventory Management -All these endpoints should support all 3 query parameters. All fields are sortable. - -#### `GET /movies/:id/current` -List customers that have _currently_ checked out a copy of the film - -URI parameters: -- `id`: Movie identifier - -Fields to return: -- `customer_id` -- `name` -- `postal_code` -- `checkout_date` -- `due_date` - -#### `GET /movies/:id/history` -List customers that have checked out a copy of the film _in the past_ - -URI parameters: -- `id`: Movie identifier - -Fields to return: -- `customer_id` -- `name` -- `postal_code` -- `checkout_date` -- `due_date` - -#### `GET /customers/:id/current` -List the movies a customer _currently_ has checked out - -URI parameters: -- `id`: Customer ID - -Fields to return: -- `title` -- `checkout_date` -- `due_date` - -#### `GET /customers/:id/history` -List the movies a customer has checked out _in the past_ - -URI parameters: -- `id`: Customer ID - -Fields to return: -- `title` -- `checkout_date` -- `due_date` - - -# Reference -- [Postman on Environments](https://www.getpostman.com/docs/environments) +* ... 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..7d3c866ff --- /dev/null +++ b/config/application.rb @@ -0,0 +1,40 @@ +require_relative 'boot' + +require "rails" +# Pick the frameworks you want: +require "active_model/railtie" +require "active_job/railtie" +require "active_record/railtie" +require "action_controller/railtie" +require "action_mailer/railtie" +require "action_view/railtie" +require "action_cable/engine" +# require "sprockets/railtie" +require "rails/test_unit/railtie" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module VideoStoreAPI + class Application < Rails::Application + config.generators do |g| + # Force new test files to be generated in the minitest-spec style + g.test_framework :minitest, spec: true + + # Always use .js files, never .coffee + g.javascript_engine :js + end + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 5.1 + + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Only loads a smaller set of middleware suitable for API only apps. + # Middleware like session, flash, cookies can be added back manually. + # Skip views, helpers and assets when generating a new resource. + config.api_only = true + end +end diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 000000000..30f5120df --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,3 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/config/cable.yml b/config/cable.yml new file mode 100644 index 000000000..ad59bcd88 --- /dev/null +++ b/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: async + +production: + adapter: redis + url: redis://localhost:6379/1 + channel_prefix: VideoStoreAPI_production diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 000000000..720570700 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,85 @@ +# PostgreSQL. Versions 9.1 and up are supported. +# +# Install the pg driver: +# gem install pg +# On OS X with Homebrew: +# gem install pg -- --with-pg-config=/usr/local/bin/pg_config +# On OS X with MacPorts: +# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config +# On Windows: +# gem install pg +# Choose the win32 build. +# Install PostgreSQL and put its /bin directory on your path. +# +# Configure Using Gemfile +# gem 'pg' +# +default: &default + adapter: postgresql + encoding: unicode + # For details on connection pooling, see Rails configuration guide + # http://guides.rubyonrails.org/configuring.html#database-pooling + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + +development: + <<: *default + database: VideoStoreAPI_development + + # The specified database role being used to connect to postgres. + # To create additional roles in postgres see `$ createuser --help`. + # When left blank, postgres will use the default role. This is + # the same name as the operating system user that initialized the database. + #username: VideoStoreAPI + + # The password associated with the postgres role (username). + #password: + + # Connect on a TCP socket. Omitted by default since the client uses a + # domain socket that doesn't need configuration. Windows does not have + # domain sockets, so uncomment these lines. + #host: localhost + + # The TCP port the server listens on. Defaults to 5432. + # If your server runs on a different port number, change accordingly. + #port: 5432 + + # Schema search path. The server defaults to $user,public + #schema_search_path: myapp,sharedapp,public + + # Minimum log levels, in increasing order: + # debug5, debug4, debug3, debug2, debug1, + # log, notice, warning, error, fatal, and panic + # Defaults to warning. + #min_messages: notice + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: VideoStoreAPI_test + +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%= ENV['DATABASE_URL'] %> +# +production: + <<: *default + database: VideoStoreAPI_production + username: VideoStoreAPI + password: <%= ENV['VIDEOSTOREAPI_DATABASE_PASSWORD'] %> diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 000000000..426333bb4 --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative 'application' + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 000000000..abc82221c --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,47 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_caching = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + config.file_watcher = ActiveSupport::EventedFileUpdateChecker +end diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 000000000..3bd8115ea --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,83 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Attempt to read encrypted secrets from `config/secrets.yml.enc`. + # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or + # `config/secrets.yml.key`. + config.read_encrypted_secrets = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment) + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "VideoStoreAPI_#{Rails.env}" + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 000000000..8e5cbde53 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,42 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb new file mode 100644 index 000000000..89d2efab2 --- /dev/null +++ b/config/initializers/application_controller_renderer.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb new file mode 100644 index 000000000..59385cdf3 --- /dev/null +++ b/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb new file mode 100644 index 000000000..3b1c1b5ed --- /dev/null +++ b/config/initializers/cors.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Avoid CORS issues when API is called from the frontend app. +# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. + +# Read more: https://github.com/cyu/rack-cors + +# Rails.application.config.middleware.insert_before 0, Rack::Cors do +# allow do +# origins 'example.com' +# +# resource '*', +# headers: :any, +# methods: [:get, :post, :put, :patch, :delete, :options, :head] +# end +# end diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb new file mode 100644 index 000000000..4a994e1e7 --- /dev/null +++ b/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 000000000..ac033bf9d --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb new file mode 100644 index 000000000..dc1899682 --- /dev/null +++ b/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb new file mode 100644 index 000000000..bbfc3961b --- /dev/null +++ b/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 000000000..decc5a857 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# 'true': 'foo' +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/config/puma.rb b/config/puma.rb new file mode 100644 index 000000000..1e19380dc --- /dev/null +++ b/config/puma.rb @@ -0,0 +1,56 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +# +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked webserver processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. If you use this option +# you need to make sure to reconnect any threads in the `on_worker_boot` +# block. +# +# preload_app! + +# If you are preloading your application and using Active Record, it's +# recommended that you close any connections to the database before workers +# are forked to prevent connection leakage. +# +# before_fork do +# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) +# end + +# The code in the `on_worker_boot` will be called if you are using +# clustered mode by specifying a number of `workers`. After each worker +# process is booted, this block will be run. If you are using the `preload_app!` +# option, you will want to use this block to reconnect to any threads +# or connections that may have been created at application boot, as Ruby +# cannot share connections between processes. +# +# on_worker_boot do +# ActiveRecord::Base.establish_connection if defined?(ActiveRecord) +# end +# + +# Allow puma to be restarted by `rails restart` command. +plugin :tmp_restart diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 000000000..787824f88 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,3 @@ +Rails.application.routes.draw do + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html +end diff --git a/config/secrets.yml b/config/secrets.yml new file mode 100644 index 000000000..278a93e06 --- /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: d2dced472a870b47cadc80d354a019dc963f6fb20f5eeeefe2daa28bc940705f7134114f7d7d505c8845e2c612d812bbd7193b5d2b72769b5c6a8f01e8a2be53 + +test: + secret_key_base: f7d1e4895b26d98f90ea131ba8b99e00a6adcf3797e4d8e833d7e66397926e3fe3cf2d41a7b7c73cd86cd285265f6fa69edf590c4f7218025ccecb52990872d3 + +# 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..10594a324 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,26 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path("../../config/environment", __FILE__) +require "rails/test_help" +require "minitest/rails" +require "minitest/reporters" # for Colorized output + +# For colorful output! +Minitest::Reporters.use!( + Minitest::Reporters::SpecReporter.new, + ENV, + Minitest.backtrace_filter +) + + +# To add Capybara feature tests add `gem "minitest-rails-capybara"` +# to the test group in the Gemfile and uncomment the following: +# require "minitest/rails/capybara" + +# Uncomment for awesome colorful output +# require "minitest/pride" + +class ActiveSupport::TestCase + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all + # Add more helper methods to be used by all tests here... +end diff --git a/tmp/.keep b/tmp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/vendor/.keep b/vendor/.keep new file mode 100644 index 000000000..e69de29bb From e614a3fa48c3ac0eb9b346f26f0e6e0b6d74e30c Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Mon, 6 Nov 2017 11:22:17 -0800 Subject: [PATCH 02/42] adds controllers, models, sets up schema, seeds --- app/controllers/customers_controller.rb | 2 + app/controllers/movies_controller.rb | 2 + app/models/customer.rb | 2 + app/models/movie.rb | 2 + config/routes.rb | 5 ++- db/migrate/20171106191445_create_customers.rb | 15 +++++++ db/migrate/20171106191554_create_movies.rb | 12 ++++++ ...6191738_add_account_credit_for_customer.rb | 5 +++ db/schema.rb | 40 +++++++++++++++++++ test/controllers/customers_controller_test.rb | 7 ++++ test/controllers/movies_controller_test.rb | 7 ++++ test/fixtures/customers.yml | 19 +++++++++ test/fixtures/movies.yml | 13 ++++++ test/models/customer_test.rb | 9 +++++ test/models/movie_test.rb | 9 +++++ 15 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 app/controllers/customers_controller.rb create mode 100644 app/controllers/movies_controller.rb create mode 100644 app/models/customer.rb create mode 100644 app/models/movie.rb create mode 100644 db/migrate/20171106191445_create_customers.rb create mode 100644 db/migrate/20171106191554_create_movies.rb create mode 100644 db/migrate/20171106191738_add_account_credit_for_customer.rb create mode 100644 db/schema.rb create mode 100644 test/controllers/customers_controller_test.rb create mode 100644 test/controllers/movies_controller_test.rb create mode 100644 test/fixtures/customers.yml create mode 100644 test/fixtures/movies.yml create mode 100644 test/models/customer_test.rb create mode 100644 test/models/movie_test.rb diff --git a/app/controllers/customers_controller.rb b/app/controllers/customers_controller.rb new file mode 100644 index 000000000..ca3b6e024 --- /dev/null +++ b/app/controllers/customers_controller.rb @@ -0,0 +1,2 @@ +class CustomersController < ApplicationController +end diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb new file mode 100644 index 000000000..6c4c51614 --- /dev/null +++ b/app/controllers/movies_controller.rb @@ -0,0 +1,2 @@ +class MoviesController < ApplicationController +end 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/config/routes.rb b/config/routes.rb index 787824f88..9d521d370 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,6 @@ Rails.application.routes.draw do - # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html + + resources :customers + resources :movies + end diff --git a/db/migrate/20171106191445_create_customers.rb b/db/migrate/20171106191445_create_customers.rb new file mode 100644 index 000000000..0a05c4bad --- /dev/null +++ b/db/migrate/20171106191445_create_customers.rb @@ -0,0 +1,15 @@ +class CreateCustomers < ActiveRecord::Migration[5.1] + def change + create_table :customers do |t| + t.string :name + t.string :registered_at + t.string :address + t.string :city + t.string :state + t.string :postal_code + t.string :phone + + t.timestamps + end + end +end diff --git a/db/migrate/20171106191554_create_movies.rb b/db/migrate/20171106191554_create_movies.rb new file mode 100644 index 000000000..84782a1b7 --- /dev/null +++ b/db/migrate/20171106191554_create_movies.rb @@ -0,0 +1,12 @@ +class CreateMovies < ActiveRecord::Migration[5.1] + def change + create_table :movies do |t| + t.string :title + t.string :overview + t.string :release_date + t.integer :inventory + + t.timestamps + end + end +end diff --git a/db/migrate/20171106191738_add_account_credit_for_customer.rb b/db/migrate/20171106191738_add_account_credit_for_customer.rb new file mode 100644 index 000000000..48fb8588f --- /dev/null +++ b/db/migrate/20171106191738_add_account_credit_for_customer.rb @@ -0,0 +1,5 @@ +class AddAccountCreditForCustomer < ActiveRecord::Migration[5.1] + def change + add_column :customers, :account_credit, :decimal + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 000000000..475fde1e2 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,40 @@ +# 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: 20171106191738) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "customers", force: :cascade do |t| + t.string "name" + t.string "registered_at" + t.string "address" + t.string "city" + t.string "state" + t.string "postal_code" + t.string "phone" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.decimal "account_credit" + end + + create_table "movies", force: :cascade do |t| + t.string "title" + t.string "overview" + t.string "release_date" + t.integer "inventory" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + +end diff --git a/test/controllers/customers_controller_test.rb b/test/controllers/customers_controller_test.rb new file mode 100644 index 000000000..5e123f6cd --- /dev/null +++ b/test/controllers/customers_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +describe CustomersController do + # it "must be a real test" do + # flunk "Need real tests" + # end +end diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb new file mode 100644 index 000000000..67fabbcfb --- /dev/null +++ b/test/controllers/movies_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +describe MoviesController do + # it "must be a real test" do + # flunk "Need real tests" + # end +end diff --git a/test/fixtures/customers.yml b/test/fixtures/customers.yml new file mode 100644 index 000000000..bf442fa90 --- /dev/null +++ b/test/fixtures/customers.yml @@ -0,0 +1,19 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + registered_at: MyString + address: MyString + city: MyString + state: MyString + postal_code: MyString + phone: MyString + +two: + name: MyString + registered_at: MyString + address: MyString + city: MyString + state: MyString + postal_code: MyString + phone: MyString diff --git a/test/fixtures/movies.yml b/test/fixtures/movies.yml new file mode 100644 index 000000000..d774de5f1 --- /dev/null +++ b/test/fixtures/movies.yml @@ -0,0 +1,13 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + title: MyString + overview: MyString + release_date: MyString + inventory: 1 + +two: + title: MyString + overview: MyString + release_date: MyString + inventory: 1 diff --git a/test/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 From 2509aed4a584ba151c07650b9acbcda873a04fe8 Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Mon, 6 Nov 2017 11:23:03 -0800 Subject: [PATCH 03/42] Ensuring that I can merge --- db/schema.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 db/schema.rb diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 000000000..2611543b3 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,18 @@ +# 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: 0) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + +end From a887d1ecc7ba6d6a4bde0524c68af1dc9c48f749 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Mon, 6 Nov 2017 11:42:18 -0800 Subject: [PATCH 04/42] adds index/customers --- app/controllers/customers_controller.rb | 5 +++++ test/controllers/customers_controller_test.rb | 17 ++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/app/controllers/customers_controller.rb b/app/controllers/customers_controller.rb index ca3b6e024..57b22b428 100644 --- a/app/controllers/customers_controller.rb +++ b/app/controllers/customers_controller.rb @@ -1,2 +1,7 @@ class CustomersController < ApplicationController + def index + customers = Customer.all + render json: customers.as_json(only: [:name, :registered_at, :address, :city, :state, :postal_code, :phone, :account_credit]), + status: :ok + end end diff --git a/test/controllers/customers_controller_test.rb b/test/controllers/customers_controller_test.rb index 5e123f6cd..0edc8943b 100644 --- a/test/controllers/customers_controller_test.rb +++ b/test/controllers/customers_controller_test.rb @@ -1,7 +1,18 @@ require "test_helper" describe CustomersController do - # it "must be a real test" do - # flunk "Need real tests" - # end + +describe 'index' do + it 'is a working route' do + get customers_url + must_respond_with :success + end + + it 'returns json' do + get customers_url + response.header['Content-Type'].must_include 'json' + end + +end + end From 5f249f919c17c74c4b014690600d8fc9c0079d68 Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Mon, 6 Nov 2017 12:34:29 -0800 Subject: [PATCH 05/42] Customers controller index method. --- app/controllers/customers_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/customers_controller.rb b/app/controllers/customers_controller.rb index ca3b6e024..b598c6e33 100644 --- a/app/controllers/customers_controller.rb +++ b/app/controllers/customers_controller.rb @@ -1,2 +1,3 @@ class CustomersController < ApplicationController + end From 813b04eeab86bf690cbbd67292bf09eb396e9bdf Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Mon, 6 Nov 2017 12:48:31 -0800 Subject: [PATCH 06/42] adds controller test for customer/index --- app/models/customer.rb | 1 + app/models/movie.rb | 1 + test/controllers/customers_controller_test.rb | 26 +++++++++++++++++++ test/models/customer_test.rb | 4 --- test/models/movie_test.rb | 5 +--- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/app/models/customer.rb b/app/models/customer.rb index 0b5277335..a62356cd9 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -1,2 +1,3 @@ class Customer < ApplicationRecord + validates :name, presence: true end diff --git a/app/models/movie.rb b/app/models/movie.rb index dc614df15..d34b59f22 100644 --- a/app/models/movie.rb +++ b/app/models/movie.rb @@ -1,2 +1,3 @@ class Movie < ApplicationRecord + validates :title, presence: true end diff --git a/test/controllers/customers_controller_test.rb b/test/controllers/customers_controller_test.rb index 0edc8943b..cae02ffa6 100644 --- a/test/controllers/customers_controller_test.rb +++ b/test/controllers/customers_controller_test.rb @@ -13,6 +13,32 @@ response.header['Content-Type'].must_include 'json' end + it 'returns an array' do + get customers_url + body = JSON.parse(response.body) + body.must_be_kind_of Array + end + + it 'returns an array even if no data' do + Customer.destroy_all + + get customers_url + body = JSON.parse(response.body) + body.must_be_kind_of Array + body.count.must_equal 0 + body.must_equal [] + + end + + it "returns customers with exactly the required fields" do + keys = %w(name registered_at address city state postal_code phone account_credit) + get customers_url + body = JSON.parse(response.body) + body.each do |pet| + pet.keys.must_equal keys + end + end + end end diff --git a/test/models/customer_test.rb b/test/models/customer_test.rb index 5ebc5c850..eee222f67 100644 --- a/test/models/customer_test.rb +++ b/test/models/customer_test.rb @@ -1,9 +1,5 @@ require "test_helper" describe Customer do - let(:customer) { Customer.new } - it "must be valid" do - value(customer).must_be :valid? - end end diff --git a/test/models/movie_test.rb b/test/models/movie_test.rb index 34d1d30a5..8798308e5 100644 --- a/test/models/movie_test.rb +++ b/test/models/movie_test.rb @@ -1,9 +1,6 @@ require "test_helper" describe Movie do - let(:movie) { Movie.new } - it "must be valid" do - value(movie).must_be :valid? - end + end From 19c4ac91b2a0389b51cc2f4f7a25bb0a9c7dbf6b Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Mon, 6 Nov 2017 12:55:51 -0800 Subject: [PATCH 07/42] adds controller test for movies/index --- app/controllers/movies_controller.rb | 5 +++ test/controllers/customers_controller_test.rb | 4 +- test/controllers/movies_controller_test.rb | 41 +++++++++++++++++-- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index 6c4c51614..6abf7e313 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -1,2 +1,7 @@ class MoviesController < ApplicationController + def index + movies = Movie.all + render json: movies.as_json(only: [:title, :overview, :release_date, :inventory]), + status: :ok + end end diff --git a/test/controllers/customers_controller_test.rb b/test/controllers/customers_controller_test.rb index cae02ffa6..9c79d24b3 100644 --- a/test/controllers/customers_controller_test.rb +++ b/test/controllers/customers_controller_test.rb @@ -34,8 +34,8 @@ keys = %w(name registered_at address city state postal_code phone account_credit) get customers_url body = JSON.parse(response.body) - body.each do |pet| - pet.keys.must_equal keys + body.each do |customer| + customer.keys.must_equal keys end end diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb index 67fabbcfb..e9b19780e 100644 --- a/test/controllers/movies_controller_test.rb +++ b/test/controllers/movies_controller_test.rb @@ -1,7 +1,42 @@ require "test_helper" describe MoviesController do - # it "must be a real test" do - # flunk "Need real tests" - # end + describe 'index' do + it 'is a working route' do + get movies_url + must_respond_with :success + end + + it 'returns json' do + get movies_url + response.header['Content-Type'].must_include 'json' + end + + it 'returns an array' do + get movies_url + body = JSON.parse(response.body) + body.must_be_kind_of Array + end + + it 'returns an array even if no data' do + Movie.destroy_all + + get movies_url + body = JSON.parse(response.body) + body.must_be_kind_of Array + body.count.must_equal 0 + body.must_equal [] + + end + + it "returns movies with exactly the required fields" do + keys = %w(title overview release_date inventory) + get movies_url + body = JSON.parse(response.body) + body.each do |movie| + movie.keys.must_equal keys + end + end + + end end From 7dcc609ccb9a63402487d34e690d325c06661dd9 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Mon, 6 Nov 2017 13:17:11 -0800 Subject: [PATCH 08/42] adds show and tests --- app/controllers/customers_controller.rb | 9 +++ app/controllers/movies_controller.rb | 9 +++ test/controllers/customers_controller_test.rb | 65 ++++++++++++------- test/controllers/movies_controller_test.rb | 13 ++++ test/fixtures/customers.yml | 28 ++++---- test/fixtures/movies.yml | 14 ++-- 6 files changed, 92 insertions(+), 46 deletions(-) diff --git a/app/controllers/customers_controller.rb b/app/controllers/customers_controller.rb index 57b22b428..81d47eba7 100644 --- a/app/controllers/customers_controller.rb +++ b/app/controllers/customers_controller.rb @@ -4,4 +4,13 @@ def index render json: customers.as_json(only: [:name, :registered_at, :address, :city, :state, :postal_code, :phone, :account_credit]), status: :ok end + + def show + customer = Customer.find_by_id(params[:id]) + if customer + render json: customer.as_json(only: [:name, :registered_at, :address, :city, :state, :postal_code, :phone, :account_credit]), status: :ok + else + render json: {ok: false}, status: :not_found + end + end end diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index 6abf7e313..6376d3827 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -4,4 +4,13 @@ def index render json: movies.as_json(only: [:title, :overview, :release_date, :inventory]), status: :ok end + + def show + movie = Movie.find_by_id(params[:id]) + if movie + render json: movie.as_json(only: [:title, :overview, :release_date, :inventory]), status: :ok + else + render json: {ok: false}, status: :not_found + end + end end diff --git a/test/controllers/customers_controller_test.rb b/test/controllers/customers_controller_test.rb index 9c79d24b3..4b248ad82 100644 --- a/test/controllers/customers_controller_test.rb +++ b/test/controllers/customers_controller_test.rb @@ -2,43 +2,58 @@ describe CustomersController do -describe 'index' do - it 'is a working route' do - get customers_url - must_respond_with :success - end + describe 'index' do + it 'is a working route' do + get customers_url + must_respond_with :success + end - it 'returns json' do - get customers_url - response.header['Content-Type'].must_include 'json' - end + it 'returns json' do + get customers_url + response.header['Content-Type'].must_include 'json' + end - it 'returns an array' do - get customers_url - body = JSON.parse(response.body) - body.must_be_kind_of Array - end + it 'returns an array' do + get customers_url + body = JSON.parse(response.body) + body.must_be_kind_of Array + end - it 'returns an array even if no data' do - Customer.destroy_all + it 'returns an array even if no data' do + Customer.destroy_all - get customers_url - body = JSON.parse(response.body) - body.must_be_kind_of Array - body.count.must_equal 0 - body.must_equal [] + get customers_url + body = JSON.parse(response.body) + body.must_be_kind_of Array + body.count.must_equal 0 + body.must_equal [] - end + end - it "returns customers with exactly the required fields" do + it "returns customers with exactly the required fields" do keys = %w(name registered_at address city state postal_code phone account_credit) get customers_url body = JSON.parse(response.body) body.each do |customer| - customer.keys.must_equal keys + customer.keys.must_equal keys end end -end + end + + describe 'show' do + + it 'can get a customer' do + get customer_path(customers(:two).id) + must_respond_with :success + end + + it 'returns error for invalid customer' do + customers(:two).destroy + get customer_path(customers(:two).id) + must_respond_with :not_found + end + + end end diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb index e9b19780e..1dcdec322 100644 --- a/test/controllers/movies_controller_test.rb +++ b/test/controllers/movies_controller_test.rb @@ -39,4 +39,17 @@ end end + + describe 'show' do + it 'can get a movie' do + get movie_path(movies(:two).id) + must_respond_with :success + end + + it 'returns error for invalid movie' do + movies(:two).destroy + get movie_path(movies(:two).id) + must_respond_with :not_found + end + end end diff --git a/test/fixtures/customers.yml b/test/fixtures/customers.yml index bf442fa90..7ec08f024 100644 --- a/test/fixtures/customers.yml +++ b/test/fixtures/customers.yml @@ -1,19 +1,19 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: - name: MyString - registered_at: MyString - address: MyString - city: MyString - state: MyString - postal_code: MyString - phone: MyString + name: Tanja + registered_at: 11/06/2017 + address: 123 Lovelace Street + city: LoveLand + state: WA + postal_code: "12345" + phone: 123-456-789 two: - name: MyString - registered_at: MyString - address: MyString - city: MyString - state: MyString - postal_code: MyString - phone: MyString + name: Kimberley + registered_at: 11/06/2017 + address: 123 Fake St + city: Seattle + state: WA + postal_code: "12346" + phone: 234-567-890 diff --git a/test/fixtures/movies.yml b/test/fixtures/movies.yml index d774de5f1..f27c216bf 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 - overview: MyString - release_date: MyString - inventory: 1 + title: Secret Life Of Pets + overview: A movie about pets + release_date: 12/27/2016 + inventory: 2 two: - title: MyString - overview: MyString - release_date: MyString + title: Baby Driver + overview: High speed chase galore + release_date: 07/07/2017 inventory: 1 From f980abc7c24b83a569543a06f3c1f7ade533a239 Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Mon, 6 Nov 2017 14:26:34 -0800 Subject: [PATCH 09/42] Customer controller minor spacing changes. --- app/controllers/customers_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/customers_controller.rb b/app/controllers/customers_controller.rb index 5f065e8ec..3540e3283 100644 --- a/app/controllers/customers_controller.rb +++ b/app/controllers/customers_controller.rb @@ -5,4 +5,6 @@ def index render json: customers.as_json(only: [:name, :registered_at, :address, :city, :state, :postal_code, :phone, :account_credit]), status: :ok end + + end From 39107b36cb51db3db65374c9634f1780649987d0 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Mon, 6 Nov 2017 14:26:35 -0800 Subject: [PATCH 10/42] adds id to jason --- app/controllers/customers_controller.rb | 4 ++-- app/controllers/movies_controller.rb | 4 ++-- test/controllers/movies_controller_test.rb | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/controllers/customers_controller.rb b/app/controllers/customers_controller.rb index 81d47eba7..51750e216 100644 --- a/app/controllers/customers_controller.rb +++ b/app/controllers/customers_controller.rb @@ -1,14 +1,14 @@ class CustomersController < ApplicationController def index customers = Customer.all - render json: customers.as_json(only: [:name, :registered_at, :address, :city, :state, :postal_code, :phone, :account_credit]), + render json: customers.as_json(only: [:id, :name, :registered_at, :address, :city, :state, :postal_code, :phone, :account_credit]), status: :ok end def show customer = Customer.find_by_id(params[:id]) if customer - render json: customer.as_json(only: [:name, :registered_at, :address, :city, :state, :postal_code, :phone, :account_credit]), status: :ok + render json: customer.as_json(only: [:id, :name, :registered_at, :address, :city, :state, :postal_code, :phone, :account_credit]), status: :ok else render json: {ok: false}, status: :not_found end diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index 6376d3827..473f76600 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -1,14 +1,14 @@ class MoviesController < ApplicationController def index movies = Movie.all - render json: movies.as_json(only: [:title, :overview, :release_date, :inventory]), + render json: movies.as_json(only: [:id, :title, :overview, :release_date, :inventory]), status: :ok end def show movie = Movie.find_by_id(params[:id]) if movie - render json: movie.as_json(only: [:title, :overview, :release_date, :inventory]), status: :ok + render json: movie.as_json(only: [:id, :title, :overview, :release_date, :inventory]), status: :ok else render json: {ok: false}, status: :not_found end diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb index 1dcdec322..a4a4f37d1 100644 --- a/test/controllers/movies_controller_test.rb +++ b/test/controllers/movies_controller_test.rb @@ -52,4 +52,5 @@ must_respond_with :not_found end end + end From 1ca59228c0a67e002fffe49419e61a790752ca08 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Mon, 6 Nov 2017 14:27:41 -0800 Subject: [PATCH 11/42] spacing --- app/controllers/customers_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/customers_controller.rb b/app/controllers/customers_controller.rb index 51750e216..23d629baf 100644 --- a/app/controllers/customers_controller.rb +++ b/app/controllers/customers_controller.rb @@ -13,4 +13,5 @@ def show render json: {ok: false}, status: :not_found end end + end From 2215a313a4558e1b71cd365b16ac57fa80de8f45 Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Mon, 6 Nov 2017 14:47:05 -0800 Subject: [PATCH 12/42] Movie create controller method and passing tests. --- app/controllers/movies_controller.rb | 16 ++++++++ test/controllers/customers_controller_test.rb | 2 +- test/controllers/movies_controller_test.rb | 41 ++++++++++++++++++- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index 473f76600..117986020 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -13,4 +13,20 @@ def show render json: {ok: false}, status: :not_found end end + + def create + movie = Movie.new(movie_params) + if movie.save + render json: { id: movie.id }, status: :ok + else + render json: {errors: movie.errors.messages}, + status: :bad_request + end + end + + private + + def movie_params + params.require(:movie).permit(:title, :overview, :release_date, :inventory) + end end diff --git a/test/controllers/customers_controller_test.rb b/test/controllers/customers_controller_test.rb index 4b248ad82..a199907cf 100644 --- a/test/controllers/customers_controller_test.rb +++ b/test/controllers/customers_controller_test.rb @@ -31,7 +31,7 @@ end it "returns customers with exactly the required fields" do - keys = %w(name registered_at address city state postal_code phone account_credit) + keys = %w(id name registered_at address city state postal_code phone account_credit) get customers_url body = JSON.parse(response.body) body.each do |customer| diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb index a4a4f37d1..52072ecc6 100644 --- a/test/controllers/movies_controller_test.rb +++ b/test/controllers/movies_controller_test.rb @@ -30,7 +30,7 @@ end it "returns movies with exactly the required fields" do - keys = %w(title overview release_date inventory) + keys = %w(id title overview release_date inventory) get movies_url body = JSON.parse(response.body) body.each do |movie| @@ -52,5 +52,42 @@ must_respond_with :not_found end end - + + describe 'create' do + let(:movie_data) { + { + title: "Pretty Woman", + overview: "Julia Roberts is there.", + release_date: "11/06/1999", + inventory: 5 + } + } + it "creates a new movie" do + assert_difference 'Movie.count', 1 do + post movies_url, params: {movie: movie_data} + assert_response :success + end + + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "id" + + Movie.find_by_id(body["id"]).title.must_equal movie_data[:title] + end + + it "returns bad request for an invalid movie" do + bad_data = movie_data.dup() + bad_data.delete(:title) + assert_no_difference 'Movie.count' do + post movies_url, params: {movie: bad_data} + assert_response :bad_request + end + + body = JSON.parse(response.body) + body.must_include "errors" + body["errors"].must_include "title" + end + + end + end From a70274ba20d79badf4bfa96365082cfb62c4ef1b Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Mon, 6 Nov 2017 14:55:56 -0800 Subject: [PATCH 13/42] Added column for checked out movie count in Customer model, updated tests to include new column. --- app/controllers/customers_controller.rb | 4 ++-- app/controllers/movies_controller.rb | 2 +- ...171106225359_add_movies_checked_out_count_to_customers.rb | 5 +++++ db/schema.rb | 4 ++-- test/controllers/customers_controller_test.rb | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 db/migrate/20171106225359_add_movies_checked_out_count_to_customers.rb diff --git a/app/controllers/customers_controller.rb b/app/controllers/customers_controller.rb index 3ab83d405..420c947ca 100644 --- a/app/controllers/customers_controller.rb +++ b/app/controllers/customers_controller.rb @@ -2,14 +2,14 @@ class CustomersController < ApplicationController def index customers = Customer.all - render json: customers.as_json(only: [:id, :name, :registered_at, :address, :city, :state, :postal_code, :phone, :account_credit]), + render json: customers.as_json(only: [:id, :name, :registered_at, :address, :city, :state, :postal_code, :phone, :account_credit, :movies_checked_out_count]), status: :ok end def show customer = Customer.find_by_id(params[:id]) if customer - render json: customer.as_json(only: [:id, :name, :registered_at, :address, :city, :state, :postal_code, :phone, :account_credit]), status: :ok + render json: customer.as_json(only: [:id, :name, :registered_at, :address, :city, :state, :postal_code, :phone, :account_credit, :movies_checked_out_count]), status: :ok else render json: {ok: false}, status: :not_found end diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index 117986020..658ee7dc0 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -1,7 +1,7 @@ class MoviesController < ApplicationController def index movies = Movie.all - render json: movies.as_json(only: [:id, :title, :overview, :release_date, :inventory]), + render json: movies.as_json(only: [:id, :title, :overview, :release_date, :inventory, :movies_checked_out_count]), status: :ok end diff --git a/db/migrate/20171106225359_add_movies_checked_out_count_to_customers.rb b/db/migrate/20171106225359_add_movies_checked_out_count_to_customers.rb new file mode 100644 index 000000000..3369a9a4f --- /dev/null +++ b/db/migrate/20171106225359_add_movies_checked_out_count_to_customers.rb @@ -0,0 +1,5 @@ +class AddMoviesCheckedOutCountToCustomers < ActiveRecord::Migration[5.1] + def change + add_column :customers, :movies_checked_out_count, :integer, default: 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index a512a1d6d..be80979cb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,8 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. - -ActiveRecord::Schema.define(version: 20171106191738) do +ActiveRecord::Schema.define(version: 20171106225359) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -27,6 +26,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.decimal "account_credit" + t.integer "movies_checked_out_count", default: 0 end create_table "movies", force: :cascade do |t| diff --git a/test/controllers/customers_controller_test.rb b/test/controllers/customers_controller_test.rb index a199907cf..621f35e1c 100644 --- a/test/controllers/customers_controller_test.rb +++ b/test/controllers/customers_controller_test.rb @@ -31,7 +31,7 @@ end it "returns customers with exactly the required fields" do - keys = %w(id name registered_at address city state postal_code phone account_credit) + keys = %w(id name registered_at address city state postal_code phone account_credit movies_checked_out_count) get customers_url body = JSON.parse(response.body) body.each do |customer| From 33166cbf6f639947c8fec1224c162f0e7ca73c63 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Mon, 6 Nov 2017 15:17:40 -0800 Subject: [PATCH 14/42] adds available_inventory migration and methods that set it in the model --- app/controllers/movies_controller.rb | 4 ++-- app/models/movie.rb | 8 ++++++++ .../20171106230147_add_default_for_available_inventory.rb | 5 +++++ db/schema.rb | 3 ++- test/controllers/movies_controller_test.rb | 2 +- 5 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20171106230147_add_default_for_available_inventory.rb diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index 658ee7dc0..085ab1d68 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -1,14 +1,14 @@ class MoviesController < ApplicationController def index movies = Movie.all - render json: movies.as_json(only: [:id, :title, :overview, :release_date, :inventory, :movies_checked_out_count]), + render json: movies.as_json(only: [:id, :title, :overview, :release_date, :inventory, :available_inventory]), status: :ok end def show movie = Movie.find_by_id(params[:id]) if movie - render json: movie.as_json(only: [:id, :title, :overview, :release_date, :inventory]), status: :ok + render json: movie.as_json(only: [:id, :title, :overview, :release_date, :inventory, :available_inventory]), status: :ok else render json: {ok: false}, status: :not_found end diff --git a/app/models/movie.rb b/app/models/movie.rb index d34b59f22..b7f12e0ee 100644 --- a/app/models/movie.rb +++ b/app/models/movie.rb @@ -1,3 +1,11 @@ class Movie < ApplicationRecord validates :title, presence: true + + before_create :set_default_avaiable_inventory + + private + + def set_default_avaiable_inventory + self.available_inventory = self.inventory + end end diff --git a/db/migrate/20171106230147_add_default_for_available_inventory.rb b/db/migrate/20171106230147_add_default_for_available_inventory.rb new file mode 100644 index 000000000..d80b9897f --- /dev/null +++ b/db/migrate/20171106230147_add_default_for_available_inventory.rb @@ -0,0 +1,5 @@ +class AddDefaultForAvailableInventory < ActiveRecord::Migration[5.1] + def change + add_column :movies, :available_inventory, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index be80979cb..91314ef5c 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: 20171106225359) do +ActiveRecord::Schema.define(version: 20171106230147) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -36,6 +36,7 @@ t.integer "inventory" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "available_inventory" end end diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb index 52072ecc6..3a034776b 100644 --- a/test/controllers/movies_controller_test.rb +++ b/test/controllers/movies_controller_test.rb @@ -30,7 +30,7 @@ end it "returns movies with exactly the required fields" do - keys = %w(id title overview release_date inventory) + keys = %w(id title overview release_date inventory available_inventory) get movies_url body = JSON.parse(response.body) body.each do |movie| From 4937beb0fda330791559a69d35dd81d181d4086c Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Mon, 6 Nov 2017 16:01:58 -0800 Subject: [PATCH 15/42] ammends movies/show to find by title --- 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 085ab1d68..4359ce67f 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -6,7 +6,7 @@ def index end def show - movie = Movie.find_by_id(params[:id]) + movie = Movie.find_by(title: params[:id]) if movie render json: movie.as_json(only: [:id, :title, :overview, :release_date, :inventory, :available_inventory]), status: :ok else From b62c83c5ac12ba4811543f24f766d0744c407e2d Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Mon, 6 Nov 2017 16:05:16 -0800 Subject: [PATCH 16/42] Updated show params for movies controller. --- 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 085ab1d68..e3538d7f1 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -6,7 +6,7 @@ def index end def show - movie = Movie.find_by_id(params[:id]) + movie = Movie.find_by(title: params[:title]) if movie render json: movie.as_json(only: [:id, :title, :overview, :release_date, :inventory, :available_inventory]), status: :ok else From b5d1b60ce4f52eced2447b3cb3c52b30de4a6f4c Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Mon, 6 Nov 2017 16:14:39 -0800 Subject: [PATCH 17/42] adds rentals controller and model and sets up rentals routes --- app/controllers/rentals_controller.rb | 13 +++++++++++++ app/models/rental.rb | 2 ++ config/routes.rb | 3 +++ db/migrate/20171107000926_create_rentals.rb | 11 +++++++++++ db/schema.rb | 10 +++++++++- test/controllers/rentals_controller_test.rb | 7 +++++++ test/fixtures/rentals.yml | 11 +++++++++++ test/models/rental_test.rb | 9 +++++++++ 8 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 app/controllers/rentals_controller.rb create mode 100644 app/models/rental.rb create mode 100644 db/migrate/20171107000926_create_rentals.rb create mode 100644 test/controllers/rentals_controller_test.rb create mode 100644 test/fixtures/rentals.yml create mode 100644 test/models/rental_test.rb diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb new file mode 100644 index 000000000..a2cb7720d --- /dev/null +++ b/app/controllers/rentals_controller.rb @@ -0,0 +1,13 @@ +class RentalsController < ApplicationController + + def check_out + end + + def check_in + end + + def overdue + end + + +end diff --git a/app/models/rental.rb b/app/models/rental.rb new file mode 100644 index 000000000..79e3a65ca --- /dev/null +++ b/app/models/rental.rb @@ -0,0 +1,2 @@ +class Rental < ApplicationRecord +end diff --git a/config/routes.rb b/config/routes.rb index 9d521d370..9fc92ab4e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,4 +3,7 @@ resources :customers resources :movies + post '/rentals/check_out', to: 'rentals#check_out', as: 'checkout' + post '/rentals/check_in', to: 'rentals#check_in', as: 'checkin' + get '/rentals/overdue', to: 'rentals#overdue', as: 'overdue' end diff --git a/db/migrate/20171107000926_create_rentals.rb b/db/migrate/20171107000926_create_rentals.rb new file mode 100644 index 000000000..6754e5b66 --- /dev/null +++ b/db/migrate/20171107000926_create_rentals.rb @@ -0,0 +1,11 @@ +class CreateRentals < ActiveRecord::Migration[5.1] + def change + create_table :rentals do |t| + t.integer :customer_id + t.integer :movie_id + t.string :due_date + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 91314ef5c..be19187dd 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: 20171106230147) do +ActiveRecord::Schema.define(version: 20171107000926) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -39,4 +39,12 @@ t.integer "available_inventory" end + create_table "rentals", force: :cascade do |t| + t.integer "customer_id" + t.integer "movie_id" + t.string "due_date" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + end diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb new file mode 100644 index 000000000..f0227216c --- /dev/null +++ b/test/controllers/rentals_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +describe RentalsController do + # it "must be a real test" do + # flunk "Need real tests" + # end +end diff --git a/test/fixtures/rentals.yml b/test/fixtures/rentals.yml new file mode 100644 index 000000000..84ac60fdf --- /dev/null +++ b/test/fixtures/rentals.yml @@ -0,0 +1,11 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + customer_id: 1 + movie_id: 1 + due_date: MyString + +two: + customer_id: 1 + movie_id: 1 + due_date: MyString 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 bec3b99123f91e6da09f55d24f4dc5faa90c3fe7 Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Mon, 6 Nov 2017 16:21:36 -0800 Subject: [PATCH 18/42] Passing controller tests for rental for checkout, checkin, rentals, path. --- test/controllers/movies_controller_test.rb | 2 +- test/controllers/rentals_controller_test.rb | 25 +++++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/test/controllers/movies_controller_test.rb b/test/controllers/movies_controller_test.rb index 3a034776b..2b77b1382 100644 --- a/test/controllers/movies_controller_test.rb +++ b/test/controllers/movies_controller_test.rb @@ -42,7 +42,7 @@ describe 'show' do it 'can get a movie' do - get movie_path(movies(:two).id) + get movie_path(movies(:two).title) must_respond_with :success end diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index f0227216c..c87623d89 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -1,7 +1,24 @@ require "test_helper" - describe RentalsController do - # it "must be a real test" do - # flunk "Need real tests" - # end + describe "check_out" do + it "succeeds" do + post checkout_path + must_respond_with :success + end + end + + describe "check_in" do + it "succeeds" do + post checkin_path + must_respond_with :success + end + end + + describe "overdue" do + it "succeeds" do + get overdue_path + must_respond_with :success + end + end + end From e5487c55d9e58355cce340603b5de8c5ab3f49e5 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Mon, 6 Nov 2017 16:37:44 -0800 Subject: [PATCH 19/42] adds formatting methods for date --- app/models/rental.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/models/rental.rb b/app/models/rental.rb index 79e3a65ca..847aa6178 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -1,2 +1,15 @@ +require 'date' + class Rental < ApplicationRecord + before_create :format_checkout, :format_due_date + + private + def format_checkout + checkout_date = Date.today.strftime('%Y-%m-%d') + end + + def format_due_date + due_date = (Date.today + 10).strftime('%Y-%m-%d') + end + end From 42e46c3ce2bc4026b67c17b4364b10ff18bf23aa Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Mon, 6 Nov 2017 16:41:54 -0800 Subject: [PATCH 20/42] adds checkout_date to rentals --- db/migrate/20171107003916_add_checkout_date_to_rentals.rb | 7 +++++++ db/schema.rb | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20171107003916_add_checkout_date_to_rentals.rb diff --git a/db/migrate/20171107003916_add_checkout_date_to_rentals.rb b/db/migrate/20171107003916_add_checkout_date_to_rentals.rb new file mode 100644 index 000000000..32638dd4c --- /dev/null +++ b/db/migrate/20171107003916_add_checkout_date_to_rentals.rb @@ -0,0 +1,7 @@ +class AddCheckoutDateToRentals < ActiveRecord::Migration[5.1] + + def change + add_column :rentals, :checkout_date, :string + end + +end diff --git a/db/schema.rb b/db/schema.rb index be19187dd..dc517ba13 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: 20171107000926) do +ActiveRecord::Schema.define(version: 20171107003916) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -45,6 +45,7 @@ t.string "due_date" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "checkout_date" end end From 4d8d5d734978bce8518ecdf649cfc41722fe1235 Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Tue, 7 Nov 2017 09:33:45 -0800 Subject: [PATCH 21/42] Serializers for customer and movie model, updated controller methods to utilize serializers. --- Gemfile | 1 + Gemfile.lock | 9 +++++++++ app/controllers/customers_controller.rb | 4 ++-- app/controllers/movies_controller.rb | 6 +++--- app/controllers/rentals_controller.rb | 3 +++ app/models/rental.rb | 4 ++-- app/serializers/customer_serializer.rb | 3 +++ app/serializers/movie_serializer.rb | 3 +++ config/routes.rb | 1 + 9 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 app/serializers/customer_serializer.rb create mode 100644 app/serializers/movie_serializer.rb diff --git a/Gemfile b/Gemfile index 4ae55c93d..91f421d0e 100644 --- a/Gemfile +++ b/Gemfile @@ -24,6 +24,7 @@ gem 'puma', '~> 3.7' # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible # gem 'rack-cors' +gem "active_model_serializers" group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console diff --git a/Gemfile.lock b/Gemfile.lock index 9593b240a..1ac6880e6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -24,6 +24,11 @@ GEM erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) + active_model_serializers (0.10.6) + actionpack (>= 4.1, < 6) + activemodel (>= 4.1, < 6) + case_transform (>= 0.2) + jsonapi-renderer (>= 0.1.1.beta1, < 0.2) activejob (5.1.4) activesupport (= 5.1.4) globalid (>= 0.3.6) @@ -48,6 +53,8 @@ GEM debug_inspector (>= 0.0.1) builder (3.2.3) byebug (9.1.0) + case_transform (0.2) + activesupport coderay (1.1.2) concurrent-ruby (1.0.5) crass (1.0.2) @@ -61,6 +68,7 @@ GEM jquery-turbolinks (2.1.0) railties (>= 3.1.0) turbolinks + jsonapi-renderer (0.1.3) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -151,6 +159,7 @@ PLATFORMS ruby DEPENDENCIES + active_model_serializers better_errors binding_of_caller byebug diff --git a/app/controllers/customers_controller.rb b/app/controllers/customers_controller.rb index 420c947ca..5db6f4c85 100644 --- a/app/controllers/customers_controller.rb +++ b/app/controllers/customers_controller.rb @@ -2,14 +2,14 @@ class CustomersController < ApplicationController def index customers = Customer.all - render json: customers.as_json(only: [:id, :name, :registered_at, :address, :city, :state, :postal_code, :phone, :account_credit, :movies_checked_out_count]), + render json: customers, status: :ok end def show customer = Customer.find_by_id(params[:id]) if customer - render json: customer.as_json(only: [:id, :name, :registered_at, :address, :city, :state, :postal_code, :phone, :account_credit, :movies_checked_out_count]), status: :ok + render json: customer, status: :ok else render json: {ok: false}, status: :not_found end diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index 4359ce67f..8529f0b1f 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -1,14 +1,14 @@ class MoviesController < ApplicationController def index movies = Movie.all - render json: movies.as_json(only: [:id, :title, :overview, :release_date, :inventory, :available_inventory]), + render json: movies, status: :ok end def show movie = Movie.find_by(title: params[:id]) if movie - render json: movie.as_json(only: [:id, :title, :overview, :release_date, :inventory, :available_inventory]), status: :ok + render json: movie, status: :ok else render json: {ok: false}, status: :not_found end @@ -17,7 +17,7 @@ def show def create movie = Movie.new(movie_params) if movie.save - render json: { id: movie.id }, status: :ok + render json: movie, status: :ok else render json: {errors: movie.errors.messages}, status: :bad_request diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index a2cb7720d..c059294b5 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -9,5 +9,8 @@ def check_in def overdue end + def create + end + end diff --git a/app/models/rental.rb b/app/models/rental.rb index 847aa6178..ac2bd75bc 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -5,11 +5,11 @@ class Rental < ApplicationRecord private def format_checkout - checkout_date = Date.today.strftime('%Y-%m-%d') + self.checkout_date = Date.today.strftime('%Y-%m-%d') end def format_due_date - due_date = (Date.today + 10).strftime('%Y-%m-%d') + self.due_date = (Date.today + 10).strftime('%Y-%m-%d') end end diff --git a/app/serializers/customer_serializer.rb b/app/serializers/customer_serializer.rb new file mode 100644 index 000000000..21de979ab --- /dev/null +++ b/app/serializers/customer_serializer.rb @@ -0,0 +1,3 @@ +class CustomerSerializer < ActiveModel::Serializer + attributes :id, :name, :registered_at, :address, :city, :state, :postal_code, :phone, :account_credit, :movies_checked_out_count +end diff --git a/app/serializers/movie_serializer.rb b/app/serializers/movie_serializer.rb new file mode 100644 index 000000000..510aad5d6 --- /dev/null +++ b/app/serializers/movie_serializer.rb @@ -0,0 +1,3 @@ +class MovieSerializer < ActiveModel::Serializer + attributes :id, :title, :overview, :release_date, :inventory, :available_inventory +end diff --git a/config/routes.rb b/config/routes.rb index 9fc92ab4e..0d68a65c1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,4 +6,5 @@ post '/rentals/check_out', to: 'rentals#check_out', as: 'checkout' post '/rentals/check_in', to: 'rentals#check_in', as: 'checkin' get '/rentals/overdue', to: 'rentals#overdue', as: 'overdue' + get '/rentals/create', to: 'rentals#create', as: 'new_rental' end From aa92e6e7480f8866a1d94af473119a674bb06fa1 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Tue, 7 Nov 2017 12:50:28 -0800 Subject: [PATCH 22/42] adds checkin and checkout pseudocode and tests --- app/controllers/rentals_controller.rb | 30 ++++++++---- test/controllers/rentals_controller_test.rb | 54 +++++++++++++++++++++ 2 files changed, 74 insertions(+), 10 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index c059294b5..e7ea6d4e3 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -1,16 +1,26 @@ class RentalsController < ApplicationController - def check_out - end + def check_out(movie_id, customer_id) + movie = Movie.find_by_id(movie_id) + customer = Customer.find_by_id(customer_id) + if movie && movie.available_inventory > 0 + movie.available_inventory -= 1 + else + error + end + if customer + customer.movies_checked_out_count += 1 + else + error + end + ++ render json status 201 created + end - def check_in - end + def check_in + end + + def overdue + end - def overdue - end - def create end - - -end diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index c87623d89..dc841851b 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -5,6 +5,31 @@ post checkout_path must_respond_with :success end + + it "decrements number of movies available in inventory" do + + end + + it "increments number of movies customer has rented" do + + end + + it "doesn't succeed for customer that doesn't exit " do + + end + + it "doesn't succeed for movie that doesn't exit " do + + end + + it "doesn't succeed if zero inventory " do + + end + + it "succeeds if sufficient inventory " do + + end + end describe "check_in" do @@ -12,6 +37,23 @@ post checkin_path must_respond_with :success end + + it "increments number of movies available in inventory" do + + end + + it "decrements number of movies customer has rented" do + + end + + it "doesn't succeed for customer that doesn't exit " do + + end + + it "doesn't succeed for movie that doesn't exit " do + + end + end describe "overdue" do @@ -19,6 +61,18 @@ get overdue_path must_respond_with :success end + + it "returns list of all customers with overdue movies" do + + end + + it "returns empty array if no customers with overdue movies" do + + end + + it "succeeds wether there are any overdue customers" do + + end end end From f8a94ada8128ce8aba7fa0b13771fa064ae9a493 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Tue, 7 Nov 2017 12:57:19 -0800 Subject: [PATCH 23/42] adds check_out, may need r4eworking --- app/controllers/rentals_controller.rb | 26 ++++++++++----------- test/controllers/rentals_controller_test.rb | 1 + 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index e7ea6d4e3..c337cee5f 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -1,26 +1,24 @@ class RentalsController < ApplicationController def check_out(movie_id, customer_id) + movie = Movie.find_by_id(movie_id) customer = Customer.find_by_id(customer_id) - if movie && movie.available_inventory > 0 + + if customer && movie && movie.available_inventory > 0 movie.available_inventory -= 1 - else - error - end - if customer customer.movies_checked_out_count += 1 + render json: {ok: true}, status: :created else - error - end - ++ render json status 201 created - end - - def check_in - end - - def overdue + render json: {ok: false}, status: :bad_request end + end + def check_in + end + def overdue end + + +end diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index dc841851b..98a647dd0 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -1,6 +1,7 @@ require "test_helper" describe RentalsController do describe "check_out" do + it "succeeds" do post checkout_path must_respond_with :success From 71b2945c7021557d0e461aee8bcc195bc90d7a60 Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Tue, 7 Nov 2017 13:39:36 -0800 Subject: [PATCH 24/42] Functioning rental checkout method and updated routes. --- app/controllers/rentals_controller.rb | 22 ++++++++++++++------- app/models/rental.rb | 22 +++++++++++++++++++++ config/routes.rb | 2 +- test/controllers/rentals_controller_test.rb | 1 + 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index c337cee5f..ad91d606f 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -1,22 +1,30 @@ class RentalsController < ApplicationController - def check_out(movie_id, customer_id) + def check_out + rental = Rental.new + rental.save + if rental.checkout(params[:movie_id], params[:customer_id]) + # render json: Movie.find_by_id(params[:movie_id]), status: :created + render json: rental.as_json(only: [:id, :checkout_date, :due_date]), status: :created + else + render json: {ok: false}, status: :bad_request + end + end + + def check_in(movie_id, customer_id) movie = Movie.find_by_id(movie_id) customer = Customer.find_by_id(customer_id) - if customer && movie && movie.available_inventory > 0 - movie.available_inventory -= 1 - customer.movies_checked_out_count += 1 + if customer && movie && customer.movies_checked_out_count > 0 + movie.available_inventory += 1 + customer.movies_checked_out_count -= 1 render json: {ok: true}, status: :created else render json: {ok: false}, status: :bad_request end end - def check_in - end - def overdue end diff --git a/app/models/rental.rb b/app/models/rental.rb index ac2bd75bc..c0704c91d 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -3,6 +3,28 @@ class Rental < ApplicationRecord before_create :format_checkout, :format_due_date + def checkout(movie_id, customer_id) + movie = Movie.find_by_id(movie_id) + customer = Customer.find_by_id(customer_id) + + if customer && movie && movie.available_inventory > 0 + puts movie.title + puts customer.name + puts movie.available_inventory + movie.available_inventory -= 1 + puts movie.available_inventory + movie.save + customer.movies_checked_out_count += 1 + customer.save + return true + else + return false + end + end + + + + private def format_checkout self.checkout_date = Date.today.strftime('%Y-%m-%d') diff --git a/config/routes.rb b/config/routes.rb index 0d68a65c1..4bffd3315 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,7 +3,7 @@ resources :customers resources :movies - post '/rentals/check_out', to: 'rentals#check_out', as: 'checkout' + post '/rentals/check_out/:movie_id/:customer_id', to: 'rentals#check_out', as: 'checkout' post '/rentals/check_in', to: 'rentals#check_in', as: 'checkin' get '/rentals/overdue', to: 'rentals#overdue', as: 'overdue' get '/rentals/create', to: 'rentals#create', as: 'new_rental' diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 98a647dd0..6358eaa05 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -8,6 +8,7 @@ end it "decrements number of movies available in inventory" do + post checkout_path() end From 05da028296e685f9ce263e832b1e4c2c97f10545 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Tue, 7 Nov 2017 14:04:46 -0800 Subject: [PATCH 25/42] adds checkin method that needs reworking --- app/controllers/rentals_controller.rb | 19 ++++++++----------- app/models/rental.rb | 19 +++++++++++++------ config/routes.rb | 2 +- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index ad91d606f..88ffb9dce 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -6,26 +6,23 @@ def check_out if rental.checkout(params[:movie_id], params[:customer_id]) # render json: Movie.find_by_id(params[:movie_id]), status: :created render json: rental.as_json(only: [:id, :checkout_date, :due_date]), status: :created - else - render json: {ok: false}, status: :bad_request + render json: {ok: false, message: "Exceeded available inventory!"}, status: :bad_request end end - def check_in(movie_id, customer_id) - movie = Movie.find_by_id(movie_id) - customer = Customer.find_by_id(customer_id) - - if customer && movie && customer.movies_checked_out_count > 0 - movie.available_inventory += 1 - customer.movies_checked_out_count -= 1 - render json: {ok: true}, status: :created + def check_in + rental = Rental.new + rental.save + if rental.checkin(params[:movie_id], params[:customer_id]) + render json: rental.as_json(only: [:id, :checkout_date, :due_date]), status: :created else - render json: {ok: false}, status: :bad_request + render json: {ok: false, error: "Customer has not checked out this movie yet."}, status: :bad_request end end def overdue + end diff --git a/app/models/rental.rb b/app/models/rental.rb index c0704c91d..9a97b26da 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -6,13 +6,8 @@ class Rental < ApplicationRecord def checkout(movie_id, customer_id) movie = Movie.find_by_id(movie_id) customer = Customer.find_by_id(customer_id) - if customer && movie && movie.available_inventory > 0 - puts movie.title - puts customer.name - puts movie.available_inventory movie.available_inventory -= 1 - puts movie.available_inventory movie.save customer.movies_checked_out_count += 1 customer.save @@ -22,7 +17,19 @@ def checkout(movie_id, customer_id) end end - +def checkin(movie_id, customer_id) + movie = Movie.find_by_id(movie_id) + customer = Customer.find_by_id(customer_id) + if customer && movie && customer.movies_checked_out_count > 0 + movie.available_inventory += 1 + movie.save + customer.movies_checked_out_count -= 1 + customer.save + return true + else + return false + end +end private diff --git a/config/routes.rb b/config/routes.rb index 4bffd3315..c77e997b1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,7 +4,7 @@ resources :movies post '/rentals/check_out/:movie_id/:customer_id', to: 'rentals#check_out', as: 'checkout' - post '/rentals/check_in', to: 'rentals#check_in', as: 'checkin' + post '/rentals/check_in/:movie_id/:customer_id', to: 'rentals#check_in', as: 'checkin' get '/rentals/overdue', to: 'rentals#overdue', as: 'overdue' get '/rentals/create', to: 'rentals#create', as: 'new_rental' end From d748e2c287495b4d2347093f6f27d0b239095728 Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Tue, 7 Nov 2017 14:16:39 -0800 Subject: [PATCH 26/42] Checkin method calls rental object, updated controller and models. --- app/controllers/rentals_controller.rb | 9 ++++++--- app/models/rental.rb | 6 +++--- config/routes.rb | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 88ffb9dce..6d2e1835e 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -2,7 +2,10 @@ class RentalsController < ApplicationController def check_out rental = Rental.new + rental.customer_id = params[:customer_id] + rental.movie_id = params[:movie_id] rental.save + if rental.checkout(params[:movie_id], params[:customer_id]) # render json: Movie.find_by_id(params[:movie_id]), status: :created render json: rental.as_json(only: [:id, :checkout_date, :due_date]), status: :created @@ -12,9 +15,9 @@ def check_out end def check_in - rental = Rental.new + rental = Rental.find_by_id(params[:rental_id]) rental.save - if rental.checkin(params[:movie_id], params[:customer_id]) + if rental.checkin render json: rental.as_json(only: [:id, :checkout_date, :due_date]), status: :created else render json: {ok: false, error: "Customer has not checked out this movie yet."}, status: :bad_request @@ -22,7 +25,7 @@ def check_in end def overdue - + end diff --git a/app/models/rental.rb b/app/models/rental.rb index 9a97b26da..f28ac4466 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -17,9 +17,9 @@ def checkout(movie_id, customer_id) end end -def checkin(movie_id, customer_id) - movie = Movie.find_by_id(movie_id) - customer = Customer.find_by_id(customer_id) +def checkin + customer = Customer.find_by_id(self.customer_id) + movie = Movie.find_by_id(self.movie_id) if customer && movie && customer.movies_checked_out_count > 0 movie.available_inventory += 1 movie.save diff --git a/config/routes.rb b/config/routes.rb index c77e997b1..af99f26d4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,7 +4,7 @@ resources :movies post '/rentals/check_out/:movie_id/:customer_id', to: 'rentals#check_out', as: 'checkout' - post '/rentals/check_in/:movie_id/:customer_id', to: 'rentals#check_in', as: 'checkin' + post '/rentals/check_in/:rental_id', to: 'rentals#check_in', as: 'checkin' get '/rentals/overdue', to: 'rentals#overdue', as: 'overdue' get '/rentals/create', to: 'rentals#create', as: 'new_rental' end From eb8034a1dffa355efdf33a25125ef3c3fdb01c3b Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Tue, 7 Nov 2017 15:25:25 -0800 Subject: [PATCH 27/42] Updated rentals controller to clean up. --- app/controllers/rentals_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 6d2e1835e..33504b44e 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -8,7 +8,7 @@ def check_out if rental.checkout(params[:movie_id], params[:customer_id]) # render json: Movie.find_by_id(params[:movie_id]), status: :created - render json: rental.as_json(only: [:id, :checkout_date, :due_date]), status: :created + render json: rental.as_json(only: [:id, :checkout_date, :due_date, :customer_id, :movie_id]), status: :created else render json: {ok: false, message: "Exceeded available inventory!"}, status: :bad_request end From c744eab1287204bdc89d709e996b5266eea47f60 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Tue, 7 Nov 2017 16:53:57 -0800 Subject: [PATCH 28/42] adds some passing and some failing controller tests --- Gemfile | 3 +- app/controllers/rentals_controller.rb | 38 ++++++--- app/models/rental.rb | 31 ++++--- config/routes.rb | 2 +- test/controllers/rentals_controller_test.rb | 95 ++++++++++++++------- test/fixtures/movies.yml | 4 +- test/fixtures/rentals.yml | 16 ++-- test/models/rental_test.rb | 69 +++++++++++++-- 8 files changed, 184 insertions(+), 74 deletions(-) diff --git a/Gemfile b/Gemfile index 91f421d0e..b0a46563d 100644 --- a/Gemfile +++ b/Gemfile @@ -29,6 +29,8 @@ gem "active_model_serializers" 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 'pry-rails' end group :development do @@ -44,7 +46,6 @@ gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'jquery-turbolinks' group :development do gem 'better_errors' - gem 'pry-rails' gem 'binding_of_caller' end diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 88ffb9dce..d2c4f22b7 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -1,29 +1,45 @@ class RentalsController < ApplicationController def check_out + rental = Rental.new + rental.customer_id = params[:customer_id] + rental.movie_id = params[:movie_id] rental.save if rental.checkout(params[:movie_id], params[:customer_id]) - # render json: Movie.find_by_id(params[:movie_id]), status: :created - render json: rental.as_json(only: [:id, :checkout_date, :due_date]), status: :created + render json: rental.as_json(only: [:id, :checkout_date, :due_date, :customer_id, :movie_id]), status: :created else - render json: {ok: false, message: "Exceeded available inventory!"}, status: :bad_request + render json: {ok: false, errors: "Exceeded available inventory!"}, status: :bad_request end end + def check_in - rental = Rental.new - rental.save - if rental.checkin(params[:movie_id], params[:customer_id]) + rental = Rental.find_by_id(params[:rental_id]) + if !rental + render json: rental.as_json(errors: "Customer has not checked out this movie yet."), status: :bad_request + elsif + rental.save + rental.checkin render json: rental.as_json(only: [:id, :checkout_date, :due_date]), status: :created else - render json: {ok: false, error: "Customer has not checked out this movie yet."}, status: :bad_request + render json: rental.as_json(errors: "Customer has not checked out this movie yet."), status: :bad_request end end - def overdue - - end - + def overdue(due_date) + # while due_date < today + # overdue is false + # + # if today > due_date + # movie's due_date is < today + # movie that is in the rental + # return + # render json: rental.as_json(only: [:title, :customer_id, :name, :postal_code, :checkout_date, :due_date ]), status: :created + # else + # return [] + # end + # List all customers with overdue movies + end end diff --git a/app/models/rental.rb b/app/models/rental.rb index 9a97b26da..b6e78d109 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -1,11 +1,14 @@ require 'date' class Rental < ApplicationRecord + before_create :format_checkout, :format_due_date def checkout(movie_id, customer_id) + movie = Movie.find_by_id(movie_id) customer = Customer.find_by_id(customer_id) + if customer && movie && movie.available_inventory > 0 movie.available_inventory -= 1 movie.save @@ -17,22 +20,24 @@ def checkout(movie_id, customer_id) end end -def checkin(movie_id, customer_id) - movie = Movie.find_by_id(movie_id) - customer = Customer.find_by_id(customer_id) - if customer && movie && customer.movies_checked_out_count > 0 - movie.available_inventory += 1 - movie.save - customer.movies_checked_out_count -= 1 - customer.save - return true - else - return false - end -end + def checkin + + customer = Customer.find_by_id(self.customer_id) + movie = Movie.find_by_id(self.movie_id) + if customer && movie && customer.movies_checked_out_count > 0 + movie.available_inventory += 1 + movie.save + customer.movies_checked_out_count -= 1 + customer.save + return true + else + return false + end + end private + def format_checkout self.checkout_date = Date.today.strftime('%Y-%m-%d') end diff --git a/config/routes.rb b/config/routes.rb index c77e997b1..af99f26d4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,7 +4,7 @@ resources :movies post '/rentals/check_out/:movie_id/:customer_id', to: 'rentals#check_out', as: 'checkout' - post '/rentals/check_in/:movie_id/:customer_id', to: 'rentals#check_in', as: 'checkin' + post '/rentals/check_in/:rental_id', to: 'rentals#check_in', as: 'checkin' get '/rentals/overdue', to: 'rentals#overdue', as: 'overdue' get '/rentals/create', to: 'rentals#create', as: 'new_rental' end diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 6358eaa05..8ef72c5f8 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -1,64 +1,99 @@ require "test_helper" describe RentalsController do - describe "check_out" do - it "succeeds" do - post checkout_path - must_respond_with :success - end + describe "checkout" do - it "decrements number of movies available in inventory" do - post checkout_path() + let(:rental_data) { + { + movie_id: Movie.first.id, + customer_id: Customer.first.id + } + } + it "is a valid route" do + post checkout_url(rental_data) + must_respond_with :created end - it "increments number of movies customer has rented" do - + it 'returns json' do + post checkout_path(Movie.first.id, Customer.first.id) + response.header['Content-Type'].must_include 'json' end - it "doesn't succeed for customer that doesn't exit " do - + it 'returns an hash' do + post checkout_path(movie_id: Movie.first.id, customer_id: Customer.first.id) + body = JSON.parse(response.body) + body.must_be_kind_of Hash end - it "doesn't succeed for movie that doesn't exit " do + it 'returns an hash even if no data' do + Movie.destroy_all + post checkout_path(Movie.first.id, Customer.first.id) + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "errors" + body["errors"].must_include "Exceeded available inventory!" end - it "doesn't succeed if zero inventory " do - + it "returns rental with exactly the required fields" do + keys = %w(id checkout_date due_date customer_id movie_id) + post checkout_path(Movie.first.id, Customer.first.id) + body = JSON.parse(response.body) + body.each do |rental| + rental.keys.must_equal keys + end end + end - it "succeeds if sufficient inventory " do + describe 'checkin' do + let(:rental_data) { + { + movie_id: Movie.first.id, + customer_id: Customer.first.id + } + } + it "is a valid route" do + post checkin_path(rentals(:not_overdue).id) + must_respond_with :created end - end - - describe "check_in" do - it "succeeds" do - post checkin_path - must_respond_with :success + it 'returns json' do + post checkin_path(rentals(:not_overdue).id) + response.header['Content-Type'].must_include 'json' end - it "increments number of movies available in inventory" do - + it 'returns an hash' do + post checkin_path(rentals(:not_overdue).id) + body = JSON.parse(response.body) + body.must_be_kind_of Hash end - it "decrements number of movies customer has rented" do + it 'returns an hash even if no data' do + Movie.destroy_all + post checkin_path(rentals(:not_overdue).id) + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "errors" + body["errors"].must_include "Customer has not checked out this movie yet." end - it "doesn't succeed for customer that doesn't exit " do - + it "returns rental with exactly the required fields" do + keys = %w(id checkout_date due_date customer_id movie_id) + post checkin_path(rentals(:not_overdue).id) + body = JSON.parse(response.body) + body.each do |rental| + rental.keys.must_equal keys + end end + end - it "doesn't succeed for movie that doesn't exit " do - - end - end describe "overdue" do + it "succeeds" do get overdue_path must_respond_with :success diff --git a/test/fixtures/movies.yml b/test/fixtures/movies.yml index f27c216bf..5208195fb 100644 --- a/test/fixtures/movies.yml +++ b/test/fixtures/movies.yml @@ -4,10 +4,12 @@ one: title: Secret Life Of Pets overview: A movie about pets release_date: 12/27/2016 - inventory: 2 + inventory: 8 + available_inventory: 8 two: title: Baby Driver overview: High speed chase galore release_date: 07/07/2017 inventory: 1 + available_inventory: 1 diff --git a/test/fixtures/rentals.yml b/test/fixtures/rentals.yml index 84ac60fdf..ae288eca8 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: - customer_id: 1 - movie_id: 1 - due_date: MyString +not_overdue: + customer_id: one + movie_id: one + due_date: "11/25/2017" -two: - customer_id: 1 - movie_id: 1 - due_date: MyString +overdue: + customer_id: two + movie_id: two + due_date: "11/05/2017" diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb index 6ea53d94f..3e05c9d7f 100644 --- a/test/models/rental_test.rb +++ b/test/models/rental_test.rb @@ -1,9 +1,60 @@ -require "test_helper" - -describe Rental do - let(:rental) { Rental.new } - - it "must be valid" do - value(rental).must_be :valid? - end -end +# require "test_helper" +# +# describe Rental do +# let(:rental) { Rental.new } +# +# it "must be valid" do +# value(rental).must_be :valid? +# end +# end +# +# +# it "decrements number of movies available in inventory" do +# post checkout_path() +# +# end +# +# it "increments number of movies customer has rented" do +# +# end +# +# it "doesn't succeed for customer that doesn't exit " do +# +# end +# +# it "doesn't succeed for movie that doesn't exit " do +# +# end +# +# it "doesn't succeed if zero inventory " do +# +# end +# +# it "succeeds if sufficient inventory " do +# +# end +# +# +# describe "check_in" do +# it "succeeds" do +# post checkin_path +# must_respond_with :success +# end +# +# it "increments number of movies available in inventory" do +# +# end +# +# it "decrements number of movies customer has rented" do +# +# end +# +# it "doesn't succeed for customer that doesn't exit " do +# +# end +# +# it "doesn't succeed for movie that doesn't exit " do +# +# end +# +# end From 2adf85235a22f03142efac87c1fe07d551183672 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Tue, 7 Nov 2017 16:59:12 -0800 Subject: [PATCH 29/42] adds controller tests --- app/controllers/rentals_controller.rb | 53 +++++++++++---------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 07087b707..0184660d2 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -8,10 +8,7 @@ def check_out rental.save if rental.checkout(params[:movie_id], params[:customer_id]) -<<<<<<< HEAD -======= - # render json: Movie.find_by_id(params[:movie_id]), status: :created ->>>>>>> eb8034a1dffa355efdf33a25125ef3c3fdb01c3b + render json: rental.as_json(only: [:id, :checkout_date, :due_date, :customer_id, :movie_id]), status: :created else render json: {ok: false, errors: "Exceeded available inventory!"}, status: :bad_request @@ -21,42 +18,34 @@ def check_out def check_in rental = Rental.find_by_id(params[:rental_id]) -<<<<<<< HEAD if !rental render json: rental.as_json(errors: "Customer has not checked out this movie yet."), status: :bad_request elsif rental.save rental.checkin -======= - rental.save - if rental.checkin ->>>>>>> eb8034a1dffa355efdf33a25125ef3c3fdb01c3b - render json: rental.as_json(only: [:id, :checkout_date, :due_date]), status: :created - else - render json: rental.as_json(errors: "Customer has not checked out this movie yet."), status: :bad_request + if rental.checkin + render json: rental.as_json(only: [:id, :checkout_date, :due_date]), status: :created + else + render json: rental.as_json(errors: "Customer has not checked out this movie yet."), status: :bad_request + end end - end - -<<<<<<< HEAD - def overdue(due_date) - # while due_date < today - # overdue is false - # - # if today > due_date - # movie's due_date is < today - # movie that is in the rental - # return - # render json: rental.as_json(only: [:title, :customer_id, :name, :postal_code, :checkout_date, :due_date ]), status: :created - # else - # return [] - # end - # List all customers with overdue movies -======= - def overdue + def overdue(due_date) + # while due_date < today + # overdue is false + # + # if today > due_date + # movie's due_date is < today + # movie that is in the rental + # return + # render json: rental.as_json(only: [:title, :customer_id, :name, :postal_code, :checkout_date, :due_date ]), status: :created + # else + # return [] + # end + # List all customers with overdue movies + end end ->>>>>>> eb8034a1dffa355efdf33a25125ef3c3fdb01c3b - end + end From d4d1c4f59504cb40865021e5d5443df06fe45990 Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Wed, 8 Nov 2017 09:43:48 -0800 Subject: [PATCH 30/42] Additional passing controller tests for rentals, some updates to rentals controller for errors and status. --- app/controllers/rentals_controller.rb | 39 ++++++++++----------- test/controllers/rentals_controller_test.rb | 19 ++++------ test/fixtures/customers.yml | 2 ++ test/fixtures/movies.yml | 2 +- 4 files changed, 27 insertions(+), 35 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 0184660d2..fd82603b5 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -17,10 +17,10 @@ def check_out def check_in - rental = Rental.find_by_id(params[:rental_id]) - if !rental - render json: rental.as_json(errors: "Customer has not checked out this movie yet."), status: :bad_request - elsif + rental = Rental.find_by(id: params[:rental_id]) + if rental == nil + render json: {ok: false, errors: "This rental does not exist"}, status: :bad_request + else rental.save rental.checkin if rental.checkin @@ -29,23 +29,20 @@ def check_in render json: rental.as_json(errors: "Customer has not checked out this movie yet."), status: :bad_request end end - - def overdue(due_date) - # while due_date < today - # overdue is false - # - # if today > due_date - # movie's due_date is < today - # movie that is in the rental - # return - # render json: rental.as_json(only: [:title, :customer_id, :name, :postal_code, :checkout_date, :due_date ]), status: :created - # else - # return [] - # end - # List all customers with overdue movies - end end - - + def overdue + # while due_date < today + # overdue is false + # + # if today > due_date + # movie's due_date is < today + # movie that is in the rental + # return + # render json: rental.as_json(only: [:title, :customer_id, :name, :postal_code, :checkout_date, :due_date ]), status: :created + # else + # return [] + # end + # List all customers with overdue movies + end end diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 8ef72c5f8..56b42779d 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -27,9 +27,7 @@ end it 'returns an hash even if no data' do - Movie.destroy_all - - post checkout_path(Movie.first.id, Customer.first.id) + post checkout_path(Movie.last.id + 1, Customer.first.id + 1) body = JSON.parse(response.body) body.must_be_kind_of Hash body.must_include "errors" @@ -37,12 +35,10 @@ end it "returns rental with exactly the required fields" do - keys = %w(id checkout_date due_date customer_id movie_id) + keys = %w(id customer_id movie_id due_date checkout_date) post checkout_path(Movie.first.id, Customer.first.id) body = JSON.parse(response.body) - body.each do |rental| - rental.keys.must_equal keys - end + body.keys.must_equal keys end end @@ -66,27 +62,24 @@ it 'returns an hash' do post checkin_path(rentals(:not_overdue).id) + body = JSON.parse(response.body) body.must_be_kind_of Hash end it 'returns an hash even if no data' do - Movie.destroy_all - post checkin_path(rentals(:not_overdue).id) + post checkin_path(999) body = JSON.parse(response.body) body.must_be_kind_of Hash body.must_include "errors" - body["errors"].must_include "Customer has not checked out this movie yet." end it "returns rental with exactly the required fields" do keys = %w(id checkout_date due_date customer_id movie_id) post checkin_path(rentals(:not_overdue).id) body = JSON.parse(response.body) - body.each do |rental| - rental.keys.must_equal keys - end + body.keys.must_equal keys end end diff --git a/test/fixtures/customers.yml b/test/fixtures/customers.yml index 7ec08f024..9a390658e 100644 --- a/test/fixtures/customers.yml +++ b/test/fixtures/customers.yml @@ -8,6 +8,7 @@ one: state: WA postal_code: "12345" phone: 123-456-789 + movies_checked_out_count: 3 two: name: Kimberley @@ -17,3 +18,4 @@ two: state: WA postal_code: "12346" phone: 234-567-890 + movies_checked_out_count: 4 diff --git a/test/fixtures/movies.yml b/test/fixtures/movies.yml index 5208195fb..d8e4dd0a4 100644 --- a/test/fixtures/movies.yml +++ b/test/fixtures/movies.yml @@ -12,4 +12,4 @@ two: overview: High speed chase galore release_date: 07/07/2017 inventory: 1 - available_inventory: 1 + available_inventory: 4 From e768ad6b92a024750902884c86a8a930a628801a Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Wed, 8 Nov 2017 10:00:25 -0800 Subject: [PATCH 31/42] All rentals controller tests passing after checking out a movie before attempting to checkin. --- app/controllers/rentals_controller.rb | 4 ++-- test/controllers/rentals_controller_test.rb | 14 +++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index fd82603b5..d65be6148 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -24,9 +24,9 @@ def check_in rental.save rental.checkin if rental.checkin - render json: rental.as_json(only: [:id, :checkout_date, :due_date]), status: :created + render json: rental.as_json(only: [:id, :checkout_date, :due_date, :customer_id, :movie_id]), status: :created else - render json: rental.as_json(errors: "Customer has not checked out this movie yet."), status: :bad_request + render json: {ok: false, errors: "Customer has not checked out this movie yet."}, status: :bad_request end end end diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 56b42779d..ed156bf9c 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -51,7 +51,11 @@ } it "is a valid route" do - post checkin_path(rentals(:not_overdue).id) + post checkout_url(rental_data) + body = JSON.parse(response.body) + id = body['id'] + + post checkin_path(id) must_respond_with :created end @@ -76,8 +80,12 @@ end it "returns rental with exactly the required fields" do - keys = %w(id checkout_date due_date customer_id movie_id) - post checkin_path(rentals(:not_overdue).id) + post checkout_url(rental_data) + body = JSON.parse(response.body) + id = body['id'] + + keys = ["id", "customer_id", "movie_id", "due_date", "checkout_date"] + post checkin_path(id) body = JSON.parse(response.body) body.keys.must_equal keys end From 80b2f6994da5a8c1bd0d3691bb549ff7c7718185 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Wed, 8 Nov 2017 10:49:02 -0800 Subject: [PATCH 32/42] adds overdue and tests --- app/controllers/rentals_controller.rb | 26 ++++++++-------- test/controllers/rentals_controller_test.rb | 34 ++++++++++++++++++--- test/fixtures/rentals.yml | 4 +-- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index d65be6148..955207948 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -1,3 +1,4 @@ +require 'date' class RentalsController < ApplicationController def check_out @@ -32,17 +33,18 @@ def check_in end def overdue - # while due_date < today - # overdue is false - # - # if today > due_date - # movie's due_date is < today - # movie that is in the rental - # return - # render json: rental.as_json(only: [:title, :customer_id, :name, :postal_code, :checkout_date, :due_date ]), status: :created - # else - # return [] - # end - # List all customers with overdue movies + overdue_rentals = [] + Rental.all.each do |rental| + if Date.parse(rental.due_date) < Date.today + overdue_rentals << rental + end + end + if overdue_rentals.count != 0 + overdue_rentals.each do |rental| + render json: rental.as_json(only: [:title, :customer_id, :name, :postal_code, :checkout_date, :due_date ]), status: :ok + end + else + render json: {ok: false, errors: "There are no overdue rentals to show."}, status: :no_content + end end end diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index ed156bf9c..44942a011 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -1,4 +1,5 @@ require "test_helper" +require 'date' describe RentalsController do describe "checkout" do @@ -43,6 +44,7 @@ end describe 'checkin' do + let(:rental_data) { { movie_id: Movie.first.id, @@ -89,27 +91,49 @@ body = JSON.parse(response.body) body.keys.must_equal keys end + + it "will not check in a movie that hasn't been checked out" do + post checkin_path(rentals(:not_overdue).id) + body = JSON.parse(response.body) + body.must_be_kind_of Hash + body.must_include "errors" + body["ok"].must_equal false + end end describe "overdue" do + # let(:rental_data) { + # { + # movie_id: Movie[1].id + # customer_id: Movie[1].id + # } + # } + it "succeeds" do get overdue_path must_respond_with :success end it "returns list of all customers with overdue movies" do - + keys = ["id", "customer_id", "movie_id", "due_date", "checkout_date"] + get overdue_path + body = JSON.parse(response.body) + body.keys.must_equal keys end - it "returns empty array if no customers with overdue movies" do - + it "if no overdue movies, status is no_content 204" do + Rental.destroy_all + get overdue_path + must_respond_with :success end - it "succeeds wether there are any overdue customers" do - + it "succeeds wether there are any overdue customers with 2XX response" do + Rental.destroy_all + get overdue_path + must_respond_with :success end end diff --git a/test/fixtures/rentals.yml b/test/fixtures/rentals.yml index ae288eca8..b54101df8 100644 --- a/test/fixtures/rentals.yml +++ b/test/fixtures/rentals.yml @@ -3,9 +3,9 @@ not_overdue: customer_id: one movie_id: one - due_date: "11/25/2017" + due_date: "25/11/2017" overdue: customer_id: two movie_id: two - due_date: "11/05/2017" + due_date: "05/11/2017" From d64fc7881c00b19c2fb8c9581d1b9317687c4f2b Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Wed, 8 Nov 2017 10:49:40 -0800 Subject: [PATCH 33/42] Deleted extra ideas from rentals overdue method. --- app/controllers/rentals_controller.rb | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index d65be6148..bfe8c9299 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -32,17 +32,6 @@ def check_in end def overdue - # while due_date < today - # overdue is false - # - # if today > due_date - # movie's due_date is < today - # movie that is in the rental - # return - # render json: rental.as_json(only: [:title, :customer_id, :name, :postal_code, :checkout_date, :due_date ]), status: :created - # else - # return [] - # end - # List all customers with overdue movies + end end From d96b6a95280880d81f9fc583425a2d45e634afb4 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Wed, 8 Nov 2017 10:50:16 -0800 Subject: [PATCH 34/42] revised overdue test --- test/controllers/rentals_controller_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 44942a011..a52e9e16e 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -127,7 +127,7 @@ it "if no overdue movies, status is no_content 204" do Rental.destroy_all get overdue_path - must_respond_with :success + must_respond_with :no_content end it "succeeds wether there are any overdue customers with 2XX response" do From 476808cd0e2052d49e87812fdf2e8e7f1138648c Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Wed, 8 Nov 2017 11:06:57 -0800 Subject: [PATCH 35/42] Passing tests for rentals and required fields, added in forced overdue rental. --- app/controllers/rentals_controller.rb | 5 +++- test/controllers/rentals_controller_test.rb | 32 +++++++++++++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 955207948..0d1e6007e 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -41,7 +41,10 @@ def overdue end if overdue_rentals.count != 0 overdue_rentals.each do |rental| - render json: rental.as_json(only: [:title, :customer_id, :name, :postal_code, :checkout_date, :due_date ]), status: :ok + render json: { + "title" => Movie.find_by_id(rental.movie_id).title, "customer_id" => rental.customer_id, "name" => Customer.find_by_id(rental.customer_id).name, "postal_code" => Customer.find_by_id(rental.customer_id).postal_code, "checkout_date" => rental.checkout_date, "due_date" => rental.due_date + }, + status: :ok end else render json: {ok: false, errors: "There are no overdue rentals to show."}, status: :no_content diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index a52e9e16e..59468e0ef 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -104,21 +104,37 @@ describe "overdue" do + let(:rental_data) { + { + movie_id: Movie.first.id, + customer_id: Customer.first.id + } + } - # let(:rental_data) { - # { - # movie_id: Movie[1].id - # customer_id: Movie[1].id - # } - # } + let(:more_rental_data) { + { + movie_id: Movie.last.id, + customer_id: Customer.last.id + } + } + + before do + Rental.destroy_all + post checkout_url(rental_data) + rental = Rental.first + rental.due_date = "2017-10-10" + rental.save + + post checkout_url(more_rental_data) + end it "succeeds" do get overdue_path must_respond_with :success end - it "returns list of all customers with overdue movies" do - keys = ["id", "customer_id", "movie_id", "due_date", "checkout_date"] + it "returns overdue rental with all required fields" do + keys = ["title", "customer_id", "name", "postal_code", "checkout_date", "due_date"] get overdue_path body = JSON.parse(response.body) body.keys.must_equal keys From bc7fe9f64af5a7ff50d22e616d62f73a956743d6 Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Wed, 8 Nov 2017 11:09:14 -0800 Subject: [PATCH 36/42] Additional test for matching overdue rental with correct one. --- test/controllers/rentals_controller_test.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 59468e0ef..550ba2555 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -140,6 +140,12 @@ body.keys.must_equal keys end + it "returns list of all customers with overdue movies" do + get overdue_path + body = JSON.parse(response.body) + body["due_date"].must_equal "2017-10-10" + end + it "if no overdue movies, status is no_content 204" do Rental.destroy_all get overdue_path From 29a9fc25f0b051a15fa3a719c544f08836ea7eff Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Wed, 8 Nov 2017 15:54:31 -0800 Subject: [PATCH 37/42] Passing model tests for rental class. --- app/controllers/rentals_controller.rb | 10 +- app/models/customer.rb | 2 + app/models/movie.rb | 2 +- app/models/rental.rb | 16 ++- config/routes.rb | 1 - test/fixtures/movies.yml | 2 +- test/models/rental_test.rb | 200 ++++++++++++++++++-------- 7 files changed, 160 insertions(+), 73 deletions(-) diff --git a/app/controllers/rentals_controller.rb b/app/controllers/rentals_controller.rb index 0d1e6007e..d7a7118de 100644 --- a/app/controllers/rentals_controller.rb +++ b/app/controllers/rentals_controller.rb @@ -33,14 +33,8 @@ def check_in end def overdue - overdue_rentals = [] - Rental.all.each do |rental| - if Date.parse(rental.due_date) < Date.today - overdue_rentals << rental - end - end - if overdue_rentals.count != 0 - overdue_rentals.each do |rental| + if Rental.find_overdue.count != 0 + Rental.find_overdue.each do |rental| render json: { "title" => Movie.find_by_id(rental.movie_id).title, "customer_id" => rental.customer_id, "name" => Customer.find_by_id(rental.customer_id).name, "postal_code" => Customer.find_by_id(rental.customer_id).postal_code, "checkout_date" => rental.checkout_date, "due_date" => rental.due_date }, diff --git a/app/models/customer.rb b/app/models/customer.rb index a62356cd9..2df118e12 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -1,3 +1,5 @@ class Customer < ApplicationRecord validates :name, presence: true + has_many :rentals + end diff --git a/app/models/movie.rb b/app/models/movie.rb index b7f12e0ee..6fe9441d6 100644 --- a/app/models/movie.rb +++ b/app/models/movie.rb @@ -1,7 +1,7 @@ class Movie < ApplicationRecord validates :title, presence: true - before_create :set_default_avaiable_inventory + has_many :rentals private diff --git a/app/models/rental.rb b/app/models/rental.rb index d41bac8af..4d32a2ff9 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -1,11 +1,11 @@ require 'date' class Rental < ApplicationRecord - before_create :format_checkout, :format_due_date + # belongs_to :customer + # belongs_to :movie def checkout(movie_id, customer_id) - movie = Movie.find_by_id(movie_id) customer = Customer.find_by_id(customer_id) @@ -24,6 +24,7 @@ def checkout(movie_id, customer_id) def checkin customer = Customer.find_by_id(self.customer_id) movie = Movie.find_by_id(self.movie_id) + if customer && movie && customer.movies_checked_out_count > 0 movie.available_inventory += 1 movie.save @@ -35,6 +36,17 @@ def checkin end end +def self.find_overdue + overdue_rentals = [] + Rental.all.each do |rental| + if Date.parse(rental.due_date) < Date.today + overdue_rentals << rental + end + end + return overdue_rentals +end + + private def format_checkout diff --git a/config/routes.rb b/config/routes.rb index af99f26d4..3fa32563f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,5 +6,4 @@ post '/rentals/check_out/:movie_id/:customer_id', to: 'rentals#check_out', as: 'checkout' post '/rentals/check_in/:rental_id', to: 'rentals#check_in', as: 'checkin' get '/rentals/overdue', to: 'rentals#overdue', as: 'overdue' - get '/rentals/create', to: 'rentals#create', as: 'new_rental' end diff --git a/test/fixtures/movies.yml b/test/fixtures/movies.yml index d8e4dd0a4..f9206da59 100644 --- a/test/fixtures/movies.yml +++ b/test/fixtures/movies.yml @@ -5,7 +5,7 @@ one: overview: A movie about pets release_date: 12/27/2016 inventory: 8 - available_inventory: 8 + available_inventory: 4 two: title: Baby Driver diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb index 3e05c9d7f..7994d0811 100644 --- a/test/models/rental_test.rb +++ b/test/models/rental_test.rb @@ -1,60 +1,140 @@ -# require "test_helper" -# -# describe Rental do -# let(:rental) { Rental.new } -# -# it "must be valid" do -# value(rental).must_be :valid? -# end -# end -# -# -# it "decrements number of movies available in inventory" do -# post checkout_path() -# -# end -# -# it "increments number of movies customer has rented" do -# -# end -# -# it "doesn't succeed for customer that doesn't exit " do -# -# end -# -# it "doesn't succeed for movie that doesn't exit " do -# -# end -# -# it "doesn't succeed if zero inventory " do -# -# end -# -# it "succeeds if sufficient inventory " do -# -# end -# -# -# describe "check_in" do -# it "succeeds" do -# post checkin_path -# must_respond_with :success -# end -# -# it "increments number of movies available in inventory" do -# -# end -# -# it "decrements number of movies customer has rented" do -# -# end -# -# it "doesn't succeed for customer that doesn't exit " do -# -# end -# -# it "doesn't succeed for movie that doesn't exit " do -# -# end -# -# end +require 'test_helper' +describe Rental do + before do + @rental = Rental.create(customer_id: 1, movie_id: 1) + end + + describe 'attributes' do + it "has all named attributes and responds to them" do + @rental.must_respond_to :customer_id + @rental.customer_id.must_equal 1 + + @rental.must_respond_to :movie_id + @rental.movie_id.must_equal 1 + + @rental.must_respond_to :checkout_date + @rental.must_respond_to :due_date + end + + it "creates a string formatted due_date equal to the day of creation" do + @rental.due_date.must_equal "2017-11-18" + end + + it "creates a string formatted checkout_date equal to 10 days after the day of creation" do + @rental.checkout_date.must_equal "2017-11-08" + end + + it "can be created" do + @rental = Rental.create(customer_id: 1, movie_id: 1) + @rental.valid?.must_equal true + end + end + + describe "checkout method" do + it "can be called on a rental object" do + @rental.must_respond_to :checkout + end + + it "requires a movie id and a customer id to work" do + # @rental.checkout(1).must_respond_with ArgumentError + end + + it "returns true if a movie can be checked out" do + @rental.checkout(Movie.first.id, Customer.first.id).must_equal true + end + + it "returns false if a movie cannot be checked out because the movie does not exist" do + @rental.checkout(Movie.last.id + 1, Customer.first.id).must_equal false + end + + it "returns false if a movie cannot be checked out because the customer does not exist" do + @rental.checkout(Movie.last.id, Customer.last.id + 1).must_equal false + end + + it "decrements available_inventory returns false if a movie cannot be checked out because there are no more copies available" do + #for a movie with 4 copies, 4 checkouts should go through + movie = Movie.create(title: "A movie!", inventory: 4) + + @rental.checkout(movie.id, Customer.last.id).must_equal true + @rental.checkout(movie.id, Customer.last.id).must_equal true + @rental.checkout(movie.id, Customer.last.id).must_equal true + @rental.checkout(movie.id, Customer.last.id).must_equal true + + #the last checkout should return false because there are no more copies left + @rental.checkout(movie.id, Customer.last.id).must_equal false + end + + it "decrements the movie's available_inventory" do + + end + end + + describe "checkin method" do + before do + @newrental = Rental.create(customer_id: Customer.first.id, movie_id: Movie.first.id) + + @badrental = Rental.create(customer_id: Customer.last.id + 1, movie_id: Movie.last.id + 1) + end + + it "can be called on a rental object" do + @newrental.must_respond_to :checkin + end + + it "requires a movie id and a customer id to work" do + # @rental.checkout(1).must_respond_with ArgumentError + end + + it "returns true if a movie can be checked in" do + @newrental.checkin.must_equal true + end + + it "returns false if a movie cannot be checked in because the movie does not exist" do + @badrental.checkin.must_equal false + end + + it "returns false if a movie cannot be checked in because the customer does not exist" do + @badrental.checkin.must_equal false + end + + it "decrements movies checked out count and returns false if a movie cannot be checked out because there are no more copies checked out by customer" do + @newrental.checkin.must_equal true + @newrental.checkin.must_equal true + @newrental.checkin.must_equal true + @newrental.checkin.must_equal true + + @newrental.checkin.must_equal false + end + + it "decrements the movie's available_inventory" do + + end + end + + describe "find overdue method" do + it "returns an array" do + Rental.find_overdue.must_be_kind_of Array + end + + it "returns an array containing overdue rentals" do + Rental.find_overdue.count.must_equal 1 + overdue = Rental.find_overdue[0] + overdue.due_date.must_be :<, Date.today.strftime('%Y-%m-%d') + end + + it "items returned are rental objects" do + Rental.find_overdue[0].must_be_kind_of Rental + end + + it "does not return rentals that are not overdue" do + Rental.all.count.must_equal 3 + Rental.find_overdue.count.must_equal 1 + end + + it "succeeds with no rental objects" do + Rental.destroy_all + Rental.find_overdue.must_be_kind_of Array + Rental.find_overdue.must_equal [] + end + + end +end From 0fe83dad2e319695691d454fb27036c8a167a498 Mon Sep 17 00:00:00 2001 From: Kimberley Mackenzie Date: Sun, 12 Nov 2017 11:56:11 -0800 Subject: [PATCH 38/42] Final updates to rentals controller and model tests. --- app/models/rental.rb | 40 ++++++++--------- test/controllers/rentals_controller_test.rb | 1 - test/models/rental_test.rb | 50 +++++++++------------ 3 files changed, 40 insertions(+), 51 deletions(-) diff --git a/app/models/rental.rb b/app/models/rental.rb index 4d32a2ff9..143901d55 100644 --- a/app/models/rental.rb +++ b/app/models/rental.rb @@ -2,8 +2,6 @@ class Rental < ApplicationRecord before_create :format_checkout, :format_due_date - # belongs_to :customer - # belongs_to :movie def checkout(movie_id, customer_id) movie = Movie.find_by_id(movie_id) @@ -21,30 +19,30 @@ def checkout(movie_id, customer_id) end -def checkin - customer = Customer.find_by_id(self.customer_id) - movie = Movie.find_by_id(self.movie_id) + def checkin + customer = Customer.find_by_id(self.customer_id) + movie = Movie.find_by_id(self.movie_id) - if customer && movie && customer.movies_checked_out_count > 0 - movie.available_inventory += 1 - movie.save - customer.movies_checked_out_count -= 1 - customer.save - return true - else - return false + if customer && movie && customer.movies_checked_out_count > 0 + movie.available_inventory += 1 + movie.save + customer.movies_checked_out_count -= 1 + customer.save + return true + else + return false + end end -end -def self.find_overdue - overdue_rentals = [] - Rental.all.each do |rental| - if Date.parse(rental.due_date) < Date.today - overdue_rentals << rental + def self.find_overdue + overdue_rentals = [] + Rental.all.each do |rental| + if Date.parse(rental.due_date) < Date.today + overdue_rentals << rental + end end + return overdue_rentals end - return overdue_rentals -end private diff --git a/test/controllers/rentals_controller_test.rb b/test/controllers/rentals_controller_test.rb index 550ba2555..08b758ac2 100644 --- a/test/controllers/rentals_controller_test.rb +++ b/test/controllers/rentals_controller_test.rb @@ -1,5 +1,4 @@ require "test_helper" -require 'date' describe RentalsController do describe "checkout" do diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb index 7994d0811..e1e2ff509 100644 --- a/test/models/rental_test.rb +++ b/test/models/rental_test.rb @@ -1,4 +1,5 @@ require 'test_helper' + describe Rental do before do @rental = Rental.create(customer_id: 1, movie_id: 1) @@ -16,12 +17,12 @@ @rental.must_respond_to :due_date end - it "creates a string formatted due_date equal to the day of creation" do - @rental.due_date.must_equal "2017-11-18" + it "creates a string formatted checkout_date equal to the day of creation" do + @rental.checkout_date.must_equal Date.today.strftime('%Y-%m-%d') end - it "creates a string formatted checkout_date equal to 10 days after the day of creation" do - @rental.checkout_date.must_equal "2017-11-08" + it "creates a string formatted due_date equal to 10 days after the day of creation" do + @rental.due_date.must_equal (Date.today + 10).strftime('%Y-%m-%d') end it "can be created" do @@ -35,12 +36,7 @@ @rental.must_respond_to :checkout end - it "requires a movie id and a customer id to work" do - # @rental.checkout(1).must_respond_with ArgumentError - end - it "returns true if a movie can be checked out" do - @rental.checkout(Movie.first.id, Customer.first.id).must_equal true end it "returns false if a movie cannot be checked out because the movie does not exist" do @@ -51,21 +47,24 @@ @rental.checkout(Movie.last.id, Customer.last.id + 1).must_equal false end - it "decrements available_inventory returns false if a movie cannot be checked out because there are no more copies available" do + it "decrements available_inventory and returns false if a movie cannot be checked out because there are no more copies available" do #for a movie with 4 copies, 4 checkouts should go through movie = Movie.create(title: "A movie!", inventory: 4) + customer = Customer.create(name: "Me") + rental = Rental.create(customer_id: customer.id, movie_id: movie.id) - @rental.checkout(movie.id, Customer.last.id).must_equal true - @rental.checkout(movie.id, Customer.last.id).must_equal true - @rental.checkout(movie.id, Customer.last.id).must_equal true - @rental.checkout(movie.id, Customer.last.id).must_equal true + print movie.available_inventory + rental.checkout(movie.id, customer.id).must_equal true + print movie.available_inventory - #the last checkout should return false because there are no more copies left - @rental.checkout(movie.id, Customer.last.id).must_equal false - end + rental.checkout(movie.id, customer.id).must_equal true + rental.checkout(movie.id, customer.id).must_equal true + print movie.available_inventory - it "decrements the movie's available_inventory" do + rental.checkout(movie.id, customer.id).must_equal true + #the last checkout should return false because there are no more copies left + rental.checkout(movie.id, customer.id).must_equal false end end @@ -73,17 +72,15 @@ before do @newrental = Rental.create(customer_id: Customer.first.id, movie_id: Movie.first.id) - @badrental = Rental.create(customer_id: Customer.last.id + 1, movie_id: Movie.last.id + 1) + @badrental = Rental.create(customer_id: Customer.last.id, movie_id: Movie.last.id + 1) + + @anotherbadrental = Rental.create(customer_id: Customer.last.id + 1, movie_id: Movie.last.id) end it "can be called on a rental object" do @newrental.must_respond_to :checkin end - it "requires a movie id and a customer id to work" do - # @rental.checkout(1).must_respond_with ArgumentError - end - it "returns true if a movie can be checked in" do @newrental.checkin.must_equal true end @@ -93,7 +90,7 @@ end it "returns false if a movie cannot be checked in because the customer does not exist" do - @badrental.checkin.must_equal false + @anotherbadrental.checkin.must_equal false end it "decrements movies checked out count and returns false if a movie cannot be checked out because there are no more copies checked out by customer" do @@ -104,10 +101,6 @@ @newrental.checkin.must_equal false end - - it "decrements the movie's available_inventory" do - - end end describe "find overdue method" do @@ -135,6 +128,5 @@ Rental.find_overdue.must_be_kind_of Array Rental.find_overdue.must_equal [] end - end end From ded423eaea3e5020d742465965cc7b1ad53a6e6f Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Sun, 12 Nov 2017 12:15:37 -0800 Subject: [PATCH 39/42] adds movie model tests --- app/models/movie.rb | 4 ++++ test/models/movie_test.rb | 17 +++++++++++++++++ test/models/rental_test.rb | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/models/movie.rb b/app/models/movie.rb index 6fe9441d6..a1b239bf9 100644 --- a/app/models/movie.rb +++ b/app/models/movie.rb @@ -1,5 +1,9 @@ class Movie < ApplicationRecord validates :title, presence: true + validates :release_date, presence: true + validates :overview, presence: true + validates :inventory, presence: true + before_create :set_default_avaiable_inventory has_many :rentals diff --git a/test/models/movie_test.rb b/test/models/movie_test.rb index 8798308e5..e881789ed 100644 --- a/test/models/movie_test.rb +++ b/test/models/movie_test.rb @@ -1,6 +1,23 @@ require "test_helper" describe Movie do + let(:one) { movies(:one) } + it "must be valid" do + one.valid?.must_equal true + end + it "must have all required fields to be valid" do + new_movie = Movie.new( release_date: "2017-11-09", overview: "blah blah blah", inventory: 8) + new_movie.valid?.must_equal false + + new_movie = Movie.new( title: "sometitle", overview: "blah blah blah", inventory: 8) + new_movie.valid?.must_equal false + + new_movie = Movie.new( release_date: "2017-11-09", title: "sometitle", inventory: 8) + new_movie.valid?.must_equal false + + new_movie = Movie.new( release_date: "2017-11-09", overview: "blah blah blah", title: "sometitle") + new_movie.valid?.must_equal false + end end diff --git a/test/models/rental_test.rb b/test/models/rental_test.rb index e1e2ff509..599f5a613 100644 --- a/test/models/rental_test.rb +++ b/test/models/rental_test.rb @@ -49,7 +49,7 @@ it "decrements available_inventory and returns false if a movie cannot be checked out because there are no more copies available" do #for a movie with 4 copies, 4 checkouts should go through - movie = Movie.create(title: "A movie!", inventory: 4) + movie = Movie.create(title: "A movie!", inventory: 4, overview: "blah", release_date: "2017-11-09") customer = Customer.create(name: "Me") rental = Rental.create(customer_id: customer.id, movie_id: movie.id) From 0e6bf6a85ad70ed15dfeb67a0c759ffbf9c4e045 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Sun, 12 Nov 2017 12:22:27 -0800 Subject: [PATCH 40/42] adds customer model tests --- test/models/customer_test.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/models/customer_test.rb b/test/models/customer_test.rb index eee222f67..097c4f356 100644 --- a/test/models/customer_test.rb +++ b/test/models/customer_test.rb @@ -1,5 +1,14 @@ require "test_helper" describe Customer do + let(:one) { customers(:one) } + it "must be valid with all fields present" do + one.valid?.must_equal true + end + + it "is invalid with missing name" do + one.name = nil + one.valid?.must_equal false + end end From a5053028d737c41123cdae5ae277864214cbc3fb Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Sun, 12 Nov 2017 12:25:22 -0800 Subject: [PATCH 41/42] adds erd gem and erd pdf --- Gemfile | 20 +------------------- Gemfile.lock | 8 ++++++++ erd.pdf | Bin 0 -> 32721 bytes 3 files changed, 9 insertions(+), 19 deletions(-) create mode 100644 erd.pdf diff --git a/Gemfile b/Gemfile index b0a46563d..09e4e0946 100644 --- a/Gemfile +++ b/Gemfile @@ -6,41 +6,23 @@ git_source(:github) do |repo_name| 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' gem "active_model_serializers" 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 'pry-rails' 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' + gem 'rails-erd', require: false 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' diff --git a/Gemfile.lock b/Gemfile.lock index 1ac6880e6..39d5f9b69 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -55,6 +55,7 @@ GEM byebug (9.1.0) case_transform (0.2) activesupport + choice (0.2.0) coderay (1.1.2) concurrent-ruby (1.0.5) crass (1.0.2) @@ -118,6 +119,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) @@ -130,6 +136,7 @@ GEM rb-fsevent (0.10.2) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) + ruby-graphviz (1.2.3) ruby-progressbar (1.9.0) ruby_dep (1.5.0) spring (2.0.2) @@ -171,6 +178,7 @@ DEPENDENCIES pry-rails puma (~> 3.7) rails (~> 5.1.4) + rails-erd spring spring-watcher-listen (~> 2.0.0) tzinfo-data diff --git a/erd.pdf b/erd.pdf new file mode 100644 index 0000000000000000000000000000000000000000..78157f6a431d694e87719426d5ce4aa707c19785 GIT binary patch literal 32721 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 literal 0 HcmV?d00001 From c1d9fe08537189c09d1e9375ec3fac1dc4dd6290 Mon Sep 17 00:00:00 2001 From: Tanja Stroble Date: Sun, 12 Nov 2017 12:26:46 -0800 Subject: [PATCH 42/42] cleans up Gemfile --- Gemfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 09e4e0946..62d3e828b 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,6 @@ git_source(:github) do |repo_name| "https://github.com/#{repo_name}.git" end - gem 'rails', '~> 5.1.4' gem 'pg', '~> 0.18' gem 'puma', '~> 3.7' @@ -24,8 +23,8 @@ group :development do end gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] - gem 'jquery-turbolinks' + group :development do gem 'better_errors' gem 'binding_of_caller'