鹿児島 K-Ruby#24 に行ってきた

https://k-ruby.connpass.com/event/151523/

10/31に開催されたK-Ruby#24に行ってきた。

LTのタイトル一覧

  1. 持田捷宏 「マジックリンク認証やってみた」
  2. 平石太郎 「エンジニアの知的生産術という本の紹介」
  3. 岩丸慎平 「エッジデバイスとObject Detection」
  4. 福重章太 「高卒未経験からweb系エンジニアへの転職」
  5. 長重佑  「Flutter+Railsで爆速アプリ開発
  6. 林健太郎「"Kenkenize"で触れるRubyの中身」
  7. 中島成博 「Railsで開発したアプリの紹介とReactについて」
  8. 下園幸一 「CentOS 8でのRuby環境」

K-Ruby自体がLTのテーマを幅広く募集していることもあってか、多彩なトークテーマ。

エッジコンピューティングやFlutterの話が出てくるとは思っていなかった。

あとはエンジニアのキャリアの話もちらほら。平石さん、福重さん、中島さん。お三方ともエンジニアとして勉強や仕事を始めてまだ日が浅いとのことで、経験や知識が少ない中でエンジニアとしてのキャリアを進んでいく上での体験や想いを発表していた。自分が同じような状況の時に、こういう場でLTをする勇気?度胸?はなかったと思う。

栗林さん(あんちぽさん(弊社CTO))がしていたLTはメタプロっぽい話で、あまりこれまで触れてこなかったので興味を持つ良いきっかけになった。帰りの新幹線でコードリーディングしてたらバグみっけたので速でプルリク出した。我ながら良い速だったと思っている。

K-Rubyは大学の先生や学生さんの発表が多くてユニークだなぁと思った。また参加したい。

Rails CHANGELOG "2019-10-28".."2019-11-03"

この期間の CHANGELOG.md へのコミットは1件。

ActionMailerに関しての1件のみ。

ActionMailer

テストヘルパーの assert_enqueued_emails メソッドが ActionMailer::Parameterized::DeliveryJob に対して期待通りに動作しない不具合を修正した

コミット: Fix ActionMailer assertions don't work for parameterized mail with le… · rails/rails@37b72ff

具体的に起きていた問題はこのissue assert_enqueued_emails does not work when sending a parameterized email · Issue #37605 · rails/rails に書いてある。

読んでみると ActionMailer::DeliveryJob を使ったこれ↓はテスト通るけど、

assert_enqueued_emails 1 do
  SubscriptionMailer.welcome.deliver_later
end

ActionMailer::Parameterized::DeliveryJob を使ったこれ↓はテスト通らない、

assert_enqueued_emails 1 do
  SubscriptionMailer.with(User.first).welcome.deliver_later
end

という問題が起きていたらしい。

ふむふむ。

ActionMailer::DeliveryJob は非同期でメール送信を行ないたい場合に使うやつ。

ActionMailer::Parameterized::DeliveryJobActionMailer::DeliveryJob を継承している。

ざっくりいうとこの2つは .with(params) でパラメータを渡すかどうかの違いしかない(雑)

ActionMailer::DeliveryJob

def perform(mailer, mail_method, delivery_method, *args) #:nodoc:
  mailer.constantize.public_send(mail_method, *args).send(delivery_method)
end

引用: rails/delivery_job.rb at f5050d998def98563f8fa4b381c09f563681f159 · rails/rails

ActionMailer::Parameterized::DeliveryJob

def perform(mailer, mail_method, delivery_method, params, *args)
  mailer.constantize.with(params).public_send(mail_method, *args).send(delivery_method)
end

引用: rails/parameterized.rb at 5d6578d15bb5d3bfe3751a6493b9e3fe99618408 · rails/rails

なんで同じようなことやってるのに後者だとテスト落ちるの?という話。

原因はActionMailer::TestHelper でジョブクラスを判定する際に使われている delivery_job_filter メソッド。 ActionMailer::Parameterized::DeliveryJob の考慮が漏れていた。


ちなみに ActionMailer::DeliveryJob を使うと「Rails 6.1系で消す予定だよ〜、代わりに MailDeliveryJob を使ってね〜」とwarningが出る。

before_perform do
  ActiveSupport::Deprecation.warn <<~MSG.squish
    Sending mail with DeliveryJob and Parameterized::DeliveryJob
    is deprecated and will be removed in Rails 6.1.
    Please use MailDeliveryJob instead.
  MSG
