diff --git a/Gemfile b/Gemfile
index e20b1260..dbd7ba62 100644
--- a/Gemfile
+++ b/Gemfile
@@ -7,6 +7,14 @@ gem 'rails', '~> 5.2.3'
gem 'pg', '>= 0.18', '< 2.0'
gem 'puma', '~> 3.11'
gem 'bootsnap', '>= 1.1.0', require: false
+gem 'rack-mini-profiler'
+gem 'bullet'
+gem 'ruby-prof'
+gem 'stackprof'
+gem 'memory_profiler'
+gem 'oj'
+gem "pghero"
+gem "pg_query", ">= 2"
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
@@ -17,6 +25,7 @@ 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 'meta_request'
end
group :test do
diff --git a/Gemfile.lock b/Gemfile.lock
index fccf6f5f..aead7a62 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,145 +1,188 @@
GEM
remote: https://rubygems.org/
specs:
- actioncable (5.2.3)
- actionpack (= 5.2.3)
+ actioncable (5.2.8.1)
+ actionpack (= 5.2.8.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
- actionmailer (5.2.3)
- actionpack (= 5.2.3)
- actionview (= 5.2.3)
- activejob (= 5.2.3)
+ actionmailer (5.2.8.1)
+ actionpack (= 5.2.8.1)
+ actionview (= 5.2.8.1)
+ activejob (= 5.2.8.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (5.2.3)
- actionview (= 5.2.3)
- activesupport (= 5.2.3)
- rack (~> 2.0)
+ actionpack (5.2.8.1)
+ actionview (= 5.2.8.1)
+ activesupport (= 5.2.8.1)
+ rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (5.2.3)
- activesupport (= 5.2.3)
+ actionview (5.2.8.1)
+ activesupport (= 5.2.8.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
- activejob (5.2.3)
- activesupport (= 5.2.3)
+ activejob (5.2.8.1)
+ activesupport (= 5.2.8.1)
globalid (>= 0.3.6)
- activemodel (5.2.3)
- activesupport (= 5.2.3)
- activerecord (5.2.3)
- activemodel (= 5.2.3)
- activesupport (= 5.2.3)
+ activemodel (5.2.8.1)
+ activesupport (= 5.2.8.1)
+ activerecord (5.2.8.1)
+ activemodel (= 5.2.8.1)
+ activesupport (= 5.2.8.1)
arel (>= 9.0)
- activestorage (5.2.3)
- actionpack (= 5.2.3)
- activerecord (= 5.2.3)
- marcel (~> 0.3.1)
- activesupport (5.2.3)
+ activestorage (5.2.8.1)
+ actionpack (= 5.2.8.1)
+ activerecord (= 5.2.8.1)
+ marcel (~> 1.0.0)
+ activesupport (5.2.8.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
arel (9.0.0)
- bindex (0.6.0)
- bootsnap (1.4.2)
- msgpack (~> 1.0)
- builder (3.2.3)
- byebug (11.0.1)
- concurrent-ruby (1.1.5)
- crass (1.0.4)
- erubi (1.8.0)
- ffi (1.10.0)
- globalid (0.4.2)
- activesupport (>= 4.2.0)
- i18n (1.6.0)
+ bindex (0.8.1)
+ bootsnap (1.16.0)
+ msgpack (~> 1.2)
+ builder (3.2.4)
+ bullet (7.0.7)
+ activesupport (>= 3.0.0)
+ uniform_notifier (~> 1.11)
+ byebug (11.1.3)
+ concurrent-ruby (1.2.2)
+ crass (1.0.6)
+ date (3.3.3)
+ erubi (1.12.0)
+ ffi (1.15.5)
+ globalid (1.1.0)
+ activesupport (>= 5.0)
+ google-protobuf (3.22.2)
+ i18n (1.12.0)
concurrent-ruby (~> 1.0)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
- loofah (2.2.3)
+ loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
- mail (2.7.1)
+ mail (2.8.1)
mini_mime (>= 0.1.1)
- marcel (0.3.3)
- mimemagic (~> 0.3.2)
- method_source (0.9.2)
- mimemagic (0.3.3)
- mini_mime (1.0.1)
- mini_portile2 (2.4.0)
- minitest (5.11.3)
- msgpack (1.2.9)
- nio4r (2.3.1)
- nokogiri (1.10.2)
- mini_portile2 (~> 2.4.0)
- pg (1.1.4)
- puma (3.12.1)
- rack (2.0.6)
- rack-test (1.1.0)
- rack (>= 1.0, < 3)
- rails (5.2.3)
- actioncable (= 5.2.3)
- actionmailer (= 5.2.3)
- actionpack (= 5.2.3)
- actionview (= 5.2.3)
- activejob (= 5.2.3)
- activemodel (= 5.2.3)
- activerecord (= 5.2.3)
- activestorage (= 5.2.3)
- activesupport (= 5.2.3)
+ net-imap
+ net-pop
+ net-smtp
+ marcel (1.0.2)
+ memory_profiler (1.0.1)
+ meta_request (0.7.4)
+ rack-contrib (>= 1.1, < 3)
+ railties (>= 3.0.0, < 7.1)
+ method_source (1.0.0)
+ mini_mime (1.1.2)
+ mini_portile2 (2.8.1)
+ minitest (5.18.0)
+ msgpack (1.6.1)
+ net-imap (0.3.4)
+ date
+ net-protocol
+ net-pop (0.1.2)
+ net-protocol
+ net-protocol (0.2.1)
+ timeout
+ net-smtp (0.3.3)
+ net-protocol
+ nio4r (2.5.8)
+ nokogiri (1.13.10)
+ mini_portile2 (~> 2.8.0)
+ racc (~> 1.4)
+ oj (3.14.2)
+ pg (1.4.6)
+ pg_query (4.2.0)
+ google-protobuf (>= 3.19.2)
+ pghero (2.8.3)
+ activerecord (>= 5)
+ puma (3.12.6)
+ racc (1.6.2)
+ rack (2.2.6.4)
+ rack-contrib (2.3.0)
+ rack (~> 2.0)
+ rack-mini-profiler (3.0.0)
+ rack (>= 1.2.0)
+ rack-test (2.1.0)
+ rack (>= 1.3)
+ rails (5.2.8.1)
+ actioncable (= 5.2.8.1)
+ actionmailer (= 5.2.8.1)
+ actionpack (= 5.2.8.1)
+ actionview (= 5.2.8.1)
+ activejob (= 5.2.8.1)
+ activemodel (= 5.2.8.1)
+ activerecord (= 5.2.8.1)
+ activestorage (= 5.2.8.1)
+ activesupport (= 5.2.8.1)
bundler (>= 1.3.0)
- railties (= 5.2.3)
+ railties (= 5.2.8.1)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
- rails-html-sanitizer (1.0.4)
- loofah (~> 2.2, >= 2.2.2)
- railties (5.2.3)
- actionpack (= 5.2.3)
- activesupport (= 5.2.3)
+ rails-html-sanitizer (1.5.0)
+ loofah (~> 2.19, >= 2.19.1)
+ railties (5.2.8.1)
+ actionpack (= 5.2.8.1)
+ activesupport (= 5.2.8.1)
method_source
rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0)
- rake (12.3.2)
- rb-fsevent (0.10.3)
- rb-inotify (0.10.0)
+ rake (13.0.6)
+ rb-fsevent (0.11.2)
+ rb-inotify (0.10.1)
ffi (~> 1.0)
+ ruby-prof (1.4.3)
ruby_dep (1.5.0)
- sprockets (3.7.2)
+ sprockets (4.2.0)
concurrent-ruby (~> 1.0)
- rack (> 1, < 3)
- sprockets-rails (3.2.1)
- actionpack (>= 4.0)
- activesupport (>= 4.0)
+ rack (>= 2.2.4, < 4)
+ sprockets-rails (3.4.2)
+ actionpack (>= 5.2)
+ activesupport (>= 5.2)
sprockets (>= 3.0.0)
- thor (0.20.3)
+ stackprof (0.2.24)
+ thor (1.2.1)
thread_safe (0.3.6)
- tzinfo (1.2.5)
+ timeout (0.3.2)
+ tzinfo (1.2.11)
thread_safe (~> 0.1)
+ uniform_notifier (1.16.0)
web-console (3.7.0)
actionview (>= 5.0)
activemodel (>= 5.0)
bindex (>= 0.4.0)
railties (>= 5.0)
- websocket-driver (0.7.0)
+ websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
- websocket-extensions (0.1.3)
+ websocket-extensions (0.1.5)
PLATFORMS
ruby
DEPENDENCIES
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)
+ ruby-prof
+ stackprof
tzinfo-data
web-console (>= 3.3.0)
diff --git a/app/controllers/trips_controller.rb b/app/controllers/trips_controller.rb
index acb38be2..1431925c 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)
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 @@
-Сервисы в автобусе:
-
- <% 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..4e26ce11 100644
--- a/app/views/trips/index.html.erb
+++ b/app/views/trips/index.html.erb
@@ -1,16 +1,27 @@
-
- <%= "Автобусы #{@from.name} – #{@to.name}" %>
-
-
- <%= "В расписании #{@trips.count} рейсов" %>
-
+<%= cache([@from.name, @to.name]) do %>
+
+ <%= "Автобусы #{@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 %>
+ <% @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 %>
+<% end %>
\ No newline at end of file
diff --git a/bin/setup b/bin/setup
index f294207b..5534a287 100755
--- a/bin/setup
+++ b/bin/setup
@@ -28,8 +28,8 @@ chdir APP_ROOT do
puts "\n== Preparing database =="
system! 'bin/rails db:setup'
- puts "\n== Loading data from fixtures/small.json =="
- system! 'bin/rake reload_json[fixtures/small.json]'
+ puts "\n== Loading data from fixtures/large.json =="
+ system! 'bin/rake reload_json[fixtures/large.json]'
puts "\n== Removing old logs and tempfiles =="
system! 'bin/rails log:clear tmp:clear'
diff --git a/case-study.md b/case-study.md
new file mode 100644
index 00000000..6577fee0
--- /dev/null
+++ b/case-study.md
@@ -0,0 +1,24 @@
+# Case-study оптимизации
+
+## Оптимизация импорта
+Изначально время импорта файла large.json занимало ~450s
+Я переписал импорт сразу с потоковой записью в `Postgres`. После рефакторинга время обарботки файла large.json стало ~13s
+
+## Оптимизация рендеринга страницы
+Изначальное время рендеринга страницы с данными из файла large.json было ~34000ms.
+
+### Правка №1
+bullet и rack mini profiler указывали на проблему n+1
+Добавил includes к формированию списка поездок:
+@trips = Trip.includes(bus: :services).where(from: @from, to: @to).order(:start_time)
+После рефакторинга время рендера страницы изменилось с ~34000ms до ~24000ms
+
+### Правка №2
+Rails panel указвыл, что практически все время тратиться на рендеринг шаблонов. Решил убрать все шаблоны и формировать html сразу на одной странице, тк он достаточно простой.
+После рефакторинга время рендера страницы изменилось с ~24000ms до ~6000ms
+
+### Правка №3
+Решил установить pg_hero и проанализировать запросы. pg_hero не предлавагал добавлять никакие индексы и все было в норме. Но я добавил индыксы на имя города, и составной индекс для трипов по from_id, to_id. Какой-то прибавки к скорости рендера это не дало, все осталось в пределах погрешности.
+
+### Правка №4
+Принял решение добавить кэширование. Закэшировал сразу всю страницу index. Время рендера закешированной версии страницы изменилось с ~6000ms до ~250ms
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 1311e3e4..bc8c0849 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -58,4 +58,14 @@
# 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
+
+ config.after_initialize do
+ Bullet.enable = true
+ Bullet.bullet_logger = true
+ Bullet.console = true
+ Bullet.rails_logger = true
+ Bullet.add_footer = true
+ Bullet.skip_html_injection = false
+ Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
+ end
end
diff --git a/config/routes.rb b/config/routes.rb
index a2da6a7b..84053141 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,5 +1,7 @@
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
+ mount PgHero::Engine, at: "pghero"
+
get "/" => "statistics#index"
get "автобусы/:from/:to" => "trips#index"
end
diff --git a/db/migrate/20230327052906_create_pghero_query_stats.rb b/db/migrate/20230327052906_create_pghero_query_stats.rb
new file mode 100644
index 00000000..fbf41263
--- /dev/null
+++ b/db/migrate/20230327052906_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/migrate/20230327070032_add_indexes.rb b/db/migrate/20230327070032_add_indexes.rb
new file mode 100644
index 00000000..9f22feab
--- /dev/null
+++ b/db/migrate/20230327070032_add_indexes.rb
@@ -0,0 +1,6 @@
+class AddIndexes < ActiveRecord::Migration[5.2]
+ def change
+ add_index :cities, :name
+ add_index :trips, [:from_id, :to_id]
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f6921e45..07f86208 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,9 +10,10 @@
#
# 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_03_27_070032) do
# These are extensions that must be enabled in order to support this database
+ enable_extension "pg_stat_statements"
enable_extension "plpgsql"
create_table "buses", force: :cascade do |t|
@@ -27,6 +28,18 @@
create_table "cities", force: :cascade do |t|
t.string "name"
+ t.index ["name"], name: "index_cities_on_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|
@@ -40,6 +53,7 @@
t.integer "duration_minutes"
t.integer "price_cents"
t.integer "bus_id"
+ t.index ["from_id", "to_id"], name: "index_trips_on_from_id_and_to_id"
end
end
diff --git a/lib/data_import.rb b/lib/data_import.rb
new file mode 100644
index 00000000..5df75dfc
--- /dev/null
+++ b/lib/data_import.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+
+class DataImport
+ def initialize(file_name)
+ @file_name = file_name
+ @cities = {}
+ @buses = {}
+ @buses_services = {}
+ end
+
+ def call
+ time = Benchmark.realtime do
+ @cities = {}
+ @buses = {}
+ @buses_services = {}
+
+ ActiveRecord::Base.transaction do
+ 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 = +""
+
+ while !ff.eof?
+ ch = ff.read(1) # читаем по одному символу
+ case
+ when ch == '{' # начинается объект, повышается вложенность
+ nesting += 1
+ str << ch
+ when ch == '}' # заканчивается объект, понижается вложенность
+ nesting -= 1
+ str << ch
+ if nesting == 0 # если закончился объкет уровня trip, парсим и импортируем его
+ trip = Oj.load(str)
+ import_trip(trip, connection)
+ str = +""
+ end
+ when nesting >= 1
+ str << ch
+ end
+ end
+ end
+ end
+
+ services_command = "copy services (name) from stdin with csv delimiter ';'"
+ connection.copy_data services_command do
+ Service::SERVICES.each do |service|
+ connection.put_copy_data("#{service}\n")
+ end
+ end
+
+ cities_command = "copy cities (name) from stdin with csv delimiter ';'"
+ connection.copy_data cities_command do
+ @cities.keys.each do |city_name|
+ connection.put_copy_data("#{city_name}\n")
+ end
+ end
+
+ buses_command = "copy buses (model, number) from stdin with csv delimiter ';'"
+ connection.copy_data buses_command do
+ @buses.keys.each do |bus_key|
+ connection.put_copy_data("#{bus_key}\n")
+ end
+ end
+
+ buses_services_command = "copy buses_services (bus_id, service_id) from stdin with csv delimiter ';'"
+ connection.copy_data buses_services_command do
+ @buses_services.each do |bus_id, service_ids|
+ service_ids.each do |service_id|
+ connection.put_copy_data("#{bus_id};#{service_id}\n")
+ end
+ end
+ end
+ end
+ end
+ puts "Finish in #{time.round(2)}"
+ end
+
+
+ private
+
+
+ def import_trip(trip, connection)
+ from_id = @cities[trip['from']]
+ if !from_id
+ from_id = @cities.size + 1
+ @cities[trip['from']] = from_id
+ end
+
+ to_id = @cities[trip['to']]
+ if !to_id
+ to_id = @cities.size + 1
+ @cities[trip['to']] = to_id
+ end
+
+ bus = trip['bus']
+ bus_key = "#{bus['model']};#{bus['number']}"
+ bus_id = @buses[bus_key]
+ if !bus_id
+ bus_id = @buses.size + 1
+ @buses[bus_key] = bus_id
+ end
+ service_ids = @buses_services[bus_id]
+ if !service_ids
+ @buses_services[bus_id] = []
+ bus['services'].each do |service|
+ service_id = Service::SERVICES.index(service) + 1
+ @buses_services[bus_id] << service_id
+ end
+ end
+
+ # стримим подготовленный чанк данных в postgres
+ connection.put_copy_data("#{from_id};#{to_id};#{trip['start_time']};#{trip['duration_minutes']};#{trip['price_cents']};#{bus_id}\n")
+ end
+end
\ No newline at end of file
diff --git a/lib/tasks/utils.rake b/lib/tasks/utils.rake
index 540fe871..0bb084d6 100644
--- a/lib/tasks/utils.rake
+++ b/lib/tasks/utils.rake
@@ -1,34 +1,7 @@
-# Наивная загрузка данных из json-файла в БД
+require 'data_import'
+
# 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
+ DataImport.new(args.file_name).call
end
+