diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..7bf3d5c --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,17 @@ +AllCops: + Exclude: + - bin/**/* + - db/**/* + - spec/**/**/**/* + - vendor/**/* + NewCops: enable + SuggestExtensions: false + +Style/Documentation: + Enabled: false + +Metrics/MethodLength: + Max: 20 + +Metrics/AbcSize: + Max: 20 \ No newline at end of file diff --git a/Gemfile b/Gemfile index e58cffc..29edf32 100644 --- a/Gemfile +++ b/Gemfile @@ -1,19 +1,24 @@ -source "https://rubygems.org" +# frozen_string_literal: true + +source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby "3.2.1" +ruby '3.2.1' -gem "rails", "~> 7.0.4", ">= 7.0.4.2" -gem "mysql2", "~> 0.5" -gem "puma", "~> 5.0" -gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] -gem "bootsnap", require: false +gem 'bootsnap', require: false +gem 'flatten' +gem 'mysql2', '~> 0.5' +gem 'puma', '~> 5.0' +gem 'rails', '~> 7.0.4', '>= 7.0.4.2' +gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] group :development, :test do - gem "debug", platforms: %i[ mri mingw x64_mingw ] - gem "rspec-rails" + gem 'debug', platforms: %i[mri mingw x64_mingw] + gem 'pry-rails' + gem 'rspec-rails' + gem 'shoulda-matchers' end group :development do - gem 'pry' + gem 'rubocop', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 6d1f2f6..2a45022 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -66,6 +66,7 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) + ast (2.4.2) bootsnap (1.16.0) msgpack (~> 1.2) builder (3.2.4) @@ -76,10 +77,12 @@ GEM debug (1.7.1) diff-lcs (1.5.0) erubi (1.12.0) + flatten (0.2.0) globalid (1.1.0) activesupport (>= 5.0) i18n (1.12.0) concurrent-ruby (~> 1.0) + json (2.6.3) loofah (2.19.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) @@ -106,9 +109,14 @@ GEM nio4r (2.5.8) nokogiri (1.14.2-x86_64-darwin) racc (~> 1.4) + parallel (1.22.1) + parser (3.2.1.0) + ast (~> 2.4.1) pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) + pry-rails (0.3.9) + pry (>= 0.10.4) puma (5.6.5) nio4r (~> 2.0) racc (1.6.2) @@ -141,7 +149,10 @@ GEM rake (>= 12.2) thor (~> 1.0) zeitwerk (~> 2.5) + rainbow (3.1.1) rake (13.0.6) + regexp_parser (2.7.0) + rexml (3.2.5) rspec-core (3.12.1) rspec-support (~> 3.12.0) rspec-expectations (3.12.2) @@ -159,10 +170,26 @@ GEM rspec-mocks (~> 3.11) rspec-support (~> 3.11) rspec-support (3.12.0) + rubocop (1.45.1) + json (~> 2.3) + parallel (~> 1.10) + parser (>= 3.2.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.24.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.26.0) + parser (>= 3.2.1.0) + ruby-progressbar (1.11.0) + shoulda-matchers (5.3.0) + activesupport (>= 5.2.0) thor (1.2.1) timeout (0.3.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) + unicode-display_width (2.4.2) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -174,11 +201,14 @@ PLATFORMS DEPENDENCIES bootsnap debug + flatten mysql2 (~> 0.5) - pry + pry-rails puma (~> 5.0) rails (~> 7.0.4, >= 7.0.4.2) rspec-rails + rubocop + shoulda-matchers tzinfo-data RUBY VERSION diff --git a/Rakefile b/Rakefile index 9a5ea73..488c551 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,8 @@ +# frozen_string_literal: true + # 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" +require_relative 'config/application' Rails.application.load_tasks diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb index d672697..9aec230 100644 --- a/app/channels/application_cable/channel.rb +++ b/app/channels/application_cable/channel.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ApplicationCable class Channel < ActionCable::Channel::Base end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb index 0ff5442..8d6c2a1 100644 --- a/app/channels/application_cable/connection.rb +++ b/app/channels/application_cable/connection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ApplicationCable class Connection < ActionCable::Connection::Base end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4ac8823..13c271f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + class ApplicationController < ActionController::API end diff --git a/app/controllers/reservations_controller.rb b/app/controllers/reservations_controller.rb new file mode 100644 index 0000000..f30192b --- /dev/null +++ b/app/controllers/reservations_controller.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +class ReservationsController < ApplicationController + LOOKUP_ITEMS = YAML.load_file('lib/lookup.yml').deep_symbolize_keys + + def create + data = parse_data(JSON.parse(request.body.read).smash) + + guest = create_or_update_guest(data[:guest]) + + unless guest.valid? + render json: { success: false, errors: guest.errors } + return + end + + reservation = create_or_update_reservation(data[:reservation], guest.id) + + unless reservation.valid? + render json: { success: false, errors: reservation.errors } + return + end + + render json: { success: true, guest:, reservation: } + end + + private + + def parse_data(data) + guest = {} + reservation = {} + + data.each do |key, value| + record = LOOKUP_ITEMS[key.to_sym] + next if record.blank? + + reservation[record[:property].to_sym] = value if record[:model] == 'reservation' + guest[record[:property].to_sym] = value if record[:model] == 'guest' + end + + reservation[:guest_id] = guest[:id] + { reservation:, guest: } + end + + def create_or_update_guest(data) + Guest.upsert(data) + Guest.find_by(email: data[:email]) + end + + def create_or_update_reservation(data, guest_id) + data[:guest_id] = guest_id + Reservation.upsert(data) + Reservation.find_by(code: data[:code]) + end +end diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb index d394c3d..bef3959 100644 --- a/app/jobs/application_job.rb +++ b/app/jobs/application_job.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ApplicationJob < ActiveJob::Base # Automatically retry jobs that encountered a deadlock # retry_on ActiveRecord::Deadlocked diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 3c34c81..d84cb6e 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,4 +1,6 @@ +# frozen_string_literal: true + class ApplicationMailer < ActionMailer::Base - default from: "from@example.com" - layout "mailer" + default from: 'from@example.com' + layout 'mailer' end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index b63caeb..08dc537 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ApplicationRecord < ActiveRecord::Base primary_abstract_class end diff --git a/app/models/guest.rb b/app/models/guest.rb new file mode 100644 index 0000000..2660ffc --- /dev/null +++ b/app/models/guest.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class Guest < ApplicationRecord + has_many :reservations + + validates :first_name, + :last_name, + :phone, + :email, presence: true + + validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }, uniqueness: true +end diff --git a/app/models/reservation.rb b/app/models/reservation.rb new file mode 100644 index 0000000..509abca --- /dev/null +++ b/app/models/reservation.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class Reservation < ApplicationRecord + belongs_to :guest + + validates :code, + :date_start, + :date_end, + :number_adults, + :number_children, + :number_infants, + :amount_payout, + :amount_security, + :currency, + :status, presence: true + + validates :code, uniqueness: true +end diff --git a/bin/bundle b/bin/bundle index ee73929..3cb2676 100755 --- a/bin/bundle +++ b/bin/bundle @@ -8,46 +8,46 @@ # this file is here to facilitate running it. # -require "rubygems" +require 'rubygems' m = Module.new do module_function def invoked_as_script? - File.expand_path($0) == File.expand_path(__FILE__) + File.expand_path($PROGRAM_NAME) == File.expand_path(__FILE__) end def env_var_version - ENV["BUNDLER_VERSION"] + ENV['BUNDLER_VERSION'] end def cli_arg_version return unless invoked_as_script? # don't want to hijack other binstubs - return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + return unless 'update'.start_with?(ARGV.first || ' ') # must be running `bundle update` + bundler_version = nil update_index = nil ARGV.each_with_index do |a, i| - if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN - bundler_version = a - end + bundler_version = a if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ - bundler_version = $1 + + bundler_version = Regexp.last_match(1) update_index = i end bundler_version end def gemfile - gemfile = ENV["BUNDLE_GEMFILE"] + gemfile = ENV['BUNDLE_GEMFILE'] return gemfile if gemfile && !gemfile.empty? - File.expand_path("../Gemfile", __dir__) + File.expand_path('../Gemfile', __dir__) end def lockfile lockfile = case File.basename(gemfile) - when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + when 'gems.rb' then gemfile.sub(/\.rb$/, gemfile) else "#{gemfile}.lock" end File.expand_path(lockfile) @@ -55,8 +55,10 @@ m = Module.new do def lockfile_version return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) end @@ -76,20 +78,24 @@ m = Module.new do end def load_bundler! - ENV["BUNDLE_GEMFILE"] ||= gemfile + ENV['BUNDLE_GEMFILE'] ||= gemfile activate_bundler end def activate_bundler gem_error = activation_error_handling do - gem "bundler", bundler_requirement + gem 'bundler', bundler_requirement end return if gem_error.nil? + require_error = activation_error_handling do - require "bundler/version" + require 'bundler/version' + end + if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + return end - return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" exit 42 end @@ -104,6 +110,4 @@ end m.load_bundler! -if m.invoked_as_script? - load Gem.bin_path("bundler", "bundle") -end +load Gem.bin_path('bundler', 'bundle') if m.invoked_as_script? diff --git a/bin/rails b/bin/rails index efc0377..a31728a 100755 --- a/bin/rails +++ b/bin/rails @@ -1,4 +1,6 @@ #!/usr/bin/env ruby -APP_PATH = File.expand_path("../config/application", __dir__) -require_relative "../config/boot" -require "rails/commands" +# frozen_string_literal: true + +APP_PATH = File.expand_path('../config/application', __dir__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/bin/rake b/bin/rake index 4fbf10b..c199955 100755 --- a/bin/rake +++ b/bin/rake @@ -1,4 +1,6 @@ #!/usr/bin/env ruby -require_relative "../config/boot" -require "rake" +# frozen_string_literal: true + +require_relative '../config/boot' +require 'rake' Rake.application.run diff --git a/bin/setup b/bin/setup index ec47b79..516b651 100755 --- a/bin/setup +++ b/bin/setup @@ -1,8 +1,10 @@ #!/usr/bin/env ruby -require "fileutils" +# frozen_string_literal: true + +require 'fileutils' # path to your application root. -APP_ROOT = File.expand_path("..", __dir__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -13,9 +15,9 @@ FileUtils.chdir APP_ROOT do # This script is idempotent, so that you can run it at any time and get an expectable outcome. # Add necessary setup steps to this file. - puts "== Installing dependencies ==" - system! "gem install bundler --conservative" - system("bundle check") || system!("bundle install") + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') # puts "\n== Copying sample files ==" # unless File.exist?("config/database.yml") @@ -23,11 +25,11 @@ FileUtils.chdir APP_ROOT do # end puts "\n== Preparing database ==" - system! "bin/rails db:prepare" + system! 'bin/rails db:prepare' puts "\n== Removing old logs and tempfiles ==" - system! "bin/rails log:clear tmp:clear" + system! 'bin/rails log:clear tmp:clear' puts "\n== Restarting application server ==" - system! "bin/rails restart" + system! 'bin/rails restart' end diff --git a/config.ru b/config.ru index 4a3c09a..6dc8321 100644 --- a/config.ru +++ b/config.ru @@ -1,6 +1,8 @@ +# frozen_string_literal: true + # This file is used by Rack-based servers to start the application. -require_relative "config/environment" +require_relative 'config/environment' run Rails.application Rails.application.load_server diff --git a/config/application.rb b/config/application.rb index 147f25a..8cbf76f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,6 +1,8 @@ -require_relative "boot" +# frozen_string_literal: true -require "rails/all" +require_relative 'boot' + +require 'rails/all' # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. diff --git a/config/boot.rb b/config/boot.rb index 988a5dd..c04863f 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,4 +1,6 @@ -ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) +# frozen_string_literal: true -require "bundler/setup" # Set up gems listed in the Gemfile. -require "bootsnap/setup" # Speed up boot time by caching expensive operations. +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/environment.rb b/config/environment.rb index cac5315..d5abe55 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + # Load the Rails application. -require_relative "application" +require_relative 'application' # Initialize the Rails application. Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb index 3d6b073..2ee6f75 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,4 +1,6 @@ -require "active_support/core_ext/integer/time" +# frozen_string_literal: true + +require 'active_support/core_ext/integer/time' Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -19,10 +21,10 @@ # Enable/disable caching. By default caching is disabled. # Run rails dev:cache to toggle caching. - if Rails.root.join("tmp/caching-dev.txt").exist? + if Rails.root.join('tmp/caching-dev.txt').exist? config.cache_store = :memory_store config.public_file_server.headers = { - "Cache-Control" => "public, max-age=#{2.days.to_i}" + 'Cache-Control' => "public, max-age=#{2.days.to_i}" } else config.action_controller.perform_caching = false @@ -53,7 +55,6 @@ # Highlight code that triggered database queries in logs. config.active_record.verbose_query_logs = true - # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true diff --git a/config/environments/production.rb b/config/environments/production.rb index cb0b679..99eddd5 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,4 +1,6 @@ -require "active_support/core_ext/integer/time" +# frozen_string_literal: true + +require 'active_support/core_ext/integer/time' Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -13,7 +15,7 @@ config.eager_load = true # Full error reports are disabled and caching is turned on. - config.consider_all_requests_local = false + config.consider_all_requests_local = false # 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). @@ -21,7 +23,7 @@ # 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? + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.asset_host = "http://assets.example.com" @@ -46,7 +48,7 @@ config.log_level = :info # Prepend all log lines with the following tags. - config.log_tags = [ :request_id ] + config.log_tags = [:request_id] # Use a different cache store in production. # config.cache_store = :mem_cache_store @@ -69,14 +71,14 @@ config.active_support.report_deprecations = false # Use default logging formatter so that PID and timestamp are not suppressed. - config.log_formatter = ::Logger::Formatter.new + 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) + if ENV['RAILS_LOG_TO_STDOUT'].present? + logger = ActiveSupport::Logger.new($stdout) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end diff --git a/config/environments/test.rb b/config/environments/test.rb index 6ea4d1e..8f3f63c 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,4 +1,6 @@ -require "active_support/core_ext/integer/time" +# frozen_string_literal: true + +require 'active_support/core_ext/integer/time' # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that @@ -14,12 +16,12 @@ # Eager loading loads your whole application. When running a single test locally, # this probably isn't necessary. It's a good idea to do in a continuous integration # system, or in some way before deploying your code. - config.eager_load = ENV["CI"].present? + config.eager_load = ENV['CI'].present? # 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}" + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" } # Show full error reports and disable caching. diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index e5a82f1..38d411f 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Avoid CORS issues when API is called from the frontend app. diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb index adc6568..3df77c5 100644 --- a/config/initializers/filter_parameter_logging.rb +++ b/config/initializers/filter_parameter_logging.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Configure parameters to be filtered from the log file. Use this to limit dissemination of # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported # notations and behaviors. -Rails.application.config.filter_parameters += [ - :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn +Rails.application.config.filter_parameters += %i[ + passw secret token _key crypt salt certificate otp ssn ] diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 3860f65..6c78420 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections diff --git a/config/puma.rb b/config/puma.rb index daaf036..1713441 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,28 +1,30 @@ +# frozen_string_literal: true + # 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. # -max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } -min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } +max_threads_count = ENV.fetch('RAILS_MAX_THREADS', 5) +min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count } threads min_threads_count, max_threads_count # Specifies the `worker_timeout` threshold that Puma will use to wait before # terminating a worker in development environments. # -worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" +worker_timeout 3600 if ENV.fetch('RAILS_ENV', 'development') == 'development' # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # -port ENV.fetch("PORT") { 3000 } +port ENV.fetch('PORT', 3000) # Specifies the `environment` that Puma will run in. # -environment ENV.fetch("RAILS_ENV") { "development" } +environment ENV.fetch('RAILS_ENV', 'development') # Specifies the `pidfile` that Puma will use. -pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } +pidfile ENV.fetch('PIDFILE', 'tmp/pids/server.pid') # Specifies the number of `workers` to boot in clustered mode. # Workers are forked web server processes. If using threads and workers together diff --git a/config/routes.rb b/config/routes.rb index 262ffd5..8374cd0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,5 @@ -Rails.application.routes.draw do - # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html +# frozen_string_literal: true - # Defines the root path route ("/") - # root "articles#index" +Rails.application.routes.draw do + post 'reservations/create' end diff --git a/db/migrate/20230218155553_create_guests.rb b/db/migrate/20230218155553_create_guests.rb new file mode 100644 index 0000000..e565adf --- /dev/null +++ b/db/migrate/20230218155553_create_guests.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class CreateGuests < ActiveRecord::Migration[7.0] + def change + create_table :guests do |t| + t.string :first_name + t.string :last_name + t.string :phone + t.string :email + + t.timestamps + end + end +end diff --git a/db/migrate/20230218160520_create_reservations.rb b/db/migrate/20230218160520_create_reservations.rb new file mode 100644 index 0000000..86e1cd3 --- /dev/null +++ b/db/migrate/20230218160520_create_reservations.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class CreateReservations < ActiveRecord::Migration[7.0] + def change + create_table :reservations do |t| + t.string :code + t.date :date_start + t.date :date_end + t.integer :number_adults + t.integer :number_children + t.integer :number_infants + t.float :amount_payout + t.float :amount_security + t.string :currency + t.string :status + + t.timestamps + end + end +end diff --git a/db/migrate/20230218161038_add_guest_to_reservations.rb b/db/migrate/20230218161038_add_guest_to_reservations.rb new file mode 100644 index 0000000..61f0966 --- /dev/null +++ b/db/migrate/20230218161038_add_guest_to_reservations.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddGuestToReservations < ActiveRecord::Migration[7.0] + def change + add_reference :reservations, :guest, null: false, foreign_key: true + end +end diff --git a/db/migrate/20230219221143_add_index_to_guests.rb b/db/migrate/20230219221143_add_index_to_guests.rb new file mode 100644 index 0000000..987d437 --- /dev/null +++ b/db/migrate/20230219221143_add_index_to_guests.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddIndexToGuests < ActiveRecord::Migration[7.0] + def change + add_index :guests, :email, unique: true + end +end diff --git a/db/migrate/20230219221354_add_index_to_reservations.rb b/db/migrate/20230219221354_add_index_to_reservations.rb new file mode 100644 index 0000000..3da53ff --- /dev/null +++ b/db/migrate/20230219221354_add_index_to_reservations.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddIndexToReservations < ActiveRecord::Migration[7.0] + def change + add_index :reservations, :code, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..c63beb1 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,51 @@ +# 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. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[7.0].define(version: 2023_02_19_221354) do + create_table "guests", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.string "first_name" + t.string "last_name" + t.string "phone" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["email"], name: "index_guests_on_email", unique: true + end + + create_table "lookups", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.string "key" + t.string "model" + t.string "property" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "reservations", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.string "code" + t.date "date_start" + t.date "date_end" + t.integer "number_adults" + t.integer "number_children" + t.integer "number_infants" + t.float "amount_payout" + t.float "amount_security" + t.string "currency" + t.string "status" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "guest_id", null: false + t.index ["code"], name: "index_reservations_on_code", unique: true + t.index ["guest_id"], name: "index_reservations_on_guest_id" + end + + add_foreign_key "reservations", "guests" +end diff --git a/db/seeds.rb b/db/seeds.rb index bc25fce..0664d1b 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # 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 bin/rails db:seed command (or created alongside the database with db:setup). # diff --git a/lib/lookup.yml b/lib/lookup.yml new file mode 100644 index 0000000..3634aa5 --- /dev/null +++ b/lib/lookup.yml @@ -0,0 +1,79 @@ +--- +reservation_code: + model: reservation + property: code +start_date: + model: reservation + property: date_start +end_date: + model: reservation + property: date_end +adults: + model: reservation + property: number_adults +children: + model: reservation + property: number_children +infants: + model: reservation + property: number_infants +status: + model: reservation + property: status +first_name: + model: guest + property: first_name +last_name: + model: guest + property: last_name +phone: + model: guest + property: phone +email: + model: guest + property: email +currency: + model: reservation + property: currency +payout_price: + model: reservation + property: amount_payout +security_price: + model: reservation + property: amount_security +code: + model: reservation + property: code +expected_payout_amount: + model: reservation + property: amount_payout +number_of_adults: + model: reservation + property: number_adults +number_of_children: + model: reservation + property: number_children +number_of_infants: + model: reservation + property: number_infants +guest_email: + model: guest + property: email +guest_first_name: + model: guest + property: first_name +guest_last_name: + model: guest + property: last_name +guest_phone_numbers: + model: guest + property: phone +listing_security_price_accurate: + model: reservation + property: amount_security +host_currency: + model: reservation + property: currency +status_type: + model: reservation + property: status diff --git a/spec/controllers/reservations_controller_spec.rb b/spec/controllers/reservations_controller_spec.rb new file mode 100644 index 0000000..a7e8c40 --- /dev/null +++ b/spec/controllers/reservations_controller_spec.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ReservationsController, type: :controller do + describe 'POST #create' do + context 'with valid params' do + context 'using payload 1' do + let(:guest_attributes) do + { + first_name: 'Wayne', + last_name: 'Woodbridge', + phone: '639123456789', + email: 'wayne_woodbridge@bnb.com' + } + end + + let(:reservation_attributes) do + { + code: 'YYY12345678', + date_start: '2021-04-14'.to_date, + date_end: '2021-04-18'.to_date, + number_adults: 2, + number_children: 2, + number_infants: 0, + amount_payout: 4200.0, + amount_security: 500.0, + currency: 'AUD', + status: 'accepted' + } + end + + before do + post :create, body: file_fixture('payload-1.json').read + end + + specify { expect(response).to have_http_status(:ok) } + specify { expect(JSON.parse(response.body)['success']).to be_truthy } + + it 'creates the guest' do + guest = Guest.last + + guest_attributes.each do |key, value| + expect(guest[key]).to eq(value) + end + end + + it 'creates the reservation' do + reservation = Reservation.last + + reservation_attributes.each do |key, value| + expect(reservation[key]).to eq(value) + end + end + end + + context 'using payload 2' do + let(:guest_attributes) do + { + first_name: 'Wayne', + last_name: 'Woodbridge', + phone: "---\n- '639123456789'\n- '639123456789'\n", + email: 'wayne_woodbridge@bnb.com' + } + end + + let(:reservation_attributes) do + { + code: 'XXX12345678', + date_start: '2021-03-12'.to_date, + date_end: '2021-03-16'.to_date, + number_adults: 2, + number_children: 2, + number_infants: 0, + amount_payout: 3800.0, + amount_security: 500.0, + currency: 'AUD', + status: 'accepted' + } + end + + before do + post :create, body: file_fixture('payload-2.json').read + end + + specify { expect(response).to have_http_status(:ok) } + specify { expect(JSON.parse(response.body)['success']).to be_truthy } + + it 'creates the guest' do + guest = Guest.last + + guest_attributes.each do |key, value| + expect(guest[key]).to eq(value) + end + end + + it 'creates the reservation' do + reservation = Reservation.last + + reservation_attributes.each do |key, value| + expect(reservation[key]).to eq(value) + end + end + end + end + + context 'with invalid guest params' do + before do + post :create, body: file_fixture('invalid-guest-data.json').read + end + + specify { expect(response).to have_http_status(:ok) } + specify { expect(JSON.parse(response.body)['success']).to be_falsey } + end + + context 'with invalid reservation params' do + before do + post :create, body: file_fixture('invalid-reservation-data.json').read + end + + specify { expect(response).to have_http_status(:ok) } + specify { expect(JSON.parse(response.body)['success']).to be_falsey } + end + end +end diff --git a/spec/fixtures/files/invalid-guest-data.json b/spec/fixtures/files/invalid-guest-data.json new file mode 100644 index 0000000..e661928 --- /dev/null +++ b/spec/fixtures/files/invalid-guest-data.json @@ -0,0 +1,21 @@ +{ + "reservation_code": "YYY12345678", + "start_date": "2021-04-14", + "end_date": "2021-04-18", + "nights": 4, + "guests": 4, + "adults": 2, + "children": 2, + "infants": 0, + "status": "accepted", + "guest": { + "first_name": "", + "last_name": "Woodbridge", + "phone": "639123456789", + "email": "wayne_woodbridge@bnb.com" + }, + "currency": "AUD", + "payout_price": "4200.00", + "security_price": "500", + "total_price": "4700.00" +} \ No newline at end of file diff --git a/spec/fixtures/files/invalid-reservation-data.json b/spec/fixtures/files/invalid-reservation-data.json new file mode 100644 index 0000000..8f21c35 --- /dev/null +++ b/spec/fixtures/files/invalid-reservation-data.json @@ -0,0 +1,27 @@ +{ + "reservation": { + "code": "XXX12345678", + "start_date": "2021-03-12", + "end_date": "2021-03-16", + "expected_payout_amount": "", + "guest_details": { + "localized_description": "4 guests", + "number_of_adults": 2, + "number_of_children": 2, + "number_of_infants": 0 + }, + "guest_email": "wayne_woodbridge@bnb.com", + "guest_first_name": "Wayne", + "guest_last_name": "Woodbridge", + "guest_phone_numbers": [ + "639123456789", + "639123456789" + ], + "listing_security_price_accurate": "500.00", + "host_currency": "AUD", + "nights": 4, + "number_of_guests": 4, + "status_type": "accepted", + "total_paid_amount_accurate": "4300.00" + } +} \ No newline at end of file diff --git a/spec/fixtures/files/payload-1.json b/spec/fixtures/files/payload-1.json new file mode 100644 index 0000000..ccb9deb --- /dev/null +++ b/spec/fixtures/files/payload-1.json @@ -0,0 +1,21 @@ +{ + "reservation_code": "YYY12345678", + "start_date": "2021-04-14", + "end_date": "2021-04-18", + "nights": 4, + "guests": 4, + "adults": 2, + "children": 2, + "infants": 0, + "status": "accepted", + "guest": { + "first_name": "Wayne", + "last_name": "Woodbridge", + "phone": "639123456789", + "email": "wayne_woodbridge@bnb.com" + }, + "currency": "AUD", + "payout_price": "4200.00", + "security_price": "500", + "total_price": "4700.00" +} \ No newline at end of file diff --git a/spec/fixtures/files/payload-2.json b/spec/fixtures/files/payload-2.json new file mode 100644 index 0000000..7843704 --- /dev/null +++ b/spec/fixtures/files/payload-2.json @@ -0,0 +1,27 @@ +{ + "reservation": { + "code": "XXX12345678", + "start_date": "2021-03-12", + "end_date": "2021-03-16", + "expected_payout_amount": "3800.00", + "guest_details": { + "localized_description": "4 guests", + "number_of_adults": 2, + "number_of_children": 2, + "number_of_infants": 0 + }, + "guest_email": "wayne_woodbridge@bnb.com", + "guest_first_name": "Wayne", + "guest_last_name": "Woodbridge", + "guest_phone_numbers": [ + "639123456789", + "639123456789" + ], + "listing_security_price_accurate": "500.00", + "host_currency": "AUD", + "nights": 4, + "number_of_guests": 4, + "status_type": "accepted", + "total_paid_amount_accurate": "4300.00" + } +} \ No newline at end of file diff --git a/spec/models/guest_spec.rb b/spec/models/guest_spec.rb new file mode 100644 index 0000000..be2847e --- /dev/null +++ b/spec/models/guest_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Guest, type: :model do + describe 'associations' do + it { should have_many(:reservations) } + end + + describe 'validations' do + it { should validate_presence_of(:first_name) } + it { should validate_presence_of(:last_name) } + it { should validate_presence_of(:phone) } + it { should validate_presence_of(:email) } + end +end diff --git a/spec/models/reservation_spec.rb b/spec/models/reservation_spec.rb new file mode 100644 index 0000000..0358fbc --- /dev/null +++ b/spec/models/reservation_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Reservation, type: :model do + describe 'associations' do + it { should belong_to(:guest) } + end + + describe 'validations' do + it { should validate_presence_of(:code) } + it { should validate_presence_of(:date_start) } + it { should validate_presence_of(:date_end) } + it { should validate_presence_of(:number_adults) } + it { should validate_presence_of(:number_children) } + it { should validate_presence_of(:number_infants) } + it { should validate_presence_of(:amount_payout) } + it { should validate_presence_of(:amount_security) } + it { should validate_presence_of(:currency) } + it { should validate_presence_of(:status) } + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index a53bdba..2370b48 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + # This file is copied to spec/ when you run 'rails generate rspec:install' require 'spec_helper' ENV['RAILS_ENV'] ||= 'test' require_relative '../config/environment' # Prevent database truncation if the environment is production -abort("The Rails environment is running in production mode!") if Rails.env.production? +abort('The Rails environment is running in production mode!') if Rails.env.production? require 'rspec/rails' # Add additional requires below this line. Rails is not loaded until this point! @@ -29,9 +31,12 @@ rescue ActiveRecord::PendingMigrationError => e abort e.to_s.strip end + +Dir[Rails.root.join('spec/support/*.rb')].each { |path| require path } + RSpec.configure do |config| # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures - config.fixture_path = "#{::Rails.root}/spec/fixtures" + config.fixture_path = "#{Rails.root}/spec/fixtures" # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false @@ -61,3 +66,10 @@ # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") end + +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end +end diff --git a/spec/requests/reservations_spec.rb b/spec/requests/reservations_spec.rb new file mode 100644 index 0000000..3361936 --- /dev/null +++ b/spec/requests/reservations_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Reservations', type: :request do + describe 'POST /create' do + it 'returns http success' do + # post '/reservations/create' + # expect(response).to have_http_status(:success) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a0d4080..89f676c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by the `rails generate rspec:install` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # The generated `.rspec` file contains `--require spec_helper` which will cause @@ -44,51 +46,49 @@ # triggering implicit auto-inclusion in groups with matching metadata. config.shared_context_metadata_behavior = :apply_to_host_groups -# The settings below are suggested to provide a good initial experience -# with RSpec, but feel free to customize to your heart's content. -=begin - # This allows you to limit a spec run to individual examples or groups - # you care about by tagging them with `:focus` metadata. When nothing - # is tagged with `:focus`, all examples get run. RSpec also provides - # aliases for `it`, `describe`, and `context` that include `:focus` - # metadata: `fit`, `fdescribe` and `fcontext`, respectively. - config.filter_run_when_matching :focus - - # Allows RSpec to persist some state between runs in order to support - # the `--only-failures` and `--next-failure` CLI options. We recommend - # you configure your source control system to ignore this file. - config.example_status_persistence_file_path = "spec/examples.txt" - - # Limits the available syntax to the non-monkey patched syntax that is - # recommended. For more details, see: - # https://relishapp.com/rspec/rspec-core/docs/configuration/zero-monkey-patching-mode - config.disable_monkey_patching! - - # Many RSpec users commonly either run the entire suite or an individual - # file, and it's useful to allow more verbose output when running an - # individual spec file. - if config.files_to_run.one? - # Use the documentation formatter for detailed output, - # unless a formatter has already been configured - # (e.g. via a command-line flag). - config.default_formatter = "doc" - end - - # Print the 10 slowest examples and example groups at the - # end of the spec run, to help surface which specs are running - # particularly slow. - config.profile_examples = 10 - - # Run specs in random order to surface order dependencies. If you find an - # order dependency and want to debug it, you can fix the order by providing - # the seed, which is printed after each run. - # --seed 1234 - config.order = :random - - # Seed global randomization in this process using the `--seed` CLI option. - # Setting this allows you to use `--seed` to deterministically reproduce - # test failures related to randomization by passing the same `--seed` value - # as the one that triggered the failure. - Kernel.srand config.seed -=end + # The settings below are suggested to provide a good initial experience + # with RSpec, but feel free to customize to your heart's content. + # # This allows you to limit a spec run to individual examples or groups + # # you care about by tagging them with `:focus` metadata. When nothing + # # is tagged with `:focus`, all examples get run. RSpec also provides + # # aliases for `it`, `describe`, and `context` that include `:focus` + # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + # config.filter_run_when_matching :focus + # + # # Allows RSpec to persist some state between runs in order to support + # # the `--only-failures` and `--next-failure` CLI options. We recommend + # # you configure your source control system to ignore this file. + # config.example_status_persistence_file_path = "spec/examples.txt" + # + # # Limits the available syntax to the non-monkey patched syntax that is + # # recommended. For more details, see: + # # https://relishapp.com/rspec/rspec-core/docs/configuration/zero-monkey-patching-mode + # config.disable_monkey_patching! + # + # # Many RSpec users commonly either run the entire suite or an individual + # # file, and it's useful to allow more verbose output when running an + # # individual spec file. + # if config.files_to_run.one? + # # Use the documentation formatter for detailed output, + # # unless a formatter has already been configured + # # (e.g. via a command-line flag). + # config.default_formatter = "doc" + # end + # + # # Print the 10 slowest examples and example groups at the + # # end of the spec run, to help surface which specs are running + # # particularly slow. + # config.profile_examples = 10 + # + # # Run specs in random order to surface order dependencies. If you find an + # # order dependency and want to debug it, you can fix the order by providing + # # the seed, which is printed after each run. + # # --seed 1234 + # config.order = :random + # + # # Seed global randomization in this process using the `--seed` CLI option. + # # Setting this allows you to use `--seed` to deterministically reproduce + # # test failures related to randomization by passing the same `--seed` value + # # as the one that triggered the failure. + # Kernel.srand config.seed end diff --git a/test/channels/application_cable/connection_test.rb b/test/channels/application_cable/connection_test.rb index 800405f..4aee9b3 100644 --- a/test/channels/application_cable/connection_test.rb +++ b/test/channels/application_cable/connection_test.rb @@ -1,11 +1,15 @@ -require "test_helper" +# frozen_string_literal: true -class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase - # test "connects with cookies" do - # cookies.signed[:user_id] = 42 - # - # connect - # - # assert_equal connection.user_id, "42" - # end +require 'test_helper' + +module ApplicationCable + class ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end + end end diff --git a/test/test_helper.rb b/test/test_helper.rb index d713e37..0c92e8e 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,13 +1,17 @@ -ENV["RAILS_ENV"] ||= "test" -require_relative "../config/environment" -require "rails/test_help" +# frozen_string_literal: true -class ActiveSupport::TestCase - # Run tests in parallel with specified workers - parallelize(workers: :number_of_processors) +ENV['RAILS_ENV'] ||= 'test' +require_relative '../config/environment' +require 'rails/test_help' - # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. - fixtures :all +module ActiveSupport + class TestCase + # Run tests in parallel with specified workers + parallelize(workers: :number_of_processors) - # Add more helper methods to be used by all tests here... + # 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 end