20190226のRubyに関する記事は14件です。

Ruby  irb、テキストエディタ使用とエラー解決方法

動作環境はMacとなります。
主に自分の勉強用メモとして残しています。

Rubyプログラム irb使用

文字列はシングルクオートかダブルクオートで囲む

irb(main):001:0> "Hello, World"
=> "Hello, World"#Rubyは最後の式を自動的に出力するので、プログラムとは言えない。

putsは何かを出力する命令で、これをつけることでプログラムと言える。

irb(main):004:0> puts 'Hello World!'#これがプログラム
Hello World!
=> nil#putsは必ずnilを返す

数値の計算

irb(main):005:0> 1 + 1
=> 2
irb(main):006:0> 10 - 1
=> 9

irbで記述したプログラムをクリアしたい時

clear か control+lキー

irbを終了したい場合

exit か contol+dキー

テキストエディタ使用

この環境ではAtom使用しています。
$はターミナルでの操作を表します。

ファイル作成

$mkdir ruby_projects #mkdirはmake directoryの略でファイル作成の意味
$ls #今いるディレクトリの中身を確認
$cd ruby_projects/ #今いるディレクトリから移動
$touch hello.rb #ファイル作成
$atom . #Atomを開く時のコマンド

rubyプログラムを作成する場合、拡張子は.rbとする。

テキストエディタ

hello.rb
puts 'Hello, World!'#行末に;をつけてもエラーにはならないが、rubyは改行で認識されるため通常書かない
puts 10 + 3

ターミナル

$ ruby hello.rb #ruby + ファイル名で出力
Hello, World!
13

Linuxコマンド

現在のディレクトリをカレントディレクトリという。
ディレクトリとファイルはざっくり同じ。

$ pwd #現在いるファイルの階層を表示
$ cd .. #現在いるフォルダの1つ上の階層に移動
$ ls #現在いるフォルダの中身を表示

エラーを自力で取り除く方法

hello.rb
puts 'Hello, World!'
put 10 + 3 #putsをputと間違えた

ターミナル

$ ruby hello.rb
Hello, World! #問題なく出力される。
hello.rb:2:in `<main>': undefined method `put' for main:Object (NoMethodError)
#hello.rbファイルの二行目のputというメソッドがないよというエラー
Did you mean?  puts
               putc
#もしかしてこういう意味?とエラーが教えてくれている。

メソッドとは処理のまとまりのこと。
エラーメッセージをグーグル検索すると、解決の意図が見つかることが多い。
特にrubyは日本で広く普及されているので、情報が見つかりやすい。

以上を踏まえて修正

hello.rb
puts 'Hello, World!'
puts 10 + 3 #putをputsへ修正

ターミナル

$ ruby hello.rb
Hello, World!
13

正常にプログラムが実行された!

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

サーチアルゴリズムとソートアルゴリズムをRubyでやってみた。

データ構造とアルゴリズムをなぜ学ぶべきか

あるデータを探索して取得したいとか、この大量のデータをソートしたいとかなった時に、既存の関数を使うのも良いけど、データの構造やその都度のデータ量を考慮して、最適なアルゴリズムを取捨選択できるようになっていた方がいいよねといった形のフィードバックを、最近就活時の面接時に頂いて、なるほどね〜と思いつつ、やってみた所存。まだまだ、学んでいる途中ですので、今後追記していきます。

サーチアルゴリズム

いわば探索アルゴリズムですよね。配列中から、指定した値があるかどうか探索するアルゴリズムです。

リニアサーチ

一番シンプルなアルゴリズムで、配列中から指定した値を先頭から順に一致するかどうか検証して、一致したもののインデックスを返す仕様です。

def linear_search(searchArr,searchNum)
  index = 0
  count = 0
  searchArr.each do |arr|
    if arr == searchNum
      count += 1
    else
      index += 1
    end
  end
  if count == 0
    puts "value:#{searchNum} Array:#{searchArr} There isn't it."
  else
    puts "value:#{searchNum} Array:#{searchArr} There is it at index\s#{index}."
  end
end

メリット

下記の通り、先頭から順に要素を探索するだけなので、実装が容易である。

デメリット

先頭から1個ずつ探索する故に、かなり速度が落ちる。

バイナリサーチ

中央の要素を取得して、探索したい値がそれより大きいか小さいかを比較して、検証する要素を右や左に移しつつ、探索するアルゴリズムです。少し複雑ですので、手順を紹介します。

手順

1.配列をソートする。(昇順でも降順でも可能)

2.配列の中央にある要素を目的の要素と比較する。

3.中央の要素が、目的の要素より大きければ、中央より後半を探索。

4.そうではない場合は、前半を探索する。

def binary_search(array,searchNum)
  array.sort!
  leftIndex = 0
  rightIndex = array.index(array.last)
  midIndex = (rightIndex + leftIndex) / 2
  loop do
    if searchNum == array[midIndex]
      puts "Array->#{array}:There is it at #{midIndex}!"
      break;
    elsif searchNum > array[midIndex]
      midIndex += 1
    else
      midIndex -= 1
    end
  end
end

メリット

リニアサーチは、先頭の要素から探索するのに対して、バイナリサーチは、ソート→中央の値を比較するという手順で、より速度が上がる。

デメリット

ソートする手順を踏む必要があるので、要素が「数値」である必要がある。

速度を比較してみた

benchmarkというライブラリを使って比較してみます。

require 'benchmark'
def linear_search(array_size,searchNum)
  array = (1..array_size).to_a
  index = 0
  count = 0
  array.each do |arr|
    if arr == searchNum
      count += 1
    else
      index += 1
    end
  end
  if count == 0
    # puts "value:#{searchNum} Array:#{array} There isn't it."
  else
    # puts "value:#{searchNum} Array:#{array} There is it at index\s#{index}."
  end
