20190302のRubyに関する記事は16件です。

Rails5 で geocoder を使って、緯度経度の取得する(Google API の設定)

Rails5 で地図アプリを作成するため、gmap4rails, geocoder を実験しました。緯度経度が取得できず苦労しましたが、APIの設定を見直すことで成功したので記録に残します。

Rails GoogleMap表示 gem gmaps4railsとgeocoder を参照に地図アプリを作成する。
https://qiita.com/yuki_chrono/items/a2638c33eedc3c036d01

作成したアプリで地図は表示できたが、緯度経度が取得できない場合があった。「埼玉県」なら取得できるが「埼玉県さいたま市」にすると取得できない...

snap_1215.png

どうやら geocoder 単体では、細かい緯度経度が取得できないらしい。Google geocoding を利用することで、(Google で調べられる地点なら)すべての場所の緯度経度を取得することができるようになる。

geocoder 設定ファイルを作成

$ bundle exec rails generate geocoder:config
config/initializers/geocoder.rb
Geocoder.configure(

  # street address geocoding service (default :nominatim)
  lookup: :google,

  # IP address geocoding service (default :ipinfo_io)
  # ip_lookup: :maxmind,

  # to use an API key:
  api_key: "AIzaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",

  # geocoding service request timeout, in seconds (default 3):
  timeout: 5,

  # set default units to kilometers:
  units: :km,

)

コンソールで動作確認

$ bundle exec rails console
![snap_1214.png](https://qiita-image-store.s3.amazonaws.com/0/320718/236a5d2c-c062-6fa6-a04b-d31ed4e963be.png)

irb(main):002:0> Geocoder.search("東京タワー")
Google API error: request denied (API keys with referer restrictions cannot be used with this API.).
=> []
irb(main):003:0> Geocoder.coordinates('埼玉県さいたま市')
Google API error: request denied (API keys with referer restrictions cannot be used with this API.).
=> nil

エラーメッセージ
Google API error: request denied (API keys with referer restrictions cannot be used with this API.).
をヒントにAPIの設定を見直すことで緯度経度を取得できるようになった。原因は(gmaps4railsのAPI設定と同じように)「HTTP リファラー」で制限をかけていたためだった。アプリケーションの制限を「なし」に変更する。

snap_1214.png

irb(main):005:0> Geocoder.search("東京タワー")
=> [#<Geocoder::Result::Google:0x00007faf8616c7f8 @data={"address_components"=>[{"long_name"=>"8", "short_name"=>"8", "types"=>["premise"]}, {"long_name"=>"2", "short

_name"=>"2", "types"=>["political", "sublocality", "sublocality_level_4"]}, {"long_name"=>"4 Chome", "short_name"=>"4 Chome", "types"=>["political", "sublocality", "s
ublocality_level_3"]}, {"long_name"=>"Shibakoen", "short_name"=>"Shibakoen", "types"=>["political", "sublocality", "sublocality_level_2"]}, {"long_name"=>"Minato", "s
hort_name"=>"Minato", "types"=>["locality", "political"]}, {"long_name"=>"Tokyo", "short_name"=>"Tokyo", "types"=>["administrative_area_level_1", "political"]}, {"lon
g_name"=>"Japan", "short_name"=>"JP", "types"=>["country", "political"]}, {"long_name"=>"105-0011", "short_name"=>"105-0011", "types"=>["postal_code"]}], "formatted_a
ddress"=>"4 Chome-2-8 Shibakoen, Minato, Tokyo 105-0011, Japan", "geometry"=>{"location"=>{"lat"=>35.6585805, "lng"=>139.7454329}, "location_type"=>"ROOFTOP", "viewpo
rt"=>{"northeast"=>{"lat"=>35.6599294802915, "lng"=>139.7467818802915}, "southwest"=>{"lat"=>35.6572315197085, "lng"=>139.7440839197085}}}, "place_id"=>"ChIJCewJkL2LG
GAR3Qmk0vCTGkg", "plus_code"=>{"compound_code"=>"MP5W+C5 Tokyo, Japan", "global_code"=>"8Q7XMP5W+C5"}, "types"=>["establishment", "point_of_interest", "premise"]}, @c
ache_hit=nil>]
irb(main):006:0> Geocoder.coordinates('埼玉県さいたま市')
=> [35.8617292, 139.6454822]

(注意)
gmaps4rails のAPI KEY は「HTTP リファラー」で制限をかける必要がある点に注意が必要です。

snap_1216.png

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

重複する条件分岐の断片の統合(Consolidate Duplicate Conditional Fragments)

image.png

1つずつリファクタリング技法まとめ
個人的に簡単かつ取り入れ易いと思うものから

目的

すぐ引き出せるようにする

基本作業サイクル

  • システムを動かして仕様を精査
  • テストメソッドを作成
  • テストの失敗を確認
  • テストの成功を確認
  • 小さい変更、随時テスト実行(パターン追加失敗確認->成功確認)
  • 最後テスト実行
  • 最後動作確認

重複する条件分岐の断片の統合(Consolidate Duplicate Conditional Fragments)とは

複数条件文内に共通で入っている処理を外出しすること

ポイント

  • 共通処理が入っている位置によって、切り出し先が変わる
  • 複数の処理が共通している場合、メソッドの抽出を行う

  • 共通処理が条件文内の末尾(冒頭)にある場合は末尾(冒頭)に切り出す。
if tax?
  total = price * 1.08
  puts(total)
else
  total = price
  puts(total)
end

   ↓

if tax?
  total = price * 1.08
else
  total = price
end
puts(total)
  • 複数の処理が共通している場合、メソッドの抽出を行う
if sunny?
  greet
  eat
  walk
else
  greet
  eat
  drive
end

   ↓

def routine
  greet
  eat
end

routine
if sunny?
  walk
else
  drive
end

書籍情報

Jay Fields (著), Shane Harvie (著), Martin Fowler (著), Kent Beck (著),
長尾 高弘(訳), リファクタリング:Rubyエディション
https://amzn.to/2VlyWML

雑感

切り分けの意味をしっかり理解する

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

Ruby 配列、ハッシュ

動作環境はMacです。

puts,print,p,ppメソッドの違い


irb(main):002:0> puts 'kaneko'
kaneko
=> nil #変数の内容に改行を加えてメソッドの戻り値をターミナルに出力
irb(main):003:0> print 'kaneko'
kaneko=> nil  
#改行なしでメソッドの戻り値をターミナルに出力
irb(main):004:0> p 'kaneko'
"kaneko"
=> "kaneko" #pメソッドの戻り値は引数のオブジェクト デバック向け。
#デバックとはプログラムの不具合を見つけて修正すること。

ppメソッドはpメソッドより適切な改行をして出力する。
pメソッドで適切でない場合は、ppメソッドを使う。

配列

  • 複数のデータを格納できるオブジェクト。
  • 配列内のデータは順番に並んでいて、添字(インデックス)を指定することで取り出すことができる。

配列の作成方法

  • 空の配列を作る
    []

  • 3つの要素が格納された配列を作る
    [要素1,要素2,要素3]

実践

irb(main):003:0> a = [1, 2, 3, 'aa', [1, 2, 3]]  #変数aに配列を代入
=> [1, 2, 3, "aa", [1, 2, 3]]
irb(main):004:0> a[0] 
=> 1
irb(main):005:0> a[1]
=> 2
irb(main):006:0> a[3]
=> "aa"
irb(main):007:0> a[2]
=> 3
irb(main):008:0> a[4]
=> [1, 2, 3]
irb(main):009:0> puts a
1
2
3
aa
1
2
3
=> nil
irb(main):010:0> p a
[1, 2, 3, "aa", [1, 2, 3]]
=> [1, 2, 3, "aa", [1, 2, 3]]
irb(main):011:0> pp a
NoMethodError: undefined method `pp' for main:Object
Did you mean?  p
    from (irb):11
    from /Users/toripurug884/.rbenv/versions/2.4.1/bin/irb:11:in `<main>'
irb(main):012:0> a.empty? #変数aが空か?
=> false
irb(main):013:0> b = [] 
=> []
irb(main):014:0> b.empty?
=> true
irb(main):015:0> a.include?('aa') #変数aに'aa'が含まれるか?
=> true
irb(main):016:0> a.include?('a')
=> false
irb(main):017:0> a.reverse #配列の中身を反転
=> [[1, 2, 3], "aa", 3, 2, 1]
irb(main):018:0> a
=> [1, 2, 3, "aa", [1, 2, 3]]
irb(main):019:0> a.reverse! #配列の中身を反転させる破壊的メソッド
=> [[1, 2, 3], "aa", 3, 2, 1]
irb(main):020:0> a
=> [[1, 2, 3], "aa", 3, 2, 1]
irb(main):021:0> a.shuffle  #配列の中身をランダムに入れ替え
=> [2, 3, 1, "aa", [1, 2, 3]]

配列を使ったメソッド(数値)

irb(main):022:0> (0..25).to_a #範囲オブジェクトを指定し、配列へ
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
irb(main):024:0> a = (0..99).to_a.shuffle! #オブジェクトの中身を破壊的メソッドでランダムに入れ替える
=> [17, 1, 46, 12, 62, 69, 39, 40, 96, 87, 77, 50, 84, 56, 70, 7, 98, 19, 64, 8, 67, 89, 14, 33, 20, 29, 22, 51, 26, 18, 68, 57, 86, 47, 55, 48, 97, 44, 34, 42, 79, 30, 61, 6, 81, 13, 85, 0, 59, 71, 65, 99, 75, 72, 23, 66, 63, 5, 15, 49, 91, 36, 35, 38, 94, 45, 95, 11, 41, 2, 4, 10, 31, 54, 88, 43, 32, 28, 73, 92, 3, 58, 80, 16, 27, 82, 52, 25, 9, 60, 90, 74, 93, 21, 24, 83, 37, 53, 78, 76]
irb(main):025:0> a  #中身が入れ替わる
=> [17, 1, 46, 12, 62, 69, 39, 40, 96, 87, 77, 50, 84, 56, 70, 7, 98, 19, 64, 8, 67, 89, 14, 33, 20, 29, 22, 51, 26, 18, 68, 57, 86, 47, 55, 48, 97, 44, 34, 42, 79, 30, 61, 6, 81, 13, 85, 0, 59, 71, 65, 99, 75, 72, 23, 66, 63, 5, 15, 49, 91, 36, 35, 38, 94, 45, 95, 11, 41, 2, 4, 10, 31, 54, 88, 43, 32, 28, 73, 92, 3, 58, 80, 16, 27, 82, 52, 25, 9, 60, 90, 74, 93, 21, 24, 83, 37, 53, 78, 76]
irb(main):026:0> z = (0..10).to_a
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
irb(main):027:0> z[0]
=> 0
irb(main):028:0> z[1]
=> 1
irb(main):029:0> z
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
irb(main):030:0> z << 20 #末尾に20を追加 pushとイコール
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20]
irb(main):031:0> z.push(30) #末尾に30を追加 <<とイコール
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30]
irb(main):033:0> z.pop #末尾の数値を削除
=> 30
irb(main):034:0> z
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20]
irb(main):035:0> z.shift #先頭の数値を削除
=> 0
irb(main):036:0> z << 3
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 3]
irb(main):037:0> z
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 3]
irb(main):038:0> z << 3
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 3, 3]
irb(main):039:0> z << 6
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 3, 3, 6]
irb(main):040:0> z
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 3, 3, 6]
irb(main):041:0> z.uniq #被っている数値を出力しない
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20]
irb(main):042:0> z.uniq! #破壊的メソッド
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20]
irb(main):043:0> z #3と6が出力されない
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20]

