Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

週刊Railsウォッチ(20200420前編)anyway_config gemでRails環境設定、ShopifyのLiquidテンプレートエンジン、書籍『Beyond the Twelve-Factor App』ほか

こんにちは、hachi8833です。Gitをただちにアップグレードしてセキュリティ修正しましょう。リンク先に日本語の丁寧な説明があります🙇。

また、OpenSSLのセキュリティ修正1.1.1gが明日リリースされるそうです。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

Rails: 先週の改修(Rails公式ニュースより)

Rails.cache.clearが高アクセスで出すErrno::ENOTEMPTYを無視するようにした

# activesupport/lib/active_support/cache/file_store.rb#L36
      def clear(options = nil)
        root_dirs = (Dir.children(cache_path) - GITKEEP_FILES)
        FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) })
-     rescue Errno::ENOENT
+     rescue Errno::ENOENT, Errno::ENOTEMPTY
      end

つっつきボイス:「ENOTEMPTY?」「あ、E NOT EMPTYか😆」「FileStoreを使っているときのエラーね☺️」「単に無視することにしてるとは😳」「raiseするよりはということかな🤔」

「他のキャッシュ操作ならともかく、キャッシュをclearするときだから、キャッシュがnot emptyになっても無視して別に大丈夫でしょうという感じですかね😆」「どうせ消すんだし😆」「正しいキャッシュの使い方をしていれば普通は問題ないはず😆」

Active Modelのerrorsメソッドの多くが非推奨化


つっつきボイス:「お、Enumerable系の操作の一部がdeprecateされてる😳」「そもそもerrorsって単純な配列やハッシュじゃなかったような覚えがあるので、そういう操作が対象になったということか: 気持ちはワカル」「おぉ」「インターフェイスは一応共通ですけど☺️」

「その後上のコミットで一部が差し戻されてました↑」「まあeachはなくさないでしょうけど😆、valuesとかkeysぐらいはあってもいい気がしますが」「そうですね」「まあdeprecationはいったんマージされても後になってやっぱり必要なんじゃないかって取り消されることもちょくちょくありますけど😆」「たしかに😆」「従来のユースケースをハードに使ってる人たちがいそうな雰囲気ですし、このプルリクももしかするとまた差し戻されるかも?」

バリデーションエラーをErrorオブジェクトとしてカプセル化する
ActiveModelerrorsコレクションは、messages/detailsのハッシュではなく、これらErrorオブジェクトの配列になった。
それらErrorオブジェクトのひとつひとつにあるmessageメソッドやfull_messageメソッドはエラーメッセージの生成用。同じくdetailsメソッドは、従来のdetailsハッシュにあった追加のエラーパラメータを返す。
今回の変更ではできる限り後方互換性を保とうとしているが、errors#firstなどのerrors.messageserrors.detailsハッシュを直接操作する一部のエッジケースまではカバーしきれない。今後は、こういった直接操作ではなく、提供されるAPIメソッドに書き換えること。
今回非推奨になったメソッドや、次のメジャーリリースで振る舞いが変更される計画のあるメソッドのリスト:
* errors#slice!(削除される)
* errors#firstErrorオブジェクトを返すよう変わる)
* errors#lastErrorオブジェクトを返すよう変わる)
* errors#eachkey, valueを引数に持つブロックを渡す機能は動かなくなるが、errorだけを引数に持つブロックはErrorオブジェクトを返す
* errors#values(削除される)
* errors#keys(削除される)
* errors#to_xml(削除される)
* errors#to_h(削除される、errors#to_hashで置き換え可能)
* errors自体をひとつのハッシュとみなす操作は無効になる(errors[:foo] = 'bar'など)
* errors#messagesが返すハッシュ(errors.messages[:foo] = 'bar'など)への操作は無効になる
* errors#detailsが返すハッシュ(errors.details[:foo].clearなど)への操作は無効になる
#36125と@a4deb63より

新機能: ActiveSupport::TimeWithZone#inspectに秒以下表示を追加

# activesupport/lib/active_support/time_with_zone.rb#L141
    def inspect
-     "#{time.strftime('%a, %d %b %Y %H:%M:%S')} #{zone} #{formatted_offset}"
+     "#{time.strftime('%a, %d %b %Y %H:%M:%S.%9N')} #{zone} #{formatted_offset}"
    end