end

def binary_search(array_size,searchNum)
  array = (1..array_size).to_a
  array.sort!
  leftIndex = 0
  rightIndex = array.index(array.last)
  midIndex = (rightIndex + leftIndex) / 2
  loop do
    if searchNum == array[midIndex]
      # puts "Array->#{array}:There is at #{midIndex}!"
      break;
    elsif searchNum > array[midIndex]
      midIndex += 1
    else
      midIndex -= 1
    end
  end
end

p "---100 elements---"
puts Benchmark.measure{linear_search(100,50)}
puts Benchmark.measure{binary_search(100,50)}
p "---1000 elements---"
puts Benchmark.measure{linear_search(1000,100)}
puts Benchmark.measure{binary_search(1000,100)}
p "---10000 elements---"
puts Benchmark.measure{linear_search(10000,1000)}
puts Benchmark.measure{binary_search(10000,1000)}

結果

要素が増えるほど、速度差が出ている事がわかります。
スクリーンショット 2019-02-26 20.20.53.png

ソートアルゴリズム

要素である数値を昇順、降順に並べるアルゴリズムです。

バブルソート

バブルソートは、そのソートの動きから由来しているそうです。やってみます。

手順

1.先頭の要素を1,その次の要素を2とします。まず、1と2を比較します。1の方が2より小さければ交換せず、そのまま、2の要素を1として、2の要素の次の要素を2として、同じように比較します。(つまり、比較する要素同士を右に移行します。)

2.隣り合う要素を比較して、1より2の要素の方が大きければ、要素を交換してあげます。そして上記のように、比較する要素同士を右に移行して同じように比較します。

3.1,2を繰り返します。

def bubble_sort(array)
  arrayLength = array.length
  for length in 1..arrayLength
    for nextIndex in 1..(arrayLength - length)
      if array[nextIndex-1] > array[nextIndex]
        t = array[nextIndex-1]
        array[nextIndex-1] = array[nextIndex]
        array[nextIndex] = t
      end
    end
  end
  p array
end

メリット

実装がかなり容易でかつ、わかりやすい。

デメリット

要素同士をスワップしながら、ソートしていくので、大量のデータをソートするので速度は落ちる。

選択ソート

ループ中に発見した最小値を予め変数に記憶しておくやり方で、ループ回数を抑えられるソートです。

手順

1.先頭の要素を仮の最小値として指定する。

2.次の要素から、1個ずつ比較していき、仮の最小値より小さい値があるとき、そのインデックスを記憶しておく。

3.さらに次の要素→次の要素と比較して、更に小さい値があれば、先ほどインデックスを記憶しておいた変数を更新する。更新した変数が最小値だった場合、先頭の要素とスワップする。

4.0番目の要素の仮最小値としての1,2,3の検証が終了したら、1番目の要素にずらして、1,2,3を繰り返す。

def selection_sort(array)
  len = array.length
  0.upto(len-2).each do |i| #検証回数
    minIndex = i #仮最小値設定
    (i+1).upto(len-1).each do |j| #検証要素範囲
      minIndex = j if array[j] < array[minIndex] #最小値を随時記憶→更新
    end
    if i != minIndex
      temp = array[minIndex]
      array[minIndex] = array[i]
      array[i] = temp
      p array
      #現在の仮最小値でない場合SWAP
    end
  end
  return array
end

メリット

バブルソートに比べて交換する手順が1回だけで抑えられるので、若干バブルソートに比べて速度が上がります。

速度を比較してみた

require 'benchmark'
def bubble_sort(array)
  arrayLength = array.length
  for length in 1..arrayLength
    for nextIndex in 1..(arrayLength - length)
      if array[nextIndex-1] > array[nextIndex]
        t = array[nextIndex-1]
        array[nextIndex-1] = array[nextIndex]
        array[nextIndex] = t
      end
    end
  end
  return array
end

def selection_sort(array)
  len = array.length
  0.upto(len-2).each do |i|
    minIndex = i
    (i+1).upto(len-1).each do |j|
      minIndex = j if array[j] < array[minIndex]
    end
    if i != minIndex
      temp = array[minIndex]
      array[minIndex] = array[i]
      array[i] = temp
    end
  end
  return array
end

p "---100 elements---"
puts Benchmark.measure{bubble_sort((1..100).to_a.shuffle)}
puts Benchmark.measure{selection_sort((1..100).to_a.shuffle)}
p "---1000 elements---"
puts Benchmark.measure{bubble_sort((1..1000).to_a.shuffle)}
puts Benchmark.measure{selection_sort((1..1000).to_a.shuffle)}
p "---10000 elements---"
puts Benchmark.measure{bubble_sort((1..10000).to_a.shuffle)}
puts Benchmark.measure{selection_sort((1..10000).to_a.shuffle)}

結果

要素が増えるほど、速度差が出ている事がわかります。
スクリーンショット 2019-02-26 20.49.07.png

参考記事

下記のリンクのサイトでは、動きでアルゴリズムがわかるようになっていて非常に理解しやすいです。是非使ってみてください。
https://visualgo.net/ja

Qiita参考
https://qiita.com/fukumone/items/5a0c0964f2c713b0699d

1週間で学ぶデータ構造とアルゴリズム
http://sevendays-study.com/algorithm/index.html

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

Ruby on RailsでRoute Error

Route Errorがでた!

簡単なTweeterみたいなやつ作ってた時の話

スクールで初のエラーっぽいエラーに出会った。

スクリーンショット 2019-02-26 20.09.54.png

