From 0dfc2259618e884f404b895e1bea16bd264666e1 Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Wed, 17 Oct 2018 11:02:33 -0700 Subject: [PATCH 001/178] add rails --- .gitignore | 27 ++ .ruby-version | 1 + Gemfile | 81 +++++ Gemfile.lock | 277 ++++++++++++++++++ Guardfile | 9 + Rakefile | 6 + app/assets/config/manifest.js | 3 + app/assets/images/.keep | 0 app/assets/javascripts/application.js | 20 ++ app/assets/javascripts/cable.js | 13 + app/assets/javascripts/channels/.keep | 0 app/assets/stylesheets/application.scss | 18 ++ 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/helpers/application_helper.rb | 2 + 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/application.html.erb | 15 + 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 | 36 +++ bin/spring | 17 ++ bin/update | 31 ++ bin/yarn | 11 + config.ru | 5 + config/application.rb | 25 ++ config/boot.rb | 4 + config/cable.yml | 10 + config/credentials.yml.enc | 1 + config/database.yml | 85 ++++++ config/environment.rb | 5 + config/environments/development.rb | 61 ++++ config/environments/production.rb | 94 ++++++ config/environments/test.rb | 46 +++ .../application_controller_renderer.rb | 8 + config/initializers/assets.rb | 14 + config/initializers/backtrace_silencers.rb | 7 + .../initializers/content_security_policy.rb | 25 ++ config/initializers/cookies_serializer.rb | 5 + .../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 | 34 +++ config/routes.rb | 3 + config/spring.rb | 6 + config/storage.yml | 34 +++ db/seeds.rb | 7 + lib/assets/.keep | 0 lib/tasks/.keep | 0 log/.keep | 0 package.json | 5 + public/404.html | 67 +++++ public/422.html | 67 +++++ public/500.html | 66 +++++ public/apple-touch-icon-precomposed.png | 0 public/apple-touch-icon.png | 0 public/favicon.ico | 0 public/robots.txt | 1 + storage/.keep | 0 test/application_system_test_case.rb | 5 + test/controllers/.keep | 0 test/fixtures/.keep | 0 test/fixtures/files/.keep | 0 test/helpers/.keep | 0 test/integration/.keep | 0 test/mailers/.keep | 0 test/models/.keep | 0 test/system/.keep | 0 test/test_helper.rb | 24 ++ tmp/.keep | 0 vendor/.keep | 0 80 files changed, 1406 insertions(+) create mode 100644 .gitignore create mode 100644 .ruby-version create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 Guardfile create mode 100644 Rakefile create mode 100644 app/assets/config/manifest.js create mode 100644 app/assets/images/.keep create mode 100644 app/assets/javascripts/application.js create mode 100644 app/assets/javascripts/cable.js create mode 100644 app/assets/javascripts/channels/.keep create mode 100644 app/assets/stylesheets/application.scss 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/helpers/application_helper.rb 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/application.html.erb 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 100755 bin/yarn 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/credentials.yml.enc 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/assets.rb create mode 100644 config/initializers/backtrace_silencers.rb create mode 100644 config/initializers/content_security_policy.rb create mode 100644 config/initializers/cookies_serializer.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/spring.rb create mode 100644 config/storage.yml create mode 100644 db/seeds.rb create mode 100644 lib/assets/.keep create mode 100644 lib/tasks/.keep create mode 100644 log/.keep create mode 100644 package.json create mode 100644 public/404.html create mode 100644 public/422.html create mode 100644 public/500.html create mode 100644 public/apple-touch-icon-precomposed.png create mode 100644 public/apple-touch-icon.png create mode 100644 public/favicon.ico create mode 100644 public/robots.txt create mode 100644 storage/.keep create mode 100644 test/application_system_test_case.rb create mode 100644 test/controllers/.keep create mode 100644 test/fixtures/.keep create mode 100644 test/fixtures/files/.keep create mode 100644 test/helpers/.keep create mode 100644 test/integration/.keep create mode 100644 test/mailers/.keep create mode 100644 test/models/.keep create mode 100644 test/system/.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 0000000000..18b43c9cd2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# 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 + +# Ignore uploaded files in development +/storage/* +!/storage/.keep + +/node_modules +/yarn-error.log + +/public/assets +.byebug_history + +# Ignore master key for decrypting credentials and more. +/config/master.key diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000000..25c81fe399 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +ruby-2.5.1 \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000000..6219256bd8 --- /dev/null +++ b/Gemfile @@ -0,0 +1,81 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '2.5.1' + +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '~> 5.2.1' +# Use postgresql as the database for Active Record +gem 'pg', '>= 0.18', '< 2.0' +# Use Puma as the app server +gem 'puma', '~> 3.11' +# Use SCSS for stylesheets +gem 'sass-rails', '~> 5.0' +# Use Uglifier as compressor for JavaScript assets +gem 'uglifier', '>= 1.3.0' +# See https://github.com/rails/execjs#readme for more supported runtimes +# gem 'mini_racer', platforms: :ruby + +# Use CoffeeScript for .coffee assets and views +# gem 'coffee-rails', '~> 4.2' +# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks +gem 'turbolinks', '~> 5' +# 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', '~> 4.0' +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# Use ActiveStorage variant +# gem 'mini_magick', '~> 4.8' + +# Use Capistrano for deployment +# gem 'capistrano-rails', group: :development + +# Reduces boot times through caching; required in config/boot.rb +gem 'bootsnap', '>= 1.1.0', require: false + +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 + # Access an interactive console on exception pages or by calling 'console' anywhere in the code. + gem 'web-console', '>= 3.3.0' + 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 + +group :test do + # Adds support for Capybara system testing and selenium driver + gem 'capybara', '>= 2.15' + gem 'selenium-webdriver' + # Easy installation and use of chromedriver to run system tests with Chrome + gem 'chromedriver-helper' +end + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] + +gem 'jquery-rails' +gem 'jquery-turbolinks' +gem 'bootstrap', '~> 4.1.3' +group :development, :test do + gem 'pry-rails' +end + +group :development do + gem 'better_errors' + gem 'binding_of_caller' + gem 'guard' + gem 'guard-minitest' +end + +group :test do + gem 'minitest-rails' + gem 'minitest-reporters' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000000..3a4afee259 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,277 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.2.1) + actionpack (= 5.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.1) + actionpack (= 5.2.1) + actionview (= 5.2.1) + activejob (= 5.2.1) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.1) + actionview (= 5.2.1) + activesupport (= 5.2.1) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.2.1) + activesupport (= 5.2.1) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.2.1) + activesupport (= 5.2.1) + globalid (>= 0.3.6) + activemodel (5.2.1) + activesupport (= 5.2.1) + activerecord (5.2.1) + activemodel (= 5.2.1) + activesupport (= 5.2.1) + arel (>= 9.0) + activestorage (5.2.1) + actionpack (= 5.2.1) + activerecord (= 5.2.1) + marcel (~> 0.3.1) + activesupport (5.2.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) + ansi (1.5.0) + archive-zip (0.11.0) + io-like (~> 0.3.0) + arel (9.0.0) + autoprefixer-rails (9.2.1) + execjs + better_errors (2.5.0) + coderay (>= 1.0.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) + bindex (0.5.0) + binding_of_caller (0.8.0) + debug_inspector (>= 0.0.1) + bootsnap (1.3.2) + msgpack (~> 1.0) + bootstrap (4.1.3) + autoprefixer-rails (>= 6.0.3) + popper_js (>= 1.12.9, < 2) + sass (>= 3.5.2) + builder (3.2.3) + byebug (10.0.2) + capybara (3.9.0) + addressable + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + xpath (~> 3.1) + childprocess (0.9.0) + ffi (~> 1.0, >= 1.0.11) + chromedriver-helper (2.1.0) + archive-zip (~> 0.10) + nokogiri (~> 1.8) + coderay (1.1.2) + concurrent-ruby (1.0.5) + crass (1.0.4) + debug_inspector (0.0.3) + erubi (1.7.1) + execjs (2.7.0) + ffi (1.9.25) + formatador (0.2.5) + globalid (0.4.1) + activesupport (>= 4.2.0) + guard (2.14.2) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.9.12) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-minitest (2.4.6) + guard-compat (~> 1.2) + minitest (>= 3.0) + i18n (1.1.1) + concurrent-ruby (~> 1.0) + io-like (0.3.0) + jbuilder (2.7.0) + activesupport (>= 4.2.0) + multi_json (>= 1.2) + jquery-rails (4.3.3) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.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.2.2) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + lumberjack (1.0.13) + mail (2.7.1) + mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) + method_source (0.9.0) + mimemagic (0.3.2) + mini_mime (1.0.1) + mini_portile2 (2.3.0) + minitest (5.11.3) + minitest-rails (3.0.0) + minitest (~> 5.8) + railties (~> 5.0) + minitest-reporters (1.3.5) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + msgpack (1.2.4) + multi_json (1.13.1) + nenv (0.3.0) + nio4r (2.3.1) + nokogiri (1.8.5) + mini_portile2 (~> 2.3.0) + notiffany (0.1.1) + nenv (~> 0.1) + shellany (~> 0.0) + pg (1.1.3) + popper_js (1.14.3) + pry (0.11.3) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry-rails (0.3.6) + pry (>= 0.10.4) + public_suffix (3.0.3) + puma (3.12.0) + rack (2.0.5) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.2.1) + actioncable (= 5.2.1) + actionmailer (= 5.2.1) + actionpack (= 5.2.1) + actionview (= 5.2.1) + activejob (= 5.2.1) + activemodel (= 5.2.1) + activerecord (= 5.2.1) + activestorage (= 5.2.1) + activesupport (= 5.2.1) + bundler (>= 1.3.0) + railties (= 5.2.1) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.2.1) + actionpack (= 5.2.1) + activesupport (= 5.2.1) + method_source + rake (>= 0.8.7) + thor (>= 0.19.0, < 2.0) + rake (12.3.1) + rb-fsevent (0.10.3) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + ruby-progressbar (1.10.0) + ruby_dep (1.5.0) + rubyzip (1.2.2) + sass (3.6.0) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sass-rails (5.0.7) + railties (>= 4.0.0, < 6) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + selenium-webdriver (3.14.1) + childprocess (~> 0.5) + rubyzip (~> 1.2, >= 1.2.2) + shellany (0.0.1) + 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.2) + 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) + tilt (2.0.8) + turbolinks (5.2.0) + turbolinks-source (~> 5.2) + turbolinks-source (5.2.0) + tzinfo (1.2.5) + thread_safe (~> 0.1) + uglifier (4.1.19) + execjs (>= 0.3.0, < 3) + web-console (3.7.0) + actionview (>= 5.0) + activemodel (>= 5.0) + bindex (>= 0.4.0) + railties (>= 5.0) + websocket-driver (0.7.0) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.3) + xpath (3.2.0) + nokogiri (~> 1.8) + +PLATFORMS + ruby + +DEPENDENCIES + better_errors + binding_of_caller + bootsnap (>= 1.1.0) + bootstrap (~> 4.1.3) + byebug + capybara (>= 2.15) + chromedriver-helper + guard + guard-minitest + jbuilder (~> 2.5) + jquery-rails + jquery-turbolinks + listen (>= 3.0.5, < 3.2) + minitest-rails + minitest-reporters + pg (>= 0.18, < 2.0) + pry-rails + puma (~> 3.11) + rails (~> 5.2.1) + sass-rails (~> 5.0) + selenium-webdriver + spring + spring-watcher-listen (~> 2.0.0) + turbolinks (~> 5) + tzinfo-data + uglifier (>= 1.3.0) + web-console (>= 3.3.0) + +RUBY VERSION + ruby 2.5.1p57 + +BUNDLED WITH + 1.16.6 diff --git a/Guardfile b/Guardfile new file mode 100644 index 0000000000..e34f706f4a --- /dev/null +++ b/Guardfile @@ -0,0 +1,9 @@ +guard :minitest, autorun: false, spring: true do + watch(%r{^app/(.+).rb$}) { |m| "test/#{m[1]}_test.rb" } + watch(%r{^app/controllers/application_controller.rb$}) { 'test/controllers' } + watch(%r{^app/controllers/(.+)_controller.rb$}) { |m| "test/integration/#{m[1]}_test.rb" } + watch(%r{^app/views/(.+)_mailer/.+}) { |m| "test/mailers/#{m[1]}_mailer_test.rb" } + watch(%r{^lib/(.+).rb$}) { |m| "test/lib/#{m[1]}_test.rb" } + watch(%r{^test/.+_test.rb$}) + watch(%r{^test/test_helper.rb$}) { 'test' } +end diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000000..e85f913914 --- /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/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 0000000000..b16e53d6d5 --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 0000000000..4f73c21a7d --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,20 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's +// vendor/assets/javascripts directory can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. JavaScript code in this file should be added after the last require_* statement. +// +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details +// about supported directives. + //= require jquery3 + //= require popper + //= require bootstrap-sprockets + +// +//= require rails-ujs +//= require activestorage +//= require turbolinks +//= require_tree . diff --git a/app/assets/javascripts/cable.js b/app/assets/javascripts/cable.js new file mode 100644 index 0000000000..739aa5f022 --- /dev/null +++ b/app/assets/javascripts/cable.js @@ -0,0 +1,13 @@ +// Action Cable provides the framework to deal with WebSockets in Rails. +// You can generate new channels where WebSocket features live using the `rails generate channel` command. +// +//= require action_cable +//= require_self +//= require_tree ./channels + +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer(); + +}).call(this); diff --git a/app/assets/javascripts/channels/.keep b/app/assets/javascripts/channels/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss new file mode 100644 index 0000000000..8b1701e581 --- /dev/null +++ b/app/assets/stylesheets/application.scss @@ -0,0 +1,18 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + */ + +/* Custom bootstrap variables must be set or imported *before* bootstrap. */ +@import "bootstrap"; +/* Import scss content */ +@import "**/*"; diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100644 index 0000000000..d672697283 --- /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 0000000000..0ff5442f47 --- /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 0000000000..09705d12ab --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000000..de6be7945c --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 0000000000..a009ace51c --- /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 0000000000..286b2239d1 --- /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 0000000000..10a4cba84d --- /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 0000000000..e69de29bb2 diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000000..f18f1b6820 --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,15 @@ + + + + Betsy + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb new file mode 100644 index 0000000000..cbd34d2e9d --- /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 0000000000..37f0bddbd7 --- /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 0000000000..f19acf5b5c --- /dev/null +++ b/bin/bundle @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) +load Gem.bin_path('bundler', 'bundle') diff --git a/bin/rails b/bin/rails new file mode 100755 index 0000000000..5badb2fde0 --- /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 0000000000..d87d5f5781 --- /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 0000000000..94fd4d7977 --- /dev/null +++ b/bin/setup @@ -0,0 +1,36 @@ +#!/usr/bin/env ruby +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = File.expand_path('..', __dir__) + +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') + + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + + # 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 0000000000..fb2ec2ebb4 --- /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 0000000000..58bfaed518 --- /dev/null +++ b/bin/update @@ -0,0 +1,31 @@ +#!/usr/bin/env ruby +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = File.expand_path('..', __dir__) + +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') + + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + + 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/bin/yarn b/bin/yarn new file mode 100755 index 0000000000..460dd565b4 --- /dev/null +++ b/bin/yarn @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +APP_ROOT = File.expand_path('..', __dir__) +Dir.chdir(APP_ROOT) do + begin + exec "yarnpkg", *ARGV + rescue Errno::ENOENT + $stderr.puts "Yarn executable was not detected in the system." + $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end +end diff --git a/config.ru b/config.ru new file mode 100644 index 0000000000..f7ba0b527b --- /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 0000000000..5c09a3eefc --- /dev/null +++ b/config/application.rb @@ -0,0 +1,25 @@ +require_relative 'boot' + +require 'rails/all' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module Betsy + 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.2 + + # Settings in config/environments/* take precedence over those specified here. + # Application configuration can go into files in config/initializers + # -- all .rb files in that directory are automatically loaded after loading + # the framework and any gems in your application. + end +end diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000000..b9e460cef3 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,4 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. +require 'bootsnap/setup' # Speed up boot time by caching expensive operations. diff --git a/config/cable.yml b/config/cable.yml new file mode 100644 index 0000000000..dd2a324c68 --- /dev/null +++ b/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: async + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: betsy_production diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc new file mode 100644 index 0000000000..cecdfdddf5 --- /dev/null +++ b/config/credentials.yml.enc @@ -0,0 +1 @@ +zDmXME5PLAQz8KijnopkvXYe3hAI5G+KXo9UvsFwL11Yu0KQZ2VFiwC/yxlhVy1V0znpsecIF+irUZSUNGP1riwVJIsF4mdnshlX3s8l+LPS9hX3yMpS93g7WMPbqMqv4RjCpv8D7mTQ1VwQ1Co78mcWAxZ02Jrne7tXwvBfoJ01/VHBomDoEkBE7kH0aoZRQDQ0NnAt5Y7woRz1jgu6e9VL3E9aGkJodw1EJKcGmiTZc6ZRcSt2L5eJAQJEGoJpSO1VkU0aOEJWOnGiwSuN69IbqZkAk8AOn4QKR+LjVg+vRyY+CnnSNGAdRN1R+jJcb8F3mP94UJGnL6RHwiDLgaZiozbt0AsZjCqh65TG9IumD95L1GBTFBU8KgAtrX0bVf3UueoaqAMNv3Nbs0zYHgDg8IxhZfJtWIDF--JPJCCoWagE1xuE9a--nIGqQ408gtXwbNOT+BlyoA== \ No newline at end of file diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 0000000000..6903bb6083 --- /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: betsy_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: betsy + + # 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: betsy_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: betsy_production + username: betsy + password: <%= ENV['BETSY_DATABASE_PASSWORD'] %> diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000000..426333bb46 --- /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 0000000000..1311e3e4ef --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,61 @@ +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. + # Run rails dev:cache to toggle caching. + 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.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + + # 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 + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + + # 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 0000000000..5f6f3058c6 --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,94 @@ +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 + + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = 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? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # 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 + + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + + # 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 = "betsy_#{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 0000000000..0a38fd3ce9 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,46 @@ +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.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 + + # Store uploaded files on the local file system in a temporary directory + config.active_storage.service = :test + + 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 0000000000..89d2efab2b --- /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/assets.rb b/config/initializers/assets.rb new file mode 100644 index 0000000000..4b828e80cb --- /dev/null +++ b/config/initializers/assets.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000000..59385cdf37 --- /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/content_security_policy.rb b/config/initializers/content_security_policy.rb new file mode 100644 index 0000000000..d3bcaa5ec8 --- /dev/null +++ b/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy +# For further information see the following documentation +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + +# Rails.application.config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https + +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end + +# If you are using UJS then enable automatic nonce generation +# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } + +# Report CSP violations to a specified URI +# For further information see the following documentation: +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +# Rails.application.config.content_security_policy_report_only = true diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb new file mode 100644 index 0000000000..5a6a32d371 --- /dev/null +++ b/config/initializers/cookies_serializer.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Specify a serializer for the signed and encrypted cookie jars. +# Valid options are :json, :marshal, and :hybrid. +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000000..4a994e1e7b --- /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 0000000000..ac033bf9dc --- /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 0000000000..dc1899682b --- /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 0000000000..bbfc3961bf --- /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 0000000000..decc5a8573 --- /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 0000000000..a5eccf816b --- /dev/null +++ b/config/puma.rb @@ -0,0 +1,34 @@ +# 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. +# +# preload_app! + +# 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 0000000000..787824f888 --- /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/spring.rb b/config/spring.rb new file mode 100644 index 0000000000..9fa7863f99 --- /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/config/storage.yml b/config/storage.yml new file mode 100644 index 0000000000..d32f76e8fb --- /dev/null +++ b/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket + +# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 0000000000..1beea2accd --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) +# Character.create(name: 'Luke', movie: movies.first) diff --git a/lib/assets/.keep b/lib/assets/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/tasks/.keep b/lib/tasks/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/log/.keep b/log/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/package.json b/package.json new file mode 100644 index 0000000000..f874acf437 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "name": "betsy", + "private": true, + "dependencies": {} +} diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000000..2be3af26fc --- /dev/null +++ b/public/404.html @@ -0,0 +1,67 @@ + + + + The page you were looking for doesn't exist (404) + + + + + + +
+
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/public/422.html b/public/422.html new file mode 100644 index 0000000000..c08eac0d1d --- /dev/null +++ b/public/422.html @@ -0,0 +1,67 @@ + + + + The change you wanted was rejected (422) + + + + + + +
+
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000000..78a030af22 --- /dev/null +++ b/public/500.html @@ -0,0 +1,66 @@ + + + + We're sorry, but something went wrong (500) + + + + + + +
+
+