つっつきボイス:「タイムゾーンのinspectに下の12345678みたいな秒以下が追加されたそうです」「inspectの挙動を直しただけみたいだけど、たしかに表示しないとキモチワルイし☺️」

# 同PRより
# before
Time.at(1498099140).in_time_zone.inspect
# => "Thu, 22 Jun 2017 02:39:00 UTC +00:00"
Time.at(1498099140, 123456780, :nsec).in_time_zone.inspect
# => "Thu, 22 Jun 2017 02:39:00 UTC +00:00"
Time.at(1498099140 + Rational("1/3")).in_time_zone.inspect
# => "Thu, 22 Jun 2017 02:39:00 UTC +00:00"

# after
Time.at(1498099140).in_time_zone.inspect
# => "Thu, 22 Jun 2017 02:39:00 UTC +00:00"
Time.at(1498099140, 123456780, :nsec).in_time_zone.inspect
# => "Thu, 22 Jun 2017 02:39:00.12345678 UTC +00:00"
Time.at(1498099140 + Rational("1/3")).in_time_zone.inspect
# => "Thu, 22 Jun 2017 02:39:00 1/3 UTC +00:00"

inspectの結果が等しいかどうかみたいなことは実装では普通やりませんけど、エラーログを調べているときなんかにはinspectしたときに中身が違っていたら表示も違ってて欲しいですよね😋」「あ、それはうれしい😂」「上のbeforeの2つ目と3つ目だと中身違うのに表示が同じになっちゃってますし😆」「今までだと、そういう知識がなかったらinspectしても違いが出ない理由が想像つかなくてハマったでしょうね😆」「これは理にかなった変更だと思います👍」

「ところでTime.at(1498099140 + Rational("1/3"))って書いてますけど、RubyのタイムにはRational食わせられるのか🤣」「うぉ〜マジですか🤣」「出力もちゃんと02:39:00 1/3になってるし😳」「そんな書き方できるんかいっ🤣」「これは『今日のへぇ〜』決定😆」

「どうやらRuby 2.7のTime#inspectがサブセカンドも表示するようになったので、今回の修正はそれに合わせたのかも↓」

$ docker run ruby:2.7.0-alpine ruby -e 'p Time.at(1498099140)'
2017-06-22 02:39:00 +0000
$ docker run ruby:2.7.0-alpine ruby -e 'p Time.at(1498099140, 123456780, :nsec)'
2017-06-22 02:39:00.12345678 +0000
$ docker run ruby:2.7.0-alpine ruby -e 'p Time.at(1498099140 + Rational("1/3"))'
2017-06-22 02:39:00 1/3 +0000
$ docker run ruby:2.6.5-alpine ruby -e 'p Time.at(1498099140)'
2017-06-22 02:39:00 +0000
$ docker run ruby:2.6.5-alpine ruby -e 'p Time.at(1498099140, 123456780, :nsec)'
2017-06-22 02:39:00 +0000
$ docker run ruby:2.6.5-alpine ruby -e 'p Time.at(1498099140 + Rational("1/3"))'
2017-06-22 02:39:00 +0000

参考: Ruby 2.7 の変更点 - Time / Date - @tmtms のメモ

match_head_routesのループを減らしてアロケーションを1/3に

IPS
Warming up --------------------------------------
               match    52.685k i/100ms
          fast_match    60.366k i/100ms
Calculating -------------------------------------
               match    606.333k (± 4.4%) i/s -      3.056M in   5.050991s
          fast_match    743.192k (± 3.0%) i/s -      3.743M in   5.040465s

Comparison:
          fast_match:   743192.4 i/s
               match:   606332.8 i/s - 1.23x  slower

MEMORY
Calculating -------------------------------------
               match   120.000  memsize (     0.000  retained)
                         3.000  objects (     0.000  retained)
                         0.000  strings (     0.000  retained)
          fast_match    40.000  memsize (     0.000  retained)
                         1.000  objects (     0.000  retained)
                         0.000  strings (     0.000  retained)

Comparison:
          fast_match:         40 allocated
               match:        120 allocated - 3.00x more

Lazyはあまり効かなかったので使わなかったそうです。

