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

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

$
0
0

更新情報

  • 2018/09/27: 初版公開
  • 2021/02/26: 細部を更新

概要

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

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

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

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

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

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

そうしたメソッドのひとつが#presenceです。これはお馴染みの#blank?(訳注: #empty?のエイリアス)や#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: ActiveRecordのスコープで`present?`を使うとパフォーマンスが落ちることがある(翻訳)

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


Rails: URLヘルパーをビューやコントローラ以外の場所で使う(翻訳)

$
0
0

概要

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

Rails: URLヘルパーをビューやコントローラ以外の場所で使う(翻訳)

アプリケーションへのURLをビューやコントローラの外で生成する必要が生じることがたまにありますが、そのような場所でもURLヘルパーを利用できます。これらのヘルパーメソッドはルーティングから生成されますuser_books_path(user)など)。

以下のように書くのではなく

URLを直に書く。

class RequestUserCallBackJob < ApplicationJob
  def perform(user)
    Net::HTTP.post(
      "http://userinfoapi.com/",
      body: {callback_to: "https://myapp.com/user/#{user.id}"})
  end
end

以下のように書く

Railsのコントローラで自動的にincludeされているルーティング用ヘルパーを使う。

class RequestUserCallBackJob < ApplicationJob
  include Rails.application.routes.url_helpers

  def perform(user)
    Net::HTTP.post(
      "http://userinfoapi.com/",
      body: {callback_to: user_url(user, host: "myapp.com")})
  end
end

URLヘルパーが使われるのは、Webリクエストのコンテキスト、つまりビューやコントローラの中です。この場合、リクエストのhostやドメインはアプリケーションが自動的に提供してくれます。このコンテキストの外では、_urlで終わるヘルパーにhostを明示的に指定する必要があります。

このパターンの発展形は、以下のようにActive Supportのconcernを用いて(おそらく)既に設定されているAction Mailerのurlオプションに乗っかるという方法です。

module Routing
  extend ActiveSupport::Concern

  included do
    include Rails.application.routes.url_helpers
  end

  def default_url_options
    Rails.application.config.action_mailer.default_url_options
  end
end

class RequestUserCallBackJob < ApplicationJob
  include Routing

  def perform(user)
    Net::HTTP.post(
      "http://userinfoapi.com/",
      body: {callback_to: user_url(user)})
  end
enda

そうする理由

URLヘルパーは便利で使い方も一貫していますが、アプリケーション全体で使う方がよいでしょう。何らかの理由でルーティングを変更して、関連するURLメソッドの更新に失敗した場合、テストで問題が警告されます。ハードコードされたURLにidが補完されている場合、事前にこのような警告を出してくれません。

上の例で示されているように、私がこのパターンをよく必要とするのは、レスポンスを受信するためのwebhookが必要な外部APIを呼び出す場合です。

このパターンは、アプリケーションのresouces URLを記述するフィールドをエクスポートするアプリケーション用のAPIを書いている場合にも使うかもしれません。

{
  "id": 23432,
  "name": "Nadia",
  "links": {
    "self": "https://yourapp.com/user/23432.json"
  }
}

しかしAPIレスポンスはそれ自身のためのものです。したがって、URLヘルパーを自分で混ぜるのではなく、 active_model_serializersのようなAPIレスポンスを生成するためのツールを使うべきでしょう。

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

ルーティングヘルパーがまだない場所でルーティングヘルパーを使おうとする場合は、このパターンを使うべきではない可能性があるかもしれません。

ルーティング(ヘルパー)をActive Recordモデル内で使うのは避けましょう。もっとふさわしい場所はきっと他にあります!

関連記事

週刊Railsウォッチ(20210308前編)書籍『Ruby on Rails Performance Apocrypha』、rswag gemほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

以下のコミットリストのChangelogを中心に見繕いました。今週はやや少なめです。

🔗 Active Storageで画像を生成できない場合にPreviewErrorをraiseするようになった

プレビューを生成できない場合、キャプチャされるIOストリームが空になり、0バイトのプレビューファイルが生成されてActive Storageサービスに保存されてしまう。
これが発生した理由は、PopplerがいくつかのPDFプレビュー生成に失敗して0バイトのファイルになってしまったことによるもの。これらの”preview”をリサイズするとMiniMagickでエラーが発生していた。MiniMagickのこのエラーは、0バイトのファイルに対して試みられた場合には正しい結果に思えるが、プレビューをキャプチャしようとした子プロセスが失敗して終了した場合にPreviewerが通常どおり処理を進めるのは正しいとは考えにくい。
そこで、previewerの子プロセスが0以外のステータスで終了した場合は例外を発生するようにした。
同コミットより大意


つっつきボイス:「今までは画像リサイズ時にMiniMagickのエラーが出ていたのに、そのまま空のプレビューファイルをストレージに置いてしまってたんですね」「プレビュー生成はいろんな理由で失敗する可能性があるので、Active Storageとして明示的にエラーを出すようになったのはいいですね👍

「ライブラリが返す例外の詳細メッセージに生のコマンドが含まれていたりすると、攻撃者がそれを元にライブラリのバージョンなどを推測して手がかりにする可能性がなくもないので、そういう意味でもライブラリの例外をそのまま出さないようにしておくのは一見地味ですが結構重要だと思います」「なるほど」

🔗 Action Textのリッチテキストでto_trix_htmlが使われるようになった

このプルリクの目的は、rich_text_area_tagを使う場合はAction Textのto_sではなくto_trix_htmlを使うようにすること。to_trix_htmlメソッドをto_sto_plain_textと同じようにもっと使いやすくするためにモデルに委譲を追加した。
同PRより大意


つっつきボイス:「Action Textで使われているTrixは、Basecampが開発したWebアプリ向けリッチテキストエディタ↓」「to_trix_htmlが前からあったのでそれを使うようにしたようですね」

basecamp/trix - GitHub

🔗 Arel::CrudArel::Tableから削除した

compile_updatecompile_deleteは、元もArel::Tableではまったく動かなかった(Arel::Table@ast@ctxもないため)。
compile_insertcreate_insertは動くが、レシーバーの情報をまったく使っていないので、代わりにArel::InsertManager.new(arel_table)を使うこと。
同PRより大意


つっつきボイス:「これは文字通り、実は動かないメソッドを削除したのか」

# activerecord/lib/arel/table.rb#L3
module Arel # :nodoc: all
  class Table
-   include Arel::Crud
    include Arel::FactoryMethods
    include Arel::AliasPredication

🔗 すべてのtree managerでイニシャライザがテーブルを受け取れるようになった

SelectManager80ad95bテーブルを受け取るようになったが、他のmanagerはクエリ生成で必須の場合でもそうなっていない。
SelectManagerと同様、他のtree managerもすべてテーブルを受け取っていいと思う。
同PRより大意


つっつきボイス:「tree managerって初めて見ました」「お、Arelの中にマネージャーがありますね↓」「ホントだ」「いかにもSQLのSELECT/INSERT/UPDATE/DELETEに対応する感じ」

80ad95bからたどっていくとvisitorsがあった↓」「名前や処理からしてVisitorパターンで実装されているようですね」

参考: rails/visitors.rb at main · rails/rails

    def to_dot
      collector = Arel::Collectors::PlainString.new
      collector = Visitors::Dot.new.accept @ast, collector
      collector.value
    end

    def to_sql(engine = Table.engine)
      collector = Arel::Collectors::SQLString.new
      collector = engine.connection.visitor.accept @ast, collector
      collector.value
    end

参考: Visitor パターン - Wikipedia

[保存版]人間が読んで理解できるデザインパターン解説#3: 振舞い系(翻訳)

🔗 excludingの修正


つっつきボイス:「この間追加されたexcludingに修正が入ったそうです(ウォッチ20210222)」「そういえばwithoutというエイリアスもexcludingに追加されてましたね」

「なるほど、1つ目は引数が1個以上必要だったのをなしでもよいことにしたのか↓: プルリクメッセージにも『Active Recordは引数がなくても1=1(つまりtrue)を追加するようになっている』とあるので、それなら引数なしでもよいと判断したんでしょうね」「なるほど」「引数があるかどうかをチェックする分岐を書かなくて済むのはうれしい👍

# activerecord/lib/active_record/relation/query_methods.rb#L1131
    def excluding(*records)
      records.flatten!(1)

-     raise ArgumentError, "You must pass at least one #{klass.name} object to #excluding." if records.empty?
-
      if records.any? { |record| !record.is_a?(klass) }
        raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to #excluding."
      end
      spawn.excluding!(records)
    end

「2つ目は引数がnilの場合に対応したとありますね」「これも実質上と同じ話ですね: Active Recordはnilが渡された場合に1=1(つまりtrue)を追加するようになっているとプルリクに書かれてます」

🔗Rails

🔗 電子書籍『Ruby on Rails Performance Apocrypha』


つっつきボイス:「TechRacho記事でもお世話になっているNate Berkopecさんは”The Complete Guide to Rails Performance”という本↓を以前から出していますが、先ごろ新たに”Ruby on Rails Performance Apocrypha”という別冊的な本を出したそうです」

以下はつっつき後に見つけたツイートです。”The Complete Guide to Rails Performance”はRails 6とRuby 2.7にも対応したそうです。

「トピックを見ると、GVLとかworker killerのような、Railsを長く触っているとどこかで遭遇しそうなパフォーマンス周りの話題を扱ってますね↓」「この本は面白そうな予感がします👍

  • Benchmarks for Rails Apps
  • Reading Flamegraphs
  • Microservices and Trends
  • Why is Ruby Slow?
  • Popularity
  • Page Weights and Frontend Load Times
  • What is the GVL?
  • Reproducing Issues Locally
  • Worker Killers
  • Multi-threading
  • Read Replicas

Rubyのスケール時にGVLの特性を効果的に活用する(翻訳)


「アポクリファって?」「こんな意味でした↓」「なるほど、外典ですか」「アポクリファっていかにもゲームのタイトルとかにありそうな響き」

apocrypha: 外伝、外典(ギリシャ語のἀπόκρυφα(複数形: 隠されたもの)由来で、cryptに通じる)

後で調べると、ゲームを元にした小説のタイトルが見つかりました↓。

参考: Fate/Apocrypha - Wikipedia

🔗 Redisベースのrate limiter(Ruby Weeklyより)


つっつきボイス:「ざっと見た感じでは、rate control用のパラメータをRedisに保存して更新・参照することで、APIサーバーのクライアントTokenごとのrate制御を実現しているようですね」

# 同記事より
class RateLimiter
  TimedOut = ::Class.new(::StandardError)

  REDIS_KEY = "harmonogram_#{Rails.env}_rate_limiter_lock".freeze

  def initialize(redis = Redis.current)
    @redis = redis
    @interval = 1 # seconds between subsequent calls
    @timeout = 15 # amount of time to wait for a time slot
  end

  def with_limited_rate
    started_at = Time.now
    retries = 0

    until claim_time_slot!
      if Time.now - timeout > started_at
        raise TimedOut, "Started at: #{started_at}, timeout: #{timeout}, retries: #{retries}"
      end

      sleep seconds_until_next_slot(retries += 1)
    end

    yield
  end

  private

  attr_reader :redis, :interval, :timeout

redis/redis - GitHub

「こういった処理はRedisでなくてもできますが、マルチスレッドな環境でもアトミックにアクセスできる高速な共有ストレージとしてRedisを使ったんでしょうね」「ふむふむ」「Redisのアトミックアクセス用命令を使うのかなと思ったら、RedisのPTTLというms単位で減っていくTTLカウンタを使うことで、clientへの割り当てタイムスロットの残時間をRedisで管理させているみたい」

## 同記事より
def seconds_until_next_slot(retries)
  ttl = redis.pttl(REDIS_KEY)
  ttl = ttl.negative? ? interval * 1000 : ttl
  ttl += calculate_next_slot_offset(retries)
  ttl / 1000.0
end

# Calculates an offset between 10ms and 50ms to avoid hitting the key right before it expires.
# As the number of retries grows, the offset gets smaller to prioritize earlier requests.
def calculate_next_slot_offset(retries)
  [10, 50 - [retries, 50].min].max
end

参考: PTTL – Redis

「RDBだけだとこういうときに困るのでRedisが欲しくなってきますね」

「あと、こういう処理をRDBで行うとDB接続のコネクションプールを使ってしまうので、Redisのような別のストレージを使う方がありがたい面はあります」「なるほど」

🔗 rswag: RSpecからSwagger JSONを生成(Ruby Weeklyより)

rswag/rswag - GitHub


つっつきボイス:「rswagは見たことなかったけど、★は1000件超えていますね」「rspecのrequest spec風DSLを書いて、Swaggerに対応した定義ファイルの出力やAPIテストができるgemだそうです」

参考: API Documentation & Design Tools for Teams | Swagger

「こんなふうにRSpecにSwagger的なspecを書ける↓、そしてそれを元にrake rswag:specs:swaggerizeでSwagger用のJSONを生成できるのか、へ〜!」

# 同リポジトリより
# spec/integration/blogs_spec.rb
require 'swagger_helper'

describe 'Blogs API' do

  path '/blogs' do

    post 'Creates a blog' do
      tags 'Blogs'
      consumes 'application/json'
      parameter name: :blog, in: :body, schema: {
        type: :object,
        properties: {
          title: { type: :string },
          content: { type: :string }
        },
        required: [ 'title', 'content' ]
      }

      response '201', 'blog created' do
        let(:blog) { { title: 'foo', content: 'bar' } }
        run_test!
      end

      response '422', 'invalid request' do
        let(:blog) { { title: 'foo' } }
        run_test!
      end
    end
  end

  path '/blogs/{id}' do

    get 'Retrieves a blog' do
      tags 'Blogs'
      produces 'application/json', 'application/xml'
      parameter name: :id, in: :path, type: :string

      response '200', 'blog found' do
        schema type: :object,
          properties: {
            id: { type: :integer },
            title: { type: :string },
            content: { type: :string }
          },
          required: [ 'id', 'title', 'content' ]

        let(:id) { Blog.create(title: 'foo', content: 'bar').id }
        run_test!
      end

      response '404', 'blog not found' do
        let(:id) { 'invalid' }
        run_test!
      end

      response '406', 'unsupported accept header' do
        let(:'Accept') { 'application/foo' }
        run_test!
      end
    end
  end
end

「RSpecで振る舞いを記述してそこからJSONを生成するというのは、TDD(Test Driven Development)的な、いやむしろBDD(Behavior Driven Development)的なアプローチを感じますね」「なるほど!」「書いたRSpecはそのままテストコードになるんでしょうね」

参考: テスト駆動開発 - Wikipedia
参考: ビヘイビア駆動開発 - Wikipedia

「実際に使ってみないとわかりませんが、小規模なSwagger+Rails案件をRailsエンジニアだけで開発するなら、このrswagを使ってみてもいいかもと思いました👍」「お〜」「この種の特殊なライブラリは、大規模な案件や複雑な案件でいきなり使うと途中で機能が足りなくなる可能性も考えられるので、最初は小規模な案件で試すのがいいでしょうね」

参考: とにかくRails6でrswagを動かす - Qiita

🔗 AS句その後


つっつきボイス:「そうそう、この間のAS句(ウォッチ20210301)について@kamipoさんがツイートしてましたね」

「興味深いのは、DBの型情報を付けているのがDBのアダプタだということ: 仮にもっと賢いアダプタを作ってそこで型情報を付与できれば、テーブルがなくてもAS句で作ったクエリでActive Recordの型付きオブジェクトを取ることが原理的には可能ということになりますね」「なるほど」「ただ、型情報の付与を厳密にやろうとすると複雑さが増して処理が重くなる可能性はありそう」

🔗 Active Recordクエリをビューに書いてもいいと思う場合(Hacklinesより)


つっつきボイス:「冒頭で『Active Recordクエリをビューに書いてはいけない』とよく言われていることを踏まえて、でもたとえばCategory.allぐらいだったらビューに直接書いてもいいのではという記事でした」「プロジェクトの方針にもよりますが、セレクトボックスの項目をCategory.allで取るぐらいならビューに書いても構わないだろうという気持ちはわかりますね: その一方で『Active Recordクエリをビューに書いてはいけない』と言われる理由もわかります」「なるほど」

「どこまで潔癖にやるかは最終的にプロジェクトの方針次第でしょうね」「開発速度重視のプロトタイプ開発なら、短期間で開発できるRailsのメリットを活かすためにもビューに書くのをありにしてもいいかもと思いました」


同記事の末尾でも「ビューにロジックを置かないようにすれば確かにコードも変更しやすくなるし理解しやすくなる」「でも他の部分にまず影響しない場合なら置いてももいいのでは」と締めくくられています。

🔗 その他Rails


前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210303後編)Bundlerのセキュリティ修正、Rubyのガベージコレクション記事、Rubyが2/24に誕生日ほか

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

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

Rails公式ニュース

Ruby Weekly

Hacklines

Hacklines

週刊Railsウォッチ(20210309後編)RubyのIRBに隠れているイースターエッグ、Power Automate Desktop、SQLクエリのありがちなミス6つほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Ruby

🔗 RubyのIRBに隠れているgem(Ruby Weeklyより)


つっつきボイス:「gemでないものも含めてIRBにはいろんなものが入っているという記事です」

「Ruby 2.7から入ったと記事に書かれているreline gem↓もIRBで使われている」「relineといえば、@aycabtaさんが心血を注いだgemですね」「他にRipper(パーサー)も使われている」

ruby/reline - GitHub

参考: class Ripper (Ruby 3.0.0 リファレンスマニュアル)

RubyKaigi Takeout 2020の動画を見つけました。

loaderというextensionが入っている: こんなふうにIRBのコンテキストでファイルを読み込んで評価できる↓」「おぉ〜」「-r付けてファイルを読み込む、そういえばやったことあったかも」「irb_loadでIRBの中でもファイルを読み込めるのか」

# 同記事より
$ irb -r ./hi.rb
Hi codenamev!
irb(main):001:0>

参考: ruby/loader.rb at master · ruby/ruby
参考: ruby/loader.rb at master · ruby/ruby · GitHub

use_tracerは自分も使ってます」「最近入ったmeasureはウォッチでも話題にしましたね(ウォッチ2021_02_02)」

参考: ruby/tracer.rb at master · ruby/ruby
参考: ruby/measure.rb at master · ruby/ruby · GitHub

ruby/tracer - GitHub

「以前だとpryを入れないとできないことがありましたけど、今はRuby標準のIRBでできることがとても増えましたよね」「IRBはすごく使いやすくなったと思います」

pry/pry - GitHub

🔗 IRBに隠された「イースターエッグ」

「お、IRBにはイースターエッグが隠されているようですよ」「どれどれ👀」「こんなのが隠れてたなんて!」


同記事より

参考: ruby/easter-egg.rb at master · ruby/ruby

「Ruby 3.0のIRBを立ち上げてIRB.send :easter_eggと入力したら動きました!」「このeaster-egg.rbを明示的に読み込めば他のバージョンのRubyでも動かせそうですね」

「手元のRuby 2.7.1だと以下のような静止画↓で表示されたけど、easter-egg.rbのソースを見るとtypeに:logo:dancingがある: ということは、IRB.send :easter_egg, :dancingと入力すると…動くイースターエッグが出た!🎉

(注: 微妙に色が付いているのはhachi8833のiTerm設定によるものです)


ちょうどもうすぐイースターなので季節感ありますね。

後で手元のrbenv環境でRubyのバージョンを変えて試したところ、easter-egg.rbはどうやらRuby 2.7.1から入ったようです。Ruby 3.0.0から動くイースターエッグがデフォルトになったんですね。もしやと思って履歴を調べると、動くバージョンを入れたのはやはりmameさんでした。

ついでにeaster-egg.rbをローカルにダウンロードし、easter-egg.rbが入っていないRuby 2.7.0でirb -r ./easter-egg.rbを実行してIRB.send :easter_eggを実行すると、動くイースターエッグが表示されました。

🔗 bundle openでgemを検索・デバッグする(Ruby Weeklyより)


つっつきボイス:「Bundlerにもいろいろ機能が隠れていますね」「bundle open gem名でgemをエディタで開けるらしい」「このopenってもしかしてMacのopenコマンドにしか対応してないのかなと思ったら、Ubuntuでも動いたので大丈夫ですね」「お、なるほど」

「自分も手元でGemfileのあるプロジェクトでやってみたら環境変数でエディタを設定しろと言われたので↓、BUNDLER_EDITOR=vi bundle open nokogiriとしたら動きました」

$ bundle open nokogiri
To open a bundled gem, set $EDITOR or $BUNDLER_EDITOR

🔗 Enumerable#filter_mapRuby Weeklyより)

↑記事の末尾にあります。filter_mapはRuby 2.7で入ったそうです。


つっつきボイス:「小ネタですが、Ruby Weeklyの末尾に載ってるTip of the Weekを拾いました」「filter_mapという名前からして、filtermapを一度にできそう」

参考: Enumerable#filter_map (Ruby 3.0.0 リファレンスマニュアル)

「まさにfilterしたものに対してmapする処理を1個のブロックでまとめて書けるんですね↓」「これは使いたい機能👍

# Ruby Weekly #541より
[1,2,3,4,5,6].filter_map { |x| x ** 2 if x.even? }
#=> [4,16,36]

「ところでfilter_mapはあるけどselect_mapはないらしい」「言われてみればfilterselectってどちらかがエイリアスでしたっけ?」「ドキュメントを見ると、Rubyのfind_allselectfilterは等価ですね↓」

参考: Enumerable#filter (Ruby 3.0.0 リファレンスマニュアル)

🔗 RubyのnotHacklinesより)


つっつきボイス:「シンタックスハイライトなしでベタに書くと混乱するタイトルをわざと使ってますね☺

後でタイトルを手元でハイライトしてみました↓。最後の「(not)」は、「でも実は同じではない」というニュアンスのようです。

Ruby’s not keyword is not not but !(not)

「Rubyの否定演算子といえば!ですけど、notってあったかな?」「自分も知らなかったんですが、探すとnotキーワードがありました↓」「not!よりも優先度が低い、なるほど」

参考: 演算子式 (Ruby 3.0.0 リファレンスマニュアル)

# docs.ruby-lang.orgより
高い   ::
       []
       +(単項)  !  ~
       **
       -(単項)
       *  /  %
       +  -
       << >>
       &
       |  ^
       > >=  < <=
       <=> ==  === !=  =~  !~
       &&
       ||
       ..  ...
       ?:(条件演算子)
       =(+=, -= ... )
       not
低い   and or

「Rubyのnotは、andorの次に優先順位が低いんですね」「スペルアウトしたandorがあるならnotがあるのもわかる気がする」「記事では!notを再定義してみてますね↓」「notを実際に使うことはあまりなかったな」

# 同記事より
# ~/code/ruby/rubys_not_is_not_not_but_!_(not).rb

class Banger
  def !
    :bang
  end
end

class Naysayer
  def not
    :nay
  end
end

(Ruby 1.9の)ドキュメントにあるように、Rubyの!はオーバーライドできるので、! message()のようにメッセージの前に置いても値をオーバーライドできます。しかしnotキーワードのオーバーライドは、instance.not()のようにインスタンスの戻り値にしか効きません。
!をオーバーライドするとnotにも効くので、notは何らかの形で!を使っていることがわかります。
ただし!notは機能(つまり優先順位)が異なるので、互いにエイリアスではありません。
同記事より大意

🔗 その他Ruby


つっつきボイス:「よく言われている話ですが一応取り上げてみました」「『macOSに最初から入っているRubyは使わないこと』というのはMacで開発するときの常識になっていますが、初心者がハマる原因でもありますね」

「最近Mac使っていないので昔の話になりますが、macOSに最初から入っているRubyはバージョンが古かったし、記事にもあるようにgemをインストールするのにroot権限を要求されたりとかいろいろ不便でしたね」「あれはつらかったです…😢

「今のmacOSに入っているRubyのバージョンっていくつなんだろう?」「あ、そういえば今はどうなってるのかな?」


後で調べると、自分のMacBook(OSはCatalina: 10.15.7)の/usr/bin/rubyは2.6.3p62でした。

$ /usr/bin/ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin19]

今ならウォッチでも取り上げたRails Girlsのインストールレシピ↓を参考にするのがよいでしょうね(ウォッチ20210126)。

参考: Rails Girls インストール・レシピ: Rails Girls - Japanese

🔗DB