We're sorry, but something went wrong.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000000..37b576a4a0 --- /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/storage/.keep b/storage/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb new file mode 100644 index 0000000000..d19212abd5 --- /dev/null +++ b/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/test/controllers/.keep b/test/controllers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/.keep b/test/fixtures/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/helpers/.keep b/test/helpers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/integration/.keep b/test/integration/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/mailers/.keep b/test/mailers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/models/.keep b/test/models/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/system/.keep b/test/system/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000000..2b5172a7d6 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,24 @@ +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 0000000000..e69de29bb2 diff --git a/vendor/.keep b/vendor/.keep new file mode 100644 index 0000000000..e69de29bb2 From 6275e9056e8b0b1e5a137725aa1d4eafed373e3b Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Thu, 18 Oct 2018 10:30:21 -0700 Subject: [PATCH 002/178] add controllers and views --- app/assets/javascripts/merchants.js | 2 ++ app/assets/javascripts/order.js | 2 ++ app/assets/javascripts/orders.js | 2 ++ app/assets/javascripts/products.js | 2 ++ app/assets/javascripts/sessions.js | 2 ++ app/assets/javascripts/welcome.js | 2 ++ app/assets/stylesheets/merchants.scss | 3 +++ app/assets/stylesheets/order.scss | 3 +++ app/assets/stylesheets/orders.scss | 3 +++ app/assets/stylesheets/products.scss | 3 +++ app/assets/stylesheets/sessions.scss | 3 +++ app/assets/stylesheets/welcome.scss | 3 +++ app/controllers/merchants_controller.rb | 2 ++ app/controllers/orders_controller.rb | 2 ++ app/controllers/products_controller.rb | 2 ++ app/controllers/sessions_controller.rb | 2 ++ app/controllers/welcome_controller.rb | 2 ++ app/helpers/merchants_helper.rb | 2 ++ app/helpers/order_helper.rb | 2 ++ app/helpers/orders_helper.rb | 2 ++ app/helpers/products_helper.rb | 2 ++ app/helpers/sessions_helper.rb | 2 ++ app/helpers/welcome_helper.rb | 2 ++ test/controllers/merchants_controller_test.rb | 7 +++++++ test/controllers/order_controller_test.rb | 7 +++++++ test/controllers/orders_controller_test.rb | 7 +++++++ test/controllers/products_controller_test.rb | 7 +++++++ test/controllers/sessions_controller_test.rb | 7 +++++++ test/controllers/welcome_controller_test.rb | 7 +++++++ 29 files changed, 94 insertions(+) create mode 100644 app/assets/javascripts/merchants.js create mode 100644 app/assets/javascripts/order.js create mode 100644 app/assets/javascripts/orders.js create mode 100644 app/assets/javascripts/products.js create mode 100644 app/assets/javascripts/sessions.js create mode 100644 app/assets/javascripts/welcome.js create mode 100644 app/assets/stylesheets/merchants.scss create mode 100644 app/assets/stylesheets/order.scss create mode 100644 app/assets/stylesheets/orders.scss create mode 100644 app/assets/stylesheets/products.scss create mode 100644 app/assets/stylesheets/sessions.scss create mode 100644 app/assets/stylesheets/welcome.scss create mode 100644 app/controllers/merchants_controller.rb create mode 100644 app/controllers/orders_controller.rb create mode 100644 app/controllers/products_controller.rb create mode 100644 app/controllers/sessions_controller.rb create mode 100644 app/controllers/welcome_controller.rb create mode 100644 app/helpers/merchants_helper.rb create mode 100644 app/helpers/order_helper.rb create mode 100644 app/helpers/orders_helper.rb create mode 100644 app/helpers/products_helper.rb create mode 100644 app/helpers/sessions_helper.rb create mode 100644 app/helpers/welcome_helper.rb create mode 100644 test/controllers/merchants_controller_test.rb create mode 100644 test/controllers/order_controller_test.rb create mode 100644 test/controllers/orders_controller_test.rb create mode 100644 test/controllers/products_controller_test.rb create mode 100644 test/controllers/sessions_controller_test.rb create mode 100644 test/controllers/welcome_controller_test.rb diff --git a/app/assets/javascripts/merchants.js b/app/assets/javascripts/merchants.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/merchants.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/order.js b/app/assets/javascripts/order.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/order.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/orders.js b/app/assets/javascripts/orders.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/orders.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/products.js b/app/assets/javascripts/products.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/products.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/sessions.js b/app/assets/javascripts/sessions.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/sessions.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/welcome.js b/app/assets/javascripts/welcome.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/welcome.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/stylesheets/merchants.scss b/app/assets/stylesheets/merchants.scss new file mode 100644 index 0000000000..2327c00afe --- /dev/null +++ b/app/assets/stylesheets/merchants.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the merchants controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/order.scss b/app/assets/stylesheets/order.scss new file mode 100644 index 0000000000..3ee74f9f68 --- /dev/null +++ b/app/assets/stylesheets/order.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the order controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/orders.scss b/app/assets/stylesheets/orders.scss new file mode 100644 index 0000000000..3b0428a94e --- /dev/null +++ b/app/assets/stylesheets/orders.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the orders controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/products.scss b/app/assets/stylesheets/products.scss new file mode 100644 index 0000000000..89e2e8db07 --- /dev/null +++ b/app/assets/stylesheets/products.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the products controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/sessions.scss b/app/assets/stylesheets/sessions.scss new file mode 100644 index 0000000000..7bef9cf826 --- /dev/null +++ b/app/assets/stylesheets/sessions.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the sessions controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/welcome.scss b/app/assets/stylesheets/welcome.scss new file mode 100644 index 0000000000..77ce11a740 --- /dev/null +++ b/app/assets/stylesheets/welcome.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the welcome controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/merchants_controller.rb b/app/controllers/merchants_controller.rb new file mode 100644 index 0000000000..ae95f7677b --- /dev/null +++ b/app/controllers/merchants_controller.rb @@ -0,0 +1,2 @@ +class MerchantsController < ApplicationController +end diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb new file mode 100644 index 0000000000..8a0e3659ae --- /dev/null +++ b/app/controllers/orders_controller.rb @@ -0,0 +1,2 @@ +class OrdersController < ApplicationController +end diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb new file mode 100644 index 0000000000..f1ad12ddea --- /dev/null +++ b/app/controllers/products_controller.rb @@ -0,0 +1,2 @@ +class ProductsController < ApplicationController +end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000000..16d11b5710 --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,2 @@ +class SessionsController < ApplicationController +end diff --git a/app/controllers/welcome_controller.rb b/app/controllers/welcome_controller.rb new file mode 100644 index 0000000000..d8c85ab84b --- /dev/null +++ b/app/controllers/welcome_controller.rb @@ -0,0 +1,2 @@ +class WelcomeController < ApplicationController +end diff --git a/app/helpers/merchants_helper.rb b/app/helpers/merchants_helper.rb new file mode 100644 index 0000000000..5337747b0f --- /dev/null +++ b/app/helpers/merchants_helper.rb @@ -0,0 +1,2 @@ +module MerchantsHelper +end diff --git a/app/helpers/order_helper.rb b/app/helpers/order_helper.rb new file mode 100644 index 0000000000..05d197a0c1 --- /dev/null +++ b/app/helpers/order_helper.rb @@ -0,0 +1,2 @@ +module OrderHelper +end diff --git a/app/helpers/orders_helper.rb b/app/helpers/orders_helper.rb new file mode 100644 index 0000000000..443227fd48 --- /dev/null +++ b/app/helpers/orders_helper.rb @@ -0,0 +1,2 @@ +module OrdersHelper +end diff --git a/app/helpers/products_helper.rb b/app/helpers/products_helper.rb new file mode 100644 index 0000000000..ab5c42b325 --- /dev/null +++ b/app/helpers/products_helper.rb @@ -0,0 +1,2 @@ +module ProductsHelper +end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb new file mode 100644 index 0000000000..309f8b2eb3 --- /dev/null +++ b/app/helpers/sessions_helper.rb @@ -0,0 +1,2 @@ +module SessionsHelper +end diff --git a/app/helpers/welcome_helper.rb b/app/helpers/welcome_helper.rb new file mode 100644 index 0000000000..eeead45fc9 --- /dev/null +++ b/app/helpers/welcome_helper.rb @@ -0,0 +1,2 @@ +module WelcomeHelper +end diff --git a/test/controllers/merchants_controller_test.rb b/test/controllers/merchants_controller_test.rb new file mode 100644 index 0000000000..6eb57f7baa --- /dev/null +++ b/test/controllers/merchants_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +describe MerchantsController do + # it "must be a real test" do + # flunk "Need real tests" + # end +end diff --git a/test/controllers/order_controller_test.rb b/test/controllers/order_controller_test.rb new file mode 100644 index 0000000000..41f714e492 --- /dev/null +++ b/test/controllers/order_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +describe OrderController do + # it "must be a real test" do + # flunk "Need real tests" + # end +end diff --git a/test/controllers/orders_controller_test.rb b/test/controllers/orders_controller_test.rb new file mode 100644 index 0000000000..68784595f3 --- /dev/null +++ b/test/controllers/orders_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +describe OrdersController do + # it "must be a real test" do + # flunk "Need real tests" + # end +end diff --git a/test/controllers/products_controller_test.rb b/test/controllers/products_controller_test.rb new file mode 100644 index 0000000000..392a20e292 --- /dev/null +++ b/test/controllers/products_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +describe ProductsController do + # it "must be a real test" do + # flunk "Need real tests" + # end +end diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb new file mode 100644 index 0000000000..c2632a720b --- /dev/null +++ b/test/controllers/sessions_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +describe SessionsController do + # it "must be a real test" do + # flunk "Need real tests" + # end +end diff --git a/test/controllers/welcome_controller_test.rb b/test/controllers/welcome_controller_test.rb new file mode 100644 index 0000000000..877b7e7456 --- /dev/null +++ b/test/controllers/welcome_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +describe WelcomeController do + # it "must be a real test" do + # flunk "Need real tests" + # end +end From 3ff0a440e7443ca8f8544d55f681dc925a5574f8 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Thu, 18 Oct 2018 10:49:58 -0700 Subject: [PATCH 003/178] created models (category, merchant, order, order_item, product, and review) plus created and migrated db --- Gemfile | 1 + Gemfile.lock | 2 +- app/models/category.rb | 2 + app/models/merchant.rb | 2 + app/models/order.rb | 2 + app/models/order_item.rb | 2 + app/models/product.rb | 2 + app/models/review.rb | 2 + db/migrate/20181018173542_create_merchants.rb | 10 +++ db/migrate/20181018173747_create_products.rb | 14 ++++ .../20181018173816_create_categories.rb | 8 +++ db/migrate/20181018174434_create_orders.rb | 14 ++++ .../20181018174517_create_order_items.rb | 9 +++ db/migrate/20181018174609_create_reviews.rb | 10 +++ db/schema.rb | 65 +++++++++++++++++++ test/fixtures/categories.yml | 11 ++++ test/fixtures/merchants.yml | 9 +++ test/fixtures/order_items.yml | 7 ++ test/fixtures/orders.yml | 17 +++++ test/fixtures/products.yml | 17 +++++ test/fixtures/reviews.yml | 9 +++ test/models/category_test.rb | 9 +++ test/models/merchant_test.rb | 9 +++ test/models/order_item_test.rb | 9 +++ test/models/order_test.rb | 9 +++ test/models/product_test.rb | 9 +++ test/models/review_test.rb | 9 +++ 27 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 app/models/category.rb create mode 100644 app/models/merchant.rb create mode 100644 app/models/order.rb create mode 100644 app/models/order_item.rb create mode 100644 app/models/product.rb create mode 100644 app/models/review.rb create mode 100644 db/migrate/20181018173542_create_merchants.rb create mode 100644 db/migrate/20181018173747_create_products.rb create mode 100644 db/migrate/20181018173816_create_categories.rb create mode 100644 db/migrate/20181018174434_create_orders.rb create mode 100644 db/migrate/20181018174517_create_order_items.rb create mode 100644 db/migrate/20181018174609_create_reviews.rb create mode 100644 db/schema.rb create mode 100644 test/fixtures/categories.yml create mode 100644 test/fixtures/merchants.yml create mode 100644 test/fixtures/order_items.yml create mode 100644 test/fixtures/orders.yml create mode 100644 test/fixtures/products.yml create mode 100644 test/fixtures/reviews.yml create mode 100644 test/models/category_test.rb create mode 100644 test/models/merchant_test.rb create mode 100644 test/models/order_item_test.rb create mode 100644 test/models/order_test.rb create mode 100644 test/models/product_test.rb create mode 100644 test/models/review_test.rb diff --git a/Gemfile b/Gemfile index 6219256bd8..e810a5e5da 100644 --- a/Gemfile +++ b/Gemfile @@ -2,6 +2,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.5.1' +ruby '2.4.1' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.2.1' diff --git a/Gemfile.lock b/Gemfile.lock index 3a4afee259..48b836da1a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -271,7 +271,7 @@ DEPENDENCIES web-console (>= 3.3.0) RUBY VERSION - ruby 2.5.1p57 + ruby 2.4.1p111 BUNDLED WITH 1.16.6 diff --git a/app/models/category.rb b/app/models/category.rb new file mode 100644 index 0000000000..54cb6aee3f --- /dev/null +++ b/app/models/category.rb @@ -0,0 +1,2 @@ +class Category < ApplicationRecord +end diff --git a/app/models/merchant.rb b/app/models/merchant.rb new file mode 100644 index 0000000000..0440407160 --- /dev/null +++ b/app/models/merchant.rb @@ -0,0 +1,2 @@ +class Merchant < ApplicationRecord +end diff --git a/app/models/order.rb b/app/models/order.rb new file mode 100644 index 0000000000..10281b3450 --- /dev/null +++ b/app/models/order.rb @@ -0,0 +1,2 @@ +class Order < ApplicationRecord +end diff --git a/app/models/order_item.rb b/app/models/order_item.rb new file mode 100644 index 0000000000..acc6099fd0 --- /dev/null +++ b/app/models/order_item.rb @@ -0,0 +1,2 @@ +class OrderItem < ApplicationRecord +end diff --git a/app/models/product.rb b/app/models/product.rb new file mode 100644 index 0000000000..35a85acab3 --- /dev/null +++ b/app/models/product.rb @@ -0,0 +1,2 @@ +class Product < ApplicationRecord +end diff --git a/app/models/review.rb b/app/models/review.rb new file mode 100644 index 0000000000..b2ca4935ed --- /dev/null +++ b/app/models/review.rb @@ -0,0 +1,2 @@ +class Review < ApplicationRecord +end diff --git a/db/migrate/20181018173542_create_merchants.rb b/db/migrate/20181018173542_create_merchants.rb new file mode 100644 index 0000000000..6fbed1393c --- /dev/null +++ b/db/migrate/20181018173542_create_merchants.rb @@ -0,0 +1,10 @@ +class CreateMerchants < ActiveRecord::Migration[5.2] + def change + create_table :merchants do |t| + t.string :username + t.string :email + + t.timestamps + end + end +end diff --git a/db/migrate/20181018173747_create_products.rb b/db/migrate/20181018173747_create_products.rb new file mode 100644 index 0000000000..806b7e381a --- /dev/null +++ b/db/migrate/20181018173747_create_products.rb @@ -0,0 +1,14 @@ +class CreateProducts < ActiveRecord::Migration[5.2] + def change + create_table :products do |t| + t.string :name + t.integer :price + t.string :description + t.string :photo_url + t.boolean :status + t.integer :inventory + + t.timestamps + end + end +end diff --git a/db/migrate/20181018173816_create_categories.rb b/db/migrate/20181018173816_create_categories.rb new file mode 100644 index 0000000000..92db7eb4be --- /dev/null +++ b/db/migrate/20181018173816_create_categories.rb @@ -0,0 +1,8 @@ +class CreateCategories < ActiveRecord::Migration[5.2] + def change + create_table :categories do |t| + + t.timestamps + end + end +end diff --git a/db/migrate/20181018174434_create_orders.rb b/db/migrate/20181018174434_create_orders.rb new file mode 100644 index 0000000000..ee826ff6a8 --- /dev/null +++ b/db/migrate/20181018174434_create_orders.rb @@ -0,0 +1,14 @@ +class CreateOrders < ActiveRecord::Migration[5.2] + def change + create_table :orders do |t| + t.string :payment_status + t.boolean :clear_cart + t.integer :total_price + t.datetime :date_time_placed + t.string :fulfillment_status + t.string :guest_user_info + + t.timestamps + end + end +end diff --git a/db/migrate/20181018174517_create_order_items.rb b/db/migrate/20181018174517_create_order_items.rb new file mode 100644 index 0000000000..cd9db5ef0d --- /dev/null +++ b/db/migrate/20181018174517_create_order_items.rb @@ -0,0 +1,9 @@ +class CreateOrderItems < ActiveRecord::Migration[5.2] + def change + create_table :order_items do |t| + t.integer :quantity + + t.timestamps + end + end +end diff --git a/db/migrate/20181018174609_create_reviews.rb b/db/migrate/20181018174609_create_reviews.rb new file mode 100644 index 0000000000..e1d038f2f5 --- /dev/null +++ b/db/migrate/20181018174609_create_reviews.rb @@ -0,0 +1,10 @@ +class CreateReviews < ActiveRecord::Migration[5.2] + def change + create_table :reviews do |t| + t.integer :star_rating + t.string :text + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000000..983771cd72 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,65 @@ +# 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: 2018_10_18_174609) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "categories", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "merchants", force: :cascade do |t| + t.string "username" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "order_items", force: :cascade do |t| + t.integer "quantity" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "orders", force: :cascade do |t| + t.string "payment_status" + t.boolean "clear_cart" + t.integer "total_price" + t.datetime "date_time_placed" + t.string "fulfillment_status" + t.string "guest_user_info" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "products", force: :cascade do |t| + t.string "name" + t.integer "price" + t.string "description" + t.string "photo_url" + t.boolean "status" + t.integer "inventory" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "reviews", force: :cascade do |t| + t.integer "star_rating" + t.string "text" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + +end diff --git a/test/fixtures/categories.yml b/test/fixtures/categories.yml new file mode 100644 index 0000000000..dc3ee79b5d --- /dev/null +++ b/test/fixtures/categories.yml @@ -0,0 +1,11 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +one: {} +# column: value +# +two: {} +# column: value diff --git a/test/fixtures/merchants.yml b/test/fixtures/merchants.yml new file mode 100644 index 0000000000..7af41d4fbe --- /dev/null +++ b/test/fixtures/merchants.yml @@ -0,0 +1,9 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + username: MyString + email: MyString + +two: + username: MyString + email: MyString diff --git a/test/fixtures/order_items.yml b/test/fixtures/order_items.yml new file mode 100644 index 0000000000..adb98ff007 --- /dev/null +++ b/test/fixtures/order_items.yml @@ -0,0 +1,7 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + quantity: + +two: + quantity: diff --git a/test/fixtures/orders.yml b/test/fixtures/orders.yml new file mode 100644 index 0000000000..a1d4afe6b1 --- /dev/null +++ b/test/fixtures/orders.yml @@ -0,0 +1,17 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + payment_status: MyString + clear_cart: false + total_price: + date_time_placed: 2018-10-18 10:44:34 + fulfillment_status: MyString + guest_user_info: MyString + +two: + payment_status: MyString + clear_cart: false + total_price: + date_time_placed: 2018-10-18 10:44:34 + fulfillment_status: MyString + guest_user_info: MyString diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml new file mode 100644 index 0000000000..83a390e3ea --- /dev/null +++ b/test/fixtures/products.yml @@ -0,0 +1,17 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + price: + description: MyString + photo_url: MyString + status: false + inventory: + +two: + name: MyString + price: + description: MyString + photo_url: MyString + status: false + inventory: diff --git a/test/fixtures/reviews.yml b/test/fixtures/reviews.yml new file mode 100644 index 0000000000..88814809c0 --- /dev/null +++ b/test/fixtures/reviews.yml @@ -0,0 +1,9 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + star_rating: + text: MyString + +two: + star_rating: + text: MyString diff --git a/test/models/category_test.rb b/test/models/category_test.rb new file mode 100644 index 0000000000..781320ad8e --- /dev/null +++ b/test/models/category_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +describe Category do + let(:category) { Category.new } + + it "must be valid" do + value(category).must_be :valid? + end +end diff --git a/test/models/merchant_test.rb b/test/models/merchant_test.rb new file mode 100644 index 0000000000..1cc99d985f --- /dev/null +++ b/test/models/merchant_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +describe Merchant do + let(:merchant) { Merchant.new } + + it "must be valid" do + value(merchant).must_be :valid? + end +end diff --git a/test/models/order_item_test.rb b/test/models/order_item_test.rb new file mode 100644 index 0000000000..19a9bf8f58 --- /dev/null +++ b/test/models/order_item_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +describe OrderItem do + let(:order_item) { OrderItem.new } + + it "must be valid" do + value(order_item).must_be :valid? + end +end diff --git a/test/models/order_test.rb b/test/models/order_test.rb new file mode 100644 index 0000000000..df80f10fb6 --- /dev/null +++ b/test/models/order_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +describe Order do + let(:order) { Order.new } + + it "must be valid" do + value(order).must_be :valid? + end +end diff --git a/test/models/product_test.rb b/test/models/product_test.rb new file mode 100644 index 0000000000..a618b0a156 --- /dev/null +++ b/test/models/product_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +describe Product do + let(:product) { Product.new } + + it "must be valid" do + value(product).must_be :valid? + end +end diff --git a/test/models/review_test.rb b/test/models/review_test.rb new file mode 100644 index 0000000000..ce8378a033 --- /dev/null +++ b/test/models/review_test.rb @@ -0,0 +1,9 @@ +require "test_helper" + +describe Review do + let(:review) { Review.new } + + it "must be valid" do + value(review).must_be :valid? + end +end From 92e3835a14fcbd8a1609caac738dc4152efed9df Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Thu, 18 Oct 2018 10:58:46 -0700 Subject: [PATCH 004/178] add orders controller --- app/controllers/orders_controller.rb | 57 +++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 8a0e3659ae..6096877221 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -1,2 +1,57 @@ class OrdersController < ApplicationController -end + +before_action :find_order, only: [:show, :edit, :update, :destroy, :review] + + def index + end + + def new + @order = Order.new + end + + def show + if @order.nil? + head :not_found + end + end + + def create + end + + def edit + if @order.nil? + head :not_found + end + end + + def update + if @order.nil? + head :not_found + end + + def destroy + if @order.nil? + head :not_found + end + + #if @order.destroy + #redirect_to orders_path + #else + #render :show + #end + end + + def review + end + + + private + + def order_params + return + end + + def find_order + @order = Order.find_by(id: params[:id]) + end + end From 4a866b950ea88b25a763f02490848deda7f9f0f5 Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Thu, 18 Oct 2018 10:59:26 -0700 Subject: [PATCH 005/178] add orders controller --- app/controllers/orders_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 6096877221..729801a13d 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -1,6 +1,6 @@ class OrdersController < ApplicationController -before_action :find_order, only: [:show, :edit, :update, :destroy, :review] +before_action :find_order, only: [:show, :edit, :update, :destroy] def index end From c42092c33ffad3cb0f7513d8bf17863f2cc650cd Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Thu, 18 Oct 2018 11:06:22 -0700 Subject: [PATCH 006/178] add resources routes --- config/routes.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/config/routes.rb b/config/routes.rb index 787824f888..e2e227718f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,11 @@ Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html + + resources :categories + resources :merchants + resources :orders + resources :products + resources :sessions + resources :welcome + end From 15fd8018a947c3aaaf0b3072be7eddd2a78daf63 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Thu, 18 Oct 2018 11:27:08 -0700 Subject: [PATCH 007/178] added model relations and foreign key references in db --- app/models/category.rb | 1 + app/models/merchant.rb | 1 + app/models/order.rb | 1 + app/models/order_item.rb | 2 ++ app/models/product.rb | 4 ++++ app/models/review.rb | 1 + ...81528_add_reference_in_product_to_merch.rb | 5 +++++ ...8181755_add_reference_in_review_to_prod.rb | 5 +++++ ...ference_in_order_item_to_prod_and_order.rb | 6 +++++ ...82126_add_reference_in_order_to_orditem.rb | 5 +++++ ...425_create_join_table_category_products.rb | 8 +++++++ db/schema.rb | 22 ++++++++++++++++++- 12 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20181018181528_add_reference_in_product_to_merch.rb create mode 100644 db/migrate/20181018181755_add_reference_in_review_to_prod.rb create mode 100644 db/migrate/20181018181934_add_reference_in_order_item_to_prod_and_order.rb create mode 100644 db/migrate/20181018182126_add_reference_in_order_to_orditem.rb create mode 100644 db/migrate/20181018182425_create_join_table_category_products.rb diff --git a/app/models/category.rb b/app/models/category.rb index 54cb6aee3f..f3218758f1 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -1,2 +1,3 @@ class Category < ApplicationRecord + has_and_belongs_to_many :products end diff --git a/app/models/merchant.rb b/app/models/merchant.rb index 0440407160..07aacc445d 100644 --- a/app/models/merchant.rb +++ b/app/models/merchant.rb @@ -1,2 +1,3 @@ class Merchant < ApplicationRecord + has_many :products end diff --git a/app/models/order.rb b/app/models/order.rb index 10281b3450..b8ff4b192c 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -1,2 +1,3 @@ class Order < ApplicationRecord + has_many :order_items end diff --git a/app/models/order_item.rb b/app/models/order_item.rb index acc6099fd0..bc16957ca1 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -1,2 +1,4 @@ class OrderItem < ApplicationRecord + belongs_to :order + belongs_to :product end diff --git a/app/models/product.rb b/app/models/product.rb index 35a85acab3..5cb8d8ab39 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -1,2 +1,6 @@ class Product < ApplicationRecord + has_many :reviews + has_many :order_items + belongs_to :merchant + has_and_belongs_to_many :categories end diff --git a/app/models/review.rb b/app/models/review.rb index b2ca4935ed..949d4ccddb 100644 --- a/app/models/review.rb +++ b/app/models/review.rb @@ -1,2 +1,3 @@ class Review < ApplicationRecord + belongs_to :product end diff --git a/db/migrate/20181018181528_add_reference_in_product_to_merch.rb b/db/migrate/20181018181528_add_reference_in_product_to_merch.rb new file mode 100644 index 0000000000..07d1716c44 --- /dev/null +++ b/db/migrate/20181018181528_add_reference_in_product_to_merch.rb @@ -0,0 +1,5 @@ +class AddReferenceInProductToMerch < ActiveRecord::Migration[5.2] + def change + add_reference :products, :merchant, foreign_key: true + end +end diff --git a/db/migrate/20181018181755_add_reference_in_review_to_prod.rb b/db/migrate/20181018181755_add_reference_in_review_to_prod.rb new file mode 100644 index 0000000000..db9fd6236b --- /dev/null +++ b/db/migrate/20181018181755_add_reference_in_review_to_prod.rb @@ -0,0 +1,5 @@ +class AddReferenceInReviewToProd < ActiveRecord::Migration[5.2] + def change + add_reference :reviews, :product, foreign_key: true + end +end diff --git a/db/migrate/20181018181934_add_reference_in_order_item_to_prod_and_order.rb b/db/migrate/20181018181934_add_reference_in_order_item_to_prod_and_order.rb new file mode 100644 index 0000000000..b174aa0126 --- /dev/null +++ b/db/migrate/20181018181934_add_reference_in_order_item_to_prod_and_order.rb @@ -0,0 +1,6 @@ +class AddReferenceInOrderItemToProdAndOrder < ActiveRecord::Migration[5.2] + def change + add_reference :order_items, :product, foreign_key: true + add_reference :order_items, :order, foreign_key: true + end +end diff --git a/db/migrate/20181018182126_add_reference_in_order_to_orditem.rb b/db/migrate/20181018182126_add_reference_in_order_to_orditem.rb new file mode 100644 index 0000000000..ebbd8d76a9 --- /dev/null +++ b/db/migrate/20181018182126_add_reference_in_order_to_orditem.rb @@ -0,0 +1,5 @@ +class AddReferenceInOrderToOrditem < ActiveRecord::Migration[5.2] + def change + add_reference :orders, :order_item, foreign_key: true + end +end diff --git a/db/migrate/20181018182425_create_join_table_category_products.rb b/db/migrate/20181018182425_create_join_table_category_products.rb new file mode 100644 index 0000000000..a67c560e5a --- /dev/null +++ b/db/migrate/20181018182425_create_join_table_category_products.rb @@ -0,0 +1,8 @@ +class CreateJoinTableCategoryProducts < ActiveRecord::Migration[5.2] + def change + create_join_table :categories, :products do |t| + # t.index [:category_id, :product_id] + # t.index [:product_id, :category_id] + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 983771cd72..e63a813e04 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: 2018_10_18_174609) do +ActiveRecord::Schema.define(version: 2018_10_18_182425) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -20,6 +20,11 @@ t.datetime "updated_at", null: false end + create_table "categories_products", id: false, force: :cascade do |t| + t.bigint "category_id", null: false + t.bigint "product_id", null: false + end + create_table "merchants", force: :cascade do |t| t.string "username" t.string "email" @@ -31,6 +36,10 @@ t.integer "quantity" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "product_id" + t.bigint "order_id" + t.index ["order_id"], name: "index_order_items_on_order_id" + t.index ["product_id"], name: "index_order_items_on_product_id" end create_table "orders", force: :cascade do |t| @@ -42,6 +51,8 @@ t.string "guest_user_info" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "order_item_id" + t.index ["order_item_id"], name: "index_orders_on_order_item_id" end create_table "products", force: :cascade do |t| @@ -53,6 +64,8 @@ t.integer "inventory" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "merchant_id" + t.index ["merchant_id"], name: "index_products_on_merchant_id" end create_table "reviews", force: :cascade do |t| @@ -60,6 +73,13 @@ t.string "text" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "product_id" + t.index ["product_id"], name: "index_reviews_on_product_id" end + add_foreign_key "order_items", "orders" + add_foreign_key "order_items", "products" + add_foreign_key "orders", "order_items" + add_foreign_key "products", "merchants" + add_foreign_key "reviews", "products" end From befcb5488151306ddfd718eb904888ceb2af6360 Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Thu, 18 Oct 2018 11:27:51 -0700 Subject: [PATCH 008/178] add more controller actions --- app/controllers/orders_controller.rb | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 729801a13d..77164b2662 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -16,6 +16,18 @@ def show end def create + @order = Order.new #(params) + @order_category = @order.category + if @order.save + flash[:status] = :success + # flash[:result_text] = "Successfully created order" + # redirect_to + else + flash[:status] = :failure + # flash[:result_text] = "Could not create order" + # flash[:messages] = @order.errors.messages + # render :new, status: :bad_request + end end def edit @@ -33,7 +45,6 @@ def destroy if @order.nil? head :not_found end - #if @order.destroy #redirect_to orders_path #else @@ -41,10 +52,6 @@ def destroy #end end - def review - end - - private def order_params From 6b39175b772538fb49a5a53e01ccde61b8afad04 Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Thu, 18 Oct 2018 12:00:14 -0700 Subject: [PATCH 009/178] added sessions, products, merchants crud actions. added omniauth. --- .gitignore | 2 ++ Gemfile | 7 +++- Gemfile.lock | 30 +++++++++++++++- app/controllers/merchants_controller.rb | 9 +++++ app/controllers/products_controller.rb | 46 +++++++++++++++++++++++++ app/controllers/sessions_controller.rb | 28 +++++++++++++++ config/initializers/omniauth.rb | 4 +++ config/routes.rb | 5 +-- 8 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 config/initializers/omniauth.rb diff --git a/.gitignore b/.gitignore index 18b43c9cd2..931707d157 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ # Ignore master key for decrypting credentials and more. /config/master.key + +.env diff --git a/Gemfile b/Gemfile index e810a5e5da..0d8c4817c6 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.5.1' -ruby '2.4.1' +# ruby '2.4.1' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.2.1' @@ -37,6 +37,9 @@ gem 'jbuilder', '~> 2.5' # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', '>= 1.1.0', require: false +gem "omniauth" +gem "omniauth-github" + 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] @@ -49,6 +52,8 @@ group :development do # 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 'dotenv-rails' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 48b836da1a..036f555ac3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -81,8 +81,14 @@ GEM concurrent-ruby (1.0.5) crass (1.0.4) debug_inspector (0.0.3) + dotenv (2.5.0) + dotenv-rails (2.5.0) + dotenv (= 2.5.0) + railties (>= 3.2, < 6.0) erubi (1.7.1) execjs (2.7.0) + faraday (0.15.3) + multipart-post (>= 1.2, < 3) ffi (1.9.25) formatador (0.2.5) globalid (0.4.1) @@ -100,6 +106,7 @@ GEM guard-minitest (2.4.6) guard-compat (~> 1.2) minitest (>= 3.0) + hashie (3.5.7) i18n (1.1.1) concurrent-ruby (~> 1.0) io-like (0.3.0) @@ -113,6 +120,7 @@ GEM jquery-turbolinks (2.1.0) railties (>= 3.1.0) turbolinks + jwt (2.1.0) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -140,6 +148,8 @@ GEM ruby-progressbar msgpack (1.2.4) multi_json (1.13.1) + multi_xml (0.6.0) + multipart-post (2.0.0) nenv (0.3.0) nio4r (2.3.1) nokogiri (1.8.5) @@ -147,6 +157,21 @@ GEM notiffany (0.1.1) nenv (~> 0.1) shellany (~> 0.0) + oauth2 (1.4.1) + faraday (>= 0.8, < 0.16.0) + jwt (>= 1.0, < 3.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + omniauth (1.8.1) + hashie (>= 3.4.6, < 3.6.0) + rack (>= 1.6.2, < 3) + omniauth-github (1.3.0) + omniauth (~> 1.5) + omniauth-oauth2 (>= 1.4.0, < 2.0) + omniauth-oauth2 (1.5.0) + oauth2 (~> 1.1) + omniauth (~> 1.2) pg (1.1.3) popper_js (1.14.3) pry (0.11.3) @@ -249,6 +274,7 @@ DEPENDENCIES byebug capybara (>= 2.15) chromedriver-helper + dotenv-rails guard guard-minitest jbuilder (~> 2.5) @@ -257,6 +283,8 @@ DEPENDENCIES listen (>= 3.0.5, < 3.2) minitest-rails minitest-reporters + omniauth + omniauth-github pg (>= 0.18, < 2.0) pry-rails puma (~> 3.11) @@ -271,7 +299,7 @@ DEPENDENCIES web-console (>= 3.3.0) RUBY VERSION - ruby 2.4.1p111 + ruby 2.5.1p57 BUNDLED WITH 1.16.6 diff --git a/app/controllers/merchants_controller.rb b/app/controllers/merchants_controller.rb index ae95f7677b..931c3c6fc5 100644 --- a/app/controllers/merchants_controller.rb +++ b/app/controllers/merchants_controller.rb @@ -1,2 +1,11 @@ class MerchantsController < ApplicationController + def index + @merchant = Merchant.all + end + + def show + @merchant = Merchant.find_by(id: params[:id]) + render_404 unless @merchant + end + end diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index f1ad12ddea..50af11e91c 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -1,2 +1,48 @@ class ProductsController < ApplicationController + before_action :find_product, only [:show, :edit, :update] + + def index + @products = Product.all + end + + def new + @product = Product.new + end + + def create + @product = Product.new(product_params) + if @product.save + flash[:status] = :success + flash[:result_text] = "Successfully created + #{@product}" + redirect_to product_path(@product) + else + flash[:status] = :failure + flash[:result_text] = "Could not create #{@product}" + flash[:messages] = @product.errors.messages + render :new, status: :bad_request + end + end + + def show + + end + + def edit + + end + + def update + end + + private + + def product_params + params.require(:product).permit(:name, :price, :description, :photo_url, :status, :inventory) + end + + def find_product + @product = Product.find_by(id: params[:id]) + head :not_found unless @product + end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 16d11b5710..cc60c88a6b 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,2 +1,30 @@ class SessionsController < ApplicationController + + def create + auth_hash = request.env['omniauth.auth'] + merchant = Merchant.find_by(uid: auth_hash[:uid], provider: 'github') + + if merchant + flash[:success] = "Logged in as merchant #{merchant.username}" + + else + merchant = Merchant.build_from_github(auth_hash) + if merchant.save + flash[:success] = "Logged in as new merchant #{merchant.username}" + else + flash[:error] = "Could not create new user account: #{merchant.errors.messages}" + redirect_to root_path + return + end + session[:username] = merchant.id + redirect_to root_path + end + + def destroy + session[:username] = nil + flash[:status] = :success + flash[:success] = "Successfully logged out!" + + redirect_to root_path + end end diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb new file mode 100644 index 0000000000..ef8c60c371 --- /dev/null +++ b/config/initializers/omniauth.rb @@ -0,0 +1,4 @@ +# config/initializers/omniauth.rb +Rails.application.config.middleware.use OmniAuth::Builder do + provider :github, ENV["GITHUB_CLIENT_ID"], ENV["GITHUB_CLIENT_SECRET"], scope: "user:email" +end diff --git a/config/routes.rb b/config/routes.rb index e2e227718f..f45c3973f6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,11 +1,12 @@ Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html - + get "/auth/:provider/callback", to: "sessions#create" + resources :categories resources :merchants resources :orders resources :products resources :sessions resources :welcome - + end From b2c070f3ba6cbe66e75f56f86945bf275fbe620d Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Thu, 18 Oct 2018 12:00:41 -0700 Subject: [PATCH 010/178] add views --- app/controllers/orders_controller.rb | 2 +- app/views/merchants/edit.html.erb | 1 + app/views/merchants/index.html.erb | 1 + app/views/merchants/new.html.erb | 1 + app/views/merchants/show.html.erb | 1 + app/views/orders/edit.html.erb | 1 + app/views/orders/index.html.erb | 1 + app/views/orders/new.html.erb | 1 + app/views/orders/show.html.erb | 1 + app/views/products/by_category.html.erb | 0 app/views/products/edit.html.erb | 1 + app/views/products/index.html.erb | 1 + app/views/products/new.html.erb | 1 + app/views/products/show.html.erb | 1 + app/views/welcome/index.html.erb | 1 + 15 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 app/views/merchants/edit.html.erb create mode 100644 app/views/merchants/index.html.erb create mode 100644 app/views/merchants/new.html.erb create mode 100644 app/views/merchants/show.html.erb create mode 100644 app/views/orders/edit.html.erb create mode 100644 app/views/orders/index.html.erb create mode 100644 app/views/orders/new.html.erb create mode 100644 app/views/orders/show.html.erb create mode 100644 app/views/products/by_category.html.erb create mode 100644 app/views/products/edit.html.erb create mode 100644 app/views/products/index.html.erb create mode 100644 app/views/products/new.html.erb create mode 100644 app/views/products/show.html.erb create mode 100644 app/views/welcome/index.html.erb diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 77164b2662..669e871621 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -27,7 +27,7 @@ def create # flash[:result_text] = "Could not create order" # flash[:messages] = @order.errors.messages # render :new, status: :bad_request - end + end end def edit diff --git a/app/views/merchants/edit.html.erb b/app/views/merchants/edit.html.erb new file mode 100644 index 0000000000..4ff1582f05 --- /dev/null +++ b/app/views/merchants/edit.html.erb @@ -0,0 +1 @@ +

