From 14f38c430340d8c0322b97f415461cca5c44973b Mon Sep 17 00:00:00 2001 From: ksilex Date: Mon, 6 Nov 2023 19:18:16 +0300 Subject: [PATCH] homework --- .rspec | 1 + Gemfile | 25 ++-- Gemfile.lock | 68 ++++++++++- app/controllers/trips_controller.rb | 2 +- app/models/bus.rb | 3 +- app/models/buses_service.rb | 4 + app/models/service.rb | 5 +- app/services/reload_json.rb | 107 ++++++++++++++++++ app/views/trips/_delimiter.html.erb | 1 - app/views/trips/_service.html.erb | 1 - app/views/trips/_services.html.erb | 6 - app/views/trips/_trip.html.erb | 21 +++- app/views/trips/index.html.erb | 12 +- case-study.md | 28 +++++ config/database.yml | 2 + config/environments/development.rb | 12 ++ config/environments/test.rb | 6 + config/initializers/rack_mini_profiler.rb | 8 ++ config/routes.rb | 5 +- ...0231106154303_create_pghero_query_stats.rb | 15 +++ db/schema.rb | 13 ++- .../Screenshot from 2023-11-06 16-52-17.png | Bin 0 -> 15503 bytes .../Screenshot from 2023-11-06 18-32-17.png | Bin 0 -> 15399 bytes lib/tasks/utils.rake | 33 +----- spec/rails_helper.rb | 64 +++++++++++ spec/services/reload_json_spec.rb | 12 ++ spec/spec_helper.rb | 94 +++++++++++++++ 27 files changed, 477 insertions(+), 71 deletions(-) create mode 100644 .rspec create mode 100644 app/models/buses_service.rb create mode 100644 app/services/reload_json.rb delete mode 100644 app/views/trips/_delimiter.html.erb delete mode 100644 app/views/trips/_service.html.erb delete mode 100644 app/views/trips/_services.html.erb create mode 100644 case-study.md create mode 100644 config/initializers/rack_mini_profiler.rb create mode 100644 db/migrate/20231106154303_create_pghero_query_stats.rb create mode 100644 images/Screenshot from 2023-11-06 16-52-17.png create mode 100644 images/Screenshot from 2023-11-06 18-32-17.png create mode 100644 spec/rails_helper.rb create mode 100644 spec/services/reload_json_spec.rb create mode 100644 spec/spec_helper.rb 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/Gemfile b/Gemfile index e20b1260..3002f03d 100644 --- a/Gemfile +++ b/Gemfile @@ -3,24 +3,33 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.6.3' -gem 'rails', '~> 5.2.3' +gem 'activerecord-import' +gem 'bootsnap', '>= 1.1.0', require: false +gem 'oj' gem 'pg', '>= 0.18', '< 2.0' gem 'puma', '~> 3.11' -gem 'bootsnap', '>= 1.1.0', require: false - +gem 'rails', '~> 5.2.3' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console - gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + gem 'bullet' + gem 'byebug', platforms: %i[mri mingw x64_mingw] + gem 'memory_profiler' + gem 'rspec-rails' + gem 'rspec-benchmark' + gem 'ruby-prof' + gem 'ruby-progressbar' end - group :development do + gem 'meta_request' # 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' end - +gem 'pghero' +gem 'pg_query', '>= 2' +gem 'rack-mini-profiler', require: false group :test do 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: %i[mingw mswin x64_mingw jruby] diff --git a/Gemfile.lock b/Gemfile.lock index fccf6f5f..2ac1a7fe 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.5.0) + activerecord (>= 4.2) activestorage (5.2.3) actionpack (= 5.2.3) activerecord (= 5.2.3) @@ -43,17 +45,25 @@ GEM minitest (~> 5.1) tzinfo (~> 1.1) arel (9.0.0) + benchmark-malloc (0.2.0) + benchmark-perf (0.6.0) + benchmark-trend (0.4.0) bindex (0.6.0) bootsnap (1.4.2) msgpack (~> 1.0) builder (3.2.3) + bullet (7.1.3) + activesupport (>= 3.0.0) + uniform_notifier (~> 1.11) byebug (11.0.1) concurrent-ruby (1.1.5) crass (1.0.4) + diff-lcs (1.5.0) erubi (1.8.0) ffi (1.10.0) globalid (0.4.2) activesupport (>= 4.2.0) + google-protobuf (3.23.4) i18n (1.6.0) concurrent-ruby (~> 1.0) listen (3.1.5) @@ -67,8 +77,14 @@ GEM mini_mime (>= 0.1.1) marcel (0.3.3) mimemagic (~> 0.3.2) + memory_profiler (1.0.1) + meta_request (0.7.4) + rack-contrib (>= 1.1, < 3) + railties (>= 3.0.0, < 7.1) method_source (0.9.2) - mimemagic (0.3.3) + mimemagic (0.3.10) + nokogiri (~> 1) + rake mini_mime (1.0.1) mini_portile2 (2.4.0) minitest (5.11.3) @@ -76,9 +92,18 @@ GEM nio4r (2.3.1) nokogiri (1.10.2) mini_portile2 (~> 2.4.0) + oj (3.14.2) pg (1.1.4) + pg_query (4.2.3) + google-protobuf (>= 3.22.3) + pghero (2.8.3) + activerecord (>= 5) puma (3.12.1) rack (2.0.6) + rack-contrib (2.4.0) + rack (< 4) + rack-mini-profiler (3.1.1) + rack (>= 1.2.0) rack-test (1.1.0) rack (>= 1.0, < 3) rails (5.2.3) @@ -109,6 +134,34 @@ GEM rb-fsevent (0.10.3) rb-inotify (0.10.0) ffi (~> 1.0) + rspec (3.12.0) + rspec-core (~> 3.12.0) + rspec-expectations (~> 3.12.0) + rspec-mocks (~> 3.12.0) + rspec-benchmark (0.6.0) + benchmark-malloc (~> 0.2) + benchmark-perf (~> 0.6) + benchmark-trend (~> 0.4) + rspec (>= 3.0) + rspec-core (3.12.2) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.6) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-rails (5.1.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + railties (>= 5.2) + rspec-core (~> 3.10) + rspec-expectations (~> 3.10) + rspec-mocks (~> 3.10) + rspec-support (~> 3.10) + rspec-support (3.12.1) + ruby-prof (1.4.3) + ruby-progressbar (1.13.0) ruby_dep (1.5.0) sprockets (3.7.2) concurrent-ruby (~> 1.0) @@ -121,6 +174,7 @@ GEM thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) + uniform_notifier (1.16.0) web-console (3.7.0) actionview (>= 5.0) activemodel (>= 5.0) @@ -134,12 +188,24 @@ PLATFORMS ruby DEPENDENCIES + activerecord-import bootsnap (>= 1.1.0) + bullet byebug listen (>= 3.0.5, < 3.2) + memory_profiler + meta_request + oj pg (>= 0.18, < 2.0) + pg_query (>= 2) + pghero puma (~> 3.11) + rack-mini-profiler rails (~> 5.2.3) + rspec-benchmark + rspec-rails + ruby-prof + ruby-progressbar tzinfo-data web-console (>= 3.3.0) diff --git a/app/controllers/trips_controller.rb b/app/controllers/trips_controller.rb index acb38be2..7acb2f7c 100644 --- a/app/controllers/trips_controller.rb +++ b/app/controllers/trips_controller.rb @@ -2,6 +2,6 @@ class TripsController < ApplicationController 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) + @trips = Trip.includes(bus: :services).where(from: @from, to: @to).order(:start_time).load end end diff --git a/app/models/bus.rb b/app/models/bus.rb index 1dcc54cb..d97de31f 100644 --- a/app/models/bus.rb +++ b/app/models/bus.rb @@ -13,7 +13,8 @@ class Bus < ApplicationRecord ].freeze has_many :trips - has_and_belongs_to_many :services, join_table: :buses_services + has_many :buses_services + has_many :services, through: :buses_services validates :number, presence: true, uniqueness: true validates :model, inclusion: { in: MODELS } diff --git a/app/models/buses_service.rb b/app/models/buses_service.rb new file mode 100644 index 00000000..6219d44e --- /dev/null +++ b/app/models/buses_service.rb @@ -0,0 +1,4 @@ +class BusesService < ApplicationRecord + belongs_to :bus + belongs_to :service +end diff --git a/app/models/service.rb b/app/models/service.rb index 9cbb2a32..3d3b64b4 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -9,10 +9,11 @@ class Service < ApplicationRecord 'Телевизор общий', 'Телевизор индивидуальный', 'Стюардесса', - 'Можно не печатать билет', + 'Можно не печатать билет' ].freeze - has_and_belongs_to_many :buses, join_table: :buses_services + has_many :buses_services + has_many :buses, through: :buses_services validates :name, presence: true validates :name, inclusion: { in: SERVICES } diff --git a/app/services/reload_json.rb b/app/services/reload_json.rb new file mode 100644 index 00000000..d3e2e653 --- /dev/null +++ b/app/services/reload_json.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require 'oj' +class ReloadJson + def initialize(file_name) + @file_name = file_name + @cities = {} + @buses = {} + @buses_services = [] + end + + def call + ActiveRecord::Base.transaction do + clear_tables + create_services + + @connection = ActiveRecord::Base.connection.raw_connection + trips_command = + "copy trips (from_id, to_id, start_time, duration_minutes, price_cents, bus_id) from stdin with csv delimiter ';'" + + @connection.copy_data trips_command do + File.open(file_name) do |ff| + nesting = 0 + str = String.new + + until ff.eof? + ch = ff.read(1) # читаем по одному символу + if ch == '{' # начинается объект, повышается вложенность + nesting += 1 + str << ch + elsif ch == '}' # заканчивается объект, понижается вложенность + nesting -= 1 + str << ch + if nesting.zero? # если закончился объкет уровня trip, парсим и импортируем его + trip = Oj.load(str) + copy_trip(trip) + + str = String.new + end + elsif nesting >= 1 + str << ch + end + end + end + end + + Bus.import buses.values + City.import cities.values + BusesService.import buses_services + end + end + + private + + attr_reader :cities, :buses, :buses_services, :services, :file_name + + def create_services + services = [] + Service::SERVICES.each do |name| + services << Service.new(name: name) + end + Service.import services + @services = Service.all.to_h { |service| [service.name, service.id] } + end + + def clear_tables + City.delete_all + Bus.delete_all + Service.delete_all + Trip.delete_all + BusesService.delete_all + end + + def copy_trip(trip) + from_id = cities.dig(trip['from'], 'id') + to_id = cities.dig(trip['to'], 'id') + service_ids = services.values_at(*trip['bus']['services']) + bus_key = "#{trip['bus']['model']} #{trip['bus']['number']}" + bus_id = buses.dig(bus_key, 'id') + + unless from_id + id = cities.size + 1 + cities[trip['from']] = { 'id' => id, 'name' => trip['from'] } + from_id = id + end + + unless to_id + if cities[trip['from']] == @cities[trip['to']] + to_id = from_id + else + id = cities.size + 1 + @cities[trip['to']] = { 'id' => id, 'name' => trip['to'] } + to_id = id + end + end + + unless bus_id + id = buses.size + 1 + buses[bus_key] = + { 'id' => id, 'number' => trip['bus']['number'], 'model' => trip['bus']['model'] } + service_ids.each { |service_id| buses_services << { 'service_id' => service_id, 'bus_id' => id } } + bus_id = id + end + + @connection.put_copy_data("#{from_id};#{to_id};#{trip['start_time']};#{trip['duration_minutes']};#{trip['price_cents']};#{bus_id}\n") + end +end 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/_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 index fa1de9aa..48846c85 100644 --- a/app/views/trips/_trip.html.erb +++ b/app/views/trips/_trip.html.erb @@ -1,5 +1,16 @@ -
  • <%= "Отправление: #{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..32597e04 100644 --- a/app/views/trips/index.html.erb +++ b/app/views/trips/index.html.erb @@ -2,15 +2,7 @@ <%= "Автобусы #{@from.name} – #{@to.name}" %>

    - <%= "В расписании #{@trips.count} рейсов" %> + <%= "В расписании #{@trips.size} рейсов" %>

    -<% @trips.each do |trip| %> - - <%= render "delimiter" %> -<% end %> +<%= render partial: 'trip', collection: @trips, as: :trip %> diff --git a/case-study.md b/case-study.md new file mode 100644 index 00000000..45384b40 --- /dev/null +++ b/case-study.md @@ -0,0 +1,28 @@ +# A. Импорт данных +Обработка medium файла занимает ~ 1мин40сек, по памяти - 111MB + +Сразу же приступил к переписанию на потоковую обработку, выделил код в класс в app/services/reload_json.rb, добавил frozen string literal, все инсерты в таблицы заменил на импорты +После оптимизации обработак medium файла - ~ 1 сек, память - 87 MB + +Обработка large.json - ~ 7.6 сек, память - 87MB + +Обработка 1M.json - ~ 68 сек, память - 116MB + +По отчетам профилировки по CPU и памяти не нашел точек для оптимизации +# Б. Отображение расписаний +Начну с rack-mini-profiler + +Без профилировщика страница с 1004 трипов загружается за 3.4 секунды, с профилировщиком - 19 сек + +Профилировщик показал 450 запросов в app/views/trips/index.html.erb, добавил ```includes``` моделей в app/controllers/trips_controller.rb, заменил @trips.count на @trips.size + +Теперь страница загружается за 11.4 сек, кол-во запросов сократилось до 6 + +Судя по логам и профилировщику большинство времени уходит на рендер паршалов, об этом же говорит и rails panel +![Alt text]() +Перенес весь код касаемый одного трипа в паршал, избавился от рендера остальных паршалов, результат +![Alt text]() +Общее время запроса в среднем 315ms + +Считаю на этом можно остановиться, т.к. улучшение запросов почти не принесет пользы, а импорты могут замедлиться из-за добавления индексов + diff --git a/config/database.yml b/config/database.yml index e116cfa6..b4952a4a 100644 --- a/config/database.yml +++ b/config/database.yml @@ -17,6 +17,8 @@ default: &default adapter: postgresql encoding: unicode + username: postgres + password: postgres # For details on connection pooling, see Rails configuration guide # http://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> diff --git a/config/environments/development.rb b/config/environments/development.rb index 1311e3e4..3f2efde1 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,4 +1,13 @@ Rails.application.configure do + # config.after_initialize do + # Bullet.enable = true + # Bullet.alert = true + # Bullet.bullet_logger = true + # Bullet.console = true + # Bullet.rails_logger = true + # Bullet.add_footer = true + # end + # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on @@ -58,4 +67,7 @@ # Use an evented file watcher to asynchronously detect changes in source code, # routes, locales, etc. This feature depends on the listen gem. config.file_watcher = ActiveSupport::EventedFileUpdateChecker + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) end diff --git a/config/environments/test.rb b/config/environments/test.rb index 0a38fd3c..bc5971ab 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,4 +1,10 @@ Rails.application.configure do + config.after_initialize do + Bullet.enable = true + Bullet.bullet_logger = true + Bullet.raise = true # raise an error if n+1 query occurs + end + # Settings specified here will take precedence over those in config/application.rb. # The test environment is used exclusively to run your application's diff --git a/config/initializers/rack_mini_profiler.rb b/config/initializers/rack_mini_profiler.rb new file mode 100644 index 00000000..ca31423c --- /dev/null +++ b/config/initializers/rack_mini_profiler.rb @@ -0,0 +1,8 @@ +#frozen_string_literal: true + +if Rails.env.development? && ENV['ENABLE_RMP'] + require "rack-mini-profiler" + + # The initializer was required late, so initialize it manually. + Rack::MiniProfilerRails.initialize!(Rails.application) +end diff --git a/config/routes.rb b/config/routes.rb index a2da6a7b..aca1fd7c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,6 @@ 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 '/' => 'statistics#index' + get 'автобусы/:from/:to' => 'trips#index' + mount PgHero::Engine, at: 'pghero' end diff --git a/db/migrate/20231106154303_create_pghero_query_stats.rb b/db/migrate/20231106154303_create_pghero_query_stats.rb new file mode 100644 index 00000000..fbf41263 --- /dev/null +++ b/db/migrate/20231106154303_create_pghero_query_stats.rb @@ -0,0 +1,15 @@ +class CreatePgheroQueryStats < ActiveRecord::Migration[5.2] + def change + create_table :pghero_query_stats do |t| + t.text :database + t.text :user + t.text :query + t.integer :query_hash, limit: 8 + t.float :total_time + t.integer :calls, limit: 8 + t.timestamp :captured_at + end + + add_index :pghero_query_stats, [:database, :captured_at] + end +end diff --git a/db/schema.rb b/db/schema.rb index f6921e45..e7c0bd4c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_03_30_193044) do +ActiveRecord::Schema.define(version: 2023_11_06_154303) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -29,6 +29,17 @@ t.string "name" end + create_table "pghero_query_stats", force: :cascade do |t| + t.text "database" + t.text "user" + t.text "query" + t.bigint "query_hash" + t.float "total_time" + t.bigint "calls" + t.datetime "captured_at" + t.index ["database", "captured_at"], name: "index_pghero_query_stats_on_database_and_captured_at" + end + create_table "services", force: :cascade do |t| t.string "name" end diff --git a/images/Screenshot from 2023-11-06 16-52-17.png b/images/Screenshot from 2023-11-06 16-52-17.png new file mode 100644 index 0000000000000000000000000000000000000000..62330084faa34f3783d715e5944a7ed8caba79f1 GIT binary patch literal 15503 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 H`uYC?YU+0t literal 0 HcmV?d00001 diff --git a/lib/tasks/utils.rake b/lib/tasks/utils.rake index 540fe871..ef7f6876 100644 --- a/lib/tasks/utils.rake +++ b/lib/tasks/utils.rake @@ -1,34 +1,3 @@ -# Наивная загрузка данных из 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 + ReloadJson.new(args.file_name).call end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 00000000..b6317b5a --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,64 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +require 'spec_helper' +ENV['RAILS_ENV'] ||= 'test' +require_relative '../config/environment' +# Prevent database truncation if the environment is production +abort("The Rails environment is running in production mode!") if Rails.env.production? +require 'rspec/rails' +# 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')].sort.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 + + # You can uncomment this line to turn off ActiveRecord support entirely. + # config.use_active_record = false + + # 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") +end diff --git a/spec/services/reload_json_spec.rb b/spec/services/reload_json_spec.rb new file mode 100644 index 00000000..abbd9526 --- /dev/null +++ b/spec/services/reload_json_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'rspec-benchmark' +require 'rails_helper' + +RSpec.configure { |config| config.include RSpec::Benchmark::Matchers } + +describe ReloadJson do + it 'performs under certain time' do + expect { described_class.new('fixtures/medium.json').call }.to perform_under(1000).ms.warmup(1).times + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 00000000..327b58ea --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,94 @@ +# 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 https://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: + # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ + config.disable_monkey_patching! + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end