This is IT

技術、日常

【Ruby】public_sendを使って、「新しいインスタンスを作成する際、既存のインスタンスの属性をコピー」する処理をDRYにした

はじめに

ruby 3.1.4

「新しいインスタンスを作成する際、既存のインスタンスの属性をコピーしたい」処理について。

Railsのコントローラのnewアクションでこんな処理をしていた。

ただDRYじゃないな~と思ったので、リファクタリング前と後でどういう風にしたか説明をさせてください。

リファクタリング前のコード

class HogeController < ApplicationController
  def new
    @hoge = Hoge.new

    return unless params[:id]
    copy_hoge(@hoge)
  end

  private

  def copy_hoge(new_hoge)
    original = Hoge.find(params[:id])
    new_hoge.title = original.title
    new_hoge.content = original.content
    new_hoge.location = original.location

    new_hoge
  end
end

なんやこれ?となってたけど、どうやら以下の流れのよう。

Viewで『コピーする』ボタンを押すと、コピー元のIDがparams[:id]として送信される。

これを利用して、「各属性をコピーしながら新しいインスタンスを作成する」という処理を実現しているみたい。

個人的に何がいやか?

  • scaffold作成時に沿ったControllerになっていない
  • コピーするべき属性が増えると、面倒くさそう
    • new_hoge.attribute = original.attributeという行をどんどん追加する必要が出てくる
  • 同じ変数出てきすぎ。DRYじゃない。

ちなみに、scaffold作成時に沿ったControllerにすべき、っていうのは、以下の記事から。

qiita.com

リファクタリング後のコード

コントローラ

