なまもの備忘録

気になったことをつらつらと書いていきます

(Ruby) Hashの要素を非破壊的に削除する

こういう欲求に出くわすたびに一々調べて、思い出すのに割合時間を喰っている気がするので。 "Ruby"、"Hash"、"要素"、"非破壊的"、"削除"、でこの方法が引っかかる様にしたかったのが大きいです。

結論

rejectを使う。

[41] pry(main)> hash = {a: 1, b: 2, c: 3}
=> {:a=>1, :b=>2, :c=>3}
[42] pry(main)> hash.reject {|key| key == :a}
=> {:b=>2, :c=>3}
[43] pry(main)> hash
=> {:a=>1, :b=>2, :c=>3}

deleteは破壊的なので使いづらいですね。 ActiveSupportHash#exceptも手軽ですが、バニラなRubyだけで終わらせられると嬉しいこともあるので。

環境

[47] pry(main)> pry-version
Pry version: 0.12.2 on Ruby 2.5.1.

関数的メソッドをメソッド引数として渡す

関数的メソッドを引数にとる関数の定義がしたかったのだが、調べてもなかなか丁度良い記事がなかったのでメモ代わりに。 関数的メソッドとは明示的なレシーバを持たないメソッドのこと。素直にグローバルスコープにメソッドを定義するとこれになる。

def test_func(a,b,c)        
  p a + b + c
end

def test_func_func(func)    
  func.call(1,2,3)
end

func = BasicObject.method(:test_func)
test_func_func(func)

# 6
# => 6

どうもにメソッドに引数としてメソッドを渡すときはObject#methodを使ってメソッドを一度オブジェクト化する見たいなのだが、Object#methodは呼び出し時にメソッドのレシーバを明示する必要がある。 では関数的メソッドのレシーバとして記述するオブジェクトは何が適切なのか、という話になるが、これについて明確にこれが良い、というものは分からなかった。 例には、取り敢えず余計な要素を持たないBasicObjectなんか適しているんじゃないだろうか、という推測のもとBasicObjectを用いている。 どなたかご存知でしたら教えてください。

20/2/3追記

id:ikngttyさんがコメントで下記のことを教えてくださいました。

  • トップレベル(グローバルスコープ)に定義されたメソッドはObjectのprivateインスタンスメソッドとして定義される。
  • トップレベルでの挙動はself.test_func(args...)に相当(selfはmainを指す)する。
  • selfは省略可能なので、method(:test_func)でのオブジェクト化が可能。

ですので、上記を踏まえると以下の書き方が適切に思えます。

def test_func(a,b,c)        
  p a + b + c
end

def test_func_func(func)    
  func.call(1,2,3)
end

func = Object.new.method(:test_func) # メソッドの所属先を明示的に表記する場合
func = method(:test_func) # 簡潔に表記する場合
test_func_func(func)

id:ikngttyさんもおすすめされていますが、トップレベルでレシーバが省略されている時点Objectのメソッドであることは確定するので、selfを省略する書き方が個人的にもしっくりきます。メソッドの所属先を明示するためだけにObjectクラスのインスタンスを生成するのはかなり気が引けます。 また、こちらもid:ikngttyさんがおすすめされていますが、特にメソッドを使い回すなどしない場合であればラムダやProcが適切そうです。

いただいたコメントにとても丁寧な解説がありますので、そちらも是非ご覧下さい。

他参考:Ruby のトップレベルメソッドって結局なんなの - Qiita

20/2/25追記

id:ikngttyにいただいたコメントがMarkdown形式のものだったので、許可をいただき、コメントをそのままここに載せさせていただくことにしました。 大変丁寧な解説をありがとうございました。

以下コメントです。

取り敢えず余計な要素を持たないBasicObjectなんか適しているんじゃないだろうか、という推測のもとBasicObjectを用いている。

残念ながら、お望みの効果は得られていない状態になっています。

Object#method と Module#instance_method

Ruby には似たような役割を持つメソッドとして、Object#methodModule#instance_method があります。