# actionpack/lib/action_dispatch/journey/router.rb#L108
        def find_routes(req)
          routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
            r.path.match?(req.path_info)
          }

-         routes =
-           if req.head?
-             match_head_routes(routes, req)
-           else
-             match_routes(routes, req)
-           end
+         if req.head?
+           routes = match_head_routes(routes, req)
+         else
+           routes.select! { |r| r.matches?(req) }
+         end

          routes.sort_by!(&:precedence)

          routes.map! { |r|
            match_data = r.path.match(req.path_info)
            path_parameters = {}
            match_data.names.each_with_index { |name, i|
              val = match_data[i + 1]
              path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
            }
            [match_data, path_parameters, r]
          }
        end

        def match_head_routes(routes, req)
-         verb_specific_routes = routes.select(&:requires_matching_verb?)
-         head_routes = match_routes(verb_specific_routes, req)
-
-         if head_routes.empty?
-           begin
-             req.request_method = "GET"
-             match_routes(routes, req)
-           ensure
-             req.request_method = "HEAD"
-           end
-         else
-           head_routes
+         head_routes = routes.select { |r| r.requires_matching_verb? && r.matches?(req) }
+         return head_routes unless head_routes.empty?
+
+         begin
+           req.request_method = "GET"
+           routes.select! { |r| r.matches?(req) }
+           routes
+         ensure
+           req.request_method = "HEAD"
          end
        end

-       def match_routes(routes, req)
-         routes.select { |r| r.matches?(req) }
-       end

つっつきボイス:「スピードも速くなってアロケーションは3倍良くなってる🎉」「HTTPのHEADリクエストのルーティングのマッチを変えたのね😋」

「HEADって結構使うんでしょうか?」「まあキャッシュを設定するとブラウザからちょいちょいHEADリクエストが飛んできますし」「そうなんですね!」「ETagの更新チェックなんかもそう🧐」

速いマシンはいい

「うむむ、変更量多いからGitHub diff画面をサイドバイサイド表示に変えようっと」「おや、そういえば今日は上下diff表示ですね」「いえいえ、今日Windows PCをギンギンに新しくしたばかりなのでその辺の設定をまだ移してなかっただけで😆」「そういえばRyzen 7にお引越ししてましたね」

「いやもう何が嬉しいって、マウスがちゃんと60fpsで動いて見えること😂」「そんなに違うとは😆」「新しい方はデスクトップPCですか?」「ですです😋、8コア16スレッド、特にメモリを64GBにしたらスラッシングが皆無になってもう神⛩」「😆」「やっぱメモリが足りてるのは大事って改めて思いましたよ: 今の時代に16GBは普通に足りなすぎ😆」「😆」

使われていないArel visitorsを削除

# activerecord/lib/arel/visitors.rb#L3
require "arel/visitors/visitor"
require "arel/visitors/to_sql"
require "arel/visitors/sqlite"
require "arel/visitors/postgresql"
require "arel/visitors/mysql"
-require "arel/visitors/mssql"
-require "arel/visitors/oracle"
-require "arel/visitors/oracle12"
require "arel/visitors/where_sql"
require "arel/visitors/dot"
-require "arel/visitors/ibm_db"
-require "arel/visitors/informix"

つっつきボイス:「@kamipoさんによる修正」「rbファイルが結構削除されてますね」「visitorsって何をやるんだろうと思ったら、まんまVisitorパターンなのか😆: Arelノードをなめる感じの」「既にどこかのタイミングでvisitorが廃止されてたんでしょうね」

既にコードベースで使われなくなったibm_db、informix、mssql、oracle、oracle12のArel visitorを削除する。
実際にはoracleとoracle12のvisitorは、sqlserverアダプタがそうであるように、アダプタのリポジトリに置いて専用Arel visitorにすべき。さもないと、自分たちにはOracleの知識がそこまであるわけではないので、#35838や#37646のようにoracle visitorのバグを見つけるのもプルリクをレビューするのもつらくなる。
同PRより