end

引用: rails/delivery_job.rb at f5050d998def98563f8fa4b381c09f563681f159 · rails/rails

この記事 Rails6 のちょい足しな新機能を試す41(MailDeliveryJob 編) - Qiita を読むと理解が捗った。

Rails CHANGELOG "2019-10-21".."2019-10-27"

この期間の CHANGELOG.md へのコミットは1件。

ActiveRecordに関しての1件のみ。

ActiveRecord

where 句に空配列が渡された時にクエリを発行せずに空配列を返すようにした

コミット: Add changelog for IN empty array · rails/rails@54f3e67

修正自体はこのプルリク Avoid making query when using `where(attr: [])` by jhawthorn · Pull Request #37266 · rails/rails でやってる。

修正前

> User.where(id: [])
  User Load (0.3ms)  SELECT "users".* FROM "users" WHERE 1=0
[]

引用: Avoid making query when using `where(attr: [])` by jhawthorn · Pull Request #37266 · rails/rails

修正後

> User.where(id: [])
[]

引用: Avoid making query when using `where(attr: [])` by jhawthorn · Pull Request #37266 · rails/rails

無駄なクエリが発行されなくなって嬉しいすね〜

Rails CHANGELOG "2019-10-14".."2019-10-20"

この期間の CHANGELOG.md へのコミットは3件。

ActiveRecordに関して1件, ActiveSupportに関して2件。

ActiveRecord

MySQLprimary_key を取得する処理を高速化した

コミット: Fixed the performance regression for `primary_keys` introduced MySQL 8.0 · rails/rails@77f4ae0

ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#primary_keys の修正。

issueはこれ Fixed performance regression introduced MySQL 8.0 by alpaca-tc · Pull Request #37465 · rails/rails

MySQL8.0でメタデータのストレージが metadata files から data dictionary に変わり、その結果 information_schema.key_column_usage の速度が下がった。

primary_keys メソッドでは information_schema.key_column_usage を使っているので影響を受けていた。

この修正では、代わりに information_schema.statistics を使うことで高速化している。

ActiveSupport

ActiveSupport::EncryptedFilecontent_path に symbolic link を使えるようにした

コミット: Keep symbolic link after editing credentials.yml.enc · rails/rails@6db6432

symbolic link を作ったあとに credentials.yml.enc を編集すると、 symbolic link が消えて通常のファイルで上書きされてしまうのを修正している。

@content_path = Pathname.new(content_path)

@content_path = Pathname.new(content_path).yield_self { |path| path.symlink? ? path.realpath : path }

ActiveSupport.parse_json_timestrue の場合に、 ActiveSupport::Messages::Metadata.verify がエラーにならないよう修正した

コミット: Fix Messages::Metadata#fresh? to handle parse_json_times = true · rails/rails@6e0aa18

verifyメソッドで使っている fresh? メソッドで、 Time.iso8601(@expires_at) となっていた。 ActiveSupport.parse_json_timestrue の場合は @expires_at が String ではないのでエラーになる。

  def fresh?
-   @expires_at.nil? || Time.now.utc < Time.iso8601(@expires_at)
+   @expires_at.nil? || Time.now.utc < @expires_at
  end

ん、これ ActiveSupport.parse_json_timesfalse の場合はどうなるの。よくわかってないけど。Stringが渡ってくるんじゃないの?

と思っていたら別コミットで修正されてた。

Follow-up 6e0aa1887d: So we do need the wrapper, doh · rails/rails@37e87fd

ブログ開設して1年が経っていた

f:id:tanaken0515:20191020084715p:plain

メールが来てた。

数えたらこの記事で30記事目だった。

ほとんどの記事はですます調だったけど、最近はやめた。

自分はですます調で文章を書くと、なんとなく「化粧した文章」になってしまうと感じ始めたから。

ほんとに書きたいことを書けないというか。別に嘘を書いてるわけじゃないんだけど。

書きなぐっている感のある文章の方が素直にかけるような、そんな気がしてる。

特に最近は、自分方向へのアウトプットというか、未来の自分がリユース(?)するための文章というかんじ。

Scrapboxも使っているから、そこに全部まとめても良いんだけど。

Scrapboxは「寝ぐせついてて無精髭でパジャマ」、ブログは「一応寝ぐせ直してヒゲは剃っておくか〜服は適当だけど」くらいの使い分けをしてる。

