Легко

Ruby / Основы

Условные конструкции

Какие условные конструкции есть в Ruby? В чём разница между if/unless/case?
if — выполняет код если условие true unless — наоборот, выполняет если false (эквивалент if !condition) case/when — множественный выбор (аналог switch в других языках) ternary — сокращённая форма: condition ? a : b Все конструкции возвращают значение (в Ruby всё выражение): x = if condition "yes" else "no" end В Ruby можно писать if в конце строки (modifier): puts "hello" if condition
Легко

Ruby / Основы

Методы

Как определить метод? Что такое неявный возврат? Что возвращает метод без return?
def greet(name) "Hello, #{name}" end greet("Alice") # => "Hello, Alice" Метод возвращает значение последнего выражения (неявный возврат). return не обязателен. def add(a, b) a + b end add(2, 3) # => 5 Значения по умолчанию: def greet(name = "World") "Hello, #{name}" end greet # => "Hello, World" greet("Bob") # => "Hello, Bob" ? в названии метода — соглашение: метод возвращает boolean: def even?(n) n % 2 == 0 end ! в названии — метод модифицирует объект на месте: [1, 2, 3].sort # => [1, 2, 3] [3, 1, 2].sort! # => [1, 2, 3] (исходный массив изменён)
Средне

Ruby / ООП

Инкапсуляция

Что такое public, private, protected? В чём разница между private и protected?
public — метод доступен отовсюду (по умолчанию) private — метод доступен только внутри объекта (нельзя вызвать с явным получателем: self.method — ошибка) protected — метод доступен внутри объекта И для других объектов того же класса class Person def initialize(age) @age = age end def older_than?(other) @age > other.age # protected позволяет обратиться к age другого объекта Person end protected def age @age end end Если бы age был private — other.age вызвало бы ошибку. protected нужен для сравнения объектов между собой.
Легко

Ruby / Переменные

Символы

Что такое символы (Symbol)? Чем :name отличается от "name"? Когда использовать символы?
Символ — неизменяемая строка-константа. Каждый :name указывает на один и тот же объект в памяти. "name".object_id != "name".object_id # разные объекты :name.object_id == :name.object_id # один и тот же объект Символы используются как ключи в хэшах, имена опций, имена методов: # Хэш с символьными ключами (частый паттерн) { name: "Alice", age: 25 } # Как ключи в options hash def connect(host:, port:, ssl: true) end Ruby 3.1+ добавил frozen string literal по умолчанию — разница между Symbol и String уменьшилась, но символы всё ещё быстрее для ключей хэша.
Легко

Ruby / Строки

Методы строк

Какие основные методы строк в Ruby вы знаете?
size / length — длина строки upcase / downcase — регистр strip — убирает пробелы по краям capitalize — первая буква заглавная reverse — разворачивает строку include?(str) — содержит подстроку split(separator) — разбивает на массив join(separator) — склеивает массив в строку start_with? / end_with? — проверка начала/конца gsub(pattern, replacement) — глобальная замена sub(pattern, replacement) — замена первого совпадения to_i / to_f — конвертация в число to_s — конвертация в строку empty? — пустая ли строка chars — массив символов chomp — убирает \n в конце squeeze — убирает дублирующиеся символы
Легко

Ruby / Строки

Интерполяция

Что такое интерполяция строк? В чём разница одинарных и двойных кавычек?
Двойные кавычки поддерживают интерполяцию и escape-последовательности: name = "Alice" "Hello, #{name}" # => "Hello, Alice" "2 + 2 = #{2 + 2}" # => "2 + 2 = 4" Одинарные кавычки — строка как есть, без интерполяции: 'Hello, #{name}' # => "Hello, \#{name}" Также в двойных кавычках работают escape-последовательности: "\n" — перенос строки "\t" — табуляция "\\" — обратный слеш Форматирование (sprintf): "%s is %d years old" % ["Alice", 25] # => "Alice is 25 years old" format("%s is %d", "Alice", 25)
Легко

Ruby / Коллекции и Enumerable

Массивы: создание и основные методы

Как создать массив в Ruby? Какие основные методы работы с массивами?
Создание: [1, 2, 3] # литерал Array.new(3, 0) # [0, 0, 0] (1..5).to_a # [1, 2, 3, 4, 5] %w[apple banana] # ["apple", "banana"] Основные методы: arr = [3, 1, 2] arr.length / arr.size # 3 arr.push(4) / arr << 4 # [3, 1, 2, 4] — добавить в конец arr.pop # 4 — удалить последний arr.unshift(0) # [0, 3, 1, 2] — добавить в начало arr.shift # 0 — удалить первый arr.include?(1) # true arr.reverse # [2, 1, 3] arr.sort # [1, 2, 3] arr.flatten # развернуть вложенные arr.uniq # уникальные arr.join(", ") # "3, 1, 2" arr.sample # случайный элемент arr.first / arr.last # первый / последний arr.empty? # false
Легко

Ruby / Коллекции и Enumerable

Хэши: создание и методы

Как создать хэш в Ruby? Какие основные методы?
Создание: { name: "Иван", age: 25 } # символы как ключи { "name" => "Иван" } # строки как ключи Hash.new(0) # с дефолтом (0 для отсутствующих) Hash.new { |h, k| h[k] = [] } # с дефолтом-массивом Основные методы: h = { a: 1, b: 2, c: 3 } h[:a] # 1 h[:d] # nil h.fetch(:d, 0) # 0 (с дефолтом) h[:d] = 4 # добавить/изменить h.keys # [:a, :b, :c] h.values # [1, 2, 3] h.key?(:a) # true h.merge(d: 4) # объединить (новый хэш) h.merge!(d: 4) # объединить (изменить оригинал) h.delete(:a) # удалить ключ, вернуть значение h.select { |k, v| v > 1 } # фильтровать h.transform_values { |v| v * 2 } # изменить значения h.dig(:a, :b, :c) # безопасный доступ к вложенным
Легко

Ruby / Коллекции и Enumerable

Enumerable: any?, all?, none?, one?

Что делают методы any?, all?, none?, one? из модуля Enumerable?
Эти методы проверяют условие на элементах коллекции и возвращают true/false. [1, 2, 3].any? { |x| x > 2 } # true (хотя бы один) [1, 2, 3].all? { |x| x > 0 } # true (все подходят) [1, 2, 3].none? { |x| x < 0 } # true (ни один не подходит) [1, 2, 3].one? { |x| x > 2 } # true (ровно один) Без блока — проверяют truthiness: [nil, false].any? # false [1, nil].any? # true Работают с любым Enumerable: массивы, хэши, диапазоны.
Средне

Ruby / Коллекции и Enumerable

Enumerable: group_by, partition, tally

Что делают group_by, partition, tally? Примеры использования.
group_by — группирует по результату блока: ["apple", "bat", "car", "ant"].group_by { |w| w[0] } # => { "a"=>["apple", "ant"], "b"=>["bat"], "c"=>["car"] } (1..6).group_by { |n| n.even? } # => { false=>[1, 3, 5], true=>[2, 4, 6] } partition — делит на два массива [true, false]: (1..6).partition { |n| n.even? } # => [[2, 4, 6], [1, 3, 5]] tally — считает сколько раз каждый элемент встречается (Ruby 2.7+): ["a", "b", "a", "c", "b", "a"].tally # => { "a"=>3, "b"=>2, "c"=>1 } count с блоком — посчитать подходящие: [1, 2, 3, 4, 5].count { |x| x.even? } # => 2
Легко

Ruby / Исключения

Обработка исключений: begin/rescue

Как обрабатывать исключения в Ruby? Как устроен блок begin/rescue?
begin/rescue/ensure/else — конструкция для обработки ошибок: begin result = risky_operation rescue StandardError => e puts "Ошибка: #{e.message}" # обработка ошибки else # выполняется если НЕ было ошибки puts "Всё хорошо: #{result}" ensure # выполняется ВСЕГДА (ошибка или нет) cleanup_resources end rescue перехватывает указанный класс исключений и его потомков. => e — сохраняет объект исключения в переменную e. Можно несколько rescue: rescue ArgumentError => e # ... rescue StandardError => e # всё остальное Типичные классы: StandardError, ArgumentError, TypeError, NoMethodError, RuntimeError, ZeroDivisionError, IOError.
Средне

Ruby / Исключения

raise и типы исключений

Как вызвать исключение вручную? Какие типы исключений бывают?
raise — выбрасывает исключение: raise "Что-то пошло не так" # RuntimeError raise ArgumentError, "Неверный аргумент" # ArgumentError raise CustomError.new("детали") # своё исключение Иерархия исключений: Exception ├── StandardError # ловить нужно ЭТО │ ├── ArgumentError │ ├── TypeError │ ├── NoMethodError │ ├── RuntimeError │ ├── ZeroDivisionError │ ├── IOError │ └── ... ├── SystemExit ├── SignalException └── ... Важно: никогда не ловите Exception — только StandardError и его потомки. rescue без класса = rescue StandardError. Пользовательское исключение: class PaymentError < StandardError; end raise PaymentError, "Недостаточно средств"
Средне

Ruby / Исключения

retry в rescue

Что такое retry в блоке rescue? Как использовать?
retry — повторяет блок begin с начала. Полезно для повторных попыток: attempts = 0 begin attempts += 1 response = HTTParty.get("https://api.example.com/data") rescue Net::OpenTimeout => e if attempts < 3 sleep(2 ** attempts) # экспоненциальная задержка: 2, 4, 8 retry # повторить begin с начала else raise # пробросить исключение дальше end end Осторожно: без счётчика attempts retry создаст бесконечный цикл. В Rails чаще используют retry_on в ActiveJob: retry_on Net::OpenTimeout, wait: 5.seconds, attempts: 3
Средне

Ruby / Исключения

Исключения: практики и антипаттерны

Какие есть антипаттерны при работе с исключениями в Ruby?
Антипаттерны: 1. Пустой rescue — проглатывает ошибку без обработки: rescue => e # ничего не делаем — ОПАСНО Лучше: хотя бы логировать Rails.logger.error(e.message) 2. rescue Exception — ловит ВСЁ, включая SystemExit: rescue Exception => e # НЕ ДЕЛАЙТЕ ТАК Правильно: rescue StandardError => e 3. Использование исключений для управления потоком: begin user = User.find(id) rescue ActiveRecord::RecordNotFound # лучше: User.find_by(id: id) end 4. Слишком широкий rescue в начале: rescue StandardError => e # ловит всё Лучше: rescue конкретный класс (ArgumentError и т.д.) Правильные практики: — Логируй ошибку — Используй ensure для очистки ресурсов — Создавай свои классы исключений для бизнес-логики — rescue конкретные классы, а не StandardError
Средне

Ruby / Блоки, Proc, Lambda

&block и передача блока в метод

Как передать блок в метод? Что такое &block?
Неявный блок (yield): def hello yield "Мир" end hello { |name| puts "Привет, #{name}!" } # => Привет, Мир! Явный &block — превращает блок в Proc: def hello(&block) block.call("Мир") end hello { |name| puts "Привет, #{name}!" } & в reverse — Proc в блок: doubler = Proc.new { |x| x * 2 } [1, 2, 3].map(&doubler) # => [2, 4, 6] # &doubler превращает Proc обратно в блок для map Передача метода как блока: ["hello", "world"].map(&:upcase) # => ["HELLO", "WORLD"] # &:upcase — создаёт Proc, вызывающий upcase на объекте block_given? — проверить, передан ли блок: def hello if block_given? yield else puts "Нет блока" end end
Средне

Ruby / Блоки, Proc, Lambda

Лямбды в Rails: scope, callback

Где лямбды используются в Rails? Приведи примеры.
Scope — именованные запросы: class Post < ApplicationRecord scope :published, -> { where(published: true) } scope :recent, -> { where("created_at > ?", 1.week.ago) } scope :by_author, ->(name) { where(author: name) } end Post.published.recent.by_author("Иван") Callbacks с условиями: class User < ApplicationRecord before_save -> { self.email = email.downcase } validates :password, length: { minimum: 8 }, if: -> { password.present? } end before_action в контроллерах: before_action -> { authorize @post }, only: [:edit, :update] Стратегия через хэш лямбд: HANDLERS = { confirmed: ->(order) { OrderMailer.confirmation(order).deliver_later }, cancelled: ->(order) { RefundService.call(order) } }.freeze Почему лямбды, а не Proc: — Строгие к аргументам (упадёт сразу если забыл передать) — return не ломает метод — Короткий синтаксис -> () { }
Средне

Ruby / Регулярные выражения

Регулярные выражения в Ruby

Что такое регулярные выражения? Какие основные методы работы с ними в Ruby?
Регулярное выражение — шаблон для поиска и замены в строках. Создание: /pattern/ # литерал Regexp.new("pat") # из строки Основные методы: "hello world" =~ /world/ # => 6 (индекс совпадения) "hello world".match(/world/) # => #<MatchData "world"> "hello 123".scan(/\d/) # => ["1", "2", "3"] "hello".gsub(/[aeiou]/, "*") # => "h*ll*" "hello 123".match?(/\d/) # => true (Ruby 2.4+) Основные паттерны: /\d/ — цифра /\D/ — НЕ цифра /\w/ — буква/цифра/_ /\W/ — НЕ буква/цифра /\s/ — пробел /\S/ — НЕ пробел /./ — любой символ /^/ — начало строки /$/ — конец строки /[a-z]/ — диапазон /pattern/i — без учёта регистра Квантификаторы: /\d*/ — 0 или более /\d+/ — 1 или более /\d?/ — 0 или 1 /\d{3}/ — ровно 3 /\d{2,4}/ — от 2 до 4 Группы и захват: /(\d{4})-(\d{2})-(\d{2})/.match("2024-01-15") # $1 => "2024", $2 => "01", $3 => "15"
Средне

Ruby / Многопоточность

Многопоточность в Ruby: Thread и GIL

Как работает многопоточность в Ruby? Что такое GIL?
Thread — класс для создания потоков: t1 = Thread.new { 3.times { puts "Поток 1"; sleep(1) } } t2 = Thread.new { 3.times { puts "Поток 2"; sleep(1) } } t1.join # ждать завершения t2.join GIL (Global Interpreter Lock) — в MRI Ruby только один поток выполняет Ruby-код одновременно. Это значит: — Потоки НЕ дают параллелизма для CPU-задач — Но дают параллелизм для I/O (сеть, файлы, БД) Race condition — проблема при работе с общими данными: @counter = 0 threads = 10.times.map do Thread.new { 1000.times { @counter += 1 } } end threads.each(&:join) @counter # может быть < 10000! Mutex — решение race condition: mutex = Mutex.new @counter = 0 threads = 10.times.map do Thread.new do 1000.times { mutex.synchronize { @counter += 1 } } end end Ractor (Ruby 3.0+) — настоящий параллелизм без GIL, но с ограничениями (объекты должны быть thread-safe).
Средне

Ruby / Многопоточность

Thread-safe в Rails

Что значит thread-safe? Почему это важно в Rails?
Thread-safe — код корректно работает при одновременном выполнении в нескольких потоках. В Rails (Puma) — несколько потоков обрабатывают запросы одновременно. Поэтому: НЕ thread-safe (опасно): — Глобальные переменные ($var) — Переменные класса (@@var) — Модификация констант — Общий mutable state между потоками Thread-safe (безопасно): — Локальные переменные (они создаются на каждый вызов) — @instance переменные в контроллере (новый объект на запрос) — Immutable объекты (замороженные) — Базы данных (транзакции) Rails по умолчанию thread-safe с Rails 4+: config.threadsafe! — больше не нужен, включено всегда. config.eager_load = true в production — загрузить весь код при старте. config/puma.rb: threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } # Puma создаёт пул потоков, каждый обрабатывает запрос
Легко

Ruby / Внутреннее устройство

Everything is an Object

Что значит «в Ruby всё — объект»? Приведи примеры.
В Ruby всё является объектом — даже числа, строки, nil, true/false: 5.class # => Integer "hello".class # => String nil.class # => NilClass true.class # => TrueClass [1,2].class # => Array :symbol.class # => Symbol У всего есть методы (потому что всё наследуется от Object): 42.is_a?(Object) # => true nil.is_a?(Object) # => true "hi".respond_to?(:upcase) # => true Даже классы — объекты: String.class # => Class Class.class # => Class Методы можно вызывать цепочкой: " hello ".strip.upcase.reverse # => "OLLEH" Нет примитивов как в Java — всё объекты с методами.
Средне

Ruby / Внутреннее устройство

method_missing

Что такое method_missing? Зачем нужен? Какие опасности?
method_missing — метод, вызываемый когда метод не найден. class User def initialize(data) @data = data end def method_missing(name, *args) if name.to_s.end_with?("?") @data.key?(name.to_s.chomp("?")) else @data[name.to_s] end end def respond_to_missing?(name, include_private = false) true # важно! иначе respond_to? вернёт false end end user = User.new("name" => "Иван", "age" => 25) user.name # => "Иван" user.age # => 25 user.name? # => true Опасности: — Ловит опечатки: user.nme вместо user.name — не упадёт — Медленнее обычных методов — Нужно переопределять respond_to_missing? тоже Используется в: OpenStruct, динамических делегатах, некоторых DSL (Rake, RSpec). Правило: если можно обойтись без method_missing — обойдись.
Средне

Ruby / Внутреннее устройство

freeze и frozen?

Что такое freeze в Ruby? Зачем нужен?
freeze — делает объект неизменяемым (immutable): arr = [1, 2, 3].freeze arr << 4 # FrozenError: can't modify frozen Array str = "hello".freeze str.upcase! # FrozenError frozen? — проверяет, заморожен ли: "hello".freeze.frozen? # => true "hello".frozen? # => false Зачем: 1. Безопасность — защита констант от изменения: COLORS = %w[red green blue].freeze 2. Оптимизация памяти — Ruby не создаёт новые объекты для одинаковых замороженных строк: "hello".freeze # один объект в памяти # без freeze — каждый раз новый объект frozen_string_literal: true — в начале файла: # frozen_string_literal: true # Все строковые литералы в файле автоматически заморожены "hello" << " world" # FrozenError Важно: freeze замораживает только сам объект, не вложенные: arr = [[1], [2]].freeze arr << [3] # FrozenError arr[0] << 10 # работает! Внутренний массив не заморожен
Средне

Ruby / Внутреннее устройство

Monkey patching

Что такое monkey patching? Зачем нужен? Какие риски?
Monkey patching — изменение или добавление методов в существующий класс во время выполнения программы: class String def shout self.upcase + "!!!" end end "hello".shout # => "HELLO!!!" Используется: — ActiveSupport в Rails добавляет методы в стандартные классы: 3.days.ago, "hello".blank?, [1,2,3].sum — Гемы расширяют классы Ruby — Быстрый фикс бага в чужой библиотеке Риски: — Конфликты: два гема добавляют метод с одинаковым именем — Неочевидность: непонятно откуда появился метод — Хрупкость: обновление Ruby/Rails может сломать патч Рефайнменты (refinements) — безопасная альтернатива: module ShoutString refine String do def shout upcase + "!!!" end end end using ShoutString # действует только в этой области "hello".shout # => "HELLO!!!" # За пределами scope метод не виден
Средне

Ruby on Rails / Основы Rails

Структура Rails

Знать и рассказать структуру папок Rails приложения.
app — основные файлы приложения assets — картинки, стили, js controllers — контроллеры helpers — хелперы jobs — задания mailers — рассыльщики models — модели views — представления layouts — макеты config — конфигурация маршрутов, базы данных и т.д environments — настройки сред приложения locales — интернационализация db — текущая схема базы данных, сиды migrates — файлы миграции lib — внешние модули log — журналы логов public — доступна извне как есть, статичные файлы и скомпилированные ассеты test — структурирована по тестам моделей / контроллеров / интеграционным tmp — временные файлы (такие как файлы кэша и pid) vendor — код сторонних разработчиков, например, внешние гемы
Легко

Ruby on Rails / Основы Rails

Scaffolding

Что такое scaffolding? Зачем он используется и где применяется?
Rails Scaffold - встроенный генератор, который запускает другие генераторы Rails, чтобы одной командой сгенерировать набор из модели, контроллера, вьюх, тестов, миграций и т.д. Предоставляется возможность создавать собственные предустановки генерации.
Средне

Frontend / Rails Frontend

form_with vs form_tag

Чем отличаются form_with и form_tag? Как form_with определяет POST или PATCH? Что значит model: [:admin, @category]?
form_with model: @article → привязана к модели form_with url: articles_path → без модели, вручную form_with + model сама определяет HTTP-метод: @article.new_record? → POST /articles (создание) @article persisted? → PATCH /articles/:id (обновление) model: [:admin, @article] — массив: :admin → namespace, префикс /admin/ @article → объект, определяет URL и метод Без model пришлось бы писать вручную: form_with url: admin_articles_path, method: :post # создание form_with url: admin_article_path(@article), method: :patch # редактирование form_with также подставляет значения полей из объекта: @article.title = "Ruby" → f.text_field :title отрендерит value="Ruby" Strong Parameters защищают от массового присвоения: params.require(:article).permit(:title, :body) — разрешены только title и body, всё остальное игнорируется
Средне

Frontend / Rails Frontend

redirect_to vs render

В чём разница между redirect_to и render? Что будет если после create использовать render вместо redirect_to и пользователь нажмёт F5?
redirect_to — браузер делает НОВЫЙ запрос (HTTP 302) redirect_to articles_path → браузер получает 302 → делает GET /articles → переменные обнуляются, данные в БД обновлены render — браузер получает HTML сразу (без нового запроса) render :new → браузер получает 200 OK → переменные сохраняются (@article с ошибками) → форма показывает ошибки валидации Почему после create/update нужен redirect_to: Если использовать render — при F5 браузер повторит POST → данные отправятся снова (дублирование) → redirect_to делает GET — F5 безопасно Паттерн CRUD: успех → redirect_to (защита от повторной отправки) ошибка валидации → render (показать ошибки в форме) status: :unprocessable_entity (422) нужен для Turbo: без него Turbo думает что всё OK и не обновляет форму
Средне

Frontend / CSS

CSS Specificity

Что такое специфичность CSS? В каком порядке применяются стили при конфликте?
Specificity (специфичность) — алгоритм браузера для выбора, какое CSS-правило применить при конфликте. Приоритет от слабого к сильному: 1. !important — перебивает всё (избегать) 2. Inline стили (style="...") — 1000 3. #id — 100 4. .class, :pseudo-class, [attribute] — 10 5. element, ::pseudo-element — 1 6. * (универсальный) — 0 Пример подсчёта: div .menu li a → 0,1,1,3 (1 класс + 3 элемента) #nav .item:hover → 1,1,1,0 (1 id + 1 класс + 1 псевдокласс) #nav .item:hover побеждает — выше специфичность Правило: чем специфичнее селектор, тем выше приоритет. При равной специфичности побеждает правило, объявленное позже.
Легко

Frontend / CSS

Box Model

Что такое CSS Box Model? Чем отличается box-sizing: content-box от border-box?
Каждый HTML-элемент — прямоугольная коробка из 4 слоёв: ┌─────────────────────────┐ │ margin │ │ ┌───────────────────┐ │ │ │ border │ │ │ │ ┌─────────────┐ │ │ │ │ │ padding │ │ │ │ │ │ ┌─────────┐ │ │ │ │ │ │ │ content │ │ │ │ │ │ │ └─────────┘ │ │ │ │ │ └─────────────┘ │ │ │ └───────────────────┘ │ └─────────────────────────┘ content-box (по умолчанию): width: 100px + padding: 20px + border: 5px → реальная ширина = 100 + 20*2 + 5*2 = 150px border-box (рекомендуется): width: 100px — ВКЛЮЧАЕТ padding и border → реальная ширина = 100px (content сжимается) Поэтому в проектах используют: *, *::before, *::after { box-sizing: border-box; } Это спасает от «съехавшей» вёрстки при добавлении padding/border.
Средне

Frontend / CSS

Flexbox vs Grid

В чём разница между Flexbox и CSS Grid? Когда использовать каждый?
Flexbox — одномерный (строка ИЛИ столбец): навбар, кнопки в ряд, карточки в одну линию Grid — двумерный (строки И столбцы одновременно): галерея, таблица, дашборд, сложная раскладка страницы Flexbox: display: flex justify-content — выравнивание по главной оси align-items — по поперечной оси flex-direction: row | column gap — отступы между элементами Grid: display: grid grid-template-columns: repeat(3, 1fr) — 3 равные колонки grid-template-rows — строки gap — отступы Правило: Компонент в одну линию → Flexbox Двумерная раскладка → Grid Не уверены → Flexbox (проще начать)
Средне

Frontend / Hotwire

Hotwire: Turbo vs Stimulus

Что такое Hotwire? Чем отличаются Turbo и Stimulus? Зачем они нужны в Rails?
Hotwire — подход Rails к фронтенду БЕЗ JavaScript-фреймворков. Вместо React/Vue — сервер рендерит HTML, а Turbo/Stimulus его оживляют. Turbo — работает с HTML поверх HTTP: Turbo Drive — перехватывает клики по ссылкам, грузит через AJAX (страница обновляется без полной перезагрузки) Turbo Frames — обновляет часть страницы (как iframe, но лучше: только нужный блок перерисовывается) Turbo Streams — сервер отправляет HTML-обновления через WebSocket (append, replace, remove элементов без JS) Stimulus — минимальный JS для интерактивности: data-controller="slider" — подключает JS-контроллер к элементу data-action="click->slider#next" — обработчик событий data-slider-index-value="0" — данные для контроллера Разница: Turbo — обновляет DOM с сервера (HTML over the wire) Stimulus — добавляет интерактивность на клиенте (код-редактор, модалки) Аналогия: Turbo — официант (приносит готовые блюда) Stimulus — приборы (нарезаешь еду сам)
Средне

Ruby on Rails / Контроллеры

Flash-сообщения

Что такое flash и flash.now? В чём разница?
flash — сообщение, доступное в СЛЕДУЮЩЕМ запросе: redirect_to @post, notice: "Создано" redirect_to @post, alert: "Ошибка" flash.now — в ТЕКУЩЕМ запросе (при render): flash.now[:alert] = "Исправьте ошибки" render :new Ключи: notice (успех), alert (ошибка), можно любые. Во layout: <% flash.each do |type, msg| %> <div class="<%= type %>"><%= msg %></div> <% end %> flash.keep — сохранить ещё на один запрос. flash.discard — очистить.
Сложно

Frontend / Hotwire

Turbo Frames

Что такое Turbo Frames? Как они работают? Приведи пример использования.
Turbo Frame — тег <turbo-frame> который обновляется отдельно от страницы. Как работает: 1. В HTML: <turbo-frame id="task_content">...</turbo-frame> 2. Клик по ссылке ВНУТРИ frame → Turbo делает AJAX-запрос 3. Из ответа берётся ТОЛЬКО содержимое этого frame (по id) 4. Остальная страница не обновляется Пример — вкладки в задаче: show.html.slim: = turbo_frame_tag "task_content" do = link_to "Задача", task_path(@task) = link_to "Вывод", check_task_path(@task) check.html.slim (ответ сервера): = turbo_frame_tag "task_content" do div Результат выполнения кода Клик по "Вывод" → GET /tasks/:slug/check → сервер возвращает только frame "task_content" → Turbo заменяет его на странице Важно: — id frame должен совпадать в запросе и ответе — ссылка ВНЕ frame обновляет всю страницу (или можно указать data: { turbo_frame: "task_content" }) — Форма с data: { turbo_frame: "task_content" } отправит AJAX и ответ заменит только этот frame
Средне

Frontend / Asset Pipeline

Asset Pipeline в Rails

Что такое Asset Pipeline? Чем Sprockets отличается от Propshaft? Как подключить CSS/JS в Rails 8?
Asset Pipeline — система обработки CSS, JS, изображений в Rails. Задачи: 1. Компиляция — Sass → CSS, TypeScript → JS, JSX → JS 2. Бандлинг — объединение файлов в один 3. Минификация — убирает пробелы, сокращает имена переменных 4. Фingerprinting — добавляет хэш в имя файла application-a1b2c3.css → при изменении хэш меняется → браузер скачивает новую версию (нет кэша) Sprockets (старый): — встроенная компиляция Sass, CoffeeScript — медленный, сложный — gem 'sprockets-rails' Propshaft (новый, Rails 7+): — только fingerprinting и отдача файлов — НЕТ компиляции — используй esbuild/vite для JS, Tailwind CLI для CSS — быстрее и проще — gem 'propshaft' Rails 8 типичная схема: CSS: Tailwind CLI (через gem 'tailwindcss-rails') JS: esbuild (npm, собирает Stimulus-контроллеры) Assets: Propshaft (отдаёт файлы) Подключение в layout: = stylesheet_link_tag "application" = javascript_include_tag "application", type: "module"
Средне

Frontend / Asset Pipeline

Организация фронтенда в Rails

Как организовать CSS и JavaScript в Rails-приложении? Где хранить стили и контроллеры?
Rails 8 — типичная структура фронтенда: app/javascript/ controllers/ — Stimulus-контроллеры code_editor_controller.js filter_controller.js application.js — точка входа, импортирует все контроллеры app/assets/stylesheets/ application.tailwind.css — главный CSS-файл app/assets/images/ — картинки Как это работает: 1. esbuild собирает app/javascript/application.js → app/assets/builds/application.js 2. Tailwind компилирует application.tailwind.css → app/assets/builds/application.css 3. Propshaft отдаёт файлы из app/assets/builds/ с fingerprinting Stimulus-контроллер: import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["output"] connect() { console.log("подключён") } } Подключается через data-атрибуты: <div data-controller="filter"> <input data-filter-target="input"> </div> Правила: — Один контроллер = один файл — Название файла = data-controller (filter_controller.js → data-controller="filter") — Стили через Tailwind-классы, не кастомный CSS — Сложную логику — в Service Object на сервере, не в JS
Средне

Frontend / CSS

CSS Positioning

Какие виды позиционирования есть в CSS? Чем отличаются position: static, relative, absolute, fixed, sticky? Как работает z-index?
Виды позиционирования: static (по умолчанию): — элемент в нормальном потоке — top/left/right/bottom НЕ работают — z-index НЕ работает relative: — элемент в нормальном потоке (занимает место) — top/left/right/bottom СДВИГАЮТ от обычной позиции — создаёт контекст позиционирования для дочерних absolute — z-index работает absolute: — ВЫПАДАЕТ из нормального потока (не занимает место) — позиционируется относительно ближайшего positioned-предка (relative/absolute/fixed/sticky) — если нет positioned-предка — относительно <html> fixed: — ВЫПАДАЕТ из нормального потока — позиционируется относительно viewport (экрана) — не скроллится вместе со страницей — пример: шапка сайта, плавающая кнопка sticky: — комбо relative + fixed — ведёт себя как relative, пока не доскроллишь до порога — потом «прилипает» как fixed — пример: заголовки в таблице, сайдбар .header { position: sticky; top: 0; } z-index — управляет порядком наложения: — работает ТОЛЬКО для positioned-элементов (не static) — выше z-index → ближе к пользователю (поверх других) — stacking context: z-index сравнивается внутри одного контекста Частая ошибка: z-index: 9999 не сработает если родитель создаёт stacking context с более низким z-index.
Средне

Frontend / CSS

Отзывчивая вёрстка

Что такое отзывчивая вёрстка (responsive)? Как работают media queries? В чём разница между rem, em и px? Что такое mobile-first?
Отзывчивая вёрстка — сайт адаптируется к размеру экрана: мобильный → планшет → десктоп Media queries — условия для CSS: @media (min-width: 768px) { ... } /* от 768px и больше */ @media (max-width: 767px) { ... } /* до 767px */ Mobile-first — сначала пишем стили для мобильных, потом добавляем для больших: .card { width: 100%; } /* мобильный */ @media (min-width: 768px) { .card { width: 50%; } } /* планшет */ @media (min-width: 1024px) { .card { width: 33%; } } /* десктоп */ Desktop-first (хуже) — наоборот, начинаем с десктопа и урезаем. Единицы измерения: px — абсолютные, фиксированный размер em — относительная, от font-size РОДИТЕЛЯ parent { font-size: 16px } → child { padding: 1em } = 16px rem — относительная, от font-size КОРНЯ (<html>) html { font-size: 16px } → 1rem = 16px всегда Практика: — font-size → rem (масштабируется с настройками браузера) — padding/margin → rem или em — max-width вместо width (не ломается на широких экранах) — CSS-функция clamp(): font-size: clamp(1rem, 2.5vw, 2rem) Viewport meta — обязателен в <head>: <meta name="viewport" content="width=device-width, initial-scale=1"> без него мобильный браузер покажет десктопную версию
Легко

Frontend / CSS

Псевдоклассы и псевдоэлементы

Что такое псевдоклассы и псевдоэлементы в CSS? В чём разница между : и ::? Какие самые полезные?
Псевдоклассы (:) — состояние или положение элемента: :hover — курсор наведён :focus — элемент в фокусе (input, button) :focus-within — фокус на элементе ИЛИ его потомке :active — момент нажатия :first-child — первый потомок родителя :last-child — последний потомок :nth-child(2) — второй потомок :nth-child(odd) — нечётные (1, 3, 5...) :nth-child(even) — чётные (2, 4, 6...) :not(.class) — все элементы БЕЗ этого класса :disabled — отключённый input Псевдоэлементы (::) — виртуальные элементы: ::before — создаёт элемент ПЕРЕД содержимым ::after — создаёт элемент ПОСЛЕ содержимого ::first-line — первая строка текста ::first-letter — первая буква ::placeholder — текст-подсказка в input ::selection — выделенный пользователем текст ::before и ::after — самые полезные: .tooltip::after { content: attr(data-tip); /* берёт текст из data-tip */ position: absolute; background: black; color: white; } Важно: — ::before/::after НЕ существуют без content: "..." — В CSS3 псевдоклассы с одним :, псевдоэлементы с двумя :: — В CSS2 всё было с одним : (::before = :before) — работает, но устарело
Средне

Frontend / Hotwire

Turbo Drive

Что делает Turbo Drive? Какие события он генерирует? Почему не работает window.onload и как это исправить?
Turbo Drive — перехватывает клики по ссылкам и отправку форм, делает AJAX-запрос вместо полной перезагрузки страницы. Что происходит при клике по ссылке: 1. Turbo перехватывает клик 2. Делает fetch() по URL ссылки 3. Получает HTML ответ 4. Заменяет <body> и мерджит <head> 5. URL в браузере обновляется через History API Проблема: JS не выполняется заново при навигации! window.onload — сработает ТОЛЬКО при первой загрузке при навигации Turbo не перезагружает страницу → onload не triggers Решение — события Turbo: document.addEventListener("turbo:load", () => { ... }) — срабатывает при каждой навигации (включая первую) document.addEventListener("turbo:render", () => { ... }) — срабатывает при рендере (включая preview из кэша) document.addEventListener("turbo:before-cache", () => { ... }) — перед кэшированием страницы (очистка) Для Stimulus-контроллеров это не проблема — connect() вызывается каждый раз при появлении элемента в DOM. Отключить Turbo для ссылки: = link_to "Выйти", logout_path, data: { turbo: false } — заставит браузер сделать обычный запрос turbo:load vs DOMContentLoaded: DOMContentLoaded — один раз при загрузке страницы turbo:load — каждый раз при навигации Turbo
Сложно

Frontend / Hotwire

Turbo Streams

Что такое Turbo Streams? Какие 7 действий поддерживаются? Когда использовать Streams, а когда Frames?
Turbo Streams — сервер отправляет HTML-инструкции для обновления DOM без полного перерендера страницы. 7 действий: append — добавить в конец контейнера prepend — добавить в начало контейнера before — вставить перед элементом after — вставить после элемента replace — заменить элемент целиком update — обновить содержимое (без замены тега) remove — удалить элемент Синтаксис: <%= turbo_stream.append "comments" do %> <%= render @comment %> <% end %> Результат — специальный MIME-тип text/vnd.turbo-stream.html: <turbo-stream action="append" target="comments"> <template> <div id="comment_5">Новый комментарий</div> </template> </turbo-stream> В контроллере: respond_to do |format| format.turbo_stream # рендерит create.turbo_stream.slim format.html { redirect_to @post } end Через модель (broadcasts): class Comment < ApplicationRecord broadcasts_to :post # автоматически при create/update/destroy end → отправляет Stream через WebSocket всем подписчикам Streams vs Frames: Frames — обновляет ОДИН блок при клике/submit (pull) Streams — сервер ОБРАТНО обновляет любой элемент (push) Frames — проще, достаточно для CRUD Streams — для real-time, уведомлений, чатов
Средне