# 同コミットで削除されたactiverecord/lib/arel/visitors/ibm_db.rb
module Arel # :nodoc: all
  module Visitors
    class IBM_DB < Arel::Visitors::ToSql
      private
        def visit_Arel_Nodes_SelectCore(o, collector)
          collector = super
          maybe_visit o.optimizer_hints, collector
        end

        def visit_Arel_Nodes_OptimizerHints(o, collector)
          hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join
          collector << "/* <OPTGUIDELINES>#{hints}</OPTGUIDELINES> */"
        end

        def visit_Arel_Nodes_Limit(o, collector)
          collector << "FETCH FIRST "
          collector = visit o.expr, collector
          collector << " ROWS ONLY"
        end

        def is_distinct_from(o, collector)
          collector << "DECODE("
          collector = visit [o.left, o.right, 0, 1], collector
          collector << ")"
        end

        def collect_optimizer_hints(o, collector)
          collector
        end
    end
  end
end

参考: Visitor パターン - Wikipedia

「こうしてみると、Railsで使われているRDBMSっていっぱいありますね」「ibm_dbはきっとDB2」「DB2は試しにインストールしたことはあるけどproductionではやったことないな〜😆」「昔いた会社ではAS400でDB2を動かしてました」「DB2もSQLだから、複雑なことをやらなければ既に動いているものにアクセスする分にはたぶん大丈夫、かな😆」「😆」

参考: DB2 と Ruby on Rails: 第 1 回 DB2 と Ruby on Rails の導入

ルーティング探索の不要なパス情報取得呼び出しを削減

# actionpack/lib/action_dispatch/journey/router.rb#L108
        def find_routes(req)
-         routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
-           r.path.match?(req.path_info)
+         path_info = req.path_info
+         routes = filter_routes(path_info).concat custom_routes.find_all { |r|
+           r.path.match?(path_info)
          }

          if req.head?
            routes = match_head_routes(routes, req)
          else
            routes.select! { |r| r.matches?(req) }
          end
          routes.sort_by!(&:precedence)

          routes.map! { |r|
 -          match_data = r.path.match(req.path_info)
 +          match_data = r.path.match(path_info)
            path_parameters = {}
            match_data.names.each_with_index { |name, i|
              val = match_data[i + 1]
              path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
            }
            [match_data, path_parameters, r]
          }
        end

つっつきボイス:「これもルーティング系の更新」「無駄な呼び出しを削ったんですね」

👋自分はShopifyで働いているのだけど、最近自分らのメインアプリでproductionのリクエストをプロファイリングして、キャッシュヒットのサブセットで時間を食っている箇所について理解を深めようとしていたところ、通常で40〜50msかかっていた。自分たちのミドルウェアスタックで何が起こっているか、実際の「動作」の前の各リクエストで行われているすべてのものが特に気になった。
そして以下の結果にたどり着いた(Speedscopeのプロファイルより)。


Rack::Request::Helpers#path_infoへの呼び出し部分に少々驚かされた。コードを覗いてみると、同じ関数が無意味に繰り返し呼び出されていたので、ローカル変数に値を保持して再利用するよう変更した。
この変更は割とシンプルかつストレートなのと、ルーティング数の少ないアプリでは基本的にパフォーマンスは向上しないだろうと踏んで(アロケーションはある程度削減されるだろうが)、特にベンチマークは書かなかった。Shopifyほどのスケールになれば、イテレーションの必要なルーティング数が膨大になるので、この変更が効いてくるだろうと見込んでいる。
同PRより

「最近ルーティングの改修をそこそこ見かけますね」「ルーティングだと、パフォーマンスの最適化は比較的手出ししやすいかもしれないですね: 機能自体に手を入れなければ☺️」

「そういえばRailsのjourneyだったかな?、何かのコンポーネントでメインのメンテナーがいなくなってうかつに触れなくなったみたいな話が以前あった気がするけど、今はどうなっているんだろう?」「そういえばそんなような話した覚えが🤔」「journeyだったかどうかも定かでないけど😆」「う〜ん何だったかな〜😅」

Rails

anyway_config: Evil Martiansの環境設定管理gem

以下の記事で紹介されていたgemです。


つっつきボイス:「今記事を翻訳し始めたところです」「これひとつで環境構築をまかなってやるぜ的な」「コンフィグ系のツール、いろいろあるな〜😆」

