Comments 31
Про 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 хорош при двух вариантах:
- Когда данные одной формы распихиваются в несколько сущностей в БД (или даже в разные БД)
- Их нужно хитро преобразовывать или инициализировать. Гораздо нагляднее видеть в контракте, что есть хитрые популяторы/препопуляторы у некоторых полей.
В любом случае, 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
Это было. Но, блин, это же вырвиглаз вообще.
Я понимаю, что иногда зачем-то нужно делать делать несколько контрактов на операцию, но в душе никак не могу представить — зачем.
А насчёт нескольких контрактов, могу предположить, например, для разных этапов обработки заказа (или способов его создания), или для пользователей с разными доступами (к атрибутам).
А пользователю тоже показывать ERROR: null value in column "username" violates not-null constraint
? Изначально обсуждались валидации, результат которых виден пользователю. Целостность данных — совсем другое, trailblazer к ней мало отношения имеет (вообще не имеет?).
А пользователю тоже показывать 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 более-менее оптимальное и достаточно гибкое (во второй версии) решение.
Такого рода статьи новичков как я, всегда загоняют в тупик. А стоит ли прокачивать свои скиллы в стеке Ruby/Rails? Или стоит приглядеться к чему-то другому?
чтобы вас любили работодатели (и коллеги, за что им самим не приходится погружаться в это) — node.js;
хотите удовольствия и выбора огромного количества библиотек — python;
максимального удобства (intellisense как в коде, так и во вьюшках) — asp.net
Для примера посмотри на VB.net, вот уж кто должен быть мертвее мертвого, а в штатах на нем проектов очень много до сих пор много
RailsClub 2017: Интервью с Nick Sutterer. Rails умирает (а Ruby нет)