Frontend / Hotwire

Stimulus: targets, values, classes

Как устроен Stimulus-контроллер? Что такое targets, values и classes? Какие lifecycle-методы есть?
Stimulus-контроллер — JS-класс, привязанный к HTML через data-атрибут: import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["input", "output"] static values = { count: Number, url: String } static classes = ["active", "hidden"] connect() { /* элемент появился в DOM */ } disconnect() { /* элемент удалён из DOM */ } } Targets — ссылки на элементы внутри контроллера: HTML: <div data-controller="search"> <input data-search-target="input"> <div data-search-target="output"></div> </div> JS: this.inputTarget — первый найденный (ошибка если нет) this.inputTargets — массив всех this.hasInputTarget — есть ли (boolean) Values — типизированные данные в HTML: HTML: <div data-controller="counter" data-counter-count-value="5"> JS: this.countValue // → 5 this.countValueChanged() // callback при изменении this.countValue = 10 // обновит data-атрибут Типы: String, Number, Boolean, Array, Object Classes — CSS-классы для toggle: HTML: <div data-controller="toggle" data-toggle-active-class="bg-green"> JS: this.activeClass // → "bg-green" this.element.classList.add(this.activeClass) Lifecycle: connect() — элемент добавлен в DOM (как mounted) disconnect() — элемент удалён из DOM (как unmounted) Actions — обработчики событий: HTML: <button data-action="click->counter#increment"> JS: increment(event) { this.countValue++ }
Средне

Frontend / Asset Pipeline

esbuild и сборка JavaScript

Зачем нужен esbuild в Rails? Что делает бандлер? Что такое import/export и tree shaking?
Проблема: браузеры не понимают: — import/export между файлами — npm-пакеты (node_modules) — TypeScript, JSX Бандлер (esbuild) решает все эти проблемы. Что делает esbuild: 1. Берёт точку входа: app/javascript/application.js 2. Рекурсивно обходит все import 3. Собирает всё в ОДИН файл: app/assets/builds/application.js 4. Минифицирует (убирает пробелы, сокращает имена) Скорость: esbuild написан на Go, в 10-100x быстрее Webpack. import/export — модули в JavaScript: // code_editor_controller.js export default class CodeEditor extends Controller { ... } // application.js import CodeEditor from "./code_editor_controller" // или автоматический импорт: import "./controllers" // import all controllers Tree shaking — удаление неиспользуемого кода: import { map, filter } from "lodash" → в бандл попадут ТОЛЬКО map и filter, не вся библиотека Команда сборки (из package.json): "build": "esbuild app/javascript/*.* --bundle --outdir=app/assets/builds" Watch-режим для разработки: "watch": "esbuild ... --watch" → пересобирает при изменении файлов Важно: esbuild НЕ делает: — type checking (это TypeScript) — полифиллы (это babel) — горячую перезагрузку (это Vite)
Средне

Frontend / Rails Frontend

importmap vs esbuild

Какие три способа подключения JavaScript в Rails 7/8? Плюсы и минусы каждого?
Три подхода к JavaScript в Rails: 1. importmap (по умолчанию в rails new): — браузер сам загружает ES-модули по HTTP — НЕ нужен бандлер (npm, node_modules) — config/importmap.rb — маппинг имён на URL — Плюсы: простота, нет шага сборки — Минусы: нет npm-пакетов, нет tree shaking, медленнее на больших проектах 2. esbuild (rails new -j esbuild): — бандлер собирает всё в один файл — npm-пакеты доступны (npm install ...) — Плюсы: быстро, npm-экосистема, tree shaking — Минусы: нужен Node.js, шаг сборки 3. Vite (rails new -j vite): — современный бандлер с HMR (горячая перезагрузка) — Плюсы: самый быстрый dev-опыт, Vue/React support — Минусы: сложнее настроить, больше магии Когда что: — Простой проект, Hotwire → importmap (или esbuild) — Средний/крупный проект → esbuild — React/Vue фронтенд → Vite Наш проект использует esbuild: npm-пакеты: @hotwired/stimulus, @codemirror/... Сборка: esbuild → app/assets/builds/application.js
Средне

Frontend / Rails Frontend

Turbolinks → Turbo: миграция

Чем Turbolinks отличается от Turbo? Что сломалось при миграции? Зачем DHH переписал Turbolinks?
Turbolinks (2013-2020): — перехватывал ссылки, обновлял <body> через AJAX — jQuery-эпоха: $(document).on("turbolinks:load", ...) — проблемы: глобальный state, утечки памяти, баги с JS Turbo (2020+, Rails 7): — замена Turbolinks, переписан с нуля — три части: Drive, Frames, Streams — работает со Stimulus (не нужен jQuery) Что сломалось при миграции Turbolinks → Turbo: — turbolinks:load → turbo:load (события переименованы) — $(document).ready → Stimulus connect() — jQuery не чистил state при навигации → утечки памяти — Turbolinks кэшировал страницу → старые данные показывались Паттерны замены: Turbolinks: Turbo: $(document).on("turbolinks:load") document.addEventListener("turbo:load") $(element).on("click") data-action="click->ctrl#method" ручной DOM-манипуляцией Turbo Streams с сервера turbo:load vs DOMContentLoaded: DOMContentLoaded — только при полной перезагрузке turbo:load — при каждой навигации через Turbo Drive В Stimulus-контроллерах используй connect() — не turbo:load История: — rails-ujs (Rails 3-5) — jQuery, data-remote, data-confirm — Turbolinks (Rails 4-6) — AJAX-навигация — Turbo + Stimulus (Rails 7+) — полная замена без jQuery
Средне

Ruby on Rails / Основы Rails

Rack

Что такое Rack?
Rack это промежуточное программное обеспечение, оно делит входящие HTTP-запросы на различные этапы, затем обрабатывает их по частям, после чего посылает ответ веб-приложения (контроллера). Программа Rack состоит из двух отдельных компонентов: обработчика и адаптера, с помощью которых происходит обмен данными между веб-серверами и приложениями (фреймворками). Какие серверы есть: WEBrick, Thin, Puma, Unicorn, Phusion Passenger, Iodine.
Легко

Ruby on Rails / MVC и Роутинг

MVC

За что отвечают Model, View, Controller уровни в Rails?
MVC — это паттерн программирования, который подразумевает схему разделения данных приложения, пользовательского интерфейса и управляющей логики на три отдельных компонента.
Легко

Ruby on Rails / MVC и Роутинг

Роутинг

Как работает роутинг? Что такое ресурсные роуты? Как они формируются?
Браузеры запрашивают страницы от Rails, выполняя запрос по URL, используя определенный метод HTTP, такой как GET, POST, PATCH, PUT и DELETE. Роутинг распознает запрос по методу и по URL и направляет его в экшн контроллера или в приложение Rack. Он также может генерировать пути и URL, избегая необходимость жестко прописывать строки в ваших вьюхах. Ресурсный роутинг позволяет быстро объявлять все общие маршруты для заданного ресурсного контроллера. Вместо объявления отдельных маршрутов для экшнов index, show, new, edit, create, update и destroy, ресурсный маршрут объявляет их одной строчкой кода.
Легко

Ruby on Rails / ActiveRecord

dependent

Что такое dependent связь?
Опция :dependent указывает, что необходимо сделать с зависимой моделью (моделями) при удалении текущей модели. Может принимать значения: :delete — связанные объекты будут удалены прямо из базы данных без вызова метода destroy :destroy — будет вызван destroy на связанных объектах :nullify — внешний ключ будет установлен NULL :restrict_with_error — при наличии связанного объекта вызовет ошибку :restrict_with_exception — при наличии связанного объекта вызовется исключение
Легко

Ruby on Rails / ActiveRecord

t.references

Что такое t.references?
Столбец таблицы в миграции, указывающий на принадлежность к другой таблице. Например, книга принадлежит автору: class CreateBooks < ActiveRecord::Migration[5.2] def change create_table :books do |t| t.references :author end end end
Легко

Ruby on Rails / ActiveRecord

ActiveRecord

Что такое ActiveRecord, и какие средства предоставляет для работы с объектами?
ActiveRecord это паттерн программирования. AR является популярным способом доступа к данным реляционных баз данных в объектно-ориентированном программировании. ActiveRecord еще называют буквой M в MVC — которая является слоем в системе, ответственным за представление бизнес-логики и данных. Active Record упрощает создание и использование бизнес-объектов, данные которых требуют персистентного хранения в базе данных. Сама по себе эта реализация паттерна Active Record является описанием системы ORM (Object Relational Mapping). Active Record это фреймворк ORM. Active Record предоставляет нам несколько механизмов, наиболее важными из которых являются способности для: Представления моделей и их данных. Представления связей между этими моделями. Представления иерархий наследования с помощью связанных моделей. Валидации моделей до того, как они станут персистентными в базе данных. Выполнения операций с базой данных в объектно-ориентированном стиле.
Легко

Ruby on Rails / ActiveRecord

scopes

Что такое скоупы (scopes)? Как использовать?
Скоупы позволяют задавать часто используемые запросы, к которым можно обращаться как к вызовам метода в связанных объектах или моделях. С помощью этих скоупов можно использовать такие методы как where, joins и includes. Все методы скоупов возвращают объект ActiveRecord::Relation, который позволяет вызывать на нем дополнительные методы (такие как другие скоупы). Для определения простого скоупа мы используем метод scope внутри класса, передав запрос, который хотим запустить при вызове этого скоупа: class Article < ApplicationRecord scope :published, -> { where(published: true) } end
Легко

Ruby on Rails / ActiveRecord

Валидации

Что такое валидации? Как написать свои валидации? Для чего нужны валидации?
Валидации используются, чтобы быть уверенными, что только проверенные данные сохраняются в вашу базу данных. Например, для вашего приложения может быть важно, что каждый пользователь предоставил валидный электронный адрес. Валидации на уровне модели - наилучший способ убедиться, что в базу данных будут сохранены только валидные данные. Они не зависят от базы данных, не могут быть обойдены конечными пользователями и удобны в тестировании и обслуживании. Пример простейшей валидации: class Person < ApplicationRecord validates :name, presence: true end Person.create(name: "John Doe").valid? # => true Person.create(name: nil).valid? # => false Разработчик также может написать свои собственные правила валидации, которые будут располагаться в каталоге app/validators.
Легко

Ruby on Rails / ActiveRecord

Связи моделей

Какие связи для связывания моделей в приложении Rails вы знаете?
Rails поддерживает шесть типов связей: belongs_to has_one has_many has_many :through has_one :through has_and_belongs_to_many
Средне

Ruby on Rails / ActiveRecord

Примеры связей

Привести примеры использования has_many, belongs_to, has_and_belongs_to_many, has_one, has_many :through?
Фильм имеет множество сезонов, сезон принадлежит фильму и имеет множество серий. У каждого фильма может быть только один официальный сайт. В каждом фильме снимается множество актёров, при этом каждый актёр снимается в разных фильмах: class Film < ApplicationRecord has_many :seasons has_many :episodes, through: :seasons has_one :official_site has_and_belongs_to_many :actors end class Season < ApplicationRecord belongs_to :film has_many :episodes end class Episode < ApplicationRecord belongs_to :season end class OfficialSite < ApplicationRecord belongs_to :film end class Actor < ApplicationRecord has_and_belongs_to_many :films end
Средне

Ruby on Rails / ActiveRecord

has_many :through vs HABTM

Что лучше выбрать has_many :through или has_and_belongs_to_many?
Это зависит от контекста связи many-to-many. Если планируется использование дополнительной логики в этой связи, создание дополнительных полей в соединительной таблице, то лучше отдать предпочтение has_many :through. В этом случае применяются промежуточные модели-связки. В том случае, если достаточно простой соединительной таблицы, то можно обойтись has_and_belongs_to_many (т.н. HBTM).
Средне

Ruby on Rails / ActiveRecord

Полиморфные связи

Что такое полиморфные связи?
Особый вид связи, при которой модель может принадлежать сразу нескольким моделям. Например, картинку можно добавлять к статье, комментарию, пользователю. class Picture < ApplicationRecord belongs_to :imageable, polymorphic: true end class Article < ApplicationRecord has_many :pictures, as: :imageable end class Comment < ApplicationRecord has_many :pictures, as: :imageable end При этом картинка сохраняет в себе имя класса и id объекта, которому она принадлежит. У картинки имеются атрибуты imageable_id и imageable_type.
Средне

Ruby on Rails / ActiveRecord

N+1 запрос

Что такое проблема N+1 запроса? Как можно решить проблему N+1 в Rails?
Проблема N+1 — когда на каждый элемент коллекции делается отдельный запрос к БД. Например, 10 клиентов + 1 запрос на адрес каждого = 11 запросов. clients = Client.limit(10) clients.each do |client| puts client.address.postcode end Решение — метод includes, загружает все связанные записи одним запросом: clients = Client.includes(:address).limit(10) Будет всего два запроса: SELECT * FROM clients LIMIT 10 SELECT addresses.* FROM addresses WHERE (addresses.client_id IN (1,2,3,4,5,6,7,8,9,10))
Легко

Ruby on Rails / Views

Partial

Что такое partial и для чего используются?
Partial — это кусочек кода, который можно вынести в отдельный темплейт, для удобства использования и для использования в других представлениях.
Легко

Ruby on Rails / Views

Haml и Slim

Что такое Haml, Slim? Какие плюсы их использования?
Haml и Slim — это шаблонизаторы, используются для удобства и минимизации написания кода в представлениях. Сокращает в несколько раз написание кода, нет проблем в закрывании тегов, не получится что тег не закрыт и код не работает. Меньше вероятность что можно ошибиться + лучше читаемость в коде.
Легко

Ruby on Rails / Views

Расширения файлов

Что означает несколько расширений файла example.html.erb?
example — название файла. html — расширение, которое позволяет использовать стандартный язык разметки HyperText Markup Language. erb — позволяет включить использование кода написанного на языке Ruby вместе с языком разметки.
Легко

Ruby on Rails / Views

ERB

Что такое ERB? Можете расшифровать аббревиатуру?
ERB — Embedded Ruby (встроенный Ruby)
Легко

Ruby on Rails / Views

Presenter

Что такое presenter и для чего он нужен? Где применяется? В чем его основная задача?
Presenter - паттерн проектирования, простой класс (в Rails), использующийся для вынесения какой-либо логики по обработке моделей из слоя контроллеров и слоя представлений. Например: module Posts class IndexPresenter def posts Posts.all end def authors Authors.all end def post_published_count Post.published_count end end end Так будет выглядеть экшн index в контроллере: def index @presenter = Posts::IndexPresenter.new end Так это будет представлено во view: <p>Всего опубликовано: <%= @presenter.published_count %></p> <%= @presenter.authors %>
Легко

Ruby on Rails / Продвинутые темы

ActiveJob

Что такое ActiveJob? Когда его использовать?
Active Job - это фреймворк для объявления заданий и их запуска на разных бэкендах очередей. Эти задания могут быть чем угодно: от регулярно запланированных чисток до списаний с карт или рассылок. В общем, всем, что может быть выделено в небольшие работающие части и запускаться параллельно. Имеет встроенные адаптеры для планировщиков фоновых задач: Sidekiq Resque Delayed Job и т.д..
Легко

Ruby on Rails / Продвинутые темы

Asset Pipeline

Что такое Asset Pipeline?
Asset Pipeline (файлопровод) - фреймворк для соединения и минимизации, или сжатия ассетов JavaScript и CSS. Он также добавляет возможность писать эти ассеты на других языках и препроцессорах, таких как CoffeeScript, Sass и ERB. Это позволяет автоматически комбинировать ассеты приложения с ассетами других гемов. Первой особенностью файлопровода является соединение ассетов, что может уменьшить количество запросов, необходимых браузеру для отображения страницы. Браузеры ограничены в количестве запросов, которые они могут выполнить параллельно, поэтому меньшее количество запросов может означать более быструю загрузку вашего приложения. Второй особенностью файлопровода является минимизация или сжатие ассетов. Для файлов CSS это выполняется путем удаления пробелов и комментариев. Для JavaScript могут быть применены более сложные процессы. Можно выбирать из набора встроенных опций или определить свои. Третьей особенностью файлопровода является то, что он позволяет писать эти ассеты на языке более высокого уровня с дальнейшей прекомпиляцией до фактического ассета. Поддерживаемые языки по умолчанию включают Sass для CSS, CoffeeScript для JavaScript и ERB для обоих.
Легко

Ruby on Rails / Продвинутые темы

Serializer

Что такое serializer и для чего он нужен? Где применяется? В чем его основная задача?
Сериализация (serialization) - процесс перевода каких-либо структур данных в последовательность битов. Обратный процесс называется десериализация (deserialization). Сериализация используется для передачи объектов по сети и сохранения их в файлы. Например: сериализация заполненного объекта в XML-документ с последующей передачей документа по HTTP или протоколам электронной почты. Также часто используется для преобразования информации в формат JSON. В Rails интерфейс базовой сериализации представлен модулем ActiveModel::Serialization. Вам необходимо объявить хэш, содержащий атрибуты, которые вы хотите сериализовать. Атрибуты должны быть строками, не символами. Что касается JSON, то Active Model также предоставляет модуль ActiveModel::Serializers::JSON для сериализации/десериализации JSON.
Легко

Ruby on Rails / Продвинутые темы

i18n

Что такое i18n (интернационализация)?
Адаптация приложения к особенностям региона, в котором он будет использоваться. Название i18n происходит от английского слова internationalization, между первой и последней буквами i и n 18 букв. Гем i18n, поставляемый с Ruby on Rails (начиная с Rails 2.2), представляет простой и расширяемый фреймворк для перевода приложения на язык, отличный от английского, а также изменения формата даты, времени, валюты и т.д. Rails автоматически добавляет все файлы .rb и .yml из директории config/locales к пути загрузки переводов.
Средне

Ruby on Rails / Продвинутые темы

Кеширование

Как реализовано кеширование в рельсах?
Кэширование означает хранение контента, генерируемого в цикле запрос-отклик, и повторное использование его при ответе на подобные запросы. Кэширование значительно ускоряет загрузку страниц, снижает количество запросов к серверу. Виды кэширования: Кэширование страницы — начиная с Rails 4 добавляется гемом actionpack-page_caching Кэширование экшна — начиная с Rails 4 добавляется гемом actionpack-action_caching Кэширование фрагмента — позволяет фрагменту логики вьюхи быть обернутым в блок кэша и обслуженным из хранилища кэша для последующего запроса Кэширование матрешкой (Russian doll caching) — можно вкладывать кэшированные фрагменты в другие кэшированные фрагменты
Средне

Ruby on Rails / Продвинутые темы

Service Objects

Что такое Service Objects, Form Objects, View Objects, Query Objects, для чего они нужны?
Это обычные классы Ruby, которые применяются для рефакторинга Rails-приложения, инкапсулируя часть логики моделей / представлений / контроллеров. Service Objects — используются, когда одновременно задействованы несколько моделей, когда производятся сложные действия с моделями. Form Objects — используются, когда отправка одной формы изменяет несколько моделей. View Objects — используются, когда большой метод внутри модели используется только для отображения данных. Query Objects — используются для сложных SQL запросов, утяжеляющих модели/контроллеры.
Легко

Ruby on Rails / Контроллеры

before_action и коллбэки контроллера

Что такое before_action, after_action, around_action? Для чего используются?
Это коллбэки контроллера, выполняющиеся до, после или вокруг экшена. before_action — самый частый. Проверка авторизации, загрузка данных: before_action :set_post, only: [:show, :edit, :update, :destroy] before_action :authenticate_user!, except: [:index, :show] after_action — после экшена. Логирование, заголовки. around_action — оборачивает экшен: around_action :wrap_in_transaction Подводные камни: — skip_before_action может сломать логику безопасности — Если before_action не делает render/redirect, выполнение продолжается — Порядок важен — выполняются в порядке объявления — Не злоупотребляйте — сложная цепочка трудно отлаживать
Легко

Ruby on Rails / Контроллеры

Strong Parameters

Что такое Strong Parameters? Зачем нужны?
Strong Parameters — защита от mass assignment. Запрещает передавать параметры в модель напрямую из params, требуя явного whitelist: def create @post = Post.create(post_params) end private def post_params params.require(:post).permit(:title, :body, :category_id) end require(:post) — обязателен ключ :post. permit(...) — разрешены только указанные атрибуты. Вложенные параметры: params.require(:post).permit(:title, comments: [:body, :author]) Без permit — ActiveModel::ForbiddenAttributesError.
Легко

Ruby on Rails / Контроллеры

render vs redirect_to

В чём разница между render и redirect_to?
render — отрисовывает шаблон в текущем запросе. Не создаёт новый запрос: render :edit # шаблон edit render json: @post # JSON render plain: "OK" # текст redirect_to — HTTP-redirect (302). Браузер делает новый запрос: redirect_to @post redirect_to posts_url redirect_back fallback_location: posts_url Правила: — Успешный create/update/destroy → redirect_to (Post/Redirect/Get) — Ошибка валидации → render (сохраняем @объект с ошибками) — render не останавливает метод! Нужно: return render :edit
Легко

Ruby on Rails / Контроллеры

params в контроллере

Что такое params в Rails-контроллере?
params — хеш-подобный объект (ActionController::Parameters) с данными запроса: — Параметры маршрута: params[:id], params[:controller], params[:action] — Query string: /posts?page=2 → params[:page] — Данные формы: params[:post][:title] — JSON body (если Content-Type: application/json) Методы: params.require(:post).permit(:title, :body) # strong params params[:id] # может быть nil params.fetch(:page, 1) # с дефолтом params.to_unsafe_h # весь хеш (осторожно!)
Средне

Ruby on Rails / Контроллеры

Sessions и Cookies

Как работают сессии и куки в Rails?
Cookies — данные в браузере: cookies[:theme] = "dark" cookies.permanent[:remember_token] = token # на 20 лет cookies.signed[:user_id] = 42 # подписанное cookies.encrypted[:secret] = "data" # зашифрованное Sessions — данные между запросами для одного пользователя: session[:user_id] = @user.id session.delete(:user_id) По умолчанию CookieStore — хранится в браузере (зашифровано, ~4KB). Другие хранилища: ActiveRecordStore, RedisStore. CookieStore: удобно, но не храните чувствительные данные!
Средне

Ruby on Rails / Контроллеры

rescue_from

Как обрабатывать исключения в контроллере? Что такое rescue_from?
rescue_from — обработчик исключений на уровне контроллера: class ApplicationController < ActionController::Base rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from Pundit::NotAuthorizedError, with: :forbidden private def not_found render file: Rails.root.join("public/404.html"), status: :not_found end def forbidden render file: Rails.root.join("public/403.html"), status: :forbidden end end Для конкретного экшена: def show @post = Post.find(params[:id]) rescue ActiveRecord::RecordNotFound redirect_to posts_path, alert: "Не найден" end В API: rescue_from + render json: { error: "..." }, status: :not_found
Средне

Ruby on Rails / Контроллеры

Concerns в контроллерах

Что такое concerns? Как применять в контроллерах?
Concerns — модули для переиспользования кода между контроллерами. app/controllers/concerns/paginatable.rb: module Paginatable extend ActiveSupport::Concern included do helper_method :current_page end private def current_page params[:page] || 1 end end Использование: class PostsController < ApplicationController include Paginatable def index @posts = Post.page(current_page) end end ActiveSupport::Concern даёт: — included { } — блок при включении — class_methods { } — добавляет методы класса Не выносите в concerns бизнес-логику — для этого Service Objects.
Легко

Ruby on Rails / API

Rails API mode

Что такое Rails в режиме API? Чем отличается от обычного?
rails new app --api — приложение без views, helpers, assets. Отличия: — ApplicationController < ActionController::API (не Base) — Нет модулей для views, cookies, flash — Нет asset pipeline — По умолчанию рендерит JSON — Тоньше middleware — быстрее ApiController включает: rendering JSON, strong params, rescue_from. Не включает: Cookies, Sessions, Flash, Helpers. Можно добавить модули вручную: class ApplicationController < ActionController::API include ActionController::Cookies end
Средне

Ruby on Rails / API

Рендеринг JSON: JBuilder, сериализаторы

Какие способы рендеринга JSON есть в Rails?
1. render json: — простой вариант: render json: @post render json: @post, only: [:id, :title] render json: @post, include: :comments 2. JBuilder — DSL для JSON (app/views/posts/show.json.jbuilder): json.extract! @post, :id, :title, :body json.author @post.author.name json.comments @post.comments, :id, :body 3. Сериализаторы (jsonapi-serializer, blueprinter): class PostSerializer include JSONAPI::Serializer attributes :id, :title, :body belongs_to :author end 4. as_json на уровне модели: def as_json(options = {}) super(only: [:id, :title]) end Простые API: render json: Сложные структуры: JBuilder или сериализаторы.
Средне

Ruby on Rails / API

REST API: HTTP-методы и статус-коды

Как спроектировать REST API в Rails?
HTTP-методы → экшены: GET /posts → index → 200 GET /posts/:id → show → 200 POST /posts → create → 201 PATCH /posts/:id → update → 200 DELETE /posts/:id → destroy → 204 Коды ошибок: 400 Bad Request — невалидные данные 401 Unauthorized — не авторизован 403 Forbidden — нет прав 404 Not Found — не найдено 422 Unprocessable Entity — ошибки валидации Пример create: def create post = Post.new(post_params) if post.save render json: post, status: :created else render json: { errors: post.errors.full_messages }, status: :unprocessable_entity end end
Средне

Ruby on Rails / API

API Versioning и CORS

Как версионировать API и настроить CORS?
Версионирование через URL (самый популярный): namespace :api do namespace :v1 do resources :posts end end → /api/v1/posts Структура: app/controllers/api/v1/posts_controller.rb V2 может наследовать V1: class Api::V2::PostsController < Api::V1::PostsController CORS (Cross-Origin Resource Sharing) — gem rack-cors: # config/initializers/cors.rb Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins 'https://myapp.com' resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options] end end Без CORS браузер блокирует AJAX-запросы с другого домена.
Легко

Ruby on Rails / Аутентификация

Devise

Что такое Devise? Какие модули входят?
Devise — гем для аутентификации (на базе Warden). Модули: — Database Authenticatable — вход по email/password (bcrypt) — Registerable — регистрация — Recoverable — восстановление пароля — Rememberable — «запомнить меня» (cookie) — Trackable — отслеживание входов (IP, дата) — Validatable — валидации email/password — Confirmable — подтверждение email — Lockable — блокировка после неудачных попыток — Omniauthable — OAuth (Google, GitHub) Настройка: rails generate devise:install rails generate devise User rails db:migrate Использование: before_action :authenticate_user! current_user # текущий пользователь user_signed_in? # авторизован?
Средне

Ruby on Rails / Аутентификация

Сессии vs токены

В чём разница между аутентификацией через сессии и токены?
Сессионная (cookie-based): — Сервер хранит сессию, браузер получает cookie — Cookie отправляется автоматически с каждым запросом — Stateful (состояние на сервере) — CSRF-защита обязательна — Для веб-приложений (MVC) Токенная (token-based): — Клиент получает токен при логине — Отправляет в заголовке: Authorization: Bearer <token> — Stateless (без состояния на сервере) — Нет CSRF (нет cookies) — Для SPA, мобильных приложений, API JWT (JSON Web Token): — Содержит payload (user_id, срок действия) — Подписан секретным ключом — Не требует БД для проверки — Минус: нельзя отозвать без чёрного списка В Rails: Devise = сессии, devise-jwt / knockout = токены.
Средне

Ruby on Rails / Аутентификация

has_secure_password

Что такое has_secure_password? Как использовать без Devise?
has_secure_password — встроенная аутентификация (ActiveModel::SecurePassword). Требует: gem bcrypt + поле password_digest в таблице. class User < ApplicationRecord has_secure_password # добавляет виртуальные: password, password_confirmation end user = User.create!(email: "...", password: "secret") user.authenticate("secret") # → user user.authenticate("wrong") # → false В контроллере: def create user = User.find_by(email: params[:email]) if user&.authenticate(params[:password]) session[:user_id] = user.id redirect_to root_path else flash.now[:alert] = "Неверный email или пароль" render :new end end Автоматически добавляет валидации presence + confirmation.
Средне

Ruby on Rails / Аутентификация

Авторизация: Pundit и CanCanCan

Чем отличаются Pundit и CanCanCan?
Pundit — политико-ориентированный: # app/policies/post_policy.rb class PostPolicy def initialize(user, post) @user, @post = user, post end def update? @user.admin? || @user == @post.author end end # В контроллере: authorize @post # Во view: policy(@post).update? CanCanCan — декларативный: # app/models/ability.rb class Ability include CanCan::Ability def initialize(user) can :read, Post can :manage, Post, author_id: user.id if user can :manage, :all if user&.admin? end end # В контроллере: authorize! :read, @post Pundit: проще, объектно-ориентированный, легче тестировать. CanCanCan: удобен для сложных систем прав.
Легко

Ruby on Rails / Тестирование

RSpec: describe, context, it

Как устроен RSpec? Что такое describe, context, it?
RSpec — фреймворк тестирования Ruby/Rails. — describe — группировка по методу/функции — context — группировка по условию/состоянию — it — отдельный тест RSpec.describe User, type: :model do describe "#admin?" do context "when role is admin" do let(:user) { User.new(role: :admin) } it { expect(user.admin?).to be true } end context "when role is user" do let(:user) { User.new(role: :user) } it { expect(user.admin?).to be false } end end end Типы: type: :model, :controller, :request, :system, :job.
Легко

Ruby on Rails / Тестирование

FactoryBot

Что такое FactoryBot? Чем лучше fixtures?
FactoryBot — создание тестовых данных. FactoryBot.define do factory :user do email { "test@example.com" } password { "password123" } name { "Test User" } trait :admin do role { :admin } end end end Использование: create(:user) # сохраняет в БД create(:user, :admin) # с trait create(:user, name: "Иван") # с переопределением build(:user) # не сохраняет attributes_for(:user) # возвращает хеш Плюсы перед fixtures: — Явное создание в каждом тесте — Легко комбинировать через traits — Последовательности: sequence(:email) { |n| "user#{n}@test.com" } — Не ломаются при изменении данных
Средне

Ruby on Rails / Тестирование

let, let! и before

В чём разница между let, let! и before?
let — ленивая инициализация. Вычисляется при первом обращении: let(:user) { create(:user) } # НЕ создаётся пока не обратишься let! — принудительная. Выполняется до каждого теста: let!(:post) { create(:post) } # Создаётся ДО каждого it before — произвольный код до тестов: before { login_as(create(:user)) } Правила: — let — по умолчанию (экономит время если не используется) — let! — когда данные нужны в БД до теста — before — для сложной настройки (логин, stub) — let внутри context переопределяет let из describe
Средне

Ruby on Rails / Тестирование

Моки и стабы (mocks/stubs)

Что такое моки и стабы? Зачем нужны?
Стаб (stub) — подмена возвращаемого значения: allow(User).to receive(:count).and_return(42) # User.count вернёт 42 без БД Мок (mock) — ожидание вызова (stub + проверка): expect(Mailer).to receive(:welcome).with(user) # Тест упадёт если Mailer.welcome не вызван double — объект-заглушка: user = double("User", admin?: true, name: "Админ") instance_double — строгая заглушка: user = instance_double(User, admin?: true) # Упадёт если у User нет метода admin? Когда использовать: — Внешние API, email — стабы — Проверка что метод вызван — моки — Изоляция тестов — не трогать БД Правило: не мокайте тестируемый объект.
Средне

Ruby on Rails / Тестирование

Request specs и System specs

В чём разница между request specs и system specs?
Request specs — тестируют HTTP-запрос/ответ (рекомендуются): get "/api/v1/posts" expect(response).to have_http_status(200) expect(JSON.parse(response.body).size).to eq(3) post "/api/v1/posts", params: { post: { title: "Test" } } System specs (Capybara) — end-to-end с реальным браузером: visit "/posts" click_on "Новый пост" fill_in "Title", with: "Мой пост" click_on "Создать" expect(page).to have_text("Мой пост") Controller specs — устаревают, не рекомендуются. Пирамида тестов: много unit (model) → меньше request → мало system.
Средне

Ruby on Rails / Тестирование

Shoulda Matchers и SimpleCov

Что такое Shoulda Matchers и SimpleCov?
Shoulda Matchers — лаконичные тесты Rails-паттернов: it { should validate_presence_of(:title) } it { should validate_uniqueness_of(:email) } it { should belong_to(:user) } it { should have_many(:comments).dependent(:destroy) } it { should validate_length_of(:password).is_at_least(6) } SimpleCov — покрытие тестами: # spec/spec_helper.rb (в самом начале): require 'simplecov' SimpleCov.start 'rails' do add_filter '/test/' minimum_coverage 80 end После тестов: coverage/index.html — отчёт по файлам и строкам. Рекомендации: — 80%+ — хороший уровень — Покрывайте критические пути, не геттеры/сеттеры — minimum_coverage — тесты упадут если покрытие ниже
Легко

Ruby on Rails / Безопасность

SQL-инъекции

Что такое SQL-инъекция? Как Rails защищает?
SQL-инъекция — вставка SQL-кода через пользовательские данные. Опасно: User.where("email = '#{params[:email]}'") Безопасно: User.where("email = ?", params[:email]) User.where(email: params[:email]) ActiveRecord экранирует параметры при: — Хеш-синтаксисе: where(name: params[:name]) — Placeholder: where("name = ?", params[:name]) Опасные методы (быть осторожным): — where с интерполяцией строки — order("#{params[:sort]}") — возможна инъекция! — find_by_sql, exec_query Правило: НИКОГДА не интерполируйте params в SQL.
Легко

Ruby on Rails / Безопасность

XSS-атаки

Что такое XSS? Как Rails защищает?
XSS (Cross-Site Scripting) — внедрение JavaScript в страницу. Rails автоматически экранирует HTML: <%= @comment.body %> # <script>alert(1)</script> → &lt;script&gt;alert(1)&lt;/script&gt; Опасно (без экранирования): <%= raw @comment.body %> # НЕ экранирует! <%= @comment.body.html_safe %> # НЕ экранирует! sanitize — экранирование с whitelist тегов: <%= sanitize @comment.body, tags: %w[b i a p] %> Content Security Policy: # config/initializers/content_security_policy.rb Rails.application.config.content_security_policy do |p| p.default_src :self end
Средне

Ruby on Rails / Безопасность

CSRF-защита

Что такое CSRF? Как Rails защищает?
CSRF — атака, когда вредоносный сайт отправляет запрос от имени авторизованного пользователя. Защита Rails (protect_from_forgery): — Для каждой формы генерируется authenticity_token — Rails проверяет токен при POST/PUT/PATCH/DELETE — По умолчанию включено в ApplicationController В forms: <%= form_with %> — токен автоматически. В AJAX: Rails-UJS добавляет X-CSRF-Token. В API (без cookies): CSRF не актуален: skip_before_action :verify_authenticity_token # или protect_from_forgery with: :null_session
Средне

Ruby on Rails / Безопасность

credentials.yml.enc и секреты

Как Rails хранит секреты?
config/credentials.yml.enc — зашифрованный файл с секретами. Редактирование: EDITOR=vim rails credentials:edit Содержимое (YAML): secret_key_base: "..." aws: access_key_id: "..." Доступ в коде: Rails.application.credentials.secret_key_base Rails.application.credentials.dig(:aws, :access_key_id) Ключ расшифровки: config/master.key (НЕ коммитить!). Без master.key нельзя расшифровать credentials. Переменные окружения: ENV["DATABASE_PASSWORD"] gem dotenv-rails — загружает .env файл
Легко

Ruby on Rails / Фоновые задачи

ActiveJob: основы

Что такое ActiveJob? Как создать фоновую задачу?
ActiveJob — абстракция над очередями задач в Rails. Создание: rails generate job send_welcome_email class SendWelcomeEmailJob < ApplicationJob queue_as :default def perform(user_id) user = User.find(user_id) UserMailer.welcome(user).deliver_now end end Запуск: SendWelcomeEmailJob.perform_later(user.id) # асинхронно SendWelcomeEmailJob.perform_now(user.id) # синхронно Параметры: только простые типы (String, Integer) и ActiveRecord (сериализуется через GlobalID). Отложенный запуск: SendWelcomeEmailJob.set(wait: 5.minutes).perform_later(user.id) SendWelcomeEmailJob.set(queue: :urgent).perform_later(user.id) config.active_job.queue_adapter = :sidekiq
Средне

