rubyで使うコンポーネント化に役立つgem 1選
今回はrubyのtrailbrazerというgemについて調べるうちに学んだことに関する記事です。
世の中いろいろな依存が存在しますよね。
恋愛依存、薬物依存、糖質依存
バランスを意識したい、極力依存しすぎることは避けたいです。
というのも、業務をしていると、「他に依存しすぎている」「疎結合させたい」といった言葉が出てきました。
コンポーネント化?疎結合?使い回し?依存度?といった用語をよく分からなかったのでまとめていき、最後にrubyでクラスをコンポーネント化させるgemをまとめていきたいと思います。
疎結合とは
疎結合とは、細分化された個々のコンポーネント同士の結びつきが比較的緩やかで、独立性が強い状態のことである。 疎結合では、個々のコンポーネント同士は相互に連携しているが、相互に依存している余地が少ない。
疎結合とは、相互に依存している余地が少ないことを指しているんですね。
クラスAの振る舞いを変更したときにクラスBに対する影響が少ないと、クラスAはクラスBに対して疎結合であるというということでしょうか。
コンポーネント化とは
使い回しやすいように要素を疎結合化させること。
もともとComponent(コンポーネント)は日本語で部品を指しているみたいです。
なので、コンポーネント化とは、一つのアプリケーションを作り上げていく中で必要な素材を小さい部品レベルに落とし込むイメージでしょうか。
それぞれの部品が他の部品に対しての依存度が低ければ、一つのアプリケーションの中で複数のプロダクトを作るときに使い回しやすくなりますし。
rubyのように責務を明らかにしてアプリの見通しをよくするような言語とコンポーネント化という概念は親和性が高いですね。
例えば、カーファクトリーというアプリケーションを作ることを想定してみます。
さらにカーファクトリーでは四輪車のみを作っていて、車タイプA、車タイプB、車タイプCという種類を作っているとする。
このとき、車タイプAで使っていた部品、ボンネットとかにしておく、を車タイプBにも使いまわしたいと考えました。しかし、その部品は車タイプAのためだけに作られた一枚板だったので、車タイプBに転用できなかった。
この場合、ボンネットのコンポーネント化が進んでいないと言えます。
ボンネットは〇〇でできていて、さらに〇〇は✖️✖︎でできている、といったように、小さな部品に落としこんでおけば、車Bにも使いまわせたかもしれません。
React,Vueなどを触っていてコンポーネント化はフロントエンドだけのものだと思っていました。
gem Trailbraizer
Tailblazer is an architectural style that provides a modern approach to implementing business logic.
ビジネスロジック層に対してモダンなアプローチを加える設計スタイルを指している。
下記のようにValidationやPolicyなど、データのやり取りを行う記述をステートレスにできることがTrailbrazerのコア部分だそうです。
class Song::Create < Trailblazer::Operation
step Model( Song, :new )
step Policy::Pundit( Application::Policy, :create? )
step Contract::Build( constant: Song::Contract::Create )
step Contract::Validate()
step Contract::Persist()
fail Notifier::DBError
pass :update_song_count!def update_song_count!(ctx, current_user:, **)
current_user.increment_song_counter
end
end
- ビジネスロジック層とは
Web アプリケーションなどのプログラムの構成を考えるとき、一番基本になる3層のうちの一つを指す。
下記の3層アーキテクチャーのうちの一つを指しているそうです。
- プレゼンテーション層 (またはユーザインターフェース層)
- ビジネスロジック層 (またはアプリケーション層)
- データアクセス層
プレゼンテーション層: ユーザーが実際に触れる部分。フロントエンド。
ビジネスロジック層: データを取り扱う部分。バックエンド。
データアクセス層: DBとやり取りをする層。
Cellとは
RubyとRailsにViewComponentを追加させるgemのこと。上記のTrailbrazerのうちの一つです。
使い方
どこからでも呼び出すことができ、下記のようにしてviewから呼び出すことができるみたいです。
- # app/views/comments/index.html.haml
%h1 Comments
@comments.each do |comment|
= concept("comment/cell", comment) #=> Comment::Cell.new(comment).show
Comment::Cell
をインスタンス化してeachを使ってデータを表示させていますね。
下記記述はComment::Cell.new(comment).showをしていることと同じみたいです。
ここで使っているconcept()とは、cellを表示させるためのhelperなので、Comment::Cellクラスのshowアクションに書かれた記述がユーザーから見えます。(今回だとThis: #{mode;.inspect})
concept("comment/cell", comment)
Comment::Cellは下記のような構成でクラス化されています
class Comment::Cell < Cell::ViewModel
def show
"This: #{model.inspect}"
end
end
アクションの中でrenderのみを記述すると、アクション名。html .(slim ,erb ,haml)を丸々表示させることができるそうです。
下記の例だと、showアクションだからapp/concepts/comment/views/show.ham l
が表示されます。
class Comment::Cell < Cell::ViewModel
def show
render # renders app/concepts/comment/views/show.haml
end
end
Using #render without any arguments will parse and interpolate the app/concepts/comment/views/show.haml
decorator
modelやcontrollerに影響を与えずviewへの表示内容を変えることのできるgem。
viewとmodelの間であるpresenter層を担っている。
下記のような形でメソッドをdecoratorでメソッドを定義することができるようです。
# app/decorators/article_decorator.rb
class ArticleDecorator < Draper::Decorator
delegate_all
def publication_status
if published?
"Published at #{published_at}"
else
"Unpublished"
end
end
def published_at
object.published_at.strftime("%A, %B %e")
end
end
viewでは.の後にdecorateを付けることでdecoratorで定義したメソッドを使うことができます。
articles.show.html.erb
<%= Article.new(Article.find(params[:id]).decorate%>
疎結合な関係って人間関係においては寂しい気もしますが、プログラムを書く上では嬉しいのですね。
参考
https://qiita.com/os1ma/items/7a229585ebdd8b7d86c2#アプリケーションアーキテクチャの基本は-3-層