Merchant Edit Page

diff --git a/app/views/merchants/index.html.erb b/app/views/merchants/index.html.erb new file mode 100644 index 0000000000..98a653d419 --- /dev/null +++ b/app/views/merchants/index.html.erb @@ -0,0 +1 @@ +

Merchant Index Page!

diff --git a/app/views/merchants/new.html.erb b/app/views/merchants/new.html.erb new file mode 100644 index 0000000000..9698c7f45f --- /dev/null +++ b/app/views/merchants/new.html.erb @@ -0,0 +1 @@ +

New Merchant Page

diff --git a/app/views/merchants/show.html.erb b/app/views/merchants/show.html.erb new file mode 100644 index 0000000000..d72b7a3c92 --- /dev/null +++ b/app/views/merchants/show.html.erb @@ -0,0 +1 @@ +

Merchant Show Page!

diff --git a/app/views/orders/edit.html.erb b/app/views/orders/edit.html.erb new file mode 100644 index 0000000000..803ef8c329 --- /dev/null +++ b/app/views/orders/edit.html.erb @@ -0,0 +1 @@ +

Edit Order Page

diff --git a/app/views/orders/index.html.erb b/app/views/orders/index.html.erb new file mode 100644 index 0000000000..2d95e40b3f --- /dev/null +++ b/app/views/orders/index.html.erb @@ -0,0 +1 @@ +

Orders Index page

diff --git a/app/views/orders/new.html.erb b/app/views/orders/new.html.erb new file mode 100644 index 0000000000..f790ae9bb0 --- /dev/null +++ b/app/views/orders/new.html.erb @@ -0,0 +1 @@ +

New Order Page

diff --git a/app/views/orders/show.html.erb b/app/views/orders/show.html.erb new file mode 100644 index 0000000000..0ce795f60e --- /dev/null +++ b/app/views/orders/show.html.erb @@ -0,0 +1 @@ +

Order Show Page

diff --git a/app/views/products/by_category.html.erb b/app/views/products/by_category.html.erb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/views/products/edit.html.erb b/app/views/products/edit.html.erb new file mode 100644 index 0000000000..26f71df1be --- /dev/null +++ b/app/views/products/edit.html.erb @@ -0,0 +1 @@ +

Product Edit Page

diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb new file mode 100644 index 0000000000..423779fa33 --- /dev/null +++ b/app/views/products/index.html.erb @@ -0,0 +1 @@ +

Product Index Page

diff --git a/app/views/products/new.html.erb b/app/views/products/new.html.erb new file mode 100644 index 0000000000..fab5598404 --- /dev/null +++ b/app/views/products/new.html.erb @@ -0,0 +1 @@ +

New Product Page

diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb new file mode 100644 index 0000000000..eae26663ce --- /dev/null +++ b/app/views/products/show.html.erb @@ -0,0 +1 @@ +

Product Show Page

diff --git a/app/views/welcome/index.html.erb b/app/views/welcome/index.html.erb new file mode 100644 index 0000000000..f36016e70b --- /dev/null +++ b/app/views/welcome/index.html.erb @@ -0,0 +1 @@ +

Redsy Homepage!

From eae050766172af0873f82ced31a8002759f55b96 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Thu, 18 Oct 2018 12:06:43 -0700 Subject: [PATCH 011/178] added basic validations to the model + category_name to category db --- Gemfile | 2 +- Gemfile.lock | 2 +- app/models/merchant.rb | 5 +++++ app/models/order.rb | 1 + app/models/order_item.rb | 3 +++ app/models/product.rb | 5 +++++ app/models/review.rb | 2 ++ db/migrate/20181018190251_add_category_name_to_category.rb | 5 +++++ db/schema.rb | 3 ++- 9 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20181018190251_add_category_name_to_category.rb diff --git a/Gemfile b/Gemfile index 0d8c4817c6..2a5bfac0f2 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.5.1' -# ruby '2.4.1' +ruby '2.4.1' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.2.1' diff --git a/Gemfile.lock b/Gemfile.lock index 036f555ac3..9d61eae183 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -299,7 +299,7 @@ DEPENDENCIES web-console (>= 3.3.0) RUBY VERSION - ruby 2.5.1p57 + ruby 2.4.1p111 BUNDLED WITH 1.16.6 diff --git a/app/models/merchant.rb b/app/models/merchant.rb index 07aacc445d..aad8adbba4 100644 --- a/app/models/merchant.rb +++ b/app/models/merchant.rb @@ -1,3 +1,8 @@ class Merchant < ApplicationRecord has_many :products + + validates :username, presence: true, uniqueness: true + validates :email, presence: true, uniqueness: true + + # TODO: maybe check for valid email form xxx@xxx.xxx end diff --git a/app/models/order.rb b/app/models/order.rb index b8ff4b192c..a3af5d28c6 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -1,3 +1,4 @@ class Order < ApplicationRecord + # QUESTION: does this cover the entirety of the "must have one or many"?? has_many :order_items end diff --git a/app/models/order_item.rb b/app/models/order_item.rb index bc16957ca1..899c431c4a 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -1,4 +1,7 @@ class OrderItem < ApplicationRecord belongs_to :order belongs_to :product + + validates :quantity, presence: true, numericality: { greater_than: 0, only_integer: true } + end diff --git a/app/models/product.rb b/app/models/product.rb index 5cb8d8ab39..f237ae8e76 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -3,4 +3,9 @@ class Product < ApplicationRecord has_many :order_items belongs_to :merchant has_and_belongs_to_many :categories + + validates :name, presence: true, uniqueness: true + # TODO: maybe product name must be unique to category??? + validates :price, presence: true, numericality: { greater_than: 0 } + # QUESTION: user will likely input a float --> so we will need to change it into an int b/c it'll be stored as an int in the db end diff --git a/app/models/review.rb b/app/models/review.rb index 949d4ccddb..651c889ec5 100644 --- a/app/models/review.rb +++ b/app/models/review.rb @@ -1,3 +1,5 @@ class Review < ApplicationRecord belongs_to :product + + validates :star_rating, presence: true, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 5, only_integer: true } end diff --git a/db/migrate/20181018190251_add_category_name_to_category.rb b/db/migrate/20181018190251_add_category_name_to_category.rb new file mode 100644 index 0000000000..cda6b5c5e6 --- /dev/null +++ b/db/migrate/20181018190251_add_category_name_to_category.rb @@ -0,0 +1,5 @@ +class AddCategoryNameToCategory < ActiveRecord::Migration[5.2] + def change + add_column :categories, :category_name, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index e63a813e04..a590078a78 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: 2018_10_18_182425) do +ActiveRecord::Schema.define(version: 2018_10_18_190251) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -18,6 +18,7 @@ create_table "categories", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "category_name" end create_table "categories_products", id: false, force: :cascade do |t| From e286d9e94341f559676e261049f25d50dafb7c0a Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Thu, 18 Oct 2018 12:20:37 -0700 Subject: [PATCH 012/178] fix bug in order controller --- Gemfile | 2 +- app/controllers/orders_controller.rb | 27 ++++++++++++++------------- config/routes.rb | 6 +++--- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Gemfile b/Gemfile index 2a5bfac0f2..0d8c4817c6 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.5.1' -ruby '2.4.1' +# ruby '2.4.1' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.2.1' diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 669e871621..fe1d0b0806 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -1,6 +1,6 @@ class OrdersController < ApplicationController -before_action :find_order, only: [:show, :edit, :update, :destroy] + before_action :find_order, only: [:show, :edit, :update, :destroy] def index end @@ -39,26 +39,27 @@ def edit def update if @order.nil? head :not_found - end - - def destroy - if @order.nil? - head :not_found end - #if @order.destroy + + def destroy + if @order.nil? + head :not_found + end + #if @order.destroy #redirect_to orders_path - #else + #else #render :show - #end - end + #end + end - private + private - def order_params - return + def order_params + return end def find_order @order = Order.find_by(id: params[:id]) end end +end diff --git a/config/routes.rb b/config/routes.rb index f45c3973f6..d219de8935 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,12 +1,12 @@ Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html get "/auth/:provider/callback", to: "sessions#create" - + + root 'products#root' resources :categories resources :merchants resources :orders resources :products - resources :sessions - resources :welcome + end From 01566bf83354d7f8f565c20d53c3c61dd3584caa Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Thu, 18 Oct 2018 12:23:13 -0700 Subject: [PATCH 013/178] add homepage to routes --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index d219de8935..a764084b10 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,7 +2,7 @@ # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html get "/auth/:provider/callback", to: "sessions#create" - root 'products#root' + root 'welcome#index' resources :categories resources :merchants resources :orders From 7cf6989ac2a5e9251e426c1ab55170edf0f71652 Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Thu, 18 Oct 2018 12:34:08 -0700 Subject: [PATCH 014/178] fixed syntax error in products controller --- app/controllers/products_controller.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index 50af11e91c..c4dd1795d3 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -1,5 +1,5 @@ class ProductsController < ApplicationController - before_action :find_product, only [:show, :edit, :update] + before_action :find_product, only: [:show, :edit, :update] def index @products = Product.all @@ -24,11 +24,10 @@ def create end end - def show - + def show; end - def edit + def edit; end From 9a74ced3c55993d9bd466efc4c180385d9e041c2 Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Thu, 18 Oct 2018 14:54:18 -0700 Subject: [PATCH 015/178] added sessions controller login/logout function --- app/controllers/sessions_controller.rb | 46 ++++++++++++++++--- app/models/merchant.rb | 9 ++++ app/views/layouts/application.html.erb | 18 +++++++- config/routes.rb | 1 + .../20181018211820_add_uid_to_merchants.rb | 5 ++ ...0181018212016_add_provider_to_merchants.rb | 5 ++ db/schema.rb | 4 +- 7 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 db/migrate/20181018211820_add_uid_to_merchants.rb create mode 100644 db/migrate/20181018212016_add_provider_to_merchants.rb diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index cc60c88a6b..060a6292e5 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,25 +1,59 @@ class SessionsController < ApplicationController + # def create + # auth_hash = request.env['omniauth.auth'] + # merchant = Merchant.find_by(uid: auth_hash[:uid], provider: 'github') + # + # if merchant + # flash[:success] = "Logged in as merchant #{merchant.username}" + # + # else + # merchant = Merchant.build_from_github(auth_hash) + # + # if merchant.save + # flash[:success] = "Logged in as new merchant #{merchant.username}" + # else + # flash[:error] = "Could not create new user account: #{merchant.errors.messages}" + # redirect_to root_path + # return + # end + # session[:username] = merchant.id + # redirect_to root_path + # end + # end + def create auth_hash = request.env['omniauth.auth'] merchant = Merchant.find_by(uid: auth_hash[:uid], provider: 'github') - if merchant - flash[:success] = "Logged in as merchant #{merchant.username}" - + # User was found in the database + flash[:success] = "Logged in as returning user #{merchant.username}" else + # User doesn't match anything in the DB + # Attempt to create a new user merchant = Merchant.build_from_github(auth_hash) + if merchant.save flash[:success] = "Logged in as new merchant #{merchant.username}" + else - flash[:error] = "Could not create new user account: #{merchant.errors.messages}" + # Couldn't save the user for some reason. If we + # hit this it probably means there's a bug with the + # way we've configured GitHub. Our strategy will + # be to display error messages to make future + # debugging easier. + flash[:error] = "Could not create new merchant account: #{merchant.errors.messages}" redirect_to root_path return end - session[:username] = merchant.id - redirect_to root_path end + # If we get here, we have a valid user instance + session[:username] = merchant.id + redirect_to root_path + end + + def destroy session[:username] = nil flash[:status] = :success diff --git a/app/models/merchant.rb b/app/models/merchant.rb index aad8adbba4..33d0abf4f9 100644 --- a/app/models/merchant.rb +++ b/app/models/merchant.rb @@ -5,4 +5,13 @@ class Merchant < ApplicationRecord validates :email, presence: true, uniqueness: true # TODO: maybe check for valid email form xxx@xxx.xxx + + def self.build_from_github(auth_hash) + merchant = Merchant.new + merchant.uid = auth_hash[:uid] + merchant.provider = 'github' + merchant.email = auth_hash['info']['email'] + merchant.username = auth_hash['info']['nickname'] + return merchant + end end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index f18f1b6820..4bb0fb629a 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -9,7 +9,23 @@ <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + + - <%= yield %> +
+ <%= yield %> +
+ diff --git a/config/routes.rb b/config/routes.rb index a764084b10..bab8d98b02 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,7 @@ Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html get "/auth/:provider/callback", to: "sessions#create" + delete "/logout", to: "sessions#destroy", as: "logout" root 'welcome#index' resources :categories diff --git a/db/migrate/20181018211820_add_uid_to_merchants.rb b/db/migrate/20181018211820_add_uid_to_merchants.rb new file mode 100644 index 0000000000..a3230fab34 --- /dev/null +++ b/db/migrate/20181018211820_add_uid_to_merchants.rb @@ -0,0 +1,5 @@ +class AddUidToMerchants < ActiveRecord::Migration[5.2] + def change + add_column :merchants, :uid, :integer, null: false + end +end diff --git a/db/migrate/20181018212016_add_provider_to_merchants.rb b/db/migrate/20181018212016_add_provider_to_merchants.rb new file mode 100644 index 0000000000..91a549f132 --- /dev/null +++ b/db/migrate/20181018212016_add_provider_to_merchants.rb @@ -0,0 +1,5 @@ +class AddProviderToMerchants < ActiveRecord::Migration[5.2] + def change + add_column :merchants, :provider, :string, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index a590078a78..9dc8079464 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: 2018_10_18_190251) do +ActiveRecord::Schema.define(version: 2018_10_18_212016) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -31,6 +31,8 @@ t.string "email" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "uid", null: false + t.string "provider", null: false end create_table "order_items", force: :cascade do |t| From 812843ad208a930354ccbf2e387fcc98a4a0c326 Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Thu, 18 Oct 2018 15:28:45 -0700 Subject: [PATCH 016/178] cleaned up controller code --- app/controllers/sessions_controller.rb | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 060a6292e5..e0bd4f7b19 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,27 +1,5 @@ class SessionsController < ApplicationController - # def create - # auth_hash = request.env['omniauth.auth'] - # merchant = Merchant.find_by(uid: auth_hash[:uid], provider: 'github') - # - # if merchant - # flash[:success] = "Logged in as merchant #{merchant.username}" - # - # else - # merchant = Merchant.build_from_github(auth_hash) - # - # if merchant.save - # flash[:success] = "Logged in as new merchant #{merchant.username}" - # else - # flash[:error] = "Could not create new user account: #{merchant.errors.messages}" - # redirect_to root_path - # return - # end - # session[:username] = merchant.id - # redirect_to root_path - # end - # end - def create auth_hash = request.env['omniauth.auth'] merchant = Merchant.find_by(uid: auth_hash[:uid], provider: 'github') From 852d64f3bbfcde17b74cc06ae38fd9e143a6f13c Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Thu, 18 Oct 2018 15:34:01 -0700 Subject: [PATCH 017/178] updated routes. --- config/routes.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index bab8d98b02..3d817c18c9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,9 +1,8 @@ Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html + root 'welcome#index' get "/auth/:provider/callback", to: "sessions#create" delete "/logout", to: "sessions#destroy", as: "logout" - - root 'welcome#index' resources :categories resources :merchants resources :orders From 8202bd9d761af04744918a0103b4a495f1c8873c Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Thu, 18 Oct 2018 15:39:55 -0700 Subject: [PATCH 018/178] add find_merchant method to application controller. --- app/controllers/application_controller.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 09705d12ab..c6821a73a0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,2 +1,17 @@ class ApplicationController < ActionController::Base + protect_from_forgery with: :exception + + before_action :find_merchant + + def render_404 + # DPR: this will actually render a 404 page in production + raise ActionController::RoutingError.new('Not Found') + end + +private + def find_merchant + if session[:username] + @login_user = User.find_by(id: session[:username]) + end + end end From baa4ba8c90d749f053a80a2b62524021a22c10e3 Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Thu, 18 Oct 2018 15:56:40 -0700 Subject: [PATCH 019/178] fixed application and sessions cntrollers --- app/controllers/application_controller.rb | 2 +- app/controllers/sessions_controller.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c6821a73a0..cdb52a0134 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -11,7 +11,7 @@ def render_404 private def find_merchant if session[:username] - @login_user = User.find_by(id: session[:username]) + @login_user = Merchant.find_by(id: session[:username]) end end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index e0bd4f7b19..6434865ae2 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -32,7 +32,7 @@ def create end - def destroy + def destroy session[:username] = nil flash[:status] = :success flash[:success] = "Successfully logged out!" From a7faf89c2cf419161d2c00cdf00775c5218a9eb5 Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Thu, 18 Oct 2018 16:36:09 -0700 Subject: [PATCH 020/178] fixed message errors in application.html.erb --- app/views/layouts/application.html.erb | 41 ++++++++++++++++++-------- app/views/products/_form.html.erb | 29 ++++++++++++++++++ app/views/products/new.html.erb | 3 +- 3 files changed, 59 insertions(+), 14 deletions(-) create mode 100644 app/views/products/_form.html.erb diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 4bb0fb629a..3b35998419 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -9,20 +9,35 @@ <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> - - + + <% if flash[:result_text] or flash[:messages] %> +
+
+ <%= flash[:status].to_s == "failure" ? "A problem occurred: " : "" %><%= flash[:result_text] %> + <% if flash[:messages] %> +
    + <% flash[:messages].each do |name, problems| %> + <% problems.each do |problem| %> +
  • <%= name %>: <%= problem %>
  • + <% end %> + <% end %> +