Ruby on Rails / Фоновые задачи

Sidekiq и Redis

Что такое Sidekiq? Как он работает?
Sidekiq — обработчик фоновых задач. Использует Redis для очередей. Архитектура: Client → Redis (хранит задачи) → Sidekiq Worker (выполняет). Очереди: queue_as :mailers queue_as :urgent Конфигурация (config/sidekiq.yml): :concurrency: 5 :queues: - urgent - default - mailers Запуск: bundle exec sidekiq Sidekiq Web UI: require 'sidekiq/web' mount Sidekik::Web => '/sidekiq' Retry: по умолчанию 25 попыток с экспоненциальной задержкой. После 25 неудач → dead jobs (видны в Web UI, можно восстановить). sidekiq_options retry: 5 # кол-во попыток sidekiq_options retry: false # без повторов
Легко

Ruby on Rails / Фоновые задачи

perform_later vs perform_now

В чём разница между perform_later и perform_now?
perform_later — ставит задачу в очередь (асинхронно): SendEmailJob.perform_later(user.id) # Мгновенно возвращается, выполнится в фоне perform_now — выполняет синхронно в текущем процессе: SendEmailJob.perform_now(user.id) # Блокирует до завершения Отложенный запуск: SendEmailJob.set(wait: 5.minutes).perform_later(user.id) SendEmailJob.set(wait_until: Date.tomorrow.noon).perform_later Правило: если задача > 100мс — perform_later. В тестах: perform_now (или заглушить perform_later).
Средне

Ruby on Rails / Фоновые задачи

Retry и dead jobs

Как обрабатывать ошибки в фоновых задачах?
ActiveJob — retry_on: class MyJob < ApplicationJob retry_on Net::OpenTimeout, wait: 5.seconds, attempts: 3 discard_on ActiveJob::DeserializationError # игнорировать end Sidekiq — автоматический retry: — 25 попыток по умолчанию с экспоненциальной задержкой — 15с, 30с, 60с, 3м, 7м, ... до 21 дня — После 25 неудач → dead jobs Dead jobs: — Хранятся 6 месяцев в Redis — Видны в Sidekiq Web UI → Dead tab — Можно восстановить вручную Обработка ошибок: def perform # логика rescue StandardError => e Rails.logger.error("Job failed: #{e.message}") raise # пробросить для retry end
Легко

Ruby on Rails / Среды и конфигурация

Среды Rails: development, test, production

Какие среды бывают? Чем отличаются?
development: — Автоперезагрузка кода — Подробные логи (SQL, время рендеринга) — Страницы ошибок с трейсбеком — config.cache_classes = false test: — Транзакционные тесты (данные откатываются) — config.cache_classes = true — Тихий режим production: — Код закеширован, не перезагружается — Минимум логов — Стандартные страницы 404/500 — config.cache_classes = true — config.eager_load = true — config.force_ssl = true (рекомендуется) Установка: RAILS_ENV=production rails server По умолчанию: development
Легко

Ruby on Rails / Среды и конфигурация

database.yml и ENV

Как настраивается подключение к БД?
config/database.yml — отдельная конфигурация для каждой среды: default: &default adapter: postgresql pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> development: <<: *default database: myapp_development production: <<: *default url: <%= ENV["DATABASE_URL"] %> DATABASE_URL — приоритетнее остальных настроек. pool — размер пула соединений (обычно = кол-во потоков). Переменные окружения: ENV["DATABASE_URL"] # из панели хостинга / Docker gem dotenv-rails # загружает .env файл .env — НЕ коммитить! .env.example — шаблон (коммитится).
Легко

Ruby on Rails / Среды и конфигурация

Initializers

Что такое initializers в Rails?
Initializers — файлы в config/initializers/, выполняются при запуске. Загружаются после фреймворка и гемов, в алфавитном порядке. Примеры: # config/initializers/cors.rb Rails.application.config.middleware.insert_before 0, Rack::Cors do ... # config/initializers/devise.rb Devise.setup do |config| config.mailer_sender = 'no-reply@example.com' end # config/initializers/time_formats.rb Time::DATE_FORMATS[:short] = "%d %b %H:%M" Правила: — Один файл на гем/фичу — Имена в snake_case — Нужен перезапуск для применения изменений
Средне

Ruby on Rails / Среды и конфигурация

Конфигурация приложения

Какие основные файлы конфигурации есть в Rails?
config/application.rb — общие настройки: config.time_zone = 'Moscow' config.i18n.default_locale = :ru config.load_defaults 7.1 config/environments/ — настройки по средам: development.rb, test.rb, production.rb config/routes.rb — маршруты config/database.yml — подключение к БД config/storage.yml — хранилища файлов (local, S3) config/cable.yml — WebSocket (Redis, async) config/puma.rb — настройки сервера (воркеры, потоки) config/credentials.yml.enc — секреты
Легко

Ruby on Rails / Миграции

Миграции: основы

Что такое миграции? Зачем нужны? Как создать?
Миграции — способ изменения структуры БД на Ruby (без SQL). Создание: rails generate migration CreatePosts title:string body:text rails generate migration AddEmailToUsers email:string:uniq rails generate migration RemoveAgeFromUsers age:integer Пример миграции: class CreatePosts < ActiveRecord::Migration[7.1] def change create_table :posts do |t| t.string :title, null: false t.text :body t.references :user, foreign_key: true t.timestamps end add_index :posts, :title end end Команды: rails db:migrate # выполнить миграции rails db:rollback # откатить последнюю rails db:rollback STEP=3 # откатить 3 миграции rails db:migrate:status # статус миграций change — автоматически обратимая миграция (Rails сам создаёт down). Если необратима — используйте up/down: def up add_column :users, :admin, :boolean, default: false end def down remove_column :users, :admin end
Легко

Elixir / Pattern Matching

Что такое Pattern Matching

Что такое Pattern Matching в Elixir? Чем отличается от оператора = в Ruby?
Pattern Matching — механизм сравнения структуры данных и извлечения значений. В Elixir оператор = не присваивание, а сопоставление. # В Ruby: x = 1 # присваивание # В Elixir: x = 1 # сопоставление (match) 1 = x # match: 1 == 1 2 = x # MatchError: нет совпадения {a, b} = {1, 2} # a = 1, b = 2 [head | tail] = [1, 2, 3] # head = 1, tail = [2, 3] %{name: name} = %{name: "Alice", age: 25} # name = "Alice" Pattern Matching работает в: - присваивании переменных - заголовках функций (function head) - case/cond - with
Средне

Ruby on Rails / Миграции

Data migrations и best practices

Как мигрировать данные? Какие best practices миграций?
Миграция данных — изменение самих данных, а не структуры: class PopulateUserNames < ActiveRecord::Migration[7.1] def up User.find_each do |user| user.update!(name: "#{user.first_name} #{user.last_name}") end end def down # обычно невозможно откатить end end Best practices: — find_each вместо all (загрузка пакетами) — Не используйте модели в миграциях — они могут измениться Лучше: execute("UPDATE users SET ...") — Одна миграция = одно изменение — Тестируйте миграции: rails db:migrate && rails db:rollback — Добавляйте индексы для внешних ключей — null: false для обязательных полей — default: для новых колонок с NOT NULL Опасно в production: — remove_column без down — данные потеряны навсегда — rename_column — сломает существующий код — change_column — необратимо
Легко

Ruby on Rails / Миграции

Schema.rb и структура БД

Что такое schema.rb? Зачем нужен?
db/schema.rb — автоматически генерируемый файл, отражающий текущую структуру БД после всех миграций. Генерируется после rails db:migrate. Зачем: — Быстрый взгляд на структуру БД без чтения всех миграций — rails db:schema:load — создаёт БД из schema.rb (быстрее migrate) — Версионирование структуры БД в git schema.rb vs migrations: — schema.rb — текущее состояние (итог) — migrations — история изменений (как пришли к итогу) Когда использовать: — Новая БД: rails db:schema:load (быстро) — Существующая БД: rails db:migrate (применить новые миграции) Важно: schema.rb коммитится в git. config.active_record.dump_schema_after_migration = false — отключить.
Легко

Ruby on Rails / Mailers

ActionMailer

Что такое ActionMailer? Как отправить email в Rails?
ActionMailer — отправка email в Rails. Создание: rails generate mailer UserMailer welcome # app/mailers/user_mailer.rb # app/views/user_mailer/welcome.html.erb # app/views/user_mailer/welcome.text.erb Пример: class UserMailer < ApplicationMailer default from: 'no-reply@example.com' def welcome(user) @user = user mail(to: @user.email, subject: 'Добро пожаловать!') end end Вызов: UserMailer.welcome(user).deliver_now # синхронно UserMailer.welcome(user).deliver_later # через ActiveJob (фон) Настройка (config/environments/development.rb): config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { address: 'smtp.gmail.com', port: 587, user_name: ENV['GMAIL_USER'], password: ENV['GMAIL_PASSWORD'], authentication: 'plain' } В development можно использовать letter_opener gem — письма открываются в браузере вместо отправки.
Средне

Ruby on Rails / Mailers

deliver_now vs deliver_later

В чём разница между deliver_now и deliver_later?
deliver_now — отправляет синхронно, блокирует текущий запрос: UserMailer.welcome(user).deliver_now # Пользователь ждёт пока email отправится deliver_later — ставит в очередь (ActiveJob), не блокирует: UserMailer.welcome(user).deliver_later # Мгновенно возвращается, email отправится в фоне deliver_later с настройками: UserMailer.welcome(user).deliver_later(wait: 5.minutes) UserMailer.welcome(user).deliver_later(wait_until: 1.hour.from_now) Правило: в контроллерах — ВСЕГДА deliver_later. deliver_now — только в rake-задачах, консоли, тестах. Если очередь не настроена — deliver_later выполняет сразу inline. config.action_mailer.deliver_later_queue_name = :mailers — своя очередь.
Легко

Ruby on Rails / ActiveStorage

ActiveStorage: загрузка файлов

Что такое ActiveStorage? Как загрузить и отобразить файл?
ActiveStorage — загрузка и хранение файлов в Rails. Настройка: config/storage.yml local: service: Disk root: <%= Rails.root.join("storage") %> Модель: class User < ApplicationRecord has_one_attached :avatar has_many_attached :documents end Загрузка: user.avatar.attach(params[:avatar]) user.avatar.attached? # => true Отображение: <%= image_tag user.avatar %> <%= link_to "Скачать", rails_blob_path(user.avatar, disposition: "attachment") %> Валидация: validates :avatar, presence: true, content_type: ['image/png', 'image/jpg'], size: { less_than: 5.megabytes } Варианты (миниатюры): <%= image_tag user.avatar.variant(resize_to_limit: [100, 100]) %> Хранилища: local (диск), Amazon S3, Google Cloud, Azure. config.active_storage.service = :local # :amazon в production
Средне

Ruby on Rails / ActiveStorage

ActiveStorage: хранилища и cleanup

Какие хранилища поддерживает ActiveStorage? Как удалять файлы?
Хранилища (config/storage.yml): local: # диск (development/test) service: Disk root: <%= Rails.root.join("storage") %> amazon: # Amazon S3 (production) service: S3 access_key_id: <%= Rails.credentials.dig(:aws, :access_key_id) %> secret_access_key: <%= Rails.credentials.dig(:aws, :secret_access_key) %> bucket: myapp-production config/environments/production.rb: config.active_storage.service = :amazon Gemfile: gem "aws-sdk-s3" # для Amazon S3 Удаление файлов: user.avatar.purge # синхронно user.avatar.purge_later # через ActiveJob (рекомендуется) user.documents.each { |d| d.purge_later } Cleanup — удаление потерянных файлов: # Задача по расписанию: OrphanedBlobJob.set(cron: "0 3 * * *").perform_later Миграция с Paperclip/Carrierwave: — PaperClip устарел, миграция на ActiveStorage встроена
Легко

Ruby / Основы

Чётные числа

Напиши метод even_numbers(range), который возвращает массив чётных чисел из диапазона.
Легко

Ruby / Основы

Хэш

Напиши метод get_name(hash), который возвращает значение ключа :name из хэша.
Легко

Ruby on Rails / ActiveRecord

Enums в ActiveRecord

Что такое enum в Rails? Как использовать?
enum — перечисления в модели, хранятся как integer в БД: class Post < ApplicationRecord enum status: { draft: 0, published: 1, archived: 2 } enum category: { article: 0, news: 1, review: 2 } end Использование: post = Post.new post.draft! # установить статус post.published? # => false post.status # => "draft" Post.published # все опубликованные Post.where(status: :published) # то же самое Методы, создаваемые enum: post.published! # установить post.published? # проверить Post.statuses # => { "draft"=>0, "published"=>1, "archived"=>2 } Важно: не удаляйте значения из середины enum — нарушится маппинг. Новые значения добавляйте в конец.
Легко

Ruby on Rails / ActiveRecord

pluck и select

В чём разница между pluck и select?
pluck — возвращает массив значений одной колонки (без создания объектов): User.pluck(:email) # => ["ivan@mail.ru", "anna@mail.ru"] # SQL: SELECT email FROM users User.pluck(:id, :email) # => [[1, "ivan@mail.ru"], [2, "anna@mail.ru"]] select — загружает объекты ActiveRecord с выбранными колонками: User.select(:id, :email) # => [#<User id: 1, email: "ivan@mail.ru">, ...] Разница: User.pluck(:email) # ["ivan@mail.ru"] — массив строк User.select(:email) # [#<User>] — массив объектов pluck быстрее — не создаёт объекты, не загружает все колонки. find_each vs all: User.all.each { } # загружает ВСЕ в память User.find_each { } # по 1000 записей (batch)
Средне

Ruby on Rails / ActiveRecord

Transactions

Что такое транзакция в ActiveRecord? Зачем нужна?
Транзакция — группа операций, которые выполняются вместе. Если одна упала — все откатываются (all-or-nothing). ActiveRecord::Base.transaction do account.update!(balance: account.balance - 100) target.update!(balance: target.balance + 100) Transfer.create!(from: account, to: target, amount: 100) end # Если любой update! упадёт — все три откатятся Важно: используйте bang-методы (save!, update!) внутри транзакции. save без ! вернёт false вместо исключения — транзакция не откатится. С block: User.transaction do user.destroy! user.posts.delete_all end Исключения, откатывающие транзакцию: — ActiveRecord::RecordInvalid, RecordNotSaved — Любое исключение, но не return!
Средне

Ruby on Rails / ActiveRecord

Counter cache и touch

Что такое counter_cache и touch?
counter_cache — кеширование количества связей: # Без counter_cache (COUNT-запрос каждый раз): user.posts.count # SELECT COUNT(*) FROM posts WHERE user_id = 1 # С counter_cache: class Post < ApplicationRecord belongs_to :user, counter_cache: true end # Нужна колонка: add_column :users, :posts_count, :integer, default: 0 user.posts_count # Берёт из колонки, без SQL! touch — обновляет updated_at родителя: class Comment < ApplicationRecord belongs_to :post, touch: true end # При создании/удалении комментария — post.updated_at обновляется # Полезно для кеширования: post обновился → кеш сбросился Сбросить счётчики: User.find_each { |u| User.reset_counters(u.id, :posts) }
Средне

Ruby on Rails / ActiveRecord

STI (Single Table Inheritance)

Что такое STI в Rails? Когда использовать?
STI — несколько моделей в одной таблице, разделённых по типу. # Таблица vehicles: type, name, speed, seats class Vehicle < ApplicationRecord; end class Car < Vehicle; end class Bicycle < Vehicle; end Car.create!(name: "Toyota", speed: 180, seats: 5) Bicycle.create!(name: "Горный", speed: 30) Vehicle.all # все транспортные средства Car.all # только машины (WHERE type = 'Car') Колонка type — обязательна, хранит имя класса. Rails автоматически добавляет WHERE type IN ('Car'). Когда использовать: — Модели с общими полями, но разным поведением — Небольшое различие между типами Когда НЕ использовать: — Сильно разные поля у подтипов (много NULL) — Лучше отдельные таблицы или polymorphic Минусы: одна большая таблица, много NULL-колонок.
Средне

Ruby on Rails / MVC и Роутинг

namespace, scope, member, collection

Какие способы группировки маршрутов есть в Rails?
namespace — добавляет префикс URL и модуль контроллера: namespace :admin do resources :posts end # /admin/posts → Admin::PostsController scope — префикс URL без модуля: scope '/api' do resources :posts end # /api/posts → PostsController member — действие над конкретным ресурсом: resources :posts do member do post :publish # POST /posts/:id/publish end end collection — действие над всей коллекцией: resources :posts do collection do get :search # GET /posts/search end end shallow — короткие URL для вложенных: resources :users do resources :posts, shallow: true end # /users/:user_id/posts (index, create) # /posts/:id (show, edit, update, destroy)
Легко

Ruby on Rails / Views

Helpers, layouts, yield

Что такое helpers, layouts и yield во views?
Layouts — обёртка для всех страниц: # app/views/layouts/application.html.erb <html> <body> <%= yield %> <!-- сюда вставится содержимое страницы --> </body> </html> content_for / yield — именованные блоки: # В шаблоне: <% content_for :title, "Моя страница" %> # В layout: <title><%= yield :title %></title> Helpers — методы для views (и контроллеров): # app/helpers/application_helper.rb module ApplicationHelper def formatted_date(date) date.strftime("%d.%m.%Y") if date end end # Во view: <%= formatted_date(@post.created_at) %> Встроенные helpers: link_to "Текст", path button_to "Удалить", post_path(@post), method: :delete form_with model: @post image_tag "logo.png" number_to_currency(1000) # => "1,000.00 ₽"
Средне

Ruby on Rails / Продвинутые темы

ActionCable (WebSockets)

Что такое ActionCable? Для чего нужен?
ActionCable — фреймворк для real-time через WebSockets. Channel (канал): # app/channels/chat_channel.rb class ChatChannel < ApplicationCable::Channel def subscribed stream_from "chat_#{params[:room]}" end def receive(data) ActionCable.server.broadcast("chat_#{params[:room]}", data) end def unsubscribed # cleanup end end Сервер — broadcast откуда угодно: ActionCable.server.broadcast("chat_1", { message: "Привет!" }) Клиент (JS): const cable = ActionCable.createConsumer() const chat = cable.subscriptions.create("ChatChannel", { room: 1 }) chat.received = (data) => { appendMessage(data) } Использование: чаты, уведомления, live-обновления. Нужен Redis в production: config/cable.yml → redis adapter. Turbo Streams — альтернатива для простых случаев (без JS).
Средне

Ruby on Rails / Продвинутые темы

Concerns в моделях

Как использовать concerns в моделях?
Concerns — модули для переиспользования кода между моделями. # app/models/concerns/searchable.rb module Searchable extend ActiveSupport::Concern included do scope :search, ->(q) { where("title ILIKE ?", "%#{q}%") } end class_methods do def fuzzy_search(q) where("title ILIKE ?", "%#{q}%") end end end class Post < ApplicationRecord include Searchable end class Article < ApplicationRecord include Searchable end Post.search("ruby") # работает Article.fuzzy_search("rails") # тоже работает ActiveSupport::Concern даёт: — included { } — блок выполняется при include — class_methods { } — добавляет методы класса Не путать с Service Objects — concerns для общих методов моделей, Service Objects для бизнес-логики.
Легко

Docker

Что такое Docker

Что такое Docker? Зачем он нужен? Чем контейнер отличается от виртуальной машины?
Docker — инструмент для упаковки приложения и его зависимостей в контейнер. Контейнер работает одинаково на любой машине. Контейнер vs ВМ: ВМ — полный клон ОС с ядром. Тяжёлый (гигабайты), медленный запуск. Контейнер — использует ядро хоста. Лёгкий (мегабайты), мгновенный запуск. Образ (image) — шаблон для создания контейнеров. Создаётся из Dockerfile. Контейнер (container) — запущенный экземпляр образа. Реестр (registry) — хранилище образов (Docker Hub). Dockerfile — файл с инструкциями для сборки образа: FROM ruby:3.3 WORKDIR /app COPY . . RUN bundle install CMD ["rails", "server"]
Легко

Docker

docker-compose

Что такое docker-compose? Зачем нужен, когда можно просто Docker?
docker-compose — инструмент для запуска нескольких контейнеров одновременно. Описывает всю инфраструктуру в одном файле docker-compose.yml. Пример — Rails + PostgreSQL: services: app: build: . ports: - "3000:3000" depends_on: - db db: image: postgres:16 volumes: - pgdata:/var/lib/postgresql/data Команды: docker compose up — запустить всё docker compose up -d — в фоне docker compose down — остановить docker compose build — пересобрать образы Без compose тебе пришлось бы вручную запускать каждый контейнер, подключать сети, пробрасывать порты.
Легко

Elixir / Основы Elixir

Что такое Elixir

Что такое Elixir? На чём он работает? Зачем его учить Rails-разработчику?
Elixir — функциональный язык программирования, работает на виртуальной машине BEAM (Erlang VM). Особенности: - Функциональный (нет mutable state, нет классов, нет наследования) - Поддерживает hot code reload — можно обновлять код без остановки приложения - Fault-tolerant — процесс упал, остальные продолжают работать (supervisor tree) - Распараллеливание — миллионы легковесных процессов одновременно - OTP — фреймворк для построения отказоустойчивых систем Зачем Rails-разработчику: - Phoenix — веб-фреймворк, конкурент Rails по скорости - LiveView — realtime UI без JavaScript - Nerves — программирование встраиваемых устройств - Расширение Rails-приложения: websocket, background jobs, code battle
Легко

Elixir / Основы Elixir

Функции в Elixir

Как объявить функцию в Elixir? Чем анонимная функция отличается от именованной?
Именованная функция (внутри модуля): defmodule Math do def add(a, b) do a + b end def greet(name) do "Hello, #{name}" end end Math.add(2, 3) # => 5 Math.greet("Alice") # => "Hello, Alice" Анонимная функция (first-class citizen): greet = fn name -> "Hello, #{name}" end greet.("Alice") # => "Hello, Alice" Обрати внимание: анонимная функция вызывается с точкой greet.(...), именованная без Math.greet(...). Capture operator: add = &Math.add/2 add.(2, 3) # => 5 Сокращённая запись анонимной функции: square = &(&1 * &1) square.(5) # => 25
Легко

Elixir / Основы Elixir

Списки и кортежи

Что такое списки (List) и кортежи (Tuple)? Чем они отличаются?
List — связный список, добавление в начало O(1), доступ по индексу O(n): [1, 2, 3] [1 | [2, 3]] # => [1, 2, 3] (добавление в голову) list = [1, 2, 3] [0 | list] # => [0, 1, 2, 3] Tuple — contiguous block of memory, доступ по индексу O(1), размер фиксирован: {:ok, "result"} person = {"Alice", 25} elem(person, 0) # => "Alice" person |> elem(0) # => "Alice" Когда использовать: List — коллекция переменного размера (много элементов, добавление/удаление) Tuple — фиксированное количество элементов (результат операции: {:ok, data} / {:error, reason})
Средне

Elixir / Pattern Matching

Pattern Matching в функциях

Как использовать Pattern Matching в определении функций?
В Elixir можно определять несколько вариантов одной функции с разным pattern matching. Elixir выбирает первый совпавший вариант (как case/when в Ruby). defmodule Math do def factorial(0), do: 1 def factorial(n) when n > 0, do: n * factorial(n - 1) end factorial(5) # => 120 factorial(0) # => 1 Анонимные функции тоже поддерживают pattern matching: handle_result = fn {:ok, value} -> "Success: #{value}" {:error, reason} -> "Error: #{reason}" end handle_result.({:ok, 42}) # => "Success: 42" handle_result.({:error, "bad"}) # => "Error: bad" Это заменяет if/else и case — код чище и декларативнее.
Средне

Elixir / Pattern Matching

Pin operator

Что такое pin operator ^? Зачем он нужен?
Pin operator (^) заставляет Elixir использовать текущее значение переменной, а не переприсваивать его. Без ^: x = 1 x = 2 # x теперь 2 (переменная переприсвоена) С ^: x = 1 ^x = 1 # match: 1 == 1 ^x = 2 # MatchError: 1 != 2 Пример в функции: defmodule Greeter do def greet(name, role) when role == :admin do "Hello, #{name} (admin)" end def greet(name, _role) do "Hello, #{name}" end end С pin operator в case: expected = 42 case some_value do ^expected -> "exactly 42" other -> "got #{other}" end
Средне

Ruby / Окружение и диагностика

Запускается не та версия Ruby

Вы установили нужную версию Ruby через rbenv, но команда ruby -v показывает другую (системную) версию. В чём причина и как это исправить?
Причина: менеджер версий (rbenv, rvm, asdf) не инициализирован в текущей оболочке, либо в $PATH системный Ruby стоит раньше. Диагностика: which ruby # /usr/bin/ruby — системный (неправильно) # /home/user/.rbenv/shims/ruby — через rbenv (правильно) which -a ruby # показать все ruby в системе Решение для rbenv — добавить в ~/.bashrc (или ~/.zshrc): export PATH="$HOME/.rbenv/bin:$PATH" eval "$(rbenv init -)" Затем: source ~/.bashrc rbenv rehash Приоритет выбора версии (от высокого к низкому): 1. Переменная окружения RBENV_VERSION 2. .ruby-version в текущей папке (rbenv local) 3. ~/.rbenv/version (rbenv global) 4. Системный Ruby (/usr/bin/ruby) Типичная ошибка: установлен rbenv, но не прописан eval "$(rbenv init -)" в rc-файл оболочки.
Средне

Ruby / Окружение и диагностика

Гем установлен, но require падает

Вы выполнили gem install pry, но в Ruby-скрипте require 'pry' выдаёт LoadError. Гем точно установлен. В чём проблема?
Причина: гем установлен для одной версии Ruby, а скрипт запускается другой. У каждой версии Ruby — своя директория с гемами: gem env gemdir # покажет путь к гемам текущего Ruby ruby -v # текущая версия Проверить: gem list pry # установлен ли для текущего Ruby? Частые сценарии: - Установили гем под Ruby 3.1, а запускаете скрипт под Ruby 3.3 - Используете системный Ruby вместо rbenv/rvm - Забыли bundle install перед запуском Правильный подход — использовать Bundler: bundle install # установить гемы из Gemfile bundle exec ruby script.rb # запустить с нужными версиями гемов Проверить, откуда загрузился гем: ruby -e "require 'pry'; puts Gem.loaded_specs['pry'].full_gem_path"
Средне

Ruby / Окружение и диагностика

bundle exec vs ruby

Скрипт работает с bundle exec ruby script.rb, но падает с обычным ruby script.rb. Почему так происходит и что делает bundle exec?
bundle exec гарантирует, что Ruby использует именно те версии гемов, которые указаны в Gemfile.lock. Без bundle exec: - Ruby ищет гемы по $LOAD_PATH — массиву директорий - Берёт первый найденный гем, независимо от версии - Может подхватить несовместимую версию С bundle exec: - Bundler подменяет $LOAD_PATH, добавляя только нужные версии - Все зависимости разрешены по Gemfile.lock - Гарантированно воспроизводимое окружение Посмотреть $LOAD_PATH: ruby -e 'puts $LOAD_PATH' bundle exec ruby -e 'puts $LOAD_PATH' # будет включать гемы из Gemfile $LOAD_PATH — это массив директорий, где Ruby ищет файлы при require. Первый найденный файл побеждает — поэтому порядок директорий важен. Проверить, какой файл реально загрузился: $LOADED_FEATURES.grep(/some_gem/)
Средне

Ruby / Окружение и диагностика

Гем удалён, но команда всё ещё работает

Вы выполнили gem uninstall rails, но команда rails -v всё ещё показывает версию. Как такое возможно?
Причин может быть несколько: 1. Несколько версий Ruby — удалили из одной, команда доступна из другой: which rails # откуда берётся команда? gem list rails # установлен ли для текущего Ruby? 2. Binstub остался — исполняемый файл в bin/ или /usr/local/bin: which rails # /usr/local/bin/rails или bin/rails file $(which rails) # посмотреть тип файла 3. Другой гем предоставляет команду rails (например, railties) 4. Spring кеширует — в Rails-проекте: spring stop # остановить Spring Диагностика: which -a rails # все исполняемые файлы rails gem environment # полная информация об окружении bundle show rails # где гем в текущем проекте Правильная очистка: gem uninstall rails -a # удалить все версии which rails # проверить, осталась ли команда
Средне

Ruby / Окружение и диагностика

Что такое $LOAD_PATH

Интервьюер просит проверить '$LOAD_PATH' и посмотреть приоритет загрузки. Что это такое, как проверить и зачем это нужно?
$LOAD_PATH (или коротко $:) — глобальная переменная Ruby, массив директорий, в которых Ruby ищет файлы при вызове require. Проверить: ruby -e 'puts $LOAD_PATH' ruby -e '$LOAD_PATH.each_with_index { |p, i| puts "#{i}: #{p}" }' bundle exec ruby -e 'puts $LOAD_PATH' # с учётом Gemfile Ruby идёт по директориям слева направо и берёт первый найденный файл. Отсюда конфликты: если в двух директориях есть файл с одинаковым именем, загрузится тот, что стоит раньше в $LOAD_PATH. Когда это важно: - Конфликты версий гемов - Отладка: какой файл реально загрузился - Понимание, почему require находит (или не находит) файл Проверить, откуда загрузился конкретный файл: ruby -e "require 'rails'; puts $LOADED_FEATURES.grep(/rails/)"
Средне

Ruby on Rails / Диагностика и проблемы

PostgreSQL: connection refused

Вы запускаете rails db:migrate и получаете ошибку connection refused. PostgreSQL установлен и работает. В чём может быть проблема?
Возможные причины и проверка: 1. Неправильный порт в config/database.yml: По умолчанию PostgreSQL слушает порт 5432, но если установлен второй инстанс — может быть 5433. Проверить порт PostgreSQL: sudo -u postgres psql -c "SHOW port;" Или посмотреть в postgresql.conf. 2. PostgreSQL слушает не на том хосте: В database.yml указан host: localhost, но PostgreSQL слушает только Unix-сокет. Или наоборот: host: 127.0.0.1, а PostgreSQL принимает только на /var/run/postgresql/. 3. Неверные учётные данные в database.yml: Проверить: username, password, host, port. 4. Файл database.yml не создан (нет config/database.yml): Rails использует дефолтные значения, которые могут не совпадать с вашей установкой. Диагностика: cat config/database.yml # проверить настройки sudo -u postgres psql # подключиться напрямую pg_isready # доступен ли PostgreSQL ss -tlnp | grep 5432 # слушает ли порт Алгоритм: сначала подключиться напрямую (psql), потом сверить настройки с database.yml.
Средне

Ruby on Rails / Диагностика и проблемы

Таблица создана, но в schema.rb её нет

Вы выполнили миграцию, таблица в базе данных создалась (проверили через psql), но в db/schema.rb она не появилась. Почему?
Причина: формат схемы установлен в :sql вместо :ruby. По умолчанию Rails использует формат :ruby — генерирует db/schema.rb. Но если в конфиге указано: config.active_record.schema_format = :sql То структура БД будет сохранена в db/structure.sql (сырой SQL-дамп). Проверить: ls db/schema.rb db/structure.sql # какой файл существует? Также возможные причины: - Миграция упала в середине, но таблица успела создаться - Забыли запустить rails db:migrate RAILS_ENV=test - Файл schema.rb закрыт для записи Решение: проверить формат в config/application.rb или config/environments/*.rb и запустить rails db:schema:dump (или db:structure:dump).
Легко

Ruby on Rails / Диагностика и проблемы

Тесты падают: database does not exist

rails server и rails console работают нормально, но при запуске тестов (rails test) возникает ошибка: database 'app_test' does not exist. Почему?
Причина: Rails использует разные базы данных для разных окружений: - development → app_development (создана, работает) - test → app_test (не создана) - production → app_production По умолчанию rails server и rails console запускаются в development. rails test запускается в test окружении — нужна отдельная база. Решение: RAILS_ENV=test rails db:create # создать тестовую базу RAILS_ENV=test rails db:migrate # применить миграции # Или одной командой: rails db:test:prepare # подготовить тестовую БД Или проще — при настройке проекта: rails db:create # создаст все базы (development + test) rails db:migrate # миграции для development rails db:test:prepare # скопировать схему в test
Легко

Ruby on Rails / Диагностика и проблемы

Новая колонка не видна в консоли

Вы добавили колонку через миграцию (rails db:migrate прошёл успешно), но в rails console вызов Model.column_names не показывает новую колонку. В чём проблема?
Причина: консоль была открыта ДО выполнения миграции. ActiveRecord кеширует схему таблиц при первой загрузке модели. Решение — обновить кеш схемы прямо в консоли: Model.reset_column_information Или просто перезапустить консоль: exit rails console Почему так происходит: - При запуске консоли ActiveRecord загружает описание таблиц - Это описание хранится в памяти до конца сессии - Новая миграция меняет БД, но консоль не знает об этом Проверить, что миграция действительно прошла: rails db:migrate:status # должна быть отметка "up" рядом с нужной миграцией
Легко

Ruby / Строки

Upcase

Напиши метод to_upcase(str), который переводит строку в верхний регистр.
Средне

Ruby on Rails / Диагностика и проблемы

Изменения в коде не применяются

Вы сохранили изменения в контроллере, перезапустили rails server, но в браузере всё равно старый результат. В чём может быть дело?
Возможные причины (от частых к редким): 1. Spring кеширует код приложения: spring stop # остановить Spring # Или отключить: DISABLE_SPRING=1 rails server 2. Кеширование в development: config.action_controller.perform_caching = false # Проверить в config/environments/development.rb 3. Браузерный кеш: Ctrl+Shift+R (жёсткое обновление) # Или открыть в режиме инкогнито 4. Редактируете не тот файл: puts "HERE" в контроллере # Если не видно в логах — открыт не тот файл 5. Запущено несколько серверов: lsof -i :3000 # проверить, что слушает порт kill -9 <PID> # убить старый процесс В development-режиме Rails автоматически перезагружает код при каждом запросе, но Spring может вмешиваться и кешировать.
Средне

Ruby on Rails / Диагностика и проблемы

link_to :delete отправляет GET

В шаблоне написано link_to 'Удалить', item_path(@item), method: :delete, но при клике отправляется GET-запрос вместо DELETE. Почему?
Причина: не работает JavaScript-обработчик для не-GET запросов. Ссылки (<a>) по умолчанию отправляют GET. Чтобы отправить DELETE, Rails генерирует атрибут data-method="delete" и полагается на JavaScript (rails-ujs или Turbo), который перехватывает клик и отправляет правильный HTTP-метод через форму. Решение зависит от версии Rails: Rails 6 и ниже — нужен rails-ujs: //= require rails-ujs в application.js Rails 7 (Turbo) — другой синтаксис: link_to 'Удалить', item_path(@item), data: { turbo_method: :delete } Rails 7 (без Turbo, с rails-ujs): link_to 'Удалить', item_path(@item), data: { method: :delete } Или использовать button_to (работает без JavaScript): button_to 'Удалить', item_path(@item), method: :delete # button_to генерирует <form>, а не <a> — форма поддерживает любой метод Лучший подход: для деструктивных действий использовать button_to.
Сложно

Ruby on Rails / Диагностика и проблемы

bundle install: native extension error

При запуске bundle install вы получаете ошибку: Gem::Ext::BuildError: ERROR: Failed to build gem native extension. Что делать?
Причина: гем содержит C-код, для компиляции которого не хватает системных библиотек. Частые виновники: - pg (PostgreSQL клиент) — нужен libpq-dev - nokogiri (XML парсер) — нужен libxml2-dev, libxslt1-dev - ffi — нужен build-essential - sqlite3 — нужен libsqlite3-dev - rmagick (ImageMagick) — нужен libmagickwand-dev Решение (Ubuntu/Debian): sudo apt install build-essential libpq-dev libxml2-dev libxslt1-dev libsqlite3-dev Для pg конкретно: sudo apt install libpq-dev # macOS: brew install postgresql Для nokogiri: sudo apt install libxml2-dev libxslt1-dev # Или использовать встроенные библиотеки: bundle config build.nokogiri --use-system-libraries Алгоритм: 1. Прочитать полное сообщение об ошибке — там указан конкретный гем 2. Найти системные зависимости этого гема (обычно в документации) 3. Установить зависимости: sudo apt install ... 4. Повторить bundle install
Легко