「最近のRailsにあるsecretなんちゃらも対応してくれてるみたい」「コンフィグといえば昔から割と定番のconfig gem(旧rails_config gem)は今どうなってるんだろう: 最近も更新されてはいるみたいですね↓」「おぉ」

「anyway_configはそのオルタナという感じ: config gemはあくまでyamlで設定するけど、anyway_configはRubyのコードで設定することもできるみたい↓」「どっちもやれるんですね❤️」「ちゃんと見ないとわからないけど、いろいろ高機能そうなツール😋: Rails標準のコンフィグだとかゆいところに手が届かなかったりもするので、こういうのを作る気持ちワカル」

# anyway_configリポジトリより
class MyConfig < Anyway::Config
  attr_config :host, :port, :url, :meta

  # 型強制を扱うためにライターをオーバーライド
  def meta=(val)
    super JSON.parse(val)
  end

  # または、値がない場合を扱うためにリーダーをオーバーライド
  def url
    super || (self.url = "#{host}:#{port}")
  end

  # v2.1まではインスタンス変数を読み取れる予定
  # つまり以下も動作する
  def url
    @url ||= "#{host}:#{port}"
  end
end

「今日はいませんがkazzさんはENVキライって言ってて、自分はどちらかというとENVの方が好きです」「ENVはENVで、万一プロセスを奪われると全部見えちゃいますけど😆」「それもそうですね😅」「まあ今はENVに入れるのが王道ですし、機会があったらanyway_config使ってみてもよさそう😋」

「記事の方を見るとAWSコンフィグにも対応しているらしい↓」

# 同記事より
class AWSConfig < ApplicationConfig
  # attr_configでパラメータ設定用の
  # リーダーとライターを定義できる
  attr_config :access_key_id, :secret_access_key,
              :region, :storage_bucket
end

「そういえば、リポジトリでanyway_configを使っているものが紹介されている中にあるAnyCable、どっかで見たな〜」

この記事↓の著者がAnyCableやってるそうです。

Rails 6のB面に隠れている地味にうれしい機能たち(翻訳)

RailsのMVCリファクタリングに役立つ7つのパターン


つっつきボイス:「ラインナップは特に目新しくはなさそうで以下の記事↓と基本的に変わらないんですが、短所や注意も書いてあるのがちょっとよさそうかなと思って」「こういうパターンはそうそう風化しないので、もし知らなければやってみればいいと思います😆」「エバーグリーンですね🌳」

肥大化したActiveRecordモデルをリファクタリングする7つの方法(翻訳)

「自分はArelで書けるならQuery Object化しなくてもいいかな派だけど😆、ちっちゃいクラスを保ちたいときにArelでQuery Objectを書くのはそれはそれでありかも」「ふむふむ」「どちらかというとQuery Objectにするのは生SQLが必要になったときかなって自分は思ってます☺️」「なるほど〜」

# 同記事より: Query Object
class Article < ActiveRecord::Base
    # t.string :status
    # t.string :type
    # t.integer :view_count
  end

 class ArticlesController < ApplicationController
    def index
      @articles = Article
                  .accessible_by(current_ability)
                  .where(type: :video)
                  .where('view_count > ?', 100)
    end
  end

「よく使われるパターンがひととおり載っているので、最近Railsを始めた人は読むといいと思います😋」

見出しより:

  • Service Object(とIntaracter Object) -- コントローラが太りやすい
  • Value Object -- 変換や比較関連のロジックがコントローラに入りがち
  • Form Object -- バリデーションロジックはモデルにあるので、Adminなど他のエンティティで再利用できない
  • Query Object -- クエリの条件がコントローラに入るので再利用できずテストが面倒
  • View Object(Serializer、Presenter) -- 計算ロジックがビューに入りすぎる
  • Policy Object -- 作成ポリシーを知っているのはコントローラだけ、コントローラのロジックが増えがち
  • Decorator -- 計算ロジックがビューに入りすぎる
  • まとめ

書籍『Beyond the Twelve-Factor App』: あのTwelve Factor Appが15項目にリライト

参考: The Twelve-Factor App (日本語訳)


12factor.netより


つっつきボイス:「3月にはてブでバズっていたんですが、取り上げようと思って忘れていました😅」「12個から15個に🍫」「お〜、順番も少し変わってるし、『APIファースト』とかが加わってるあたりはマイクロサービスを意識している感じがしますね🔬」