🔗 SQLクエリのありがちなミス6つ(DB Weeklyより)

つっつきは6つから抜粋しました。

🔗 NULL値に対してNOT INを使うミス

つっつきボイス:「記事にあるNOT IN (c001, c003, c004, NULL)のように書いたり、なんとか=NULLのように書いたりしても期待どおりには評価されないので、基本的にはカラム定義でNULLを許すときは気をつける必要がありますね」「たしかに」「なお、SQL標準でも多くのRDBでもNULL = NULLはfalseになります」

「RailsのActive Recordではそういう場合にIS NOT NULLをAND条件でつないでくれますが、生SQLを書くときには自分でこうした点に注意する必要があります」

🔗 クエリの句の順序が正しくないミス

SELECTFROMWHEREの次は、『GROUP BYしてHAVINGしてからORDER BYする』という順序で書くのがSQLの構文レベルで決まっているので、順序を間違えるとその時点でシンタックスエラーになります」

-- 同記事より: 間違っている例
SELECT empName 
FROM employees 
WHERE empCategory='DevOps' 
ORDER BY empName 
GROUP BY branchCode 
HAVING count(*) = 1;
-- 同記事より: 正しい例
SELECT empName 
FROM employees 
WHERE empCategory='DevOps' 
GROUP BY branchCode 
HAVING count(*) = 1
ORDER BY empName;

🔗 BETWEENを両端の値を含まない範囲指定として使うミス

「そうそう、SQLのBETWEENは両端の値を含むのが要注意ですね: 4月中の日付で取ろうとして以下のように書いてしまうと、5月1日のものまで取ってしまう↓」

-- 同記事より
SELECT *
FROM products
WHERE manuf_date BETWEEN ‘2020-04-01’  AND ‘2020-05-01’

「そういえば学校で教える英語では常識的にbetweenは両端を含まない(exclusive)なので、英語圏の人がそこで間違えやすそうですね」「そうそう、自分もそこが昔から気になっているんですが、どういうわけかSQLのBETWEENは境界値を含む(inclusive)仕様になっているんですよ」「英語圏では直感に逆らいそう…」

参考: [SQL] 5. データの参照 5 | TECHSCORE(テックスコア)

「少なくとも翻訳の世界では、たとえば”between 2020-01-01 and 2020-02-01″と書かれていたら、1月1日と2月1日は含まないと解釈するんですが、原文が正しくそのつもりで書かれているとも限らないのが要注意だったりします」「へ〜」「ちなみに境界値を含めたい場合はfromとtoで書きます」「たしかにfromとtoで書く方が誤解されなさそう」

「SQLのBETWEENはそうやって面倒になりがちなので、最終的には以下のように書くことが多いかな↓」「なるほど」「これもRailsのActive Recordなら、rangeで指定するとinclusive/exclusiveに応じてBETWEENを使うか使わないかを自動で判断してくれますね」

-- 同記事より
SELECT *
FROM products
WHERE manuf_date => ‘2020-04-01’
AND manuf_date < ‘2020-05-01’

後で調べて思い出しましたが、英語のbetweenは「その期間のどこかの時点」という単発的なニュアンスを含み、fromとtoだと「その期間ずっと」という継続のニュアンスを含むことがよくあります。

参考: 7つの不思議な仕事?(from A to Bの違い?) | 実践で使える英語をマスター!GLJ英語学院/ビズ英アップ!スクール

ついでながら、英語の”between you and me”は「ここだけの話なんだけど」「これは二人だけの秘密だよ」というイディオムです。

🔗 実行時の暗黙のフィールド定義型変換

「これはRDBMSに依存する部分も大きいのでなかなか厄介な問題ですね: 以下のpinがvarcharの場合暗黙で強制変換されて、ランタイムエラーにこそならないけどパフォーマンスが落ちることがある」

-- 同記事より
SELECT *
FROM myAccount acc
WHERE acc.pin = 123654789286
AND acc.isPending IS null;

「MySQLはこういう場合に割といい感じに型変換してくれますが、PostgreSQLはデフォルトだとお固い傾向がありますね」「なるほど」「PostgreSQLもimplicit conversionを指定すればMySQL並に型変換できるようになりますが、それらも含めてこの問題はRDBMSによって変わってきます」

参考: PostgreSQL 12.4文書 CREATE CAST

「これについてもActive Recordはよしなに対応してくれるので、自分でクエリを書くときもActive Recordが出すクエリを参考にすれば、MySQLでは通るのにPostgreSQLでは通らないということを避けたいときにいいでしょうね」「なるほど!」

🔗 kamipo TRADITIONAL

「特にMySQLを使う場合は、通称『kamipo TRADITIONAL』を参考にするといいと思います」「お、何ですかそれ?」「これです↓」

参考: ルーク!MySQLではkamipo TRADITIONALを使え! | おそらくはそれさえも平凡な日々

「記事にもあるようにsql_modeを以下のような感じで設定しておくと、MySQLでも標準SQLに近いクエリしか通らなくなりますので、そうした問題を回避できます」「知らなかった〜」

# https://songmu.jp/riji/entry/2015-07-08-kamipo-traditional.html より
SET SESSION sql_mode='TRADITIONAL,NO_AUTO_VALUE_ON_ZERO,ONLY_FULL_GROUP_BY'

「特にONLY_FULL_GROUP_BYは付けることを習慣づけておくといいと思います: ただMySQLの楽な書き方に慣れていると、SELECTするカラムが多い場合にGROUP BYをすべて書くのが面倒に感じられたりもするんですよね…」


「以上、昔からよくあるありがちなSQLのミスでした」

🔗言語/ツール/OS/CPU

🔗 マイクロソフトのRPA「Power Automate Desktop」(Publickeyより)


つっつきボイス:「ちょっと試してみたそうですね」「そうそう、こんな感じでEC2インスタンスを取得したりできます↓」

「Power Automate Desktopは、一度動かすと右のペインで変数の値を参照できたりするのがなかなかよくできていると思いました」「お〜、ExcelのVBA画面みたいですね」「少しずつ動かしながら変数の値を確認して書き足す、といった使い方ができます」

参考: Visual Basic for Applications - Wikipedia

「もちろんまだ成熟していない機能もいろいろありますが、変数の宣言方法以外はドキュメントをほとんど見ないで書けたのはよかった」「へ〜!」「Power Automate Desktopは前から有料版があったのが無料でもダウンロードして利用できるようになったそうです」「記事にも今後はWindows 10に標準搭載されるとありますね」

参考: Power Automate Desktop のよくある質問集 - 吉田の備忘録

「Macに入っているApple ScriptのAutomatorよりはイケてる感じでしょうか?」「Automatorはほとんど使ったことがありませんが、感覚的にはZapierに近いかな」「なるほど」

参考: Automator Actionを実行 – AppleScriptの穴
参考: Zapier | The easiest way to automate your work

「Power Automate Desktopはプログラマーでない人でも作ることができて、この種のツールとしては比較的機能が揃っているのがいいですね: プログラマー向けにはPythonスクリプトの実行やSFTP接続といった項目もあります」「ホントだ」「ちょっとした日々の運用タスクをある程度自動化するのによさそう👍

🔗 cosmopolitan: 複数環境で実行できるポータブルバイナリを出力(StatusCode Weeklyより)

jart/cosmopolitan - GitHub

cosmopolitan: 国籍や国境にとらわれない国際人


つっつきボイス:「マルチな環境で動くシングルバイナリを出力できるそうです」「リポジトリに貼ってある動物の絵はスカンクかな?」「Linux + Mac + Windows + FreeBSD + OpenBSD + NetBSD + BIOSで動くって、そんなことが可能なのかしら?」

「リポジトリに謎のタイトルの関連ブログ記事が貼ってある↓」「あ、それはニセのギリシャ語で書かれた英語タイトルです」「ニセギリシャ語😆」「記事を眺めた限りでは、シングルバイナリをマルチな環境で動かす方法があるらしい」「へ〜!」

参考: αcτµαlly pδrταblε εxεcµταblε

「以下のredbeanも同じ人がcosmopolitanをベースに作ったそうです」「実用性はともかく技術的なチャレンジとして面白い」「誰得感ありますね」「スゴい人がいるもんだ…」


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210308前編)書籍『Ruby on Rails Performance Apocrypha』、rswag gemほか

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

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

Ruby Weekly

Hacklines

Hacklines

StatusCode Weekly

statuscode_weekly_banner

Publickey

publickey_banner_captured

DB Weekly

db_weekly_banner

実践Capistrano 3(1): タスク、ロール、変数(翻訳)

$
0
0

概要

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

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

実践Capistrano 3(1): タスク、ロール、変数(翻訳)

Capistranoは、Webアプリケーションのデプロイやメンテナンスを自動化するのに有用なツールです。私の場合、Ruby on Railsアプリケーションやその他のRackベースのアプリケーションのデプロイに用いています。依存関係のインストール、アセットのコンパイル、データベーススキーマのマイグレーションといった厄介な作業は、Capistranoがまとめて面倒を見てくれます。Capistranoを使わない場合、手動でサーバーにsshログインして必要なコマンドを手入力することになりますが、長時間に渡る忍耐力が求められますし、うっかりミスにつながりかねません。ありがたいことに、今やそうした作業は必須ではありません。

Capistranoの設計はモジュラーなので、プラグインやスクリプトを多数利用できます。これらを用いることで、要求の厳しいデプロイも可能になります。欲しい機能が見当たらなくても、Capistranoは本質的に柔軟なので、タスクを簡単に書き下ろせます。どのようなデプロイにしたいかをCapistranoに正確に指示すると、Capistoranoはサーバーを準備してアプリケーションを最新に保つよう支援します。

本シリーズにおける私の目標は、Capistranoというデプロイツールの中心となるコンセプトを解説するとともに、多くの高度な機能をご紹介することです。そのために、Rubyで書かれたWebアプリケーションをメンテナンスする場合に、現実的によくある問題のソリューションをさまざまな事例で見ていきます。これらの事例によって、作業を短時間で完了できるCapistranoスクリプトの書き方をもれなく学ぶことができ、Capistranoの内部に関する勘も養えるようになるはずです。

本記事のコードサンプルは、すべてCapistrano 3を前提としています。執筆時点では、Capistrano 3.11.2をインストールすることになります。Capistranoを自分のプロジェクトでセットアップする方法について詳しくは、公式ガイドをご覧ください。

前置きはこのぐらいにして、早速始めましょう!

Capistranoのタスク

Capistranoのコア部分では、Rakeビルドツールが提供するタスクを用いて、アプリケーションのアップデートやデプロイの操作を記述します。Capistranoはそれらのタスクを直感的な独自DSLにまとめ、ローカルコンピュータやリモートサーバー上のUnixシェルで実行しやすい形にします。

私は長い間、あっちこっちのサーバーに手動でsshログインしてはその場限りのアップデートに明け暮れていたこともありました。手動作業では、たとえばstagingサーバーにアクセスしたい場合に、デプロイユーザーだのサーバー名だのをいちいち把握した上でシェルに入力する必要がありました。これでは、つらいうえに操作ミスも起こりがちです。

「なるほど、ではもっといい方法はないものか?」

詳細情報を一切把握せずに、デプロイ環境であらゆるサーバーに同じ方法でログインできれば理想的です。つまり「stagingサーバーにログインしたい」と一言で済ませたいということです。Capistraoでは、以下のコマンドを実行することがこれに相当します。

$ cap staging login

このコマンドを「タスク」として実装する方法を見ていきましょう。

Capistranoは、デフォルトでlib/capistrano/tasksディレクトリのファイルを読み込みます。このディレクトリの下にlogin.rakeというファイルを作成します。*.rakeという拡張子は、Railsタスクを含むRubyファイルでよく使われます。Rakeはdescというメソッドを提供しますが、これはタスクにdescription(詳細)を追加できます。記述したdescriptionは、すべての有効なタスクを表示するcap --tasksを実行すると表示されます。Rakeにはtaskというメソッドもあり、これはタスクの振る舞いを説明するのに用いられます。以下のようにdescriptionの直後にtaskブロックを置きます。この新しい:loginタスクは取りあえず空にしておきます。

# lib/capistrano/tasks/login.rake

desc "Login into a server based on the deployment stage"
task :login do
  ...
end

このままではloginタスクで何も行われません。ログインするには、アプリケーション・サーバーのログイン情報を集める必要がありますが、ここで「ロール(role)」という概念についてお話ししておく必要があります。

Capistranoのロール

Capistranoでは、どのタスクをどのサーバーで実行すべきかをきめ細かに制御するためにロールという概念を用います。たとえば、データベースサーバーにのみ適用したいが、Webサーバーにデプロイするときはスキップしたいタスクがあるとします。ロールは、ちょうどフィルタのように動作するので、特定のロールにマッチするサーバーでタスクを実行する前に、同じロールでひとまとめにグループ化するようCapistranoに指示できます。

Capistranoタスクでは、以下の3つのロールがよく使われます。

appロール
アプリケーションサーバー(コンテンツを動的に生成するサーバー)で実行するタスクに用いる。これは、Railsではpumaサーバーに相当する。
Capistranoの組み込みタスクのうち、deploy:checkdeploy:publishingdeploy:finishedはいずれもappロールで実行される。
dbロール
データベースサーバーで実行するタスクに用いる。
例: capistrano-railsプラグインが提供するdeploy:migrate(Railsデータベーススキーマのマイグレーションに用いられる)。
webロール
静的コンテンツを提供するWebサーバーを扱うタスクに用いる。
Nginxの場合、コミュニティの作ったcapistrano3-nginxプラグインではnginx:startnginx:reloadnginx:site:disableといったタスクはすべてwebロールを用いる。

もちろん独自のロールも定義できます。例: Redisデータベースインスタンスに関連するタスクのみを実行するのに用いるredisロール。

role :redis, "redis-server"

サーバーに指定されているロールの種類にかかわらず、タスクをあらゆるサーバーにマッチさせたい場合はallロールを用います。Capistranoの組み込みタスクでは、柔軟性を保つためにallロールが用いられています。

ここでは「アプリケーション」「Webサーバー」「データベースサーバー」をホストする1台のコンピュータにデプロイすることにし、サーバーにappロールとdbロールとwebロールをすべて適用するために、serverメソッドによるショートハンド定義を用います。

# config/deploy/staging.rb
server "staging-server.example.com", roles: %w[app db web], primary: true

上の定義をよく見ると、primary:というプロパティがあることに気が付くでしょう。このプロパティは、タスク実行時の優先順位をCapistranoに通知します。primary: trueを指定したサーバーに関連付けられているロールを持つタスクは、最初に実行されます。これは、アプリケーションを多数のホストに分割している場合に特に便利です。そのような場合は、以下のようにロール中心の定義に組み換えられます。

# config/deploy/staging.rb

role :app, "app-server.example.com"
role :db, "db-server.example.com", primary: true
role :web, "static-server.exmaple.com"

ここで定義したロールをタスク定義に適用する方法については後述します。

ホストの設定ファイルを取得する

サーバー設定ファイルにアクセスするには、rolesヘルパーメソッドを用います。rolesメソッドはロール定義を1つ以上受け取り、マッチするすべてのホストのインスタンスをリストにして返します。ここでは:appロールを指定すると、Capistrano::Configuration::Serverという1台のサーバー設定インスタンスだけがリストに含まれます。

Capistrano 3ではさまざまなイノベーションが行われていますが、よりモジュール性の高いアーキテクチャの導入もそのひとつです。Capistrano 3では、sshセッション管理もRubyのsshkit gemという別の依存関係に移行しました。SSHKitが提供するDSLを用いるとonというメソッドが導入され、ブロックスコープで記述したコマンドをさまざまなサーバーで実行できます。onメソッドはホスト設定オブジェクトの配列を受け取ると、SSHKit::Coordinatorを用いてコマンドをホストごとにパラレル実行します。

# lib/sshkit/dsl.rb

module SSHKit
  module DSL
    def on(hosts, options={}, &block)
      Coordinator.new(hosts).each(options, &block)
    end
  end
  ...
end

設定済みホストが複数ある場合は、おそらく次のようにin: :sequenceを用いて各サーバーに順番にログインするようCapistranoに指示する方がよいでしょう。

on roles(:app), in: :sequence do |server|
  ...
end

onのスコープ内では、サーバーに関する情報を含むホストインスタンスにアクセスできるようになります。本質的に「このサーバーで以下の作業を行え」と指示するのと同じです。

# lib/capistrano/tasks/login.rake

desc "デプロイのステージに応じてサーバーにログインする"
task :login do
  on roles(:app) do |server|
    ...
  end
end

Capistranoの変数

サーバーにssh接続する場合、「ユーザー名」「サーバー名」「ログイン先のパス」を知る必要があります。Capistranoではsetメソッドが提供されています。グローバルなタスクや特定のタスクを設定する変数をこれで設定すると、スクリプトの他の部分でも利用できるようになります。たとえば、以下のように:user変数や:deploy_to変数を設定できます。

# config/deploy/staging.rb

set :user, "deploy-user"
set :deploy_to, "/path/to/deploy/directory"

Capistranoが提供するfetchメソッドを使えば、設定変数を楽に読み出せます。たとえば、以下のようにユーザー名やデプロイパスを取得できます。

# lib/capistrano/tasks/login.rake

desc "デプロイのステージに応じてサーバーにログインする"
task :login do
  on roles(:app) do |server|
    user = fetch(:user)
    path = fetch(:deploy_to)
    ...
  end
end

これでユーザー名とログイン先パスを取得できたので、sshコマンドユーティリティに引数を渡してURIを組み立てられるようになります。URI文字列は「ユーザー名」「サーバー名」「ポート番号」を結合してビルドします。以下のコードでは、ユーザー名やポート番号が指定されていない場合を扱います。

# lib/capistrano/tasks/login.rake

desc "デプロイのステージに応じてサーバーにログインする"
task :login do
  on roles(:app) do |server|
    user = fetch(:user)
    path = fetch(:deploy_to)

    uri = [
      user,
      user && '@',
      server.hostname,
      server.port && ":",
      server.port
    ].compact.join
  end
end

sshコマンド

これで、sshコマンドにURIとデプロイパスを渡して実行できるようになりました。先に進む前にssh周りについて軽く補足しておきます。

特に-tフラグはsshユーティリティに「teletypeモード」で実行するよう指示します。teletypeモードとは何でしょう?Capistranoは、デフォルトではssh接続でのデプロイ時にビジュアルターミナルをアタッチしません。誰も見ようがないので、洗練されたインターフェイスを表示する必要はないということになります。しかし本記事では、ユーザーがシェル機能をすべて利用できるようにしておきたいと思います。

-tフラグに続けてサーバーのURIを指定し、続いて、ログイン後に1回実行するシェルコマンドを引用符で囲みます。ここでは2つのシェルコマンド文字列をつなげて使います。1つ目はWebサイトのルートディレクトリに移動するコマンド、2つ目は$SHELL変数の実行です。この$SHELLは、デフォルトシェルの位置を表す環境変数で、通常はBashになっています。シェルをわざわざこの形で起動する必要がある理由は何でしょうか?sshは、シェルを実行しないと接続を終了してログアウトしてしまいます。このタスクでやりたいのはログインを継続することなのです!

bashを指定してシェルを実行する場合、-lフラグを指定すると、ユーザーがログインしているかのようにシェルを呼び出します(対話モード)。ここでは、.bash_profileなどの隠しファイルにある設定をすべて事前に読み込んでおくために使っています。

最終的なコマンド文字列は以下のようになります。

"ssh -t #{uri} 'cd #{path}/current && exec $SHELL -l'"

Rubyのexecを用いて、sshコマンドをローカルで実行します。

We use Ruby’s exec to run our ssh command locally:

exec("ssh -t #{uri} 'cd #{path}/current && exec $SHELL -l'")

以上をまとめると、以下のようなタスクのできあがりです。

desc "Login into a server based on the deployment stage"
task :login do
  on roles(:app) do |server|
    user = fetch(:user)
    path = fetch(:deploy_to)

    uri = [
      user,
      user && '@',
      server.hostname,
      server.port && ":",
      server.port
    ].compact.join

    exec("ssh -t #{uri} 'cd #{path}/current && exec $SHELL -l'")
  end
end

ここまでは、自分のコンピューターからサーバーに手早くログインする方法について説明しました。しかしCapistranoで日々の作業を強化する方法は、これだけではありません。

リモートのRailsコンソール

私の場合、サーバーのRailsコンソールを取りあえず開いて、今動いているデータベースにクエリをかけなければならなくなることがよくあります。CapistranoのログインタスクでログインしてからRailsコンソールを開くコマンドを手打ちしてもいいのですが、いっそproductionのRailsコンソールを一発で開くCapistranoスクリプトを使ったらどうでしょう?以下のようなコマンドを実行してやれるようにするというイメージです。

$ cap production console

上のコマンドを実装するには、以下の「ベアボーン」:consoleタスクとdescriptionを、同じlogin.rakeファイルの中に書きます。

# lib/capistrano/tasks/login.rake

desc "デプロイのステージに応じてRailsコンソールを開く"
task :console do
    ...
end

ログインタスクのときと同様、「ユーザー名」「デプロイのパス」を知る必要があります。Railsコンソールを開く場合は、現在Railsを実行している環境の名前も必要になります。これはcapistrano-rails gemで設定される:rails_env設定変数から取れます。

# lib/capistrano/tasks/login.rake

desc "デプロイのステージに応じてRailsコンソールを開く"
task :console do
  on roles(:app) do |server|
    env = fetch(:rails_env)
    user = fetch(:user)
    path = fetch(:deploy_to)
    ...
  end
end

Railsの実行可能ファイルを探索する

sshでRailsコンソールを開く前に、もうひとつ対処が必要になります。そのままではsshの仮想ターミナルにアクセスできないので、ユーザープロファイルの設定ファイルを読み込めません。そこで、どうにかしてCapistranoがrails実行可能ファイルを探索し、そしてインストールされているRubyを探索できるようにする必要があります。私はRubyのインストールをrbenvユーティリティで管理し、gemのインストールをBundlerで管理するのが好みです。他の方法でRubyを管理している方は、適宜パスを調整してください。

rbenvのパスについては、デプロイするユーザーの$HOME/.rbenvで指定されるローカルのインストール場所を用いることにします。rbenvの「shims」という概念は、実際にインストールされているgemirbrailsなどのRubyコマンドをrbenv execコマンドに対応付けるのに使われます。railsコマンドを実行する場合、shimをbundleして、必要な依存関係をすべて読み込むようにする必要があります。利用できるbundle実行ファイルは$HOME/.rbenv/shimsの中にあります。

最後の手順として、-eフラグでrails consoleコマンドに現在の環境の情報を伝えます。Railsコンソールを開く完全なコマンドは以下のようになります。

console_cmd = "$HOME/.rbenv/shims/bundle exec rails console -e #{env}"

パズルの最後のピースがはまったので、以下でRailsアプリケーションにssh接続してRailsコンソールを開けるようになります。

"ssh -t #{uri} 'cd #{path}/current && #{console_cmd}'"

最後は、いよいよひとつのスクリプトにまとめてみましょう。

desc "デプロイのステージに応じてRailsコンソールを開く"
task :console do
  on roles(:app) do |server|
    env  = fetch(:rails_env)
    user = fetch(:user)
    path = fetch(:deploy_to)

    uri = [
      user,
      user && '@',
      server.hostname,
      server.port && ":",
      server.port
    ].compact.join

    console_cmd = "$HOME/.rbenv/shims/bundle exec rails console -e #{env}"

    exec("ssh -t #{uri} 'cd #{path}/current && #{console_cmd}'")
  end
end

重複の除去

本記事を鵜の目鷹の目で読んでいる方であれば、タスクとタスクの間に少々冗長な繰り返しがあることにお気づきかと思います。重複を退治してタスクを強化し、メンテナンス性を高ることにしましょう。私は、コードの繰り返し部分をいったん「完全な繰り返し」に揃えてから重複を取り除くのが好みです。こうすることで、コード内のまったく同じ部分がきれいに浮かび上がってくるからです。今回の場合、Railsの環境変数や実行するsshコマンドの部分を別にすれば、タスクは完全に同一です。

重複削除の精神に則って、セットアップ部分やsshコマンド実行部分をrun_ssh_withという独自のメソッドに移動することにします。このメソッドは、サーバー設定と実行すべきコマンドを引数に取ります。

