20190502のRubyに関する記事は15件です。

RubyでOpenWeatherMapを使って天気を取得してみた

はじめに

Rubyを使って何かできないか考え、ふと天気が取得できたらいいなと思ったのがきっかけです。

環境

macOS Mojave(10.14.4)
Ruby 2.6.3

OpenWeatherMapについて

現在の天気と天気予報を行ってくれるサイトです。
またAPIを利用することで、以下機能を無料で実行できます。

  • 20万以上の都市を含む、あらゆる場所の現在の天気データにアクセス
  • 4万以上の気象観測所からのデータと、グローバルモデルに基づいて天気を頻繁に更新
  • JSON、XML、HTMLフォーマットでデータを取得可能
  • 3時間毎の天気を5日後まで取得可能

API Key取得

OpenWeatherMapにアクセスします。
スクリーンショット 2019-05-02 21.35.16.png

Sign upをクリックし、アカウントを作成します。
CreateAccount.png

アカウントを作成すると、登録したメールアドレス宛にメールが届きます。
その中にAPI KEYがあります。

取得処理

こちらの記事を参考にさせていただきました。
無料天気予報APIのOpenWeatherMapを使ってみる

調べたい場所は東京を指定しました。

response = open(URL + "?q=Tokyo,jp&APPID=#{API_KEY}")
puts JSON.pretty_generate(JSON.parse(response.read))

取得結果

5/2 22時過ぎごろに実行したときの取得結果です。