Git / Основы Git

VCS и Git: что и зачем

Что такое VCS? Что такое Git? Почему его используют?
VCS (Version Control System) — система контроля версий — программа для работы с изменяющейся информацией. Git — распределённая система контроля версий, которая даёт возможность отслеживать изменения в файлах и работать совместно с другими разработчиками. Подход Git к хранению данных — набор снимков файловой системы. При каждом сохранении Git запоминает, как выглядит каждый файл, и сохраняет ссылку на этот снимок. Почему Git: — Распределённый: полная копия репозитория у каждого — Быстрый: большинство операций локально — Ветвление: лёгкое создание и слияние веток — Целостность: каждое изменение проверяется SHA-1 хешем
Легко

Git / Основы Git

Создание и подключение репозитория

Как создать репозиторий, подключить внешний репозиторий?
Создание нового репозитория и подключение к GitHub: git init # инициализация локального репозитория git add . # добавить все файлы в staging git commit -m "first commit" # первый коммит git remote add origin git@github.com:username/project.git git push -u origin master # отправить на GitHub remote — указатель на внешний репозиторий. origin — стандартное имя для основного remote. Посмотреть remote: git remote -v git remote remove origin # удалить remote
Легко

Git / Основы Git

Клонирование удалённого репозитория

Как загрузить удалённый репозиторий?
git clone — создаёт локальную копию удалённого репозитория: git clone git@github.com:username/project.git Клон по SSH (git@github.com:...) — нужна настройка SSH-ключа. Клон по HTTPS (https://github.com/...) — логин/пароль или токен. Клон в другую папку: git clone git@github.com:user/repo.git my-folder Клон определённой ветки: git clone -b branch-name git@github.com:user/repo.git
Легко

Git / Основы Git

Коммит и история коммитов

Что такое коммит? Как посмотреть историю коммитов?
Коммит (commit) — подтверждение изменений, снимок состояния проекта. Каждый коммит имеет уникальный SHA-1 хеш. История коммитов: git log # полная история git log --oneline # краткий формат (хеш + сообщение) git log --oneline -10 # последние 10 коммитов git log --graph --oneline # с визуализацией веток git log --author="username" # фильтр по автору git log --since="2 weeks ago" # фильтр по дате Показать изменения в коммите: git show abc1234 # содержимое конкретного коммита git diff abc1234^..abc1234 # что изменилось в коммите
Легко

Git / Основы Git

Состояния файлов в Git

Какие состояния файлов существуют в системе Git?
В Git файлы могут находиться в одном из трёх состояний: 1. Зафиксированный (committed) — файл сохранён в локальной базе 2. Изменённый (modified) — файл изменён, но не зафиксирован 3. Подготовленный (staged) — изменённый файл отмечен для включения в следующий коммит Три области проекта: — .git/ (Git directory) — метаданные и база объектов — Рабочий каталог (working directory) — файлы проекта — Staging area (index) — подготовленные файлы Переходы: modified → git add → staged → git commit → committed
Легко

Git / Основы Git

git commit: флаги -am, -a, -m

git commit — в каких случаях писать -am, -a и -m?
-m "message" — создать коммит с сообщением для файлов из staging area (предварительно добавленных через git add): git add some.file git commit -m "Your message here" -a — автоматически добавить в staging все отслеживаемые (индексированные) файлы. Новые (неотслеживаемые) файлы в коммит НЕ попадут: git commit -a -m "Your message here" -am — объединение флагов -a и -m: git commit -am "Your message here" Разница: -m — только файлы из staging (после git add) -a — все отслеживаемые файлы автоматически -am — все отслеживаемые + сообщение
Легко

Git / Основы Git

SSH-ключ для Git

Для чего нужен SSH-ключ?
SSH-ключи используются для авторизации на сервисах (GitHub, GitLab, серверы) без ввода пароля. SSH-ключ состоит из двух частей: — id_rsa — закрытая часть. Должна быть доступна только вам. Никому не давайте доступ. Один ключ можно использовать на нескольких машинах, но это увеличивает риск. — id_rsa.pub — открытая часть. Можно показывать всем. Добавляется в ~/.ssh/authorized_keys на сервере или в настройки GitHub/GitLab. Генерация ключа: ssh-keygen -t ed25519 -C "your@email.com" Добавить ключ в ssh-agent: eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519 Проверить подключение к GitHub: ssh -T git@github.com
Легко

Git / Ветвление и слияние

Ветки в Git

Что такое ветка в Git?
Ветка (branch) — организованная система ссылок на коммиты. Ветка по умолчанию — master (или main). При каждом коммите ветка сдвигается вперёд автоматически. Основные операции: git branch # список веток git branch feature # создать ветку git checkout feature # переключиться на ветку git checkout -b feature # создать + переключиться git switch -c feature # новый способ (Git 2.23+) Ответвление от основной ветки делается для работы с определённой фичей, исправлением бага или экспериментом. Ветка — это просто указатель (ref) на коммит. Создание ветки — очень быстрая операция.
Средне

Git / Ветвление и слияние

merge и rebase: отличия

Отличия между командами merge и rebase?
Два способа включить изменения из одной ветки в другую: merge — создаёт коммит-слияние (merge commit): git checkout main git merge feature — Сохраняет полную историю — Создаёт новый коммит с двумя предками — Безопасно для публичных веток — История может быть нелинейной rebase — переписывает коммиты поверх целевой ветки: git checkout feature git rebase main — Находится общий предок двух веток — Дельта каждого коммита сохраняется — Текущая ветка ставится на коммит целевой — Изменения применяются одно за другим — История становится линейной и аккуратной — Хеши коммитов переписываются! rebase даёт чистую историю, но переписывает хеши. merge сохраняет реальную историю.
Легко

Git / Ветвление и слияние

Золотое правило rebase

Какое золотое правило rebase?
Не перемещайте (rebase) коммиты, которые вы уже отправили в публичный репозиторий. Правило вытекает из свойства rebase переписывать историю коммитов с новыми хешами. Если сделать rebase публичных коммитов: — У других разработчиков будут конфликты при pull — История разойдётся — Git будет считать коммиты разными Безопасно: git rebase main # свою feature-ветку на main Опасно: git rebase main # если main — публичная ветка Если запушили — используйте merge, не rebase.
Легко

Git / Ветвление и слияние

Удаление веток

Как удалить ветку локально и с удалённого репозитория?
Удалить локальную ветку: git branch -d feature-branch # безопасное удаление (только если слита) git branch -D feature-branch # принудительное удаление Удалить ветку с удалённого репозитория (GitHub): git push origin --delete feature-branch Удалить отслеживающую ветку (если удалённая удалена): git fetch --prune # очистить устаревшие refs git branch -vv # показать, какие ветки отслеживают git remote prune origin # удалить stale отслеживающие ветки Проверить, слита ли ветка: git branch --merged main # ветки, слитые в main
Средне

Git / Ветвление и слияние

cherry-pick: перенос коммитов

Как перенести изменения из одной ветки в другую?
Способ 1 — cherry-pick (перенос конкретного коммита): git log --oneline -5 # найти хеш нужного коммита git checkout target-branch # перейти в целевую ветку git cherry-pick abc1234 # применить коммит cherry-pick создаёт НОВЫЙ коммит с теми же изменениями, но другим хешем. Способ 2 — создать ветку и смержить в обе: git checkout -b feature main # создать ветку от main # ... сделать изменения, закоммитить git checkout main git merge feature # слить в main git checkout other-branch git merge feature # слить в другую ветку cherry-pick удобнее для единичных коммитов. merge — для целых наборов изменений.
Легко

Git / Ветвление и слияние

Pull requests

Что такое запросы на слияние (pull requests)? Как их создавать?
Pull Request (PR) — запрос на слияние вашей ветки с основной. Удобная система взаимодействия между автором изменений и хозяином репозитория. Позволяет: — Обсуждать изменения (комментарии к строкам кода) — Вносить правки до слияния — Запускать CI/CD проверки — Требовать аппрувы (code review) Как создать PR: 1. Создать ветку и закоммитить изменения 2. Запушить ветку на GitHub 3. Зайти на GitHub → "Compare & pull request" 4. Описать изменения, назначить ревьюеров 5. После аппрува — "Merge pull request" В GitLab — Merge Request (MR). Суть та же.
Легко

Git / Рабочий процесс

Отправка изменений на удалённый репозиторий

Как отправить свои изменения на удалённый репозиторий?
Стандартный workflow: git add . # добавить изменения в staging git commit -m "Commit message" # зафиксировать git push # отправить на удалённый Первый push новой ветки: git push -u origin feature-branch # -u = --set-upstream Последующие push: git push # запомнила после -u Загрузить чужие изменения перед push: git pull --rebase # получить + переместить свои коммиты наверх Push с принудительной перезаписью (ОСТОРОЖНО): git push --force # перезаписать удалённую историю git push --force-with-lease # безопаснее — не перезапишет чужие коммиты
Легко

Git / Рабочий процесс

Получение последних изменений

Как загрузить последние изменения с определённой ветки?
git pull — получить и слить изменения: git pull origin main # получить main и слить с текущей git pull --rebase — получить и переместить: git pull --rebase # ваши коммиты будут поверх полученных Предпочтительный вариант — чистая история без merge-коммитов git fetch — только получить (без слияния): git fetch origin # скачать все изменения git fetch origin main # скачать только main git log origin/main # посмотреть, что нового git merge origin/main # слить когда готовы Разница: — pull = fetch + merge — pull --rebase = fetch + rebase — fetch — безопаснее, даёт контроль
Легко

Git / Рабочий процесс

Изменение последнего коммита (amend)

Как добавить изменения в уже созданный коммит? Как изменить название коммита?
git commit --amend — изменить последний коммит: Добавить забытые файлы в последний коммит: git add forgotten_file.rb git commit --amend --no-edit # добавить без изменения сообщения Изменить сообщение последнего коммита: git commit --amend -m "New message" Изменить и файлы, и сообщение: git add . git commit --amend -m "Updated commit" Важно: amend создаёт НОВЫЙ хеш коммита. Если вы уже запушили этот коммит — нужно: git push --force-with-lease Но! Не делайте amend коммитов, которые уже в публичной ветке и другие разработчики могли на них основываться.
Средне

Ruby / Основы

Максимальное произведение соседних элементов

Дан массив целых чисел. Найди максимальное произведение двух соседних элементов. Массив содержит как минимум 2 элемента и может содержать положительные, отрицательные числа и нули. Примеры: [1, 2, 3] → 6 (2 * 3) [9, 5, 10, 2, 24, -1, -48] → 50 (5 * 10) [-23, 4, -5, 99, -27, 329, -2, 7, -921] → -14 (-2 * 7)
Легко

Git / Рабочий процесс

Git vs SVN

Разница между Git и SVN (Subversion)?
Главное отличие: Git — распределённая VCS, SVN — централизованная. Преимущества Git: — Сервер не нужен. Можно работать полностью локально — Если сервер «прилёг» — коммиты в локальный репозиторий продолжают работать, а когда сервер вернётся — push — Шифрование «из коробки» (SHA-1 хеши) — Служебная информация только в .git/ в корне проекта, а не в каждом каталоге (как .svn у Subversion) — Быстрое ветвление и слияние SVN особенности: — Централизованная модель: нужен сервер для几乎所有 операций — Нумерация коммитов — последовательная (1, 2, 3...) — Лучше работает с большими бинарными файлами — Можно клонировать поддерево каталога В продакшене Git — стандарт де-факто.
Средне

Git / Рабочий процесс

Gitflow: модель ветвления

Что такое Gitflow?
Gitflow — модель ветвления для управления релизами. Основное правило GitHub Flow: всё, что в master (main) — гарантированно стабильно и готово к деплою в любой момент. Основные ветки в Gitflow: — main/master — стабильный код (production) — develop — ветка разработки (интеграция фич) — feature/* — отдельные фичи (от develop) — release/* — подготовка к релизу — hotfix/* — срочные исправления (от main) Workflow: 1. Создать feature-ветку от main 2. Разработать, коммитить в feature-ветку 3. Открыть Pull Request в main 4. Code review, тесты 5. Слить в main после аппрува Каждая новая ветвь создаётся от main. Вносить правки напрямую в main — нельзя.
Средне

Git / Проблемы и решения

Push прошёл, но на продакшене изменений нет

Вы сделали git push, команда прошла без ошибок, но на продакшене (или на тестовом сервере) изменений нет. В чём может быть проблема?
Возможные причины: 1. Запушили не в ту ветку: git branch -vv # посмотреть, куда привязана текущая ветка git log origin/main # проверить, есть ли коммиты в main 2. Push был в origin, а деплой настроен с другого remote: git remote -v # показать все remote'ы # origin → github.com:you/repo # deploy → production-server 3. Запушили в свою feature-ветку, а не в main: git push origin feature-branch # запушили сюда git push origin main # а нужно было сюда 4. CI/CD не прошёл — изменения не попали на сервер: Проверить статус pipeline (GitHub Actions, GitLab CI и т.д.) 5. На сервере не выполнили миграции: Даже если код обновился, БД может быть старой. На сервере: rails db:migrate RAILS_ENV=production Диагностика: git log --oneline -5 # последние коммиты git diff origin/main...HEAD # что не в origin/main git remote -v # куда пушите
Легко

Git / Проблемы и решения

Конфликты слияния: unmerged paths

Вы делаете git merge main в свою ветку, возникают конфликты. Вы исправили файлы вручную, но git status всё ещё показывает 'unmerged paths'. Что пропущено?
После ручного разрешения конфликтов нужно сообщить Git, что файлы исправлены: git add <файл> # отметить файл как разрешённый # Или все сразу: git add . Затем завершить слияние: git commit # (без -m — Git предложит сообщение о слиянии) Полный алгоритм разрешения конфликтов: 1. git merge main # начать слияние 2. Git помечает файлы с конфликтами: <<<<<<<, =======, >>>>>>> 3. Открыть файлы, удалить маркеры, оставить нужный код 4. git add . # ← ЭТО ЧАСТО ЗАБЫВАЮТ 5. git commit # завершить слияние Проверить статус: git status # зелёный — готово, красный — ещё конфликт Отменить слияние, если запутались: git merge --abort # вернуться к состоянию до слияния
Средне

Git / Проблемы и решения

Случайный коммит не в ту ветку

Вы сделали коммит в main, хотя хотели сделать его в feature-ветке. Как перенести коммит, не потеряв работу?
Решение через git cherry-pick: 1. Запомнить хэш коммита: git log --oneline -3 # abc1234 Мой важный коммит 2. Переключиться на нужную ветку: git checkout feature-branch 3. Перенести коммит: git cherry-pick abc1234 4. Вернуться в main и откатить коммит: git checkout main git reset --soft HEAD~1 # сохранить изменения в staging # Или: git reset --mixed HEAD~1 # вернуть изменения в рабочую директорию # Или (ОСТОРОЖНО — теряет изменения): git reset --hard HEAD~1 # удалить коммит полностью Важно: если вы уже запушили коммит в общий репозиторий — не используйте reset. Используйте git revert: git revert abc1234 # создаст обратный коммит
Легко

Ruby / Основы

Отфильтровать строки из массива

Дан массив, содержащий неотрицательные целые числа и строки. Верни новый массив, в котором остались только целые числа. Примеры: filter_list([1, 2, 'a', 'b']) → [1, 2] filter_list([1, 'a', 'b', 0, 15]) → [1, 0, 15] filter_list([1, 2, 'aasf', '1', '123', 123]) → [1, 2, 123]
Легко

Базы данных / Основы БД

Реляционная база данных

Что такое реляционная база данных?
Реляционная БД — набор данных с предопределёнными связями между ними. Данные организованы в виде таблиц из столбцов и строк. — Столбцы — определённый тип данных (имя, возраст, email) — Строки — набор связанных значений (одна запись/сущность) — Каждая ячейка — значение атрибута Связи между таблицами реализуются через ключи (primary key, foreign key). Примеры реляционных БД: PostgreSQL, MySQL, SQLite, Oracle, SQL Server. Примеры нереляционных: MongoDB (документы), Redis (ключ-значение), Neo4j (графы), Cassandra (колоночная).
Легко

Базы данных / Основы БД

Таблица, кортеж, первичный ключ

Что такое таблица, кортеж? Что такое primary key?
Таблица — набор элементов данных в виде столбцов (с уникальным именем) и строк. Содержит определённое число столбцов, но любое количество строк. Кортеж — это набор именованных значений заданного типа (одна строка таблицы). Primary key (PK) — подмножество столбцов, которое уникально идентифицирует строку. Каждая строка однозначно определяется одним или несколькими уникальными значениями. Свойства PK: — Не позволяет создавать одинаковые записи (строк) — Обеспечивает логическую связь между таблицами — Не может быть NULL — В таблице только один PK В Rails: по соглашению используется столбец id (auto-increment), который автоматически создаётся для каждой записи. create_table :users do |t| t.string :name t.timestamps end # id создаётся автоматически как PRIMARY KEY
Легко

Базы данных / Основы БД

Связи между таблицами: foreign key

Как реализованы связи между таблицами? Что такое foreign key?
Между таблицами существуют три вида связей: Один-ко-многим (1:N): Один пользователь — много постов users(id) ← posts(user_id) Один-к-одному (1:1): Один пользователь — один профиль users(id) ← profiles(user_id) с UNIQUE Многие-ко-многим (N:N): Много студентов — много курсов Нужна промежуточная таблица (join table): students(id), courses(id), enrollments(student_id, course_id) Foreign key (FK) — столбец в дочерней таблице, ссылающийся на PK родительской. Обеспечивает ссылочную целостность. В Rails: belongs_to :user # posts.user_id → users.id has_many :posts # users.id ← posts.user_id has_many :through # для N:N через промежуточную таблицу По соглашению Rails: столбец FK называется как модель + _id (user_id, post_id).
Средне

Базы данных / Основы БД

Нормализация и денормализация

Что такое нормализация и денормализация базы данных?
Нормализация — преобразование структуры БД к виду, отвечающему нормальным формам (НФ). Цель: устранить избыточность и аномалии. Нормальные формы (основные): — 1НФ: атомарные значения (не массивы в ячейках) — 2НФ: все неключевые столбцы зависят от всего PK — 3НФ: нет транзитивных зависимостей между неключевыми столбцами Денормализация — намеренное нарушение нормальных форм для ускорения чтения за счёт добавления избыточных данных. Когда денормализовать: — Частые сложные JOIN-запросы замедляют работу — Read-heavy нагрузка (много чтений, мало записей) — Кэширование вычисляемых полей (counter_cache в Rails) Пример денормализации: # Нормализовано: отдельная таблица posts(id, title), comments(id, post_id, text) # Денормализовано: счётчик в posts posts(id, title, comments_count) # comments_count обновляется триггером/counter_cache
Легко

Базы данных / Основы БД

Redis vs PostgreSQL: почему быстрее

Основная причина, по которой Redis работает быстрее PostgreSQL?
Причина в месте хранения данных: — Redis хранит данные в оперативной памяти (RAM) — PostgreSQL хранит данные на жёстком диске (SSD/HDD) RAM на порядки быстрее диска: — RAM: ~100 нс доступ (наносекунды) — SSD: ~100 мкс доступ (микросекунды, в 1000 раз медленнее) Redis — in-memory key-value хранилище: — Нет парсинга SQL-запросов — Нет планировщика запросов — Нет сложных транзакций — Данные в памяти — прямой доступ по ключу Но: Redis теряет данные при перезагрузке (если не настроена персистенция). PostgreSQL — надёжное хранение, ACID-транзакции, сложные запросы, но медленнее. Обычно используются вместе: PostgreSQL — основное хранилище, Redis — кэш и сессии.
Легко

Ruby / Основы

Целочисленное деление

Почему в Ruby 1660 / 100 не равно 16.6? Что нужно сделать, чтобы получить 16.6?
Если все аргументы арифметического выражения — целые числа (Integer), то результат будет целым числом. Если хотя бы одно число с плавающей запятой (Float), то результат будет Float. 1660 / 100 #=> 16 (Integer) 1660.0 / 100 #=> 16.6 (Float) 1660 / 100.0 #=> 16.6 (Float) Чтобы получить 16.6, нужно чтобы хотя бы один операнд был Float: 1660.fdiv(100) #=> 16.6 1660.to_f / 100 #=> 16.6
Легко

Базы данных / SQL запросы

SELECT: оператор запроса

Как работает SELECT оператор?
SELECT — оператор запроса, возвращающий набор данных из БД. Состоит из предложений (разделов): SELECT — список возвращаемых столбцов FROM — источник данных (таблица, join, подзапрос) WHERE — фильтр строк GROUP BY — группировка с агрегатными функциями HAVING — фильтр групп (после GROUP BY) ORDER BY — сортировка LIMIT — ограничение количества строк Порядок выполнения: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT Пример: SELECT department, COUNT(*), AVG(salary) FROM employees WHERE age > 25 GROUP BY department HAVING COUNT(*) > 5 ORDER BY AVG(salary) DESC LIMIT 10;
Средне

Базы данных / SQL запросы

Виды JOIN

Какие бывают виды JOIN? Как каждый работает?
INNER JOIN — только совпадающие строки из обеих таблиц: SELECT * FROM users INNER JOIN posts ON users.id = posts.user_id # Только пользователи с постами LEFT OUTER JOIN — все строки из левой + совпадающие из правой: SELECT * FROM users LEFT JOIN posts ON users.id = posts.user_id # Все пользователи, даже без постов (posts = NULL) RIGHT OUTER JOIN — все строки из правой + совпадающие из левой: # То же что LEFT, но таблицы меняются местами FULL OUTER JOIN — объединение LEFT и RIGHT: # Все строки из обеих таблиц, NULL где нет совпадений CROSS JOIN — декартово произведение: # Каждая строка первой × каждая строка второй # Не требует условия ON SELECT * FROM colors CROSS JOIN sizes # 3 цвета × 4 размера = 12 строк В Rails: User.joins(:posts) # INNER JOIN User.left_joins(:posts) # LEFT JOIN User.includes(:posts) # LEFT OUTER JOIN (eager loading)
Легко

Базы данных / SQL запросы

INSERT, UPDATE, DELETE

Как работают INSERT, UPDATE, DELETE операторы?
INSERT — добавление строк в таблицу: INSERT INTO users (name, email) VALUES ('Alice', 'alice@mail.com'); INSERT INTO users (name, email) SELECT name, email FROM archive; UPDATE — обновление значений: UPDATE users SET name = 'Bob', updated_at = NOW() WHERE id = 1; Важно: без WHERE обновятся ВСЕ строки! DELETE — удаление записей: DELETE FROM users WHERE id = 1; Важно: без WHERE удалятся ВСЕ строки! В Rails: User.create(name: 'Alice') # INSERT User.update(name: 'Bob') # UPDATE User.delete(id) # DELETE (без callbacks) User.destroy(id) # DELETE (с callbacks)
Легко

Базы данных / SQL запросы

WHERE и HAVING: отличия

Чем HAVING отличается от WHERE?
WHERE — фильтрует строки ДО группировки: SELECT department, COUNT(*) FROM employees WHERE age > 25 # фильтр по строкам GROUP BY department HAVING — фильтрует группы ПОСЛЕ группировки: SELECT department, COUNT(*) FROM employees GROUP BY department HAVING COUNT(*) > 5 # фильтр по группам Порядок выполнения: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY Правило: — WHERE — для условий на отдельные строки — HAVING — для условий на агрегаты (COUNT, SUM, AVG) Можно использовать оба: SELECT department, AVG(salary) as avg_sal FROM employees WHERE age > 25 # фильтр строк GROUP BY department HAVING AVG(salary) > 50000 # фильтр групп
Легко

Базы данных / SQL запросы

Оператор BETWEEN

Что такое BETWEEN в SQL? Как работает?
BETWEEN — оператор для проверки вхождения значения в диапазон. Включает ГРАНИЧНЫЕ значения. Синтаксис: SELECT * FROM products WHERE price BETWEEN 100 AND 500; # Эквивалентно: SELECT * FROM products WHERE price >= 100 AND price <= 500; С датами: SELECT * FROM orders WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31'; NOT BETWEEN — вне диапазона: SELECT * FROM products WHERE price NOT BETWEEN 100 AND 500; Важные нюансы: — Включает обе границы (closed interval) — Для дат: BETWEEN '2024-01-01' AND '2024-01-31' НЕ включит 31 января 15:30 — нужно: created_at >= '2024-01-01' AND created_at < '2024-02-01' — Работает с числами, датами, строками (по алфавиту) В Rails: Product.where(price: 100..500) # BETWEEN Product.where.not(price: 100..500) # NOT BETWEEN Order.where(created_at: Date.today.beginning_of_month..Date.today.end_of_month)
Легко

Базы данных / SQL запросы

LIKE и ILIKE: поиск по шаблону

Чем LIKE отличается от ILIKE?
LIKE — поиск по шаблону (case-sensitive, регистрозависимый): SELECT * FROM users WHERE name LIKE 'Иван%'; # Найдёт: Иван, Иванов, Иванова # НЕ найдёт: иван, ИВАН ILIKE — то же, но case-insensitive (нечувствителен к регистру): SELECT * FROM users WHERE name ILIKE 'иван%'; # Найдёт: Иван, иван, ИВАН, Иванов, иванова Шаблоны: — % — любое количество символов (0 и более) LIKE '%test%' # содержит 'test' где угодно — _ — ровно один символ LIKE '_ван' # Иван, Ован, но не Ван Производительность: — LIKE 'prefix%' — может использовать индекс (B-tree) — LIKE '%suffix' — full scan, индекс не поможет — LIKE '%middle%' — full scan — Для быстрого поиска: полнотекстовый поиск или GIN-индекс ILIKE доступен только в PostgreSQL. В MySQL: LIKE всегда case-insensitive (зависит от collation). В SQLite: LIKE case-insensitive для ASCII. В Rails: User.where('name LIKE ?', "#{params[:q]}%") User.where('name ILIKE ?', "%#{params[:q]}%") # PostgreSQL only User.where('name LIKE ?', "%#{params[:q]}%".downcase) # универсально
Средне

Базы данных / Индексы

Индексы: зачем и как

Что такое индексы? Для чего используются? Плюсы, минусы?
Индекс — объект БД, создаваемый для повышения производительности поиска данных. Формируется из значений столбцов и указателей на соответствующие строки таблицы. Без индекса — последовательный просмотр всей таблицы (full scan). С индексом — поиск по оптимизированной структуре (B-дерево). Плюсы: — Ускорение SELECT, WHERE, JOIN, ORDER BY — Быстрый поиск по ключу — Уникальные индексы гарантируют целостность Минусы: — Замедляют INSERT, UPDATE, DELETE (нужно обновлять индекс) — Занимают дополнительную память — Лишние индексы — пустая трата ресурсов Когда создавать индекс: — Столбцы в WHERE, JOIN, ORDER BY — Часто запрашиваемые поля — "Золотое правило": индекс под каждый частый запрос В Rails: add_index :users, :email, unique: true add_index :posts, [:user_id, :created_at] # составной add_index :articles, :body, using: :gin # полнотекстовый
Сложно

Базы данных / Индексы

Виды индексов

Какие виды индексов бывают?
По структуре: — B-дерево (B-tree) — по умолчанию в PostgreSQL — Хэш-индекс — точное совпадение (оператор =) — GIN — полнотекстовый поиск, массивы, JSONB — GiST — геоданные, диапазоны, полнотекстовый — BRIN — для огромных таблиц с естественной сортировкой По количеству столбцов: — Простой (один столбец) — Составной (несколько столбцов) Порядок столбцов важен! (a, b) ≠ (b, a) По характеристике: — Уникальный (UNIQUE) — гарантирует уникальность значений — Частичный (WHERE condition) — индексирует подмножество строк — Покрывающий — содержит все столбцы запроса — Полнотекстовый (инвертированный) — для поиска по тексту — Функциональный — индекс по выражению: LOWER(email) По связи с таблицей: — Кластерный — физически упорядочивает данные — Некластерный — отдельная структура с указателями В PostgreSQL: CREATE INDEX idx_email ON users (email); -- B-tree CREATE INDEX idx_lower ON users (LOWER(email)); -- функциональный CREATE INDEX idx_active ON users WHERE active; -- частичный CREATE INDEX idx_tags ON articles USING gin(tags); -- GIN
Средне

Базы данных / Индексы

Полнотекстовый поиск

Что такое полнотекстовый поиск? Как работает?
Полнотекстовый поиск — поиск по содержимому текста с учётом морфологии (окончания, склонения), ранжированием по релевантности. В отличие от LIKE '%word%': — LIKE ищет точную подстроку, медленный на больших текстах — Полнотекстовый поиск знает, что «бег» = «бегать» = «бежал» Как работает: 1. Текст разбивается на токены (лексемы) 2. Токены нормализуются (стемминг — приведение к основе) 3. Строится инвертированный индекс: слово → список документов 4. При поиске запрос тоже нормализуется и ищется в индексе В PostgreSQL: -- Создать колонку tsvector для индексации ALTER TABLE articles ADD COLUMN search_vector tsvector; -- Обновить вектор UPDATE articles SET search_vector = to_tsvector('russian', title || ' ' || body); -- GIN-индекс для быстрого поиска CREATE INDEX idx_articles_search ON articles USING gin(search_vector); -- Поиск SELECT * FROM articles WHERE search_vector @@ to_tsquery('russian', 'рубей & релс'); # Найдёт: Ruby, Ruby on Rails, руби, рельсы В Rails: — gem pg_search — обёртка над PostgreSQL full-text search — gem searchkick — через Elasticsearch/Meilisearch — gem ransack — простой поиск (не полнотекстовый)
Легко

Базы данных / Транзакции

Транзакции в БД

Что такое транзакции?
Транзакция — группа последовательных операций с БД, представляющая логическую единицу работы с данными. Транзакция выполняется либо целиком и успешно (Commit), либо не выполняется вообще (Rollback), и тогда не производит никакого эффекта. Свойства ACID: — Atomicity (Атомарность) — все или ничего — Consistency (Согласованность) — БД переходит из одного корректного состояния в другое — Isolation (Изолированность) — транзакции не влияют друг на друга — Durability (Долговечность) — после Commit данные сохраняются В SQL: BEGIN; # начать транзакцию UPDATE accounts SET balance = balance - 100 WHERE id = 1; UPDATE accounts SET balance = balance + 100 WHERE id = 2; COMMIT; # зафиксировать -- или ROLLBACK; # откатить В Rails: ActiveRecord::Base.transaction do account1.debit(100) account2.credit(100) end # Commit автоматически, или ROLLBACK при exception
Легко

Ruby / ООП

Геттеры и сеттеры

Как создать геттер и сеттер методы в Ruby?
С помощью методов: - attr_reader — создаёт геттер (метод чтения) - attr_writer — создаёт сеттер (метод записи) - attr_accessor — объединяет attr_reader и attr_writer Без attr_* (вручную): class Tovar def price=(price) @price = price end def price @price end end С attr_accessor: class Tovar attr_accessor :price end
Сложно

Базы данных / Транзакции

Уровни изолированности транзакций

Расскажите об уровнях изолированности транзакций.
Уровень изолированности — степень защиты от несогласованности данных при параллельном выполнении транзакций. Проблемы параллельного доступа: — Lost update (потерянное обновление) — две транзакции меняют одни данные, одна перезаписывает другую — Dirty read (грязное чтение) — чтение незафиксированных данных — Non-repeatable read (неповторяющееся чтение) — при повторном чтении данные изменились — Phantom read (фантомное чтение) — при повторном чтении появились/исчезли строки Уровни (от слабого к сильному): 1. Read uncommitted — видны незафиксированные изменения Грязное чтение возможно 2. Read committed — видны только зафиксированные данные PostgreSQL по умолчанию. Нет dirty read 3. Repeatable read — повторное чтение вернёт те же данные Нет dirty + non-repeatable read 4. Serializable — транзакции выполняются как будто последовательно Максимальная изоляция, минимальная производительность Чем выше уровень — лучше согласованность, но меньше параллелизм. В PostgreSQL по умолчанию: Read committed. В Rails: ActiveRecord::Base.transaction(isolation: :serializable)
Средне

Базы данных / Транзакции

Журнал транзакций (WAL)

Что такое журнал транзакций SQL?
Журнал транзакций (WAL — Write-Ahead Log) — файл, содержащий записи всех транзакций, произошедших в БД. WAL — последовательный по своей природе, делится на виртуальные файлы журнала (VLF). Для чего нужен: — Восстановление незавершённых транзакций после сбоя — Rollback транзакций — Высокая доступность (replication) — Point-in-time recovery (восстановление на момент времени) — Резервное копирование: полное + инкрементальное Принцип WAL: — Изменения сначала пишутся в журнал, потом в файлы данных — Если сбой произошёл между ними — при перезапуске БД накатит (replay) журнал и восстановит состояние В PostgreSQL: — pg_wal/ директория хранит WAL-файлы — Настройки: wal_level, max_wal_size, wal_keep_size
Средне

Базы данных / Масштабирование

Репликация данных

Что такое репликация, для чего нужна?
Репликация — техника масштабирования БД. Данные с одного сервера постоянно копируются на один или несколько других (реплики). Позволяет распределить нагрузку: чтение с реплик, запись на master. Два основных подхода: Master-Slave (Primary-Replica): — Один master — принимает записи — Несколько slave — принимают чтение — Slave асинхронно копирует данные с master — Простой: slave может немного отставать (replication lag) Master-Master (Active-Active): — Оба сервера принимают и чтение, и запись — Сложнее в настройке, возможны конфликты — Редко используется В PostgreSQL: — Streaming replication (встроенная) — Logical replication (по таблицам) — Настройка: primary_conninfo в recovery.conf В Rails: — config.database.yml с replica: replica: database: myapp host: replica-server — ActiveRecord.reading_role = :reading
Средне

Базы данных / Масштабирование

Шардинг и партиционирование

Что такое шардинг (партиционирование)?
Шардинг — разделение БД на части, каждая на отдельном сервере. В отличие от репликации (копирование данных), шардинг — разделение данных. Вертикальный шардинг — выделение таблиц на отдельные серверы: Сервер 1: users, profiles Сервер 2: posts, comments Просто, но не решает проблему огромных таблиц Горизонтальный шардинг — разделение одной таблицы на серверы: users_shard_1: id 1..1_000_000 (Сервер 1) users_shard_2: id 1_000_001..2_000_000 (Сервер 2) Для огромных таблиц, не умещающихся на одном сервере Партиционирование в PostgreSQL (без шардинга): CREATE TABLE logs ( id serial, created_at timestamp, data text ) PARTITION BY RANGE (created_at); CREATE TABLE logs_2024_q1 PARTITION OF logs FOR VALUES FROM ('2024-01-01') TO ('2024-04-01'); Когда шардинг нужен: — Таблицы > 100GB — Нагрузка, с которой один сервер не справляется — Географическое распределение данных
Сложно

Базы данных / Масштабирование

Блокировочные и версионные СУБД

Что такое блокировочные и версионные СУБД?
Два подхода к управлению параллельным доступом: Блокировочные (Lock-based) СУБД: — При изменении данных строка БЛОКИРУЕТСЯ (lock) — Другие транзакции ждут снятия блокировки — Гарантирует строгую консистентность — Возможны deadlocks (взаимоблокировки) — Пример: MySQL с InnoDB (по умолчанию) Транзакция 1: UPDATE users SET name = 'A' WHERE id = 1 -- lock Транзакция 2: UPDATE users SET name = 'B' WHERE id = 1 -- ждёт... Версионные (MVCC — Multi-Version Concurrency Control) СУБД: — При UPDATE создаётся НОВАЯ версия строки — Старая версия доступна для чтения другим транзакциям — Читатели не блокируют писателей, писатели не блокируют читателей — Нужен VACUUM для очистки старых версий — Пример: PostgreSQL, Oracle, MySQL (InnoDB тоже использует MVCC) Транзакция 1: UPDATE → создаёт версию 2 (видна после COMMIT) Транзакция 2: SELECT → видит версию 1 (старую) Преимущества MVCC: — Высокая пропускная способность при OLTP — Чтение не блокирует запись — Меньше deadlocks Недостатки MVCC: — Bloat (раздувание) от старых версий строк — Нужен VACUUM для очистки — Больше потребление памяти
Средне

Базы данных / PostgreSQL

pgBouncer: пул соединений

pgBouncer — что это и зачем нужно?
pgBouncer — пул соединений (connection pooler) для PostgreSQL. Проблема: каждое соединение в PostgreSQL — отдельный процесс. 1000 подключений = 1000 процессов = много памяти. Создание нового соединения — дорого. pgBouncer решает это: — Приложение подключается к pgBouncer (как к PostgreSQL) — pgBouncer переиспользует существующие соединения — Меньше процессов на сервере PostgreSQL Режимы пулинга: Session pooling — соединение привязано к сессии клиента. Пока клиент не отключится, соединение не переиспользуется. Transaction pooling — соединение привязано к транзакции. После COMMIT/ROLLBACK соединение возвращается в пул. Наиболее популярный режим. Statement pooling — соединение на каждый оператор. Максимальная эффективность, но не поддерживает транзакции. В Rails + pgBouncer: database.yml → host: pgbouncer-host PgBouncer → PostgreSQL (мало соединений) Rails (много процессов) → PgBouncer (переиспользует)
Средне

Базы данных / PostgreSQL

PgQ: очередь на базе PostgreSQL

Что такое PgQ и как работает?
PgQ — система очередей на базе PostgreSQL (из skytools). Если написать очередь на БД вручную — она будет медленной с большой нагрузкой. PgQ избегает этого за счёт использования внутренней механики PostgreSQL. Особенности: — Транзакционная: каждое событие доставляется хотя бы один раз — События достаются пачками (batch) — Нужно быть внимательным, чтобы не обработать одно событие дважды (например, при аварии обработчика) Альтернативы: — Sidekiq (Redis) — стандарт для Rails — GoodJob, QueueClassic — очереди на PostgreSQL — RabbitMQ, Kafka — внешние брокеры сообщений В Rails обычно используют Sidekiq (Redis-based). PgQ/GoodJob — когда не хочется добавлять Redis в стек.
Сложно

Базы данных / PostgreSQL

Индексы PostgreSQL

Как устроены и функционируют индексы в PostgreSQL?
Основные типы индексов PostgreSQL: B-tree (по умолчанию): — Сбалансированное дерево, ускоряет =, <, >, <=, >=, BETWEEN, LIKE('prefix%') — Подходит для большинства случаев Hash: — Только точное совпадение (=) — Редко используется, B-tree обычно лучше GIN (Generalized Inverted Index): — Полнотекстовый поиск (tsvector) — Массивы, JSONB, hstore — Медленнее обновляется, быстрее ищет GiST (Generalized Search Tree): — Геометрические данные, диапазоны, полнотекстовый — Гибкий, но зависит от реализации оператора BRIN (Block Range Index): — Для огромных таблиц с естественным порядком (time-series) — Хранит только мин/макс для блоков страниц — Очень компактный, но менее точный Частичный индекс: CREATE INDEX idx_active_users ON users (email) WHERE active = true; Покрывающий индекс (Index-only scan): CREATE INDEX idx_covering ON posts (user_id, title, created_at); — Запрос получит все данные из индекса, без обращения к таблице
Средне

Базы данных / PostgreSQL

Транзакции в PostgreSQL

Как воплощён и работает механизм транзакций в PostgreSQL?
Транзакция в PostgreSQL: BEGIN; # начать UPDATE accounts SET balance = balance - 100 WHERE id = 1; UPDATE accounts SET balance = balance + 100 WHERE id = 2; COMMIT; # зафиксировать -- или ROLLBACK; # откатить все изменения Если ни COMMIT ни ROLLBACK не вызваны — транзакция висит открытой. Посмотреть висячие транзакции: SELECT * FROM pg_stat_activity WHERE state = 'idle in transaction'; Висячие транзакции опасны: — Блокируют другие транзакции (locks) — Раздувают WAL — Могут остановить всю БД MVCC (Multi-Version Concurrency Control): — PostgreSQL не перезаписывает строки при UPDATE — Создаёт новую версию строки (tuple) — Старая версия видна другим транзакциям — VACUUM очищает устаревшие версии В Rails: ActiveRecord::Base.transaction do account1.update!(balance: new_balance1) account2.update!(balance: new_balance2) end # exception → ROLLBACK, иначе COMMIT
Средне

Базы данных / PostgreSQL

Системы репликации PostgreSQL

Какие системы репликации есть в PostgreSQL? Зачем нужны?
Репликация — копирование данных между серверами PostgreSQL. Зачем: отказоустойчивость, масштабирование чтения, бэкап. Физическая (streaming) репликация: — Копирует изменения на уровне блоков (WAL) — Реплика — точная копия master (все БД) — Асинхронная или синхронная — Реплика только для чтения — Настройка: primary_conninfo в postgresql.conf Логическая репликация: — Копирует изменения на уровне строк (INSERT/UPDATE/DELETE) — Можно реплицировать отдельные таблицы — Реплика может быть на другой версии PostgreSQL — Гибче, но медленнее физической — Настройка: CREATE PUBLICATION / CREATE SUBSCRIPTION Синхронная репликация: — Master ждёт подтверждения от replica перед COMMIT — Данные не потеряются при падении master — Медленнее (зависит от сети до replica) — synchronous_commit = on, synchronous_standby_names Асинхронная репликация (по умолчанию): — Master не ждёт подтверждения — Быстрее, но возможна потеря данных при падении master — replication lag может достигать секунд/минут Каскадная репликация: — Replica может быть источником для других replica — Снижает нагрузку на master
Средне

Базы данных / PostgreSQL

Синхронные и асинхронные операции

Что такое синхронные и асинхронные операции в PostgreSQL?
Синхронные операции: — Клиент отправляет запрос и ждёт результат — Блокируется пока сервер не ответит — Простая модель программирования — Медленнее при множестве параллельных запросов Пример: обычный SELECT/INSERT — клиент ждёт завершения. Асинхронные операции: — Клиент отправляет запрос и продолжает работу — Результат обрабатывается когда готов (callback/poll) — Не блокирует другие операции — Сложнее в реализации, но эффективнее В контексте репликации: — Синхронная: master ждёт подтверждения записи на replica Гарантия данных, но выше задержка — Асинхронная: master не ждёт replica Быстрее, но возможна потеря данных при сбое В контексте PostgreSQL драйверов: — Синхронный:PG::Connection.exec — ждёт результат — Асинхронный: PG::Connection.send_query + get_result Можно отправить несколько запросов и получать результаты по мере готовности В Rails: — По умолчанию синхронные запросы к БД — Async: ActiveRecord::Base.async do User.count end (Rails 7+)
Средне

Базы данных / Диагностика

Запрос стал медленнее без изменений

Приложение, базу, сервер не трогали — но спустя какое-то время запрос стал работать медленнее. Почему?
Возможные причины: 1. Устаревшая статистика: PostgreSQL собирает статистику для планировщика запросов. Если статистика устарела — планировщик выберет плохой план. Решение: ANALYZE; или VACUUM ANALYZE; 2. Раздувание таблицы (bloat): При UPDATE/DELETE PostgreSQL создаёт новые версии строк. Мёртвые строки накапливаются — таблица разрастается. Решение: VACUUM (авто) или VACUUM FULL (полная перестройка) 3. Изменение данных: Данные выросли — индекс перестал помещаться в RAM. Решение: EXPLAIN ANALYZE — проверить план запроса 4. Планировщик сменил план: При росте данных planner может решить использовать sequential scan вместо index scan (и ошибиться). Решение: ANALYZE; или увеличить статистику: ALTER TABLE users ALTER COLUMN status SET STATISTICS 500; Диагностика: EXPLAIN ANALYZE SELECT ...; # план + реальное время SELECT pg_size_pretty(pg_total_relation_size('users')); # размер SELECT * FROM pg_stat_user_tables WHERE relname = 'users';
Средне

Базы данных / Диагностика

Типичные узкие места в БД

Типичные bottle necks (узкие места) в базах данных?
Типичные узкие места (bottlenecks) в БД: 1. Отсутствующие индексы: — Full table scan вместо index scan — Решение: EXPLAIN ANALYZE + CREATE INDEX 2. N+1 запросы (в Rails): — 100 пользователей → 100 запросов за их постами — Решение: includes(), eager_load(), joins() 3. Медленные JOIN-ы: — Объединение больших таблиц без индексов — Решение: индексы на FK, покрывающие индексы 4. Блокировки (locks): — Долгие транзакции блокируют другие запросы — Решение: короткие транзакции, избегать idle in transaction 5. Раздутые таблицы (bloat): — Мёртвые строки от UPDATE/DELETE — Решение: autovacuum, VACUUM FULL 6. Неоптимальные типы данных: — TEXT вместо VARCHAR, UUID вместо INTEGER для PK — Решение: выбирать типы под задачу 7. Слишком много соединений: — Каждый connection = отдельный процесс (в PostgreSQL) — Решение: pgBouncer, connection pool 8. Отсутствие партиционирования: — Огромные таблицы логов/аудита — Решение: PARTITION BY RANGE/LIST
Легко

Базы данных / Диагностика

SQL Injection и XSS

Объяснить разницу между SQL Injection и XSS (CSS Injection)?
SQL Injection — атака на БАЗУ ДАННЫХ: — Вредоносный SQL-код вставляется в запрос к БД — Выполняется на сервере БД Пример: # Опасно! query = "SELECT * FROM users WHERE name = '#{params[:name]}'" # Если params[:name] = "'; DROP TABLE users; --" # Выполнится: SELECT * FROM users WHERE name = ''; DROP TABLE users; --' Защита (в Rails — автоматически): User.where(name: params[:name]) # параметризованный запрос User.where("name = ?", params[:name]) # плейсхолдер Никогда НЕ делать: User.where("name = '#{params[:name]}'") XSS (Cross-Site Scripting) — атака на БРАУЗЕР: — Вредоносный JavaScript вставляется в HTML-страницу — Выполняется в браузере пользователя Пример: <!-- Если вывести без экранирования: --> <div><%= raw params[:comment] %></div> <!-- params[:comment] = "<script>stealCookies()</script>" --> Защита (в Rails — по умолчанию): <%= params[:comment] %> # экранируется автоматически <%= sanitize params[:comment] %> # разрешить безопасные теги Не использовать raw() для пользовательского ввода Разница: — SQL Injection → атака на сервер/БД — XSS → атака на браузер/клиента
Легко

Ruby / ООП

attr_reader, attr_writer, attr_accessor

Что такое attr_reader, attr_writer, attr_accessor?
Все классы наследуют методы Module. attr_reader, attr_writer, attr_accessor являются его методами. attr_reader создаёт переменную экземпляра и метод-геттер: attr_reader :name # Эквивалентно: def name @name end attr_writer создаёт метод-сеттер: attr_writer :name # Эквивалентно: def name=(name) @name = name end attr_accessor объединяет функционал attr_reader и attr_writer.
Легко

DevOps и CI/CD / Системные проблемы

Address already in use

При запуске rails server появляется ошибка: Address already in use - bind(2) for 127.0.0.1:3000. Что делать?
Причина: порт 3000 занят другим процессом (старый сервер, другая программа). Найти процесс: lsof -i :3000 # показать PID и имя процесса # Или: fuser 3000/tcp # показать PID Убить процесс: kill -9 <PID> # убить конкретный процесс # Или одной командой: fuser -k 3000/tcp # убить всё, что занимает порт 3000 Альтернатива — запустить на другом порту: rails server -p 3001 # использовать порт 3001 Почему так происходит: - Предыдущий сервер не завершился корректно (Ctrl+C не нажали) - Spring запустил фоновый процесс - Другой проект тоже запущен на 3000 Профилактика: spring stop # остановить все процессы Spring pkill -f "rails server" # убить все rails server процессы
Средне

DevOps и CI/CD / Системные проблемы

rails new зависает

Команда rails new myapp зависает и ничего не происходит. В чём причина и как это исправить?
Возможные причины: 1. Spring завис — самая частая причина: spring stop DISABLE_SPRING=1 rails new myapp 2. Нет интернета — bundle install внутри rails new не может скачать гемы: Проверить: ping -c 3 rubygems.org Запустить без bundle: rails new myapp --skip-bundle Потом: cd myapp && bundle install 3. За прокси — нужна настройка: export HTTP_PROXY=http://proxy:port export HTTPS_PROXY=http://proxy:port 4. Старый кеш RubyGems: gem sources -c # очистить кеш gem sources -u # обновить индекс Алгоритм: 1. spring stop 2. Проверить интернет 3. Запустить с DISABLE_SPRING=1 4. Если всё равно висит — добавить --skip-bundle и установить гемы вручную
Средне

DevOps и CI/CD / Системные проблемы

Системная диагностика: базовые команды

Назовите основные команды для диагностики проблем в терминале Linux/macOS, которые полезны Ruby/Rails-разработчику.
Процессы и порты: ps aux # все запущенные процессы ps aux | grep ruby # найти процессы Ruby lsof -i :3000 # кто занимает порт 3000 kill -9 <PID> # убить процесс top / htop # мониторинг ресурсов Файлы и директории: which ruby # где находится Ruby which -a ruby # все Ruby в системе ls -la # подробный список файлов du -sh * # размер директорий df -h # свободное место на диске Сеть: curl -I http://localhost:3000 # проверить HTTP-ответ ping rubygems.org # доступность сервера ss -tlnp # открытые порты Логи: tail -f log/development.log # логи Rails в реальном времени tail -f /var/log/postgresql/... # логи PostgreSQL grep "ERROR" log/development.log # искать ошибки Окружение: env # все переменные окружения echo $PATH # показать PATH echo $RAILS_ENV # текущее окружение Rails
Сложно

DevOps и CI/CD / Системные проблемы

Docker: контейнер не запускается

Вы запускаете docker compose up, но контейнер с приложением падает (exited with code 1). Как найти причину?
Алгоритм диагностики: 1. Посмотреть логи упавшего контейнера: docker compose logs app docker compose logs --tail 50 app # последние 50 строк 2. Если контейнер уже остановлен: docker compose ps -a # статус контейнеров docker logs <container_id> # логи конкретного контейнера 3. Частые причины: - bundle install не прошёл (забыли пересобрать образ): docker compose build --no-cache - База данных ещё не готова, а приложение уже стартует: depends_on не дожидается готовности, использовать healthcheck - Неправильные переменные окружения в .env или docker-compose.yml - Порт занят на хосте: ports: "3000:3000" — проверить lsof -i :3000 - Файлы не скопированы: проверить Dockerfile (COPY . .) 4. Зайти внутрь контейнера для отладки: docker compose exec app bash docker compose exec app sh # если нет bash (Alpine) 5. Перезапуск с нуля: docker compose down -v # удалить контейнеры и volumes docker compose up --build # пересобрать и запустить
Легко

Ruby / Основы

Hello World

Выведи строку 'Hello, World!' на экран.
Легко

Ruby / Основы

Сумма двух чисел

Напиши метод sum(a, b), который возвращает сумму двух чисел.
Легко

Ruby / Основы

Реверс строки

Напиши метод reverse_string(str), который возвращает строку задом наперёд.
Легко

Ruby / Строки

Длина строки

Напиши метод string_length(str), который возвращает длину строки.
Легко

Ruby / Переменные

Массивы

Напиши метод array_sum(arr), который возвращает сумму элементов массива.
Легко

Ruby / ООП

Простой класс

Создай класс Dog с методом bark, который возвращает строку 'Woof!'.
Средне

Ruby / Основы

Проблемы чисел с плавающей точкой

Почему в Ruby 24.0 * 0.1 не равно 2.4? Как правильно работать с дробными числами?
Компьютеры используют binary floating point формат, который не может точно представить числа вроде 0.1, 0.2 или 0.3. Когда код компилируется, 0.1 уже округляется до ближайшего числа в этом формате, что приводит к ошибке округления ДО вычисления. 24.0 * 0.1 #=> 2.4000000000000004 (не ровно 2.4!) 0.1 + 0.2 #=> 0.30000000000000004 (не ровно 0.3!) Решения: 1. Для денег — используйте Integer (копейки/центы): 2499 вместо 24.99 2. BigDecimal для точных вычислений: require 'bigdecimal' BigDecimal("24.0") * BigDecimal("0.1") #=> 0.24e1 3. Сравнение с допуском (epsilon): (a - b).abs < 0.0001
Средне

Ruby / Основы

Struct и OpenStruct

Что такое Struct и OpenStruct в Ruby? Когда их использовать?
Struct — упрощённый способ создания классов с предопределёнными атрибутами. Создаёт класс с геттерами и сеттерами. Point = Struct.new(:x, :y) p = Point.new(1, 2) p.x #=> 1 p.y = 5 #=> 5 p[:x] #=> 1 (доступ по индексу) OpenStruct — позволяет создавать объекты с любыми атрибутами динамически. Требует require 'ostruct'. require 'ostruct' person = OpenStruct.new person.name = "Alice" person.age = 30 person.name #=> "Alice" Разница: - Struct — фиксированный набор атрибутов, быстрый - OpenStruct — любые атрибуты, медленный (использует method_missing) Когда использовать: - Struct — для простых DTO, вместо хеша с известными ключами - OpenStruct — редко, в основном для парсинга/тестов - В продакшене лучше Plain Ruby классы или dry-struct
Средне

Ruby / Основы

Способы вызова методов

Какие способы вызова методов есть в Ruby? В чём разница между .send и .call?
1. Обычный вызов: obj.method_name(arg) 2. .send — вызывает любой метод, включая приватные: obj.send(:method_name, arg) obj.__send__(:method_name, arg) # безопаснее (нельзя переопределить) 3. .call — вызывает Proc/Lambda/Method объект: my_proc = Proc.new { |x| x * 2 } my_proc.call(5) #=> 10 my_proc.(5) #=> 10 (синтаксический сахар) my_proc[5] #=> 10 (ещё один вариант) 4. method(:name).call — получает Method объект и вызывает: method(:puts).call("hello") 5. .eval — выполняет строку как Ruby код: eval("2 + 2") #=> 4 # Очень медленный, опасный (code injection), НЕ используется .send используют для метапрограммирования и динамического вызова. .call — для Proc/Lambda.
Легко

Ruby / Основы

Safe navigation operator (&.)

Что такое safe navigation operator (&.) в Ruby? Чем отличается от try в Rails?
&. (safe navigation) — вызывает метод только если объект не nil. Если объект nil, возвращает nil вместо NoMethodError. user&.name # Эквивалентно: user.nil? ? nil : user.name Цепочка: user&.profile&.avatar_url Важные отличия от Rails try: - &. проверяет на nil, но НЕ на false: false&.to_s #=> "" (вызов произойдёт!) - &. не вычисляет аргументы если объект nil: obj&.foo(expensive_call()) # expensive_call НЕ вызовется если obj nil - try в ActiveSupport всегда вычисляет аргументы &. быстрее try — реализован на уровне парсера, а не Ruby кода.
Средне

Ruby / ООП

self в Ruby

Что означает ключевое слово self в Ruby? Какие значения оно принимает?
self — ссылка на текущий объект (контекст выполнения). Внутри метода экземпляра — self это сам объект: class User def greet "Hello from #{self.name}" end end Внутри определения класса — self это класс: class User puts self #=> User def self.all # self здесь = User (метод класса) end end class << self открывает синглтон-класс — все методы становятся методами класса: class User class << self def find(id) # метод класса User.find end end end self используется для: - Определения методов класса (def self.method) - Вызова других методов того же объекта (self.other_method) - Явного указания атрибута (self.name = "Alice" а не name = "Alice")
Легко

Ruby / ООП

super: вызов родительского метода

Как работает ключевое слово super в Ruby? Что будет если вызвать super без аргументов?
super вызывает метод с тем же именем из родительского класса. class Animal def speak "sound" end end class Dog < Animal def speak super + " woof!" end end Dog.new.speak #=> "sound woof!" Варианты вызова: - super — передаёт ВСЕ аргументы текущего метода в родительский - super() — вызывает БЕЗ аргументов (пустые скобки важны!) - super(arg1, arg2) — передаёт конкретные аргументы Частая ошибка: def initialize(name:) @name = name super # передаёт name: в родительский initialize end Если родительский initialize не принимает аргументов: def initialize(name:) @name = name super() # скобки обязательны, иначе ArgumentError end
Средне

Ruby on Rails / Legacy-проекты

Чтение чужого кода: как разобраться

Вы пришли на новый проект. Бизнес-логика в views, SQL в контроллерах, контроллеры по 500 строк. Нет документации. Как разобраться?
Навык чтения чужого кода — главный навык на legacy. Вы будете читать код в 10 раз больше, чем писать. Алгоритм разбора legacy: 1. Найти точку входа: Роут → Контроллер → Модель → Service config/routes.rb — карта приложения 2. Следовать за запросом: Открыть браузер → DevTools → Network → кликнуть → увидеть URL → найти в routes → найти controller action 3. Нарисовать карту: Пометить: "этот метод вызывает тот, который зовёт API, а результат используется в этом колбэке" Зарисовать на бумаге / diagrams.net 4. Искать "запахи" (code smells): — Метод > 30 строк — скорее всего делает слишком много — Метод с 5 параметрами — слишком много ответственностей — Комментарий "TODO: refactor" — кто-то уже запутался — if/else на 5 уровней — упростить можно — send(dynamic_method) — вызов по строке, хардкор 5. Записать вопросы: "Зачем этот callback?" "Почему два поля делают одно и то же?" "Почему validate в before_save, а не в validation?" Спросить у команды — не угадывать. Инструменты: — grep / ripgrep — найти все использования метода — git blame — кто и когда написал эту строку (спросить автора) — git log --follow file.rb — история изменений файла — rails routes — список всех endpoints — rubocop -l (lint) — найти проблемные места автоматически — traceroute gem — найти неиспользуемые роуты/экшены Не делать: — Не рефакторить "пока разбираетесь" — сломаете — Не судить автора — он работал в других условиях — Не переписывать с нуля — это ловушка (second-system effect) Делать: — Документировать по ходу — комментарии, README — Писать тесты на то, что поняли — Задавать вопросы — это не слабость Как ИИ помогает: ИИ объяснит что делает конкретный метод за 5 секунд. "Объясни этот код" — и получите детальный разбор. Но ИИ НЕ знает контекст: "а почему ОНО так работает?" Потому что 2 года назад клиент потребовал фичу за 1 день. Исторический контекст — только от команды.
Сложно

Ruby / ООП

Синглтон-методы и синглтон-классы

Что такое синглтон-методы и синглтон-классы в Ruby?
Синглтон-метод — метод, принадлежащий только одному объекту. cat = Animal.new dog = Animal.new def dog.bark "WOOF!" end dog.bark #=> "WOOF!" cat.bark #=> NoMethodError dog.singleton_methods #=> [:bark] Методы класса — это тоже синглтон-методы: class User def self.all # self.all — синглтон-метод объекта User end end Синглтон-класс — анонимный класс, хранящий синглтон-методы. Встраивается в цепочку поиска метода: dog.singleton_class #=> #<Class:#<Animal:0x...>> dog.singleton_class.superclass #=> Animal Поиск метода: singleton_class -> Animal -> Object -> BasicObject class << dog def bark "WOOF!" end end # То же самое, что def dog.bark
Средне

Ruby / ООП

extend self в модуле

Что произойдёт если в модуле сделать extend self? Зачем это нужно?
extend self делает инстанс-методы модуля доступными как методы модуля (можно вызывать без создания экземпляра и без include). Без extend self: module MyModule def greet "Hello!" end end MyModule.greet #=> NoMethodError MyModule.greet # не работает напрямую! С extend self: module MyModule extend self def greet "Hello!" end end MyModule.greet #=> "Hello!" # работает! Эквивалентно: module MyModule def greet "Hello!" end module_function :greet end Зачем: создание утилитных модулей, которые используются и как mixin (include) и как namespace (Module.method).
Легко

Ruby / Окружение и диагностика

Серверы приложений Ruby

Какие серверы приложений существуют для Ruby/Rails? В чём разница?
Основные серверы: Puma — текущий стандарт в Rails (по умолчанию). - Многопоточный + мультипроцессный (clustered mode) - Быстрый, хорошо справляется с медленными запросами - Поддерживает concurrent I/O Unicorn — predecessor Puma. - Форкает процессы (workers), каждый обрабатывает по одному запросу - Простой и надёжный, но не многопоточный - Не подходит для медленных I/O (long polling) Passenger (Phusion Passenger) — коммерческий + бесплатный. - Встраивается в Nginx/Apache - Автоматическое управление процессами - Удобен для простого деплоя WeBrick — встроенный сервер Ruby. - Только для разработки, не для продакшена - Однопоточный, медленный Thin — EventMachine-based, редко используется сейчас. Рекомендация для продакшена: Puma (стандарт Rails).
Средне

Ruby / Окружение и диагностика

Benchmark: измерение скорости кода

Как измерить скорость работы Ruby кода? Чем benchmark отличается от benchmark-ips?
Стандартный Benchmark (встроен в Ruby): require 'benchmark' Benchmark.bm do |x| x.report("each:") { (1..1_000_000).each { |i| i } } x.report("map:") { (1..1_000_000).map { |i| i } } end benchmark-ips (gem) — замеряет итерации в секунду: require 'benchmark/ips' Benchmark.ips do |x| x.report("each") { (1..1000).each {} } x.report("map") { (1..1000).map {} } x.compare! # показывает разницу в процентах end Разница: - Benchmark — измеряет время выполнения (секунды) - benchmark-ips — измеряет итерации/сек (более точный), автоматически подбирает количество итераций, показывает сравнение в % между методами Установка: gem install benchmark-ips
Средне

Ruby / Внутреннее устройство

Ruby интерпретаторы

Какие существуют реализации Ruby интерпретатора? Чем они отличаются?
MRI (Matz's Ruby Interpreter) / CRuby — основная реализация. - Написан на C - Самый распространённый, стандарт de facto - Использует GIL (Global Interpreter Lock) JRuby — Ruby на платформе JVM. - Настоящая многопоточность (без GIL) - Доступ к Java библиотекам - Быстрее для некоторых задач, медленнее для других - Совместим с большинством Ruby-кода TruffleRuby — Ruby на GraalVM. - Самый быстрый для многих бенчмарков - JIT-компиляция через Graal - Экспериментальный, растущая совместимость Rubinius — Ruby написанный на Ruby (большая часть). - Исторически важен, сейчас не развивается активно В продакшене 99% случаев — MRI/CRuby (тот самый ruby который устанавливается через rbenv/rvm).
Средне

Ruby / Внутреннее устройство

JIT-компиляция в Ruby

Что такое JIT-компиляция? Как JIT работает в Ruby?
JIT (Just-In-Time) компиляция — оптимизация часто вызываемых методов путём компиляции их в машинный код на лету (во время работы программы). Цель JIT — пропустить шаги интерпретации для горячих методов. В Ruby (начиная с 2.6) есть MJIT: - Компилирует часто вызываемые методы в C код - Затем компилирует C в машинный код через GCC/Clang - Отключён по умолчанию (включается --jit) Ruby 3.1+: YJIT (от Shopify): - Написан на Rust, встроен в CRuby - Быстрее разогревается чем MJIT - Включён по умолчанию в Ruby 3.3+ Плюсы JIT: - Ускорение долгоживущих процессов (серверы) - Бесплатное ускорение без изменения кода Минусы: - Cold start медленнее (нужно время на компиляцию) - Больше потребление памяти - Для коротких скриптов бесполезен
Легко

Ruby / Основы

Типы данных в Ruby

Какие типы данных используются в Ruby? Что такое массив? хэш? строка? число? символ?
Числа (Numeric): 5 # Integer — целое число 4.5 # Float — число с плавающей точкой 2+3i # Complex — комплексное число Rational(2, 3) # Rational — рациональная дробь ⅔ Логический тип (Boolean): true и false Всё есть true, кроме false и nil Массивы (Array): [1, 2, 3] # целочисленный [1, "two", 3.0] # смешанный (гетерогенный) Динамические, неограниченные по размеру push/pop для стека, обширная библиотека методов Строки (String): 'hello' # одинарные кавычки (принято по умолчанию) "hello #{name}" # двойные (поддерживают интерполяцию) '2' + '2' #=> "22" (конкатенация) Не ограничены по длине, динамические Хэши (Hash): { key: "value" } # символы как ключи { "key" => "value" }# строки как ключи Ассоциативные массивы — ключ → значение Диапазоны (Range): 1..10 # включительно (1, 2, ..., 10) 1...10 # исключая конец (1, 2, ..., 9) array[3..] # бесконечный диапазон (Ruby 2.6+) Символы (Symbol): :name, :status Один экземпляр на имя (экономия памяти): :slovo.object_id == :slovo.object_id #=> true "slovo".object_id == "slovo".object_id #=> false Неизменяемые, часто используются как ключи хэшей nil (NilClass): nil — единственное значение "пустоты" false и nil — единственные "falsy" значения в Ruby
Легко

Ruby / Основы

loop, while, map, each

Что такое loop, while, map, each? Чем отличаются?
loop, while — управляющие конструкции, создающие циклы, повторение кода по условию/без условий: loop { puts "infinity"; break } # бесконечный цикл while x < 10 do x += 1 end # цикл по условию each, map — итераторы, перебирают все элементы у объекта (унаследованы от Enumerable). Принимают блоки и выполняют код для элементов коллекций (массивов, диапазонов, хэшей): [1, 2, 3].each { |x| puts x } # перебор [1, 2, 3].map { |x| x * 2 } # преобразование Итераторы предпочтительнее циклов — меньше ошибок, чище код.
Легко

Ruby / Коллекции и Enumerable

each и map: отличия

Чем отличается each от map?
each занимается просто перебором, возвращает исходный массив: [1, 2, 3].each { |x| puts x } # печатает, возвращает [1, 2, 3] map занимается перебором и возвращает НОВЫЙ массив с результатами: [1, 2, 3].map { |x| x * 2 } #=> [2, 4, 6] map! — изменяет исходный массив each используется для побочных эффектов (печать, запись). map — для трансформации данных.
Легко

Ruby / Основы

Другие циклы и итераторы

Какие ещё циклы и итераторы есть в Ruby?
Циклы: until x > 10 do x += 1 end # противоположность while for item in array do ... end # перебор (редко используется) Числовые итераторы: 3.times { puts "hi" } # повтор 3 раза 1.upto(5) { |n| puts n } # от 1 до 5 5.downto(1) { |n| puts n } # от 5 до 1 1.step(10, 2) { |n| puts n } # 1, 3, 5, 7, 9
Средне

Ruby / Коллекции и Enumerable

inject и reduce

Назовите отличия inject и reduce.
inject — алиас reduce. Это один и тот же метод. [1, 2, 3].reduce(:+) #=> 6 [1, 2, 3].inject(:+) #=> 6 (то же самое)
Легко

Ruby / Переменные

Типы переменных и области видимости

Какие переменные бывают, где они используются, где они доступны (области видимости)?
Локальные переменные (variable) — доступны только в текущей области: def method x = 1 # x видна только внутри method end Переменные экземпляра (@variable) — доступны во всех методах объекта: class Person def initialize(name) @name = name end end При первом вызове возвращают nil Переменные класса (@@variable) — общая для всех экземпляров: class Counter @@count = 0 # одна на все экземпляры и наследники end Опасна: легко создать баг при наследовании Глобальные переменные ($variable) — видна отовсюду: $debug = true # доступна в любом месте программы Почти никогда не используется, сложно отследить кто изменил Константы (CONSTANT) — все заглавные: PI = 3.14 # можно изменить, но Ruby выдаст warning
Легко

Ruby / Переменные

Переменные @ и @@

Что такое переменная с одной @ и переменная с двумя @@?
Переменные экземпляра (@variable) — начинаются с @. Доступны в методах экземпляра класса, где они определены. При первом вызове возвращают nil. Переменные класса (@@variable) — начинаются с @@. Их область видимости — класс, в котором они определены, и все экземпляры данного класса.
Легко

Ruby / Внутреннее устройство

require и require_relative

Чем require отличается от require_relative?
require подключает файлы/гемы по относительному пути в строгом соответствии ./1/ruby.rb, начиная с корня приложения. Загружает один раз (повторный вызов вернёт false). require "json" # поиск в $LOAD_PATH require "./lib/my_file" # с указанием пути от корня require_relative подключает файлы без относительного пути и без указания расширения файла, запускает из той же директории, где лежит файл запуска. Тоже загружает один раз. require_relative "1/ruby.rb" load — загружает файл КАЖДЫЙ РАЗ (нужно указывать расширение). В Rails: Zeitwerk автозагружает по конвенции — require почти не нужен.
Средне

Ruby / ООП

Модули и классы: отличия

Что такое модуль в Ruby? Какая разница между классом и модулем?
Модули в Ruby похожи на классы — содержат методы, константы, другие модули и определения классов. Задаются как классы, только слово module вместо class. В отличие от классов: - Создать объекты на основе модуля нельзя (нет new) - Модуль не может иметь подклассы (нет наследования) - Модули — одиночки, нет иерархии Вместо этого модули добавляют функциональность класса или отдельного объекта через include/extend/prepend.
Легко

Ruby / ООП

Наследование в Ruby

Как организовано наследование в Ruby?
Наследование в Ruby — прямое (один родитель): class Animal end class Dog < Animal end В Ruby всё в конечном счёте принадлежит классу BasicObject: str = "Я - строка" str.class #=> String str.class.superclass #=> Object str.class.superclass.superclass #=> BasicObject Множественное наследование невозможно, но модули (include/extend) дают тот же эффект.
Средне

Ruby / ООП

include, extend, prepend

Чем отличается include от extend? Что такое prepend?
include — методы модуля становятся инстанс-методами класса: class Person include Greetable end Person.new.greet #=> "Hi!" Необходимо создать экземпляр класса extend — методы модуля становятся методами класса: class Person extend Greetable end Person.greet #=> "Hi!" Без создания экземпляра класса prepend — модуль вставляется перед классом в цепочке поиска метода: Методы модуля устанавливаются первоочередными Позволяет переопределить метод и вызвать super
Легко

Ruby / ООП

Множественное наследование

Реализация множественного наследования в Ruby?
Множественное наследование в Ruby недоступно (у класса один родитель). Реализация возможна через модули с помощью include/extend: module A def method_a; "a"; end end module B def method_b; "b"; end end class C include A, B end C.new.method_a #=> "a" C.new.method_b #=> "b"
Средне

Ruby / Блоки, Proc, Lambda

proc, lambda, block

Что такое proc, lambda, block? И какие отличия есть между ними?
Это анонимные функции — кусочки Ruby кода. Block — код в фигурных скобках или do..end: [1, 2, 3].each { |x| puts x } Не является объектом, нельзя сохранить в переменную Proc — объект, хранящий блок: p = Proc.new { |x| x * 2 } p.call(5) #=> 10 Нестрогий к аргументам (лишние игнорирует, недостающие = nil) return выходит из метода-обёртки Lambda — строгий Proc: l = -> (x) { x * 2 } l.call(5) #=> 10 Строгий к аргументам (ArgumentError при несовпадении) return выходит только из лямбды Все три — класса Proc: Proc.new { }.class #=> Proc -> { }.class #=> Proc -> { }.lambda? #=> true
Легко

Базы данных / Распределённые БД

Репликация: Leader-Replica

Что такое репликация? Кто такой Leader и Replica? Зачем нужны несколько копий данных?
Репликация — копирование данных на несколько серверов. Зачем: — Надёжность: один сервер упал — данные живы на других — Скорость: можно читать с ближайшего сервера — Нагрузка: распределить запросы между серверами Leader (Master, Primary) — главный сервер. Все записи (INSERT, UPDATE, DELETE) идут через него. Он решает, что записывать и в каком порядке. Replica (Slave, Secondary) — копия лидера. Получает данные от Leader и обновляется. Обычно используется только для чтения (SELECT). Схема: Клиент ──WRITE──→ Leader ──копирует──→ Replica 1 Leader ──копирует──→ Replica 2 Клиент ──READ──→ Replica 1 (быстро, ближе к клиенту) Аналогия из жизни: Leader — редактор газеты, пишет статью Replicas — типографии в разных городах, печатают копии Читатель (клиент) берёт газету в ближайшем киоске
Легко

Базы данных / Распределённые БД

Синхронная vs Асинхронная репликация

Чем отличается синхронная репликация от асинхронной? Какие у них плюсы и минусы?
Синхронная репликация: Leader записывает данные И ЖДЁТ подтверждения от реплик. Только после подтверждения от ВСЕХ — отвечает клиенту "OK". + Данные на всех нодах всегда одинаковы (нет лага) - Медленно: скорость записи = скорость самой медленной реплики - Если одна реплика недоступна — запись блокируется Асинхронная репликация: Leader записывает данные и СРАЗУ отвечает клиенту "OK". Реплики обновляются позже, в фоне. + Быстро: клиент не ждёт реплики - Есть лаг: реплики могут быть устаревшими на несколько миллисекунд/секунд - Если Leader упадёт — последние записи могут потеряться Пример: Синхронная: клиент ──WRITE──→ Leader ──ждёт──→ Replica OK → клиенту "OK" Асинхронная: клиент ──WRITE──→ Leader → клиенту "OK" → потом копирует на Replica В реальности чаще всего используют асинхронную — ради скорости. PostgreSQL по умолчанию — асинхронная репликация.
Легко

Базы данных / Распределённые БД

Eventual Consistency

Что такое Eventual Consistency? Что значит 'в конечном счёте'?
Eventual Consistency (согласованность в конечном счёте) — модель, при которой реплики НЕ обязаны быть одинаковыми прямо сейчас, но ГАРАНТИРУЮТ стать одинаковыми через какое-то время. Ключевое слово: eventually = "в конечном счёте / в конце концов". Что это значит на практике: — В любой момент времени реплики могут отдавать РАЗНЫЕ данные — Но если перестать писать, все реплики догонят и станут одинаковыми — Лаг обычно от миллисекунд до секунд Пример из жизни: Вы загрузили фото в Instagram. Ваш друг открывает профиль — фото ещё не видно (реплика не обновилась). Через 2 секунды обновляет страницу — фото появилось. Это eventual consistency. Противоположность — Strong Consistency: Все реплики всегда одинаковы. Клиент всегда читает актуальные данные. Но это медленнее. Зачем используют Eventual: — Быстрее (запись не ждёт реплики) — Доступнее (система работает даже если часть реплик недоступна) — Подходит для большинства реальных задач
Легко

Базы данных / Распределённые БД

Stale Read

Что такое Stale Read? Почему клиент может читать старые данные?
Stale Read (устаревшее чтение) — когда клиент читает данные, которые уже НЕ являются актуальными, потому что реплика ещё не обновилась. Как это происходит: 1. Клиент A записывает: user.name = "Иван" 2. Leader сохраняет: user.name = "Иван" 3. Клиент A читает user → попадает на Replica 1 4. Replica 1 ещё не обновилась → возвращает user.name = "Ольга" 5. Клиент A только что записал "Иван", а читает "Ольга" — это stale read Почему это проблема: — Пользователь обновил профиль, но видит старую версию — Отменил заказ, но он всё ещё отображается как активный — Перевёл деньги, но баланс не изменился Когда возникает: — При асинхронной репликации (почти всегда) — Когда запрос на чтение попадает на отстающую реплику — При высокой нагрузке, когда репликация не успевает Stale read — это НЕ баг, это норма в eventual consistency системах. Вопрос в том: как с этим бороться, когда это критично.
Легко

Базы данных / Распределённые БД

Read-After-Write Consistency

Что такое Read-After-Write Consistency? Чем отличается от Strong Consistency?
Read-After-Write Consistency (чтение после записи) — гарантия: "Если клиент записал данные, он сразу увидит эту запись при чтении". Тот же самый клиент. Свои собственные данные. Важно: это гарантия только для АВТОРА записи. Другие клиенты могут видеть старые данные — это нормально. Сравнение уровней гарантий (от слабого к сильному): Eventual Consistency: Все в конечном счёте увидят новые данные. Никаких гарантий когда. Read-After-Write: Автор записи видит свои изменения сразу. Другие — когда-нибудь потом. Strong (Linearizable) Consistency: Все клиенты всегда видят самые свежие данные. Максимальная гарантия, но самая медленная. Пример: Вы поменяли аватарку. Eventual: вы можете видеть старую аватарку Read-After-Write: вы сразу видите новую аватарку, другие — позже Strong: все сразу видят новую аватарку Read-After-Write — это компромисс между скоростью и консистентностью. Для большинства API этого достаточно.
Средне

Базы данных / Распределённые БД

CAP-теорема

Что такое CAP-теорема? Почему нельзя иметь всё одновременно?
CAP-теорема говорит: в распределённой системе можно выбрать только ДВА из трёх: C — Consistency (Согласованность) Все ноды видят одни и те же данные в один момент времени. A — Availability (Доступность) Каждый запрос получает ответ (без ошибки), даже если ноды упали. P — Partition Tolerance (Устойчивость к разделению) Система работает даже если сеть между нодами оборвалась. Почему нельзя все три: Сеть ВСЕГДА может сломаться (P обязателен в реальном мире). Значит реальный выбор между C и A: CP — жертвуем доступностью ради консистентности Пока реплики не синхронизируются — система отвечает ошибкой. Пример: HBase, MongoDB (по умолчанию) AP — жертвуем консистентностью ради доступности Система всегда отвечает, но данные могут быть старыми. Пример: Cassandra, DynamoDB (в режиме eventual) Как это связано с нашей задачей: — Eventual consistency — это AP (доступность важнее) — Strong consistency — это CP (консистентность важнее) — Задача "read-after-write" — попытка получить C для автора, оставаясь в AP для остальных
Средне

Ruby on Rails / Legacy-проекты

Data Migration: безопасное изменение данных

Нужно переименовать колонку у 10M записей. Или добавить NOT NULL на таблицу, где есть NULL. Как не положить продакшен?
Data migration — миграция, которая меняет ДАННЫЕ, а не схему. На большой таблице (10M+ строк) это опасно. Правило: schema migration (DDL) и data migration (DML) — РАЗДЕЛЯТЬ. Проблема 1: add_column с default на большой таблице: # Старый PostgreSQL (< 11): add_column :users, :active, :boolean, default: true # PostgreSQL переписывает ВСЮ таблицу → lock на 10 минут # PostgreSQL 11+: add_column :users, :active, :boolean, default: true # Мгновенно! PostgreSQL использует "fast default" Проблема 2: remove_column: remove_column :users, :old_field # Старый код (ещё не задеплоенный) обращается к old_field → crash Решение: два релиза Релиз 1: убрать из кода все обращения к old_field Релиз 2: remove_column в миграции Проблема 3: rename_column: rename_column :users, :name, :full_name # Все User.where(name: ...) сломаются Решение: три релиза Релиз 1: добавить full_name (alias, скопировать данные) Релиз 2: перевести код на full_name Релиз 3: удалить name Проблема 4: Data migration на 10M записей: # Плохо: всё в одной транзакции User.update_all(role: :active) # lock на 5 минут # Хорошо: батчами User.find_in_batches(batch_size: 1000) do |batch| User.where(id: batch).update_all(role: :active) sleep(0.1) # дать БД передохнуть end # Ещё лучше: через Sidekiq class MigrateUserJob < ApplicationJob def perform(user_id) User.find(user_id).update!(role: :active) end end User.pluck(:id).each { |id| MigrateUserJob.perform_later(id) } Проблема 5: NOT NULL constraint: # На таблице есть NULL-значения: change_column_null :users, :email, false # → PG::NotNullViolation Решение: 1. Data migration: UPDATE users SET email = '' WHERE email IS NULL 2. Schema migration: change_column_null :users, :email, false Разные миграции, разный порядок! Чеклист data migration: — Сколько строк в таблице? (SELECT COUNT(*)) — Сколько времени займёт? (EXPLAIN ANALYZE на тестовых данных) — Блокирует ли таблицу? (SELECT pg_locks) — Можно откатить? (нужен down метод) — Запустить на staging с копией production данных Как ИИ помогает: ИИ напишет батчированную миграцию с find_in_batches. Но ИИ НЕ знает: у вас 1000 строк или 10M, какой RPS, можно ли залочить таблицу на 1 секунду или нельзя никогда. Решение о safety принимает разработчик, знающий продакшен.
Средне

Базы данных / Распределённые БД

Паттерн: Sticky Sessions

Как Sticky Sessions решают проблему stale read? В чём идея и какие минусы?
Идея: все запросы одного клиента всегда идут на одну и ту же ноду. Как это работает: 1. Клиент делает первый запрос 2. Балансировщик назначает ему конкретную ноду (по IP, cookie, session) 3. Все последующие запросы этого клиента — на ту же ноду 4. Клиент пишет на Leader → Leader реплицирует → раз клиент всегда читает с одной ноды, она успеет обновиться (запись быстрая, следующий запрос через сотни мс) Пример: Балансировщик видит cookie user_id=42 → всегда роутит на Replica 1 Клиент 42 пишет данные → Leader реплицирует на Replica 1 Клиент 42 читает → идёт на Replica 1 → данные уже свежие Плюсы: + Простая реализация (настройка балансировщика) + Нет дополнительной нагрузки на БД + Работает для большинства случаев Минусы: - Неравномерная нагрузка: одна нода перегружена, другая простаивает - При падении ноды клиент переходит на другую — stale read вернётся - Не гарантирует read-after-write при быстром чтении после записи - Не работает, если клиент переключает устройство/браузер
Средне

Базы данных / Распределённые БД

Паттерн: Token/Version Consistency

Как решить stale read с помощью токенов версий? Что клиент передаёт при чтении?
Идея: при записи сервер возвращает версию/токен. При чтении клиент передаёт этот токен, и реплика ждёт, пока не обновится до нужной версии. Как это работает: 1. Клиент: POST /users { name: "Иван" } 2. Сервер записывает, возвращает: { version: 42 } 3. Клиент: GET /users/1, заголовок: If-Version: 42 4. Реплика проверяет: "у меня версия 40, нужно догнать до 42" 5. Реплика ждёт обновления (или спрашивает Leader) 6. Когда версия >= 42 — возвращает свежие данные Вместо version можно использовать: — Timestamp: "я записал в 12:00:05, дай данные не старше" — WAL offset: позиция в логе репликации — Vector clock: векторная часы (более сложный вариант) Плюсы: + Строгая гарантия: клиент ВИДИТ свои записи + Работает при failover (не привязан к конкретной ноде) + Гибкость: клиент решает, когда нужна строгая консистентность Минусы: - Реплика может ждать (latency spike) если сильно отстаёт - Нужна поддержка на уровне БД или middleware - Клиент должен хранить и передавать токен в каждом запросе - Сложнее реализовать, чем sticky sessions
Средне

Базы данных / Распределённые БД

Паттерн: Quorum Reads/Writes (R + W > N)

Что такое Quorum reads/writes? Как формула R+W>N гарантирует свежие данные?
Идея: читать и писать с нескольких реплик одновременно. Если при записи подтвердили W реплик, а при чтении спросили R реплик, и R + W > N (общее число реплик) — хотя бы одна реплика в пересечении будет свежей. N — общее количество реплик W — сколько реплик должны подтвердить запись R — сколько реплик спросить при чтении Пример: N=3, W=2, R=2 Запись: клиент пишет → Leader отправляет на 3 реплики ждёт подтверждения от 2 (W=2) → отвечает "OK" Реплики A, B подтвердили. C — ещё нет. Чтение: клиент читает → спрашивает 2 реплики (R=2) Получает данные от A (свежие!) и C (устаревшие) Сравнивает версию/таймстемп → берёт свежие от A Пересечение: W=2 (A,B) + R=2 (A,C) = реплика A общая значит хотя бы один свежий ответ гарантирован Варианты настройки: N=3, W=2, R=2 — баланс (quorum) N=3, W=3, R=1 — быстрые чтения, медленные записи N=3, W=1, R=3 — быстрые записи, медленные чтения N=3, W=1, R=1 — максимальная скорость, НО нет гарантии (1+1=2 < 3) Плюсы: + Математическая гарантия свежести данных + Не нужен токен на клиенте + Автоматический failover (если одна нода упала — quorum из оставшихся) Минусы: - Latency: ждём несколько реплик (и при записи, и при чтении) - При падении части нод может не хватить quorum → запись/чтение недоступны - Выше нагрузка на сеть (больше межнодового трафика) Используют: Cassandra, DynamoDB, Riak
Средне

Базы данных / Распределённые БД

Паттерн: Consensus-протоколы (Raft, Paxos)

Как Raft и Paxos обеспечивают строгую консистентность? Что такое consensus?
Consensus (консенсус) — алгоритм, при котором все ноды договариваются о состоянии данных. Запись считается успешной, когда majority (большинство) нод её подтвердили. Raft — самый популярный consensus-алгоритм (понятнее чем Paxos). Как работает Raft: 1. Выбирается Leader (через голосование) 2. Все записи идут через Leader 3. Leader отправляет запись всем Followers 4. Ждёт подтверждения от majority (N/2 + 1) 5. Только после этого запись считается подтверждённой (committed) 6. Чтение тоже идёт через Leader (или через read-index механизм) Важно: majority — это больше половины. 5 нод → нужно 3 подтверждения 3 ноды → нужно 2 подтверждения Что при падении Leader: — Оставшиеся ноды начинают выборы нового Leader — Новый Leader гарантирует, что все committed записи сохранены — Система продолжает работу, если живо majority Плюсы: + Строгая линеаризация (linearizability) — максимальная гарантия + Прозрачно для клиента (не нужно передавать токены) + Автоматический failover с гарантиями Минусы: - Высокая latency: каждая запись ждёт round-trip на majority нод - При partition теряется доступность (если нет majority) - Leader — bottleneck (все записи через него) - Сложная реализация (обычно берут готовые решения) Используют: etcd, Consul (Raft), ZooKeeper (Zab), CockroachDB, Spanner
Средне

Базы данных / Распределённые БД

Паттерн: Client-Side Cache / Write-Through

Как кэширование на стороне клиента помогает избежать stale read?
Идея: после записи клиент сохраняет результат у себя (в кэше). При следующем чтении — берёт из кэша, а не из БД. БД успевает реплицировать данные, и когда кэш протухнет (TTL) — данные уже свежие на всех репликах. Как это работает: 1. Клиент: POST /users { name: "Иван" } 2. Сервер записывает в БД, возвращает: { name: "Иван" } 3. Клиент кэширует: cache["user:1"] = { name: "Иван" }, TTL=5sec 4. Клиент: GET /users/1 5. Проверяет кэш → есть! → возвращает из кэша (мгновенно, без запроса в БД) 6. Через 5 секунд кэш протухает → следующий запрос идёт в БД → данные уже свежие Где хранить кэш: — В памяти клиента (браузер, мобильное приложение) — На API-сервере (Redis, Memcached) — для множества инстансов — CDN (для публичных данных) Write-Through вариант: При записи — одновременно обновляем и БД, и кэш. Следующее чтение всегда попадёт в кэш с актуальными данными. Плюсы: + Нулевая latency на чтение своих записей + Не нужно менять БД или балансировщик + Снижает нагрузку на БД Минусы: - Другие клиенты видят старые данные (пока кэш не протух) - Сложная инвалидация кэша (когда данные меняются извне) - Если несколько API-инстансов — нужен distributed cache (Redis) - Риск рассинхронизации кэша и БД
Средне

Базы данных / Распределённые БД

Паттерн: Leader-Only Reads

Что если всегда читать с Leader? Какие плюсы и минусы?
Идея: чтение идёт только на Leader, где данные гарантированно свежие. Реплики используются только для других целей (аналитика, бэкап). Как это работает: 1. Клиент: POST /users { name: "Иван" } → Leader 2. Leader записывает, подтверждает 3. Клиент: GET /users/1 → ТОЖЕ на Leader 4. Leader возвращает свежие данные (он же сам только что записал) Варианты применения: — Все чтения через Leader — Только "чтения после записи" через Leader (по сигналу от клиента) — Чтение через Leader в течение N секунд после записи В PostgreSQL: SET default_transaction_read_only = on; — на репликах Чтение идёт на primary (leader), реплики — для отчётов Плюсы: + 100% гарантия read-after-write (данные на Leader всегда свежие) + Простая реализация (на уровне роутинга запросов) + Не нужны токены, кэши, quorum Минусы: - Leader — bottleneck: все запросы через одну ноду - Реплики простаивают (не используются для обслуживания чтения) - Высокая latency если Leader далеко от клиента географически - Leader — единая точка отказа (SPOF): упал → всё встало - Не масштабируется: добавление реплик не помогает с нагрузкой Когда подходит: — Небольшая нагрузка на чтение — Данные критичны (финансы, платежи) — Географически один регион
Средне

Базы данных / Распределённые БД

Паттерн: Per-Query Consistency (гибридный подход)

Как Cassandra и DynamoDB позволяют выбирать консистентность для каждого запроса?
Идея: клиент выбирает уровень консистентности ДЛЯ КАЖДОГО ЗАПРОСА отдельно. Для важных операций — строгая консистентность (медленнее). Для неважных — eventual (быстрее). Уровни консистентности (на примере Cassandra): ONE — записать/прочитать с одной реплики Самый быстрый, но нет гарантий. Для лайков, просмотров. QUORUM — записать/прочитать с majority реплик (N/2 + 1) Баланс скорости и гарантий. Для профилей, настроек. ALL — все реплики должны подтвердить Максимальная гарантия, но упала одна нода — всё встало. Для критичных данных (пароли, баланс). LOCAL_QUORUM — quorum в локальном дата-центре Для multi-DC: быстро локально, без межконтинентальных задержек. Как это выглядит в коде (Cassandra): # Запись профиля — важно, используем QUORUM session.query("INSERT INTO users ...", consistency: :quorum) # Запись лайка — не важно, используем ONE session.query("INSERT INTO likes ...", consistency: :one) # Чтение профиля после редактирования — важно session.query("SELECT * FROM users WHERE id = ?", consistency: :quorum) # Чтение ленты — не критично, eventual ok session.query("SELECT * FROM feed", consistency: :one) Плюсы: + Гибкость: разные гарантии для разных операций + Можно оптимизировать: строго только где нужно + Один кластер БД обслуживает все сценарии Минусы: - Сложнее reasoning: разные запросы — разные гарантии - Нужна дисциплина в команде: все должны понимать уровни - Ошибка в выборе уровня — stale read вернётся Используют: Cassandra, DynamoDB, CosmosDB, ScyllaDB
Сложно

Базы данных / Распределённые БД

Задача: Read-After-Write в распределённой системе

На собеседовании вам дают задачу: У вас есть API поверх распределённой БД (несколько нод). Данные записываются на Leader и асинхронно реплицируются. Используется Eventual Consistency. Проблема: клиент записывает данные, сразу читает — и получает старую версию, потому что попал на отстающую реплику (stale read). Задача: предложите способы обеспечить Read-After-Write Consistency — чтобы клиент всегда видел свои последние записи. Для каждого способа опишите плюсы и минусы. Подсказка: вспомните 7 паттернов из карточек выше.
=== 7 ПАТТЕРНОВ РЕШЕНИЯ === 1. STICKY SESSIONS Все запросы клиента на одну ноду. +: простая реализация -: при падении ноды — stale read возвращается 2. TOKEN / VERSION Сервер возвращает версию при записи, клиент передаёт при чтении. Реплика ждёт пока не догонит до нужной версии. +: строгая гарантия, работает при failover -: latency spike, нужна поддержка в БД 3. QUORUM READS/WRITES (R+W>N) Читать и писать с нескольких реплик. Пересечение гарантирует свежесть. +: математическая гарантия, автоматический failover -: выше latency, при падении нод может не хватить quorum 4. CONSENSUS (Raft/Paxos) Запись подтверждается majority нод через алгоритм консенсуса. +: строгая линеаризация, прозрачна для клиента -: максимальная latency, сложная реализация 5. CLIENT-SIDE CACHE После записи кэшировать результат, читать из кэша, а не из БД. +: нулевая latency, не трогаем БД -: другие клиенты видят старые данные, сложная инвалидация 6. LEADER-ONLY READS Читать только с Leader, где данные всегда свежие. +: 100% гарантия, простая реализация -: Leader — bottleneck, не масштабируется 7. PER-QUERY CONSISTENCY Клиент выбирает уровень консистентности для каждого запроса. +: гибкость, оптимизация где можно -: нужна дисциплина, ошибка = stale read === КАК ВЫБРАТЬ === Высоконагруженный API: Per-Query + Quorum (варианты 7 + 3) Быстрый фикс, мало трафика: Sticky Sessions (вариант 1) Финансы, критичные данные: Consensus (вариант 4) Много чтений, свой кэш: Client-Side Cache (вариант 5) На собеседовании: назовите 3-4 варианта с +/- и обоснуйте выбор.
Средне

Ruby on Rails / Инфраструктура Rails

Memory Leak в Ruby-процессе

Процесс Ruby (Puma worker) растёт до 2GB и падает (OOM Killer). Что такое memory leak в Ruby? Как найти и починить?
Memory leak (утечка памяти) — когда объекты создаются, но никогда не удаляются GC. Процесс растёт и растёт, пока не упадёт. Частые причины в Rails: 1. Глобальные переменные / константы-хранилища: $cache = {} # растёт бесконечно, GC не чистит CACHE << user # каждый запрос добавляет, никто не удаляет 2. Массивы/хэши в long-running процессах (Sidekiq, Puma): class MyJob < ApplicationJob @@processed = [] # растёт с каждой джобой end 3. Забыли .limit / .find_each: User.all.each { |u| ... } # загрузил 500 000 пользователей в память # Правильно: User.find_each { |u| ... } # по 1000 за раз 4. Замыкания (closures) держат ссылки: def process big_array = (1..1_000_000).to_a proc { big_array.size } # big_array никогда не удалится end Как найти: — gem 'memory_profiler' — показать кто аллоцирует — gem 'derailed_benchmarks' — бенчмарк memory — NewRelic / Datadog — график памяти по времени — Sentry — алерт при OOM Как ИИ помогает: ИИ найдёт forgot .limit или глобальный массив в коде. Но ИИ НЕ увидит, что leak происходит только при определённом traffic pattern в продакшене. Profiling и мониторинг — ваша задача. Что сделать на практике: 1. Посмотреть график памяти (Datadog/NewRelic) 2. Воспроизвести: ab -n 10000 http://localhost:3000/api/endpoint 3. Запустить memory_profiler на этот endpoint 4. Найти кто аллоцирует и почему не чистится
Средне

Ruby on Rails / Инфраструктура Rails

Connection Pool: откуда берутся ошибки

В продакшене появляются ошибки: ActiveRecord::ConnectionTimeoutError или PG::ConnectionBad. Что такое connection pool и почему он заканчивается?
Connection Pool — набор заранее созданных подключений к БД. Каждый поток (Puma thread, Sidekiq thread) берёт соединение из пула. Если свободных нет — ждёт. Если ждёт дольше timeout — ошибка. Как это работает: Puma: 5 threads → нужен pool минимум 5 Sidekiq: 25 concurrency → нужен pool минимум 25 config/database.yml: pool: 5 # максимум 5 одновременных подключений к БД Почему пул заканчивается: 1. Долгий запрос: 5 потоков ждут БД → 6-й поток не может получить connection 2. Не закрытое соединение: забыли .release в raw SQL 3. N+1: 100 запросов в одном action = долго держат connection 4. Thread mismatch: Puma 5 threads, pool: 2 → 3 потока в очереди Как диагностировать: — rails dbconsole: SELECT * FROM pg_stat_activity; — сколько active connections — Логи: "could not obtain a connection from the pool" — NewRelic: график "Database connections" Как чинить: 1. Увеличить pool (но не больше max_connections PostgreSQL): pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %> 2. Убрать долгие запросы (N+1, missing index) 3. Настроить PostgreSQL: # postgresql.conf max_connections = 200 # по умолчанию 100 4. PgBouncer — пулинг на уровне PostgreSQL: Позволяет 1000 Rails connections → 50 реальных PostgreSQL connections (transaction-level pooling) Формула: Puma processes × Puma threads + Sidekiq concurrency = минимальный pool Умножить на количество приложений, подключённых к одной БД
Средне

Ruby on Rails / Legacy-проекты

Рефакторинг: когда и как

Менеджер просит новую фичу. Код — лапша. Делать фичу в этом коде или сначала рефакторить? Что такое Boy Scout Rule?
Главное правило: не рефакторить "на потом". Рефакторинг = часть работы над фичей. Boy Scout Rule: "Оставь код чище, чем ты его нашёл." Не нужно переписывать весь файл. Достаточно: — Вынести метод, пока разбираешься — Добавить тест на код, который меняешь — Убрать закомментированный код — Переименовать непонятную переменную Когда рефакторить: — Добавляете фичу и не понимаете код → сначала упростить — Дублирование: копипаста в 3 местах → вынести в метод — Тест на новую фичу требует mock 10 зависимостей → упростить связи Когда НЕ рефакторить: — Код работает и вы его НЕ трогаете — Нет тестов и нет времени их написать — Рефакторинг не приносит бизнес-ценности — "Мне не нравится как написано" — не причина Стратегии рефакторинга: 1. Extract Method — вынести кусок в отдельный метод: # Было: def process data = fetch_from_api data.map { |d| d["value"] * 1.2 } data.reject { |d| d > 100 } data.sort.reverse # ... ещё 20 строк end # Стало: def process data = fetch_from_api normalized = normalize(data) filtered = filter(normalized) sorted = sort_desc(filtered) end 2. Replace Conditional with Polymorphism: # Было: def price case type when 'book' then base * 0.9 when 'premium' then base * 1.5 when 'sale' then base * 0.5 end end # Стало (STI): class BookProduct < Product def price = base * 0.9 end 3. Move Method — метод в правильный класс: # User#calculate_order_total — это про Order → Order#total 4. Introduce Parameter Object: # Было: def create_order(user, product, quantity, discount, gift_wrap) # Стало: def create_order(order_params) Опасный рефакторинг (не делать без тестов): — Переименование методов (breaks external callers) — Изменение public API — Перемещение между модулями — Любой рефакторинг без запуска тестов после Как ИИ помогает: ИИ мгновенно вынесет метод, переименует переменные, упростит if/else. Но ИИ НЕ знает: "а этот метод вызывается из 10 мест, включая rake task, который запускается по cron на продакшене". ИИ может сломать то, о чём не знает. Review каждого изменения.
Средне

Ruby on Rails / Legacy-проекты

Undocumented Code: нет даже README

На проекте нет документации. Ни README, ни комментариев. Как понять что делает приложение? Как начать документировать?
Недокументированный проект — норма, не исключение. Документация устаревает, а код — всегда актуален. Порядок разбора (от быстрого к глубокому): 1. README.md — если есть: — Как запустить? (setup instructions) — Что делает приложение? (purpose) — Как запустить тесты? 2. config/routes.rb — карта всех endpoints: Ресурсы = основные сущности приложения Namespace = модули/домены 3. db/schema.rb — структура данных: Таблицы = бизнес-сущности Foreign keys = связи между сущностями Индексы = важные поля (по ним ищут) 4. Gemfile — технологии и зависимости: gem 'stripe' → есть платежи gem 'sidekiq' → фоновые задачи gem 'devise' → аутентификация gem 'pundit' → авторизация 5. app/jobs/ — фоновые процессы: Какие процессы работают в фоне? Рассылки, импорт, синхронизация? 6. app/services/ (если есть) — бизнес-логика: Главные процессы приложения Что документировать в первую очередь: 1. Как запустить проект (setup guide): — Ruby version, Node version, PostgreSQL version — bundle install, rails db:setup — export ENV_VAR=... (не забудьте .env.example) — rails server 2. Архитектурные решения (ADR — Architecture Decision Records): docs/adr/001-why-sidekiq-not-resque.md "Мы выбрали Sidekiq потому что..." 3. Схема доменов: docs/architecture.md "Пользователь → делает Заказ → содержит Товары → Оплата через Stripe" 4. Deployment process: docs/deployment.md "Merge в main → CI → deploy to staging → ручная проверка → deploy to production" Инструменты: — rails-erd gem — автоматически сгенерировать ER-диаграмму — annotate gem — комментарии-схемы в модели (зависит от CPU) — Yard / RDoc — документация из кода — diagrams.net — нарисовать архитектуру Антипаттерны документации: — Дублировать код в комментариях: "x = x + 1 # прибавляем 1" — Устаревшие комментарии: "используем Rails 5" (а уже 7) — Документировать ОЧЕВИДНОЕ — Документация вместо понятного кода Как ИИ помогает: ИИ сгенерирует README по коду проекта за минуту. ИИ нарисует схему связей между моделями. Но ИИ НЕ знает: "а это legacy-поле, его используют только в старом мобильном приложении v1, уже никем не поддерживаемом". Бизнес-контекст — только от команды.
Средне

Ruby on Rails / Инфраструктура Rails

Sidekiq: очередь растёт, джобы падают

Sidekiq-очередь растёт до 100 000 джоб. Джобы падают, retry создают avalanche effect. Как диагностировать и чинить?
Avalanche effect (лавинообразный эффект): Джоба падает → Sidekiq retry → падает снова → retry с задержкой + новые джобы постоянно добавляются → очередь растёт экспоненциально. Частые причины падений: 1. Timeout: внешнее API не отвечает за 30 секунд Net::ReadTimeout при HTTP запросе 2. Memory: Sidekiq process OOM, worker убит Обработка 10000 записей в памяти 3. Not idempotent: джоба выполнена наполовину, retry дублирует данные Отправили email, потом retry → клиент получил 2 письма 4. Exception в середине: обработали 500 из 1000 записей, упали При retry начинаем СНАЧАЛА → первые 500 обработаются повторно Что делать: 1. Idempotency — джоба безопасна для повторного выполнения: class SendEmailJob < ApplicationJob def perform(user_id) return if user_already_emailed?(user_id) # guard clause UserMailer.welcome(user_id).deliver_now mark_emailed!(user_id) end end 2. Батчинг вместо обработки всего сразу: # Плохо: User.all.each { |u| process(u) } # OOM на 500K записей # Хорошо: User.find_in_batches(batch_size: 500) do |batch| batch.each { |u| process(u) } end 3. Circuit breaker — временно отключить падающий сервис: gem 'circuitbox' Не пытаться вызвать API, если он уже упал 5 раз подряд 4. Dead jobs queue — лимит retry: sidekiq_options retry: 3, dead: true После 3 retry → dead queue → алерт → ручное разбирательство 5. Мониторинг: Sidekiq Web UI: /sidekiq — размер очереди, retry count Sentry: алерт при росте очереди Как ИИ помогает: ИИ напишет idempotent джобу с guard clause. Но СТРАТЕГИЮ обработки ошибок (retry vs dead vs circuit breaker) вы определяете на основе бизнес-требований.
Средне

Ruby on Rails / Инфраструктура Rails

Блокировки БД: row locks, deadlocks

В логах: PG::LockNotAvailable или deadlock detected. Что такое блокировки в БД? Почему возникают deadlocks?
Блокировка (lock) — механизм БД, чтобы две транзакции не меняли одни и те же данные одновременно. Когда возникает lock: Transaction A: UPDATE users SET balance = 100 WHERE id = 1 Transaction B: UPDATE users SET balance = 200 WHERE id = 1 B ждёт пока A не завершится (commit или rollback). Deadlock (взаимная блокировка): Transaction A: UPDATE users SET ... WHERE id = 1 (lock id=1) Transaction B: UPDATE users SET ... WHERE id = 2 (lock id=2) Transaction A: UPDATE users SET ... WHERE id = 2 (ждёт B) Transaction B: UPDATE users SET ... WHERE id = 1 (ждёт A) → Обе ждут друг друга = deadlock. PostgreSQL убьёт одну из них. В Rails это выглядит так: ActiveRecord::Deadlocked PG::Error: ERROR: deadlock detected Как избежать: 1. Одинаковый порядок обновления: ВСЕГДА обновлять записи по id по возрастанию sort_by(&:id) перед обновлением 2. Короткие транзакции: # Плохо: User.transaction do user.update!(balance: ...) sleep(5) # внешний API order.update!(status: ...) # lock держится 5 секунд! end # Хорошо: result = call_external_api # БЕЗ транзакции User.transaction do user.update!(balance: ...) order.update!(status: result) end 3. Optimistic locking: add_column :products, :lock_version, :integer, default: 0 # ActiveRecord автоматически проверит версию при update # Если кто-то изменил раньше — ActiveRecord::StaleObjectError 4. advisory locks для бизнес-логики: ActiveRecord::Base.connection.execute( "SELECT pg_advisory_lock(12345)" ) # Только один процесс может держать этот lock 5. SELECT FOR UPDATE (pessimistic): User.transaction do user = User.lock.find(1) # заблокировать строку user.update!(balance: user.balance - 100) end Как ИИ помогает: ИИ добавит lock_version, перепишет транзакцию. Но ИИ не знает, что deadlock возникает только в пятницу вечером при определённом traffic pattern. Читать PostgreSQL logs — ваша задача.
Средне

Ruby on Rails / Инфраструктура Rails

Медленные запросы: EXPLAIN ANALYZE

Endpoint отвечает 5 секунд. Как понять почему? Что такое EXPLAIN ANALYZE и как читать его вывод?
EXPLAIN ANALYZE — команда PostgreSQL, которая показывает: — КАК БД выполняет запрос (план) — СКОЛЬКО времени занимает каждый шаг (реальное время) Как использовать: В rails console: User.joins(:orders).where(orders: { status: 'active' }).explain → ActiveRecord сгенерирует EXPLAIN для запроса В psql: EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@mail.com'; Что смотреть в выводе: Seq Scan (sequential scan) — БД читает ВСЮ таблицу. ПЛОХО. → Нужен индекс по этому полю Index Scan — БД использует индекс. ХОРОШО. → Запрос быстрый Bitmap Heap Scan — использует индекс, но читает много строк. → Может быть ок, или нужен более точный индекс Sort — сортировка без индекса, в памяти. → На больших данных будет медленно Nested Loop — для каждой записи из одной таблицы ищет в другой. → Проверить join, возможно нужен индекс по foreign key Типичные проблемы и решения: 1. Нет индекса: WHERE email = '...' → нет index on email → Seq Scan Добавить: add_index :users, :email 2. Функция в WHERE: WHERE LOWER(email) = '...' → index не используется Решение: add_index :users, "lower(email)" Или: WHERE email ILIKE '...' (case-insensitive) 3. SELECT * вместо нужных колонок: User.all вместо User.select(:id, :name) На 100 колонок / 1M строк = передача GB данных 4. Missing foreign key index: JOIN orders ON orders.user_id = users.id → add_index :orders, :user_id Инструменты: — rails console: User.explain — Bullet gem: алерт при N+1 — Rack Mini Profiler: показать SQL запросы на странице — PostgreSQL slow query log: log_min_duration_statement = 500 Как ИИ помогает: ИИ добавит .includes, создаст индекс, перепишет запрос. Но ИИ не знает: у вас 100 строк или 10M. Index на 100 строк не нужен. Решение принимаете вы на основе данных.
Средне

Ruby on Rails / Инфраструктура Rails

Медленный deploy: причины и оптимизация

Deploy занимает 10 минут. Asset compilation на production. Docker image 2GB. Как ускорить?
Типичный Rails deploy: git push → CI → build → deploy → restart → health check Каждый шаг может быть узким местом. Что тормозит: 1. bundle install: Gemfile на 200 гемов → скачивание + компиляция native extensions (pg, nokogiri) Решение: Docker layer caching — копировать Gemfile отдельно COPY Gemfile Gemfile.lock ./ RUN bundle install COPY . . Gemfile не меняется → cache hit → мгновенно 2. Asset precompilation: rails assets:precompile компилирует JS/CSS/SCSS На production может занимать 2-5 минут Решения: — Компилировать в CI, не на сервере — Использовать esbuild/vite (быстрее чем Sprockets) — Кэшировать node_modules и tmp/cache/assets 3. Миграции БД: add_column :users, :bio, :text на таблице 50M строк PostgreSQL залочит таблицу на минуты Решение: — add_column с default: → PostgreSQL 11+ не блокирует — remove_column → сначала убрать из кода, потом через релиз — data migrations — отдельные джобы, не в deploy 4. Docker image слишком большой: Ruby:3.2 образ = 1GB + gems + assets = 2GB Решение: multi-stage build FROM ruby:3.2 AS builder RUN bundle install && rails assets:precompile FROM ruby:3.2-slim # slim = 200MB вместо 1GB COPY --from=builder /app /app 5. Restart всех Puma workers: kill -USR2 master_pid — phased restart По одному воркеру, но каждый грузит приложение Решение: preload_app! + phased restart Метрики для мониторинга: — CI time: от push до готового artifact — Deploy time: от artifact до живого pod — Downtime: сколько пользователи видят ошибку Как ИИ помогает: ИИ напишет Dockerfile с multi-stage, оптимизирует Gemfile. Но стратегию zero-downtime deploy (blue-green, canary, rolling) выбираете вы на основе инфраструктуры.
Средне

Ruby on Rails / Инфраструктура Rails

Graceful Shutdown: что происходит при деплое

При деплое Puma перезапускается. Что происходит с текущими запросами? Что такое graceful shutdown и зачем он нужен?
Проблема: при деплое процесс убивается. Если в этот момент обрабатывается запрос — клиент получит ошибку. Если выполняется Sidekiq job — данные обработаны наполовину. Graceful shutdown (плавное завершение) — дать процессу завершить текущую работу перед остановкой. Как работает Puma: 1. Получает сигнал TERM (от Kubernetes, systemd, Capistrano) 2. Перестаёт принимать НОВЫЕ запросы 3. Даёт текущим запросам завершиться (timeout: 30 секунд) 4. Если не успели — форсированно убивает 5. Запускается новая версия Настройка в config/puma.rb: workers 2 threads 1, 5 worker_timeout 30 # сколько ждать перед kill preload_app! # загрузить app один раз, fork workers on_worker_boot do # подключиться к БД после fork ActiveRecord::Base.establish_connection end Sidekiq graceful shutdown: Sidekiq при TERM: 1. Перестаёт брать новые джобы 2. Текущие джобы дорабатывают (timeout: 25 секунд) 3. Не завершённые → push обратно в Redis 4. Процесс завершается # config/initializers/sidekiq.rb Sidekiq.configure_server do |config| config.timeout = 25 end Kubernetes и graceful shutdown: spec: terminationGracePeriodSeconds: 60 1. Kubernetes отправляет SIGTERM 2. Ждёт 60 секунд 3. Если жив — SIGKILL (форсированное убийство) Проблемы без graceful shutdown: — Пользователь видит 502 в момент деплоя — Sidekiq job выполнена наполовину → данные в неконсистентном состоянии — Файл загружен наполовину → битый файл на диске — Транзакция открыта → lock в БД на 30 секунд Как ИИ помогает: ИИ напишет правильный puma.rb и sidekiq initializer. Но timeout значения (30s? 60s?) зависят от ваших SLA и времени выполнения запросов. Это определяете вы.
Средне

Ruby on Rails / Инфраструктура Rails

Мониторинг: Sentry, APM, логи

На продакшене что-то сломалось. Как узнать? Что такое APM, Sentry, structured logging?
Мониторинг — способность ЗНАТЬ что происходит в продакшене без ручной проверки. Три слоя мониторинга: 1. Errors — Sentry / Rollbar Автоматически ловит все unhandled exceptions Показывает: stack trace, user info, request params, breadcrumbs Алерт в Slack при росте ошибок # config/initializers/sentry.rb Sentry.init do |config| config.dsn = Rails.application.credentials.sentry_dsn config.environment = Rails.env end 2. Performance (APM) — NewRelic / Datadog / AppSignal Показывает: время запроса, время SQL, время view Трейсы: какой запрос куда ходил и сколько ждал Графики: response time, throughput, error rate, memory Находит: — "Endpoint /api/orders отвечает 3 секунды" — "SELECT * FROM products занимает 80% времени" — "Memory растёт каждый деплой — leak" 3. Logs — ELK / Loki + Grafana Structured logging = логи в JSON формате Легко искать и фильтровать # Вместо: puts "User logged in" # Использовать: Rails.logger.info({ event: "user_login", user_id: user.id, ip: request.ip }) Поиск в логах: event: "user_login" AND ip: "1.2.3.*" error: true AND controller: "PaymentsController" Что мониторить в первую очередь: — Error rate (ошибки / запросы) — >1% = проблема — P95 response time — 95% запросов укладываются в X ms — Queue size (Sidekiq) — растёт = что-то не успевает — Memory usage — растёт = leak — Database connections — >80% pool = скоро OOM Alerting (алерты): — Error rate > 1% → Slack #alerts — Response time p95 > 2s → Slack #alerts — Sidekiq queue > 10000 → Slack #alerts — Memory > 1.5GB → PagerDuty (ночью будит дежурного) Как ИИ помогает: ИИ настроит Sentry, напишет structured logging. Но ЧТО мониторить и КАКИЕ thresholds — знаете вы из бизнес-контекста. "P95 > 500ms — это норма или катастрофа?" — зависит от приложения.
Средне

Ruby on Rails / Инфраструктура Rails

Race Condition: когда два потока ломают данные

Два Sidekiq-джоба одновременно списывают баланс. Или два запроса создают дублирующую запись. Что такое race condition и как с ним бороться?
Race condition (состояние гонки) — когда результат зависит от порядка выполнения параллельных операций. Пример 1: Списание баланса: Job A: читает balance = 1000 Job B: читает balance = 1000 Job A: записывает balance = 1000 - 500 = 500 Job B: записывает balance = 1000 - 300 = 700 Итог: balance = 700, а должно быть 200. 300 рублей потеряно. Пример 2: Дублирование записи: Request A: проверяет unique? → да, создаёт Request B: проверяет unique? → да, создаёт (A ещё не закончил) Итог: две одинаковые записи. Как чинить: 1. Atomic update (для чисел): # Плохо: user.update!(balance: user.balance - 500) # Хорошо: User.update_counters(user.id, balance: -500) # SQL: UPDATE users SET balance = balance - 500 WHERE id = ? 2. Database unique constraint: add_index :subscriptions, [:user_id, :plan_id], unique: true # БД гарантированно не допустит дубль # В коде: rescue ActiveRecord::RecordNotUnique 3. SELECT FOR UPDATE (pessimistic locking): User.transaction do user = User.lock.find(user_id) user.update!(balance: user.balance - 500) end # Другая транзакция будет ждать 4. Optimistic locking: add_column :users, :lock_version, :integer, default: 0 # update проверит lock_version, если изменился — StaleObjectError 5. Redis distributed lock: lock_key = "process_order:#{order_id}" Redis.current.set(lock_key, "1", nx: true, ex: 30) # Только один процесс возьмёт lock 6. Idempotency key: class CreatePaymentJob < ApplicationJob def perform(order_id, idempotency_key) return if ProcessedKey.exists?(key: idempotency_key) # ... обработка ... ProcessedKey.create!(key: idempotency_key) end end Как ИИ помогает: ИИ перепишет на atomic update, добавит unique index. Но ИИ не знает: "а может ли джоба вызваться дважды?" Это вы должны знать из бизнес-логики.
Средне

Ruby on Rails / Инфраструктура Rails

Zero-Downtime Deploy: стратегии

Как деплоить без остановки сервиса? Что такое rolling deploy, blue-green, canary?
Zero-downtime deploy — пользователи не замечают обновления. Ни одной 502 ошибки. Стратегии: 1. Rolling Deploy (по умолчанию в Kubernetes): — Запущено 4 pod (instance) — Kubernetes убивает 1, запускает новый — Когда новый healthy — убивает следующий — Всегда живо минимум 3 из 4 Проблема: если новая версия сломана — 25% запросов упадут перед тем как rollback сработает. 2. Blue-Green Deploy: — Blue = текущая версия (живая) — Green = новая версия (разворачивается параллельно) — Когда Green healthy → переключаем traffic (load balancer) — Если проблема → переключаем обратно на Blue + Мгновенный rollback - Нужна двойная инфраструктура (2x серверов) - Миграции БД должны быть совместимы с ОБЕИМИ версиями 3. Canary Deploy: — Новая версия запускается для 5% трафика — Мониторим 15 минут: error rate, latency — Если ок → увеличиваем до 25% → 50% → 100% — Если проблема → откатываем 5% обратно + Ловим баги на малом трафике - Сложнее инфраструктура - Нужен feature flags для разделения Важное правило для миграций: Деплой происходит в два релиза: Релиз 1: Код работает со СТАРОЙ И НОВОЙ схемой БД Миграция: add_column (backward compatible) Релиз 2: Код использует только новую схему Миграция: remove_old_column Нарушение = ошибка на продакшене. Инструменты: — Kubernetes: rolling update (default) — Heroku: preboot (blue-green автоматически) — Capistrano: release folders + symlink switch — Feature flags (Flipper): включить фичу для canary % пользователей Как ИИ помогает: ИИ напишет Kubernetes manifests, Dockerfile, migration strategy. Но выбор стратегии (rolling vs blue-green vs canary) зависит от размера команды, бюджета инфраструктуры, SLA.
Средне

Ruby on Rails / Инфраструктура Rails

ИИ в разработке: что делегировать, что проверять

Вы используете ИИ для написания кода. Что ИИ делает хорошо, а где он ошибается? Как правильно работать с ИИ-ассистентом?
ИИ отлично генерирует шаблонный код. Но вы — последняя линия защиты. ИИ делает хорошо: — CRUD-контроллеры, модели, миграции — RSpec тесты на happy path — N+1: добавить .includes, переписать на .joins — Документация, комментарии — Regex, парсинг, форматирование — Объяснение чужого кода ИИ делает ПЛОХО: — Архитектурные решения (не знает размер вашей команды и roadmap) — Race conditions (пишет как будто всё sequential) — Миграции на production (не знает размер таблицы и RPS) — Безопасность (может сгенерировать код с уязвимостями) — Бизнес-контекст (не знает "почему так, а не иначе") — Связи между модулями (не видит полную картину проекта) Правильный workflow: 1. Вы описываете задачу максимально точно 2. ИИ генерирует код 3. Вы ЧИТАЕТЕ каждую строку (code review) 4. Проверяете: security, edge cases, performance 5. Запускаете тесты 6. Если ошибка — ИИ фиксит, вы снова проверяете Красные флаги в коде от ИИ: — SQL без параметров: "WHERE email = '#{params[:email]}'" → injection! — Глобальное состояние: $cache, @@all_users → memory leak — .all.each вместо .find_each → OOM на больших таблицах — Без .limit на запросах → загрузка всей таблицы — Нет обработки nil → NoMethodError — Сложный код без тестов → наверняка сломается Навыки, которые стали важнее: 1. Code review — теперь 50% вашей работы 2. Формулирование задач — точный промпт = точный код 3. Архитектурное мышление — ИИ не решит за вас 4. Debugging — ИИ не имеет доступа к вашему продакшену 5. Security — вы ловите уязвимости до того как они попадут в код
Средне

Ruby on Rails / Инфраструктура Rails

API Versioning: не сломать мобильное приложение

Вы изменили ответ API. Мобильное приложение перестало работать. Как правильно версионировать API?
Проблема: мобильное приложение уже у пользователей на телефонах. Вы не можете заставить всех обновиться одновременно. Изменили API → старые версии приложения сломались. Стратегии versioning: 1. URL-based (самый распространённый): /api/v1/users /api/v2/users # config/routes.rb namespace :api do namespace :v1 do resources :users end namespace :v2 do resources :users end end 2. Header-based: GET /api/users Accept: application/vnd.myapp.v2+json 3. Parameter-based (не рекомендуется): GET /api/users?version=2 Правила безопасных изменений: BACKWARD COMPATIBLE (можно без новой версии): — Добавить новое поле в ответ: { name: "...", email: "..." } → + avatar_url — Добавить новый optional параметр — Добавить новый endpoint BREAKING CHANGE (нужна новая версия): — Переименовать поле: name → full_name — Удалить поле из ответа — Изменить тип: integer → string — Изменить структуру: { user: {} } → { data: { user: {} } } Workflow при breaking change: 1. Создать /api/v2/ с новой структурой 2. /api/v1/ продолжает работать 3. Мобильная команда обновляет приложение на v2 4. Когда все пользователи обновились → удалить v1 5. Обычно v1 живёт 3-6 месяцев Deprecation headers: response.headers["X-API-Deprecated"] = "v1 will be removed on 2025-06-01" response.headers["Link"] = "</api/v2/users>; rel=\"successor-version\"" Как ИИ помогает: ИИ сгенерирует namespace routing, serializers. Но что является breaking change, а что нет — решаете вы. ИИ может добавить поле и не сказать, что старые клиенты сломаются.
Средне

Ruby on Rails / Инфраструктура Rails

Background Jobs: что делать в фоне

Что отправлять в Sidekiq, а что выполнять синхронно? Как определить —foreground или background?
Правило: если пользователю нужен РЕЗУЛЬТАТ прямо сейчас — foreground. Если результат нужен ПОЗЖЕ — background. Foreground (в запросе): — Чтение данных (show, index) — Создание записи, пользователь видит результат — Авторизация, пользователь ждёт ответа — Простые вычисления (< 100ms) Background (Sidekiq): — Отправка email (пользователь не ждёт) — Обработка изображений (resize, оптимизация) — Генерация PDF/CSV отчётов — Интеграции: отправка в CRM, уведомления в Slack — Массовые операции: импорт 10000 записей из CSV — Периодические задачи: ежедневная агрегация, очистка Серая зона (зависит от контекста): — Обновление поискового индекса (можно в фоне) — Обновление статистики/счётчиков (можно в фоне) — Валидация данных (обычно foreground) Паттерны: 1. Fire and forget: SendWelcomeEmailJob.perform_later(user.id) # Отправили и забыли, результат не нужен 2. Polling (клиент спрашивает статус): # Создаём задачу report = Report.create!(status: :pending) GenerateReportJob.perform_later(report.id) # Клиент периодически спрашивает: GET /api/reports/1 → { status: "pending" } GET /api/reports/1 → { status: "completed", url: "..." } 3. WebSocket/ActionCable (push-уведомление): GenerateReportJob.perform_later(report.id) # Когда готово — Job отправляет через ActionCable: ActionCable.server.broadcast("user_#{user.id}", { event: "report_ready" }) 4. Webhook (уведомить внешний сервис): ProcessPaymentJob.perform_later(order.id) # Когда готово — отправляем webhook на URL клиента Ошибки при работе с фонами: — Передать object вместо id: SendEmailJob.perform_later(user) # user сериализуется, может быть устаревшим SendEmailJob.perform_later(user.id) # правильно: загрузим свежего из БД — Долгая джоба без прогресса: # Плохо: импорт 100K записей в одной джобе ImportUsersJob.perform_later(csv_path) # Лучше: разбить на батчи csv.each_slice(1000) { |batch| ImportBatchJob.perform_later(batch) } Как ИИ помогает: ИИ определит, что email → background, чтение → foreground. Но сложные бизнес-решения ("обновлять ли статистику в фоне?") требуют понимания product requirements.
Средне

Ruby on Rails / Legacy-проекты

God Object: модель на 2000 строк

Вы пришли на проект и увидели: класс User — 2000 строк, 50 методов, 30 has_many, 20 колбэков. Что такое God Object и как его разобрать?
God Object (божественный объект) — класс, который делает ВСЁ. Нарушает принцип единственной ответственности (SRP). Изменил одно поле — сломалась рассылка, статистика и оплата. Признаки God Object: — Файл > 300 строк — > 10 public методов — > 5 колбэков (after_save, before_create и т.д.) — Зависит от > 10 других моделей — Тесты на эту модель занимают 1000 строк — Любое изменение страшно — "а что ещё сломается?" Пример типичного God Object: class User < ApplicationRecord has_many :orders, :subscriptions, :notifications, :posts, ... after_save :send_welcome_email, :update_stats, :notify_admin after_create :create_stripe_customer, :setup_trial, :log_event before_destroy :cancel_subscriptions, :archive_orders, :send_farewell def full_name; end def display_name; end def admin?; end def can_access?(resource); end def calculate_lifetime_value; end def generate_report; end def send_weekly_digest; end def export_to_csv; end def process_payment(amount); end # ... ещё 40 методов end Как разбирать (стратегия): 1. Вынести в Service Objects — бизнес-процессы: UserPaymentService.new(user).process(1000) UserReportService.new(user).generate 2. Вынести в Query Objects — сложные запросы: ActiveUsersQuery.new(User).call UsersWithExpiredSubscriptionQuery.new.call 3. Вынести в Form Objects — сложные формы: RegistrationForm.new(params) # создаёт User + Subscription + sends email 4. Вынести в Concerns — общее поведение: module Billable ( Stripe integration ) module Notifiable ( email/push логика ) 5. Вынести в отдельные модели (STI или новые таблицы): UserBillingProfile вместо полей billing_* в User Порядок рефакторинга: 1. Написать тесты на текущее поведение (characterization tests) 2. Вынести один метод/группу за раз 3. Запустить тесты после каждого изменения 4. Не трогать несколько групп одновременно Как ИИ помогает: ИИ может вынести метод в Service Object за 10 секунд. Но ИИ НЕ видит 30 скрытых зависимостей между методами User. Он удалит колбэк, не зная, что его вызывает Sidekiq job в 3 местах. Стратегию разборки планируете вы.
Средне

Ruby on Rails / Legacy-проекты

Callback Hell: 15 действий при сохранении

after_save запускает send_email, update_stats, notify_admin, sync_crm... Добавили поле — всё сломалось. Что такое callback hell и как из него выбраться?
Callback Hell — когда модельные колбэки создают цепочку неявных действий, которые невозможно отследить. Пример: class Order < ApplicationRecord after_create :reserve_inventory after_create :send_confirmation_email after_create :notify_warehouse after_create :update_user_stats after_create :sync_to_crm after_save :recalculate_totals after_save :update_search_index after_commit :push_to_analytics def reserve_inventory Inventory.reserve(items) # может упасть → откатит create? end end Проблемы: 1. Неявность: Order.create запускает 8 действий. Разработчик не знает. 2. Порядок: после change статуса срабатывает after_save, но order ещё не saved 3. Тестирование: чтобы протестировать email, нужно создать заказ и mock 7 других колбэков 4. Производительность: Order.create делает 8 HTTP/SQL запросов 5. Отладка: "Почему отправилось 3 письма?" — ищи в колбэках Антипаттерны: — after_save { send_email } → email отправляется при ЛЮБОМ сохранении — Колбэк дергает внешнее API → Order.create занимает 5 секунд — Колбэк падает → весь save откатывается (или нет, если after_commit) Как чинить: 1. Заменить колбэки на явные Service Objects: # Было: after_create :send_confirmation_email # Стало: class CreateOrderService def call(params) order = Order.create!(params) OrderMailer.confirmation(order).deliver_later # явно NotifyWarehouseJob.perform_later(order.id) # явно order end end 2. Оставить колбэки ТОЛЬКО для data integrity: # OK — это про целостность данных: before_validation :normalize_email before_create :generate_uuid after_destroy :nullify_references # УБРАТЬ — это про бизнес-процессы: after_save :send_email → Service Object after_create :sync_crm → Service Object after_update :recalculate → Service Object 3. Использовать события (event-driven): ActiveSupport::Notifications.instrument("order.created", order: order) # Подписчики в отдельных файлах решают что делать Пошаговый план: 1. Список всех колбэков модели 2. Каждый: "это data integrity или бизнес-логика?" 3. Бизнес-логику → в Service Object 4. Data integrity → оставить 5. Тесты на каждый вынесенный метод Как ИИ помогает: ИИ перепишет колбэк в Service Object. Но ИИ не знает: "а этот колбэк вызывается из 5 мест — через Order.create, Order.update, из Sidekiq, из rake task..." Все 5 мест нужно обновить — это ваша ответственность.
Средне

Ruby on Rails / Legacy-проекты

Нет тестов: как начать покрывать legacy

Проект 3 года, 50 моделей, 0 тестов. С чего начать? Что такое characterization tests?
Legacy без тестов — это ходьба по минному полю. Любое изменение может сломать что угодно, и вы не узнаете об этом. Проблема: нельзя написать тесты сразу на всё. 50 моделей × 10 методов = 500 тестов. Это месяцы работы. Пока пишете — код меняется. Тесты устаревают. Стратегия (приоритеты): 1. Characterization Tests — зафиксировать текущее поведение: Не "как должно работать", а "как работает СЕЙЧАС". Запускаете код, записываете что вернул → это и есть тест. it "processes payment" do result = PaymentService.new(user).process(1000) expect(result.status).to eq("pending") # не "success"! # Даже если это баг — сначала фиксируем, потом чиним end 2. Приоритет покрытия: — Критичные бизнес-процессы (оплата, регистрация) — Места, которые сейчас рефакторите — Места, где чаще всего бывают баги (Sentry подскажет) — Public API / методы, которые используют другие команды 3. Не тратьте время на: — Геттеры/сеттеры — Simple CRUD без логики — Тесты на фреймворк (validates_presence_of) Что добавить в проект первым: 1. RSpec + FactoryBot + Shoulda Matchers 2. SimpleCov (показывает % покрытия) 3. Brakeman (security) 4. Rubocop (стиль, не на CI — только warning) Workflow при работе с legacy: 1. Получили таск 2. Написать тест на текущее поведение (characterization) 3. Убедиться что тест проходит 4. Изменить код 5. Убедиться что тест всё ещё проходит (или намеренно изменился) 6. Отправить PR Скелет теста для legacy-метода: describe "#calculate_discount" do subject { service.calculate_discount } context "with regular customer" do let(:customer) { create(:customer, tier: :regular) } it { is_expected.to eq(0) } # фиксируем текущее поведение end end Как ИИ помогает: ИИ сгенерирует тест по существующему методу за секунды. Но ИИ не знает: "а этот метод вызывается с nil в проде каждый вторник" — edge case'ы из бизнес-контекста добавляете вы. ИИ может сгенерировать тесты, которые проходят, но не покрывают реальные баги. Review — обязательно.
Средне

Ruby on Rails / Legacy-проекты

Rails Upgrade: миграция с 5 на 7

Проект на Rails 5.2. Нужно обновить до Rails 7. С чего начать? Какие подводные камни?
Rails upgrade — всегда пошаговый. Никогда не перепрыгивать версии. Правило: обновлять на одну MINOR версию за раз. 5.2 → 6.0 → 6.1 → 7.0 → 7.1 На каждом шаге: 1. Обновить Gemfile: gem 'rails', '~> 6.0.0' 2. Запустить bundle update rails 3. Запустить rails app:update (применить изменения конфигов) 4. Запустить тесты 5. Починить всё что сломалось 6. Закоммитить 7. Следующая версия Типичные проблемы при upgrade: 1. Deprecation warnings → ошибки: Rails 5: belongs_to необязателен Rails 6.1: belongs_to обязателен по умолчанию (optional: false) Решение: добавить optional: true или foreign key 2. Изменившийся API: Rails 5: update_attribute (без валидации) Rails 6: update_attribute deprecated → update_column Или: смена поведения ActiveStorage, ActionMailbox 3. Удалённые методы: Rails 5: find_or_create_by! Rails 6: работает, но create_or_find_by! — новый метод Rails 7: некоторые методы удалены 4. Зависимости (gems): Gem может не поддерживать новую версию Rails. Проверить: rubygems.org → versions → runtime dependencies Решение: обновить gem, найти замену, или форкнуть 5. Config файлы: rails app:update покажет diff для каждого конфига Нужно решить: принять новый, оставить старый, или объединить 6. Asset Pipeline: Rails 5: Sprockets Rails 7: importmap / esbuild / vite Это отдельный проект на неделю+ 7. Autoloading: Rails 5: classic (autoload_paths) Rails 7: zeitwerk (строгие правила命名) Zeitwerk не прощает: CONST = vs const_set, вложенные модули Чеклист перед upgrade: — Все тесты проходят на текущей версии — CI зелёный — Есть бэкап БД — Деплой на staging first — Deprecation warnings изучены (они подсказка что сломается) Как ИИ помогает: ИИ знает ВСЕ deprecations и изменения API между версиями. "Что изменилось в Rails 6.1?" — ИИ ответит мгновенно. Но ИИ не знает: "а этот gem используется в 3 местах и у него нет поддержки Rails 7". Проверка совместимости всех gems — ваша задача.
Средне

Ruby on Rails / Legacy-проекты

Gem Dependency Hell: заброшенные гемы

Проект зависит от gem 'rails_admin', который не обновлялся 4 года. Обновляешь Rails — gem ломается. Что делать?
Dependency Hell (ад зависимостей) — когда gems конфликтуют друг с другом или с версией Rails. Типичная ситуация: — rails_admin последний комит 2 года назад — Он зависит от rails < 7.0 — Вы обновляете Rails до 7 → rails_admin ломается — bundle update не помогает — нет новой версии гема Варианты решения (от простого к сложному): 1. Обновить gem: Проверить: есть ли новая версия? github.com/rails_admin/rails_admin → Releases Может быть beta/rc с поддержкой Rails 7 2. Форкнуть и починить: fork → исправить совместимость → указать в Gemfile: gem 'rails_admin', github: 'your-org/rails_admin', branch: 'rails-7' 3. Найти замену: rails_admin → administrate, activeadmin, avid Но замена = переписать все кастомные страницы 4. Изолировать: Вынести admin-панель в отдельное приложение (mountable engine) Оно живёт на старом Rails, основное приложение обновляется 5. Переписать: Если функционала немного — проще написать свой Как избежать в будущем: — Перед добавлением gem: проверить last commit, issues, PR count — Популярные гемы: > 1000 stars, активные maintainers — Minimal dependencies: gem с 3 зависимостями лучше чем с 30 — Периодически: bundle audit (security vulnerabilities) — Dependabot / Renovate — автоматические PR с обновлениями Команды для диагностики: bundle outdated # какие гемы устарели bundle audit # security уязвимости bundle viz # дерево зависимостей gem install gemnasium-parser # анализ совместимости Как ИИ помогает: ИИ найдёт альтернативу, форкнёт и починит совместимость. Но решение "форкнуть vs заменить vs переписать" зависит от бюджета, сроков и объёма кастомизации. Это бизнес-решение.
Средне

Ruby on Rails / Legacy-проекты

Монолит: как выжить с 500K строк

Проект — один монолит на 500 000 строк. 30 разработчиков наступают друг другу на ноги. Деплой = деплой всего. Что делать?
Монолит — норма. Большинство Rails-проектов — монолиты. Проблема не в монолите, а в неорганизованном монолите. Что болит при большом монолите: — Deploy: изменил 1 строку → деплой всего приложения (10 минут) — Конфликты: 30 разработчиков меняют одни файлы — Тесты: полный прогон 40 минут → никто не запускает — Случайные поломки: изменение в billing сломало рассылку — Onboarding: новичок разбирается месяц Стратегии (не microservices!): 1. Модульный монолит (Module Boundaries): Организовать код по доменам, а не по типу: # Вместо: app/models/user.rb, order.rb, payment.rb app/services/... # Структура по доменам: app/ billing/ models/, services/, controllers/ notifications/ models/, services/, controllers/ catalog/ models/, services/, controllers/ Каждая команда владеет своим доменом. 2. Engines (Rails Engines): Вынести домен в mountable engine: # engines/billing/lib/billing/engine.rb module Billing class Engine < ::Rails::Engine isolate_namespace Billing end end Engine = отдельный namespace, свои модели, роуты, views. Но в рамках одного приложения (одна БД, один деплой). 3. Strangler Fig Pattern: Новую функциональность — в отдельный сервис. Старую — постепенно вырезать. # роутер направляет: /api/v2/orders → новый микросервис /api/v1/orders → старый монолит Постепенно strangler "обвивает" старую систему. 4. Ускорение тестов: — Запускать только изменённые: TEST_CHANGED_ONLY=1 — Параллельный запуск: parallel_tests — TestProf: let_it_be (shared context), before_all Когда реально нужны микросервисы: — 5+ независимых команд — Разные требования к масштабированию (billing = мало запросов, catalog = много) — Разные технологии (одна часть на Ruby, другая на Go) — Чёткие границы между доменами Как ИИ помогает: ИИ разобьёт God Object на модули, создаст Engine, напишет тесты. Но решение "какие границы между доменами" — архитектурное. ИИ не знает, что billing и notifications — одна команда, а catalog — другая. Организацию команд вы определяете.
Средне

Ruby on Rails / Новые проекты

rails new: как начать проект правильно

Нужно создать новый Rails-проект с нуля. Какие флаги использовать? Что настроить до первой строчки бизнес-кода?
rails new myapp --skip-jbuilder --skip-action-mailbox --skip-action-text Что включить обязательно: — devise (аутентификация) — pundit (авторизация) — sidekiq (фоновые задачи) — rspec-rails + factory_bot_rails (тесты) — rubocop (стиль кода) — brakeman (безопасность) Что настроить сразу: — .ruby-version — зафиксировать версию Ruby — Gemfile — группы :development, :test, :production — .env.example — шаблон переменных окружения — .editorconfig — одинаковые отступы у всей команды — .gitignore — не коммитить .env, логи, tmp — docker-compose.yml — для локальной разработки (PostgreSQL, Redis, Sidekiq — один docker-compose up) Что НЕ делать: — Не добавлять гемы "на всякий случай" — Не начинать с дизайн-системы — начни с минимального UI — Не копировать структуру старого проекта один-в-один Как ИИ помогает: ИИ сгенерирует Gemfile с нужными гемами под задачу. ИИ напишет docker-compose.yml за 30 секунд. ИИ настроит CI/CD pipeline по описанию. Но ИИ НЕ знает: какие гемы уже использует команда, какие у вас внутренние соглашения по структуре. Архитектурные решения — за людьми.
Средне

Ruby on Rails / Новые проекты

Монолит vs Микросервисы: когда выбирать

Начинаешь новый проект. Нужно ли сразу дробить на микросервисы? Или лучше монолит? Когда микросервисы оправданы?
Монолит (Majestic Monolith): — Один код, одна база данных, один деплой — Проще разрабатывать, тестировать, деплоить — DHH (создатель Rails) рекомендует монолит для 95% проектов — Проблемы начинаются при 20+ разработчиках или когда разные части нужно деплоить независимо Микросервисы: — Каждый сервис — отдельное приложение — Своя база данных, свой деплой — Плюсы: независимый деплой, разные технологии, команда владеет своим сервисом — Минусы: сложность сети, distributed transactions, debugging между сервисами — ад, нужен DevOps Когда микросервисы ОПРАВДАНЫ: — 10+ разработчиков с чёткими командами — Разные требования к нагрузке (одна часть — x100 другой) — Разные SLA (payment — 99.99%, блог — 99%) — Нужно использовать разные языки (ML на Python, API на Go) Стратегия для нового проекта: 1. Начни с монолита 2. Выдели границы (bounded contexts) в коде 3. Когда мониторинг покажет узкие места — выдели в сервис ТОЛЬКО эту часть Как ИИ помогает: ИИ нарисует архитектурную схему по описанию. ИИ предложит границы сервисов на основе домена. Но ИИ НЕ знает: сколько у вас разработчиков, какой бюджет на инфраструктуру, как будет расти проект. Решение "монолит или сервисы" — всегда за командой.
Средне

Ruby on Rails / Новые проекты

12-Factor App: принципы конфигурации

Что такое 12-Factor App? Какие принципы важны для Rails-разработчика? Зачем знать это на новом проекте?
12-Factor App — манифест от создателей Heroku (2011). 12 правил для приложений, которые работают в облаке. Самые важные для Rails: 1. Codebase — один код в Git, много деплоев (dev, staging, production — один репозиторий) 3. Config — конфигурация в переменных окружения НЕ в коде! DATABASE_URL, SECRET_KEY_BASE, API_KEY В Rails: ENV['KEY'], credentials.yml.enc, .env 4. Backing services — БД, Redis, S3 — как ресурсы Меняешь PostgreSQL → Amazon RDS? Меняешь только URL. В Rails: config/database.yml берёт DATABASE_URL 5. Build, Release, Run — разделить стадии Build: bundle install, assets:precompile Release: container image с версией Run: запустить процесс (puma, sidekiq) 6. Processes — приложение НЕ хранит состояние Сессия — в Redis/БД, не в памяти процесса Файлы — в S3, не на диске сервера Иначе при перезапуске всё потеряется 7. Port binding — приложение self-contained Rails сам поднимает HTTP-сервер (Puma) Не нужен Apache/Nginx перед ним (в development) 10. Dev/Prod parity — минимум различий Одинаковые версии Ruby, PostgreSQL, Redis Docker помогает: одинаковый контейнер везде Как ИИ помогает: ИИ сгенерирует docker-compose.yml по 12-factor. ИИ проверит config на хардкод секретов. Но ИИ НЕ знает: какие переменные нужны именно вам, как устроен ваш деплой-процесс.
Средне

Ruby on Rails / Новые проекты

CI/CD Pipeline: GitHub Actions для Rails

Нужно настроить автоматическую проверку и деплой для нового Rails-проекта. Что должно быть в pipeline?
CI (Continuous Integration) — автоматическая проверка при каждом push/PR: — bundle install — установить зависимости — rubocop — проверить стиль кода — brakeman — проверить безопасность — rspec — запустить тесты — Если хоть один шаг упал — PR не мержить CD (Continuous Deployment) — автоматический деплой: — После мержа в main → деплой на staging — После тега (v1.2.0) → деплой на production — Rollback: вернуться к предыдущей версии Минимальный .github/workflows/ci.yml: name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest services: postgres: ... redis: ... steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 - run: bundle install - run: bundle exec rubocop - run: bundle exec rspec Что добавляют на grown-up проектах: — Parallel jobs (rspecsplit на 4 параллельных потока) — Code coverage (SimpleCov + Coveralls) — Deploy на Heroku/AWS/VPS после мержа — Slack-уведомления о падениях Как ИИ помогает: ИИ сгенерирует CI/CD конфиг с нуля за минуту. ИИ исправит упавший pipeline по логу ошибки. Но ИИ НЕ знает: какие у вас секреты в GitHub Secrets, как устроен ваш деплой на конкретный сервер.
Средне

Ruby on Rails / Новые проекты

Puma: настройка workers и threads

Rails-приложение запущено на Puma. Что означают workers, threads, preload_app? Как настроить под свой сервер?
Puma — веб-сервер для Rails (по умолчанию с Rails 5+). Workers (процессы): — Каждый worker — отдельный процесс Ruby — Изолированная память (нет shared state) — Больше workers = больше памяти — Стабильность: если один worker упал, остальные живут — workers_count = CPU cores (обычно) Threads (потоки внутри worker): — Делят память внутри процесса — Rails thread-safe благодаря GVL (GIL) — Количество threads = сколько запросов обрабатывает один worker одновременно — threads 5, 5 — минимум и максимум одинаковые — threads_count = 5 (для I/O-тяжёлых можно больше) preload_app: — Загрузить приложение ОДИН раз перед fork workers — Экономит память (copy-on-write) — НО: нужно reconnect к БД после fork! config/puma.rb: workers Integer(ENV.fetch("WEB_CONCURRENCY") { 2 }) threads_count = Integer(ENV.fetch("RAILS_MAX_THREADS") { 5 }) threads threads_count, threads_count preload_app! plugin :tmp_restart Формула: сервер на 1 ГБ RAM ≈ 2-3 workers × 5 threads Каждый worker ≈ 150-300 МБ (зависит от приложения) Как ИИ помогает: ИИ сгенерирует config/puma.rb под характеристики сервера. ИИ объяснит, почему 50 threads — это плохо. Но ИИ НЕ знает: реальное потребление памяти вашего приложения. Нужно измерить (monitoring) — потом настроить.
Средне

Ruby on Rails / Новые проекты

Error Handling API: как возвращать ошибки

API возвращает ошибки. Какой формат правильный? HTTP-статусы + JSON body? Что возвращать при разных ошибках?
Правильный формат ошибки в API: — HTTP статус — МАШИНА понимает что случилось — JSON body — ЧЕЛОВЕК (фронтендер) понимает детали HTTP статусы (самые частые): 400 Bad Request — клиент послал ерунду 401 Unauthorized — не авторизован (нет токена) 403 Forbidden — авторизован, но нет прав 404 Not Found — ресурс не найден 422 Unprocessable Entity — валидация не прошла 500 Internal Server Error — наша вина (баг) Формат JSON body (JSON:API): { "errors": [ { "status": "422", "title": "Validation Error", "detail": "Email не может быть пустым", "source": { "pointer": "/data/attributes/email" } } ] } Или проще (многие так делают): { "error": "Email не может быть пустым", "code": "validation_error" } В Rails: rescue_from ActiveRecord::RecordNotFound do |e| render json: { error: "Not found" }, status: :not_found end rescue_from ActiveRecord::RecordInvalid do |e| render json: { errors: e.record.errors.full_messages }, status: :unprocessable_entity end Что НЕ делать: — Не возвращать 200 с {"error": "..."} — это ломает контракт — Не возвращать stack trace в production (безопасность!) — Не использовать один статус для всех ошибок Как ИИ помогает: ИИ сгенерирует error handler для всего API. ИИ подскажет правильный HTTP статус по ситуации. Но ИИ НЕ знает: какие ошибки ожидает ваш фронтенд, какой формат уже используется в существующих эндпоинтах.
Средне

Ruby on Rails / Новые проекты

Поиск: PostgreSQL full-text → Elasticsearch

Нужен поиск по приложениям. Когда достаточно PostgreSQL, а когда нужен Elasticsearch/Meilisearch? Как развивать поиск?
Уровень 1 — ILIKE (самый простой): Task.where("title ILIKE ?", "%#{params[:q]}%") — Работает для 100-1000 записей — Не понимает морфологию ( "задач" ≠ "задача") — Медленный на больших таблицах Уровень 2 — PostgreSQL Full-Text Search: Task.where("to_tsvector('russian', title) @@ to_tsquery('russian', ?)", params[:q]) — Понимает морфологию: "задача" найдёт "задачи" — Ранжирование по релевантности (ts_rank) — Можно добавить индекс GIN для скорости — Достаточно для 90% Rails-проектов Уровень 3 — Elasticsearch / Meilisearch / Typesense: — Когда нужен поиск по НЕСКОЛЬКИМ моделям одновременно — Когда нужен autocomplete (подсказки при вводе) — Когда нужен faceted search (фильтры + подсчёт) — Когда данных миллионы Meilisearch — проще для нового проекта: — Устанавливается как Docker-контейнер — Индексация через гем meilisearch-rails — Из коробки: typos, фильтры, сортировка Стратегия для нового проекта: 1. Начни с PostgreSQL full-text 2. Добавь индекс GIN 3. Когда PostgreSQL перестанет справляться — добавь Meilisearch Как ИИ помогает: ИИ напишет scope для полнотекстового поиска. ИИ настроит интеграцию с Meilisearch за минуты. Но ИИ НЕ знает: какие поля важны для поиска, какие фильтры нужны пользователям.
Сложно

Ruby on Rails / Новые проекты

Multitenancy: SaaS с несколькими клиентами

Нужно сделать SaaS-приложение, где у каждого клиента свои данные. Как изолировать данные? Какой подход выбрать?
Multitenancy — одна программа, много клиентов (tenants). Примеры: Shopify, Notion, Basecamp. Подход 1 — Shared Database, Shared Schema: — Все данные в одной БД, таблица tenants — Каждая запись: tenant_id — Все запросы: WHERE tenant_id = current_tenant — Плюсы: просто начать, одна БД — Минусы: легко забыть WHERE и показать чужие данные — Гем: acts_as_tenant Подход 2 — Shared Database, Separate Schema: — Одна БД PostgreSQL, но у каждого tenant свой schema — tenant_a.users, tenant_b.users — Плюсы: изоляция данных на уровне БД — Минусы: миграции нужно прогонять по всем schemas — Гем: apartment Подход 3 — Separate Database: — У каждого tenant своя база данных — Плюсы: полная изоляция — Минусы: дорого, сложно управлять миграциями Что выбрать для нового проекта: — Начни с подхода 1 (tenant_id + acts_as_tenant) — Добавь Row-Level Security в PostgreSQL: ALTER TABLE tasks ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON tasks USING (tenant_id = current_setting('app.tenant_id')::uuid); — Это НЕ пропустит запрос без tenant_id даже если забыл WHERE Как ИИ помогает: ИИ сгенерирует мультитенантную модель с scope. ИИ настроит acts_as_tenant за минуты. Но ИИ НЕ знает: какие данные общие между tenants, как будет выглядеть биллинг, нужна ли кастомизация.
Средне

Ruby on Rails / Новые проекты

Testing Strategy: что тестировать в новом проекте

Начинаешь новый проект. Что тестировать? Что НЕ тестировать? Как не потратить половину времени на тесты?
Пирамида тестов для нового Rails-проекта: Снизу вверх: 1. Unit-тесты (модели) — МНОГО, быстрые: — Валидации (presence, uniqueness, format) — Методы модели (calculate_total, full_name) — Scopes (active, recent, by_user) — Это основа, без неё никак 2. Integration-тесты (сервисы, взаимодействия) — средне: — Service Objects (CreateOrder, ImportData) — Background Jobs (perform) — Mailers (что письмо отправляется) — Взаимодействие модель + событие 3. System-тесты (пользовательские сценарии) — мало: — Критические пути: регистрация, оплата, создание — НЕ тестируй каждый click и hover — 5-10 system-тестов на весь проект Что НЕ тестировать: — Rails-фреймворк (belongs_to работает — тестирует Rails) — Гемы (Devise логинит — тестирует Devise) — Simplecov (покрытие) — не цель, а инструмент — Очевидные вещи (getter/setter) Правило для нового проекта: — Начни с тестов моделей (самые полезные) — Добавь 3-5 system-тестов на критические пути — Остальное — по мере роста Как ИИ помогает: ИИ сгенерирует тест из контроллера или модели. ИИ напишет FactoryBot-фабрику по схеме. Но ИИ НЕ знает: какие сценарии критичны для бизнеса, какие edge cases были в продакшене.
Средне

Ruby on Rails / Новые проекты

Hotwire vs SPA: когда какой фронтенд

Начинаешь новый Rails-проект. Какой фронтенд выбрать: Hotwire (Turbo + Stimulus) или SPA (React/Vue)? Когда какой подход лучше?
Hotwire (Turbo + Stimulus): — Сервер рендерит HTML, JS только обновляет части — Turbo Drive — навигация без перезагрузки страницы — Turbo Frames — обновление частей страницы — Turbo Streams — real-time обновления через WebSocket — Stimulus — лёгкий JS для интерактивности — Плюсы: один язык (Ruby), меньше кода, SEO из коробки — Минусы: сложная интерактивность (drag-and-drop, complex forms) — мучительно SPA (React / Vue / Svelte): — Бэкенд — JSON API, фронтенд — отдельное приложение — Плюсы: полная свобода UI, rich interactions, огромная экосистема компонентов — Минусы: два стека, два деплоя, больше сложности, SEO нужен SSR (Next.js / Nuxt) Когда Hotwire: — CRUD-приложения (админки, CRM, блоги) — Команда знает Ruby, но не JS — Нужен быстрый MVP — SEO важен (контентные сайты) — 80% Rails-проектов Когда SPA: — Real-time dashboard с графиками — Сложные формы (конструкторы, редакторы) — Мобильное приложение (React Native — тот же React) — Фронтенд-команда отдельно от бэкенда Стратегия для нового проекта: 1. Начни с Hotwire 2. Когда Hotwire начинает мешать — выдели ЭТУ часть в SPA 3. Не нужно "всё или ничего" — можно миксовать Как ИИ помогает: ИИ пишет Turbo/Stimulus код так же легко как React. ИИ переведёт ERB-шаблон в React-компонент. Но ИИ НЕ знает: какие навыки у вашей команды, какая интерактивность нужна пользователям.
Средне

Ruby on Rails / Новые проекты

Feature Flags: Flipper для безопасных релизов

Нужно выкатить новую фичу, но боишься, что всё сломается. Как выкатывать безопасно? Что такое Feature Flags?
Feature Flag (фича-флаг) — переключатель функции в коде: — Включён → пользователь видит новую фичу — Выключен → пользователь видит старый вариант — БЕЗ нового деплоя Гем Flipper: # Включить фичу для всех Flipper.enable(:new_dashboard) # Включить для конкретного пользователя Flipper.enable_actor(:new_dashboard, user) # Включить для 10% пользователей Flipper.enable_percentage(:new_dashboard, 10) # В коде: if Flipper.enabled?(:new_dashboard, current_user) render "new_dashboard" else render "old_dashboard" end Сценарии использования: 1. Canary release — выкатили на 1% пользователей, смотрим метрики. Если ок — увеличиваем до 100% 2. A/B тестирование — 50% видят вариант А, 50% — вариант Б. Смотрим конверсию 3. Kill switch — если фича сломалась, выключаем одной кнопкой БЕЗ отката 4. Preview для QA — включили фичу только для тестировщиков, они проверяют на production Адаптеры хранения (Flipper): — Flipper-ActiveRecord — в БД (для начала) — Flipper-Redis — в Redis (быстрее) — Flipper UI — веб-интерфейс для управления флагами Правило: удаляй флаги после раскатки! Флаги — временный инструмент, не архитектура. Как ИИ помогает: ИИ добавит Flipper в проект за минуты. ИИ обернёт код в if Flipper.enabled? по описанию. Но ИИ НЕ знает: какие метрики смотреть при canary, каков порог "достаточно хороший" результат.
Средне

Ruby on Rails / Новые проекты

Docker Compose: среда разработки с нуля

Новый проект, нужно настроить среду разработки через Docker. Что должно быть в docker-compose.yml? Как не усложнить?
Docker Compose — один файл, одна команда, вся среда разработки готова. Минимальный docker-compose.yml для Rails: services: db: image: postgres:16 environment: POSTGRES_PASSWORD: password volumes: - pgdata:/var/lib/postgresql/data ports: - "5432:5432" redis: image: redis:7 ports: - "6379:6379" sidekiq: build: . command: bundle exec sidekiq depends_on: [db, redis] env_file: .env volumes: pgdata: Что нужно для нового проекта: — PostgreSQL — основная БД — Redis — кэш, сессии, Sidekiq — Sidekiq — фоновые задачи — (опционально) Meilisearch — если нужен поиск — (опционально) Mailpit/MailCatcher — тестовая почта Что НЕ контейнеризировать в development: — Само Rails-приложение! Запускай локально: bin/rails s — быстрее, проще дебажить — Только внешние сервисы (db, redis) в Docker .env для development: DATABASE_URL=postgres://postgres:password@localhost:5432/myapp_dev REDIS_URL=redis://localhost:6379/0 SECRET_KEY_BASE=dev_only_secret После git clone новый разработчик делает: cp .env.example .env docker compose up -d bin/setup bin/rails s Как ИИ помогает: ИИ сгенерирует docker-compose.yml под стек. ИИ добавит любой сервис (Elasticsearch, MinIO). Но ИИ НЕ знает: какие порты заняты на вашей машине, какие сервисы нужны именно этому проекту.
Легко

Сети / Интернет и браузер

Веб-приложение и клиент-серверная архитектура

Что такое веб-приложение? Что такое клиент-серверная архитектура?
Веб-приложение — программа, доступная через браузер по сети (HTTP). Работает по клиент-серверной архитектуре: Клиент (браузер) — отправляет запросы, отображает UI. Сервер — обрабатывает запросы, работает с БД, возвращает ответ. Клиент и сервер общаются через протокол HTTP. Клиент не знает, как устроен сервер. Сервер не знает, кто клиент. В Rails: браузер → роутер → контроллер → модель → БД → view → HTML → браузер.
Средне

Сети / Интернет и браузер

Что происходит при вводе URL в браузере

Когда пользователь вводит запрос в адресной строке браузера или кликает на ссылку, что происходит? Расскажи про этапы рендера.
1. DNS-запрос — браузер резолвит домен в IP-адрес 2. TCP-соединение — handshake (SYN → SYN-ACK → ACK) 3. TLS-рукопожатие — если HTTPS 4. HTTP-запрос — браузер отправляет GET-запрос 5. Сервер обрабатывает запрос и возвращает HTTP-ответ (HTML) 6. Браузер начинает рендер: - Парсинг HTML → DOM-дерево - Парсинг CSS → CSSOM - Композиция DOM + CSSOM → Render Tree - Layout (вычисление размеров и позиций) - Paint (отрисовка пикселей) - Composite (сборка слоёв) 7. Загрузка и выполнение JS (блокирует рендер, если нет async/defer)
Легко

Сети / HTTP и протоколы

Как работает HTTP

Как работает HTTP? Из чего состоит HTTP-запрос?
HTTP — текстовый протокол прикладного уровня (клиент-серверный). HTTP-запрос состоит из трёх частей: 1. Стартовая строка: METHOD /path HTTP/1.1 2. Заголовки (headers): Host, Content-Type, Authorization и т.д. 3. Тело (body): опционально, данные для POST/PUT/PATCH Пример: GET /users HTTP/1.1 Host: example.com Accept: application/json HTTP-ответ: 1. Стартовая строка: HTTP/1.1 200 OK 2. Заголовки 3. Тело (HTML, JSON, файл и т.д.)
Легко

Сети / HTTP и протоколы

Стартовая строка HTTP-запроса

Из чего состоит стартовая строка HTTP-запроса?
Стартовая строка (request line) состоит из трёх элементов: 1. HTTP-метод: GET, POST, PUT, PATCH, DELETE 2. URI: /users/1?page=2 3. Версия протокола: HTTP/1.1 Пример: GET /users/1 HTTP/1.1 Для ответа (status line): HTTP/1.1 200 OK Состоит из: версия, статус-код, причина (reason phrase).
Легко

Сети / HTTP и протоколы

Структура HTTP-запроса и тело

Что из себя представляет HTTP-запрос? Структура? Есть ли тело у всех HTTP-запросов?
HTTP-запрос — текстовое сообщение от клиента серверу. Структура: 1. Стартовая строка (method + URI + version) 2. Заголовки (Content-Type, Authorization, Accept ...) 3. Пустая строка (разделитель) 4. Тело (body) — опционально Тело есть НЕ у всех запросов: - GET, HEAD, DELETE, OPTIONS — обычно без тела - POST, PUT, PATCH — с телом (JSON, form-data, файл) Тело может отсутствовать даже у POST (не рекомендуется, но допустимо).
Легко

Сети / HTTP и протоколы

HTTP vs HTTPS

В чем отличие протокола HTTP от HTTPS?
HTTPS = HTTP + TLS/SSL (шифрование). HTTP: - Данные передаются в открытом виде - Порт 80 - Уязвим к перехвату (MITM) HTTPS: - Данные зашифрованы (TLS) - Порт 443 - Проверка подлинности сервера через сертификат - Современный стандарт — TLS 1.3 Без HTTPS нельзя: HSTS, HTTP/2 (практически), trustworthy API. В Rails: force_ssl в конфиге принудительно перенаправляет на HTTPS.
Средне

Сети / HTTP и протоколы

PUT vs PATCH

Чем PUT-запрос отличается от PATCH?
PUT — полная замена ресурса. Нужно передать ВСЕ поля. PATCH — частичное обновление. Передаём только изменённые поля. Пример (ресурс user: {name: "Alice", email: "a@mail.ru"}): PUT /users/1 → заменяет целиком { "name": "Bob", "email": "b@mail.ru" } PATCH /users/1 → обновляет только name { "name": "Bob" } Если в PUT не указать email — он станет null. В Rails: update (PATCH) vs update! — но оба делают PATCH по REST-конвенции.
Средне

Сети / HTTP и протоколы

PUT vs POST и идемпотентность

Чем PUT отличается от POST? Что такое идемпотентность HTTP-методов?
POST — создание нового ресурса. Не идемпотентный. PUT — замена существующего ресурса. Идемпотентный. Идемпотентность — повторный запрос даёт тот же результат: POST /users → создаёт нового пользователя каждый раз (3 запроса = 3 записи) PUT /users/1 → обновляет одну и ту же запись (3 запроса = 1 запись) Таблица идемпотентности: GET — идемпотентный, безопасный PUT — идемпотентный PATCH — может быть идемпотентным DELETE — идемпотентный POST — НЕ идемпотентный
Легко

Сети / HTTP и протоколы

GET vs POST

В чем разница между GET и POST?
GET: - Получение данных (чтение) - Параметры в URL (query string): /users?name=alice - Безопасный (не меняет состояние сервера) - Идемпотентный - Кэшируется браузером - Тело игнорируется (по спецификации) POST: - Создание/отправка данных - Параметры в теле запроса - НЕ безопасный (меняет состояние) - НЕ идемпотентный (повтор = новая запись) - Не кэшируется В формах Rails: method: :get для поиска, method: :post для создания.
Средне

Сети / HTTP и протоколы

GET с телом запроса

Может ли GET-запрос иметь тело? Можно ли в теле GET-запроса отправить картинку на сервер?
По RFC 7231: у GET нет запрещённого тела, но оно не имеет семантики. Сервер МОЖЕТ проигнорировать тело GET-запроса. Некоторые прокси и кэши отбрасывают тело GET. Отправить картинку через GET — плохая практика: - GET предназначен для чтения - Картинка — изменение состояния сервера → нужен POST - URL имеет ограничение длины Правильно: POST /uploads с файлом в теле (multipart/form-data) или PUT /images/123 с бинарником в теле В практике: GET с телом — почти всегда ошибка проектирования.
Легко

Сети / HTTP и протоколы

GET с телом в теории

Можно ли в GET в теории поместить тело?
Технически — да. HTTP-спецификация не запрещает тело в GET. Но: - RFC 7231 говорит: тело запроса GET не имеет определённой семантики - Сервер МОЖЕТ его проигнорировать - Прокси, кэши и фреймворки могут отбросить тело - Elasticsearch использовал GET с телом для поиска — это известное исключение, но его критиковали На практике: никогда не полагайтесь на тело в GET. Используйте query-параметры или POST для сложных запросов.
Легко

Сети / HTTP и протоколы

HTTP-метод для частичного обновления по REST

Какой HTTP-метод используется для обновления небольшого кусочка по REST?
PATCH — метод для частичного обновления ресурса. Пример: PATCH /users/1 { "email": "new@mail.ru" } Обновит только email, остальные поля останутся без изменений. PUT заменил бы ресурс целиком (все поля обязательны). В Rails routes: resources :users — автоматически добавляет PATCH.
Легко

Сети / HTTP и протоколы

Приватный ресурс без авторизации

Что будет, если попытаться обратиться к приватному ресурсу без авторизации?
Сервер вернёт ошибку: 401 Unauthorized — если пользователь не прошёл аутентификацию (нет токена, неверный логин/пароль) 403 Forbidden — если пользователь аутентифицирован, но нет прав на этот ресурс Разница: 401 — «Кто ты? Представься» 403 — «Я знаю кто ты, но тебе сюда нельзя» В Rails: before_action :authenticate_user! → 401 если не залогинен before_action :authorize_admin! → 403 если не админ CanCanCan / Pundit — для авторизации (403)
Сложно

Сети / HTTP и протоколы

REST API для каталога автомобилей

Как бы вы спроектировали REST API для каталога автомобилей (ресурсы, методы, коды ответов)?
Ресурс: cars (автомобили) Метод URI Действие Код ответа ─────── ─────────────── ────────────── ────────── GET /cars Список 200 GET /cars/1 Детали 200 POST /cars Создать 201 (Created) PATCH /cars/1 Обновить часть 200 PUT /cars/1 Заменить 200 DELETE /cars/1 Удалить 204 (No Content) Фильтрация через query-параметры: GET /cars?brand=toyota&year=2024&sort=price Ошибки: 400 — невалидные параметры 401 — не авторизован 404 — /cars/999 не найден 422 — невалидные данные при создании В Rails routes: resources :cars Генерирует все 6 маршрутов автоматически.
Средне

Сети / HTTP и протоколы

Фильтрация: GET-параметры vs POST-тело

Как реализовать фильтрацию: параметры в GET vs тело POST, и когда оправдан POST для непубличных фильтров?
По REST: фильтрация — это чтение → GET с query-параметрами: GET /products?category=electronics&min_price=100 Когда оправдан POST для фильтрации: - Слишком сложные фильтры (вложенные, массивы) — тело POST удобнее - Тело GET может отбрасываться прокси - Конфиденциальные данные в фильтрах (не попадут в логи URL, историю) - ElasticSearch-style: POST /products/_search с JSON в теле Правило: если фильтр bookmarkable/cachable — GET. Если сложный или приватный — POST. В Rails: # GET Product.where(category: params[:category]) # POST с复杂 фильтрами Product.search(params[:filters])
Легко

Сети / Коды ответов и заголовки

Семейства кодов ответов HTTP

Какие семейства кодов ответов HTTP знаешь? Чем код ответа 200 отличается от 201?
Семейства (первая цифра): 1xx — информационные (100 Continue) 2xx — успех (200 OK, 201 Created, 204 No Content) 3xx — перенаправление (301 Moved Permanently, 302 Found, 304 Not Modified) 4xx — ошибка клиента (400 Bad Request, 401, 403, 404) 5xx — ошибка сервера (500 Internal Server Error, 502, 503) 200 vs 201: 200 OK — запрос успешен, тело содержит результат 201 Created — ресурс создан (POST), обычно с Location header и телом созданного ресурса В Rails: render json: @user, status: :ok # 200 render json: @user, status: :created # 201 render json: @user, status: :no_content # 204
Легко

Сети / Коды ответов и заголовки

Код ответа 400

Что такое код ответа 400? Что означает?
400 Bad Request — сервер не понял запрос из-за ошибки клиента. Причины: - Невалидный JSON в теле - Отсутствуют обязательные параметры - Неверный формат данных (строка вместо числа) - Некорректный синтаксис запроса Пример: POST /users { "name": "" } → 400 (имя не может быть пустым) В Rails: render json: { errors: user.errors }, status: :bad_request # или через respond_to_with_error
Легко

Сети / Коды ответов и заголовки

Код ответа 500

Что такое код ответа 500? Что означает?
500 Internal Server Error —通用 серверная ошибка. Что произошло: - Необработанное исключение в коде - Ошибка подключения к БД - Ошибка в бизнес-логике Важно: 500 не раскрывает детали ошибки клиенту (безопасность). Детали логируются на сервере (Rails: log/production.log). Родственные коды: 502 Bad Gateway — прокси не получил ответ от upstream 503 Service Unavailable — сервер перегружен / на обслуживании В Rails: rescue_from StandardError do |e| Rails.logger.error e.message render json: { error: "Internal error" }, status: 500 end
Легко

Сети / Коды ответов и заголовки

Часто используемые HTTP-заголовки

Какие чаще всего используются заголовки? Приведи примеры.
Заголовки запроса (Request): Host: example.com — целевой хост (обязательный в HTTP/1.1) Content-Type: application/json — формат тела Authorization: Bearer token — аутентификация Accept: application/json — ожидаемый формат ответа User-Agent: Mozilla/5.0 — информация о клиенте Cookie: session=abc123 — куки Заголовки ответа (Response): Content-Type: text/html — формат тела ответа Set-Cookie: token=xyz — установить куку Location: /users/5 — URI нового ресурса (при 201) Cache-Control: no-cache — управление кэшем Access-Control-Allow-Origin: * — CORS В Rails: response.headers["X-Custom"] = "value" render json: data, content_type: "application/json"
Средне

Сети / Коды ответов и заголовки

Редирект и ссылка в ответе

Если мы сделали запрос, и произошел редирект, как достать ссылку, которая ведет к редиректу из этого запроса?
При редиректе сервер возвращает: - Статус-код: 301, 302, 303, 307 или 308 - Заголовок Location: https://new-url.com/path Новый URL берётся из заголовка Location. Коды редиректа: 301 — постоянный (кэшируется, метод может смениться на GET) 302 — временный (браузер может сменить метод) 307 — временный, метод сохраняется 308 — постоянный, метод сохраняется В Rails: redirect_to @user # 302 по умолчанию redirect_to @user, status: 301 HTTP-клиенты (Ruby): response = Net::HTTP.get_response(URI(url)) response["Location"] # → URL редиректа response.code # → "302"
Средне

Сети / API и тестирование

SOAP vs REST

В чем разница SOAP от REST API?
SOAP — протокол, REST — архитектурный стиль. SOAP: - Строгий стандарт (WSDL-контракт) - Только XML - Встроенная обработка ошибок, безопасность (WS-Security) - Тяжёлый, много boilerplate - Используется в enterprise, банках, платёжных системах REST: - Лёгкий, использует стандарты HTTP (методы, коды, заголовки) - Любой формат (JSON, XML, HTML) - Нет строгого контракта (OpenAPI — опционально) - Stateless - Де-факто стандарт для веб-API Сравнение: SOAP REST Формат XML только Любой (JSON) Протокол Свой HTTP Контракт WSDL OpenAPI (опц.) Сложность Высокая Низкая Кэширование Нет Через HTTP
Легко

Сети / API и тестирование

Что такое REST API

Что такое REST API?
REST (Representational State Transfer) — архитектурный стиль для API. Принципы: 1. Ресурсы — всё есть ресурс (URI): /users, /posts/1 2. HTTP-методы — CRUD через GET/POST/PUT/PATCH/DELETE 3. Stateless — каждый запрос содержит всю нужную информацию 4. Единый интерфейс — стандартные методы и коды ответов 5. Представление — клиент получает представление ресурса (JSON) Пример REST API для постов: GET /posts — список GET /posts/1 — один пост POST /posts — создать PATCH /posts/1 — обновить DELETE /posts/1 — удалить REST не протокол, а набор рекомендаций. Строгое следование — редкость.
Легко

Сети / API и тестирование

SOAP и JSON

Можно ли в SOAP отправить JSON?
Нет. SOAP по спецификации использует только XML. SOAP-сообщение — это XML-конверт (Envelope): <soap:Envelope> <soap:Header>...</soap:Header> <soap:Body>...</soap:Body> </soap:Envelope> JSON не может заменить XML в SOAP — структура конверта требует XML. Если нужен JSON — используйте REST API или gRPC (с protobuf).
Средне

Сети / API и тестирование

Как протестировать API

Как протестировать API веб-приложения? Какие инструменты применимы?
Уровни тестирования API: 1. Ручное тестирование (exploratory): - curl: curl -X POST http://api.example.com/users -d '{"name":"A"}' - Postman / Insomnia — GUI-клиенты - HTTPie — curl с человеческим лицом 2. Интеграционные тесты: - Rails: ActionDispatch::IntegrationTest - RSpec: request specs (spec/requests/) - Проверяют полный цикл: запрос → роут → контроллер → БД → ответ 3. Контрактные тесты: - OpenAPI / Swagger — валидация по схеме 4. Нагрузочное тестирование: - Apache JMeter, k6, Locust Что проверять: - Статус-коды (200, 201, 400, 404) - Структуру JSON-ответа - Валидацию данных - Аутентификацию/авторизацию - Граничные случаи
Легко

Сети / API и тестирование

Инструменты для тестирования API

Какие инструменты для тестирования API можешь назвать?
GUI-клиенты: Postman — самый популярный, коллекции, автотесты Insomnia — легче Postman, open-source Bruno — offline, файлы хранятся рядом с кодом CLI: curl — стандарт де-факто, есть везде HTTPie — curl с удобным синтаксисом: http POST api.com/users name=Alice В коде: RSpec + rack-test (request specs) Minitest + ActionDispatch::IntegrationTest Нагрузка: k6 — JS-скрипты, современный Apache JMeter — Java, мощный, сложный Locust — Python Документация + тестирование: Swagger UI — интерактивная документация по OpenAPI
Легко

Сети / API и тестирование

Библиотеки для HTTP-запросов

Какие библиотеки использовал для HTTP-запросов?
Ruby: Net::HTTP — стандартная библиотека (встроена) HTTParty — простой DSL для API-клиентов Faraday — гибкий, middleware-based (от команды Omise) RestClient — простой REST-клиент Typhoeus — параллельные запросы (обёртка над libcurl) Пример HTTParty: class GithubApi include HTTParty base_uri "api.github.com" def user(name) self.class.get("/users/#{name}") end end В Rails-тестах: get "/users", headers: { "Authorization" => token } post "/users", params: { name: "Alice" }, as: :json
Легко

Сети / API и тестирование

Что такое токен

Что такое токен? Для чего используется?
Токен — строка, подтверждающая аутентификацию пользователя. Альтернатива сессиям (cookies) для API. JWT (JSON Web Token) — популярный формат: header.payload.signature Как работает: 1. Пользователь логинится (email + пароль) 2. Сервер генерирует JWT, отправляет клиенту 3. Клиент отправляет токен в каждом запросе: Authorization: Bearer eyJhbGciOiJIUzI1NiIs... 4. Сервер проверяет подпись токена (без запроса в БД — stateless) Зачем: - Stateless аутентификация (масштабирование) - Мобильные приложения (cookies неудобны) - SSO (Single Sign-On) - Микросервисы (один токен для всех сервисов) В Rails: gem devise-jwt, gem jwt
Легко

Сети / API и тестирование

Авторизация

Что отвечает за авторизацию?
Авторизация — проверка прав доступа (что пользователь может делать). Аутентификация (кто ты?) vs Авторизация (что тебе можно?). Инструменты в Rails: Pundit — простой, policy-объекты для каждого ресурса CanCanCan — ability-класс, проверка через can? :edit, @post Пример Pundit: # app/policies/post_policy.rb class PostPolicy < ApplicationPolicy def update? record.user == user || user.admin? end end # Контроллер authorize @post # → вызовет PostPolicy#update? HTTP-уровень: 401 Unauthorized — не аутентифицирован 403 Forbidden — не авторизован (нет прав)
Легко

Сети / Форматы данных

Что такое JSON

Что такое JSON?
JSON (JavaScript Object Notation) — текстовый формат обмена данными. Типы данных: строка, число, boolean, null, массив, объект. Пример: { "name": "Alice", "age": 30, "active": true, "tags": ["ruby", "rails"], "address": null } Почему популярен: - Легче XML (нет закрывающих тегов) - Нативная поддержка в JS (JSON.parse / JSON.stringify) - Человекочитаемый - Поддерживается всеми языками В Ruby: require "json" JSON.parse('{"name":"Alice"}') # => {"name"=>"Alice"} {name: "Alice"}.to_json # => '{"name":"Alice"}' В Rails: render json: @users — автоматическая сериализация.
Легко

Сети / Форматы данных

Тело HTTP-запроса и форматы

Что может быть в теле HTTP-запроса? Какие форматы данных могут передаваться?
Тело запроса (body) может содержать любые данные. Формат указывается в заголовке Content-Type. Форматы: application/json — JSON (самый частый для API) application/x-www-form-urlencoded — данные формы (key=value&key2=value2) multipart/form-data — файлы + поля формы text/xml — XML (SOAP) text/plain — простой текст application/octet-stream — бинарные данные (файл) Примеры: Content-Type: application/json {"title": "Hello", "body": "World"} Content-Type: multipart/form-data [файл image.png + поле name=Alice]
Легко

Сети / Форматы данных

Тело HTTP-ответа и форматы

Что приходит в теле ответа от сервера? Какие бывают форматы?
Тело ответа зависит от заголовка Content-Type: Частые форматы: text/html — HTML-страница (браузер рендерит) application/json — JSON (для API, SPA) application/xml — XML (SOAP, RSS) text/css — стили application/javascript — JS-скрипт image/png, image/jpeg — картинка application/octet-stream — бинарный файл (скачивание) Пример (JSON API): HTTP/1.1 200 OK Content-Type: application/json {"id": 1, "name": "Alice", "email": "alice@mail.ru"} Пустое тело: 204 No Content — пустой ответ (после DELETE) 304 Not Modified — пустой ответ (кэш актуален) В Rails: respond_to do |format| format.html format.json { render json: @users } end
Средне

Сети / Сети и уровни

TCP vs UDP

В чем различие TCP и UDP?
TCP (Transmission Control Protocol): - Надёжная доставка (гарантия порядка, повтор при потере) - Установление соединения (3-way handshake) - Контроль потока и перегрузки - Медленнее UDP - Используется: HTTP, SSH, SMTP, FTP UDP (User Datagram Protocol): - Без гарантии доставки (fire-and-forget) - Нет соединения, нет порядка пакетов - Быстрее, меньше накладных расходов - Используется: DNS, видеостриминг, онлайн-игры, VoIP Сравнение: TCP UDP Надёжность Да Нет Скорость Медленнее Быстрее Порядок Гарантирован Нет Соединение Нужно Не нужно Примеры HTTP, SSH DNS, видео QUIC (HTTP/3) — поверх UDP, но с надёжностью TCP.
Средне

Сети / Сети и уровни

Модель OSI и уровень HTTP

Расскажи про OSI. На каком уровне находится HTTP?
OSI — 7 уровней сетевой модели: 7. Application — HTTP, FTP, DNS, SMTP (для пользователя) 6. Presentation — SSL/TLS, JPEG, JSON (кодирование/шифрование) 5. Session — управление сессиями 4. Transport — TCP, UDP (доставка между хостами) 3. Network — IP, маршрутизация 2. Data Link — Ethernet, MAC-адреса 1. Physical — кабели, радиоволны, биты HTTP — уровень 7 (Application). TCP — уровень 4 (Transport). IP — уровень 3 (Network). Упрощённая модель TCP/IP (4 уровня): Application → HTTP, DNS Transport → TCP, UDP Internet → IP Network Access → Ethernet, Wi-Fi
Средне

Сети / Сети и уровни

Что такое CORS

Что такое CORS? Это связано именно с HTTP?
CORS (Cross-Origin Resource Sharing) — механизм безопасности браузера. Регулирует: может ли скрипт с origin A обращаться к API на origin B. Origin = протокол + домен + порт. https://app.com ≠ http://app.com ≠ https://api.app.com Без CORS браузер блокирует запрос (Same-Origin Policy). Сервер должен вернуть заголовок: Access-Control-Allow-Origin: https://app.com или Access-Control-Allow-Origin: * (разрешить всем) Preflight-запрос: Браузер сначала отправляет OPTIONS-запрос. Если сервер разрешает — браузер отправляет основной запрос. CORS — это механизм HTTP (использует заголовки). Но ограничение налагает БРАУЗЕР, не сервер. curl/Postman — не подвержены CORS. В Rails: gem "rack-cors" config.middleware.use Rack::Cors do allow { origins "*"; resource "*", headers: :any, methods: :any } end
Сложно

Сети / Микросервисы

Коммуникация между микросервисами

Какие способы коммуникации между микросервисами знаешь: брокеры сообщений (например, Kafka), REST, RPC? Их особенности, плюсы и минусы.
Три основных способа: 1. REST (HTTP API): + Простота, стандарты HTTP + Легко отладить (curl, Postman) - Синхронный — ждёт ответ (coupling по времени) - Зависимость от доступности сервиса 2. RPC (gRPC, JSON-RPC): + Быстрый (бинарный протокол в gRPC) + Строгая типизация (protobuf) - Синхронный - Тесное связывание (shared contract) - Сложнее отладить 3. Брокеры сообщений (Kafka, RabbitMQ): + Асинхронный — отправитель не ждёт + Loose coupling — сервисы не знают друг о друге + Буферизация при сбоях (durable queues) - Сложность инфраструктуры - Eventual consistency - Сложнее отладить Выбор: REST — простые запросы, CRUD RPC — высокая производительность между своими сервисами Kafka — событийная архитектура, логирование, streaming
Сложно

Сети / Микросервисы

Партиции в Kafka

Что такое партиции (partitions) в Kafka? Как они устроены и зачем нужны?
Партиция — единица параллелизма в Kafka. Topic разбит на партиции — упорядоченные, append-only логи. Каждая партиция хранится на одной машине (replica на других). Зачем: - Параллелизм — разные consumer-ы читают разные партиции одновременно - Масштабирование —吞吐量 пропорциональна количеству партиций - Порядок гарантируется ВНУТРИ партиции (не между) Как сообщение попадает в партицию: - По ключу: hash(key) % num_partitions → одна партиция (сообщения с одним ключом в одном порядке) - Без ключа: round-robin (произвольная партиция) Consumer Group: - Каждый consumer в группе читает свои партиции - Партиций ≥ consumer-ов (иначе кто-то простаивает) Schema: Topic "orders" ├── Partition 0: [msg1, msg2, msg3, ...] ├── Partition 1: [msg4, msg5, msg6, ...] └── Partition 2: [msg7, msg8, msg9, ...]
Сложно

Сети / Микросервисы

Transactional Outbox

Что такое паттерн transactional outbox? Для чего нужен? Какие альтернативы существуют?
Проблема: нужно обновить БД и отправить сообщение в Kafka — атомарно. Если отправить в Kafka, а потом БД упадёт — сообщение уже ушло (орфанное). Если сначала БД, потом Kafka — можем забыть отправить. Transactional Outbox: В той же транзакции записываем сообщение в outbox-таблицу: BEGIN UPDATE orders SET status = 'paid' WHERE id = 1 INSERT INTO outbox (event_type, payload) VALUES ('order.paid', '{...}') COMMIT Отдельный процесс (CDC / poller) читает outbox и отправляет в Kafka. Плюсы: - Атомарность (одна транзакция) - Гарантия доставки (outbox не потеряется) - At-least-once доставка Альтернативы: - Transactional messaging (Kafka transactions) — встроенная поддержка - Dual write с компенсацией — сложнее, ненадёжно - Debezium CDC — читает WAL PostgreSQL и отправляет в Kafka напрямую В Rails: gem outboxer, или самописный poller + outbox-таблица.