Madogiwa Blog

主に技術系の学習メモに使っていきます。

Ruby on Rails: KombuというRuby on Railsでjsのcomponentを指定してrenderできるgemをリリースしました。

タイトル通り、KombuというRuby on Railsjavascriptで描画するcomponentを指定してrenderできるgemをリリースしました💎✨ (コンポーネント・レンダラブルを略してコンブ です)

github.com

モチベーション

Ruby on Railsを利用してサービスが成長してくるとVue.jsといったフロントエンドライブラリを利用することになると思いますが、 これがだんだん成長してくるとRailsのViewとフロントエンドフレームワークがどんどん密結合になっていき、 見通しが悪くなったり適切な境界を持たないことによるE2Eテストの肥大化及び明示的な検証が困難といった課題が出てくるなぁと思っていたので、 以下の記事に書いたようなControllerから直接ComponentをrenederすることでRailsのView層を隠蔽し一定の境界を手軽に作れるのでは?という感じで作りました。

madogiwa0124.hatenablog.com

似たようなライブラリだと以下のようなものがありますが、React-Railsの場合はReactを採用してないと採用できないのとInertia.js Rails Adapterはinertia.jsを導入しないと採用できず境界を分けたいだけのニーズではtoo muchかなと思い作成に至った次第です。(Vue.jsでの使用を主に想定してします)

使い方

使い方は割と簡単で以下をGemfileに追加 + bundle installして

gem "kombu"

以下の通りに自身のアプリケーションに合わせて、jsからmountする要素のid、css/jsの読み込み用のタグの生成ロジック、隠蔽するview templateを設定して

Rails.application.configure do
  # NOTE: (OPTIONAL) id of the element (div) to mount (default: `vue-root`)
  # config.kombu.default_mount_element_id = 'vue-root'

  # NOTE: (REQUIIRED) Specify a proc that generates a tag that reads a javascript entry.
  # See `lib/kombu/renderable.rb` for instance variables provided by kombu that can be used within proc.
  config.kombu.javascript_entry_tag_proc = -> { helpers.javascript_pack_tag(@entry, defer: true) }

  # NOTE: (REQUIIRED) Specify a proc that generates a tag that reads a css entry.
  # See `lib/kombu/renderable.rb` for instance variables provided by kombu that can be used within proc.
  config.kombu.stylesheet_entry_tag_proc = -> { helpers.stylesheet_pack_tag(@entry) }

  # NOTE: (OPTIONAL) template of the view to render that contains the component. (default: See below)
  # config.kombu.default_entry_view_template = <<~ERB
  #   <div id="<%= @kombu_mount_element_id %>">
  #     <%= kombu_component_tag %>
  #   </div>
  #   <% content_for :stylesheet do %>
  #     <%= kombu_stylesheet_entry_tag %>
  #   <% end %>
  #   <% content_for :javascript do %>
  #     <%= kombu_javascript_entry_tag %>
  #   <% end %>
  # ERB
end

任意のControllerでKombu::Renderableをincludeするとkombu_render_componentを使って任意のComponentを含んだhtmlタグをrenderすることが出来ます。renderする際にViewを必要としないので、layouts以外のViewファイルを削除することが可能になり、フロントエンドとサーバーサイドの境界を作ることが出来ます。

class ArticlesController < ApplicationController
  include Kombu::Renderable

  def index
    @title = "Articles"
    @articles = [{id: 1, title: "artile1", body: "body1"}, {id: 2, title: "artile2", body: "body2"}]
    kombu_render_component("article-index-page", attributes: {title: @title, ":articles": @articles.to_json})
    # NOTE: The following html is rendered.
    # <div id="vue-root"><artile-index-page title="Articles" :articles="[{"id":1,"title":"artile1","body":"body1"},{"id":2,"title":"artile2","body":"body2"}]"></div>
  end
end

またkombu_render_componentに渡された値を明示的に検証できるRSpecのmatcherを用意しているのでrequest specでサーバーサイドから渡す値を明示的に検証することが出来ます🙆‍♂️

describe "GET /articles", type: :request do
  before { get articles_path }

  it "Specific arguments must be passed to kombu_render_component." do
    title = "Articles"
    articles = [{id: 1, title: "artile1", body: "body1"}, {id: 2, title: "artile2", body: "body2"}]
    expect(controller).to kombu_component_rendered("article-index-page", attributes: {title: title, ":articles": articles.to_json})
  end
end

仕組み

基本的にはkombu_component_renderedで指定した名称とattiributesを持つタグとjavascript_entry_tag_proc、stylesheet_entry_tag_procで生成したタグをconfig.kombu.default_entry_view_template`で指定したtemplateに埋め込んで返却しているだけです🧑‍🏭

詳しくはこちらの該当コードを参照してください。

github.com

おわりに

実際に自分の個人サービスで利用してるのですがapp/views配下がlayouts以外消せてスッキリでした👍