原因

view側のForm_tagにてルートにリクエストを送っていたが、ルート側でgetとしてしまった。
スクリーンショット 2019-02-26 20.25.38.png

解決

get → postに変更して解決

エラーに出会い思ったこと

スクールのカリキュラムでエラーに出会わず来ていたので
人生初エラーで少しテンション上がりました。
1回エラー出ると、そこの部分は凄い気を付けて見るようになるから
これからもどんどんエラーに出会っていきたい...

sublimeTextで思ったこと

.html.erbのファイルからSCSSのクラスの定義に飛べるアドオンないかな
.htmlからcssファイルには飛べるのに、上記は飛べないの少し不便

以上!

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

Daruを使う

この記事ではRubyのデータフレームであるDaruを紹介します。

Daruとは

Pythonにおけるpandas。Daruはインドのお酒の意味だそうです。

  image.png   image.png

Daruの特徴

columnをvectorと呼ぶところ。
image.png
青い四角で強調されている部分がVectorです。

関連プロジェクト

  • Daru::View
    • Google Charts(GoogleVisualr), Highcharts(lazy_high_charts) による可視化ライブラリ。
  • Daru-IO
    • 標準でサポートされていないデータ形式等の読み込み。完成度はそんなに高くないかも。
  • Nyaplot
    • Daru標準のプロッティングライブラリ。綺麗だが古いのでおすすめしにくい。

クラス

  • Daru::DataFrame
  • Daru::Vector
  • Daru::Index
  • Daru::DateTimeIndex

それから

  • Daru::Core::GroupBy
  • Daru::Core::MergeFrame

インストール

gem install specific_install
gem specific_install https://github.com/SciRuby/daru
require 'daru'

Jupyter Notebook + IRuby環境が推奨される。

データを読み込む

CSVファイル

Daru::DataFrame.from_csv "filepath"

インターネット上のCSVファイル

require 'open-uri'
url = 'https://raw.githubusercontent.com/pandas-dev/pandas/master/pandas/tests/data/iris.csv'
df = Daru::DataFrame.from_csv(url)

一般的なもの

Daru::DataFrame.new hogehoge
Daru::DataFrame.rows 配列
# 数値がStringになる場合あり、何らかの改善の余地

RのデータセットでDaruをためすとき。

require 'rdatasets'
df = RDatasets.load :datasets, :iris

そのほか
from_activerecord from_excel from_html from_plaintext from_sql など
それでも困る場合はあまり期待せずにDaru-ioを見ると色々ある。

データの状態を確認する

先頭の5行を表示する

df.head 5

最後尾の5行を表示する

df.tail 5

行数・列数を確認する

df.shape
df.ncols # 列数
df.nrows # 行数
df.size  # 行数

列名の取得

df.vectors
#<Daru::Index(5): {SepalLength, SepalWidth, PetalLength, PetalWidth, Name}>

返却されるのはIndexクラスのオブジェクト。Vectorや配列ではない。

データを選択する

Ⅲ 列 Ⅲ

[] でVectorの名前を指定

df['vector1', 'vector2']
df.vector1

at 列数で指定

df.at 0, 1

[列数] は返却されたデータフレームのベクトルの名前も数になる

df[0, 1]

≡ 行 ≡

row[] でindexを指定

# 個別
df.row[100] # df.row 100 はエラー df.row [100] もエラー
# 範囲
df.row[100..105]
# 複数
df.row[100,101,102]
# indexは数値とは限らない
df.row[:a]

row_at で行数を指定

df.row_at 100
df.row_at 100, 101
df.row_at 100..105

df.row[100] 等として、取り出すことは可能だが、取り出したベクトルの名前も100になる。

行列同時指定

df['col1'][3]

where で抽出

df.where(df.col1.eq "hoge")
df.where(df.col1 > 10 )
df.where(df.col1 > 10 | df.col2 < 10)
df.where((df.col1 > 10 | df.col2 < 10 ) & df.col3.eq 10)

# and or
df.where(((df.col1 > 10).or df.col2 < 10).and df.col3.eq 10)

gt lt eq & | and or など
データフレーム全体を真偽値でマスクする方法はないか

filter で抽出

df2 = df.filter do |vector|
  vector.type == :numeric and vector.median < 50
end

df2 = df.filter(:row) do |row|
  row[:a] + row[:d] < 100
end

keep_row_if / keep_vector_if

破壊的メソッド

df.keep_row_if do |row|
  row[:a] > 5
end
# return [index, index, ...]

要素のカウント

vector.value_counts
df.Species.value_counts

Daru::Vector(3)
setosa 50
versicolor 50
virginica 50

vector.uniq

欠損値を扱う

df.has_missing_data?
df.include_values? nil
vector.count_values nil
df.filter_rows{|r| r.include? nil, Float::NAN}
df.dup_only_valid
df.clone_only_valid # 'shallow' copy
df.collect{|v| v.count_values nil}

補間

現状ではできないと思われる。

df.replace_values nil, Float::NAN
df.rolling_fillna
df.rolling_rillna!

each map collect reduce inject all? any?

  • each mapaxis:(:row :vector :column)を指定する。all? any? も同様
  • each_vector, each_row, map_vectors, map_rows
  • each_vector_with_index, each_row_with_index, map_vectors_with_index, map_rows_with_index
  • collect の挙動は map と違い、vectorになる。
  • map は配列を返却する。
  • map! は破壊的でdataframeになる
  • recode は破壊的ではないが、dfを返却する
  • inject はない。
  • collect_matrix

clone / dup