{
  "cod": "200",
  "message": 0.008,
  "cnt": 40,
  "list": [
 ・・・略・・・
    {
      "dt": 1556841600,
      "main": {
        "temp": 291.03,
        "temp_min": 291.03,
        "temp_max": 292.081,
        "pressure": 1015.3,
        "sea_level": 1015.3,
        "grnd_level": 1012.9,
        "humidity": 40,
        "temp_kf": -1.05
      },
      "weather": [
        {
          "id": 803,
          "main": "Clouds",
          "description": "broken clouds",
          "icon": "04d"
        }
      ],
      "clouds": {
        "all": 83
      },
      "wind": {
        "speed": 1.19,
        "deg": 84.806
      },
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2019-05-03 00:00:00"
    },
  ・・・略・・・
  "city": {
    "id": 1850147,
    "name": "Tokyo",
    "coord": {
      "lat": 35.6828,
      "lon": 139.759
    },
    "country": "JP",
    "population": 8336599
  }

5/2 24時ごろは曇りのようですね。
broken cloudsが83%となっているので、ちぎれ雲のようなものが空のほとんどを占めているようです。

比較のためにヤフー天気を見てみました。
スクリーンショット 2019-05-02 22.18.09.png
雲ひとつないですね、全く逆ですね。。。

まとめ

予報は外れているものの、APIを使用した取得は面白かったです。
他にも天気予報のAPIを公開しているところがあるようなので、OpenWeatherMapと他を組み合わせながら出力すると面白いと思います。

またOpenWeatherMapは都市のIDや、郵便番号や緯度経度も指定できるようなのでそちらも試してみます。

その際には天気のアイコンも出したいですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyの改行はそのまま出力される

printの引数内で改行すると…?

printの引数で改行するには以下のように「\n」を入れる必要がある。

sample.rb
print("Hello,\nHogehoge\n")

んで、以下のコードでこれと同じ出力ができる。

sample_rewrite.rb
print("Hello,
Hogehoge\n")

書き方的に汚いと思ったものの、「\n」を複数回挟むことがあるなら有用かも…?

ひねくれてないで素直に書け

ま、こう書いた方が素直ですな。

print2puts.rb
puts("Hello,")
puts("Hogehoge")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

本番環境にデプロイできない時の対処法(初心者向け)

AWSで本番環境にデプロイできない時の対処法(初心者向け)

前提

AWSを利用してwebサイトを公開し、開発したものをデプロイすると本番環境に反映されないことや、サイトが閲覧できない状態(We're sorry ~ )になることがよくあるので、対処法を解説します。

開発環境

Ruby 2.3.1
Rails 5.2.2.1
AWS EC2(無料枠使用)
※一旦デプロイが完了し、自動デプロイの設定が完了している前提で解説します。
自動デプロイには、capistranoを使用しています。

対処法(1) EC2のインスタンスを再起動

何度も自動デプロイを行なっていると、EC2側で変更が反映されず、場合によっては変更箇所が見れないことがあります。(ex.ユーザーログインの機能を実装したのに反映されていない等)
その場合、AWSのマネージメントコンソールから
EC2 → インスタンス → 該当のインスタンスをクリック → アクションのインスタンスの状態 → 再起動 を行います。
その後、再度ターミナルからEC2にログインし
sudo service nginx start 
sudo service mysqld start

のコマンドを実行し、WEBサーバのnginxとmysqlを立ち上げます。
その後、ローカルで自動デプロイのコマンドを実行すると、アプリケーションサーバのunicornが立ち上がり、サイト上に変更が反映され、閲覧できるようになります。

対処法(2) ローカルでエラーが起きていないか確認する

ローカルでは反映されないエラーが、本番ではエラーと認識されることがあります。
例えば、ECサイトを作成し商品一覧は問題なく表示されるが、商品の詳細画面にアクセスするとsyntaxエラー(hamlで記述している場合はインデント等)が起きている場合、本番ではエラーがあると認識されサイト全体が閲覧できないことがあります。
ローカルで確認し、エラーの箇所が分からなければ、サーバー側から

less log/production.log
less log/unicorn.log
 
のコマンドでログを確認し、エラーの箇所を特定します。
ここにsyntaxエラーやFATALと記載されている項目があれば、そのエラーを解消した上で再度デプロイします。
具体的な例としては、gemでfont-awesome-railsを使用していると、記述の方法によって、ローカルでエラーは出ないが本番ではエラーとなることがあり、このようなエラーはログを確認しなければ特定することは困難です。

対処法(3) AWSの無料枠を確認する

直接的なエラーではありませんが、AWSの無料枠(メモリの容量等)を越えるとデプロイできなくなることがあります。
AWSの無料枠はあくまでお試しのような枠なので、同時に2つサイトを公開するとなると容量が足りなくなることがあり、その結果デプロイできないことや変更内容が反映されないことがあります。
また、gemもメモリに影響を与えるので、一度確認しておきましょう。
私の場合、無料枠を利用して2つ目のアプリケーションを公開し、gemのfont-awesome-sassを導入しようとしたら、ターミナルでメモリの容量が足りないというエラーが発生しました。font-awesome-sassを削除し、再度デプロイするとエラーが解消されました。

以上が初歩的な本番環境で発生するエラーの解消方法です。
他にも対処法は多くありますので、参考程度にして下さい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】ローカルにないエラーがherokuで表示された時の対処法

「ローカル環境での挙動はバッチリ、さあ herokuへアップロードだ」

と思ったら、アップロード後にはなんのヒントもないエラーページ。

オリジナルアプリケーション開発で遭遇したシチュエーションをもとに、このような時の解決法を整理します。

1.エラー画面を表示する

config/environments/production.rb
config.consider_all_requests_local = false

本番環境だとエラー画面のデフォルトが下記のように設定されています。
これではエラー内容がわかりづらいので、本番環境でもエラーを表示できるようにしましょう。

スクリーンショット 2019-04-30 7.50.16.jpg

エラー解決後は、速やかに戻しましょう。

config/environments/production.rb
config.consider_all_requests_local = true

2.ログを確認する

heroku logs -t #-tでも-tailでもOK

ログを確認すると、

  • 本番環境ではカラムが追加されていない(※heroku rails db:migrateで解消)

などのエラー内容ががつかめるはずです。

参考:Heroku コマンド・設定 メモメモ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】コードを少し変えてYARV命令の違いを確認する

はじめに

自分が書いたRubyのコードがどのような道のりを辿って実行されるのか興味を持ったことはあるでしょうか。

RubyのしくみによるとRubyコードは以下の流れでYARV命令へと変換されます。

Rubyコード
↓
字句解析
↓ 
構文解析
↓ 
コンパイル
↓
YARV命令

この記事では、YARV命令の部分をピックアップし、YARV命令がコードによってどう変わるのか簡単に見ていきます。

なお、今回利用したRuby処理系、Rubyバージョンは以下の通りです。

Ruby処理系 Rubyバージョン
MRI 2.6.1

YARV命令表示

まず、YARV命令を表示してみます。

簡単なRubyのコード 4 + 3 のYARV命令をみてみましょう。YARV命令は RubyVM::InstructionSequence を使うことで見ることができます。

irb(main):001:0> puts RubyVM::InstructionSequence.compile('4+3').disasm
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,3)> (catch: FALSE)
0000 putobject                    4                                   (   1)[Li]
0002 putobject                    3
0004 opt_plus                     <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache>
0007 leave
=> nil

putobjectでスタックに値をプッシュし、opt_plusで加算を実行するのが確認できます。

すなわち、以下の流れでコードが実行されます。

  1. 4をスタックにプッシュ
  2. 3をスタックにプッシュ
  3. 加算(4 + 3

4 + 3 * 2 vs (4 + 3) * 2

続いて、4 + 3 * 2(4 + 3) * 2のYARV命令を表示して結果を比べてみます。

irb(main):001:0> puts RubyVM::InstructionSequence.compile('4+3*2').disasm
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,5)> (catch: FALSE)
0000 putobject                    4                                   (   1)[Li]
0002 putobject                    3
0004 putobject                    2
0006 opt_mult                     <callinfo!mid:*, argc:1, ARGS_SIMPLE>, <callcache>
0009 opt_plus                     <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache>
0012 leave
=> nil
irb(main):001:0> puts RubyVM::InstructionSequence.compile('(4+3)*2').disasm
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,7)> (catch: FALSE)
0000 putobject                    4                                   (   1)[Li]
0002 putobject                    3
0004 opt_plus                     <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache>
0007 putobject                    2
0009 opt_mult                     <callinfo!mid:*, argc:1, ARGS_SIMPLE>, <callcache>
0012 leave
=> nil

上記2つを比べるとopt_plusの位置が変わっていることに気がつきます。

それぞれ以下の流れでコードが実行されます。

・ 4 + 3 * 2

  1. 4をスタックにプッシュ
  2. 3をスタックにプッシュ
  3. 2をスタックにプッシュ
  4. 乗算(3 * 2
  5. 加算(4 + 6

(4 + 3) * 2

  1. 4をスタックにプッシュ
  2. 3をスタックにプッシュ
  3. 加算(4 + 3
  4. 2をスタックにプッシュ
  5. 乗算(7 * 2

命令の順番によって、計算結果が変わることが確認できます。

まとめ

コードを少し変えることでYARV命令がどう変わるのかみてきました。
普段YARVの内部スタックの状態を意識することはなかったですが、より深いところまで理解できるよう知識を深めていきたいものです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RubyKaigi 2019 Cookpad Daily Ruby Puzzles を全パターン試して解く

RubyKaigi 2019のクックパッドブースで"Cookpad Daily Ruby Puzzles"というのをやっていたらしくて、イベント終了後に正解と解説というエントリーも出ていました。

RubyKaigi 2019 Cookpad Daily Ruby Puzzles の正解と解説

この問題、結果的には全て問題のコードに1文字を追加するだけで解けるらしいので、全パターン試して解くという方法を試してみました。こんなコードです。

solv.rb
CODES = {}

CODES["Example"] = <<'EOS'
def foo
  "Hello world" if
       false
end
puts foo
EOS

CODES["Problem 1-1"] = <<'EOS'
# Hint: Use Ruby 2.6
puts "#{"Goodbye" .. "Hello"} world"
EOS

CODES["Problem 1-2"] = <<'EOS'
puts&.then {
  # Hint: &. is a safe
  # navigation operator
  "Hello world"
}
EOS

CODES["Problem 1-3"] = <<'EOS'
include Math
# Hint: the most beautiful equation
Out, *,
     Count = $>,
             $<, E ** (2 * PI)
Out.puts("Hello world" *
         Count.abs.round)
EOS

CODES["Problem 2-1"] = <<'EOS'
def say
  -> {
    "Hello world"
  }
  # Hint: you should call the Proc.
  yield
end

puts say { "Goodbye world" }
EOS

CODES["Problem 2-2"] = <<'EOS'
e = Enumerator.new do |g|
  # Hint: Enumerator is
  # essentially Fiber.
  yield "Hello world"
end

puts e.next
EOS

CODES["Problem 2-3"] = <<'EOS'
$s = 0
def say(n = 0)
  $s = $s * 4 + n
end

i, j, k = 1, 2, 3

say i
say j
say k

# Hint: Binary representation.
$s != 35 or puts("Hello world")
EOS

CODES["Problem 3-1"] = <<'EOS'
def say s="Hello", t:'world'
  "#{ s }#{ t } world"
end
# Hint: Arguments in Ruby are
# difficult.

puts say :p
EOS

CODES["Problem 3-2"] = <<'EOS'
def say s, t="Goodbye "
  # Hint: You can ignore a warning.
  s = "#{ s } #{ t }"
  t + "world"
end

puts say :Hello
EOS

CODES["Problem 3-3"] = <<'EOS'
def say
  "Hello world" if
    false && false
  # Hint: No hint!
end

puts say
EOS

CODES["Extra 1"] = <<'EOS'
Hello = "Hello"

# Hint: Stop the recursion.
def Hello
  Hello() +
    " world"
end

puts Hello()
EOS

CODES["Extra 2"] = <<'EOS'
s = ""
# Hint: https://techlife.cookpad.com/entry/2018/12/25/110240
s == s.upcase or
  s == s.downcase or puts "Hello world"
EOS

CODES["Extra 3"] = <<'EOS'
def say
  s = 'Small'
  t = 'world'
  puts "#{s} #{t}"
end

TracePoint.new(:line){|tp|
  tp.binding.local_variable_set(:s, 'Hello')
  tp.binding.local_variable_set(:t, 'Ruby')
  tp.disable
}.enable(target: method(:say))

say
EOS


require 'timeout'

CHARS =
  ('0'..'9').to_a +
  ('a'..'z').to_a +
  ('A'..'Z').to_a +
  %w( ! ? # % & | + - * / ^ ' . , < > = ~ $ @ _ " : ` \\ ; ) +
  [' ', "\n", "Dz"]

CODES.each do |name, code|
  puts
  puts "#{name}"
  puts Time.now
  (0...code.length).each do |i|
    putc '.'
    CHARS.each do |char|
      code_inserted = code.clone.insert(i, char)
      File.open("code.rb", "w") { |f| f.puts(code_inserted) }
      begin
        Timeout.timeout(1) do
          result = `ruby code.rb 2>/dev/null`
          if result == "Hello world\n"
            puts
            puts '-----'
            puts code_inserted
            puts '-----'
          end
        end
      rescue Timeout::Error
      end
    end
  end
end

このコードと、実行結果はgistに置いておきました。

最初はevalでコードを実行して試していたのですが、evalで実行した結果、たとえば!を再定義してしまって、次のループでの実行結果に影響を及ぼすことがあったりして、別ファイルにしてバッククオートでrubyコマンドで実行することにしました。

正解と解説に書いてありますが、"Problem 1-3"には「ブルートフォースよけ」が仕込まれています。まんまとはまってしまいました。Timeout.timeout(1) do ... endで囲って、一秒以上かかる場合は諦めるようにしました。

CHARSには数字、アルファベット、記号、空白文字、改行文字、そして"Dz"を入れています。"Dz"は"Extra 2"のためだけに入れました。。。("Extra 2"はヒントがなければ全パターン試したつもりでも解けませんね。。。)

全パターン試して解こうと思ったモチベーションの1つは、別解を見つけることだったのですが、"Example"以外は別解が見つかりませんでした。"Example"の別解は、if !falseの代わりにif :falseを使うというものです。ただ、"Example"は解答が発表されているわけではないので、厳密には別解というわけではないですね。別解が見つからないということは、問題作成者の方も私がやったような全パターンを試すスクリプトを書いて事前に確認しているのだと思います。

"Extra 3"は1文字解答が2つあるらしいのですが、まだ解答が1つしか見つかっていません。全パターン試しているはずなのに、なんででしょうかね。。。

(追記:2019/05/03 08:07)
コードにミスがありました。バックスラッシュ(\)とセミコロン(;)を別々の文字列にしたかったのに、\ ;と書いていて、空白とセミコロンの2文字の文字列ということになっていました。\\ ;に書き換えました。それにより、"Extra 3"の2つ目の1文字解答も見つけることができました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

臭うコードを消臭しよう Railsのリファクタリング

こんにちは。じゅんといいます。(ついったー)
普段仕事でRubyを使っています。フレームワークはRailsです。

ゼロイチのフェーズのプロジェクトに関わっていると、スピードをとにかく重視することがあります。
その結果、これまでに私は数々の「臭うコード」を生み出してしまいました。

スピードを重視していたとしても、「臭うコード」を生み出さずクリーンなコードを生み出し続ける優秀なエンジニアもいます。
そう。私はまだまだエンジニアとして未熟なので「臭うコード」を生み出してしまっていたのです。

エンジニアになって一年が経とうとしているので、いつまでも未熟とは言っていられません。
これを機にこれまで私が生み出してきた「臭うコード」を消臭していきたいと思います。

(随時追加していきます)

1, evalを使って消臭(リファクタリング)してみる

元々のコードがこちら。(メソッド名や変数名などは変えてあります。)

profile.rb
class Profile < ApplicationRecord

  #色ごとの勝利数を変更するメソッド
  #red_win_num, blue_win_num, white_win_num, green_win_numというカラムが存在するよ
  def change_win_num(color)
    if color == "red"
      self.red_win_num += 1
    elsif color == "blue"
      self.blue_win_num += 1
    elsif color == "white"
      self.white_win_num += 1
    elsif color == "green"
      self.green_win_num += 1
    end
  end
end

このchange_win_numというインスタンスメソッドですが、ほとんど同じ処理を繰り返し書いています。
色(color)によって処理が違うならまだしも、今回の場合やっている処理は「その色のwin_numを+1する」だけで全て共通です。
このように、if文で分岐しているのに繰り返し同じ処理を記述している場合は、evalを使えないか検討してみましょう。

evalの詳しい説明はリファレンスをみていただくとして、簡単に説明すると、evalは第一引数として渡した文字列をRubyプログラムとして実行してくれます。

以下の例をみていただければevalがやっていることが分かると思います。
(文字列 "p message" をevalに渡すとそれを実行してくれるという例)

evalの例
  message = "I love Ruby"
  eval "p message" # => "I love Ruby"

今回の「臭うコード」にはぴったりそうです。
早速evalを使って先ほどのコードをリファクタリングしていきます。

profile.rb
class Profile < ApplicationRecord

  def change_win_num(color)
    eval "self.#{color}_win_num += 1"
  end

end

めちゃくちゃスッキリしましたね。
colorをいちいちベタがきしていたらそこでバグを埋め込んでしまう可能性もありますが、これなら安心です。万が一他の色が増えたとしても、このメソッドを変更する必要はありません。

(若干の懸念点としては、カラムに存在しないcolorをこのメソッドの引数として渡された場合、エラーが起きてしまいます。)

(また、そもそもProfileに色ごとの勝利数のカラムを持たせていることへの懸念も残ります。このままだと色が増えて行ったらそれだけカラムを増やさないといけなくなってしまいます。それを考慮すると、「profile_id」と「color」と「win_num」の3つのカラムをもつ別テーブルを作った方がよりいい気がします。が今回はevalを使ったリファクタリングのみで一旦やめておきます。)

戻り値が複数のものを1つにしてみよう

Rubyでは、戻り値を複数持つことができます。
この場合、メソッドの呼び出し側でも複数の受け皿(変数)を用意しておく必要があります。

class Sample < ApplicationRecord

  #2つの数字を渡すと、足し算、引き算、掛け算、割り算した値を返してくれるクラスメソッド
  #本来それぞれは別のメソッドにすべきだが、複数の戻り値をもつメソッドの例として無理やり1つのメソッドにしました
  def self.calculate(num_1, num_2)
    add_result       = num_1 + num_2 #足し算
    substract_result  = num_1 - num_2 #引き算
    multiple_result  = num_1 * num_2 #掛け算
    divide_result    = num_1 / num_2 #割り算

    #複数の結果をまとめて返す
    return add_result, substrac_result, multiple_result, divide_result
  end

end

呼び出す方はこんな感じで呼び出せます。

add, substract, multiple, divide = Sample.calculate(30, 10)

p add       #=> 40
p substract #=> 20
p multiple  #=> 300
p divide    #=> 3

今回の例だと4つの戻り値があります。これがさらに増えると、メソッドの呼び出し元のコードがかなり汚くなります。
呼び出すたびに、その戻り値分の受け皿を用意する必要があるからです。

また、戻り値の順番も気にしないといけなくなるので、これも厄介です。
今回の例だと、1つ目の戻り値が足し算の結果で、2つ目が引き算で、3つ目が掛け算で、4つ目が割り算ということを呼び出し元が認識しておく必要があります。これを間違えると事故が起きる可能性があります。

これらの問題を解消する為に、戻り値を1つにしてみましょう。

class Sample < ApplicationRecord

  def self.calculate(num_1, num_2)
    add_result        = num_1 + num_2 #足し算
    substract_result   = num_1 - num_2 #引き算
    multiple_result   = num_1 * num_2 #掛け算
    divide_result     = num_1 / num_2 #割り算

    calculated_result = {
      add:       add_result,
      substract: substract_result,
      multiple:  multiple_result,
      divide:    divide_result 
    }

    return calculated_result
  end

end

やっていることは一緒ですが、結果的に戻り値がcalculated_resultという1つのハッシュだけになりました。

呼び出し元を見てみましょう。

data = Sample.calculate(30, 10)

p data[:add]       #=> 40
p data[:substract] #=> 20
p data[:multiple]  #=> 300
p data[:divide]    #=> 3

戻り値が1つなので、呼び出す際にかなりスッキリしました。また、戻り値の順番を機にする必要もなくなりました。
これで多少は消臭(リファクタリング)できたかと思います。

今後随時追加していきます(週に一個のペースで追加していく予定です)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

竹内関数をメモ化で高速化する工程 (Python には負けたくない)

はじめに

Ruby で竹内関数をメモ化によって、高速にしようとあれこれやります。

竹内関数とは

これです。Ruby で書くとこんな感じです。

Ruby
def tarai(x, y, z)
  return y if x <= y
  tarai(
    tarai(x - 1, y, z),
    tarai(y - 1, z, x),
    tarai(z - 1, x, y)
  )
end

引数をたらい回しにして、再帰しまくることにより、重い処理になっています。

迂闊に、

tarai(30, 15, 0)

とかすると、後悔します。

メモ化とは

竹内関数が遅いのは、引数をたらい回しにして再帰的に関数を実行することで、引数が同じなのに、何度もなんども同じ計算をしなければならないためです。

そこで、ある引数の組合せで計算を一度してしまえば、その答えをメモしておいて、もう一度同じ引数の計算をしなければならなくなった時に、実際に計算するのでなく、メモに記録した答えを返すようにすることで、速く答えを出す、というものです。

で、何をしようというの?

Fibonacci 数を求める fibo(n) のように引数がひとつの時には、特に問題はありません。

Ruby
def make_fibo
  cache = {}
  fibo = lambda do |n|
    return n if n < 2
    cache[n] ||= fibo.call(n - 2) + fibo.call(n - 1)
  end
end

fibo = make_fibo
fibo.call(100)

すればOKです。
ここでは、計算結果を、引数をキーにした Hash オブジェクト cache にメモしています。ちなみに、cache をグローバル変数にしたくないので、lambda を使ってクロージャを作っています。

def ... end で囲まれた空間は、局所変数の独立した 名前空間 を作るので、def で作ったメソッドは外側の局所変数をキャプチャーできず、クロージャにはなりません。

その代わり、lambda 等を使ってクロージャを作る時には、def ... end の中は、局所変数に関してはクリーンルームになるので、クロージャを返すメソッドを作った上で、クロージャを作るのがオススメのはずです。多分。

話が飛んでしまいましたが、Fibonacci の場合と違い、竹内関数の場合、引数が3つあります。これをどう Hash オブジェクトに登録するかです。

Python ならどうするか

Python なら、当然引数を tuple にして、dict オブジェクトに登録するはずです。

まずは、クロージャを使って、

Python
def make_tarai:
    cache = {}
    def tarai(x, y, z):
        if x <= y:
            return y
        key = (x, y, z)
        val = cache.get(key)
        if val is not None:
            return val
        val = tarai(
            tarai(x-1, y, z),
            tarai(y-1, z, x),
            tarai(z-1, x, y)
        )
        cache[key] = val
        return val
    return tarai

tarai = make_tarai()
tarai(100, 50, 0)

あるいは、Python のデフォルト引数は関数を定義した時に一度だけ評価されるので、

Python
def tarai(x, y, z, cache={}):
    if x <= y:
        return y
    key = x, y, z
    val = cache.get(key)
    if val is not None:
        return val
    val = tarai(
        tarai(x-1, y, z, cache),
        tarai(y-1, z, x, cache),
        tarai(z-1, x, y, cache),
        cache
    )
    cache[key] = val
    return val

tarai(100, 50, 0)

でもOKでしょう。tarai 関数を何度実行しても、常に同じ dict オブジェクトが使われるので、一度計算結果をメモしてしまえば、次に tarai を実行した時にもそのメモが使えます。

手元で計算すると、tarai(100, 50, 0) で約0.03 秒程度です。

Ruby で同じようなことをすると

Rubyの場合、デフォルト引数はメソッドを実行するたびに評価されるので、Python と違い、メソッドを実行するたびに一からメモを取り直すことになってしまいます。

どちらがいいのか私にはよく分かりません。Python が変わっているような印象ですが、どちらが勝ちで、どちらが負けという話でないことは間違いなさそうです。

そういうわけで、Ruby の場所、クロージャでいきます。
Ruby には tuple がないので、配列でやるしかありません。

Ruby
def make_tarai
  cache = {}
  tarai = lambda do |x, y, z|
    return y if x <= y
    cache[[x, y, z]] ||= tarai.call(
      tarai.call(x - 1, y, z),
      tarai.call(y - 1, z, x),
      tarai.call(z - 1, x, y)
    )
  end
end

tarai = make_tarai
tarai.call(100, 50, 0)

できるにはできましたが、Python に比べると圧倒的に遅いです。手元で計算すると、0.09 秒前後かかってしまいました。完敗です。

Ruby
def time
  start = Time.now
  result = yield
  diff = Time.now - start
  puts "Elapsed time: #{diff} secs"
  p result
end

time { tarai.call(100, 50, 0) }

で時間を計算しましたが、以下略。

Python の tuple は見た目が同じなら同じオブジェクトです(少なくとも中身がintの場合)。なので高速に dict のキーを比較することができます。

それに対し、Ruby の配列は見た目が同じでも別のオブジェクトになりうるので、キーの比較に時間がかかってしまうのが大きな原因だと思います。

ここからが知恵の出しどころです。

計算結果を多重 Hash に登録する

配列をキーにするから遅いのなら、メモを多重 Hash にして、Integer である、個々のx, y, z をキーにして、計算結果をメモすることにします。

戦略的には、まず、引数 x のための cache_x = {} を用意します。
そして、cache_x[x] は引数 y のための Hash オブジェクト cache_y を返して欲しいのですが、引数 x が初見であれば nil が返ってきます。
そこで、cache_y = cache_x[x] ||= {} とすることで、x が初見の場合には、新たな Hash オブジェクトを cache_[x] に登録した上で、変数 cache_y に代入することができます。

そうすると、こんな感じになります。

Ruby
def make_tarai
  cache_x = {}
  tarai = lambda do |x, y, z|
    return y if x <= y
    cache_y = cache_x[x] ||= {}
    cache_z = cache_y[y] ||= {}
    cache_z[z] ||= tarai.call(
      tarai.call(x - 1, y, z),
      tarai.call(y - 1, z, x),
      tarai.call(z - 1, x, y)
    )
  end
end

tarai = make_tarai
tarai.call(100, 50, 0)

0.012 秒前後です。
やりました。Python に勝ちました。圧勝です。

ちなみに、Python でこの方法を使うと少し遅くなってしまいました。恐るべし、Python の tuple 。

しかし、Ruby が勝ったとはいえ、

Ruby
    cache_y = cache_x[x] ||= {}
    cache_z = cache_y[y] ||= {}
    cache_z[z] ||= tarai.call(...

の部分がまだだるいです。どうせなら cache[x][y][z] ||= tarai.call( ... としたいです。

Hash のデフォルト値を使う

Ruby の場合、

Ruby
cache = Hash.new { |hash, key| hash[key] = {} }

とすることで、未登録のキーに出くわしたとき、新たな Hash オブジェクトを作って、そのキーに登録するような Hash オブジェクトを作ることができます。
これを多重にすればいいわけです。

Ruby
def make_tarai
  cache = Hash.new do |hash, key|
    hash[key] = Hash.new do |hash, key|
      hash[key] = {}
    end
  end

  tarai = lambda do |x, y, z|
    return y if x <= y
    cache[x][y][z] = tarai.call(
      tarai.call(x - 1, y, z),
      tarai.call(y - 1, z, x),
      tarai.call(z - 1, x, y)
    )
  end
end

tarai = make_tarai
tarai.call(100, 50, 0)

これでOKです。
とはいえ、この cache の定義もだるいです。何とかスマートにしたいものです。

そこで、cache の右辺をよく見ると、ラスボス的な {}Hash.new { |hash, key| hash[key] = ... } でラップしていけば良さそうだと分かります。メタプログラミングで何とかしましょう。

メタプログラミング登場

方針としては、何重にもなった Hash.new {} の文字列を作って、eval することにします。

引数の数が3つの場合、{} を2回 Hash.new {} でラップすればいいから、メモ化する際の引数の数を arity とすると、

Ruby
def make_cache(arity)
  seed = "{}"
  (arity - 1).times do
    seed = "Hash.new { |hash, key| hash[key] = #{seed} }"
  end
  eval(seed)
end

この make_cache があれば、簡単に多重 Hash を作ることができます。

Ruby
def make_cache(arity)
  seed = "{}"
  (arity - 1).times do
    seed = "Hash.new { |hash, key| hash[key] = #{seed} }"
  end
  eval(seed)
end

def make_tarai
  cache = make_cache(3)
  tarai = lambda do |x, y, z|
    return y if x <= y
    cache[x][y][z] ||= tarai.call(
      tarai.call(x - 1, y, z),
      tarai.call(y - 1, z, x),
      tarai.call(z - 1, x, y)
    )
  end
end

tarai = make_tarai
tarai.call(100, 50, 0)

かなりいい感じになりました。でも、もっと高速を目指したいです。

いろんな事情があって、Proc オブジェクトはメソッドより遅いと聞きます。そこで、Proc オブジェクトではなく、メソッドでクロージャを作ってみます。

メソッドでクロージャを作る

すでに述べたように、def でクロージャは作れません。そこで、def を使わずにメソッドを作ることにします。

define_method の登場です。

Ruby
def make_cache(arity)
  seed = "{}"
  (arity - 1).times do
    seed = "Hash.new { |hash, key| hash[key] = #{seed} }"
  end
  eval(seed)
end

def make_tarai
  cache = make_cache(3)
  define_method(:tarai) do |x, y, z|
    return y if x <= y
    cache[x][y][z] ||= tarai(
      tarai(x - 1, y, z),
      tarai(y - 1, z, x),
      tarai(z - 1, x, y)
    )
  end
  private :tarai
end

make_tarai
tarai(100, 50, 0)

トップレベルで make_tarai を定義すると、定義式の中の selfmain になります。define_method は Module クラスのメソッドなので、一般的には module ... endclass ... end の中でしか使えないのですが、main には同名のシングルトン・メソッドが定義されていて、実行すると、Object クラスにメソッドが登録されます。

ここでは、トップクラスでメソッドを定義したときのように、private にしました。

ただ、メソッドにしてもほとんど速度は変わりません。Proc にしろ、メソッドにしろ、環境を背負っている分、遅いのでしょうか。

そこで、この辺りでクロージャに見切りをつけ、cache をインスタンス変数で持つ方法を試してみます。

メモをインスタンス変数に格納する

Ruby
def make_cache(arity)
  seed = "{}"
  (arity - 1).times do
    seed = "Hash.new { |hash, key| hash[key] = #{seed} }"
  end
  eval(seed)
end

class Tarai
  def initialize
    @cache = make_cache(3)
  end

  def call(x, y, z)
    return y if x <= y
    @cache[x][y][z] ||= call(
      call(x - 1, y, z),
      call(y - 1, z, x),
      call(z - 1, x, y)
    )
  end
end

tarai = Tarai.new
tarai.call(100, 50, 0)

ついに、0.007 秒の世界に突入しました。
ぶっちぎりの速さです。

関数型言語に惑わされてなのか、メモ化というとすぐにクロージャだと思い込んでいましたが、Purely Object-Oriented Programming Language である Ruby の場合、素直にオブジェクトを使えばよかったんですね。

まとめ

Ruby でメソッドをメモ化する時には、クロージャではなく、普通にインスタンスを作って、インスタンス変数にメモを格納する。

引数が複数あるときは、配列にして Hash オブジェクトのキーにするのではなく、引数の数だけ多重 Hash にする。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

連番のインスタンス変数をinstance_variable_getメソッドでDRYにする

DRY後

  • コントローラー側で @day_1@day_2 の2つのインスタンス変数をセットしているとします。
  • 下のコードでは _day.html.erb パーシャルを呼び出していますが、こう書くことで動的に生成したインスタンス変数名を指定できます。
schedule.html.erb
<% 2.times do |n| %>

...中略(Rubyとは直接関係のないHTML、CSSの記述など)...

  <% day_i = "@day_#{n + 1}" %>
    <%= render 'day', day: instance_variable_get(day_i) %>
<% end %>

DRY前

  • 下記のように render メソッドを並べても動きますが、中略にした部分も並べないといけませんので、やはりDRYにした方が良い感じですね。
schedule.html.erb
...中略(Rubyとは直接関係のないHTML、CSSの記述など)...

<%= render 'day', day: @day_1 %>

...中略(Rubyとは直接関係のないHTML、CSSの記述など)...

<%= render 'day', day: @day_2 %>

参考

instance_variable_get (Object) - Rubyリファレンス

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

連番のインスタンス変数をinstance_variable_getメソッドでDRYにしようとしたけど用途が違った

追記:DRY後(最終形)

@znz さんにコメントでご指摘をいただきました。ありがとうございます。
結局 instance_variable_get メソッドを使うより普通に配列でやるべきでした。
以下のように書けました。

schedule_controller.rb
# day_1,day_2 に値をセットする過程は省略
@days = [day_1,day_2]
schedule.html.erb
<% @days.each do |day| %>

...中略(Rubyとは直接関係のないHTML、CSSの記述など)...

  <%= render 'day', day: day %>
<% end %>

DRY後(一回目)

  • コントローラー側で @day_1@day_2 の2つのインスタンス変数をセットしているとします。
  • 下のコードでは _day.html.erb パーシャルを呼び出していますが、こう書くことで動的に生成したインスタンス変数名を指定できます。
schedule.html.erb
<% 2.times do |n| %>

...中略(Rubyとは直接関係のないHTML、CSSの記述など)...

  <% day_i = "@day_#{n + 1}" %>
    <%= render 'day', day: instance_variable_get(day_i) %>
<% end %>

DRY前

  • 下記のように render メソッドを並べても動きますが、中略にした部分も並べないといけませんので、やはりDRYにした方が良い感じですね。
schedule.html.erb
...中略(Rubyとは直接関係のないHTML、CSSの記述など)...

<%= render 'day', day: @day_1 %>

...中略(Rubyとは直接関係のないHTML、CSSの記述など)...

<%= render 'day', day: @day_2 %>

参考

instance_variable_get (Object) - Rubyリファレンス

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsで多対多のリレーションを作るときにはまったこと

概要

Railsで多対多モデルのリレーションを貼る際にハマったことを記載しておきます。

環境

  • Ruby 2.5.1
  • Rails 5.2.3

モデル

下記のようなモデルを作るとします。

                      +------------+
+------------+        | PostTag    |
| Post       |        +------------+        +------------+
+------------+ 1    * | id         |        | Tag        |
| id         |--------| post_id    | *    1 +------------+
| content    |        | tag_id     |--------| id         |
+------------+        +------------+        | name       |
                                            +------------+

ハマった内容

通常はPostからTag(その逆も)を参照できるように以下のようなリレーションを定義すると思います。

class Post < ApplicationRecord
  has_many :post_tags
  has_many :tags, through: :post_tags
end
class PostTag < ApplicationRecord
  belongs_to :post
  belongs_to :tag
end
class Tag < ApplicationRecord
  has_many :post_tags
  has_many :posts, through: :post_tags
end

この際、上記のような記述順ではうまくいきますが、Postモデル、Tagモデルのhas_manyの順番が逆の場合は、上記の環境ではうまくいきませんでした。

class Post < ApplicationRecord
  has_many :tags, through: :post_tags
  has_many :post_tags
end

多対多で検索すると下記のような順番で記述している例もたくさんあるのですが、Rails5になって厳しくなったんでしょうかね。
ActiveRecord::HasManyThroughOrderErrorエラーが出てしまうようです。

[1] pry(main)> Post.find(1).tags
  Post Load (0.4ms)  SELECT  `posts`.* FROM `posts` WHERE `posts`.`id` = 1 LIMIT 1
ActiveRecord::HasManyThroughOrderError: Cannot have a has_many :through association 'Post#tags' which goes through 'Post#post_tags' before the through association is defined.
from /mnt/e/Development/gunplus/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.3/lib/active_record/reflection.rb:943:in `check_validity!'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jsonapi-resources で nil を返したい

やりたいこと

Rails で jsonapi-resources を用いて REST API を構築した場合 controller-resource-model がそれぞれ密接に連携した状態になるため(まぁ json api specification 準拠だしそういう趣旨のライブラリだという前提はあるんですが)、以下のようなかたちで nil を返そうとすると Internal Server Error: undefined method 'id' for nil:NilClass が出てしまう。

    if something_wrong?
      jsonapi_render json: nil, status: 500 and return
    end

これは返却しようとしている json: データを jsonapi-resources が再構築する際、 json: データがモデルのインスタンスであることを期待されているにもかかわらず nil になってしまっていることから .id メソッドが参照できすエラーとなるかたちだ。

方法

jsonapi-resources のコントローラで nil を返したい場合は、

    if something_wrong?
      jsonapi_render_errors status: 500 and return
    end

としてやる。こうすると 500 Internal Server Error かつ nil のデータが返される。

この jsonapi_render_errors は依存ライブラリである jsonapi-utilsのヘルパメソッド となっているため、jsonapi-resources の API ドキュメントには掲載されていない。

ここで挙げた例の他に、例えば Bad Request の返送なども jsonapi-utils で実装されているので、必要な場合は参考にされたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby 配列とブロックについて〜チェリー本4章〜

Ruby 配列とブロックについて〜チェリー本4章〜

※この記事は初心者の私がチェリー本で学習するにあたって新しく知ったことや考えたことアウトプットするための場として書いています。
この記事は以下の情報を参考にして記述し、一部引用しています。
プロを目指す人のためのRuby入門

ブロックに使うメソッド

delete_if

irb(main):003:0> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb(main):004:0> a.delete_if do |n|
irb(main):005:1* n.odd?
irb(main):006:1> end
=> [2, 4]

delete_ifメソッドはブロック内で指定した条件で配列の数字や文字等を削除するメソッドです。

irb(main):007:0> numbers = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb(main):008:0> sum = 1
=> 1

irb(main):009:0> numbers.each do |n|
irb(main):010:1* sum_value = n.even? ? n * 10 : n 
irb(main):011:1> sum += sum_value
irb(main):012:1> end

irb(main):013:0> sum 
=> 70

このようにブロック内は複数行に渡って条件を書くことやブロック外からの参照も可能(ただしブロック内から外への参照は不可)。
sum_value = n.even? ? n * 10 : nあとブロックとは関係無いですが、最初この文の2つ目に出てくる?が何を意味してるものなのか意味がわからなかったですが、1つ目の?がeven?で一つのメソッドになっていて、2つ目の?は条件演算子の?です。

map

irb(main):035:0> numbers = [1,2,3,4]
=> [1, 2, 3, 4]
irb(main):036:0> new_numbers = numbers.map {|n| n * 10}
=> [10, 20, 30, 40]

他の配列を利用してブロック処理をしたもの新しい配列に作るメソッドです。

select / reject

even_numbers = numbers.select{|n| n.even?}
=> [2, 4]

irb(main):038:0> odd_numbers = numbers.reject{|n| n.odd?}
=> [2, 4]

selectはブロックの戻り値の中で条件式が一致したものだけを取り出すメソッドになります。
rejectは逆でブロックの戻り値の中で条件式が一致したものだけを除外するメソッドになります。

find

irb(main):040:0> find_numbers = numbers.find{|n| n.even?}
=> 2

ブロックの戻り値が最初に真になった最初の要素を返します。

each_with_index

drink = %w(coffee tee water cola)
=> ["coffee", "tee", "water", "cola"]

drink.each.with_index{|d, i| puts "#{i}:#{d}" }
0:coffee
1:tee
2:water
3:cola

drink.each.with_index(1){|d, i| puts "#{i}:#{d}"}
1:coffee
2:tee
3:water
4:cola

each文で処理する結果に.with_indexでインデックスの数字をつけてます。
インデックスの数字は指定しないと0から始まるので引数に1を指定してます。
簡単に書くとブロック処理の結果にもう一つ何か処理を一緒に加えたい時に使えるメソッドです。
ちなみにeach以外にもmapやdelete_ifと組み合わせてmap_with_indexやdelete_with_index,その他のメソッドと組み合わせて使うことができます。


リファクタリングに使える配列の書き方

do...endの代わりに{}を使う

numbers = [1,2,3,4]
numbers.each do |n|
sum += n
end
sum
=> 10

numbers.each {|n| sum += n}
sum
=> 10

ブロック内の条件式が1行で終わる場合はdo...endの代わりに{}を使って1行で終わらせる方が見やすいです。

(&:メソッド名)でもっと簡潔に書く

[1,2,3,4,5,].select{|n| n.even?}
=> [2, 4]
[1,2,3,4,5,].select(&:even?)
=> [2, 4]

1.ブロック引数が1つで
2.ブロックの中で呼び出すメソッドには引数が無く
3.ブロックの中ではメソッドを一回呼び出す以外の処理が無い
場合にのみこの方法が使えます!


あとがき

この記事でなんとか2日目も書くこともができた!
けど4時間もかかってしまった、主に記事構成を考えるのと記事のトピック毎にどのように書こうかというところに時間がかかってしまっているのでどうしようか...
このままだと時間の無い平日とかに続けるのが厳しそうなのでなんとか解決策を見つけます!
2日目でも既にただ本を読んでただけの頃と比べるとちゃんと自分の頭で考えて消化出来てると感じるので明日も書きます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【10日間でポートフォリオ作成に挑戦】5日目:CKEditorへ画像アップロード機能を追加

概要

今回は、2019年のGW期間(10日間)を全て費やして取り組むポートフォリオの製作過程
取りまとめた内容を投稿させて頂きます。(投稿は毎日行う予定)

全体通した取り組みの詳細については、前回までの記事をご参照ください。

【10日間でポートフォリオ作成に挑戦】1日目:要件定義〜記事投稿のCRUD
【10日間でポートフォリオ作成に挑戦】2日目:アクセス制限〜コメントのCRUD機能
【10日間でポートフォリオ作成に挑戦】3日目:ページネーション~CKEditorの導入
【10日間でポートフォリオ作成に挑戦】4日目:テーブル分割〜CKEditorのフォームへの反映

今日一日の作業内容

ここからは、今日1日で取り組んだ作業内容をご説明します。

CKEditorへ画像アップロード機能を追加

先日実装したCKEditorで、画像を利用した編集が出来る様に
画像アップロード機能を実装しました。

画像アップロードについては
shrineをベースに下記のgemを導入しています。

gem 'shrine'
gem 'image_processing'
gem 'mini_magick'

最終的に出来上がったものが、こちらです。

4ef17f9dada9d93de20e28ccb9084739.gif

'shrine'は初めて利用したという事もあり、かなり苦労しました。
その辺りのエピソードは、「今日の失敗」にてご紹介します。

テストコードの記述

テストについては、
単体テスト(モデル・コントローラー)と統合テストの
両方を書いて行きます。

テストには、Rspecを利用しています。

$ rails g rspec:install

まだ全てのテストを描ききれていませんが、
参考にPostモデルのテストコードを掲載しておきます。

spec/models/post_spec.rb
require 'rails_helper'

RSpec.describe Post, type: :model do
  describe '#create' do
    let!(:user) { create(:user) }
    context 'can save' do
      it 'is valid with title' do
        expect(build(:post, user_id: 1)).to be_valid
      end
    end

    context 'can not save' do
      it 'is invalid without title' do
        messages = build(:post, title: nil, user_id: 1)
        messages.valid?
        expect(messages.errors[:title]).to include('を入力してください')
      end

      it 'is invaid without user_id' do
        messages = build(:post, user_id: nil)
        messages.valid?
        expect(messages.errors[:user]).to include('を入力してください')
      end
    end
  end
end

まだまだ触り程度ですが、
やっぱりテストコードはいいですね!!
ゲームみたいで書いていて楽しいです!!!!

今日の失敗

ここからは今日の失敗をまとめて行きます。

CKEditorへ画像アップロード機能追加で手間取る

401エラー

一通りコードを書いて、実際にアップロードを試そうとすると、
下記の様なエラーが発生しました。

7323822fe54e1dc21fed059205e4c969.gif

ターミナルを見てみると、下記の様な処理結果が出ていました。

Started POST "/images?image_relation=DescriptionImages&csrf_token=" for ::1 at 2019-05-01 13:25:52 +0900
Processing by ImagesController#create as */*
  Parameters: {"upload"=>#<ActionDispatch::Http::UploadedFile:0x00007fbcffb81848 @tempfile=#<Tempfile:/var/folders/m0/h9_nmc1n5lg1ghdq5s4_4jc40000gn/T/RackMultipart20190501-49423-16e02tc.png>, @original_filename="pop_reiwa_hatsu.png", @content_type="image/png", @headers="Content-Disposition: form-data; name=\"upload\"; filename=\"pop_reiwa_hatsu.png\"\r\nContent-Type: image/png\r\n">, "ckCsrfToken"=>"xJ5fKnWr0dw2IepjwvbFBWvHbJ7o3TqH9qpYKyK7", "image_relation"=>"DescriptionImages", "csrf_token"=>""}
Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms)

401エラーは認証に失敗している事を表しますが、
慌ててページを更新すると、ログイン状態が解除されていました。

そういえば、初期データを投入し直した(bin/setup)ので、
その時にログインが解除されていたのですが、ページを更新していなかったので、
それに気づかなかったという間抜けなエラーです・・・

image_processingの導入漏れ

次にログインしてアップロードを試しましたが、同じ動きになりました。
再度ターミナルを見ると、下記の様なエラーでした。

Completed 500 Internal Server Error in 16ms (ActiveRecord: 0.4ms)

LoadError (cannot load such file -- image_processing/mini_magick):

app/uploaders/image_uploader.rb:1:in `<top (required)>'
app/models/image.rb:2:in `<class:Image>'
app/models/image.rb:1:in `<top (required)>'
app/controllers/images_controller.rb:6:in `create'

また、gemの導入漏れです・・・何回やったら気が済むのでしょう・・・

imageableの追加漏れ

次に発生したエラーは下記の内容です。

Screen Shot 2019-05-01 at 14.37.05.png

ちなみにエラーが発生した箇所は、
下記のコントローラーのimage.saveです。

controllers/images_controller.rb
def create
  image = Image.new(
      image: params[:upload],
      image_relation: params[:image_relation]
  )
  if image.save
    render json: {
        url: image.image[:standard].url,
        uploaded: true
    }
  else
    render json: {
        error: {
            message: image.errors.full_messages
        },
        uploaded: false
    }
  end
end

今回、Imageテーブルは、Postの画像以外にも、Userのアイコンなどでも
利用する可能性がある事から、ポリモーフィック関連付けを使用しています。

なので、生成したImageインスタンスには、
imageable_typeimageable_idのデータを持たせる必要があるのですが、
pryで生成されたインスタンスの中身を確認してみるとnilになっていました。

Screen Shot 2019-05-01 at 15.09.22.png

正直、このポリモーフィック関連付けの仕組みを理解しておらず、

本来はPostDescriptionの情報を持たせるべきなのでしょうが
新しく記事を投稿する際など、idが確定していない状態で、
どの様に紐づければ良いのか、判断が付きませんでした・・・

なので、一旦current_userの情報を持たせることにしました。

controllers/images_controller.rb
def create
  image = current_user.images.new(
      image: params[:upload],
      image_relation: params[:image_relation]
  )
  if image.save
(中略)

とりあえずUserのアイコンは、現時点で実装の予定は無いので、
一先ずはこれで動作する状態に持って行きたいと考えています。

Turbolinksの干渉

下記の通り、新しく編集ページを開くと、CKEditorが表示されず、
再読み込みを行うと表示されるというバグが発生しました。

cdf35c945cbbd1eae22f052a9444db2e.gif

コンソールのエラーを確認すると、どうやらTurbolinksが干渉している様でした。

Screen Shot 2019-05-01 at 17.26.56.png

なので、一旦Turbolinksは外しています。

明日の予定

  • 現時点で実装済みの機能のテストコードを全て記述
  • 検索機能(ransack)の実装
  • いいね機能実装の下準備

ransackは使った事が無いのですが、
またそれで予定が狂いそうな予感がしています・・・
見積もりスキルをきちんと伸ばしたいですね。

※追記:六日目を投稿しました
【10日間でポートフォリオ作成に挑戦】6日目:テストコードの実装

おまけ

最後になりますが、現在、私は下記の目標を立てて学習に取り組んでいます。

  • 3年間で「10,000時間」をプログラミングに費やす
  • その間、毎日ブログの投稿を行う

Twitterでは、その過程で学んだ事などを発信しています。
もし宜しければフォローしてみてください。

Twitter:@ryoutaku_jo
ブログ:りょうたくのWEBエンジニア日記

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【競技プログラミング】偶数・奇数の配列を取得する方法| Ruby |+α Pyhton

はじめに

  • 以前AtCoder(ABC124)で偶数を含む配列・奇数を含む配列を使用したのでまとめました
  • 言語はRubyを紹介していますが、競技プログラミングではPythonの使用者も多いと思うのでPythonもおまけ程度にまとめてます

Ruby ver1_1 #group_by

  • group_byでまとめた場合、Hashとして返ってきます
  • ここで配列のようにしていますが、keyが1や0で値を取得しています
ary = [1,2,3,4,5,6]
odd  = ary.group_by{|i| i%2}[1] #Keyが1というだけ
#=> [1, 3, 5]
even = ary.group_by{|i| i%2}[0] #Keyが0というだけ
#=> [2, 4, 6]

Ruby ver1_2 #group_by

  • 先ほどのHashを#valuesメソッドで値を取得し、配列を取得します
ary = [1,2,3,4,5,6]
odd  = ary.group_by{|i| i%2}.values[0] #配列の1番目
#=> [1, 3, 5]
even = ary.group_by{|i| i%2}.values[1] #配列の2番目
#=> [2, 4, 6]

Ruby ver2 #select #odd|#even

  • 次に目的とする配列を取得する#selectメソッドを使用し、その後#odd|#evenメソッドを使用します
ary = [1,2,3,4,5,6]
odd  = ary.select(&:odd?)
#=> [1, 3, 5]
even = ary.select(&:even?)
#=> [2, 4, 6]

Ruby ver3 #select #with_index

  • 次に目的とする配列を取得する#selectメソッドを使用し、その後#with_indexメソッドを使用します
ary = [1,2,3,4,5,6]
odd = ary.select.with_index{|e, i| i % 2 == 0}
#=> [1, 3, 5]
even = ary.select.with_index{|e, i| i % 2 == 1}
#=> [2, 4, 6]

Ruby ver4 #each_slice

  • 次に#each_sliceメソッドを使用します
  • 以前にQiitaでも紹介があった記事を参考にしています
  • 注意ですが、each_sliceは配列の個数が偶数の場合に成り立ちますが、奇数個の場合#lastメソッドを使用した際に、偶数の配列に奇数が含まれてしまうので競技プログラミングでは使用しないようにして下さい
#配列の個数が偶数のとき
ary = [1,2,3,4,5,6]
odd = ary.each_slice(2).map(&:first)
#=> [1, 3, 5]
even = ary.each_slice(2).map(&:last)
#=> [2, 4, 6]
#配列の個数が奇数のとき
ary = [1,2,3,4,5,6]
odd = ary.each_slice(2).map(&:first)
#=> [1, 3, 5, 7]
even = ary.each_slice(2).map(&:last) #偶数配列に奇数が含まれてしまう
#=> [2, 4, 6, 7]

Python 内包表記

  • おまけです
ary = [1,2,3,4,5,6]
odd  = [i for i in ary if i%2 == 1]
#=> [1, 3, 5]
even = [i for i in ary if i%2 == 0]
#=> [2, 4, 6]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む