配列を使ったメソッド(文字列)

irb(main):044:0> a = [ "my", "name", "is", "nakamura"]
=> ["my", "name", "is", "nakamura"]
irb(main):045:0> a
=> ["my", "name", "is", "nakamura"]
irb(main):046:0> a.join  #配列の中身を結合
=> "mynameisnakamura"
irb(main):048:0> a.join(' ') #配列の中身を結合し、スペースを開ける スプリット
=> "my name is nakamura"
irb(main):049:0> a.join('_')
=> "my_name_is_nakamura"
irb(main):050:0> a.sort #配列の中身を入れ替える
=> ["is", "my", "nakamura", "name"]
irb(main):051:0> a.sort.reverse #配列の中身を逆にする
=> ["name", "nakamura", "my", "is"]
irb(main):053:0> a
=> ["my", "name", "is", "nakamura"]
irb(main):054:0> a.sort!.reverse!  #破壊的メソッド
=> ["name", "nakamura", "my", "is"]
irb(main):055:0> a.size #配列に入っているデータ数の出力
=> 4

ハッシュ

  • キー(key)と値(value)でデータの管理をするオブジェクト
  • 連想配列、マップ、ディクショナリと呼ばれる場合もある

作成方法

空のハッシュを作成
{}

キーと値の組み合わせを3つ格納するハッシュ
{ キー1 => 値1, キー2 => 値2, キー3 => 値3 }


実践

irb(main):001:0> {}
=> {} #空のブランケット作成
irb(main):002:0> kaneki = {'name' => 'ken', 'birthplace' => 'saga'} #変数kanekiにハッシュで代入
=> {"name"=>"ken", "birthplace"=>"saga"}
irb(main):003:0> kaneki 
=> {"name"=>"ken", "birthplace"=>"saga"}
irb(main):007:0> puts kaneki['name']
ken
=> nil #nameキーを出力
irb(main):009:0> puts kaneki['birthplace']
saga #birthplaceキーを出力
=> nil
irb(main):010:0> kaneki['age'] = 20
=> 20 #ageキーを追加
irb(main):011:0> kaneki
=> {"name"=>"ken", "birthplace"=>"saga", "age"=>20}
irb(main):013:0> kaneki['age'] = 21
=> 21 #ageキーを書き換え
irb(main):014:0> kaneki
=> {"name"=>"ken", "birthplace"=>"saga", "age"=>21}
irb(main):017:0> kaneki.delete('age') #ageキーを削除
=> 21
irb(main):019:0> puts kaneki
{"name"=>"ken", "birthplace"=>"saga"}
=> nil

シンボル

・ソースコード上は文字列に見えるが、内部では整数として扱われる
・文字列を使う場合よりシンボルを使った方が処理が速い


実践


irb(main):031:0> imawano.keys #変数が持つキーを出力
=> [:name, :birthplace]
irb(main):032:0> imawano.values #変数が持つ値の出力
=> ["kiyosirou", "okinawa"]
irb(main):034:0> imawano.has_key?(:name)#変数が引数に渡されるキーを持っているか?
=> true #持っているのでtrue
irb(main):035:0> imawano.has_key?(:id)
=> false  #持っていないのでfalse
irb(main):036:0> imawano.size #変数に入っているキーの数
=> 2 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

『オブジェクト指向設計実践ガイド』に学ぶ単一責任のクラス設計

『オブジェクト指向設計実践ガイド』の「第2章:単一責任のクラスを設計する」のまとめです。

ポイント:「クラスに属するものをどのように決めるか」

・クラスを使い、"今すぐに"求められている動作を行い、かつ"あとにも"かんたんに変更できるようにする

・設計とは、アプリケーションの可変性を保つために技巧を凝らすことであり、完璧を目指すものではない

変更がかんたんなようにコードを組成する

「変更がかんたんである」とは
1.変更は副作用をもたらさない
2.要件の変更が小さければ、コードの変更も相応して小さい
3.既存のコードはかんたんに再利用できる
4.最もかんたんな変更方法はコードの追加。ただし追加するコードはそれ自体変更が容易なもの

どんな性質のコードを書けばいいか?

「変更がかんたんなコード」の定義を受けて、実際にコードを書くときには「TRUEなコード」を心がける。

「TRUEなコード」
1.見通しが良い(Transparent):変更がもたらす影響が明白
2.合理的(Reasonable):かかるコストは変更がもたらす利益にふさわしい
3.利用性が高い(Usable):新しい環境、予期していなかった環境でも再利用できる
4.模範的(Exemplary):コードに変更を加える人が、上記の品質を自然と保つようなコードになっている

・TRUEなコードを書くための最初の第一歩は、「それぞれのクラスが、明確に定義された単一の責任を持つように徹底する」こと

単一の責任を持つクラスをつくる

・クラスはできる限り最小で有用なことをするべき -> 単一の責任を持つべき
・クラスになるにふさわしいもの -> 「データ」と「振る舞い」

Q.なぜ単一責任が重要なのか
A.2つ以上の責任を持つクラスは、アプリケーションが予期せず壊れる可能性があるから
->変更が加わるたびに、クラスに依存するクラスを全て破壊する可能性がある

クラスが単一責任かどうかを見極める

Q.あるクラスが、別のどこかに属する"振る舞い"を含んでいるかどうかを見極める方法は?
A1.あたかもそれに"知覚"があるかのように仮定して問いただす。
->クラスの持つメソッドを質問に言い換えたときに、意味をなす質問になっているべき
例)「〇〇クラスさん、あなたの××を教えてください」 <-理にかなった質問になっているか?

A2.1文でクラスを説明したときに、「それと」「または」が含まれない
・「クラスでできる限り最小で有用なことをすべき」 => 「かんたんに説明できるものであるべき」
・「それと」が含まれる => おそらく2つ以上の責任を負っている
・「または」が含まれる => クラスの責任が2つ以上あるだけでなく、互いにあまり関連しない責任を負っている

設計を決定する時を見極める

・「早い段階で設計を決定しなければ」という気持ちに駆られるのはダメ
・何もしないことによる将来的なコストが今と変わらない時、設計の決定は延期する
・決定は必要なときにのみ、その時点で持っている情報を使ってする

単一責任の概念はクラス以外のコードでも役立つ

メソッドから余計な責任を抽出する

・単一責任にすることで、メソッドの変更も再利用もかんたんになる
・メソッドに対しても、役割がなんであるか質問をし、また1文で責任を説明できるようにするとわかりやすい
・「単に振る舞いを別のメソッドに分離する」リファクタリングは、最終的な設計がわかっていない段階でも実施すべき

単一責任のメソッドがもたらす恩恵

1.隠蔽された性質を明らかにする
2.コメントをする必要がない
3.再利用を促進する
4.他のクラスへの移動がかんたん
1.隠蔽された性質を明らかにする

クラス内のメソッドを他のクラスに再編する意図がなくても、それぞれのメソッドが単一の目的を果たすようにすることによって、クラスが行うこと全体がより明確になる

2.コメントをする必要がない

もしメソッド内のコードにコメントが必要な部分があれば、その部分のコードを別のメソッドに抽出する。
別のメソッドに抽出した新しいメソッドの名前が、コメントの目的を果たす。

