Note

3年後の自分のために書いています

graphql-ruby の lib/graphql/schema.rb のコードに興奮した

GraphQL はほとんど関係なく Ruby の話です。

GraphQL Ruby を実行する時はだいたいこんな感じで使う。

# app/graphql/my_schema.rb
class MySchema < GraphQL::Schema
  # ...
end

# app/controllers/graphql_controller.rb
result = MySchema.execute(
  params[:query],
  variables: params[:variables],
  context: { current_user: current_user },
)
render json: result

だが、MySchema の親クラスである GraphQL::Schema (lib/graphql/schema.rb) のコード を読んでもインスタンスメソッドの GraphQL::Schema#execute しか定義されていない。

呼び出し側でどうして MySchema.execute(args) のように特異メソッド的に呼べるのだろうと不思議だった。

以下が答えでした。

require 'forwardable'

class MyClass
  alias :my_definition :itself
  
  def execute(arg)
    arg
  end
  
  class << self
    extend Forwardable

    def_delegator :my_definition, :execute

    def my_definition
      self.new
    end
  end
end

> MyClass.execute('hoge')
#=> "hoge"

興奮した。

参考

instance method Object#itself (Ruby 2.6.0)

恒等写像 - Wikipedia

クラス/メソッドの定義/alias (Ruby 2.6.0)

module Forwardable (Ruby 2.6.0)

追記

alias :my_definition :itself はこの例には余計だった。

lib/graphql/schema.rb で定義している場所と理由。

こっちはもう少し調べないとわからなさそうです。

ていうかこの例に関していうと委譲がポイントでしたね。

require 'forwardable'

class MyClass
  def execute(arg)
    arg
  end
  
  class << self
    extend Forwardable

    def_delegator :my_definition, :execute, :my_execute

    def my_definition
      self.new
    end
  end
end

> MyClass.my_execute('hoge')
#=> "hoge"
> MyClass.instance_methods(false)
#=> [:execute]
> MyClass.singleton_methods(false)
#=> [:my_execute, :my_definition]

ちょっとスッキリしましたが、委譲難しい。

追記の参考

instance method Forwardable#def_delegator (Ruby 2.6.0)

instance method Module#instance_methods (Ruby 2.6.0)

instance method Object#singleton_methods (Ruby 2.6.0)

さらに追記

require 'forwardable'

class MyClass
  alias :my_definition :itself
  
  def execute(arg)
    arg
  end
  
  class << self
    extend Forwardable

    def_delegator :my_definition, :execute, :my_execute

    def my_definition
      self.new
    end
  end
end

> MyClass.my_execute('hoge')
#=> "hoge"
> MyClass.instance_methods(false)
#=> [:execute, :my_definition]
> MyClass.singleton_methods(false)
#=> [:my_execute, :my_definition]

alias :my_definition :itself を追加することで、 MyClass#my_definition が定義されるのか。

けっこうスッキリしたので、今日はここまでにしておきます。

Ruby の Array#each で空の配列をレシーバに取るとブロック内は実行されない

タイトルのまんまですが。

nil が回ったりするのかなと勝手に思ってました。

# レシーバが空の配列だとブロック内は実行されない
[].each { puts 'hoge'} #=> []
[].each { |i| puts i } #=> []
# Hash#each も同じく
{}.each { puts 'hoge'} #=> {}
{}.each { |k, v| puts k, v } #=> {}
# ブロック無しの場合に返す Enumerator オブジェクトももちろん空
e = [].each #=> #<Enumerator: ...>
e.each { |i| puts i} #=> []

参考

あんまり詳細載ってないけれど。

instance method Array#each (Ruby 2.6.0)

instance method Hash#each (Ruby 2.6.0)

Git で既存のリポジトリを複製して別名のリポジトリとして始める方法

最短かつスマートな方法を考えた結果こうなった。

## あらかじめ GitHub で hoge リポジトリを新規作成しておく
$ git clone {複製元リポジトリのURL}.git hoge
$ cd hoge
$ git remote set-url origin https://github.com/{user_name}/hoge.git
$ git push origin master

おまけ

最初は以下のようにやっていたが、職場の方に clone してくるときに root ディレクトリの名前を指定できるということを教えていただいた。

## あらかじめ GitHub で hoge リポジトリを新規作成しておく
$ mkdir hoge
$ cd hoge
$ git clone {複製元リポジトリのURL}.git .
$ git remote set-url origin https://github.com/{user_name}/hoge.git
$ git push origin master

Ruby 2.6 の変更点で個人的に気になったところをまとめておく

サンプルコードは参考リンクに豊富に載ってるので、ほぼ省略しています。

ローカル変数の shadowing 警告を削除

Effective Ruby の項目5で出てきたやつ、なくなったんですね。

to_h がブロックを受け取れるように

['Hoge', 'Fuga'].to_h { |x| [x.upcase, x.downcase] } みたいにブロック内で [key, value] となるような配列を返すと、それを要素としたハッシュが生成される。

Enumerable#chain, Enumerator#+

イテレータを連鎖させるやつ、便利そう。

Kernel#yield_self の別名に Kernel#then が導入された

そもそも Kernel#yield_self を知らなかった…レシーバをブロック引数として、ブロックの結果がそのまま返り値になるやつ。

Object#tap と似てるなーと思ったら、#tapself を返すんですね。

ref. instance method Object#tap (Ruby 2.6.0)

:exception オプションの導入

メソッドの実行失敗時に true なら例外を吐かせ、false なら nil を返す。

Array#union, Array#difference

