Pull to refresh

Comments 31

Рельсы умирают… Никогда такого не было и вот опять!
Рельсы регулярно умирают, как минимум каждую конференцию
UFO just landed and posted this here
На самом деле доклад, с которым будет выступать Ник на Rails Club называется еще круче — «Ruby is dead». :) Но это просто провокация, литературный прием, так скажем, для привлечения внимания. То же самое можно сказать и о названии данной статьи.
Выкиньте, пожалуйста, свой traiblazer куда подальше, чтобы неокрепшие умы больше никогда его не использовали. Только выпиливать эту «красивую» абстракцию все время приходится

Про Trailblazer: начинание у него очень хорошее — этакий фреймворк над фреймворком, со своим Convention over Configuration и оговорённой структурой проекта. Т.е. человек, делавший один проект на Rails+Trailblazer будет знать где что искать и куда складывать новый код в любом другом проекте на Rails+Trailblazer. Потому что всем нужны сервисные объекты, форм-объекты, и прочая и прочая и в разных проектах все делают по разному, да даже в одном проекте кто в лес, кто по дрова. В результате переход между проектами затруднён — код приходится натурально распутывать. Первый Trb был хорош тем, что был прост, как доска, хотя и немного негибким. Второй Trailblazer сделали гибким, но вот тут-то подкрался п****ц — он перестал быть интуитивным и в документации к нему ты начинаешь проводить больше времени, чем в редакторе (и ещё больше времени в их чатике).


Operation, кстати, очень хороши для сложной «многоходовой» бизнес-логики — когда надо сформировать или распарсить замороченную Excel'ку или сделать хитрое проксирование в другой API (которое раскладывается на 2N + 3 запроса к нему) и т. п. Потому что тут начинает хорошо проявлять себя вот этот DSL операций и заморочки с прокидыванием состояния между шагами.


Это, кстати, классная идея у них: вытаскивание с помощью keyword arguments метода-шага операции нужных тебе данных из переданных в операцию параметров, зависимостей и внутреннего состояния. Получаются очень явно видны зависимости между шагами и где какие данные используются. Операции — добро.


А вот прочие компоненты Trb надо использовать с осторожностью. На вид они вкусные, но шаг влево или шаг вправо, задача чуть посложнее — и можно просто весь день убить на чтение доков, исходников и чатика Trb. А внутри там местами очень нечитаемый и магичный код. С тем же Representable я хлебнул как-то горя.


Резюме: если вы погрязли в Rails Way, модели трещат по швам от бизнес логики, любой вызов Model.first.save вызывает взлёт и крах парочки империй (и вы до сих пор не используете сервисные объекты, форм-объекты и прочее) — возьмите Trailblazer, хуже не будет. Но возьмите сначала только операции, потом, когда с ними освоитесь, прикрутите Reform. И, в принципе, на этом можно и остановиться.

А чем reform лучше active_model?


И про операции не совсем понятно. Я допускаю, что в некоторых ситуациях удобно. При этом для простых экшенов вполне достаточно ActiveRecord, а для тех что по-сложнее можно использовать ActiveModel или PORO. Операции выглядят как декларативный способ написания метода call:


class Song::Create < Trailblazer::Operation
  extend Contract::DSL

  contract do
    property :title
    validates :title, presence: true
  end

  step     Model( Song, :new )
  step     :assign_current_user!
  step     Contract::Build()
  step     Contract::Validate( )
  failure  :log_error!
  step     Contract::Persist(  )
end

# вместо простого:
class Song::Create
  def call(attrs:, user:)
    @model = Song.new(attrs)
    @model.current_user = user
    @model.save!
  rescue => e
    log_error!(e)
    raise
  end
end

При этом наверняка реализован (или нужно реализовывать) свой способ расширения, ведь super/yield так просто не получится использовать.

Reform хорош при двух вариантах:


  1. Когда данные одной формы распихиваются в несколько сущностей в БД (или даже в разные БД)
  2. Их нужно хитро преобразовывать или инициализировать. Гораздо нагляднее видеть в контракте, что есть хитрые популяторы/препопуляторы у некоторых полей.

В любом случае, Trailblazer начинает себя оправдывать, только когда логика замороченная, а приложение большое.

А чем reform лучше active_model?

Умеет работать с Nested forms и виртуальными атрибутами.

У ActiveModel все атрибуты виртуальные (если виртуальные — те, которые не хранятся в базе). Через delegate можно настроить проброс атрибутов из AR.


Nested forms — это fields_for и подобные? Тоже все работает.


С i18n непонятно. Нашел reform-rails, выглядит редко поддерживаемым, с такими вот комитами https://github.com/trailblazer/reform-rails/commit/5bd93ed1ee825f107f248c9c67e6bb81f0c75e00. В самом reform такого функционала нет.