# Object#method
abs123 = -123.method(:abs)
p abs123 # => #<Method: Integer#abs>
p abs123.call # => 123

# Module#instance_method
abs = Integer.instance_method(:abs)
p abs # => #<UnboundMethod: Integer#abs>
abs234 = abs.bind(-234)
p abs234 # => #<Method: Integer#abs>
p abs234.call # => 234

つまり、以下の2つが大体同じ関係になっています。

インスタンス.method(:name) クラス.instance_method(:name) これを混同して クラス.method(:name) としてしまうと、以下のようになります。

abs = Integer.method(:abs) # => undefined method `abs' for class `#<Class:Integer>' (NameError)

本記事の

func = BasicObject.method(:test_func)

こちらも同様の構造です。

クラス.method(:name) では何が起きているのか

紛らわしい文章になりますが、クラスをインスタンスとして見た場合、それのクラスはClass クラスです。

紛らわしいので、以下のコードを見た方がおそらく早いです。

class Foo
end

foo = Foo.new
p foo.class # => Foo
p Foo.class # => Class

p BasicObject.class # => Class

なので BasicObject.method(:name)Class.instance_method(:name) と意味合いが近いです。

p BasicObject.methods == Class.instance_methods # => true

これは BasicObject に限らず全てのクラスについて言えます。

p Foo.methods == Class.instance_methods # => true

つまり、以下の2つのコードは同じということです。

func = BasicObject.method(:test_func)
func = Foo.method(:test_func)

この辺が「(BasicObject を用いても)お望みの効果が得られてない」という根拠です。

「グローバルスコープに定義したメソッド」の正体

では改めて、何がレシーバーとして適切なのか。これは、「グローバルスコープに定義したメソッド」について詳しく調べることが近道となります。

main オブジェクトについてのリファレンスに、この辺の挙動が詳しく書いてあります。

要約

トップレベルでの self を表すオブジェクトです。

main では参照できない事に注意してください。トップレベルで self から参照してください。

トップレベルで定義したメソッドは Object の private インスタンスメソッドと して定義されます。

(太字による強調はコメント主の私による)

例えば以下のコードで test_func を呼べるのも、test_func が「グローバルスコープ」だからなのではなく、 FooObject を継承してるから、というのが真相です。

def test_func(a,b,c)
p a + b + c
end

class Foo
def hoge
test_func(2, 3, 4)
end
end

Foo.new.hoge # => 9

また、BasicObject.method(:test_func) がうまく動いたのも、ClassObject を継承しているからです。

余談ですが、pputs などの多くの組み込み関数も似たような仕組みになっています。実はこれらは Kernel クラスのメソッドとして定義されていて、 Object はこの Kernel を継承しています。なので、test_func と同様に pputs もいろんな場所から呼べるというわけです。

適切なレシーバーは何なのか

ここまでを踏まえてようやく本題です。

案1. BasicObject のインスタンス

とりあえず記事内での考えを延長して、以下のようなコードを考えたくなるかもしれません。しかしこれはエラーになります。

func = BasicObject.new.method(:test_func) # => undefined method `method' for #<BasicObject:0x00007f9c988641b0> (NoMethodError)

理由はもちろん、test_funcObject のメソッドだからであり、BasicObject には含まれないからです。

案2. Object のインスタンス

じゃあ Object なら動くのか。その通りです。

func = Object.new.method(:test_func)
test_func_func(func) # => 6

案3. main オブジェクト(オススメ)

トップレベルで動かすのと全く同じ状態で渡したいなら、トップレベル(=main オブジェクト)をレシーバーにするのが良いです。上で抜粋した通り、main オブジェクトは self で参照します。

func = self.method(:test_func)
test_func_func(func) # => 6

なお、self は省略できます。

func = method(:test_func)
test_func_func(func) # => 6

なんと、「レシーバーなんて要らなかったんや!」というオチ。

案4. そもそもメソッドを使わない(一番オススメ)

レシーバーが必要なのはメソッドだからです。Proclambda を使えばレシーバー無しの「関数」が扱えます。

