diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 00000000..4f9cf4b9
Binary files /dev/null and b/.DS_Store differ
diff --git a/.gitignore b/.gitignore
index 59c74047..86980021 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
/tmp
/log
/public
+.byebug_history
diff --git a/.rspec b/.rspec
new file mode 100644
index 00000000..c99d2e73
--- /dev/null
+++ b/.rspec
@@ -0,0 +1 @@
+--require spec_helper
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 00000000..4079a696
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,15 @@
+require:
+ - rubocop-performance
+ - rubocop-rspec
+ - standard/cop/semantic_blocks
+
+inherit_mode:
+ merge:
+ - Exclude
+
+inherit_gem:
+ standard: config/base.yml
+
+AllCops:
+ Exclude:
+ - 'bin/**'
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
index e20b1260..b4d6d6e8 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,26 +1,44 @@
-source 'https://rubygems.org'
+source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
-ruby '2.6.3'
+ruby "2.6.3"
-gem 'rails', '~> 5.2.3'
-gem 'pg', '>= 0.18', '< 2.0'
-gem 'puma', '~> 3.11'
-gem 'bootsnap', '>= 1.1.0', require: false
+gem "rails", "~> 5.2.3"
+gem "pg", ">= 0.18", "< 2.0"
+gem "puma", "~> 3.11"
+gem "bootsnap", ">= 1.1.0", require: false
+gem "activerecord-import"
+gem "json-streamer"
+gem "dry-container"
+gem "redis"
group :development, :test do
+ gem "rubocop"
+ gem "rubocop-performance"
+ gem "rubocop-rspec"
+ gem "standard", "0.1.7"
+
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
- gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
+ gem "byebug", platforms: [:mri, :mingw, :x64_mingw]
+ gem "ruby-prof"
+ gem "meta_request"
+ gem "rails_panel"
+
+ gem "factory_bot_rails"
+ gem "database_cleaner"
+ gem "rspec-rails"
+ gem "rspec-benchmark"
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'
+ gem "web-console", ">= 3.3.0"
+ gem "listen", ">= 3.0.5", "< 3.2"
end
group :test do
+ gem "capybara"
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
-gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
+gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
diff --git a/Gemfile.lock b/Gemfile.lock
index fccf6f5f..1dc50566 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -33,6 +33,8 @@ GEM
activemodel (= 5.2.3)
activesupport (= 5.2.3)
arel (>= 9.0)
+ activerecord-import (1.0.2)
+ activerecord (>= 3.2)
activestorage (5.2.3)
actionpack (= 5.2.3)
activerecord (= 5.2.3)
@@ -42,20 +44,53 @@ GEM
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
+ addressable (2.6.0)
+ public_suffix (>= 2.0.2, < 4.0)
arel (9.0.0)
+ ast (2.4.0)
+ benchmark-malloc (0.1.0)
+ benchmark-perf (0.5.0)
+ benchmark-trend (0.3.0)
bindex (0.6.0)
bootsnap (1.4.2)
msgpack (~> 1.0)
builder (3.2.3)
byebug (11.0.1)
+ capybara (3.20.2)
+ addressable
+ mini_mime (>= 0.1.3)
+ nokogiri (~> 1.8)
+ rack (>= 1.6.0)
+ rack-test (>= 0.6.3)
+ regexp_parser (~> 1.2)
+ xpath (~> 3.2)
concurrent-ruby (1.1.5)
crass (1.0.4)
+ database_cleaner (1.7.0)
+ diff-lcs (1.3)
+ dry-configurable (0.8.3)
+ concurrent-ruby (~> 1.0)
+ dry-core (~> 0.4, >= 0.4.7)
+ dry-container (0.7.2)
+ concurrent-ruby (~> 1.0)
+ dry-configurable (~> 0.1, >= 0.1.3)
+ dry-core (0.4.8)
+ concurrent-ruby (~> 1.0)
erubi (1.8.0)
+ factory_bot (5.1.1)
+ activesupport (>= 4.2.0)
+ factory_bot_rails (5.1.1)
+ factory_bot (~> 5.1.0)
+ railties (>= 4.2.0)
ffi (1.10.0)
globalid (0.4.2)
activesupport (>= 4.2.0)
i18n (1.6.0)
concurrent-ruby (~> 1.0)
+ jaro_winkler (1.5.4)
+ json-stream (0.2.1)
+ json-streamer (2.1.0)
+ json-stream
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
@@ -67,6 +102,9 @@ GEM
mini_mime (>= 0.1.1)
marcel (0.3.3)
mimemagic (~> 0.3.2)
+ meta_request (0.7.2)
+ rack-contrib (>= 1.1, < 3)
+ railties (>= 3.0.0, < 7)
method_source (0.9.2)
mimemagic (0.3.3)
mini_mime (1.0.1)
@@ -76,9 +114,15 @@ GEM
nio4r (2.3.1)
nokogiri (1.10.2)
mini_portile2 (~> 2.4.0)
+ parallel (1.19.1)
+ parser (2.7.0.2)
+ ast (~> 2.4.0)
pg (1.1.4)
+ public_suffix (3.1.1)
puma (3.12.1)
rack (2.0.6)
+ rack-contrib (2.1.0)
+ rack (~> 2.0)
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (5.2.3)
@@ -99,16 +143,59 @@ GEM
nokogiri (>= 1.6)
rails-html-sanitizer (1.0.4)
loofah (~> 2.2, >= 2.2.2)
+ rails_panel (0.0.1)
railties (5.2.3)
actionpack (= 5.2.3)
activesupport (= 5.2.3)
method_source
rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0)
+ rainbow (3.0.0)
rake (12.3.2)
rb-fsevent (0.10.3)
rb-inotify (0.10.0)
ffi (~> 1.0)
+ redis (4.1.0)
+ regexp_parser (1.5.1)
+ rspec (3.9.0)
+ rspec-core (~> 3.9.0)
+ rspec-expectations (~> 3.9.0)
+ rspec-mocks (~> 3.9.0)
+ rspec-benchmark (0.5.1)
+ benchmark-malloc (~> 0.1.0)
+ benchmark-perf (~> 0.5.0)
+ benchmark-trend (~> 0.3.0)
+ rspec (>= 3.0.0, < 4.0.0)
+ rspec-core (3.9.1)
+ rspec-support (~> 3.9.1)
+ rspec-expectations (3.9.0)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.9.0)
+ rspec-mocks (3.9.1)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.9.0)
+ rspec-rails (3.9.0)
+ actionpack (>= 3.0)
+ activesupport (>= 3.0)
+ railties (>= 3.0)
+ rspec-core (~> 3.9.0)
+ rspec-expectations (~> 3.9.0)
+ rspec-mocks (~> 3.9.0)
+ rspec-support (~> 3.9.0)
+ rspec-support (3.9.2)
+ rubocop (0.77.0)
+ jaro_winkler (~> 1.5.1)
+ parallel (~> 1.10)
+ parser (>= 2.6)
+ rainbow (>= 2.2.2, < 4.0)
+ ruby-progressbar (~> 1.7)
+ unicode-display_width (>= 1.4.0, < 1.7)
+ rubocop-performance (1.5.2)
+ rubocop (>= 0.71.0)
+ rubocop-rspec (1.38.1)
+ rubocop (>= 0.68.1)
+ ruby-prof (1.2.0)
+ ruby-progressbar (1.10.1)
ruby_dep (1.5.0)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
@@ -117,10 +204,14 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
+ standard (0.1.7)
+ rubocop (~> 0.77.0)
+ rubocop-performance (~> 1.5.1)
thor (0.20.3)
thread_safe (0.3.6)
tzinfo (1.2.5)
thread_safe (~> 0.1)
+ unicode-display_width (1.6.1)
web-console (3.7.0)
actionview (>= 5.0)
activemodel (>= 5.0)
@@ -129,17 +220,35 @@ GEM
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
+ activerecord-import
bootsnap (>= 1.1.0)
byebug
+ capybara
+ database_cleaner
+ dry-container
+ factory_bot_rails
+ json-streamer
listen (>= 3.0.5, < 3.2)
+ meta_request
pg (>= 0.18, < 2.0)
puma (~> 3.11)
rails (~> 5.2.3)
+ rails_panel
+ redis
+ rspec-benchmark
+ rspec-rails
+ rubocop
+ rubocop-performance
+ rubocop-rspec
+ ruby-prof
+ standard (= 0.1.7)
tzinfo-data
web-console (>= 3.3.0)
diff --git a/Rakefile b/Rakefile
index e85f9139..9a5ea738 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,6 +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'
+require_relative "config/application"
Rails.application.load_tasks
diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb
deleted file mode 100644
index d6726972..00000000
--- a/app/channels/application_cable/channel.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-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
deleted file mode 100644
index 0ff5442f..00000000
--- a/app/channels/application_cable/connection.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-module ApplicationCable
- class Connection < ActionCable::Connection::Base
- end
-end
diff --git a/app/containers/trips_container.rb b/app/containers/trips_container.rb
new file mode 100644
index 00000000..424a1a0f
--- /dev/null
+++ b/app/containers/trips_container.rb
@@ -0,0 +1,10 @@
+class TripsContainer
+ extend Dry::Container::Mixin
+
+ register("import") do
+ Trips::Import.new(
+ Utils::TruncateTables.new,
+ BusServices::Seed.new
+ )
+ end
+end
diff --git a/app/controllers/trips_controller.rb b/app/controllers/trips_controller.rb
index acb38be2..6a988517 100644
--- a/app/controllers/trips_controller.rb
+++ b/app/controllers/trips_controller.rb
@@ -1,7 +1,34 @@
class TripsController < ApplicationController
+ helper_method :index_html
+
def index
- @from = City.find_by_name!(params[:from])
- @to = City.find_by_name!(params[:to])
- @trips = Trip.where(from: @from, to: @to).order(:start_time)
+ if index_cache.nil?
+ @from = City.find_by_name!(params[:from])
+ @to = City.find_by_name!(params[:to])
+ @trips = Trip.where(from: @from, to: @to).includes(bus: :services).order(:start_time)
+
+ prepare_index_cache
+ end
+ end
+
+ private
+
+ def index_cache
+ @index_cache ||= Redis.current.get("TripsController#index_#{params[:from].downcase}_#{params[:to].downcase}")
+ end
+
+ def index_html
+ @index_html ||=
+ index_cache || render_to_string(
+ "trips/_index",
+ locals: {:@from => @from, :@to => @to, :@trips => @trips}
+ )
+ end
+
+ def prepare_index_cache
+ Redis.current.set(
+ "TripsController#index_#{params[:from].downcase}_#{params[:to].downcase}",
+ index_html, ex: 1.minutes
+ )
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
deleted file mode 100644
index de6be794..00000000
--- a/app/helpers/application_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module ApplicationHelper
-end
diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb
deleted file mode 100644
index a009ace5..00000000
--- a/app/jobs/application_job.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-class ApplicationJob < ActiveJob::Base
-end
diff --git a/app/lib/bus_services/seed.rb b/app/lib/bus_services/seed.rb
new file mode 100644
index 00000000..7bb4da1c
--- /dev/null
+++ b/app/lib/bus_services/seed.rb
@@ -0,0 +1,27 @@
+module BusServices
+ class Seed
+ def call
+ columns = [:id, :name]
+ import_data = Service::SERVICES.each_with_index.map { |v, i|
+ [i + 1, v]
+ }
+
+ Service.import(columns, import_data)
+
+ # Result of insertion in format:
+ # {
+ # "WiFi" => 1,
+ # "Туалет" => 2,
+ # "Работающий туалет" => 3,
+ # "Ремни безопасности" => 4,
+ # "Кондиционер общий" => 5,
+ # "Кондиционер Индивидуальный" => 6,
+ # "Телевизор общий" => 7,
+ # "Телевизор индивидуальный" => 8,
+ # "Стюардесса" => 9,
+ # "Можно не печатать билет" => 10
+ # }
+ Hash[import_data.map { |id, value| [value, id] }]
+ end
+ end
+end
diff --git a/app/lib/trips/import.rb b/app/lib/trips/import.rb
new file mode 100644
index 00000000..49a2db42
--- /dev/null
+++ b/app/lib/trips/import.rb
@@ -0,0 +1,135 @@
+module Trips
+ class Import
+ DEFAULT_TABLES_FOR_CLEAN = %w[buses buses_services cities services trips]
+
+ def initialize(truncate_tables_command, seed_services_command)
+ @truncate_tables_command = truncate_tables_command
+ @seed_services_command = seed_services_command
+
+ @cities = {}
+ @buses = {}
+ @buses_services = {}
+ end
+
+ def call(file_path: nil, tables_for_clean: nil)
+ @file_path = file_path
+ @tables_for_clean = tables_for_clean
+
+ import
+ end
+
+ private
+
+ attr_reader :truncate_tables_command, :seed_services_command, :file_path, :tables_for_clean
+
+ def import
+ clean_tables
+ seed_bus_services
+ process_file
+ import_cities
+ import_buses
+ import_bus_services
+ end
+
+ def clean_tables
+ tables = tables_for_clean || DEFAULT_TABLES_FOR_CLEAN
+
+ truncate_tables_command.call(tables: tables)
+ end
+
+ def seed_bus_services
+ @buses_services = seed_services_command.call
+ end
+
+ def process_file
+ ActiveRecord::Base.transaction do
+ trips_command = "copy trips (from_id, to_id, start_time, duration_minutes, price_cents, bus_id) \
+ from stdin with csv delimiter ';'"
+
+ ActiveRecord::Base.connection.raw_connection.copy_data trips_command do
+ # TODO: Change file path
+ file_stream = File.open(file_path, "r")
+ chunk_size = 1_000
+ streamer = Json::Streamer.parser(file_io: file_stream, chunk_size: chunk_size)
+
+ streamer.get(nesting_level: 1) do |object|
+ import_row(object)
+ end
+ end
+ end
+ end
+
+ def import_row(row)
+ from_city_id = fetch_city_id(row["from"])
+ to_city_id = fetch_city_id(row["to"])
+ bus_id = fetch_bus_id(row["bus"])
+
+ connection = ActiveRecord::Base.connection.raw_connection
+ connection.put_copy_data("#{from_city_id};#{to_city_id};#{row["start_time"]};#{row["duration_minutes"]};\
+ #{row["price_cents"]};#{bus_id}\n")
+ end
+
+ def fetch_city_id(city_name)
+ return @cities[city_name] if @cities[city_name]
+
+ id = @cities.size + 1
+ @cities[city_name] = id
+
+ id
+ end
+
+ def fetch_bus_id(bus_info)
+ bus_key = "#{bus_info["number"]} #{bus_info["model"]}"
+
+ return @buses[bus_key][:id] if @buses[bus_key]
+
+ id = @buses.size + 1
+ bus_services_ids = [].tap do |x|
+ bus_info["services"].each { |s| x << @buses_services[s] }
+ end
+
+ @buses[bus_key] = {
+ id: id,
+ services_ids: bus_services_ids,
+ number: bus_info["number"],
+ model: bus_info["model"],
+ }
+
+ id
+ end
+
+ def import_cities
+ columns = [:id, :name]
+ data = @cities.map { |name, id|
+ [id, name]
+ }
+
+ City.import(columns, data)
+ end
+
+ def import_buses
+ columns = [:id, :number, :model]
+ data = @buses.map { |key, bus_data|
+ [bus_data[:id], bus_data[:number], bus_data[:model]]
+ }
+
+ Bus.import(columns, data)
+ end
+
+ def import_bus_services
+ id = 1
+ services_command = "copy buses_services (id, bus_id, service_id) from stdin with csv delimiter ';'"
+ connection = ActiveRecord::Base.connection.raw_connection
+
+ ActiveRecord::Base.connection.raw_connection.copy_data services_command do
+ @buses.each do |key, bus_data|
+ bus_data[:services_ids].each do |servce_id|
+ connection.put_copy_data("#{id};#{bus_data[:id]};#{servce_id}\n")
+
+ id += 1
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/lib/utils/truncate_tables.rb b/app/lib/utils/truncate_tables.rb
new file mode 100644
index 00000000..249adbf4
--- /dev/null
+++ b/app/lib/utils/truncate_tables.rb
@@ -0,0 +1,9 @@
+module Utils
+ class TruncateTables
+ def call(tables:)
+ tables.each do |table|
+ ActiveRecord::Base.connection.execute("truncate table #{table};")
+ end
+ end
+ end
+end
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
deleted file mode 100644
index 286b2239..00000000
--- a/app/mailers/application_mailer.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-class ApplicationMailer < ActionMailer::Base
- default from: 'from@example.com'
- layout 'mailer'
-end
diff --git a/app/models/bus.rb b/app/models/bus.rb
index 1dcc54cb..0c4b17fd 100644
--- a/app/models/bus.rb
+++ b/app/models/bus.rb
@@ -1,20 +1,20 @@
class Bus < ApplicationRecord
MODELS = [
- 'Икарус',
- 'Мерседес',
- 'Сканиа',
- 'Буханка',
- 'УАЗ',
- 'Спринтер',
- 'ГАЗ',
- 'ПАЗ',
- 'Вольво',
- 'Газель',
+ "Икарус",
+ "Мерседес",
+ "Сканиа",
+ "Буханка",
+ "УАЗ",
+ "Спринтер",
+ "ГАЗ",
+ "ПАЗ",
+ "Вольво",
+ "Газель",
].freeze
has_many :trips
has_and_belongs_to_many :services, join_table: :buses_services
validates :number, presence: true, uniqueness: true
- validates :model, inclusion: { in: MODELS }
+ validates :model, inclusion: {in: MODELS}
end
diff --git a/app/models/city.rb b/app/models/city.rb
index 19ec7f36..53c2e295 100644
--- a/app/models/city.rb
+++ b/app/models/city.rb
@@ -3,6 +3,6 @@ class City < ApplicationRecord
validate :name_has_no_spaces
def name_has_no_spaces
- errors.add(:name, "has spaces") if name.include?(' ')
+ errors.add(:name, "has spaces") if name.include?(" ")
end
end
diff --git a/app/models/service.rb b/app/models/service.rb
index 9cbb2a32..a0d57f10 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -1,19 +1,19 @@
class Service < ApplicationRecord
SERVICES = [
- 'WiFi',
- 'Туалет',
- 'Работающий туалет',
- 'Ремни безопасности',
- 'Кондиционер общий',
- 'Кондиционер Индивидуальный',
- 'Телевизор общий',
- 'Телевизор индивидуальный',
- 'Стюардесса',
- 'Можно не печатать билет',
+ "WiFi",
+ "Туалет",
+ "Работающий туалет",
+ "Ремни безопасности",
+ "Кондиционер общий",
+ "Кондиционер Индивидуальный",
+ "Телевизор общий",
+ "Телевизор индивидуальный",
+ "Стюардесса",
+ "Можно не печатать билет",
].freeze
has_and_belongs_to_many :buses, join_table: :buses_services
validates :name, presence: true
- validates :name, inclusion: { in: SERVICES }
+ validates :name, inclusion: {in: SERVICES}
end
diff --git a/app/models/trip.rb b/app/models/trip.rb
index 9d63dfff..06e83400 100644
--- a/app/models/trip.rb
+++ b/app/models/trip.rb
@@ -1,19 +1,19 @@
class Trip < ApplicationRecord
HHMM_REGEXP = /([0-1][0-9]|[2][0-3]):[0-5][0-9]/
- belongs_to :from, class_name: 'City'
- belongs_to :to, class_name: 'City'
+ belongs_to :from, class_name: "City"
+ belongs_to :to, class_name: "City"
belongs_to :bus
validates :from, presence: true
validates :to, presence: true
validates :bus, presence: true
- validates :start_time, format: { with: HHMM_REGEXP, message: 'Invalid time' }
+ validates :start_time, format: {with: HHMM_REGEXP, message: "Invalid time"}
validates :duration_minutes, presence: true
- validates :duration_minutes, numericality: { greater_than: 0 }
+ validates :duration_minutes, numericality: {greater_than: 0}
validates :price_cents, presence: true
- validates :price_cents, numericality: { greater_than: 0 }
+ validates :price_cents, numericality: {greater_than: 0}
def to_h
{
diff --git a/app/views/trips/_delimiter.html.erb b/app/views/trips/_delimiter.html.erb
deleted file mode 100644
index 3f845ad0..00000000
--- a/app/views/trips/_delimiter.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-====================================================
diff --git a/app/views/trips/_index.html.erb b/app/views/trips/_index.html.erb
new file mode 100644
index 00000000..2a2179f0
--- /dev/null
+++ b/app/views/trips/_index.html.erb
@@ -0,0 +1,26 @@
+
+ <%= "Автобусы #{@from.name} – #{@to.name}" %>
+
+
+ <%= "В расписании #{@trips.length} рейсов" %>
+
+
+<% @trips.each do |trip| %>
+
+ - <%= "Отправление: #{trip.start_time}" %>
+ - <%= "Прибытие: #{(Time.parse(trip.start_time) + trip.duration_minutes.minutes).strftime('%H:%M')}" %>
+ - <%= "В пути: #{trip.duration_minutes / 60}ч. #{trip.duration_minutes % 60}мин." %>
+ - <%= "Цена: #{trip.price_cents / 100}р. #{trip.price_cents % 100}коп." %>
+ - <%= "Автобус: #{trip.bus.model} №#{trip.bus.number}" %>
+
+ <% if trip.bus.services.present? %>
+ - Сервисы в автобусе:
+ <% trip.bus.services.each do |service| %>
+
+ - <%= "#{service.name}" %>
+
+ <% end %>
+ <% end %>
+
+ ====================================================
+<% end %>
diff --git a/app/views/trips/_service.html.erb b/app/views/trips/_service.html.erb
deleted file mode 100644
index 178ea8c0..00000000
--- a/app/views/trips/_service.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= "#{service.name}" %>
diff --git a/app/views/trips/_services.html.erb b/app/views/trips/_services.html.erb
deleted file mode 100644
index 2de639fc..00000000
--- a/app/views/trips/_services.html.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-Сервисы в автобусе:
-
- <% services.each do |service| %>
- <%= render "service", service: service %>
- <% end %>
-
diff --git a/app/views/trips/_trip.html.erb b/app/views/trips/_trip.html.erb
deleted file mode 100644
index fa1de9aa..00000000
--- a/app/views/trips/_trip.html.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-<%= "Отправление: #{trip.start_time}" %>
-<%= "Прибытие: #{(Time.parse(trip.start_time) + trip.duration_minutes.minutes).strftime('%H:%M')}" %>
-<%= "В пути: #{trip.duration_minutes / 60}ч. #{trip.duration_minutes % 60}мин." %>
-<%= "Цена: #{trip.price_cents / 100}р. #{trip.price_cents % 100}коп." %>
-<%= "Автобус: #{trip.bus.model} №#{trip.bus.number}" %>
diff --git a/app/views/trips/index.html.erb b/app/views/trips/index.html.erb
index a60bce41..832d5c5d 100644
--- a/app/views/trips/index.html.erb
+++ b/app/views/trips/index.html.erb
@@ -1,16 +1 @@
-
- <%= "Автобусы #{@from.name} – #{@to.name}" %>
-
-
- <%= "В расписании #{@trips.count} рейсов" %>
-
-
-<% @trips.each do |trip| %>
-
- <%= render "trip", trip: trip %>
- <% if trip.bus.services.present? %>
- <%= render "services", services: trip.bus.services %>
- <% end %>
-
- <%= render "delimiter" %>
-<% end %>
+<%= index_html.html_safe %>
diff --git a/bin/bundle b/bin/bundle
index f19acf5b..67efc37f 100755
--- a/bin/bundle
+++ b/bin/bundle
@@ -1,3 +1,3 @@
#!/usr/bin/env ruby
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
-load Gem.bin_path('bundler', 'bundle')
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
+load Gem.bin_path("bundler", "bundle")
diff --git a/bin/rails b/bin/rails
index 5badb2fd..40d2d5e1 100755
--- a/bin/rails
+++ b/bin/rails
@@ -1,9 +1,9 @@
#!/usr/bin/env ruby
begin
- load File.expand_path('../spring', __FILE__)
+ load File.expand_path("../spring", __FILE__)
rescue LoadError => e
- raise unless e.message.include?('spring')
+ raise unless e.message.include?("spring")
end
-APP_PATH = File.expand_path('../config/application', __dir__)
-require_relative '../config/boot'
-require 'rails/commands'
+APP_PATH = File.expand_path("../config/application", __dir__)
+require_relative "../config/boot"
+require "rails/commands"
diff --git a/bin/rake b/bin/rake
index d87d5f57..3df5805b 100755
--- a/bin/rake
+++ b/bin/rake
@@ -1,9 +1,9 @@
#!/usr/bin/env ruby
begin
- load File.expand_path('../spring', __FILE__)
+ load File.expand_path("../spring", __FILE__)
rescue LoadError => e
- raise unless e.message.include?('spring')
+ raise unless e.message.include?("spring")
end
-require_relative '../config/boot'
-require 'rake'
+require_relative "../config/boot"
+require "rake"
Rake.application.run
diff --git a/bin/setup b/bin/setup
index f294207b..0d488cce 100755
--- a/bin/setup
+++ b/bin/setup
@@ -1,9 +1,9 @@
#!/usr/bin/env ruby
-require 'fileutils'
+require "fileutils"
include 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 +13,9 @@ chdir APP_ROOT do
# This script is a starting point to setup your application.
# Add necessary setup steps to this file.
- puts '== Installing dependencies =='
- system! 'gem install bundler --conservative'
- system('bundle check') || system!('bundle install')
+ puts "== Installing dependencies =="
+ system! "gem install bundler --conservative"
+ system("bundle check") || system!("bundle install")
# Install JavaScript dependencies if using Yarn
# system('bin/yarn')
@@ -26,14 +26,14 @@ chdir APP_ROOT do
# end
puts "\n== Preparing database =="
- system! 'bin/rails db:setup'
+ system! "bin/rails db:setup"
puts "\n== Loading data from fixtures/small.json =="
- system! 'bin/rake reload_json[fixtures/small.json]'
+ system! "bin/rake reload_json[fixtures/small.json]"
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/bin/spring b/bin/spring
index fb2ec2eb..b44ad1a7 100755
--- a/bin/spring
+++ b/bin/spring
@@ -4,14 +4,14 @@
# It gets overwritten when you run the `spring binstub` command.
unless defined?(Spring)
- require 'rubygems'
- require 'bundler'
+ 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'
+ gem "spring", spring.version
+ require "spring/binstub"
end
end
diff --git a/bin/update b/bin/update
index 58bfaed5..7bf318b6 100755
--- a/bin/update
+++ b/bin/update
@@ -1,9 +1,9 @@
#!/usr/bin/env ruby
-require 'fileutils'
+require "fileutils"
include 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,19 +13,19 @@ chdir APP_ROOT do
# This script is a way to update your development environment automatically.
# Add necessary update steps to this file.
- puts '== Installing dependencies =='
- system! 'gem install bundler --conservative'
- system('bundle check') || system!('bundle install')
+ puts "== 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'
+ system! "bin/rails db:migrate"
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/bin/yarn b/bin/yarn
index 460dd565..99e5e772 100755
--- a/bin/yarn
+++ b/bin/yarn
@@ -1,11 +1,9 @@
#!/usr/bin/env ruby
-APP_ROOT = File.expand_path('..', __dir__)
+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
+ exec "yarnpkg", *ARGV
+rescue Errno::ENOENT
+ warn "Yarn executable was not detected in the system."
+ warn "Download Yarn at https://yarnpkg.com/en/docs/install"
+ exit 1
end
diff --git a/case-study.md b/case-study.md
new file mode 100644
index 00000000..46430a5b
--- /dev/null
+++ b/case-study.md
@@ -0,0 +1,66 @@
+# Case-study оптимизации
+
+## Актуальная проблема
+1. Оптимизировать время обработки файла `large.json`
+2. Оптимизировать время для отображения необходимых рейсов.
+
+## Формирование метрики
+
+- small.json (1K трипов)
+- medium.json (10K трипов)
+- large.json (100K трипов)
+
+Время полной обработки `large.json` должно составлять не более 60сек.
+
+## Гарантия корректности работы оптимизированной программы
+1. Для импорта данных был написан тест, который гарантирует, что код до и после оптимизации создает одинаковое кол-во данных.
+2. Для отображения данных на странице так же был написан тест, который проверяет, что кол-во элементов до и после оптимизации одинаковое для тестового файла.
+
+## Feedback-Loop
+### Для импорты данных
+Прежде чем приступить к оптимизации – я написал тест, проверяющий корректность данных после запуска импорта.
+
+1. Определение точки роста.
+2. Рефакторинг.
+3. Тест.
+
+### Для отображения данных на странице.
+Прежде чем приступить к оптимизации – я написал тест, проверяющий корректность данных на странице рейсов.
+
+1. Определение точки роста.
+2. Рефакторинг.
+3. Тест.
+
+## Вникаем в детали системы, чтобы найти главные точки роста
+Для того, чтобы найти "точки роста" для оптимизации я воспользовался
+- rspec-benchmark
+- ruby-prof
+
+## Вот какие проблемы удалось найти и решить
+### Импорт данных
+Основная проблема была из-за построчной обработки данных, что было мною сделано:
+
+1. Bulk insert все сервисов, что позволяет сразу же ссылаться на нужные для всех автобусов.
+2. Стриминговая вставка всех рейсов.
+3. Bulk insert городов и автобусов.
+4. Стриминговая вставка сервисов для автобусов.
+
+Суммарно мне удалось ускорить импорт в ~36 раз, проверяя на файле `small.json`. Было ~25 секунд, стало ~0.7 секунд.
+
+### Отображение данных.
+В ходе профилирования мною было выявлено 2 основных проблемы:
+
+1. N + 1.
+2. Затраты на partial render.
+
+Что было сделано:
+1. Устранены все N + 1, путем includes.
+2. Убрал partials render, делая это прямо во view.
+3. Добавил кэширование страницы в зависимости от параметров на 1 минуту (время было выбрано случайно, оно очень сильно зависит от бизнеса и задачи).
+
+## Результаты
+### Импорт
+Удалось ускорить время выполнения в ~36 раз (с 25 секунд до 0.7), был написано тест, проверяющий корректность выполнения и проверяющий, что программа работает быстрее, чем 1 секунду.
+
+### Отображение данных на странице
+Удалось ускорить время ответа страницы в ~19 раз (с 16 секунд до ~0.8 без кэширования, с кэширование 28ms). Был написан тест, проверяющий корректность данных на странице.
diff --git a/config.ru b/config.ru
index f7ba0b52..441e6ff0 100644
--- a/config.ru
+++ b/config.ru
@@ -1,5 +1,5 @@
# This file is used by Rack-based servers to start the application.
-require_relative 'config/environment'
+require_relative "config/environment"
run Rails.application
diff --git a/config/application.rb b/config/application.rb
index 9c331097..04574e6e 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -1,6 +1,6 @@
-require_relative 'boot'
+require_relative "boot"
-require 'rails/all'
+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 b9e460ce..988a5ddc 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -1,4 +1,4 @@
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
+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.
+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 426333bb..cac53157 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -1,5 +1,5 @@
# 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 1311e3e4..9f03d2f3 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -14,12 +14,12 @@
# 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.action_controller.perform_caching = true
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
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 613d8289..4057e4c6 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -11,7 +11,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
config.action_controller.perform_caching = true
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
@@ -20,7 +20,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?
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
@@ -54,7 +54,7 @@
config.log_level = :debug
# 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
@@ -84,9 +84,9 @@
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
if ENV["RAILS_LOG_TO_STDOUT"].present?
- logger = ActiveSupport::Logger.new(STDOUT)
+ logger = ActiveSupport::Logger.new(STDOUT)
logger.formatter = config.log_formatter
- config.logger = ActiveSupport::TaggedLogging.new(logger)
+ config.logger = ActiveSupport::TaggedLogging.new(logger)
end
# Do not dump schema after migrations.
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 0a38fd3c..1231a86a 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -15,11 +15,11 @@
# 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.
- config.consider_all_requests_local = true
+ config.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Raise exceptions instead of rendering exception templates.
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
index 4b828e80..c1f948d0 100644
--- a/config/initializers/assets.rb
+++ b/config/initializers/assets.rb
@@ -1,12 +1,12 @@
# 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'
+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')
+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
diff --git a/config/initializers/redis.rb b/config/initializers/redis.rb
new file mode 100644
index 00000000..d9845936
--- /dev/null
+++ b/config/initializers/redis.rb
@@ -0,0 +1,2 @@
+# TODO: move redis config to configuration file.
+Redis.current = Redis.new(url: "redis://localhost:6379/")
diff --git a/config/puma.rb b/config/puma.rb
index a5eccf81..400b9133 100644
--- a/config/puma.rb
+++ b/config/puma.rb
@@ -9,7 +9,7 @@
# 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.
#
diff --git a/config/routes.rb b/config/routes.rb
index a2da6a7b..07036acf 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,5 +1,5 @@
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
get "/" => "statistics#index"
- get "автобусы/:from/:to" => "trips#index"
+ get "автобусы/:from/:to" => "trips#index", :as => "trips_index"
end
diff --git a/db/schema.rb b/db/schema.rb
index f6921e45..7ae4542e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,6 @@
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_03_30_193044) do
-
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -41,5 +40,4 @@
t.integer "price_cents"
t.integer "bus_id"
end
-
end
diff --git a/lib/tasks/utils.rake b/lib/tasks/utils.rake
index 540fe871..e2522f9c 100644
--- a/lib/tasks/utils.rake
+++ b/lib/tasks/utils.rake
@@ -1,34 +1,5 @@
# Наивная загрузка данных из json-файла в БД
# rake reload_json[fixtures/small.json]
task :reload_json, [:file_name] => :environment do |_task, args|
- json = JSON.parse(File.read(args.file_name))
-
- ActiveRecord::Base.transaction do
- City.delete_all
- Bus.delete_all
- Service.delete_all
- Trip.delete_all
- ActiveRecord::Base.connection.execute('delete from buses_services;')
-
- json.each do |trip|
- from = City.find_or_create_by(name: trip['from'])
- to = City.find_or_create_by(name: trip['to'])
- services = []
- trip['bus']['services'].each do |service|
- s = Service.find_or_create_by(name: service)
- services << s
- end
- bus = Bus.find_or_create_by(number: trip['bus']['number'])
- bus.update(model: trip['bus']['model'], services: services)
-
- Trip.create!(
- from: from,
- to: to,
- bus: bus,
- start_time: trip['start_time'],
- duration_minutes: trip['duration_minutes'],
- price_cents: trip['price_cents'],
- )
- end
- end
+ TripsContainer["import"].call(file_path: args.file_name)
end
diff --git a/spec/factories/buses.rb b/spec/factories/buses.rb
new file mode 100644
index 00000000..7b1373c1
--- /dev/null
+++ b/spec/factories/buses.rb
@@ -0,0 +1,6 @@
+FactoryBot.define do
+ factory :bus do
+ number { rand(1..999).to_s.rjust(3, "0") }
+ model { Bus::MODELS.sample }
+ end
+end
diff --git a/spec/features/trips/visit_trips_spec.rb b/spec/features/trips/visit_trips_spec.rb
new file mode 100644
index 00000000..72c25c82
--- /dev/null
+++ b/spec/features/trips/visit_trips_spec.rb
@@ -0,0 +1,19 @@
+require "rails_helper"
+
+feature "User visits trips index page" do
+ let(:file_path) { File.join(Rails.root, "fixtures", "example.json") }
+
+ context "when from city Самара, to city Москва" do
+ before do
+ TripsContainer["import"].call(file_path: file_path)
+ end
+
+ it "should contain valid blocks count" do
+ visit trips_index_path(from: "Самара", to: "Москва")
+
+ trips = page.all(:css, ".trip").count
+
+ expect(trips).to eq(5)
+ end
+ end
+end
diff --git a/spec/lib/bus_services/seed_spec.rb b/spec/lib/bus_services/seed_spec.rb
new file mode 100644
index 00000000..55b8d61d
--- /dev/null
+++ b/spec/lib/bus_services/seed_spec.rb
@@ -0,0 +1,11 @@
+require "rails_helper"
+
+describe BusServices::Seed do
+ let(:operation) { described_class.new }
+
+ describe "#call" do
+ it "should create all services" do
+ expect { operation.call }.to change(Service, :count).to(Service::SERVICES.count)
+ end
+ end
+end
diff --git a/spec/lib/trips/import_spec.rb b/spec/lib/trips/import_spec.rb
new file mode 100644
index 00000000..5818b0fa
--- /dev/null
+++ b/spec/lib/trips/import_spec.rb
@@ -0,0 +1,35 @@
+require "rails_helper"
+
+describe Trips::Import do
+ include RSpec::Benchmark::Matchers
+
+ let(:truncate_tables_command) { Utils::TruncateTables.new }
+ let(:seed_services_command) { BusServices::Seed.new }
+ let(:operation) { Trips::Import.new(truncate_tables_command, seed_services_command) }
+ let(:connection) { ActiveRecord::Base.connection }
+
+ describe "#call" do
+ context "when example.json file" do
+ let(:file_path) { File.join(Rails.root, "fixtures", "example.json") }
+
+ it "should create valid data" do
+ expect { operation.call(file_path: file_path) }
+ .to change(Bus, :count).to(1)
+ .and change(Service, :count).to(10)
+ .and change(City, :count).to(2)
+ .and change(Trip, :count).to(10)
+ .and change { connection.execute("select count(*) from buses_services")[0]["count"] }.to(2)
+ end
+ end
+
+ # Old import - ~25 sec
+ # New import - ~0.7 sec
+ context "when small.json file" do
+ let(:file_path) { File.join(Rails.root, "fixtures", "small.json") }
+
+ it "should work less then 1 second" do
+ expect { operation.call(file_path: file_path) }.to perform_under(1).sec.warmup(2).times.sample(5).times
+ end
+ end
+ end
+end
diff --git a/spec/lib/utils/truncate_tables_spec.rb b/spec/lib/utils/truncate_tables_spec.rb
new file mode 100644
index 00000000..c4334d16
--- /dev/null
+++ b/spec/lib/utils/truncate_tables_spec.rb
@@ -0,0 +1,17 @@
+require "rails_helper"
+
+describe Utils::TruncateTables do
+ let(:operation) { described_class.new }
+
+ describe "#call" do
+ it "should remove all table rows" do
+ FactoryBot.create(:bus)
+
+ expect(Bus.count).to eq(1)
+
+ operation.call(tables: ["buses"])
+
+ expect(Bus.count).to eq(0)
+ end
+ end
+end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
new file mode 100644
index 00000000..176a8e1e
--- /dev/null
+++ b/spec/rails_helper.rb
@@ -0,0 +1,75 @@
+# This file is copied to spec/ when you run 'rails generate rspec:install'
+require "spec_helper"
+ENV["RAILS_ENV"] ||= "test"
+
+require File.expand_path("../config/environment", __dir__)
+
+# Prevent database truncation if the environment is production
+abort("The Rails environment is running in production mode!") if Rails.env.production?
+require "rspec/rails"
+require "capybara/rspec"
+# Add additional requires below this line. Rails is not loaded until this point!
+
+# Requires supporting ruby files with custom matchers and macros, etc, in
+# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
+# run as spec files by default. This means that files in spec/support that end
+# in _spec.rb will both be required and run as specs, causing the specs to be
+# run twice. It is recommended that you do not name files matching this glob to
+# end with _spec.rb. You can configure this pattern with the --pattern
+# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
+#
+# The following line is provided for convenience purposes. It has the downside
+# of increasing the boot-up time by auto-requiring all files in the support
+# directory. Alternatively, in the individual `*_spec.rb` files, manually
+# require only the support files necessary.
+#
+# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
+
+# Checks for pending migrations and applies them before tests are run.
+# If you are not using ActiveRecord, you can remove these lines.
+begin
+ ActiveRecord::Migration.maintain_test_schema!
+rescue ActiveRecord::PendingMigrationError => e
+ puts e.to_s.strip
+ exit 1
+end
+RSpec.configure do |config|
+ # Remove this line if you're not using ActiveRecord or ActiveRecord 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
+ # instead of true.
+ config.use_transactional_fixtures = true
+
+ # RSpec Rails can automatically mix in different behaviours to your tests
+ # based on their file location, for example enabling you to call `get` and
+ # `post` in specs under `spec/controllers`.
+ #
+ # You can disable this behaviour by removing the line below, and instead
+ # explicitly tag your specs with their type, e.g.:
+ #
+ # RSpec.describe UsersController, :type => :controller do
+ # # ...
+ # end
+ #
+ # The different available types are documented in the features, such as in
+ # https://relishapp.com/rspec/rspec-rails/docs
+ config.infer_spec_type_from_file_location!
+
+ # Filter lines from Rails gems in backtraces.
+ config.filter_rails_from_backtrace!
+ # arbitrary gems may also be filtered via:
+ # config.filter_gems_from_backtrace("gem name")
+
+ config.before(:suite) do
+ DatabaseCleaner.strategy = :transaction
+ DatabaseCleaner.clean_with(:truncation)
+ end
+
+ config.around(:each) do |example|
+ DatabaseCleaner.cleaning do
+ example.run
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 00000000..ce33d66d
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,96 @@
+# 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
+# this file to always be loaded, without a need to explicitly require it in any
+# files.
+#
+# Given that it is always loaded, you are encouraged to keep this file as
+# light-weight as possible. Requiring heavyweight dependencies from this file
+# will add to the boot time of your test suite on EVERY test run, even for an
+# individual file that may not need all of that loaded. Instead, consider making
+# a separate helper file that requires the additional dependencies and performs
+# the additional setup, and require it from the spec files that actually need
+# it.
+#
+# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
+RSpec.configure do |config|
+ # rspec-expectations config goes here. You can use an alternate
+ # assertion/expectation library such as wrong or the stdlib/minitest
+ # assertions if you prefer.
+ config.expect_with :rspec do |expectations|
+ # This option will default to `true` in RSpec 4. It makes the `description`
+ # and `failure_message` of custom matchers include text for helper methods
+ # defined using `chain`, e.g.:
+ # be_bigger_than(2).and_smaller_than(4).description
+ # # => "be bigger than 2 and smaller than 4"
+ # ...rather than:
+ # # => "be bigger than 2"
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
+ end
+
+ # rspec-mocks config goes here. You can use an alternate test double
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
+ config.mock_with :rspec do |mocks|
+ # Prevents you from mocking or stubbing a method that does not exist on
+ # a real object. This is generally recommended, and will default to
+ # `true` in RSpec 4.
+ mocks.verify_partial_doubles = true
+ end
+
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
+ # have no way to turn it off -- the option exists only for backwards
+ # compatibility in RSpec 3). It causes shared context metadata to be
+ # inherited by the metadata hash of host groups and examples, rather than
+ # 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:
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#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
+end
diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb
deleted file mode 100644
index d19212ab..00000000
--- a/test/application_system_test_case.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-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
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/fixtures/.keep b/test/fixtures/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/helpers/.keep b/test/helpers/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/integration/.keep b/test/integration/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/mailers/.keep b/test/mailers/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/models/.keep b/test/models/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/system/.keep b/test/system/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/test_helper.rb b/test/test_helper.rb
deleted file mode 100644
index 3ab84e3d..00000000
--- a/test/test_helper.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-ENV['RAILS_ENV'] ||= 'test'
-require_relative '../config/environment'
-require 'rails/test_help'
-
-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