Если все проблемы, решаемые Reform'ом, можно решить ActiveModel'ями, то пишите статьи и книги, выступайте на конференциях, и тогда это тоже начнут использовать. Успех современного OpenSource-проекта большей частью зависит от пиара и маркетинга, чем от кода. Ник давно и плотно продвигает именно свой Trailblazer, так же как и чуваки из ROM, DRY и Hanami пилят и пиарят, как могут, свои проекты, которые большей частью призваны заменить рельсу (и не все понимают, зачем её на что-то менять, если в их конкретном случае она работает хорошо и не путается под ногами), просто потому, что Rails — это омакасэ, а выбор ингридиентов и способ готовки шеф-повара DHH устраивает не всех.

Спасибо! Возможно, стоит попробовать выступить с похожей темой. Статьи в гугле находятся и про form object, и про nested attrs, но получается следует периодически дописывать новые, чтобы держать технологию на слуху.


и пиарят, как могут

Без грязи бы пиарили, вроде "рельсы мертвы", было бы лучше. Кроме этого, многие не понимают, что этот пиар — часть бизнеса, и Ник просто зарабатывает так деньги, трэилбрэйзер — это его продукт. Он пишет статьи и говорит на конференциях, чтобы зарабатывать деньги (продавать больше консультаций), в отличие от других разработчиков, у которых цель — делиться опытом или подобная. Поэтому он может тратить на это больше времени, чем другие Что он и делает.


Как и других сферах, продукт может быть и не очень хорошим, но маркетинг вытаскивает.

На мой взгляд, практически все, кто выступает на конференциях, что-либо пиарят в том или ином виде. :)
Я тоже не совсем понимаю, зачем рельсы на что-то менять, пока они работают (а они работают). Но в том, что люди пытаются придумать что-то новое, и рассказывают об этом, ничего плохого не вижу.
Название статьи, видимо, не особо удачное (и мы его сами придумали). Доклад Ника на RailsClub будет называться «Ruby Is Dead». Я спрашивал, почему, Ник ответил, что эта провокация для привлечения внимания. :)
А чем reform лучше active_model?

Если ActiveModel используется в отдельном классе (в form object, вне самой бизнес-модели), то вполне годится. Иначе (в дефолтном случае), на модель возлагается слишком много задач. Но тогда придётся мастерить свой reform.
По поводу операций, опять же, дело в SRP.
И как в «простом» варианте должно показываться пользователю что он не так заполнил в форме? В контроллере тоже должны быть перехваты исключений?

Да, я именно про ActiveModel как form object писал.


Пример я взял с http://trailblazer.to/gems/operation/2.0/. Сам я трэйлблэйзер не использовал и не знаю, что именно делает failure. Предположил, что он ловит ошибки. Ошибки валидации можно сделать доступными разными способами (все под задачу и/или по договоренностям в команде):


operation.model.errors
# или
delegate :errors, to: :model
# или
include ActiveModel::Validations
# или
def ...
  @errors = ActiveModel::Errors.new(self)
end

call вполне может быть изменен, чтобы возвращать false в случае ошибки вместо рерэйза.

failure вызывает метод обработчик, если один из предыдущих step вернул false. Он не ловит эксепшены. Если эксепшен вылетел и он нигде явно не ловится — всё, приплыли, 500.


Если где-то в операции какой-то шаг вернул false, то у возвращаемого из Trailblazer::Operation.call значения метод success? тоже будет false. А вот дальше как организовать доставание ошибок из форм-объекта или из какого-то из шагов позднее — тут у Trailblazer чёткого ответа не было (по крайней мере полгода назад).


В итоге, я пришёл к такому флоу: входные данные валидируются в операции форм-объектом, если не проходит валидацию — false и рендерим ошибки форм-объекта на форме пользователю. Если проходит валидацию — скармливаем объектам-моделям и у них вызываем save!. Если где-то в модели не проходит валидация — exception, 500 (из моделей бизнес-логика вычищается, валидации остаются минимальные, чтобы гарантировать data integrity).


operation.model.errors
# или
delegate :errors, to: :model

Именно от этого Trailblazer призывает уйти и не держать валидации в моделях. Потому что они неминуемо обрастают условными проверками на хз что и приходится пробрасывать какие-то вещи каким-то макаром из контроллеров(!!! верный звонок, что дела плохи), чтобы отключать часть валидаций из админки и т.д. и т.п.). Валидации уходят в форм-объекты. Свои для юзерской части и свои для админки. Типа того.

failure вызывает метод обработчик, если один из предыдущих step вернул false

Неявный возврат в руби не располагает к такому решению. Видимо, ещё предстоит переход на throw :abort.


В итоге, я пришёл к такому флоу ...

Ситуация "форма разрешила сохранять, а модель нет, и 500 из-за этого", не всем бы подошла. Получается в форме просто забыли добавить валидацию, которая появилась в модели. Архитектура должна защищать от таких ошибок. Думаю, общие валидации в AR/главных моделях не надо избегать. А вот админские/юзерские выносить в операции/форм-объекты/другое-название — это хорошо.


Именно от этого Trailblazer призывает уйти

Не проблема — последние 2 варианта. Еще можно объединить валидации из модели и формы так:


include ActiveModel::Validations
delegate :errors, to: :model
validates :checkbox, presence: true
validate { errors.add :name if smth? }