3.再利用を促進する

メソッドを小さくすることで、アプリケーションにとって健康的なコードの書き方を促進する。

4.他のクラスへの移動がかんたん

設計のための情報が増え変更をしようと決めたとき、小さなメソッドならかんたんに動かせる。
移動のためにいくつものリファクタリングやメソッドの抽出もする必要がない。

クラス内の余計な責任を隔離する

・いったん全てのメソッドを単一責任にしてしまえば、クラスのスコープはより明白に
・単一責任にこだわり、余計な振る舞いをそのクラスに「あるorない」の二択にするのは短絡的すぎる
⇨設計の目的は、設計に手を加える数を可能な限り最小にしつつ、クラスを単一責任に保つこと
⇨普段から変更可能なコードを書いておくことで、どうしてもしなければならない時まで決断を先延ばしすることができる

終わり

単一責任とか、ただ書籍で読んで知った知識はなかなか理解できないのですが、ちょうどこの章を読んでいるときに自分の初めて作ったサービスのコードを思い出しました。

それはメソッド部分だったのですが、コードを読み直すと明らかに単一責任ではない部分があったので実践しました。

明らかにメソッドもシンプルに、かつ読みやすくなったと思います。

書籍から学ぶ場合も、勉強したことをすぐに取り入れるのは効率的な学習をするためにも必須ですね。

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

サーバのキャッシュ情報を利用してキャッシュする実装(Ruby)

参考URL

Working with HTTP cache
https://www.brianstorti.com/working-with-http-cache/

はじめに

httpのレスポンスのヘッダに last-modifiedEtag 等のキャッシュに関する情報が載っていることがあります.
Webサーバ側がせっかくキャッシュ情報を設定しているんだから,クライアント側も実装しよう.

基本

以降,Ruby 2.4.3(Windows10 64bit),2.5.1(Windows Subsystem for Linux)で説明します.

次のコードは,httpsでdocs.ruby-lang.org から / をダウンロードします.
httpのヘッダやステータスコードも出力させています.

require 'net/https'

https = Net::HTTP.new("docs.ruby-lang.org", 443)
https.use_ssl = true
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
https.start do |http|
    response = http.get("/")
    p response.code
    puts response.each.map{|k,v| k+"\t => "+v }.to_a
    puts response.body[0..200] if response.body
end

結果はこんな感じでした.

"200"
server   => nginx/1.10.3
content-type     => text/html
last-modified    => Thu, 27 Dec 2018 00:19:32 GMT
etag     => W/"5c241a94-394"
cache-control    => public, max-age=43200, s-maxage=172800, stale-while-revalidate=86400, stale-if-error=604800
via      => 1.1 varnish, 1.1 varnish
fastly-debug-digest      => 33272ba139e924bc977ca4803fe9bfe699d76ef3818ac8b578bbfe42b3afa6cc
content-length   => 579
accept-ranges    => bytes
date     => Sat, 02 Mar 2019 01:49:34 GMT
age      => 102622
connection       => keep-alive
x-served-by      => cache-nrt6147-NRT, cache-itm18822-ITM
x-cache  => HIT, HIT
x-cache-hits     => 1, 3
x-timer  => S1551491375.978827,VS0,VE5
vary     => Accept-Encoding
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<!-- Global Site Tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-620926-3"></script

キャッシュ

last-modified を使う

ヘッダに含まれているlast-modifiedは,「最後にそのページが更新された時間」です.
last-modifiedの値が前回アクセスした時と同じならば,
更新が無かったとみなすことができるので,bodyを取得する必要はありません.

require 'net/https'
require 'time'

@cache = {}

def get(path)
    https = Net::HTTP.new("docs.ruby-lang.org", 443)
    https.use_ssl = true
    https.verify_mode = OpenSSL::SSL::VERIFY_NONE
    https.start do |http|
        # ヘッダのみを取得
        response = http.head(path)
        # そのパスのキャッシュが無い OR キャッシュが古いならば
        if @cache[path].nil? || Time.parse(@cache[path][:last_modified]) < Time.parse(response["last-modified"])
            STDERR.puts "get"
            # bodyも含めて取得
            response = http.get(path)
            # 取得結果を記憶
            @cache[path] = {
                last_modified: response["last-modified"],
                body: response.body
            }
            @cache[path][:body]
        else
            STDERR.puts "cached"
            # 記憶済みの取得結果を返す
            @cache[path][:body]
        end
    end
end

puts get("/")[0..50]
sleep 1
puts get("/")[0..50]
sleep 1
puts get("/")[0..50]

If-Modified-Sinceを使う

クライアント側で更新時刻を比較するのではなく,サーバ側で比較してもらう方法です.
If-Modified-Since ヘッダを付けて,クエリを投げます.
更新がなかった場合,304が返ってきます.

require 'net/https'
require 'time'

@cache = {}

def get(path)
    https = Net::HTTP.new("docs.ruby-lang.org", 443)
    https.use_ssl = true
    https.verify_mode = OpenSSL::SSL::VERIFY_NONE
    https.start do |http|
        # 最後に取得した日付
        since = @cache[path] ? @cache[path][:last_modified] : ""
        # サーバから取得する
        response = http.get(path, {"If-Modified-Since"=>since})
        if (response.code == "200") # OK(変更がある)
            STDERR.puts "get"
            # 取得結果を記憶
            @cache[path] = {
                last_modified: response["last-modified"],
                body: response.body
            }
            @cache[path][:body]
        elsif (response.code == "304") # Not Modified
            STDERR.puts "cached"
            # 記憶済みの取得結果を返す
            @cache[path][:body]
        else
            "?"
        end
    end
end

puts get("/")[0..50]
sleep 1
puts get("/")[0..50]
sleep 1
puts get("/")[0..50]

Etag

ヘッダにEtagが記載されている場合があります.
ウェブページのハッシュ値のような識別子で,前回のクエリとEtag値が同じならば,
ページに変化がない,とみなすことが出来ます.
If-Modified-Since同様に,If-None-Match ヘッダを付けてクエリを投げることで,
サーバ側でEtag値の比較をしてくれるはずです.

require 'net/https'
require 'time'

@cache = {}

def get(path)
    https = Net::HTTP.new("docs.ruby-lang.org", 443)
    https.use_ssl = true
    https.verify_mode = OpenSSL::SSL::VERIFY_NONE
    https.start do |http|
        # 最後に取得した日付
        etag = @cache[path] ? @cache[path][:etag] : ""
        # サーバから取得する
        response = http.get(path, {"If-None-Match"=>etag})
        if (response.code == "200") # OK(変更がある)
            STDERR.puts "get"
            # 取得結果を記憶
            @cache[path] = {
                etag: response["Etag"],
                body: response.body
            }
            @cache[path][:body]
        elsif (response.code == "304") # Not Modified
            STDERR.puts "cached"
            # 記憶済みの取得結果を返す
            @cache[path][:body]
        else
            "?"
        end
    end
end

puts get("/")[0..50]
sleep 1
puts get("/")[0..50]
sleep 1
puts get("/")[0..50]

ETagには「強いETag値」「弱いETag値」の2種類があるようです.
詳細は日本語版Wikipediaに記載されています.
https://ja.wikipedia.org/wiki/HTTP_ETag

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

Docker + Rails + MySQL + Vue で Todo アプリ作成 〜その2 RSpec 〜

おさらい

最終目標 : trello
前回 : seed データを投入して、ルートページと index ページで seed データの一覧表示できるところまで

その2 の目標

1.前回の分のテストを書く

1. なぜテストを書くのか

なぜなんだ

→ 安全だからです。
あー、とっても答えになってないですね
そう言う時は先生に聞きます

Rails Tutorial が僕の基本的な先生です。(10章までやった)
先生は テスト駆動開発(TDD) を推奨していました

テスト駆動開発とはなんぞや

  1. テストを先に書く
  2. もちろん失敗する
  3. 失敗しないように実装していく

だいたいこんな感じ。

なんでそんなことすんの?

あれ?なんでそんなことするの?
なぜなんだ(本日2回目)
ということで一旦ググる

良さげな記事を発見
【初心者向け】テスト駆動開発とRspecについて調べてみた
ありがとうございます。勉強になります。

2. インストールする

だいたいわかったので
とりあえず RSpec をインストールする。。。

Gemfile
# ~~省略~~
group :development, :test do
  gem 'rspec-rails'
end

参考:RSpec 公式

Gemfile を更新したらば、web コンテナで gem をインストールしていく

web
# bundle install

# rspec -v
RSpec 3.8
  - rspec-core 3.8.0
  - rspec-expectations 3.8.2
  - rspec-mocks 3.8.0
  - rspec-rails 3.8.2
  - rspec-support 3.8.0

# rails  g rspec:install
Running via Spring preloader in process 495
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

入ってますね。

3. どんなテストを書くか

/ (ルートページ) と /index にアクセスした際に
seed に入れたデータが全て取得できることを確認する。

