新規ブランチのpush時に--set-upstream origin xxxを省略する
今まで、新規ブランチのpush時に一旦git pushして、以下のようなエラーが出て怒られてからgit push --set-upstream origin xxxをコピペして実行するということをしていました。
fatal: The current branch xxx has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin xxx
以下のコマンドを実行して設定しておくと、新規ブランチでもgit push xxxだけでpushできることを知りました。
git config --global push.autoSetupRemote true
Rubyでは!や+や-はメソッドとしても使える
タイトルの通りですが、Rubyでは!や+や-はメソッドとしても使えるということを知りました。
!"abc".include?("a")というコードは以下のようにも書けます。
irb(main):006> "abc".include?("a").! => false
もっと純粋化すると、!trueはtrue.!と同じであるということです。
irb(main):010> true.! => false
+や-も同様です。
irb(main):008> 1.+ 2 => 3 irb(main):009> 3.- 2 => 1
既存のコードリーディングをしていたらこのような書き方を見かけて分からず、AIに解説してもらいました。
自分で書く機会はほとんどないと思いますが、実際にコードリーディングで出会ったので知ってはおこうと思いました。
Active Recordのmergeメソッドで結合先のモデルのscopeを条件に含む検索を実現する
前提として、以下のようにscopeが定義されたTagモデルを定義します。
class Tag < ApplicationRecord has_and_belongs_to_many :restaurants scope :south_india, -> { where(name: "南インド") } end
Tag.south_indiaとすると以下のSQLが発行されます。
SELECT "tags".* FROM "tags" WHERE "tags"."name" = '南インド';
Tagモデルと多対多の関係にあるRestaurantモデルからTagモデルのscopeを使って絞り込むSQLを発行したいです。
その場合はmergeメソッドが使えます。
Restaurant.joins(:tags).merge(Tag.south_india)
以下のようなSQLが発行されます。
SELECT "restaurants".* FROM "restaurants" INNER JOIN "restaurants_tags" ON "restaurants_tags"."restaurant_id" = "restaurants"."id" INNER JOIN "tags" ON "tags"."id" = "restaurants_tags"."tag_id" WHERE "tags"."name" = '南インド';
Railsガイドにも「高度な条件指定や既存の名前付きスコープの再利用を行いたい場合は、mergeが利用できるでしょう。」とあります。
複雑なクエリを直観的に扱えて便利だなという印象です。
「委譲」の適切な使い方を『リファクタリング』を読んで学ぶ
最近、オブジェクト指向における「委譲」の適切な使い方について考える機会があったのでマーティン・ファウラーの『リファクタリング』を読みながら勉強してみます。サンプルコードはRubyです。
サンプルコード
以下のようにNameクラスとPersonクラスがあるとします。PersonクラスはNameクラスに依存しています。
※ PersonクラスのコンストラクタにNameクラスのインスタンスを渡す方法もあり、そこでも議論ができそうですが本記事では割愛します。
Nameクラス
class Name attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end def full_name "#{@last_name} #{@first_name}" end end
Personクラス
class Person def initialize(first_name, last_name) @name = Name.new(first_name, last_name) end end
PersonクラスのインスタンスからNameクラスのfull_nameメソッドを呼ぶ方法を考えます。以下の2パターンがありそうです。
1.Personクラスで委譲メソッドを提供する
1つ目は以下のようにPersonクラスのインスタンスから直接full_nameメソッドを呼ぶ方法です。
person = Person.new("太郎", "山田") puts person.full_name # 山田 太郎
このためにはPersonクラスでName#full_nameメソッドに委譲するメソッドを提供する必要があります。
class Person # 省略 def full_name @name.full_name end end
2.nameフィールド経由で呼ぶ
2つ目は以下のようにPersonのnameフィールド(=Nameクラスのインスタンス)を経由してfull_nameメソッドを呼ぶ方法です。
person = Person.new("太郎", "山田") puts person.name.full_name # 山田 太郎
そのためにはPersonクラスにattr_readerを定義してnameフィールドを公開する必要があります。
class Person attr_reader :name # 省略 end
メリット/デメリット
それぞれのパターンのメリット/デメリットについて考えます。マーティン・ファウラーの『リファクタリング』に詳しく解説されています。
- 委譲の隠蔽(p.196)
- 仲介人の除去(p.199)
です。1.2.は対のパターンとして紹介されています。
1.委譲の隠蔽
「1.委譲の隠蔽」は「1.Personクラスで委譲メソッドを提供する」に該当します。
メリットは、クライアント側が委譲先のオブジェクトのことを知らなくてよいことです。
今回の例では、full_nameメソッドを呼び出したい側はNameクラスの存在を意識せずに呼ぶことができます。
『リファクタリング』では「(部下が)上司に対して会議に出席できるか尋ねると、手帳をみてから答えます(p.84)」というとても分かりやすい例で説明されています。
sequenceDiagram
autonumber
participant Subordinate as 部下
participant Boss as 上司
participant Calendar as 手帳(スケジュール)
Subordinate->>Boss: 出席できますか?(会議日時)
Boss->>Calendar: 空き確認する(会議日時)
Calendar-->>Boss: 可否(OK/NG)
Boss-->>Subordinate: 出席可否を回答(OK/NG)
この例では「上司」は「手帳」への委譲を隠蔽しているといえます。従って、「部下」は上司の手帳を見に行かなくても、上司に聞くだけでほしい回答を得ることができます。
このパターンは「直接の隣人にのみ話しかける」という「デメテルの法則」とも相性がいいです。
デメリットはこのパターンを多用すると委譲するだけのメソッドが増えていくことです。例えば、Nameクラスにfull_name_kanaメソッドが追加されたら、Personクラスにもfull_name_kanaメソッドを追加する必要があります。
class Person # 省略 def full_name @name.full_name end def full_name_kana @name.full_name_kana end end
このように委譲を過剰に使用した結果、委譲先のオブジェクトに転送するだけのメソッドが多くを占めるようになったクラスをファウラーは「ただの仲介人」と呼んで批判しています。
加えて開発メンバーに「デメテルの法則」が好きな人が多いと、このアンチパターンに陥りやすいと述べられています。
2.仲介人の除去
「1.委譲の隠蔽」は「2.nameフィールド経由で呼ぶ」に該当します。これは委譲を過剰に使用したコードへのリファクタリング手法として紹介されています。
やることは単純で、委譲をやめて委譲先のオブジェクトのメソッドを直接呼ぶことです。
メリット/デメリットは1.の逆で、委譲先のオブジェクトがどのようなメソッドを提供しているかという知識は必要になりますが、「ただの仲介人」を削除することができます。
まとめ
「1.委譲の隠蔽」と「2.仲介人の除去」はそれぞれメリット/デメリットがあるため、混在してもよいとファウラーは主張しています。目安として、よく使われる委譲であれば「1.委譲の隠蔽」を用いてよいとのことです。
加えて、委譲をどの程度隠蔽すべきかはシステムの変化によっても変わるので、都度リファクタリングすることが重要とのことです。
RubyでAND条件とOR条件にand,orではなく&&,||を使う理由
Rubyでは基本的にAND条件とOR条件に&&と||を使います。
他方でandとorも構文ではサポートされています。従って、Python経験がある身としては、andとorを使いそうになってしまうときがあります。
※ PythonではAND条件とOR条件にはandとorを使う
しかし、一般的なRubyのコードでandとorを使っているケースはほとんど見ません。Rubocopの標準にもandとorの使用をチェックするルールが存在します。
従って、なぜ原則は&&と||を使ったほうがよいのかを確認したいと思います。
理由
&&、||、andとorはそれぞれ演算子の評価の優先順位が異なり、andとorは直観に反する挙動をする場合があるからです。
演算子の評価の優先順位は公式ドキュメントに明記されています。
andとorは「低い」に分類されています。
挙動が変わる代表的な例としてAIが挙げたのは以下でした。
a = true && false # aには`false`が代入される b = true and false # bには`true`が代入される
1個目は&&の方が=よりも優先的に評価されるため、aにはtrue && falseの評価結果であるfalseが代入されます。
これは直観的な挙動です。
他方で2個目は、=の方がandよりも優先的に評価されるため、先にb = trueが実行されます。次にb(true) && falseが実行されます。評価結果自体はfalseですが、この結果は捨てられるので、変数bの値は意図とは異なりtrueになってしまいます。
このようなバグのリスクがあるので、原則は&&と||を使うようです。
【Ruby】rescue節のStandardErrorは省略できる
RuboCopに怒られて知ったのですが、Rubyでは以下のようにrescue節でStandardErrorを捕捉する場合はStandardErrorを省略することができます。
修正前
def divide(m, n) begin m / n rescue StandardError => e "[ERROR] #{e.message}" end end puts divide(2, 2) # 1 puts divide(2, 0) # [ERROR] divided by 0
修正後
def divide(m, n) begin m / n rescue => e # 暗黙的にStandardErrorが捕捉される "[ERROR] #{e.message}" end end puts divide(2, 2) # 1 puts divide(2, 0) # [ERROR] divided by 0
公式ドキュメントにも明記されていました。
Rails開発でのDISABLE_SPRING=trueのおまじないの意味をある程度理解する
前提
今の案件に参画当時、Railsのローカルの開発環境でrails consoleを実行してActive RecordでのDB操作を試みたり、rails runnerでDB操作を伴うコマンドを実行したりすると以下のようなエラーが発生するという事象に遭遇しました。
※ rails serverは正常に実行できました。
エラー
objc[8247]: +[Swift.__SharedStringStorage initialize] may have been in progress in another thread when fork() was called. objc[8247]: +[Swift.__SharedStringStorage initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
案件内では既知の事象で対応が確立されており、以下のようにrails console実行時に先頭にDISABLE_SPRING=trueを付与することで解消できました。
DISABLE_SPRING=true bundle exec rails console
この事象はRailの公式リポジトリ上でもissue化されています。
ただ自分の中で完全におまじない化しているので、この機会にこのオプションの意味をある程度理解してみたいと思います。
環境
DISABLE_SPRING=true の意味
DISABLE_SPRING=true は直接的にはSpringというライブラリを無効化しています。
Springについては、以下のREADMEの説明の通り、開発環境での性能を改善してくれるライブラリのようです。
Spring is a Rails application preloader. It speeds up development by keeping your application running in the background, so you don't need to boot it every time you run a test, rake task or migration.
※ Rails7以降はSpringはデフォルトではインストールされない
SpringはRails7以降はデフォルトではインストールされなくなっています。従って、Rails7以降を使っている場合は明示的にSpringを使っている環境が対象になります。
Spring is no longer on by default, as faster computers have made it less relevant on anything but the largest applications.
なぜSpringを使うと冒頭のエラーが発生するのか
上記のissueのやり取りをAIに解説してもらったところ、ざっくり以下のような感じのようです。
そのため、issueではOBJC_DISABLE_INITIALIZE_FORK_SAFETY=YESとしてMacのセキュリティ設定の方を変更する対応も提案されています。 (私の環境では機能しませんでした)
加えて、Springが使われるのはconsole, runner, generate, destroy, test のコマンドのみで、server は対象ではないそうです。
たしかにrails consoleを実行した場合は、Running via Spring preloader in process xxxというメッセージが表示されますが、rails serverでは表示されません。
rails serverでは発生しなかった理由が理解できます。
まとめ
おまじないだったDISABLE_SPRING=trueの意味が多少は理解できました。
- 冒頭のエラーの原因はSpringという開発環境の性能向上用のライブラリがMac OSと相性が悪いことに起因
DISABLE_SPRING=trueはSpringを無効化している