def valid?
  model.valid? && super
end
failure вызывает метод обработчик, если один из предыдущих step вернул false

Неявный возврат в руби не располагает к такому решению. Видимо, ещё предстоит переход на throw :abort.

Никто не запрещает делать явный возврат. Но, как правило, этого не требуется. И это, кстати, уже предусмотрено там, есть разные варианты, включая досрочное прерывание операции. А исключения уже были в первой версии и от них избавились во второй, даже #find не используется.

include ActiveModel::Validations
delegate :errors, to: :model
validates :checkbox, presence: true
validate { errors.add :name if smth? }

def valid?
  model.valid? && super
end

Так в этом случае будут доступны только ошибки из модели. И опять же, получается создание своих костылей. А в случае нескольких моделей на форме или вложенных всё становится ещё сложней. И снова получается свой аналог Reform.
А вот дальше как организовать доставание ошибок из форм-объекта или из какого-то из шагов позднее — тут у Trailblazer чёткого ответа не было (по крайней мере полгода назад).

Разве этого тогда не было?
result["contract.default"].errors.messages

Либо, если использовать гем trailblazer-rails, то в контроллере будет создаваться переменная "@form". Да и в первой версии аналогично было.

из моделей бизнес-логика вычищается, валидации остаются минимальные, чтобы гарантировать data integrity

ИМХО, это лучше реализовать в БД. Что б уж наверняка :) А 500-е исключения лучше не перехватывать, а исправлять вызвавшие их ситуации.
result["contract.default"].errors.messages

Это было. Но, блин, это же вырвиглаз вообще.


Я понимаю, что иногда зачем-то нужно делать делать несколько контрактов на операцию, но в душе никак не могу представить — зачем.

Вот для этого и сделали метод #run в контроллере, который записывает записывает контракт в "@form". Но вообще, да, это не очень удобно.

А насчёт нескольких контрактов, могу предположить, например, для разных этапов обработки заказа (или способов его создания), или для пользователей с разными доступами (к атрибутам).
Полностью поддерживаю, data integrity должна поддерживаться на уровне базы. Причем самое важное здесь, что это дает гарантию, что она действительно будет поддерживатся. Валидация в моделях такой гарантии принципиально не дает.

А пользователю тоже показывать ERROR: null value in column "username" violates not-null constraint? Изначально обсуждались валидации, результат которых виден пользователю. Целостность данных — совсем другое, trailblazer к ней мало отношения имеет (вообще не имеет?).

Пользователь в таких случаях 500-ку увидит, и это нормально. Так как попытка нарушения целостности — это именно ошибка, она должна выглядеть как ошибка и исправляться как ошибка, а не маскироваться.
А пользователю тоже показывать ERROR: null value in column «username» violates not-null constraint?

Так никто не предлагает отказываться от валидации и взваливать эту задачу целиком на БД. Если такая ошибка возникла, то где-то не хватает валидации, и это надо исправлять. А без исключения разработчик этого не увидит (лично я использую для этого гем exception_notification). А если такое пропускать в базу, то где-то в другом месте выскочит исключение типа «undefined method x for nil». Иначе придётся лепить кучу проверок или использовать безопасные вызовы методов.
call вполне может быть изменен, чтобы возвращать false в случае ошибки вместо рерэйза.

И как тогда получить ошибки из операции? А если в операции обновления не будет найдена редактируемая запись? Добавлять перехват RecordNotFound? Такими темпами и получится свой аналог Operation. Там уже присутствую типовые макросы для взаимодействия с model, form object, policy. И избавляет от нагромождений if-else.

Лично я уже прошёл через все эти костыли с самодельными form object (ActiveModel и Virtus) и service object и считаю, что в Trailblazer более-менее оптимальное и достаточно гибкое (во второй версии) решение.
А если в операции обновления не будет найдена редактируемая запись?

404 нам всегда было достаточно.


Вполне возможно, что у меня все задачи проще были, и я не могу оценить пользу reform. В любом случае, на примерах реальных приложений было бы продуктивнее обсуждать, чем на абстрактных задачах.

Такого рода статьи новичков как я, всегда загоняют в тупик. А стоит ли прокачивать свои скиллы в стеке Ruby/Rails? Или стоит приглядеться к чему-то другому?

хотите чтоб один раз выучить технологию и работать на ней до самой пенсии — ява;
чтобы вас любили работодатели (и коллеги, за что им самим не приходится погружаться в это) — node.js;
хотите удовольствия и выбора огромного количества библиотек — python;
максимального удобства (intellisense как в коде, так и во вьюшках) — asp.net

Очень хорошее распутье возможные альтернативы, спасибо за ответ!

Если тебе нравится руби и по душе rails way конечно стоит. В мире IT любят хоронить технологии, обращать на это внимание себе дороже. Rails не доминирующий фреймворк сегодня, но с рынка он никуда не ушёл и не уйдёт в обозримом будущем.
Для примера посмотри на VB.net, вот уж кто должен быть мертвее мертвого, а в штатах на нем проектов очень много до сих пор много
Sign up to leave a comment.