test_func = lambda { |a, b, c| p a + b + c }

def test_func_func(func)
func.call(1, 2, 3)
end

test_func_func(test_func) # => 6

ちなみに lambda の代わりとして -> という特殊なリテラル記法もあります。

test_func = ->(a, b, c) { p a + b + c }

以上、コメントで長々と書いてしまいましたが、参考になりましたら幸いです。

ブロック付きgsubの返り値の違いについて

基本的な話なのだがRubyのブロックには二通りの記法がある。

Objects.each { |object|
    object.method
}

Objects.each do  |object|
    object.method
end

だ。

僕は、手続き上両記法は等価なものだと思っていたのだが、どうもそうでは無いらしい、ということが分かったのでメモとして残しておく。

違い

さて、どのようなケースで等価ではないかというと、下記のような時だ。

pry(main)> s = "hello, world"
pry(main)> puts s.gsub(/\w+/) {|word| word.capitalize }
Hello, World
=> nil

このコードをdo...endによる記法で書くと以下の様な挙動になる。

pry(main)> s = "hello, world"
pry(main)> puts s.gsub(/\w+/) do |word|
pry(main)*   word.capitalize
pry(main)* end
#<Enumerator:0x000056474a99ef70> 
=> nil 

原因

原因は、{...}do...endの結合の強さの違いにある。 Ruby 2.4.0 リファレンスマニュアルには下記のようにある

{ ... } の方が do ... end ブロックよりも強く結合します。 次に例を挙げますが、このような違いが影響するコードは読み辛いので避けましょう:

foobar a, b do .. end # foobarの引数はa, bの値とブロック
foobar a, b { .. } # ブロックはメソッドbの引数、aの値とbの返り値とがfoobarの引数

つまり、

pry(main)> s = "hello, world"
pry(main)> puts s.gsub(/\w+/) do |word|
pry(main)*   word.capitalize
pry(main)* end
#<Enumerator:0x000056474a99ef70> 
=> nil 

では、putsメソッドの引数はs.gsub(/\w+/)do...endブロックと解釈されているのだ。

それぞれ、分けて実行してみると、以下のようになる

pry(main)> puts s.gsub(/\w+/)
#<Enumerator:0x0000560d6a307a78>
=> nil
pry(main)> puts do
pry(main)*   s = 10
pry(main)* end

=> nil

なるほどこの2つが合わさっていたのか。

Rubyではブロックを取るメソッドをブロックを省略して書いた場合、Enumeratorオブジェクトを返す。 gsubの引数に正規表現パターンのみを与えた場合、ブロックを使っての置換の記法になるため、1つめはこれだろう。

2つめはなんだろうか。 チラッと調べてみた所、ブロックを取らないメソッド(内部にyieldがない)にブロックを渡した場合、ブロックは単に無視される、というが引っかかった。 多分これなんじゃないだろうか。こっちについてはもう少し確認する必要がありそうだけれども。

バージョン

$ ruby --version
ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-linux]
=> nil 

参考

Enumerator とブロックの省略 - まめめも

ブロックをdo…endで書くか{…}で書くかにより挙動が変わる例 - Qiita

ブロックを与えない場合にEnumeratorを返すメソッドを作る - Qiita

Ubuntu 16.04 LTSに最新のrbenvを入れた

概要

手持ちのUbuntu 16.04 LTSにaptでrbenvを入れたのだが、バージョンが古くて2.4系のrubyがインストールできなかった。 どうしても2.4系が使いたい気持ちだったため、purgeして野良ビルドを試みた。 その時の記録。

参考

ほぼ上記の内容の通りなので、英語が読めるならこっちを読めばいい。

rbenvのビルド

# リポジトリをクローンしてくる
$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv 
# rbenvを高速化するためにbash拡張を試みる。失敗しても別に気にしなくて良い
$ cd ~/.rbenv && src/configure && make -C src
# パスを通す。zshを使うなら~/.zshrcにリダイレクトする
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc

