app
├── concepts
│ ├── comment
│ │ ├── cell
│ │ │ └── new.rb
│ │ ├── operation
│ │ │ ├── create.rb
│ │ │ └── update.rb
│ │ └── view
│ │ ├── new.erb
│ │ └── show.erb
concept - самостоятельная сущность со своим поведением
class CommentsController < ApplicationController
def create
op = Comment::Create.(params)
@model = op.model
end
class Comment < ActiveRecord::Base
class Create < Trailblazer::Operation
model Comment, :create
contract do
property :body
property :author_id
validates :body, presence: true
end
def process(params)
validate(params[:comment]) do
contract.save
end
end
end
end
Поместив бизнес-логику в операции, можем:
- переиспользовать операции;
- получить модульную структуру проекта, которую
легче редактировать и масштабировать;
- писать простые и понятные тесты на каждую операцию;
- поменять framework
describe Comment::Create do
it "persists valid input" do
op = Comment::Create.(comment: { body: "RoR!", author_id: 1 })
op.model.persisted?.must_equal true
op.model.body.must_equal "RoR!"
end
end
- простые Unit-тесты операций покрывают всю бизнес-логику;
- integration, API тесты и тестирование контроллеров упрощаются в разы - можно проверять лишь общее поведение без бизнес-логики.
- не нужны фабрики, их заменяют операции, которые одинаково работают как в тестовом режиме, так и в production mode;
class CommentsController < ApplicationController
def show
comment = Comment::Create.present(params)
render Comment::Cell::New, comment, layout: Application::Layout
end
module Comment::Cell
class New < Trailblazer::Cell
property :body
def author_name
model.author.full_name || "Anonymous"
end
end
end
%h1 Comment
.row
= body
.row
= author_name
<%= concept('menu/cell/main_panel',
nil,
{ current_user: current_user })%>
внутри операций
require 'roar/decorator'
class SongRepresenter < Roar::Decorator
include Roar::JSON
property :title
end
song = Song.new(title: "The Wall")
SongRepresenter.new(song).to_json
class Create < Trailblazer::Operation
representer do
include Roar::JSON
include Roar::Hypermedia
property :id
property :body
property :author_id
link(:self) { comment_path(represented.id) }
end
отдельным классом
Может как передавать параметры в исходном виде, добавлять, переименовывать их, так и записывать прямо в модель
Comment::Create.('{"body": "Solnic rules!"}').json
Репрезентеры
gem 'reform-rails' gem 'roar-rails', github: 'alaibe/roar-rails' gem 'roar', github: 'apotonick/roar' |
gem 'cells-erb' gem 'cells-rails' gem 'trailblazer-cells' gem 'kaminari-cells' |
gem 'trailblazer' gem 'trailblazer-rails' |
"Trailblazer. A new Acrhitecture For Rails"