+ <% end %> +
+
+ <% end %>
<%= yield %>
diff --git a/app/views/products/_form.html.erb b/app/views/products/_form.html.erb new file mode 100644 index 0000000000..69892ee890 --- /dev/null +++ b/app/views/products/_form.html.erb @@ -0,0 +1,29 @@ +
+ <%= form_with model: @product do |f| %> + + +
+ <%= f.label :name %> + <%= f.text_field :name, class: "form-control" %> +
+ +
+ <%= f.label :price%> + <%= f.text_field :price, class: "form-control" %> +
+ +
+ <%= f.label :description %> + <%= f.text_field :description, class: "form-control" %> +
+ +
+ <%= f.label :inventory %> + <%= f.text_area :inventory, class: "form-control" %> +
+ +
+ <%= f.submit class: "btn btn-primary" %> +
+ <% end %> +
diff --git a/app/views/products/new.html.erb b/app/views/products/new.html.erb index fab5598404..dd588129d2 100644 --- a/app/views/products/new.html.erb +++ b/app/views/products/new.html.erb @@ -1 +1,2 @@ -

New Product Page

+

Add a New Product Page

+<%= render partial: "form" %> From 7ef1ce0c8ec79ac2f7e1bba46dba64d975c14794 Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Thu, 18 Oct 2018 16:49:37 -0700 Subject: [PATCH 021/178] add list to index page --- app/views/products/index.html.erb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb index 423779fa33..e90c8424f2 100644 --- a/app/views/products/index.html.erb +++ b/app/views/products/index.html.erb @@ -1 +1,14 @@ -

Product Index Page

+

List of Products