以上で完了。下記を実行して、

$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash

下のような表示が出ればうまくビルドできている。

Checking for `rbenv' in PATH: /usr/local/bin/rbenv
Checking for rbenv shims in PATH: OK
Checking `rbenv install' support: bash: line 114: : command not found ()
Counting installed Ruby versions: none
  There aren't any Ruby versions installed under `~/.rbenv/versions'.
  You can install Ruby versions like so: rbenv install 2.2.4
Checking RubyGems settings: OK
Auditing installed plugins: OK

ruby-buildのビルド

rbenv installコマンドを使えるようにするために、ruby-buildをインストールする。 rbenvのプラグインとしてruby-buildをインストールする。

# プラグイン用ディレクトリの作成
$ mkdir -p "$(rbenv root)"/plugins
# 先のディレクトリ内にリポジトリをクローンしてくる
$ git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build

インストール可能なrubyバージョンの確認

$ rbenv install --list

で確認できる。後は、希望のバージョンを

rbenv install 2.4.2

でインストールすれば良い。

group_collection_selectメソッドでのエラー[Ruby on Rails 5 アプリケーションプログラミング]

grouped_collection_selectメソッドでのエラー

最近「Ruby on rails 5 アプリケーションプログラミング」の本を読みながらrailsの勉強をしているのだが、本の記述と配布されているサンプルコード(2017/10/29時点)に誤りがあり、割と長い時間嵌ってしまった。 同じ問題に突き当たる人がいないとも限らないので、解決方法を置いて置く。

問題は「4.1.9 選択ボックスの選択をグループ化するーgrouped_collection_selectメソッド」で解説されているサンプルコード

<%= form_for(@review) do |f| %>
    レビュー対象書籍:
    <%= f.grouped_collection_select :book_id, @authors, :books, :name, :id, :title %>
<% end %>

についてのものだ。 本の手順通りに進めてこのテンプレートファイルを記述し(もちろん対応するルート定義と、コントローラーにメソッドが用意されている必要がある)、サーバーを立ち上げてアクセスすると、

undefined method 'books' for #<Author:~~~>

といったようなエラーに出くわし、ページへのアクセスができない。 これは、この時点でauthorモデルとbookモデルの間にアソシエーションを定義していないことに起因している。配布されているサンプルプログラムの方も同様の理由でエラーを吐く。

ここで問題になっているアソシエーションとはなんだろうか。少し調べた内容を置いておく。

アソシエーション

データベースのテーブル間の関係をrailsのモデル間の関係に落とし込んでrails側から操作できる用にするためのもの。データベースの基本的な用語についてはリンク参照。 テーブルの一つのレコードに対して別のテーブルの一つのレコードが結びついている場合を1:1の関係、別のテーブルのn個のレコードが結びついている場合を1:nの関係などと呼んだりする。

どうも、grouped_collection_selectメソッドはこのアソシエーションの情報を元に対応するbookモデルのパラメータを取得するはずだったのだが、記述がなかったので叶わなかった、ということみたいだ。

対処

そんなわけで、欠けているアソシエーションの情報を補ってやれば解決するということが分かった。今回実装したいauthorとbookの関係性は複数:複数の関係性で、このようなものは中間テーブルを介した表現が一般的らしい。 メソッドとしてはhas_and_belongs_to_manyを使い、下記の用になる。

.../railsbook/model/app/models/ 以下のbook.rbとauthor.rbにアソシエーションを記述する

class Book < ApplicationRecord
    has_and_belongs_to_many :authors #authorとのアソシエーションを追加
end
class Author < ApplicationRecord
    belongs_to :user
    has_and_belongs_to_many :books #booksとのアソシエーションを追加
end

has_and_belongs_to_manyメソッドは複数対複数のアソシエーションをモデルに追加するメソッド。対応するテーブルの関係に上下をつけない為、双方のモデルに記述する必要がある。

以上の変更を加えれば、/view/group_selectのページにアクセスできるようになる。

その他参考

https://teratail.com/questions/80528