名前の違うメソッドは、それぞれ微妙に挙動が違う傾向がある。mapは配列を返し、collectはVectorを返し、map!はdfを返し破壊的、recodeもdataframeを返すが破壊的ではないという関係性はわかりにくい。ここに書いてあることも間違っているかもしれない。現状まだ把握しきれていない。

apply_method

引数はsymbol。procも引数に取る。

aggregate

df.aggregate(num_100_times: ->(df) { (df.num*100).first })
df.aggregate(num: :mean)

インデックス

列名の変更

rename_vectors

df.rename_vectors :a => :alpha, :b => :beta

単なるrenameはデータフレームの名称変更を意味するので要注意。

df.name # データフレームの名前

列の並び替え

df.order = ["col2", "col1"]
df.order = Daru::Index.new["col2", "col1"]

行の並び替え

df.reindex Daru::Index.new([1,3,2,4])

特定の列をindexにする

df.set_index "vector"

ソートする

df.sort ["col1"]
df.sort ["col1", "col2"]
df.sort ["col1"], assending: false
df.sort! ["col1"]

引数は配列をとるので注意する

df.sort [:b], by: {b: lambda { |a| a.length } }, handle_nils: true

マージする

Daruでは join というメソッドを使う。
how:inner :outer :right :left などを指定。

new_df = df.join(df2, how: :inner, on: [:name])

一方で merge という名前のメソッドもある。

時系列インデックス

DateTimeIndex

from_csv => datetime => set_index

vecotor['2014']

