Quantcast
Channel: Ruby on Railsの記事一覧|TechRacho by BPS株式会社
Viewing all 1384 articles
Browse latest View live

Rails: pluckでメモリを大幅に節約する(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

Rails: pluckでメモリを大幅に節約する(翻訳)

Active Recordのモデルは信じられないほど柔軟性が高く、機能が山ほど搭載されています。メソッドのAPIが大量にあるため、Active Recordの個別のオブジェクトはメモリに読み込まれると非常に場所を取ります。

Active Recordのモデルにフィールドがたった1つあるだけでも(たとえばidだけでも)、Railsが背後に控えているのです。

次のように書くのではなく

多数のオブジェクトをイテレーションしてメモリに全部読み込む。

Book.paperbacks.map { |book| book.title }
#=> ["Eloquent Ruby", "Sapiens", "Agile Web Development With Rails"]
Book.paperbacks.map(&:title)
#=> ["Eloquent Ruby", "Sapiens", "Agile Web Development With Rails"]

次のように書く

ActiveRelation#pluckメソッドを用いて、必要なフィールドをデータベースから直接読み込む。

Book.paperbacks.pluck(:title)
#=> ["Eloquent Ruby", "Sapiens", "Agile Web Development With Rails"]

そうする理由

この方法は、スピードとメモリ使用量の効率に関連します。

Active Recordモデルが大量のメモリを食う問題は、特に巨大なコレクションを操作するときにつらくなります。最初の例では、データベースから読み込まれた行をひとつひとつオブジェクトに変換しているにもかかわらず、たった1つのフィールドしか使っていません。Railsがメモリ上に用意する、モデルのその他の機能は必要ありません。

必要なフィールドだけを読み込むことで、アプリが高速化し、メモリ使用量も削減されます。

そうしない理由があるとすれば

#pluckメソッドがリクエストに応じて返す値は、Rubyの素の配列のみです。モデルのメソッドは一切読み込まれません。

Active Recordの実際のオブジェクトがどうしても必要な場合や、#pluckの後でモデルを更新する必要がある場合は、残念ながら使えません。

素の配列の代わりに、スコープに続けてselectすることでActive Recordの完全なモデルを使うことを検討してもよいのです、リクエストするフィールドしか含まれません。#pluckほどメモリ効率のよい方法は他にありません。

Book.paperbacks.select(:title).map { |book| book.title }
#=> ["Eloquent Ruby", "Sapiens", "Agile Web Development With Rails"]

関連記事

Rails: モデルの外では名前付きスコープだけを使おう(翻訳)

Rails: ActiveRecord::Relationで生SQLは避けよう(翻訳)


Rails: `present?`より便利なActiveSupportの`presence`(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしています。

Rails: present?より便利なActiveSupportのpresence(翻訳)

Active SupportはRubyのコアライブラリにメソッドをたくさん追加するので、何かと非難が集中します。特に評判がよろしくないのは、RubyのObjectクラスへのパッチです。

RubyのあらゆるオブジェクトはObjectのサブクラスなので、Objectクラスにメソッドを追加すればコードのあらゆるオブジェクトにそのメソッドが追加されることになります。

Active Supportでの拡張に関するドキュメントでもう少し詳しく見てみましょう。

そうしたメソッドのひとつが#presenceです。これはお馴染みの#blank?#present?に比べて馴染みの薄いメソッドです。

次のように書くのではなく

変数の値を表示するのに、次のように長ったらしい条件を使う。

class User < ApplicationRecord
  validates :email, presence: true

  def friendly_name
    if nickname.present?
      nickname
    elsif given_name.present?
      given_name
    else
      email.split('@').first
    end
  end
end

次のように書く

Active Supportの#presenceメソッドを使う

class User < ApplicationRecord
  validates :email, presence: true

  def friendly_name
    nickname.presence || given_name.presence || email_local_part
  end

  private

  def email_local_part
    email.split('@').first
  end
end

そうすべき理由

#presenceメソッドは、オブジェクトが存在すればそのオブジェクトを返し、存在しなければnilを返したい場合にとても便利なショートカットです。

このメソッドは、Railsのビューでデータが存在するかどうかをチェクする部分でよく見かけるobject.present? ? object : nilという書き方と同等です。

このメソッドは、文字列や配列が空の場合にも有用なソリューションです。空の場合には#presencenilを返します。

そうすべきでない理由があるとすれば

#presenceメソッドはRailsでしか利用できません。Rubyだけを使う場合、このメソッドのためだけにActive Supportをインクルードするほどの価値はおそらくないでしょう。

Railsを使う場合でも、既存のRubyクラスを拡張するこうしたRailsの習慣に抵抗を覚えるのも無理もないかもしれません。

自分のコードでモンキーパッチを使って既存クラスを改変する場合、標準のRubyクラスが思わぬ振る舞いを示すときにバグを踏みやすくなります。この種のコーディングスタイルで悩ましいのは、主にこうした点です。

モンキーパッチによる落とし穴がどうしても心配になる方だと、たとえRailsが当てるパッチであっても避けたい気持ちになるかもしれません。

Railsのライブラリセットはがっつりメンテされていて広く用いられているので、私はこうしたパッチは安全とみなしてコードがきれいになる方を選びます。

関連記事

Rails: pluckでメモリを大幅に節約する(翻訳)

Ruby: `unless`はここぞというときまで使わないこと(翻訳)

Rails: ActiveRecordのコールバック/セッター/派生データについて再び(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

Rails: ActiveRecordのコールバック/セッター/派生データについて再び(翻訳)

Arkencyではこれまで数度に渡って、Railsで使えるコールバック以外の手法についての記事をお送りし、どんな問題が発生する可能性があるかを考察してきました。

しかし、まだまだ多くのシナリオでコールバックが使われているのを今も目にします。そこで今回はもう少し別の例を使って、今一度コールバックについて書いてみたいと思います。

コントローラでメソッドが2回呼び出される

class Controller
  def update
    @cart = Cart.find(params[:id])
    @cart.update_attributes!(...)
    @cart.update_tax
    head :ok
  end
end

私はこの手のパターンをかなり見かけてきました。ActiveRecord::Baseの(既存の)定義済みメソッドが一部の属性の設定に使われています。ツリーの奥深くのオブジェクトを編集するためにaccepts_nested_attributes_forと組み合わせて使われることもしばしばです。

そして、後になってtaxsumcounterdiscountなどの派生データを再計算しなければならなくなった場合の対応方法は、アプリによってまちまちです。たとえば、米国の売上税(sales tax)は出荷元の住所によって変動することがあります。そのため、出荷元住所を設定したら、アプリで使われているOrderSaleCartなどの税金も再計算したいことがあります。

普通これらの計算結果をデータベースに保持しておく理由は、価格や税金やディスカウント額などが将来変更された場合に金額が変わっては困るからです。そのため、現在の値から派生するデータを算出して保存します。他に、データベース上の計算を簡単かつ高速に出力したいという理由付けもあります。

そのような場合には、update_attributes!attributes=で出荷元住所を設定してからupdate_taxを読んで再計算をトリガするよりも、shipping_address=セッターのように意図が明白なpublicメソッドを1つだけ使う方がよいのです。

私がpublicなインターフェイスを使うときには必ず、メソッドの呼び出し順序や引数がどのように変わったとしても最終的に正しいステートを得られる(あるいは、オブジェクトの使い方が誤った場合にはそれがはっきりわかる例外を発生する)よう自分に課していることを申し上げておかなければなりません。オブジェクトを書くときは、順序に依存しないようにするか、内部ステートを保つことで誤った順序を防止できるようにしましょう。私が求めるのは「可換(commutative)」すなわち交換可能性であると信じています。

こうしておけば、リファクタリングもずっと簡単になります。派生データが常に正しく再計算されるので、たとえば住所の前または後でディスカウントを設定するようチェックアウト処理を変更しても大丈夫です。10個の値を変更できる1つの大画面を2つの小画面に分割して、値をそれぞれに振り分ける場合にも問題なく動作するので、安心して作業できます。

モデル内で値を再計算するコールバック

典型的な例をもうひとつご紹介しましょう。

class Order
  before_save :set_amount

  def add_line(...)
    # ...
  end

  private

  def set_amount
    self.amount = line_items.map(&:amount).sum
  end
end

これも先ほどと同様の問題を抱えています。save!を呼び出すとset_amountメソッドが自動的に呼び出されるよう軽く自動化してあります。しかし、これでは次のようなテストを書けません。

order.add_line(product)
expect(order.amount).to eq(product.price)

この場合、次のように再計算やsaveを手動でトリガする必要があります・

order.add_line(product)
order.save!
expect(order.amount).to eq(product.price)
order.add_line(product)
order.set_amount       # privateにできない
expect(order.amount).to eq(product.price)

これでは面白くも何ともありません(少なくとも私は)。

回避する方法はないものでしょうか。そのために、amountや taxなどの派生データを再計算するadd_lineremove_lineupdate_lineなどの「意図が明確な」メソッドを追加しましょう。このようなドメイン操作はコールバックのようなところに隠さず、明示的にしましょう。Railsではたいていの場合、superを呼び出してゲッターやセッターを上書きしてから作業を続行できることを思い出しましょう。

class Wow < ActiveRecord::Base
  def column_1=(val)
    super(val)
    self.sum = column_1 + column_2
  end

  def column_2=(val)
    super(val)
    self.sum = column_1 + column_2
  end
end

この手法は、再評価の必要な計算が多数ある場合に特に便利です。

class Wow < ActiveRecord::Base
  def column_1=(val)
    super(val)
    compute_derived_calculations
  end

  def column_2=(val)
    super(val)
    compute_derived_calculations
  end

  private

  def compute_derived_calculations
    self.sum = column_1 + column_2
    self.discounted = sum * percentage_discount
    self.tax = (sum - discounted) * 0.02
    self.total = sum - discounted + tax
  end
end

今日はこの手の値に6つも出くわしました😉

これらの問題に取り組むべき理由

これらの問題の根本的な原因は、ActiveRecordのサブクラスがデフォルトで持っているpublicなメソッドが多すぎることだと信じています。あらゆる属性や関連付けにもれなくpublicメソッドがついてきて、誰でも好きな場所で変更できてしまいます。このような状況に置かれた開発者は、アプリで実際に使うAPIを絞り込み、縮小して予測可能にすることに責任を持つ必要があります。

他の言語(他のフレームワークとする方がRubyの責任でないという意味で正確だと思いますが)のコードでは、ルールを保護して派生データの算出をトリガするカプセル化メソッドの方がはるかに一般的に使われています。Railsの場合は、何でもカラムで設定しておいて、バリデーション中やオブジェクトの保存時に面倒を見るのが普通なので、そのためのコールバックに強く依存します。私は、オブジェクトがいついかなるときでも問題が生じないようにしておくのが好みです。

遠回しなたとえ

ここまでご理解いただけましたでしょうか。ところで皆さんはReact.jsをお使いですか?React.jsのrenderは、コンポーネントがstatepropsにのみ依存する純粋関数である場合に最大の効果を発揮します。React.jsでrenderを呼び出した結果は派生データになり、引数が同じであれば常に同じ結果を得られます。

以下のようなメソッドについても同じように考えられます。

  def compute_derived_calculations
    self.sum = column_1 + column_2
    self.discounted = sum * percentage_discount
    self.tax = (sum - discounted) * 0.02
    self.total = sum - discounted + tax
  end

column_1や column_2などに値を設定できます。

  def column_2=(val)
    super(val)
    compute_derived_calculations
  end

その他に、sumdiscountedtaxtotalなどの自動で再計算される派生値があります。いずれもわかりきったことではありますが、ActiveRecordがあるとこの辺を簡単には実現できないので、もう少し頑張る必要があります。

凝集度を高めて「集約」を作り出す

本ブログをお読みの方でDomain-Driven Design(電子書籍)をお読みいただいた方であれば、本記事で説明したリファクタリングによってよりよい「集約(aggregate)」を実現できるということにお気づきかと思います。内部ルールはこの集約によって常に保護されます。

詳しく知りたい方へ

本記事をお楽しみいただけましたら、ぜひ私たちのニュースレターの購読をお願いします。私たちが日々追求している、開発者を驚かさないメンテ可能なRailsアプリの構築方法を皆さんにお届けいたします。

以下の記事も参考にどうぞ。

私たちの最新書籍『Domain-Driven Rails』をぜひチェックしてみてください。巨大で複雑なRailsアプリを扱っている方に特におすすめします。

関連記事

Rails: Active Recordのコールバックを避けて「Domain Event」を使おう(翻訳)

Ruby: 年に1度だけ発生する夏時間バグ(翻訳)

週刊Railsウォッチ(20181001)Railsアップグレード記事と各種支援ツール、CLI向けRubyワンライナー集、Rubyアプリをワンバイナリ化ほか

$
0
0

こんにちは、hachi8833です。ついにGobyのREPLを修正できました。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを社内有志でつっついたときの会話です👄

今回のつっつきは軽めでお送りします🙇

⚓【お知らせ】週刊Railsウォッチ「公開つっつき会」#3開催

いよいよ今週となりました「週刊Railsウォッチ 公開つっつき会 第3回」(10月4日(木)19:30〜)引き続き応募中です(つっつき・懇親会ともに無料)。ノートPC片手にお気軽にご来場いただけます。皆さまのご参加をお待ちしております🙇🙇

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

今回はすべてコミットリストから見繕いました。

⚓ルーティングがコミットされてからルーティングヘルパーモジュールを一括ビルド

tenderloveさんのコミットです。engine/configuration.rbにpaths.add "config/routes.rb", eager_load: trueが追加されてました。

このコミットでは、ルーティングが完了してからルーティングヘルパーをeagerビルドする。これでヘルパーモジュールがキャッシュされるようになるが、ルーティング定義が「進行中(in-flight)」でもモジュールに安全にアクセスでき、ルーティングが再構成されてモジュールが再生成されるとキャッシュが自動的にinvalidになる。
これにより、URLヘルパーモジュールにアクセスできるのはルーティングが確定した後だけになるという制約が生じる。
また、コントローラが読み込まれるとrailtie経由でURLヘルパーメソッドを(withメソッドで)インクルードする。development環境ではこれが頻繁に発生する可能性がある。上の「URLヘルパーモジュールにアクセスできるのはルーティングが確定した後だけになるという制約」を適用すれば、モジュールのeagerビルドとキャッシュを安全に行える。
同PRより大意

# railties/lib/rails/engine.rb#L473
    # files inside eager_load paths.
    def eager_load!
      config.eager_load_paths.each do |load_path|
-       matcher = /\A#{Regexp.escape(load_path.to_s)}\/(.*)\.rb\Z/
-       Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
-         require_dependency file.sub(matcher, '\1')
+       if File.file?(load_path)
+         require_dependency load_path
+       else
+         matcher = /\A#{Regexp.escape(load_path.to_s)}\/(.*)\.rb\Z/
+         Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
+           require_dependency file.sub(matcher, '\1')
+         end
        end
      end
    end

つっつきボイス:「routes.rb」「よくわからないけど最適化というか安全性を高めた感じ?」「url_helper記事と関係あるかしら」「ところで、ルーティングの再読み込みはproductionでは基本発生しないから関係ないけど、development環境では大いに関係アル😊」「開発中のリロードはしょっちゅうですしね: rails routesで結構待たされる重たい処理だし」「なのでこのあたりの最適化はありがたい🙏」「普段開発してても、ルーティングの更新によく追従するな〜ってときどき感心するし: resourcesでやれるものはいいけど、RESTに収まらないようなルーティングもざらにあるし」

Railsのルーティングを極める(前編)

⚓Railsエンジンのrailtieでのルーティングの無限ループを解消

上の#33970に含まれているコミットで、こちらもtenderloveさんでした。

# actionpack/lib/action_dispatch/routing/route_set.rb#L446
      def include_helpers_now(klass, include_path_helpers)
-       if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_routes_url_helpers) }
          klass.include(namespace.railtie_routes_url_helpers(include_path_helpers))
+       namespace = klass.parents.detect { |m| m.respond_to?(:railtie_include_helpers) }
+        if namespace && namespace.railtie_namespace.routes != self
+         namespace.railtie_include_helpers(klass, include_path_helpers)
        else
          klass.include(url_helpers(include_path_helpers))
        end
       end

つっつきボイス:「ルーティングが無限ループすることって割となさそうですけど?」「あ、ルーティングにはconcernとかでshared example的に外に出せる機能があるんですよそういえば💡」「そこで循環参照しちゃうことがある?」「普通にやってればネストするだけなんで起きないんですけど、書き方によってはもしかすると起きるかも🤔

⚓関連付けプリロードの検査を改善

# activerecord/lib/active_record/associations/preloader.rb#L100
        def preloaders_on(association, records, scope, polymorphic_parent = false)
-          case association
-          when Hash
+          if association.respond_to?(:to_hash)
            preloaders_for_hash(association, records, scope, polymorphic_parent)
-          when Symbol
+          elsif association.is_a?(Symbol)
            preloaders_for_one(association, records, scope, polymorphic_parent)
-          when String
+          elsif association.respond_to?(:to_str)
            preloaders_for_one(association.to_sym, records, scope, polymorphic_parent)
          else
            raise ArgumentError, "#{association.inspect} was not recognized for preload"
           end
         end

つっつきボイス:「hash-like objectって『ハッシュのようだけどハッシュでない』ってことでしょうか?」「確かにActionController::Parametersのstrong parametersはハッシュのように見えるけどあれハッシュじゃないっす: ハッシュのAPIもそこそこ備わってますが」「お〜やっぱり」「修正前はwhen Hashだったのがif association.respond_to?(:to_hash)に変わってますね」「うんうん、is_a?なんかじゃなくてrespond_to?を使おうぜってことね、ワカル〜😋」「ハッシュじゃないからwhen Hashでは引っかからないけど、to_hashには応答する: Rubyっぽい書き方💎

参考: Action Controller の概要 — 4.5 Strong Parameters | Rails ガイド

⚓シャドウイングを解消

# activerecord/test/cases/connection_adapters/connection_handler_test.rb#L170
        ActiveRecord::Base.configurations = config
-        ActiveRecord::Base.configurations.configs_for.each do |config|
-          assert_instance_of ActiveRecord::DatabaseConfigurations::HashConfig, config
+        ActiveRecord::Base.configurations.configs_for.each do |db_config|
+          assert_instance_of ActiveRecord::DatabaseConfigurations::HashConfig, db_config
        end

つっつきボイス:「ちょうど今日BPS社内勉強会でRubyのシャドウイングを扱ったので」「うは〜configがもろシャドウイングしてた: 動くは動くけどお行儀はよくない」「上手の手から水が漏ってた感じですね」「Rubocopも気づかないヤツかも?」「これは適切ないい修正😊

⚓fixture_pathnilの場合にエラーをraise

元のissue #19414が2015年と随分昔です。

# activerecord/lib/active_record/fixtures.rb#L893
      def fixtures(*fixture_set_names)
        if fixture_set_names.first == :all
+         raise StandardError, "No fixture path found. Please set `#{self}.fixture_path`." if fixture_path.blank?
          fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"].uniq
          fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
        else
           fixture_set_names = fixture_set_names.flatten.map(&:to_s)
         end

         self.fixture_table_names |= fixture_set_names
         setup_fixture_accessors(fixture_set_names)
       end

つっつきボイス:「fixture_pathって、active_record/fixtures.rbにあるみたい」「ActiveRecord本編で使うのかなと一瞬思ったけど、Railsのテストヘルパーでしたか」

「今更だけどRailsでfixtureとseedの違いについて教えてくださいって質問見つけた」「fixtureはテストデータを登録するもので、seedはマスタデータの登録用という使い分けですね😊

⚓データベースconfigをシンボルのハッシュで渡せるようにした

# activerecord/lib/active_record/database_configurations.rb#L106
        build_db_config = configs.each_pair.flat_map do |env_name, config|
-          walk_configs(env_name, "primary", config)
+          walk_configs(env_name.to_s, "primary", config)
        end.compact
# activerecord/lib/active_record/database_configurations.rb#L117
       def walk_configs(env_name, spec_name, config)
         case config
        when String
          build_db_config_from_string(env_name, spec_name, config)
        when Hash
-          build_db_config_from_hash(env_name, spec_name, config)
+          build_db_config_from_hash(env_name, spec_name, config.stringify_keys)
        end
      end

つっつきボイス:「env_nameto_sするから文字列でもシンボルでもOKになったと」「文字列のシンボルをキーに取るハッシュか(ややこしい)」「stringify_keysはRubyのメソッドかなと思ったらRailsでした」「例のHashWithIndifferentAccessですね」「全部シンボルにできたらいいんだけどな〜、と思いつつハイフン-が入ってくるかもしれないから文字列のほうが安全なんですよね😅

参考: ActiveSupport::HashWithIndifferentAccessstringify_keys

⚓Rails

⚓Shopifyの「deprecation_toolkit」gem


同リポジトリより

このgemは--record-deprecationsを付けてテストを実行するとshitlist(ブラックリストというか「殺す」リスト)を.ymlファイルに保存します。設定では#deprecation_path#behavior#allowed_deprecationswarnings_treated_as_deprecationを設定できます。

# 同リポジトリより
DeprecationToolkit::Configuration.deprecation_path = 'test/deprecations'
DeprecationToolkit::Configuration.deprecation_path = -> (test_location) do
  if test_location == 'admin_test.rb'
    'test/deprecations/admin'
  else
    'test/deprecations/storefront'
  end
end

つっつきボイス:「deprecation_toolkitって、まさか非推奨になったものを復活させるとかじゃないよね😆」「違います😆」「gemも含めてdeprecationをチェックできるのはありがたいかも」「まだ★は少ないですが、Shopifyは『ぜひお試しを!』と胸を張ってます💪」「ActiveSupport::Deprecationを使ってないgemのwarningをdeprecation扱いすることもできるようです」「おぉ〜😘

「RailsやRubyのバージョンアップをほとんど諦めてるようなプロジェクトってざらにあるけど、こういうツールがあると上げやすくなりますね😋: やってみたらわんさか出てきてそっ閉じかもだけど😬」「RailsのdeprecationはRailsが面倒見るとしても、gemまでは面倒見ないし」「一気にバージョン上げようとしても詰まるだけなので、RailsもRubyもバージョンは基本少しずつ上げるのが鉄則」「Aを上げるにはBを上げないといけなくて、Bを上げるにはRubyを上げないと、Rubyを上げるには…とかね⛈」「昔にDebian Linuxのパッケージをアップグレードするときに確かglibcの依存関係で似たような玉突き事故がぐるっとひと回りしたことありました💦

参考: Rails アップグレードガイド | Rails ガイド
参考: GNU Cライブラリ - Wikipedia

shitlistドリブンについてのShopifyのプレゼン↓です。

⚓ShopifyのRails 5へのアップグレード


つっつきボイス:「今回は何だかRailsアップグレード特集みたいになってきましたが、これは同じくShipfyの昨年6月の記事で、依存gemが370個あったそうです」「うへぇ~😭」「で5.0へのアップグレードに成功したツィートが記事に貼り付けられてますね(実物は消えてますが)」「DHHにメンション飛ばしてるのが『ほめてほめて😍』って感じ😆」「このLinesとかLOCとかClassesのリストってどうやって出すんでしたっけ?🤔」「何だったかな…rake statsか」「そうでした: 今ならrails statsですね↓」「案件で既存のRailsアプリをチェックするときはまずこれとrails routesあたりから調べるのが定番」

ツィートのスクショを貼ると規約違反になるので中々面倒です😅

「記事のまとめは『deprecatedはなる早で取り除け』『gemはこまめにアップグレードせよ』『Railsアプリの2本立て起動とCIが武器』『データベースやキャッシュにシリアライズデータを極力保存しないこと』となってる」「確かに〜😎」「シリアライズしたデータをDBに保存するのは基本的に悪手なので、アップグレード時にはそういうのを空っぽにしておきたい」「お😳」「というのもデータのシリアライズはRubyのバージョンに依存するから」「あー、マーシャリングですね」「それそれ👉」「以前のウォッチでも、Rubyバージョンが変わって以前のマーシャリングデータが読み込めなくなった話がありました」

参考: module Marshal (Ruby 2.5.0)

⚓スライド「ここ10年間のRailsアップグレード対応を振り返る」ともうひとつのアップグレード支援ツール


つっつきボイス:「こちらもアップグレードねたのスライドで、絵が中心なのでさっと見ていけますね😋


同スライドより

「そうそう、アップグレードはこのあたり↑が鬼門だってmorimorihogeさんも言ってました」「Railsは3.0->3.1のアップグレードが最も面倒なことで有名で、Sprockets周りが特にキツイ」「5.xはそんなにキツくない印象ですね」「そこまでにちゃんとアップグレードしておけば基本的に通るハズ」「gemをアップグレードするかしないかの選択肢、あるある」「時間ないので次へ〜」

後で動画も見つけました(米国ピッツバーグでのRails Conf 2018)。

これも後で気づきましたが、発表者のJordan Raine氏は発表にちなんだten_years_rails_conf_2018という、上のdeprecation_toolkitとコンセプトの似たgemをこしらえていました。gem install ten_years_rails_conf_2018でインストールできます。

# 同リポジトリより
RSpec.configure do |config|
  # Tracker deprecation messages in each file
  if ENV["DEPRECATION_TRACKER"]
    DeprecationTracker.track_rspec(
      config,
      shitlist_path: "spec/support/deprecation_warning.shitlist.json",
      mode: ENV["DEPRECATION_TRACKER"],
      transform_message: -> (message) { message.gsub("#{Rails.root}/", "") }
    )
  end
end

アップグレードの最終的な成果は以下のとおりだそうです🎊


同スライドより

なお、GitHubがRailsを3.2から5.2というハードなアップグレードを終えたというニュースがつい先ごろ飛び込んできました。次回のつっつきで取り上げると思います。

参考: Upgrading GitHub from Rails 3.2 to 5.2 | GitHub Engineering

⚓GitHubがRails以外にもさまざまな技術を導入


つっつきボイス:「訳したかったこれ😢: 上の記事ではGitHubはモノリスでなくなりつつあるということですが、以前TechRachoに掲載したAaron Pattersonさんのインタビュー↓では、GitHubはマイクロサービス化を進めてはいるけどそれでもRailsモノリスだという見解でした(会社の見解はまた違うかも?)」「ふむぅ、それはそれでRailsのライフサイクルかな〜」「Go言語が使われ始めているのもさもありなんです」

[インタビュー] Aaron Patterson(前編): GitHubとRails、日本語学習、バーベキュー(翻訳)

⚓Active Storageのvariantに渡せるオプション(RubyFlowより)

# 同記事より
my_attachment.variant(combine_options: { resize: "400x300>", extent: "400x300", background: "grey", gravity: "center"})

つっつきボイス:「ActiveStorage、まだ使う機会なかった」「ほほぉ、ちょっと工夫すれば原理的にはvariantImageMagickのフラグを何でも渡せるのか」「combine_options:にさらにハッシュで渡すってことみたい」

⚓has-many-with-set: Railsの多対多をスマートに扱うgem(GitHub Trendingより)

# 同記事より
class Article < ApplicationRecord
  has_many_with_set :tags   # <--- ここが重要!
end
Tag.create(:name => 'programming')
Tag.create(:name => 'open source')
Tag.create(:name => 'startups')
Tag.create(:name => 'ruby')
Tag.create(:name => 'development')

tags = Tag.all

1000.times do
  a = Article.new(:title => "Buzzword about buzzwords!",
                  :body => "Lorem ipsum")

  a.tags = tags.sample(rand(tags.size + 1))

  a.save
end

ArticlesTagsSetsTag = Class.new(ApplicationRecord)
ArticlesTagsSetsTag.count # this class doesn't exist by default,
                          # I had to create it by hand for the example.
=> 80

つっつきボイス:「★は200超え」「with setだから文字どおりいわゆる数学の集合として扱うってことなんでしょうね」「数学の集合は重複を認めないものだから、それに基づいて記事ごとのタグが重複なしに扱えるってことかな?」「いわゆるブログ記事のタグにぴったりな感じ」「時間ないので次へ〜」

一方で、どんな「集まり」でも集合と呼んでよいわけではない。その「集まり」が集合と呼ばれるためには、対象が「その集まりの元であるかどうかが不確定要素なしに一意に決定できる」ように定義されていなければならない。
Wikipedia: 集合より

つっつきの後で関連記事を見つけました。集合として扱う場合、引き換えに保存と更新のコストが上がるとのことです。用途を選んで使うものですね。


同記事より

⚓astah* communityの無償提供が終了


astah.change-vision.comより


つっつきボイス:「今日社内Slackで流れてきました」「astahのCommunityエディションが提供終了したってヤツですね: お、無料のビューアー↑はあるみたいよ?」「あ、ほんとだ👀」「UML図といえばastahが長年の定番で、実績も多いですね」「むしろそれに代わるツールが意外にないという」「チームでも無料のdraw.ioとastahとどちらにするかで最終的に使いやすさでastahになりましたし👍」「ポンチ絵レベルのものが描きたいだけなら他のドロー系ツールの方が融通は効くけど、実装寄りの図とかプロパティの明確なUMLを描くならやっぱりこれかな😋: お客さまや技術者に渡せる成果物としては」「astahがJetBrainsのIDEと一緒に買えたらいいのにと思ったりします😆」「そういえばJavaのIDEであるEclipseにはモデル図を描けるプラグインもあった気がする(参考)」「あと韓国製のStarUMLを使ったことあったけど日本語化けた(今はわかりませんが)」


Wikipediaより

⚓Ruby

⚓コマンドラインで使えるRubyワンライナー集

# 同記事より
$ s='foo=5, bar=3; x=83, y=120'

$ # 数値を大きさ順に取り出す: 「perl -lne 'print join " ", /\d+/g'」と同じ
$ echo "$s" | ruby -lne 'print $_.scan(/\d+/).join(" ")'
5 3 83 120

つっつきボイス:「learnbyexample/Command-line-text-processingというワンライナーをたくさん集めてるリポジトリがあって、その中のひとつです: ★7500超え」「こういうワンライナーは深く考えずにターミナルにペタッと貼り付けて使えるのがいいっすね」「いわゆるsed的に使う感じですね: このリポジトリもどちらかというとそれが目的みたいです」「パズル系のワンライナーではなさそうなので結構実用的かも❤

⚓ruby-warning: Rubyの不要なwarningを除外

# 同リポジトリより
# 初期化されてないインスタンス変数のwarningをすべて無視
Warning.ignore(/instance variable @\w+ not initialized/)

# 現在のファイルについて、初期化されてないインスタンス変数のwarningをすべて無視
Warning.ignore(/instance variable @\w+ not initialized/, __FILE__)

# 現在のファイルについて、初期化されてないインスタンス変数のwarningをすべて無視
Warning.ignore(:missing_ivar, __FILE__)

# 現在のファイルについて、FixnumやBignumのwarningをすべて無視
Warning.ignore([:fixnum, :bignum], __FILE__)

# warningをwarningレベルでLOGGERに書き込む
Warning.process do |warning|
  LOGGER.warning(warning)
end

# 現在のファイルについて、warningをerrorレベルでLOGGERに書き込む
Warning.process(__FILE__) do |warning|
  LOGGER.error(warning)
end

つっつきボイス:「y-yagiさんの昨年のブログ記事でこのruby-warningを見かけました」「gemのwarningを抑え込むヤツか〜😍: warningが多すぎると本来のものが埋もれちゃうし、かといって単にwarningを抑制しちゃうと、今度は自分がwarning出すようなコードを書いても気づけなくなっちゃうという」「さっきのdeprecation系ツールに通じるものがありますね」「いぶし銀的なツール: よさそう👍」「今更ですが、Rodaなどでお馴染みのJeremy Evansさん↓のgemでした」

Ruby: 高速/高性能ルーティングエンジンgem「Roda」README: 前編(翻訳)

⚓ruby-packer: Rubyアプリをワンバイナリ化するツール(RubyFlowより)


enclose.ioより

コンセプトの似ているtraveling-rubyというツールもありますが、ruby-packerはずっと新しいようです。LinuxのSquashFSというファイルシステムを使ってるそうです。

参考: SquashFS - Wikipedia


つっつきボイス:「Railsもフルサポートだそうです(データベースは明らかに別物になるでしょうけど)」「Rubyで書いたアプリを社内限定で配布するときによさそう」「RubyやPythonなどのスクリプト言語で書かれたツールを配布するときって、ユーザーの環境を揃えないといけないし、トラブルシューティングの問い合わせが社内から山ほど押し寄せたりしますもんね」「Rubyは一般ユーザーの環境にはデフォルトで入ってないし、その手間がバカにならない」「ワンバイナリならダブクリすれば動くから世話要らず😊

後で試しに小規模なオレオレRailsアプリをビルドしてみたところ、Rubyをコンパイルするところから始まり、ビルドに30分ほどかかりましたが、gemの依存関係もbundler経由で面倒を見ているようです。時間切れでまだアプリとして動かすところまではたどり着いてませんが、バイナリサイズは100MBほどになりました。後でもう少し追ってみたいと思います。

⚓imageflow: 新しい画像処理ライブラリ


imageflow.ioより


同リポジトリより

Ruby製ではありませんが、Rubyバインディングもあるということなので。ImageMagickより17倍速いと言ってます。

⚓その他Ruby


つっつきボイス:「来年のRubyKaigi 2019@Fukuokaもエントリしてます」

⚓クラウド/コンテナ/インフラ/Linux/Serverless

⚓コンテナのデザインパターン


つっつきボイス:「ついにコンテナにもデザインパターンが」「Leader electionパターンやらAmbassadorパターンやら: こういうのがあると設計の幅が広がってくるし😋」「共通言語にならないと意味がないから、普及するかどうかですね: まだ途中でしょうし」「いいパターン名を付けられるかどうかもポイントのひとつかな」「名前が長すぎないのも」


同記事より

参考: ギャング・オブ・フォー (情報工学) - Wikipedia

⚓クラウド4強のサーバーレスをベンチマーク比較してみる(Serverless Statusより)


同記事より


つっつきボイス:「4強はAWS、Microsoft Azure、Google GCF、IBM」「パフォーマンス以外にも課金方法とか使い勝手とかあるから単純な比較は難しいですね」「あと宗教も😆」「↑これだけ見れば、AWSはメモリを増やすと計算時間が短縮されるけどある程度以上は頭打ちで、Googleも似た傾向だけど分散が大きい、IBMのは時間の分散に埋もれがちな感じ」「Azureは変化なし😆」「Azureのメモリ量は計算パワーと連動させないつくりなのかな?」「ともあれ、あくまで取っ掛かりというか参考までにということで」「自分たちとしてはいくらかかるか、つまり課金体系が一番気になる💰

⚓その他クラウド

⚓SQL

⚓SQLスタイルガイド(DB Weeklyより)


同サイトより

-- 同サイトより
(SELECT f.species_name,
        AVG(f.height) AS average_height, AVG(f.diameter) AS average_diameter
   FROM flora AS f
  WHERE f.species_name = 'Banksia'
     OR f.species_name = 'Sheoak'
     OR f.species_name = 'Wattle'
  GROUP BY f.species_name, f.observation_date)

  UNION ALL

(SELECT b.species_name,
        AVG(b.height) AS average_height, AVG(b.diameter) AS average_diameter
   FROM botanic_garden_flora AS b
  WHERE b.species_name = 'Banksia'
     OR b.species_name = 'Sheoak'
     OR b.species_name = 'Wattle'
  GROUP BY b.species_name, b.observation_date)

つっつきボイス:「↑こうやって縦を揃えたりするみたい」「SQLのスタイルガイドってありそうで見たことなかったかも?」「SQLの整形ツールもいろいろあるんですけど、実は結構難しくて🤔: JOINのときにONを改行するかしないかとか流派がいろいろあるし」「SELECTとかFROMを大文字で書くというのは割と行われてる感じですね」「『SQLやデータベース構造にオブジェクト指向を適用するな』というルールもある」「そうね☺(もちろん😤)」

「SQLだとlintかけろみたいなことあまり言われないですね」「SELECTの次がFROMみたいのは確実だけど、うんと長いSQLになるともうキリがないし、一般のプログラミング言語と比べて、SQL文の整形にコストをかける価値があまりない気がする: そもそもアプリ層やってるときにそうそう生SQLは書かないし」「そのためのORMですしね」「それに生SQLを書くときってバッチ処理みたいなワンショットのものが多いし、lintかけようにもそもそもSQL文をファイルに保存しなかったりするし」

⚓その他SQL

⚓JavaScript

⚓GAEがNode.jsを標準サポート


つっつきボイス:「知人が「めちゃ速い」と感激してましたので」「Node.jsやらんといかんかな〜」「Node.js、ありそうでなかったんですね」

参考: Google App Engine とは?  |  App Engine  |  Google Cloud

⚓30秒でわかるJavaScriptサンプルコード集i(GitHub Trendingより)


同リポジトリより


つっつきボイス:「↓こんな感じでJavaScriptのコードスニペットと利用例がずらっとあります: GitHubにも同じものもありますし」「オレの知らないJavaScript😅: deepFlatten↓とかもある〜」「この手のサイトはいっぱいあるけど、結構使いやすそうな感じ😋


同サイトより

const deepFlatten = arr => [].concat(...arr.map(v => (Array.isArray(v) ? deepFlatten(v) : v)));

⚓その他

⚓Amazonのコワーキングスペース『AWS Loft Tokyo』


つっつきボイス:「AWS Loft Tokyo、AWSアカウント持ってれば無料だそうです」「10/1から目黒でオープンすると: 何だかすげ〜な〜😳」「AWSの専門家が常駐してて予約なしで無料相談できるとも」「そこ重要!」「Amazonダンボール模様のエレベーター!」「マイクロソフトやGoogleも同じようなサービス始めるかも?なんて😆」「コワーキングスペースはいっとき流行ったけど今後はどうだろう?🤔」「資金力のあるクラウド会社が提供するというのが今までのコワーキングスペースと違うかも」「さすがに守秘義務を伴う請負開発作業には向いてないと思いますが、学生さんやスタートアップ系の人にはよさそうですね」

あまりに遠い昔ですが、かつてNECが「Bit-INN」という一種のたまり場を作って相当な人気を博したのを思い出してしまいました。

参考: NEC Bit-INN - Studio RUM サイト

⚓今までなかったのが不思議


つっつきボイス:「VSCodeのプラグインなんだそうです」「これは( ・∀・)イイ!!欲しいヤツ〜😍」「コマンドライン向けのツールとしてGoとかでも作ったら人気出そう」「色とか細かく指定できなくてもいいからとにかく色分けしてくれればいい、みたいな感じで☺」「5色ぐらいを循環させるだけでもいいくらい」「そうそう😆

⚓番外

⚓いにしえのスプリングリバーブ

参考: スプリングリバーブ:Spring Reverbとは | 偏ったDTM用語辞典 - DTM / MIDI 用語の意味・解説 | g200kg Music & Software
参考: The Shadow Of Your Smile クリスマス鉄板バージョン! ( その他音楽 ) - ひやしんす通信 - Yahoo!ブログ

⚓最古の動物の化石

脂肪を検出できたというのが驚きです。

参考: 地球最古・5億5800万年前の生き物の痕跡が報告され、古生物学が追い求めてきた数十年来の謎が解明へ - GIGAZINE


今回は以上です。

バックナンバー(2018年度後半)

週刊Railsウォッチ(20180925)Rails大規模支払サービス開発のノウハウ、RailsのMySQLがutf8mb4に移行、Rpush gemほか

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

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

Rails公式ニュース

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

WebOps Weekly

webops_weekly_banner

Postgres Weekly

postgres_weekly_banner

DB Weekly

db_weekly_banner

Serverless Status

serverless_status_banner

GitHub Trending

160928_1701_Q9dJIU

Rails: ActiveRecordのスコープで`present?`を使うとパフォーマンスが落ちることがある(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

Rails: ActiveRecordのスコープでpresent?を使うとパフォーマンスが落ちることがある(翻訳)

RailsではあらゆるRubyオブジェクトで#present?メソッドが利用できるので、このメソッドは多用される傾向があります。たとえば、ビューで配列をループで回す前に配列に要素があるかどうかをチェックしてからデータを表示するなどです。

しかし、#present?をスコープで使うと、思わぬパフォーマンス低下が生じることがあります。

次のように書くのではなく

Active Relation(Active Recordクラスのスコープ)で#present?を使う。

books = Book.recently_released
if books.present?
  books.paperbacks.each { |book| puts book.title }
end

次のように書く

#any?#exists?を使う。

books = Book.recently_released
if books.any?
  books.paperbacks.each { |book| puts book.title }
end

そうする理由

Active Relationで#present?を呼び出すと、そのスコープのActive Recordオブジェクトがすべてメモリ上の配列に読み込まれ、それから(おそらく非常に巨大な)配列が空かどうかがチェックされます。

多くの場合これで問題はありませんが、極端なケース(モデルが複雑な場合やデータセットが巨大な場合)ではメモリ使用量が大きく跳ね上がってパフォーマンスが低下するかもしれません。この現象は、それらのオブジェクトが使われるかどうかにかかわらず発生します。

#any?を使えば、配列を一切ビルドしない別のもっとシンプルなSQLコマンドが実行されます。一般に、SQL呼び出しを追加するときのパフォーマンス低下は、全オブジェクトをメモリに読み込む場合と比べればたかが知れています。

そのリレーションが既に利用されている(つまりオブジェクトがメモリに読み込まれている)のであれば、#present?を実行してもペナルティは受けません。この場合#present?は新しい配列を読み込むのではなく、既存の配列を使います。

アプリでのデータベースの使い方が変わるとなれば、きっと皆さんも自分たちのアプリで現実のパフォーマンスにどう影響するかを監視したいと思うでしょう。その場合、#any?で発行される余分なSQLリクエストによるパフォーマンス低下が、データに#present?を使ったときのパフォーマンス低下を下回らないことを確認する必要があります。

Railsでattendance gemを用いれば、Active Relationsの#present?で常に#any?を使うようデフォルトの振る舞いを変更するモンキーパッチを当てることも可能です。attendanceは少々そっけないツールですが、このgemでまとめて解決した場合の自分たちのアプリのパフォーマンスをじっくり監視したくなることでしょう。

そうしない理由があるとすれば

#any?を用いる場合、SQLクエリを余分に発行することになるので、パフォーマンスが必ずしも改善するとは限りません。レコードを事前に読み込む方が効率よくできるのであれば、そうしましょう。

Railsで、attendance gemと同様の修正(Active Relationの#present?の動作を変更する)である#10539がいったんマージされましたが、その後2b76313で取り消され、既存の振る舞いが採用されました。このため、この振る舞いについては各自が注意して#any?を明示的に使わなければなりません。

ここから、このパフォーマンスチューニングは非常に微妙であることがわかります。皆さんのアプリで必ずしも効果を上げるとは限らないでしょう。

関連記事

Rails: `present?`より便利なActiveSupportの`presence`(翻訳)

Rails: pluckでメモリを大幅に節約する(翻訳)

Rails: コードをシンプルにする2種類の委譲(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

Rails: コードをシンプルにする2種類の委譲(翻訳)

オブジェクト指向プログラミングは、オブジェクト間のメッセージ受け渡しと考えることができます。

関連オブジェクトでは多くの場合、元のオブジェクトのメソッドであるかのように関連オブジェクトのpublicメソッドとしてアクセスできます。

これを実現する主な方法は次の2つです。

変更前

関連オブジェクトを直接呼び出す新しいメソッドを書く。

# 素のRuby
class Workspace
  attr_reader :user

  def initialize(user)
    @user = user
  end

  def user_email
    @user.email
  end
end

# Railsの場合
class Workspace < ApplicationRecord
  belongs_to :user

  def user_email
    user.email
  end
end

変更後

Forwardableの機能かdelegateメソッドを用いる。

# 素のRuby
class Workspace
  extend Forwardable

  attr_reader :user

  def initialize(user)
    @user = user
  end

  def_delegator :@user, :email, :user_email
end

# Railsの場合
class Workspace < ApplicationRecord
  belongs_to :user

  delegate :email, to: :user, allow_nil: true, prefix: true
  # allow_nil: trueにするとuserがnilの場合にエラーにならない
  # prefix: trueにするとメソッド名がuser_emailになる
end

そうすべき理由

RubyやRailsでメッセージを「パススルー」する場合、いずれかのスタイルで新しいメソッドを作成するのが望ましい方法です。これによりコードで達成しようとしていることが明確に表現できます。「単に関連オブジェクトのメソッドを呼び出す」ことと「新しい機能を実際に実装する」ことの違いを見た目ではっきりと区別できるようになり、便利です。

Railsの場合のコードでは、構文が洗練され、柔軟性も機能も向上するのが普通です。私が特に気に入っているのは、prefixオプションとallow_nilオプションです。Railsをお使いの方は、Railsがdelegation向けに提供している拡張機能をこの方法で利用できます。

そうしない理由があるとすれば

これらの機能を使ってworkspace.user.emailworkspace.user_emailに改善されるぐらいでは大したメリットは感じられません。しかしメソッド名をworkspace.owner_emailなどに変更する場合は、意図が明確になるというメリットもあるでしょう。

RubyのDelegatorライブラリ(ドキュメント)など、委譲の手法は他にもありますが、しかしこれらはいくつかのメソッドを渡すよりも、機能ごとクラス全体をラップする場合に使う方が便利です。

関連記事

Rails: ActiveRecordのスコープで`present?`を使うとパフォーマンスが落ちることがある(翻訳)

Rails: pluckでメモリを大幅に節約する(翻訳)

週刊Railsウォッチ(20181009)Rails 6の新機能:WYSIWYGエディタ「Action Text」、Rails 6の青写真スライド、Apache POIはスゴイほか

$
0
0

こんにちは、hachi8833です。休日があると平日が減ってむしろ大変です。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを社内有志でつっついたときの会話です👄

今回は「Railsウォッチ 公開つっつき会 第3回」の日でした。おかげさまで本編・懇親会ともに盛り上がりました🎊🍻。参加いただいた皆さま、ありがとうございます!🙇


つっつきボイス:「(開始前の雑談)Action Text出ましたね」「そうそう、今回はそれがトップです」「え、そんなのが出てた?!😳」「ではぼちぼち始めま〜す🎬

最初Active Textと書き間違えたりしてました💦

⚓臨時ニュース: Rails 6で新機能: ブラウザ向けリッチテキストエディタTrixを取り入れた「Action Text」が導入(Rails公式ニュースより)

先週ぐらいにBasecampがTrixをリリースしたというDHHのツイート↓を見かけたのですが、その時点では様子がよくわからなかったので保留にしていました。TrixはBasecampでは前から使われていたらしく、公開間もないのに★が11000超えです。みんな欲しかったんでしょうね。

既存のブラウザ用リッチテキストエディタのほとんどがIE5.5のcontenteditableexecCommandのAPIを引きずっていたのを、新たにCoffeeScriptで作り直し(コンパイルはBladeを使用)、コードからの制御がうんとやりやすくなったということのようです。

<form …>
  <input id="x" type="hidden" name="content">
  <trix-editor input="x"></trix-editor>
</form>

つっつきボイス:「BPS社内Slackでも話題になってたヤツ」「どれ、動画↑をポチッと」「15分あるので飛ばし飛ばしで」「(動画を見つつ)scaffoldしてちょろちょろっと何か書いて…おぉ〜〜?!テキスト入力にいきなりデスクトップから画像をドラッグアンドドロップしてるし😋」「これ自力でやろうとすると面倒なヤツ」「DHH、GCPをGCSと言い間違えてたっぽい😆」「はい〜じゃこの辺で🎬」「😆


同動画10:10より

# 同動画より post.rb
class Post < AppicationRecord
  has_rich_text :content
end
<!-- 同動画より _form.html.erb -->
<%= form_with(model: post, local: true) do |form| %>
...

<div class="field">
  <%= form.rich_text_area :content %>
</div>
...
<% end %>
<!-- 同動画より show.html.erb -->
...
<%= @post.content %>
...

「ところでこの手のWYSIWYGエディタ機能って使ったことあります?」「あります〜: そのときはWYSIWYGエディタで検索して出てきたものをもう片っ端から試したんですけど、結局どれを使ってもつらかった😭」「これ↓に載ってるCKEditor、自分も使ったな〜」「TinyMCEも」「結局どれもつらいんですよね~」

参考: 商用でも利用できる、イケてるWYSIWYGエディタ7選 2017年版 - ランサーズ(Lancers)エンジニアブログ

「この手の汎用的なWYSIWYGエディタってJavaScriptで書かれているんですけど、どれを使おうと結局アップロードのエンドポイントは自分でサーバー側に用意しないといけないんですよね😅」「それそれ!」「PHPのサンプルコードぐらいはあったりするけど当然そのまま使えるはずがないので、エディタ側の制約に合わせて一生懸命エンドポイントを作り込まないといけないし、UIを日本語化したり不要な機能をオンオフできるようにするconfigを書いたりとか、YouTube動画を埋め込むためにカスタムタグを作ったりとかしようとすると、面倒が無限に続く😇」「froala使ったときとかapplication.jsとかCSSとかをひたすら書きまくったし」「WYSIWYGやった人は誰もがそうなる」

「つまりAction Textがここまで簡単になったのはバックエンドがRailsという前提が置けるからですね」「ちょうどWordPressも今度WYSIWYGをリニューアルするとか言ってるけどあれも同じ」「例のGutenberg↓ですね」

参考: WordPressの新しいエディタGutenberg(グーテンベルク)の長短所について

「Action Textが使えるのはRails 6からか〜」「TrixそのものはRails前提?」「独立してるはずですが、CoffeeScriptで書かれます(コンパイルはBladeで)」「こーひーすくりぷとか〜😅、フロントエンジニアからめっちゃマサカリが飛んできそう⚔」「私も普段から『CoffeeScript滅べ』って言ってるし」「自分もCoffeeScriptはもういいや」「(Trixのリポジトリを開きながら)var使ってる↓の見つけた😆」「🤣」「🤣」「CoffeeScript以前にES2015ですらない😆」「🤣

# bascamp/trixより
var element = document.querySelector("trix-editor")
element.editor  // is a Trix.Editor instance

「ともあれTrixはRailsがなくても使えるということね」「だから★が11000超えなのかも」「Rails専用だとここまで★取れないでしょうね」

「まあ、ぺろっと貼るだけで使えるWYSIWYGエディタなんてものはこの世にありませんから😆」「🤣」「ちなみにWordPressのWYSIWYGが出力するHTMLって見たことあります?空の<div>タグとか、かなり物凄いのが出てくる🤯」「今度はWordPressにほこ先が😆

「ついでにお見せすると、TechRachoのWordPressにはMarkdownプラグインを導入しています↓」「おぉ〜😊」「とは言え一部のスタイルでまだHTMLタグ必要ですが」「Markdownならローカルのエディタで書いてさっと貼り付けられるので(画像はしょうがないけど)」

「やっぱりMarkdownかなっ❤」「プログラミングとかに興味のない人に普及しなさそうだけど😢」「WYSIWYGももう少し秩序のあるコードが書けるものだったらいいんですけどね〜ほんに☺

「果たしてAction Textはどうなるかな?」「フロント勢から『これだからRailsは』が飛んでくる予感が少々😆」「画像アップロードなんかが入ってくると設計的にフロントエンドとバックエンドのどっちの責務なのかが怪しくなってきそうで、そこが難しいっすね🤔: たとえば画像にカスタムタグのボタンを付けたりすると今度はサーバー側でもちょっとタグに手を加えてから保存するとか、やりたくなってくるわけですよ」「うんうん」

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

今回の公式情報が豊作だったので自分でコミットログを掘り起こさずに済みました。

⚓credentialを複数環境でサポート

# 同PRより
rails credentials:edit --environment staging

現在の環境で特定のcredentialファイルが存在する場合はそれを優先する。
これによってproduction環境ではENV["RAILS_MASTER_KEY"]からマスターキーで取得したconfig/credentials/production.yml.encを最初にチェックし、またはconfig/credentials/production.keyを読み込む。
デフォルトの振る舞いはconfig.credentials.content_pathかconfig.credentials.key_pathで変更可能
credentials APIドキュメントより

# railties/lib/rails/application.rb#L445
    def credentials
-     @credentials ||= encrypted("config/credentials.yml.enc")
+     @credentials ||= encrypted(config.credentials.content_path, key_path: config.credentials.key_path)
    end

つっつきボイス:「--environment stagingを指定できるようになったと」「ありそうでなかった」「credentialを環境で切り替えたいことがどのぐらいあるのかはさておいて😆

「このcredentialって何でしたっけ?🤔」「そこに書かれているconfig/credentials.yml.encがcredentialファイルなんですが、鍵を暗号化してこのファイルに保存することでGitとかのリポジトリに登録できるようにする機能です🗝」「これで鍵変更の履歴も追えるようになる」

「サーバーのデータベースパスワードなどは、本来なら履歴を追えるGitリポジトリのような秘密を保てない場所に登録すべきではなくて、デプロイスクリプトとかAWSのKMSから引っ張ってくるべきなんですが、こういうのって地味に大変じゃないですか: 特にHerokuを使っている場合、サーバー側で任意のスクリプトを実行しようとするときに面倒なので、リポジトリに登録できて安全かつ楽に使えるようにしようというのがcredential機能ですね」「なるほど〜そういうノリでしたか😊

パスワードをうっかりGitリポジトリに登録してしまい、後から消そうとしても以後のハッシュがごっそり変わってしまうのでチーム全員に迷惑がかかるというのはよくありますね。

参考: GitHub にパスワードとかセンシティブなファイルを push してしまったときの対処法 - Qiita

「鍵を環境変数に入れる手ももちろんあるんですけど、環境変数の容量も無限ではないんですよね: その点credentialならyamlに保存するのでサイズを気にしないで済むというメリットもあります😊」「その代り、このファイルは直接編集できない」「vimとかで開いたらあかんし、そもそも読めないという」「rails credentials:editコマンドで編集することで指定のエディタが開いて、暗号化と復号化を自動でやってくれる☺」「なるほど、ちゃんとこれを使いましょうということですね☺

「個人的にはそれほど好きな機能ではないけど、この機能が欲しい人がいるのはワカル」「確かRails 5.2で入った機能でしたね」

Rails 5.2のcredential機能をKubernetesで使う(翻訳)

「なお、credentialの肝心のマスターキーが流出したらその瞬間に全滅する💥」「🌋」「credentialを使うと、たとえばチーム開発でメンバーが退職したら即マスターキーを変更しないといけなくなるので、そこが自分にはちょっと美しくないかな😅」「そういえば以前のウォッチでもその話になりましたね」「マスターキーをKMSに保存する手もありますけど」

⚓Action Cableチャンネルの単体テストをサポート

ActionCable::Channel::TestCaseが新たに導入されます。

# actioncable/lib/action_cable/channel/test_case.rb#L21
+   module ChannelStub
+     def confirmed?
+       subscription_confirmation_sent?
+     end
+     def rejected?
+       subscription_rejected?
+     end
+     def stream_from(broadcasting, *)
+       streams << broadcasting
+     end
+     def stop_all_streams
+       @_streams = []
+     end
+     def streams
+       @_streams ||= []
+     end
+      # Make periodic timers no-op
+     def start_periodic_timers; end
+     alias stop_periodic_timers start_periodic_timers
+   end

つっつきボイス:「この間も似たようなヘルパーが導入されてなかったっけ?」「私もそんな気がしてたので後で調べます↓」

以前のウォッチの「ActionCableのテストに便利なアダプタを追加」だったようです。

「ActionCableを手動でデバッグするのは絶対ヤダ😝」「ActionCableって使うことあります?」「ちょろっと作りたいときにはActionCableは便利だと思いますね: 何か処理が終わったときに右上あたりにピコっと通知を表示したいなんてことはよくあるんですが、そういうシンプルな通知機能を作るときなんかにいい」「そのためにワーカースレッドを1つ使うことになりますが😆、管理画面ぐらいだったらユーザー数が少ないので問題なくやれる」

⚓サポート外のストアにあるキャッシュキーを誤って使わないよう修正

# activerecord/lib/active_record/railtie.rb#L91
+   initializer "Check for cache versioning support" do
+     config.after_initialize do |app|
+       ActiveSupport.on_load(:active_record) do
+         if app.config.active_record.cache_versioning && Rails.cache
+           unless Rails.cache.class.try(:supports_cache_versioning?)
+             raise <<-end_error
+You're using a cache store that doesn't support native cache versioning.
+Your best option is to upgrade to a newer version of #{Rails.cache.class}
+that supports cache versioning (#{Rails.cache.class}.supports_cache_versioning? #=> true).
+ Next best, switch to a different cache store that does support cache versioning:
+https://guides.rubyonrails.org/caching_with_rails.html#cache-stores.
+ To keep using the current cache store, you can turn off cache versioning entirely:
+    config.active_record.cache_versioning = false
+end_error
+           end
+         end
+       end
+     end
+   end

つっつきボイス:「リサイクラブル!」「プルリク投げたschneemsさんは、TechRachoでも何度か翻訳記事出しています」「この設定↓が追加されるそうです」「しれっとマージされたっぽいけど、もしやバグ?😆

config.active_record.cache_versioning = true

「あー、キャッシュエンジンがバージョニングに対応しているかどうかで挙動を変えないといけなかったってことか」「なるほど〜」「ファイルストアなのかmemcacheストアなのかとかmemoryストアなのかnullストアなのかで変えると」「それぞれにファイルが分かれてダックタイピングしてる」

# 同PRより
# activesupport/lib/active_support/cache/memory_store.rb
      # Advertise cache versioning support.
      def self.supports_cache_versioning?
        true
      end

「フレームワークが個別のキャッシュエンジンにどこまで対応するかというのはあるけど、Railsは割と積極的に対応する方ですね: MySQLとPostgreSQLとかも既に独自のものが入ってきてるぐらいだし、キャッシュもそういう感じで独自のものが入ってくると」「😃

⚓モデル生成に--migrations-pathsオプションを追加

# 同PRより
bin/rails g model Room capacity:integer --migrations-paths=db/kingston_migrate
      invoke  active_record
      create    db/kingston_migrate/20180830151055_create_rooms.rb

scaffoldとマイグレーションどちらでも使えるようです。

--migrations-pathsは8月末に#33760で追加されたばかりなので、どちらも新しいですね。
他にも--databaseオプションが#34021で追加されました。

# activerecord/lib/rails/generators/active_record/migration.rb#L26
        def db_migrate_path
-        if migrations_paths = options[:migrations_paths]
-          migrations_paths
-        elsif defined?(Rails.application) && Rails.application
-         Rails.application.config.paths["db/migrate"].to_ary.first
+         if defined?(Rails.application) && Rails.application
+           configured_migrate_path || default_migrate_path
          else
            "db/migrate"
          end
        end

+       def default_migrate_path
+         Rails.application.config.paths["db/migrate"].to_ary.first
+       end

+       def configured_migrate_path
+         return unless database = options[:database]
+         config = ActiveRecord::Base.configurations.configs_for(
+           env_name: Rails.env,
+           spec_name: database,
+         )
+         config&.migrations_paths
+       end

つっつきボイス:「マイグレーションパスを指定って…デフォルトでやろうよみんな😅」「db/migrate/以外にも置けるんですか」「デフォルトのマイグレーションフォルダ以外のものを使いたいときってあるのかな?」「どうしてもdb/migrateにだけは置きたくない人用だったりなんかしてね😆

「何でそんな闇を作るんですか🤣」「マジでどんな使いみちが…?🤔」「db/migrateの下でフォルダを分けたいとか?」「うーん、あるとすれば、たとえばRailsアプリを複数の顧客に納品していて、顧客ごとに違うdb:migrateをやりたい、とか?」「あるいは、experimental用のマイグレーションパスを用意しておいて、masterにはexperimentalなコードをどんどんマージするけど、そのパスでマイグレーションしないとexperimentalなコードが発動しない、とか?」「うーん」「使い方間違ってる気がするけどな〜😅

「下の方を見ると、マルチDBのdatabase.ymlに使えるんじゃ?みたいなコメントがありますね」「ははぁ、今のRailsは1マイグレーションで1つのDBにしか対応していないので、--dbと組み合わせて使えば複数データベースのマイグレーションができる、みたいな感じかな?その場合マイグレーションパスを変えないと破綻するし」「あ、確かに〜」「こんなことしないといけない状況って…とっほっほ〜😭」「自分ならridgepoleとか使うなっ😎」「ridgepole、以前のウォッチでも扱いましたね」

「おそらくですが、最近のRailsがマルチDBをサポートしたことの一環なんじゃないかな?」「あ、それならわかる😋」「マルチDBをサポートするならマイグレーションもマルチDBを使えないとね、ってことなのかも: 使うかどうかはともかく」「もうちょいスマートなやり方はありそうなので、Rails 6が出るまでにまた変わるかも😊

⚓productionでのAR属性の定義をlazyからeagerに変更

# activerecord/lib/active_record/railtie.rb#L140
+   initializer "active_record.define_attribute_methods" do |app|
+     config.after_initialize do
+       ActiveSupport.on_load(:active_record) do
+         descendants.each(&:define_attribute_methods) if app.config.eager_load
+       end
+     end
+   end

#29559↓でコントローラのアクションをeagerに定義したのと同じような要領でやったそうです。

# actionpack/lib/action_controller/railtie.rb#L80
    initializer "action_controller.eager_load_actions" do
      ActiveSupport.on_load(:after_initialize) do
        ActionController::Metal.descendants.each(&:action_methods) if config.eager_load
      end
    end

つっつきボイス:「バグ修正でしょうか?」「パフォーマンス改善かなという気もするけど、productionモードなのに想定どおりじゃなかったからバグだったのかも」

「それにしてもeagerな処理になってるかどうかをどうやってテストするんだろうか?🤔」「テストコードを見るとassert_includes Post.instance_methods, :titleでeagerかどうかがわかるってことなのかな…」「いいのかそれで😆」「なぜこれでテストできているのかはわからないけどっ🤣Rubyの挙動的にそうなのかもしれない」

⚓.psqlrcファイルを無視する-Xオプションを追加

# activejob/test/support/integration/adapters/queue_classic.rb#L3
   def start_workers
     uri = URI.parse(ENV["QC_DATABASE_URL"])
    user = uri.user || ENV["USER"]
    pass = uri.password
    db   = uri.path[1..-1]
-   %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database if exists "#{db}"' -U #{user} -t template1}
-  %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1}
+   %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -X -c 'drop database if exists "#{db}"' -U #{user} -t template1}
+   %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -X -c 'create database "#{db}"' -U #{user} -t template1}
    QC::Setup.create
     QC.default_conn_adapter.disconnect
     QC.default_conn_adapter = nil
     @pid = fork do
       worker = QC::Worker.new(q_name: "integration_tests")
       worker.start
     end

   rescue PG::ConnectionBad
     puts "Cannot run integration tests for queue_classic. To be able to run integration tests for queue_classic you need to install and start postgresql.\n"
     exit
   end

structure.sqlファイルも同様に-Xで無視されます(#34001)。


つっつきボイス:「database.ymlと.psqlrcの設定がかぶるのをどうにかする感じ?」「そのようです」「どれ、psql --helpしてみると…むむ、自分のマシンには今psqlが入ってない、なぜならDockerでやってるから😅」「こちらで見ると-X, --no-psqlrc do not read startup file (~/.psqlrc)となってました」「じゃやっぱりpsqlのオプションだ🎯: .psqlrcに起動時のシグネチャ表示設定とかあるとゴミが入って容易にバグるし」

⚓ActiveJobのテストで実行ジョブの引数のサブセットをチェックできるようになった

# activejob/lib/active_job/test_helper.rb#L362
     def assert_enqueued_with(job: nil, args: nil, at: nil, queue: nil)
       expected = { job: job, args: args, at: at, queue: queue }.compact
       expected_args = prepare_args_for_assertion(expected)

       if block_given?
         original_enqueued_jobs_count = enqueued_jobs.count

         yield

         jobs = enqueued_jobs.drop(original_enqueued_jobs_count)
       else
         jobs = enqueued_jobs
       end
       matching_job = jobs.find do |enqueued_job|
        deserialized_job = deserialize_args_for_assertion(enqueued_job)
-       expected_args.all? { |key, value| value == deserialized_job[key] }
+       expected_args.all? do |key, value|
+         if value.respond_to?(:call)
+           value.call(deserialized_job[key])
+         else
+           value == deserialized_job[key]
+         end
+       end
      end
       assert matching_job, "No enqueued job found with #{expected}"
       instantiate_job(matching_job)
     end

つっつきボイス:「job_args↓で引数を取れるようになったとかそういう感じかな?」「上のif value.respond_to?(:call)↑があるからLambdaを取って、それをハッシュ的に使うようになったっぽい: ActiveJobのデータのとり方に関係あるんだと思いますが」「ちょっと理解を超越気味🤯

# activejob/test/cases/test_helper_test.rb#L
  def test_assert_performed_with_selective_args_fails
    args = ->(job_args) do
      false
    end
     assert_raise ActiveSupport::TestCase::Assertion do
      assert_performed_with(job: MultipleKwargsJob, args: args) do
        MultipleKwargsJob.perform_later(argument2: { b: 2, a: 1 }, argument1: 1)
      end
    end
  end

⚓Rails

⚓スライド:「The Future of Rails 6 — Scalable by Default」(Rails公式ニュースより)

今年4月にピッツバーグで開催されたRailsConf 2018で発表されたスライドです。上のAction Textのプレスリリースにありました。


つっつきボイス:「Rails 6の青写真がだいたいここで説明されているようです: これまでウォッチでコミットを追いかけてきたこともあって、実際この通りに進んでいるものをいくつも見かけました」「(スライドをめくりながら)Rails 6ではスレッドベースのワーカーがデフォルトになるとか、マルチスレッドのテストがデフォルトになるとか」「ほぉ〜😳」「ここにもマルチデータベースやる宣言が出ているし、スケーラビリティがテーマになっている感」「database.ymlも書式が変わる↓」

# 従来
production:
  <<: *default
    database: db_production

production_animals:
  <<: *default
  database: db_animals_production

# 今後: 3-tier database.yml
production:
  primary: 
    <<: *default
      database: db_production

  animals:
    <<: *default
    database: db_animals_production

「さっきの#33994のマイグレーションパスの話もある」「で、こんなふうにモデルでコネクションを指定できる↓: おそらくApplicationModel的なところでこれを指定すれば個別のモデルにはいちいち書かなくていいはず」「マルチデータベースなら名前空間切るっしょ普通😎

# app/model/animals_base.rb

class AnimalsBase < ActiveRecord::Base
  self.abstract_class = true

  establish_connection :animals
end

「で、マイグレーションでもestablish_connectionを指定できる↓: これを使うのがいいのか、switchpointみたいに実績のあるgemにするのがいいかというのはあるにしても」

path = Rails.root.to_s = "/db/animals_migrate"

desc "Migrate the cluster db"
task migrate: :environment do
  ActiveRecord::Migrator.migration_path = path
  ActiveRecord::Base.establish_connection(:animals)
  ActiveRecord::Taks::DatabaseTasks.migrate
  db_namespace["db:schema:dump"].invoke
end

「ちなみにswitch_pointはマスター/スレーブ形式の割とメジャーなマルチDBのgemですね: 以下のuse_switch_pointが今のestablish_connectionに相当する感じです」「ほほぉ〜👀」「switch_pointもこの間のウォッチで(略」

# switch_point READMEより
class Article < ActiveRecord::Base
  use_switch_point :blog
end

⚓active_storage_validations gem(RubyFlowより)

# 同リポジトリより
class User < ApplicationRecord
  has_one_attached :avatar
  has_many_attached :photos

  validates :name, presence: true

  validates :avatar, attached: true, content_type: 'image/png'
  validates :photos, attached: true, content_type: ['image/png', 'image/jpg', 'image/jpeg']
end

以前のウォッチで取り上げたlog_analyzerと同じ作者です。


つっつきボイス:「こういうバリデーターってRails標準にもありそうだけど?」「自分が欲しくて作ったのかも: 入れ違いでRailsにも同じのができるか既にあるかもしれないので慌てて入れなくてもいいのかな🤔」「content_typeはセキュリティ問題につながることが多いから、こういうバリデーターは欲しい😍

⚓GitHubがRailsを3.2->5.2.1にアップグレード


同記事より

先週追加で貼った記事です。


つっつきボイス:「この記事読んでびっくりした~🤓」「3.0からかなと思ったら3.2だったという」「記事はあっさり目ですが苦労が偲ばれます」「もちろん何年もかけて準備してたでしょうね👍

「何かこんなif文↓使ってるんですけど😅」「これも時間をかけて少しずつデプロイしてるからでしょうね: バージョンが変わっても同じコードで動くようにしておかないといけないだろうし、まあしょうがない😆」「きっとユーザーも一斉に切り替わるんじゃなくて、たとえば1/10のユーザーを人柱的に新しいバージョンに移して、様子を見ながらユーザーを少しずつ移すなんてこともしてるでしょうね」

# 同記事より
if GitHub.rails_3_2?
  ## 3.2 code (i.e. production a year and a half ago)
elsif GitHub.rails_4_2?
  # 4.2 code
else
  # all 5.0+ future code, ensuring we never accidentally
  # fall back into an old version going forward
end

「記事最後の教訓にも『勢いが大事』ってありますね🏄‍♀️」「だらだらアップグレードしていると永遠に終わらないし、その間にRailsのバージョンがまた1つ先に進んじゃったりするし😆」「😆

⚓remote_bootstrap_modal: 外部リンクをBootstrapのモーダルダイアログに読み込むgem(RubyFlowより)

Railsエンジン型式だそうです。

  1. アプリにBootstrapをインストールしておく
  2. Gemfileにこのgemを追加
  3. アプリのレイアウトファイルに<div id="modal-holder"></div>を追加(ここでモーダルがレンダリングされる)
  4. app/assets/javascripts/application.jsでjQueryよりも後に//= require remote_bootstrap_modalを追加
  5. respond_toに応答させるフォーマットを設定(:html:jsonなど)
  6. コントローラでrespond_modal_withに必要な引数を渡して呼び出す
  7. モーダルに読み込みたいリンクにdata: { modal: true }を渡す(例: link_to 'Customers', customers_path, class: 'btn btn-default', data: { modal: true }

つっつきボイス:「↓こういうことができるみたいです」「data: { modal: true }とすると指定したURLがこうやってモーダルの中でさくっと開くということね😋最初の方しか見てないけどやりたいことはだいたいワカル」


同記事より

「モーダル、使うかなぁ?」「たとえば単語の意味をWikipediaで引いてモーダルで表示したいなんてときに使えるかも: 実はこういうgemってあると地味に便利😍」「おぉ〜😋

⚓Railsでjoinするときの生SQLを見つつ最適化する(Hacklinesより)


つっつきボイス:「これに近い最適化はたまにやるかな: 割とよくあるヤツ」「最終的にexec_queryしてるし😆: まあ生SQLで書くと結局こうなるし」

# 同記事より
# Find Payment RMH
rmh_raw_query = "SELECT ri.name, ri.amount, inv.id FROM remittances ri LEFT JOIN invoices inv ON LOWER(ri.name) = LOWER(inv.number) WHERE ri.payment_id = #{payment.id} ORDER BY remittance_sequence_number ASC"
rmh_records = ActiveRecord::Base.connection.exec_query(rmh_raw_query).rows
payment_rmh = rmh_records.map{|rme| {name: rme[0], amount: rme[1].to_f.round(2),invoice_id: rme[2]}}

⚓Dockerコンテナを複数プロジェクトで共有する(Hacklinesより)


つっつきボイス:「お、まるで今日自分が社内で発表した内容みたいだ」「今日たまたま見つけた記事なんですが、怖いくらいシンクロニシティを感じてしまいました」「複数のdocker-compose.ymlがあってそれらを連携させるという、まさに今日話した内容😆: そうそう、networks:にはこうやってexternal:オプション↓を付けないと参照できない」

# project_one/docker-compose.yml
version: '3.2'

networks:
  default:
    external:
      name: my_net

services:
  # ...

「今日話したときは、これ以外にconfigファイルを生成するときにenvsubstを使わないと2つのdocker-compose間での環境変数を同期できなくてつらかった話なんかもしたナ」「そうでしたね😋

「docker-compose使います?」「大好きですね〜😋」「docker-composeを複数使い始めると、なぜKubernetesが必要になるのかがとってもよくわかってきますよ😭」「自分は複数使ってたっけか…?あ、1つあった気がする: 確かにつらいです😭」「Kubernetesで言うpodが、1つのdocker-composeに相当する感じで、Kubernetesならpod間の連携も定義できる💪」「なるほど〜」「こういう連携をやり始めるあたりが、いわゆるオーケストレーションツールが必要になってくる境目でしょうね: 個人的にはしばらくdocker-composeでもう少し頑張りたい気はするけど」「まだKubernetesに魂は売りたくないです〜🤣」「少なくともKubernetesクラスタを自前で管理するのは絶対やりたくない🤣」「🤣

参考: Pods - Kubernetes
参考: オーケストレーション (コンピュータ) - Wikipedia

⚓Ransackパラメータをsearch_form_forなしで作成する(Hacklinesより)

# 同記事より
# in index.html.erb

<%= search_form_for @q do |f| %>

  # Search if the name field contains...
  <%= f.label :name_cont %>
  <%= f.search_field :name_cont %>

  <%= f.submit %>
<% end %>

つっつきボイス:「あぁこのぐらいなら全然大した複雑さじゃないし: もっと極まったのがいくらでもあるし😎」「😆」「何か複雑なサンプルはと…これでも全然やさしい方だし↓: Ransackの本当の地獄はorとandが出てきてからでしょう、やっぱ💀」「😆」「😆」「そういえば以前のウォッチでも教わりました」「orやandが出てくるとJSONがネストし始めるので、JSONを自分で組み立てる気がなくなる感がヤバい😭

参考: Complex Object Searching in Rails with Ransack | Lewis’ Blag

# 参考記事より
class Ticket < ActiveRecord::Base
  belongs_to :person

  def self.search(query = nil)
    if query
      params = {
        full_name_cont: query,
        subject_cont: query,
        id_eq: query
      }
    end

    ransack(params).result(distinct: true)
  end
end

週刊Railsウォッチ(20180702)Ruby 2.2メンテ正式終了、Ransackがつらくなるとき、書籍『Domain-Driven Rails』、GitHubの高可用MySQLほか

⚓その他Rails


つっつきボイス:「このツイート↑が反響を呼んでました」「この間もそんな話になりましたけど、RSpecの書き方って最初に誰かが『これです!』って教えてくれないと本当に迷う😅」「そういえばmizchiさんがこの間出した良記事があるんですけど」「お、その記事この後に貼ってあります💪」「その記事にもあったように、新しいフレームワークなどが出たときに最初の時点できれいな雛形を書いてくれる人がいると、後の人はそれを追いかけるだけでよくなるのでとってもありがたい❤」「最初が肝心」

参考: Everyday Rails – RSpecによるRailsテスト入門 Aaron Sumner 著 et al. [Leanpub PDF/iPad/Kindle]
参考: 【英語論文TIPS】意外に知らないet al.の書き方、読み方 | 英語猿

「et al.?」「上のEveryday Railsのリンクタイトルにあって気になったので: エトセトラ(etc.)のラテン語版みたいなやつだそうです」「ああ確かに論文ではよく見かけますね😋: ちょいとカッコつけてる感😆」「漢文の熟語みたいな感じで😆

⚓Ruby trunkより

⚓提案: 配列に共通集合があるかどうかをチェックするArray#intersect?


つっつきボイス:「はてな付きのArray#intersect?だそうです」「ああ、(a1 & a2).any?のショートハンドね」「元々#intersect?ってなかったっけ?」「Setクラスの#intersect?はあるようなので、それのArray版が欲しいということみたい」「そうそう、Setなら&でできるんだった」

参考: instance method Set#intersect? (Ruby 2.5.0)

⚓日本の新元号のUnicode合字どうするよ?


つっつきボイス:「martin先生からの//の次をRubyではどうしようかという問いかけです」「次の年号っていつ決まるんでしたっけ?」「改元の1か月前だった気がする」「マジで!😰

参考: 新元号公表、改元1カ月前に システム改修対応促す  :日本経済新聞

「コードポイントに空きが1個だけあったような気がするけどどうだったかな…」「Macの文字パレットで見るとこんなんなってますけど↓」「くわっ😱」「空きがあるっちゃあるけど、の前後に半端に空きがあるし」「今後どうする気なんだろう😅」「まあ空きさえあれば実装はできますけどね」「コメントでnaruseさんが面白がってるのは、きっと開き直ってる😆」「『今後の展開が楽しみです』的な😆

「元号が決まったら、例のwareki gemが即対応するだろうな」「wareki?そんなものがあったんですか😳」「皇紀も含めて日本の昔の元号もやたら充実してます↓」「何これ〜🤣」「漢数字も対応してます💪」「『作者は暦の専門家ではまったくありません』という一文が😆」「こんなの使うところあるんですか?」「特に官公庁は公式文書が元号ベースなので、普通に業務アプリでよく使われてますよ😋

# warekiリポジトリより
# 和暦のパース (標準 Date インスタンス)
Date.parse("㍻一〇年 肆月 晦日")       # => #<Date: 1998-04-30 ...
Date.parse("安政七年 弥生")           # => #<Date: 1860-03-22 ...
Date.parse("元仁元年閏七月朔日")       # => #<Date: 1224-08-17 ...
Date.parse("萬延三年 5月 廿一日")     # => #<Date: 1862-06-18 ...
Date.parse("皇紀二千皕卌年")          # => #<Date: 1580-01-17 ...
Date.parse("正嘉元年 うるう3月 12日") # => #<Date: 1257-04-27 ...

# Wareki::Date を直接扱う場合
Date.today.to_wareki_date # => Wareki::Date インスタンス
Wareki::Date.parse("正嘉元年 うるう3月 12日") # => Wareki::Date インスタンス
Wareki::Date.new("明治", 8, 2, 1).to_date   # => 標準 Date インスタンス 1875-02-01

「そういえば省庁が順次西暦に切り替えると発表してましたね↓」「やるならぜひ一律でやって欲しい😅

参考: 省庁データ、西暦に一本化=証明書など元号継続-政府:時事ドットコム

⚓Ruby

Railsのエントリが多かったので今回は控えめです。

⚓JRubyに移行する理由(Hacklinesより)


つっつきボイス:「JRubyのノウハウを持っている人がいるならやってもいいけど」「JRubyを使う理由というと、Javaを愛しているからでなければ、後はJavaのライブラリを使いたい場合かな」「それはありますね」「私も最初に触ったRailsはJRubyで動いてました☕

「Javaのライブラリで使いたいものと言えば、やっぱりApache POIでしょう↓」「POI?!」「丸めてポイ?」


同サイトより

「POIは信じられないほどよくできていて、世の中にあるExcelを操作できるJavaのライブラリとして最も優れているのがこれ」「知らなかった〜!😳」「POIはJavaの世界ではもう必須😎」「POIなら、人間にしか作れなさそうな神Excelですらグラフなんかも含めて作れるし」「マジで!?」「RailsとかでExcelを扱うgemは他にもいろいろありますけど、画像貼ったりグラフを作ったり、あとマクロも入れられるんだったかな?」「画像は実装したことある
」「しかも画像の位置調整や印刷のサイズ合わせまでできるので、Excelを印刷フォーマットと思っている人の要求にも応えられるほど再現性が高い」「言葉が出てこない…😳

「POIってExcelの外部仕様を元に作ったんでしょうか?」「何でここまで作り込まれているのかは謎: こういうのが欲しい人が作ったんでしょうね」「RubyからJava経由でPOIを呼び出すこともできるけど、それだと遅いので、そういう人がJRubyを使いたいんじゃないかな🤔: 個人的には他にJRubyを使う理由が思いつかないけどっ😆」「POIはSIな人ならきっと知ってる😆」「とにかく信頼性というか実績が凄い: セル結合されているファイルとか、隠しワークシートなんかを正しく読めると確信できるのは、たぶん他にはない」「パスワードロックなんかも対応できそう」「Encryptionサポートがあるからできますね」「バイナリのExcelでも扱える」「えぇ〜!😳」「何しろ歴史が長いから(最初のバージョンが出たのは2001年)、XLSXじゃなくても扱える」「Excel 97以降が扱えるんですか!」「Excelより先にOSの期限が切れそう😅: Excel 97が動くWindowsって今あるんだろうか?」

「個人的には使わずに済めばそれに越したことはないけど😆、いざというときの最終手段としてPOIは知っておきたい」「いいこと聞いた😋」「きりがないので次行きましょう〜」

⚓あなたがたぶん知らない5つのRuby技(Hacklinesより)


つっつきボイス:「このHash.newにブロックで渡すやつ↓、あまり使ったことないけど、キーと値を入れ替えるときか何かで使った気がする: まあこういうコードは正直書いて欲しくないけど😆

# 同記事より
h = Hash.new { |h, k| h[k] = {} }

h[:layer_1]           # => {}
h[:layer_1][:layer_2] # => nil

「ああヒアドキュメントにメソッドチェインするヤツか↓: <<-SQL.gsub("\n", ' ').squishみたいな書き方ができる」「え、ここにメソッドチェインするの?😳」「squishはRailsのメソッドか」

# 同記事より
sql = <<-SQL.gsub("\n", ' ').squish
SELECT MIN(u.popularity)
FROM users u
WHERE
  u.created_at <= #{Time.now} AND u.created_at >= #{Time.now - 7.days}
SQL

puts sql

参考: Active_supportのString#squishで邪魔な改行文字や空白を綺麗にする - Bye Bye Moore

「ちなみにですが、ヒアドキュメントはあのRubyMineですらシンタックスハイライトがうまくいかなかったりします」「難易度高そう😭

「最後のは単項演算子のオーバーライド↓はやって欲しくないヤツ」「Trueなんてクラス、Rubyにありましたっけ?」「あ、true.classするとTrueClassが出てくるから独自クラスか」

# 同記事より
class True
  def -@
    false
  end

  def +@
    true
   end
end

t = True.new

p -t
p +t

「まあRubyの隠し機能なんて、『Ruby隠し機能カルタ』なんてのが作れるぐらいいくらでもありますから😆」「😆

⚓GitLabコントリビュータにインタビュー(Hacklinesより)


つっつきボイス:「GitLabのWeb IDEが好きだそうです」「Full stack developer!」「強い😆」「そういえばGitLabのコミッターに日本人が1人いた気がする」

⚓クラウド/コンテナ/インフラ/Linux/Serverless

⚓オンラインプロジェクト管理


同サイトより


つっつきボイス:「Backlogは最近BPS社内でも一部使われ始めてて、機能がシンプルでわかりやすい分、細かなことはできなかったりもする」「ところでBacklog使ってます?」「この間使いにくいって別のツールに移りました😝」「お、そうでした?」「そういう人はどういうツールを使ってます?Jiraとか?」「Asanaです」


同サイトより


同サイトより

「プロジェクト管理ツールもさまざまですけど、個人的には機能が少ないものが好き❤」「GitHubがこれまで日本語化してこなかったので、日本のそうしたシェアをBacklogがかなり取っているらしいと聞きました」「Backlogはシェア大きくて、官公庁の仕事でよく使われています: おそらくポイントは日本語が100%とおって、日本語サポートがあって、あと円で決済できること」「それ重要!」「Redmineだと難しいと思う人もいそうですね」「Redmineは細かく設定できる分複雑なので、サーバーのおもりをする人も必要🤔: Redmineはチケット職人がいると運営がとてもスムーズだし(いないとつらいけど)、秩序立てて管理することを徹底できればとてもいい」


同サイトより

「BacklogはMarkdownに対応しているのがとりあえずありがたい(Redmineはアドオンが必要)」「Backlogは、メンバーに非エンジニアがいる比較的小さな案件で導入しやすいですね😋: BacklogにはGitのリポジトリ機能もあるけど、エンジニアには物足りないかもしれない」

「こういうツールは好みや案件との相性などもあるので一概には言えませんが、Backlogはその中では割といい線行っていると個人的には思います😋

⚓SQL

⚓sqlfmt: 独断に基づくSQLフォーマッタ(DB Weeklyより)


同サイトより


つっつきボイス:「↓こうやって縦を揃えたい派っぽい」「あーなるほど👀」「書籍に印刷されるSQLにこういうスタイル多いですよね」「気持ちはわかるけど😆


同記事より

「まあたとえばWHEREANDをどういうスタイルで配置する人が多いのかなというのはちょっと気になるところかも: ANDの後で改行する人と、ANDを次の行の頭に書く人とか」「ANDを行の頭に書くと他のSELECTINSERTと同じところに並ぶのがいや、という意見もありそうだし」「ありそう〜」

⚓JavaScript

⚓mizchiさんの「TypeScript入門以前ガイド」


つっつきボイス:「さっきもお話に出たmizchiさんのブログです」「そうそう、これはいい記事😍: 上から順に読んでいけばJavaScriptの大きな流れが押さえられるようになっている」「ミジンコから人間へ進化する過程というか😆」「最後の方でちょっと力尽きた感があるのがまた親しみが持てるし😆」「普段からTypeScript書いている人にはだいたい当たり前の話ですが、初めてTypeScriptを書く人やES2015をちゃんと書いたことのない人にはうってつけの力作❤

⚓CSS/HTML/フロントエンド/テスト

⚓WordCamp: WordPressの巨大コミュニティによるイベント

イベントは既に終了していますが。
WordPressコミュニティはRubyコミュニティの数倍程度の規模で、かつ集まっている方々のWebデザインの水準が非常に高いと小耳に挟みました。


つっつきボイス:「WordPressのコミュニティは知らない世界」「デザイナーがたくさん集まっていて水準も高いらしいです」「あとSEO関係者も😆」「WordPressとパフォーマンスに関するスピーチ↓もある」「KUSANAGIみたいな高速WordPressホスティングサービスがあるぐらいなので、WordPressのパフォーマンスへの関心は高いと思う: 自分もWordPress高速に動かしたいし🚅」「TechRachoもこういうところに置けたらいいなと思ったりします😃」「今のTechRachoは?」「自前サーバーです」


同サイトより

「話は少し違いますけど、私たちのところではLOLIPOP!を使うことがありますね」「え、そのレンタルサーバー知らなかった💦」「昔からありますよ?」「普段AWSとさくらぐらいしか耳にしてなかった…😅


同サイトより

⚓AsciiDoc: パブリッシングを意識したマークアップ言語


同サイトより

拡張方法が仕様で定められていて、Markdownよりもレイアウトを細かく記述できるようです。Markdownユーザー向けというより、XMLを捨てたい人向け?

参考: AsciiDoc - Wikipedia
参考: AsciiDoc入門 - Qiita


つっつきボイス:「日本の企業ユーザーが多いらしいです」「へぇ、こういうポストMarkdown的なドキュメントエンジンだとSphinx↓の方が有名かと思ってた」「お、知りませんでした💦


同サイトより

「Sphinxは歴史も長いし、LaTexも含めてひととおりのフォーマットに対応してるし↓、ドキュメントエンジンでいろんなフォーマットに出したいというとたいていSphinxという印象💪」「Pythonベースみたいですね」「それもたぶんあまり気にされてないかも: 自分は使ったことないし使うこともたぶんないと思うけど😆


http://www.sphinx-doc.org/en/master/contents.htmlより

⚓言語よろずの間

⚓icecream: C++の分散コンパイラicecc

参考: Icecream - らるるの自宅と職場を往復する人生@それをすてるなんてとんでもない!


つっつきボイス:「babaさんの日報で知りました」「分散コンパイラは全然詳しくないなー: たぶんクラスタでどかんとビルドするんだろうけど」「ちなみにbabaはBPSのCTOで、超縦書↓というブラウザベースのEPUBビューアを主にやっているんですが、あれほどの規模になると分散コンパイルが必要なのは想像できるし、何しろそのためだけに超高速のSSDをマシンのPCI Expressに直接ぶっ刺してるほど: ディスクがどうしてもボトルネックになるから」「おー😳」「『コンパイルが30分で終わる❤』って喜んでました😊」「(以下昔のビルド話)」

参考: PCI Express - Wikipedia

⚓その他

⚓全米のスマホで「大統領アラート」試験が実施

これが全米のスマホに一斉表示されたそうです。


つっつきボイス:「Facebookで知人がこれを見てびっくりしたと言ってたので」「これはびっくりするヤツ😨」「大統領アラート、何に使うんでしょうね?」「やっぱりミサイルが飛んできたときかしら🚀

⚓番外

⚓太陽を周回する新天体「ゴブリン」が発見

Dwarfとなっていますが、質量はもしかすると地球並かそれ以上かも?


つっつきボイス:「Dwarf planet?」「惑星未満の準惑星ってことみたいです」「この軌道がめちゃめちゃアウトしていてスゴイ」「地球の近くまで来たら何か起きるかな?👽」「そのときボクら生きてるんでしょうかね😆」「公転周期どえらく長そうだし😆」「お、予定時間ピッタリに終わった🎉」「ではこのまま親睦会に移りましょう🍻

参考: 準惑星 - Wikipedia
参考: 東京新聞:太陽との距離、海王星の80倍 新天体を米チーム発見:社会(TOKYO Web)


今回は以上です。公開つっつき終了後の親睦会もいつものように盛り上がりました🍻🍕。ご参加いただいた皆さま、お疲れさまでした&ありがとうございました!🙇

バックナンバー(2018年度後半)

週刊Railsウォッチ(20181001)Railsアップグレード記事と各種支援ツール、CLI向けRubyワンライナー集、Rubyアプリをワンバイナリ化ほか

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

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

Rails公式ニュース

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

DB Weekly

db_weekly_banner

週刊Railsウォッチ(20181015)Rails初心者と一発でバレる書き方、次のVue.js構想、RubyのOpenStruct、Twilioほか

$
0
0

こんにちは、hachi8833です。Pixel 3がちょい気になっています。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを社内有志でつっついたときの会話です👄

つっつきボイス:「お、Pixel 3か、値段の割にストレージが少ないんですよね📱: 今の自分のiPhoneも、いつか見るつもりのNetFlix動画やら音楽やらであっというまにストレージ埋まっちゃうし」「私はWikipedia(日と英)がまるっと入ればいいかな…」「Pixelを見てると、ちゃんとしたスマホを作れば結局このぐらいの値段になるよなって思いますね😎

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

⚓名前付きスコープに委譲するメソッドを定義のタイミングで生成

名前付きスコープへ委譲するメソッドは、リレーションでmethod_missingが呼び出されるときに定義される。
名前付きスコープ内のレシーバは#29301以降、一貫性のためdefault_scopeなどと同様にリレーションに変更されている。
大半の名前付きスコープはmethod_missingでリレーションから委譲される。というのも、Relationのインスタンスメソッドと競合しないよう#31179でスコープの定義を許さなくなったため。しかし、名前付きスコープと同じ名前のメソッドがスーパークラス(Kernel.openとか)のいずれかのメソッドで定義されていると、リレーションのmethod_missingが呼び出されていなかった。
問題修正のため、名前付きスコープに委譲するメソッドは定義時に生成されるようにする。
同PRより大意

# activerecord/lib/active_record/relation/delegation.rb#L34
+     protected
+       def include_relation_methods(delegate)
+         superclass.include_relation_methods(delegate) unless base_class?
+         delegate.include generated_relation_methods
+       end
+     private
+       def generated_relation_methods
+         @generated_relation_methods ||= Module.new.tap do |mod|
+           mod_name = "GeneratedRelationMethods"
+           const_set mod_name, mod
+           private_constant mod_name
+         end
+       end
+       def generate_relation_method(method)
+         if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method)
+           generated_relation_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
+             def #{method}(*args, &block)
+               scoping { klass.#{method}(*args, &block) }
+             end
+           RUBY
+         else
+           generated_relation_methods.send(:define_method, method) do |*args, &block|
+             scoping { klass.public_send(method, *args, &block) }
+           end
+         end
+       end
+     end
+   end

つっつきボイス:「generated_relation_methods@generated_relation_methods ||= Module.new.tapでメモ化してから定数をprivateにして渡している」「generated_relation_methods.send(:define_method, method)define_methodを使って定義してるんですね」「難しい…」

参考: instance method Module#define_method (Ruby 2.5.0)
参考: instance method Module#private_constant (Ruby 2.5.0)

⚓関連付けの再読み込み時にQueryCacheをクリアすることで振る舞いを一貫させた

# 「Post --has_many--> comments」「Post --has_one/belongs_to--> author」が前提

# この場合QueryCacheがクリアされ、レコードが読み込まれる
post.reload

# この場合QueryCacheの外でクエリが実行されることでレコードが読み込まれるが
# QueryCacheはクリアされない
post.reload_author

# この場合レコードは読み込まれず、QueryCacheもクリアされない
post.comments.reload

つっつきボイス:「現状が上↑のようになってると」「関連付けがある場合とそうでない場合でのキャッシュのクリア方法を揃えたってことね☺」「バグではなさそう?」「バグではないと思うけど元の挙動がわかりにくいしバグの温床になりやすいからでしょうね😎: で、以下のように↓キャッシュのクリアを一貫させるようになったと」

    # 以下はいずれもキャッシュをクリアするようになった
    post.reload_category
    post.reload_author
    post.comments.reload

「キャッシュはだいたい難しいですね: 細かく制御しようとするとよくバグるので🕷

⚓skip-webpack-installオプションを追加

# railties/lib/rails/generators/testing/behaviour.rb#L67
        def run_generator(args = default_arguments, config = {})
          capture(:stdout) do
            args += ["--skip-bundle"] unless args.include? "--dev"
            args |= ["--skip-bootsnap"] unless args.include? "--no-skip-bootsnap"
-           args |= ["--skip-javascript"] unless args.include? "--no-skip-javascript"
+           args |= ["--skip-webpack-install"] unless args.include? "--no-skip-webpack-install"
             generator_class.start(args, config.reverse_merge(destination_root: destination_root))
          end
         end

つっつきボイス:「お、今まではスキップできなかった?」「今までは--skip-javascriptだったのが削除されて--skip-webpack-installに変わったみたいです」「なるほど: skip-javascriptだとJavaScriptを全部無効にするみたいに見えてよくないという考え方もあるし🤔」「Webpackerのインストールが重いから外せるようにしたとか?」「むしろNode.jsなどのJSエンジンが入ってないとrails newに失敗するから、それを回避するためかも: --skip-webpack-installすればJSエンジンがなくても動くんじゃないかな?と予想してみる」

⚓db:migrate:statusDatabaseTasksに引っ越し

# activerecord/lib/active_record/railties/databases.rake#L150
    desc "Display status of migrations"
    task status: :load_config do
-     unless ActiveRecord::SchemaMigration.table_exists?
-       abort "Schema migrations table does not exist yet."
-     end
-     # output
-     puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n"
-     puts "#{'Status'.center(8)}  #{'Migration ID'.ljust(14)}  Migration Name"
-     puts "-" * 50
-     ActiveRecord::Base.connection.migration_context.migrations_status.each do |status, version, name|
-       puts "#{status.center(8)}  #{version.ljust(14)}  #{name}"
-     end
-     puts
+     ActiveRecord::Tasks::DatabaseTasks.migrate_status
    end
  end

つっつきボイス:「単なる移動にしては、コメントを見るとやけに喜ばれてますね」「『Nice refactor! 👏』とか😊」「気になってたけど後回しにしてた箇所をリファクタリングしてくれてありがとう!みたいな感じかな: お、今までなかったテストも追加されているし: コードの移動だけでこのテストが増えるはずはないから」「あー、移動前はrakeタスクだったからテストがやりにくかったんだ!」「あー、それで喜ばれたのか😃」「移動のついでにカバレッジも増やしてくれたから」

rakeタスクのテストってかなりやりづらいし、タスクをrunするのも遅いし、rakeのテスト書きたくない😆」「ほんにそうですね😣

『Nice refactor! 👏』でこれを思い出してしまいました↓。

⚓ActiveRecordにconnects_toconnected_toを実装

モデルで複数のデータベースに接続する機能と、それらをブロックで切り替える機能を実装したそうです。

class AnimalsBase < ApplicationRecord
  connects_to database: { writing: :animals, reading: :animals_replica }
end
ModelInPrimary.connected_to(database: { slow_readonly: :primary_replica_slow }) do
  ModelInPrimary.do_something_thats_slow
end

つっつきボイス:「レプリカを参照できるようになった!」「あーなるほど、上のconnected_toは、たとえばこのブロックの中でだけデータベースを切り替えたいときに使うのか😍」「どんな場合でしょう?」「確かにこういうのやりたいことある: たとえばものすごく重いデータベースクエリ(集計クエリとか)を管理画面から出力したいとすると、通常のDBにそういう重いクエリを投げてしまうとフロントまで重くなってしまうんだけど、そういうクエリだけ別の集計用レプリカDBに投げる、なんてことができる」「お〜」「その処理自体は大したコードじゃなくて、その場でちょろっとできればいいのに、これまではそのためにdatabase.ymlを書き換えないといけなかった: でもこれなら不要になるし」「お〜😃

「DHHの#33877の続きみたい」「その#33877の話を見ると、まさに先週のウォッチで話したマルチDBがらみそのものですね: やっぱりこのあたりはまだまだ変わる可能性ありそう😆」「そういえばプルリクのコメントもめちゃ伸びてますね」

⚓テンプレートやパーシャルのアロケーション情報を表示

変更前:

  Rendering posts/new.html.erb within layouts/application
  Rendered posts/_form.html.erb (9.7ms)
  Rendered posts/new.html.erb within layouts/application (10.9ms)
Completed 200 OK in 902ms (Views: 890.8ms | ActiveRecord: 0.8ms)

変更後:

  Rendering posts/new.html.erb within layouts/application
  Rendered posts/_form.html.erb (Duration: 7.1ms | Allocations: 6004)
  Rendered posts/new.html.erb within layouts/application (Duration: 8.3ms | Allocations: 6654)
Completed 200 OK in 858ms (Views: 848.4ms | ActiveRecord: 0.4ms | Allocations: 1539564)

つっつきボイス:「コンソールの項目が1個増えてAllocations: 6004みたいに表示されるそうです」「ロガーで出てくるヤツ」「このアロケーションってメモリということでいいんでしょうか?」「単位がわからんけどpayloadと書いてあるし、たぶん普通にメモリのバイト数だと思うけどな〜🤔」「ともあれこういう情報が出てくれるとアプリが遅くなったときの原因特定がやりやすくなりますね❤: アプリのビューがでかすぎてパフォーマンスが落ちたときとか」

⚓Rails

⚓無名コントローラでconcernsをテストする(RubyFlowより)


つっつきボイス:「とてもあっさりした記事ですが」「モジュール名にあるRescuerって英語的にありなんだろか?😆」「造語っぽい響き…と思ったら辞書にあった💦」「モジュールだから-erで終わらせたかったんだろうなきっと🤓

「この記事みたいな書き方、確かあったはず🤔」「うん、ポイントはRSpecの中でこうやって一時的にコントローラを作れること↓: その中でモジュールをインクルードしておいてそのコントローラをテストする」

# 同記事より
describe BillingErrorRescuer, type: :controller do
  controller do
    include BillingErrorRescuer

    def action
      @account = FactoryBot.create :account
      raise Billing::LimitExceeded
    end
  end

  # 略
end

「元々は記事にあるBillingErrorRescuerモジュールをテストしたいんだけど、モジュールは直接テストできないのでインクルード用に何かダミーのコントローラが欲しい、でもそのコントローラはproductionでは使わないので本編のコードには置きたくない、ていうときにこれを使うんじゃないかな」「なるほど!」「このcontrollerってメソッド、ちゃんとRSpecに生えてるみたいですね: 元々RSpecにあるのかな?🤔」「おー、あったあった↓🎯」「確かにこういうのはRSpecにないとできないヤツ: controller(ApplicationControllerSubclass)みたいに引数も渡せると」

参考: anonymous controller - Controller specs - RSpec Rails - RSpec - Relish

# relishapp.comより
require "rails_helper"

class ApplicationController < ActionController::Base
  class AccessDenied < StandardError; end
end

class ApplicationControllerSubclass < ApplicationController

  rescue_from ApplicationController::AccessDenied, :with => :access_denied

private

  def access_denied
    redirect_to "/401.html"
  end
end

RSpec.describe ApplicationControllerSubclass do
  controller(ApplicationControllerSubclass) do
    def index
      raise ApplicationController::AccessDenied
    end
  end

  describe "handling AccessDenied exceptions" do
    it "redirects to the /401.html page" do
      get :index
      expect(response).to redirect_to("/401.html")
    end
  end
end

「これならモジュールの単体テストがキレイに書けるので、当然これ使う方がいいですね😋: なおこのモジュールは本来はconcernとしてコントローラで使います」

⚓render_async 2.0がリリース(RubyFlowより)

関連記事を昨年翻訳したrender_asyncの2.0では、jQueryが素のJavaScriptに変更され、IEでのエラーハンドリングやイベント発火などに対応したそうです。

Rails: render_async gemでレンダリングを高速化(翻訳)


つっつきボイス:「お、2.0になってjQueryじゃなくなったし👍」「ちなみにrender_asyncがやってることは全然大したことなくて😆、以下のようにerbでrender_async書くとAjaxをロードしてdocument.writeとかができるようになる」「へぇ〜」「古いIEの対応も向上したようです」「render_asyncは、ものすごくアクセスの多いサイトなんかでは効果的だと思います😊: 某漫画サイトとかなら使いたいやつでしょうね😆」「😆

<-- 同リポジトリより -->
<%= render_async comment_stats_path %>

⚓Screencast: Twilio APIのService Object

Service Objectの話題にと思って取り上げましたが、つっつきは予想を超えてTwilioの話になりました😊


つっつきボイス:「Twilioは、SMS(ショートメッセージサービス)とかと電話を連携させたりするAPIサービスで、かなり前から有名ですね☎: 確かテキストをXMLで放り込むと音声を再生できるんじゃなかったかな(Programmable Voice)」「知らなかった〜」


twilio.comより

「これも確かXMLで設定を投げると、電話の音声案内によくある『〜の場合は1、〜の場合は2を押してください』みたいなのをやってくれたと思う」 「ふむふむ」「サポセン構築を支えるサービスでもあるんですね」「Twilioは日本法人もあるはずで、(ググると)そうそう、KDDI↓」「確かに電話会社ならやりたいAPIサービスでしょうね」「よくあるSMS認証なんかもこれでさっとやれる: サイトで電話番号を入力するとそこに自動でSMSが送信されるヤツですね」「お〜」「SMS認証サービスは他にもいろんな会社もやってますが、Twilioは随分前からやってる」


twilio.kddi-web.comより

参考: 企業が「SMS認証」を導入するワケ!? | SMS送信サービス 「空電プッシュ」

「お、Faxサービスもある😆」「やっぱ日本に進出するにはFaxもサポートしないとあかんのでしょうね🤣」「未だにFax使ってるなんてもう日本ぐらい🤣

「そういえばAWSのアカウント作成にもSMS認証使われてますね: アカウントを作ると電話がかかってきて、キーを音声で知らせてくる」「そういえばそう!」「そういうのも含めた文字の音声化や音声の文字化もやれる💪

「Twilioは老舗なんで、その分APIも割と古い感じだった覚えがあります(今はわかりませんが)」「XML使うあたりがそんな感じですね: 今ならJSONとかYAMLでやりたいでしょうし」「TwilioはWebRTCと連携したり、あと確かSIPサーバーと直結させることもできたりとか、実は割と高機能」「確かに〜」

参考: WebRTC - Wikipedia
参考: SIPサーバ(SIP server)とは - IT用語辞典

「Twilioは日本のバックエンドがKDDIなんで、たぶんめったなことでは落ちないだろうという期待もできるし😎」「ちなみにもしこういう上位のTierで障害が起きたらたぶん日本のインターネットのかなりの部分に影響する💀」「ひょえ~」「Tier 1かどうかはどの地域から見るかで違ってくるところもあるので一概には言いにくいですが、日本のTier 1というとIIJWikipediaを見るとNTTコミュニケーションズがアジア地域でTier 1: お、Tier 1のSprintもソフバン傘下になっているからTier 1か」「それにしてもさっきから障害関連の情報ってなぜかググってもうまく出てこないし…」「もしかして検索結果に出にくくなってるとか😆

参考: ソフトバンクが最上位インターネットプロバイダ「Tier 1」に:Geekなぺーじ
参考: ティア1 - Wikipedia


Wikipediaより(CC BY-SA 3.0

そういえば子どもの頃読んだ科学読み物で、モナリザの体格や身長から声を推測してモナリザの声をコンピュータで再現するという記事を見たことがあります。当時はまだ大型コンピュータやミニコンの時代だったので大変だったと思います。以下はもっと新しいもののようです。

⚓Active Storageの添付ファイルを削除する(RubyFlowより)


つっつきボイス:「このCodemy.netっていうサイト、意外にscreencastが盛りだくさんですね」「動画の下に解説部分があるからすぐ見えるのがいいな: リスト部分はもっと狭くていいんだけど😆


codemy.netより

# 同screencastより
class Post < ApplicationRecord
  # ...

  # add :remove_cover_picture to attr_accessor
  attr_accessor :state_event, :remove_cover_picture

  # add purge_cover_picture callback
  after_save :purge_cover_picture, if: :remove_cover_picture
  private def purge_cover_picture
    cover_picture.purge_later
  end

  # ...
end

「へー、Active Storageにpurgeってメソッドあるんだ↑」「そこは自分で実装するんですね😆: carrierwaveならremove_なんちゃらって付ければそう判定してくれるところだけど、そういうのじゃないのか😆」「Active Storageはそこまではやらなさそう」「まあやんなくてもいい気はします🤣

参考: ActiveStorage::Attached::Many — purge

「removeといえば、carrierwaveだと確かサイズを引数で指定して、そのサイズ以上だったら消すってのができますね」「バージョンとかも引数に渡せたと思います」「Active Storageでもそのうちできるようになるんじゃないかな?なんて」

「ちなみにバージョンを指定して添付ファイルを消したいというのは割と需要がありますね: carrierwaveでバージョン指定することでオリジナルは消さずにサムネイルだけ一括で消すとかしといて、後は新規アクセスでサムネイルが再生成されるようになってればおkみたな」「ソース見てみっか」

「…く、最近Macbookが不調でなかなかIntelliJ IDEAが起動しない〜😅」「そういえば私の古いMacbookをこの間Mojaveにアップグレードしたら↓、Chromeのレインボーカーソルくるくるがピタッと止みました」「自分のMacbook、まだHigh SierraですらないSierraだからな〜、不調の原因それかもしれない…」

Rails: macOSをMojaveにアップグレード後`bundle install`がエラーになった場合の対応方法

「でActive Storageのpurgeメソッドは、と…お、Attached::Oneなんてクラスが😆」「ワン?!」「そういえばActive StorageのAttached::Oneってhas_manyっぽく書けますよね?」「てことはManyもあるってことか: あった🎯」「こいつらはActiveSupport::Autoloadで動いてないから、各自好きにロードして使ってくれってことなんでしょうね」

参考: ActiveStorage::Attached::One < ActiveStorage::Attached
参考: ActiveSupport::Autoload

「で、これか↓」「purgeには引数がないのでActive Storageでは特定のバージョンだけ削除とかはできないということがこれでわかった🧐」「detachってのあるんですね」「attachdetachとあるけどpurgeは実際にファイルを消すところまでやるっぽい」「detachはpurgeまではしないとあるからデータベースから消すだけで、ファイルが残ると」「purgeはデータベースもファイルも消すと」「これは知らないと間違えるヤツ😆」「detachしたけど残ってましたみたいな😆」「両方並んでればdetachの方が弱そうというのは想像つきますけど」

#5-2-stable/activestorage/lib/active_storage/attached/one.rb#L47
    # Deletes the attachment without purging it, leaving its blob in place.
    def detach
      if attached?
        attachment.destroy
        write_attachment nil
      end
    end

    # Directly purges the attachment (i.e. destroys the blob and
    # attachment and deletes the file on the service).
    def purge
      if attached?
        attachment.purge
        write_attachment nil
      end
    end

    # Purges the attachment through the queuing system.
    def purge_later
      if attached?
        attachment.purge_later
      end
    end

「バージョン指定云々はたぶんBasecampで使われてないだけな気がするから、そのうちサポートされるかもね😊

⚓Railsアプリで実際に起きたセキュリティ問題5つ(RubyFlowより)


つっつきボイス:「実は既に翻訳許可取って、翻訳だけは終わってる状態なので近々公開します😃

「1.のセッション期限って、Railsがサポートしてなかったっけ?」「期限がものすごく未来の時間に設定されるやつですよね」「Devise使ってるとtimeoutableって指定が使えると」

# 同記事より
class User < ActiveRecord::Base
  devise :timeoutable
end

「お、Rails 5.2ではセッションcookieに期限が含まれるようになってるって記事に追記がある(翻訳したときはなかった)」「そういえば以前のTechRachoで5.2の署名済みcookieが非互換になった云々の話があった気がするけどそれじゃないですか?」「あったかもそういえば」「ともあれ5.2ならひとまず対応済みってことね😊

↓5.2アップグレードガイドの更新情報に含まれているのを見つけました。

参考: Rails アップグレードガイド | Rails ガイド

セキュリティ向上のため、Railsでは暗号化または署名付きcookieに有効期限情報を埋め込むようになりました。
有効期限情報が付与されたcookieは、Rails 5.1 以前のバージョンとの互換性はありません。
Rails 5.1 以前で新しいcookieを読み込みたい場合、もしくは Rails 5.2 でうまくデプロイできるか確認したい場合は (必要に応じてロールバックできるようにしたい場合は) Rails.application.config の action_dispatch.use_authenticated_cookie_encryption を false に設定してください。
同ガイドより


「2.は何回間違えたらアカウントをロックするか」


「3.は、入力したユーザー名やパスワードが間違ってたときに『そのユーザーは登録されてません』とか『パスワードが間違ってます』みたいな親切すぎるメッセージを出すと、むしろ攻撃の手がかりになっちゃうというヤツですね」「バリデーションで余計な情報は出すべきでないという話: ただね、サービスの種類によっては親切に出してあげないと問い合わせが大量に押し寄せるので、ポリシーをどうするかは頭の痛い問題」「具体的にはどんな感じでしょう?」

「『パスワードの再設定』フォームなんかがそうなんですが、再設定のために入力したメールアドレスが登録済みなので送信しましたというメッセージを出してしまうと、登録してない第三者でもそのメールアドレスが登録されているかどうかがわかっちゃう: ネットには流出したメールアドレスとパスワードのリストを売ってるダークなサイトがいろいろあるんで、それを元に攻撃者が『こいつはいない、こいつはいる』と試していけば存在するユーザー名のリストを作れる: なのでメールアドレスが登録済みかどうかをうかつに表示すると防衛力が落ちるという話」「お〜」

「その意味ではそういう情報を表示しないほうがセキュアなんですが、表示しないと今度は『このメールアドレスは登録されてないって言われちゃったんですけど』みたいな問い合わせが山のようにやってくるという😅: リテラシーの高い人にはわかってもらえるんですけど、世の中そういう人ばかりではないので」「こういうのってメッセージの文面の書き方ひとつで問い合わせが目に見えて増えたり減ったりしますよね☺


「4.の権限昇格のコード例にあるこの書き方↓、ログインしないとできないはずの操作の中でこれ書いたらコードレビューで100%ツッコまれる」「そういう状況ではidで直接findしてはいけない」「リレーションがあるならそれを使えと: 反論の余地なし」「職務質問もの👮🏼‍♀️

# 同記事より
Project.find(params[:id])               # あかん書き方

current_user.projects.find(params[:id]) # たとえばこんなふうに書くべき

「もっと言うと、たとえstrong parametersとかも正しく書かれていて、設計も十分吟味されているとしても、current_user.projects.find(params[:id])のように書くべき」「そう!ホントそう!」

「コード例の上と下ではコードが放つ臭いも違ってくるし: 下はどこから取ってきているのかが明らかだけど、上はもしかするとそのユーザー以外のところから取ってきているかもしれないから」

「テストを書くときにも、下は現在のユーザーについてテストを書けばいいけど、上はそれ以外のところから取ってきた場合についてもすべてテストを書かないと網羅できないし」

「上の書き方は基本的にデメリットしかないと思う😡」「同意!」「上はいかにも教科書に書いてありそうですよね」「アクセス制限とかないシンプルな場合なら確かに上のようになっちゃうし、IDEでデータベースを何も考えずに取りに行くと上のになっちゃうんですけどね」

「あと、このあたりはURLの設計にもよるんですよね: たとえばTwitterはログインしていなくてもある程度タイムラインとかを表示できるんですが、そんなふうにログインしてもしなくても同じURLで同じように表示したいときなんかはこういう書き方になることもないわけではない」

current_userみたいなスコープをいちいち介さないといけないのは面倒に思えるかもしれませんし実際面倒ですが、これはやらないわけにはいかないことなので」「たった2行のコードでご飯が三杯食べられそうな話になった😋

「上はRails初心者にとても多い書き方」「初心者を見つけるのに使えそう」「ずっと前に指摘されたことあった気もする💦」「上の書き方してたら『あ、書き慣れてないな』って即断定する😎: モデルのクソコードは読み込まないと見分けが難しいんですが、コントローラのこういう書き方とか、他にはfreezeのし忘れとかをやってるのはコードレビューで見つけやすい」「こういう指摘は反論しようがないからレビュー戦争になる心配もないし」


「ちなみにCanCanだとaccessible_byっていうスコープが入るから、どのユーザーでも同じように書けるという謎のメリットはありますけどね: 自分はあまり好きじゃないけど」「なるほどね☺」「あれ、Punditの方が好きかと思ってたけど?」「ん〜どっちもあんまり好きじゃない🤣: CanCanはやってることがいろいろ多すぎてちょっとウザい」「CanCanはビューにも生えてくるぐらい幅広いし: まあ必要ですけどね」「でもビューに寄りすぎるのはちょっとな〜とは思うし」

「確かにCanCanは責務がコントローラからビューまで広がっているのでちょっとつらい: Punditの方は、自分のやりたいことをやろうと思うとん〜?という気持ちになりがちではある」「自由度という意味では、Punditの方がどこまで生やすかを自分で決められるから高いですよね」「Punditは『概念モデルとしては正しい!』みたいな感じで書けるけど、頑張って設計していると結局モデルクラスを増やすしかない気がするんですよね〜: 1個のモデルクラスにいろんな可視性を持ち込むより、別のモデルを作ってそっちで生やす方がいいんじゃないかって」「ちょうど今の案件でPundit使っててつらみ理解できる😢」「Punditの方が原理主義っぽいから性に合うかと思った😆」「権限とかなくていいし🤣


「最後の5.はweak password問題」「keypass、どっかで一度使おうとした覚えがあったけど結局1passwordにした」「1password、サブスクリプションに変わって買い切りできなくなったのが悔しいです😭」「有料だけど、もう自分も1passwordにロックインされちゃったしな〜😆


1password.comより

「パスワードチェッカーって実装する側にとっては割と厄介ですよね: さっと導入できればいいけど、バリデーションで通らなかったパスワードをサーバーに投げないかどうかって意見が分かれるところだし」

「ところで、サイトによってはチェックが恐ろしく厳しいところがあるじゃないですか: 英大文字小文字と記号と数字を全部含めろぐらいならまだしも、ユーザー名と同じ並びの文字列が何文字以上あったら他がセキュアでも却下するところとか🔐」「あるある〜😅」「しかも記号や数字が固まってたらダメで、散らさないといけないとか」「あれはホントつらいし、それならパスワードを30文字にしろと言われる方がマシ😤」「あ、残り時間が🕑」「巻きで行きましょ〜」

⚓Passengerでプロセスをオーケストレーションする


同ブログより


つっつきボイス:「Kubernetesを使わずにKubernetesみたいなことをする?」「英語タイトルからはわかりにくいんですが、PhusionなのでPassengerの記事でした: Hongliって何だろうと思ったら中国の企業で、Phusionがそこのお手伝いをしたようです」「最初の2つの図はPassengerで、3つ目のこれ↓がオーケストレーションの概略か: お?図だけ見ると、フロントのサーバーが複数あってもそれをPassengerに集約できるってことなのかな?もしかしたらPassengerにもクラスタ的なプロダクトがあるのかも🤔


同記事より

⚓Itamae


つっつきボイス:「Itamae、最近あまり見かけてない気もするけどコミットは着実に行われてる」「chefのオルタナティブですね」「クックパッドさんのgemだから当然そこでは使ってるだろうけど」「むしろ最近serverspecを見かけない気がする: たぶんDockerが普及したから🐳」「そういえばそうかも」「serverspecを一生懸命書く時間があるなら、Dockerでアイソレーションする方が考えることが減るだろうし」


itamae.kitchenより


serverspec.orgより

⚓その他Rails



つっつきボイス:「速さが自慢というfast_jsonapiは以前のウォッチでも取り上げましたが一応」「Netflixはオープンソースでもいろいろ公開していますね」

↓こちらはつっつきの後で見つけました。

⚓Ruby trunkより

⚓String内部表現にUTF-8 BOMを含めるべきでない

BOM入りUTF-8が許容されているために、BOM入りCSVファイルでトラブったそうです。以下が回避方法だそうです。

# 同issueより
IO.read(mode:'r:BOM|UTF-8')

つっつきボイス:「あーBOM☺」「BOMキライ😭: むかーしにUTF-16の納品ファイルにBOMありのとBOMなしのとあってスクリプトで外したりしないといけなかったことがありました」「そういうのこそCIでチェックすべき🧐」「CSVでBOM踏んだことあったナ😤」「エンジニアなら一度はBOMを踏むし😆

参考: バイトオーダーマーク - Wikipedia

「Wikipedia見ると、UTF-8の場合BOMは本来不要だし必須ですらないのに、あっても許容されているそうです😢」「そうそう、たまにわざわざ付けちゃう人いるんですよね」「みんな、UTF-8にBOM要らないからね〜!」

⚓リクエスト: Net::HTTPでHTTPSのSNIにserver_nameが欲しい


つっつきボイス:「あー、HTTPS接続のときはSNIで使うserver_nameが欲しいってことか: そういえばRubyコアライブラリのNet::HTTPっていつの間にかHTTPS対応してますね」

参考: Server Name Indication - Wikipedia

⚓Ruby

⚓HTTParty gemでログを出力する方法

# 同記事より
def GoogleGateway 
  include HTTParty 
  logger Rails.logger, :info, :apache 
  ... 
end

つっつきボイス:「HTTPartyは割と有名なRuby製HTTPクライアントソフトウェア: faradayなんかと並ぶ」「もっと好きなHTTPクライアントがあるって以前おっしゃってましたが何でしたっけ?」「自分が好きなのはhttpclient❤」「そうでした: 以前のウォッチで話が出てた」

httpclientは重要な情報がREADMEではなくドキュメントにあるんだそうです。

⚓Rubyのメソッドでdestructureする

# 同記事より
destructure def adds(a: 1, b: 2)
  a + b
end
adds(a: 1, b: 2)
# => 3
adds(OpenStruct.new(a: 1, b: 2))
# => 3
Foo = Struct.new(:a, :b)
adds(Foo.new(1,2))
# => 3

つっつきボイス:「Rubyって無茶して遊ぶ人がいろいろいるのが微笑ましいというか☺」「遊ばれる言語🤓」「で、上のOpenStructって初めて見るけどコレナニ??」「いる、いるぞ、オレオレクラスかと思いきや標準ライブラリにいるぞ↓」「オープンクラスになってるStructってことか」「えー!?🤯

要素を動的に追加・削除できる手軽な構造体を提供するクラスです。
docs.ruby-lang.orgより

参考: class OpenStruct (Ruby 2.5.0)
参考: class Struct (Ruby 2.5.0)
参考: Ruby のオープンクラスとは (メタプログラミングRuby)

「て、手軽ってw」「ハッシュよりは手堅くていいかも?」「データ構造が違いますけどね😎」「destructureメソッド↓はどうやらStructでdestructするってことか」

# 同記事より
def destructure(method_name)
  meta_klass  = class << self; self end
  method_proc = method(method_name)
  unless method_proc.parameters.all? { |t, _| t == :key }
    raise "Only works with keyword arguments"
  end
  arguments = method_proc.parameters.map(&:last)
  destructure_proc = -> object {
    values = if object.is_a?(Hash)
      object
    else
      arguments.map { |a| [a, object.public_send(a)] }.to_h
    end
    method_proc.call(values)
  }
  meta_klass.send(:define_method, method_name, destructure_proc)
  method_name
end

「定義したdestructure DSLを使うとこんな書き方できるのかっ↓😆」「😆」「このdefはキーワードじゃないんでしょうか?」「たぶんdef adds()が先に評価されてそれがdestructureに渡されるんでしょうね」「あーそっか!😳

# 同記事より
destructure def adds(a: 1, b: 2)
  a + b
end

「そういえば確かjoker1007さんがこれと形が似た感じのgemをRubyKaigiで発表してましたよね: アブストラクターとかなんとかいう名前」「何て名前だっけ?」(一同ググる)「abstrikerだっ!!」「そうそう、abstract def fooみたいなDSLを追加するヤツ: defの定義がメソッド名のシンボルを返すのを利用して、defの手前にabstractっていう、Javaで使われているような構文が使えるようになって抽象クラスを定義できるという」「そこまでして抽象クラスにしたいかな〜?😆」「ま、ま、こういう仕組みを知ってるとより一層楽しめますよね😋

# 同リポジトリより
class A1
  extend Abstriker

  abstract def foo
  end
end

class A3 < A1
  def foo
  end
end # => OK

class A2 < A1
end # => raise

Class.new(A1) do
end # => raise

⚓hatefreeweb-gem: ヘイトスピーチ検出API gem(RubyFlowより)

# 同リポジトリより
require 'hatefreeweb'

client = Hatefreeweb::Client.new("Your API KEY HERE")

detection = client.detect("This content is fortunately clean!!","en")
-> 0
detection = client.detect("Here the tone has suffered because of some krauts!!","en")
-> 1

つっつきボイス:「まだ★はゼロですが、変わり種ということで」「このgemでヘイトスピーチを検出するのかと思ったら、hatefreeweb.org↓っていうサービスのAPIを使うのね」「あ、ほんとだ」「このサイトがどのぐらい使われているかはわかりませんが😆」「EmotiScoreなんてのも」「どのぐらいキラワれてるかもわかるんでしょうね☺


hatefreeweb.orgより

「過去形で言っちゃいますけど、以前こういうサービスってすごく流行ったんですよね: Twitterでポジティブなコメントやネガティブなコメントを検出してクラスタリングして、ユーザーの気分を探ろう!みたいなのがひと頃の学生の研究発表で山ほどありました😆」「😆」「だいたいみんなおんなじこと考えてる😆」「ひととおり終わった分野🏁

⚓csvreader: ゼロコンフィグを目指すCSVリーダー(RubyFlowより)

# csv11/csvreaderより
txt <<=TXT
1,2,3
4,5,6
TXT

records = Csv.parse( txt )     ## or CsvReader.parse
pp records
# => [["1","2","3"],
#     ["4","5","6"]]

# -or-

records = Csv.read( "values.csv" )   ## or CsvReader.read
pp records
# => [["1","2","3"],
#     ["4","5","6"]]

# -or-

Csv.foreach( "values.csv" ) do |rec|    ## or CsvReader.foreach
  pp rec
end
# => ["1","2","3"]
# => ["4","5","6"]

「csvいれぶん!っていうユーザー名が気になる😆」「integerとかdateみたいに型推論するようです」

「リポジトリにcsv11.github.ioというリンクがあるんで開いてみた」

「ん?これって公式…じゃないよね?」「この縦を揃える書式、どっかで見たことがあるナ」「『CSV Evolved (for Humans) – Easy-to-Write, Easy-to-Read』ってあるから独自なのかな?🤔」「どうもそうみたい」

# 同サイトより
ID,Name,Code,Area,Pop
ca,Canada,CAN,9984670,34278406
us,United States,USA,9629091,314167157
mx,México [Mexico],MEX,1972550,112322757
...

vs

#####################
# North America

# area (in sq km), pop(ulation)

ca, Canada,          CAN,   9 984 670 km²,  34 278 406
us, United States,   USA,   9 629 091 km², 314 167 157
mx, México [Mexico], MEX,   1 972 550 km², 112 322 757
...

個人的にはシンパシー感じます。

⚓>その他Ruby


つっつきボイス:「そうそう、Subversionだと慣例的にtrunkが使われていたけどGitだとmasterなんですよね」「名前変えるのってそんなに大変なんすかね?」「賛同しない人もいそうだし、Ruby trunkって名前も既にあちこちにあるから、そういうのを全部変えて回るのは大変かも」「なるほどなるほど☺

「もしかしたら昨今のポリコレの影響でGitが将来改名を迫られたりとか😆」「昔はマスター/スレーブだったのがクライアント/サーバーになったみたいな😆」「主人と奴隷」「まあGitでいうmasterはそっちの意味のマスターではないと思いますが」「master意味多すぎ😢

「素朴な疑問だけど、マスターテープみたいな『オリジナル/原本/原盤』的な意味のmasterって、マスター/スレーブのmasterと元は同じなのかな?」「反対語のスレーブを仮定しないmasterってことですよね?そういえばどうなんだろう…音楽だとマスターテープ(原盤)とかゴールデンマスターという言葉がありますけど」「元は別なんじゃないかなという気もしますね」「それで言うとGitのorigin/masterって見ようによっては意味がかぶってるのかな🤣オリジナル/オリジナルみたいな」「🤣」(しばらく一同で辞書を引きまくる)

「trunkも辞書引くといろいろ意味があるけど、Subversionの場合は『(木の)幹』なんでしょうね」「そういう意味だとtrunkは語義的にはGitにはふさわしくないのかも?: Gitに幹がありまっか?という話で」「確かに『Gitは本来すべてのブランチが平等であるべき』という考え方ですしね」「その意味ではブランチ名はGitの慣例に従ってmasterにするけど、意味的にはtrunkという決定は何となくワカル」「運用上は変わらないでしょうし」「origin/trunkなら意味的にも筋は通っていそうだし」

「(初参加の社員に)つっつきではだいたいこんな雑談をよくやってます😆

⚓クラウド/コンテナ/インフラ/Linux/Serverless

⚓サーバーレスの秘訣(Serverless Statusより)

# 同記事より
$ echo "s3cr3t" | gcloud kms encrypt \
    --location=global \
    --keyring=serverless-secrets \
    --key=app1 \
    --ciphertext-file=- \
    --plaintext-file=- \
    | base64

CiQAePa3VEpDBjS2acf...

つっつきボイス:「割と普通の記事かなと思いつつ」「AWSのIAMや環境変数の話の他にGCPも扱ってると」

「そうそう、IAMとKMSってAWSとGCPのどちらにもあるのが意外に厄介なんですよね〜😅」「言われてみれば😳」「固有のサービス名じゃないんだ?!」「なのでIAMとかKMSの話をしているときって、たいていはAWSの方なんだけど、たまにGCPの話をしている可能性もあるので油断ならない😣」「用語とか普通名詞に近い感じですね」「まあ役割も一緒だからいいんですけど」

参考: AWS Identity and Access Management (IAM - ユーザのアクセスを安全に制御)| AWS
参考: Cloud IAM - Identity & Access Management  |  Cloud Identity and Access Management  |  Google Cloud
参考: AWS Key Management Service (暗号化キーを簡単に作成・管理 - KMS)| AWS
参考: Cloud Key Management Service  |  Cloud KMS  |  Google Cloud

「ただ、AWSは『IAMでIAMユーザーのアカウントを作る』のに対し、GCPの場合は基本的に『IAMでアカウントに権限を付与する』だったと思う」

後で調べると、GCPの場合は個々のエンドユーザーではなくアプリケーションに属する「サービスアカウント」を作成できるのだそうです。

参考: サービス アカウントの作成と管理  |  Cloud Identity and Access Management のドキュメント  |  Google Cloud

IAMからは離れますが、GCPは最近「Cloud Identity」というソリューションを発表し、これを使うとG-Suiteを使ってない個人用Gmailアカウントのユーザーも一元管理できることもついでに知りました。

参考: Cloud Identity への移行  |  Cloud Identity and Access Management のドキュメント  |  Google Cloud

⚓AWS AmplifyとサーバーレスVueアプリ(Serverless Statusより)


aws-amplify.github.ioより


つっつきボイス:「またAWSの新しいサービスかと思ったらJavaScriptのライブラリだそうです」「AWS公式?」「だと思うんですが、上のサイトが微妙にパチもんっぽいデザインで考え込んじゃいました😅」「色とかね☺

AWSのブログでもAMplify CLI Toolchainのアナウンスがあり、内容やリポジトリからして公式の位置づけのようです。

「Amplifyって名前わかりづらすぎるでしょ😆」「AMPよりはマシだと思います😆」「なるほどわからん😆が、ググって出た記事↓を大急ぎで読むと、AWSのサービスとの連携の他にJSの拡張とかも含めてAWSと強結合した形で提供するライブラリ、ってことなのかな🤔: AWSと強結合するなら公式だろうし、自分は使わないだろうけど、まあいいんじゃないかと☺」「もしかするとFirebaseと勝負しようとしてる?🚒

参考: AWSの次世代JavaScriptライブラリ「AWS Amplify」の概要とReactアプリに導入する手順 #serverless #adventcalendar | DevelopersIO


firebase.google.comより

⚓SQL

⚓PostgreSQLのソートのパフォーマンス改善(Postgres Weeklyより)

-- 同記事より
test=# explain analyze SELECT * FROM t_test ORDER BY x;
                                QUERY PLAN
--------------------------------------------------------------------------
Sort (cost=804270.42..816770.42 rows=5000000 width=11)
     (actual time=4503.484..6475.222 rows=5000000 loops=1)
     Sort Key: x
     Sort Method: external merge Disk: 102896kB
     -> Seq Scan on t_test (cost=0.00..77028.00 rows=5000000 width=11)
        (actual time=0.035..282.427 rows=5000000 loops=1)
Planning time: 0.139 ms
Execution time: 6606.637 ms
(6 rows)

つっつきボイス:「PostgreSQLは例によって正攻法でやってけばいいんじゃないかと☺: クエリプランも読みやすいし」「ぽすぐれのクエリプランは、MySQLほど勘に頼らなくていいし😎」「そういえばうちらのチームにOracleを10年やってる人がいることがこのたび判明しましたね💪」「Oracleチョットワカル😆」「PostgreSQLだとEXPLAIN ANALYZEで実際に動かした結果を取れるのがいいですよね: あれってMySQLにはなかった気がしますが」「MySQLだとコストは取れなかったかも」「PostgreSQLのEXPLAINEXPLAIN ANALIZEって実際に動かすかどうかという違いがありますよね」「クエリオプティマイザを通すだけか、クエリオプティマイザを通した上でさらにコストを比較するという違い」

参考: 50.5. プランナ/オプティマイザ

「ぽすぐれはそこを比較できるのがすごく重要: だからこそ結果を見てクエリオプティマイザがの最適化がイマイチだなと気づいたらたとえば手動でFORCE INDEXしたりするなどの手が打てる😍」「MySQLだとそこが勘になりがち😆: 本当は勘じゃないにしても複雑になると勘を働かせないといけなくなる」

参考: 14.1. EXPLAINの利用

⚓JavaScript

⚓Vue.jsの次の構想(JavaScript Weeklyより)


vuejs.orgより


つっつきボイス:「Vue.jsは今後こうなる的なロードマップのようです」「フロント勢はjQueryに死んで欲しいという流れにほぼなりつつあるけど、その先の軽いJSライブラリの当てがあるかというと、ね☺」「明日はどっちだ」

⚓CSS/HTML/フロントエンド/テスト

⚓CSS Gridプロパティの覚え方(Frontend Focusより)

Understanding the difference between grid-template and grid-autoという記事もありました。


つっつきボイス:「あーこういうヤツね↓」「CSS Gridプロパティは、覚える価値はあると思う🧐」「たぶん書きながら覚えるのが一番早いっすね」

See the Pen Explicit Grid properties by Zell Liew (@zellwk) on CodePen.

「そうそう、Gridはこういう書き方ができるのがいい↓」「おぉ〜😳

.grid {
  grid-template-areas: "header header header header"
                      ".      main   main   .     "
                      "footer footer footer footer";
}

⚓言語よろずの間

⚓書籍『The Art of Prolog』が無料ダウンロード

https://twitter.com/tyru/status/1050172005645922304


つっつきボイス:「ツイートのリンクがそのまま出てますけど😆」「そうなんですよ😭: ユーザー名にアンダースコアが入っているとWordPressが埋め込みに変換できない」

「それにしてもProlog」「ひっさびさに名前を見かけました」「学生のときに一瞬やった気がする」「何というか、関数型言語が台頭するより前のコワモテな言語というイメージ😨」「Prologはやったことなし」

参考: Prolog - Wikipedia

「そういえば最近授業のために作ったJavaScript講習スライドで、『手続き型でない言語』の例をいろいろ調べてたらちょうどProlog(論理型言語)が出てきた」「そういえばそうだった!」「ついでにSQLも非手続き型言語(宣言型言語)だというのも久しぶりに思い出したし」「あー確かに〜!」「あまりに普段からSQL使っているんで気にしてなかった😅

参考: 非手続き型言語 - Wikipedia

「『The Art of Prolog』は無料でダウンロードできるそうです: 初版が1980年代みたいですが」「表紙の浮世絵が渋い」「何となく品川の宿に見える」「左上に『品川』って書いてあるし」「ほんとだ😅

参考: 品川宿 - Wikipedia

後で探したら、さすがMatzがPrologを押さえてました↓。Erlangに影響を与えたんだそうです。

参考: Rubyist のための他言語探訪 【第 13 回】 Prolog

⚓その他

⚓awesome-vscode


同リポジトリより

GitHubにあるawesome何とかを載せるのは何となく悔しいのですが、えらくパッケージが増えてるので。


つっつきボイス:「最近VSCodeって快進撃ですね」「VSCodeはフロントエンジニアにかなり人気が高いらしい」「それにしてもすごい量のパッケージ」

⚓短時間の観測で将来を高精度に予測?

⚓1mm×2mmのスパイハードウェア

このサイズで成立するのか不思議です。

参考: 中国軍がSupermicro製マザーボードにスパイ・チップを製造段階で仕込んだという仰天報道がもたらしたものとは? - GIGAZINE


つっつきボイス:「そうそう、これどこまで本当に可能なのか謎: Supermicro自身も否定してるし」「スパイチップ、めちゃ小さいけど端子が6つある」「そもそもこのチップにどうやってアタッチするのかがわからないし、ちょっとやそっとじゃこういうことはできないと思うんだけどな〜」

「ちなみにSupermicroといえば自分も昔あこがれてたサーバー向けのハイエンドマザーボードメーカー」「これは知らんかった😳」「何しろマザボだけで10万円、しかしとても安定しているという評判」「そんなに凄かったんだ…」

参考: Supermicro - Wikipedia

⚓番外

⚓世界で最も正確な原子時計

参考: 「世界で最も正確な原子時計」を作る研究者に話を聞いたムービーが公開中 - GIGAZINE

そういえば東大でもこのストロンチウム光格子時計を研究してるそうです。従来のセシウム原子時計よりも破格に精度が高いらしいので、身の回りの物体の運動でも相対性理論を検証できるレベルみたいです。早く自転車に積んでみたい。

参考: 原子時計 - Wikipedia


今回は以上です。

バックナンバー(2018年度後半)

週刊Railsウォッチ(20181001)Railsアップグレード記事と各種支援ツール、CLI向けRubyワンライナー集、Rubyアプリをワンバイナリ化ほか

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

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

Rails公式ニュース

RubyFlow

160928_1638_XvIP4h

Postgres Weekly

postgres_weekly_banner

Serverless Status

serverless_status_banner

Frontend Focus

frontendfocus_banner_captured

JavaScript Weekly

javascriptweekly_logo_captured


Rails5.1/5.2: `rails new`した「後に」yarn/webpackerでjQueryとBootstrap 4を追加する

$
0
0

Rails 5.1や5.2でrails newした後でwebpackerでjQueryやBootstrap 4を足したくなったときの手順をメモします。Bootstrap 4のインストールは、できるだけTwitterリポジトリの公式情報に沿うようにしました。

1. jQueryをWebpackerで後から追加する

  • yarnがインストールされていない場合はbrew install yarnでyarnをインストールする(Macの場合)

yarnがインストールされているかどうかはyarn -vで確認できます。

  • Gemfileにgem 'webpacker', github: 'rails/webpacker'を追加し、bundle installを実行する

webpackerはすべての環境で使うので、上の設定はgroupの外に追加します。

  • プロジェクトディレクトリでrails webpacker:installを実行し、Webpackerをインストールする

以下が追加・更新されます。

A  .babelrc
A  .browserslistrc
M  .gitignore
A  .postcssrc.yml
A  app/javascript/packs/application.js
A  bin/webpack
A  bin/webpack-dev-server
A  config/webpack/development.js
A  config/webpack/environment.js
A  config/webpack/production.js
A  config/webpack/test.js
A  config/webpacker.yml
M  package.json
A  yarn.lock
  • yarn add jqueryを実行し、jQueryをインストールする

package.jsonとyarn.lockが更新されます。なお、yarn installは現在非推奨です。

  • app/assets/javascripts/application.jsのrequire_tree .の前の行に以下を追加する
//= require jquery
//= require popper
//= require bootstrap

これは、後述のbootstrap-rubygemの設定を元にしました。元の設定ではrequire jquery3ですが、Webpackerでインストールする場合はrequire jqueryにしないと動きませんでした。

なお、最後の//= require bootstrap//= require bootstrap-sprocketsにすると、コンパイルにSprocketsが使われます。

2. Bootstrap 4をインストールする

Twitterの公式リポジトリには以下の2つのgemがあります。今新たにRailsアプリを構築するなら、古いIEが対象でない限りBootstrap 4一択なので、bootstrap-rubygemを使います。

インストールはbootstrap-rubygemの公式に従います。なお、SASSはここまでに既にインストールされています。


  • Gemfileに以下を追加してbundle installを実行する。
gem 'bootstrap', '~> 4.1.3'

sprockets-railsは2.3.2以上が必要ですが、Rails 5.2なら3.2.1以降がインストールされるので問題はありません。

  • Bootstrapのスタイルをインポートする

app/assets/stylesheets/application.cssの拡張子を.sassまたは.scssにリネームし(以下は.sassが前提)、以下を追加する。

// Bootstrapのカスタム変数は"bootstrap"のインポートより前に行わなければならない
@import "bootstrap"

sassの場合、同じファイルのrequire行をすべて削除する(sassでは@importで読み込むため)。

 *= require_tree .
 *= require_self

参考: Railsアプリで Bootstrap 4 を利用する

関連記事

Rails 5: Webpacker公式README — Webpack v4対応版(翻訳)

【保存版】Rails 5 Webpacker公式ドキュメントの歩き方+追加情報

【お知らせ】毎月第一木曜日は週刊Railsウォッチ「公開つっつき会」を開催します【定例】

$
0
0

こんにちは、TechRachoと週刊Railsウォッチを担当しているhachi8833です。

毎回何が飛び出すかわからない異色の勉強会ともいうべき「公開つっつき会」もおかげさまで11月に第4回目を迎えることとなり、あらためて公開つっつき会の定例開催についてお知らせいたします🙇

「公開つっつき会」第4回の応募先

詳しくは応募先をご覧ください。

定例「公開つっつき会」とは

定例開催情報

以下は定例開催の全般的な情報です。詳しくは個別の応募先をご覧ください。

趣旨
TechRachoのWebマガジン「週刊Railsウォッチ」の翌週公開向けドラフト記事のエントリを一同でつっつきながら、気楽に技術雑談したり普段できない今更な質問をしたりツッコんだりする
日時
原則として毎月初めの第一木曜日 19:30〜21:00(休日・祝日に応じて変更する場合があります)
費用
つっつき会・懇親会ともに無料です(お酒のみカンパ運営): 持ち込みも歓迎です🍻
会場
BPS株式会社 会議スペース
Googleマップ
想定参加者
Web/Ruby/Rails開発者、およびそれらに興味のある学生や一般の方ならどなたでも参加いただけます
リクルーティングや勧誘などを目的とする参加はお断りいたします
外部参加可能人数
原則5名
BPSエンジニアも随時参加しますので総勢ではこれより多くなることもあります
持ち物
特にありませんが、ご自分のノートPCをお持ちいただくと、つっつきながら調べたりするのに便利です
懇親会
つっつき会終了後その場にて懇親会に移ります(22:30撤収)

週刊Railsウォッチとつっつき会について

今調べてみると、最初の週刊Railsウォッチは私がTechRacho専任になって1月目ぐらいの2016年9月13日に公開していますので、実はもう2年以上も続いています🎉

当初は翻訳に使えそうな英語記事をサーベイしつつ記事化するという趣旨でしたが、しばらくしてBPS Webチームリーダーのmorimorihogeさんが「せっかくだから公開前にエントリをチェックする時間を作ろうか」とおっしゃっていただき、本業で忙しい中、毎週木曜日夜に定例で記事をチェックするようになりました。

それがいつしか「つっつき会」という呼び名になって社内からもポツポツ参加者が出るようになり、私もRailsの更新情報やRuby Trunkを盛り込んだり、Rails/Rubyに限定しないクラウドやデータベースやJavaScriptや言語といった話題を盛り込んだりしているうちに現在のような構成になりました。RailsとRubyの技術が中心ではありますが、それ以外の技術情報にも目配りすることを心がけています。

つっつき会でのやりとりをどう記事に盛り込むかを試行錯誤しながら、やがて「つっつきボイス」という形でアノニマイズする方法を編み出しました。これによって、社員および外部から参加いただいた方も含めて名前出しをせずに済んでいます。

このあたりの経緯については関連記事の「TechRachoの舞台裏」をどうぞ。

ドラフト仕込み、つっつき、つっつきボイス聞き取りの記事化、整形も含めて制作に2日以上もかかるコンテンツですが、おかげさまで週刊Railsウォッチは技術ブログ「TechRacho」の目玉コンテンツと言えるものになってきたと思います。morimorihogeさんも「やっぱり継続は力なりだね」とおっしゃってたように、週刊Railsウォッチは毎週のつっつき会の継続によって支えられてきました。morimorihogeさんを始め、参加いただいている皆さまにお礼を申し上げます。

関連記事にもありますが、週刊Railsウォッチに限らず読者層を間接的に調べるのは一般にどうしても限界があります。特に、RSSリーダー購読者数の把握はまず困難です。

そこで、TechRacho記事や週刊Railsウォッチの読者の反応やフィードバックを得たいという思いから、2018年の8月に満を持して「公開つっつき会」を開催して外部の方の参加を募りました。

公開つっつき会をこれまで3回開催いたしましたが、早くもリピーターの方が出現するなど大きな手応えを得ることができ、かつ、普段のつっつき会の「気楽な技術雑談」というオープンかつ質問やツッコミをしやすい雰囲気を保つことができたと思います。私も率先して今更な質問をするよう心がけています。

定例「公開つっつき会」では毎月皆さまの参加をお待ちしています。学生の方やIT専門でない方も大歓迎です。ぜひお誘い合わせのうえお気軽にご応募ください。

今後もよろしくお願いします🙇

関連記事

「TechRachoの舞台裏」をRails Developers Meetup 2018で発表してきました

週刊Railsウォッチ(20181022)Railsの名前空間地獄とrequire_dependency、PostgreSQL 11がリリース、clean-rails.orgほか

$
0
0

こんにちは、hachi8833です。今日のGitHubは何だか不調ですね。

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

⚓臨時ニュース

⚓PostgreSQL 11が正式リリース

つっつきの後でニュースが飛び込んできました。PostgreSQL 10リリースをウォッチでお伝えしたのが2017/10/06なので、わずか1年という驚きのメジャーアップデートですね。

参考: PostgreSQL 11が正式リリース。ハッシュパーティショニングやJITコンパイルによる高速化、ストアドプロシージャでのトランザクションサポートなど - Publickey

⚓Ruby 2.5.3が追加リリース(Ruby公式ニュースより)

2.5.2のパッケージングの問題(#15232)のみを2.5.3で修正したそうです。

TechRacho記事にも反映しました↓。

Ruby 2.5.2→2.5.3/2.4.5/2.3.8リリース(脆弱性修正)

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

今回公式の更新情報がなかったので直近のコミットから見繕いました。ドキュメント(Railsガイド)の更新が目立ちます。

⚓ActiveRecord#respond_to?のアロケーションを削減

#34227と関連しています。

# activerecord/lib/active_record/attribute_methods.rb#L261
    def respond_to?(name, include_private = false)
      return false unless super

-     case name
-     when :to_partial_path
-       name = "to_partial_path"
-     when :to_model
-       name = "to_model"
-     else
-       name = name.to_s
-     end

      # If the result is true then check for the select case.
      # For queries selecting a subset of columns, return false for unselected columns.
      # We check defined?(@attributes) not to issue warnings if called on objects that
      # have been allocated but not yet initialized.
-     if defined?(@attributes) && self.class.column_names.include?(name)
-       return has_attribute?(name)
+     if defined?(@attributes)
+       if name = self.class.symbol_column_to_string(name.to_sym)
+         return has_attribute?(name)
+       end
      end
        true
    end

つっつきボイス:「Active Recordのrespond_to?ってそもそも使ったことないけど何に使うんだろか?🤔」「改修そのものは:to_partial_pathでStringをアロケーションしてたのをやめたという単純な内容で、アロケーションと速度が少し改善したと↓」

# 同PRより
Before: Total allocated: 752009 bytes (6527 objects)
After: Total allocated: 743921 bytes (6325 objects)
Diff: (752009 - 743921) / 752009.0 # => 1.05%

respond_to :to_partial_pathとかrespond_to :to_modelとか知らないし: ちょっと追ってみよう🐇(IDEを起動)」

参考: ActiveRecord::AttributeMethods::ClassMethods respond_to?(name, include_private = false)

name属性を持つPersonオブジェクトにはperson.respond_to?(:name)person.respond_to?(:name=)person.respond_to?(:name?)するとtrueを返す。また、属性メソッドが生成されていなければ定義する。
APIドキュメントより大意

class Person < ActiveRecord::Base
end

person = Person.new
person.respond_to?(:name)    # => true
person.respond_to?(:name=)   # => true
person.respond_to?(:name?)   # => true
person.respond_to?('age')    # => true
person.respond_to?('age=')   # => true
person.respond_to?('age?')   # => true
person.respond_to?(:nothing) # => false

「ふむぅ、APIドキュメント↑に書いてあることはわかるんだけど、to_partial_pathto_modelが何なのかだな〜🤓こいつらはメソッド呼び出しじゃないし」「↓このあたりの記事を見るとActiveModelに前からto_partial_pathがあるのか」「日本語記事にはto_partial_pathをオーバーライドできる↓って書いてるし」

# o.inchiki.jpより
class User < ApplicationRecord
  def to_partial_path
    if user.admin?
      "users/admin_user"
    elsif user.guest?
      "users/guest_user"
    else
      "users/user"
    end
  end
end

参考: ActiveRecordのto_partial_pathをオーバーライドしてパーシャルを文脈によって使い分ける [俺の備忘録]
参考: Rendering Collections in Rails — Thoughtbot blog

「このpartialはビューのパーシャルなんですよね?」「ですね: renderにActive Recordオブジェクトを直接渡すとパーシャルに展開してくれるメソッドがto_partial_path」「改修の話と離れてきたのでとりあえずこの辺で☺

参考: ActiveModel::Conversion to_partial_path()


「ところで、コントローラには?なしのrespond_toというとっても紛らわしい名前のメソッドがありますね😓」「あーそういえば!」「述語メソッドじゃないから?は付かないんだけどホント紛らわしい」

参考: ActionController::MimeResponds respond_to(*mimes)

def index
  @people = Person.all

  respond_to do |format|
    format.html
    format.js
    format.xml { render xml: @people }
  end
end

⚓Active SupportのChars#reverseChars#grapheme_lengthをリファクタリング

# activesupport/lib/active_support/multibyte/chars.rb#L115
      def reverse
        chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack("U*"))
-       chars(@wrapped_string.scan(/\X/).reverse.join)
+     end
...
      def grapheme_length
-       Unicode.unpack_graphemes(@wrapped_string).length
+       @wrapped_string.scan(/\X/).length
      end

つっつきボイス:「これも含めて今回はUnicode/マルチリンガル関連の改修が目立ちました: Rubyのマルチリンガル機能がよくなってきたからActive SupportよりRubyのメソッドを使おうという流れみたいです」「あ〜、確かにそれは正しい方向☺」「reverseはともかくgrapheme_length?ぐらふぇめ?」「graphemeは確か言語学方面の用語ですね↓日本語だと『書記素』…🤯」「今はscan(/\X/).lengthで取れるようになったと」

参考: 書記素 - Wikipedia

Ruby 2.5.3 + Rails 5.2.1のコンソールで試してみました。

'क्षि'.mb_chars.length            #=> 4
'क्षि'.mb_chars.grapheme_length   #=> 2
'क्षि'.scan(/\X/).length          #=> 2

以下のプは見た目ではわかりませんが半濁点が分離しています(は゜のように分離が見えるものとは異なります)。

a = "プ"
a.mb_chars.length            #=> 2
a.mb_chars.grapheme_length   #=> 1
a.mb_chars.scan(/\X/).length #=> 1

# normalizeするとmb_chars.lengthは1になる
b = ActiveSupport::Multibyte::Unicode.normalize(a, :c)
b.mb_chars.length            #=> 1
b.mb_chars.grapheme_length   #=> 1
b.mb_chars.scan(/\X/).length #=> 1

mb_chars.lengthはnormalizeで変わりましたが、mb_chars.grapheme_lengthは変わりませんでした。

参考: Unicodeのgrapheme cluster (書記素クラスタ) | hydroculのメモ

⚓scaffoldでフィールドに:referencesを指定するとindexページやshowページでidではなくメモリアドレスが表示される問題を修正

#29200の修正です。

# railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt#L18
<% attributes.reject(&:password_digest?).each do |attribute| -%>
-       <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
+       <td><%%= <%= singular_table_name %>.<%= attribute.column_name %> %></td>
<% end -%>

つっつきボイス:「これはもろバグ🐞」「attribute.column_nameにしたかったのにattribute.nameだとモデルオブジェクトを取っちゃう」「実はscaffoldで:referencesを指定する人がほとんどいなかったりして😆

「ところで最後にscaffold使ったのっていつですか?」「scaffold、ほぼ使わないっすね🤓: rails gですらマイグレーションにしか使わないし」「やっぱり〜😆」「scaffoldって初心者アイテムなんでしょうか🔰?」「というより、ある程度以上複雑なRailsアプリを書いているとscaffoldでは追いつかないですね」

「コントローラだけならscaffoldでもよさそう?」「scaffoldだと余分なものが作られがちなのであまりしないけど、先にモデルを作ったときなんかにscaffoldすることはたまにあるかな」「ボク全部手で書いてますけど🤣」「さすが職人!」「わかりみ: 書く量も大したことないですしね☺」「むしろscaffoldのオプションを覚える方が面倒😅

「ちなみにRubyMineではscaffoldも一応GUIでできて↓、アクション名とか名前空間も指定できる: ま使わないけど😆

「Railsのscaffoldって名前空間があると急に使いづらくなってくるところがある」「名前空間を指定してscaffoldすることは可能みたいですね」「できるけど微妙にかゆいところに手が届かなくて、たとえばモデルだけ名前空間を変えたいみたいな指定ができなかった気がする: 何しろ普段scaffoldしないので」「そこで考えるぐらいだったらとっとと普通にファイル作って書いた方が早い😎」「scaffoldだとアクションのURLコメント↓を自動的に付けるけど、コードが変わったときに自動更新してくれるわけでもないからそんなに意味ないし🤣

  # POST /users
  # POST /users.json
  def create
    ...

「そういえば最近のscaffoldはstrong parametersも自動的に付けるんじゃなかったかな↓」「strong parametersのテンプレ、いらね〜😓」「お、scaffoldのテンプレを開くとparams.fetchparams.requireが使い分けられてるし」「ちょうど今日のチームミーティングでfetchrequireをどう使い分けるかって話になりましたね」「この2つは要件に応じて使い分けるべき」

# rails/generators/rails/scaffold_controller/templates/controller.rb.tt
  ...
  private
    # Use callbacks to share common setup or constraints between actions.
    def set_<%= singular_table_name %>
      @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
    end

    # Only allow a trusted parameter "white list" through.
    def <%= "#{singular_table_name}_params" %>
      <%- if attributes_names.empty? -%>
      params.fetch(:<%= singular_table_name %>, {})
      <%- else -%>
      params.require(:<%= singular_table_name %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>)
      <%- end -%>
    end
end
<% end -%>

「ところで上はscaffold用だからstrong parametersのテンプレがあるけど、通常のコントローラをgenerateするときのテンプレートにはstrong parametersはないはずですよね?🤔」「お、言われてみれば確かに↓たったこれだけしかない」「意外とシンプルっすね☺」「そしてこっちにはURLコメントのテンプレは含まれてないな」

# rails/generators/rails/controller/templates/controller.rb.tt
<% if namespaced? -%>
require_dependency "<%= namespaced_path %>/application_controller"

<% end -%>
<% module_namespacing do -%>
class <%= class_name %>Controller < ApplicationController
<% actions.each do |action| -%>
  def <%= action %>
  end
<%= "\n" unless action == actions.last -%>
<% end -%>
end
<% end -%>

⚓「定数読み込み順と名前空間」地獄とrequire_dependency

require_dependencyから、名前空間と定数読み込み順の話題に大きくシフトしました。

「お、上のテンプレでrequire_dependencyを使ってるし」「へー、名前空間があるとrequire_dependencyを付けるのかー」「普通のrequireとは違うみたいですけど、これって何のおまじないでしたっけ?」

「むむ…うろ覚えだけど、確かletter_openerっていう本番以外でメールを飛ばさないようにするgemでどハマったときに見たのを思い出してきたぞ😇」「自分も同じようなところでハマったことあり😇

「たとえば以下のようにHogeモジュールの下にPiyoControllerがあるとするじゃないですか、するとHoge::ApplicationControllerと、トップレベルからの::ApplicationControllerという2とおりの探索パスが存在することになるんですよ」「この2つは、そのままだと先に見つかったものが使われるんです」「読み込み順は必ずしも一定じゃないので」

module Hoge
  class PiyoController < ApplicationController
end

# 上のようにネストで名前空間を作ると、以下の2つの探索パターンが形成される
# Hoge::ApplicationController
# ::ApplicationController

参考: 定数の自動読み込みと再読み込み — 7 require_dependency | Rails ガイド

「ところがこれでは、Hoge::ApplicationControllerを指定して取りたくても、先にトップレベルの::ApplicationControllerが見つかるとHoge::ApplicationControllerが取れなくなってしまうんですよ👻」「それを回避するために、欲しい名前空間をrequire_dependencyで確定しておく必要があるという」「ほぁ〜🤯

「RailsのModule#autoloadって定数の読み込みに「失敗したとき」(const_missing)でないと動き出さないので、定数が既に読み込まれちゃっていると動いてくれないんですよ😭」「あー、定数がもう埋まっちゃってるから発動しないのか」「letter_openerでも回避のためにrequire_dependencyを追加したコミットが確かあったと思います(たぶん#82😤

参考: 定数の自動読み込みと再読み込み — Module#autoloadが関与しない場合| Rails ガイド

「今の例はApplicationControllerだから動かなくなればすぐ気づけるだけまだマシな方: これが名前空間化されたモデルへのアクセス中に発現すると致命的な問題になる可能性があるのがコワイ☠」「ぎょぎょ!」

「たとえば、コントローラのアクションでCustomer.find(params[:id])のようにモデルにアクセスするとして、モデルがAdminで名前空間化されていると::CustomerAdmin::Customerのどちらかが使われてしまうんですが、こういう場合えてしてAdmin::Customerの方がはるかに権限が強いので、::Customerのつもりで書いていたのがAdmin::Customerから取ってきてしまったり、逆にAdmin::Customerのつもりなのに::Customerから取ってきてしまうと(こっちの方がありそうかな)トンデモナイことになる😇」「何というお漏らし…」

「回避方法としてはrequire_dependencyもあるけど、やっぱり名前空間べた書きですかね」「そっ☺: 自分もこれでハマって以来Customer.find(params[:id]じゃなくて必ず::Customer.find(params[:id]みたいに::を明示的に書くようになったし↓」「何だかすっごく不便…😅

# require_dependency

module Admin
  class PiyoController < ApplicationController
    #   Admin::ApplicationController
    #   ::ApplicationController
    def show
      @customer = ::Customer.find(params[:id])
    # Admin::Customer 
    # ::Customer
    end
  end
end

「でなかったら、そもそもネストじゃない方法で名前空間を作ることでしょうね」「同意👍: 自分も名前空間はこうやってネストなしで書くようにしている↓」「これなら名前空間が揺れようがないし❤」「階層も浅くなるし😋

# morimorihoge like
class Admin::PiyoController < Admin::ApplicationController
  ...
end

「ただ、流派としてはどちらのやり方もあるみたい」「自分は今のclass Admin::PiyoControllerがいいと思います!」「自分も☺


「ちなみにこれはRailsエンジンを書くときにも注意しなければいけないハズ(某案件でそういう目に遭ったし)」「そうそう、エンジンなんかは特にそうだけど、想定外の名前空間掘り下げにはホント要注意⚠: サードパーティのモジュール内で発生すると地獄を見る👹

参考: Rails エンジン入門 | Rails ガイド

「現象としては、動いているはずのないモジュールのメソッドがなぜか呼ばれる」「しかもそのモジュールをいくら調べても原因がわからなくて、::を付けて呼び出すとなぜか解消するという」

「もうひとつの現象としては、たとえばdevelopmentモードだと正しく動くのにproductionだと動かなくなる(あるいはその逆)」「読み込み順が環境で変わることがあるのか…」「productionだと全部読み込んでから開始するけど、developmentだと使おうとして見つからないときに初めて読み込まれるから」「letter_openerはdevelopmentでしか使わないgemだから、まさにdevelopmentでだけ動かないという現象を引き起こしました😤

「この問題を一度踏めば『二度と踏みたくない!』って気持ちになるから、無精しないで名前空間をきちんと書くようになりますよ😎」「踏んだことないと、何が起こったのかすらわかりませんからね…自分も踏んだのは今年になってからでしたし」

「今思えば、自分はずっと前からclass Admin::PiyoControllerみたいに名前空間べた書きにしてたんでこの問題を踏みようがなくて、逆に気づく機会がなかったんですよ」「それがたまたま誰かが他の書き方したか何かでネストになった箇所でrequire_dependencyし忘れたりして、それで初めて遭遇しました😩

「ネストしない書き方ならこの問題は起きないんでしょうか?」「ネストしない書き方なら完全にグローバルスコープになるので起きませんね🧐: さっきの例で言うと、Customerと書いたときに必ずグローバルなCustmerだけを見に行くし、Admin::名前空間に入らないのでそもそも探索が発生しない」「なるほど!😃速度的にもよさそう」

「すごく詳しい説明!ありがとうございます😊

⚓ActiveSupportのUnicode関連メソッドが続々変更・deprecation

使われなくなったActiveSupport::Multibyte::Chars.consumes?がdeprecateになって今後はString#is_utf8?を使い、ActiveSupport::Multibyte::Unicode#downcase#upcase#swapcaseがdeprecateになって今後はStringのメソッドを直接使うようにとのことです。

また、Unicode#normalizeChars#normalizeもdeprecateになり、今後はRubyのメソッドを使うことになるようです。


つっつきボイス:「RubyでできるようになったからRubyでやろうぜってことみたいです」「なるほど、Rubyが変わるとこういう改修が入りますね😋: #upcase#downcaseもRubyでやれるようになったのか」「マルチリンガルでできるということでしょうね❤

[Rails5] Active Support::Inflectorの便利な活用形メソッド群

#capitalizeもRubyでやれるようになってるのかな…なってるし↓!」「Rubyはこういうのに対応しててエライな💪

参考: instance method String#capitalize (Ruby 2.5.0)

なお、Rubyの#10085ではentrなどで言語を指定する形になっていましたが、現在はオプションなし以外は:ascii:turkic:lithuanian:foldのみとなっています。また、置き換え方法はエンコーディングにも依存します。大文字小文字の変換はマルチリンガルになると単純にはいかないので苦労が偲ばれます。

参考: instance method String#downcase (Ruby 2.5.0)

#normalizeって何だったかな」「えっと、ひらがなやかたかなの濁点や半濁点が本体の文字と泣き分かれになってるときなんかに使いますね: MacとWindowsでファイル交換するとファイル名でよく起きるヤツ」「あぁそれね☺」「アルファベット語圏だと文字とアクサンが分離しているときにも使うみたいです」「normalizeってUnicodeだとそういう文脈なんだ〜: normalizeっていろんな意味があるし: 特にコンピュータサイエンスの文脈だと」「確かに」

参考: MacでPDFからコピペした時のNFD問題対策

UnicodeのnormalizeはNFD/NFC/NFKD/NFKCとありますが、分離したものも再合成されたものもそれぞれ正当です。さらにMacのファイル名正規化は独自仕様が含まれるらしいので困ったことです。処理対象に複数の正規化形式が入り混じってしまうと厄介なので変換の際はnormalizeを心がけたいですね。

参考: Unicode正規化 - Wikipedia

[Rails5] Active Support Core Extensionsのマルチバイト系メソッド: String#mb_charsとis_utf8?

⚓モデルattribute accessorのメソッド名を改善

# activemodel/lib/active_model/attributes.rb#L29
      private

-       def define_method_attribute=(name)
-         safe_name = name.unpack1("h*")
-         ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name
-
-         generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
-           def __temp__#{safe_name}=(value)
-             name = ::ActiveModel::AttributeMethods::AttrNames::ATTR_#{safe_name}
-             write_attribute(name, value)
-           end
-           alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
-           undef_method :__temp__#{safe_name}=
-         STR
+         ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
+           generated_attribute_methods, name, writer: true,
+         ) do |temp_method_name, attr_name_expr|
+           generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
+             def #{temp_method_name}(value)
+               name = #{attr_name_expr}
+               write_attribute(name, value)
+             end
+           RUBY
+         end
        end

つっつきボイス:「リファクタリングかな?」「あー、属性のアクセサメソッドをメタプログラミングで動的に定義しちゃうとバックトレースで名前が出てこないことがあるから、attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)の条件がtrueならそのままの名前で定義するってことか」「上のコードの↓この部分で名前を明示的に宣言するように変わってる: それ以前はmodule_evalで無名メソッドを作ってからalias_methodしてたけど、その場合バックトレースにはエイリアスではなくオリジナルの__temp__が出てしまう」「確かに__temp__で塗りつぶされちゃうと追いにくいですね」

          ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
            generated_attribute_methods, name, writer: true,
          )

define_attribute_accessor_methodって、define_methodの属性アクセサ版みたいなものかしら」「わがんね🤣」「attribute_methods.rbに今回追加されたクラスメソッドがこのdefine_attribute_accessor_methodですね: 直接使うことはまずなさそうだけど😆

# activemodel/lib/active_model/attribute_methods.rb#L499
        def self.define_attribute_accessor_method(mod, attr_name, writer: false)
          method_name = "#{attr_name}#{'=' if writer}"
          if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)
            yield method_name, "'#{attr_name}'.freeze"
          else
            safe_name = attr_name.unpack1("h*")
            const_name = "ATTR_#{safe_name}"
            const_set(const_name, attr_name) unless const_defined?(const_name)
            temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
            attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
            yield temp_method_name, attr_name_expr
            mod.send(:alias_method, method_name, temp_method_name)
            mod.send(:undef_method, temp_method_name)
          end
        end

参考: instance method Module#define_method (Ruby 2.5.0)

⚓Rails

⚓clean-rails.org: Railsのきれいなコードについて日本語で議論するコミュニティ


discourse.clean-rails.orgより

以下の記事で知りました。

参考: Rails開発で技術的負債を増やさないために知っておきたいこと - ログミーTech(テック)


つっつきボイス:「あーこれか: 以前誰かがこういうサイトやろうぜって話してた気がする」「上の記事に登場する前島真一さんが始めたみたいです」「知ってる顔が多いかも」「中級者以上が集う感じなので人数が多くないのは致し方ないかな」「中級者以上がそもそも少ないですし」

「実は中級者以上の設計上の悩みを説明するのってすごく大変なんですよ: 社内みたいにコンテキストがある程度共有されていればまだしも、そうでないと説明が恐ろしく長くなりがち」「確かに説明を書いているうちにダルくなりそう」「請負開発案件の悩みだったりすると、ソースコードをそのまま貼ったらNDAに違反するからソースを作り直さないといけないし」「ソースを貼らなくても前提条件の説明だけでNDAに抵触するかもしれないので、案件から切り離した前提を考えるのも大変」

「設計って最終的に好みが分かれるものだし、どこまでやるか/これはやりすぎかみたいなのも悩ましいし」「そういう感じで純粋に設計上の話をするならこのclean-railsはいい場なんじゃないかと思いますね❤

⚓Rails 6.0の新機能をRailsコントリビュータがひたすら記録するサイト(Ruby Weeklyより)


つっつきボイス:「このbogdanvlvivさんはRailsのコントリビュータで、彼が収集したRails 6.0の新機能が上の記事にとってもきれいにまとまってます」「これまでウォッチで扱ってきたからだいたい見覚えある💪」「Changelogから抜いてきた感」「でもChangelogよりずっと見やすいですね😍

「へー!#31944delegateprivate: trueオプションが追加↓ですって🤓」「えっナニソレ?」「これはウォッチで扱ってなかったか…」

# 31944より
class Foo
   # ...
   delegate :foo, :bar, to: :baz
   delegate :car, :dar, to: :daz, private: true
   # ...
end

foo = Foo.new
foo.foo
foo.bar
foo.car # => NoMethodError: private method `car' called for #<Foo:0x000000015e8b10>
foo.dar # => NoMethodError: private method `dar' called for #<Foo:0x000000015e8b10>

「ここしか見ずに言うと、delegateしたメソッドをあたかもdelegateしたかのように自分のクラスでだけ呼び出したいときに使うんだろうな: かつpublicにはしたくないと」

「そもそもRubyのprivateメソッドってdelegateできるものでしたっけ?あるいはdelegateはできるけどprivateにしかできないとか?🤔」「Rubyのprivateメソッドはdelegateできないはず」

「だから上のコードはcardarはそのメソッドをFooクラスの中でprivateにするという指定なんだろうと理解してる」「delegateはする側とされる側の関係がややこしい😭

⚓SQLインジェクションを防ぐ正規表現がRailsに導入?

#27947はcloseされているので改修ではありませんが。#32995も現在openです。


つっつきボイス:「最近はてブで話題になったkamipoさんの記事です」「そういえばこの記事読んだ」「Post.order(params[:order])みたいにorderの中に直接paramsを置くとアブナイよというヤツ: orderに限らず、ActiveRecordのwhereとかgroup_byとかに直接文字列を突っ込むのは基本的に怪しみを感じるべき⚠」「😆」「シンボルとかハッシュで定義しないと怪しみが残る」

「で#27947を見に行ったらcloseされてました」「正規表現でチェックってw」「でもそうするしかないだろうな…」

「その#27947のホワイトリストは↓のようになってて、これ以外のものは突破できないようにするってことか: たいていこうなるから自分は別に構わないっすけど」「いやいや油断は禁物、MySQLはSELECTしてないカラムでorderingできるんですよ😎」「あーそうだったかも」「だから関数カラムでもorderingできるはず」

      # Regexp whitelist. Matches the following:
      #   "#{table_name}.#{column_name}"
      #   "#{table_name}.#{column_name} #{direction}"
      #   "#{column_name}"
      #   "#{column_name} #{direction}"

「たしか標準SQLではSELECTしたカラムでないとorderingできないことになってるので、関数を使ったカラムはいったんAS なんちゃらして、それに対してorderingする必要があった」「それがMySQLはそこんところが残念というかアグレッシブで、SELECTしてなくてもorderingできる: しかもこれがMySQLであまりにも普通に使われてるせいで、DBMSが変わると動かなくなる😇

「あ、今はもしかするとMySQLもstrictレベル上がって修正されてるかもしれないけど?」「でも後方互換性を維持しているなら今でもできちゃいそう😎」「少なくとも以前のMySQLはそうでしたね」

#27947のこの正規表現↓って、スペースではない文字に+を付けてるってことかな…?」「正規表現の\wは語の境界を取るときに使いますね: だから\w+が1つの語を表す」「\wによる語の境界はスペースでも区切られるし、「:」とか「_」みたいな記号でも区切られるヤツ:日本語の漢字熟語の区切りは判定しようがありませんが😆

COLUMN_NAME_ORDER_WHITELIST = /\A(?:\w+\.)?\w+(?:\s+asc|\s+desc)?\z/i

「ともあれ、この書き方はひたすら語をチェックしているだけで、それが実際のSELECTやカラム名かどうかまではチェックしてないってことですかね」「SQLインジェクションを防ぐのが目的だからそこまでは頑張ってなさそう」「だからMySQLでしか通らないクエリも通って幸せ🤣」「幸せっちゃ幸せですが🤣」「そしてPostgreSQLに投げるとエラーになると」

ちなみに(?:)はキャプチャを抑制する記法で、少々うざい代わりに高速化されます。普通に()でグループ化すると常にキャプチャされます。

参考: 正規表現 (Ruby 2.5.0)

「明らかに安全だと思うリテラルまでRailsに危険とみなされるとつらい人もいるってkamipoさん記事にありますね」「そうそう、この改修が入るとDiscourseが5.2にあげようとするとつらいって言ってますね」「Discourseってまさにさっきのclean-railsが置かれているサイトですよね」「まあそれは元の書き方がそもそもよくないから直せって思うけどなっ😎」「直せるなら、ね😆

⚓ActiveSupport::StringInquirerのマジック(RubyFlowより)

# 同記事より
class StringInquirer < String
  private
  def respond_to_missing?(method_name, include_private = false)
    (method_name[-1] == "?") || super
  end
  def method_missing(method_name, *arguments)
    if method_name[-1] == "?"
      self == method_name[0..-2]
    else
      super
    end
  end
end

つっつきボイス:「すとりんぐいんくわいやー、使ったことないなー」「自分もないけどRailsの内部で使ってるんですかね」「あ、string_inquirer.rbのAPIドキュメントにそのまんま書いてある: Rails.env == 'production'っていうダサい比較をしなくてもRails.env.production?で判定できるようになる、いつも使ってるヤツだ」「いかにもメタプロですね」

参考: ActiveSupport::StringInquirer
参考: ActiveSupport::StringInquirer を使って、ステータスを active? みたいに管理する

「これはRailsのconfigでもよくRails.configuration.なんちゃら?みたいに使われてるやつですね」「そして中身はrespond_to_missing?method_missingでやってる: そうするしかなさそうだし」「ハッシュに’staging’とかが入っててもメソッドが生えてくるのはこいつのおかげでしたか」

参考: Railsアプリを設定する | Rails ガイド

StringInquirerって、名前から想像が付きにくい機能😆」「英語圏では自然な名前なんだろか?」「inquireってaskやqueryに近いけどもっと堅苦しくて、広い意味では『調査』ですけど、手元の串刺し検索辞書で見ると警察などによる公の調査というニュアンスもあるみたいです👮‍♂️」「method_missingするまで捜査するみたいな」

映画「市民ケーン」では、主人公のケーンが世論操作に使う架空の新聞がその名も「The New York Inquirer」だそうです。皮肉も入っていると思いますが。

⚓ActiveSupportのArray#extract!メソッド

⚓webpack-serveはdeprecatedになった

「ついこの間Webpackerの記事を出したときに調べたら、本命だったはずのwebpack-serveがいつの間にかdeprecateになってて、元のwebpack-dev-serverを使ってくれとのことです」「マジでー?😩」「webpack-serveがメンテナー不在になったらしく」「ひどい🤮: webpack-serveとwebpack-dev-serverって実装は別だった気がするけど」「結局その間webpack-serveを使わなかった人が優勝✌ってことじゃないすか🤣」「🤣」「もうコントか何かみたいに振り回されて」

「古い情報が半端に残ってると今後も踏み続ける人が出る予感」「はい、以前出した『Webpacker公式ドキュメントの歩き方』↓や『Rails 5: Webpacker公式README』の訳注で『今後はwebpack-serve』と書いていたので、もちろんこのことを追記しておきました」

【保存版】Rails 5 Webpacker公式ドキュメントの歩き方+追加情報

⚓その他Rails


  • サイト: Rails Assets — Railsのアセットを一元管理するサイト
source 'https://rails-assets.org' do
  gem 'rails-assets-bootstrap'
  gem 'rails-assets-angular'
  gem 'rails-assets-leaflet'
end

つっつきボイス:「Railsのこういうアセット一元管理って一度はみんな考えそうかなと思って」「ああ昔こういうの流行りましたね: ここはrails-assets.orgにホスティングしてるようですが、もう今はWebpackerでやるし」「やらないとマサカリが飛んでくるし🔨

⚓Ruby trunkより

⚓Ruby 2.5.2/2.4.5/2.3.8リリース

Ruby 2.5.2→2.5.3/2.4.5/2.3.8リリース(脆弱性修正)

記事そのままですが。

⚓提案: OpenStructでハッシュオブジェクトを再帰的にOpenStructオブジェクトに変換する機能


つっつきボイス:「ちょうど先週のウォッチでもOpenStructが話題になりましたね」「OpenStruct使うつもりないし😆

⚓Ruby

⚓Rubocopをパフォーマンス改善ツールとして使う(Ruby Weeklyより)

翻訳記事でもお世話になっているschneemsさんのブログです。

array = ["a", "b", "c"]
array.compact.flatten.map { |x| x.downcase } # compact, flatten, mapでarrayがアロケートされる

つっつきボイス:「duplicate array allocationのチェックにRubocopを使う、そういうルールを使えば確かにできるな」「もともとRubocopってそういうものだし☺

⚓CLI版「12 Factor CLIアプリ」(Ruby Weeklyより)


つっつきボイス:「12 FactorといえばThe Twelve-Factor Appですけど、そのCLI版ってことみたいです」「どことなく自称感漂う😆


The Twelve-Factor App (日本語訳)より

「あー、1.のヘルプの出し方といえばJavaが頭に来る😤」「というと?」「Javaだけ-help」「-が1個だけ!」「やめて欲しいわ〜」

「4.のストリームを重視、これはUNIXの基本ですし😎

「6.の↓こういうメタな文字を入れるのはちょっとな〜、コピペすると化けたりするし」「Powerlineで頑張りすぎてそういう目に遭ってます😭」「こうやっていい感じにして、そして重くなるという🤣」「カラーリング頑張りすぎるのもね、たまにVT-100みたいなむちゃ古い端末でssh rootすると文字化けまくったりするし😇

「RubyKaigiか何かでこんな感じにできるCLIのgemについて発表してた人がいた気がする」「んー、ググってもそれらしいのが見つからない…」

⚓Rubyのメソッド探索、RubyVM.stat、グローバルステート(Ruby Weeklyより)


つっつきボイス:「Rubyのパフォーマンスチューニングでお馴染みのNoah Gibbsさんの記事です」「メソッドキャッシュがいつできるか、みたいなお話」「Global Constant Stateなんてのもあるのね」

Ruby 2.5.0はどれだけ高速化したか(翻訳)

⚓Ruby 1.8〜2.5のStringの進化(Ruby Weeklyより)


つっつきボイス:「Rubyに限らず、Stringって一番使われるだけあって結構変遷があるんですよね」「2.1から2.5はそれほど変わってないのか」「バージョンごとのベンチも取ってますね↓」


同記事より

「何やかやで、RubyのStringは手厚くてよくできてるなって思う」「JavaScriptのはつらいな〜😢」「つかRubyのStringが強すぎる!ホント何でもできる💪」「万能感ありありですね」「Stringのメソッドめちゃめちゃ多いですし、正規表現使わなくてもかなりのことができますよね😋」「正規表現も必要ならさっと呼べるし、楽すぎる」

⚓monotime: 単調増加クロックgem(Ruby Weeklyより)

# 同リポジトリより
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
do_something
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start

Rustにインスパイアされたそうです。


つっつきボイス:「名前でもろわかるように、ちゃんとCPUクロックから時間を取ってくるヤツですね」

⚓EndohさんがRubyを「簡潔ビットベクトル」で高速化


つっつきボイス:「これも話題になってましたね」「簡潔ビットベクトル!」「英語で何て言うんだろう🤔」「TracePoint化での速度を改善するのか」「かなりコアな部分の改造っぽいですね」「めちゃニッチ😊


同記事より

簡潔ビットベクトルは「succinct bit vector」だそうです。

参考: 簡潔データ構造 - Wikipedia
参考: 簡潔データ構造超入門III ~簡潔ビットベクトルで転置インデックスを効率的に実装する~ - EchizenBlog-Zwei

「こういうふうにテーブルから引っ張ってくるシチュエーションって、プログラムのあらゆるところに数えきれないぐらいありますね: それこそJavaScriptのイベントハンドラなんかもこんな感じでできているし、あとOSの割り込みベクタテーブルなんかもそうだし」

参考: JavaScriptのイベントハンドラ一覧|イベント|JavaScript/DOM|PHP & JavaScript Room
参考: 割り込み

⚓Matz手ずからのRubyスタイルガイド

2004年です。


つっつきボイス:「とっても短いです」「おーシンプルなスタイルガイド」「kind_of?is_a?を使わずにダックタイピングする」「末尾が!のメソッドはなるべく作らない…今とだいぶ違うなー」

【保存版】Rubyスタイルガイド(日本語・解説付き)総もくじ

「これ↓って何?…あー、ライブラリファイルをそのまま呼び出すとサンプルプログラムとかが走るってことか」「あーなるほど!」「ライブラリとして使うと何も起きないのね」「$0は実行中のRubyスクリプト名を取れる特殊変数か」「スタイルというよりハックっぽいな〜😆

ライブラリファイルの末尾には if __FILE__ == $0 で囲んで テストケース(かサンプルプログラム)を書いておくとよい。
rubyist.netより

[Ruby] Kernelの特殊変数をできるだけ$記号なしで書いてみる

「Matzもちゃんと書いてる: 未来の自分が混乱しそうと思ったことは躊躇せずコメントするって」「これは大事」

⚓その他Ruby

⚓クラウド/コンテナ/インフラ/Linux/Serverless

⚓ELBSecurityPolicyとは


つっつきボイス:「社内Slackに投下してもらったやつですが、ELBSecurityPolicyって何だったかなと思って」「このセキュリティポリシーは、SSLやTLSでサーバーとハンドシェイクをするときに、どういう暗号化方式を使える/使ってよいかという情報を最初に交換するんですよ」「サーバーはこれこれの暗号化方式を使ってよいというリストを渡して、クライアントはその中から暗号化方式を選べます」「あーなるほど!」「そのときに選べるのがこのELBSecurityPolicy↓」


同記事より

「あんまり新しい暗号化方式を選ぶと困ることとかあった気がする」「古代のガラケーなんかでは新しい暗号化方式に対応できなかったりするので、ポリシーを厳しくしすぎてたとえばAES256-*系を禁止したりするとTLS通信がコケることがある」「DES-CBC3もめちゃ古いな〜😆

「ELBSecurityPolicyは基本的にデフォルトのポリシーを選びます(今は「2016-08」)が、実は今のデフォルトポリシーではTLSv1やTLSv1.1が許可されているのであまりよくない👎」「あー」「AES128やAES256も今は時間をかければ突破できますし」「通信でサポートする環境にもよりますが、デフォルトより強くしたければ、もっと強いポリシーを選択する必要がある」「ちなみにELBのLoad Balancer(確かClassic)だとセキュリティポリシーは自分で定義することもできるはず😎」「なるほど!」

参考: Classic Load Balancer での事前定義された SSL のセキュリティポリシー - Elastic Load Balancing

⚓「GitHub Actions」とは


同サイトより

まだ本格稼働はしてない様子です。


つっつきボイス:「つい最近発表されたようです」「あーこれね☺GitLab Pipelinesのいただきっぽいやつ」「あ、そっちに前からあるんですね😓」「↓こうやってactionで書けるところとかもGitLab Pipelinesみたい」「この辺はGitLabの方が進んでます😎」「とは言え、GitHubがあんまりこの辺を自前でやるとCircleCIとかがボヤくかもしれませんが😆

参考: Introduction to pipelines and jobs | GitLab
参考: gitlab.com で いますぐCI してみよう - Qiita


gitlab.comより



circleci.comより

「GitLabといえば、morimorihogeさんが今年の夏書いたGitLab記事↓のビューが凄く伸びてるんですけど、もしかするとマイクロソフトがGitLabを獲得したのと関係あるのかなと思って」「そんなに伸びてる?みんなどういう部分を読んでるのかな🤔」「そのGitLab記事ってどんな内容ですか?」「GitLabサーバーを自前で立てる話です」「とするとタイミング的にはありそうですね😆

GitLab自社運用のための注意点とノウハウ(2018/06版)

「記事にもありますけどBPSのGitLabサーバーはユーザー数が多いんですよ↓: これをGitHubでやろうとすると大変」

「GitHub Actionsの料金体系ってどうなるんだろう?」「まだベータだからかもしれませんが値段表が見当たらない感じです」

⚓TwilioがSendGridを買収


つっつきボイス:「ちょうど先週のウォッチでTwilioのことをいろいろ教わったのでタイムリーだなと思って」「しかも今うちらもSendGrid使ってるし」「ちなみにSendGridには日本法人があるけどKDDIではなかったと思う」「構造計画研究所ですね↓」


sendgrid.kke.co.jpより

⚓SQL

⚓データベース接続を効果的に管理するには(Postgres Weeklyより)


つっつきボイス:「これは普通にコネクションプールを作ろうみたいな話かな?」「たぶんそんな感じ」「お、Minimum viable checkoutsなんてのがある」

「コネクションプールって好き派と嫌い派がいますよね」「オレは嫌い😆」「そういえばMySQLはあまりコネクションプールって作らないですよね、PostgreSQLは割とやるけど」

「MySQLってパッケージ機能があまり強くないからいいんですけど、Oracleみたいにパッケージ機能が強いと(PostgreSQLはパッケージじゃなくてプラグインだけど同じかどうかよくわからない)、コネクションプールを張ったときにコンパイル結果がコネクションごとに乗っかるじゃないですか」「そうそう」「すると何が起きるかというと、ヘビーに使われているJava+Oracleのサイトでパッケージも積極的に使っているところで、コネクションプールを張った状態でパッケージを更新すると、パッケージで何か呼ぶたびにfailしてリコンパイルが走る🤮、しかもそれがコネクションごとに起きるという現象です」「🤣」「🤣」「だから当時、コンパイルが失敗したというエラーメッセージを捕まえて再実行するという悲しいコードをよく書きましたもん😤

「それは可哀想すぎる…コネクションプールを自分で管理するとかやりたくない〜😆」「とは言え、OracleとかPostgreSQLで大規模なことやるとコネクションプールを使わざるを得ないこともありますし」「あとTLS経由のコネクションって往復回数が多くなるからやりたくない: そういうときはコネクションプールをローカルに置ければ接続も早いしオーバーヘッドも小さくできるんじゃないかなと思うし」

「うほ、コネクションプールをさらに束ねるPgBouncerだって↓」「ここまでくるともうDBAの仕事😆」「大規模になって応答速度とかを改善しようとしたらDBAが管理していかないといけないでしょうね」「コネクションプールの状態を管理して、足りなかったら生成したりとか」


同記事より

参考: Postgres Plus ユーザーサイト - FAQ — PgBouncerについて

⚓PostgreSQLの正規表現

# 同ドキュメントより
'abc' SIMILAR TO 'abc'      true
'abc' SIMILAR TO 'a'        false
'abc' SIMILAR TO '%(b|d)%'  true
'abc' SIMILAR TO '(b|c)%'   false

POSIX正規表現だそうです。


つっつきボイス:「PostgreSQLで正規表現を書けるというのをたまたま知ったので」「お?MySQLでもこういうの書けますよ」「やや、そうでしたか💦」「ポスグレはSIMILAR TOで、たしかMySQLはREGEXP、なお後者は演算子」「個人的にPOSIXなのが残念😢

参考: MySQL :: MySQL 5.6 リファレンスマニュアル :: 12.5.2 正規表現

「そういえばそんなのがあった気がするけど、自分はこういうの超遅くなりそうなんで基本使わないです😓」「DBMSならパラレルで処理できるからそんなには遅くならないと思いますけどね」「集約関数で使ったらさすがにヤバいだろうけど、普通にカラム名を評価するぐらいなら大丈夫だろうし、事と次第によってはLIKEより速いこともあるんじゃないかなという気がしてきた」「文字数が確定する正規表現なら速くできそう」「+とか*を使わないのがポイントですね」「インデックス化もできますかね?」「さすがにそれは苦しいのでは😆

はじめての正規表現とベストプラクティス#2: 正規表現とは何か/ワイルドカードとの違い

⚓CSS/HTML/フロントエンド/テスト

⚓TLS 1.0/1.1無効化の動き

参考: Apple、Google、Microsoft、Mozillaが各社のブラウザでTLS 1.0/1.1のサポートを2020年前半に終了すると発表。 | AAPL Ch.


つっつきボイス:「さっきのELBSecurityPolicyは、このbabaさんが投下したこの記事の続きとしてSlackに貼ってたヤツですね」「ですです」「とは言え古いスマホもまだ残ってますけどね☺

⚓言語よろずの間

⚓Zig言語とは


ziglang.orgより


つっつきボイス:「究極だからZで始まる?」「C言語を置き換えるってw」「めちゃ鼻息荒いです」「D言語でやっとくれ😆」「そういえばあった!D言語って」

参考: D言語 - Wikipedia


dlang.orgより

⚓PCRE2の仕様


pcre.orgより


つっつきボイス:「これもたまたま見つけました」「PCREに2がある?!いつの間に」「つかPCREにちゃんと仕様があるっていうのが驚き☺Perlの正規表現ぐらいにしか思ってなかったけど」「実は2で初めて仕様を作ったりなんかして😆

参考: Perl Compatible Regular Expressions - Wikipedia

⚓その他言語


つっつきボイス:「😆」「😆」「このおかしみを知りたくて」「あ、malloc(sizeof(32))の流れを知らなかったんですね😆ついこの間はてブでも上がってたこの記事↓を受けたツイートです」「あ、そういうことか😳」「ちゃんとハロウィンらしく季節感出してるし」

参考: 侍エンジニア塾のC言語のサンプルがヤバすぎる。 - Qiita

「良い子のみんなはmalloc(sizeof(32))しちゃダメだよ😆: 32は数字じゃなくてシンボルとして解釈されるから」「32だと期待どおりにならないのはわかる: 普通はsizeof(int)とか書くヤツですよね」「数字でも書けるけどどれかのシンボルになっちゃう」「ついでに言うとC言語のsizeof演算子です: 関数じゃないからねっ」

参考: sizeof演算子



つっつきボイス:「Nintendo SwitchでLLD…ま使うよね動くんなら😋

⚓その他

⚓オープンソースのISA「RISC-V」


つっつきボイス:「ISAってぱっと見で昔のISAバスかと思ったら、Instruction Set Architectureの方でした」「確かにISAって言いますね」「しかしRISCか😆」「今はLLVMがあれだけ流行ってるぐらいだし、中間言語で動くようになってしまうとエンドのCPUの種類ってあまり関係なくなってくるところがありますね🤓

「このRISC-Vは実装ではないようなので、つまりARMみたいなものか」「というと?」「ARMはCPUではなくて命令セットでありライセンスなんですね: だからARMのCPUというものはなくて、ARM社は命令セットを持っていてそのライセンスで収入を得てるということ」「知らなかった〜☺

参考: ARMアーキテクチャ - Wikipedia

「このRISC-Vはオープンソースだから命令セットがオープンということか」「記事によると研究者がオープンな命令セットを長年欲していたらしいです」「確かにクローズドだと研究もままならないし」

⚓番外

⚓今から備えるか

各種業務への影響もでかそうな気が。


つっつきボイス:「そうそう、10連休で銀行系が青ざめてるらしいじゃないですか」「え、来年10連休?」「やべー」「やべー」「ガチのゴールデンウィークじゃないですか」「ガチすぎる😆」「厄介なのはたとえば給与支払日がここにかかっちゃうと労働基準法あたりの『月に1度支払うべし』とかに引っかかる可能性があることかな: あ、今は銀行が24時間365日で振込できるようになったからだいぶマシか」「ついこの間からでしたね」

参考: 9日から24時間365日「即時振込」のサービスが開始、それを可能にしたモアタイムシステムとは


今回は以上です。

バックナンバー(2018年度後半)

週刊Railsウォッチ(20181015)Rails初心者と一発でバレる書き方、次のVue.js構想、RubyのOpenStruct、Twilioほか

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

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

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

Postgres Weekly

postgres_weekly_banner

Railsアプリで実際にあった5つのセキュリティ問題と修正方法(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

Railsアプリで実際にあった5つのセキュリティ問題と修正方法(翻訳)

私は長年に渡って、Ruby on Railsアプリでさまざまなセキュリティ問題を発見・修正する機会が何度もありました。この経験を元に、皆さんのRailsアプリをさらにセキュアにする方法をご紹介いたします。ここでご紹介するセキュリティ問題が皆さまのアプリで発生していなければ幸いです。

Rails組み込みのセキュリティ機能について詳しくは、公式のRailsセキュリティガイドをご覧ください。

1. 「セッションに期限を設定していない」問題

セキュリティ問題の解説

「Railsセキュリティガイド」には以下のように記載されています。

セッションを無期限にすると、攻撃される機会を増やしてしまいます (クロスサイトリクエストフォージェリ (CSRF)、セッションハイジャック、セッション固定など)。

ユーザーエクスペリエンスを考慮すれば(ユーザーはずっとサインインしたままになるので、アプリを開くたびにサインインしなくても済む)、無期限セッションの方が正しいアプローチと思われそうですが、無期限セッションは悪手です。公共の場所に置かれたPCでユーザーがアプリからサインアウトし忘れた場合に、何者かにユーザーセッションをハイジャックされる状況を防止するためには、セッションをできる限り早期に無効にするべきです。

解決方法

最も単純な解決方法は、config/initializers/session_store.rbで以下のようにセッションcookieの有効期限を設定することです。

Rails.application.config.session_store  :cookie_store, expire_after: 12.hours

これによって、セッションcookieは作成後12時間で期限が切れます。この方法は実装が容易なのですが、実は大きな欠点があります。有効期限はユーザーのブラウザに設定されるので、セッションcookieにアクセスできさえすれば誰でもcookieを書き換えて期限を簡単に延長できてしまいます

よりセキュアな方法でこの問題を解決するには、セッションの有効期限をサーバー側に保存すべきです。この方法は「Railsセキュリティガイド」でも提案されています。

セッションIDを持つcookieのタイムスタンプに有効期限を設定するという対応策も考えられなくはありません。しかし、ブラウザ内に保存されているcookieをユーザーが編集できてしまう点は変わらないので、やはりサーバー側でセッションを期限切れにする方が安全です。

ユーザー認証にDevise gemを使っているRailsアプリの場合、Timeoutableモジュールが組み込まれます。このモジュールは、ユーザーセッションが期限切れかどうかの検証を行います。これを利用するには、アプリでユーザーを表すモデルの中でこのモジュールを次のように有効にする必要があります。

class User < ActiveRecord::Base
  devise :timeoutable
end

続いてdeviseイニシャライザのtimeout_inオプションに必要な値(デフォルトは30分)を設定します。

# ==> Configuration for :timeoutable
# The time you want to timeout the user session without activity. 
# After this time the user will be asked for credentials again. 
# Default is 30 minutes.
config.timeout_in = 30.minutes

Devise gemを使っていない場合は、Sessionモデルを作成してcreated_atタイムスタンプとupdated_atタイムスタンプをそこに保存し、古くなったレコードはそこから削除するようにしておきます。繰り返しになりますが、このコード例は「Railsセキュリティガイド — セッションの期限切れ」にあります。

2018/10/09原文更新: InCaseOfEmergency氏からのredditのコメントで、組み込みのソリューションがRails 5.2で改善され、期限切れタイムスタンプがセッションcookieの一部に含まれるようになったとの情報をいただきました。素晴らしい!

2. 「アカウントロックの仕組みがない」問題

セキュリティ問題の解説

1人のユーザーが何回サインインに失敗したらユーザーを無効にしますか?失敗回数を「無制限」にすると、セキュリティホールが生じます。ユーザーがメールアドレスとパスワードをさまざまに変えてサインインしようとすることを許すと、攻撃者にそれを許したも同然です。辞書攻撃や総当たり攻撃を準備するスクリプトを使って、ものの数分で突破されてしまうでしょう。

  • 総当たり攻撃: ユーザー名/パスワードのあらゆる組み合わせを試す攻撃
  • 辞書攻撃: ありがちなパスワードのリストを元に推測する攻撃

この問題を修正するには、間違ったユーザー名/パスワードの組み合わせを入力できる上限回数を設定し、それを超えたらユーザーをブロックすべきです。

解決方法

Devise gemを使っていれば先ほどと同様に話は簡単です。Deviseには、サインイン試行回数を超えたユーザーをブロックできるLockableモジュールがあります。回数は自由に設定できますが、まずは5回に設定しておくのがよいでしょう。ユーザーから不満の声が上がったときに、いつでも値を変更できます。

Lockableモジュールは以下の2つのアンロック戦略を提供します。

  1. :time: 指定の時間が経過したらユーザーのブロックを自動解除する。
  2. :email: ユーザーがロックされた場合にロック解除のリンクをメールで通知する。

この2つの戦略はロック対策を何も行わないよりずっとましですが、最終的にどちらを選ぶかはあなた次第です。Deviseでは両方を同時に使うこともできます。詳しくはDeviseの公式ドキュメントをどうぞ。

Devise gemを使っていない場合は、自力で同じようなソリューションを実装できます(コードはネット上にいろいろあります🙂)。今使っているライブラリに同じようなソリューションがあるかどうかチェックするとよいでしょう。

CAPTCHAを実装することで総当たり攻撃や辞書攻撃の防止に役立てることもできます

出典: https://hakiri.io/blog/rails-login-securityより

3. 「ユーザーリストを取られる」「メールアドレスを推測される」問題

セキュリティ問題の解説

さほど問題には見えないかもしれませんが、深刻な問題です。試しに自分のアプリでめったに使われない「パスワードをリセットする」ページを開いてみてください。

入力したメールアドレスのユーザーは存在しません」といううかつなバリデーションエラーメッセージが表示されなければ幸いです。このメッセージがよくない理由はおわかりでしょうか?攻撃者がこれを使って、そのシステムに存在するメールアドレスのリストを集めることができてしまう可能性があるからです。

既存のメールアドレスのリストを数百万件のリクエストとしてアプリに送信し、その結果を元にリストのどのユーザーが本当にそのシステムに存在するかをチェックするスクリプトは実に簡単に作れます。もしロックアウトのしくみがなければ、実在ユーザーのメールアドレスリストを手に入れた攻撃者が上述のセキュリティホールを衝いてユーザーアカウントへのアクセスを奪取するかもしれません。

解決方法

ユーザーが入力するメールアドレスがアプリのユーザーに割り当てられたものであろうと、ランダムなメールアドレスであろうと、アプリは同じレスポンスを返すべきです(APIからのJSONレスポンスか、確認メッセージを表示するページへのリダイレクトで)。こうすることで、攻撃者はアプリのユーザーのメールアドレスのリストを取れなくなります。

Devise gemを使っている場合は、コードのコメントにあるparanoidオプションが使えます。

入力したメールアドレスが正しいかどうかにかかわらず、確認やパスワード復元といったワークフローの振る舞いを同じにする。
Deviseコメントより抜粋

Deviseを使っていない場合は、入力したメールアドレスが正しいかどうかにかかわらず振る舞いが同じになるよう、自分でアプリを調整すべきです。

このようなレスポンスを返すと、`non-existing-email@domain.come-mail`というメールアドレスがどのユーザーにも使われていないことが攻撃者に知られてしまい、次のアドレスを試されてしまう可能性があります。

メールアドレスが漏洩せず、かつ明確なメッセージ(正確には上下2つのメッセージ)

Patron did not receive Password Reset Emailより

4.「権限昇格(権限のないリソースへのアクセス)」の問題

訳注: 週刊Railsウォッチの「Rails初心者とバレる書き方」もどうぞ。

セキュリティ問題の解説

権限昇格は起きてはならないことですが、起きるときは起きます。あなたが仮に、ユーザーのプロジェクトをIDで参照できる、新しいAPIエンドポイントを作成したとしましょう。

GET https://my-rails-app.com/api/projects/:project_id

テストユーザーに割り当てたいくつかのプロジェクトIDを使うcUrlリクエストをいくつか試し、プロジェクトの詳細を含むJSONペイロードが首尾よく返されたので、ようやくこのエンドポイントをproductionにデプロイしました。しかしちょっと待った!別のユーザーに割り当てられたプロジェクトIDを使ってリクエストをこしらえたらどうなりますか?

残念でした。プロジェクトへのアクセスをcurrent_userに限定するのを忘れていたのです。

公式のRailsガイドには、このセキュリティ問題をまとめた秀逸な一文があります。

ユーザー入力は安全確認が終わるまではセキュアではなく、ユーザーから送信されるどのようなパラメータにも、何らかの操作が加えられている可能性が常にあります 。
Rails セキュリティガイド | Rails ガイドより「6.7 権限昇格」

解決方法

いかなる場合も、アクセスを可能な限り絞り込むことを忘れてはいけません。自分のアプリのコントローラでcurrent_userメソッドにアクセスできるのであれば、以下のように置き換えて修正します。

Project.find(params[:id])

上を以下のように置き換えます。

current_user.projects.find(params[:id])

複数のリソースへのアクセスをオブジェクト指向的に制御したい場合は、pundit gemかcancancan gemを利用できます。

私はpunditの方が使い慣れています。また、punditにはdevelopment環境で常に使用すべき便利な機能が1つあります。たとえば、他のコントローラに継承されるメインのコントローラに以下のフィルタを追加できます。

after_action :verify_authorized

こうしておけば、コントローラのアクションでauthorizeメソッド(リソースへのアクセスを基本的に制限する)を呼び忘れたときにpunditが知らせてくれるので、コードをリポジトリにプッシュする前でも対応できます。

punditもcancancanも使ったことのない方には、ぜひお試しいただくことをおすすめします。

5. 「弱いパスワードを排除していない」問題

セキュリティ問題の解説

アプリユーザーの大多数は1passwordKeePassも使っていません。これらはセキュアなパスワードを生成して安全に保存し、サインインのたびに自動入力してくれます。

覚えやすいパスワードをあらゆるアプリで使いまわしているのが平均的なユーザー像です。

私の個人的な意見ですが、たとえユーザーに少々不便を強いることがあっても、ユーザーを指導してセキュリティに配慮することが私たち開発者の義務だと思います

くれぐれも12345678qwertyなどという弱々しいパスワードの作成をユーザーに許してはいけません。このようなパスワードが使われていると、簡単に総当たり攻撃や辞書攻撃の餌食になってしまいます。

解決方法

パスワードポリシーを導入し、適用しましょう。

パスワードポリシーとは、強いパスワードの利用をユーザーに促して正しく運用することでコンピュータのセキュリティを高める目的で制定されたルールのセットのことである。
Password policy - Wikipediaより

基本的なポリシーは、Userモデルに以下のカスタムバリデーションメソッド追加するだけで適用できます。

validate :password_complexity
def password_complexity
  return if password.blank? || password =~ /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,70}$/
  errors.add :password, "パスワードの強度が不足しています。パスワードの長さは8〜70文字とし、大文字と小文字と数字と特殊文字をそれぞれ1文字以上含める必要があります。"
end

上のメソッドはDeviseのwikiから引用したものです。本記事を読んだ方は一刻も早くアプリのコードにこれを追加すべきです。

オーバーキル気味かもしれませんが🙂、もっと細かく制限したい方は、strong_password gemをチェックするとよいでしょう。

eBayやGmailやDropboxで使われているパスワード強度の表示です。ユーザーにポリシーをわかりやすく伝えることは、ポリシーそのものと同じぐらい重要です。

出典: https://css-tricks.com/password-strength-meter/

まとめ

Ruby on Railsアプリでこれまで発生した5つのセキュリティ問題をご紹介しました。これらは今後も発生する可能性があります。

本記事をお読みいただいている皆さまのアプリに、これらの問題が1つもないことを願っています。修正可能な問題が見つかった方が、本記事でご紹介したソリューションを活用してセキュリティホールを塞ぐことができれば幸いです。グッドラック🙂!

リンク集

  1. The official OWASP Ruby on Rails security checklist
  2. Rails Security Checklist
  3. The SaaS CTO Security Checklist

関連記事

Ruby 2.5.2→2.5.3/2.4.5/2.3.8リリース(脆弱性修正)

RailsのCSRF保護を詳しく調べてみた(翻訳)

週刊Railsウォッチ20181029: 特集『肥大化したActiveRecordリファクタリング7つの方法』今ならどうなる?Redis 5のストリーム機能他

$
0
0

こんにちは、hachi8833です。遅ればせながら先週はGitHubの中の皆さまお疲れさまです。


つっつきボイス:「自分はあの日たまたまGitHubには触ってなかったんでそんなに影響なかったかな」「チームではハマった人もいたみたい」「私はGitHubでリポジトリを追加した後プッシュしたら半日何も出てこなくて、夜リロードしたらやっと出てきました🌙

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

公開つっつき会のお知らせ

おかげさまで公開つっつき会の第4回の参加希望者が初めて6人になりました😂。定員を引き上げましたのでご安心を🙇

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

公式の更新情報がなかったので、例によってコミットログから見繕いました。今回はますますドキュメントの更新が多くなっていてpreview-releaseを予感させ、その分改修は小粒な感じです。

⚓ActiveJobテストヘルパーから返されるジョブインスタンスにdeserializeされた引数を含めるようにした

# activejob/lib/active_job/test_helper.rb#L615
      def instantiate_job(payload)
-       job = payload[:job].new(*payload[:args])
+       args = ActiveJob::Arguments.deserialize(payload[:args])
+       job = payload[:job].new(*args)
        job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at)
        job.queue_name = payload[:queue]
        job
      end

つっつきボイス:「ActiveRecordをARと略すのはよくやるけど、ActiveJobはAJ☺」「Ajaxかと思た」「payloadを加工しないでそのまま渡すようにしたと: 前は通らなかったキーワード引数がこれで通る↓」

# activejob/test/cases/test_helper_test.rb#L477
  def test_assert_enqueued_with_returns
    job = assert_enqueued_with(job: LoggingJob) do
-     LoggingJob.set(wait_until: 5.minutes.from_now).perform_later(1, 2, 3)
+     LoggingJob.set(wait_until: 5.minutes.from_now).perform_later(1, 2, 3, keyword: true)
    end
...

⚓IN句の値がboundable?かどうかにかかわらずpre-checkingをlazyにした

#33844以降eager-loadingやpreloadingでidが大量かつ巨大な場合、値がconstructableかどうかというpre-checkで阻止されないようになった。しかしこのpre-checkはクエリ実行時ではなくリレーションのビルド時に型を評価していて、一部のアプリで期待どおり動作しないことがあった。
そこでpre-checkをできる限りlazyにするようにし、リレーションのビルド時に型評価が発生しないようにした。
同PRより大意

# activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb#L13
-     def visit_Arel_Nodes_In(*)
+     def visit_Arel_Nodes_In(o, collector)
        @preparable = false
+         if Array === o.right && !o.right.empty?
+         o.right.delete_if do |bind|
+           if Arel::Nodes::BindParam === bind && Relation::QueryAttribute === bind.value
+             !bind.value.boundable?
+           end
+         end
+       end
         super
      end

つっつきボイス:「boundable?ってタイトルでわからなかったけどメソッド名でした」「oって何だ?😆」「Go言語みたいなあっさりしたローカル変数名ですね☺」「コードはSQLのINになるパラメータを渡したときの挙動を変更したようだ」

参考: 逆引きSQL構文集 - IN句を用いた副問合せ

「うん、bind if bind.value.boundable?ArrayHandlerからDetermineIfPreparableVisitorに移動して展開のタイミングを後ろにずらしたんですね」「2**63というめちゃでかい値でテストしてる↓⛰」「2の63乗!」

# activerecord/test/cases/bind_parameter_test.rb#L37
      def test_too_many_binds
        bind_params_length = @connection.send(:bind_params_length)
-       topics = Topic.where(id: (1 .. bind_params_length + 1).to_a)
+       topics = Topic.where(id: (1 .. bind_params_length).to_a << 2**63)
        assert_equal Topic.count, topics.count
      end

「ついでにテストコードも軽くリファクタリングされてreferencesを使うようになってる↓」

# activerecord/test/schema/schema.rb#L160
  create_table :citations, force: true do |t|
-   t.column :book1_id, :integer
-   t.column :book2_id, :integer
+   t.references :book1
+   t.references :book2
    t.references :citation
  end

追記2018/10/30: こちらもご指摘をいただきました。ありがとうございます!🙇

「そういえば修正前の(*)は引数を全部握りつぶす記法だというのをこの記事書いてて知りました↓」

Ruby2.5.xのパラメータの制約についてまとめてみた

⚓MySQL 8.0.13で親テーブルへのアクセス権がない場合のエラーを追加

# activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb#L639
        def translate_exception(exception, message)
          case error_number(exception)
          when ER_DUP_ENTRY
            RecordNotUnique.new(message)
-         when ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
+         when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
            InvalidForeignKey.new(message)
          when ER_CANNOT_ADD_FOREIGN
            mismatched_foreign_key(message)
           when ER_CANNOT_CREATE_TABLE
             if message.include?("errno: 150")
               mismatched_foreign_key(message)
             else
               super
             end
           when ER_DATA_TOO_LONG
             ValueTooLong.new(message)
           when ER_OUT_OF_RANGE
             RangeError.new(message)
           when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
             NotNullViolation.new(message)
           when ER_LOCK_DEADLOCK
             Deadlocked.new(message)
           when ER_LOCK_WAIT_TIMEOUT
             LockWaitTimeout.new(message)
           when ER_QUERY_TIMEOUT
             StatementTimeout.new(message)
           when ER_QUERY_INTERRUPTED
             QueryCanceled.new(message)
           else
             super
           end
         end

つっつきボイス:「エラーを追加したようです」「MySQL 8.0.13で追加されたエラーコードER_NO_REFERENCED_ROWER_ROW_IS_REFERENCEDに対応したんですね☺

「そういえばMySQLでテーブル継承ってできたっけか…?🤔ぽすぐれは昔からテーブル継承できるけど↓」

参考: PostgreSQL 10.4文書 — 5.9. 継承

「ぐぐると単一継承テーブル(STI: Single Table Inheritance)が出てきて探しにくい…😢」「STIはRailsにある機能で、今言ってるテーブル継承はDBMSの機能の話だから別なんですけどね☺」「とりあえずMySQLのは見当たらないですね」

参考: Active Record の関連付け (アソシエーション) | Rails ガイド — ※近々ガイドの「シングルテーブル継承」を「単一テーブル継承」に統一しようと思います🙇
参考: 単一テーブル継承 - Wikipedia

⚓CSP周りのバグ修正

# actionpack/lib/action_dispatch/http/content_security_policy.rb#L16
       def call(env)
         request = ActionDispatch::Request.new env
         _, headers, _ = response = @app.call(env)

         return response unless html_response?(headers)
         return response if policy_present?(headers)
         if policy = request.content_security_policy
          nonce = request.content_security_policy_nonce
-         headers[header_name(request)] = policy.build(request.controller_instance, nonce)
+         context = request.controller_instance || request
+         headers[header_name(request)] = policy.build(context, nonce)
        end
         response
      end
# actionpack/lib/action_dispatch/http/content_security_policy.rb#L251
       def resolve_source(source, context)
         case source
         when String
           source
         when Symbol
           source.to_s
         when Proc
          if context.nil?
            raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}"
          else
-           context.instance_exec(&source)
+           resolved = context.instance_exec(&source)
+           resolved.is_a?(Symbol) ? apply_mapping(resolved) : resolved
          end
        else
          raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
         end
      end

つっつきボイス:「pixeltrix先生の修正です」「なるほど、W3CのCSP仕様だと'self'と引用符で囲むようになってる↓のでその書式に合わせたということか🤓」「ほんとだ😳」「バグだけど、もしかするとブラウザによっては引用符なしでも許しちゃってるのがあるのかも?」

Content-Security-Policy: default-src self    # 仕様違反
Content-Security-Policy: default-src `self`  # OK

参考: Content Security Policy Level 2 — default-src usage
参考: コンテンツセキュリティポリシー (CSP) - HTTP | MDN

⚓read/write_attributeの文字列アロケーションを削減

先週のウォッチ#34270も文字列アロケーション削減でしたね。

# activerecord/lib/active_record/attribute_methods/read.rb#L31
      def read_attribute(attr_name, &block)
-       name = if self.class.attribute_alias?(attr_name)
-         self.class.attribute_alias(attr_name).to_s
-       else
-         attr_name.to_s
+       name = attr_name.to_s
+       if self.class.attribute_alias?(name)
+         name = self.class.attribute_alias(name)
        end
         primary_key = self.class.primary_key
         name = primary_key if name == "id" && primary_key
         sync_with_transaction_state if name == primary_key
         _read_attribute(name, &block)
      end

つっつきボイス:「PRにもあるように、name = attr_name.to_sで前もってシンボルを文字列に変換することで、その後のattribute_alias?で無用な変換を避けてアロケーションを減らしたということか」

up front: 前金、前払い、あらかじめ

⚓ドキュメントに記述を追加(2件)

# activesupport/lib/active_support/callbacks.rb#L26
+ # By default callbacks are halted by throwing +:abort+.
+ # See +ClassMethods.define_callbacks+ for details.
+ #

つっつきボイス:「:abortでコールバックを止められるという記述がAPIドキュメントに追加されました」「詳しくはソースを読んでくれと↓😆

The default terminator halts the chain when a callback throws :abort.
api.rubyonrails.orgより

参考: ActiveSupport::Callbacks::ClassMethods.define_callbacks

「Railsにabortがあるのかなと思ったら、これはRubyのKernel.abortなんでしょうね」

;追記2018/10/30: 以下のご指摘をいただきました。ありがとうございます!

参考: 終了処理 (Ruby 2.5.0)

# guides/source/action_mailer_basics.md#L842
-Intercepting Emails
+Intercepting and Observing Emails
-------------------
- There are situations where you need to edit an email before it's
-delivered. Fortunately Action Mailer provides hooks to intercept every
-email. You can register an interceptor to make modifications to mail messages
-right before they are handed to the delivery agents.
++Action Mailer provides hooks into the Mail observer and interceptor methods. +These allow you to register classes that are called during the mail delivery life cycle of every email sent.
+ ### Intercepting Emails
+ Interceptors allow you to make modifications to emails before they are handed off to the delivery agents. An interceptor class must implement the `:delivering_email(message)` method which will be called before the email is sent.

つっつきボイス:「ObserverのドキュメントがAction Mailerのガイドに追加されたそうです」「あー、受信のObserverね😃一時期探したことあるんですけど、確かにAction Mailerには前からそういう機能(register_observer)があって、いわゆる受信メールルックを行う」「Observerはデザパタなんですね」「そんな大層なものでもなくて、Observerに登録しておけばメール受け取ったときに動くよというヤツです😎

参考: ActionMailer::Base.register_observer

# 同PRより
class EmailDeliveryObserver
  def self.delivered_email(message)
    EmailDelivery.log(message)
  end
end

参考: ActionMailer の送信ログをとりたい - Qiita — Observer
参考: Observer パターン - Wikipedia

[保存版]人間が読んで理解できるデザインパターン解説#3: 振舞い系(翻訳)

「メール受信をフックして何かを起動するという処理は昔よく行われてて、たとえば外部向けのAPI呼び出し機能はないけどメールだけは受信できるなんていう古〜いシステムでは、特定のメールを受け取ると機能をトリガするものがありましたよ」「へ〜!😳」「今も覚えているのは、APIなんかないAS/400(IBMの代表的なミニコン)のシステムとデータ連携するときにCSVファイルをメール添付で渡すようになってたこと」「おほAS/400😆

参考: System i - Wikipedia — AS/400の後継だそうです。

⚓Rails

⚓特集「肥大化したActiveRecordモデルをリファクタリングする7つの方法」今ならどうなる?

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

「上はTechRachoで長年ずっと参照いただいてる定番記事ですが、以下のブクマ↓が気になったのでもう少し現代的な内容で別記事にできないかなと思って(できるんだろうか😓): 大ネタなんで次回の公開つっつき会にも持ち込もうかと考えてます」「確かにこれ相当昔の記事ですよね☺」「Code Climateさんの元記事は2012年ですし👵

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

だいぶ古い記事、Service Objectは今で言うUse Caseと同じパターンと言えそう

2018/01/06 03:08

⚓その1: 設計パターン名の呼び方はまちまち

「そもそもこういう設計のパターン名って言語やらフレームワークやら時代やらで相当ばらついてますけどね😎」「村ごとにというか山を越えるともう呼び名が違うみたいな😆妖怪ですか」「GoF以外のパターン名がありすぎ😆

「でも記事の内容の一部は古くなってるのは感じますね」「以前のウォッチでも『Form Objectの役割のひとつはform_withのおかげで消えたかも』という話題もありましたし」

「時とともにパターンの名前やあり方が変わっているものもあるし: たとえば記事中のPolicy Objectなんてのは今や自分でこしらえるよりはPunditとかCanCanCanあたりをポリシーとして使う方向になってるし☺」「それもそうか」

「あとDecoratorとかPresenterってのも最近あまり聞かないな〜🤔」「そういえば」「元々キライだけど😆」「Decoratorって割と揉めやすい印象ですね: Presenterという呼び名がよくないなんて言われたり」「というよりDecoratorはトゥーマッチなことが多くて🧐

Form Objectはキライな人もいるみたいだけど、明らかにビューとコントローラで使いやすいので自分はあっていいと思うし死んでもいないと思う: それをForm Objectと呼ぶかActive Modelと呼ぶかというのはあるにしても」

Query Objectも今も普通にあるよねって思うし」

View Objectみたいなのは自分は書かないな〜☺

「この辺は内容よりも呼び名の問題という気もするし、記事自体の主観が強い気もするし(自分にはトゥーマッチと思えるパターンもあったりするし)」「もう少ししたら他の社員もつっつきに来ると思うのでまたつっつきましょう😋

⚓その2: 案件ごとに設計用語を揃えたい

「ちーっす」「Service Objectパターンが一部で『Use Case』って呼ばれてるんだそうです」「Use Case?🤔それは知らんかったな〜」「そう呼んでいる村はある😆」「気持ちはわかる: ServiceというよりUse Caseの方がそれっぽいと言えばそれっぽいかも?」「Martin先生は同じものを『Application Service』と呼んでる🕶

参考: Design and Coding Applications for Performance and Scalability in WebSphere — p35にApplication Serviceが説明されています

「結局人によって呼び方が全然違っているという🤣」「統一はされてない🤣」「ということは、GoF以外のデザインパターンや設計パターンは『誰の』『何というパターン』まで指定しないと確定できないってことになる🤣」「さすがにGoFのデザインパターンにオレオレの名前をかぶせてくるヤツはいないだろうし😆」「いないいない😆」「自分がつらくなるだけだし😆」「GoF以外だと名前どんどんかぶせられるけどなっ」

参考: デザインパターン (ソフトウェア) - Wikipedia
参考: ギャング・オブ・フォー (情報工学) - Wikipedia — GoFと略されます

「まぁデザインやアーキのパターンって世の中にいっぱいありますし」「何しろデザインのパターンですから☺」「上の『肥大化したActiveRecordモデル…』に出てくるパターンはRails村ではコンセンサスと言っていいんでしょうか?🤔」「たぶんそんなことはない😆: この記事を書いた人が使ってるパターン名だと思うし」「Railsでは流通してるとも言い切れないし☺

「ただこういうパターンは名前からだいたいどういうものを指すのかは見当がつくといえばつくから、それで持っているところはあるかも🤓」「それはありますね😋

「それで言うとForm Objectには名前や意味のぶれはあまりないと思う」「まあフォームが入れ子になってるとか、やけに高機能なForm Objectはあったりしますけどね〜😆」「それでもHTMLの1つのフォームに1つのForm Objectが対応するという点では変わらないし」「確かに〜」

「一方Value ObjectService Objectは名前や意味がぶれまくり😆」「そういえば昼間タバコ吸いながら『Service Objectって言わずにCallableって言えばいいのに』って話になりましたね」「そうそう🤣また一個できたし」「callするだけだからCallableはそれなりに納得」「ところがService ObjectをApplication Facadeだと考える人たちはきっと『違う!』って言うんですよ😆」「そうなりそう😆

参考: Application Facades — martinfowler.com

「なお自分は(Martin先生の言う)Application Facadeの方がどっちかというとService Objectに近いと思ってます😋」「確かに設計の上位レイヤから降りてくるものだとFacadeになるんですけど、局所的な実装はcallだからCallableでよくね?って思ったり」「ってな具合に文脈がズレると話が噛み合わなくなったりするという」「それぞれのパターンでやりたいことはわかるんだけど、サンプルコードが出てきた途端に『これ違うんじゃ?』という気持ちが湧き起こったり😆

Query Objectも、他の言い方はあまりないかなという気はするけどどうだろう?」「ふーむ🤔」「記事の中ではForm ObjectQuery Objectは割とぶれない方かな」

View ObjectPolicy Objectは村によってかなり意味合いが違っていそうではある」「それあるかも」「下手をすると村ごとにまるで意味が違っていそう😆: Policyがビジネスロジックのポリシーなのか、フレームワークレベルのポリシーなのかとか」「Viewもどのビューかとかね😆」「殴り合いの原因になりがちな筆頭はだいたいView ObjectとPolicy Objectという印象💣

「やっぱり会社とかチームでは設計用語を合わせないといかんなという気持ちになってきますね:『このプロジェクトではGoFとMartin先生の設計用語を使います』みたいに決めとかないとそのうち殴り合いが🤣」「🤣」「それマジで必要な気がします」「殴り合いが始まったら決めないといかんかも😆

「その意味ではプロジェクトでrails newして最初にREADMEを書くときに設計用語についても決めて書いておくべきかもですね」「READMEに書いておけば誤読しないで済むし」「確かに〜」「rails newした直後にそんな意識高いこと書けっかな〜自分😆」「設計用語の決めごとのテンプレート作ってそのURLを貼るとかするといいかも😋

⚓その3: Railsのディレクトリ構成をどうにかしたい

※この日のつっつきはここが最後でした。

「ちーっす」「上の『肥大化したActiveRecordモデル…』が古いんじゃないかっていう話してました」「☺

「そうそう、当時のこの記事でapp/の下にディレクトリを全部並べる↓のってどう考えてもやりすぎだろって思ってた😆」「これ当時の私が全然わかってなくてサンプル用にもらったスクショをそのまま載せてしまって何度かツッコまれました😅」「こんな究極のトゥーマッチアプリが本当にあったら、マイクロサービスとまでいかなくても分離すべきっしょ😆」「この図今度削除しときます🙇

「ところがこういうディレクトリって最初にえいやっと作っておかないといつまで経っても作られないのよね😓」「それもそうなんですよね😆

「少なくともmodels/ディレクトリの下がカオスになるくらいだったら、モデルの役割を分担するためのディレクトリを早めに作っておくべきと思うし」「そう思うしっ😄

「理想を言えばだけど、ActiveRecordのものを入れるディレクトリとActiveRecord以外のものを入れるディレクトリは別にしたい!」「それ昼にタバコ吸いながら同じ話が出ました☺」「そう強く思うしっ😄

「もっと言うと、たとえばactive_records/みたいな複数形のディレクトリがあればよかったのにっ😆「ActiveRecordを継承するものを置くディレクトリと、概念モデルとかApplication Object的なものを置くディレクトリは分けたいっっ」「ほんに: ActiveRecordは永続化層ですし」「分けたいっっ」「もう常に分けたいと願っているし🙏

「その置き場所はきっとlib/じゃなさそうですね」「lib/じゃな〜い😆」「じゃな〜い: 置きたいのはライブラリじゃなくてApplication Objectだから」「そのアプリケーションにしかない固有のものだから☺

「気持ち的には、ActiveRecordがらみのものを置くディレクトリには別の新たな名前をつけたい: だって本来モデルはモデルであって永続化層じゃないんだから、本当ならmodels/にはActiveRecordじゃないものを置きたいし」

参考: ドメインモデル - Wikipedia

「ところがそうしたとすると、今度は『じゃActiveRecordのクラスを置くディレクトリは何て名前にするの?』ということになって考え込んでしまうという😆」「正直、いい名前が全然思いつかないっ😅

「自分もいろいろそういうのを考えた時期があって、たとえばmodels/の下で名前空間を切ってそこに閉じ込めるべきか、とか😆」「でも、追加のディレクトリはmodels/とかと同じ階層に並列させる方が気持ちとしても収まりがつくし、結局追いやすいという」「モデルと違うものは違うディレクトリに置いてくれって思うし😆

「その流れで言うと、kazzさんが作ったクラス(この後を参照)がActive Modelに置かれているのは、Form Objectのところに置くべきかなと思うわけですよ🤣」「ま、ま、そこは自分も悩ましくて、外から見たときにそれが永続化層なのかビジネスロジックなのかあいまいという点については自分は許せる、でもそのクラスの中にビュー的なものは入れたくないという気持ちがあってですね〜😅」「それもわかる☺」「本当ならそれも切り分けたいところなのに😭

「ヘキサゴナルアーキテクチャじゃないけど、こうやって置き場所を考えているとレイヤが違うだろという疑問は常に付いて回る」「そう思うっ😢」「そう考えるとRailsのapp/の下のディレクトリ構成ってもう限界なんだと思うし」「同意っ😤」「app/の下についてそのあたりをもう少し自由にやれればいいのにって思ったりするし」

参考: ヘキサゴナルアーキテクチャ(Hexagonal architecture翻訳) | blog.tai2.net

「たとえばヘキサゴナルアーキテクチャの層的に、外側のサービス層と内側のモデル層は一回どこかでサブディレクトリ化してもいいのでは?って思うことあるし」「そうそうっ😤」「そうなれば、プログラミングに慣れてない人でも『こういうことか』と納得してくれて、変なところに変なクラスを置いたりしなくなる効果があるんじゃないかって思うわけですよ」「同意同意っ😤」「そして一見似ているけどレイヤが違うものも、きっとちゃんと分けてもらえると思いたい☺

「そもそもapp/の下にassets/があるのっておかしいし🤣」「🤣」「たしかに〜」「assets/はむしろpublic/の方が近い」「assets/ってコントローラやビューやモデルと並列されるものじゃないし😅」「実に不思議」「ヘルパーまで並列に置いてあるし😭

「シンプルなアプリだったら今のRailsのディレクトリ編成でも別にいいんですけど、アプリがでかくなってきたときにそういうところが苦しくなってくる」「ディレクトリを動かそうと思えば動かせるのに現実には動かす時間が取れないという😅

「ちょっと思ったんですけど、どうせならComplexRailsみたいな拡張に耐えられるバージョンをこしらえて欲しいっっ🤣」「🤣」「新幹線だって線路の軌道広くしてるんだからRailsだって外側にもう一本線路を敷いて欲しいっっ🚅」「わかる〜🤣」「🤣」「このアプリはローカル線、このアプリは成長したから広軌にお引越しね、みたいな: rails newで新幹線モードみたいなオプション付けたらディレクトリ構造ががらっと変わるとか」「expressモードというかcomplexモードというか」「DHHなら『それRailsで作るのやめろ』とか言いそうですけど🤣

参考: 広軌 - Wikipedia

「ってなことをさっきの肥大化記事を読みながら思ったわけですよ😆: 5年前に読んだときはトゥーマッチかなって思ったけど、今見ると結構必要なものがあるなって思えてきたし」「個別のパターンやディレクトリの良し悪しじゃなくて、必要なものはこの中に確かにあるー」「今のRailsのデフォルトのディレクトリ編成も、アプリが複雑になったときにスムーズに対応しやすくする方法があるんじゃないかという気がしてくるんですよね🧐」「ありそう!ありそうなんだけどそれが中々見えない😢

「だからTrailbrazerが出てきたんですね」「そういう意味でTrailbrazerは結構いい😍: 巨大なアプリになるとわかれば一度使ってみたいと思うし」「ただね〜、最初に軽いものを作るときはトゥーマッチなんですよね😅」「ほんとそこが悩ましい😩: プロトタイプで作ったアプリがそのまま本番行きで拡張されまくったり」「シンプルとコンプレックスの間にギャップがあるのが問題」

「オーバーキル覚悟で一度Trailbrazerに挑戦するしかないのかな…」「ただその一方で、今のシンプルなRailsで十分足りる人もいっぱいいるわけですよ: ここで今したような話も、最先端というよりは真ん中やや遅れ気味ぐらいのところかなとも思うし」


trailblazer.toより

「このお話、次回の公開つっつきに持ち込みたいと思います: お疲れさまでした〜」

⚓ものも言わずにコケるWebpackerのデバッグ(RubyFlowより)


つっつきボイス:「Webpackerはいくらでもサイレントリーに死にそうではある😇」「実は今自分のオレオレRailsアプリをWebpackerベースでrails newして作り直そうとごそごそやっているんですが、やっているうちにだんだんWebpackerに変える意義がよくわからなくなってきてて💦」「まあアセットの作り方が変わっただけといえばそれまでで、Webpackerで何か新しいことができるわけでもないし手間は増えるし🤣」「🤣

「新オレオレアプリではWebpackerを入れる代わりにSprocketsとTurbolinksを殺してみたんですが、よかったのかな…?🤔」「そこは確かに悩ましくて、Sprocketsはフロントエンジニアと連携するときに邪魔になるので殺したいというのはわかる」「そしてそれ以前にWebpackとSprocketsを両方使うと死にそうになるんで、どっちかを殺すとなったらSprocketsを殺すという感じですかね〜」「なるほど!😃」「アセットパイプラインでWebpackとSprocketsを両方使うぐらいならWebpackに寄せるだろうな、と自分は理解してます☺

参考: アセットパイプライン | Rails ガイド — Sprocketsの解説
参考: Rails で JavaScript を使用する | Rails ガイド — Turbolinksの解説

⚓「Railsが遅い」とボヤく前に


つっつきボイス:「原文タイトルは煽りまくりですが内容は割と普通で、上の日本語タイトルで補完してあります😆: Railsアプリが遅いならまずSQLクエリとか設定とかを疑えという感じの内容でした」「自分も『Railsって遅いんでしょ?』って言われたらそう説明すること多いな😎: Rails自身の速度がそこまでクリティカルに影響するような凄いアプリってそうそうあるものでもないですし😆

⚓PayPalで使われているGraphQL


つっつきボイス:「PayPalもGraphQL使うようになったのか、へぇ〜」「BPSではもっぱらアレ使ってますよね(何だっけ)」「Stripe↓」「そうでした」

Stripe決済を自社サービスに導入してわかった5つの利点と2つの惜しい点

「PayPalそのものはともかく、古いAPIがずっと生き延びているのと、古いAPIで作ったオブジェクトを新しいAPIで参照できないなど新旧APIで設計上の互換性がないところがつらい😩」「あー」「あとドキュメントもあんまりイケてない: 新旧サービスがあるからドキュメントも新旧あるのは当然なんですが、新旧で同じようなサービス名や同じようなAPI名がたくさん出てきてわからなくなったり😢」「そうでしたか…」「おそらくですが命名方針がビジネスに寄り過ぎてて統一性が後回しになってるんじゃないかな」

「ところでPayPalに限らないけど決済サービスでGraphQLを使う意義ってどのぐらいあるんだろうか?🤔決済でそんなに複雑なクエリが欲しくなるとも思いにくいし、あるとすればPayPal側で集計処理させてその結果をいろんな形で取り出したいときとかかな?」「それもそうかも」

⚓Railsのバリデーションとマルチページのフォーム(Hacklinesより)


つっつきボイス:「最近kazzさんがフォームで複数のモデルを扱おうとして苦心しているので」「kazzさんは今作っているクラスをActive Modelと呼んでるけど自分はそれをForm Objectと呼んでますね(コードはどっちでもまったく同じですが😎)」「置き場所が違うだけなんですね😃」「結局そういうものを作るのが一番近道だし後々メンテしやすいし」

「この記事はマルチページフォームの話だけど、確かにマルチページフォームはつらい!😖」「Windowsインストーラのウィザードみたいに複数ページにまたがるフォームですね」「あ〜validatesif:を書く↓のはヤバい👻

# 同記事より
Class Person < ActiveRecord::ApplicationRecord
  validates :name, presence: true
  validates :date_of_birth, presence: true, if: create_stage > 1
  validates :ni_number, presence: true, if: create_stage > 2
end

「モデルのvalidatesif:を書くくらいだったらForm Object的なクラスに切り出すべき: そうしないとあっという間につらくなるし、それに限らずActiveRecordを継承しているところでvalidatesに条件を付けるとだいたい後で酷い目に遭う🎃」「副作用がひどそうですね…😓」「フォーム側で必要なバリデーションが永続化データのバリデーションの中に入ってくるともうそれだけで混乱するし、DBで永続化されたデータが正しいのかどうかも確信できなくなる😩

追いかけボイス:「モデルのvalidateson:使うのも要注意よ〜」

参考: Active Record バリデーション | Rails ガイド

⚓セキュリティチェックリスト2本立て


つっつきボイス:「2つの記事はこの間公開した記事↓で参考に挙げられていました」「1本目はあまり見たことない感じですが普通にRailsのセキュリティチェックリストとしてよさそうですね」「★も1000超えてますし😊」「RailsはとりあえずBrakemanで機械的にチェックできるセキュリティホールを全部つぶしておくのが吉」

Railsアプリで実際にあった5つのセキュリティ問題と修正方法(翻訳)


presidentbeef/brakemanより

「へぇぇ、RedcarpetHTMLSafeってあるって初めて知った😁」「Redcarpetって久しぶりに見る名前だけど何だったっけ…」「Markdownが使えるようになるエンジン」「でした」「1本目のセキュリティチェックはRailsだけじゃなくてgemも対象にしてますね: Brakemanをちゃんとアップデートしろとかも書いてあるし」

# eliotsykes/rails-security-checklist
# bad
renderer = Redcarpet::Render::HTML.new

# less risky
renderer = Redcarpet::Render::Safe.new

「Detectify?」「detectify.comっていう脆弱性チェックサービスみたいです」「なるほど: こういうブラックボックステストやってくれるところはいろいろありますけどね😊


detectify.comより

「2本目↑は文字通りCTO向けの自社セキュリティチェックリスト: RailsやWebアプリ以外にも広く使える」「SaaSでなくてもいいと」


cto-security-checklist.sqreen.ioより

⚓Deviseのパスワードを安全に保つには


つっつきボイス:「そうそう、パスワードハッシュってこうやって$マークのところで意味が区切られるんですよ↓」「えっ知らなかった😳構造があるんだ…」「こういうのはPHPなんかでも作られますね: フレームワークの数だけこういうのがある😎


同記事より

⚓その他Rails

⚓Ruby trunkより

⚓Setではダックタイピングよりis_a?がいいのでは?

# 同issueより
require 'set'
class MySet
  include Enumerable
  def each(&block) [:my, :set].each(&block) end
  def size() to_a.size end
  def is_a?(klass) super || klass == Set end # <== Hack! 😭
end
puts Set[:set].subset?(MySet.new)

# => true

つっつきボイス:「ダックタイピングだと苦しいということみたいです」「Setとか使ったことないわ〜: ふと思ったけどRailsでSetって使われてたりするんだろうか?」「素のRailsにはさすがにないみたいです」「Setじゃないといけない状況があまり思いつかない😆

参考: class Set (Ruby 2.5.0)
参考: 集合 - Wikipedia

よく見たら、issueを開いたのは以前のウォッチで紹介したpersistent-💎 gemの作者でした。

参考: Ivo Anjo / persistent-💎 · GitLab

⚓Ruby

⚓複数のコレクションをイテレートする(Hacklinesより)

# 同記事より
class Group
  include Enumerable

  def initialize(members)
    @members = members.dup
  end

  def iterate_members_alphabeticaly
    ite = @members.dup.sort
    while k = ite.shift()
      yield k
    end
  end

  def iterate_members_by_name_length
    ite = @members.dup.sort_by { |x| x.size }
    while k = ite.shift()
      yield k
    end
  end

  def each(&block)
    enum_for(:iterate_members_by_name_length).each(&block)
  end
end


group1 = Group.new(["Nick", "Petros", "Ana"])

group1.each {|x| p x }

つっつきボイス:「何か変わったことしてるのかな?と思って」「イテレーターをこんなふうに返すみたいな普通の話かなー🤔」「お、#enum_forなんてメソッドがあるし: enumを取ってくるメソッド↓」

参考: instance method Object#enum_for (Ruby 2.5.0)

Enumerator.new(self, method, *args) を返します。
ブロックを指定した場合は Enumerator#sizeがブロックの評価結果を返します。ブロックパラメータは引数argsです。

「enumを取ってきてeachすればそりゃ確かに動く😆

⚓Fir: fish風の派手目なRuby REPL


dnasseri/firより


つっつきボイス:「fish使ってないしな〜」「一瞬使ってみたけどbashに戻したことあります😅」「Firは試しに動かしてみたけどところどころつっかえるかな…終了もexitじゃなくてCtrl-Dだったし」「ま、お好きな人はどぞ〜☺

参考: fish shell


fishshell.comより

⚓Ruby内部の動きを探ってみる


同記事より


つっつきボイス:「まさにRuby内部の動きを追っているし↑パースしてASTを切ってみたいな」「『Ruby Under Microscope』的なところですね: 私はGobyでこのあたりを学びました😅」「これはいい記事っぽい😍

参考: 書籍紹介『Rubyのしくみ Ruby Under a Microscope』

記事のタイトルはこちら↓のもじりのようです。

参考: 計算機プログラムの構造と解釈 - Wikipedia
参考: SICP Web Site for the Japanese Edition — 第二版の日本語訳が全文公開されています。

⚓強力な暗号化鍵(Hacklinesより)

# 同記事より
SecureRandom.random_bytes(32)

つっつきボイス:「Rubyで安全な乱数はSecureRandom↓から取るというのはもう基本ですね」「randを暗号化とかに使ったらあかんと」「randはセキュアでないのでよろしくない🧐

参考: module SecureRandom (Ruby 2.5.0)
参考: module function Kernel.#rand (Ruby 2.5.0) — 「疑似乱数」なので
参考: 擬似乱数 - Wikipedia
参考: Rubyで乱数生成 - 或るプログラマの開発日記

SecureRandomにはhexってのがありますね」「あ、昔それ踏んで直してもらっちゃったことあったな〜💦

require 'securerandom'
p SecureRandom.hex(10) #=> "52750b30ffbc7de3b362"

「もっとちゃんとしたdigestを取りたかったらOpenSSL::Digest↓(重いけど😅)を使うとか、いろいろやり方はありますね」

参考: class OpenSSL::Digest (Ruby 2.5.0)

「お、記事にあったrbnacl↓っていうgemの方が面白そう😃: ★も700超えてる」「Railsでも使えると書いてる」「ライブラリが今後メンテされるかどうかがちょっとコワイ🎃: こういうコアライブラリは基本的にメジャーなものを使うべきだと思いますね🤓


同リポジトリより

「READMEに書いてあったGPG↓、一瞬PGPに見えちゃった😅」「GPGはものすごく歴史古いですね📃

参考: GNU Privacy Guard - Wikipedia

⚓Yabeda: Rubyアプリのinstrumentation gem(Hacklinesより)


同リポジトリより

TechRacho記事でおなじみのEvilmartiansのブログです。

Rails+PostgreSQLのパーティショニングを制覇する(翻訳)


つっつきボイス:「ebay向けに作ったgemみたいです」「やべだ?」「Prometheusをこれだけ密にインテグレーションできるとしたらなかなかいいかも?」


prometheus.ioより

RailsのパフォーマンスをPrometheusで測定する(翻訳)

「ははぁ、Yabedaを使ってまとめてPrometheusとかに投げられる↓ってことか: よさそう😍

「yabeda-muninとかないのかな😆: そしたらBPSで使える」「まぁmuninのアダプタはたぶん超楽勝で書けそう🤓: コマンド実行したら値を返すスクリプトを書けばそれでおしまいなので」「muninってそんなにシンプルなんだ!」「シェルスクリプトでも書けるぐらいなので😉

参考: Munin


munin-monitoring.orgより

参考: サーバー監視ツール Munin - Qiita

⚓その他Ruby


つっつきボイス:「2本のうち1本はRubyでスクレイピングする記事ですが、こういうのはいっぱいありそうですね」「スクレイピング情報はいっぱいあるけど、本当にちゃんと書かれているのは意外に少ないですね」「そっかも」「スクレイピングは結構ノウハウが必要なんですよ: お、この動画は不定形なデータのスクレイピングも解説しているようだ」

⚓クラウド/コンテナ/インフラ/Linux/Serverless

⚓AWS Auroraがプライムデー初日にコケた

参考: Amazonプライムデー初日にサーバーがダウンしたのは「DBをOracleから自社製に乗り換えた」ことが最大の理由 - GIGAZINE


つっつきボイス:「そうそうこれね☺」「記事を読み進めないとAuroraだということがわからなかった💦」「むしろ今までAmazonがまだOracleだったんだという勢い😆」「とは言え、AWSほどの規模でAuroraに乗り換えたのはさすがだなと思いますね💪」「おそらくAWSでもまだOracleは使っていそう」


その後続報が出ました↓。

参考: Amazonプライムデーのサーバ障害、AmazonがOracleからAurora DBに乗り換えたのが原因ではない。Amazon CTOがCNBCの報道を否定 - Publickey

⚓TerraformやVaultなどがアップデート


つっつきボイス:「HashiCorpのVaultって何でしたっけ?」「名前だけ見て言うとセキュリティとか認証がらみでしょうね」「いわゆるKMS的な機密情報を管理する」

vault: 金庫室、地下納骨所、墓地

参考: Key management - Wikipedia


vaultproject.ioより

「HashiCorpは結構頑張ってますね: マイクロサービスを自社で建てるみたいなことをやってる会社はHashiCorpの製品のお世話になってることが多いと思う」「おー😃

⚓その他クラウド


つっつきボイス:「ライナスがきれいなライナスになったか😆」「寺に籠もって反省したんでしたっけ」「Richard Stallman御大がコメントを寄せてたのにはつい笑った😆」「えっ知らなかった😳

後で探してみたところ、この辺かなと思いました↓。

参考: Richard Stallman Says Linux Code Contributions Can’t Be Rescinded - Slashdot
参考: Richard M. Stallman on the Linux CoC : linux

参考: リチャード・ストールマン - Wikipedia

Stallmanと言うと彼の自作の歌を思い出してしまいます↓。


終わっちゃいましたが。

⚓SQL

⚓大物リリース2本立て(DB Weeklyより)


つっつきボイス:「先週PostgreSQL 11が出たのはウォッチでも簡単に書きましたが」「ぽすぐれも年1でがんがんリリースするポリシーに変えたのかな?」「昨年10月にPostgreSQL 10をリリースしてちょうど1年ですしね」「PostgreSQLはメジャーアップデートで機能を増やすというポリシーでこれまでやってきてたはず」

「そしてその後でRedis 5も出たそうです」「Redisを自分でアップグレードするとかあまり考えたことなかったナ😁」「とりあえずredisでStreamができるようになった↓というのが目を引きました」「redisは何でもできるから😋


redis.ioより

参考: Introduction to Redis Streams – Redis

「ほー、Streamってこういうことか↓」「というと?」「ものすごく大量のデータを時系列でぶちこんでいくデータストリーム的なものをここではStreamと呼んでますね: いわゆるDSMSと呼ばれるヤツ」「おー」

# redis.ioより
> XADD mystream * sensor-id 1234 temperature 19.8
1518951480106-0

参考: データストリーム: 用語解説辞典|【公式】NTTPC
参考: データストリーム管理システム(DSMS) - Wikipedia

「PostgreSQLだとウィンドウ関数↓でたとえば『直近10万件のデータを保持取得する』 などのストリーム処理ができますが、redisでもそういうことができるようになったんでしょうね☺」「データが追加されると古い方から消えていくとか、あるいはそのシステムにクエリを置いておくとデータを取り出せるみたいなことに使ったりします🧐」「redisすげー😳」「ここまでくるとストリーム管理システムと言っていいでしょうね: ちゃんと見てないけど、これができるということはContinuous Queryとかも投げられるんだと思う」

参考: PostgreSQL 10.4文書: 3.5. ウィンドウ関数
参考: InfluxDBのContinuous Query その1 | Yakst

⚓ストリームシステムのクエリ

「ちなみにストリームシステムのクエリは、SQLとかのクエリとやや概念が違います」「おぉ?」「SQLとかだとクエリを投げたらトランザクショナルに処理されて必ず返ってくる」「ストリームシステムの場合、クエリを投げるんじゃなくて前もってストリームシステムに置いておく: たとえば『直近10件の移動平均を求めるクエリ』なんてのを置いておくわけです」「むむ?」「これがContinuous Queryという考え方で、その後でデータがストリームとしてやってくると、配置したクエリがそれに応じて結果をストリームで返す」「なるほどー!😃

「このシステムはストリームを扱うので、ストリームの処理結果をストリームで返すところがポイント」「だからContinuousか〜😋」「ってなことを学部生時代に研究でやってました🤓

「そしてPostgreSQLは同じようなことをウィンドウ関数でDBMSでやれる😎」「そういうことかー」「redisのStreamはざざっと見た感じではクエリの書き方がぽすぐれのに近そうな印象: 型のあるデータストリームっぽいし」

「そしてこういうストリーム処理をニューラルネットワークっぽく相互接続するのがAWS Kinesis↓とかApacheのHadoop↓とかですね」「なるほどなるほど!🤩」「KinesisやHadoopは、単なるレコード単位の処理ではない、もっと有機的なストリーム接続を扱うので、MapReduceとか使ったりとかまた少し違いますが🤓」「何だかすげ〜🤖」「やっぱredisは何でもできるなっと😋

参考: Amazon Kinesis (フルマネージド型リアルタイム大規模ストリーミング処理) | AWS
参考: Apache Hadoop


hadoop.apache.orgより

⚓JavaScript

⚓V8のソート(JavaScript Weeklyより)

記事中の「Timsort」が気になってググると、過去にJSでTimsortがバグったという記事がわらわら出てきました。

参考: Timsort - Wikipedia


つっつきボイス:「Timsortっていう言葉が気になって」「prototypeチェインとかいろいろ頑張ってる感」


同記事より

「あとV8内部で使われているというTorqueというDSLも気になりました: V8が自動車のエンジンだからそれになぞらえたっぽい名前」「やっぱりこういうマルチプラットフォームで動作する言語系ではこういう中間言語的なものがあると便利なんでしょうね😋: LLVMやJavaもまさにそうだし」「そうそうJavaのJVMとか」


同記事より

参考: Torque user manual · V8
参考: 中間言語 - Wikipedia
参考: LLVM - Wikipedia
参考: Java仮想マシン - Wikipedia

ちょうどV8 Torqueの日本語スライドも見つけました↓。

⚓ES6ではコレクションを扱える

「それでちょっと思い出したけど、今大学の授業で使うスライドのためにES6を調べながらJavaScriptのArray.forEach()↓の説明を書いてるところですよ☺」「ES6だとそうやってRubyみたいに書ける!?」「そう、ついにJavaScriptでコレクションを触れるようになる😂: もうこれで何でもできるぜって教えられるし💪

参考: Array.prototype.forEach() - JavaScript | MDN

// developer.mozilla.orgより
let array1 = ['a', 'b', 'c'];

array1.forEach(function(element) {
  console.log(element);
});

// expected output: "a"
// expected output: "b"
// expected output: "c"

⚓CSS/HTML/フロントエンド/テスト

⚓絵で見るWebAssemblyのポストMVP機能(Frontend Focusより)


同記事より


つっつきボイス:「MVCかと思ったらMVPでした💦」「Minimum Viable Productだよね?」「ですです」

参考: Model View Controller - Wikipedia
参考: MVP(Minimum Viable Product)とは | リーン・スタートアップの基本とMVPの実践 - その他ビジネス | ボクシルマガジン

「図が豊富なのはいいんですけど、英語圏はこのぐらい↓でもカートゥーンって呼ぶんだなと思って😆」「ポンチ絵的な😆」「まあポンチ絵よりはカートゥーンの方がまだ人様の前では格好がつくかも🤣

同記事より

「そういえばポンチ絵って明治時代の新聞だか雑誌のタイトルから来てたのを思い出しました」「(ググって)《ジャパン・パンチ》ね」「それそれ」「浮世絵!そんな時代とは🤣

参考: ポンチ絵 - Wikipedia
参考: 《ジャパン・パンチ》(じゃぱんぱんち)とは - コトバンク

⚓「ソフトウェアメトリックス」2018版

日本情報システム・ユーザー協会というところが作成しているらしい。元経済産業省所管らしいので怪しいところではないようだ。
気になったのは
* 仕様変更の発生とプロジェクト全体満足度の割合で「変更なし」73%,「軽微な変更が発生」76%に上がっていたこと
* 「概要レベルのCRUD図」「業務構成表」「概要レベルのER図」「ビジネスプロセス関連図」は三分の一以上が作成していない。
* 受入テストやユーザー総合テストでの設定項目は「業務シナリオ」「要件確認」が多い。
* 保守容易性確保のガイドラインの有無
気になったというかそうなんだなーと思った程度


つっつきボイス:「社内Slackのtest板からいただきました🙇」「一般社団法人!」「テスティングやってる人ならこの辺は既に常識なのかなと思うけど、外からだとなかなかわからないよね😆」「しかも日本ドメスティックだし😆」「まあたとえばISCなんかも業界の外の人は普通知らないでしょうし☺」「ともあれ、テスティングを体系的に行う方法はソフトウェア工学としてはちゃんと存在しているので」

参考: Internet Systems Consortium - Wikipedia


isc.orgより

「こういうグローバルな調査はもちろん大事だけど、母集団がどれぐらい偏ってるか恣意的な要素が混じってないかとかが気になるところだし😆自分たちの開発における意義としては、たとえばCIのエラー件数の推移を取るとか、コード量の増加に対してカバレッジがどう増えているかとかの方が恣意的な要素が入りづらいし実用的かなという気はしますね: Railsならrake statsとかで取れるし」「そうかも☺

参考: rails アプリの統計情報を一発で取れる rake stats を試した - yagihiro output

⚓その他HTML


つっつきボイス:「お、PinPをWeb APIで提供できるのか!どっかでこれちらっと見たな…」「へぇ〜😳」「みんな凄いこと考えるな〜: ついに動画がブラウザの外に飛び出すぞっと😆」「ホントだー飛び出してるー」「ブラウザ閉じたら動画も閉じちゃうんだろな😆

⚓言語よろずの間

⚓Pythonってそんなに遅かったかしら


つっつきボイス:「日本語訳があるのでそっちで」「PythonにもGILはやっぱりあるんだなー」「インタプリタ言語だと先読みとかできないだろうし、GILを使わずにやろうとしたら人間が最初から並列実行できるコードを書かないといけなくなるでしょうね」「それはつらい😢

参考: グローバルインタプリタロック - Wikipedia

「だいぶ前からPythonコードからCのコードを出力できた気がしますが、Rubyだと難しいのかな…?」「Rubyのオープンクラスがあると実行中に変わるから静的なコードにするのは難しいでしょうね」「うん難しいと思う」「もし仮にバイナリにできたとすると、きっとその中にはRubyインタプリタが丸ごと入ってると思う🤣」「🤣🤣」「mrubyならその方法でやれそう」

参考: Rubyのオープンクラス - DesignAssembler
参考: mruby-cli で ワンバイナリなツールを作ってみた - ねこのて

後で探してみると、PythonからCへの変換は決定版がなさそうな雰囲気でした。以下のshedskinもアーカイブされていました。

参考: Google Code Archive - Long-term storage for Google Code Project Hosting.

⚓中国剰余定理


同記事より


つっつきボイス:「チャイニーズリメインダーセオリー、聞いたことだけある😆」「数学やってる人だったら11次方程式作れば原理的にこういうことできるってわかるヤツ😆」「できるところまではわかっても手作りは大変そう😅」「Mathematicaなら無理やりこういうのやれそう😁」「今ならWolfram Alphaでできるかも?」「ま、これは数学におけるワンライナーみたいなもんだし🤓」「たしかに〜」「フーリエ展開かマクローリン展開かテイラー展開のどれかでできそうな気はするんだけどなー」「数学科だったけど忘れたー😇」「忘れたー😇

参考: Mathematica - Wikipedia


wolframalpha.comより

ふと、昔ちら見したジョゼフ・ニーダム「中国の科学と文明」を連想しました。

参考: 中国の剰余定理 - Wikipedia

参考: ジョゼフ・ニーダム - Wikipedia

⚓その他

⚓AI丸投げ開発とは

元記事のタイトルが秀逸だと思いました。それだけでした。

参考: そして誰もいなくなった - Wikipedia

⚓スパイチップその後


つっつきボイス:「チップは置けてもどうやってデータを抜くんだろうなと気になってたけどやっぱり」「実はチップを人力で回収するとか😆」「それならわかるけど、メモリも内蔵してなさそうだしな〜謎🤔

「まあ意表を衝いたデータの抜き方ってときどき出現しますけどね: クランプメーター(アンペア計)で配電盤にアクセスすることで隣の部屋のディスプレイ表示を電源ラインから取り出す裏技とかありましたし」「それできそう!」「電源ラインのノイズからデータを取り出すとは」「天才だよな😆」「そうやっていろんな技があったりするんで、スパイチップを一概に否定するのは難しい気はしますね🤓

参考: クランプメーターの正しい選び方、使い方【図解】

⚓その他のその他


つっつきボイス:「記事に出てくるものがいろいろなつかしくてつい😅」「当時は大学のコンピュータ部とかでこんな感じで凄いゲームを作ったりしてましたね☺

⚓番外

⚓いかにもじゃない方が好き


つっつきボイス:「トンガッたデザインにすると陳腐化早そうですよね」「電脳メガネって言葉が既に恥ずかしいぞっ😆」「😆」「網膜に直接投影するタイプのもあった気がする」「それ確実そうですね」「目に何か差し込むのかとおもた👁

参考: 網膜投影型ARグラスの一般向け受注が開始 | Think IT(シンクイット)

⚓教師データなしで動いたロボットハンド


つっつきボイス:「やってることはクールなんですが、ナレーションの英語がとってもインド訛りなのが気になって😆」「お、インドにしてはゆっくり喋ってる方かな: カンファレンスとかでこの数倍の速さで喋られると聞き取り大変😅」「教師データなしというより自分で教師データを作りながら学習してるんじゃないかな🤔


今回は以上です。今度の公開つっつき会でお会いしましょう!😄

おたより発掘

バックナンバー(2018年度後半)

週刊Railsウォッチ(20181022)Railsの名前空間地獄とrequire_dependency、PostgreSQL 11がリリース、clean-rails.orgほか

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

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

Rails公式ニュース

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

DB Weekly

db_weekly_banner

Frontend Focus

frontendfocus_banner_captured

JavaScript Weekly

javascriptweekly_logo_captured

Rails: 5.1以降タグヘルパーの#tagの新しい記法は#content_tagより便利

$
0
0

こんにちは、hachi8833です。少し前の話ですが、Rails 5.0->5.1で変更されたビューヘルパーの#tagメソッドについてメモします。

なお5.1と5.2では変化はありません。

Railsビューヘルパーの#tagメソッド

きっかけはDHHによる以下のissueです。

# #25195より
# 以前は<span class="bookmark"></span>を次で生成していた
content_tag :span, nil, class: 'bookmark'
# 今後は次でできる(<span>など閉じタグが必要なタグや<br>など閉じタグ不要なタグのインデックスを保持するので) 
tag.span class: 'bookmark'

# 以前は<div id="post_1">を次で生成していた
content_tag :div, post.title, id: dom_id(post)  
# 今後は以下でできる
tag.div post.title, id: dom_id(post)

# 以前は<br>を以下で生成していた
tag :br, nil, true
# 今後は以下でできる(spanのときと同様。このタグは閉じタグ不要であることがわかっている)
tag.br

# ネストもサポートできるとよい: <div id="header"><span>hello</span></div>を以下で生成する
tag.div(id: 'header') { |tag| tag.span 'hello' }

その後#25543でマージされ、5.1に取り入れられました。Rubyらしく書けるtag.タグ名記法はデフォルトでHTML 5をサポートします。

かつ、content_tagはレガシ記法であるとされ、その記述は5.2の現在も変わっていません。ただしcontent_tagは今のところ非推奨にはなっていないようです。非推奨にするには影響が大きすぎるからだろうと思いました。

その代り、tagの古い記法(tag('br')など)は#25543の時点で非推奨になっており、今後廃止の予定です。

参考: Rails 5.1 からは tag(:br) より tag.br を推奨 - Qiita

Rails 5.2.1で確認

5.2.1でrails newし、Postをscaffoldで生成し、レコードを2つ作ってから上のcontent_tag記法とtagの新しい記法をそれぞれ試してみました。

<!-- view/posts/index.html.erb -->
...
<%= content_tag :span, nil, class: 'bookmark' %>
<%= tag.span class: 'bookmark' %>

<% @posts.each do |post| %>
  <%= content_tag :div, post.title, id: dom_id(post) %>
  <%= tag.div post.title, id: dom_id(post) %>
<% end %>

<%= tag :br, nil, true %>
<%= tag.br %>

<%= tag.div(id: 'header') { |tag| tag.span 'hello' } %>
...

content_tag :spantag.spanも動きました。

ビューでtag.div tag.p('Hello world!')と書けば<div><p>Hello world!</p></div>のようにネストできたり、以下のようにdoendブロックで文字列を渡せたりする機能もcontent_tagと同じですが、記法がシンプルなのが嬉しい点です。

<%= tag.p do %>
  パラグラフの文章
<% end %>

使い勝手やHTML 5対応を考えると、基本的にcontent_tagよりもtag.タグ名を使うのがよさそうです。その割にはedgeguidesにもtagについて記載が見当たりませんが。

関連記事

Rails tips: ビューの`content_tag`のあまり知られていないオプション(翻訳)

週刊Railsウォッチ(20181105)DBマイグレーション9つのコツとハマった話、Railsのモデルとディレクトリの設計ほか

$
0
0

こんにちは、hachi8833です。ハロウィーン色に染まったGitHubの草を見そびれてしまいました(´・ω・`)。


つっつきボイス:「お、こんなイベントやってたんだ」「他の人の日報で気づいて、見に行ったらもう緑に戻ってました😢」「毎年やってるみたいですよ?😆」「かぼちゃ色🎃



つっつきボイス:「ハロウィンデバッグ、最初何だろうと思ったらネタでした😆」「diff芸というかマージリクエスト芸🧶」「修正後は常にtreatしそう😆

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

「それでは公開つっつき第5回、やっていきで始めたいと思いますー😃

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

今回もコミットリストから見繕いました。ドキュメント更新がますます増えています。

Update authorization.rb by esquith · Pull Request #34355 · rails/railsのような1文字のタイポ修正もありました。こういうのだったら自分にも投げられるかも?

⚓i18nロケール設定はaround_actionを使うようドキュメントを修正

#34356はドキュメントの更新ですが、#34043のissueがきっかけになっていました。

参考: rails/callbacks.rb at fc5dd0b85189811062c85520fd70de8389b55aeb · rails/rails*_actionコールバックのソース


つっつきボイス:「あら、あらら?今日の昼にプルリクメッセージにあったthread safeという言葉↓が取り消されてる😳」「早技😆」「コメントで『これはスレッドセーフにするためじゃないし』と教えてもらって訂正したのか」

#34356より

「まあswitch_localeするんだったらaround_actionで囲む方がスマートだろうし」「#34043は?」「そのissueが元でこのプルリクが投げられたようです」「なるほど、Railsガイドのとおりに書いたら期待と違ってたみたいな話がスレッドセーフ云々とに発展してたと🤓

「お、ロケール情報を書き換えてるし↓」「へー!😳」「I18nのこういう罠って一度は踏むんですよ🤣」「🤣

# guides/source/i18n.md#L
```ruby
-before_action :set_locale
+around_action :switch_locale

-def set_locale
-  I18n.locale = extract_locale_from_tld || I18n.default_locale
+def switch_locale(&action)
+  locale = extract_locale_from_tld || I18n.default_locale
+  I18n.with_locale(locale, &action)
end

「ぱっと見スレッドセーフでないように見えるんだけど、以前どこかのタイミングでスレッドセーフになるようにI18nが修正されたような覚えがあるし、随分前のRailsガイドでもI18n.localみたいなところを直接上書きしてロケールを変更できるという記述があったような覚えや、I18n入ってきたのはRails 3あたりだったような覚えもありますね(この辺記憶ベースですが)」「へー!😳

「お、with_localeって何だろう?: RailsのI18nを追ってみるか」「んー見当たらない…🤔?」「I18nってそういえばサードパーティgemだった気がする」「それだ!Rails本体には入ってないんだった😅

with_localあった↓: 指定の間ロケールを変える: まさにaround系アクションのためにあるようなメソッド😋」「やっぱり直接上書きしてる🤔

# i18n/lib/i18n.rb#L278
    # Executes block with given I18n.locale set.
    def with_locale(tmp_locale = nil)
      if tmp_locale
        current_locale = self.locale
        self.locale    = tmp_locale
      end
      yield
    ensure
      self.locale = current_locale if tmp_locale
    end

「お、config↓がスレッド対応してる: だから直接上書きしても大丈夫ということか」

# i18n/lib/i18n.rb#L39
  module Base
    # Gets I18n configuration object.
    def config
      Thread.current[:i18n_config] ||= I18n::Config.new
    end

    # Sets I18n configuration object.
    def config=(value)
      Thread.current[:i18n_config] = value
    end

    # Write methods which delegates to the configuration object
    %w(locale backend default_locale available_locales default_separator
      exception_handler load_path enforce_available_locales).each do |method|
      module_eval <<-DELEGATORS, __FILE__, __LINE__ + 1
        def #{method}
          config.#{method}
        end
        def #{method}=(value)
          config.#{method} = (value)
        end
      DELEGATORS
    end

「少々横道にそれましたので次へ〜」

⚓ActiveJob::Arguments.deserializeHashWithIndifferentAccessサポートを復活


つっつきボイス:「お、HWIAなんてのが出てきたし☺」「ウォッチ読者にはお馴染み?のHashWithIndifferentAccessですね: 略語で見たのは2回目ぐらいかな」「何でしょうかこれは?😆

参考: ActiveSupport::HashWithIndifferentAccess

「あ、HashWithIndifferentAccessはハッシュのキーにシンボルを渡しても文字列を渡しても同じようにアクセスできるようにするモジュールです: Railsのparamsってまさにこういう挙動ですよね」「あー言われてみれば機能として使ってるけど名前知らなかった😅」「長いですからねー名前😆: 長い名前といえばHABTM(has and belongs to many)という今や滅びた書式があります↓」「はぶとぅむ」「見たことなかった〜😅」「こういうのってチームで開発してないとなかなか知る機会がないですよね: 自分も『名前の長いヤツ』って覚えてるし🤣

参考: has_and_belongs_to_many (ActiveRecord::Associations::ClassMethods) - APIdock

「でコードはというと、#to_hしたことで必ずハッシュになるようになった↓」「Railsで開発しているとHashWithIndifferentAccessは普通に使いますね😋

# activejob/lib/active_job/arguments.rb#L149
      def transform_symbol_keys(hash, symbol_keys)
-       hash.transform_keys do |key|
+       # NOTE: HashWithIndifferentAccess#transform_keys always
+       # returns stringified keys with indifferent access
+       # so we call #to_h here to ensure keys are symbolized.
+       hash.to_h.transform_keys do |key|
          if symbol_keys.include?(key)
            key.to_sym
          else
             key
           end
         end
       end

⚓ActionController::APIはcookieやセッションをサポートする」が正しい

# actionpack/lib/action_controller/api.rb#L13
  # An API Controller is different from a normal controller in the sense that
  # by default it doesn't include a number of features that are usually required
- # by browser access only: layouts and templates rendering, cookies, sessions,
+ # by browser access only: layouts and templates rendering,

つっつきボイス:「サポートしないかと思ったらサポートする、でした😅」「確かにActionController::APIはcookieとかセッションをサポートするのが普通だろうし、ドキュメントは間違ってたけど誰も使えないとは思わなかったでしょうね☺」「修正よりプルリクメッセージの方が長い😆」「よく気づいたなーって思うし」「APIモードだからcookieやセッションが使えないなんて、そんな発想なかったし😆」「APIでもログインするし😆

⚓fixtureのHABTM関連付けで起きていたキー制約の違反を修正

よく見たら2015年に投げられたプルリクでした。

# activerecord/lib/active_record/fixture_set/table_rows.rb#L41
       def initialize(table_name, model_class:, fixtures:, config:)
         @table_name  = table_name
         @model_class = model_class

        # track any join tables we need to insert later
        @tables = Hash.new { |h, table| h[table] = [] }
+       # ensure this table is loaded before any HABTM associations
+       @tables[table_name] = nil
+       build_table_rows_from(fixtures, config)
      end

つっつきボイス:「お、言った端からHABTM出てきた🍄」「ハッシュの内容の順序が保証されるようになったのってRuby 1.9からでしたっけ?」「たしかそうだったと思います」「Ruby 1.9はいろいろbreaking changesがありましたからね👷

参考: Hash#keysの順番は保証されるのか? - Qiita
参考: class Hash (Ruby 2.5.0)

「Ruby 1.8も影響を受けるだろうってあるけどとっくにサポート外だし」「fixtureの書き方によってはこういうエラーが起きることがあるんだろうか?」「この修正で直る理由がよくわからない😭」「謎🤔

WARNING: Rails was not able to disable referential integrity.

This is most likely caused due to missing permissions.
Rails needs superuser privileges to disable referential integrity.

    cause: PG::InsufficientPrivilege: ERROR:  permission denied: "RI_ConstraintTrigger_c_3340781" is a system trigger

⚓ActiveRecordのconnected_toでハッシュやURLをサポート

# activerecord/lib/active_record/connection_handling.rb#L108
     def connected_to(database: nil, role: nil, &blk)
      if database && role
        raise ArgumentError, "connected_to can only accept a database or role argument, but not both arguments."
      elsif database
-       config_hash = resolve_config_for_connection(database)
-       handler = lookup_connection_handler(database.to_sym)
-        with_handler(database.to_sym) do
-         handler.establish_connection(config_hash)
-         return yield
+       database = { database => database } if database.is_a?(Symbol)
+       database.each do |role, database_key|
+         config_hash = resolve_config_for_connection(database_key)
+         handler = lookup_connection_handler(role.to_sym)
+          with_handler(role.to_sym) do
+           handler.establish_connection(config_hash)
+           return yield
+         end
        end
      elsif role
        with_handler(role.to_sym, &blk)
       else
         raise ArgumentError, "must provide a `database` or a `role`."
       end
     end

つっつきボイス:「connected_toは最近のActiveRecordに入った複数接続先をサポートするヤツでしたっけ」「そうだったと思います(参照: Railsウォッチ20181015: ActiveRecordにconnects_toconnected_toを実装)」

「お、何とpostgres://fooみたいな形式でも指定できるようになってる↓: このナンチャッテURLみたいな書き方、データベースの世界では割とメジャーです」「へぇぇ!😳」「コロンでパスワードを渡したりもできるし」「PHPのフレームワークでもこの書き方使うものがあったし(なつかし)、あとは、んーとJDBCだったかな」

User.connected_to(database: { writing: "postgres://foo" }) do
  User.create!(name: "Gannon")
end

config = { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" }
User.connected_to(database: { reading: config }) do
  User.count
end

「↓これこれ」「おー😳

参考: Connection URL Formats and Examples

# 上記事より抜粋
jdbc:mysql://localhost:3306/HerongDB?user=Herong&password=Secret

jdbc:oracle:thin:Herong/TopSecret@localhost:1521:XE

jdbc:sqlserver://localhost;user=sa;password=Herong

「たぶんですけど、これってRFCみたいな仕様に則ったものじゃなくてその世界で編み出された書式なんじゃないかな?」「そういえば私もJavaでEclipseで書いてたときにこういうURLっぽい書式よく使ってましたし、あとsshなんかでもユーザー名やホスト名なんかをURLっぽく書けたりするんで、そういうのって統一されてるんだろうって思い込んでましたね😅」「URI(URL)そのものは仕様がきっちりありますが、URLのプロトコルというかスキームで使っていいものについては仕様では言及されていないので、上みたいなデータベース接続のスキームが正式に認定されているかどうかですね🤔

参考: Uniform Resource Identifier (URI): 一般的構文

後で調べると、URL schemeの一覧には「すべてのスキームを網羅したリストはない」とありました。Stackoverflowを見ると、以下のIANAのリストがそれだということですが、このリストにはpostgresoraclemysqlといったスキームはありませんでした。

「ちなみにconfig = { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" }みたいなハッシュで渡せる形式もハッシュを1つ渡せば済むので便利ですね😋

⚓ActionControllerのいくつかのpublicメソッドをprivate化

「タイトルに気持ちのこもったamatsudaさんのコミットです」「new_controller_threadlog_errorがprivateになったと: 誰かが悪さしたんだろか😆」「log_errorは確かにpublicだと無理やり叩こうとする人が出かねないという気持ちは伝わってきますね」「response_bodyは逆にpublicになった?」「いや、response_bodyは移動しただけでpublicのまま変わってない」「あそうか💦

⚓番外: ActiveStorageのhas_one_attachedhas_many_attachedが NoMethodError


つっつきボイス:「たまたま上のツイートが目に止まったんですが、モジュールの読み込み順とかそのあたりの問題らしくworkaroundしか示されてないので時間があればという感じで」「オートローダーとかに絡んでたらつらそうなヤツ😅」「苦戦してそう…😥

⚓Rails

⚓特集「肥大化したActiveRecordモデルをリファクタリングする7つの方法」今ならどうなる?続編

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

先週の続きです。

前回の個人の感想のおさらい:

  • そもそもGoF以外の設計パターンの名前がまちまち
  • 「肥大化した…」記事は意味が広がったりしたパターンもあるが有用性が増しているパターンもある
  • Form ObjectとQuery Objectは今も有用性高そう
  • View ObjectとPolicy ObjectとService Objectは意味と名前がばらつきがち
  • Policy ObjectはPunditやCanCanCanに任せる方がよいかも
  • Decorator(Presenter)は最近あまり使われてないかも?
  • Railsのapp/の下にディレクトリを増やそうとすると「レイヤが違う」気がして仕方がない
  • モデルばドメインモデルと考えたいのに、Railsのmodels/はActiveRecordを継承するクラスの置き場に使われている
  • Railsがシンプルなアプリから複雑なアプリまでシームレスに拡張できれば理想
  • Trailbrazerはいいけど最初から大規模になるとわかっていないと導入しづらそう
  • 少なくとも案件単位では設計用語を揃えるとよさそう

つっつきボイス:「お、先週の続き」「要点を簡単にまとめてみました」「ちなみに『肥大化…』の記事は読んだことあります?」「どうだったかなー😅」「要するにActiveRecordモデルがfatになったときの切り分け方パターンです: 当時かなりPVあったはず」「今もPVかなりありますね😋

「この辺ってみなさんのところではどうされてます?Form Objectとかぐらいは使うとか?」「Value Objectは使ったかも🤔」「Value Objectは受け取られ方が相当ばらついてますね😆

「『肥大化…』の記事のValue Objectは割と軽いcomparableなオブジェクトを作る感じですが、Structになれるものをクラスで展開することをValue Objectとみなす人もいたりするし」「以前VirtusっていうgemがValue Objectに使われるっていう話を聞いた気がしましたけど」「たぶんそのときはVirtusをForm Objectとして使う話だったんじゃないかな」「そうだったかも😅

参考: class Struct (Ruby 2.5.0)
参考: solnic/virtus

「社内勉強会で自分が作ったスライドにMartin FowlerによるValue Objectの定義があったはず(探す)…あった🎯↓」

参考: マーティン・ファウラー - Wikipedia
参考: ValueObject

「上はあくまでMartin先生による定義ですが、Value Objectは等価性の比較で値だけをチェックするもので、(これもMartin先生の言う)参照オブジェクト(Reference Object)と違ってobject idを見ない」

「Value Object、仕事では使わないけど、以前個人の小さなプロジェクトで使って見事にどハマりしたことがあります😩」「おぉ?」「ピュアなオブジェクト同士を比較している分にはいいんですけど、Rubyでハッシュとかeql?とかを書き換えることがあるじゃないですか: 自分しか使わないからいいだろうと思ってValue Objectにしてみたら、純粋なRubyの部分はたとえ正しく動いたとしても、たとえばActiveRecordのクエリの中でuniqとか使うとそういう部分まで書き換えられて軒並みぶっ壊れて参ったことはあります😇」「南無🙏」「少なくともValue ObjectはピュアなRubyオブジェクト以外ではやらないと強く思いました」「いわゆるPORO(Pure Old Ruby Object)ですね: Arelとかに落とすとおかしくなる」

「ちょうど上のスライドにもありますけど、どうしてもArelに落としたければ組み込みバリューやシリアライズLOBに保存するという手はありますね: でもシリアライズしたら同値比較はできても大なり小なりの比較はできないからあまり意味はないかもですが☺」「Arelとか他のライブラリがちょっとでも絡んでくると独自の比較をやり始めるんで、自分以外は絶対に誰もちょっかいを出さない自分だけのクラスじゃないと使えないかもって思いました😤

「ActiveRecordはデータベースとのマッパーをやっているので、ActiveRecordが絡むとどうしても厄介になりますね: マッパー意識してまで書こうとするとつらくなるばかり😨」「ActiveModelまでだったらまだピュアなオブジェクトなので何とかなるかもしれないけど」「Hashie::Mashみたいに同質性をいじるgem使うと壊れそうだし、ましてや仕事だと怖いので愚直に値を比較しまくるみたいな方向に逃げがちですね😅

「まさにそういうときにapp/の下に置くディレクトリをたとえば『ここにあるオブジェクトはActiveRecordには使うなよ!』『ここのはActiveRecordと結合していいぞ』みたいにわかりやすく示せたらそういう事故が減るよね、という話を先週してました😆」「まああの後自分もまた考えてみたんですけど、どちらかというと自分が本当にやりたかったのはActiveRecordのモデルをDAOに移すことだったのかなって思ったり」「データアクセスオブジェクト!」

参考: Data Access Object - Wikipedia

「つまりmodels/にはアプリケーションオブジェクトを置いてdao/の下にActiveRecordを置いたらすっきり分けられるんじゃないかなーって思ったわけですよ🤓」「あえて分けるとしたらそうかなー?🤔」「どうですか?」「…自分はやらないと思う🤣」「まあね🤣

「ともあれ、現状だと同じmodels/の下にActiveRecordとアプリケーションオブジェクトがひしめき合いがちになるんですけど皆さんはつらくないですか?」「ルール守らないヤツがいるんでひしめき合いまくってます😇」「🤣」「🤣」「そういうのは静かにマージリクエストをcloseする🤣

「さっきのパターンで言うと、View Objectとか自分も使わないですし、そういうのが使われたときに自分がやらかさないという自信はあんまりないかも😅」「名前空間掘るとかもですね☺」「ちょっと意識高くconcerns使うと今度はディレクトリをまたいでconcernsしたくなったりとか: 自分はあまりconcernsとかモジュールのincludeとかしないんですが😆

参考: Rails/Model(Active Record)のConcern | 酒と涙とRubyとRailsと

「concernsで下にさらにディレクトリを掘るとeager loadingのときに特定のタイミングでモジュールが読み込まれてるというエラーが出たり出なかったりするんであまりconcernsを掘り掘りしたくないですね😅: だったらもう純粋にmodels/viesw/controllers/だけにするのがいいのかなとも思いますし🤔」「すると今度はmodels/の下がごっちゃになるのをどうするかですよね」「…もうごっちゃでいいと思います🤣」「マジで🤣」「🤣

models/の下に200個ぐらいあるとつらくありません?」「つらいです😭」「10個20個なら我慢できるけど100個200個になるとmodels/の下を見る気が失せる🤮

「自分は基本的に30個ぐらいしか作らないし、例のattributes APIができて楽になったかなというのはありますね」「y-yagiさんの記事↓にあるヤツですね」「そうなってたらmodels/の下が混じっててもいい?」「いいと思います」「2、30個までならいけそうですね🤔」「自分たちのチームは受託が多いんでそれこそ限界まで膨れ上がったアプリがやってきたりしますが😩

参考: Rails 5のActive Record attributes APIについて | 日々雑記

「今もしRailsアプリを最初から作れるんだったら、DiscourseとかMastodonとかRedmineあたりの大規模CMS系アプリを参考にするかもしれないですね: この人たちがこうやってるならこうするか、みたいな」「Redmineは設計の古い部分もぼちぼちあるんで、自分はDiscourseとかMastdonあたりがおすすめかなと」「世界のすごい人に習う☺」「それは正しいかも☺

「Eコマース系のSpree↓もRails系でしたっけ?」「そのはずですね」「お、Spreeのリポジトリ見ると既に標準のRailsアプリの構成と違ってる🤣」「🤣」「backend/とかfrontend/とかある」「エンジンで分けたんでしょうね」「ルートディレクトリにapp/がないから、サブディレクトリにapp/がある感じ: おーこういう設計かー」「これはこれでフロントエンドとバックエンドで同じモデル使いたいときどうするんだろう?とか思ったり😆

spreecommerce.orgより

「お、Spreeのサブディレクトリは薄くできてる」「core/もある」「最近フロントエンドを分離することが増えてきましたね☺

「ところで先週も少し話しましたけど、どなたかTrailblazer↓って使ったことあります?」「知らなかったー😅」「このgemシリーズは、エンタープライズな方向に拡張しても大丈夫そうなディレクトリ構造になっていますね」「たしかoperation/の下でアクションがファイルに分かれてたりとか」「小さいアプリを書くにはトゥーマッチな雰囲気なんですが、大規模アプリにはいいんじゃないかという気がしています」「Trailblazer使ったことある人がいたら話聞きたいなと思ってるんですけど、まだ使ってる人に出会えたことがなくて💦: RubyKaigiで見かけた気もするんですが」


trailblazer.toより

# trailblazer/trailblazerより
app
├── concepts
│   ├── song
│   │   ├── operation
│   │   │   ├── create.rb
│   │   │   ├── update.rb
│   │   ├── contract
│   │   │   ├── create.rb
│   │   │   ├── update.rb
│   │   ├── cell
│   │   │   ├── show.rb
│   │   │   ├── index.rb
│   │   ├── view
│   │   │   ├── show.haml
│   │   │   ├── index.rb
│   │   │   ├── song.css.sass

「Trailblazerとかできちっと設計すればいいエンタープライズアプリになりそうなんですが、アプリってだんだん育っていくものだから最初から導入はためらわれるし、Railsでこれが欲しくなった頃にはもう移植するには手遅れになったり😅」「ははは😅」「Javaでエンタープライズアプリをやってきた人にはTrailblazerがしっくりくるのかなとも思ったり」

「お、このコピペここ数日あたりやたら見かけますよね😆:『いや本当さ』で始まるヤツ」「やっぱりあったかー」

「まあ設計が壊れるかどうかはやっぱりモラル次第かな〜😆」「😆」「PHPの某フレームワークの上にオレオレの設計が積まれているシステムを手がけたことあるんですけど、かなりきちんとメンテされてて感心したことありますね」「ああアレですね☺」「今思えばメンテナーの治安維持が効いていたんだと思うし😆」「愛のある治安維持がポイント😆」「そういう嫌われるのを覚悟で愛をもって治安維持する人がいることが大事なのかも」「名奉行ですね」「だめなプルリクをちゃんとrejectする人も必要😆

「設計って確固たる正解というものがないのが常ですが、それでも『これは絶対おかしい』みたいなパターンは踏まずに生きていきたいですよね☺」「この辺の話はエンドレスになりそうなので、この後の懇親会で飲みながらでも」「☺

⚓Hyperstack: Ruby DSLだけで書けるクライアントサイドWebアプリ(Ruby Weeklyより)


同サイトより

Opal、Webpack、Reactを動員してます。

とりあえずRails向けHyperstackテンプレートにあるrails new MyApp -m https://rawgit.com/hyperstack-org/hyperstack/edge/install/rails-webpacker.rbを実行して動きました。


つっつきボイス:「はいぱーすたっく?」「どうやらJavaScriptを一行も書きたくない勢のようです」「RubyでDSLを書くとJSが生成されて、HTMLが出力される..と?」「生成されたJSがめっちゃ長い」「何かのギャグだったり?😆」「とりあえず動きました」

class HelloWorld < Hyperloop::Component
  render(DIV) do
    # try changing 'world' to your own name
    H1 { 'Hello world' }
    P(class: 'green-text') { "Let's gets started!" }
  end
end
/* Generated by Opal 0.11.3 */
(function(Opal) {
  var self = Opal.top, $nesting = [], nil = Opal.nil, $breaker = Opal.breaker, $slice = Opal.slice, $klass = Opal.klass, $send = Opal.send, $hash2 = Opal.hash2;

  Opal.add_stubs(['$render', '$H1', '$P']);
  return (function($base, $super, $parent_nesting) {
    function $HelloWorld(){};
    var self = $HelloWorld = $klass($base, $super, 'HelloWorld', $HelloWorld);

    var def = self.$$proto, $nesting = [self].concat($parent_nesting), TMP_HelloWorld_1;

    return $send(self, 'render', [Opal.const_get_relative($nesting, 'DIV')], (TMP_HelloWorld_1 = function(){var self = TMP_HelloWorld_1.$$s || this, TMP_2, TMP_3;


      $send(self, 'H1', [], (TMP_2 = function(){var self = TMP_2.$$s || this;

      return "Hello world"}, TMP_2.$$s = self, TMP_2.$$arity = 0, TMP_2));
      return $send(self, 'P', [$hash2(["class"], {"class": "green-text"})], (TMP_3 = function(){var self = TMP_3.$$s || this;

      return "Let's gets started!"}, TMP_3.$$s = self, TMP_3.$$arity = 0, TMP_3));}, TMP_HelloWorld_1.$$s = self, TMP_HelloWorld_1.$$arity = 0, TMP_HelloWorld_1))
  })($nesting[0], Opal.const_get_qualified(Opal.const_get_relative($nesting, 'Hyperloop'), 'Component'), $nesting)
})(Opal);
<div><h1>Hello world</h1><p class="green-text">Let's gets started!</p></div>

「Opalって使ったことあります?」「いいえ〜」「OpalはRubyのコードをJavaScriptにトランスパイルするものなんですが、両者の型とかが違うので等価なコードにはならないという」「へぇ〜😳


opalrb.comより

「最終的なHTMLのためにJSを経由する理由って何なんでしょう?🤔」「はて…JSでdocument.write()したいからとか?」「Rubyでここまでネストして書くぐらいなら最初からHTMLで書いた方がいいような気も😆」「Opalを使う理由…もしかするとReactコンポーネントを生成したいということなのかも?」「あーそれあるかも」「だからJSにしたいのか」「この生成JSをフロントエンジニアに見せたら何と言われるか😆」「『後生だから全部Reactで書かせてください』って言われたりとか😆

参考: Reactコンポーネントへの理解を深める (1/2):CodeZine(コードジン)

「Hyperstackの作者をここまで駆り立てるものって…」「JS絶対書きたくないマンなんでしょう😆

⚓Hanami 1.3.0がリリース(Ruby Weeklyより)


つっつきボイス:「お、Hanami登場🌸: Hanami使ったことある人います?」「うーん😅」「後発のRuby製Webフレームワークで、最近のRubyKaigiにもよく登場してますね」「Railsよりはクリーンに書けるというのが売りだそうです」「何をもってクリーンとするかですけどね😆

「もしかするとですが、案外Rails 6あたりでHanamiと合体したりして😆: Railsは過去にもそういうことがあったので」「そうなったらうれしいかも😋」「そういえば何と合流したんだっけ…?🤔

後で調べると、Rails 3でMerb↓と合流してました。

参考: Ruby on Rails - Wikipedia
参考: Merb - Wikipedia

⚓Rails 5.2とDalliのキャッシュストア(Ruby Weeklyより)


つっつきボイス:「お、ダリ」「以前のウォッチでも取り上げたキャッシュストアですね」「使ったことあります?」「うーん😅」「memcache的に使う感じでしたっけ」

リポジトリにはmemcache-client gem(現在は非推奨)の置き換えとあります。


同リポジトリより

「そういえばmemcached最近全然使ってないなー: 以前は使ってましたけど」「Redisが出るまでは使ってたかも」「Redisはどんどん強くなってますよね💪」「そういえば未だにKyoto Tycoonで動いているシステムありますよ😆」「懐かし!」「クラスタ化もしてないのに今も動いてるという」


redis.ioより

⚓Railsのマイグレーション9つの秘訣(Ruby Weeklyより)


つっつきボイス:「9つの秘訣」「1はとりあえず基礎を押さえとく感じ: 既にお馴染みのものっすね」

⚓マイグレーション中のカラムデータをどう更新するか

「2.はActiveRecordのクラスを臨時に作るか生SQLを書くかという話🤓: こういう↓書き方にするかどうかはともかく、マイグレーションの外でActiveRecordを継承するオブジェクトを作って操作すると後で詰むことがあるんですよね」「全然知らなかったけどそうなんですね😅」「きっと皆さんも一度は踏んだんじゃないかなっと😎

# 同記事より
class DoThisAndThat < ActiveRecord::Migration[123]
  MyObject = Class.new(ActiveRecord::Base) # Barebones model bound to table `my_objects`

  def up
    MyObject.where(foo: :bar)
  end

  # ...
end

「どういうことかというと、後になってマイグレーションを走らせるときにそのActiveRecordを継承するクラスがなくなってるかもしれないということなんですよ」「へぇえ!🤯」「ほら、テーブル名を変更したりテーブルを再統合したりすると当然models/の下のActiveRecord継承クラスは消しちゃうじゃないですか」「なのでActiveRecord継承オブジェクトを作るなら上のコードみたいにマイグレーションの(updownとかの)中でやるか生SQLを書かないといけない🧐

「ところでどういう場合にこういうコードを書きたくなるんでしょうか?」「たとえばですが、マイグレーションで新しいカラムを作りつつ既存のデータから計算した値を入れたいときとか」「はーなるほど」「たとえば顧客ランクみたいなカラムを追加するときって過去の取引データを集計して指定の額を超えたらランクをAにしたいなんてことがあるわけですよ」

「ちなみに私が使ってるのは最近のRailsなんでupdownじゃなくてchangeでやってて、値を入れるときは別途rakeタスクを書いたりしてますね」「でもchangeでは書けない場合があるじゃないですか: そういうときはupdownで書かないといけない」「ああ確かに: changeはちょっと話がずれちゃいましたが、カラムのデフォルト値でどうにかできないかなとも思ったり」「CREATE TABLE直後ならデフォルト値でもいいんですけど、マイグレーション実行後にデータの一貫性を保証したい場合は計算ベースでデータを入れないといけなくなりますよね」「もちろんrakeタスクを作っておいて、マイグレーション後には必ずこれを実行するようにという運用にするのもありです: でも本来はマイグレーションの一部としてやりたいタスク」「そうできたらいいですよね😂

「ともあれこの2.は知っておいた方がいいですね: よくあるのがdb:migrateは通るのにdb:migrate:resetが通らないというパターン」「あーありそう…😅

⚓ロールバック

「3.はロールバック前にテストせいと」「そもそもロールバックってしないな〜😆」「あんまりやりたくない作業の代表かも😆」「理想はいつでもロールバックできるようにすることなんでしょうけど😅

⚓カラムをdropする前にdeprecate

「4.はカラムをいったんdeprecateして、本当に安全だとわかってからdropせいと」「Railsに限らないエンタープライズアプリあるあるな感じ」

⚓トランザクショナルなDLLコマンドのサポート

「5.のDDLって?」「Data Definition Languageでしたっけ」「CREATE TABLEとかのあたり」「そういうときのDDLがトランザクショナルかどうかをチェックせいと」

参考: データ定義言語 - Wikipedia

「CREATEとかROLLBACKとかをトランザクショナルに書かないといけないことってあるんでしょうか😅」「たとえば今のテーブルとまったく同じテーブルコピーを作っておいてトランザクショナルにリネームして差し替えることはありますね」「(Perconaの)pt-online-schema-changeの中でやってるような作業ですか」「こういう作業ってDBMSに依存するので、トランザクショナルかどうかは知っておけということですね」

参考: pt-online-schema-change

⚓途中のテーブルの様子をコメントするかエラーをraiseする

# 同記事より
class MyCoolMigration < ActiveRecord::Migration[5.1]
  def change
    # change_table :foobars do |t|
    #   t.references :baz, polymorphic: true, index: true
    #   t.integer :cool_number, null: true
    #   t.boolean :awesome, null: false, default: false
    # end

    # change_column_default :foobars, :this_thing, '123'

    reversible do |dir|
      dir.up do
        update "update foobars set blablabla complex SQL" # Troubleshooting this bit..
      end

      # ...
    end
  end
end

「ところで6.のこのreversible↑って??見たことないんだけど」「何だっけ…?」「updownを別々に書かなくていいヤツかな?と思ったら、changeの中に個別にupdownを書けるってことか!😳」「やーこれはマジ知らなかった💦」「これ知ってるとはスゴイですね😍」「言われてみればこう書ける方が( ・∀・)イイ!!: changeでできることもあるので」「お、記事の7.でもreversibleが説明されてる」

参考: 3.9 reversibleを使用する — Active Record マイグレーション | Rails ガイド

⚓schema.rbやstructure.sqlが肝心

「8.のschema.rbね…..ワイルドすぎるRailsプロジェクトなんかでカラム順序がschema.rbとズレてることってありますよね😇」「😇」「『誰がやった!』みたいな😆」「productionとdevelopmentとかでschema.rbのカラム順序がズレてたりするのマジ勘弁😤」「生SQLでSELECT *とかやったときに発覚したり」「とても悲しい気持ちになれる😭

「schema.rbの型が違ってたことならあります😇」「それもヤダ😆」「そうそう、詳しい方いたら聞きたいんですけど、Rails 4あたりまでは確かschema.rbでidのintegerbigintじゃなかったんですよね?」「あぁぁぁそれあった!😫」「あれもツライ😇

参考: Rails5.1からidカラムがbigintになるのでその対応 - koukiblog
参考: Rails 5.1: Default Primary Keys Are Now BIGINT

「カラム順序の話に戻りますけど、MySQLだとafterとか使ってカラムの挿入場所を指定できるのにPostgreSQLだとできないんですよね🥶」「それはできないっ…ぽすぐれではっ🥺」「どうしてもやりたかったら、さっき話したように別テーブルを作るときに欲しい順番でカラムを作ってからリネームして差し替えるぐらいしかないかと😓」「ぽすぐれユーザーは過去に軽く100回ぐらい検索したことあると思う🤣」「🤣」「🤣

参考: MySQLでカラムの順番を変更する - Qiita
参考: PostgreSQLでカラムを指定の位置に追加・変更する2つの方法 | Minory

⚓マイグレーションのタイムスタンプ

「cognisant…?」「辞書には『精通する』とありますね」

「そうそう、マイグレーションファイルの名前に刻まれる時間って、生成した人のローカル時間が使われるんですよ」「むむ?」「だからコミットでマイグレーションの間に他人のマイグレーションが差し込まれることって平気で起きますよね🤣」「なぬー🤣」「🤣

db:migrateは今存在するマイグレーションファイルを上から順に実行することは当然保証されるんですけど、コミットで間にマイグレーションが差し込まれてしまうとdb:migrateのたびに巻き戻って実行されてしまう可能性は常に残ってる😎」「ふぇえ」「なかなかマージされなかったコミットなんかでそうなりそうでしょ?😅」「そういう可能性があることを認識せいということなんでしょうね」

「CIがちゃんと書かれて回っていればCIでも同じ状態になっているはず」「CIの書き方にもよるかな」「CIでのdb:migrateを毎回頭から実行するんじゃなくて、必ず前回からのdiffで実行するようにしないとずれそう…はっ、そうやってカラム順がズレるのかっっっ🎃」「犯人いた〜👮‍♂️」「マイグレーションの地雷を踏めば踏むほどマイグレーションの仕様に詳しくなれる😆

⚓マイグレーションよもやま話

「質問: 開発当初でたとえばテーブルが3つしかないときってマイグレーションファイルの中身を書き換えたりしてて😆、その時点では何の影響もないからいいんですけど、その後カラムが増えてくるとそれもコワイからちゃんとマイグレーション生成してカラム追加してるうちにマイグレーションファイルが際限なく増えていくじゃないですか: 皆さんその辺ってどうやってます?意地でもCREATE TABLEを書き換えるか、マイグレーションでやり続けるか」「自分は、いったんマージされたマイグレーションファイルの書き換えはダメだと思っている派」「あーやっぱり」「featureブランチで作業している間とかならやってもいいけど、マージされたマイグレーションファイルはいじらない方がいいと思ってます😎

「それはproductionリリース直前でも?」「直前でも、です🧐: stagingとかもあるんだし、チームメンバーの手元にもそれぞれデータベースがあるし」「そうでしょうね〜」「だってgit pullしてdb:migrateしたときにマイグレーションエラー出たらテンション下がるじゃないですか🥶」「😆」「あとマイグレーションエラーを解決するために、手元の一時的なデータを消さないと巻き戻せなかったりするのもテンション下がる😥

「そういうのをやりたかったらRidgepole↓を使うべきですね」「えっそれ知らなかった😳」「Ridgepoleは確かクックパッドさんも使ってて、出来上がりのスキーマファイルしか扱わないようにするgemです」「マイグレーションを使わなくなるヤツですね」「Ridgepoleは、現在のデータベーススキーマと、Ridgepoleが把握している最終スキーマとの差分をチェックしてその差分を反映するので、どんな状態からでも最新のスキーマが得られる😋」「おぉお〜よさそう😃」「今質問したようなことをやりたかったらたぶんRidgepole使う方がいいですね🤔

参考: クックパッドにおける最近のActiveRecord運用事情 - クックパッド開発者ブログ

「でなかったら、過去のマイグレーションをスカッシュするgemも確かありますよ↓」「へぇ〜」「使ったことある!」「個人的にはどうかなぁ〜と思いますが😆

「マイグレーションファイルって増えれば増えるほどマイグレーションに時間かかりますし」「自分も個人プロジェクトで大規模なデータベースのマイグレーションが増えすぎて30分もかかるようになったことがあって、そういうときは仕方なく手動でALTER TABLEとかやって切り抜けました😅」「Railsでそのサイズになると最悪マイグレーションのせいで本編のサービスが止まります😇」「デプロイで止まるのヤダからそうやって切り抜けました😭」「マイグレーションファイルの中身を生SQLでALTERするなんてのもやったり: みんなこういうところでは悩んでます」「やはり〜」


「今日はデータベースの話がいっぱいできて嬉しいです: データベース好きだから😍」「😆

⚓minitest-mock_expectations 1.0.0をリリース(Ruby Weeklyより)

記事の中で、今年の夏にMochaをRails内部のテストから削除したという話が気になりました。


つっつきボイス:「あれ、このMochaってJavaScriptの?コネクションプールの話とか出てて変だな」「え😳」「どうやら上で外したと言ってるMochaはfreerange/mochaの方で、これはRubyのモック/スタブのライブラリですね」「ありゃー失礼しました💦」「どっちもテストに使うものだからややこしいですね☺

以下がJavaScriptのテストフレームワークの方のMochaでした🙇


mochajs.orgより

「ところで皆さんはRSpec派?minitest派?」「RSpecが多いかー」「minitestは玄人感ある😆」「ちなみに自分はRSpec使ってますが実質ほぼassertしか使ってないという😆」「😆」「いわゆるイコールマッチングで押し通す感じ😆」「たまにminitestでもいいかな?って思ったり☺

⚓その他Rails


⚓Ruby

⚓Gemoji: Rubyで絵文字を扱うライブラリ

Railsで使えるようです。

# 同リポジトリより
>> Emoji.find_by_alias("cat").raw
=> "🐱"  # Don't see a cat? That's U+1F431.

>> Emoji.find_by_unicode("\u{1f431}").name
=> "cat"

つっつきボイス:「Slackの絵文字補完機能みたいなものを作るときによさそうかなと思って」「げもじって濁音が多い響きが何というか😆」「★3000超えてて、しかも何年も前からやってるって初めて知りました☺」「確かにカスタマイズ絵文字ってたまに欲しくなるときあるし: これは★押しとくか😋

⚓RubyのReadline(Ruby Weeklyより)


つっつきボイス:「なるほど、GNU ReadlineをRubyで実装したものは前からありますね☺

参考: GNU Readline - Wikipedia

そういえば私がGobyREPL機能を実装したときはchzyerのGo言語向けreadline↓のお世話になりました。

⚓エラーになりにくいRubyコードを書くには(Ruby Weeklyより)


つっつきボイス:「Thoughtbotさんの記事です」「お、Thoughtbotの記事久しぶりに見る」

「Timecopをまとめて呼ぶ↓と期待どおりにならないことがあるという話とかかな」「後は三項演算子より普通にif文の方が読みやすいという話とか: 短い三項演算子なら別にいいと思いますけど」

# 同記事より
RSpec.describe "holiday promotions" do
+ after { Timecop.return }

  context "on Independence day" do
    before { Timecop.freeze Date.parse("2017-07-04") }
-   after { Timecop.return }

    it "displays the fireworks banner" do
      # ...
    end
  end

  context "on Thanksgiving" do
    before { Timecop.freeze Date.parse("2017-11-23") }
-   after { Timecop.return }

    it "displays the turkey banner" do
      # ...
    end
  end

  context "on Black Friday" do
    before { Timecop.freeze Date.parse("2017-11-24") }
-   after { Timecop.return }

    it "displays the TV banner" do
      # ...
    end
  end
end

参考: 演算子式 (Ruby 2.5.0) — 条件演算子

⚓Falcon: HTTP/2、HTTPS対応のRuby製高性能Webサーバー(Ruby Weeklyより)


同リポジトリより


つっつきボイス:「h2o↓はmrubyだけどこのFalconはRuby?」「のようです」「rackで動くようだ」


h2o.examp1e.netより

Priority Business Supportというのもやっているそうです。このsockertyはasync-何とかというgemをいろいろ出していて、Falconはその上で構築されているそうです。


socketry/asyncより

そしてそのasync gemはnio4rとtimersというこれまたsocketryのgemの上に構築されているそうです。徹底的にモジュール化するあたり、dry-rbを連想しました。


socketry/nio4rより

Ruby: Dry-rb gemシリーズのラインナップと概要

⚓Salus: セキュリティチェッカーをまとめて動かす(Ruby Weeklyより)


同リポジトリより


つっつきボイス:「BundlerAuditBrakemannpm searchPatternSearchをサポートしてるようです」「あーDockerになっててまとめて動かせるってことか」「今後ちゃんと更新されるかな😆」「いわゆるホワイトボックス系テストをひととおり回してくれると」「ホワイトボックスだからソースコードの中身をチェックするタイプ」

参考: みんな知ってるホワイトボックステスト、ブラックボックステスト。でもグレーボックステストとは…? | ハートランド・ザ・ワールド

⚓Ruby正規表現のパフォーマンスを測定してみた(RubyFlowより)


つっつきボイス:「つい自分の好みで選びました😅めちゃ長い記事なので後で追ってみます」「正規表現といえば☺

⚓RubyWorld Conference 2018開催: 10周年!


018.rubyworld-conf.orgより

この日の公開つっつき会はRuby World Conference 2018の初日でした。10周年おめでとうございます🎉🎂🎁!そして今年のRuby Prize受賞者はk0kubunさんでした。おめでとうございます🎉🍶🍺

⚓その他Ruby


つっつきボイス:「コードハイライトエンジン!そういえば皆さんはコードハイライトに何使ってます?自分はIDEAとかRubyMineですが」「Vimで色付けるときはSolarizedとか」「カラースキームですね: これはこれで好みが色々ありそう」「シンタックスハイライトには?」「VimのRubyモードとか」

「何となくですが、カラースキームって瞳の青い系の好みの色で作られている感じがして、そのせいなのか何なのか暗いんですよね」「あーそれある!」「瞳の黒いアジア系には暗すぎる感」「Vimのデフォルトの青もとっても暗い」「目を凝らさないとわからなかったり」

「それで思い出したんですけど、英語圏というか米国だとピンクは基本色らしいんですよ: 日本人の感覚だとピンクって追加の色って感じなんですけど」「へぇ〜」

「おっと時間がないのでここからは駆け足で🦵🏻


⚓クラウド/コンテナ/インフラ/Linux/Serverless

⚓Kinesis Data FirehoseとSplunk

Splunkはサードパーティでした。

参考: Amazon Kinesis (フルマネージド型リアルタイム大規模ストリーミング処理) | AWS
参考: Amazon Kinesis Data Firehose とは - Amazon Kinesis Data Firehose
参考: Amazon CloudWatch(AWS リソースのモニタリング)| AWS
参考: AWS ソリューション | Splunk


つっつきボイス:「よく知らない名前が出てきたので: firehouseかと思ったら馬のfirehoseでした」「Kinesis周りはまだ使う機会なかったな〜」

⚓AWS RDS for MySQLがMySQL 8.0をサポート


つっつきボイス:「お、RDSなら対応するでしょうね: もしAWS AuroraのMySQLが8.0になれば結構なニュースかも🤓」「Auroraはまだ5.65.7ですね」

参考: Amazon Aurora(MySQL、PostgreSQL 互換のリレーショナルデータベース)|AWS

※訂正(2018/11/06)

お知らせありがとうございました!🙇

⚓その他クラウド


つっつきボイス:「そういえばGCPのPythonってまだ2?」「あ、どっちだったけ?」「まだ3じゃなかった気がする」

と思ったら今年7月にPython 3をサポートしたそうです↓。私がGCPやってた頃はPythonの2と3の共存で割と苦しみました🥺

参考: GoogleAppEngineスタンダード環境のPython3サポートはもう少し話題になっていいと思う - Qiita



つっつきボイス:「よくあるDockerのレイヤー図は違うんじゃないかっていう記事です」「Dockerの図にコンテナエンジンの層を置いていいのかどうかってことか: うーん、Dockerのcgroupsがサンドボックス化されているのをレイヤと呼んでいいんだったらこの層があっても別にいいんじゃないかって気はするけどなー🤔」「OSのプロセスという視点なら確かにこっちの図になる: そりゃそうだ」「でもPIDの変換とかもやってるからそういう意味では概念上レイヤがあると考えてもいいんじゃないかな☺」「難しい😅」「自分は上の図で別にいいし」

参考: Docker内部で利用されているLinuxカーネルの機能 (namespace/cgroups) - Qiita

⚓SQL

⚓PostgreSQLでユーザーデータをanonymizeする(Postgres Weeklyより)


同記事より


つっつきボイス:「む、むむ、むむ?すげー、PostgreSQLってこんなことできるんだ!😍」「お、どの辺でしょう?」「ざっとしか見てないけど、Extensionを作ってそれを既存のカラムに適用してからMASKED WITH FUNCTIONで使えるっぽい!さすがぽすぐれ、何でもできる💪」「??🤔

記事が短いので全部は引用しません🙇

-- step 1: マスキングエンジンを有効にする
=# CREATE EXTENSION IF NOT EXISTS anon CASCADE;
=# SELECT anon.mask_init();

-- ...

-- step 3: マスキングルールを宣言
=# COMMENT ON COLUMN people.name IS 'MASKED WITH FUNCTION anon.random_last_name()';

=# COMMENT ON COLUMN people.phone IS 'MASKED WITH FUNCTION anon.partial(phone,2,$$******$$,2)';

「つまりデータベース内のデータは一切変更しなくても、SELECT文でこのMASKED WITH FUNCTIONを通すことでそのルールに従ってマスクできるということらしい」「おぉ~😳

「もしかすると、これを使ってproductionのデータベースをマスクすれば、たとえばproductionのデータを安全に開発側からアクセスするなんてこともできるかも: しかもfunctionだからパフォーマンスもそんなに落ちないはず」「やっとだんだんわかってきた気が😅」「ぽすぐれは常に新しい発見があるなっ🧐

「ついでに皆さんはPostgreSQLとMySQLどっちを使ってます」「どっちもかなー」「どっちも使う」「やっぱそうですよね☺」「Oracleは?😆」「元おらくらーの人が少なくとも約2名」「おらくらーでもないヨ😆」「業務だとOracleは不可避」「RailsだとOracleは1、2個ぐらいしか見たことないな〜」

この後PostgreSQLがらみで親睦会以後もずっと盛り上がり続けたのですが割愛いたします🙇

⚓PostgreSQL最大の過ち(Postgres Weeklyより)


つっつきボイス:「って何のことかと思ったら名前のことでした: post-GRES-que-ellって書いてあるから『ぐれ』のところを強く発音するってことみたいです」「へぇ〜😆」「雑学でした😆

⚓JavaScript

⚓JavaScript製AI・MLアプリ7つ


つっつきボイス:「JavaScriptはあまり関係ないかも?」「確かに〜」

⚓IronDB: ブラウザ向けのキーバリューストア(JavaScript Weeklyより)


同リポジトリより


つっつきボイス:「relentless?」「容赦ないとか無慈悲な、みたいです」「cookieとIndexedDBとLocalStorageとSessionStorageを冗長化に使ってキーバリューを保存すると言ってます」「IndexedDBともうひとつよく似たやつでえっと」「Web SQL Database: たしかSQLiteをベースにしてる」「それですっ: でそのWeb SQL Databaseを知ったと思ったらもうIndexedDBに移ってたことを今頃知りました💦

参考: IndexedDB - Web API インターフェイス | MDN
参考: HTML5 - Web SQLデータベース

「昼にタバコ吸いながら雑談してたときに、『ブラウザに欲しいのはSQLデータベースというより容量を気にしなくていいキーバリューストアじゃないかな』って話になったんですが、ちょうどこのIronDBを見つけたので」「LocalStorageは割と容量大きかった気はする」

↓こんなページを見つけました。

参考: LocalStorageの容量や動作を調べるページ

「まあこういう容量はいくら増やしても足りないって言われがちですよね😆: MS-DOSの640KBの壁じゃないけど、そこにリソースがある限りみんな使い切ろうとするし」「😆」「😆

参考: 640Kバイトの壁 ‐ 通信用語の基礎知識

関係ありませんが、IronDBの画像でつい少女鉄仮面を思い出してしまいました。

⚓CSS/HTML/フロントエンド/テスト

⚓gRPC-Webとは


grpc.ioより


つっつきボイス:「お、gRPC-Web💖」「よくわかんないんですけど、JSのAPIがブラウザにも生えてる感じなんでしょうか?」「gRPCはまさにRPCで、gRPC自体はHTTP/2でも動くし、あとgRPC独自のプロトコルでも動いたはずで、gRPC-Webは、それを直接叩けるAPIがブラウザにできたと理解してる」「おぉ〜」

参考: 遠隔手続き呼出し - Wikipedia — RPC

「gRPCのメリットは、RPCなんでデータのスキーマが作れること: そういえばJSON Schemaとかあるけど流行ってるかというと🤣」「🤣」「🤣」「そんなものもありましたねー☺」「JSONはスキーマの夢を見たけども…というか☺

「ちゃんと追わないとわかりませんが、gRPCはRPCなんで相当バルクかつ細かいやりとりにも耐えられる作りになってるんじゃないかと推測」「ふーむ」

「話逸れますけど、このPublickeyさんは的確な情報が多くていいなと思います😃」「POSTDさんも頑張ってる感」「お、今気づいたけどPublickeyって個人で運営してるんだ!😳」「マジで!?」「だとしたらPublickeyってスゴすぎる」

⚓言語よろずの間

⚓Python < Go < Rust


つっつきボイス:「もろ無双系の記事ですが、タイトルの不等号が小なり<なんだなと思って: 2ちゃんだと>が多い気がして」「Rustは低レイヤやってる人には人気高いですよね☺

⚓Ruiさんの「低レイヤを知りたい人のためのCコンパイラ作成入門」


つっつきボイス:「作り中みたいですが、はてブで1000超えてました」「すげぇ~」「この方はどうやって食べてるのか何となく不思議で」「たとえば研究職なんかだとサバティカルみたいな長期休暇を取ったり、博士号取るためにある程度休暇を取ってその費用も会社持ちなんてことが割とありますけどね🧐」「そういえばこの間も中間試験受けたとか言ってた気がする😃

参考: サバティカル - Wikipedia

「それで言うと自分が今もっと知りたいのはLLVMかな」

参考: The LLVM Compiler Infrastructure Project

⚓その他

⚓シストリックアレイアーキテクチャとは


つっつきボイス:「これはたまたまsystolic(=心臓の収縮期、最大血圧値など)っていう単語を見かけて検索して見つけました: アーキテクチャというよりアルゴリズムかなと思いつつ」「何となくだけどニューラルネットワークが双方向になってるような」「そんな雰囲気で、えらく自由に組み合わせられるようです」「このPDF、1992年か」「え😳そんなに古かった?!」「スキャン画像が既に古いですしね😆」「確かに網掛けの部分が真っ黒だったりするし」「昔はこういう図を手書きしてたみたいですし」

⚓警戒しよう


つっつきボイス:「普通の商売だったら注文増えてチャンス!って思うところなのに誰も思わないという😆」「自分たちも受託開発なんで今だと軽減税率の行方が気になるところ😆」「あと平成の次の年号も」「年号を表示しないといけないシステムがだいぶ減ってるからそれほどでもないと思うんですが、サマータイムや軽減税率はテーブル設計にかかわるので、ね」「そうでした💦」(以下延々)

⚓番外

⚓細胞サイズのナノロボットの量産

AIとかよりこっちの方がいろんな意味でクリティカルな気が。


つっつきボイス:「へー、量産できるようになったんだ☺」「これが暴走したときにディアクティベートできるんだろうかと思って」「この手のは無理かも🤣」「硫酸かけるとか」

参考: 大きさは細胞並み「ナノロボット」量産する新技法が発表される…MIT研究チーム | ROBOTEER


今回は以上です。つっつき会にご参加いただいた皆さま、社内の皆さま、遅くまでお疲れさまでした!🙇

バックナンバー(2018年度後半)

週刊Railsウォッチ20181029: 特集『肥大化したActiveRecordリファクタリング7つの方法』今ならどうなる?Redis 5のストリーム機能他

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

Postgres Weekly

postgres_weekly_banner

JavaScript Weekly

javascriptweekly_logo_captured


Rails: ジョブでループせずに個別のジョブを生成しよう(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

Rails: ジョブでループせずに個別のジョブを生成しよう(翻訳)

アプリの遅い処理や本質的でない処理は、できるだけ非同期ジョブに落とし込む方がアプリ全体のパフォーマンス向上に有効です。

以下のように書くのではなく

1つのジョブでオブジェクトのグループをイテレートし、繰り返しごとに何らかの処理を行う。

class DoABunchOfTranslationsJob < ApplicationJob
  def perform
    Text.find_each do |text|
      text.do_a_slow_translation
    end
  end
end

以下のように書く

最初に「キューに積む」ジョブを使って小規模な個別のジョブを多数作成し、個別のオブジェクトはそれらのジョブ内で処理する。

class DoABunchOfTranslationsJob < ApplicationJob
  Text.find_each do |text|
    DoASingleTranslationJob.perform_later(text)
  end
end

class DoASingleTranslationJob < ApplicationJob
  def perform_later(text) 
    text.do_a_slow_translation
  end
end

そうする理由

理想的には、ジョブをできる限り速やかに実行し、バックグラウンドのコンカレンシーを活用すべきです。

ジョブが失敗する理由はさまざまです。ジョブ自身の中でエラーがraiseされることもあれば、ジョブに関連する外部の環境(再起動や何らかのシステムエラーなど)でエラーになることもあります。

ジョブが長時間実行されると、ジョブの実行中に中断する可能性が高まります。そうしたタスクでは大量のメモリも使われるでしょう。

実行に時間のかかるジョブでエラーが発生すると、2つの問題が発生します。作業中のステートに不整合が生じたままになることと、そのジョブを最初からやり直さなければならなくなることです。つまり、ジョブが失敗すると、一部の処理がもう一度(おそらく2回以上)行われてしまうことになります。場合によっては、ジョブがいつまでたっても終了できなくなるかもしれません。

処理を分割して、繰り返しても大丈夫な小規模な複数の処理に分けることで、個別のジョブも全体の「タスク」もより柔軟になります。

こうすることで、コードの完了が早まるというメリットも得られます。すべての処理を順に実行するのではなく、細かな「チャンク」に分割することでコンカレンシー性が高まり、多数のジョブを同時に実行できるようになります。

そうしない理由があるとすれば

間接的な制御レベルが1つ増えます。最終的に、ジョブをキューに積むあらゆるタスクで個別のジョブを作成することになります。つまりコードがその分複雑になり、後で見返したときに混乱しやすくなるかもしれません。

短時間で終わるループに余分な複雑さを持ち込むのはオーバーキルになる可能性があります。

関連記事

Rails tips: あまり知られてない機能1: ActiveJobとActiveModel(翻訳)

RailsをHerokuで5.1.3から5.2.1にアップグレードしてheroku-18スタックに対応した

$
0
0

こんにちは、hachi8833です。オレオレRailsアプリを5.1.3から5.2.1に、Rubyを最新の2.5.3にアップグレードしました。

環境

  • macOS: Mojave
  • Homebrewでrbenvをインストール
    • ただしruby-buildは ~/.rbenv/plugins/ruby-buildgit cloneで取得している
  • rbenvでRuby環境を構築している
  • bundlerを使ってプロジェクトディレクトリのvendor/ディレクトリにgemを置いている
  • デプロイ先: Heroku(ステージングと本番)

1. Rubyのアップグレード

まずはRubyのアップグレードから。2.5.0を最新の2.5.3にアップグレードします。

cd ~/.rbenv/plugins/ruby-build
git pull
rbenv install 2.5.3

次はRailsのRubyを2.5.3にアップグレードします。

  • Gemfileを更新します。
# Gemfile
-ruby '2.5.0'
+ruby '2.5.3'
  • 以下を実行してvendorディレクトリのgemを更新します。
bundle install

bundle exec rails sで動作を確認します。オレオレアプリは動的なページが1つしかないのでテストを回す意味がほぼありませんが、皆さんは必ずテストを回してください。

Herokuのステージングにデプロイして動作を確認できました。

git push enno-hachi-stg master

Railsのアップグレード

Rails アップグレードガイドの更新情報をみっちり読んでおきます。オレオレアプリに影響するところはなさそうでした。

  • Gemfileを更新します。
# Gemfile
-gem 'rails', '5.1.3'
+gem 'rails', '5.2.1'

アップグレードガイドに従い、bundle exec rails app:updateを実行します。

何もなければこれでおしまいです。いつもはだいたいこんなものです。

参考: Rails アップグレードガイド | Rails ガイド

Railsアップグレードのトラブルシューティング

しかしこんな恥ずかしいほどちっぽけなRailsアプリですら、今回はエラーメッセージが表示されます。

$ bundle exec rails app:update
Fetching gem metadata from https://rubygems.org/..........
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies....
Bundler could not find compatible versions for gem "activemodel":
  In snapshot (Gemfile.lock):
    activemodel (= 5.1.3)

  In Gemfile:
    kaminari was resolved to 1.0.1, which depends on
      kaminari-activerecord (= 1.0.1) was resolved to 1.0.1, which depends on
        activerecord was resolved to 5.1.3, which depends on
          activemodel (= 5.1.3)

    rails (= 5.2.1) was resolved to 5.2.1, which depends on
      activemodel (= 5.2.1)

Running `bundle update` will rebuild your snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.

まずはkaminariを単独でアップデートします。bundle update kaminariを実行します。

しかしメッセージはまったく同じでした。

そこでちょっと考えてからbundle updateを試してみました。ただしgem名を指定しないbundle updateは諸刃の剣。アップデートしてはまずいgemも含め全部を一気にアップデートするので、逆に事態をこじらせてしまう可能性もあります(↓以下の記事を参照)。オレオレアプリだからできることなので、業務アプリではこんな雑なことは絶対しないようにしましょう。

ちょっと待った! Railsでgitリポジトリから除外すべきでないファイル:Gemfile.lockとdb/schema.rb

どきどきしながらbundle updateし、bundle exec rails sしてみると、上のメッセージは解消しました。

factory_girlをfactory_botに変更

今度は以下です。ご存知のとおりfactory_girlはfactory_botに名前が変わったのですが、オレオレアプリではまだfactory_girlのままでした。

DEPRECATION WARNING: The factory_girl gem is deprecated. Please upgrade to factory_bot. See https://github.com/thoughtbot/factory_bot/blob/v4.9.0/UPGRADE_FROM_FACTORY_GIRL.md for further instructions. (called from require at /Users/hachi8833/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/bundler-1.16.6/lib/bundler/runtime.rb:81)

factory_girlをfactory_botに変更して再度bundle updateします(使ってないのでいっそ外してもいいくらいですが)。

案の定factory_girlがちょっぴりspecに残っていたのでfactory_botに更新しました。

FactoryBot.define do
  factory :mypattern, class: Pattern do
    regex 'ありがとうございません'
    # ruby_block {}
    comment '名作'
    posi_sample 'ありがとうございません'
    nega_sample 'ありがとうございます'
    # hit_count
    display_name '感謝'
    comment_e 'Fuck you very much'
    memorandom '世界遺産にしたい'
  end
end

私の場合あまり意味はないのですが、変更/削除に伴いbundle exec rspecを実行するとそこそこエラーが出ました。spec_helper.rbも修正が必要でした。

# spec_helper.rb

...
  config.before :all do
    FactoryGirl.reload   # FactoryBotに変更する
  end

  config.include FactoryGirl::Syntax::Methods # FactoryBotに変更する

  # 以下のDatabaseCleaner関連は削除する

  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each, js: true) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end

  system('bundle exec rake db:seed')

  # my config
  config.include AuthHelper
end

bundle exec rspecすると今度は以下が出ました。factory_botの書式が変わったようです。このあたりは今度まとめて対応することにしました。

DEPRECATION WARNING: Static attributes will be removed in FactoryBot 5.0. Please use dynamic
attributes instead by wrapping the attribute value in a block:

【翻訳】factory_bot 4.11で非推奨になった静的属性(static attributes) - Qiita

不要なgemを削除

今度はrails_best_practicesが駄々をこねました。

/Users/hachi8833/deve/rails/enno/enno_master/vendor/bundle/ruby/2.5.0/gems/require_all-2.0.0/lib/require_all.rb:102:in `rescue in block in require_all': Could not require /Users/hachi8833/deve/rails/enno/enno_master/vendor/bundle/ruby/2.5.0/gems/rails_best_practices-1.18.1/lib/rails_best_practices/core/controllers.rb (uninitialized constant RailsBestPractices::Core::Klasses). Please require the necessary files (RequireAll::LoadError)

私の場合rails_best_practicesを直す意味がなかったので、この機会にgemを見直すことにしました。

  • 使ってないので削除
    • rails_best_practices
    • sdoc
    • derailed_benchmarks
    • yard
    • listen
  • Rails 5に同じ機能があるので削除
    • awesome_print
    • launchy
    • database_cleaner
    • capybara-screenshot

gem見直しの際は以下を参考にしました。

[Rails 5]実は不要なgem・使われなくなりつつあるgem(2017年版)

Webサーバーを修正

さらに、development/testのWebサーバーがPumaなのに、productionがunicornだということに今頃気づいたのでPumaに統一しました。環境ごとにWebサーバーを変える意味まったくなし、お恥ずかしい。

+gem 'puma'
...

group :production do
- gem 'unicorn'
  gem 'rails_12factor', '0.0.3' #Just for Heroku
  gem 'google-analytics-rails'
end

JavaScript/CSSの応急処置

Gemfileのgemを削除/更新してからbundle update後、bundle exec rails sすると起動しました。以下のwarningが出ました。

autoprefixer: /Users/hachi8833/deve/rails/enno/enno_master/app/assets/stylesheets/introjs.css:11:3: Gradient has outdated direction syntax. Replace `cover` to `farthest-corner`.

仰せのとおりにintrojs.cssのcoverfarthest-cornerに変更しました。サードパーティのファイルを直接変更するなど普通ならしませんが、を全然使ってないのでまたの機会に削除することにしました。

  background: -moz-radial-gradient(center,ellipse farthest-corner,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%);
  background: -webkit-gradient(radial,center center,0px,center center,100%,color-stop(0%,rgba(0,0,0,0.4)),color-stop(100%,rgba(0,0,0,0.9)));
  background: -webkit-radial-gradient(center,ellipse farthest-corner,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%);
  background: -o-radial-gradient(center,ellipse farthest-corner,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%);
  background: -ms-radial-gradient(center,ellipse farthest-corner,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%);
  background: radial-gradient(center,ellipse farthest-corner,rgba(0,0,0,0.4) 0,rgba(0,0,0,0.9) 100%);

再度bundle exec rails sでローカルで実行できるかどうか確認しました。

# bundle exec rails s
=> Booting Puma
=> Rails 5.2.1 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.12.0 (ruby 2.5.3-p105), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

これで一応めでたしです。

bootstrap-sassはまだsass-railsに依存している

しかしさっきupdate:appしたときに以下のメッセージが出ていました。

Ruby Sass is deprecated and will be unmaintained as of 26 March 2019.

* If you use Sass as a command-line tool, we recommend using Dart Sass, the new
  primary implementation: https://sass-lang.com/install

* If you use Sass as a plug-in for a Ruby web framework, we recommend using the
  sassc gem: https://github.com/sass/sassc-ruby#readme

* For more details, please refer to the Sass blog:
- gem 'sass-rails'
+ gem 'sassc'

しかし今度は以下のエラーです。

/Users/hachi8833/deve/rails/enno/enno_master/vendor/bundle/ruby/2.5.0/gems/bootstrap-sass-2.3.2.2/lib/bootstrap-sass.rb:14:in `require': cannot load such file -- sass-rails (LoadError)
enno_masterdevelopement8⬆1✔1✎ERROR$

bootstrap-sassのバージョンロックがいけないのかと思い、外してbundle updateしました。

-gem 'bootstrap-sass', '2.3.2.2'
-gem 'bootstrap-sass'

しかしうまくいきません。そもそもbootstrap-sassのgemspecにはsass-railsが指定されているので無理もありません。

bootstrap-sassはBootstrap 3向けですが、後発のBootstrap 4向け公式gemであるbootstrap-rubygemのgemspecをチェックすると、ちゃんとsasscが使われていました。使うならやはりこっちですね。

Bootstrap 4へのアップグレードはまたの機会とすることにします。

Herokuでのトラブル

しかしHerokuでstagingにデプロイすると、思いきりアプリケーションエラーになりました。さっきRubyのみをアップデートした時点では起きなかったのに…

どうやら以下のデプロイメッセージが原因のようです。

remote:  !   Warning: You are running on a deprecated stack.
remote:  !   Please upgrade to the latest stack by following the instructions on:
remote:  !   https://devcenter.heroku.com/articles/upgrading-to-the-latest-stack

Upgrading to the Latest Stack | Heroku Dev Centerを参照すると、以下の実行が必要なようです。

$ heroku stack:set heroku-18 -a <app name>

heroku-18って何?と思ったら以下の記事にありました。今は16ではなく18なんですね。

参考: Herokuの新しいスタック「Heroku-16」で何が変わったか? (2017年5月8日よりデフォルトに) - Qiita

$ heroku stack:set heroku-16 -a enno-hachi-stg

なお、Herokuのスタックはデプロイするまでは前のスタックが使われ続けます。

Webサーバーの変更: 続き

ここでherokuのダッシュボードを見ると、web
bundle exec unicorn -p $PORT -c ./config/unicorn.rb
となっているじゃありませんか。これをPumaに変えないとあかんようです。

Heroku側で変えるのかと思いきや、Railsプロジェクト直下のProcfileを書き換える必要がありました。

- web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb
+ web: bundle exec puma -C config/puma.rb

これでDynoの起動コマンドも更新されました。

これで無事stagingで動きました!

後は本番デプロイです。以下を忘れず実行してスタックを更新してから、git push heroku masterを実行します。

$ heroku stack:set heroku-16 -a enno-hachi

本番デプロイも成功しました!

参考: Deploying Rails Applications with the Puma Web Server | Heroku Dev Center

関連記事

Ruby 2.5.2→2.5.3/2.4.5/2.3.8リリース(脆弱性修正)

ちょっと待った! Railsでgitリポジトリから除外すべきでないファイル:Gemfile.lockとdb/schema.rb

[Rails 5]実は不要なgem・使われなくなりつつあるgem(2017年版)

週刊Railsウォッチ(20181112)Ruby 2.6.0-preview3リリース、非同期スレッドのテストはつらい、MySQL 8のGROUP BYほか

$
0
0

こんにちは、hachi8833です。そろそろ身が持たないのでウォッチのボリューム削減を図っています。

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

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

⚓.freezeをごっそり削除

Rails 6ではRuby 2.4.1以降がサポート対象になるので、従来のマジックコメントをRubocopStyle/FrozenStringLiteralCommentに一元化するそうです。

# .rubocop.yml#L133
+Style/RedundantFreeze:
+ Enabled: true
+ Exclude:

つっつきボイス:「RailsでサポートされるRubyもいよいよ2.4.1以降か〜: この処置は納得、つかrubocop -aで自動的に.freezeを消せたと思うから作業はラク😋

rubocop -aでスキッと消えました。

⚓マルチDB系改良: 接続切り替え用基本API


つっつきボイス:「これはこの間のつっつきでもチェックした気がしてきた」「connects_toとか既視感ありますね👁: プルリクの番号は違いますが」「前回はサンプルコードがなかったからその辺もカバーしたんでしょうね」

プルリクを雑に訳してみました。

1) モデルで複数データベースに接続できるconnects_toメソッドを追加:

class AnimalsModel < ApplicationRecord
  self.abstract_class = true
  connects_to database: { writing: :animals_primary, reading: :animals_replica }
end

class Dog < AnimalsModel
  # 書き込み用のanimals_primary、読み出し用のanimals_replicaという2つのDBに接続される
end

2) connected_toブロックメソッドを追加: 接続のロール切り替えや、モデルで接続していなかったDBへの接続に用いる。そのブロック内でデータベースに接続できると、slow_replicaなど普段は接続したくないが、コンソールや特定のコードブロックで接続したい場合に便利。

ActiveRecord::Base.connected_to(role: :reading) do
  Dog.first  # AnimalsBaseに接続されたreplicaでdogをfind
  Book.first # readingで接続していないためエラーをraise
end
ActiveRecord::Base.connected_to(database: :slow_replica) do
  SlowReplicaModel.first # DB設定にslow_replica設定があれば探索し、なければエラーをraise
end

⚓MySQL 8.0.13のデフォルト式などに対応

MySQL 8.0.13については「SQL」にもエントリを置きました。


つっつきボイス:「MySQL、マイナーバージョンアップにしてはいろいろ追加されてるみたいですね」「MySQLは結構いろいろアップデートされますよ🤓」「何がアップデートされたのかな👀

「ほうほう、まずdefault function/expressionはCREATE TABLE t2 (a BINARY(16) DEFAULT uuid_to_bin(uuid()));みたいにデフォルト値に式や関数を置けるようになったと: むしろ今までできなかったのが驚き😳」「へぇ〜!」「ぽすぐれでは普通にできる😋

「functional indexは今までもMySQLでできた気がするけど、お、”not a column”ということは、カラムを作らなくてもfunctional indexできるようになったってことか!🤓: つまり今まではカラムを作らないとできなかったと」「そうでしたか!」「MySQLもついにという感じ🥳」「kamipoさんが速攻このプルリクを出したということは待ち望んでいたのかもしれないですね」「これはそれなりのRDBMSが備えている機能ですね」

⚓Railsガイドにデバッグ用詳細ログ出力方法を追加


同PRより


つっつきボイス:「今までRailsガイドに記載されてなかったのかー: Railsのログ周りを見ればわかるんだろうけど」「ActiveRecord::Base.verbose_query_logs = trueでアクティベートできるみたい」「お、マルチプルデータベース対応の一環っぽい」「そっか、マルチになったら欲しいヤツですね」

これも雑に訳してみました。

ログのデータベースクエリでは、1つのメソッドが呼ばれたときに複数のデータベースクエリがトリガされる理由がすぐにわからないことがある。
rails consoleセッションでActiveRecord::Base.verbose_query_logs = trueを実行して詳細クエリログをオンにしてメソッドを再度実行すると、どの1行の呼び出しですべての個別のデータベース呼び出しが生成されるかがよくわかるようになる。

irb(main):003:0> Article.pamplemousse
  Article Load (0.2ms)  SELECT "articles".* FROM "articles"
  ↳ app/models/article.rb:5
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
  ↳ app/models/article.rb:6
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 2]]
  ↳ app/models/article.rb:6
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 3]]
  ↳ app/models/article.rb:6
=> #<Comment id: 2, author: "1", body: "Well, actually...", article_id: 1, created_at: "2018-10-19 00:56:10", updated_at: "2018-10-19 00:56:10">

あるデータベース呼び出しを行うメソッドがある特定のソースファイル名と行番号が、データベースステートメントごとにで示されている。N+1クエリ(1つのデータベースクエリが多数の追加クエリを生成する)によるパフォーマンス低下の原因特定や修正に有用。

⚓ドキュメント: I18nのdeep_interpolation:オプション

訳文ハッシュをバルクで式展開したい場合、deep_interpolation: trueを渡す必要がある。以下の辞書があるとする。

en:
  welcome:
    title: "Welcome!"
    content: "Welcome to the %{app_name}"

これを指定しないと、ネストした式展開が無視される。

I18n.t 'welcome', app_name: 'book store'
# => {:title=>"Welcome!", :content=>"Welcome to the %{app_name}"}

I18n.t 'welcome', deep_interpolation: true, app_name: 'book store'
# => {:title=>"Welcome!", :content=>"Welcome to the book store"}

つっつきボイス:「これもドキュメント追加ですね」「deep_interpolation…こんなのあったのかー: yamlで式展開する話は前のウォッチでも扱った気がする」

後で探すと、Railsアプリの基本的なセキュリティの注意の項で話題にしていました。

  • yamlの式展開はそのままでは出力されない(html_safeで無理やり出すのはよくない)
  • yamlのキー名末尾に_htmlを付けると自動でエスケープしてくれる(Railsガイド↓にもある推奨の方法)
  • _htmlによるエスケープはビューヘルパーのttranslate)メソッドでのみ利用可能

参考: 4.4 安全なHTML変換 — Rails国際化 (I18n) API | Rails ガイド

⚓ActiveStorage::Downloaderのオートロードの問題を修正

# activesupport/lib/active_support/dependencies.rb#L257
      def load_dependency(file)
        if Dependencies.load? && Dependencies.constant_watch_stack.watching?
-         Dependencies.new_constants_in(Object) { yield }
+         descs = Dependencies.constant_watch_stack.watching.flatten.uniq
+          Dependencies.new_constants_in(*descs) { yield }
+       else
          yield
        end
       rescue Exception => exception  # errors from loading file
         exception.blame_file! file if exception.respond_to? :blame_file!
         raise
       end

つっつきボイス:「もしかすると先週のウォッチでちらっと取り上げたActive Storageが特定の状況でNoMethodErrorになった問題と関係あるのかな?」「番号は違っていますけどちょっと似た雰囲気ですね🤔

⚓その他

以前のウォッチで既におおよそ取り上げていますが。

⚓Rails

⚓Rubocopによる自動修正の注意点(Ruby Weeklyより)


つっつきボイス:「たまたまRubocopの話題が続きました」「Rubocopにこういうオプションがあるのね↓」「安全でないcopがあるという話を追ってるようです」

# 同記事より
# 安全なcopだけを実行
$ rubocop --safe

# 安全なオートコレクトだけを実行
$ rubocop --safe-auto-correct

# 安全なcopだけを実行してから安全なオートコレクトだけを実行
$ rubocop --safe --safe-auto-correct

「ちょうど記事にもこの設定にSafe: falseとかSafeAutoCorrect: false↓がある: 詳しくは記事に書いてあると思うけど、確かに書き換え系のcopだとオートコレクト後に意味が変わってしまう可能性がある場合も考えられるので、その辺を見てくれる設定なのかもしれない」

# まったく安全でないcop
Style/CollectionMethods:
  Description: 'Preferred collection methods.'
  StyleGuide: '#map-find-select-reduce-size'
  Enabled: false
  VersionAdded: 0.9
  VersionChanged: 0.27
  Safe: false

# 信頼できるwarningを出すcopだが、オートコレクトは安全ではない
Style/SpecialGlobalVars:
  Description: 'Avoid Perl-style global variables.'
  StyleGuide: '#no-cryptic-perlisms'
  Enabled: true
  VersionAdded: 0.13
  VersionChanged: 0.36
  SafeAutoCorrect: false

「安全なcopは何も考えずにオートコレクトできるけど、安全でないcopもあるよってことなんでしょうね: Rubocopのドキュメントをざざっと見てみるとConfigurable attributesにSafeModeあるヤツがいくつか見つかった↓」「後で見てみようっと」「ちゃんとメンテされているならこういうオプションを活用できますね😋


rubocop.readthedocs.ioより

ActiveRecordとの互換性: ActiveRecordにはdetectメソッドは実装されておらず、findには独自の意味があるため、ActiveRecordのメソッドにこのcopを適用するのは安全でないと考えるべき。
同サイトより大意


同リポジトリより

⚓RocketJob: Ruby/Rails向けバッチジョブ管理(Ruby Weeklyより)


同サイトより


つっつきボイス:「ジョブ管理のGUIが使えるgemのようです」「まだ★は少ないかなー?」「新しいからかもですね」「おー、こんな感じのUI↓」


同サイトより

「そういえばSidekiqのマウンタブルエンジンの管理コンソールって、使い勝手の面でもうひと頑張りして欲しいなって思うときあるんですよね…RocketJobは後発の分イケてそうかな🤔」「このgem単体で立ち上げられそう」「でしょうね: たぶん共通のジョブAPIを叩いてるだけだと思うので」


sidekiq.orgより

cc by sa

⚓Phusion Passengerのドキュメントサイトが公開(Ruby Weeklyより)


phusionpassenger.comより


つっつきボイス:「トップページの動画が気合入ってます」「Passengerはいろいろ頑張ってますね」

⚓autoload_reloader: Shopifyによるオートロードの別実装(Ruby Weeklyより)

# 同リポジトリより
require 'autoload_reloader'

File.write "foo.rb", "Foo = 1"

# ファイルシステムをスキャンしてオートロードをセットアップ
AutoloadReloader::Paths.push(Dir.pwd)

# オートロードされた定数を使って開始
Foo # => 1

File.write "foo.rb", "Foo = 2"

# 定数をアンロードしてパスを再度スキャン
AutoloadReloader.reload
Foo # => 2

# パスにあるオートロード可能な定数をすべて読み込む
AutoloadReloader.eager_load

つっつきボイス:「オートロードリローダーってloadが2回も出てくる😆」「なるほど、const_missingを使わずにやれるのね: これはこれで挙動が変わりそうではあるけどconst_missingよりは、ね🤓

「読み込み順で言うことを聞かせたいときとかに使うんでしょうか?🤔」「というよりreloadできるのがいいんでしょうね: const_missingで読み込まれると、きれいにリロードしようと思ったら普通はプロセスを立ち上げ直すしかないんですが、プロセスを落とさずに再スキャンしてリロードできるというのがうれしいポイントなんだと思う🧐」「あーなるほど!」「Railsもそうやってプロセスを止めずにリロードできるんでしょうね: カジュアルに再読み込みしたいRailsアプリなんかにはいいのかも」

リポジトリのREADME#How it worksによると、Railsでは利用できないとガイドに書かれているModule#autoloadを使ってやれるようにしたそうです。

参考: autoload — class Module - Documentation for Ruby 2.5.0

⚓その他Rails


つっつきボイス:「割と普通の記事っぽいです」「GraphQLにしたときのパフォーマンスも気になるけど、主導権がユーザー側に来るのがちょっと怖い気はするなー」


つっつきボイス:「今日昼の社内勉強会でちょうどOctopusの話が出たので見てみたらREADMEにそう書いてました」「昔はシャーディングといえばOctopusだったんですが、最近Octopusの話題を聞かなくなってきているからメンテモードになったのもわかる気がする」

参考: レプリケーションとシャーディング、MySQLでレプリケーションの張り方 - WebエンジニアのLoL日記

ハンズオン: PostgreSQLシャーディング(翻訳)

⚓Ruby

⚓Ruby 2.6.0-preview3リリース(Ruby公式ニュースより)

ひとまず記念写真。


つっつきボイス:「早っ、もう2.6.0が近いのかー」「詳しくは上の公式ページにひととおり載っていました」「JITがまた一段と速くなっていそう🚀

k0kubunさんがpreview3のパフォーマンスについてメモしていることをRubyWeeklyで知りました。

# 同Gistより
Comparison:
             Optcarrot Lan_Master.nes
      2.6.0-preview3+JIT:        86.6 fps
      2.6.0-preview2+JIT:        73.9 fps - 1.17x  slower
      2.6.0-preview1+JIT:        59.2 fps - 1.46x  slower
          2.6.0-preview3:        54.6 fps - 1.59x  slower
          2.6.0-preview2:        53.3 fps - 1.62x  slower
          2.6.0-preview1:        53.0 fps - 1.63x  slower
                   2.5.3:        48.5 fps - 1.78x  slower
                   2.0.0:        34.6 fps - 2.50x  slower

⚓Bundler 2.0のその後


つっつきボイス:「あーBundler 2.0ってその後どうなってるのかな?🤔」「現在のBundler 2.0はリポジトリにstableがあるようですが、まだ正式ではないっぽいですね」「そりゃBundler 2.0にはbreaking changesが入るからそう簡単には2.0には移行できないでしょうね」「あ、そうか」

「記事をよく見ると今日新しいBundler 2.0.0をリリースするとある: breaking changesは以下だけにとどめるみたいです」

  • Bundler 1.17から2.0の全breaking changes:
    • Ruby 1.8.7〜2.2のサポート終了
    • RubyGems 1.3.6〜2.5のサポート終了
    • エラーがSTDOUTではなくSTDERRに出力される

「今の段階でgem install bundlerで2.0が入ったら下手するとDockerfileとかも含めて軒並み動かなくなるかもしれないし👹」「くわっ😵」「BundlerってあらゆるRubyアプリで使われるから、Bundlerがもしいきなり消えたりしたらすげー困る😰

なお、記事執筆時点ではgem install bundlerでインストールされるのは1.17.1です。2.0-stableブランチの最新コミット2.0.0.pre.1となっています。

参考: Bundler 2.0.0.devを使ってみる - koicの日記

⚓Rubyの「サーキットブレーカー」とは(Hacklinesより)


同記事より

CircuitBreakerはMartin Fowler先生のパターンだそうです↓。

参考: CircuitBreaker


martinfowler.comより


つっつきボイス:「日本語の手頃な記事↓があったので見てみよう: clientとsupplierの間にCircuitBreakerオブジェクトを配置して障害モニタリングすると: ↑おーこの図が欲しかった😍

NetflixのHystrixにも使われるCircuit Breaker patternを調べてみた – ~ my tech diary ~

「CircuitBreakerがエラーハンドリングをやってくれることでエラーを握りつぶされなくなるのがメリット🧐: CircuitBreakerが死ぬと全体が死ぬことになりますが」「元記事にはcircuitboxというgemが紹介されてました↓」「見た感じ、双方向にプロキシする、とても薄い層っぽい」「シンプルだけど有効そう」「エラーハンドリングやログフォーマットを統一するのによさそうですね😋

# 同リポジトリより
class ExampleServiceClient
  def circuit
    Circuitbox.circuit(:yammer, exceptions: [Zephyr::FailedRequest])
  end

  def http_get
    circuit.run do
      Zephyr.new("http://example.com").get(200, 1000, "/api/messages")
    end
  end
end

「こういうのをインフラでハンドリングするなら、たとえばFluentdあたりで取ってそこからイベントを出すみたいな方法も考えられますが、メッセージ中のエラーなんかはアプリ側でハンドリングしたいことがあるから、こういうCircuitBreakerパターンになるでしょうね🤓


fluentd.orgより

⚓Michael Hartlインタビュー(Hacklinesより)


rubytestingpodcast.comより


つっつきボイス:「Michael HartlはおなじみRailsチュートリアルの原著者ですね」


railstutorial.jpより

「このPodcastなんですけど、その名も『Ruby Testing Podcast』というそれ専門みたいです」「へぇ〜!」「まだ聞いてませんが、Michale HartlさんはRailsチュートリアルの版を一新したときにRSpecを捨ててminitestに切り替えてたから、そのあたりの話をしてるんじゃないかなと思って」「push-upに続けて何故シュワルツネッガーの話まで😆

Ruby Testing Podcastのバックナンバーを見ると2018年5月から凄い勢いで出してますね。

後で調べたらpush-upは「腕立て伏せ」でした。

デスクワーカーは今のうちに筋力と体力をつけよう

⚓Rubyの非同期スレッドをテストするには(Awesome Rubyより)


つっつきボイス:「非同期スレッドのテストは、ダルいぞつらいぞ〜😭」「そもそも完全にテストできるのかというのもあるし、時間もかかるし」「どのタイミングでどの処理を実行するとすべてのrace conditionを網羅できるか、というのをやり始めると一瞬で指数関数的に組み合わせが増大するので📈」「あー😅」「なのでマルチスレッドのテストはもうそれだけでつらい🤯」「そしてそれをテストで実装しようとするとなおつらい🤯」「再現性が、ね😅

参考: 組合せ爆発 - Wikipedia

「しかも単純にsleepかけてもダメ」「そもそもsleepかけた瞬間にテストが激遅になるし🐢」「人間がこのタイミングとこのタイミングみたいな感じでsleepをかけておりゃぁ!と調べるのはまだやれなくもないんですが」「それでも『完全に同時』みたいなタイミングの制御は難しいし😩

「そう思うと、RailsのActionCableあたりで『何でこんな書き方になるの?』みたいなテストを見かけたりするのはたぶんそれ」「もう本質的に難しいんですね…😨」「そうなんですよー」「結局、下のレイヤの特定の実行タイミングにかかわるようなテストって書きようがないので」

「そして非同期処理にはエントリポイントというものがないから!😤」「そうっ😤: エントリポイントがあればそこで待ち構えられるけど、ないからブレークポイントで止めてえいやっと調べるしかない」「本当にそうするしかないんですよ👽

「そしてそこまでやっても、網羅できてる保証はないという😭」「人力で止めてやってると客観性も再現性もないし」「やっぱり非同期スレッドのテストはどう転んでもつらい😇

「ここでこういうタイミングでこれを実行するとバグが発生する、みたいなのはまだやれる」「そうそう😆: このぐらいの負荷で30分ぐらい回すとだいたい何件のエラーが起きるとか😆」「0.000何パーセントの割合でコケるみたいなの」

「そういうポイントをたまたま見つけられるとうれしい気持ちになる😊」「でもそれで本当に修正されたかどうかはわからないという🙃」「そういうときは論理を駆使して絞り込むとか」「確固たる原因を突き止めるのが半端なく大変」

⚓その他Ruby



つっつきボイス:「igaigaさんの本はこれまでいろいろやってきたリソースの蓄積をうまくまとめてそう」「かんたんRubyというタイトルからの連想ですが、趣味で簡単なコードを書くのと仕事でちゃんとしたコードを書くときの差が大きいんですよね結局☺」「結局そう」「動きさえすればいいコードを教えるならいろんな方法で本を書けそうだけど、クォリティを問題にし始めると途端に書き方をきちっと合わせなければならなくなるので😆」「よく入門書なんかではattr_accessorで書けばいいよ❤って教えてるけど、現場に来ると『違うでしょっ😤』って言われちゃったりするんで、そういう本を書くのは難しい…」

⚓Ruby trunkより

⚓RefinementがForwardableで使えない->想定どおり

require "forwardable"

module URIExtensions
  refine URI::Generic do
    def authority
      port_string = port == default_port ? nil : ":#{port}"
      "#{host}#{port_string}"
    end

    def origin
      "#{scheme}://#{authority}"
    end
  end
end

using URIExtensions

class Container
  extend Forwardable
  def_delegator :@uri, :origin
  def_delegator :@uri, :authority

  def initialize(uri)
    @uri = uri
  end
end


u = URI("https://google.com")
container = Container.new(u)
puts container.origin
puts container.authority

つっつきボイス:「お、Forwardableですよ」「デリゲートするためのモジュール」「RefinementしたらForwardableが届かなくなったと思ったら『Refinementはレキシカル(静的)にメソッドを上書きするので想定どおり』という回答でした」「仕様どおり😆」「usingでRefinementした後extend Forwardabledef_delegatorは効かなくなると」「よぐわがんね😅」「こういうコードって普段書かないな〜」

参考: module Forwardable (Ruby 2.5.0)
参考: 静的スコープ - Wikipedia

「Refinementってproductionコードで使ったことあります?」「developmentですら1回も書いたことないです🤓」「でしょうね〜: あるとすればモンキーパッチを当てるときとか」「業務のコードでモンキーパッチを当てないといけなくなったとしたら、仕様を『それは利用できません』の方に倒す方が確実な気がする😆」「でもたまに当てないといけなくなるんですよね😆」「幸いにして今までそういう事態になったことはない☺」「かなり昔に、RFCに準拠していないサーバーに接続するために、RailsのRFCに準拠している部分にモンキーパッチを当てたことならあります🐵」「😆」「😆

「パッチ当てるときにRefinementを使うかどうかという選択肢はありますね」「他でも使われている可能性があるものだったらRefinementを使う方が安全でしょうね🧐

RubyのRefinement(翻訳: 公式ドキュメントより)

後で動かしてみたら確かにエラーになりました。

puts container.origin
#=> NoMethodError: undefined method `origin' for #<URI::HTTPS https://google.com>
from /Users/hachi8833/.rbenv/versions/2.5.3/lib/ruby/2.5.0/forwardable.rb:229:in `origin'

⚓クラウド/コンテナ/インフラ/Linux/Serverless

⚓VMWareがHeptioを買収


vmware.comより


つっつきボイス:「VMWareもくばねてに手を伸ばしたんだなと思って」「よく知らないけど前からやってるんじゃないかな?🤔

その後以下の記事も出ました。

⚓その他クラウド


つっつきボイス:「もしかしてカレンダー予約かな〜と思ったらそっちではない方向でした」「お、GoogleのはAWS Batchとは違って本当にスケジューラなんですね😋

参考: AWS Batch – 簡単に使えて効率的なバッチコンピューティング機能 – AWS

「こういうスケジューラ、AWSで欲しいんですよね」「ないんですか?」「AWSには所定の時刻にバッチを実行するシンプルなサービスは単品ではなかったと思う」「へぇ〜😳」「AWS BatchはSNS↓トリガーで動かすことはできますが、そのSNSを別の何かでスケジューリングするとかになるんじゃないかな🤔」「やりようはあると思うけど、cron程度のものはあってもいいよねみたいな」

参考: Amazon Simple Notification Service(プッシュ方式のメッセージ送信) | AWS

AWS Instance SchedulerはEC2/RDSインスタンス向けのスケジューラなので別物ですね。

参考: AWS Instance Scheduler の紹介

「ふと思ったんですが、クラウド運営側からするとcron的な単体サービスってあまり置きたくないものなんでしょうか?」「ふむむ、置きたくないとまではいかなくても、インフラとして設置しようとすると時刻同期の問題とかがあるから面倒というのはあるかもしれないですね」「おー」「バッチの実行時刻をきっちり同期して保証するのって地味にダルいので😟

「たとえばですが、インスタンスが落ちているときにcronの時刻が来てしまったらどうするか、なんてことを考えたりすると、インスタンスの終了に要する期間のスケジュールも見越した上でやらないとサービスとして不完全になってしまうので」「ははぁ」「コンテナとかAWS Lambdaみたいに作り捨てるようなものならcronでサービスしやすいと思いますが、特定のEC2インスタンスでcronジョブを実行するみたいなのは、エラー通知とかもしっかり設計しないといけなさそう」

「いわゆるtrustedなサービスや、呼ばれてから動き出すようなサービスぐらいならcronでも問題なくスケジューリングできそうですが、untrustedなサービスが動いていなかったときの挙動をサポートするのは地味にダルい」「ふむふむ」「ちゃんとやるとなったら、ジョブをキューに入れて、SNSとSQLSQSを間に挟んで…と結局大げさになってしまう」「なるほど!😃

「まあAWSもそのうちこういうスケジューリングサービスやるんじゃないかなという気はしています: 最近のAWSは既存のサービスをCloud Formationのテンプレートで組み合わせたものに名前を付けて新しいサービスにしてたりしますし😆、スケジューリングサービスは既存の組み合わせで実現できるので」「😆

参考: AWS CloudFormation (設定管理とオーケストレーション) | AWS

「Googleのスケジューラは、単体で使うよりもたとえばGoogleカレンダーあたりと組み合わせるといいかもしれないっすね: App Engineで使えるってあるし」「それはありかも」「ちょろっと何かやるにはよさそう: retryループとか発生しそうだけど🤣」「🤣

参考: App Engine - 任意の言語でスケーラブルなウェブ バックエンドやモバイル バックエンドを構築  |  Google Cloud

⚓SQL

⚓MySQL 8.0.13リリース


mysql.comより

以下の資料↓もよさそうです。


つっつきボイス:「さっきの先週の改修でも取り上げましたが一応」「Upgrade Checker↓なんてものもあるのか」

参考: MySQL Shell 8.0.4: Introducing “Upgrade checker” utility | MySQL Server Blog

GROUP BYのソートがなくなるんでしたっけ」「マジで?!😱」「Twitterだかはてブだかで、日本のMySQL強者の誰かがそういうことを言ってたのを見た覚えがあります: GROUP BYにORDER BYかけないヤツはまさかいないよな、MySQL 8からは順序保証されないよ、みたいな感じで」「あ〜それ見たことある気がする」

探しましたがうまく見つけられませんでした🙇

「MySQLは昔からそういうところある😆」「そういうところって😆」「SQLの仕様ではGROUP BYの順序は保証されないんですが、MySQLはたまたま順序が保証されてたという😆」「じゃそれを当てにしてるコードは…😰」「MySQLのオーダリング系は結構そういうのがあったりするし」「そのGROUP BYがこのたびちゃんとするようになったと」「正しい」「よくわかってないですが😅

「たしかにクラスタリングとかやり始めると暗黙のオーダリングはどうしてもパフォーマンスが落ちてしまう」「RailsのActiveRecordのgroupだけ叩いたときの挙動がどう変わるのかが気になるところです☺」「どうだろう?🤔シンタックスが間違ってたわけじゃないから、単に今までどおり順序保証のないGROUP BYするんじゃないかな: ぽすぐれでも通らないと困るし」「そうですね〜」

参考: group — ActiveRecord::QueryMethods

「それにしてもMySQLの今までの暗黙の順序保証って、どういう順序を保証してたんでしょうね😅」「😆」「もしMySQLのストレージエンジンに依存するような順序保証だとしたら、今までの暗黙の順序保証もそもそも当てにすべきじゃなかったろうし😆」「MySQLの順序保証がなくなった後、ぽすぐれみたいに割とクエリの結果が違うようになったら苦情出そうで、それはそれで困る😭: ぽすぐれはUPDATEとかDELETEとかかけると割と順序が変わるので」「やっぱりORDER BY付けろと」「ORDER BYしないといけないときはORDER BYすべき🧐

参考: MySQLの「InnoDB」と「MyISAM」についての易しめな違い - (2015年までの)odaillyjp blog

⚓JavaScript

⚓bootstrap.native: Bootstrap 4でjQueryを使いたくない人に


thednp.github.ioより


つっつきボイス:「今日の社内勉強会で言及されてたので」「そうそう、これね☺」「この手のものではこれがメジャーっぽいですね」「こういうののVue.js版を作ってる人もいたと思う」「へぇ〜😳」「jQueryじゃないBootstrap JSを作っている人々」

これ↓のようです。★6000超えです。


同サイトより

「追従するの大変そう…😅」「今度オレオレRailsアプリでbootstrap.native試してみます😍」「BootstrapのJSってそんなにヤバいことはしてないと思う: まあモーダルのアニメーションなんかは地味に面倒ですが☺」「モーダルアニメーションをjQueryとまったく同じにしてブラウザ互換を確保するとか、たしかに面倒くさそう😇

⚓Node.jsのSocket.IOとは


socket.ioより

★44000超え!

参考: Node.jsからSocket.IOを使うための事前知識 - Qiita


つっつきボイス:「socket.ioはJavaScript界隈では普及してるようですが、自分が単純に知らなかったので😅」「Nodeでソケット開くときに使うヤツですよね😎」「WebSocket用かと思ったら、WebSocketに限らないみたい」「まあNodeでWebSocketやろうとすると、このsocket.ioがめちゃめちゃ使われてるのは確かかも😋

参考: ソケットプログラミング

「socket.ioの使い方↓を見ると、メッセージ指向っぽい気もするけど、いわゆるNodeサーバーっぽい書き方っすね」「おー」「普通のソケットサーバーというか、任意ソケットっぽい」「socket.ioのREADMEにも『socket.ioはWebSocketの実装じゃないよ』って書いてあるし」「ホントだ」「JSというかNodeでこんなの書けるんだ〜」「socket.ioはかなり前からあるはずで、Node.jsでサーバー作るときによく使われる、OS系にアクセスするためのライブラリのひとつですね☺」「本当の生ソケットを開きたいときとか」「Node.jsでプロキシ書くときとかも使うでしょうね」

// 同リポジトリより
const server = require('http').createServer();
const io = require('socket.io')(server);
io.on('connection', client => {
  client.on('event', data => { /* … */ });
  client.on('disconnect', () => { /* … */ });
});
server.listen(3000);

「全部Node.jsで書けちゃうとは、すげ〜💪」「結構前からそうですけどね☺」「例のIsomorphicなんてのはもろにNode.jsから始まったヤツだし」「そうそう」「JS最強説👹

Isomorphic JavaScriptは最近「Universal JavaScript」と呼ばれてることを知りました↓。

参考: Universal / Isomorphic JavaScript について - Qiita


nodejs.orgより

「そういえばIsomorphicが話題になってた頃に、JSの強いヤツを集めた略語があった気がするけど何でしたっけ…🤔LAMP↓みたいな感じで」「Nodeと、Expressと、んーと」「MEANか↓!」「それだっ」「Firebaseが出る前でしたね☺

参考: MEAN(MongoDB, Express, AngularJS, Node.js)スタックが優れている理由 - Mozilla Open Web Day in Tokyoを終えて - albatrosary’s blog
参考: LAMP (ソフトウェアバンドル) - Wikipedia

「まあ確かに全部JSにすればそれぞれをJSONでつなげられるみたいなメリットはありますよね」「JSONにスキーマがあれば🤣

⚓CSS/HTML/フロントエンド/テスト

⚓UIデザインをオブジェクト指向的に考える

はてブでバズっていたので。


同記事より


つっつきボイス:「ここでいうオブジェクトって何を指すんだろう?🤔」「名詞→動詞構文に、実装モデル・表現モデル・メンタルモデルか」「いわゆるヒューマンインターフェースアナライズみたいな分野ではこういうふうに考えますね」

参考: UX/UI – U-Site


u-site.jpより

「表面・骨格・構造・要件・戦略か」「抽象度高い」「特に海外のUIデザイナーやUXデザイナーは以前からこんな感じで進めているし、日本でもやってる人たちはいる」「案件でもこういう背後の要件とか戦略とか伝えてもらったらうれしいなっ😆」「『ユーザーインターフェイスは実装モデルではなくユーザーの脳内モデルに基づいて作れ』正解〜🎯

「こういうUI/UX分野は昔からAppleが強いですね: Xcodeのインターフェイスビルダー↓なんかまさにそうですし」「あーたしかに」「Appleはツールも一緒に作ってきたのが凄い😍

参考: Interface Builder - Wikipedia

「その意味ではデザインツールのSketch↓なんかもそうで、上の記事みたいなUI/UXの基礎や階層をきちんと心得ておかないと単なる使いにくいPhotoshopみたいになってしまう😆」「😆」「Sketchに限らず、コンポーネント化されたWebデザインをやろうと思ったらこのあたりは避けて通れないでしょうね😎」「そうでないと、こういうツールを使ってても、あるコンポーネントを変更しても他のにさっぱり反映されない、みたいなことになりかねない😆」「😆

「いわゆるフロントエンドエンジニアにもこういう知識が求められますね」「フロントも大変だ😅」「コーダーならともかくですが☺

⚓言語よろずの間

⚓Facebookの「Reason」とは

Reasonは新言語ではなく、OCamlをJavaScript風に書けるよう制約して、BuckleScriptでJavaScriptにコンパイルするのだそうです。よく見たらFacebookのリポジトリでした。


つっつきボイス:「ReasonはGobyの@st0012さんから教わりました: OCamlなんだけどJavaScriptっぽく書けるみたいな感じです」「おほ😊、1行目のtypeのあたりは関数型っぽくて、letから先はJSっぽいけど関数型っぽい部分もある」「へぇ〜😳ガードっぽく書いたり」「いろいろ考えるなー」

type schoolPerson = Teacher | Director | Student(string);

let greeting = person =>
  switch (person) {
  | Teacher => "Hey Professor!"
  | Director => "Hello Director."
  | Student("Richard") => "Still here Ricky?"
  | Student(anyOtherName) => "Hey, " ++ anyOtherName ++ "."
  };

「そういえばMatzの『言語のしくみ』でHaskellよりOCamlが好きって書かれてました」「OCamlは結構息の長い言語で、金融系や保険系の商品設計あたりではかなり使われてたと思う」「おーそっちですか」

参考: OCaml - Wikipedia
参考: OCaml.jp


ocaml.orgより

「ファンドの商品設計とかは関数型的に記述しないと複雑になりすぎてしまうからでしょうね」「手続き型だとつらそう…😭」「商品そのものがものすごく複雑だから人間がバグを発見するのは無理だし、障害発生の影響も大きすぎるし」「確かに扱ってるのがお金だし」「なのでそういう分野では構造上バグが出にくい関数型系の言語がよく使われてて、記憶ベースですが日本のとある大手銀行の商品開発や戦略系の部門でOCamlが使われてたって聞いたことあるし、Haskell使ってる金融系もあった気がする💵」「へぇ〜😳

参考: Haskell、Scala、ML、Scheme:あなたが次に学ぶ関数型言語 | POSTD

「そういえばOCamlってプログラミング言語としては珍しくフランス🇫🇷産なんですね」「フランスそっち方面ではあまり聞かないかも」「雰囲気的にはですが、諜報・天文・暗号解読とかだとイギリス🇬🇧あたりが強い印象」「あとイスラエル🇮🇱ですかね」「ドイツ🇩🇪とか」

⚓その他

⚓Linuxノートどうよ


つっつきボイス:「最近Linuxのトラックパッドドライバがだいぶよくなったらしいのでこの辺ちょっと気になってます😆」「☺

⚓最近の深層強化学習


つっつきボイス:「やべーこれ😆わからん」「ドキュンあるし」「DQNありますねー☺」「こういう記事を一度はざざーっと読んで用語を押さえておくと、次に調べるときの脳内インデックスとして役立ちますね📖」「ですね☺


同記事より

方策関数ってPolicy Functionなんですね。

ロボットは東大に入れるか。Todai Robot Projectでデータが公開されたことも話題になってます。


21robot.orgより

「これも結構前からやってますね」「研究期間終わらないうちにクローズしてた気がする」「そうだったかも」「結局東大入れたんだろか🎓」「研究費を引っ張るのにはいい題材😆

⚓番外

⚓今一番凄いのってどれだろう


つっつきボイス:「今ってどのスパコンが一番凄いのかだんだんよくわからなくなってきて😅」「今だとピーク性能よりも電力効率あたりの比重が上がってたりしますね」

シン・ゴジラの設定のうちクラウド周りだけは早晩古くなりそうだったのを思い出しました。

⚓図解シリーズ

何だかかわゆかったので😍


今回は以上です。

バックナンバー(2018年度後半)

週刊Railsウォッチ(20181105)DBマイグレーション9つのコツとハマった話、Railsのモデルとディレクトリの設計ほか

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

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

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

Awesome Ruby

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

Rails: スコープはスコープを返すべき(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

Rails: スコープはスコープを返すべき(翻訳)

RailsアプリをコーディングしていてRailsというレールに乗っている期間が長くなればなるほど、アプリを楽にメンテできるようになり、つらさが軽減されます。

レールに乗るよい方法の1つは、標準のRails APIのパターンに沿って進めることです。そうやって進めるときに自分を導いてくれるパターンの1つが、よく使われるクエリをスコープ化することです。

次のように書くのではなく

スコープ内でオブジェクトを1つ返すメソッドを書く。

class Message < ActiveRecord
  scope :sent, -> { where.not(sent_at: nil) }
  scope :recently_sent, -> { sent.order(sent_at: :desc) }
  scope :most_recently_sent, -> { recently_sent.first } # オブジェクトかnilを返す
end

常に次のように書くこと

名前付きスコープからはActiveRelationを1つ返すようにする。

class Message < ActiveRecord
  scope :sent, -> { where.not(sent_at: nil) }
  scope :recently_sent, -> { sent.order(sent_at: :desc) }

  def self.most_recently_sent
    recently_sent.first
  end
end

そうすべき理由

このように書き換えることで、モデルの使われ方を変えずにコードの構成を改善できます。

.where.orderを呼ぶとActive Relationの「スコープ」が1つ返されますが、これは別のスコープと互いにチェインできます。これによって、あらゆる名前付きスコープが互いにチェイン可能になっているこの振る舞いをメンテするときに、柔軟性と再利用性が高まります。

驚き最小の原則は、コードを編成するうえで確かなヒューリスティクスとして役立ちます。

そうすべきでない理由があるとすれば

同じことは、単にスコープ同士を注意深く扱うことでも実現できるといえばできるのですが、1つのスコープ内で.first.lastを使うときのルールというものは存在しません。

このヒューリスティクスは、自分や同僚が今後作成するであろうスコープで、コードを上手に組み立てるのにうってつけのガイドとなります。ぜひとも使いましょう。

関連記事

Rails: pluckでメモリを大幅に節約する(翻訳)

Rails: `present?`より便利なActiveSupportの`presence`(翻訳)

PHPエンジニアがRailsのコードを読んでみた

$
0
0

初めまして、yoshitamaです。
PHPをこれまで触っていましたが、Railsを触り始めて1ヶ月くらい経ちました。

今回はRailsについての理解を深めるため、Rails本体のコードを読んでみたのでその解説をしたいと思います。

私自身以前PHPのMVCフレームワークであるCakePHPを使っていて、初めてCakePHP本体のソースを読もうと思った時に、フレームワークのコードは難しくて自分のレベルではまだ読めないだろうと思っていました。

けれど、読んでみるとコード自体は普段使っている言語で書かれているものですし、少しづつ丁寧に追っていけば理解できることが多いです。(追いきれないこともありますが)

それに何より普段使っているものがどのような仕組みで動いているのかを紐解いていく過程は気づきや意外な発見があったり、なかなか楽しい作業です。

今回コードを読む箇所については、delegateメソッドにしました。

delegateメソッドとは

まずはdelegateメソッドについての簡単な解説です。

ソースはここから抜粋(少し簡略化してます)。

class User < ActiveRecord::Base
  #t.string :name

  has_one :profile
end

class Profile < ActiveRecord::Base
  #t.integer :age
  #t.string :favorites
  #t.string :location

  belongs_to :user
  delegate :name, to: :user
end

profile = Profile.find(1)
# 普通はuser を経由してから呼び出します
profile.user.name #=> "太郎"

# delegate するとprofile から直接呼び出せるようになります
profile.name #=> "太郎"

ProfieクラスからdelegateメソッドでUserオブジェクト(委譲先)にname(委譲するメソッド)を指定することで、profile.nameのように、Profileクラスから直接Userオブジェクトのnameが呼び出せます。

また、delegateメソッドの引数にprefixを指定するとメソッド名にプリフィックスをつけることができます。
今回の例だとprefix:hogeを指定するとprofile.hoge_nameのような形で呼び出せます。
また、prefixtrueを指定するとprofile.user_nameのように委譲先のオブジェクト名をプリフィックスとして呼び出せます。

以降のソースコード解説の際には適宜上記ProfileクラスとUserクラスを使用して説明します。

delegateメソッドのソースコード

Rails本体のdelegateメソッドのソースコードです。
Railsのバージョンは5.2.2。
delegateメソッドが定義されているファイルはdelegation.rbになります。

def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil)
  unless to
    raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter)."
  end

  if prefix == true && /^[^a-z_]/.match?(to)
    raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
  end

  method_prefix = \
    if prefix
      "#{prefix == true ? to : prefix}_"
    else
      ""
    end

  location = caller_locations(1, 1).first
  file, line = location.path, location.lineno

  to = to.to_s
  to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)

  method_names = methods.map do |method|
    # Attribute writer methods only accept one argument. Makes sure []=
    # methods still accept two arguments.
    definition = /[^\]]=$/.match?(method) ? "arg" : "*args, &block"

    # The following generated method calls the target exactly once, storing
    # the returned value in a dummy variable.
    #
    # Reason is twofold: On one hand doing less calls is in general better.
    # On the other hand it could be that the target has side-effects,
    # whereas conceptually, from the user point of view, the delegator should
    # be doing one call.
    if allow_nil
      method_def = [
        "def #{method_prefix}#{method}(#{definition})",
        "_ = #{to}",
        "if !_.nil? || nil.respond_to?(:#{method})",
        "  _.#{method}(#{definition})",
        "end",
      "end"
      ].join ";"
    else
      exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")

      method_def = [
        "def #{method_prefix}#{method}(#{definition})",
        " _ = #{to}",
        "  _.#{method}(#{definition})",
        "rescue NoMethodError => e",
        "  if _.nil? && e.name == :#{method}",
        "    #{exception}",
        "  else",
        "    raise",
        "  end",
        "end"
      ].join ";"
    end

    module_eval(method_def, file, line)
  end

  private(*method_names) if private
  method_names
end

ソースコードの解説

詳細にコードを見て行く前に、delegateメソッドが実際にやっていることは、呼び出し元に動的にメソッドを展開することです。

どういうことかというと、

class User < ActiveRecord::Base
  has_one :profile
end

class Profile < ActiveRecord::Base
  belongs_to :user
  delegate :name, to: :user
end

上記コードが、

class User < ActiveRecord::Base
  has_one :profile
end

class Profile < ActiveRecord::Base
  belongs_to :user
  def name
    user.name
  end
end

になるイメージです。
delegateの部分が書き換えられています。
(わかりやすくするため、展開されたメソッドは簡略化しています。)
実際にnameメソッドを動的に生成することでProfileクラスからuser.namenameとして呼び出せるわけです。

それではdelegateメソッドのソースコードを見て行きます。
まずは冒頭部分。

def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil)
  unless to
    raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter)."
  end

  if prefix == true && /^[^a-z_]/.match?(to)
    raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
  end

  method_prefix = \
    if prefix
      "#{prefix == true ? to : prefix}_"
    else
      ""
    end

最初のunless toは説明不要でしょう。to:に何も指定していない場合に例外を投げています。

次はprefixtrueを指定した場合のチェック。

delegateメソッドとは」の箇所で解説したように、prefixtrueを指定すると委譲先のオブジェクト名をプリフィックスとして設定するのでした。

実際に、続くmethod_prefix変数の定義箇所をみると、prefixtrueの時はto(委譲先)にアンダースコアを繋げたものを設定していることがわかります。

また、同時にif文の条件として指定されている/^[^a-z_]/.match?(to)ですが、^[^a-z_]の部分は正規表現で「小文字のaからzとアンダースコア以外から始まっている」を表しています。

つまりtoには小文字のaからzの小文字かアンダースコアから始まる文字を設定しないといけません。

prefixtrueであれば、toがメソッド名のプリフィックスになるので、toに例えば数値で始まる文字列を指定してしまうとRubyのシンタックスエラーになってしまいますからそれを回避してるのだと思います。

次のコードを見ていきます。

location = caller_locations(1, 1).first
file, line = location.path, location.lineno

to = to.to_s
to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)

初めの2行に関してはあとで実際に変数を利用している箇所でまとめて解説します。
そのあとに出てくるDELEGATION_RESERVED_METHOD_NAMESですが、定義は同じファイル内にあります。

RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do
else elsif END end ensure false for if in module next nil not or redo rescue retry
return self super then true undef unless until when while yield)
DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block)
DELEGATION_RESERVED_METHOD_NAMES = Set.new(
  RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
).freeze

DELEGATION_RESERVED_METHOD_NAMESはどうやらrubyの予約語(RUBY_RESERVED_KEYWORDS)に_ arg args blockの4つのキーワードを足したもののようです。

delegateメソッド内のコードではDELEGATION_RESERVED_METHOD_NAMESに委譲先のオブジェクト名が含まれていたらself.を足しています。

これをしないと、委譲先のオブジェクト名がwhileなどのRubyの予約語だった場合に、Rubyのwhile構文と解釈されシンタックスエラーになってしまいますのでself.をつけているのだと思います。

いよいよメソッドの肝である部分である動的にメソッドを定義している箇所です。

method_names = methods.map do |method|
  # Attribute writer methods only accept one argument. Makes sure []=
  # methods still accept two arguments.
  definition = /[^\]]=$/.match?(method) ? "arg" : "*args, &block"

  # The following generated method calls the target exactly once, storing
  # the returned value in a dummy variable.
  #
  # Reason is twofold: On one hand doing less calls is in general better.
  # On the other hand it could be that the target has side-effects,
  # whereas conceptually, from the user point of view, the delegator should
  # be doing one call.
  if allow_nil
    method_def = [
      "def #{method_prefix}#{method}(#{definition})",
      "_ = #{to}",
      "if !_.nil? || nil.respond_to?(:#{method})",
      "  _.#{method}(#{definition})",
      "end",
    "end"
    ].join ";"
  else
    exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")

    method_def = [
      "def #{method_prefix}#{method}(#{definition})",
      " _ = #{to}",
      "  _.#{method}(#{definition})",
      "rescue NoMethodError => e",
      "  if _.nil? && e.name == :#{method}",
      "    #{exception}",
      "  else",
      "    raise",
      "  end",
      "end"
    ].join ";"
  end

  module_eval(method_def, file, line)
end

この部分でやっていることは、
delegateメソッドの呼び出し元で定義したいメソッドを文字列の形で生成してmethod_defに格納し、それをmodule_evalメソッドに渡すことでmethod_defに格納された、文字列で表現されたメソッドがdelegateメソッドの呼び出し元のメソッドのように定義されるということです。

それではソースの解説をしていきます。

definition = /[^\]]=$/.match?(method) ? "arg" : "*args, &block"

definition変数は、後で出てくるdef #{method_prefix}#{method}(#{definition})の部分で使われています。これはdelegateメソッドで定義されたメソッドを呼ぶ時に指定する引数のことです。

どういうことかというと、

class User < ActiveRecord::Base
  has_one :profile

  def hoge(foo)
    # 何かの処理
  end
end

class Profile < ActiveRecord::Base
  belongs_to :user
  delegate :hoge, to: :user
end

上記コードのように、委譲するメソッドが引数をとる場合に対応できるようにdefinitionで引数を指定できるようにしています。

definition定義箇所の/[^\]]=$/=$は「最後が=で終わること」を表現していて、これがmethodに対する条件になっています。最後が=で終わるメソッドはセッターメソッドのことです。

また、[^\]]は「]ではない」を表現しているので]=は許可しないということです。これは、コード内のコメントにもあるようにArrayクラスのメソッド[]=を除いています。

つまり、definitionにはセッターメソッドであれば引数は一つなのでarg、それ以外であれば*args, &blockを設定しています。

次のallow_nilによる分岐は、委譲先オブジェクトが指定メソッドを持っていない場合にNoMethodError例外を発生させるかどうかです。

method_def変数は先ほど解説したように、module_evalに渡すとことで、それを呼び出し元に展開されたコードであるように定義してくれます。

module_eval実行部分ですが、後回しにしていたcaller_locationsの箇所と一緒に見ていきます。

location = caller_locations(1, 1).first
file, line = location.path, location.lineno

(省略)

module_eval(method_def, file, line)

caller_locationを呼び出しているのはdelegateメソッドの呼び出し元のファイル名と行番号を取得するためです。

そしてmodule_evalにそれらを渡すことで、delegateメソッドに指定したメソッドの呼び出し時にエラーが発生した場合に、呼び出し元の情報をスタックトレースに出力することができます。

Profileクラスの例でいうと、profile.nameの呼び出しでエラーが発生した時に、delegate :name, to: :userを定義したファイル名と行番号がスタックトレースに表示されます。

そして最後。

private(*method_names) if private
method_names

delegateメソッドの引数のprivatetrueの場合にprivateメソッドにmethod_namesを渡しています。

引数を持つprivateメソッドについて知らなかったので調べてみると、引数にメソッド名を渡すとその名前を持つメソッドをprivate扱いにしてくれるようです。

参考: private (Module)

そのあとのmethod_namesはdelegateメソッドの戻り値になります。

おわりに

以上がdelegateメソッドの解説になります。

私と同じようにRailsを始めたてだったり、フレームワークのコードを読んでみたいけどレベル高くて読むのが大変そうと思っている人が興味を持ったり、読んでみるきっかけになれば嬉しいです。

関連記事

Rails: コードをシンプルにする2種類の委譲(翻訳)

Ruby: delegate.rb標準ライブラリの動作を解明する(翻訳)

Viewing all 1384 articles
Browse latest View live