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| %> + + ==================================================== +<% 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 @@ -
  • Сервисы в автобусе:
  • - 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 "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