必要なテストは2つ
1. / ルートページにアクセスした際に、 期待するデータの全てが取得できること
2. /index にアクセスした際に、期待しt(ry

こんなもんかな
まだしなきゃいけないことあるなら思いついた時にやります。
とりあえず今回はこの二つ

4. 作業開始(準備)

まずはテストファイルを作成します。

$ touch spec/controllers/index_page_spec.rb

今後 Contoroller のテストを行うものは全て spec/controllers/ に入れようと思います。

memo

hogehoge_spec.rb_spec これ

書かなくてもテストは実行できるんです。
じゃあなんで書くのかと言うと
bundle exec rspec でテストを実行した時に _spec がついてるファイルを
わざわざ指定しなくても、まとめて実行できるんです。

つまり

$ bundle exec rspec spec/hoge/hoge.rb
$ bundle exec rspec spec/fuga/fuga.rb
$ bundle exec rspec spec/piyo.rb
$ bundle exec rspec poyo.rb

と、しないといけないところを
お尻に _spec とつけるだけで

$ bundle exec rspec

上記のをまとめて実行できるようになるんですね、すごい!
めっちゃ便利なので積極的にお尻にスペックをつけましょう。

(この認識に誤りがあったら教えてください。mm)


それでは作業に戻ります。

spec/controllers/index_page_spec.rb
RSpec.describe 'TasksController' do
  context 'show index page' do
    it 'show task list on root page' do
    end

    it 'show task list on index page' do
    end
  end
end

TasksController を文字列にした理由

web
# bundle exec rspec 
..

Finished in 0.00874 seconds (files took 0.13328 seconds to load)
2 examples, 0 failures

何も書いてないので、当然グリーンになります。

公式 に書いてあった書き方を真似てみました。
一行目で、君は RSpec の仲間だよ〜って教えてあげてるんだと思います。
んー、ちょっと微妙な理解っぽいので、もう少し調べてみます
公式を読み進めてみると。。

image.png

Controller のテストの書き方かな? リンクがありましたね
こちら
いいぞ、すごいそれっぽいぞ

Controller specs are marked by :type => :controller or if you have set
config.infer_spec_type_from_file_location! by placing them in spec/controllers.

コントローラーのテストだよーって教えてあげるためには、どこかに :type => :controller を書くか spec/controllers/ に配置する必要があるみたいです。
コントローラーのテストだよって教えてあげないといけないんですね

render_views というものがあるみたい
僕のは こちら に該当したので、書き方を変えます。

spec/controllers/index_page_spec.rb
require 'rails_helper'

RSpec.describe TasksController do
  render_views

  context 'show index' do
    it 'show task list on root page' do
    end

    it 'show task list on index page' do
    end
  end
end

type ~~ を書くように書いてありましたが、さっき spec/controllers/ 配下なら書かなくていいよとも書いてあったので書きません。
あと、ここではコントローラーを' 'で囲わなくても大丈夫だったので、こうします

require 'rails-helper' ????
...誰ですか???!!!

〜回想〜
$ bundle install
$ rails g rspec:install
Running via Spring preloader in process 495
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb

いた...。
いた!!
いました。

どうやら このヘルパーを require しないと render_views が使えないみたいです。

オマケがありました
おそらく Rails のヘルパーを使う事を明記したので、Rails で作成したコントローラーが文字列でなくても認識してくれたのかなって思います。
書かなくていいことは書きたくないので ' ' 消しました

web
# bundle exec rspec 
..

Finished in 0.04538 seconds (files took 7.39 seconds to load)
2 examples, 0 failures

は??なんか7秒もかかりました。
ふざけんな、なんでじゃ、なんもテストしとらんのに!

ちょっとテストの時間がかかるのはストレスですが、一旦後回しにします。
テストの時間がかかる事を対策することに時間がかかりそうなので

5. 作業開始(本当に)

ちょっと予想外に調べるものが多かったので、章分けしました。
それでは気を取り直してやっていきますか
(もう結構しんどい、こんなややこしいの...?)

とりあえず、index を開いてほしい。
公式をみるとそれっぽいことが書いてあった。

spec/controllers/index_page_spec.rb
~~
it 'show task list on index page' do
  get :index
end
~~
web
# bundle exec rspec 
..

Finished in 0.48295 seconds (files took 5.18 seconds to load)
2 examples, 0 failures

いい感じ、なぜか時間も短くなってるし、なげえけどな

spec/controllers/index_page_spec.rb
~~
it 'show task list on index page' do
  get :index
  expect(response).to render_template('index')
  expect(response.body).to eq 'task 001 : hogehoge'
end
~~
web
# bundle exec rspec 
.F

Failures:

  1) TasksController show index show task list on index page
     Failure/Error: expect(response).to render_template('index')

     NoMethodError:
       assert_template has been extracted to a gem. To continue using it,
               add `gem 'rails-controller-testing'` to your Gemfile.
     # ./spec/controllers/index_page_spec.rb:12:in `block (3 levels) in <top (required)>'

Finished in 0.48037 seconds (files took 5.57 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/controllers/index_page_spec.rb:10 # TasksController show index show task list on index page

なんか Gem がないよって怒ってますね
どうやら assert_template を使うには rails-controller-testing という Gem が必要みたい
は?なにこいつめっちゃ親切にエラーを出すじゃないか、やさしい

5.1 Gem install

インストールは公式を見た方が最新の情報が書いてあったりするので、安全だと思っています。
ので
公式 を見ます。
Gemfile のどこに書いてもいいみたい、けどまあ RSpec を使う環境でしか使わないし、一緒の所に書いておきましょうか。

Gemfile
group :development, :test do
  gem 'rspec-rails'
  gem 'rails-controller-testing'
end
web
# bundle

あっ、へ〜、公式の install 方法に書いてあったので真似たのですが bundle installbundle と省略できるんですね、便利。
よく使うからきっと用意されたのでしょう、楽チンなので覚えました

web
  1) TasksController show index show task list on index page
     Failure/Error: expect(response.body).to eq 'task 001 : hogehoge'

       expected: "task 001 : hogehoge"
            got: "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Myapp</title>\n    \n    \n\n    <link rel=\"styleshee...\"></script>\n  </head>\n\n  <body>\n    <h1>Your Tasks</h1>\n\n<ul>\n</ul>\n\n  </body>\n</html>\n"

       (compared using ==)

       Diff:
       @@ -1,2 +1,20 @@
       -task 001 : hogehoge
       +<!DOCTYPE html>
       +<html>
       +  <head>
       +    <title>Myapp</title>
       +    
       +    
       +
       +    <link rel="stylesheet" media="all" href="/assets/application-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css" data-turbolinks-track="reload" />
       +    <script src="/assets/application-3c2e77f06bf9a01c87fc8ca44294f3d3879d89483d83b66a13a89fc07412dd59.js" data-turbolinks-track="reload"></script>
       +  </head>
       +
       +  <body>
       +    <h1>Your Tasks</h1>
       +
       +<ul>
       +</ul>
       +
       +  </body>
       +</html>

     # ./spec/controllers/index_page_spec.rb:13:in `block (3 levels) in <top (required)>'

なんで..
天才なので閃きました
rails db:seed をしたのは開発環境、 RSpec はテスト環境に対してテストを実行している、つまり、テスト環境には seed データが投入されていない!!!!!!!!
Q.E.D.
俺でなきゃ見逃しちゃうね

..え、そうなの??
まあ、よくわかんねーけど、テスト環境でも rails db:seed してもらえばいいんじゃね?(鼻ほじ)

5.2 VS テスト環境

spec/controllers/index_page_spec.rb
before do
  `rails db:seed`
end

ダメでした

まあ流石にダメな予感はしてました。

With RSpec, how to seed the database on load?

めっちゃそれっぽい!!!!!!

spec/controllers/index_page_spec.rb
before do
  `rails db:reset RAILS_ENV=test`
end

さっきのはニアピンだったんですね。
rails コマンドは ENV=development に向けてやってる見たい
ここでしっかりと「お前はテスト向きにコマンドやりやがれ」と命令してあげる事で、はいわかりましたテスト向きですね。と聞き分けよくやってくれるみたい

それと、毎回 rails db:seed をやると無限にデータが増えるので、 rails db:reset にしました

web
  1) TasksController show index show task list on index page
     Failure/Error: expect(response.body).to eq 'task 001 : hogehoge'

       expected: "task 001 : hogehoge"
            got: "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Myapp</title>\n    \n    \n\n    <link rel=\"styleshee.../li>\n  <li>task 002 : fugafuga</li>\n  <li>task 003 : piyopiyo</li>\n</ul>\n\n  </body>\n</html>\n"

       (compared using ==)

       Diff:
       @@ -1,2 +1,23 @@
       -task 001 : hogehoge
       +<!DOCTYPE html>
       +<html>
       +  <head>
       +    <title>Myapp</title>
       +    
       +    
       +
       +    <link rel="stylesheet" media="all" href="/assets/application-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css" data-turbolinks-track="reload" />
       +    <script src="/assets/application-3c2e77f06bf9a01c87fc8ca44294f3d3879d89483d83b66a13a89fc07412dd59.js" data-turbolinks-track="reload"></script>
       +  </head>
       +
       +  <body>
       +    <h1>Your Tasks</h1>
       +
       +<ul>
       +  <li>task 001 : hogehoge</li>
       +  <li>task 002 : fugafuga</li>
       +  <li>task 003 : piyopiyo</li>
       +</ul>
       +
       +  </body>
       +</html>

     # ./spec/controllers/index_page_spec.rb:17:in `block (3 levels) in <top (required)>'

おおおおおおお!!!入ってるっぽいじゃん!!!

まあただ、全然違う箇所見てるよ、と怒られてしまったので、
expect(response.body).to eq 'task 001 : hogehoge'
この辺をいじる