「credentialの話が入っているのもなかなかオモシロイ😋: デプロイ周りでは常にここが問題になってきますし」「前は『ENVでやれ。以上』みたいな感じでしたっけ?」「こちらではENVをどこに置くか、どのタイミングでロードするかみたいなところまで踏み込んでますね🧐」「おぉ」

「ぶっちゃけ開発者の環境なんかはどうでもよくて😆、production環境で本当に隠さないといけない情報をどう扱うかが重要: 記事にもあるようにcredentialをVaultとかに隔離してそっちから引っ張って来るなんてのは最近普通にやるようになってきましたし😎」「ヴォールトって記事にあるHashiCorpのVaultのことか😅」「TerraformだとVaultで鍵管理しますね🗝」「一般的には、マスターキーがないとコンフィグ自体にアクセスできなくする機能☺️」


vaultproject.ioより

「『Environment Parity(環境一致)』の概念か〜」「『Build、release、run』が1個増えて『Design、build、release、run』になってる😳」「テレメトリーはAPM(Application Performance Monitor)なんかも含むと」「authentication(認証)とauthorization(認可)なんかはサービスメッシュにやらせたい😂」

authorizationは日本語訳が「認可」だったり「承認」だったりして、いつも迷います😅。

「このBeyond the Twelve-Factor Appは詳細に読み込んでおく価値ありそう: Twelve-Factor Appが書かれた当時はまだ未来だと思われていたものがいろいろ出現しているのを反映しているし、とてもいいんじゃないでしょうか👍」「おぉ😋」「勉強会のお題にうってつけ❤️」


後でスライドを見つけました。

AppSignalの「Citadelアーキテクチャ」(Ruby Weeklyより)


つっつきボイス:「Citadel(シタデル?)って知らない単語だったんですが、辞書で見てみると「要塞」とか「とりで」という意味で、周辺サービスをOutpost(前哨基地)と呼んでるようです」

参考: シタデル:永炎の魔法と古の城塞 | スパイク・チュンソフト

「どういう趣旨の記事かしら😆」「ざっと眺めたところでは、DHHが『Majestic Monolith↓パターン』に続く『Citadelパターン』という呼び名を考えたようなんですが、Majestic Monolithをまず知りませんでした😅」

参考: The Majestic Monolith - bon's bookmarks

「Majestic Monolithという言葉は今ひとつしっくりきませんけど😆、そういえばDHHは前からこういうことを言ってましたね」「基本はモノリスでという感じでしょうか?」「まあ上のリンク先で以下のようにまとめられているとおりですね: Railsもマイクロサービスに対応しないのかと聞かれるたびにDHHがこんな感じで答えてた覚えありますね☺️」「そうでしたか!」「『マイクロサービスが流行っているから』『マイクロサービスだからいい』という理由で導入するのって違いますし😆」

  • 組織の規模に合わせてマイクロサービス化する必要性は理解している
  • 組織の成長に合わないのであればマイクロサービスにする必要はない
  • モノリスであることを誇るのではなく、モノリスだからこそ保守性を上げる努力をする
    scrapbox.io/bon/より

「で記事ではOutpostという概念を持ち込んで幸せになったと」「とりあえず追っておくとよさそうな記事😋」「ですね😋」

記事ではKafkaに対応するgemを作ることでOutpostを実現したそうです。

liquid: Shopifyのマークアップ言語(GitHub Trendingより)


つっつきボイス:「liquidは随分前からあるみたいで★も多いんですけど、どこで使われているんだろうと思って」「{{ }}で囲んで記述するテンプレートエンジンか😳」

<!-- 同リポジトリより -->
<ul id="products">
  {% for product in products %}
    <li>
      <h2>{{ product.name }}</h2>
      Only {{ product.price | price }}

      {{ product.description | prettyprint | paragraph }}
    </li>
  {% endfor %}
</ul>
# 同リポジトリより
@template = Liquid::Template.parse("hi {{name}}") # Parses and compiles the template
@template.render('name' => 'tobi')                # => "hi tobi"