# lib/capistrano/tasks/login.rake

def run_ssh_with(server, cmd)
  user = fetch(:user)
  path = fetch(:deploy_to)

  uri = [
    user,
    user && "@",
    server.hostname,
    server.port && ":",
    server.port
  ].compact.join

  exec("ssh -t #{uri} 'cd #{path}/current && #{cmd}'")
end

run_ssh_withにまとめたおかげで、どちらのタスクもシンプルになりました。

# lib/capistrano/tasks/login.rake

desc "デプロイのステージに応じてサーバーにログインする"
task :login do
  on roles(:app) do |server|
    run_ssh_with(server, "exec $SHELL -l")
  end
end

desc "デプロイのステージに応じてRailsコンソールを開く"
task :console do
  on roles(:app) do |server|
    env  = fetch(:rails_env)
    console_cmd = "$HOME/.rbenv/shims/bundle exec rails console -e #{env}"
    run_ssh_with(server, console_cmd)
  end
end

随分見違えましたね!しめくくりに、あらゆるキー入力を短く済ませたがるプログラマーの本性をなだめることにしましょう。2つのタスクにはエイリアスを作成できるのです!rakeそのものにエイリアス機能はありませんが、それらしくすることは可能です。その方法とは、実行内容が最初に別のタスクに依存する新しいタスクを定義することです。2つのタスクを1文字に短縮してみましょう。

# lib/capistrano/tasks/login.rake

task :c => :console
task :l => :login

まとめ

怒涛のようなCapistranoツアーが終わりました。Capistranoスクリプトを書いたことのない人は本記事で多くのことを学べます。願わくば、Capistranoスクリプトを書いたことがある方にとっても機能のいくつかが明確になることでしょう。本記事では、Capistranoタスクのしくみ、変数の設定方法、コマンドをローカルで実行する方法について一般的なことを学べます。

ログインタスクとコンソールタスクを使えば、繰り返しに満ちた作業を手軽に自動化できます。タスクにすることで「Railsプロジェクト間の作業を統一できる」という重要なオマケもあります。リモートサーバーにクエリをかけるのにプロジェクトファイルを急いで開いて接続情報を知る必要がなくなります。ひとつひとつのタスクによる効率化はささやかですが、時間をかけてタスクを蓄積していけばスムーズな開発フローを得られます。同じ用に便利なCapistranoタスクをご存知の方がいらっしゃいましたら、ぜひ共有してください!

私はPiotr Murachと申します。これまで蓄積したプログラミング経験をドキュメント化し、日々の作業を改善できる実用的なコード例とともに皆さんにご紹介しています。私のブログ記事やオープンソースプロジェクトをお楽しみいただけましたら、GitHub Sponsorsで応援お願いします。ニュースレターRSSフィードもぜひどうぞ。

関連記事

productionやdevelopment、stagingという言葉の使い分けについて

週刊Railsウォッチ(20210315前編)Active Recordのenum関連改修、Active SupportのEnumerableでpluckが使えるほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

以下のコミットリストのChangelogを中心に見繕いました。

🔗 config.action_text.attachment_tag_nameが追加

Railsフォーラムで、Action TextのHTMLタグ名を変えたいと思う人たちがいる。今はデフォルトではaction-text-attachmentとなっている。現時点でそうする方法のひとつは、リッチテキスト形式の出力をパースしてnokogiriでタグ名を変更すること。
このプルリクは、action-text-attachmentを任意の文字列に変えられるconfig.action_text.attachment_tag_nameオプションを追加する。
同PRより大意


つっつきボイス:「従来のActionText::Attachment::TAG_NAMEは名前がaction-text-attachmentに固定されていたのをユーザーがコンフィグで名前を変えられるようにしたのか↓」「Action Text周りでは最近こんな感じの変更がちょくちょく入ってるようですね」「コンフィグで変えられるものが増えるのはいいと思います👍

# actiontext/app/helpers/action_text/content_helper.rb#L5
module ActionText
  module ContentHelper
    mattr_accessor(:sanitizer) { Rails::Html::Sanitizer.safe_list_sanitizer.new }
-   mattr_accessor(:allowed_tags) { sanitizer.class.allowed_tags + [ ActionText::Attachment::TAG_NAME, "figure", "figcaption" ] }
+   mattr_accessor(:allowed_tags) { sanitizer.class.allowed_tags + [ ActionText::Attachment.tag_name, "figure", "figcaption" ] }
    mattr_accessor(:allowed_attributes) { sanitizer.class.allowed_attributes + ActionText::Attachment::ATTRIBUTES }
    mattr_accessor(:scrubber)
...
# actiontext/lib/action_text/attachment.rb#L6
  class Attachment
    include Attachments::TrixConversion, Attachments::Minification, Attachments::Caching

-   TAG_NAME = "action-text-attachment"
-   SELECTOR = TAG_NAME
+   mattr_accessor :tag_name, default: "action-text-attachment"
+
    ATTRIBUTES = %w( sgid content-type url href filename filesize width height previewable presentation caption )
...

🔗 enumの予約済みオプション名に_を付けずに書けるようになった


つっつきボイス:「これは今週のPRではありませんが、以下の記事で知りました↓」

参考: Rails introduces new syntax for enum | Saeloun Blog

「なるほど、enumの引数受け取りをenum(attr_name, ..., **options)とすることで、オプションを最後に付ければアンダースコア付きの_prefix_scopesなどではなくアンダースコアなしのprefixscopesなどを使えるようにしたのか」「アンスコなしで書けるのは嬉しい👍

Attribute APIに組み込まれている他の機能と違い、enumの予約済みオプション名の冒頭には_が付いている。

_付きだった理由は、enumがハッシュ引数を1個しか受け取らないため(このハッシュ引数はenumの定義と予約済みオプションの両方を含む)。

enumの予約済みオプション名でこの_を避けるために、他のAttribute APIの構文のようなenum(attr_name, ..., **options)を使える新しい構文をここに提案する。

変更前:

class Book < ActiveRecord::Base
  enum status: [ :proposed, :written ], _prefix: true, _scopes: false
  enum cover: [ :hard, :soft ], _suffix: true, _default: :hard
end

変更後

class Book < ActiveRecord::Base
  enum :status, [ :proposed, :written ], prefix: true, scopes: false
  enum :cover, [ :hard, :soft ], suffix: true, default: :hard
end

同PRより大意

「既存の_付きオプションとの互換性も確保されているようですね↓」

-     default = {}
-     default[:default] = definitions.delete(:_default) if definitions.key?(:_default)
+     definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default)
+     options.transform_keys! { |key| :"#{key[1..-1]}" }

コメントを見ると、このリリースでは既存の書式をdeprecateにしないけど、cop(注: RuboCopのルール)でオートコレクトするのが難しくなければdeprecateすべきと書かれてるので、今後非推奨になるのかも」「たぶん最終的には_なしの書式が正式になるんでしょうね」「_付きの_prefix_suffixあたりは既に使っている人がいそう」「そういえば_prefix_defaultは使ったことあります」

参考: ActiveRecord::Enum

enumを本格的に使うようになると、_scope_defaultなどのオプションが欲しくなりますね: 特に_prefixが使えないと不便」「なるほど」「次の2つはこの#41328に関連していそうなenum関連のプルリクです」

🔗 Enum型の属性aggregationを修正


つっつきボイス:「summinimummaximumといった集約系の処理を修正したのか: テストコードが変わっている↓」「”aggregation”は集約関数の集約なんですね」

# activerecord/test/cases/calculations_test.rb#L1165
- def test_aggregate_attribute_on_custom_type
-   assert_nil Book.sum(:status)
-   assert_equal "medium", Book.sum(:difficulty)
-   assert_equal "easy", Book.minimum(:difficulty)
-   assert_equal "medium", Book.maximum(:difficulty)
-   assert_equal({ "proposed" => "proposed", "published" => nil }, Book.group(:status).sum(:status))
-   assert_equal({ "proposed" => "easy", "published" => "medium" }, Book.group(:status).sum(:difficulty))
-   assert_equal({ "proposed" => "easy", "published" => "easy" }, Book.group(:status).minimum(:difficulty))
-   assert_equal({ "proposed" => "easy", "published" => "medium" }, Book.group(:status).maximum(:difficulty))
+ def test_aggregate_attribute_on_enum_type
+   assert_equal 4, Book.sum(:status)
+   assert_equal 1, Book.sum(:difficulty)
+   assert_equal 0, Book.minimum(:difficulty)
+   assert_equal 1, Book.maximum(:difficulty)
+   assert_equal({ "proposed" => 0, "published" => 4 }, Book.group(:status).sum(:status))
+   assert_equal({ "proposed" => 0, "published" => 1 }, Book.group(:status).sum(:difficulty))
+   assert_equal({ "proposed" => 0, "published" => 0 }, Book.group(:status).minimum(:difficulty))
+   assert_equal({ "proposed" => 0, "published" => 1 }, Book.group(:status).maximum(:difficulty))
  end

参考: PostgreSQL 12.4文書9.20. 集約関数
参考: MySQL :: MySQL 5.6 リファレンスマニュアル :: 12.19.1 GROUP BY (集約) 関数

「たとえばテストコードのBook.group(:status).sum(:difficulty))は従来だと{ "proposed" => "easy", "published" => "medium" }を返していたけど、修正後は{ "proposed" => 0, "published" => 1 }を返すようになった: enumが設定されたカラムで#groupしたときの挙動を修正したんですね」

issue #39248#39271を修正するため、#39255#39274では集約結果をattributeの型ごとにキャストするようにした。
しかし#41431を実装したときに、enumのマッピングを回避できることに気づいた。
今回の変更によって、#39039のexpectationが復帰する(特にEnumの場合)。
修正対象: #41600
同PRより大意

🔗 enum attributeに対するserialize(value)がサブタイプごとにキャストされるよう修正


つっつきボイス:「これもenumのattribute関連ですね」「この#39039を含むChangelogが別プルリクになっていたので併記しました↓」「SQLite3ではnilが返り、MySQLとPostgreSQLではエラーになってたのが修正されたんですね」

enum値を元のattributeの型で型キャストするようになった。
注目すべき変更点は、不明なラベルがMySQLの0にマッチしなくなったこと。

class Book < ActiveRecord::Base
  enum :status, { proposed: 0, written: 1, published: 2 }
end

変更前:

# SELECT `books`.* FROM `books` WHERE `books`.`status` = 'prohibited' LIMIT 1
Book.find_by(status: :prohibited)
# => #<Book id: 1, status: "proposed", ...> (for mysql2 adapter)
# => ActiveRecord::StatementInvalid: PG::InvalidTextRepresentation: ERROR:  invalid input syntax for type integer: "prohibited" (for postgresql adapter)
# => nil (for sqlite3 adapter)

変更後:

# SELECT `books`.* FROM `books` WHERE `books`.`status` IS NULL LIMIT 1
Book.find_by(status: :prohibited)
# => nil (for all adapters)

Ryuta Kamizono
同Changelogより大意

🔗 RAILS_DEVELOPMENT_HOSTS環境変数のサポートを追加


つっつきボイス:「RAILS_DEVELOPMENT_HOSTSとは?」「なるほど、ホスト名でアクセス制限するHostAuthorizationで許可済みのホストを、development環境でのみ環境変数からも渡せるようになったんですね↓」「言われてみれば欲しいときがありそう」「現状ではRAILS_ENV=productionでは効かないようなので注意ですね」