class HogeController < ApplicationController
  def new
    @hoge =
      if params[:id]
        Hoge.new_with_copied_attributes(Event.find(params[:id])
      else
        Hoge.new
      end
  end
end

モデル

class Hoge < ApplicationRecord
  class << self
    def new_with_copied_attributes(original_hoge)
      new_hoge = Hoge.new
      %i[title content location].each do |attribute|
        new_hoge.public_send("#{attribute}=", original_hoge.public_send(attribute))
      end

      new_hoge
    end
  end
end

何がよくなったのか?

  • newアクション内を、Railsっぽい書き方に変えた。
    • scaffold時に寄せたので、可読性が上がった(はず)
  • コピーをするメソッドを、モデルのクラスメソッドとして、コントローラから分離した。
    • テスト書きやすいし、FatControllerの予防になっている(はず)
  • 各属性をコピーするメソッド内ではpublic_sendメソッドを使って、DRYにできた。

あくまで個人の見解。

public_sendメソッドとは

public_sendメソッドの前に、そもそもsendメソッドの理解から。

class Foo
  def foo() "foo" end
end


p Foo.new.send(:foo) # => "foo"
p Foo.new.send("foo") # => "foo"

# シンボル、文字列どちらでもOK

この方法で、オブジェクトの任意のメソッドを呼び出すことができる。でも、privateメソッドも呼べてしまう点が問題。

class Foo
  def foo() "foo" end

  private
  def private_foo() "private foo" end
end

p Foo.new.send(:foo) # => "foo"
p Foo.new.send(:private_foo) # => "private foo"

こうすると、セキュリティガバガバ。できるだけ避けたい。そこでpublic_sendメソッドの出番。

名前の通り、publicなメソッドしか呼び出せない。

class Foo
  def foo() "foo" end

  private
  def private_foo() "private foo" end
end

p Foo.new.public_send(:foo) # => "foo"
p Foo.new.public_send(:private_foo) # => public_send': private method `baz' called for #<Foo:0x00007f4e3ed1f978> (NoMethodError)

具体例を振り返る

リファクタリングでは、下記のような使い方をしていた。

%i[title content location].each do |attribute|
  new_hoge.public_send("#{attribute}=", original_hoge.public_send(attribute))
end

これは以下のように解読できる。

new_hoge.public_send("title=", original_hoge.title)
new_hoge.public_send("content=", original_hoge.content)
new_hoge.public_send("location=", original_hoge.location)

もっとシンプルに書き直すと、リファクタリング前と同じになる。

new_hoge.title = original.title
new_hoge.content = original.content
new_hoge.location = original.location

やっぱり

%i[title content location].each do |attribute|
  new_hoge.public_send("#{attribute}=", original_hoge.public_send(attribute))
end

が一番すっきりしている気がする。もし属性が増えても、%i[title content location]内をいじるだけでいいしね。

【Rails】【migration】ローカルの範囲で、1個前のmigrationファイルのカラム名を変更メモ

tl;dr

  1. rails db:rollbackで1個前のmigrationファイルをdownする。
  2. rails db:resetで、ロールバック後のDB状態にする。
  3. 新しく、カラム名追加のmigrationファイルを書く。
  4. rails db:migrateで反映。

背景

まだチームに共有されていないので、わざわざRename用のmigrationファイルを書くと、逆に意図がよくわかってもらえなさそう。 勝手にカラム追加して、勝手に名前変更している。

だったら、1個前のを無しにして、ちゃんとカラム名を追加し直そう、ってところだけどおっかなびっくりだったので備忘録。

【Rails】DISABLE_SPRING=1 PARALLEL_WORKERS=1でテスト

メモレベル。 DISABLE_SPRING=1 PARALLEL_WORKERS=1 bin/rails test ...でテストをする。

「テストがバリデーションに引っかかるぞ?」 「バリデーションに引っかかる変更なんてしていないぞ?」 って時に。

  • DISABLE_SPRING=1

    • spring gemをOFFにする
    • 悪さをすることがあるため、デフォルトではインストールされない
    • spring gemはデフォルトインストールから削除されてる techracho.bpsinc.jp
  • PARALLEL_WORKERS=1

    • プロセス数を指定
    • 1なら直列、2以上なら並列
    • 並列の方が効率は良いが、無駄なDBが出来ることで、2回目以降のテストでバリデーションに引っかかるかも?

【github】PRにほかの人のコミットが入ってしまったとき

メモレベル。

OSSで開発していて、普通にpullとかしていたら、なんか他人のコミットが紛れ込んできた。

ブランチ派生元のmainブランチが変わってしまったときとかに起こりうるっぽい。

そんな時は、

いったんgit pull --rebase origin main そいで、git push -f

アーイ!!!!!

サウナとヘッドスパで瞑想の練習

寝れへん

寝るとき、いろんなやなこと考えたり、昔の失敗を思い出して2~3時間くらい寝られない時があります。 もうほんと些細な発言の反省とか、あの人のあの言動は僕にこう思ってるのかな~とか。なんと気の小さい人間なのだろう。

「もう寝たいのに~」となっても考えちゃう。これは『頭を空っぽにする』ことが苦手なのかなぁと思ったり。

よし、何も考えずに頭を空っぽにする練習をしよう。

瞑想がいいらしい

「瞑想」は難しくない。ビル・ゲイツも絶賛した瞑想のやり方を紹介 | 未来想像WEBマガジン

よし瞑想しよう!と思っても、いつもの日常に加えるのは難しい。 どうしてもこの「何も考えていない」時間がもったいなく感じてそわそわしちゃう。

サウナとヘッドスパ

ということで、瞑想がしやすいタイミング、というより瞑想するしかない時にしようと思い立つ。

サウナの終盤と、ヘッドスパの間って思考がまったく回らない。

頭を空っぽにして、身を全て委ねる。何も考えない。もし何か考えちゃっても、それを否定せずに認める。

おいおい寝るときもすぐ寝れるようになったらな~と思います。

禅のコツ

とはいえ頭を空っぽって難しい。禅とかも調べてみたところ、こんなコツがあるらしい。

”無”という漢字だけを思い続ける

いや、これ漢字を考えてね?とも一瞬思ったけど、まぶた裏の視界のみをずっと見つめるのってまぁまぁしんどい。 本当に頭を空っぽにできるまでは、この方法でやってみよう

【VisualStudioCode】Vimの拡張機能を入れたけど、Ctrl+A・Ctrl+C・Ctrl+VのWindowsショートカット機能は残したい

手順

  1. VisualStudioCodeで「Ctrl + Shift + P」を押下
  2. コマンドパレットが開く
  3. Preferences: Open Keyboard Shortcuts を検索
  4. (JSON)の方を選ぶ 5.以下を書き込む
[
  {
    "key": "ctrl+c",
    "command": "editor.action.clipboardCopyAction",
    "when": "textInputFocus"
  },
  {
    "key": "ctrl+v",
    "command": "editor.action.clipboardPasteAction",
    "when": "textInputFocus"
  },
  {
    "key": "ctrl+a",
    "command": "editor.action.selectAll",
    "when": "textInputFocus"
  }
]

ハッピー :)

【npm】【WSL2】ブラウザを開かず、CLIのみで`npm login`を実行したい

環境

❯ npm -version
10.0.0

❯ node -v
v20.6.0

❯ cat /etc/debian_version
12.1

結論

npm login --auth-type legacyを実行してください。

解説

なぜCLIでログインする必要があるのか

WSL2ではいい感じにパスを通したりしない限り、CLIからGUIのブラウザを開くことが出来ません。

そのため、WSL2でnpmにログインをしようとnpm loginを行うと...

❯ npm login --registry=https://registry.npmjs.org/
npm notice Log in on https://registry.npmjs.org/
Login at:
https://www.npmjs.com/login?next=/login/cli/c021847a-5631-4bcc-b912-207ed62d8fa5
Press ENTER to open in the browser...

と、"Press ENTER to open in the browser..."で詰んでしまいます。もちろんEnterキーを押したところで、エラーが出ます。

npmの認証方式

npmのログイン時の認証方式はデフォルトでWebブラウザを利用するようになっています。 なので明示的にlegacyオプションで、古い形式でのログインを指定してあげることでCLIからのログインが可能になります。

npm Docs