Rails CHANGELOG "2019-10-07".."2019-10-13"

この期間の CHANGELOG.md へのコミットは3件。

ActionPack, ActiveModel, ActiveStorageに関してそれぞれ1件。

ActionPack

ActionDispatch::Request クラスのインスタンス変数 @remote_ip を更新できない不具合を修正した

コミット: Updated `ActionDispatch::Request.remote_ip=` · rails/rails@bf14a8e

修正は remote_ip= メソッドの @remote_ip = nil の一行だけ。

def remote_ip
  @remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s
end

def remote_ip=(remote_ip)
  @remote_ip = nil
  set_header "action_dispatch.remote_ip", remote_ip
end

引用: rails/request.rb at bf14a8e23526447b9893ed66090f898da68eb7f1 · rails/rails

@remote_ip = nil をせずに remote_ip メソッドを呼んでいたため、set済みの @remote_ip のまま書き変わらない不具合があった。

ActiveModel

freezeされた ActiveModel のオブジェクトに対して attribute の書き変えをできないようにした

コミット: Raise FrozenError for frozen objects when trying to write to a · rails/rails@54b1574

概要はCHANGELOG.mdにある例の通り。

class Animal
  include ActiveModel::Attributes  
  attribute :age 
end

animal = Animal.new
animal.freeze 
animal.age = 25 # => FrozenError, "can't modify a frozen Animal"

引用: rails/CHANGELOG.md at 54b157442171d28c7e9553537aef7a6ae571aad7 · rails/rails

修正は ActiveModel:: Attributes::ClassMethods モジュールの define_method_attribute= メソッド。

def define_method_attribute=(name)
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
    generated_attribute_methods, name, writer: true,
  ) do |temp_method_name, attr_name_expr|
    generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
      def #{temp_method_name}(value)
        raise FrozenError, "can't modify frozen #{self.class.name}" if frozen?
        name = #{attr_name_expr}
        write_attribute(name, value)
      end
    RUBY
  end
end

引用: rails/activemodel/lib/active_model/attributes.rb at 54b157442171d28c7e9553537aef7a6ae571aad7 · rails/rails

なるほどこういう実装になっていたのか〜。

ActiveStorage

ActiveStorage::Blob#url でパーマネントリンクを作れるようになった

コミット: Permanent URLs for public storage blobs · rails/rails@feab703

config/storage.yml に新しいキー public: true | false を設定することで、 true の場合はパーマネントリンクを作ることができる。未設定の場合は false で、今まで通り期限付きのリンクになる。

ActiveStorage::Blob#url を見てみる。ここでは service.url(ActiveStorage::Service#url) を呼んでる。

def url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline, filename: nil, **options)
  filename = ActiveStorage::Filename.wrap(filename || self.filename)

  service.url key, expires_in: expires_in, filename: filename, content_type: content_type_for_service_url,
    disposition: forced_disposition_for_service_url || disposition, **options
end

引用: rails/blob.rb at feab7031b57040afa2b2f5f78f9251dbad6cbdf8 · rails/rails

ActiveStorage::Service#url を見てみる。if public?public_url or private_url に振り分けてる。

def url(key, **options)
  instrument :url, key: key do |payload|
    generated_url =
      if public?
        public_url(key, **options)
      else
        private_url(key, **options)
      end

    payload[:url] = generated_url

    generated_url
  end
end

引用: rails/service.rb at feab7031b57040afa2b2f5f78f9251dbad6cbdf8 · rails/rails

ActiveStorage::Service クラスの public_urlprivate_url は未実装(raise NotImplementedError)となっており、このクラスを継承している各ストレージサービスのクラスで public_urlprivate_url を実装している。具体的な実装は各クラスを参照。

子クラスで実装を分けたい(かつ未実装だったらエラー吐きたい)ときに、親クラスで raise NotImplementedError にしとくの便利。よくある(定石的な)実装なのかな。これまでまともにOSSのコードリーディングしてなかった&設計関連の本もほぼ読んでないからこの辺の設計論?みたいなもの、自論として確立できてないんだよなぁ。ともかく勉強になった。

Rails CHANGELOG "2019-10-01".."2019-10-06"

この期間の CHANGELOG.md へのコミットは5件。

ActiveRecordに関して2件、ActiveStorageに関して3件。

ActiveRecord

has_secure_token に文字数を指定できるようになった