5.3 データの取得方法

spec/controllers/index_page_spec.rb
it 'show task list on index page' do
      get :index
      expect(response).to render_template('index')
      expect(response.body.ui.li).to include 'task 001 : hogehoge'
    end

すげー適当

web
  1) TasksController show index show task list on index page
     Failure/Error: expect(response.body.ui.li).to include 'task 001 : hogehoge'

     NoMethodError:
       undefined method `ui' for #<String:0x0000561683469d40>
     # ./spec/controllers/index_page_spec.rb:17:in `block (3 levels) in <top (required)>'

だよね。

ということは、これまでの経験的に、画面に表示されている要素を取得するため Gem が用意されているのではないだろうか?という仮説が立つ

capybaraのhave_contentなどで、含まれていてほしい単語の数を指定したい。
ほら
で、次は capybara を調べる
capybara
ほらぁ!!!!

成長を感じている...
そしてググり力も上がってきている...
にっこり。

Gemfile
group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  # Easy installation and use of chromedriver to run system tests with Chrome
  gem 'chromedriver-helper'
end

あれれ??すでにいるんですけど...ドユコト

If the application that you are testing is a Rails app, add this line to your test helper file:
翻訳:
テストしているアプリケーションがRailsアプリケーションの場合は、この行をテストヘルパーファイルに追加します。

へ〜、入ってるだけじゃ使えないのね

spec/controllers/index_page_spec.rb
require 'rails_helper'
require 'capybara/rails'
~~

it 'show task list on index page' do
    get :index
    expect(response).to render_template('index')
    expect(page).to have_content('task 001 : hogehoge')
end

よし、これで使えるわけだ

  1) TasksController show index show task list on index page
     Failure/Error: expect(page).to have_content('task 001 : hogehoge')

     NameError:
       undefined local variable or method `page' for #<RSpec::ExampleGroups::TasksController::ShowIndex:0x0000558ab86d98e8>
     # ./spec/controllers/index_page_spec.rb:19:in `block (3 levels) in <top (required)>'

ダメみたい
そもそもpageって何よって感じに思ってたらそこで怒られた、capybaraを理解する必要がありそう

5.4 VS capybara

いや、長い。まじで

5.4.1 エラー発見しやすくしてみた
it 'show task list on index page' do
  visit '/index'
  get :index
  expect(response).to render_template('index')
  expect(page).to have_content('task 001 : hogehoge')
end

capybara が使えてんのかダメなのか、確認するために
capybara の method かな? visit を最初に書いておく
ここで落ちれば capybara がちゃんと入ってないことが証明されるだろう

1) TasksController show index show task list on index page
     Failure/Error: visit '/index'

     NoMethodError:
       undefined method `visit' for #<RSpec::ExampleGroups::TasksController::ShowIndex:0x00005564b14ad970>

ちなみにダメでした。

5.4.2 requore の記載場所を変えてみた

capybara の公式に

Load RSpec 3.5+ support by adding the following line (typically to your spec_helper.rb file):

spec_helper.rb にかけってって書いてありました
せっかくなのでrails_helper も一緒に引っ越ししましょう。

spec_helper.rb
require 'rails_helper'
require 'capybara/rails'
require 'capybara/rspec'

同じエラー

ちょっとまじできつい、わからない

やろうとしてることが難しいのかなとも思ったけど、
画面に表示されているもの取得するだけなんだから難しくないでしょうに

助けてください。

  • したいこと。
    • index ページできちんとモデルが表示されているかのテスト
    • seed データを入れて、ローカルホストに接続したら seed データが表示された
    • テストでも同じことをしたい
  • わからないこと
    • そもそも開発環境で使っている seeds をテストでも使うのが普通なのか
    • capybara を使う必要があるのか
    • 使うとしたら何が間違っていたのか
    • 使わないならどうしたらいいのか

すごくざっくりした質問になってしまって申し訳ございません。

また、質問をするなら Qiita じゃなくて、ここの方がいいよ、とかあったら教えてください。

5.4.3 敗退

やめました。
capybara 使うのやめました。

it 'show task list on index page' do
  get :index
  expect(response).to render_template('index')
  expect(response.body).to include 'task 001 : hogehoge'
end

これで通りました。
この方向でもっとちゃんと探したい。

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

RubyXLを使ってExcelを編集してクライアントに返す

railsでエクセルを編集してクライアントに返そうとしたときのやり方を残しておきます。

作るもの

  1. クライアントがエクセルファイルをアップロードする
  2. サーバが受け取ったエクセルに何かしらの変更を加える
  3. クライアントが変更されたファイルをダウンロードする

といった動きになる機能をRailsで作ります。
出来上がったソースコードはこちら

Excelの編集

RubyXLというgemを使います。

https://github.com/weshatheleopard/rubyXL

他にもエクセルを開けるgemはあるのですが、

  • Roo: 読み込みのみ
  • AXSLX: 新規作成のみ

という感じで用途が絞られています。
今回は、編集ができるRubyXLを使います。

単体だと、以下のような感じで使えます。

require 'rubyXL'

# ファイルを読み込んでRubyXL::Workbookにデシリアライズ
workbook = RubyXL::Parser.parse("path/to/Excel/file.xlsx")
# ブック→シート→行→セルという構造になっている
worksheet1 = workbook[0]
row1 = worksheet1[0]
cell1 = row1[0]
# 書き込むときはWorksheetのメソッドが使える
worksheet1.add_cell 0, 0, 'changed'
# 保存
workbook.save

rails new

railsプロジェクトを作ります。
今回は横着してモデルを作らないので、ActiveRecordなどいらないモジュールを生成しないようオプションを付けます。

rails new -MOCJ

ルーティング

excelリソースへのルートを作ります。
加えて、ルートをexcelsにします。

下記とおりルーティングされます

  • GET / -> ExcelsController#show
  • GET /excels -> ExcelsController#show
  • POST /excels -> ExcelsController#create

GET /excelsでファイルを受け取るフォームを表示し、POST /excelsでサーバへのファイルアップロードと編集後ファイルの送信を行います。

Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  root to: "excels#show"
  resource :excel, only: [:show, :create]
end

https://github.com/ytnk531/excel-example/blob/master/config/routes.rb

コントローラ

showメソッドでは何もせず、ページを表示するだけです。
createにはエクセルファイルが送られて来るので、ファイルの編集とクライアントへの送信を行います。

class ExcelsController < ApplicationController
 def show
 end

 def create
   file = params[:file]
   # ファイルを開く
   workbook = RubyXL::Parser.parse file.path
   # 編集する
   workbook[0].add_cell 0, 0, 'changed'
   # 編集したファイルを送る
   send_data workbook.stream.string, type: file.content_type, filename: 'modified.xlsx'
 end
end

アップロードされたファイルはUploadedFileオブジェクトとして受け取ることができます。
RubyXL::Parser.parseはFileオブジェクトではなくパスを引数に取るので、アップロードされたファイルのパスを指定してRubyXL::Workbookオブジェクトに変換します。
アップロードされたファイルは、tempfileとして保存されていて、
UploadedFile#pathでこのtempfileのパスを取得できます。

編集したファイルは、send_dataメソッドを使って送信します。

データとして渡すのは、Workbook#streamで取得できるバイナリのストリームを、Stream#stringで文字列にしたものです。
コンテントタイプを指定する必要がありますが、UploadedFileは受信したときのContent-Typeを記録しているので、同じものを指定しておけばいいでしょう。

https://github.com/ytnk531/excel-example/blob/master/app/controllers/excels_controller.rb

ビュー

エクセルファイルをアップロードするためのビューを作ります。

<%= form_tag({action: :create}, multipart: true) do %>
  <div>
  <%= file_field_tag :file %>
  </div>
  <div>
  <%= submit_tag "Send" %>
  </div>
<% end %>

https://github.com/ytnk531/excel-example/blob/master/app/views/excels/show.html.erb

ファイル選択と送信ができます。
送信したファイルはExcelsController#createで処理します。

image.png

試してみる

このようなEXCELファイルを用意し、

image.png

送ってみます。

image.png

編集されたファイルのダウンロードが始まります。

image.png

開いてみます。期待通り編集されたエクセルをダウンロードできました。

image.png

まとめ

  • RubyXLでエクセルの編集ができる
  • アップロードされたファイルはUploadedFileオブジェクトに記録される
  • RubyXLで保存したワークブックは、保存しなくてもデータ送信できる

UploadedFileの存在を知らなかったので、うまくいくかわからなかったのですが、うまくrailsと一緒に使うことができました。

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

selectとoptionタグをjQueryを使って動的に追加する

デモ(動画)

Image from Gyazo

やりたいこと

上記のGIF通り、
カテゴリーが選択されたと同時に、親カテゴリーに紐づく子カテゴリーが出てくるようにしたい。
入れ子構造については以下を参照
【追記予定】awesome_nested_setを使った入れ子構造のDB設計

 実装概要

jQueryのajaxメソッドを使って、カテゴリーが選択されたら子カテゴリーがappendされるようにした。

 コード

routing

route.rb
Rails.application.routes.draw do
  root 'products#new'
  resources :products,  only: [:create] do
<!-- 今回はsearchアクションをajaxメソッドで叩きます-->
    collection do
      get 'search'
    end
  end
end

view

new.html.haml
= form_for @product do |f|
  = f.label :name
  = f.text_field :name, placeholder: "商品名を入力"

  #cat
    = f.select :category_id, Category.roots.map {|i| ["#{i.name}", i.id]}, { selected: @product.category_id, include_blank: true }, {id: "l_category"}

js

category.js
$(document).on('turbolinks:load', function() {

  // Mカテゴリーのselectを追加するHTML
  var cat_seach = $("#cat");

  function appendMselect() {
    var html =  
    `<select name="product[category_id]" id="m_category">
        <option value>---</option>
    </select>`
    cat_seach.append(html)
  }

  // Sカテゴリーのselectを追加するHTML
  function appendSselect() {
    var html =  
    `<select name="product[category_id]" id="s_category">
        <option value>---</option>
    </select>`
    cat_seach.append(html)
  }

  // Mカテゴリーのoptionを追加するHTML
  function appendMcat(m_cat) {
    $("#m_category").append(
      $("<option>")
        .val($(m_cat).attr('id'))
        .text($(m_cat).attr('name'))
    )
  }

  // Sカテゴリーのoptionを追加するHTML
  function appendScat(s_cat) {
    $("#s_category").append(
      $("<option>")
        .val($(s_cat).attr('id'))
        .text($(s_cat).attr('name'))
    )
  }

  // Lカテゴリーが選択された時のアクション
  $("#l_category").on('change', function() {
    l_cat = $(this).val()
    $("#m_category").remove()
    $("#s_category").remove()

    // ajaxでリクエストを送信
      $.ajax({
        type: "GET",
        url: "/products/search",
        data: {l_cat: l_cat},
        dataType: 'json'
      })
      // doneメソッドでappendする
      .done(function(m_cat) {
        appendMselect()
        m_cat.forEach(function(m_cat) {
          appendMcat(m_cat)
        })
      })
    })

// Mカテゴリーが選択された時のアクション
  $(document).on('change', "#m_category", function() {
    m_cat = $(this).val()
    $("#s_category").remove()

    $.ajax({
      type: "GET",
      url: "/products/search",
      data: {m_cat: m_cat},
      dataType: 'json'
    })
    .done(function(s_cat) {
      appendSselect()
      s_cat.forEach(function(s_cat) {
        appendScat(s_cat)
      })
    })
  })
})

controller

categories_controller.rb
<!--関係ないアクションは省略しています-->
  def search
    if params[:l_cat]
      @m_cat = Category.find(params[:l_cat]).children
    else
      @s_cat = Category.find(params[:m_cat]).children
    end
    respond_to do |format|
      format.html
      format.json
    end
  end

jbuilder

search.json.jbuilder
json.array! @m_cat do |m_cat|
  json.id   m_cat.id
  json.name m_cat.name
end

json.array! @s_cat do |s_cat|
  json.id   s_cat.id
  json.name s_cat.name
end

勉強になったところ

optionタグの追加の方法

optionの追加
$("#m_category").append(
      $("<option>")
        .val($(m_cat).attr('id'))
        .text($(m_cat).attr('name'))
    )
  }

こういう風に書くことで、selectタグのoptionが追加できることを知りました。
始めはselectを追加すればいいじゃん!と思ってましたが、配列形式で渡ってくるため、配列の中身分selectが増えてしまい、上手な方法が浮かばなかったので、こちらの方法にしました。

参考記事
・jQuery でセレクトボックスのプルダウン項目(option 要素)を追加/削除する方法
https://webllica.com/jquery-select-option-add-del/

まとめ

なんとか狙った機能は実装できましたが、MカテゴリーとSカテゴリーで二回書いているようなコードになっているので、リファクタリングしたいけどどうしていいのかわからん・・・

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

Failed to load resource: the server responded with a status of 404 (Not Found)の原因と解決策

問題:画像が表示できない

image.png

エラー

Failed to load resource: the server responded with a status of 404 (Not Found)

404 (Not Found)は、ファイルが見つからないという意味。
推定原因は、2つあるらしい。

①ファイルがデータベースに保存されていない
②画像がDBに保存されているけど、パスがずれてる

ファイルは保存できていた。なので、原因は、②。
参照記事:https://teratail.com/questions/3951

パスの確認

views/user/show.html.erb
<img src="<%= "/img/#{@user.id}"%>">

解決策

/img/#{@user.id}に、jpgを付け加えてみたら、パスが特定されて、無事画像が表示された。

views/user/show.html.erb
<img src="<%= "/img/#{@user.id}.jpg"%>">
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ActiveRecordでのレコード数のカウント方法の差(count, length, size)

メソッド 機能 キャッシュ利用
count SQLの COUNT
を使ってカウント
しない
length SQLの 実行結果の行数
をカウント
する
size SQLのCOUNT
を使ってカウント
する

参考

ActiveRecordのcountとlengthとsize

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

[WIP]ActiveRecordでのレコード数のカウント方法の差(count, length, size)

メソッド 機能 キャッシュ利用
count SQLのCOUNTを使ってカウント しない
length SQLの実行結果の行数をカウント する
size SQLのCOUNTを使ってカウント する

参考

ActiveRecordのcountとlengthとsize

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

`require': cannot load such fileエラーをインタプリタ指定から探る ~ファイル1行目のshebang設定~

