Madogiwa Blog

主に技術系の学習メモに使っていきます。

Ruby: Haml v6アップデート時のカスタム属性の振る舞いの互換性を維持するメモ📝

Haml v6からHamlの内部実装がHamlitに置き換わりパフォーマンス向上等のメリットがありますが、

github.com

以下のissueでコメントされている通り、

github.com

Vue.js等を利用している場合に以下のようなfalsyの値がv5系では<cutsom-element />となっていたのが、

%cutsom-element{ ":costom-attributes": nil  }

Haml v6では<cutsom-element :costom-attributes='' />となってしまいます。

上記の場合Vue.jsを利用しているHaml v6の挙動ではpropsのデフォルト値が利用されなくなり、明示的にundefinedを渡すような実装に修正する必要があり非常に影響が大きいです😢

これに対応する仕組みが以下のHaml v6.2.2でリリースされたHaml::BOOLEAN_ATTRIBUTESに任意の属性名の文字列・正規表現を追加することでHaml v5相当の振る舞いにすることができます。

github.com

全部やるなら以下とすれば良さそうですが、

Haml::BOOLEAN_ATTRIBUTES.push(/.*/)

この変更自体がパフォーマンス向上のトレードオフで発生しているもののようなので、パフォーマンスに問題がないか等は計測して対応を入れる必要がありそうです。

It varies. No impact, as slow as Haml 5, or slower than Haml 5, depending on the benchmark.

https://github.com/haml/haml/issues/1148#issuecomment-1754421295

📝以下のようなスクリプトで既存のhamlファイルを読み込んで利用されているカスタムタグの属性のリストを抽出し、それがだけ許可するような感じでもいいのかもしれない🤔

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'haml'
end

require 'haml/parser'

haml = <<~HAML
  %h1 Hello!
  %custom-element{ ":custom-attributes1": false, "string-attributes1": "hello" }
  %custom-element{ ":custom-attributes2": nil, "string-attributes2": "" }
  %p
    %span World!
HAML

parser = Haml::Parser.new({})
parsedHaml = parser.call(haml)

def extract_tags_and_attributes(node)
  result = []

  node.children.each do |child|
    tag_name = child.value[:name]
    attribute_names = child.value[:attributes].keys
    dynamic_attributes = child.value[:dynamic_attributes]
    dynamic_attribute_hash = dynamic_attributes.old ? eval(dynamic_attributes.old) : {}
    result << { name: tag_name, attributes: attribute_names + dynamic_attribute_hash.keys }
    result.concat(extract_tags_and_attributes(child))
  end

  result
end

tags_and_attributes = extract_tags_and_attributes(parsedHaml)
is_custom_tag = ->(tag) { tag[:name].include?("-") }

puts tags_and_attributes.select(&is_custom_tag)
                        .flat_map { |tag| tag[:attributes] }
                        .uniq
# =>
# :custom-attributes1
# string-attributes1
# :custom-attributes2
# string-attributes2

`vscode-standard-ruby`でプリインストールのrubyが利用されてしまうのを直した時のメモ📝

vscode-standard-rubyでプリインストールのrubyで実行されてしまいパッケージマネージャーで関しているバージョンで実行されずLSPが落ちてしまいハマったので対応したことをメモ📝

github.com

事象

以下のようにターミナルで確認するとパッケージマネージャで管理している最新のRubyが利用されているが、

$ ruby -v
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-darwin22]

vscode-standard-rubyのLSPの起動時に以下の通り謎にプリインストールのrubyが利用されてしまっており失敗していた。