# railties/lib/rails/application/configuration.rb#L28
      def initialize(*)
        super
        self.encoding                            = Encoding::UTF_8
        @allow_concurrency                       = nil
        @consider_all_requests_local             = false
        @filter_parameters                       = []
        @filter_redirect                         = []
        @helpers_paths                           = []
        @hosts                                   = Array(([".localhost", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")] if Rails.env.development?))
+       @hosts.concat(ENV["RAILS_DEVELOPMENT_HOSTS"].to_s.split(",").map(&:strip)) if Rails.env.development?
        @host_authorization                      = {}

Rails API: ActionDispatch::HostAuthorization

「CI連携で動作確認をプルリク単位で用意するようなHeroku Review Appsのなど環境を、ログ出力の関係などからRAILS_ENV=developmentで動かしたいようなケースでは、環境変数から許可済みホスト名を渡したいというケースがありそうですね: これまでは必要になったら自分でそういうコードを書いていたでしょうけど、Railsが用意してくれるならそれを使いたい👍」「なるほど!」

参考: 環境変数 - Wikipedia

🔗Rails

🔗 Active Recordのconcernをテストする(Ruby Weeklyより)


つっつきボイス:「ActiveSupport::Concernじゃないのかなと思ったら、Active RecordにincludeするActiveSupport::Concernモジュールが対象なのね」

参考: ActiveSupport::Concern

「Active Recordのconcernそのものをテストするには、たしかに記事にもあるようにFakeReviewableのようなfakeのクラスを作ることになるでしょうね↓」

# 同記事より
require_relative 'path/to/reviewable/shared/examples'

class FakeReviewable < ApplicationRecord
  include Reviewable
end

describe Reviewable do
  include InMemoryDatabaseHelpers

  switch_to_SQLite do
    create_table :fake_reviewables do |t|
      t.datetime :reviewed_at
    end
  end

  describe FakeReviewable, type: :model do
    include_examples 'reviewable' do
      let(:reviewable) { FakeReviewable.create }
    end
  end
end

「Rubyのモジュールは、ActiveSupport::Concernも含めて単体ではテストできないので、このようにモジュールをincludeするテスト用のfakeクラスを作ってテストするしかないでしょうね」「ふむふむ」

「これはテストにおけるfakeのパターンというヤツです」「fakeはテスト用語としてのfakeなんですね」「そうです」

参考: xUnit Test PatternsのTest Doubleパターン(Mock、Stub、Fake、Dummy等の定義) - 千里霧中

「テスト関連の記事にfakeとかdoubleという語が出てきたときに用語だとわかりにくくて😅」「慣れれば大丈夫です: テスト関連ドキュメントの文脈でたとえばfakeという語が出てくれば、それはテスト用語としてのfakeのはずです」「なるほど」「fakeやdoubleのような一般的な語が用語に使われるのは、コンピューターサイエンスだと割とあります」「こういう用語はそのつもりで読まないといけないんですね」

morimorihoge注)

専門用語は理解できる人たちにとっては解釈の揺れのない便利な用語なのですが、理解できない人たちにとっては意味不明だったりミスリードを誘ってしまう厄介なものでもあります。
# 類似のもので代表的なのはプロセスにSIGNALを送信するkillコマンドですね

専門用語を知らない人にも分かるよう説明することももちろん大事ですが、技術者同士の会話では専門用語を使う方がより短い時間で精度の高い情報が伝えられるという要素もあるので、こうした技術用語の脳内辞書を育てておくのはエンジニアとして大事なポイントかもしれません。

🔗 Railsセットアップ時に既存データを永続化するときの注意

Everyday Railsサイトの技術ブログです。


つっつきボイス:「Railsのセットアップで注意すべき点に関する記事だそうです」

新しい開発者がプロジェクトに参加しやすくできるよう、Railsアプリのbin/setupスクリプトを活用していますか?そうなっていないのであれば、セットアッププロセスをできるだけ自動化しておく価値があると思います。rails newで提供されているデフォルトのbin/setupは最初に手を付けるのに手頃です。そうしておけば今後参加する開発者の貴重な初動時間を節約できますし、セットアップ手順の潜在的な抜け落ちも見つけられます。(bin/setupは)Visual Studio Codeでコンテナベースの開発環境を構築する際にも非常に便利です。
しかしbin/setupの利用には注意点があります。
同記事冒頭より大意

「なるほど、この記事に書かれているような問題はRailsで開発しているとときどきありますね: Railsアプリのプロジェクトをフルスクラッチでgit cloneしたときはbin/setupが完走するのに、開発を始めた後に「一度全部やり直したい」と思ってbin/setupするとdb:prepareあたりで既にDBが作られていてエラーになるといったことがあります」「あ、心当たりが…😅

「記事にもあるように、bin/setupのRubyコードを以下のように変更すれば↓、DBが存在していればdb:migrateを実行し、存在しなければdb:setupを実行するようになります」「へ〜、bin/railsコマンドにはdb:existsっていうオプションもあるんですね」

# 同記事より
puts "\n== Preparing database =="
system! 'bin/rails db:exists && bin/rails db:migrate || bin/rails db:setup'

後で調べると、bin/setupの該当部分はデフォルトでは以下のようになっていました(Rails 6.1.2)。

puts "\n== Preparing database =="
system! 'bin/rails db:prepare'

「たとえばCI(Continuous Integration)環境などでも、コンテナおよび依存する一時ファイルが存在していれば使い回し、存在しなければ新規で作成することを意識しないといけませんが、それに通じるものを感じますね」「なるほど」

🔗 Active SupportはEnumerablepluckを追加している

つっつきボイス:「お、再びboringrails.comの記事」「記事冒頭はActive Recordのpluckの使い方の説明、これは普通かな」「mapするよりpluckする方がいいというのはよく言われますね」「単にpluckすると結果が重複することがあるので、一意にしたければdistinct.pluckする、そうそう」

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

「おぉ、Active Supportにもpluckがあるって、マジで?」「やや」「Active SupportがEnumerablepluckを追加しているとは知らなかった!」

# 同記事より
[
  { id: 1, name: "David" },
  { id: 2, name: "Rafael" },
  { id: 3, name: "Aaron" }
].pluck(:name)
# rubydoc.infoより
[{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)
# => ["David", "Rafael", "Aaron"]

[{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name)
# => [[1, "David"], [2, "Rafael"]]

「これができるということはハッシュにもpluckできますね: 元記事でもJSON.parseで取得したハッシュにpluckを実行している」「おぉ〜!」「いいこと知りました!」「pluckEnumerableに生えてるのって夢が広がりそう」「Ruby 3.0ならパターンマッチでやりそうな処理ですが、それ以前のRubyでもEnumerable#pluckでやれるのはいい👍

🔗 その他Rails


つっつきボイス:「お、Rails.benchmarkをいろんな場所で呼べるようになったのか」「割と最近の修正ですね」

# 同記事より: 従来
mattr_accessor :logger, default: Rails.logger
extend ActiveSupport::Benchmarkable
include ActiveSupport::Benchmarkable

def process
  benchmark("=== Processing invoices ===") { process_invoices }
end

「従来だとモデルなどでbenchmarkを呼ぶときは上のようにextendincludeなどを使ったややこしい書き方をしないといけなかった↑けど、変更後は以下のようにRails.benchmarkのブロックに書けばできるようになったんですね↓」「なるほど!」「extendincludeなどを書かなくてもどこにでも書けるのは便利👍

# 同記事より: 変更後
def process
  Rails.benchmark("=== Processing invoices ===") do
    logger.debug("=== Processing started ===")

    process_invoices

    logger.debug("=== Processing done ===")
  end
end
=== Processing started ===
=== Processing done ===
=== Processing invoices === (400.7ms)

Rails.benchmarklevel: :debugsilence: trueを指定できるのも便利ですね↓」

def process
  Rails.benchmark("=== Processing invoices ===", level: :debug, silence: true) do
    logger.debug("=== This won't be logged ===")

    process_invoices

    logger.debug("=== This won't be logged ===")
  end
end

前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210309後編)RubyのIRBに隠れているイースターエッグ、Power Automate Desktop、SQLクエリのありがちなミス6つほか

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

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

Rails公式ニュース

Ruby Weekly

週刊Railsウォッチ(20210316後編)testdouble/standard gem、DockerfileベストプラクティスとDockerfileのlintツールhadolintほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Ruby

🔗 RuboCopがオーガニゼーションをgithub.com/rubocop-hqからgithub.com/rubocopへ移行(現在は完了)


つっつきボイス:「そうそう、これまでrubocop-hq/rubocopだったのがrubocop/rubocopに変わるそうですね🎉」「これまで他で取得されていたrubocopオーガニゼーションを昨年10月ぐらいに譲渡してもらったんですって」

従来の rubocop-hq オーガニゼーション経由でのアクセスでも、GitHub の方で良きにリダイレクトしてくれるのでユーザー影響はあまりないと思いますが、無駄なリダイレクトを挟むこともないので rubocop-hq になっている箇所があれば rubocop に指定し直すと良いです。
同記事より


その後見に行ってみると、rubocop-hqにあったリポジトリは3日ほど前にrubocopに移行完了していました(2021/03/16時点)。

特に設定を変えずに手元でgem updateしても問題なくRuboCopをアップデートできました。

...
Updating rubocop
Fetching rubocop-1.11.0.gem
Successfully installed rubocop-1.11.0
...

🔗 testdouble/standard: Rubyスタイルガイドとlinterとフォーマッタのセット(Ruby Weeklyより)

testdouble/standard - GitHub


つっつきボイス:「testdouble/standardは最近 1.0になったというのをどこかで見たかも」「先週取り上げようと思って漏れてました😅

このgemは、JavaScriptのstandard JSのエッセンスを移植したもので、以下の3とおりの方法で自分や他の人の時間を節約する。

  • コンフィグ不要: プロジェクトに投入するだけで統合スタイルを矯正する最も簡単な方法
  • コードのオートフォーマット: standardrb --fixを実行するだけでコードのばらつきとおさらば
  • スタイルの問題やプログラマーミスの早期発見: レビュアーやコントリビューターとのやりとりを減らして貴重なコードレビュー時間を節約
    同リポジトリより大意

「testdouble/standardはコンフィグ不要が売りのひとつなんですね」「testdouble/standardはRubyMineやVSCodeなどでも動かせるのか↓」「お〜」「testdouble/standardはRuboCopのラッパーなんですね」

「RuboCopがあるのにtestdouble/standardを使うのかという意見が出ることも予想されますけど、RuboCopは新しいルールがどんどん追加されていくので、あれについていくのが大変だと感じる人がいる気持ちもわかります」「たしかに」「Rubyで何か作ろうとすると、RuboCopを黙らせるのに時間がかかったりしますよね」「コンフィグ不要でやれるのはいい」「このツイート↓でも言っているように、RuboCopほどstrictにしないプロジェクトによさそう」


「そういえば最近rufoってどうなってるのかな?」「あ、最近触ってなかった😅

RuboCop作者がRubyコードフォーマッタを比較してみた: 前編(翻訳)

🔗 Ruby組み込みライブラリの型をRBSで書く

# 同記事より: 最終的な型定義
def self.chmod: (int mode, *(string | _ToPath) file_name) -> Integer

つっつきボイス:「永和システムマネジメントさんの技術ブログです」「Ruby 3.0のRBSが使われ始めているようですね」「この記事の組み込みライブラリはCで書かれたライブラリなのか」「RBSの記事がまだ少ないのでありがたいです🙏

ruby/rbs - GitHub

🔗 その他Ruby

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

🔗 Dockerfileのベストプラクティス Top 20


つっつきボイス:「はてブで知った記事です」「この記事はDockerfileのセキュリティを中心に解説しているのがいいですね: ざっと見た感じでは細かくなりすぎずにちゃんと書かれているようなので一度目を通しておくとよいと思います👍

「この記事ではGoogleが公開しているdistrolessについても解説されてますね↓」

GoogleContainerTools/distroless - GitHub

参考: 軽量Dockerイメージに安易にAlpineを使うのはやめたほうがいいという話 - inductor’s blog

「よく言われているように、Dockerではマルチステージビルドして実行バイナリだけを集めるのが理想で、Go言語のシングルバイナリのようにシンプルな実行バイナリならやりやすいんですけど、Railsなどで外部ライブラリに依存していてファイル数が多いとマルチステージビルドが大変なんですよね」「たしかに!」

参考: マルチステージビルドの利用 | Docker ドキュメント

「そういえば日本語版の記事も英語版と同じドメインで公開されていますね↓」

参考: Dockerfileのベストプラクティス Top 20 | Sysdig

🔗 Dockerfileのlinter: hadolint

「お、記事の末尾にhadolintというDockerfileのlinterが紹介されている↓」「ホントだ」「Haskell言語で書かれているそうです」

hadolint/hadolint - GitHub

「上のDockerfileベストプラクティス記事に書かれているようなチェックをhadolintでやれるのか」「hadolintのルールを見た感じではなかなかよくできていそう」「お〜」「メッセージのDL3000とかSC2002は何だろうと思ったらlinterのルール名だった」「SCはshell checkの略みたいだけどDLは何の略だろう?🤔

「hadolintのオンライン版もあるんですね↓」「これはなかなかよさそう👍」「Dockerfileのlinterはたしかにあってもいいですね」

参考: Dockerfile Linter


後で自分のDockerfileに試しにhadolintをかけてみたらだいぶ怒られました😅

hadolint .dockerdev/Dockerfile
.dockerdev/Dockerfile:10 DL3008 warning: Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
.dockerdev/Dockerfile:23 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
.dockerdev/Dockerfile:27 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
.dockerdev/Dockerfile:30 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
.dockerdev/Dockerfile:35 SC2046 warning: Quote this to prevent word splitting.
.dockerdev/Dockerfile:35 SC2002 style: Useless cat. Consider 'cmd < file | ..' or 'cmd file | ..' instead.
.dockerdev/Dockerfile:35 DL3005 error: Do not use apt-get upgrade or dist-upgrade
.dockerdev/Dockerfile:35 DL3008 warning: Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
.dockerdev/Dockerfile:35 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check

🔗 git cloneに脆弱性(StatusCode Weeklyより)


つっつきボイス:「gitの脆弱性、久しぶり」「自分も今brew upgrade gitで更新中です」

「この脆弱性はgit LFSに関連しているのか: git config --global core.symlinks falseでシンボリックリンクをオフにするとリスクを軽減できるとあるので、その部分がLFSで問題があったんでしょうね」「git LFSというと、動画やAdobe系ファイルのようなdiffがうまく取れない巨大なバイナリ系ファイルをgitリポジトリの外で管理するしくみでしたね」「BPSのアプリチームではgit LFSを使っているらしいです」

参考: Git LFS をちょっと詳しく - Qiita

🔗CSS/HTML/フロントエンド/テスト/デザイン

🔗 書籍『Web ブラウザセキュリティ』


つっつきボイス:「記事を書いているのはChromiumブラウザの開発に携わっている人だそうです」「Webセキュリティの新しい話題、特に最近のブラウザでやっているプロセス分離アーキテクチャについても詳しく解説されているのがよさそう👍

参考: Chromium - Wikipedia

「少し前の本ですが、この記事の中でも紹介されている『めんどうくさいWebセキュリティ』↓はBPS CTOのbabaさんが推していました」


「Webのセキュリティは、Web技術自体が歴史的に大きく進化していく中で建て増しを繰り返しているので複雑になりがちですね」「HTML5でだいぶリセットはかかった感じはありますけどね」「そういえばDOCTYPEにHTML 4.01 Transitionalとか書いてた頃もありましたよね」「そうそう」

参考: HTML5 - Wikipedia
参考: <!DOCTYPE>-HTMLタグリファレンス

「記事を見ていて、若手向けにWebの歴史も含めて詳しく解説してくれる本もあるといいなと思いました」「今のWebはなぜこうなっているのかを知ろうと思うと、歴史を調べることになりますよね」「歴史を完全に網羅しなくてもいいんですが、現在までにWebの歴史にどんなものが現れて何が消えていったかというざっくりした流れだけでも脳内地図を作れるといいですよね」「XMLが華々しく登場してその後JSONが台頭した流れとか」

参考: Extensible HyperText Markup Language - Wikipedia — XML
参考: JavaScript Object Notation - Wikipedia — JSON

🔗言語/ツール/OS/CPU

🔗 QEMUでARMエミュレータ


つっつきボイス:「プログラミング言語のSlackで、ARMのエミュレータを構築するにはQEMUしかないんでしょうかという質問にこのリンクが貼られていたので拾ってみました」「ARMのエミュレーションだけなら他にもありそうですけど、フリーで速度もそこそこ出せて、かつ広く使われていて実績があることを考えるとQEMUがやりやすいでしょうね: 特にARMのバージョンを切り替えてエミュレーションしたい場合とか」「なるほど」

参考: QEMU - Wikipedia

「こんなふうにgccに長いプレフィックスを指定してビルドするとか昔やりましたよ↓」「懐かしいですね」「クロスコンパイル用のgccは通常のgccと名前がかぶらないようにこんな名前になっていました」「Zaurus SL-C700のIntel XScaleで動かすためにクロスコンパイルしたことがあったのも思い出した」

# 同記事より
sudo apt-get install gcc-arm-linux-gnueabi

参考: GNUコンパイラコレクション - Wikipedia – gcc
参考: Zaurus - SL-C700|仕様表
参考: XScale - Wikipedia


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210315前編)Active Recordのenum関連改修、Active SupportのEnumerableでpluckが使えるほか

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

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

Ruby Weekly

StatusCode Weekly

statuscode_weekly_banner

SupervisorでDocker環境のRailsサーバを楽に再起動する

$
0
0

BPSの福岡拠点として一緒にお仕事をさせて頂いています、株式会社ウイングドアのタカキと申します。

ウイングドアでは複数のテーマの勉強会が定期的に開催されており、私は未経験であるRubyの勉強会に参加しています。
その際にDocker環境で試行錯誤した内容について書いていきたいと思います。

Rubyの話?Dockerの話?

本記事の内容はRubyをきっかけとしたDockerの話です。
「Docker環境のRailsサーバをコンテナを止めずに再起動する」ために迷走した記録になります。
結論としてはSupervisorを使用して実現しました。
Dockerについて有用な記事が多数ある中初歩的な内容で恐縮ですが、勉強の一環として見ていただけますと幸いです。

※Supervisorについては既にこちらの記事に大変詳しい内容が載っています。

Dockerでsupervisorを使う時によくハマる点まとめ

⚓Docker環境でRailsサーバを再起動するには?

環境

RubyのDocker環境構築は主にこちらを参考にさせていただき、シェルを実行するだけで設定ファイルの作成〜Railsサーバの起動まで完了するようにしました。

参考:Rails 6 + MySQL on Dockerの環境を秒速で構築する
参考:丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)

Ctrl+Cできない

チュートリアルを進めていく中で、Railsサーバを再起動する必要が出てきました。
Ctrl+Cでサーバ停止できるとのことですが、Dockerコンテナ起動と共にバックグラウンドでサーバを起動している場合はどこでCtrl+Cを押せば良いのだろう?と詰まってしまいました。

調べてみると、Docker環境ではコンテナ自体を再起動させるしかないようでした。

コンテナ再起動で無事進めることができましたが、その後も何度か再起動する度に
コンテナ再起動→docker execでコンテナに入り直す
という一手間が地味に面倒に感じるようになりました。

そこで、コンテナ接続中にコマンドでRailsサーバを再起動したいと思い色々試してみることにしました。

⚓プロセスのkill

Apache等とは違い、Railsサーバの停止・再起動のコマンドはないとのこと。
それならプロセスをkillして再度立ち上げればいいのではないかと思いました。

プロセスを確認します。

root@e7abaa0edd69:/test_app# ps aux
USER  PID %CPU %MEM VSZ  RSS TTY STAT START  TIME COMMAND
root  1 0.0 0.0  2384  636 ? Ss  09:25  0:00 /bin/sh -c rm -f tmp/pids/server.pid && bundle exec rails s -p 3900 -b '0.0.0.0'
root  7 1.4 5.1 1305396 105096 ? Sl  09:25  0:06 puma 4.3.7 (tcp://0.0.0.0:3900) [test_app]
root 68 0.2 0.1  5748 3364 pts/0 Ss  09:32  0:00 bash
root 73 0.0 0.1  9388 2956 pts/0 R+  09:32  0:00 ps aux

pumaというのがRubyのwebサーバなので、pid 7をkillします。

kill 7

コンテナが停止しました。😐

⚓init: trueの追加

※結果としてこの設定及びpidは無関係でした。

webサーバのプロセスだけkillしたつもりが、コンテナごと落ちてしまいました。

よく見ると起動時のコマンドがpid 1で動いています。

USER  PID %CPU %MEM VSZ  RSS TTY STAT START  TIME COMMAND
root  1 0.0 0.0  2384  636 ? Ss  09:25  0:00 /bin/sh -c rm -f tmp/pids/server.pid && bundle exec rails s -p 3900 -b '0.0.0.0'

Linuxにおけるpid 1はinitプロセスと言って特別らしい🤔 だからkillできなかったのでは? と考えました。
そのため起動コマンドをpid 1以外で起動させるためにdocker-compose.ymlにinit: trueの設定を追加しました。

  • docker-compose.yml
version: '3.7'
services:
  web:
    build: .
    init: true  # 追加
    command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3300 -b '0.0.0.0'"

再度プロセスを確認します。

root@e7b5f06e79c9:/test_app# ps aux
USER  PID %CPU %MEM VSZ  RSS TTY STAT START  TIME COMMAND
root  1 0.0 0.0 992  4 ? Ss  09:39  0:00 /sbin/docker-init -- /bin/sh -c rm -f tmp/pids/server.pid && bundle exec rails s -p 3900 -b '0.0.0.0'
root  6 0.0 0.0  2384  764 ? S 09:39  0:00 /bin/sh -c rm -f tmp/pids/server.pid && bundle exec rails s -p 3900 -b '0.0.0.0'
root  8 1.2 3.6 1090032 73864 ?  Sl  09:39  0:02 puma 4.3.7 (tcp://0.0.0.0:3900) [test_app]
root 52 0.0 0.1  5748 3648 pts/0 Ss  09:39  0:00 bash
root 74 0.0 0.1  9388 3052 pts/0 R+  09:41  0:00 ps aux

先程pid 1で動いていたコマンドがpid 6になっているので、設定が反映されていそうです。
pumaをkillしてみます。

kill 8

コンテナが停止しました。😑

⚓Supervisorを使用する

この後もtty: trueを設定してみたり起動用シェルを作って呼び出してみたりと迷走し、
Dockerは通常1コンテナにつき1プロセスが実行される
という基本的なことに気がつくまで随分かかりました。

docker-compose.ymlにて、docker起動時のcommandでbundle exec rails s -p 3300 -b '0.0.0.0'を実行しています。

command: /bin/sh -c “rm -f tmp/pids/server.pid && bundle exec rails s -p 3300 -b ‘0.0.0.0’”

これはもちろんRailsサーバとしてコンテナを立ち上げるためですが、そうして指定したRailsサーバのプロセスを殺しているのでコンテナも終了してしまうということでした。(改めて書くと当たり前過ぎてお恥ずかしい限りです…。)

そこでSupervisorを使い複数プロセスを起動できるようにしました。

設定ファイルの変更・追加

  • Dockerfile
# apt-getにsupervisorを追加
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs sudo nano vim psmisc supervisor && rm -rf /var/lib/apt/lists/*

(中略)

# supervisorの設定を追記
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

EXPOSE 80
CMD /bin/sh -c "rm -f tmp/pids/server.pid && /usr/bin/supervisord"
  • supervisord.conf
[supervisord]
nodaemon=true

[program:rails]
command=bundle exec rails s -p 3300 -b '0.0.0.0'
  • docker-compose.yml
version: '3.7'
services:
  web:
    build: .
    init: true
    # commandは使用しない
    #command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3300 -b '0.0.0.0'"

ビルド後のプロセス確認

supervisordからrailsが起動しています。
pumaをkillしてみます。

root@cbf135dc5728:/test_app# ps aux
USER  PID %CPU %MEM VSZ  RSS TTY STAT START  TIME COMMAND
root  1 0.0 0.0 992  4 ? Ss  11:21  0:00 /sbin/docker-init -- /bin/sh -c /bin/sh -c "rm -f tmp/pids/server.pid && /usr/b
root  7 0.0 0.0  2384  724 ? S 11:21  0:00 /bin/sh -c /bin/sh -c "rm -f tmp/pids/server.pid && /usr/bin/supervisord"
root  8 0.0 0.0  2384  744 ? S 11:21  0:00 /bin/sh -c rm -f tmp/pids/server.pid && /usr/bin/supervisord
root 10 0.2 1.0 27168 21136 ? S 11:21  0:01 /usr/bin/python2 /usr/bin/supervisord
root 13 1.7 3.8 1093432 77640 ?  Sl  11:21  0:08 puma 4.3.7 (tcp://0.0.0.0:3900) [test_app]
root 71 0.6 0.1  5748 3480 pts/0 Ss  11:29  0:00 bash
root 78 0.0 0.1  9388 2896 pts/0 R+  11:29  0:00 ps aux
kill 13

コンテナは停止していません🎉

再起動コマンド

直接killできるようになりましたが、その必要はなく
supervisorctl restart rails
を実行すればrailsのプロセスが再起動されます。

このrestartコマンドが使えるということもすぐには理解できず、プロセスkill+rails sするシェルを作成するなどしてもう一迷走したりしました…。🤦‍♀️

⚓シェルにまとめる

ということで、最終的に環境構築用のシェルはこのようになりました。
あれこれ手順を踏まずにコマンド1つで楽に環境作成できます。
※可読性・保守性はあまり良くありませんが、あくまで勉強用として作成しています。

  • init.sh
#!/bin/bash

#config setting#############
APP_NAME="test_app"
###########################

echo "docker pull ruby2.7.2"
docker pull ruby:2.7.2

echo "docker images"
docker images

if [ ! -e "Dockerfile" ]; then
  echo "make Dockerfile"
  cat <<EOF > Dockerfile
FROM ruby:2.7.2

ENV LANG C.UTF-8
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs sudo nano vim psmisc supervisor && rm -rf /var/lib/apt/lists/*

#yarnのセットアップ
RUN curl -o- -L https://yarnpkg.com/install.sh | bash
ENV PATH /root/.yarn/bin:/root/.config/yarn/global/node_modules/.bin:\$PATH

# 作業ディレクトリの作成、設定
ENV APP_HOME="/$APP_NAME"
RUN mkdir -p \$APP_HOME
WORKDIR \$APP_HOME

# ホスト側(ローカル)のGemfileを追加する
ADD ./src/Gemfile \$APP_HOME/Gemfile
ADD ./src/Gemfile.lock \$APP_HOME/Gemfile.lock

# Gemfileのbundle install
RUN bundle install
ADD ./src/ \$APP_HOME

# gem版yarnのuninstall
RUN gem uninstall yarn -aIx

# supervisor
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
RUN sed -i -e "$ a alias rbrestart='supervisorctl restart rails'" ~/.bashrc

EXPOSE 80
CMD /bin/sh -c "rm -f tmp/pids/server.pid && /usr/bin/supervisord"
EOF
else
  echo "Dockerfile already exists"
fi

if [ ! -d "src" ]; then
  mkdir src && cd src

  # Gemfileを作成、編集
  echo "make Gemfile"
  cat <<EOF > Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

gem 'rails', '~> 6.0.3'
EOF

  echo "make Gemfile.lock"
  touch Gemfile.lock

  cd ../
else
  echo "src already exists"
fi

# docker-compose.ymlを作成、編集
if [ ! -e "docker-compose.yml" ]; then
  echo "make docker-compose.yml"
  cat <<EOF > docker-compose.yml
version: '3.7'
services:
  web:
    build: .
    init: true
    volumes:
      - ./src/:/$APP_NAME
    ports:
      - '3900:3900'
EOF
else
  echo "docker-compose.yml already exists"
fi

# supervisord.confを作成、編集
if [ ! -e "supervisord.conf" ]; then
  echo "make supervisord.conf"
  cat <<EOF > supervisord.conf
[supervisord]
nodaemon=true

[program:rails]
command=bundle exec rails s -p 3900 -b '0.0.0.0'
EOF
else
  echo "supervisord.conf already exists"
fi

# rails newを実行する
echo "docker-compose run web rails new . --force --skip-bundle"
docker-compose run web rails new . --force --skip-bundle

# webpacker install
echo "docker-compose run web rails webpacker:install"
docker-compose run web rails webpacker:install

# コンテナをビルド・起動
echo "docker-compose up -d --build"
docker-compose up -d --build

init.shを実行するとこのようにファイルが配置されてwebコンテナが起動します。

ホームディレクトリ
├── init.sh
├── src
│   ├── Gemfile
│   └── Gemfile.lock
├── docker-compose.yml
├── Dockerfile
└── supervisord.conf

おまけ

少しでも労力を減らすためにaliasを追加してrbrestartと打てば再起動コマンドが実行されるようにしました。
これで楽に再起動ができます🙌

⚓まとめ

これまでDockerを使っていてもプロセスについてはあまり意識することがなかったので、理解を深めることができ良い機会になりました。
また、サクッとコンテナ作成・削除できるDockerの手軽さを改めて感じることもできました🐳

晴れて気軽に再起動できるようになったので、肝心のRubyも理解を深められるように引き続き精進していきたいと思います。
お読みいただきありがとうございました🙇‍♀️


株式会社ウイングドアでは、Ruby on RailsやPHPを活用したwebサービス、webサイト制作を中心に、
スマホアプリや業務系システムなど様々なシステム開発を承っています。


週刊Railsウォッチ(20210322前編)Active Recordのstrict loadingの修正、セキュリティリリースのポリシー追加、N+1チェッカーprosopite gemほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

以下のコミットリストから見繕いました。コミットは少なめで、Changelogの変更はありませんでした。

🔗 Active Recordでprecisionなしのnumericalityバリデーションを修正

#32852で追加されたテストケースはActive Modelではパスするが、#38210Float::DIGBigDecimal.double_figに変更されてからパスしなくなっていた。

irb(main):001:0> require 'bigdecimal/util'
=> true
irb(main):002:0> 65.6.to_d
=> 0.656e2
irb(main):003:0> 65.6.to_d(Float::DIG)
=> 0.656e2
irb(main):004:0> 65.6.to_d(BigDecimal.double_fig)
=> 0.6559999999999999e2

同PRより大意


つっつきボイス:「precision(DBの有効桁数)が指定されてないnumericalityバリデーションを修正したのね」「to_dはBigDecimalへの変換で、以前は桁数をFloat::DIGで15桁に指定していたのがBigDecimal.double_figに変わって桁数が違ってしまったのでFloat::DIGに戻したんですね」

# activerecord/lib/active_record/validations/numericality.rb#L4
  module Validations
    class NumericalityValidator < ActiveModel::Validations::NumericalityValidator # :nodoc:
      def validate_each(record, attribute, value, precision: nil, scale: nil)
-       precision = [column_precision_for(record, attribute) || BigDecimal.double_fig, BigDecimal.double_fig].min
+       precision = [column_precision_for(record, attribute) || Float::DIG, Float::DIG].min
        scale     = column_scale_for(record, attribute)
        super(record, attribute, value, precision: precision, scale: scale)
      end

Float::DIGBigDecimal.double_figはFloatの最大桁数の定数だけど、前者は最大の10進桁数で後者はFloatクラスが保持できる有効数字の数だから同じではないということか↓」

Float が表現できる最大の 10 進桁数です。通常はデフォルトで 15 です。

Ruby の Float クラスが保持できる有効数字の数を返します。

# docs.ruby-lang.orgより
require 'bigdecimal'
p BigDecimal::double_fig  # ==> 20 (depends on the CPU etc.)

「お〜、C言語では有効桁数をこうやって計算するのね↓」「これは参考になりそう」

// docs.ruby-lang.orgより
double v = 1.0;
int double_fig = 0;
while (v + 1.0 > 1.0) {
   ++double_fig;
   v /= 10;
}

🔗 Preloader::Association::LoaderQueryが追加


つっつきボイス:「今週はPreloader周りの改修がほかにもいくつかあったんですが、それぞれの関連がよくわからなかったのでこれを代表としてピックアップしてみました」

「このプルリクで引用されている#41385はこれか↓」「サマリーが長いですね…」

# 41385より
# ケース1: 親モデルは異なり、associationは同じ
ActiveRecord::Associations::Preloader.new(records: [book, post], associations: :author).call

# ケース2: 親モデルは同じで、同じテーブルに複数のassociationがある
ActiveRecord::Associations::Preloader.new(records: favorites, associations: [:author, :favorite_author]).call

「従来は以下のように?でプレースホルダ化されるクエリが上のどちらでも同じになっていたけど↓、同じクエリを2回実行するのは冗長なので#41385でクエリをグループ化して少し速くなり、#41597でそのロジックをLoaderQueryで整理したということみたい」「#41385にはベンチマークも貼られていますね」「#41597や#41385は、アプリ開発者が普段それほど意識することはなさそうかな: 普通に使っていれば恩恵を得られそう」

SELECT "authors".* FROM "authors" WHERE "authors"."id" = ?
SELECT "authors".* FROM "authors" WHERE "authors"."id" = ?

#41385のフォローアップ。
このクラスはPreloader::Associationがそのレコードを読み込むのに使うクエリを表す。これは従来のgrouping_keyの概念を置き換えて、Preloader::Associationをクエリごとにグループ化することとそのレコードを読み込むクエリの実行の両方に使われる(これで.firstをうまい具合に回避できる)。
この変更で機能は変わらないはずだが、不要なscope.to_sql呼び出しを回避できるのでほんのわずか速くなるはず。
同PRより大意

🔗 Active Recordのstrict loadingの修正


つっつきボイス:「strict loadingは最近Railsに入った機能だったかな(ウォッチ20200302)」「strict loadingは昨年3月頃に入ってたんですね」

「なるほど、モデル全体にstrict_loading_by_defaultを設定したうえで、個別のリレーションにstrict_loadingを設定した場合に、前者の設定だけが効いてしまうバグだったのか」「あ、そういうことですか」「詳細度は個別のリレーションに設定するstrict_loadingの方が高いので、モデルにデフォルトを設定するstrict_loading_by_defaultより優先されるべきということなんでしょうね」

「モデルではデフォルトでstrict loadingをオンにするけど特定のリレーションでオフにしたいときは、プルリクのコード例のように書くでしょうね↓」「リレーションのstrict_loading: falseが効くはずが効いてなかったのか、なるほど」


従来は、モデルのstrict_loading_by_defaultをtrueに設定している状態で、strict_loadingをfalseに設定すると、strict loadingに関するraiseやwarningを行うかどうかの決定でこのfalseが考慮されていなかった。

class Dog < ActiveRecord::Base
  self.strict_loading_by_default = true

  has_many :treats, strict_loading: false
end

上の例では、strict_loadingをfalseにしているにもかかわらずdog.treatsがraiseされてしまう。このバグはActive Storage以外にも影響するバグなので、#41461(closed)よりも広範囲に渡るPRにした。自分はこの挙動に少々驚かされたので、この問題はすべてのアプリケーションで解決する必要がある。
#41461のテストと#41453の提案を元にいくつかの追加を行った。
同PRより大意

🔗 ドキュメント: メンテナンスポリシーに追記


つっつきボイス:「Railsガイドの更新です」「Railsのメンテナンスポリシーはちょうどこの間話題にしたような気がしますね」

「なるほど、セキュリティリリースのポリシーのこの部分が明文化されたのか↓」「stableブランチとの関係についても解説されてますね」「Railsを最近始めた人にはありがたい情報🙏

セキュリティリリースには直接的なセキュリティパッチのみが含まれる。セキュリティパッチに起因する、セキュリティに関連しないバグの修正は、リリースのx-y-stableブランチで公開され、バグ修正ポリシーに基づいて新しいgemとしてのみリリースされる。
同PR更新部分より大意

🔗Rails

🔗 スライド: Ganbaranai wo ganbaru


つっつきボイス:「スライドのタイトルが2つあったので、埋め込みに出ていない短い方を見出しにしてみました」「がんばらないを頑張る」

「リリースを2段階に分けて、本番では通知だけ行って例外は握りつぶす↓、なるほど」「本番で動いているアプリのリリースではこういう感じでリリースすることもありますね」「いろいろ参考になりそう」

「このように本番で運用中のアプリをいかに障害を避けてリリースするかというノウハウは、ある程度運用の経験を積んでくるとありがたみがわかってきますね👍」「たしかに」

🔗 fast_blank: Active SupportのString#blank?を高速化するgem

SamSaffron/fast_blank - GitHub


つっつきボイス:「String#blank?をCで高速化するgemだそうです」「C拡張でやってるのか」「よく見るとfast_rubyを作ったのはSam Saffronさんでした」

# 同リポジトリより抜粋
================== Test String Length: 136 ==================
Calculating -------------------------------------
          Fast Blank   201.772k i/100ms
  Fast ActiveSupport   189.120k i/100ms
          Slow Blank   129.439k i/100ms
      New Slow Blank    90.677k i/100ms
-------------------------------------------------
          Fast Blank     16.718M (± 2.8%) i/s -     83.534M
  Fast ActiveSupport     17.617M (± 3.6%) i/s -     87.941M
          Slow Blank      3.725M (± 3.0%) i/s -     18.639M
      New Slow Blank      1.940M (± 4.8%) i/s -      9.702M

Comparison:
  Fast ActiveSupport: 17616782.1 i/s
          Fast Blank: 16718307.8 i/s - 1.05x slower
          Slow Blank:  3725097.6 i/s - 4.73x slower
      New Slow Blank:  1940271.2 i/s - 9.08x slower

「RubyのJITはこういう高速化に効きそうな気もしますけどね」「String#blank?は、Railsを使っててありがたいと思うRubyらしいメソッドだと思います」「そうですね」「PHPなどのように暗黙の型キャストをあてにして直接比較する方法よりも明示的に書けるのがいい👍」「オレオレblank?を定義しなくて済みますよね」

参考: Ruby 3.0のJIT変更解説 - Qiita

String#blank?はActive Supportのメソッドですが、Ruby本体に取り込まれてもよさそうな気がしました」「今のところRuby本体には取り込まれてないようですね」「Active SupportのメソッドがRuby本体に取り込まれることがたまにありますよね」「Active Supportのpresent?ならもうRubyに入っているかなと思ったけど入っていなかった」

後で調べると、to_procはActive SupportからRubyに取り込まれたそうです↓。

参考: 開発コアメンバが語るRubyの今とこれから(前編) - @IT

「ちなみにfast_blank gemは以下のgemから参照されていて知りました↓」「こちらはRustでString#blank?を高速化しているんですね」

malept/rusty_blank - GitHub

🔗 RuboCop Performance 1.10がリリース


つっつきボイス:「RuboCopがいくつかに分かれたうちのひとつがRuboCop Performanceですね」

rubocop/rubocop-performance - GitHub

Performance/RedundantSplitRegexpArgumentというcop(RuboCopのルール)が追加されてる↓」「区切り文字を,と指定するのに正規表現で/,/と書く必要はない、たしかに」「gsubなどのような文字列でも正規表現でも渡せるメソッドで、しかも完全一致を使うなら正規表現で書かなくてもいいんですよね」「そういえばgsubで無駄に正規表現を書いてしまったことありました😅」「文字列の方が正規表現よりも高速になるというのはRubyだとちょくちょくあります」

# 同記事より
# bad
'a,b,c'.split(/,/)

# good
'a,b,c'.split(',')

Performance/RedundantEqualityComparisonBlockというcopも@kamipoさんのリクエストで追加されていますね」「こちらは安全ではないとあるので-aオプションで自動修正されないヤツか」

# 同記事より
# bad
items.all? { |item| pattern === item }
items.all? { |item| item == other }
items.all? { |item| item.is_a?(Klass) }
items.all? { |item| item.kind_of?(Klass) }

# good
items.all?(pattern)

🔗 prosopite: false positive/negativeゼロをうたうN+1クエリチェッカー

charkost/prosopite - GitHub


つっつきボイス:「プロソパイト?」「造語かと思ったら鉱物の名前でした↓」

参考: Prosopite(プロソパイト)

「prosopiteは、よく使われているbullet gem↓のようなN+1クエリ検出gemなんですね」

flyerhzm/bullet - GitHub

「READMEに偽陽性と偽陰性がゼロって書かれてますけど、そんなことができるんですね」「N+1クエリが確実に発生している場合だけを検出するということは、bulletと検出方式が違うんでしょうね: 実際に発行されるSQLから検出するか、Rubyのコードから検出するかなど、方法はいくつか考えられます」「bulletでは検出できないN+1クエリも検出できるとも書かれてる」「確実に発生しているN+1クエリだけを検出できるならなかなかよさそう👍

# 同リポジトリより: レコード作成で発生するN+1
FactoryBot.create_list(:leg, 10)

Leg.last(10).each do |l|
  l.chair
end

prosopiteはActive Support instrumentationを用いてすべてのSQLクエリを監視し、すべてのN+1クエリケースに存在する以下のパターンを検出します。
「同じコールスタックと同じクエリフィンガープリントが複数のクエリに存在する場合」
同リポジトリより

参考: Active Support の Instrumentation 機能 - Railsガイド


Rails: Bulletで検出されないN+1クエリを解消する

🔗 その他Rails


つっつきボイス:「自分はRailsアプリをこう書く、というZennの記事を見つけました」「どれが正解かとかではなく、自分はこういうときにはこう書く、というポリシーをこうやって見えるところに書いておくのはいいですね👍

「Railsアプリをチームで開発するときに各メンバーの『自分はこういうときにこう書く』という嗜好が事前にわかっていると、それを踏まえた上で設計を議論できるので話が早くなる」「たしかに」「この人はこういう好みがあるから自分のこの設計にはたぶん反対するだろう、そしてそのうえでチームリーダーやプロジェクトの方針に合わせてもらう、というのは普段からよくやっています」

「長年一緒に組んで仕事をしてきたメンバーとならお互いの嗜好もわかっているのでいいんですが、新しく加わったメンバーだと設計の嗜好が最初のうちはなかなか見えてこないんですよ」「そうそう」「そうした部分が明文化されているとそのあたりが多少やりやすくなりそうですね」「こういう試みはもっと行われてもいいと思いました」


前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210316後編)testdouble/standard gem、DockerfileベストプラクティスとDockerfileのlintツールhadolintほか

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

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

Rails公式ニュース

週刊Railsウォッチ(20210323後編)GitHub Actionsで使えるruby/setup-ruby、中高生国際Rubyプログラミングコンテスト2020ほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Ruby

🔗 RubyのRBS: soutaroさんスライドとgem_rbs_collection


つっつきボイス:「soutaroさんのスライドです」「Ruby 3の静的型検査を開発した経緯などを解説しているんですね」

「お、Javaなどの言語にあるジェネリクス的な構文も検討していたのか↓」「コレクションの扱いを考えたらこの落とし所はわかる気がします」



参考: ジェネリックプログラミング - Wikipedia

Rubyスタイルガイドを読む: コレクション(Array、Hash、Setなど)

「スライドで以下のリポジトリも紹介されていました↓」「こちらはコレクションはコレクションでも別のコレクションですね」「gemsディレクトリを開くと、RailsのコンポーネントなどのRBSファイルが集められている」「RBSファイルはまだGitHubでシンタックスハイライトが効かないのか…」

ruby/gem_rbs_collection - GitHub

untypedな部分もまだありますね↓」「通常のRBSファイルの他にgeneratedファイルも置かれています」

# https://github.com/ruby/gem_rbs_collection/blob/main/gems/actionpack/6.0.3.2/actioncontroller.rbs#L95
module AbstractController::Callbacks::ClassMethods
  def before_action: (*untyped) -> void
  def around_action: (*untyped) -> void
  def after_action: (*untyped) -> void
  def skip_before_action: (*untyped) -> void
  def skip_around_action: (*untyped) -> void
  def skip_after_action: (*untyped) -> void
  def prepend_before_action: (*untyped) -> void
  def prepend_around_action: (*untyped) -> void
  def prepend_after_action: (*untyped) -> void
  def append_before_action: (*untyped) -> void
  def append_around_action: (*untyped) -> void
  def append_after_action: (*untyped) -> void
end

RubyのRBSについては『WEB+DB PRESS Vol.121』の「特集: Ruby 3」でもsoutaroさんが詳しい記事を書いています↓。

🔗 ruby/setup-ruby: ビルド済みRubyを5秒でセットアップ

ruby/setup-ruby - GitHub


つっつきボイス:「お、ruby/setup-rubyですね」「fork元はどこかな?」「あれ、リポジトリの左上に”generated from actions/javascript-action“って書かれてる」「generated fromってforked fromとどう違うんだろう?」

後で探すと、以下の記事でgenerated fromってforked fromの違いについて解説されていました。

参考: GitHubのTemplate Repository機能のすゝめ - Qiita

「このactionsはGitHub Actionsのことです↓」「あ、なるほど」

「ruby/ruby-setupにあるRubyのビルド済みバイナリをGitHub ActionsのCIなどで手軽に利用できるんですね」「以下のOSがサポートされてる↓」

OS 推奨バージョン その他のサポート対象バージョン
Ubuntu ubuntu-latest (= ubuntu-18.04) ubuntu-20.04, ubuntu-16.04
macOS macos-latest (= macos-10.15) macos-11.0
Windows windows-latest (= windows-2019) windows-2016

「READMEにあるGitHub Actions用yamlファイルのようにuses: ruby/setup-ruby@v1と書いてRubyのバージョンなどを指定するだけで使えるのか↓」「Ruby公式が提供しているのはありがたい🙏」「こうやって手軽に使えるのは嬉しいですね」

# 同リポジトリより
name: My workflow
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - uses: ruby/setup-ruby@v1
      with:
        ruby-version: 2.6   # .ruby-versionファイルは不要
        bundler-cache: true # 'bundle install'を実行してインストール済みgemを自動でキャッシュする
    - run: bundle exec rake

つっつきの後で、ruby/setup-ruby以前に使われていたactions/setup-rubyがアーカイブ化されたことを以下の記事で知りました。

こうして、晴れて全プラットフォームのVMでRubyコミュニティが提供する公式バイナリに一本化された後で、actions/setup-ruby はarchivedされました。引き続きactions/setup-ruby を使っても、ruby/setup-rubyに乗り換えても使われるバイナリは同じということになりますから、丁寧な移行ですね。action実行時にdeprecatedのメッセージが出るようになったので、これまで ruby/setup-ruby の存在を知らなかった開発者も乗換えをしているようです。VMのtoolcacheに入っているRubyバイナリは actions/setup-ruby を使わなければ自力でPATHを通さない限り使われることはないはずなので、今後新しくVMとして追加されるOSでは toolcacheにそもそもRubyが入らない(必要無い)ということになるかもしれません。
同記事より

actions/setup-ruby - GitHub

🔗 パッチモンスター中田さんインタビュー


つっつきボイス:「パッチモンスターの異名を持つnobuさんこと中田さんのインタビューです」「これだけ長い間継続的にRubyにコミットする活動を続けているのは本当に凄い」「Rubyの歴史のごく初期から活動していたんですよね」「そうそう、RubyKaigi↓などでRubyの歴史の話になるとたいていnobuさんの話も出ますね」

以下はつっつき後に見つけたツイートです。なお今年7月に予定されていたRubyKaigi 2021はキャンセルとなり、RubyKaigi Takeout 2021として秋にオンライン開催されます。

「Rubyのコミットログからコミットの動きとコミッターの活動をアニメーション化した動画がありましたよね、あそこにもnobuさんの姿がしょっちゅうあった覚えがあります」「前にウォッチで取り上げたことがありましたね」

後で過去記事を探しましたが当該動画がなくなっていたので、社内で教えてもらった以下のツイートを貼ります。


「ところで、このインタビュー記事を書いた中薗昴さんという方はシステムエンジニアと技術ライターの仕事を半分ずつやっていてすごいなと思いました」「技術記事は専門性が重要なのでそういう素養は大事ですね: 専門性が高まりすぎると読者が少なくなるのが悩みどころですが」

「技術に強いライターと言えば、CPU関連の濃い記事を昔からたくさん書いている大原雄介さんの記事はとてもアツいですよ↓」「お〜」「プロフィールを見るとハードウェア製作に携わってて、ある時期からライターに転じたんですね」

参考: Yusuke Ohara -Top Page-
参考: ASCII.jp:ロードマップでわかる!当世プロセッサー事情

「失敗したCPUについて大原さんが書いた記事は半端なく面白いので時間のあるときに読んでみるといいと思います」「これですね↓」

参考: 人気連載「忘れ去られたCPU黒歴史」の書籍が10日に発売! - 週刊アスキー

🔗 ブロックで設定するアレは何と呼ぶのか


つっつきボイス:「Rubyでコンフィグをブロックで設定する、ああこれのことですね↓」

# 同記事より
Foo.configure do |config|
  config.name = "foo"
  config.email = "foo@example.com"
end

「Railsのコンフィグもこういう感じで書けますよ」「あ、たしかに」「この記事ではそういうコンフィグを自分で書けるようにする方法が紹介されていますね」

参考: Rails アプリケーションを設定する - Railsガイド

# railsgudes.jpより
config.generators do |g|
  g.orm :active_record
  g.test_framework :test_unit
end

「この書き方に名前があってもいい気がしてきました」「結局『blockで設定するヤツ』になっちゃうかな😆」

「ちなみにこういう書き方ではRubyのクラスインスタンス変数が使われるので、マルチスレッドのアプリで使う場合は注意が必要です」「あ、そうか」

参考: クラス変数とクラスインスタンス変数を理解する - Qiita

「起動後にグローバル設定してその後変更されないならいいんですが、マルチスレッドで動かしながら設定値を更新すると死を招くので、Railsであれば設定をCurrentAttributesに入れるなどの対策を取る必要があります」「なるほど」「マルチスレッドで更新するときに気をつけるべきことなので、記事のコードの書き方は問題ありません」

「なお、クラスインスタンス変数を使わずにコンフィグそのものをインスタンス化するなど、他にも方法はいろいろ考えられます」

🔗 2021年のmruby情報


つっつきボイス:「最近のmruby事情がまとまっていてありがたい」「mrubyをこれから学ぶ人向けの情報があるのもいいですね👍」「紹介されている書籍も新しい↓」

🔗 その他Ruby


つっつきボイス:「中高生国際Rubyプログラミングコンテスト2020、しかもin Mitaka?」「Mitakaって東京の三鷹市?」「恥ずかしながら、このイベントを初めて知りました」「これは知らなかった」「しかも国際」「公式サイトを見ると2011年から毎年開催されているんですね↓」「サイトを見ると三鷹市が後援団体にリストアップされてるから確かに三鷹市だ」「三鷹市は以前からベンチャーの投資が盛んなことで知られていてインキュベーション施設もありますが、プログラミング教育にも力を入れているんですね」

参考: 中高生国際Rubyプログラミングコンテスト in Mitaka
参考: 三鷹市SOHOパイロットオフィス 施設概要 | 株式会社 まちづくり三鷹

以下はつっつき後に見つけたツイートです。

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

🔗 Graviton 2ベースのAmazon Aurora(Publickeyより)


つっつきボイス:「BPS社内Slackに貼られていたのを拾いました」「AWSが独自開発したArmベースのGraviton 2は、RDSでの利用だと比較的使いやすいのがいいですね👍: Graviton 2をEC2などで使おうとすると、Dockerイメージのバイナリ互換性などの問題がつきまとうので簡単に移行できませんが、TCPレベルで接続するRDSならプロセッサの種類はそれほど関係なくなるので」「なるほど」

参考: AWS、自身でプロセッサを開発していく姿勢を明らかに 独自開発の第二世代ARMプロセッサ「Graviton 2」発表:AWS re:Invent 2019 - ITmedia NEWS
参考: Amazon RDS(マネージドリレーショナルデータベース)| AWS

「何かあるとすれば、PostgreSQLやMySQLをカリカリにチューニングして動かそうとするとGraviton 2での最適化がまだあまり進んでいなくて思ったほどパフォーマンスが出ない、といったことはあるかもしれませんが、普通に使う分にはほぼ影響はないだろうと思います」「お〜」「機会があったら使ってみようかな」


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210322前編)Active Recordのstrict loadingの修正、セキュリティリリースのポリシー追加、N+1チェッカーprosopite gemほか

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

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

Publickey

publickey_banner_captured

Railsの使わない機能をオフにする(翻訳)

$
0
0

概要

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

Railsの使わない機能をオフにする(翻訳)

Railsフレームワークは、互いに協調動作するさまざまなサブシステムによって構成されています。これらのサブシステムは、多くの場合ActionまたはActiveという名前で始まります(Active Record、Active Support、Action Packなど)。

個別のコンポーネントgemは、以下のrails/railsリポジトリのディレクトリにあります。

rails/rails - GitHub

Railsが最初にリリースされて以来、新しい便利なライブラリが継続的に追加されてきました。たとえば、Action Cableを介したWebソケットのサポートの追加はRails 5の目玉機能でした。Railsに大規模な新しいサブフレームワークが次々に追加されたにもかかわらず、Rails 3でMerbがRailsにマージされて以来(当時はRubyのビッグニュースでした)、モジュラーな構造を保っています。

以下のようにするのではなく

Railsの機能をそのまま使う。

  • config/application.rb
require_relative "boot"

require "rails/rails"

# ...

以下のようにする

自分が欲しいRailsの機能だけを使う。

  • config/application.rb
require_relative "boot"

require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
# require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
# require "action_mailbox/engine"
# require "action_text/engine"
require "action_view/railtie"
# require "action_cable/engine"
# require "sprockets/railtie"
require "rails/test_unit/railtie"

# ...

私が立ち上げたプロジェクトでは、このアプローチをよく使います。

Railsアプリでメール受信機能(Action Mailbox)やリアルタイムWebSocket(Action Cable)を使う必要がないことは珍しくありません。

ファイルアップロード機能(Active Storage)やリッチテキスト機能(Action Text)には他のソリューションもあります。

さらに、最近はWebpackですべてをまかないたいこともあります。その場合はsprocketsをオフにする方が理にかなっているでしょう。

そうする理由

Railsスタックをスリムにする方が文字どおり少ないコードで済み、読み込みや実行が速くなります。どの程度高速化するかは、Railsで使う機能によって変わります。

使わない機能をオフにする主な理由は、「余分な設定を削ぎ落とす」「プロジェクトを整理して全般的な見通しをよくする」ためです。

使っていないサードパーティgemは削除する方がよいのと同様に、使っていないRailsの機能も削除するとすっきりすることがよくあります。

さらに、特定のRailsサブシステムを他のものに置き換えたい場合(Active Recordの代わりにrom-rbを使うなど)両方を使うのではなく、Active Recordを無効にする方がよい可能性もあります。

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

Railsはひとつに統合されたフレームワークです。Railsアプリがproduction環境で読み込まれた後の速度の向上は、ほとんど無視できる範囲です。

フルスタックのRailsコンポーネントは現場で実績を積んでいてメンテナンスも行き届いているので、たとえ個人的な好みに合わないとしても、ほとんどの場合まともなソリューションです。

アプリケーションから削除したサブフレームワークを後で再度追加すると、設定を適切に行うのがより面倒になる可能性もあります。

関連記事

Rails: URLヘルパーをビューやコントローラ以外の場所で使う(翻訳)

Rails: Rails.benchmarkメソッドをどこからでも呼び出せるようになった(翻訳)

$
0
0

概要

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

RailsでRails.benchmarkメソッドをどこからでも呼び出せるようになった(翻訳)

Railsの最新の変更によって、benchmarkメソッドを任意の場所から呼び出せるようになりました(#40734)。

変更前

変更前は、ビューやコントローラで以下のようにbenchmarkを呼べます。

<% benchmark '=== Processing invoices ===' do %>
  <%= process_invoices %>
<% end %>

しかし、モデルやサービスなどにあるメソッドのベンチマークを取りたいときは以下のように書く必要がありました。

require 'benchmark'

def process
  logger.info("=== Processing invoices ===")
  logger.info Benchmark.measure { process_invoices }
  # or
  logger.info Benchmark.realtime { process_invoices }
end

出力されるログは以下のようになります。

=== Processing invoices ===
0.406537   0.082624   0.489161 (  0.503110)
0.5047359999734908

特定のメソッドやコードの実行に要する時間を測定する場合は、次のように書けます。唯一の問題は、ベンチマークを行う必要が生じるたびにクラスにモジュールをincludeしなければならないことです。

mattr_accessor :logger, default: Rails.logger
extend ActiveSupport::Benchmarkable
include ActiveSupport::Benchmarkable

def process
  benchmark("=== Processing invoices ===") { process_invoices }
end

上のスニペットは、操作ごとに要した時間を以下のようにログに出力します。

Started GET "/companies/1/invoices" for ::1 at 2021-02-24 16:38:59 +0530
Processing by CompaniesController#invoices as HTML
  Parameters: {"id"=>"1"}
=== Processing invoices === (402.1ms)
  Rendered companies/invoices.html.erb within layouts/application (Duration: 1.5ms | Allocations: 426)
  Rendered layout layouts/application.html.erb (Duration: 13.0ms | Allocations: 4003)
Completed 200 OK in 427ms (Views: 14.3ms | ActiveRecord: 1.0ms | Allocations: 7079)

変更後

今回の変更により、以下のようにbenchmarkメソッドをどこでも使えるようになりました。従来のようにベンチマークのためだけにモジュールを追加する必要はありません。

def process
  Rails.benchmark("=== Processing invoices ===") do
    logger.debug("=== Processing started ===")

    process_invoices

    logger.debug("=== Processing done ===")
  end
end

上のコードは、以下のようにログレベルに応じたログを出力します(デフォルトのログレベルは:infoです)。

Started GET "/companies/1/invoices" for ::1 at 2021-02-24 16:40:35 +0530
Processing by CompaniesController#invoices as HTML
  Parameters: {"id"=>"1"}
=== Processing started ===
=== Processing done ===
=== Processing invoices === (400.7ms)
  Rendered companies/invoices.html.erb within layouts/application (Duration: 1.1ms | Allocations: 426)
[Webpacker] Everything's up-to-date. Nothing to do
  Rendered layout layouts/application.html.erb (Duration: 7.6ms | Allocations: 4025)
Completed 200 OK in 417ms (Views: 8.3ms | ActiveRecord: 0.9ms | Allocations: 7397)

benchmarkには以下の2つのオプションを渡せます。

level
ログレベル(:debug:info:warn:error)を設定することで、詳細なログを特定の環境でのみ出力するようにできます。デフォルトのログレベルは:infoです。
silence
このオプションを指定すると、ブロック内にあるベンチマークのタイミング情報以外のログ出力をすべて抑制します。

オプションの例

def process
  Rails.benchmark("=== Processing invoices ===", level: :debug, silence: true) do
    logger.debug("=== This won't be logged ===")

    process_invoices

    logger.debug("=== This won't be logged ===")
  end
end

上のメソッドは、コストの高い処理に要した時間をログレベル:debugで表示します。Rails.benchmarkブロック内の他のログはサーバーログに出力されません。今回の例では、Rails.benchmarkブロック内の最初と最後の行が出力されなくなります。

Started GET "/companies/1/invoices" for ::1 at 2021-02-24 16:13:44 +0530
Processing by CompaniesController#invoices as HTML
  Parameters: {"id"=>"1"}
=== Processing invoices === (404.6ms)
  Rendered companies/invoices.html.erb within layouts/application (Duration: 3.4ms | Allocations: 1111)
[Webpacker] Everything's up-to-date. Nothing to do
  Rendered layout layouts/application.html.erb (Duration: 18.5ms | Allocations: 7370)
Completed 200 OK in 458ms (Views: 22.7ms | ActiveRecord: 0.9ms | Allocations: 14473)

関連記事

Railsの使わない機能をオフにする(翻訳)

Railsの技: highlightヘルパーで検索結果を強調表示する(翻訳)

$
0
0

概要

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

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

Railsの技: highlightヘルパーで検索結果を強調表示する(翻訳)

Railsのhighlightヘルパーで、以下のように検索結果のマッチ部分を<mark>タグでラップします。

通知リストの”comment”という語の検索結果をハイライトする

使い方

検索する語をparams経由でコントローラに渡し(params[:search]など)、これを用いて結果をフィルタします。

# app/controllers/inbox_controller.rb
class InboxController < ApplicationController
  def index
    @notifications = Current.user.notifications.for_search(params[:search])
  end
end
# app/models/notification.rb
class Notification < ApplicationRecord
  belongs_to :recipient, class_name: "User"

  validates :message, presence: true

  def self.for_search(term)
    if term.present?
      # Implement searching however you'd like
      where("message ILIKE ?", "%#{term}%")
    else
      all
    end
  end
end

以下のように@notificationsをレンダリングし、TextHelper#highlightでメッセージのクエリにマッチする部分を強調表示します。

<% @notifications.each do |notification| %>
  <%= link_to highlight(notification.message, params[:search]), notification %>
<% end %>

<mark>タグには自由にスタイルを付けられます。

mark {
  background-color: yellow;
}

オプション

highlightには、マッチするフレーズとして「文字列」「配列」「正規表現」のどれでも渡せます。マッチでは大文字小文字を区別しません。

# 文字列の場合
highlight('Boring Rails is the best', 'rails')
# => Boring <mark>Rails</mark> is the best

# 正規表現の場合
highlight('Boring Rails is the best', /rails|best/)
# => Boring <mark>Rails</mark> is the <mark>best</mark>

# 配列の場合
highlight('Boring Rails is the best', ['is', 'best'])
# => Boring Rails <mark>is</mark> the <mark>best</mark>

マッチしない場合やフレーズが空の場合でも問題なく動作します。

highlight('Boring Rails is the best', 'JavaScript')
# => Boring Rails is the best

highlight('Boring Rails is the best', '') # nil works too
# => Boring Rails is the best

ハイライトオプションを渡して、マッチした部分の周りのHTMLマークアップをオーバーライドすることも可能です。マッチを参照する\1を使います。出力はデフォルトでサニタイズ(危険な文字列を除去すること)されます。

highlight('Boring is best', 'best', highlight: '<b>\1</b>')
# => Boring is <b>best</b>

highlight('Boring is best', 'best', highlight: '<a href="tagged?q=\1">\1</a>')
# => Boring is <b>best</b>

追加コードを実行する必要がある場合は、レンダリングを行うブロックを以下のように渡せます。

highlight('Blog: Boring Rails', 'rails') do |match|
  link_to(match, public_share_path(term: match))
end
# => Blog: Boring <a href="/public/share?term=Rails">Rails</a>

参考資料

関連記事

週刊Railsウォッチ(20210329前編)特集: Rails更新版の臨時リリースとmimemagic gemのGPL問題

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗 特集: mimemagic gemのGPL問題とRails更新版リリース

現状: Rails 5.2.5、6.0.3.6、6.1.3.1がリリース

最初に本日(2021/03/29)時点の情報から。
後述するmimemagicの問題を解決したRailsが日本時間の2021/03/27(土)03:24にリリースされました。

上の更新版Railsにアップデートすることでmarcelがmimemagicに依存しなくなります。

mimemagicが必要な場合の対応方法やRails以外のサードパーティgemの問題について詳しくは、ruby-jp有志がまとめている以下の情報もどうぞ。随時更新されています。

参考: mimemagicの最新動向 - HackMD

なおgemごとのライセンス情報表示はbundle licensesコマンドでやるのが一番手軽でした。

🔗 経緯

つっつきボイスは2021/03/25夜(木)時点のものです。更新版Railsがリリースされる前の流動的な状態につき、ご了承ください。


つっつきボイス:「今ちょうどmimemagicのGPL問題であちこちが騒ぎになっていますね」「気づいてなかった〜」「気づかずに済んだのは幸い: 組織内gemサーバーを使っていないところや、gemのキャッシュが残っていないRailsアプリがこの問題を踏むとbundle installでmimemagicを取得できずにCI(Continuous Integration)がコケます」「げ」

「単にmimemagic gemが取れなくなっただけなら参照先を変えるなどすれば対応可能なんですが、mimemagicで見つかったGPL汚染が現時点で解決していないのが問題なんですよ(注: Rails更新版で対応済み)」「なるほど」

参考: GPL汚染 ‐ 通信用語の基礎知識

「今の時点(3/25 20時頃)ではbundle update mimemagicを実行すれば更新版のmimemagicを取得してエラーを解決できますが、更新版mimemagicがGPL-2.0ライセンスのまま(注: 3/29時点では後述の対応でMITライセンスに変更済みです)なので、Railsアプリ全体のライセンスがGPL-2.0になってしまいます」「mimemagicは随分前からRailsで使われてたのか」

「こういう依存関係があったそうです↓」

mimemagicの依存関係

  • RailsのActive Storageは、rails/marcel gemに依存している
  • rails/marcel gemは、Railsリポジトリの外にあるmimemagic gemに依存している

rails/marcel - GitHub

旧minad/mimemagic↓は、現在minemagicrb/minemagicにリダイレクトされます(3/29時点)。

minad/mimemagic - GitHub

「以下は発端となったRailsのissue #41750ですが↓、既にロックされて書き込めなくなってます」「それほど書き込みが激しかったということですね」「また新しいissue(41517)が開いてる」

「以下が現時点(3/25夜)までのおおよその流れです↓」

ここまでの流れ

  • minad/mimemagic gemで使っているMIMEリストXMLファイルfreedesktop.org.xmがGPL-2.0ライセンスであることが発覚した(#97
  • minad/mimemagic 0.3.6で以下を行った
    • ライセンスをMITからGPL-2.0に変更
    • minad/mimemagicのリポジトリをアーカイブ化
    • 従来MITライセンスとして公開されていたminad/mimemagic gemをyankした

issue: freedesktop.org.xml file license · Issue #97 · minad/mimemagic

「mimemagicはActive Storage添付ファイルのMIMEタイプの判別に使っているんでしょうね」

参考: MIME タイプ (IANA メディアタイプ) - HTTP | MDN

「現時点(3/25夜)では、少なくともrails/marcel gemのminad/mimemagic依存を「ruby-magicに切り替える」「libmagicに切り替える」という2つの方向が検討されているようです↓」「#26のCIが全部失敗しているのでまだ作業中か」「緊急の対応がどうしても必要でなければ、Railsフレームワーク側でmimemagicを置き換えて対応するのを待つのが確実でしょうね」「そうします」

その後

(3/29夜の時点)最終的に冒頭の更新版Railsでは、rails/marcel gemがアップグレードしてmimemagic↓に依存しなくなり、rails/marcel gemでMIMEデータファイルにApache Tika(Apache License 2.0)のファイルを用いるようにしたことで対応しました。

mimemagicrb/mimemagic - GitHub

参考: Apache Tika – Apache Tika
参考: Apache License - Wikipedia

Railsのバージョンが微妙に古いなどの理由でmarcelをアップグレードできない場合は、minimagicを更新したうえでshared-mime-infoパッケージ↓でインストールされるfreedesktop.org.xmlをFREEDESKTOP_MIME_TYPES_PATHに指定して使う必要があるでしょう。

参考: shared-mime-info
参考: mimemagicの最新動向 - HackMD

🔗 GPL-2.0の解釈

ライセンスの解釈は最終的に弁護士など法務関係者の判断や法的手続きが必要と思われます。本記事におけるライセンスの解釈についてはあくまで参考情報にとどめていただくようお願いします。

「SaaSとしてのRailsアプリであれば、GPL-2.0に基づいたソースコード開示の対象にはならないのではないかという情報をruby-jpで見かけました: 以下のNate Berkopecさんのツイートにも同趣旨のことが書かれています↓」「そこはどうだったかな…GPL-3.0やLGPLではそうした点の明文化が改善されていたと思うんですが、GPL-2.0はそのあたりが曖昧だったような覚えがあります」「う〜む」「GPL-2.0は古くからあるライセンスですし、SaaSのような利用形態を想定していない可能性もありそうなので、この話を信じていいかどうかは判断できないかな」「ソフトウェアライセンスに強い弁護士じゃないと無理そうですね」(しばし議論)

参考: GNU General Public License - Wikipedia
参考: GNU Lesser General Public License - Wikipedia
参考: PDF IoT時代におけるOSS の利用と法的諸問題Q&A集

「近年MITライセンス↓などのより制約の少ないライセンスが広く使われるようになったのはそうした理由が大きいと思います: GitHubにリポジトリを作るときもたしかデフォルトでMITライセンスファイルが作られますよね」「はい、GitHubはそうなってます」

参考: MIT License - Wikipedia

🔗 ユーザー環境でRailsベースの製品を動作させるプロダクトへの影響

「これもruby-jpで見かけたのですが、この問題の影響が大きいのは主にGitLabやGitHub EnterpriseのようにRailsアプリをライセンス販売している会社だそうです」

「GPLライセンスのソフトウェアを自分達の作ったソフトウェア内に含めてしまうと、配布した際にソフトウェア全体をGPL規約に従ってユーザーに公開する必要があります: 非公開のコードをユーザー側環境のアプリケーションサーバーで動かせるようなプロダクト(GitHub EnterpriseやGitLab EEなど、クラウドサービスのEnterprise版でオンプレ環境に対応したものなど)の場合、大きな問題になります」

🔗 DMCA takedown

「以下はGo言語のmimemagicリポジトリですが、こちらには同様の指摘がメールでメンテナーに届いたとのことで、やはりライセンスをGPL-2.0に変更しています」「DMCA takedownの通告を受けたのか、これは大変そう」

zRedShift/mimemagic - GitHub

「GitHubのようなソフトウェア配布事業者やコンテンツ事業者がDMCA takedownの通告を受けたら、DMCA違反かどうかの詳細を確認するよりも先に配布を速やかに停止しないといけないんですよ」「えぇ?」「もちろん異議申し立てなどのプロセスもありますが、手順としては最初に公開を停止してから事実確認や審議を行うことになっていて、その意味では事業者よりも利用者の保護を優先していると言えますね」

参考: デジタルミレニアム著作権法 - Wikipedia — DMCA
参考: DMCAテイクダウンポリシー - GitHub Docs

通知がリポジトリのコンテンツ全体、またはパッケージが著作権侵害に該当すると主張する内容の場合、当社はステップ 6 に移動し、リポジトリ全体またはパッケージを迅速に無効にします。 そうでない場合、GitHub はリポジトリ内の特定のファイルへのアクセスを無効にすることはできないため、リポジトリを作成したユーザに連絡し、約 1 営業日以内に通知で指定されたコンテンツを削除または変更するよう求めます。 ユーザに変更を行う機会を提供した場合、当社はその旨を著作権者に通知します。 パッケージはイミュータブルであるため、パッケージの一部が著作権を侵害している場合、GitHub はパッケージ全体を無効にする必要がありますが、侵害している部分が削除された場合は復帰を許可しています。
docs.github.comより(強調は編集部)

「GitHubのドキュメント↑にもあるように、通告を受けた事業者は1営業日以内にコンテンツを削除または変更しないといけません」「たった1日ですか?」「通知が来た日の翌営業日までだと思います」「それでもキビシイ🥲」「リポジトリのメンテナーがたまたま休んでたりしたら間に合わなさそう」「ドキュメントには『うっかり期間内に変更できなかった場合』の項目もあるから対応はできそうかな」

「DMCA takedownは強い指示で、それでいて申し立ても比較的やりやすいので、これまでも炎上記事の火消しなどのために記事公開を差し止めさせるといった、DMCA本来の目的から外れた使われ方がしばしば話題になってますね」「あぁなるほど!」「言われてみればその手の記事をときどき見かけますね」

参考: 史上最高に馬鹿げた著作権侵害のDMCA通告 | TechCrunch Japan

「その代わり、悪用防止のためにDMCA takedownを依頼した側の情報は事業者がすべて公開しています」「なるほど、そうしないといくらでもイケないことに使われそう」「それでもしばしばそういうふうに使われていますけどね」「『DMCA 悪用』でググるといっぱい出てくる…」「今見ている記事でも、DMCAを悪用するとかえってブランドイメージを傷付けるから止めた方がいいよって書かれてました」

参考: Google 透明性レポート
参考: DMCAとは 意味・用途 - アイオイクスの社員ブログ

🔗 まとめ

「minad/mimemagicの場合はDMCA通告ではなくshared-mime-infoメンテナーからのissue(#97)でGPLの件が知らされていたようですが、今回の件でminad/mimemagicリポジトリをアーカイブしたのはDMCA違反回避のためにやむを得ず緊急で行ったのだろうと推測しました」「悪意とかではなく、事が重大なだけに真っ先に公開を止めてそれからRails側に知らせるしかなかったんでしょうね、何となくわかってきました」「リポジトリを止めたときのRailsへの影響の大きさがわかっていてもリポジトリを一時的に復旧できなかったのは、そういうことだったのかも」「学びを得た気持ちです」

無事に復旧して何よりでした。関係者の皆さまお疲れさまでした!


「ところで、#41750のコメント↓に貼られているxkcd: Dependencyの絵は身につまされますね」「あと、mimemagicという名前がminimagickに似てて何度も間違えそうになりました」「たしかに」

minimagick/minimagick - GitHub


前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210323後編)GitHub Actionsで使えるruby/setup-ruby、中高生国際Rubyプログラミングコンテスト2020ほか

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

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

Rails公式ニュース

Ruby Weekly

週刊Railsウォッチ(20210330後編)Active Recordモデル属性暗号化が標準で入る可能性、Flipper Cloud、awesome_printほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

今週は以下のコミットリストのChangelogを中心に見繕いました。

🔗 RemoteIPミドルウェアでtrusted_proxiesに単独の値代入を非推奨化


つっつきボイス:「app.config.action_dispatch.trusted_proxiesに渡すIPアドレスは単独でも配列でも渡せるけど、前者を非推奨化したということのようですね↓」

# 同PRより
# Adds 4.2.42.0/24 to default list of trusted proxies
app.config.action_dispatch.trusted_proxies = IPAddr.new("4.2.42.0/24")

# Replaces default list of trusted proxies with 4.2.42.0/24
app.config.action_dispatch.trusted_proxies = [IPAddr.new("4.2.42.0/24")]

「RackミドルウェアのRemoteIptrusted_proxiesは、たとえばRailsの手前にリバースプロキシを置いてある場合、リクエストの流れがリバースプロキシ->Railsアプリの場合はレスポンスを返すけど、リバースプロキシ以外の怪しいIPアドレスからRailsアプリにリクエストが来た場合はレスポンスを返さないようにするのに使ったりします(IP Spoofing対策)」「あ、そうやって使うんですね」

参考: IPスプーフィング - Wikipedia

「こうした制約をインフラレベルだけでかけられれば理想なんですが、CDNなどの制約が絡んだりしてインフラが複雑になると穴を塞ぐのが大変になるので、そういうときにtrusted_proxiesを使うことがあります」

参考: コンテンツデリバリネットワーク - Wikipedia

trusted_proxiesに配列を渡せるなら単独の値もカバーできるけど、単独の値を非推奨化する必要はあるのかな?🤔」「まだしばらくは単独の値も使えそう」「今後はenumerableな値を渡せば大丈夫ですね」

🔗 個別のテストファイル実行でパラレルテストが無効になった


つっつきボイス:「bin/test test/controller/parameters/accessors_test.rbのようにファイルを1個だけ指定する場合はパラレルテストをオフにするようになったのか: たしかに1ファイルだけのテストでパラレルテストのワーカーをセットアップするのは完全に無駄ですね」「たしかに」「IDEの保存時実行機能やguard gemなどを使って保存ファイルだけテストを自動実行するのが速くなりそう👍」

# 改修前
actionpack $ bin/test test/controller/parameters/accessors_test.rb
Run options: --seed 48261

........................................................................

Finished in 0.211923s, 339.7460 runs/s, 552.0873 assertions/s.
72 runs, 117 assertions, 0 failures, 0 errors, 0 skips
# 改修後
actionpack $ bin/test test/controller/parameters/accessors_test.rb

Run options: --seed 5461

........................................................................

Finished in 0.008411s, 8560.2189 runs/s, 13910.3557 assertions/s.
72 runs, 117 assertions, 0 failures, 0 errors, 0 skips

guard/guard - GitHub

🔗 strict_loading!n_plus_one_onlyモードが追加


つっつきボイス:「ビックリマーク付きのstrict_loading!n_plus_one_onlyオプションが入ったそうです」「Changelogの方が見やすそう↓」

  • レコードレベルのstrict_loading!にモード引数を追加
    この引数は、単一レコードに対してstrict loadingを有効にし、N+1クエリに対してのみエラーをraiseできる。
developer.strict_loading!(mode: :n_plus_one_only)
developer.projects.to_a # Does not raise
developer.projects.first.client # Raises StrictLoadingViolationError

従来はstrict loadingを有効にすると、lazy loadされたすべての関連付けでエラーがraiseされた。n_plus_one_onlyモードを指定すると、単一のクエリでフェッチされたbelongs_tohas_manyなどの関連付けをlazy loadできる。
Dinah Shi
同Changelogより大意

n_plus_one_onlyを有効にすると、N+1問題が発生していないとき(レコードを1件だけ読み込む場合)なら関連付けをlazy loadingできて、N+1問題が発生している場合だけエラーをraiseするようになったということのようですね: strict loadingはN+1クエリ問題が発生しないときに行いたいことが多いし、N+1が発生したときはstrict loadingのところで止めたくなると思うので、これが欲しい気持ちはわかります」「なるほど」「n_plus_one_onlyモードは自分的にはなかなか実用的な印象👍」

レコードをstrict_loadingモードに設定する。レコードが関連付けをlazy loadingしようとするとエラーをraiseする。

user = User.first
user.strict_loading!
user.comments.to_a
=> ActiveRecord::StrictLoadingViolationError

api.rubyonrails.orgより大意

🔗 Enumerable#in_order_ofが追加

ActiveRecord::Base#findは、引数として使われている主キーの順でレコードを返す。これはindex_bymapの合せ技で、#find以外でも有用。このプルリクはそのパターンをEnumerable#in_order_ofに切り出したもの。
同PRより大意


つっつきボイス:「これは今年2月にマージされたので少し前のPRですが、DHH自らによるものでした」「Active Supportに入ったんですね」「このコードサンプルがわかりやすいかな↓: [1, 5, 3]を渡すと、idを持つものをその順序で返す」「あ、なるほど」

  #   [ Person.find(5), Person.find(3), Person.find(1) ].in_order_of(:id, [ 1, 5, 3 ])
  #   => [ Person.find(1), Person.find(5), Person.find(3) ]

「Enumerableに追加されたのがいいですね」「順序を指定せずに取りたいことの方が多いと思うけど、このメソッドがあることを知っていれば順序を指定して取りたい場合に使うかも」

# activesupport/lib/active_support/core_ext/enumerable.rb#L194
+ def in_order_of(key, series)
+   index_by(&key).values_at(*series).compact
+ end

🔗 (マージ前)Active Recordモデルの属性を暗号化する機能(Ruby Weeklyより)


つっつきボイス:「現時点(2021/03/25夜)ではまだマージされていませんが、Ruby Weeklyで取り上げられていました」「おぉ、属性の暗号化がついにRailsに統合されそう?」

# 同PRより
class Person < ApplicationRecord
  encrypts :name
end

「Action TextやActive Recordなどにも変更が入っている」「よく見たら更新ファイルが82個もあるんですね」「変更多いな〜」「これまでなかった機能が丸ごと入ったから変更量は多いでしょうね」

「Active Record属性の暗号化といえばattr_encrypted gemがかなり昔からよく使われています↓」「そうそう」「GitLabでもattr_encryptedを使っていたと思います」「類似のlockboxやvault-railsもプルリクメッセージで紹介されていますね↓」

attr-encrypted/attr_encrypted - GitHub
ankane/lockbox - GitHub
hashicorp/vault-rails - GitHub

「しかもこれはBasecampがやっているHEYのコードから切り出したそうですよ↓」「お〜」「HEYで実績を積んでいるコードが使われているのはありがたい」「encryptorもちゃんと独自に指定できる: これはないと困る機能」「HEYを立ち上げる前にセキュリティファームがこのライブラリをレビューしたと書かれていますね」

# 同PRより
config.active_record.encryption.encryptor = MyEncryptor.new

「属性暗号化機能は多くのアプリで使われるので、Rails標準で入っていてもいい気がしますね」「今どきのフルスタックWebフレームワークなら入っていてもおかしくないかも」「”いいね”もすごくたくさん付いてますね」「これはRailsにあっていい機能👍」「Rails 7にこれが入るなら目玉機能になるでしょうね」


本日(2021/03/30夕方)時点ではまだマージされていません。

🔗Rails

🔗 authorizationのつらみをFlipper Cloudで軽減する(Ruby Weeklyより)


つっつきボイス:「上のFlipper Cloudというサービスを推している記事のようです」「サイトを見た感じでは、いわゆるフィーチャーフラグをクラウドベースで管理できるオープンソースのサービスということらしい」

参考: フィーチャートグル - Wikipedia

「gemも公開されている↓:gemでやる場合は自分たちで管理する必要があると思いますが」「設定はIRBでもFlipper::UIでもできるとありますね」

jnunemaker/flipper - GitHub


同リポジトリより

「コードはフィーチャーフラグそのものという感じなので↓、シンプルなフィーチャーフラグの実装に使えそうかな」「おぉ」

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

# check if search is enabled
if Flipper.enabled?(:search)
  puts 'Search away!'
else
  puts 'No search for you!'
end

puts 'Enabling Search...'
Flipper.enable(:search)

# check if search is enabled
if Flipper.enabled?(:search)
  puts 'Search away!'
else
  puts 'No search for you!'
end

「眺めた限りでは、リポジトリの★も多いし、フィーチャーのオンオフとかも含めて割とよくできていそうな予感がしますね: 興味があれば調査してみてもいいかも」


「元記事の方は、たとえばこれはよくあるポリシー型の権限管理ですね↓」「Flipper Cloudはユーザーごとの権限管理までやれそうに見える」

# 同記事より
class TokenPolicy < ApplicationPolicy
  def show?
    project_member?
  end

  def update?
    project_member?
  end

  def destroy?
    project_member?
  end

  private 

  def project_member?
    record.project.member?(user)
  end
end

「URLエンドポイントレベルのシンプルな権限管理ならこうしたgemやサービスでやれると思いますが、エンティティレベルで権限管理をしようとするとおそらくpunditのようなものが必要になってくるでしょうね」「なるほど」「そこにGraphQLが絡むとさらに複雑になるんですよ」

varvet/pundit - GitHub


「ところで、記事ではmemoistというgemも紹介されていますね↓」「サンプルコードのとおり、いわゆるメモ化を簡単に行えるようですね」「今はRailsからなくなったActiveSupport::Memoizableから切り出したらしい」「ポリシーは実行中に変えないのが普通なので、記事ではこれでメモ化して高速化を図っているんでしょうね」

matthewrudy/memoist - GitHub

# matthewrudy/memoistより
require 'memoist'
class Person
  extend Memoist

  def social_security
    puts "execute!"
    decrypt_social_security
  end
  memoize :social_security
end

person = Person.new

person.social_security
# execute!
# => (returns decrypt_social_security)

person.social_security
# => (returns the memoized value)

参考: メモ化 - Wikipedia

🔗 sqldef: 冪等なスキーマ管理ツール

k0kubun/sqldef - GitHub

Goで書かれています。


つっつきボイス:「k0kubunさんの上の記事でsqldefを知りました」「ridgepoleのオルタナ的なツールをk0kubunさんが自分で作ったんですね」「スキーマ管理をSQLで書けるというツールなのか: そういえばずっと前に見たことがあったかも」「実はだいぶ前のウォッチで取り上げてたんですが、ridgepole的なツールとは気づきませんでした(ウォッチ20191119)」

winebarrel/ridgepole - GitHub

「スキーマ管理をSQLで書きたいor書く必要がある場合にいいかも: たとえばプロジェクト横断的な案件で、Railsでないシステムのデータベースをリードオンリーで共有している場合を考えると、RailsのマイグレーションはRubyがないと動かないのでRubyをインストールするなどの対応が必要ですが、こうやってSQLで書けるならそういう場合に便利かもしれませんね」「ちょうどk0kubunさんの記事の脚注にもそのことが書かれていたので引用しました↓」「そうそう、こういうふうに非Railsプロジェクトとデータベーススキーマを共有したいとか、Railsを知らない人がスキーマ管理するときなどに使えそう」

元々ActiveRecordが入っているRailsだとRidgepoleを使わない理由はほとんどない気がするけど、同じDBをActiveRecord以外からも読むので素のSQLで管理されてる方が扱いやすい (今回のアプリがそれ) とか、DDLは覚えてるけどActiveRecordのDSLは調べないとわからないというようなニッチな用途には便利かもしれない。
同記事脚注より


以下のスライドでもsqldefが取り上げられているのを見つけました。

🔗Ruby

🔗 dead_end: endの対応関係の誤りを検出する(Ruby Weeklyより)

zombocom/dead_end - GitHub


つっつきボイス:「endの誤りを検出するという、よくある感じのgemですが、このdead_endという名前がいいですね👍」「ネーミングセンスが光ってる」「RubyMineなどのIDEもやれますけどね」

# 同リポジトリより: endが足りない場合
class Dog
  def bark
    puts "bark"

  def woof
    puts "woof"
  end
end
# => scratch.rb:8: syntax error, unexpected end-of-input, expecting `end'
# 同リポジトリより: endが余分な場合
class Dog
  def speak
    @sounds.each |sound| # Note the missing `do` here
      puts sound
    end
  end
end
# => scratch.rb:7: syntax error, unexpected `end', expecting end-of-input

🔗 シェルのディレクトリ一括作成


つっつきボイス:「koicさんのRuboCop記事でシェルの話が載っていました」「mkdir {a,z}は当然できるけど、mkdir {a..c}mkdir {1..10}でディレクトリを一括作成できるとは知らなかった」「これ初めて見ました」「そういえばシェルに..記法があったのを今思い出した」「最近シェルスクリプト書いてないな〜😅」

# 同記事より
% mkdir {a,z}
% ls 
a z

% mkdir {a..c}
% ls 
a b c 

% mkdir {1..10}
% ls 
1 10 2 3 4 5 6 7 8 9

% mkdir {01..10}
% ls
01 02 03 04 05 06 07 08 09 10

参考: Bashによるパス名の展開まとめ | LFI

「そして{}が空だと{}というディレクトリができてしまう、たしかに」

「そういえばシェルのtestコマンドに[という別名がありますよね」「あ〜、ありますあります」「あれを最初知ったときはびっくりしましたけど、わかってみるとシェルすごいかもという気持ちになりますね」「人によりそうですけどね😆」

参考: test と [ と [[ コマンドの違い - 拡張 POSIX シェルスクリプト Advent Calendar 2013 - ダメ出し Blog

🔗 awesome_printが強くなって帰ってきた(Ruby Weeklyより)

awesome-print/awesome_print - GitHub


つっつきボイス:「awesome_printは、以前は色付けに使うgemという印象でしたが、知らないうちにいろいろ機能が足されているようです」「お〜、オプションがずいぶん増えてますね」

「こういう感じで表示できるのはいい↓👍」「いいな〜」

# 同リポジトリより
$ rails console
rails> require "awesome_print"
rails> ap Account.limit(2).all
[
    [0] #<Account:0x1033220b8> {
                     :id => 1,
                :user_id => 5,
            :assigned_to => 7,
                   :name => "Hayes-DuBuque",
                 :access => "Public",
                :website => "http://www.hayesdubuque.com",
        :toll_free_phone => "1-800-932-6571",
                  :phone => "(111)549-5002",
                    :fax => "(349)415-2266",
             :deleted_at => nil,
             :created_at => Sat, 06 Mar 2010 09:46:10 UTC +00:00,
             :updated_at => Sat, 06 Mar 2010 16:33:10 UTC +00:00,
                  :email => "info@hayesdubuque.com",
        :background_info => nil
    },
    [1] #<Account:0x103321ff0> {
                     :id => 2,
                :user_id => 4,
            :assigned_to => 4,
                   :name => "Ziemann-Streich",
                 :access => "Public",
                :website => "http://www.ziemannstreich.com",
        :toll_free_phone => "1-800-871-0619",
                  :phone => "(042)056-1534",
                    :fax => "(106)017-8792",
             :deleted_at => nil,
             :created_at => Tue, 09 Feb 2010 13:32:10 UTC +00:00,
             :updated_at => Tue, 09 Feb 2010 20:05:01 UTC +00:00,
                  :email => "info@ziemannstreich.com",
        :background_info => nil
    }
]

「リポジトリにcodeclimate.ymlが置かれている: ちなみにCode Climateは自動コードレビューなどが使えるサービスです」「Code Climateの設定はこういう感じで書くんですね」

「こういうgemが環境に入っていて使えたら嬉しいけど、ただGemfileには書いて欲しくない気持ちがあります」「あ、それわかります」「このgemならrequireに書いてもいいぐらい好きですけど、Gemfileのdevelopmentブロックにgemをいっぱい足すのはあまり好みではないんですよ」「たしかに」

🔗 AWS SDK RubyでRuby 2.2以前のランタイムサポートが終了


つっつきボイス:「お、サポート終了のお知らせか」「ここにリストされているバージョンならRubyソースコードも更新されなくなっていますし、影響はそんなにないと思いますね」

  • Ruby 1.9.3 – EOL began on 2015-02-23
  • Ruby 2.0.0 – EOL began on 2016-02-24
  • Ruby 2.1 – EOL began on 2017-03-31
  • Ruby 2.2 – EOL began on 2018-03-31
    aws.amazon.comより

後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210329前編)特集: Rails更新版の臨時リリースとmimemagic gemのGPL問題

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

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

Rails公式ニュース

Ruby Weekly


Railsの技: cycleメソッドでビューのループの「i%2==0」を避ける(翻訳)

$
0
0

概要

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

Railsの技: cycleメソッドでビューのループの「i%2==0」を避ける(翻訳)

ビューのレンダリングで、ループの回数をトラッキングする必要が生じることがあります(1行ごとに背景色を変える「ストライプテーブル」を作る場合など)。

<!-- @foods = ["apple", "orange", "banana"] -->

<% @foods.each do |food| %>
  <tr class="???">
    <td><%= food %></td>
  </tr>
<% end %>

皆さんも、CSSのoddeven子セレクタ1を使うか、以下のようにRubyのeach_with_indexメソッドで背景色を切り替えてみたことがおありでしょう。

<% @foods.each_with_index do |food, i| %>
  <tr class="<%= i % 2 == 0 ? 'bg-gray-200' : 'bg-gray-100' %>">
    <td><%= food %></td>
  </tr>
<% end %>

以下のようにi.odd?i.even?で少々リファクタリングする手もあります。

<% @foods.each_with_index do |food, i| %>
  <tr class="<%= i.even? ? 'bg-gray-200' : 'bg-gray-100' %>">
    <td><%= food %></td>
  </tr>
<% end %>

Railsには、こんなときに重宝するcycleヘルパーメソッドも用意されています。

使い方

cycleヘルパーは1個の配列を引数に取り、呼ばれるたびに配列の要素を順に返します。

上のコードは以下に置き換えられます。

<% @foods.each_with_index do |food, i| %>
  <tr class="<%= cycle('bg-gray-200', 'bg-gray-100') %>">
    <td><%= food %></td>
  </tr>
<% end %>

cycleヘルパーにオプションを3つ以上渡すと、本当のありがたみが見えてきます。

<% @foods.each_with_index do |food, i| %>
  <tr class="<%= cycle('bg-red-100', 'bg-orange-100', 'bg-yellow-100') %>">
    <td><%= food %></td>
  </tr>
<% end %>

cycleを手動でリセットする必要がある場合や、コード間で共有する必要がある場合は、オプションにname:キーを渡せます。

cycle("red", "white", "blue", name: "colors")
cycle("sm", "md", "lg", "xl", name: "sizes")

reset_cycle("colors")
reset_cycle("sizes")

cycleでは、to_sに応答するオブジェクトなら何でも使えます。

<% @items.each do |item| %>
  <div class="rotate-<%= cycle(0, 45, 90, 135, 180) %>">
    <%= item %>
  </div>
<% end %>

参考資料

関連記事

Railsの技: highlightヘルパーで検索結果を強調表示する(翻訳)



  1. CSS擬似クラス:nth-child()oddevenキーワードか、記事末尾のTailwindのodd-childやeven-childの指定を指していると思われます(おそらく後者)。 

Rails 6.1のDocker開発環境構築をEvil Martians流にやってみた(更新)

$
0
0

更新情報

  • 2019/11/20: 初版公開
  • 2021/03/25: Rails 6.1.3.1に合わせて更新

先々月に公開したこちらの翻訳記事の実践編ということで。試行錯誤しているうちにRailsが6.0.1になりました。

クジラに乗ったRuby: Evil Martians流Docker+Ruby/Rails開発環境構築(翻訳)

Docker Desktop for Macについて

これまではピュアな環境を求めてParallels Desktop for MacのUbuntu VM上でDockerを使っていたのですが、久しぶりにDocker Desktop for Macを使ってみると速度や使い勝手が随分よくなっていて驚きました。

  • Docker Desktop for Macの方がUbuntu VMのDockerよりビルドが速い(体感ですが)
    • ただしEvil Martiansの記事にもあるように、docker-composeでボリュームに:cachedを記述しておかないとDocker Desktop for Macで遅くなります
  • 今はdmgファイルをダウンロードしてアプリケーションフォルダにドロップするだけでインストール完了できる

Ubuntuだと基本的にsudoを打たないとDockerが使えないのと(回避方法は一応ありますが)、macOSとの間のレイヤが増えるつらみの方が大きくなってきたので、Docker Desktop for Macでやることにしました😅。

環境

  • macOS: 11.2.3(Big Sur)
  • Docker Desktop for Mac: 3.2.2(61853)
    • Docker: 20.10.5
    • Docker-compose: 1.28.5
    • Dockerhubにログインしておく
    • (既存のDockerがあれば削除しておく)
  • Rails: 6.1.3.1
  • Ruby: 3.0.0
  • Git: 2.31.0
  • dipをインストールする

本記事でのDocker操作は全面的にEvil Martians謹製のdipを使っています。dipの説明は割愛しますが、とてもよかったので別記事にしたいと思います。

追記(2019/12/04): 書きました↓

docker-composeを便利にするツール「dip」を使ってみた

リポジトリ

今回のサンプルを以下のリポジトリに置きました。

プロジェクトディレクトリ
├── .dockerdev
│   ├── .bashrc
│   ├── .psqlrc
│   ├── Aptfile
│   └── Dockerfile
├── .env(ダミー)
├── dip.yml
└── docker-compose.yml

変更の方針

  • Evil Martiansの設定からの変更は最小限とし、できるだけ汎用性を高める
    • docker-compose内のimage名は自分のプロジェクトに合わせてリネームし、:1.0.0は残す
  • Evil Martians流でやるとイメージのサイズが1GBになるので軽量化する
    • その代わり基本的なツールは自分で補わないといけない😅
  • SidekiqとRedisは使わないのでdocker-compose.ymlから削除
  • dip.ymlは多少カスタマイズする
  • .psqlrcも入れておく
  • BundlerはRubyに同梱されるようになったのでDockerfileのBundlerのインストールスクリプトは含めなかった。
  • 9bd609でDockerfileに追加されたfreedesktop.org.xmlのダウンロードスクリプトは含めなかった。詳しくは以下をご覧ください。

週刊Railsウォッチ(20210329前編)特集: Rails更新版の臨時リリースとmimemagic gemのGPL問題

変更後のDockerfile

ARG RUBY_VERSION
# 後述
FROM ruby:$RUBY_VERSION-slim-buster

ARG PG_MAJOR
ARG NODE_MAJOR
ARG YARN_VERSION

# 共通の依存関係
RUN apt-get update -qq \
  && DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
    build-essential \
    gnupg2 \
    curl \
    less \
    git \
    shared-mime-info \
  && apt-get clean \
  && rm -rf /var/cache/apt/archives/* \
  && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
  && truncate -s 0 /var/log/*log

# PostgreSQLをsources listに追加
RUN curl -sSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
  && echo 'deb http://apt.postgresql.org/pub/repos/apt/ buster-pgdg main' $PG_MAJOR > /etc/apt/sources.list.d/pgdg.list

# NodeJSをsources listに追加
RUN curl -sL https://deb.nodesource.com/setup_$NODE_MAJOR.x | bash -

# Yarnをsources listに追加
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
  && echo 'deb http://dl.yarnpkg.com/debian/ stable main' > /etc/apt/sources.list.d/yarn.list

# 依存関係をインストール
COPY ./.dockerdev/Aptfile /tmp/Aptfile
RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && \
  DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
    libpq-dev \
    postgresql-client-$PG_MAJOR \
    nodejs \
    yarn=$YARN_VERSION-1 \
    $(grep -Ev '^\s*#' /tmp/Aptfile | xargs) && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
    truncate -s 0 /var/log/*log

# bundlerとPATHを設定
ENV LANG=C.UTF-8\
  GEM_HOME=/bundle\
  BUNDLE_JOBS=4\
  BUNDLE_RETRY=3
ENV BUNDLE_PATH $GEM_HOME
ENV BUNDLE_APP_CONFIG=$BUNDLE_PATH\
  BUNDLE_BIN=$BUNDLE_PATH/bin
ENV PATH /app/bin:$BUNDLE_BIN:$PATH

# RubyGemsをアップグレードして必要なバージョンのbundlerをインストール
RUN gem update --system &&\
    gem install rails &&\
    mkdir -p /app

WORKDIR /app

Dockerfileの変更のポイント

  • イメージをruby:2.6.5からruby:3.0.0-slim-busterに変更
    • イメージが軽くなる代わりに、そのままだとcurlpingなどの基本的なパッケージも入らないので、apt-getでインストールするかAptfileに書いておきます
  • curlを実行するRUNコマンドをまとめてイメージの肥大化を防ぐ
  • gem install railsを追加

これでイメージは1GBから671MBに半減しました。もっと減らしたいならAlpine Linuxベースのイメージを使う手もあります。

Dockerのマルチステージビルドもやってみたかったのですが、手間の割にあまり報われない気がしたのと、Evil Martians流からあまり離れたくなかったので見送りました。

変更後のdocker-compose.yml

version: "3.8"

services:
  app: &app
    build:
      context: .
      dockerfile: ./.dockerdev/Dockerfile
      args:
        RUBY_VERSION: "3.0.0"
        PG_MAJOR: "13"
        NODE_MAJOR: "12"
        YARN_VERSION: "1.22.4"
    image: yourapp_docker:1.0.0
    tmpfs:
      - /tmp

  backend: &backend
    <<: *app
    stdin_open: true
    tty: true
    volumes:
      - .:/app:cached
      - rails_cache:/app/tmp/cache
      - bundle:/bundle
      - node_modules:/app/node_modules
      - packs:/app/public/packs
      - .dockerdev/.psqlrc:/root/.psqlrc:ro
      - .dockerdev/.bashrc:/root/.bashrc:ro
    environment:
      - NODE_ENV=development
      - RAILS_ENV=${RAILS_ENV:-development}
      - DATABASE_URL=postgres://postgres:postgres@postgres:5432
      - BOOTSNAP_CACHE_DIR=/bundle/bootsnap
      - WEBPACKER_DEV_SERVER_HOST=webpacker
      - WEB_CONCURRENCY=1
      - HISTFILE=/app/log/.bash_history
      - PSQL_HISTFILE=/app/log/.psql_history
      - EDITOR=vi
    depends_on:
      - postgres
      - webpacker
    env_file: .env

  runner:
    <<: *backend
    command: /bin/bash
    ports:
      - "3000:3000"
      - "3002:3002"

  rails:
    <<: *backend
    command: bundle exec rails server -b 0.0.0.0
    ports:
      - "3000:3000"

  postgres:
    image: postgres:13
    volumes:
      - .psqlrc:/root/.psqlrc:ro
      - postgres:/var/lib/postgresql/data
      - ./log:/root/log:cached
    environment:
      - PSQL_HISTFILE=/root/log/.psql_history
      - POSTGRES_HOST_AUTH_METHOD=trust
    ports:
      - 5432
    env_file: .env

  webpacker:
    <<: *app
    command: ./bin/webpack-dev-server
    ports:
      - "3035:3035"
    volumes:
      - .:/app:cached
      - bundle:/bundle
      - node_modules:/app/node_modules
      - packs:/app/public/packs
    environment:
      - NODE_ENV=${NODE_ENV:-development}
      - RAILS_ENV=${RAILS_ENV:-development}
      - WEBPACKER_DEV_SERVER_HOST=0.0.0.0

volumes:
  postgres:
  bundle:
  node_modules:
  rails_cache:
  packs:

docker-compose.ymlの変更のポイント

  • redisなど、自分の使わないサービスを削除した
  • 認証情報は.envファイルに含め、.gitignoreに追加した
    • そのためenv_file: .envをyamlに2箇所追加した
    • (リポジトリの.envは空にしてあります)
  • credentialの暗号化機能はまだ使っていません

たとえ空であっても本当は.envをリポジトリに入れたくありませんでしたが、すぐ試せるようにしたかったのでした。

補足

このdocker-compose.ymlはローカル開発環境の構築のみを想定しています。Evil Martiansもそのように作っています。

PostgreSQLの接続文字列はdocker-compose.ymlに以下のように丸ごと埋め込まれています↓ので、db/database.ymlを開発用に設定する必要はありませんし、db/database.ymlにdevelopment環境用の設定を加えても効きません(ハマりました😅)。db/database.ymlを設定するのはCIやproductionぐらいになると思います。

  • DATABASE_URL=postgres://postgres:postgres@postgres:5432

docker-compose.ymlのimage名のyourapp_dockerの部分は適宜自分のプロジェクト名に変えてください。

追記(2021/03/27)

PostgreSQLを13にアップグレードしたところ、従来のパスワードなしログインが効かなくなったため、docker-compose.ymlに以下を追加しました。この設定はローカルのdevelopment以外では使わないでください。

- POSTGRES_HOST_AUTH_METHOD=trust

参考: PostgreSQL(Docker)にRails(Docker)が接続できなくなったから調べてみた。(could not translate host name “db” to address: Name or service not known) - Qiita

セットアップ

  • dipをインストールする(homebrewでもできます)
gem install dip
  • git clone git@github.com:hachi8833/rails_docker_like_evilmartians.gitでリポジトリをcloneしてディレクトリに移動する
  • docker-compose.ymlのyourapp_dockerは自分のプロジェクト名に変更する(本記事では変更していません)

  • git checkinしてコミット

  • dip compose buildでビルド

$ dip compose build
# 略
Successfully tagged yourapp_docker:1.0.0
  • dip shでコンテナにログインする
$ dip sh
Creating network "rails_docker_like_evilmartians_default" with the default driver
Creating volume "rails_docker_like_evilmartians_postgres" with default driver
Creating volume "rails_docker_like_evilmartians_bundle" with default driver
Creating volume "rails_docker_like_evilmartians_node_modules" with default driver
Creating volume "rails_docker_like_evilmartians_rails_cache" with default driver
Creating volume "rails_docker_like_evilmartians_packs" with default driver
root@7b09aea79b9f:/app#

以下はDockerコンテナ内部での作業です。

  • yarn -vruby -vbundle -vwhich railsが実行できることを確認
  • rails newに好みのオプションを付けて実行

なおbundle execは不要です。

今回は以下のオプションにしました。アプリ名はappで固定されます。Webpackerもまとめてセットアップされます。

rails new . --database=postgresql --skip-active-storage --skip-action-mailer --skip-active-job --skip-action-cable --skip-action-mailbox --skip-action-text --skip-turbolinks --skip-sprockets --skip-spring --skip-bootsnap --webpacker --webpack=vue

root@7b09aea79b9f:/app# rails new . --database=postgresql --skip-active-storage --skip-action-mailer --skip-active-job --skip-action-cable --skip-action-mailbox --skip-action-text --skip-turbolinks --skip-sprockets --skip-spring --skip-bootsnap --webpacker --webpack=vue
# 略
success Saved lockfile.
success Saved 12 new dependencies.
info Direct dependencies
├─ vue-loader@15.9.6
├─ vue-template-compiler@2.6.12
└─ vue@2.6.12
info All dependencies
├─ @vue/component-compiler-utils@3.2.0
├─ consolidate@0.15.1
├─ de-indent@1.0.2
├─ he@1.2.0
├─ merge-source-map@1.1.0
├─ prettier@1.19.1
├─ vue-hot-reload-api@2.3.4
├─ vue-loader@15.9.6
├─ vue-style-loader@4.1.3
├─ vue-template-compiler@2.6.12
├─ vue-template-es2015-compiler@1.9.1
└─ vue@2.6.12
Done in 6.40s.
Webpacker now supports Vue.js 🎉
  • 環境によっては以下も実行して、bundlerの余分なメッセージを抑制する
bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java
  • 使われていないvendorディレクトリをrm -rf vendorで削除
  • exitでDockerをいったん抜ける
  • git checkinする

起動前の設定

  • config/environments/development.rbに以下を追加する
config.hosts << "localhost"
config.web_console.whitelisted_ips = '0.0.0.0/0'

Rails 6からはconfig.hostを明示的に指定する必要があります(ウォッチ20190401)。

また、Dockerで開発する場合はおそらくwhitelisted_ipsの制約も解除が必要になります。

追記(2021/03/27): dip provisionを実行する前にGemfileのtestブロックに以下を追記し、dip bundle installを実行してrexml gemをインストールしてください。

gem 'rexml', '~> 3.2', '>= 3.2.4'

参考: Rails 6.1, Ruby 3.0.0: tests error as they cannot load rexml - Stack Overflow


  • dip provisionを実行してdevelopmentとtestの空データベースを作成
    • (dip.yamlをそれ用にカスタマイズしています)
    • データベース名はapp_developmentapp_testで固定
$ dip provision
# (略)
== Preparing database ==
Created database 'app_development'
== Installing dependencies ==
The Gemfile's dependencies are satisfied

== Preparing database ==

== Removing old logs and tempfiles ==

== Restarting application server ==
== Installing dependencies ==
The Gemfile's dependencies are satisfied

== Preparing database ==
Created database 'app_test'

== Removing old logs and tempfiles ==

== Restarting application server ==
  • 念のためdip minitestでminitestを実行
$ dip minitest
Starting rails_docker_like_evilmartians_postgres_1 ... done
Run options: --seed 59169

# Running:



Finished in 0.000523s, 0.0000 runs/s, 0.0000 assertions/s.
0 runs, 0 assertions, 0 failures, 0 errors, 0 skips
  • git checkinでコミット

いよいよ起動

できた!

$ dip rails s
Starting rails_docker_like_evilmartians_postgres_1 ... done
=> Booting Puma
=> Rails 6.0.1 application starting in development
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.0 (ruby 2.6.5-p114), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

後は好きに進めます。dip lsすればdipのサブコマンド一覧が表示されます。

作業が終わったらdocker-compose downでコンテナを終了します。

参考: 後からプロジェクトに参加する人の作業

  • Git、Docker for Mac、git-flow、dipなどをセットアップ
  • リポジトリをgit cloneする
  • dip compose build
  • dip bundle install
  • dip yarn install --check-files
  • .envに秘密鍵を入れるのであれば、.gitignoreに.envを追加する(必須)
  • データベースにseedデータを入れる(略)

Dockerfileを改定した場合は、docker-compose.ymlのimage名のタグにあるバージョン番号を適宜更新します。

おたより発掘

関連記事

クジラに乗ったRuby: Evil Martians流Docker+Ruby/Rails開発環境構築(翻訳)

Railsの技: pluckはActive RecordモデルでもEnumerableでも使える(翻訳)

$
0
0

概要

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

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

Railsの技: pluckはActive RecordモデルでもEnumerableでも使える(翻訳)

Railsには、これはレコードからデータのサブセットを(訳注: 配列として)取り出せるpluckという素晴らしく表現力の大きいメソッドがあります。Active Recordモデルのカラムを1個または数個取り出すのにpluckを使えます。

しかし、昔からある標準的なEnumerableに同じpluckメソッドを用いて、指定のキーに対応する値をすべて取り出すこともできます。

pluckの使い方

Railsでは、以下のようにpluckでカラムのサブセットにクエリをかけられます。

Shoe.all.map(&:name)
# SELECT "shoes.*" from "shoes"
# => ["Air Force 1", "NMD_2", "Air Jordans", ... ]
# すべての靴の名前を含む配列を返すが、データベースクエリは`shoes`テーブルのすべてのカラムを取得している

Shoe.pluck(:name)
# SELECT "shoes.name" from "shoes"
# => ["Air Force 1", "NMD_2", "Air Jordans", ... ]
# 上と同じ結果になるが、欲しいカラムだけを取り出すクエリをかける

Shoe.pluck(:name, :brand)
# SELECT "shoes"."name", "shoes"."brand" FROM "shoes"
# => [["Air Jordan 1 Mid", "Nike"], ["Air Jordan 1 Mid", "Nike"], ... ]

Shoe.distinct.pluck(:brand)
# SELECT DISTINCT "shoes"."brand" FROM "shoes"
# => ["Nike", "Adidas", ... ]

ActiveSupportが有効になっていれば、以下のようにEnumerableでもpluckを使えます。

[
  { id: 1, name: "David" },
  { id: 2, name: "Rafael" },
  { id: 3, name: "Aaron" }
].pluck(:name)

# => [ "David", "Rafael", "Aaron" ]

このEnumerablepluckは、特に外部APIから取得したJSONデータを扱うときに重宝することがわかってきました。

require "httparty"
require "active_support"
require "active_support/core_ext"

response = HTTParty.get('http://api.stackexchange.com/2.2/questions?site=stackoverflow')
questions = JSON.parse(response.body)["items"]
questions.pluck("title")

# => [
#   "JavaScript to Python - Interpreting JavasScript .filter() to a Python user",
#   "Nuxt generate and firebase gives timer warning",
#   "Variable expected error when I increment the value of a map",
#   ...
# ]

pluckが最もよく使われるのはハッシュですが、渡したメッセージに応答できるオブジェクト(通常のオブジェクトやStructも含む)なら何でもpluckを使えるようになります。

今後、値を1個取り出すのにmapを呼ぼうとしていることに気づいたら、pluckに切り替えてコードを改善できるかどうか試してみましょう。

関連リソース

関連記事

Railsの技: cycleメソッドでビューのループの「i%2==0」を避ける(翻訳)

週刊Railsウォッチ(20210406前編)GitHubが修正したRailsセッションハンドリングの競合、erb/haml/slimの速度比較ほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

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

今回は以下のコミットリストから見繕いました。Changelog更新はほとんどありませんでした。


つっつきボイス:「先週mimemagicの件があったせいか今週の変更は少なめでした」

週刊Railsウォッチ(20210329前編)特集: Rails更新版の臨時リリースとmimemagic gemのGPL問題

🔗 マイグレーション生成の--pretendオプションのエラーを修正

# railties/lib/rails/generators/actions/create_migration.rb#L22
        def invoke!
+         return super if pretend?
+
          invoked_file = super
          File.exist?(@destination) ? invoked_file : relative_existing_migration
        end

つっつきボイス:「マイグレーションに--pretendオプションがあるとは知らなかった」「dry run的なものかな?」

後でbin/rails g migration --helpして確認しました。

Runtime options:
  -f, [--force]                    # Overwrite files that already exist
  -p, [--pretend], [--no-pretend]  # Run but do not make any changes
  -q, [--quiet], [--no-quiet]      # Suppress status output
  -s, [--skip], [--no-skip]        # Skip files that already exist

また、手元のRails 5.2.5ではマイグレーションの--pretendはエラーにならず、Rails 6.1.3.1だとこのプルリクにかかれているのと同じエラーになりました。

🔗 ActiveSupport::NumericWithFormat#to_sを最適化


つっつきボイス:「to_snilチェックを移動して早期脱出させたようですね」

# activesupport/lib/active_support/core_ext/numeric/conversions.rb#L109
    def to_s(format = nil, options = nil)
+     return super() if format.nil?
+
      case format
-     when nil
-       super()
      when Integer, String
        super(format)
      when :phone
        ActiveSupport::NumberHelper.number_to_phone(self, options || {})
      when :currency
        ActiveSupport::NumberHelper.number_to_currency(self, options || {})
      when :percentage
        ActiveSupport::NumberHelper.number_to_percentage(self, options || {})
      when :delimited
        ActiveSupport::NumberHelper.number_to_delimited(self, options || {})
      when :rounded
        ActiveSupport::NumberHelper.number_to_rounded(self, options || {})
      when :human
        ActiveSupport::NumberHelper.number_to_human(self, options || {})
      when :human_size
        ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
      when Symbol
        super()
      else
        super(format)
      end
    end

case format when nilは最終的にNilClass === nilを呼ぶので非常に効率がよい。これはObject#==のエイリアスなので、基本的には最終的にnil == nilとなる。
しかしformat.nil?は専用のopコードのおかげで著しく速い。
この場合Integer#to_sは引数なしで呼ばれることが非常に多いので、頻度が最も高いケースについて最適化する価値がある。
同PRより大意

🔗 issue: Rails 5.2.5とCSRFトークンフォーマット

mimemagic に依存しなくなった rails 5.2.5 に CSRF トークンのフォーマットが Urlsafe Base64 になる変更が(意図せず?)入っているようで 5.2.5 で生成されたセッションが 5.2.4.x 以下 or 6.0.x で読めなくなってしまっているようなので rails 5.2.5 と 6.1.x 以外の rails を組み合わせて使っている人は要注意です。
hackmd.io『mimemagicの最新動向』より

参考: mimemagicの最新動向 - HackMD


つっつきボイス:「mametterさんやruby-jp Slackの有志がまとめてくださった上のmimemagic動向記事に追記されていたのを見て知りました」「Rails 5.2.5でRailsがmimemagicに依存しなくなったときにCSRFトークンフォーマットが変わってしまったということですね」

「Railsサーバーコンテナを複数動かして段階deployする場合は、deployが完了するまで旧フォーマットで動くRailsサーバーと新フォーマットで動くRailsサーバーが混在してエラーレートが跳ね上がる可能性があるので、規模の大きなサービスほど問題になるでしょうね(#41783コメント)」「なるほど」「mimemagicの件でアップグレードする時にチェックしておく必要はあると思います」「Rails 5.2向けに#41797がオープンしてますね↓」

🔗Rails

🔗 rails_multisite: Discourseから切り出されたRailsマルチサイトgem(Ruby Weeklyより)

discourse/rails_multisite - GitHub


つっつきボイス:「DiscourseのRailsから切り出されたマルチサイトgemだそうです」

参考: Discourse - Civilized Discussion

「以下のようにホスト名ベースでデータベースを切り替えられるようですね↓」

# config/multisite.yml

db_one:
  adapter: ...
  database: some_database_1

db_two:
  adapater: ...
  database: some_database_2

参考: Active Record で複数のデータベース利用 - Railsガイド

「いわゆるマルチテナントのRailsアプリで、データベースもテナントごとに分けたいというときに使うのかな?案件の事情によってはこういう設計にしないといけない場合もあるのかもしれないけど、マイグレーションやデプロイが大変そうですし、このようにひとつのRailsアプリでデータベースをテナントごとに分けるよりもRailsアプリのインスタンスを分ける方が一般的かなと思いました」「それもそうですね」「自分たちが運用することを考えるとアプリを分ける方が楽だと思います」

参考: Apartment でマルチテナントサービスを作成する - Qiita

🔗 GitHubが発見・修正したRailsセッションハンドリングの競合(Ruby Weeklyより)


つっつきボイス:「GitHubの技術ブログです」「この図がポイントかな↓」


同記事より

「Railsのセッションcookieでレアなrace conditionが発生したということらしい」「race conditionの解決は大変そう」「記事冒頭を見ると、少し前にGitHubからユーザーが突然ログアウトさせられた件について書かれていますね: そのときの修正内容の解説なのか」「あ、そういえば1か月ほど前にそんなことがありましたね」

参考: GitHub security update: A bug related to handling of authenticated sessions - The GitHub Blog

「これは読んでみてもよさそう👍」「もう少ししたらGitHubブログの日本語版にも公開されるかもしれませんね↓」

参考: GitHubブログ - 製品アップデートや開発に関するアイディアやインスピレーションなど、エンジニアの皆さんに役立つ情報を発信します。

🔗 erbとhamlとslimの速度を比較してみた


つっつきボイス:「単純に出力するならerbが一番速いんじゃないかな?」「条件を変えたりして測定してますね」

haml/haml - GitHub

slim-template/slim - GitHub

「ループの中で毎回newしないで、以下のように最初にnewしたインスタンスを使い回せば速くなる↓、たしかに」

# 同記事より
erb_engine = ERB.new(erb_example, 0, '-', '__result')
slim_engine = Slim::Template.new { slim_example }
haml_engine = Haml::Engine.new(haml_example)

Benchmark.bmbm(10) do |bcmk|
  bcmk.report("erb_test") { (1..2000).each { erb_engine.result binding } }
  bcmk.report("slim_test") { (1..2000).each{ __result = slim_engine.render(context) } }
  bcmk.report("haml_test") { (1..2000).each { __result = haml_engine.render(binding) } }
end

「ただ、CMSなどでキャッシュを一括生成するような場合なら上のように書けるでしょうけど、通常のWebレンダリングではこういう書き方はできないと思います」「あ、それもそうか」

「記事末尾にある現実的な例だと予想通りerbが一番速い↓」「そんなに大きくは違わなそうですね」

# 同記事より
$ hey -n 1200 http://localhost:3000/notes_erb/index

Summary:
  Total:        52.2586 secs
  Slowest:      19.2837 secs
  Fastest:      0.0389 secs
  Average:      0.6960 secs
  Requests/sec: 22.9627

$ hey -n 1200 http://localhost:3000/notes_haml/index
Summary:
  Total:        61.7637 secs
  Slowest:      18.5290 secs
  Fastest:      0.0442 secs
  Average:      0.8557 secs
  Requests/sec: 19.4289

$ hey -n 1200 http://localhost:3000/notes_slim/index

Summary:
  Total:        63.1625 secs
  Slowest:      19.9744 secs
  Fastest:      0.0874 secs
  Average:      0.7959 secs
  Requests/sec: 18.9986

🔗 Railsのジェネレータを改造する(Ruby Weeklyより)


つっつきボイス:「Railsのジェネレータを自分で作ったことあったのを思い出した」「ジェネレータを作るのって大変そうですけど」「既存のジェネレータのコードを見ながらやれば作れますよ: erbが二重になっている箇所があってそこは少し面倒ですが」

「自分はジェネレータを手作りまでしようとはあまり思わないかな」「ジェネレータに手を加えたらRailsをアップデートしたときに困ったりしませんか?」「既存のジェネレータを上書きするならともかく、独自のジェネレータを作るなら影響は小さいと思います」「なるほど」「単純なジェネレータなら一度自分で作ってみると勉強になりますよ🎓

🔗 RailsでキャッシュをクリアしたらSidekiqジョブが吹っ飛んた話(RubyFlowより)


つっつきボイス:「あ〜、redisのキャッシュをクリアしたらSidekiqジョブが飛んだのか、結構でかい事故」「これはキツそう」

mperham/sidekiq - GitHub

「redisはたしかデフォルトで16個のデータベースが使えて、キャッシュ用やジョブ用でデータベースを使い分けるものなんですが、それを同じデータベースに保存したらキャッシュをクリアした瞬間にジョブも消えるでしょうね」「う〜む」

参考: RedisのDB番号を増やす - tsunokawaのはてなダイアリー

redis/redis - GitHub

# 同記事より: Active Supportのコード
# Redisサーバーのすべてのキャッシュをクリアする。
# キャッシュが名前空間化されていれば共有サーバーでも安全。
#
# Failsafe: Raises errors.
def clear(options = nil)
  failsafe :clear do
    if namespace = merged_options(options)[:namespace]
      delete_matched "*", namespace: namespace
    else
      redis.with { |c| c.flushdb }
    end
  end
end

「元記事ではredisを名前空間化して修正したとあるけど↓、Redisのdatabase idを分ける方がいいんじゃないかな…」

# 同記事より
# config/application.rb
config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"], namespace: "rails" }

🔗 その他Rails

つっつきボイス:「お〜、Rubyのバックトレースをフィルタできるんですね」「add_silencerはどこかで見たことあったかも」

# api.rubyonrails.orgより
bc = ActiveSupport::BacktraceCleaner.new
bc.add_filter   { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix
bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems
bc.clean(exception.backtrace) # perform the cleanup

前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210330後編)Active Recordモデル属性暗号化が標準で入る可能性、Flipper Cloud、awesome_printほか

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

週刊Railsウォッチ(20210407後編)エイプリルフールのRuby構文プロポーザル、AWSのVPC Reachability Analyzerほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Ruby

🔗 エイプリルフールのRuby構文2つ


つっつきボイス:「mameさんがエイプリルフールに提案したこのRuby文法は強烈」「issueのタグにjokeと書いてあるのを見るまですっかり本気にしちゃってました😅」「downward assignmentという名前まで付けるとは」「あまりのことについ笑っちゃいました😆

# 17768より
puts("Hello" + "World")  #=> HelloWorld
     ^^^^^^^x  ^^^^^^^y

p x  #=> "Hello"
p y  #=> "World"

^^^^^^^xで『この上がxだよ』、^^^^^^^yで『この上がyだよ』という二次元的な表記」「すごい構文」「mameさんよく思いつくな〜」「下向きのvvvvまで作ってますね↓」「irbではつらいのでは?というレスも付いてる(#17768)」

# 17768より
      vvvv line
while gets

「本当にこんなふうに書けたら面白いかも」「行を超える解析になると大変そう」「パーサーの前で前処理すればやれるかも?」


その後mameさんが自らissueをrejectしていました。後で気づきましたがパッチまで作ってあったんですね。


ugs.ruby-lang.orgより

以下はつっつき後に見つけたツイートです。


同じ日にmrknさんもジョーク構文をアップしていたことに後から気づきました↓。Julia言語のように「係数を掛け算記号なしで書ける」構文だそうです。

# #17769より
irb(main):001:0> x = 3
=> 3
irb(main):002:0> 2x
=> 6
irb(main):003:0> def pi = Math::PI
=> :pi
irb(main):004:0> 2pi
=> 6.283185307179586

🔗 fast_statistics: Ruby向け高速な統計ライブラリ


つっつきボイス:「統計的な処理をC++で高速化したライブラリですね」「さすがに速い」

# 同リポジトリより
require "fast_statistics"

FastStatistics::Array2D.new(data).descriptive_statistics
# Result: 
#
# [{:min=>0.1477,
#   :max=>0.6269,
#   :mean=>0.347575,
#   :median=>0.30785,
#   :q1=>0.214975,
#   :q3=>0.44045,
#   :standard_deviation=>0.18100761551658537},
#  {:min=>0.1055,
#   :max=>0.8,
#   :mean=>0.38217500000000004,
#   :median=>0.3116,
#   :q1=>0.1781,
#   :q3=>0.515675,
#   :standard_deviation=>0.26691825878909076},
#  ...,
#  {:min=>0.3918,
#   :max=>0.8546,
#   :mean=>0.639025,
#   :median=>0.6548499999999999,
#   :q1=>0.536025,
#   :q3=>0.75785,
# 同リポジトリより
Comparing calculated statistics with 10 values for 8 variables...
Test passed, results are equal to 6 decimal places!

Benchmarking with 100,000 values for 12 variables...
Warming up --------------------------------------
descriptive_statistics   1.000  i/100ms
           Custom ruby   1.000  i/100ms
                narray   1.000  i/100ms
ruby_native_statistics   1.000  i/100ms
        FastStatistics   3.000  i/100ms
Calculating -------------------------------------
descriptive_statistics   0.473  (± 0.0%) i/s -      3.000  in   6.354555s
           Custom ruby   2.518  (± 0.0%) i/s -     13.000  in   5.169084s
                narray   4.231  (± 0.0%) i/s -     22.000  in   5.210299s
ruby_native_statistics   5.962  (± 0.0%) i/s -     30.000  in   5.041869s
        FastStatistics   28.417  (±10.6%) i/s -    141.000  in   5.012229s

Comparison:
        FastStatistics:   28.4 i/s
ruby_native_statistics:    6.0 i/s - 4.77x  (± 0.00) slower
                narray:    4.2 i/s - 6.72x  (± 0.00) slower
           Custom ruby:    2.5 i/s - 11.29x  (± 0.00) slower
descriptive_statistics:    0.5 i/s - 60.09x  (± 0.00) slower

🔗 geminabox(Ruby Weeklyより)

geminabox/geminabox - GitHub


つっつきボイス:「rubygems.orgでやっているようなgemのホスティングを簡単に行えるようですね」「★が1400超えてる」「プロキシも簡単に設定できるらしい↓」

RUBYGEMS_PROXY=true rackup

「そういえばRubyGemsのgemコマンドにも似たような機能がありませんでしたっけ?」「使ったことはありませんが、そういえばあったような」

以前gem serverコマンドについて記事を書いたのを後で思い出しました↓。

gem serverでローカルgemサーバーを立ててみよう

「もしかするとこの間のminimagicの件↓でこうしたgemのセルフホスティングのニーズがにわかに高まったりするかも」「それありそうですね」

週刊Railsウォッチ(20210329前編)特集: Rails更新版の臨時リリースとmimemagic gemのGPL問題

🔗 その他Ruby

こちらは日本時間今夜18:00の開催です。もう始まっている頃ですね。

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

🔗 AWSのVPC Reachability Analyzerは便利


つっつきボイス:「VPC Reachability Analyzerは昨年末にリリースされているみたいですね」

参考: 新機能 – VPC Reachability Analyzer | Amazon Web Services ブログ

「へ〜、このツールは到達グループやセキュリティグループも可視化してくれるのがなかなかいいですね」「おぉ」「この図↓の上の端と下の端はインスタンスのアイコンなので、インスタンスからインスタンスへの接続の可視化もできるらしい」


aws.amazon.comの同記事より

「VPC接続でどこまで到達できているのかを確認するのは地味に面倒なんですが、こういうツールの存在を知っていれば便利そうですね: 今度使ってみよう👍」「いいこと知りました!」

追記(morimorihoge)

VPC Reachability Analyzerその後実際に業務で使ってみた感想です。

設定できる到達パスのsource / destination設定がAWSリソース(ENIインターフェースやEC2インスタンス、各種Gateway)であり、IPアドレスなどでの指定ができないためtracerouteなどのIPネットワーク系ツールのつもりで使おうとすると最初ややとっつきにくいところがあります。

ただ、AWSリソースベースであるが故にセキュリティグループなども参照して見てくれること、一度作った設定を再テストさせることが容易なことから、ある程度複雑な構成(複数VPCだったり、外部のAWSアカウントとVPC Peering / Transit Gateway接続する)の場合には有用なトラブルシューティングツールとして利用できそうです。知っておいて損はないツールかなと思いました。

🔗言語/ツール/OS/CPU

🔗 MacBookの物理キーボードが廃止?


つっつきボイス:「まさかこの記事もエイプリルフールじゃないかと心配になってきました」「日付は3/31だから大丈夫でしょう😆

「MacBookのタッチバーは触ったときの手応えがなくて使いにくかったけど、あれにフォースフィードバックがあれば随分違っていただろうと思うので、Appleの新しいキーボードがそこを解決したなら見込みがあるかもしれませんね」「それたしかに!」「一方トラックパッドの場合は、随分前に物理的な押し込みスイッチがなくなってハプティック(感圧タッチ)トラックパッドに変わったときは使いにくくなるのかなと思ったけど、実際はまったく問題なく使えましたね」「あれをハプティックって言うとは知りませんでした」

参考: 次期MacBook Pro、ついにTouch Bar廃止で物理ファンクションキー復活のうわさ - Engadget 日本版
参考: 感圧タッチトラックパッドの使い方 - Apple サポート

「記事にあるAppleの新しいタッチ式のキーボードも、実際に登場して触ってみたら案外印象が変わって流行ったりするかも」「早く触って確かめたいです」「物理キーボードがなくなれば故障の可能性も減りますよね」


そういえばiPhoneの「戻るボタン」も割と前から物理ボタンではなくなっていますね。たまたまiPhoneの電源を落としたときに戻るボタンの手応えが消滅していて、初めてそれに気づきました。

参考: ASCII.jp:iPhone 7のホームボタンは“ボタン”ではない

🔗 CPUの話題2つ


つっつきボイス:「新しいモバイル向けのRyzenは、アイドル時の消費電力がかなり抑えられていたりして徐々によくなってきてるみたいですね」「モバイルRyzenの今後に期待したくなってきました」「Intel CPUが今後大きく進化するのは難しそうかなと思っています」

「ARMも新しいアーキテクチャを発表」「今さらですけど、ARMが出しているのはアーキテクチャだけということでいいんでしょうか?」「現在のARMはアーキテクチャの仕様を設計してライセンス販売しているので、そうですね」「なるほど」「そのうちQEMUでArmv9が動くようになるかも」

参考: ARMアーキテクチャ - Wikipedia

🔗 その他

🔗 最近のセキュリティ関連

つっつきボイス:「kramdownというmarkdown用gemで見つかった脆弱性が2.3.1で修正されたそうです」「3日前(3/31)に出ているからかなり最近なのか」「markdownでリモートコード実行はなかなかヤバいですね」「kramdown自体はそこそこメジャーなGemなので、CMSなどを運用・開発しているプロジェクトでは念のため確認した方がよいと思います」

gettalong/kramdown - GitHub

追記(morimorihoge)

その後確認したところ、Redmineが採用しているMarkdown対応Gemはredcarpetだったので、別途プラグインとかを入れてなければ恐らく大丈夫かなと思います。

vmg/redcarpet - GitHub


「もうひとつはphp.netにバックドアが仕掛けられてgit.php.netが奪われた: これはキツい」「この件でPHPリポジトリがGitHubに引っ越したそうですね↓」「久しぶりに大きな事案かも」「今回の場合は早期に発見・対応されたのが幸いですね」「たしかに」「PHPのnightlyビルドを自動取得していたところは影響があったかもしれませんが、リリース版PHPに影響が出たという話は見かけていないので普通にPHPを使っているところは今のところ大丈夫そうかな」

php/php-src - GitHub

「開発者のアカウントがいったん乗っ取られたらどうしようもないので、2要素認証(2FA)や多要素認証(MFA)を義務付けたのはわかる」「正直とても面倒ですが、そうするよりしょうがないですよね」

参考: 多要素認証 - Wikipedia
参考: 二要素認証と二段階認証の違いを理解していますか? | サイバーセキュリティ情報局

🔗 パブリックIPアドレスをDNS経由で知る方法(StatusCode Weeklyより)


つっつきボイス:「この記事で紹介されている方法って珍しいんでしょうか?」「どれどれ👀

「以下↓を実行すると自分がいるネットワークのパブリックIPアドレスがわかるとある、本当かな?」(しばしやってみる)「本当に取れた!」「こっちも取れました」

# 同記事より
dig @ns1.google.com TXT o-o.myaddr.l.google.com +short

参考: dig コマンド|Linuxコマンド

o-o.myaddr.l.google.comって公に公開されているのかな?」「ググってみるとAWSのドキュメントにもこの方法が載ってた↓」「ホントだ」「他にもいくつか方法が載ってますね」「知る人ぞ知る機能という感じ」「使っても大丈夫そう」

参考: Route 53 の DNS リゾルバーの IP アドレスが ECS 拡張機能をサポートしているかどうかを確認する


後で、Googleのドキュメントにもo-o.myaddr.l.google.comについて記載があったのをやっと見つけました↓。

参考: Google Maps Platform ルート認証局(CA)の移行に関するよくある質問  |  Google Developers

🔗 EDNS

「AWSのドキュメントに出てくる”EDNS”という言葉をググったらこれが出てきた↓」「RFC7871で仕様が定義されているんですね」

参考: インターネット用語1分解説~EDNS Client Subnetとは~ - JPNIC
参考: RFC 7871 - Client Subnet in DNS Queries

EDNS Client Subnetとは、 DNSの拡張プロトコルであるEDNS01を用いて、 問い合わせ先のDNSサーバに対して問い合わせ元の情報を伝達する技術であり、 RFC78712で標準化されています。
www.nic.ad.jpより

「以下の記事↓にはEDNSはGoogle public DNSやCloudFrontやAkamaiが対応していると書かれているということは、どうやらCDNに関連する技術らしい」「知らなかった」「推測ですが、CDN業者がクライアントのDNSリクエストを最寄りのエッジに振り向けるのにEDNSのような技術が必要なのかもしれませんね」

参考: JANOG38 :: EDNS-client-subnetってどうよ? 改めRFC7871ってどうよ

増加するコンテンツトラフィックに対応するため送信側は複数個所から送信を行っている。適切な送信箇所を選択するための技術としてEDNS-client-subnetが提案され、Google public DNS(8.8.8.8)やAmazon CloudFront, Akamaiが対応している。
www.janog.gr.jpより

参考: コンテンツデリバリネットワーク - Wikipedia


「ついでにcurlコマンドでIPを取る方法も見つけた↓」「こんなのまであるんですね」「ifconfig.ioやifconfig.me、myip.opendns.comとかいろんなのが使えるらしい」「lvh.meのような感じで誰かが考えそうなサービスかも」

参考: 【Linux】curlでグローバルIPを確認 (まとめ) | Startialab Dev Blog

# dev.startialab.blogより
curl ifconfig.io

lvh.me ドメインを使って、サブドメイン形式の WordPress マルチサイトの Docker 開発環境構築をしよう


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210406前編)GitHubが修正したRailsセッションハンドリングの競合、erb/haml/slimの速度比較ほか

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

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

Ruby Weekly

StatusCode Weekly

statuscode_weekly_banner

Viewing all 1387 articles
Browse latest View live