これまで |- 演算子で実現していた配列の和集合と差集合を返すやつ、こちらは複数の引数を同時に取れる。

Hash#merge, Hash#merge! が複数の引数を受け取れるように

{k1: :v1}.merge({k2: :v2}).merge({k3: :v3}) と書かなくてよくなる。

String#split がブロックを受け取れるように

ブロック引数には分割された部分文字列が順に渡される。ブロックを使った場合、 #split メソッド自体の返り値はレシーバの文字列になるので注意が必要そう。

参考

プロと読み解く Ruby 2.6 NEWS ファイル - クックパッド開発者ブログ

サンプルコードでわかる!Ruby 2.6の主な新機能と変更点 - Qiita

ruby/NEWS at v2_6_0 · ruby/ruby · GitHub

Ruby 2.6.0 Released

おまけ

いろいろ調べてたら Ruby の Object クラスと Kernel モジュールの関係が曖昧になってきたので、ついでに整理。

以下の Kernel モジュールの要約がとてもよくまとまっている。

全てのクラスから参照できるメソッドを定義しているモジュール。 Object クラスはこのモジュールをインクルードしています。

Object クラスのメソッドは実際にはこのモジュールで定義されています。 これはトップレベルでのメソッドの再定義に対応するためです。

ref. module Kernel (Ruby 2.6.0)

Rails で静的なトップページをいい感じにルーティングしたい

例えば静的なトップページを作りたくて GET /welcomewelcome#hoge(不自然じゃないアクション) を対応づけたいとする。

Rails っぽくリソースベースな感じで書きたいなと思ったが、単数形リソースを使って以下のようにやってもうまくいかない。

Rails.application.routes.draw do
  resource :welcome, only: [:show]
end

ルーティングを覗いてみると、

$ rails routes

 Prefix Verb URI Pattern        Controller#Action
welcome GET  /welcome(.:format) welcomes#show

コントローラが複数形の WelcomesController になってしまう…嫌だ。

Rails ガイドの該当箇所 によると、

単数形リソースは複数形のコントローラに割り当てられます。これは、同じコントローラで単数形のルーティング (/account) と複数形のルーティング (/accounts/45) を両方使いたい場合を想定しているためです。従って、resource :photoとresources :photosのどちらも、単数形ルーティングと複数形ルーティングを両方作成し、同一のコントローラ (PhotosController) に割り当てられます。

とのこと。単数形リソースは Rails が良かれと思って複数形のコントローラに割り当ててくれているんですね。

じゃあ最初から複数形リソースとして扱えばいいのではと思って、

Rails.application.routes.draw do
  resources :welcome, only: [:index]
end

としてみたが、ルーティングを覗いてみると、

$ rails routes

       Prefix Verb URI Pattern        Controller#Action
welcome_index GET  /welcome(.:format) welcome#index

Prefix の _index が気になる…!どうやらコントローラ名が単数形だと Railsindex アクションにのみ _index を勝手に付与してくれるようだ、おせっかいさん。

結論

はい、普通にこうやりました。

Rails.application.routes.draw do
  get '/welcome', to: 'welcome#index'
end

当たり前ですがルーティングはこうなります。

$ rails routes

 Prefix Verb URI Pattern        Controller#Action
welcome GET  /welcome(.:format) welcome#index

そもそも全然リソースフルでないルーティングなので最初からこう書けよって話でしたね。勉強になりました。

Rails でモデルを generate する時に migration ファイルを作らない方法

ridgepole とか使ってると、 $ rails g model Hoge した時に migration ファイルは作って欲しくない。

$ rails g model Hoge --no-migration

で、いけました。

おまけ

コマンドラインツールのオプションについては Rails ガイド よりも -h で調べた方がたくさん情報が出てきますね。

$ rails g model -h

Usage:
  rails generate model NAME [field[:type][:index] field[:type][:index]] [options]

Options:
      [--skip-namespace], [--no-skip-namespace]  # Skip namespace (affects only isolated applications)
      [--force-plural], [--no-force-plural]      # Forces the use of the given model name
  -o, --orm=NAME                                 # ORM to be invoked
                                                 # Default: active_record

ActiveRecord options:
      [--migration], [--no-migration]        # Indicates when to generate migration
                                             # Default: true
      [--timestamps], [--no-timestamps]      # Indicates when to generate timestamps
                                             # Default: true
      [--parent=PARENT]                      # The parent class for the generated model
      [--indexes], [--no-indexes]            # Add indexes for references and belongs_to columns
                                             # Default: true
      [--primary-key-type=PRIMARY_KEY_TYPE]  # The type for primary key
  -t, [--test-framework=NAME]                # Test framework to be invoked

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
...

curl でリクエスト・レスポンス両方のヘッダーだけを取得する方法

レスポンスのヘッダーのみ取得するならば -I でできるが、リクエストも見たい。そして body は見なくて良い、という時に。

$ curl -v -s -o /dev/null https://example.com/

-v は verbose、 -s は silent、 -o /dev/null で body を /dev/null に捨てている。

職場の方に教えていただいた以下もシンプルで良い。 (こちらはレスポンスが標準出力にも表示される)

$ curl -vI https://example.com/

[追記]

こちらも標準出力を /dev/null に捨てれば同じ結果になりました。

id:masutaka26 コメントありがとうございます!

雑記

curl って絶対 cURL って表記じゃないとダメかと思ってたけど、そうでもないらしい。

公式ドキュメントcurl だし、書籍だと Real World HTTP とかも curl 表記だった。