結論... shebangで指定すべきインタプリタを誤って設定していた。 :sob: ※コメントだとおもっていた件

前提をお伝えすると、
ソースコードのフローチャートを自動化してくれるGemを、ソースコード可視化ツールを作りたかった。
こんなの↓

class_worker.gif
関連記事:Rails要らず、CGIとRubyでソースコードをフローチャートにするツール作成
関連リポジトリ:VisualizeSrc

詰まったところ

Gemを読み込みのためのrequire bundle/setup で「該当ファイルないぞ」って怒られる。
エラー内容は↓

/usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- bundler/setup (LoadError), referer: http://localhost/index.cgi

状況を整理すると

  • 特定の実行Rubyファイル(A)にて、require bundler/setupがロードエラーになっていた.
    • rbenvで環境指定はできている。
    • 同じ環境下の他のRubyファイル(B)は実行は可能
      • かつRubyファイル(B)ではrequire bundler/setupは成功する
      • しかし、Rubyファイル(B)をRubyファイル(A)に取り込んで処理させると、やはり上述のロードエラーになる。
  • 権限問題も解消
    • 全て777に変更してみたが、それでも解決せず。

問題ソースコードはこちら

# 問題ファイル(result.rb)中身
----
  1 #! usr/bin/ruby
  2 require "bundler/setup" ← ここで死んでいた。
  3 require 'cgi'
  4 cgi = CGI.new
  5 print "Content-Type: text/html\n\n"
  6 print "<html>\n"
  7 print "<head>\n"
  8 print "<title>CGI Test</title>\n"
  9 print "</head>\n"
 10 print "<body>\n"
 11 print "<a href=\"/form.cgi\">\"こちらのページから画像が見れます\"</a>"
 12 print "</body>\n"
 13 print "</html>\n"
 14 $src = cgi["sub"]
...

エラー内容を詳しくみてみる。

教えてApache先生 :bow_tone1:
apacheのエラーログを見てみると、(再掲になるが↓)

# /var/log/httpd/error_logのログ
/usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- bundler/setup (LoadError), referer: http://localhost/index.cgi
  • エラーの内容から明らかに、pathに誤りがあることがわかる。
    • 失敗箇所は先に触れているが、指定ファイルがrequireできない、というもの...
    • おそらく指定しているファイル/ロードパスに誤りがある...

心の声(その1)

 ん?でも、同じ環境下に作っているruby fileなのに1つは実行可能で、もう一つは実行不可能ってどういうこと?? Why Ruby!

※ちなみに、/usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55をggるとたくさん、悩んでいる人がいた印象。軒並み検証してみましたが、解決せず。requireのあとに指定しているパスが間違っているよ、というアドバイスが多かった印象でした。

ということで、冷静にみましょう、ロードパス :thinking:

RubyGems Environment:
  - RUBYGEMS VERSION: 3.0.2
  - RUBY VERSION: 2.3.8 (2018-10-18 patchlevel 459) [x86_64-linux]
  - INSTALLATION DIRECTORY: /usr/local/rbenv/versions/2.3.8/lib/ruby/gems/2.3.0
  - USER INSTALLATION DIRECTORY: /root/.gem/ruby/2.3.0
  - RUBY EXECUTABLE: /usr/local/rbenv/versions/2.3.8/bin/ruby
  - GIT EXECUTABLE: /usr/bin/git
  - EXECUTABLE DIRECTORY: /usr/local/rbenv/versions/2.3.8/bin
  - SPEC CACHE DIRECTORY: /root/.gem/specs
  - SYSTEM CONFIGURATION DIRECTORY: /usr/local/rbenv/versions/2.3.8/etc
  - RUBYGEMS PLATFORMS:
    - ruby
    - x86_64-linux
  - GEM PATHS:
     - /usr/local/rbenv/versions/2.3.8/lib/ruby/gems/2.3.0
     - /root/.gem/ruby/2.3.0
  - GEM CONFIGURATION:
     - :update_sources => true
     - :verbose => true
     - :backtrace => false
     - :bulk_threshold => 1000
  - REMOTE SOURCES:
     - https://rubygems.org/
  - SHELL PATH:

心の声(その2)

うん、rbenvで指定できている。。。Why Ruby!

と、もうわからん。頭を抱えているとshebangに出会ったのでした。

天の声(その1)

