yucatio@システムエンジニア

趣味で作ったものいろいろ

Rubyでは、式の途中で改行するときは、算術オペレータを一番後ろに置いてから改行する

Javaの人間がRubyでやってしまった失敗を1つ紹介します。

意図した通りに動かないコード

こんなコードを書きました

sum = 100 + 200
        + 300 + 400
puts sum
#=> 300

100+200+300+400 (=10000)のつもりで書いていましたが、最初の2つしか足されていません。

デバッグ

Rubyでは、原則として行の終わり=式の終わりなので、明示的に行が続くことを示さなければいけません。

算術オペレータを最後に置く

明らかに式が続いていると判断される場合には、次の行も続いて評価されるので、算術オペレータを後ろに置けば続く行も1つの式として評価されます。

sum = 100 + 200 +
      300 + 400
puts sum
#=> 1000

バックスラッシュを使う

バックスラッシュを使うと、明示的に式が続いていることを示せます。

sum = 100 + 200 \
      + 300 + 400
puts sum
#=> 1000

入門書にちゃんと書いてあった

『初めてのRuby』(Yugui[著])を読み返してたらちゃんと書いてありました。

次のように記述すると1と-2という別個の式が並んでいるものと解釈されます。(中略)

1
   - 2

括弧を用いる方法はうまくいかない

ちなみに以下のように、式全体を括弧でくくる方法はうまくいきません。

sum = (100 + 200
       + 300 + 400)
puts sum
#=> 700

これは、 Rubyの改行 - 永遠に未完成 のコメントで言及されているように、

sum = (100 + 200
       + 300 + 400)

は、

sum = (100 + 200;
       + 300 + 400)

と解釈され、(100 + 200; + 300 + 400)(+ 300 + 400)と等価になるからです。

Pythonだと式全体を括弧でくくるとうまくいくので、Rubyでもそうかと思いましたが、違いました。

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

初めてのRuby [ Yugui ]
価格:2376円(税込、送料無料) (2018/6/23時点)


複数行の式とRubyMineのフォーマッタとの相性が悪い

複数行にまたがって式を書く方法は、現在、RubyMineのフォーマッタ(Code > Reformat Code)との相性がよくないです。(RubyMine version: 2018.1.3)

yucatio.hatenablog.com

環境

Rubyでは、メソッド引数のカッコはメソッド名の直後に書くこと。空白を入れちゃだめ

引き続き、『プロを目指す人のためのRuby入門』(伊藤淳一[著])を読んでいます。

問題発生

本の中のコードを(間違って)写して、下記を実行したところ、

class DeepFreezableTest < Minitest::Test
  def test_deep_freeze_to_hash
    # ハッシュの値は正しいか
    assert_equal (
        {'Japan' => 'yen', 'US' => 'dollar', 'India' => 'rupee'},
        Bank::CURRENCIES
    )
  end
end
syntax error, unexpected ',', expecting ')' (SyntaxError)

ハッシュの直後にあるカンマで文法エラーが発生しました。

解決方法