month などを利用して df["month"] = df.index.month
と行を追加して、`group_by("month") などとすれば月別の集計もできるかも。(現状は既知のバグあり)

統計量

df.max
df.min
df.mean
df.median
df.std
df.variance
df.rolling_XXX #XXXにはmin, max, meanなどが入る
df.standardize
# etc

表示が見にくいときはroundが使える。

df.group_by("Species").mean.round(3)

describe

df.describe

summary はterminal出力用

group_by

gb = df.group_by [:col1, :col2]
gb.mean
gb.get_group :col1

gbは Daru::Core::GroupBy クラスのオブジェクト。

pivot

df.pivot_table(index: [:a], vectors: [:b], agg: :sum, values: :e)

行や列の追加

最後の列に追加するとき

df["new_name"] = Vector.new

add_vector add_row などで列数・行数を指定できる。
列の追加は

only_numerics

type には objectnumeric の2種類がある。
only_numerics は便利で使用頻度が高い

df.only_numerics # 数値だけのデータフレーム

カテゴリー

split_by_category

可視化ライブラリ

Nyaplotを使いたい場合は erector もインストールする。
Nyaplotはかっこいいのだが、メンテナンスが事実上停止しており、今後に不安がある。
現状では実用性で Daru::View が頭一つ抜ける。Google Chartsは使いやすい。

Daru::View.plotting_library = :googlecharts
Daru::View::Plot.new(df,
  title: "TITLE",
  type: :line,
  vAxis: {title: "y"},
  hAxis: {title: "x"},
  width: 600,
  height: 500).show_in_iruby

しかし、Rubyの可視化ライブラリは色々あるので、今後状況が変わるかもしれない。

書き出す

write_csv

おわりに

Daruの歩き方

困ったら yard server -g でドキュメントを読みます。
IRuby上で ri でドキュメントを見たり、 show-source や でソースコードに当たってみます。

show-source Daru::DataFrame#from_csv

とすれば、該当部分のソースがJupyterに表示される。
ソースコードは平易で、詳しくない人でもまあまあ読める。怪しいと思ったら、ソースコード全体をセルにコピペして、オープンクラスの仕組みを使ってデバッグ。バグを踏んだらぜひissueに投稿しよう。

ri Daru::DataFrame.some_nice_method

Daruからの声

下記のポエムを頭の片隅におくと、Daruのメソッドの名付け方が理解しやすいかもしれない。

…きこえますか…きこえますか…ルビイストよ…Daruです。いいですか、データは大事です。データとは、単なる数字の列ではありません。それは誰かが膨大な時間をかけて収集した貴重な資料です。たとえば…パン10個、ゾウ10頭、10光年、10年、Version10…。いずれも同じ数字の 10 ですが意味はだいぶ違うでしょ? 実はデータには、数字以上の情報が含まれているのですよ!君は、その数字の列が本当は何を意味しているのか理解しなければなりません…。

…ベクトルをいくつか集めたのがデータフレームです。ベクトルの「名前」には、データを記述した人間が観測した目的や意図が、名前という形で刻印されています。データフレームとは、単なる行列計算の道具ではないのです。それは、数字に付与された人間の残存思念を、ベクトルの名称という形でサポートするツールなのです。だから、ベクトルは番号ではなく、なるべく「名前」で呼んであげなければなりません。Daruの世界ではベクトルはRow(行)などよりずっと偉いのです!

(※Daruの公式見解ではなく筆者がDaruをみて感じた感想です)

QiitaにおけるDaruの記事

参考資料(pandas)

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

React Nativeアプリをmacのjenkinsでビルドしたらxcprettyでエラー

iMacをjenkinsのスレーブとしている環境でReact Nativeアプリのビルドを使用としたところ、以下のエラーが出力されジョブがフリーズしてしまいました。

[14:11:54]: ▸ /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/lib/xcpretty/parser.rb:429:in `===': invalid byte sequence in US-ASCII (ArgumentError)
[14:11:54]: ▸ from /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/lib/xcpretty/parser.rb:429:in `update_test_state'
[14:11:54]: ▸ from /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/lib/xcpretty/parser.rb:304:in `parse'
[14:11:54]: ▸ from /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/lib/xcpretty/formatters/formatter.rb:87:in `pretty_format'
[14:11:54]: ▸ from /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/lib/xcpretty/printer.rb:19:in `pretty_print'
[14:11:54]: ▸ from /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/bin/xcpretty:84:in `block in <top (required)>'
[14:11:54]: ▸ from /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/bin/xcpretty:83:in `each_line'
[14:11:54]: ▸ from /usr/local/lib/ruby/gems/2.4.0/gems/xcpretty-0.2.8/bin/xcpretty:83:in `<top (required)>'
[14:11:54]: ▸ from /usr/local/bin/xcpretty:22:in `load'
[14:11:54]: ▸ from /usr/local/bin/xcpretty:22:in `<main>'

解決方法はjenkinsの環境変数のインジェクトで RUBYOPT=-EUTF-8 を設定しておくこと。
なんかrubyのこの問題よくハマる。

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

Sidekiqで処理待ち、処理中のjobを取得する

ActiveJobでqueue adaptorにSidekiqを使ってて、ジョブの状況を確認したいので調査しました :bulb:

環境

sidekiq (5.1.1)

コード

# 処理中のjob
workers = Sidekiq::Workers.new
workers.each do |_process_id, _thread_id, work|
  p work
end

# 処理待ちのjob
queues = Sidekiq::Queue.all
queues.each do |queue|
  queue.each do |job|
    p job.klass, job.args, job.jid
  end
end

以上です :hugging:

参考

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

each内でのrenderパーシャルをcollectionに変更し高速化

問題

ビュー内で部分テンプレートをeachで呼び出す際に困ったこと。

例)ルート(welcome)画面でピックアップした商品一覧を表示(商品一覧は他の画面でも使うためパーシャル化)

welcome_controller.rb
 @pickups = Product.all.where.not(pickup_at: nil).joins_i18n(locale_id: @current_lang.id).includes(countries: :country_i18ns).shuffle
welcome.html.erb
    #問題点のみ抜粋
    <% @pickups.each do |p| %>
      <%= render partial: '/shared/products/index_item', locals: { p: p } %>
    <% end %> -->
/shared/products/_index_item.html.erb
<div class="col-xs-6 col-md-3">
  <section class="product_list">
    <%= link_to product_maker_path(id: p.maker_id, product_id: p.id) do %>
      <div class="photo_box">
        <div class="photo_inner photo_contain" style="background-image:url(<%= asset_path p.main_image.thumb %>);" >
          <% p.campaigns.each do |camp| %>
            <div class="cam_lavel"><%= camp.i18n.label %></div>
          <% end %>
        </div>
      </div>
      <div class="pdt_list_ex">
#以下省略

スクリーンショット 2019-02-26 9.50.07.png

eachで回す度にパーシャルファイルを呼んでしまう。
読み込み速度が遅い。。。。。

解決策

each内ではなく、collectionを使ってパーシャルをrenderする

welcome.html.erb
 <%= render partial: '/shared/products/index_item', collection: @pickups, as: :p %>

パーシャルを呼び出すのは1回。

スクリーンショット 2019-02-26 9.59.05.png

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

Error loading the 'sqlite3' Active Record adapter. Missing a gem it depends on? can't activate sqlite3 (~> 1.3.6), already activated sqlite3-1.4.0. Make sure all dependencies are added to Gemfile. (Gem::LoadError)

bin/rails g ... する際に sqlite3 のversion依存関係で怒られたので解決法を調べた。

解決方法

Gemfile.
- gem 'sqlite3'
+ gem 'sqlite3', '~> 1.3.6'
$ bundle install

原因

リンク先に詳細が記載されている。
- Railsを始めてsqlite3まわりのエラーで躓いている人たちへ

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

Error: qt@5.5: unknown version :mountain_lion

解決方法

$ source ~/.bash_profile を実行するとタイトルのエラーを出力したので解決方法を調べた。

$ cd $( brew --prefix )/Homebrew/Library/Taps/homebrew/homebrew-core
$ cd Formula/
$ grep mountain -rl ./
$ vi .//qt@5.5.rb
qt@5.5.rb
25 - depends_on :macos => :mountain_lion
25 + # depends_on :macos => :mountain_lion

参考

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

gsubで文字列変換

gsub

gsubは、パターンにマッチした文字列を置き換えたい時に使います。
また、パターンには、正規表現が使えます。

使い方

文字列.gsub(パターン, 置き換え後の文字)
phone = '090-1234-5678'
phone.gsub(/-/, '')

結果

=>  "09012345678"

* 2019/2/27追記

コメント欄にて、@scivolaさんにアドバイス頂いたので追記です!
単純な削除はString#deleteの方が良いとのこと。

phone = '090-1234-5678'
phone.delete('-')

=> "09012345678"

benchmark_driverというgemでベンチマークするとgsubよりdeleteの方が速いらしいです。
ベンチマーク載せた方が良い記事書けそうですね。勉強になりました。

gsub!

gsubと同じですが、破壊的メソッドです。

phone = '090-1234-5678'
phone.gsub!(/-/, '')

実行した時の返り値もgsubと同じ

=>  "09012345678"

破壊的メソッドなので、レシーバであるphoneの中身も返り値と同じになります。

print phone

結果

=>  "09012345678"

ブロックを使ったgsub

gsub、gsub!は、パターンにマッチした文字列をブロックに渡して加工出来ます。
ブロックを使うとどんな文字列にも加工できそうです。

文字列.gsub(パターン) { |ブロック| 処理 }

試しにスネークケースで書かれたhello_worldの先頭のhと_の後ろのwを大文字にしてキャメルケースのHelloWorldにしてみましょう。

snake = 'hello_world'
snake.gsub!(/^.|(?<=_)./) { |camel| camel.upcase } # 先頭と_の後ろを大文字にします
snake.gsub!(/_/, '') # _を削除します

結果

=> "HelloWorld"

例題だとgsubじゃなくてscanとかでも出来そうですが、scanはまたの機会にまとめます。

参考
https://www.sejuku.net/blog/14685#gsub-3
https://www.sejuku.net/blog/60033

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

Rubyでもbcryptはバイナリセーフではない

RubyのBCryptはバイナリセーフなのか

徳丸先生が注意喚起としてあげられていたこちらの記事に関して



記事ではPHPの例が上がっていましたが、Rubyではどのような影響があるのかが気になります。

BCryptは非常にメジャーなアルゴリズムで、例えば DeviseSorcery などの定番の認証gemを使う場合、デフォルトでBCryptを利用する設定になっています。Railsアプリを開発されている方なら、ほとんどの方が使っているのではないでしょうか。

詳しくは徳丸ブログを見ていただくとして、以下ではbcrypt-rubyについて、同じ現象が起こるのかどうか簡単に調査しています。

TL;DR

  • BCryptを使って バイナリをハッシュ化してはならない

    • バイナリコード0x00にヒットすると以降のハッシュ化を止める
    • それにより実際より短い部分文字列だけを照合してしまう可能性がある
    • とくに冒頭に0x00を含むパスワードを指定すると、簡単に破られてしまう
  • BCryptに通す前に自前でハッシュ関数を適用したり、BCryptでバイナリをハッシュ化するというような特殊用途で使用している場合は要注意

    • セキュリティ面の問題がある
    • 未来のバージョンで、今まで通っていたパスワードが通らなくなる可能性がある(後述)

「ユーザーからの入力パスワードをテキストのままdeviseに渡す」というように、一般的な使い方をしている分には問題にならないと思われます。

実験してみる

徳丸ブログに指摘されている内容はいくつかのステップがあります。
それぞれRubyのBCrypt実装においても同様の現象が起こるかどうかを確認していきます。

72文字を超えるパスワード文字列は切り捨てられるか

実験のために、BCryptでハッシュ化された長いパスワードを用意します。
(ストレッチ回数などはデフォルト設定です)

>> require 'bcrypt'

# 72文字を超える長いパスワードを用意する
>> password1 = 'a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooong_password'
>> password1.length
=> 81

# BCryptオブジェクトを作る
>> bcrypted_password1 = BCrypt::Password.create(password1)
=> "$2a$10$gSsEckxXdY26QjgVuibuZ.J.nAxVpAyMVW6GWjKI9ouOVBEbPiitm"

ここで作成したBCryptオブジェクトと候補文字列を == で比較すると、「ハッシュ化した時に同じハッシュを得られるかどうか(≒ 元のパスワードが一致するかどうか)」を判定してくれます。1

パスワード認証を通す・通さないの判定に使われている仕組みですね。

# 一致する
>> crypted_password1 == 'a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooong_password'
=> true

# もちろん適当な文字列を入れても一致しない
>> crypted_password1 == 'a_totally_different_thing'
=> false

ところが・・

# パスワード後半を変更する

>> crypted_password1 == 'a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooong_cucumber'
=> true

>> crypted_password1 == 'a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooongSpeach'
=> true

後半(73文字以降)がハッシュ化の際に捨てられていることがわかります。

もちろん72文字以前を変更した場合は不一致となります。

>> 'a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooong'.length
=> 72

>> crypted_password1 == 'a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooonG_password'
=> false

前提となるパスワード文字列の制限については、ドキュメントには明記されていないようですが、RubyのBCryptでも同様に起こることがわかります。

実装はcrypt(3)ライブラリに準拠していると思われるので当然といえば当然ですね。

長い文字列をハッシュ化するときの落とし穴

ここで何らかの事情で「どうしても72文字以上ある長い文字列のハッシュをBCryptで取りてえ」となったとします。

上記の切り捨てを回避するため、「別のアルゴリズムでいったん短いハッシュを作ってから、BCryptに食わせる」という手段が思い浮かびます。ハッシュが長いテキストだと結局また切り落としが発生するのですが、バイナリハッシュを使うと72文字以下におさめることができそうです 2

比較演算子 == が使えるように、雑に実装してみたのが下記です。

require 'digest'

class VeryUnsafeDigest
  def initialize(password)
    @password = password
    @digest = create_digest
  end

  def create_digest
    temporary_digest = Digest::SHA512.digest(@password)
    BCrypt::Password.create(temporary_digest)
  end

  def ==(other)
    @digest == Digest::SHA512.digest(other)
  end
end
>> digest1 = VeryUnsafeDigest.new(password1)

>> digest1 == "a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooong_password"
=> true
>> digest1 == "a_rediculously_loooooooooooooooooooooooooooooooooooooooooooooooooooooong_beard"
=> false

今度は長いパスワードをうまく区別してくれました。

一見うまくいったように見えます。が・・

>> digest2 = VeryUnsafeDigest.new('good_looking_password_380')

>> digest2 == 'RandomString105'
=> true

>> digest2 == 'good_looking_password_420'
=> true

なんと、3つの完全に異なるパスワードが すべて衝突してしまいました。

これらの文字列は、バイナリハッシュの段階で冒頭に0x00が含まれてしまっています。

>> Digest::SHA512.digest 'good_looking_password_380'
=> "\x00\xB6\xE5\xE65\\\x91j\xD3\t\xB4\x98h\x81\xBD\x0E\xB1\\\xC3$\xCB\xD2\x80\x85\xB6\xEC\x18B\xCC\xE4\r\xC3\x93\xA1j*4P\x97\xF0y\x17\xD7P\x11h\x96iq\xFE\x7F\xDB\x10(\n\xDC\xB8\xAF8ESe>\xDD"

つまりBCryptは、この文字列の冒頭が終端だと判断して処理を終了してしまい、残りのハッシュ化をしません(空文字列を渡された場合と実質同じ処理になる)。

やはりRuby実装でも、BCryptは0x00を終端文字列と解釈し、残りの文字列を切り捨ててしまうことが確認できます。

今後の動き

この問題についてはbcrypt-rubyリポジトリでもイシューがいくつか出ているようで、「文字列中に0x00が入ってきた場合には例外を吐かせる」というパッチを我らがたこやきアーロン巨匠が提案してくれています。

これによって、何かの間違いでうっかりBCryptにバイナリを食わせる仕様にしてしまったとしても、上で書いたような「後ろが切り捨てられるパスワード」についてはDBに投入される前に阻止できそうです。
みんなでいいねを押して応援しましょう!

もし上記のようなオレオレ実装をしていた場合、このパッチが入るバージョン以降は一部のパスワードが通らなくなると思われるので、万が一そんなことがあった場合は注意してください。

セキュリティの分野において、ライブラリをハックしたりアルゴリズムを再発明するのは、かえって脆弱性を産むことになるので避けましょうということですね ?

じゃあどうすりゃいいの

RubyGemsで確認すると、Argon2のリポジトリがあるようです。徳丸先生のブログによると、PHPのArgon2は72文字制限はなくバイナリセーフであるようだ、となっています(Ruby実装については不明)

私には使える・使えないの判断ができるほど実装が理解できていない上に、まだまだ利用実績が少ないため(編集時点で64,607ダウンロード。対するBCryptは51,359,582ダウンロード)紹介するのは控えます。

もっと乱暴に「SHAだけを通す」という方法が考えられますが、今度は短いパスワードがレインボーテーブルで簡単に突破される、という状態になり極めて脆弱になります。(ためしに SHA rainbow tableでググって出てきたサイトに、SHA512でハッシュ化した短いパスワードを投げてみたところ、ものの数秒で解読されました)。パスワード管理については流出リスクなどを考えて使わない方がよいと思われます。

というわけで、素直にBCryptを使い、長いパスワードを末尾部分についてはあきらめて「72文字までの一致を見る」(入力パスワードが72文字を超えた場合はユーザーにエラーを返す)ということで割り切るのが、現時点ではもっとも素直な解決策のように思いました。

追記

2/26 ご指摘を受けてタイポ修正、一部わかりにくい表現を追記・編集しました


  1. BCryptは :== メソッドを再定義している。比較時には、saltについてもオブジェクトと同じものに揃えてくれます 

  2. 詳しくは徳丸先生のブログを参照してください。Rubyで書くと、Digest::SHA512.hexdigestは128文字のテキストハッシュを生成しますが、Digest::SHA512.digest でバイナリを生成すると64バイトとなって、一見切り落としが発生しなさそうに思える・・というハナシです。 

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

RubyのGem::FilePermissionErrorを解決する

Macでgemをインストールしようとするとなにやらパーミッションエラー。

$ gem install bundler

ERROR:  While executing gem ... (Gem::FilePermissionError)
    You don't have write permissions for the /Library/Ruby/Gems/2.3.0 directory.

sudoでパーミッションを与えてやる。

$ sudo gem install bundler

ERROR:  While executing gem ... (Gem::FilePermissionError)
    You don't have write permissions for the /usr/bin directory.

また別の場所でパーミッションエラーが出たので、次は保存場所もlocalにしてやる。

$ sudo gem install -n /usr/local/bin bundler

Done installing documentation for bundler after 5 seconds
1 gem installed

できた。

$ bundle init

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

BlackjackのGemを作ったった

完成品はこちら(アニメのBlackjackではないです:relaxed:
ezgif.com-video-to-gif.gif

経緯

何か簡単にアウトプットできる物ないかな
        ↓
Gemってどうやってできているんだ?(普段Rubyを主に使っているためGemが身近にあった)
        ↓
調べたらGemを公開するのに面倒な作業をする必要がなかった(webサイト等だとサーバを借りるなどの必要がある)
        ↓
Qiitaの記事を見てブラックジャックを作りたくなる
        ↓
Gemでブラックジャック作ったらいいんじゃね????

そして

やらない理由がない・・・・・・→ 実行

考慮した事

・だらだらと開発していると他のものに興味が移ってしまい開発が止まる危険性があった為、開発スピードを優先させた。

結果、実装時間は数時間で完了したので上手くいったかなと(^ ^)

・とりあえず最低限の機能でもいいからデプロイまでは完了させる(デプロイするとモチベはそこそこ保てるため)

これは正解だった。モチベありすぎてデプロイした後に見つかったバグを全て直してしまった^ ^

実装

まず初期段階でGemがどのように作られ、どのようにデプロイするのかが全くわからなかった為以下の記事を参考にした

【Ruby】gemの作り方から公開まで

RubyGemsの作り方

後はコードを書いていくのみです! なんとシンプル!!

ロジック等はあえて載せません。(githubは最後にあります。)
ブラックジャックはとても良い勉強題材だと思うので、ぜひ初心者の方は自分で考えて実装してほしいと思います!

あと僕の場合は最初にRSpecを入れ忘れてしまったので、

bundle gem プロダクト名 -t ← これ

tオプションを忘れないようにしてください!まぁRSpecなくてもデプロイはできますけどね(^_^;)

作ってみて感じた事

普段見るGemは規模が大きいものが多い為、Gem作成が難しいと思いがちだけどそんな事はなかった!

むしろ簡単な物だと、サーバの知識やRailsの知識もいらないので、今後Rubyやりたい初心者の方や僕みたいな駆け出しの方々に丁度良い題材なんじゃないかとも感じました!

最後に

やはり、簡単な物でも実際に作ってみると本を読むだけでは得られない知識、経験が得られるなぁと感じました。
これからもアウトプットは積極的にやっていきたいと思います!
コードはhttps://github.com/kSunaga/blackjack_jp に置いてあります。(汚いのであまり見られたくないのは秘密)

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

Ruby Silverに合格したので、勉強方法をまとめてみた(2019年2月版)

先日Ruby Silverに合格した。
86点という普通ちょい上感あふれる絶妙な点数。
需要の程は分からないが、勉強方法をまとめてみる。

Ruby Silverは基本暗記Onlyで、
恐らく他のIT系の資格と比較して範囲も広くはない。
勉強にしてもできる事の幅は広くないので、
恐らく再現性は高いと思う。
(かといって決して楽だという話ではない)

とりあえず結論

  • 合格記事をさら〜っと眺める
  • とにかく色々な問題集を解きまくる
  • 各問題集で9割以上正答&理由も説明できるようにする
  • 仕上げに先人が残してくれた要注意情報に目を通す

前提

とりあえず学習開始時点での私のスペックを記載する。
以下勉強方法を読む際の参考に。

  • Rubyに関しては3ヶ月独学&研修で2週間
    • Rubyの経験は無くはない、という程度
  • 他言語の経験あり
    • 大学時代 C/Javaを履修(?)
    • 会社員になってから5年程度、たまーに業務でコードに触れる程度
  • IT系の知識は薄く広く
    • 基本情報は持っているとかその程度。

学習期間 & 学習時間

  • 学習期間:約2週間
  • 学習時間:約30時間

学習方法

1. 合格記事をさら〜っと眺める

"Ruby Silver", "勉強法"あたりでググって記事を眺めてみる。
勉強方法やら出題傾向やらが書いてあるので、
なんとなく雰囲気を理解する。

例えば、
- String/Array/Hashクラスのインスタンスメソッドに関する出題が多い
- 同じ機能で別の名前を持つメソッドの記憶は必須
- 破壊的メソッドとそうでないメソッドの見分けも重要
- File/Dir/Timeクラスに関しての出題もそこそこ多い
などなど。。。

細かく理解する必要はなく、そんな感じなんだなぁと思うくらいでOK。
適当にGoogle検索結果の上から5,6個いい感じのを、流し読みするくらい。

2. とにかく問題集を解く

具体的な対策としては、問題集を周回するのが一番手っ取り早い。
ソシャゲのマラソンみたいなノリで、ひたすら走るべし。

(ただしRubyのことを何も知らない、プログラミングもほとんどやったことない、
という人は対象外。Rubyの入門本を1冊消化した方が多分良い)

1つだけじゃ無くて、色々な問題集を取っ替え引っ替えするのがオススメ。
やってるうちに、共通点やら傾向が自然と理解できる。

私が実際に使った問題集は、主に以下の3つ。

公式模擬問題集

https://www.ruby.or.jp/assets/images/ja/certification/examination/exam_prep_jp.pdf
pdfと見せかけてどこぞのgithubリポジトリに飛ぶ。無料。
一番最初にやって7割弱取れた。恐らく一番難易度低め。

REx

https://www.ruby.or.jp/ja/certification/examination/rex
受ける度に問題の内容が変わる。8割共通で残りの2割が入れ替わる。
もちろん以前の問題を再び受け直すことも可能。解説も割と書いてある。
スマホからも受けられて便利。もちろん無料。恐らく一番使った。
githubアカウントとの連携が必要。

[改訂2版]Ruby技術者認定試験合格教本(Silver/Gold対応)Ruby公式資格教科書

https://www.amazon.co.jp/dp/B0756VF9Y3/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
これだけ普通の書籍。
3600円と結構なお値段がするが、十分その価値はある。
問題集だけでなく、出題範囲に関する一通りの知識も記載されているので、
リファレンスとしても活用できる。

時間があるなら、3,4,5章あたりは一通り読んでおいても良い。
ただし問題集を解いた上で、不明な点に絞って読んだ方が遥かに効果がある。

各問題集で9割以上正答&理由も説明できるようにする

上記で挙げたいくつかの種類の問題集で、
9割以上解ける&理由も説明できるようになるまで、
知識の完成度を高めていく。

その際、理解するための手段は色々な方法を試すと良い。

  • 問題集の解答を読む
  • Ruby技術者認定試験合格教本の該当箇所を読む
  • ネットで関連情報をググる(主にRubyリファレンスなど)
  • irbで実際に動作を確かめる

などなど。。。
この辺りを広くやればやるほど、
応用の効く知識が身につき、恐らく本番に強くなる。
(例えば正答に関係ない選択肢でも、分からなければ積極的に調査する、など)

逆に問題集の解答を丸暗記しただけだと、
本番で足元をすくわれる可能性が高くなる、と思う。

仕上げに先人が残してくれた要注意問題に目を通す

本番の試験は、6,7割は問題集ほぼそのままの内容だったが、
残りは妙にひねった所のある、いやらしい内容だった。

試験の前日などに、先人が残したくれた情報に目を通すとその辺りの対策になる。

私が実際に直前に確認して助かったのは以下2つの記事。
そのおかげで多分3問くらいは助かった。

Ruby Silver試験前に見直すと幸せになれるメモ
http://tamata78.hatenablog.com/entry/2015/08/07/200454

Ruby技術者認定試験Silver version 2.1を受けて…
https://qiita.com/motty93/items/413485469e4ec665c329

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