shebang/シバン(Unix) ってのがあるんだよ??
[引用] シバンまたはシェバン (英: shebang) とはUNIXのスクリプトの #! から始まる1行目のこと。起動してスクリプトを読み込むインタプリタを指定する。ハッシュ・バンまたはシェル・バン、シャープ・バンとも言うが、これらを縮めたシェバンという呼び方が一般的かつ簡素である。*

rbenv指定のrubyとgemが正しく指定できてなかった。shebangの指定先が間違っていた...YES, Ruby!!!

本来使いたかったもの(正解) 誤って設定していたもの
設定したshebang #! /usr/local/rbenv/versions/2.3.8/bin/ruby #! usr/bin/ruby
結果 この配下にgemは存在するので成功する gemをinstallしていないので、ロードエラーになる

本来は#! /usr/local/rbenv/versions/2.3.8/bin/rubyにすべて設定していて、ここを使いたかった。が、問題Rubyファイル(A)では特に関係のない(設定していない)インタプリタを指定してしまっていた。
恥ずかしながら、shebangをずっとコメントだとおもっていたため、原因特定が難しかった。
最終的にはgem envで右記を確認「RUBY EXECUTABLE: /usr/local/rbenv/versions/2.3.8/bin/ruby」して、shebang指定してあげたらOKだった。

[補足] 助けてくれた資料達...thanks!!
[余談] そもそもbundler/setupが何をしているのかは、こちらの記事がわかりやすかった。

お読み頂きありがとうございました。
以上になります。誰かのお役に立てば...
なにか間違っていたり、ベターな方法がある場合はご教示いただけると幸いです。

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

Threadクラスを利用した例外処理

Threadクラスを利用した例外処理の方法に苦戦したので、自分用としてアウトプットします。

Threadクラスとは?

スレッドとはメモリ空間を共有して同時に実行される制御の流れです。 Thread を使うことで並行プログラミングが可能になります。

プログラムは通常処理を記述した順に実行されるので、複数の処理を同時に実行することができません。

しかし、AとBという二つに処理を同時に実行した場合ってありますよね?

そんなとき使用できるのがThreadクラスです。

メインスレッドとカレントスレッド

プログラム開始時に生成されるスレッドはメインスレッド、現在実行中のスレッドはカレントスレッドと呼ばれます。

RubyではThread#mainを用いる事でメインスレッドを確認できます。

また、Thread#listでプログラム上に存在するスレッドが配列で表示されます。

joinメソッド

Thread#joinは、 スレッド self の実行が終了するまで、カレントスレッドを停止させるメソッドです。 limit を指定して、limit 秒過ぎても自身が終了しない場合、nil を返します。

要するに、AとBという二つの処理を同時に実行した際に、片方の処理が終わるまでもう片方の処理を停止させることが出来るメソッドです。

Aという処理が終わるまで、Bという処理を停止させるようなイメージ。

使用方法

①Threadのインスタンスを生成(t1,t2)
②それぞれのThreadに対してrescue文を使用し例外処理を行う
(どちらが先に実行されるのかわからないため)
③rescue文で定義した例外処理のeにe1を代入。
④どちらか片方が例外処理によって抜け出した際の、エラー文とクラス(ActiveRecord::RecordInvalid)が一致しているか?のテストを実施。

require 'rails_helper'

RSpec.describe Ec::ItemImagesRegisterService do

  context 'with 2 images uploading at the same time' do
    let(:ec_item) { FactoryBot.create(:ec_item) }
    let(:ec_item_images_nearest_max) { FactoryBot.create_list(:ec_item_image, 19, ec_item: ec_item) }
    let(:params){ ActionController::Parameters.new( ec_item_id: ec_item.id,
                                                    files:[{ file: File.open(Rails.root.join('spec', 'fixtures', 'sample_image_01.jpg')),
                                                    order: 2}])}

    before do
      ec_item_images_nearest_max
    # ①
      t1 = Thread.new {
        ActiveRecord::Base.connection_pool.with_connection {
          service = described_class.new(params)
          service.run!     
        }
      }
     # ①
      t2 = Thread.new {
        ActiveRecord::Base.connection_pool.with_connection {
          service = described_class.new(params)
          service.run!
        }
      }
    end

    it 'returns invalid with the 21st image' do  
      e1 = nil
      e2 = nil

     # ②
      begin
        t1.join # JoinメソッドはThreadクラスを拡張したクラスのインスタンスから使用できるメソッド。別のスレッドの処理が終了するまで待機させたい処理がある場合に使用
      rescue => e
     # ③
        e1 = e
      end

     # ②
      begin
        t2.join
      rescue => e
     # ③
        e2 = e
      end

     # ④
      if e1.nil?
        expect(e2.class).to eq(ActiveRecord::RecordInvalid)
        expect(e2.record.errors.messages).to match({:file=>["は20枚までしか登録できません。"]})
        expect(ec_item.ec_item_images.count).to eq 20
      end

     # ④
      if e2.nil?
        expect(e1.class).to eq(ActiveRecord::RecordInvalid)
        expect(e1.record.errors.messages).to match({:file=>["は20枚までしか登録できません。"]})
        expect(ec_item.ec_item_images.count).to eq 20
      end
    end
  end
end

【参考記事】
https://docs.ruby-lang.org/ja/latest/class/Thread.html
https://qiita.com/k-penguin-sato/items/1326882c400cac8c109b

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

条件式の統合(Consolidate Conditional Expression)

image.png

1つずつリファクタリング技法まとめ
個人的に簡単かつ取り入れ易いと思うものから

目的

すぐ引き出せるようにする

基本作業サイクル

  • システムを動かして仕様を精査
  • テストメソッドを作成
  • テストの失敗を確認
  • テストの成功を確認
  • 小さい変更、随時テスト実行(パターン追加失敗確認->成功確認)
  • 最後テスト実行
  • 最後動作確認

条件式の統合(Consolidate Conditional Expression)とは

同じ返り値を返す複数の条件式を、メソッドとして抽出してワンライナーにすること
条件式をORやANDでまとめること

ポイント

  • 返り値が同じ

  • OR系
    • 条件式が順に並んでいる
def calorie
  return 0 if @food == 'donut'
  return 0 if @food == 'castella'
  return 0 if @food == 'ice'
end

   ↓

def calorie
  return 0 if @food == 'donut' || @food == 'castella' || @food == 'ice'
end

 ↓
# そうすると条件文の分解ないしメソッドの抽出もできる
def calorie
  return 0 if date?
end

def date?
  @food == 'donut' || @food == 'castella' || @food == 'ice'
end
  • AND系
    • 条件式がネストしている
def calorie
  if @user == 'date'
    if @food == 'donut'
      0
    end
  end
end

   ↓

def calorie
  return 0 if @user == 'date' && @food == 'donut'
end

書籍情報

Jay Fields (著), Shane Harvie (著), Martin Fowler (著), Kent Beck (著),
長尾 高弘(訳), リファクタリング:Rubyエディション
https://amzn.to/2VlyWML

雑感

やってる事は同じなんだけど説明の観点が違う(もしくは包含している)というのがたくさんある

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

ActiveStorageを使って複数画像管理をしてみる

ActiveStorageについて仕事で利用するかも、となったので、少し調べてみた。
その時の情報を簡単にまとめる。

環境

Mac High Sierra
ruby 2.5.1
Rails 5.2.2

実装したこと

複数画像をアップして表示する際の手順を記載してみる。
・index画面に画像一覧を表示させる
・new画面を用意する
バリデーションやエラーチェックなどはあまり気にせず作成
最低限の機能ってことで、一覧と新規追加しか搭載しない

1.rails new

rails newする

rails new as_sample

記事作成時はsqlite3でエラーが出た

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.

ので、Gemfileの編集をする

Gemfile
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.3.6'

2.active_storageインストール、マイグレーション

cd as_sample
rails active_storage:install
rails db:migrate

[マイグレーション結果]
active_storage_blobs,active_storage_attachmentsテーブルが作られる

== 20190301155320 CreateActiveStorageTables: migrating ========================
-- create_table(:active_storage_blobs)
   -> 0.0027s
-- create_table(:active_storage_attachments)
   -> 0.0124s
== 20190301155320 CreateActiveStorageTables: migrated (0.0153s) ===============

3.モデル作成、画像関連づけ

rails g model item name:string
rake db:migrate
app/models/item.rb
class Item < ApplicationRecord
  has_many_attached :images
end

4.コントローラー作成

rails g controller items

5.ルーティング設定

config/routes.rb
Rails.application.routes.draw do
  resources :items
end

6.コントローラー設定

app/controller/items_controller.rb
class ItemsController < ApplicationController

  # item一覧
  def index
    @items = Item.all
  end

  # item新規作成
  def new
    @item = Item.new
  end

  # item作成
  def create
    @item = Item.new(item_params)

    if @item.save
      redirect_to items_path
    else
      render :new
    end
  end

  # item削除
  def destroy
    @item = Item.find(params[:id])
    @item.destroy
    redirect_to items_path
  end

  private

  def item_params
    params.require(:item).permit(:name, images: [])
  end
end

7.ビューの設定

一覧と新規作成画面を用意する。

一覧画面

app/views/items/index.html.erb
<h1>Items Index</h1>
<%= link_to 'Add item', new_item_path %>