[client] stderr:
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems.rb:283:in `find_spec_for_exe': Could not find 'bundler' (2.4.10) required by your /Users/morita.jun/Documents/repo/dogfeeds/Gemfile.lock. (Gem::GemNotFoundException)

解決方法

以下の設定が入っていたので削除し、ターミナルで利用しているもの(zsh)が利用されるようにした。

- "terminal.integrated.defaultProfile.osx": "bash"

そしてVSCodeを単純に再起動するのではなく、ターミナルからcodeコマンドでVSCodeを再起動するとパッケージマネージャで管理しているRubyが利用されLSPの起動に成功した🤔

[server] Standard Ruby v1.32.0 LSP server initialized, pid 81066

"terminal.integrated.defaultProfile.osx": "bash"の設定によりbashで最初起動してしまったので、zshのターミナルで再起動するまではパッケージマネージャーの設定が無いbashが利用されてしまいプリインストールのRubyが使われてしまっていたっぽい?(VSCodeは起動時のShellの設定を引き継ぐ?)

参考

qiita.com

rbenv+nodebrewからasdfに移行したので作業メモ📝

rbenv+nodebrewからasdfに移行してみたのでやったことをメモしておきます📝

github.com

asdfインストール

公式はgit cloneの方法のようだが、homebrewからinstallできるようなのでinstall

https://asdf-vm.com/guide/getting-started.html#_2-download-asdf

$ brew install asdf

以下に従ってbrew + zshのinstallコマンドを実行 https://asdf-vm.com/guide/getting-started.html#_3-install-asdf

$ echo -e "\n. $(brew --prefix asdf)/libexec/asdf.sh" >> ${ZDOTDIR:-~}/.zshrc

出来た🆗

$ asdf --version
v0.13.1

asdfrubyを管理

asdfruby pluginを追加

$ asdf plugin add ruby 

install可能なrubyのバージョンを確認

$ asdf list-all ruby

任意のバージョンのrubyのinstall

$ asdf install ruby 3.2.2

任意のバージョンのrubyのグローバル利用

$ asdf global ruby 3.2.2

出来た🆗

$ ruby -v
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-darwin22]

rbenv uninstall

rbenvは不要なので削除しておく

$ brew uninstall rbenv
$ rm -rf ~/.rbenv 

.zshrcから以下も削除

# for rbenv
eval "$(rbenv init - zsh)"

asdfでnodeを管理

asdfのnodeプラグインを追加

$ asdf plugin add nodejs 

インストール可能なnodeの一覧を表示

$ asdf list-all nodejs

任意のバージョンのnodeをinstall

$ asdf install nodejs 18.18.2

任意のバージョンのnodeをグローバル利用

$ asdf global nodejs 18.18.2

出来た🆗

$ node -v
v18.18.2

nodebrew uninstall

$ brew uninstall nodebrew
$ rm -rf ~/.nodebrew 

.zshrcから以下も削除

# for nodebrew
export PATH=$HOME/.nodebrew/current/bin:$PATH

参考

scrapbox.io

https://asdf-vm.com/guide/getting-started.html#_4-install-a-plugin

Ruby on Rails: テスト用にrouteを動的に追加するメモ📝

テスト用にRoutingを動的にいじってテストしたいことがたまにあるのでやり方をメモ📝

まず以下のようなテスト用のhelperを用意します。 中でやっていることは動的にrouteを追加するdraw_test_routesとそれをリセットするreload_routes!を実装しています。

module RoutesHelper
  def draw_test_routes(&block)
    Rails.application.routes.disable_clear_and_finalize = true

    Rails.application.routes.draw do
      instance_exec(&block)
    end
  end

  def reload_routes!
    Rails.application.reload_routes!
  end
end

Rails.application.routes.disable_clear_and_finalize = trueをすることで以下のroutesのclearやfinalizeを無効化してroutesを固定化させず、動的に追加したものを初期化させないようにします。

https://github.com/rails/rails/blob/v7.1.2/actionpack/lib/action_dispatch/routing/route_set.rb#L428-L433

またRails.application.reload_routes!を実行することで、clear!し再読み込みされfinalize!することで動的に追加したroutesを削除するとともにRails.application.routes.disable_clear_and_finalizeがfalseに再設定されるので、元に戻ります。

https://github.com/rails/rails/blob/v7.1.2/railties/lib/rails/application/routes_reloader.rb#L22-L29

以下の通り、任意のcontrollerを使って動的にrouteを追加することができました🎉

require "spec_helper"
require_relative "../../test/support/routes_helper"

class TestsController < ActionController::Base
  def index
    head :ok
  end
end

describe "GET /test", type: :request do
  include RoutesHelper

  before(:example) do
    draw_test_routes do
      get "/tests", to: "tests#index"
    end
  end

  after(:example) do
    reload_routes!
  end

  it "success" do
    get "/tests"
    expect(response).to have_http_status :ok
  end
end

参考

techracho.bpsinc.jp

Ruby on Rails: メーラー関連のファイルを`app/mailers`に集約するメモ📝

Ruby on RailsメーラーのViewはデフォルトでapp/views配下に置かれてしまい数が多くなってくるとControllerからrenderされるものとMailerからrenderされるものが混在してしまい見通しが悪くなるケースがあります。

メーラーのビューは app/views/name_of_mailer_class ディレクトリに置かれます。
Action Mailer の基礎 - Railsガイド

そういうわけでメーラー関連のファイルはapp/mailers配下に集約する方法をメモ📝

やり方

目指す姿は以下のような感じで、mailer配下にviewsを持ち、そこにlayoutや各mailerのviewを配置することを目指します。

app/mailers
├── application_mailer.rb
├── sample_mailer.rb
└── views
    ├── layouts
    │   ├── application_mailer.html.erb
    │   └── application_mailer.text.erb
    └── sample_mailer
        ├── sample_mail.html.erb
        └── sample_mail.text.erb

やり方は、そこまで難しくなくて以下のようなApplicationMailerを用意してあげるだけで大丈夫でした!

class ApplicationMailer < ActionMailer::Base
  default from: "from@example.com"
  layout Rails.root.join("app/mailers/views/layouts/application_mailer")
  prepend_view_path "app/mailers/views"
end

layout Rails.root.join("app/mailers/views/layouts/application_mailer")app/mailers内のlayout用のファイルを参照するようにし、

別のレイアウトファイルを明示的に指定したい場合は、メーラーでlayoutを呼び出します。
Action Mailer の基礎 - Railsガイド

prepend_view_path "app/mailers/views"でviewの参照パスにapp/mailers/viewsを追加しています。

prepend_view_pathメソッドやappend_view_pathメソッドを用いることで、パスの解決時に優先して検索される別のディレクトリを追加できます。
Action View の概要 - Railsガイド

参考

techracho.bpsinc.jp

VSCodeの`rebornix.ruby`から`shopify.ruby-lsp`に乗り換えるメモ

今までVSCodeRubyを使うときにはrebornix.rubyを使うことが多かったと思うのですが、

marketplace.visualstudio.com

現状は非推奨となっており、

Shopify's ruby-lsp and associated vscode-ruby-lsp are recommended alternatives to this extension. It is substantially easier to produce a high-quality LSP implementation using Ruby itself vs relying on another language such as TypeScript. GitHub - rubyide/vscode-ruby: Provides Ruby language and debugging support for Visual Studio Code

推奨されているshopify.ruby-lspに乗り換えてみたのでメモ📝

marketplace.visualstudio.com

乗り換えは簡単でVSCode拡張機能からinstallした後にGemfileにruby-lspを追加してbundle installすれば大丈夫でした🙆‍♂️

group :development do
  gem "ruby-lsp"

Gemfileに入れないとエラーになったのでGemfileに入れましたが、公式のReadme的には起動時に.ruby-lsp配下にGemfileが追加されてよしなに利用されるため不要なようです。(自分はなぜか動かなかった・・・)

NOTE: starting with v0.7.0, it is no longer recommended to add the ruby-lsp to the bundle. The gem will generate a custom bundle in .ruby-lsp/Gemfile which is used to identify the versions of dependencies that should be used for the application (e.g.: the correct RuboCop version).
GitHub - Shopify/ruby-lsp: An opinionated language server for Ruby

あとは私はrubocopじゃなくてstarndardrbを利用しているのですが、Ruby LSPが出すrubocopの警告は不要のためdiagnosticsfalseにしました。

  "rubyLsp.enabledFeatures": {
    "diagnostics": false,

これでいい感じにコードジャンプ等が動くようになりました💎

あとrails用のもあるらしい。

github.com

ShopfyこういったRubyの基盤的な技術もOSSで提供してくれていてありがたい🙏

参考)

code.visualstudio.com

Ruby on Rails: 個人のサービスをRails v7.1にアップデートしたのでやったこととかメモ📝

2023/10/05にRuby on Rails v7.1がリリースされました🎉

rubyonrails.org

個人のwebサービスなので以下のように規模はかなり小さめですがやったこととかをメモ📝

+----------------------+--------+--------+---------+---------+-----+-------+
| Name                 |  Lines |    LOC | Classes | Methods | M/C | LOC/M |
+----------------------+--------+--------+---------+---------+-----+-------+
| Controllers          |    572 |    425 |      21 |      71 |   3 |     3 |
| Helpers              |     51 |     37 |       0 |       8 |   0 |     2 |
| Jobs                 |     31 |     23 |       3 |       2 |   0 |     9 |
| Models               |   1100 |    712 |      32 |     102 |   3 |     4 |
| Mailers              |      6 |      4 |       1 |       0 |   0 |     0 |
| Views                |    393 |    371 |       0 |       0 |   0 |     0 |
| JavaScript           |   3033 |   2195 |       0 |       2 |   0 |  1095 |
| Libraries            |    262 |    214 |       5 |      18 |   3 |     9 |
+----------------------+--------+--------+---------+---------+-----+-------+
| Total                |   5448 |   3981 |      62 |     203 |   3 |    17 |
+----------------------+--------+--------+---------+---------+-----+-------+

やったこと

installするRailsのバージョンを更新

dependabotのPRが作成されていたので、それを利用しました🤖

bundle update --conservativeを使って最低限のgemだけあげた方が安全。

--conservative Use bundle install conservative update behavior and do not allow indirect dependencies to be updated. https://bundler.io/v2.4/man/bundle-update.1.html

設定周りをRails v7.1に合わせて更新

config.load_defaults 7.1に更新してRails 7.1の設定を反映するようにしたのと、 Rails v7.1時のRails newのdiffを確認し以下の変更を追加した。

https://railsdiff.org/7.0.8/7.1.0

config.autoload_libのignoreオプションを利用するように修正

config.autoload_lib(ignore:)という新しい設定メソッドが導入されました。 このメソッドは、デフォルトでは自動読み込みパスに含まれていないlibディレクトリをアプリケーションの自動読み込みパスに追加するために利用されます。 また、新しいアプリケーションではconfig.autoload_lib(ignore: %w(assets tasks))が生成されます。 https://railsguides.jp/v7.1/7_1_release_notes.html

config.cache_classesconfig.enable_reloadingに変更

3.2.13 config.cache_classes 後方互換性のためにサポートされている古い設定であり、!config.enable_reloadingと同等です。 https://railsguides.jp/configuring.html

config.action_controller.raise_on_missing_callback_actionsをtrueに設定

onlyやunlessで既存のメソッドに存在しないシンボルを指定した場合にコールバックでエラーを発生するようになった。 https://techracho.bpsinc.jp/hachi8833/2023_09_27/135156

config.active_job.verbose_enqueue_logsをtrueに設定

3.15.8 config.active_job.verbose_enqueue_logs バックグラウンドジョブをエンキューするメソッドのソースコードの場所を、 関連するエンキューログ行の下にログ出力するかどうかを指定します。 デフォルトは、development環境ではtrue、それ以外の環境ではfalseです。 https://railsguides.jp/configuring.html

config.action_dispatch.show_exceptions:rescuableに設定

Deprecate true and false values for config.action_dispatch.show_exceptions in favor of :all, :rescuable, or :none. https://edgeguides.rubyonrails.org/7_1_release_notes.html#action-pack-deprecations

警告が出ている部分のFIX

アプリケーション起動時に以下のような警告が出ていましたが、どちらもDevise起因ぽかったので一旦無視しました。

DEPRECATION WARNING: DeprecatedConstantAccessor.deprecate_constant without a deprecator is deprecated (called from <top (required)> at /app/config/application.rb:16)
DEPRECATION WARNING: `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2. (called from <top (required)> at /app/config/environment.rb:7)

DeprecatedConstantAccessor.deprecate_constant側はissueが上がっている模様。

github.com

DEPRECATION WARNINGの調査はconfig.active_support.deprecation = :raise にするとスタックトレースが出るので発生箇所が調査しやすい。

おわりに

Rails v7.1はasync queriesや複数主キーのサポートなどいい感じの機能が楽しみですね✨

参考

techracho.bpsinc.jp

inside.pixiv.blog