+ +
    + <% @products.each do |p| %> +
  • + + <%= p.name %> + <%= p.price %> + +
    + <%= link_to "View Details - Image", product_path(p.id) %> +
    +
  • + <% end %> From 260faeb0bf8b2bb305dd5ce7227ef5f8d34f97c8 Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Thu, 18 Oct 2018 17:30:29 -0700 Subject: [PATCH 022/178] add image to product page --- app/views/products/index.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb index e90c8424f2..c44229b343 100644 --- a/app/views/products/index.html.erb +++ b/app/views/products/index.html.erb @@ -5,10 +5,10 @@
  • <%= p.name %> - <%= p.price %> + <%= p.price.to_f.round(2) %>
    - <%= link_to "View Details - Image", product_path(p.id) %> + <%= link_to (image_tag p.photo_url), product_path(p.id), class: "btn btn-outline-secondary" %>
  • <% end %> From b9460461ca1eb3ed65ad09833b311e28ae80f546 Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Thu, 18 Oct 2018 18:20:38 -0700 Subject: [PATCH 023/178] add show page details --- app/views/products/show.html.erb | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb index eae26663ce..ffa3ba9116 100644 --- a/app/views/products/show.html.erb +++ b/app/views/products/show.html.erb @@ -1 +1,30 @@ -

    Product Show Page

    +

    Product Details

    + +

    <%= @product.name %>

    + +
    + <%= image_tag @product.photo_url %> +
    +
    Price:$ + <%= @product.price %> +
    + +
    Description: + <%= @product.description %> +
    + +
    Seller: + <%= @product.merchant.username %> +
    + +
    Description: + <%= @product.description %> +
    + +
    Categories: + <%= %> +
    + +
    Add to Cart + <%= %> +
    From 22c5bc62e1b854f5fe4f6b3fed6965b6e0e5a853 Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Thu, 18 Oct 2018 18:34:39 -0700 Subject: [PATCH 024/178] add info and links to product page --- app/views/products/show.html.erb | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb index ffa3ba9116..2b072f0e7b 100644 --- a/app/views/products/show.html.erb +++ b/app/views/products/show.html.erb @@ -11,16 +11,21 @@
    Description: <%= @product.description %> -
    + -
    Seller: - <%= @product.merchant.username %> +
    Rating: + <%= %>
    -
    Description: - <%= @product.description %> +
    Reviews: + <%= %>
    +
    Seller: + <%= link_to @product.merchant.username, merchant_path %> +
    + +
    Categories: <%= %>
    @@ -28,3 +33,9 @@
    Add to Cart <%= %>
    + +
    Inventory: + +
    <%= link_to "Back to All Products", products_path %> + +
    From ab26195b1f11c42cf72c7c866e2e92213c794845 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Thu, 18 Oct 2018 21:17:09 -0700 Subject: [PATCH 025/178] added comments/questions in models --- app/models/application_record.rb | 2 ++ app/models/order_item.rb | 2 ++ app/models/product.rb | 1 + 3 files changed, 5 insertions(+) diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 10a4cba84d..bec12ae5a9 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -1,3 +1,5 @@ class ApplicationRecord < ActiveRecord::Base self.abstract_class = true + + # QUESTION: overall --> protect from forgery?? how to secure user input? end diff --git a/app/models/order_item.rb b/app/models/order_item.rb index 899c431c4a..01a30ff731 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -4,4 +4,6 @@ class OrderItem < ApplicationRecord validates :quantity, presence: true, numericality: { greater_than: 0, only_integer: true } + # QUESTION: date_time_placed --> redundant b/c of created_at time stamp?? + end diff --git a/app/models/product.rb b/app/models/product.rb index f237ae8e76..cc8e50aa9e 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -8,4 +8,5 @@ class Product < ApplicationRecord # TODO: maybe product name must be unique to category??? validates :price, presence: true, numericality: { greater_than: 0 } # QUESTION: user will likely input a float --> so we will need to change it into an int b/c it'll be stored as an int in the db + # QUESTION: how to handle invalid photo URLS since that will 'show up' ?? end From e81fee0c3fea2ac8dc54368e9f7d00e057642d3b Mon Sep 17 00:00:00 2001 From: Layla Date: Thu, 18 Oct 2018 22:01:46 -0700 Subject: [PATCH 026/178] worked on tests for products controller --- Gemfile.lock | 2 +- test/controllers/order_controller_test.rb | 7 --- test/controllers/orders_controller_test.rb | 1 + test/controllers/products_controller_test.rb | 47 ++++++++++++++++++++ test/fixtures/merchants.yml | 4 ++ test/models/category_test.rb | 1 + test/models/merchant_test.rb | 1 + test/models/order_item_test.rb | 1 + test/models/order_test.rb | 1 + test/models/product_test.rb | 1 + test/models/review_test.rb | 1 + test/test_helper.rb | 4 +- 12 files changed, 62 insertions(+), 9 deletions(-) delete mode 100644 test/controllers/order_controller_test.rb diff --git a/Gemfile.lock b/Gemfile.lock index 9d61eae183..036f555ac3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -299,7 +299,7 @@ DEPENDENCIES web-console (>= 3.3.0) RUBY VERSION - ruby 2.4.1p111 + ruby 2.5.1p57 BUNDLED WITH 1.16.6 diff --git a/test/controllers/order_controller_test.rb b/test/controllers/order_controller_test.rb deleted file mode 100644 index 41f714e492..0000000000 --- a/test/controllers/order_controller_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require "test_helper" - -describe OrderController do - # it "must be a real test" do - # flunk "Need real tests" - # end -end diff --git a/test/controllers/orders_controller_test.rb b/test/controllers/orders_controller_test.rb index 68784595f3..a105e3f85e 100644 --- a/test/controllers/orders_controller_test.rb +++ b/test/controllers/orders_controller_test.rb @@ -1,6 +1,7 @@ require "test_helper" describe OrdersController do + # it "must be a real test" do # flunk "Need real tests" # end diff --git a/test/controllers/products_controller_test.rb b/test/controllers/products_controller_test.rb index 392a20e292..1264899874 100644 --- a/test/controllers/products_controller_test.rb +++ b/test/controllers/products_controller_test.rb @@ -4,4 +4,51 @@ # it "must be a real test" do # flunk "Need real tests" # end + + describe "create" do + + it "can create a product" do + product_hash = { + product: { + name: 'Practical Object Oriented Programming in Ruby', + price: 20, + description: 'A look at how to design object-oriented systems', + photo_url: 'test url', + status: true, + inventory: 1 + } + } + + expect { + post products_path, params: product_hash + }.must_change 'Product.count', 1 + + must_respond_with :redirect + + expect(Product.last.name).must_equal product_hash[:product][:name] + expect(Product.last.price).must_equal product_hash[:product][:price] + expect(Product.last.description).must_equal product_hash[:product][:description] + end + + + it "will not create a product with invalid params" do + product_hash = { + product: { + name: 'Practical Object Oriented Programming in Ruby', + price: 20, + description: 'A look at how to design object-oriented systems', + photo_url: 'test url', + status: "test string not boolean", + inventory: 1 + } + } + + expect { + post products_path, params: product_hash + }.wont_change 'Product.count' + + must_respond_with :bad_request + end + + end end diff --git a/test/fixtures/merchants.yml b/test/fixtures/merchants.yml index 7af41d4fbe..2cf8d88d97 100644 --- a/test/fixtures/merchants.yml +++ b/test/fixtures/merchants.yml @@ -3,7 +3,11 @@ one: username: MyString email: MyString + uid: 1 + provider: MyString two: username: MyString email: MyString + uid: 2 + provider: MyString diff --git a/test/models/category_test.rb b/test/models/category_test.rb index 781320ad8e..cde85f3e39 100644 --- a/test/models/category_test.rb +++ b/test/models/category_test.rb @@ -4,6 +4,7 @@ let(:category) { Category.new } it "must be valid" do + skip value(category).must_be :valid? end end diff --git a/test/models/merchant_test.rb b/test/models/merchant_test.rb index 1cc99d985f..ad61facb07 100644 --- a/test/models/merchant_test.rb +++ b/test/models/merchant_test.rb @@ -4,6 +4,7 @@ let(:merchant) { Merchant.new } it "must be valid" do + skip value(merchant).must_be :valid? end end diff --git a/test/models/order_item_test.rb b/test/models/order_item_test.rb index 19a9bf8f58..bb499e2558 100644 --- a/test/models/order_item_test.rb +++ b/test/models/order_item_test.rb @@ -4,6 +4,7 @@ let(:order_item) { OrderItem.new } it "must be valid" do + skip value(order_item).must_be :valid? end end diff --git a/test/models/order_test.rb b/test/models/order_test.rb index df80f10fb6..b342672bdf 100644 --- a/test/models/order_test.rb +++ b/test/models/order_test.rb @@ -4,6 +4,7 @@ let(:order) { Order.new } it "must be valid" do + skip value(order).must_be :valid? end end diff --git a/test/models/product_test.rb b/test/models/product_test.rb index a618b0a156..162c183029 100644 --- a/test/models/product_test.rb +++ b/test/models/product_test.rb @@ -4,6 +4,7 @@ let(:product) { Product.new } it "must be valid" do + skip value(product).must_be :valid? end end diff --git a/test/models/review_test.rb b/test/models/review_test.rb index ce8378a033..fbe2b1460b 100644 --- a/test/models/review_test.rb +++ b/test/models/review_test.rb @@ -4,6 +4,7 @@ let(:review) { Review.new } it "must be valid" do + skip value(review).must_be :valid? end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 2b5172a7d6..188632c8e5 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,7 +1,9 @@ ENV["RAILS_ENV"] = "test" require File.expand_path("../../config/environment", __FILE__) require "rails/test_help" -require "minitest/rails"require "minitest/reporters" # for Colorized output +require "minitest/rails" +require "minitest/reporters" +# for Colorized output # For colorful output! Minitest::Reporters.use!( Minitest::Reporters::SpecReporter.new, From 8d92e19d6b79104daa2c87d5111d90d1e0a15425 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Thu, 18 Oct 2018 22:25:15 -0700 Subject: [PATCH 027/178] added fixtures and left comments re design questions in corresponding models --- app/assets/images/.DS_Store | Bin 0 -> 6148 bytes app/assets/images/test/americat1.jpg | Bin 0 -> 53103 bytes app/assets/images/test/americat2.jpg | Bin 0 -> 52710 bytes app/models/order.rb | 8 +++ app/models/order_item.rb | 2 +- app/models/product.rb | 1 + test/fixtures/categories.yml | 28 +++++++-- test/fixtures/merchants.yml | 34 +++++++++-- test/fixtures/order_items.yml | 32 ++++++++-- test/fixtures/orders.yml | 44 ++++++++++---- test/fixtures/products.yml | 86 ++++++++++++++++++++++----- test/fixtures/reviews.yml | 29 +++++++-- 12 files changed, 215 insertions(+), 49 deletions(-) create mode 100644 app/assets/images/.DS_Store create mode 100644 app/assets/images/test/americat1.jpg create mode 100644 app/assets/images/test/americat2.jpg diff --git a/app/assets/images/.DS_Store b/app/assets/images/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e6f2e2e77994ce35b1b3c543940531e4ca014fff GIT binary patch literal 6148 zcmeHKJ5Iw;5Znb9kthfq<-0;|U`5Ueasi|mL4oB+IK8_Zkz;TG8fHI4iHr*xB+#t1 zZ~S(5{F&mdS44DuXjdXj5m~?uCI0hmD)1U%_sySk4&=D_LS0|2v zK^M*8L-Wa+6N>uNasT4wqIDowDnJD;6}XP=()#}fJ~RJclDMM+RN!AJpjESN*7&6C zt&@+lUR&S~@GnEHmt*l(4D?ovjkV(Yv$|r}$k&Nupwkg|I*>mCrVEV<{DuOb|E(vG literal 0 HcmV?d00001 diff --git a/app/assets/images/test/americat1.jpg b/app/assets/images/test/americat1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f7fb9e53aacc33e804ea8dfdb83c10852c975297 GIT binary patch literal 53103 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 VBMmO*3*rs4l2Q1npC3~*|JliSq9FhP literal 0 HcmV?d00001 diff --git a/app/assets/images/test/americat2.jpg b/app/assets/images/test/americat2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9380cdc6ab4576f8080b85461cf95d1b41a3090d GIT binary patch literal 52710 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 VqKX8^2BzkUDTTu{QAHrR|Jf#y7AOD! literal 0 HcmV?d00001 diff --git a/app/models/order.rb b/app/models/order.rb index a3af5d28c6..f14283c93e 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -1,4 +1,12 @@ class Order < ApplicationRecord # QUESTION: does this cover the entirety of the "must have one or many"?? has_many :order_items + + # QUESTION: add all the billing info like cc? + # QUESTION: date_time_placed --> redundant b/c of created_at time stamp?? + # QUESTION: fulfillment_status --> check to make sure that statuses are one of the allowed ones (ex: pending, completed, paid, etc.) + # QUESTION: clear_cart default value? true vs false? + # QUESTION: total_price --> has to come from the sum of all the order_items via products.... (also needs to be int) + # QUESTION: should not put order_item id down b/c it could then take many (ex: koolaid, nailpolish, etc.) + end diff --git a/app/models/order_item.rb b/app/models/order_item.rb index 01a30ff731..ddd6f80e23 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -4,6 +4,6 @@ class OrderItem < ApplicationRecord validates :quantity, presence: true, numericality: { greater_than: 0, only_integer: true } - # QUESTION: date_time_placed --> redundant b/c of created_at time stamp?? + # QUESTION: add shipment? column: "Link to transition the order item to marked as shipped" end diff --git a/app/models/product.rb b/app/models/product.rb index cc8e50aa9e..e2fab39990 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -9,4 +9,5 @@ class Product < ApplicationRecord validates :price, presence: true, numericality: { greater_than: 0 } # QUESTION: user will likely input a float --> so we will need to change it into an int b/c it'll be stored as an int in the db # QUESTION: how to handle invalid photo URLS since that will 'show up' ?? + # QUESTION: status default --> true?? to show it's automatically active? end diff --git a/test/fixtures/categories.yml b/test/fixtures/categories.yml index dc3ee79b5d..b1bd2b9e25 100644 --- a/test/fixtures/categories.yml +++ b/test/fixtures/categories.yml @@ -4,8 +4,26 @@ # model remove the "{}" from the fixture names and add the columns immediately # below each fixture, per the syntax in the comments below # -one: {} -# column: value -# -two: {} -# column: value +readables: + category_name: readables + +wearables: + category_name: wearables + +cozyables: + category_name: cozyables + +snackables: + category_name: snackables + +hairables: + category_name: hairables + +plantables: + category_name: plantables + +houseables: + category_name: houseables + +beautifiables: + category_name: beautifiables diff --git a/test/fixtures/merchants.yml b/test/fixtures/merchants.yml index 7af41d4fbe..3adb41b549 100644 --- a/test/fixtures/merchants.yml +++ b/test/fixtures/merchants.yml @@ -1,9 +1,31 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -one: - username: MyString - email: MyString +mercury: + username: mercury + email: m@1mercury.com + uid: 12345678 + provider: github -two: - username: MyString - email: MyString +jupiter: + username: jupiter + email: jup@jup.net + uid: 87654321 + provider: github + +neptune: + username: neptune + email: neptune123@nep.com + uid: 23456789 + provider: github + +saturn: + username: saturn + email: 123saturn@sat.org + uid: 98765432 + provider: github + +mars: + username: mars + email: marsmars@mars1.com + uid: 01234567 + provider: github diff --git a/test/fixtures/order_items.yml b/test/fixtures/order_items.yml index adb98ff007..62dc5d9557 100644 --- a/test/fixtures/order_items.yml +++ b/test/fixtures/order_items.yml @@ -1,7 +1,31 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -one: - quantity: +orderitem1: + quantity: 1 + product: poodr + order: order1 -two: - quantity: +orderitem2: + quantity: 1 + product: wig1 + order: order2 + +orderitem3: + quantity: 1 + product: nailpolish1 + order: order2 + +orderitem4: + quantity: 1 + product: koolaid + order: order3 + +orderitem5: + quantity: 1 + product: onesie1 + order: order3 + +orderitem6: + quantity: 1 + product: snuggie + order: order4 diff --git a/test/fixtures/orders.yml b/test/fixtures/orders.yml index a1d4afe6b1..7643b9ff04 100644 --- a/test/fixtures/orders.yml +++ b/test/fixtures/orders.yml @@ -1,17 +1,37 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -one: - payment_status: MyString +order1: + payment_status: pending clear_cart: false - total_price: - date_time_placed: 2018-10-18 10:44:34 - fulfillment_status: MyString - guest_user_info: MyString + total_price: TBD OR DELETE + date_time_placed: 2018-10-18 10:44:34 TBD OR DELETE + fulfillment_status: pending + guest_user_info: TBD + order_item: TBD or DELETE -two: - payment_status: MyString +order2: + payment_status: paid clear_cart: false - total_price: - date_time_placed: 2018-10-18 10:44:34 - fulfillment_status: MyString - guest_user_info: MyString + total_price: TBD OR DELETE + date_time_placed: 2018-10-18 10:44:34 TBD OR DELETE + fulfillment_status: complete + guest_user_info: TBD + order_item: TBD OR DELETE + +order3: + payment_status: pending + clear_cart: false + total_price: TBD OR DELETE + date_time_placed: 2018-10-18 10:44:34 TBD OR DELETE + fulfillment_status: pending + guest_user_info: TBD + order_item: TBD OR DELETE + +order4: + payment_status: pending + clear_cart: false + total_price: TBD OR DELETE + date_time_placed: 2018-10-18 10:44:34 TBD OR DELETE + fulfillment_status: pending + guest_user_info: TBD + order_item: TBD OR DELETE diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml index 83a390e3ea..255f4ae2fb 100644 --- a/test/fixtures/products.yml +++ b/test/fixtures/products.yml @@ -1,17 +1,73 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -one: - name: MyString - price: - description: MyString - photo_url: MyString - status: false - inventory: - -two: - name: MyString - price: - description: MyString - photo_url: MyString - status: false - inventory: +poodr: + name: Poodr + price: 2000 + description: ruby is practical and full of bicycles and flexible + photo_url: /../app/assets/images/test/americat1.jpg + status: true + inventory: 0 + merchant: jupiter + +koolaid: + name: koolaid + price: 100 + description: a sugary drink that breaks through a wall + photo_url: /../app/assets/images/test/americat2.jpg + status: true + inventory: 1 + merchant: mercury + +wig1: + name: wig1 + price: 4444 + description: not just for halloween + photo_url: /../app/assets/images/test/americat1.jpg + status: true + inventory: 100 + merchant: saturn + +houseplant1: + name: houseplant1 + price: 500 + description: seymour + photo_url: /../app/assets/images/test/americat2.jpg + status: true + inventory: 11 + merchant: saturn + +lamp: + name: lamp + price: 6600 + description: let there be light + photo_url: /../app/assets/images/test/americat1.jpg + status: true + inventory: 50 + merchant: mars + +nailpolish1: + name: nailpolish1 + price: 1 + description: fancy toes + photo_url: /../app/assets/images/test/americat2.jpg + status: true + inventory: 2 + merchant: saturn + +onesie1: + name: onesie1 + price: 900 + description: not your grandma's jammies + photo_url: /../app/assets/images/test/americat1.jpg + status: true + inventory: 5 + merchant: mars + +snuggie: + name: snuggie + price: 500 + description: hygge time + photo_url: /../app/assets/images/test/americat2.jpg + status: true + inventory: 44 + merchant: jupiter diff --git a/test/fixtures/reviews.yml b/test/fixtures/reviews.yml index 88814809c0..d040b37025 100644 --- a/test/fixtures/reviews.yml +++ b/test/fixtures/reviews.yml @@ -1,9 +1,26 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -one: - star_rating: - text: MyString +review1: + star_rating: 1 + text: SO BAD. IT BROKE!!!!!! + product: lamp -two: - star_rating: - text: MyString +review2: + star_rating: 2 + text: It wiggles. Not stable. + product: lamp + +review3: + star_rating: 3 + text: Average in every way. + product: snuggie + +review4: + star_rating: 4 + text: Happy with this purchase, but you know -- it's not perfect... + product: nailpolish1 + +review5: + star_rating: 5 + text: SO HAPPY VERY WOW TWO THUMBS WAY UP THXXXXXX + product: poodr From b014af70fd3795870fb4b3943ebfa946d5320a48 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Thu, 18 Oct 2018 22:41:12 -0700 Subject: [PATCH 028/178] added validation to category for presence and uniqueness of category_name --- app/models/category.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/category.rb b/app/models/category.rb index f3218758f1..ab08728787 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -1,3 +1,4 @@ class Category < ApplicationRecord has_and_belongs_to_many :products + validates :category_name, presence: true, uniqueness: true end From bf3c1184b6c2781a7143aa49bcf464aa70b6adf1 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Thu, 18 Oct 2018 23:50:40 -0700 Subject: [PATCH 029/178] added feature to allow merchants to view+edit their products from account page --- Gemfile | 2 +- app/controllers/merchants_controller.rb | 1 + app/controllers/products_controller.rb | 32 +++++++++++- .../_merchant_account_products.html.erb | 52 +++++++++++++++++++ app/views/merchants/show.html.erb | 4 ++ app/views/products/edit.html.erb | 2 + config/routes.rb | 2 + 7 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 app/views/layouts/_merchant_account_products.html.erb diff --git a/Gemfile b/Gemfile index 0d8c4817c6..2a5bfac0f2 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.5.1' -# ruby '2.4.1' +ruby '2.4.1' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.2.1' diff --git a/app/controllers/merchants_controller.rb b/app/controllers/merchants_controller.rb index 931c3c6fc5..2d7190de20 100644 --- a/app/controllers/merchants_controller.rb +++ b/app/controllers/merchants_controller.rb @@ -8,4 +8,5 @@ def show render_404 unless @merchant end + end diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index c4dd1795d3..c26dca7472 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -27,11 +27,39 @@ def create def show; end - def edit; - + def edit + @product = Product.new() end def update + if !@login_user + flash[:status] = :failure + flash[:result_text] = "You must log in to update #{@product}" + flash[:messages] = @product.errors.messages + redirect_back fallback_location: root_path + end + + if !find_product + flash[:status] = :failure + flash[:result_text] = "Unable to find #{@product}" + flash[:messages] = @product.errors.messages + redirect_back fallback_location: root_path + end + + @product = find_product() + result = @product.update(product_params) + + if result + flash[:status] = :success + flash[:result_text] = "Successfully updated + #{@product}" + redirect_to product_path(@product) + else + flash[:status] = :failure + flash[:result_text] = "Could not update #{@product}" + flash[:messages] = @product.errors.messages + render :edit, status: :bad_request + end end private diff --git a/app/views/layouts/_merchant_account_products.html.erb b/app/views/layouts/_merchant_account_products.html.erb new file mode 100644 index 0000000000..c1c7677c7d --- /dev/null +++ b/app/views/layouts/_merchant_account_products.html.erb @@ -0,0 +1,52 @@ +

    Products

    + + + + + + + + + + + + +<% @merchant.products.each do |product| %> + + + + + + + + + + <% end %> + +
    + Item Name + + Inventory + + Price + + Status + + + + +
    + <%= link_to product.name, product_path(product) %> + + <%= product.inventory %> + + + <%= product.price %> + + + <%= product.status %> + + <%= link_to "Edit", edit_product_path(product) %> + + Delete/Discontinue??? +
    diff --git a/app/views/merchants/show.html.erb b/app/views/merchants/show.html.erb index d72b7a3c92..d81b90a08d 100644 --- a/app/views/merchants/show.html.erb +++ b/app/views/merchants/show.html.erb @@ -1 +1,5 @@

    Merchant Show Page!

    + +Add Product link + +<%= render partial: "/layouts/merchant_account_products" %> diff --git a/app/views/products/edit.html.erb b/app/views/products/edit.html.erb index 26f71df1be..9173eb7b15 100644 --- a/app/views/products/edit.html.erb +++ b/app/views/products/edit.html.erb @@ -1 +1,3 @@

    Product Edit Page

    + +<%= render partial: "form", locals: { action_type: "Update Product" } %> diff --git a/config/routes.rb b/config/routes.rb index 3d817c18c9..61b1dcff26 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,8 +1,10 @@ Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html root 'welcome#index' + get "/auth/:provider/callback", to: "sessions#create" delete "/logout", to: "sessions#destroy", as: "logout" + resources :categories resources :merchants resources :orders From cd4e4451325c7ae97630ec733e4ca17e60860e1d Mon Sep 17 00:00:00 2001 From: kangazoom Date: Thu, 18 Oct 2018 23:56:19 -0700 Subject: [PATCH 030/178] added idea for nested routes to help display products to guests --- config/routes.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 61b1dcff26..7d7c810903 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,11 +4,12 @@ get "/auth/:provider/callback", to: "sessions#create" delete "/logout", to: "sessions#destroy", as: "logout" - + resources :categories resources :merchants resources :orders resources :products + # NOTE: nested route ideas: merchants/id#/products and categories/id#/products ??? end From 0341b3beae1a3d84bf3e16753d90f763b2614ef0 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Thu, 18 Oct 2018 23:58:14 -0700 Subject: [PATCH 031/178] added question about authorizations for merchant account page --- app/controllers/merchants_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/merchants_controller.rb b/app/controllers/merchants_controller.rb index 2d7190de20..44fe745620 100644 --- a/app/controllers/merchants_controller.rb +++ b/app/controllers/merchants_controller.rb @@ -4,6 +4,7 @@ def index end def show + # QUESTION: add if/else so only login_user can view merchant account page?? @merchant = Merchant.find_by(id: params[:id]) render_404 unless @merchant end From b1f5ec57c60f02fa0d71886951c91d961e476a18 Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Fri, 19 Oct 2018 10:09:58 -0700 Subject: [PATCH 032/178] Add link to products, homepage --- app/views/welcome/index.html.erb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/views/welcome/index.html.erb b/app/views/welcome/index.html.erb index f36016e70b..b108f0c840 100644 --- a/app/views/welcome/index.html.erb +++ b/app/views/welcome/index.html.erb @@ -1 +1,4 @@

    Redsy Homepage!

    +
    <%= link_to "All Products", products_path %> + +
    From fc2f9e167ba7c80039fd6d9ffdbded1cfbe0fbb6 Mon Sep 17 00:00:00 2001 From: Layla Date: Fri, 19 Oct 2018 15:11:17 -0700 Subject: [PATCH 033/178] began wireframe process --- app/views/layouts/application.html.erb | 77 +++++++++++++++------- test/controllers/orders_controller_test.rb | 43 ++++++++++++ 2 files changed, 96 insertions(+), 24 deletions(-) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 3b35998419..82da5b9979 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,46 +1,75 @@ - - Betsy - <%= csrf_meta_tags %> - <%= csp_meta_tag %> + + Betsy + <%= csrf_meta_tags %> + <%= csp_meta_tag %> - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> - <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> - + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + - <% if flash[:result_text] or flash[:messages] %>
    <%= flash[:status].to_s == "failure" ? "A problem occurred: " : "" %><%= flash[:result_text] %> <% if flash[:messages] %> -
      - <% flash[:messages].each do |name, problems| %> - <% problems.each do |problem| %> -
    • <%= name %>: <%= problem %>
    • - <% end %> - <% end %> -
    +
      + <% flash[:messages].each do |name, problems| %> + <% problems.each do |problem| %> +
    • <%= name %>: <%= problem %>
    • + <% end %> + <% end %> +
    <% end %>
    - <% end %> -
    - <%= yield %> -
    + <% end %> + +
    + <%= yield %> +
    +
    +

    + ~~~~~~~~~~~
    + This will be the footer area
    + <%= link_to "Homepage", root_path %> | + <%= link_to "Find An Order", root_path %> | + <%= link_to "About Us", root_path %>

    +

    Copyright Red Jaguars 2018

    +

    +
    diff --git a/test/controllers/orders_controller_test.rb b/test/controllers/orders_controller_test.rb index a105e3f85e..7e0c244a4a 100644 --- a/test/controllers/orders_controller_test.rb +++ b/test/controllers/orders_controller_test.rb @@ -5,4 +5,47 @@ # it "must be a real test" do # flunk "Need real tests" # end + it "can create a product" do + product_hash = { + product: { + name: 'Practical Object Oriented Programming in Ruby', + price: 20, + description: 'A look at how to design object-oriented systems', + photo_url: 'test url', + status: true, + inventory: 1 + } + } + + expect { + post products_path, params: product_hash + }.must_change 'Product.count', 1 + + must_respond_with :redirect + + expect(Product.last.name).must_equal product_hash[:product][:name] + expect(Product.last.price).must_equal product_hash[:product][:price] + expect(Product.last.description).must_equal product_hash[:product][:description] + end + + + it "will not create a product with invalid params" do + product_hash = { + product: { + name: 'Practical Object Oriented Programming in Ruby', + price: 20, + description: 'A look at how to design object-oriented systems', + photo_url: 'test url', + status: "test string not boolean", + inventory: 1 + } + } + + expect { + post products_path, params: product_hash + }.wont_change 'Product.count' + + must_respond_with :bad_request + end + end From e27c569cbb84c675371d58b4219e760dc8215cea Mon Sep 17 00:00:00 2001 From: Layla Date: Fri, 19 Oct 2018 15:19:15 -0700 Subject: [PATCH 034/178] adding button for when youre logged in --- app/views/layouts/application.html.erb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 82da5b9979..aeeb3e7e08 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -27,8 +27,14 @@ + +
  • <% if session[:username] %> +
  • + From 139d7443b284ba3ec95ad3be01b2aff8ad4cb101 Mon Sep 17 00:00:00 2001 From: Layla Date: Fri, 19 Oct 2018 15:25:43 -0700 Subject: [PATCH 035/178] updated link names in application --- app/views/layouts/application.html.erb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index aeeb3e7e08..101f8c19ed 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -19,10 +19,13 @@
    Seller: - <%= link_to @product.merchant.username, merchant_path %> + <%= link_to @product.merchant.username, merchant_path(@product.merchant.id) %>
    From dcca7594665bafc891ea5926479a96ebe289220b Mon Sep 17 00:00:00 2001 From: Layla Date: Fri, 19 Oct 2018 15:44:01 -0700 Subject: [PATCH 040/178] updated logout message --- app/controllers/sessions_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 26b8d40470..b391c46706 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -42,7 +42,7 @@ def create def destroy session[:username] = nil - flash[:success] = "Successfully logged out!" + flash[:result_text] = "Successfully logged out!" redirect_to root_path end From bb168e71ada8b7cb763deb6bafd21823ac8bc5ec Mon Sep 17 00:00:00 2001 From: kangazoom Date: Fri, 19 Oct 2018 15:47:52 -0700 Subject: [PATCH 041/178] updated/fixed fixtures, expecially for orders --- test/fixtures/order_items.yml | 2 +- test/fixtures/orders.yml | 45 ++++++++++++++++++++++------------- test/fixtures/products.yml | 6 ++--- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/test/fixtures/order_items.yml b/test/fixtures/order_items.yml index 62dc5d9557..c254b4553b 100644 --- a/test/fixtures/order_items.yml +++ b/test/fixtures/order_items.yml @@ -6,7 +6,7 @@ orderitem1: order: order1 orderitem2: - quantity: 1 + quantity: 2 product: wig1 order: order2 diff --git a/test/fixtures/orders.yml b/test/fixtures/orders.yml index 7643b9ff04..dae102c051 100644 --- a/test/fixtures/orders.yml +++ b/test/fixtures/orders.yml @@ -3,35 +3,48 @@ order1: payment_status: pending clear_cart: false - total_price: TBD OR DELETE - date_time_placed: 2018-10-18 10:44:34 TBD OR DELETE fulfillment_status: pending - guest_user_info: TBD - order_item: TBD or DELETE + guest_email: person@person.com + guest_mailing: 123 somewhere st. + guest_cc_name: Person A. Person + guest_cc_num: 1234567890123456 + guest_cc_exp_date: 01/2020 + guest_cc_cvv_code: 123 + guest_cc_zip: 07747 order2: payment_status: paid clear_cart: false - total_price: TBD OR DELETE - date_time_placed: 2018-10-18 10:44:34 TBD OR DELETE fulfillment_status: complete - guest_user_info: TBD - order_item: TBD OR DELETE + guest_email: me@wnbc.net + guest_mailing: 321 somewhere sq. + guest_cc_name: Person Moo + guest_cc_num: 0987654321098765 + guest_cc_exp_date: 02/2021 + guest_cc_cvv_code: 321 + guest_cc_zip: 11238 order3: payment_status: pending clear_cart: false - total_price: TBD OR DELETE - date_time_placed: 2018-10-18 10:44:34 TBD OR DELETE fulfillment_status: pending - guest_user_info: TBD - order_item: TBD OR DELETE + guest_email: herc@hercules.org + guest_mailing: 1 highline st. + guest_cc_name: Hercules Hercules + guest_cc_num: 1234567890123456 + guest_cc_exp_date: 11/2020 + guest_cc_cvv_code: 999 + guest_cc_zip: 98121 + order4: payment_status: pending clear_cart: false - total_price: TBD OR DELETE - date_time_placed: 2018-10-18 10:44:34 TBD OR DELETE fulfillment_status: pending - guest_user_info: TBD - order_item: TBD OR DELETE + guest_email: person@person.com + guest_mailing: 123 somewhere st. + guest_cc_name: Person A. Person + guest_cc_num: 1234567890123456 + guest_cc_exp_date: 01/2020 + guest_cc_cvv_code: 123 + guest_cc_zip: 07747 diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml index 255f4ae2fb..c3930e5cb7 100644 --- a/test/fixtures/products.yml +++ b/test/fixtures/products.yml @@ -6,7 +6,7 @@ poodr: description: ruby is practical and full of bicycles and flexible photo_url: /../app/assets/images/test/americat1.jpg status: true - inventory: 0 + inventory: 1 merchant: jupiter koolaid: @@ -15,7 +15,7 @@ koolaid: description: a sugary drink that breaks through a wall photo_url: /../app/assets/images/test/americat2.jpg status: true - inventory: 1 + inventory: 100 merchant: mercury wig1: @@ -33,7 +33,7 @@ houseplant1: description: seymour photo_url: /../app/assets/images/test/americat2.jpg status: true - inventory: 11 + inventory: 0 merchant: saturn lamp: From 840e2f4e15c95302649e5472514e3311dd89be8b Mon Sep 17 00:00:00 2001 From: Layla Date: Fri, 19 Oct 2018 16:20:53 -0700 Subject: [PATCH 042/178] added dropdown menu function to shop by merchant --- app/assets/stylesheets/application.scss | 22 ++++++++++++++++++++++ app/views/layouts/application.html.erb | 14 +++++++++++--- app/views/merchants/index.html.erb | 2 ++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 8b1701e581..437ee42494 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -16,3 +16,25 @@ @import "bootstrap"; /* Import scss content */ @import "**/*"; + +.dropdown { + position: relative; + display: inline-block; +} + +.dropdown-content { + display: none; + position: absolute; + background-color: #FFE5E7; + min-width: 130px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + padding: 12px 36px; + z-index: 1; + font-weight: bold; + white-space: nowrap; + +} + +.dropdown:hover .dropdown-content { + display: block; +} diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 101f8c19ed..6eb3ab3be1 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -21,9 +21,17 @@ - + + + + diff --git a/app/views/merchants/index.html.erb b/app/views/merchants/index.html.erb index 98a653d419..a2bced8503 100644 --- a/app/views/merchants/index.html.erb +++ b/app/views/merchants/index.html.erb @@ -1 +1,3 @@

    Merchant Index Page!

    + +This will display all of the merchants.
    From 911bc07e6267cbae47d2c42632fc95d16a7c81d8 Mon Sep 17 00:00:00 2001 From: Layla Date: Fri, 19 Oct 2018 16:23:24 -0700 Subject: [PATCH 043/178] uncommented some code --- app/controllers/application_controller.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2d12f69ab8..cdb52a0134 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,12 +1,12 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception - # before_action :find_merchant - # - # def render_404 - # # DPR: this will actually render a 404 page in production - # raise ActionController::RoutingError.new('Not Found') - # end + before_action :find_merchant + + def render_404 + # DPR: this will actually render a 404 page in production + raise ActionController::RoutingError.new('Not Found') + end private def find_merchant From 85d87ae5598b753a762a74e712ef91f0e983099c Mon Sep 17 00:00:00 2001 From: Layla Date: Fri, 19 Oct 2018 16:26:45 -0700 Subject: [PATCH 044/178] updated path link for dropdown under merchant --- app/views/layouts/application.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 6eb3ab3be1..6213ca500c 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -25,7 +25,7 @@ <%= link_to "Shop By Merchants", merchants_path, class: "nav-link" %>
    From fa5ecbbc7965cc1ab03b5d3276ce59bd53034b06 Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Fri, 19 Oct 2018 16:28:12 -0700 Subject: [PATCH 045/178] fixed create product --- app/controllers/products_controller.rb | 27 ++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index c26dca7472..f7f9359d97 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -10,18 +10,28 @@ def new end def create - @product = Product.new(product_params) - if @product.save - flash[:status] = :success - flash[:result_text] = "Successfully created - #{@product}" - redirect_to product_path(@product) - else + if @login_user + # merchant_id = @login_user.id + # @login_user = @merchant.product.id + @product = Product.new(product_params) + @product.merchant_id = @login_user.id + if @product.save + flash[:status] = :success + flash[:result_text] = "Successfully created + #{@product}" + redirect_to product_path(@product) + # debugger + else + render :new + end + end + if !@login_user flash[:status] = :failure flash[:result_text] = "Could not create #{@product}" flash[:messages] = @product.errors.messages - render :new, status: :bad_request + redirect_to root_path end + # debugger end def show; @@ -66,6 +76,7 @@ def update def product_params params.require(:product).permit(:name, :price, :description, :photo_url, :status, :inventory) + end def find_product From a059aaed1025d2a3a76b3ca2c423407bea1342d9 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Sat, 20 Oct 2018 00:06:08 -0700 Subject: [PATCH 046/178] added validations and relations tests for merchant --- Gemfile | 2 +- test/models/merchant_test.rb | 60 ++++++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 0d8c4817c6..2a5bfac0f2 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.5.1' -# ruby '2.4.1' +ruby '2.4.1' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.2.1' diff --git a/test/models/merchant_test.rb b/test/models/merchant_test.rb index ad61facb07..77e0fa041e 100644 --- a/test/models/merchant_test.rb +++ b/test/models/merchant_test.rb @@ -2,9 +2,63 @@ describe Merchant do let(:merchant) { Merchant.new } + let(:mercury) { merchants(:mercury) } - it "must be valid" do - skip - value(merchant).must_be :valid? + describe 'relations' do + it 'has a list of products' do + expect(mercury).must_respond_to :products + + mercury.products.each do |product| + expect(product).must_be_kind_of Product + end + end + end + + describe 'validations' do + it "must be valid" do + value(mercury).must_be :valid? + end + + it 'requires a username present' do + mercury.username = nil + + expect(mercury.valid?).must_equal false + expect(mercury.errors.messages).must_include :username + + mercury.username = "merch" + expect(mercury.valid?).must_equal true + end + + it 'requires a unique username' do + mars = merchants(:mars) + saturn = merchants(:saturn) + saturn.username = "mars" + + expect(saturn.valid?).must_equal false + expect(saturn.errors.messages).must_include :username + end + + it 'requires an email address present' do + mercury.email = nil + + expect(mercury.valid?).must_equal false + expect(mercury.errors.messages).must_include :email + end + + it 'requires a unique email address' do + mars = merchants(:mars) + saturn = merchants(:saturn) + mars.email = "hello@hello.com" + saturn.email = "hello@hello.com" + mars.save + saturn.save + + expect(saturn.valid?).must_equal false + expect(saturn.errors.messages).must_include :email + end + end + + describe 'model logic' do + # TODO: add tests for business logic below end end From aceb64c1f6da97d54d6684b3fa927d2cace43975 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Sat, 20 Oct 2018 00:12:57 -0700 Subject: [PATCH 047/178] added a few more tests for when true in merchant model testing --- test/models/merchant_test.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/test/models/merchant_test.rb b/test/models/merchant_test.rb index 77e0fa041e..c7cc59832e 100644 --- a/test/models/merchant_test.rb +++ b/test/models/merchant_test.rb @@ -20,13 +20,12 @@ end it 'requires a username present' do + expect(mercury.valid?).must_equal true + mercury.username = nil expect(mercury.valid?).must_equal false expect(mercury.errors.messages).must_include :username - - mercury.username = "merch" - expect(mercury.valid?).must_equal true end it 'requires a unique username' do @@ -36,9 +35,14 @@ expect(saturn.valid?).must_equal false expect(saturn.errors.messages).must_include :username + + saturn.username = "saturn" + expect(saturn.valid?).must_equal true end it 'requires an email address present' do + expect(mercury.valid?).must_equal true + mercury.email = nil expect(mercury.valid?).must_equal false @@ -49,12 +53,15 @@ mars = merchants(:mars) saturn = merchants(:saturn) mars.email = "hello@hello.com" - saturn.email = "hello@hello.com" mars.save + saturn.email = "hello@hello.com" saturn.save expect(saturn.valid?).must_equal false expect(saturn.errors.messages).must_include :email + + saturn.email = "unique@email.org" + expect(saturn.valid?).must_equal true end end From 4d3f213ef0cae599485849a6a833005bb08e85ec Mon Sep 17 00:00:00 2001 From: kangazoom Date: Sat, 20 Oct 2018 00:18:13 -0700 Subject: [PATCH 048/178] minor change in model email uniqueness validation test --- test/models/merchant_test.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/models/merchant_test.rb b/test/models/merchant_test.rb index c7cc59832e..363e452d1e 100644 --- a/test/models/merchant_test.rb +++ b/test/models/merchant_test.rb @@ -52,10 +52,8 @@ it 'requires a unique email address' do mars = merchants(:mars) saturn = merchants(:saturn) - mars.email = "hello@hello.com" - mars.save - saturn.email = "hello@hello.com" - saturn.save + + saturn.email = mars.email expect(saturn.valid?).must_equal false expect(saturn.errors.messages).must_include :email From aba9097b3694e425735a9bf368366758294317ef Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Sat, 20 Oct 2018 13:13:28 -0700 Subject: [PATCH 049/178] seed db --- app/models/order.rb | 12 ++++++++++++ db/seeds.rb | 17 +++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/app/models/order.rb b/app/models/order.rb index f14283c93e..afe39f6bac 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -9,4 +9,16 @@ class Order < ApplicationRecord # QUESTION: total_price --> has to come from the sum of all the order_items via products.... (also needs to be int) # QUESTION: should not put order_item id down b/c it could then take many (ex: koolaid, nailpolish, etc.) + def subtotal + order_items.collect { |oi| oi.valid? ? (oi.quantity * oi.unit_price) : 0 }.sum + end + private + def set_order_status + self.order_status_id = 1 + end + + def update_subtotal + self[:subtotal] = subtotal + end + end diff --git a/db/seeds.rb b/db/seeds.rb index 1beea2accd..683ea901a4 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -5,3 +5,20 @@ # # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) # Character.create(name: 'Luke', movie: movies.first) + +Product.delete_all +Product.create! id: 10, name: "Cherry", price: 22.49, status: true, merchant_id: 1 +Product.create! id: 11, name: "Apple", price: 0.29, status: true, merchant_id: 1 +Product.create! id: 12, name: "Strawberries", price: 1.99, status: true, merchant_id: 1 + +Order.delete_all +Order.create! id: 1, fulfillment_status: "In Progress" +Order.create! id: 2, fulfillment_status: "Placed" +Order.create! id: 3, fulfillment_status: "Shipped" +Order.create! id: 4, fulfillment_status: "Cancelled" + +# Merchant.delete_all +Merchant.create! id: 20, username: "Small Fry", email: "fry@gmail.com", uid: 33, provider: "github" +Merchant.create! id: 21, username: "Momma", email: "momma@gmail.com", uid: 44, provider: "github" +Merchant.create! id: 22, username: "Big Papa", email: "big_papa@gmail.com", uid: 55, provider: "github" +Merchant.create! id: 23, username: "Disco", email: "disco@gmail.com", uid: 66, provider: "github" From 22c3b0ea67c9ee3d1041a65bca0f2166bda20dd2 Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Sat, 20 Oct 2018 13:29:38 -0700 Subject: [PATCH 050/178] add actions to order and order_item --- app/models/order.rb | 8 ++++++-- app/models/order_item.rb | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/app/models/order.rb b/app/models/order.rb index afe39f6bac..79b8644fe2 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -10,10 +10,12 @@ class Order < ApplicationRecord # QUESTION: should not put order_item id down b/c it could then take many (ex: koolaid, nailpolish, etc.) def subtotal - order_items.collect { |oi| oi.valid? ? (oi.quantity * oi.unit_price) : 0 }.sum + order_items.collect { |oi| oi.valid? ? (oi.quantity * oi.price) : 0 }.sum + + #products.collect { |p| p.valid? ? (p.price * )} end private - def set_order_status + def set_fullfillment_status self.order_status_id = 1 end @@ -22,3 +24,5 @@ def update_subtotal end end + +# The set_order_status gets fired when the order is created and sets the fullfillment_status_id column to 1 (in progress). The update_subtotal function is called during save and sums up our order item's total cost and stores it in the subtotal field. The subtotal function actually overrides the field named subtotal, so calling order.subtotal calls the function. You can still access the field internally by calling self[:subtotal], so we use that to update the field. This allows us to dynamically update subtotal as needed. A side effect of this is user can't write any arbitrary value to the subtotal field. diff --git a/app/models/order_item.rb b/app/models/order_item.rb index ddd6f80e23..2da7b5eb75 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -4,6 +4,42 @@ class OrderItem < ApplicationRecord validates :quantity, presence: true, numericality: { greater_than: 0, only_integer: true } + validate :product_present + validate :order_present + + before_save :finalize # QUESTION: add shipment? column: "Link to transition the order item to marked as shipped" +# take the associated product's price if the order item is not currently persisted. If the order item is persisted then the contents of the unit_price field will be returned instead. + + def unit_price + if persisted? + self[:unit_price] + else + product.price + end + end + + def total_price + unit_price * quantity + end + + private + def product_present + if product.nil? + errors.add(:product, "is not valid or is not active.") + end + end + + def order_present + if order.nil? + errors.add(:order, "is not a valid order.") + end + end + + def finalize + self[:unit_price] = unit_price + self[:total_price] = quantity * self[:unit_price] + end + end From 35e4dc25ab05302dec0da00a0ba41dc95cc35e9f Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Sat, 20 Oct 2018 14:18:27 -0700 Subject: [PATCH 051/178] add order items controller --- app/assets/javascripts/order_items.js | 2 ++ app/assets/stylesheets/order_items.scss | 3 +++ app/controllers/application_controller.rb | 11 ++++++++- app/controllers/order_items_controller.rb | 23 +++++++++++++++++++ app/controllers/orders_controller.rb | 1 + app/helpers/order_items_helper.rb | 2 ++ app/models/order.rb | 11 ++++++--- app/models/order_item.rb | 2 +- app/models/product.rb | 5 ++++ .../order_items_controller_test.rb | 7 ++++++ 10 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 app/assets/javascripts/order_items.js create mode 100644 app/assets/stylesheets/order_items.scss create mode 100644 app/controllers/order_items_controller.rb create mode 100644 app/helpers/order_items_helper.rb create mode 100644 test/controllers/order_items_controller_test.rb diff --git a/app/assets/javascripts/order_items.js b/app/assets/javascripts/order_items.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/order_items.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/stylesheets/order_items.scss b/app/assets/stylesheets/order_items.scss new file mode 100644 index 0000000000..c02e2068ea --- /dev/null +++ b/app/assets/stylesheets/order_items.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the order_items controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index cdb52a0134..dc66500391 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -8,7 +8,16 @@ def render_404 raise ActionController::RoutingError.new('Not Found') end -private + # return the current order or create a new order if none exists + def current_order + if !session[:order_id].nil? + Order.find(session[:order_id]) + else + Order.new + end + end + + private def find_merchant if session[:username] @login_user = Merchant.find_by(id: session[:username]) diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb new file mode 100644 index 0000000000..ea50f0ae91 --- /dev/null +++ b/app/controllers/order_items_controller.rb @@ -0,0 +1,23 @@ +class OrderItemsController < ApplicationController + + def create + @order = current_order + @order_item = @order.order_items.new(order_item_params) + @order.save + session[:order_id] = @order.id + end + + def update + @order = current_order + @order_item = @order.order_items.find(params[:id]) + @order_item.update_attributes(order_item_params) + @order_items = @order.order_items + end + + def destroy + @order = current_order + @order_item = @order.order_items.find(params[:id]) + @order_item.destroy + @order_items = @order.order_items + end +end diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index fe1d0b0806..9113b19a99 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -13,6 +13,7 @@ def show if @order.nil? head :not_found end + else @order_items = current_order.order_items end def create diff --git a/app/helpers/order_items_helper.rb b/app/helpers/order_items_helper.rb new file mode 100644 index 0000000000..e197528ae1 --- /dev/null +++ b/app/helpers/order_items_helper.rb @@ -0,0 +1,2 @@ +module OrderItemsHelper +end diff --git a/app/models/order.rb b/app/models/order.rb index 79b8644fe2..b19c63c80c 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -9,11 +9,16 @@ class Order < ApplicationRecord # QUESTION: total_price --> has to come from the sum of all the order_items via products.... (also needs to be int) # QUESTION: should not put order_item id down b/c it could then take many (ex: koolaid, nailpolish, etc.) - def subtotal - order_items.collect { |oi| oi.valid? ? (oi.quantity * oi.price) : 0 }.sum + #def subtotal + #order_items.collect each do |oi| + #oi.valid? ? (oi.quantity * oi.price) + + #return total_amout.sum * + #need a price? + #add price to order_items?? or add a method to #products.collect { |p| p.valid? ? (p.price * )} - end + #end private def set_fullfillment_status self.order_status_id = 1 diff --git a/app/models/order_item.rb b/app/models/order_item.rb index 2da7b5eb75..735be620ef 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -10,7 +10,7 @@ class OrderItem < ApplicationRecord before_save :finalize # QUESTION: add shipment? column: "Link to transition the order item to marked as shipped" -# take the associated product's price if the order item is not currently persisted. If the order item is persisted then the contents of the unit_price field will be returned instead. +# take the associated product's price if the order item is not currently persisted. If the order item is persisted (Returns true if the record is persisted, i.e. it’s not a new record and it was not destroyed, otherwise returns false.)then the contents of the unit_price field will be returned instead. def unit_price if persisted? diff --git a/app/models/product.rb b/app/models/product.rb index e2fab39990..c44f311096 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -7,6 +7,11 @@ class Product < ApplicationRecord validates :name, presence: true, uniqueness: true # TODO: maybe product name must be unique to category??? validates :price, presence: true, numericality: { greater_than: 0 } + + # Deleted/inactive products aren't shown + default_scope { where(status: true) } + + # QUESTION: user will likely input a float --> so we will need to change it into an int b/c it'll be stored as an int in the db # QUESTION: how to handle invalid photo URLS since that will 'show up' ?? # QUESTION: status default --> true?? to show it's automatically active? diff --git a/test/controllers/order_items_controller_test.rb b/test/controllers/order_items_controller_test.rb new file mode 100644 index 0000000000..96e3063c9c --- /dev/null +++ b/test/controllers/order_items_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +describe OrderItemsController do + # it "must be a real test" do + # flunk "Need real tests" + # end +end From 8add78d6b27842ce6951d9bc65f66c43f7af74db Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Sat, 20 Oct 2018 14:26:02 -0700 Subject: [PATCH 052/178] add cart to nav --- app/controllers/products_controller.rb | 1 + app/views/layouts/application.html.erb | 5 +++++ app/views/products/index.html.erb | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index 936c874c59..5a2944f943 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -7,6 +7,7 @@ def index def new @product = Product.new + @order_item = current_order.order_items.new end def create diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 6213ca500c..9aebf1b9b4 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -27,6 +27,11 @@ <% Merchant.all.each do |merchant| %>
  • <%= link_to merchant.username, merchant_path(merchant.id) %>
  • <% end %> +
    + <%= link_to "Cart", orders_path, class: "nav-link" %> + + +
    diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb index c44229b343..d40829dbfd 100644 --- a/app/views/products/index.html.erb +++ b/app/views/products/index.html.erb @@ -8,7 +8,7 @@ <%= p.price.to_f.round(2) %>
    - <%= link_to (image_tag p.photo_url), product_path(p.id), class: "btn btn-outline-secondary" %> +
    <% end %> From 4c9de3a6eca17a5a3cb67499abd414253241a20a Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Sat, 20 Oct 2018 15:51:11 -0700 Subject: [PATCH 053/178] add quantity to product view --- app/models/order.rb | 19 ++++++++----------- app/models/order_item.rb | 30 ++++++++++++++++++++---------- app/views/products/index.html.erb | 6 +++++- app/views/products/show.html.erb | 15 ++++++++++----- 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/app/models/order.rb b/app/models/order.rb index b19c63c80c..97f9f8f4c7 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -9,24 +9,21 @@ class Order < ApplicationRecord # QUESTION: total_price --> has to come from the sum of all the order_items via products.... (also needs to be int) # QUESTION: should not put order_item id down b/c it could then take many (ex: koolaid, nailpolish, etc.) - #def subtotal - #order_items.collect each do |oi| + # + # def total_price + # total = 0 + # self. + # end - #oi.valid? ? (oi.quantity * oi.price) - #return total_amout.sum * - #need a price? - #add price to order_items?? or add a method to - #products.collect { |p| p.valid? ? (p.price * )} - #end private def set_fullfillment_status self.order_status_id = 1 end - def update_subtotal - self[:subtotal] = subtotal - end + # def update_subtotal + # self[:subtotal] = subtotal + # end end diff --git a/app/models/order_item.rb b/app/models/order_item.rb index 735be620ef..aab000ae3b 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -12,17 +12,27 @@ class OrderItem < ApplicationRecord # take the associated product's price if the order item is not currently persisted. If the order item is persisted (Returns true if the record is persisted, i.e. it’s not a new record and it was not destroyed, otherwise returns false.)then the contents of the unit_price field will be returned instead. - def unit_price - if persisted? - self[:unit_price] - else - product.price - end - end + # def unit_price + # if persisted? + # self[:unit_price] + # else + # product.price + # end + # end + + # def total_price + # unit_price * quantity + # end + + # def subtotal + # subtotal = 0 + # self.products.each do |p| + # subtotal += p.price + # end + # return subtotal + # end + - def total_price - unit_price * quantity - end private def product_present diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb index d40829dbfd..3acc40bdc6 100644 --- a/app/views/products/index.html.erb +++ b/app/views/products/index.html.erb @@ -8,7 +8,11 @@ <%= p.price.to_f.round(2) %>
    - +
    + +
    Add to Item to Cart + <%= %> +
    <% end %> diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb index c6351ebe18..b2a12b900a 100644 --- a/app/views/products/show.html.erb +++ b/app/views/products/show.html.erb @@ -2,9 +2,7 @@

    <%= @product.name %>

    -
    - <%= image_tag @product.photo_url %> -
    +
    Price:$ <%= @product.price %>
    @@ -30,8 +28,15 @@ <%= %> -
    Add to Cart - <%= %> +
    Add to Item to Cart + + <%= form_with model: @product do |f|%> + + <%= f.label :quantity%> + <%= f.select :quantity, (1..10) %> + <%= f.submit "Submit Quantity" %> + <% end %> +
    Inventory: From 7645739d91c284aae566b0923f1d7b386c6f3618 Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Sat, 20 Oct 2018 16:59:30 -0700 Subject: [PATCH 054/178] added nested routes and categories --- app/assets/javascripts/categories.js | 2 + app/assets/stylesheets/categories.scss | 3 ++ app/controllers/categories_controller.rb | 38 +++++++++++++++++++ app/helpers/categories_helper.rb | 2 + app/views/categories/_form.html.erb | 15 ++++++++ app/views/categories/new.html.erb | 2 + config/routes.rb | 11 +++++- .../controllers/categories_controller_test.rb | 7 ++++ 8 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/categories.js create mode 100644 app/assets/stylesheets/categories.scss create mode 100644 app/controllers/categories_controller.rb create mode 100644 app/helpers/categories_helper.rb create mode 100644 app/views/categories/_form.html.erb create mode 100644 app/views/categories/new.html.erb create mode 100644 test/controllers/categories_controller_test.rb diff --git a/app/assets/javascripts/categories.js b/app/assets/javascripts/categories.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/categories.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/stylesheets/categories.scss b/app/assets/stylesheets/categories.scss new file mode 100644 index 0000000000..42976cbc11 --- /dev/null +++ b/app/assets/stylesheets/categories.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Categories controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb new file mode 100644 index 0000000000..259e23fc54 --- /dev/null +++ b/app/controllers/categories_controller.rb @@ -0,0 +1,38 @@ +class CategoriesController < ApplicationController + def index + @categories = Category.all + end + + def new + if @login_user.nil? + render_404 + else + @category = Category.new + end + end + + def create + if @login_user.nil? + render_404 + else + @category = Category.new(category_params) + if @category.save + flash[:status] = :success + flash[:result_text] = "Successfully created + #{@category.category_name}" + redirect_to root_path + else + flash[:status] = :failure + flash[:result_text] = "Could not create #{@category}" + flash[:messages] = @category.errors.messages + render :new, status: :bad_request + end + end + end + + private + + def category_params + params.require(:category).permit(:category_name) + end +end diff --git a/app/helpers/categories_helper.rb b/app/helpers/categories_helper.rb new file mode 100644 index 0000000000..e06f31554c --- /dev/null +++ b/app/helpers/categories_helper.rb @@ -0,0 +1,2 @@ +module CategoriesHelper +end diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb new file mode 100644 index 0000000000..917b4a3bf5 --- /dev/null +++ b/app/views/categories/_form.html.erb @@ -0,0 +1,15 @@ +
    + <%= form_with model: @category do |f| %> + + +
    + <%= f.label :category_name %> + <%= f.text_field :category_name, class: "form-control" %> +
    + + +
    + <%= f.submit class: "btn btn-primary" %> +
    + <% end %> +
    diff --git a/app/views/categories/new.html.erb b/app/views/categories/new.html.erb new file mode 100644 index 0000000000..3ba7df7cd8 --- /dev/null +++ b/app/views/categories/new.html.erb @@ -0,0 +1,2 @@ +

    Add a New Category

    +<%= render partial: "form" %> diff --git a/config/routes.rb b/config/routes.rb index 7d7c810903..abec79adda 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,11 +5,18 @@ get "/auth/:provider/callback", to: "sessions#create" delete "/logout", to: "sessions#destroy", as: "logout" - resources :categories - resources :merchants + resources :categories do + resources :products, include: [:index, :show, :create] + end + + resources :merchants do + resources :products, include: [:index, :show, :create] + end + resources :orders resources :products + # NOTE: nested route ideas: merchants/id#/products and categories/id#/products ??? end diff --git a/test/controllers/categories_controller_test.rb b/test/controllers/categories_controller_test.rb new file mode 100644 index 0000000000..125dd4c91d --- /dev/null +++ b/test/controllers/categories_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +describe CategoriesController do + # it "must be a real test" do + # flunk "Need real tests" + # end +end From 1d827905f06ec2b38a56c6d3731bb93ee3f6e455 Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Sat, 20 Oct 2018 17:00:58 -0700 Subject: [PATCH 055/178] fixed routes --- config/routes.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index abec79adda..5d11351efc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,16 +5,18 @@ get "/auth/:provider/callback", to: "sessions#create" delete "/logout", to: "sessions#destroy", as: "logout" + resources :orders + resources :products + resources :categories do resources :products, include: [:index, :show, :create] end - + resources :merchants do resources :products, include: [:index, :show, :create] end - resources :orders - resources :products + # NOTE: nested route ideas: merchants/id#/products and categories/id#/products ??? From 53d6e72ce678dd002ae4cde3d6ac5bc0f11045a9 Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Sat, 20 Oct 2018 17:41:58 -0700 Subject: [PATCH 056/178] add route and add to order method --- app/controllers/order_items_controller.rb | 6 ++++++ app/controllers/products_controller.rb | 14 ++++++++++++++ app/models/order_item.rb | 10 +--------- app/views/products/show.html.erb | 4 +++- config/routes.rb | 6 +++++- 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb index ea50f0ae91..38da309aad 100644 --- a/app/controllers/order_items_controller.rb +++ b/app/controllers/order_items_controller.rb @@ -5,6 +5,7 @@ def create @order_item = @order.order_items.new(order_item_params) @order.save session[:order_id] = @order.id + flash[:result_text] = "Successfully created order" end def update @@ -20,4 +21,9 @@ def destroy @order_item.destroy @order_items = @order.order_items end + + private + + def order_item_params + end diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index 5a2944f943..0b61e82330 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -36,6 +36,7 @@ def create end def show; + end def edit @@ -73,6 +74,19 @@ def update end end + def add_to_order + @order_item = OrderItem.new + #(order_id: @current_cart.id, product_id: product.id, quantity: params[:order_items][:inventory], status: 'pending') + if @order_item.save + # product.inventory -= params[:order_products][:inventory].to_i + # product.save + flash[:success] = "Successfully added product to cart" + redirect_to product_path(@product.id) + else + flash[:alert] = "Failed to add to cart" + render :show + end + end private def product_params diff --git a/app/models/order_item.rb b/app/models/order_item.rb index aab000ae3b..a190efc7fc 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -10,15 +10,7 @@ class OrderItem < ApplicationRecord before_save :finalize # QUESTION: add shipment? column: "Link to transition the order item to marked as shipped" -# take the associated product's price if the order item is not currently persisted. If the order item is persisted (Returns true if the record is persisted, i.e. it’s not a new record and it was not destroyed, otherwise returns false.)then the contents of the unit_price field will be returned instead. - - # def unit_price - # if persisted? - # self[:unit_price] - # else - # product.price - # end - # end + # def total_price # unit_price * quantity diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb index b2a12b900a..96719cf2a3 100644 --- a/app/views/products/show.html.erb +++ b/app/views/products/show.html.erb @@ -30,13 +30,15 @@
    Add to Item to Cart - <%= form_with model: @product do |f|%> + <%= form_with model: @product, url: add_to_order_path(@product.id), method: :get do |f|%> <%= f.label :quantity%> <%= f.select :quantity, (1..10) %> <%= f.submit "Submit Quantity" %> + <% end %> +
    Inventory: diff --git a/config/routes.rb b/config/routes.rb index 7d7c810903..7b26c52352 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,13 +2,17 @@ # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html root 'welcome#index' + get '/product/:id/add_to_order', to: 'products#add_to_order', as: 'add_to_order' + get "/auth/:provider/callback", to: "sessions#create" + delete "/logout", to: "sessions#destroy", as: "logout" resources :categories resources :merchants resources :orders - resources :products + resources :products, only: [:index, :new, :create, :edit, :show] + # NOTE: nested route ideas: merchants/id#/products and categories/id#/products ??? From e42021dc8744b36853d3cc062eea1c3f2ce67f5e Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Sat, 20 Oct 2018 17:45:45 -0700 Subject: [PATCH 057/178] added an add categories form --- app/controllers/products_controller.rb | 11 ++++++++++- app/views/categories/show.html.erb | 0 app/views/merchants/index.html.erb | 2 ++ app/views/merchants/show.html.erb | 3 ++- app/views/products/index.html.erb | 5 ++--- 5 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 app/views/categories/show.html.erb diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index 936c874c59..b7f4604212 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -2,7 +2,16 @@ class ProductsController < ApplicationController before_action :find_product, only: [:show, :edit, :update] def index - @products = Product.all + if params[:merchant_id] + merchant = Merchant.find_by(id: params[:merchant_id]) + @products = merchant.products + + elsif params[:category_id] + category = Category.find_by(id: params[:category_id]) + @products = category.products + else + @products = Product.all + end end def new diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/views/merchants/index.html.erb b/app/views/merchants/index.html.erb index a2bced8503..234bd08101 100644 --- a/app/views/merchants/index.html.erb +++ b/app/views/merchants/index.html.erb @@ -1,3 +1,5 @@

    Merchant Index Page!

    This will display all of the merchants.
    + +<%= link_to "All Categories", products_path %> diff --git a/app/views/merchants/show.html.erb b/app/views/merchants/show.html.erb index d81b90a08d..0ed41a280b 100644 --- a/app/views/merchants/show.html.erb +++ b/app/views/merchants/show.html.erb @@ -1,5 +1,6 @@

    Merchant Show Page!

    -Add Product link +<%= link_to "Add Product", new_product_path %> +<%= link_to "Add Category", new_category_path %> <%= render partial: "/layouts/merchant_account_products" %> diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb index c44229b343..9e0c8adb67 100644 --- a/app/views/products/index.html.erb +++ b/app/views/products/index.html.erb @@ -7,8 +7,7 @@ <%= p.name %> <%= p.price.to_f.round(2) %> -
    - <%= link_to (image_tag p.photo_url), product_path(p.id), class: "btn btn-outline-secondary" %> -
    + <% end %> +
From 9b9ff8916135e1eae0cfd1f71ccda0b5f8ce4934 Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Sat, 20 Oct 2018 17:57:23 -0700 Subject: [PATCH 058/178] fix bug in add_to_order --- app/controllers/products_controller.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index 0b61e82330..c19af3b972 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -80,11 +80,13 @@ def add_to_order if @order_item.save # product.inventory -= params[:order_products][:inventory].to_i # product.save - flash[:success] = "Successfully added product to cart" + flash[:status] = :success + flash[:result_text] = "Successfully added product to cart" redirect_to product_path(@product.id) else - flash[:alert] = "Failed to add to cart" - render :show + flash[:status] = :failure + flash[:result_text] = "Failed to add to cart" + # render :show end end private From a6f7661ccaf7554a0681304aabf9ed2e78dd3657 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Sat, 20 Oct 2018 19:29:02 -0700 Subject: [PATCH 059/178] seeded db with fixtures --- Gemfile | 2 +- app/views/products/index.html.erb | 6 ++---- db/seeds.rb | 32 +++++++++++++++---------------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/Gemfile b/Gemfile index 0d8c4817c6..2a5bfac0f2 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.5.1' -# ruby '2.4.1' +ruby '2.4.1' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.2.1' diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb index 4ecbb1158a..73606af617 100644 --- a/app/views/products/index.html.erb +++ b/app/views/products/index.html.erb @@ -7,13 +7,11 @@ <%= p.name %> <%= p.price.to_f.round(2) %> -<<<<<<< HEAD - -======= +
->>>>>>> cart +
Add to Item to Cart diff --git a/db/seeds.rb b/db/seeds.rb index 683ea901a4..e7f27e68af 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -6,19 +6,19 @@ # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) # Character.create(name: 'Luke', movie: movies.first) -Product.delete_all -Product.create! id: 10, name: "Cherry", price: 22.49, status: true, merchant_id: 1 -Product.create! id: 11, name: "Apple", price: 0.29, status: true, merchant_id: 1 -Product.create! id: 12, name: "Strawberries", price: 1.99, status: true, merchant_id: 1 - -Order.delete_all -Order.create! id: 1, fulfillment_status: "In Progress" -Order.create! id: 2, fulfillment_status: "Placed" -Order.create! id: 3, fulfillment_status: "Shipped" -Order.create! id: 4, fulfillment_status: "Cancelled" - -# Merchant.delete_all -Merchant.create! id: 20, username: "Small Fry", email: "fry@gmail.com", uid: 33, provider: "github" -Merchant.create! id: 21, username: "Momma", email: "momma@gmail.com", uid: 44, provider: "github" -Merchant.create! id: 22, username: "Big Papa", email: "big_papa@gmail.com", uid: 55, provider: "github" -Merchant.create! id: 23, username: "Disco", email: "disco@gmail.com", uid: 66, provider: "github" +# Product.delete_all +# Product.create! id: 10, name: "Cherry", price: 22.49, status: true, merchant_id: 1 +# Product.create! id: 11, name: "Apple", price: 0.29, status: true, merchant_id: 1 +# Product.create! id: 12, name: "Strawberries", price: 1.99, status: true, merchant_id: 1 +# +# Order.delete_all +# Order.create! id: 1, fulfillment_status: "In Progress" +# Order.create! id: 2, fulfillment_status: "Placed" +# Order.create! id: 3, fulfillment_status: "Shipped" +# Order.create! id: 4, fulfillment_status: "Cancelled" +# +# # Merchant.delete_all +# Merchant.create! id: 20, username: "Small Fry", email: "fry@gmail.com", uid: 33, provider: "github" +# Merchant.create! id: 21, username: "Momma", email: "momma@gmail.com", uid: 44, provider: "github" +# Merchant.create! id: 22, username: "Big Papa", email: "big_papa@gmail.com", uid: 55, provider: "github" +# Merchant.create! id: 23, username: "Disco", email: "disco@gmail.com", uid: 66, provider: "github" From 6051c61f0ea4183dcac717b179b3a1e2261be7b6 Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Sun, 21 Oct 2018 13:34:48 -0700 Subject: [PATCH 060/178] cleaned up order_item model --- app/models/order_item.rb | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/app/models/order_item.rb b/app/models/order_item.rb index a190efc7fc..30aac5899d 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -7,41 +7,12 @@ class OrderItem < ApplicationRecord validate :product_present validate :order_present - before_save :finalize # QUESTION: add shipment? column: "Link to transition the order item to marked as shipped" # def total_price - # unit_price * quantity # end - # def subtotal - # subtotal = 0 - # self.products.each do |p| - # subtotal += p.price - # end - # return subtotal - # end - - - - private - def product_present - if product.nil? - errors.add(:product, "is not valid or is not active.") - end - end - - def order_present - if order.nil? - errors.add(:order, "is not a valid order.") - end - end - - def finalize - self[:unit_price] = unit_price - self[:total_price] = quantity * self[:unit_price] - end end From 72b32e30359d3b8a86fb818b78fe8433e9442fc9 Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Sun, 21 Oct 2018 13:47:08 -0700 Subject: [PATCH 061/178] cleaned up order_item model --- app/controllers/order_items_controller.rb | 4 ++-- app/controllers/orders_controller.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb index 38da309aad..0f1f6f4bd1 100644 --- a/app/controllers/order_items_controller.rb +++ b/app/controllers/order_items_controller.rb @@ -1,5 +1,5 @@ class OrderItemsController < ApplicationController - + # the first time the user adds an order_item to his cart, the new order is persisted to the database. From there on, the order's state is saved every time an order_item is added. def create @order = current_order @order_item = @order.order_items.new(order_item_params) @@ -25,5 +25,5 @@ def destroy private def order_item_params - + end diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 9113b19a99..48d82eeaf2 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -8,7 +8,7 @@ def index def new @order = Order.new end - + # add @order_items def show if @order.nil? head :not_found From 2dd5f738e0821d35011fc83857fb08fa5022914a Mon Sep 17 00:00:00 2001 From: Layla Bristol Date: Sun, 21 Oct 2018 23:13:10 -0700 Subject: [PATCH 062/178] updated product controller tests --- app/controllers/order_items_controller.rb | 2 + config/routes.rb | 2 +- test/controllers/orders_controller_test.rb | 40 ------------ test/controllers/products_controller_test.rb | 67 ++++++++++++++++++++ 4 files changed, 70 insertions(+), 41 deletions(-) diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb index 0f1f6f4bd1..0154b99aa5 100644 --- a/app/controllers/order_items_controller.rb +++ b/app/controllers/order_items_controller.rb @@ -26,4 +26,6 @@ def destroy def order_item_params + end + end diff --git a/config/routes.rb b/config/routes.rb index b0b7e1ccb6..573918d316 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -19,7 +19,7 @@ resources :products, include: [:index, :show, :create] end - resources :products, only: [:index, :new, :create, :edit, :show] + resources :products, only: [:index, :new, :create, :edit, :show, :update] diff --git a/test/controllers/orders_controller_test.rb b/test/controllers/orders_controller_test.rb index 7e0c244a4a..f28d718edc 100644 --- a/test/controllers/orders_controller_test.rb +++ b/test/controllers/orders_controller_test.rb @@ -5,47 +5,7 @@ # it "must be a real test" do # flunk "Need real tests" # end - it "can create a product" do - product_hash = { - product: { - name: 'Practical Object Oriented Programming in Ruby', - price: 20, - description: 'A look at how to design object-oriented systems', - photo_url: 'test url', - status: true, - inventory: 1 - } - } - expect { - post products_path, params: product_hash - }.must_change 'Product.count', 1 - must_respond_with :redirect - expect(Product.last.name).must_equal product_hash[:product][:name] - expect(Product.last.price).must_equal product_hash[:product][:price] - expect(Product.last.description).must_equal product_hash[:product][:description] - end - - - it "will not create a product with invalid params" do - product_hash = { - product: { - name: 'Practical Object Oriented Programming in Ruby', - price: 20, - description: 'A look at how to design object-oriented systems', - photo_url: 'test url', - status: "test string not boolean", - inventory: 1 - } - } - - expect { - post products_path, params: product_hash - }.wont_change 'Product.count' - - must_respond_with :bad_request - end - end diff --git a/test/controllers/products_controller_test.rb b/test/controllers/products_controller_test.rb index 1264899874..494504b664 100644 --- a/test/controllers/products_controller_test.rb +++ b/test/controllers/products_controller_test.rb @@ -4,6 +4,13 @@ # it "must be a real test" do # flunk "Need real tests" # end + describe "index" do + + it 'can display all products' do + get products_path + must_respond_with :success + end + end describe "create" do @@ -49,6 +56,66 @@ must_respond_with :bad_request end + end + + describe 'update' do + + it 'can update a product if a logged in user' do + @login_user = Merchant.first + product = Product.first + + product_hash = { + product: { + name: 'Practical Object Oriented Programming in Ruby', + price: 20, + description: 'A look at how to design object-oriented systems', + photo_url: 'test url', + status: true, + inventory: 1 + } + } + + patch product_path(product.id), params: product_hash + must_respond_with :success + + + end + + it 'will not update a product if not logged in' do + product = Product.first + product_hash = { + product: { + name: 'Practical Object Oriented Programming in Ruby', + price: 20, + description: 'A look at how to design object-oriented systems', + photo_url: 'test url', + status: true, + inventory: 1 + } + } + + patch product_path(product.id), params: product_hash + must_respond_with :failure + + + end + end + + describe 'show' do + + it 'can display a product details page' do + product = Product.first + + get product_path(product.id) + must_respond_with :success + end + + it 'returns not found if invalid id' do + + get product_path(0) + must_respond_with :not_found + end end + end From 2533f56e7785c10004e7724dd073260f6a37c157 Mon Sep 17 00:00:00 2001 From: Layla Bristol Date: Sun, 21 Oct 2018 23:36:52 -0700 Subject: [PATCH 063/178] created tests for sessions controller --- config/routes.rb | 2 +- test/controllers/sessions_controller_test.rb | 57 ++++++++++++++++++++ test/test_helper.rb | 19 +++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 573918d316..c25109e39e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,7 +4,7 @@ get '/product/:id/add_to_order', to: 'products#add_to_order', as: 'add_to_order' - get "/auth/:provider/callback", to: "sessions#create" + get "/auth/:provider/callback", to: "sessions#create", as: 'auth_callback' delete "/logout", to: "sessions#destroy", as: "logout" diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb index c2632a720b..77397e3142 100644 --- a/test/controllers/sessions_controller_test.rb +++ b/test/controllers/sessions_controller_test.rb @@ -4,4 +4,61 @@ # it "must be a real test" do # flunk "Need real tests" # end + + it "can successfully log in with github as an existing merchant" do + # Arrange + # make sure that for some existing merchant, everything is configured! + + mercury = merchants(:mercury) + + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new( mock_auth_hash( mercury ) ) + + # Act + # Simulating logging in with github being successful (given the OmniAuth hash made above) + get auth_callback_path(:github) + + # Assert + + must_redirect_to root_path + expect(session[:merchant_id]).must_equal mercury.id + expect(flash[:success]).must_equal "Logged in as existing merchant #{mercury.username}" + + end + + it "creates a new user successfully when logging in with a new valid merchant" do + + start_count = Merchant.count + + new_merchant = Merchant.new(username: "new user", email: "some email", uid: 3, provider: :github) + + # if new_merchant is not valid, then this test isn't testing the right thing + expect(new_merchant.valid?).must_equal true + + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new( mock_auth_hash( new_merchant ) ) + + get auth_callback_path(:github) + + must_redirect_to root_path + # binding.pry + expect( Merchant.count ).must_equal start_count + 1 + expect( session[:merchant_id] ).must_equal Merchant.last.id + end + + it "does not create a new user when logging in with a new invalid merchant" do + start_count = Merchant.count + + invalid_new_merchant = Merchant.new(username: nil, email: nil) + + # if invalid_new_user is valid, then this test isn't testing the right thing + expect(invalid_new_merchant.valid?).must_equal false + + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new( mock_auth_hash( invalid_new_merchant ) ) + + get auth_callback_path(:github) + + must_redirect_to root_path + expect( session[:merchant_id] ).must_equal nil + expect( Merchant.count ).must_equal start_count + end + end diff --git a/test/test_helper.rb b/test/test_helper.rb index 188632c8e5..0e6d606c1e 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -23,4 +23,23 @@ 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... + + + def setup + # Once you have enabled test mode, all requests + # to OmniAuth will be short circuited to use the mock authentication hash. + # A request to /auth/provider will redirect immediately to /auth/provider/callback. + OmniAuth.config.test_mode = true + end + + def mock_auth_hash(merchant) + return { + uid: merchant.uid, + provider: merchant.provider, + info: { + nickname: merchant.username, + email: merchant.email + } + } + end end From d6e930d3ee4f84c3757360ad6e56ea631ef9f819 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Mon, 22 Oct 2018 00:17:27 -0700 Subject: [PATCH 064/178] added order_item model tests --- app/models/order_item.rb | 8 ++-- test/models/category_test.rb | 53 ++++++++++++++++++++++-- test/models/merchant_test.rb | 2 +- test/models/order_item_test.rb | 74 ++++++++++++++++++++++++++++++++-- 4 files changed, 126 insertions(+), 11 deletions(-) diff --git a/app/models/order_item.rb b/app/models/order_item.rb index 30aac5899d..9c610318cc 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -4,10 +4,12 @@ class OrderItem < ApplicationRecord validates :quantity, presence: true, numericality: { greater_than: 0, only_integer: true } - validate :product_present - validate :order_present + # TODO: change from true/false to pending etc. [this will cause errors i think until we can coordinate it] + validates :shipped, inclusion: { in: [true, false] } - # QUESTION: add shipment? column: "Link to transition the order item to marked as shipped" + # NOTE: The two lines below made the model tests fail + # validate :product_present + # validate :order_present diff --git a/test/models/category_test.rb b/test/models/category_test.rb index cde85f3e39..ab616ce171 100644 --- a/test/models/category_test.rb +++ b/test/models/category_test.rb @@ -2,9 +2,54 @@ describe Category do let(:category) { Category.new } + let(:readables) { categories(:readables) } - it "must be valid" do - skip - value(category).must_be :valid? - end +# describe 'relations' do +# it 'responds to products' do +# expect(readables).must_respond_to :products +# +# readables.products.each do |product| +# expect(product).must_be_kind_of Product +# end +# end +# +# it 'can have products' do +# expect(readables).must_include products(:poodr) +# end +# +# it 'can have zero products' do +# expect(readables).must_include products(:poodr) +# end +# end +# +# describe 'validations' do +# it "must be valid" do +# value(category).must_be :valid? +# end +# +# it 'requires a category name present' do +# expect(readables.valid?).must_equal true +# +# readables.category_name = nil +# +# expect(category.valid?).must_equal false +# expect(category.errors.messages).must_include :category_name +# end +# +# it 'requires a unique name' do +# cozyables = categories(:cozyables) +# wearables = categories(:wearables) +# cozyables.username = wearables.username +# +# expect(cozyables.valid?).must_equal false +# expect(cozyables.errors.messages).must_include :category_name +# +# cozyables.username = "cozyables" +# expect(cozyables.valid?).must_equal true +# end +# end + +describe 'model logic' do + # TODO: add tests for business logic below +end end diff --git a/test/models/merchant_test.rb b/test/models/merchant_test.rb index 363e452d1e..277384946d 100644 --- a/test/models/merchant_test.rb +++ b/test/models/merchant_test.rb @@ -31,7 +31,7 @@ it 'requires a unique username' do mars = merchants(:mars) saturn = merchants(:saturn) - saturn.username = "mars" + saturn.username = mars.username expect(saturn.valid?).must_equal false expect(saturn.errors.messages).must_include :username diff --git a/test/models/order_item_test.rb b/test/models/order_item_test.rb index bb499e2558..809a55330f 100644 --- a/test/models/order_item_test.rb +++ b/test/models/order_item_test.rb @@ -1,10 +1,78 @@ require "test_helper" + describe OrderItem do let(:order_item) { OrderItem.new } + let(:orderitem1) { order_items(:orderitem1) } + + describe 'relations' do + it 'belongs to a product' do + expect(orderitem1).must_respond_to :product + expect(orderitem1.product).must_be_kind_of Product + expect(orderitem1.product).must_equal products(:poodr) + end + + it 'belongs to an order' do + expect(orderitem1).must_respond_to :order + expect(orderitem1.order).must_be_kind_of Order + expect(orderitem1.order).must_equal orders(:order1) + end + end + + describe 'validations' do + it "must be valid" do + value(orderitem1).must_be :valid? + end + + it 'must have a quantity present' do + expect(orderitem1.valid?).must_equal true + + orderitem1.quantity = nil + + expect(orderitem1.valid?).must_equal false + expect(orderitem1.errors.messages).must_include :quantity + end + + it 'must have a quantity greater than zero present' do + orderitem1.quantity = 0 + + expect(orderitem1.valid?).must_equal false + expect(orderitem1.errors.messages).must_include :quantity + + orderitem1.quantity = -1 + + expect(orderitem1.valid?).must_equal false + expect(orderitem1.errors.messages).must_include :quantity + end + + it 'must have an integer present' do + orderitem1.quantity = 1.5 + + expect(orderitem1.valid?).must_equal false + expect(orderitem1.errors.messages).must_include :quantity + + orderitem1.quantity = "number" + + expect(orderitem1.valid?).must_equal false + expect(orderitem1.errors.messages).must_include :quantity + end + + it 'must have a shipping status present' do + orderitem1.shipped = nil + + expect(orderitem1.valid?).must_equal false + expect(orderitem1.errors.messages).must_include :shipped + end + + # it 'must have a valid shipping status' do + # orderitem1.shipped = "string" + # + # expect(orderitem1.valid?).must_equal false + # expect(orderitem1.errors.messages).must_include :shipped + # end + end - it "must be valid" do - skip - value(order_item).must_be :valid? + describe 'model logic' do + # TODO: add tests for business logic below end end From 0c5a9db6aa89a8a347b10384e3bf89629d645c9a Mon Sep 17 00:00:00 2001 From: kangazoom Date: Mon, 22 Oct 2018 01:35:05 -0700 Subject: [PATCH 065/178] added order tests (relations) --- app/models/order.rb | 10 ++-------- test/models/order_test.rb | 23 ++++++++++++++++++++--- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/app/models/order.rb b/app/models/order.rb index 97f9f8f4c7..807f8297f2 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -1,15 +1,9 @@ class Order < ApplicationRecord - # QUESTION: does this cover the entirety of the "must have one or many"?? + # TODO: decide/add more validations+testing (cc info, mailing address, etc) has_many :order_items - # QUESTION: add all the billing info like cc? - # QUESTION: date_time_placed --> redundant b/c of created_at time stamp?? - # QUESTION: fulfillment_status --> check to make sure that statuses are one of the allowed ones (ex: pending, completed, paid, etc.) - # QUESTION: clear_cart default value? true vs false? - # QUESTION: total_price --> has to come from the sum of all the order_items via products.... (also needs to be int) - # QUESTION: should not put order_item id down b/c it could then take many (ex: koolaid, nailpolish, etc.) - # + # # def total_price # total = 0 # self. diff --git a/test/models/order_test.rb b/test/models/order_test.rb index b342672bdf..71dcab3a87 100644 --- a/test/models/order_test.rb +++ b/test/models/order_test.rb @@ -2,9 +2,26 @@ describe Order do let(:order) { Order.new } + let(:order1) { orders(:order1) } - it "must be valid" do - skip - value(order).must_be :valid? + describe 'relations' do + it 'has many order items' do + expect(order1).must_respond_to :order_items + + order1.order_items.each do |order_item| + expect(order_item).must_be_kind_of OrderItem + end + + order1.order_items.each do |order_item| + expect(order_item).must_equal order_items(:orderitem1) + end + end + end + + # TODO: decide/add more validations+testing (cc info, mailing address, etc) + describe 'validations' do + it "must be valid" do + value(order).must_be :valid? + end end end From faac04b4f6e7999614ce089f4835a0a9f72ce753 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Mon, 22 Oct 2018 10:26:41 -0700 Subject: [PATCH 066/178] added products+categories relations to fixtures + reseeded --- test/fixtures/categories.yml | 8 ++++++++ test/fixtures/products.yml | 8 ++++++++ test/models/order_item_test.rb | 3 ++- test/models/product_test.rb | 8 ++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/test/fixtures/categories.yml b/test/fixtures/categories.yml index b1bd2b9e25..e81a6947ca 100644 --- a/test/fixtures/categories.yml +++ b/test/fixtures/categories.yml @@ -6,24 +6,32 @@ # readables: category_name: readables + products: poodr wearables: category_name: wearables + products: onesie1, snuggie cozyables: category_name: cozyables + products: snuggie snackables: category_name: snackables + products: koolaid hairables: category_name: hairables + products: wig1 plantables: category_name: plantables + products: houseplant1 houseables: category_name: houseables + products: lamp beautifiables: category_name: beautifiables + products: nailpolish1 diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml index c3930e5cb7..f5e5231635 100644 --- a/test/fixtures/products.yml +++ b/test/fixtures/products.yml @@ -8,6 +8,7 @@ poodr: status: true inventory: 1 merchant: jupiter + categories: readables koolaid: name: koolaid @@ -17,6 +18,7 @@ koolaid: status: true inventory: 100 merchant: mercury + categories: snackables wig1: name: wig1 @@ -26,6 +28,7 @@ wig1: status: true inventory: 100 merchant: saturn + categories: hairables houseplant1: name: houseplant1 @@ -35,6 +38,7 @@ houseplant1: status: true inventory: 0 merchant: saturn + categories: plantables lamp: name: lamp @@ -44,6 +48,7 @@ lamp: status: true inventory: 50 merchant: mars + categories: houseables nailpolish1: name: nailpolish1 @@ -53,6 +58,7 @@ nailpolish1: status: true inventory: 2 merchant: saturn + categories: beautifiables onesie1: name: onesie1 @@ -62,6 +68,7 @@ onesie1: status: true inventory: 5 merchant: mars + categories: wearables snuggie: name: snuggie @@ -71,3 +78,4 @@ snuggie: status: true inventory: 44 merchant: jupiter + categories: wearables, cozyables diff --git a/test/models/order_item_test.rb b/test/models/order_item_test.rb index 809a55330f..4c3fa932dd 100644 --- a/test/models/order_item_test.rb +++ b/test/models/order_item_test.rb @@ -9,7 +9,7 @@ it 'belongs to a product' do expect(orderitem1).must_respond_to :product expect(orderitem1.product).must_be_kind_of Product - expect(orderitem1.product).must_equal products(:poodr) + # expect(orderitem1.product).must_equal products(:poodr) end it 'belongs to an order' do @@ -26,6 +26,7 @@ it 'must have a quantity present' do expect(orderitem1.valid?).must_equal true + p orderitem1.quantity orderitem1.quantity = nil diff --git a/test/models/product_test.rb b/test/models/product_test.rb index 162c183029..e56a93e723 100644 --- a/test/models/product_test.rb +++ b/test/models/product_test.rb @@ -1,4 +1,12 @@ require "test_helper" +# has_many :reviews +# has_many :order_items +# belongs_to :merchant +# has_and_belongs_to_many :categories + +# validates :name, presence: true, uniqueness: true +# # TODO: maybe product name must be unique to category??? +# validates :price, presence: true, numericality: { greater_than: 0 } describe Product do let(:product) { Product.new } From 38f33e3202c67f941457538bde809250876e214e Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Mon, 22 Oct 2018 10:53:38 -0700 Subject: [PATCH 067/178] added categories show page --- app/views/categories/index.html.erb | 7 +++++++ app/views/merchants/show.html.erb | 6 ++++-- app/views/products/index.html.erb | 5 +---- db/seeds.rb | 32 ++++++++++++++--------------- 4 files changed, 28 insertions(+), 22 deletions(-) create mode 100644 app/views/categories/index.html.erb diff --git a/app/views/categories/index.html.erb b/app/views/categories/index.html.erb new file mode 100644 index 0000000000..28696520e0 --- /dev/null +++ b/app/views/categories/index.html.erb @@ -0,0 +1,7 @@ +
    + <% @categories.each do |category| %> +
  • +
    <%= link_to "#{category.category_name}", category_products_path(category[:id]) %>
    +
  • + <% end %> +
diff --git a/app/views/merchants/show.html.erb b/app/views/merchants/show.html.erb index 0ed41a280b..a73a07cef8 100644 --- a/app/views/merchants/show.html.erb +++ b/app/views/merchants/show.html.erb @@ -1,6 +1,8 @@

Merchant Show Page!

-<%= link_to "Add Product", new_product_path %> -<%= link_to "Add Category", new_category_path %> +<% if @login_user != nil %> + <%= link_to "Add Product", new_product_path %> + <%= link_to "Add Category", new_category_path %> +<% end %> <%= render partial: "/layouts/merchant_account_products" %> diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb index 4ecbb1158a..a3d14876e0 100644 --- a/app/views/products/index.html.erb +++ b/app/views/products/index.html.erb @@ -7,13 +7,10 @@ <%= p.name %> <%= p.price.to_f.round(2) %> -<<<<<<< HEAD - -======= +
->>>>>>> cart
Add to Item to Cart diff --git a/db/seeds.rb b/db/seeds.rb index 683ea901a4..e7f27e68af 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -6,19 +6,19 @@ # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) # Character.create(name: 'Luke', movie: movies.first) -Product.delete_all -Product.create! id: 10, name: "Cherry", price: 22.49, status: true, merchant_id: 1 -Product.create! id: 11, name: "Apple", price: 0.29, status: true, merchant_id: 1 -Product.create! id: 12, name: "Strawberries", price: 1.99, status: true, merchant_id: 1 - -Order.delete_all -Order.create! id: 1, fulfillment_status: "In Progress" -Order.create! id: 2, fulfillment_status: "Placed" -Order.create! id: 3, fulfillment_status: "Shipped" -Order.create! id: 4, fulfillment_status: "Cancelled" - -# Merchant.delete_all -Merchant.create! id: 20, username: "Small Fry", email: "fry@gmail.com", uid: 33, provider: "github" -Merchant.create! id: 21, username: "Momma", email: "momma@gmail.com", uid: 44, provider: "github" -Merchant.create! id: 22, username: "Big Papa", email: "big_papa@gmail.com", uid: 55, provider: "github" -Merchant.create! id: 23, username: "Disco", email: "disco@gmail.com", uid: 66, provider: "github" +# Product.delete_all +# Product.create! id: 10, name: "Cherry", price: 22.49, status: true, merchant_id: 1 +# Product.create! id: 11, name: "Apple", price: 0.29, status: true, merchant_id: 1 +# Product.create! id: 12, name: "Strawberries", price: 1.99, status: true, merchant_id: 1 +# +# Order.delete_all +# Order.create! id: 1, fulfillment_status: "In Progress" +# Order.create! id: 2, fulfillment_status: "Placed" +# Order.create! id: 3, fulfillment_status: "Shipped" +# Order.create! id: 4, fulfillment_status: "Cancelled" +# +# # Merchant.delete_all +# Merchant.create! id: 20, username: "Small Fry", email: "fry@gmail.com", uid: 33, provider: "github" +# Merchant.create! id: 21, username: "Momma", email: "momma@gmail.com", uid: 44, provider: "github" +# Merchant.create! id: 22, username: "Big Papa", email: "big_papa@gmail.com", uid: 55, provider: "github" +# Merchant.create! id: 23, username: "Disco", email: "disco@gmail.com", uid: 66, provider: "github" From e0d2c4d441bcb9add90acbf8f66b23382d1b91d1 Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Mon, 22 Oct 2018 10:56:22 -0700 Subject: [PATCH 068/178] fixed weird head message in products/index.html.erb --- app/views/products/index.html.erb | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb index 0dfd362a3d..73606af617 100644 --- a/app/views/products/index.html.erb +++ b/app/views/products/index.html.erb @@ -11,10 +11,7 @@
-<<<<<<< HEAD -======= ->>>>>>> fd59ac9934c35fe77e79cbbc6d9279fe3d84aa22
Add to Item to Cart From c1cc6840e63e24b878e40297e2542b3aa558dd80 Mon Sep 17 00:00:00 2001 From: Layla Bristol Date: Mon, 22 Oct 2018 11:21:51 -0700 Subject: [PATCH 069/178] updated controller testing --- app/controllers/application_controller.rb | 4 ++-- app/controllers/products_controller.rb | 6 ++++-- app/controllers/sessions_controller.rb | 10 +++++----- app/views/layouts/application.html.erb | 4 ++-- test/controllers/products_controller_test.rb | 16 +++++++++++++--- test/controllers/sessions_controller_test.rb | 8 +------- 6 files changed, 27 insertions(+), 21 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index dc66500391..0eda4f7239 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -19,8 +19,8 @@ def current_order private def find_merchant - if session[:username] - @login_user = Merchant.find_by(id: session[:username]) + if session[:merchant_id] + @login_user = Merchant.find_by(id: session[:merchant_id]) end end end diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index 1acdd044b8..082fc5d82a 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -38,7 +38,7 @@ def create if !@login_user flash[:status] = :failure flash[:result_text] = "Could not create #{@product}" - flash[:messages] = @product.errors.messages + # flash[:messages] = @product.errors.messages redirect_to root_path end # debugger @@ -58,13 +58,15 @@ def update flash[:result_text] = "You must log in to update #{@product}" flash[:messages] = @product.errors.messages redirect_back fallback_location: root_path + return end if !find_product flash[:status] = :failure flash[:result_text] = "Unable to find #{@product}" flash[:messages] = @product.errors.messages - redirect_back fallback_location: root_path + redirect_to root_path + return end @product = find_product() diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index b391c46706..43ed6dc789 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -3,11 +3,11 @@ class SessionsController < ApplicationController def create auth_hash = request.env['omniauth.auth'] merchant = Merchant.find_by(uid: auth_hash[:uid], provider: 'github') + if merchant # User was found in the database flash[:status] = "success" flash[:result_text] = "Logged in as returning merchant #{merchant.username}" - flash[:messages] = merchant.errors.messages else # User doesn't match anything in the DB @@ -17,7 +17,7 @@ def create if merchant.save flash[:status] = "success" flash[:result_text] = "Logged in as new merchant #{merchant.username}" - flash[:messages] = merchant.errors.messages + # flash[:messages] = merchant.errors.messages else # Couldn't save the user for some reason. If we @@ -30,18 +30,18 @@ def create flash[:messages] = merchant.errors.messages redirect_to root_path - + return end end # If we get here, we have a valid user instance - session[:username] = merchant.id + session[:merchant_id] = merchant.id redirect_to root_path end def destroy - session[:username] = nil + session[:merchant_id] = nil flash[:result_text] = "Successfully logged out!" redirect_to root_path diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 9aebf1b9b4..c9f2eaf2c3 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -13,7 +13,7 @@

- <%= link_to "REDSIE", root_path %> + <%= link_to "REDSY", root_path %>

+
+ +
<% end %> + From 78e2ee9044eacbf562e38951e672f59431cf764c Mon Sep 17 00:00:00 2001 From: kangazoom Date: Tue, 23 Oct 2018 14:42:33 -0700 Subject: [PATCH 100/178] fixed route+link to add item to cart --- app/controllers/order_items_controller.rb | 5 ----- app/controllers/orders_controller.rb | 8 -------- app/views/products/show.html.erb | 2 +- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb index 45cfffe864..e1d7cbc0b0 100644 --- a/app/controllers/order_items_controller.rb +++ b/app/controllers/order_items_controller.rb @@ -57,13 +57,8 @@ def create # params.require(:order_items).permit(:product_id, :quantity) # end -<<<<<<< HEAD # end -======= - - # end ->>>>>>> master end diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 646f58efec..3c4afd7b06 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -62,10 +62,6 @@ def create # # flash[:messages] = @order.errors.messages # # render :new, status: :bad_request # end -<<<<<<< HEAD -======= - ->>>>>>> master if sessions[:order_id] == @order = Order.new #(params) @order_category = @order.category @@ -80,10 +76,6 @@ def create # render :new, status: :bad_request end end -<<<<<<< HEAD -======= - ->>>>>>> master end def edit diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb index 28901ec688..2ad9c44e86 100644 --- a/app/views/products/show.html.erb +++ b/app/views/products/show.html.erb @@ -32,7 +32,7 @@
Add to Item to Cart - <%= form_with model: @order, url: product_create_order_item_path(@product.id), method: :post do |f|%> + <%= form_with model: @order, url: product_order_items_create_path(@product.id), method: :post do |f|%> <%= f.label :quantity%> <%= f.select :quantity, (1..@product.inventory) %> From 782ff755b0958b6394cf7b5ccdaba886087733c2 Mon Sep 17 00:00:00 2001 From: Melissa O'Hearn Date: Tue, 23 Oct 2018 15:52:13 -0700 Subject: [PATCH 101/178] add update medthod to order items --- app/controllers/order_items_controller.rb | 7 ++++++- app/controllers/products_controller.rb | 1 - app/views/orders/index.html.erb | 12 +++++++++++- app/views/products/show.html.erb | 2 +- config/routes.rb | 5 ++++- 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb index e1d7cbc0b0..bda15dd187 100644 --- a/app/controllers/order_items_controller.rb +++ b/app/controllers/order_items_controller.rb @@ -12,7 +12,7 @@ def create #TODO: translate into strong params @order_item = OrderItem.new(product_id: params[:product_id].to_i, quantity: params[:quantity].to_i, order_id: current_order) - session[:order_id] = @order_item.order.id + session[:order_id] = @order_item.order.id # order_id = OrderItem.add_order_item_to_order(@order_item) # @order_item.order_id = order_id @@ -34,6 +34,11 @@ def create end + def update + @order_item.update_attributes(params[:quantity]) + redirect_to orders_path + + end # NOTE: actions below are for order... should instead be for order_item? # def update diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index 986c8fc184..107d1b0393 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -16,7 +16,6 @@ def index def new @product = Product.new - @order_item = current_order.order_items.new end def create diff --git a/app/views/orders/index.html.erb b/app/views/orders/index.html.erb index 216cb5c245..e178ae11e2 100644 --- a/app/views/orders/index.html.erb +++ b/app/views/orders/index.html.erb @@ -43,7 +43,17 @@ - Edit/Delete + <%= form_with model: @order_item, url: order_items_update_path(order_items.id), method: :put do |f|%> + + <%= f.label :quantity%> + <%= f.select :quantity, (1..order_items.product.inventory) %> + <%= f.submit "Edit" %> + + <% end %> + + + + Remove from cart diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb index 2ad9c44e86..b3cbff6d40 100644 --- a/app/views/products/show.html.erb +++ b/app/views/products/show.html.erb @@ -36,7 +36,7 @@ <%= f.label :quantity%> <%= f.select :quantity, (1..@product.inventory) %> - <%= f.submit "Submit Quantity" %> + <%= f.submit "Add Item to Cart" %> <% end %> diff --git a/config/routes.rb b/config/routes.rb index de5000dbae..07307e2547 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,7 +11,10 @@ resources :orders - resources :order_items, include: [:show, :edit, :delete] + resources :order_items, include: [:show, :delete] + + put '/update_order_item', to: 'order_items#update', as: 'order_items_update' + resources :categories do From 28120a2d74197dff7e31be6a6c9f510c020507d6 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Tue, 23 Oct 2018 15:53:50 -0700 Subject: [PATCH 102/178] minor spacing changes --- app/controllers/products_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index 986c8fc184..107d1b0393 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -16,7 +16,6 @@ def index def new @product = Product.new - @order_item = current_order.order_items.new end def create From 71a9aa60ec04ee0f5f68822df1c01c75769a44d9 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Tue, 23 Oct 2018 16:16:47 -0700 Subject: [PATCH 103/178] added functionality to delete an item from order/cart --- app/controllers/order_items_controller.rb | 26 ++++++++++++++++++++++- app/views/orders/index.html.erb | 14 ++++++------ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb index bda15dd187..123a7bcfbd 100644 --- a/app/controllers/order_items_controller.rb +++ b/app/controllers/order_items_controller.rb @@ -35,7 +35,16 @@ def create end def update - @order_item.update_attributes(params[:quantity]) + @order_item.update(quantity: params[:quantity]) + if @order_item.save + flash[:status] = :success + flash[:result_text] = "Successfully updated order item" + flash[:messages] = @order_item.errors.messages + else + flash[:status] = :failure + flash[:result_text] = "Did not update order item" + flash[:messages] = @order_item.errors.messages + end redirect_to orders_path end @@ -55,6 +64,21 @@ def update # @order_items = @order.order_items # end + def destroy + @order_item = OrderItem.find_by(id: params[:id]) + + result = @order_item.destroy + + if result + flash[:status] = :success + flash[:result_text] = "Successfully destroyed item: #{@order_item.product.name}" + redirect_to orders_path + else + flash[:status] = :failure + flash[:result_text] = "Did not delete #{@order_item.product.name}" + flash[:messages] = @order_item.errors.messages + end + end # private # diff --git a/app/views/orders/index.html.erb b/app/views/orders/index.html.erb index e178ae11e2..95149fbaf9 100644 --- a/app/views/orders/index.html.erb +++ b/app/views/orders/index.html.erb @@ -26,34 +26,34 @@ -<% @orders.order_items.each do |order_items| %> +<% @orders.order_items.each do |order_item| %> - <%= link_to order_items.product.name, product_path(order_items.product.id) %> + <%= link_to order_item.product.name, product_path(order_item.product.id) %> - <%= order_items.product.price %> + <%= order_item.product.price %> - <%= order_items.quantity %> + <%= order_item.quantity %> - <%= form_with model: @order_item, url: order_items_update_path(order_items.id), method: :put do |f|%> + <%= form_with model: @order_item, url: order_items_update_path(order_item.id), method: :put do |f|%> <%= f.label :quantity%> - <%= f.select :quantity, (1..order_items.product.inventory) %> + <%= f.select :quantity, (1..order_item.product.inventory) %> <%= f.submit "Edit" %> <% end %> - Remove from cart + <%= link_to 'Remove from Cart', order_item_path(order_item.id), method: :delete, data: { confirm: "Are you sure you want to delete item: #{order_item.product.name}?" } %> From 82f6e21f8e013c0cd7a82d7abe983791fcfa10b1 Mon Sep 17 00:00:00 2001 From: Layla Bristol Date: Tue, 23 Oct 2018 16:31:06 -0700 Subject: [PATCH 104/178] order form and temp css --- app/assets/stylesheets/application.scss | 50 ++++++++++++++++++++- app/controllers/products_controller.rb | 1 - app/views/layouts/application.html.erb | 58 +++++++++++-------------- app/views/orders/_order_form.html.erb | 44 +++++++++++++++++++ app/views/orders/new.html.erb | 2 + 5 files changed, 120 insertions(+), 35 deletions(-) create mode 100644 app/views/orders/_order_form.html.erb diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 437ee42494..f1f0d2719a 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -1,3 +1,6 @@ +@import url('https://fonts.googleapis.com/css?family=Love+Ya+Like+A+Sister'); +@import url('https://fonts.googleapis.com/css?family=Quicksand'); + /* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. @@ -17,6 +20,51 @@ /* Import scss content */ @import "**/*"; +body { +text-transform:capitalize; +background-color: #B1B3B5; +font-family: 'Quicksand', sans-serif; + +} + +li { + list-style: none; +} + + +.header{ + background-image: url('http://www.printawallpaper.com/upload/designs/red_diamond_detail.jpg'); + background-attachment: fixed; + background-size: cover; + background-position: center; + font-family: 'Love Ya Like A Sister', cursive; + +} + +.logo { + text-align: center; + font-weight: 600; + font-size: 10em; + letter-spacing: .2em; +} + +.nav-app-header { + display: flex; + justify-content: space-between; + flex-direction: row; +} + +a:visited{ + color: #F7EBE8; +} +a:link{ + color: #F7EBE8; +} +a:hover{ + color: #E54B4B; + text-decoration: none; +} + .dropdown { position: relative; display: inline-block; @@ -25,7 +73,7 @@ .dropdown-content { display: none; position: absolute; - background-color: #FFE5E7; + background-color: #292F36; min-width: 130px; box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); padding: 12px 36px; diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index 986c8fc184..107d1b0393 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -16,7 +16,6 @@ def index def new @product = Product.new - @order_item = current_order.order_items.new end def create diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index bcbfcc3a39..362b2431e4 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -11,43 +11,34 @@ -
-

+
+

<%= link_to "REDSY", root_path %>

- <% if flash[:result_text] or flash[:messages] %>
@@ -86,6 +77,7 @@

<% end %> +
<%= yield %> diff --git a/app/views/orders/_order_form.html.erb b/app/views/orders/_order_form.html.erb new file mode 100644 index 0000000000..47b9ef7f02 --- /dev/null +++ b/app/views/orders/_order_form.html.erb @@ -0,0 +1,44 @@ +
+ <%= form_with model: @order do |f| %> + + +
+ <%= f.label :name %> + <%= f.text_field :guest_cc_name, class: "form-control" %> +
+ +
+ <%= f.label :address%> + <%= f.text_field :guest_mailing, class: "form-control" %> +
+ +
+ <%= f.label :email %> + <%= f.text_field :guest_email, class: "form-control" %> +
+ +
+ <%= f.label :credit_card_number %> + <%= f.text_field :guest_cc_num, class: "form-control" %> +
+ +
+ <%= f.label :expiration_date %> + <%= f.text_area :guest_cc_exp_date, class: "form-control" %> +
+ +
+ <%= f.label :cvv %> + <%= f.text_area :guest_cc_cvv_code, class: "form-control" %> +
+ +
+ <%= f.label :billing_zip_code %> + <%= f.text_area :guest_cc_zip, class: "form-control" %> +
+ +
+ <%= f.submit action_type, class: "btn btn-primary" %> +
+ <% end %> +
diff --git a/app/views/orders/new.html.erb b/app/views/orders/new.html.erb index f790ae9bb0..7ab2e1a69d 100644 --- a/app/views/orders/new.html.erb +++ b/app/views/orders/new.html.erb @@ -1 +1,3 @@

New Order Page

+ +<%= render partial: "order_form", locals: { action_type: "Create New Order" } %> From 39826228801b6adf51f766f3aa6d5e678bf742a7 Mon Sep 17 00:00:00 2001 From: Layla Bristol Date: Tue, 23 Oct 2018 16:36:26 -0700 Subject: [PATCH 105/178] moved session out of header and added success background to logout --- app/controllers/sessions_controller.rb | 1 + app/views/layouts/application.html.erb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 515b7fdd60..6de1d9bef2 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -41,6 +41,7 @@ def create def destroy session.delete(:merchant_id) + flash[:status] = "success" flash[:result_text] = "Successfully logged out!" redirect_to root_path diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 362b2431e4..4d34b1f00b 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -60,6 +60,7 @@ + <% if flash[:result_text] or flash[:messages] %>
@@ -77,7 +78,6 @@
<% end %> -
<%= yield %> From 2c4e65950d71497434ad11f30e98cda08de6f7fb Mon Sep 17 00:00:00 2001 From: kangazoom Date: Tue, 23 Oct 2018 16:42:00 -0700 Subject: [PATCH 106/178] added destroy functionality + some model biz logic - very prelim --- app/controllers/order_items_controller.rb | 7 +++++-- app/models/product.rb | 18 +++++++++++++++++- app/views/orders/index.html.erb | 4 ++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb index 123a7bcfbd..8a9318c671 100644 --- a/app/controllers/order_items_controller.rb +++ b/app/controllers/order_items_controller.rb @@ -35,14 +35,17 @@ def create end def update + @order_item = OrderItem.find_by(id: params[:order_item_id]) + @order_item.update(quantity: params[:quantity]) + if @order_item.save flash[:status] = :success - flash[:result_text] = "Successfully updated order item" + flash[:result_text] = "Successfully updated order item: #{@order_item.product.name}" flash[:messages] = @order_item.errors.messages else flash[:status] = :failure - flash[:result_text] = "Did not update order item" + flash[:result_text] = "Did not update order item: #{@order_item.product.name}" flash[:messages] = @order_item.errors.messages end redirect_to orders_path diff --git a/app/models/product.rb b/app/models/product.rb index 2e29e52c47..bb6dde3e0d 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -16,5 +16,21 @@ class Product < ApplicationRecord # QUESTION: how to handle invalid photo URLS since that will 'show up' ?? # QUESTION: status default --> true?? to show it's automatically active? - + # NOTE: may need to move + # def reduce_inventory(order_item) + # product = order_item.product + # inventory = product.inventory + # + # quantity = order_item.quantity + # + # difference = inventory - quantity + # + # if difference >= 0 + # inventory = difference + # inventory.save + # else + # raise ArguementError, "Guest cannot purchase more inventory than is available" + # end + # end + end diff --git a/app/views/orders/index.html.erb b/app/views/orders/index.html.erb index 95149fbaf9..e31fc2fedf 100644 --- a/app/views/orders/index.html.erb +++ b/app/views/orders/index.html.erb @@ -43,11 +43,11 @@ - <%= form_with model: @order_item, url: order_items_update_path(order_item.id), method: :put do |f|%> + <%= form_with model: @order_item, url: order_items_update_path(:order_item_id => order_item), method: :put do |f|%> <%= f.label :quantity%> <%= f.select :quantity, (1..order_item.product.inventory) %> - <%= f.submit "Edit" %> + <%= f.submit "Update" %> <% end %> From e7f38fbc7b079871ed13337599720299f82688cf Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Tue, 23 Oct 2018 20:38:17 -0700 Subject: [PATCH 107/178] tests for models/order_test with all passing. --- test/models/order_test.rb | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/test/models/order_test.rb b/test/models/order_test.rb index 71dcab3a87..751ce1342a 100644 --- a/test/models/order_test.rb +++ b/test/models/order_test.rb @@ -3,6 +3,7 @@ describe Order do let(:order) { Order.new } let(:order1) { orders(:order1) } + let(:order2) { orders(:order2) } describe 'relations' do it 'has many order items' do @@ -21,7 +22,40 @@ # TODO: decide/add more validations+testing (cc info, mailing address, etc) describe 'validations' do it "must be valid" do - value(order).must_be :valid? + expect(order2).must_be :valid? end + + it 'must have an email address' do + expect(order2.guest_email).must_equal 'me@wnbc.net' + # test passes as long as it matches what's in the fixtures. Is this a good test? + expect(order2).must_respond_to :guest_email + + end + + it 'has a name corresponding with the credit card' do + expect(order2.guest_cc_name).must_equal 'Person Moo' + end + + it 'has a credit card number with 16 digits' do + expect(order2.guest_cc_num).must_equal "0987654321098765" + + expect(order2.guest_cc_num.length).must_equal 16 + end + + it 'has a three-digit CVV number' do + expect(order2.guest_cc_cvv_code).must_equal "321" + + expect(order2.guest_cc_cvv_code.length).must_equal 3 + end + + it 'must have a 5 digit zip code' do + expect(order2.guest_cc_zip).must_equal "11238" + + expect(order2.guest_cc_zip.length).must_equal 5 + end + + # it 'rejects an invalid order' do + # invalid_order = Order.new + # end end end From 514a1aad71c1ef5235660c8f62ecbcceb8fc5324 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Tue, 23 Oct 2018 21:06:57 -0700 Subject: [PATCH 108/178] added subtotal and total model methods in order_item and order, respectively --- app/models/order.rb | 22 ++++++++++------------ app/models/order_item.rb | 29 +++-------------------------- app/views/orders/index.html.erb | 25 ++++++++++++++++++++----- 3 files changed, 33 insertions(+), 43 deletions(-) diff --git a/app/models/order.rb b/app/models/order.rb index 513d3e454c..7cde1c34f6 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -5,22 +5,20 @@ class Order < ApplicationRecord # QUESTION: fulfillment_status --> check to make sure that statuses are one of the allowed ones (ex: pending, completed, paid, etc.) - # - # def total_price - # total = 0 - # self. - # end + def order_total() + total = self.order_items.sum do |order_item| + order_item.order_item_subtotal() + end - private - def set_fullfillment_status - self.order_status_id = 1 + return total end - # def update_subtotal - # self[:subtotal] = subtotal + + private + # QUESTION: what does this do? is this old? + # def set_fullfillment_status + # self.order_status_id = 1 # end end - -# The set_order_status gets fired when the order is created and sets the fullfillment_status_id column to 1 (in progress). The update_subtotal function is called during save and sums up our order item's total cost and stores it in the subtotal field. The subtotal function actually overrides the field named subtotal, so calling order.subtotal calls the function. You can still access the field internally by calling self[:subtotal], so we use that to update the field. This allows us to dynamically update subtotal as needed. A side effect of this is user can't write any arbitrary value to the subtotal field. diff --git a/app/models/order_item.rb b/app/models/order_item.rb index ad16334e8c..94a9b612ae 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -5,34 +5,11 @@ class OrderItem < ApplicationRecord validates :quantity, presence: true, numericality: { greater_than: 0, only_integer: true } - # validate :product_present - # validate :order_present - # TODO: change from true/false to pending etc. [this will cause errors i think until we can coordinate it] validates :shipped, inclusion: { in: [true, false] } - - # NOTE: The two lines below made the model tests fail - # validate :product_present - # validate :order_present - - # def self.add_order_item_to_order(order_item) - # if order_item.order_id.nil? || order_item.order_id.empty? - # new_order = Order.new() - # return new_order.id - # - # elsif - # ongoing_order = Order.find_by(id: order_item.order_id) - # return ongoing_order.id - # else - # # TODO: what if not found? - # puts "oh no" - # end - # end - - - # def total_price - # end - +def order_item_subtotal() + return self.quantity * self.product.price +end end diff --git a/app/views/orders/index.html.erb b/app/views/orders/index.html.erb index e31fc2fedf..7ebf6f0a73 100644 --- a/app/views/orders/index.html.erb +++ b/app/views/orders/index.html.erb @@ -14,10 +14,13 @@ Unit Price - Quantity + Current Quantity - Edit/Delete? + Update Quantity + + + Remove Subtotal @@ -29,7 +32,7 @@ <% @orders.order_items.each do |order_item| %> - + img <%= link_to order_item.product.name, product_path(order_item.product.id) %> @@ -57,13 +60,25 @@ - Subtotal + <%= order_item.order_item_subtotal %> <% end %> - TOTAL + + + + + + + + + + + + + TOTAL: <%= @orders.order_total %> From 0e9589cc6daec31b333c6e0482dd50be0fb986d7 Mon Sep 17 00:00:00 2001 From: kangazoom Date: Tue, 23 Oct 2018 21:56:51 -0700 Subject: [PATCH 109/178] formatted money + edited subtotal and total model methods + created one to turn ints to fs --- app/models/application_record.rb | 6 ++++++ app/models/order.rb | 4 ++-- app/models/order_item.rb | 9 ++++++--- app/models/product.rb | 7 ++++++- app/views/orders/index.html.erb | 6 +++--- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/app/models/application_record.rb b/app/models/application_record.rb index bec12ae5a9..4cde8220a4 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -2,4 +2,10 @@ class ApplicationRecord < ActiveRecord::Base self.abstract_class = true # QUESTION: overall --> protect from forgery?? how to secure user input? + + + # QUESTION: should this instead be in a view helper? + def convert_int_to_f(int) + return int/100.to_f + end end diff --git a/app/models/order.rb b/app/models/order.rb index 7cde1c34f6..0fd7e1d5fb 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -7,11 +7,11 @@ class Order < ApplicationRecord def order_total() - total = self.order_items.sum do |order_item| + total_as_float = self.order_items.sum do |order_item| order_item.order_item_subtotal() end - return total + return total_as_float end diff --git a/app/models/order_item.rb b/app/models/order_item.rb index 94a9b612ae..d432fa6b66 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -8,8 +8,11 @@ class OrderItem < ApplicationRecord # TODO: change from true/false to pending etc. [this will cause errors i think until we can coordinate it] validates :shipped, inclusion: { in: [true, false] } -def order_item_subtotal() - return self.quantity * self.product.price -end + def order_item_subtotal() + price = convert_int_to_f(self.product.price) + quantity = self.quantity + + return price * quantity + end end diff --git a/app/models/product.rb b/app/models/product.rb index bb6dde3e0d..70f4ba7310 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -12,7 +12,7 @@ class Product < ApplicationRecord default_scope { where(status: true) } - # QUESTION: user will likely input a float --> so we will need to change it into an int b/c it'll be stored as an int in the db + # QUESTION: how to handle invalid photo URLS since that will 'show up' ?? # QUESTION: status default --> true?? to show it's automatically active? @@ -33,4 +33,9 @@ class Product < ApplicationRecord # end # end + def price_int_to_float() + return convert_int_to_f(self.price) + end + + end diff --git a/app/views/orders/index.html.erb b/app/views/orders/index.html.erb index 7ebf6f0a73..54c97981f6 100644 --- a/app/views/orders/index.html.erb +++ b/app/views/orders/index.html.erb @@ -39,7 +39,7 @@ - <%= order_item.product.price %> + <%= number_to_currency((order_item.product.price_int_to_float), precision: 2) %> <%= order_item.quantity %> @@ -60,7 +60,7 @@ - <%= order_item.order_item_subtotal %> + <%= number_to_currency(order_item.order_item_subtotal, precision: 2) %> <% end %> @@ -78,7 +78,7 @@ - TOTAL: <%= @orders.order_total %> + TOTAL: <%= number_to_currency(@orders.order_total, precision: 2) %> From c21278acd183ac1aeecd90ad4c50efcab0c74c4b Mon Sep 17 00:00:00 2001 From: kangazoom Date: Tue, 23 Oct 2018 22:09:35 -0700 Subject: [PATCH 110/178] added if/else view logic based on whether cart is empty --- app/models/order.rb | 4 ++++ app/views/orders/index.html.erb | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/models/order.rb b/app/models/order.rb index 0fd7e1d5fb..709b828cad 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -6,6 +6,10 @@ class Order < ApplicationRecord # QUESTION: fulfillment_status --> check to make sure that statuses are one of the allowed ones (ex: pending, completed, paid, etc.) + def empty_cart?() + return self.order_items.nil? || self.order_items.empty? + end + def order_total() total_as_float = self.order_items.sum do |order_item| order_item.order_item_subtotal() diff --git a/app/views/orders/index.html.erb b/app/views/orders/index.html.erb index 54c97981f6..a72ca531d4 100644 --- a/app/views/orders/index.html.erb +++ b/app/views/orders/index.html.erb @@ -1,5 +1,10 @@ -

Orders Index page

+

Orders

+ +<% if @orders.empty_cart? %> +

Cart is empty!

+ +<% else %>

Products

@@ -83,3 +88,13 @@
+ +
+ <%= link_to 'Proceed to Checkout', new_order_path %> +
+ +
+ Remove all items / Clear Cart? +
+ +<% end %> From aaa52bd8ae20842d512e23ae65ff4ce3b1b1a6bf Mon Sep 17 00:00:00 2001 From: kangazoom Date: Wed, 24 Oct 2018 00:14:27 -0700 Subject: [PATCH 111/178] updated empty cart logic in view --- app/views/orders/index.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/orders/index.html.erb b/app/views/orders/index.html.erb index a72ca531d4..457620c16c 100644 --- a/app/views/orders/index.html.erb +++ b/app/views/orders/index.html.erb @@ -1,7 +1,7 @@

Orders

-<% if @orders.empty_cart? %> +<% if !@orders || @orders.empty_cart? %>

Cart is empty!

<% else %> From 28cceae6b23d60b544efc0296e1572352bacd28b Mon Sep 17 00:00:00 2001 From: Dionisia Montanez Date: Wed, 24 Oct 2018 09:23:37 -0700 Subject: [PATCH 112/178] css stuff --- app/assets/stylesheets/application.scss | 71 ++++++++++++++++++++----- app/views/layouts/application.html.erb | 13 ++--- 2 files changed, 64 insertions(+), 20 deletions(-) diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index f1f0d2719a..8b8b8d9f8c 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -27,44 +27,88 @@ font-family: 'Quicksand', sans-serif; } +main { + padding: 2rem; +} + li { list-style: none; } .header{ - background-image: url('http://www.printawallpaper.com/upload/designs/red_diamond_detail.jpg'); - background-attachment: fixed; - background-size: cover; - background-position: center; + background-color: #FFFFFF; + // background-attachment: fixed; + // background-size: cover; + // background-position: center; font-family: 'Love Ya Like A Sister', cursive; + padding: 2rem 2rem .1rem 2rem; } +#logo-hover:hover a { + color:#EA1300; +} + .logo { - text-align: center; + text-align: left; font-weight: 600; - font-size: 10em; + font-size: 7em; letter-spacing: .2em; + margin-bottom: .4em; +} + +.little-header { + font-size: 50px; + color: #292F36; } +// .logo:hover { +// text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; +// } .nav-app-header { display: flex; - justify-content: space-between; + justify-content: flex-end; flex-direction: row; } -a:visited{ - color: #F7EBE8; +.app-header__nav_item { + padding-left: 20px; } -a:link{ - color: #F7EBE8; + +a:visited { + color: #E54B4B; + } -a:hover{ +a:link { color: #E54B4B; + +} +a:hover { + color: #292F36; + transition: .6s; text-decoration: none; } +#session { + background-color: #FFFFFF; +} + +#session:hover { + color: #292F36; + border: #292F36 solid 1px; +} + +.btn-primary { + background-color: #EA1300; + border: 1px #E54B4B solid; +} + +.btn-primary:hover { + background-color: #EA1300; + border: 1px #EA1300 solid; +} + .dropdown { position: relative; display: inline-block; @@ -73,14 +117,13 @@ a:hover{ .dropdown-content { display: none; position: absolute; - background-color: #292F36; + background-color: #FFF; min-width: 130px; box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); padding: 12px 36px; z-index: 1; font-weight: bold; white-space: nowrap; - } .dropdown:hover .dropdown-content { diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 4d34b1f00b..9531b50478 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -12,9 +12,10 @@
-

- <%= link_to "REDSY", root_path %> -

+

+ <%= link_to "REDSY", root_path %>...all things red +

+