assert_equal(の間の空白(半角スペース)を削除する

class DeepFreezableTest < Minitest::Test
  def test_deep_freeze_to_hash
    # ハッシュの値は正しいか
    assert_equal(
        {'Japan' => 'yen', 'US' => 'dollar', 'India' => 'rupee'},
        Bank::CURRENCIES
    )
  end
end

なぜエラーが発生したのか

assert_equalとメソッド引数のカッコに半角スペースを入れたことにより、({'Japan' => 'yen', 'US' => 'dollar', 'India' => 'rupee'}, Bank::CURRENCIES)全体が1つ目の引数と解釈されてしまいました。どうやら、メソッド名の後に空白があるとメソッド呼び出しのカッコが省略されたと解釈されるらしいです。

{'Japan' => 'yen', 'US' => 'dollar', 'India' => 'rupee'}, Bank::CURRENCIES自体は式として解釈されないので、文法エラーになりました。

あとがき

空白の有無でSyntaxErrorになってしまうのは予想外でした。 エラーが出た時にtypoかと思って本と見比べるも、空白の有無は見逃してしまい、解決に時間がかかってしまいました。 RubyMineでこの文法エラーとしては表示されませんでした。よく見たら警告出てました。見逃してました。

f:id:yucatio:20180617171129p:plain

googleでエラーメッセージを検索しましたが、なにせ記号が入っているため、別のエラーメッセージが検索結果に出てしまい、なかなか同じエラーメッセージが出ているページにたどり着けなかったです。

色々試した結果、解決してよかったです。どんなエディタでも空白文字を表示しておくのはおすすめです。

yucatio.hatenablog.com

補足

念のため書いておきますが、本に書いてあるコードを間違わずに写せばエラーは出ません。

環境

RubyMineでminitestを実行する

今、『プロを目指す人のためのRuby入門』(伊藤淳一[著])を読んでるんですが、使用するテストフレームワークminitestなのですよね。

RubyMineでminitestを追加しようとしたら、”New”の一覧にもないし、とりあえず手動でファイル作って

require 'minitest/autorun'

と書いてみたら”そんなファイルないよ”とのエラーが出ました。

f:id:yucatio:20180612114010p:plain

f:id:yucatio:20180612114019p:plain

公式ドキュメント見たら、minitestの実行要件が載っていたのでその通りにします。

Minitest - Help | RubyMine

と言っても、詳細な手順はさすがに載っていなくて、久々にrubyをさわったらBundlerの使い方を忘れていたので、詳細な手順を残しておきます。

Bundle Init

Tools > Bundler > Init でBundlerを初期化します。

f:id:yucatio:20180612120255p:plain

GemFileの書き換え

GemFileが作成されたので、下記を追加します。

group :test do
    gem "minitest"
    gem 'minitest-reporters', '>= 0.5.0'
end

minitest-reportersは、minitest のバージョンが 5.0.0ならばインストールは必須ではないのですが、入れておくとテスト結果をツリー状に表示してくれるので、追加するのがおすすめです。(Important noteに書いてある)

公式ドキュメントには、'cucumber-rails'も書かれていますが、今回は必要ないので外しています。

Bundle Install

Tools > Bundler > Install を選択して、gemをインストールします。

f:id:yucatio:20180612121439p:plain

これでminitestを使用する準備が整いました。

テストファイル作成

“New”メニューにminitestのテンプレートが追加されるかと思いましたが、されませんでした。 テストファイルは、New > Ruby Class Template らへんから作ります。

minitestのテストファイルは、(拡張子を除いた部分が)_testで終わるか、test_で始まり、拡張子が.rbである必要があります。

また、テストクラス名はTestで終わる必要があります。

テストメソッドはtest_で始める必要があります。

ヘッダーはこんな感じ

require 'minitest/autorun'
require 'minitest/reporters'
MiniTest::Reporters.use!

テストクラスはMinitest::Testを継承します。

f:id:yucatio:20180612121741p:plain

テスト実行

テストの実行はソースコードの実行と同じようにファイル右クリック > Runでできます。testフォルダ右クリック > Run All Tests ..で全てのテストを実行可能です

f:id:yucatio:20180612135028p:plain

テスト実行結果です。テストを再実行したい場合は、左上の緑の三角ボタンを押します。

f:id:yucatio:20180612134439p:plain

環境

番外編

minitest-reportsをbundle installした直後に、以下のエラーが出ましたが、Rubymineを再起動したらエラーは出なくなりました。

`rescue in specs': Your bundle is locked to ansi (1.5.0), but that version could not be found in any of the sources listed in your Gemfile. If you haven't changed sources, that means the author of ansi (1.5.0) has removed it. You'll need to update your bundle to a different version of ansi (1.5.0) that hasn't been removed in order to install. (Bundler::GemNotFound)

f:id:yucatio:20180612125428p:plain

yucatio.hatenablog.com

リソースにfirstとかlastとかtodayでアクセスしたい場合のリンクパスの生成方法 (Ruby on Rails)

railsでリンクパスを作成するときに、

events/123/tickets/1

のようにIDを指定するのではなく、

events/123/tickets/last

lastように、

リソースパスに文字列を指定する方法。

うまくいく方法

シンボルを渡せばよいだけでした。

event_ticket_path(@event, :last)
#=> /events/123/tickets/last
# routes.rb

Rails.application.routes.draw do
  resources :events  do
    resources :tickets
  end
end

うまくいかない方法

モデルのIDに文字列を渡したところ、0に変換されてしまいました。

ticket = @event.tickets.build(id: 'last')
event_ticket_path(@event, ticket)
#=> /events/123/tickets/0
環境

RubyでArrayをHashに変換する

前回の記事で、ArrayをHashに変換するのにArray#mapとArray#to_hを使用して、ArrayをHashに変換しました。

yucatio.hatenablog.com

オブジェクトの配列を、オブジェクトのIDをハッシュのキー、オブジェクト自身をハッシュのバリューとして登録したい場合があります。

例として、配列のデータをハッシュに変換します。idをキーとして、データ自体を値としたHashを作成するには下記で実現できます。

arr = [
  {id: 1, name: "Alice", age: 20}, 
  {id: 2, name: "Bob", age: 30}
]

arr.map {|data| [data[:id], data]}.to_h
# => 
# {
#   1=>{:id=>1, :name=>"Alice", :age=>20}, 
#   2=>{:id=>2, :name=>"Bob", :age=>30}
# }

順を追って見ていきます。まずmapオブジェクトで、配列の各要素を、[data[id], data]に変換します。

arr.map {|data| [data[:id], data]}
# => 
# [
#   [1, {:id=>1, :name=>"Alice", :age=>20}], 
#   [2, {:id=>2, :name=>"Bob", :age=>30}]
# ]

これを、to_hを使用してハッシュに変換します。

[[1, {:id=>1, :name=>"Alice", :age=>20}], [2, {:id=>2, :name=>"Bob", :age=>30}]].to_h
# => 
# {
#   1=>{:id=>1, :name=>"Alice", :age=>20}, 
#   2=>{:id=>2, :name=>"Bob", :age=>30}
# }

to_hの方法を知る前は以下のように書いていましたが、to_hの方がすっきり書けるように思います。

obj_hash = {}
arr.each { |data|
  obj_hash[data[:id]] = data
}
環境

Railsでajax通信時にattribute名とfull_messageの組み合わせでレスポンスを返す

ajaxでの非同期通信時に、attributeごとのメッセージを表示するのに手間取ったので記録しておきます。

ajax通信でない時は、errors.full_messages_for(:attribute_name)で各attributeのエラーメッセージは取得できます。

やりたいこと

ajax通信時に、入力エラーがある場合に、入力項目ごとに表示したい。

こうなればよい。

f:id:yucatio:20170414023228p:plain:w400

errorsを使用する場合(うまくいかなかった例)

コード

class TicketsController < ApplicationController
  def create
    ticket = Ticket.create(ticket_params)

    if ticket.save
      render json: {message: 'success'}, status: :created
    else
      error_messages = ticket.errors
      render json: {message: error_messages}, status: :unprocessable_entity
    end
  end
end

レスポンス

{"message":
  {"password":["を入力してください"],
    "email":["を入力してください","は有効でない形式です"]}
}

画面表示

f:id:yucatio:20170414023247p:plain:w400

メッセージボディしか表示されません。これじゃない。

errors.full_messages を使用する場合(うまくいかなかった例)

コード

class TicketsController < ApplicationController
  def create
    ticket = Ticket.create(ticket_params)

    if ticket.save
      render json: {message: 'success'}, status: :created
    else
      error_messages = ticket.errors.full_messages
      render json: {message: error_messages}, status: :unprocessable_entity
    end
  end
end

レスポンスはこのようになります。

{"message":["パスワードを入力してください","メールアドレスを入力してください","メールアドレスは有効でない形式です"]}

画面に表示するとしたらこうなります。

f:id:yucatio:20170414023306p:plain:w400

1ヶ所に全てのエラーを表示する場合はこれでもよいですが、項目ごとに表示するといった目的は達成できません。

ticket.errors.keys と errors.full_messages_for 組み合わせる(うまくいった例)

コード

class TicketsController < ApplicationController
  def create
    ticket = Ticket.create(ticket_params)

    if ticket.save
      render json: {message: 'success'}, status: :created
    else
      error_messages = ticket.errors.keys.map { |key| [key, ticket.errors.full_messages_for(key)]}.to_h
      render json: {message: error_messages}, status: :unprocessable_entity
    end
  end
end

レスポンス

{"message":
  {"password":["パスワードを入力してください"],
   "email":["メールアドレスを入力してください","メールアドレスは有効でない形式です"]}
}

画面表示

f:id:yucatio:20170414023228p:plain:w400

できました!

環境

RubyMineでRuby on railsの開発をする その8: おすすめ設定3つ

ナビゲーションバーの表示

現在開いているファイルの階層をウィンドウ上部に表示させます。

View > Nabigation Bar を選択

f:id:yucatio:20170207185038p:plain:w200

画面上部に awesome_events > app >views > events > new.html.slim と、現在開いているファイルの階層が表示されるようになりました。

f:id:yucatio:20170204195918p:plain

階層をクリックすると、同階層の他のファイルが表示され、ここからファイルを開くことができます。同一コントローラの他のファイルを参考のため開くときに便利です。

f:id:yucatio:20170204195931p:plain

空白文字の表示

半角空白や全角空白はデフォルトでは単なる空白ですが、空白文字の表示を設定すると、それぞれ別の記号で表示してくれるようになります。

RubyMine メニュー > Preferences.. を選択

f:id:yucatio:20170204195806p:plain:w200

Editor > General > Appearence をい開いて、Show whitespaces のチェックを入れます。

f:id:yucatio:20170204195823p:plain

半角スペースは.(ドット)、全角スペースは□(四角)、タブは→|で表示されるようになりました。

f:id:yucatio:20170204195735p:plain

チームで開発している場合は、チームメンバーにもこの設定を入れてもらいましょう。 新人が全角空白や、タブを仕込んでくるのをちょっとは防げるはず。

ツールバーの表示

各種操作を行うためのツールバーを表示します。

View > Toolbar を選択します。

f:id:yucatio:20170204195840p:plain:w200

ツールバーが表示されました。各種タスクを実行するのに便利です。

f:id:yucatio:20170204195853p:plain

環境