<ul>
  <% @items.each do |item| %>
    <li>
      <p><%= item.name %></p>
      <%= link_to 'Destroy', item, method: :delete, data: { confirm: 'Are you sure?' } %>
      <% item.images.each do |image| %>
        <%= image_tag(image, width:100) %>
      <% end %>
    </li>
  <% end %>
</ul>

画像のサイズは一旦width:100で設定

新規作成画面

app/views/items/new.html.erb
<h1>Add Item</h1>

<%= form_with model: @item do |form| %>
  <div>
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>

  <div>
    <%= form.label :images %>
    <%= form.file_field :images, multiple: true %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>

8.ローカルサーバー起動

rails s

8-1. http://localhost:3000/items にアクセス

スクリーンショット 2019-03-02 1.26.29.png

8-2.item新規作成で画像を選択

ファイル選択から画像ファイル2つを選択した状態
スクリーンショット 2019-03-02 1.29.33.png

8-3.Create_itemして一覧に遷移した後

スクリーンショット 2019-03-02 1.29.42.png

とりあえず、想定した動作を作ることができた。

images: []に入るパラメータ

viewで選択したimagesはcontroller側にどんな値で渡されているかを確認してみる。
個人的に見やすくするために改行する。

# ActionDispatch::Http::UploadedFile クラスのインスタンス

"images"=>
[
#<ActionDispatch::Http::UploadedFile:0x00007fa524251a18 
@tempfile=#<Tempfile:/var/folders/nf/0ssmnnv545d65f2xd8fcmz2cnr376k/T/RackMultipart2019xxxx-51778-hpwm2k.jpg>,
@original_filename="hogehoge.jpg", 
@content_type="image/jpeg", 
@headers="Content-Disposition: form-data; name=\"image[images][]\"; filename=\"hogehoge.jpg\"\r\nContent-Type: image/jpeg\r\n">
, 
#<ActionDispatch::Http::UploadedFile:0x00007fa5242519c8 @tempfile=#<Tempfile:/var/folders/nf/0ssmnnv545d65f2xd8fcmz2cnr376k/T/RackMultipart2019xxxx-51778-6nsna7.jpg>,
@original_filename="fugafuga.jpg",
@content_type="image/jpeg",
@headers="Content-Disposition: form-data; name=\"image[images][]\"; filename=\"fugafuga.jpg\"\r\nContent-Type: image/jpeg\r\n">
]

それぞれの情報は以下のようになる

@tempfile : 画像データ情報(パス情報?)
@content_type: 画像の形式 (jpg,pngとか)
@original_filename : ファイル名情報
@headers: ヘッダーに関する情報 (データ形式など)

コード

簡単な動きと渡されるパラメータについて少し知れたので、
次はVueを使って画像登録処理を実装してみようと思う。

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

Rails要らず、CGIとRubyでソースコードをフローチャートにするツール作成

きっかけは怠惰なお気持ちでした。ということで、こんなの作ってみました。よかったら、ご覧ください。

要は、フォームに入力されたRubyのソースコードを、フローチャートにしてくれる、というもの。

class_worker.gif

具体的には、以下のソースコードが

class Worker
    def initialize(hungry:)
      @hungry = hungry
    end

    def next_action
      if hungry?
        :eat
      else
        :work
      end
    end

    def hungry?
      @hungry
    end
  end

こんな画像になって出てくる。

source_code.png
ソースコードのフローチャート化のために、RubyのGemvisualize_rubyとグラフ描画してくれるGraphvizを使っています。

きっかけは怠惰なお気持ちでした(T_T)

  • 業務の中で仕様書になっていないソースコードを読んで、読んで、理解して、フローチャートにして、要件詰めて、また読んでが面倒に思えた。
    • 読むだけなら、まだいいんですが、他の人と共有とかを考えると可視化していく必要があった
    • 1回ならいいんだけれど、いろんなところでそれが発生していた
    • どんどん変化していく類のものだったときに、仕様書にしてもメンテされない
あと普段Railsを使うことが多いのですが、乳離というか、Rails離れもしたいというお気持ちでした
  • Railsにはいつもお世話になっており、細かいことを気にしなくても、ワタシが気持ちよく働ける環境を作ってくれています
    • が、いっぽうで、それゆえに根本的なRubyという言語やWebサービスがどう動いているのかの理解が浅いままになっちゃているな、とも思うようになった。
    • ミニマムでやりたいことだけをやるとしたときに、なにがあればいいんだろうかっていうのも知りたかった。恥ずかしながら、たぶんRailsなしでは飛べない小鳥ですので。
フローチャートに自動でしてくれるやついないかなって探していたら、街角でばったり出くわしたのがvisualize_rubyでした。
  • 有り難いと思いながら、卑屈な人間なので
    • 簡単に自分の環境で実装ができたのですが、環境依存とかで設定が面倒だなって思い
    • コンソールからイジルの、絶対みんなできないよ〜って思い

そうだ!環境に依存しないDockerでRailsを使わない(CGIとRuby)でブラウザからフォーム入力ができて、描画してくれるツールを作ってみようと決心したのでした。

こちらが該当のリポジトリ、名付けてVisualizeSrc!!
ざっくり取り上げると、以下のような構成で作ってみました。詳細な中身はリポジトリをご覧ください。基本的には、リポジトリを各人のローカル開発環境にpullしてもらって、ビルドしてもらえればlocalhostのport80で動くような設定をしています。

ファイル構成

/var配下の処理スクリプト達
├── www
│   ├── cgi-bin
│   │   ├── Gemfile
│   │   ├── Gemfile.lock
│   │   ├── result.rb ...フォームから値を受け取ってモジュールに渡す
│   │   ├── source_code.png ...作成される画像データ
│   │   └── visualize_mod.rb ...画像を作るモジュール
│   └── html
│       └── form.cgi ...フォームから入力値を受け取る

/etc配下の設定ファイル達
├── conf
│   ├── httpd.conf ... PortやAliasの設定
├── conf.d
│   ├── cgi-enabled.conf ... CIGでRubyファイルが処理できるように設定

ちなみにrbenvを使って管理しており、Gemは以下のパスへ
/usr/local/rbenv/versions/2.3.8/lib/ruby/gems/2.3.0/gems/

環境

- Dockerfile
- CENTOS:7
- Apache
    - 変更した設定(Dockerでイメージビルドするときに差し替えました)
        - httpd.conf
        - cgi-enabled.conf
- Ruby:2.3.8
    - 使ったライブラリ
        - parser
        - visualize_ruby
- 詳細はDockerfileを!

使い方はこんな感じ。

1. 該当リポジトリを各環境へpull 

git pull https://github.com/taishinagasaki/visualize_src.git

2. Docker imageの作成 

docker build -t repository_name/image_name:tag_name . --no-cache=true

3. できたイメージを確認

docker images

4. dockerのイメージは以下の特権モードで起動する。ポートは80と80を繋げる。 

docker run --privileged -d -p 80:80 --name container_name repository_name/image_name:tag_name /sbin/init

5. containerが立ち上がっていることを確認する。statusがUPになればOK。 

docker start <container_id>

6. 立ち上がっていればcontainer idを指定して起動 

docker exec -it <container_id> /bin/bash

7. apacheの起動 

systemctl start httpd

8. apacheの起動を確認 

systemctl start httpd

9. ブラウザから localhost/form.cgiへアクセス
使い方は該当リポジトリのREADME.mdに記載しております。

各工程と、助けてくれた資料達

作業内容 資料名
ApacheのインストールからCGI設定まで Apache httpd : インストール設定
RubyをCGIで動かす設定まで Apache httpd : Ruby を利用する
フォーム入力値をRubyファイルで受け取る [Linux][Ruby]ApacheでRubyのCGIを動かす方法
Dockerでsystemctlを使えるようにする CentOS 7のDockerコンテナ内でsystemdを使ってサービスを起動する
Dockerでイメージ作ってコンテナを立ち上げる Dockerコマンドメモ

ハマったこと

Shebangという、インタプリタの指定にハマってしまいました。

シバンまたはシェバン (英: shebang) とはUNIXのスクリプトの #! から始まる1行目のこと。起動してスクリプトを読み込むインタプリタを指定する。

恥ずかしながらShebagの存在すら知らず、インストールしたgemを一括で取り込むためにrequire 'bundler/setup'を実行しても延々とエラーが出続ける、というもので、これに関してはデバッグ方法もわからずでした。ググってもなかなかなく、根本的な理解がなってないな〜ということを改めて知りました。
また後日、投稿したいと思います。
[追記: 2019/03/02] `require': cannot load such fileエラーをインタプリタ指定から探る ~ファイル1行目のshebang設定~...shebang設定について言及

まだできてないこと/TODO

  • Web化してもいいかなっておもったんですがセキュリティ周りの設定が間に合わず(怖かったので)、またの機会に持ち越しとしました。
  • DockerfileのCMD複数指定を実現する。
    • CMDでshell scriptを指定してあげるとできるとおもっていたが、できず。
    • どうにかやってみたい。
  • VIMで環境を整えたい。

学び

  • Railsから離れたり、いつも使っている高機能エディタから離れることでより根本理解が深まった印象。
  • MW(Apache)やOS層も普段は殆ど触らないので勉強することが多かった。
  • 実はQiita投稿も初めてなので、その他「初めて」が多くてよかった。

次にやりたいこと

  • TODOを潰す
  • Dappに触ってみる

お読み頂きありがとうございました。
なにか間違っていたり、ベターな方法がある場合はご教示いただけると幸いです。

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