コミット: Merge pull request #35915 from bernardoamc/allow-has-secure-token-len… · rails/rails@a273da7

もともとは24文字で固定だった。24文字以上の文字数を指定できるようになったので、セキュリティ的な要件で「めっちゃ長くしたい」場合もこれで安心。

Before:

has_secure_token :auth_token

After:

has_secure_token :default_token             # 24 characters
has_secure_token :auth_token, length: 36    # 36 characters
has_secure_token :invalid_token, length: 12 # => ActiveRecord::SecureToken::MinimumLengthError

引用: rails/activerecord/CHANGELOG.md at a273da7619ac6a2b2f98532a5610238c68ad219b · rails/rails

belongs_toinverse_of: オプションを has_many associationでも使えるようになった

コミット: Add support for belongs_to to has_many inversing. · rails/rails@d45c9ad

もともと belongs_toinverse_of: オプションは has_one associationにしか対応していなかった。これを has_many の場合でも使えるようになった。

たぶんこんな感じ(動作確認はしてない)

class User < ApplicationRecord
  has_many :writings,
           class_name: 'Book'
end
 
class Book < ApplicationRecord
  belongs_to :author,
             class_name: 'User',
             foreign_key: 'author_id'
             # ここに inverse_of: 'writings' を指定できるようになった。 
end

ActiveStorage

添付ファイルごとに保存先の外部サービスを選べるようになった

コミット: Allow configure services for individual attachments · rails/rails@e7f798c

ActiveStorageあまり触っていなくてよく分からん太郎だったのでRailsガイドで復習した。Active Storage の概要 - Rails ガイド

「セットアップ」に書かれているようにActiveStorageで使うサービスは config/storage.yml で定義して、環境ごとに config/environments/ 配下で設定する。これだと同一環境内で使えるサービスは1つだけになる。例えば「production では Amazon S3 を使う!!!1」など。

で、今回のコミットでは添付ファイルごとに保存先のサービスを選べるようになるよ、便利だね、という話。

class User < ActiveRecord::Base
  has_one_attached :avatar, service: :s3
end
class Gallery < ActiveRecord::Base
  has_many_attached :photos, service: :s3
end

引用: rails/activestorage/CHANGELOG.md at e7f798c3f549502d696167e9b29f18106cf2cbe0 · DmitryTsepelev/rails

active_storage_blobs テーブルに service_name カラムが追加されているからversion upの際にはmigrationが必要になりそう。

外部サービスに画像のvariantをアップロードするときにContent-Typeを指定するようになった

コミット: Set Content-Type on variant uploads · rails/rails@698e9ce

Content-Typeが指定されていなかったので指定したよ、というコミット。

Content-Typeは delegate :filename, :content_type, :format, to: :specification となっていて specification はこうなってた。

def specification
  @specification ||=
    if WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
      Specification.new \
          filename: blob.filename,
          content_type: blob.content_type,
          format: nil
    else
      Specification.new \
          filename: ActiveStorage::Filename.new("#{blob.filename.base}.png"),
          content_type: "image/png",
          format: "png"
    end
end

引用: rails/variant.rb at 698e9ce0ffb516564e02053872077a2d5a287617 · rails/rails

ので、Content-Typeは WEB_IMAGE_CONTENT_TYPES(= %w[ image/png image/jpeg image/jpg image/gif ]) のどれか or "image/png" になる。

明示的にサービス名を指定しなくてもよくなった

コミット: Make Active Storage services aware of configuration names · rails/rails@e6487e8

1つ目の 添付ファイルごとに保存先の外部サービスを選べるようになった に関連する修正。

ActiveStorage::Service::Configurator クラスをbuildするときに config/storage.yml に指定されているサービス名を使うよう修正されている

def build(service_name)
  config = config_for(service_name.to_sym)
  resolve(config.fetch(:service)).build(
    **config, configurator: self, name: service_name
  )
end

引用: rails/configurator.rb at e6487e84fc0dae30d3b20b396404881e962f1f3a · rails/rails

で、ActiveStorage::Service クラスにインスタンス変数 name が追加され、has_one_attached :avatar, service: :s3 のようにサービスが指定された時だけ、その値で上書きする、という感じになっている

def build(configurator:, name:, service: nil, **service_config) #:nodoc:
  new(**service_config).tap do |service_instance|
    service_instance.name = name
  end
end

引用: rails/service.rb at e6487e84fc0dae30d3b20b396404881e962f1f3a · rails/rails