- Ruby / Rails関連
週刊Railsウォッチ(20200420前編)anyway_config gemでRails環境設定、ShopifyのLiquidテンプレートエンジン、書籍『Beyond the Twelve-Factor App』ほか
こんにちは、hachi8833です。Gitをただちにアップグレードしてセキュリティ修正しましょう。リンク先に日本語の丁寧な説明があります🙇。
- 追記(2020/04/21): セキュリティ修正を追加したGit 2.26.2が翌日に追加リリースされました: Git credential helper vulnerability announced (Update) - The GitHub Blog
GitにCriticalな脆弱性がでてるので、Gitを2.26.1にアップデートしましょう!
XcodeとかSourceTreeとかも同じ問題があります。 "Gitの認証情報を奪い取れるGit 2.26.0以下にある脆弱性について" https://t.co/oJFBJiLSB7— azu (@azu_re) April 19, 2020
また、OpenSSLのセキュリティ修正1.1.1gが明日リリースされるそうです。
- メーリングリスト: Forthcoming OpenSSL Release
- 各記事冒頭には⚓でパーマリンクを置いてあります: 社内や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
オブジェクトとしてカプセル化する
ActiveModel
のerrors
コレクションは、messages/detailsのハッシュではなく、これらError
オブジェクトの配列になった。
それらError
オブジェクトのひとつひとつにあるmessage
メソッドやfull_message
メソッドはエラーメッセージの生成用。同じくdetails
メソッドは、従来のdetails
ハッシュにあった追加のエラーパラメータを返す。
今回の変更ではできる限り後方互換性を保とうとしているが、errors#first
などのerrors.messages
やerrors.details
ハッシュを直接操作する一部のエッジケースまではカバーしきれない。今後は、こういった直接操作ではなく、提供されるAPIメソッドに書き換えること。
今回非推奨になったメソッドや、次のメジャーリリースで振る舞いが変更される計画のあるメソッドのリスト:
*errors#slice!
(削除される)
*errors#first
(Error
オブジェクトを返すよう変わる)
*errors#last
(Error
オブジェクトを返すよう変わる)
*errors#each
にkey, 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
の更新チェックなんかもそう🧐」
- API:
HEAD
- HTTP | MDN - API:
ETag
- HTTP | MDN
⚓速いマシンはいい
「うむむ、変更量多いから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
「こうしてみると、Railsで使われているRDBMSっていっぱいありますね」「ibm_dbはきっとDB2」「DB2は試しにインストールしたことはあるけどproductionではやったことないな〜😆」「昔いた会社ではAS400でDB2を動かしてました」「DB2もSQLだから、複雑なことをやらなければ既に動いているものにアクセスする分にはたぶん大丈夫、かな😆」「😆」
参考: DB2 と Ruby on Rails: 第 1 回 DB2 と Ruby on Rails の導入
⚓ルーティング探索の不要なパス情報取得呼び出しを削減
- PR: Reduce calls to get path info when finding routes by jasonhl · Pull Request #38914 · rails/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です。
- 元記事: Anyway Config: Keep your Ruby configuration sane — Martian Chronicles, Evil Martians’ team blog
つっつきボイス:「今記事を翻訳し始めたところです」「これひとつで環境構築をまかなってやるぜ的な」「コンフィグ系のツール、いろいろあるな〜😆」
「最近の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のMVCリファクタリングに役立つ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」を15項目に見直した「Beyond the Twelve-Factor App」を読んだ - kakakakakku blog
- 元記事: Beyond the Twelve-Factor App -- 書籍の抜粋です
- 書籍: Beyond the Twelve-Factor App [Book] -- お試し版あり
参考: The Twelve-Factor App (日本語訳)
つっつきボイス:「3月にはてブでバズっていたんですが、取り上げようと思って忘れていました😅」「12個から15個に🍫」「お〜、順番も少し変わってるし、『APIファースト』とかが加わってるあたりはマイクロサービスを意識している感じがしますね🔬」
「credentialの話が入っているのもなかなかオモシロイ😋: デプロイ周りでは常にここが問題になってきますし」「前は『ENVでやれ。以上』みたいな感じでしたっけ?」「こちらではENVをどこに置くか、どのタイミングでロードするかみたいなところまで踏み込んでますね🧐」「おぉ」
「ぶっちゃけ開発者の環境なんかはどうでもよくて😆、production環境で本当に隠さないといけない情報をどう扱うかが重要: 記事にもあるようにcredentialをVaultとかに隔離してそっちから引っ張って来るなんてのは最近普通にやるようになってきましたし😎」「ヴォールトって記事にあるHashiCorpのVaultのことか😅」「TerraformだとVaultで鍵管理しますね🗝」「一般的には、マスターキーがないとコンフィグ自体にアクセスできなくする機能☺️」
- サイト: Vault by HashiCorp
「『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より)
In addition to the Majestic Monolith, someone should write up the pattern of The Citadel: A single Majestic Monolith captures the majority mass of the app, with a few auxiliary outpost apps for highly specialized and divergent needs.
— DHH (@dhh) April 7, 2020
つっつきボイス:「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を実現したそうです。
- リポジトリ: appsignal/rdkafka-ruby: Modern and performant Kafka client library for Ruby based on librdkafka
⚓liquid: Shopifyのマークアップ言語(GitHub Trendingより)
- リポジトリ: Shopify/liquid: Liquid markup language. Safe, customer facing template language for flexible web apps.
- サイト: Liquid template language
つっつきボイス:「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.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年度版ほか
- 20200407後編 RubyのTracePointでデバッグ、Rubyとモナド、Gitノウハウ集、リモートワークほか
- 20200406前編 Ruby 2.7.1セキュリティ修正、RailsビューHTMLにテンプレート名を出力、Action Mailboxテスト用フォーム改良ほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp Slackなど)です。