「ははぁ、ノリとしてはSmartyみたいなヤツかな↓」「スマーティ?」「PHPのテンプレートエンジンですね🚗: READMEにも『Smartyスタイルのテンプレートエンジンがお好きならどうぞ』って書いてあるし😆」「おぉなるほど」

参考: Smarty マニュアル | Smarty

# smarty.netより
{if $name eq 'Fred'}
    Welcome Sir.
{elseif $name eq 'Wilma'}
    Welcome Ma'am.
{else}
    Welcome, whatever you are.
{/if}

「RailsのビューはERBの中でRubyのコードが直接動くので、どんなひどいコードでも書けちゃうんですよ: そもそもビューにそんな複雑なロジックを書くべきではないという話も昔からありますし」「たしかに」「こういうLiquidとかSmartyは特定のコードしか動かないようにすることで、何でもできるようにさせないというものです」「なるほど、テンプレート専用のDSLにすることでやんちゃさせないと😆」「そうそう、Rubyで言うDSL」「概念としては昔からこういうのがありますね☺️」

参考: ドメイン固有言語 - Wikipedia -- DSL

「そういうふうにするともうひとつメリットがあって、キャッシュがよく効くようになります: 生Rubyコードを書くとどんなコードが来るかわからないので、うまくキャッシュに乗らなかったりプリコンパイルも限界があったりするんですけど、こういう機能制限した言語なら先にコンパイル済みキャッシュを作りやすいんですよ」「おぉ❤️」「まあ新たにその専用言語を覚えないといけないのがしんどいですけど😆」「😆」

「そういえば以下の記事なんかでエンドユーザーがLiquidのテンプレートで書けるようにしてますね」「そうそう、比較的安全度高いのでそういう用途に向いてますね😋: APIに載ってるものしか書けないので、lambda書いて動かすような無茶はできない😆」「😆」

参考: Rails with Liquid - Qiita
* 元記事: 【Shopify②】テーマのカスタマイズ(Liquidコード編集)|Osamu Iwasaki|note

「まあ自分はたぶん使わないというか、エンジニアが書くならLiquidみたいなのは不要かなと思いますが」「やっぱりエンドユーザー向けでしょうか?」「エンドユーザーよりは管理者に使ってもらうのにいいでしょうね: たとえばメールテンプレートをカスタマイズできるようにするみたいなオーダーはよくありますけど、BPSでやっている入退くん↓のように複数テナントの管理者が個別に通知メールをカスタマイズするような案件では、管理者がRubyを生で書けてしまうと何が起きるかわかりませんし事前チェックもしづらいんですけど、Liquidみたいなものなら事前にコンパイルがとおるかどうかをチェックするようにもできますし😋」「なるほど!」

個別指導塾での「入退くん」の使い方

その他Rails

つっつきボイス:「2本目は永和システムマネジメントさんの記事です」「リファ練はリファクタリングの練習か🤔」「リファクタリングは、やり方さえ間違ってなければ、チームの足を引っ張らずに練習できるという意味で新人に向いている作業ですね」「とっかかりによさそう😋」「最悪マージできなくてもロジックに影響しないので☺️」

「ただ、リファクタリングばかりするということだと業務としてはちょっと違うんですよね😅」「たしかに機能面での進捗ないですし🤔」「もちろんリファクタリングする人は必要ですし、いてくれることでコードの健康が保たれるんですけど売上に直接貢献する作業ではないので」「そうですね」

「自社システムをたくさん持っている大きな会社だったら、リファクタリングの鬼になって社内のあらゆるシステムのリファクタリングを一手に引き受けることで会社にもメリットがあると思いますけど、受託開発で案件もそんなにたくさんないような職場だとリファクタリング専任の社員を抱える余裕はそうそうないでしょうし☺️」「ただ、本物のリファクタリングのプロになれば、たとえばフリーランスとして週2日とか週3日とかのペースでリファクタリングを請け負う人になれるでしょうし、実際そういう人がいてくれるとありがたいです😋」「😋」


前編は以上です。

週刊Railsウォッチ(20200413前編)最近macOSでRailsが遅い、トランザクションでのreturnやbreakなどが非推奨化、Rails監視ツールリスト2020年度版ほか

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp Slackなど)です。

Rails公式ニュース

Ruby Weekly

GitHub Trending

160928_1701_Q9dJIU


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。