20200208のRubyに関する記事は13件です。

Ruby用2Dゲームライブラリ「DXRuby」使い方の初歩

概要

この記事は中高生向けプログラミング教室の教材として作ったものを一部改変したものです。
Rubyでプログラミングの初歩を学んだ次のステップとして、ゲームライブラリDXRubyの使い方を学んでいきます。

技術解説

使用ライブラリ

Windows向けRuby用2DゲームライブラリDXRubyを使用します。
バージョン1.4.2以上を想定しています。

1.4.2より前のバージョンとの主な相違点

  • Window.loopが複数置ける

  • マウス位置を取得するInput.mouse_pos_xInput.mouse_pos_yの新しい書き方として、Input.mouse_xInput.mouse_yが追加

→DXRuby 1.4.6:更新履歴
http://mirichi.github.io/dxruby-doc/CHANGELOG.html

DXRubyインストールの注意点

→ DXRuby 1.4.6 をWindows10で使う時の注意点とインストール方法 - noanoa07 - Qiita
https://qiita.com/noanoa07/items/0ce14c2404df38de94b7

参考サイト

困ったときは、このページの「チュートリアル」と「マニュアル」にだいたい書いてあります。

古いバージョンのリファレンスも役に立つときがあります。

このテキストのブログ記事での解説です。

ライセンス

ソースコード、本解説ともにパブリックドメイン

プログラム解説

手順

1. DXRubyの練習

DXRubyの基本の使い方を練習します。

使い方を学んだ後は、「ブロック崩し」を作っていくのを目標にしています。

1-1. DXRubyのウィンドウを出す(dx01.rb)

DXRubyの基本の形です。
大きさを指定しないと、横640、縦480のウィンドウが開きます(左上の座標が(0, 0))。

Window.loop do 〜 end の間に書いたコードは、1秒間に60回繰り返し実行されます。

→DXRubyリファレンス:チュートリアル 1. 基本の形
http://mirichi.github.io/dxruby-doc/tutorial/basic.html

require 'dxruby'

Window.loop do

end

dx01.png

1-2. ウィンドウの大きさを変える(dx02.rb)

ウィンドウの大きさを変えるには、Window.width =Window.height =を使います。

プログラムの内、最初の1回だけ実行されて以後変える必要のないものは、Window.loop do 〜 end より前に書いておきます。

→DXRubyリファレンス:API INDEX;module Window
http://mirichi.github.io/dxruby-doc/api/Window.html

require 'dxruby'

Window.width  = 400
Window.height = 300

Window.loop do

end

dx02.png

1-3. ウィンドウの背景の色を変える(dx03.rb)

ウィンドウの背景の色を変えるには、Window.bgcolor =を使います。

色の指定は、「DXRubyリファレンス:色配列と色定数について」
http://mirichi.github.io/dxruby-doc/api/constant_color.html

を見てください。

→DXRubyリファレンス:API INDEX;module Window
http://mirichi.github.io/dxruby-doc/api/Window.html

require 'dxruby'

Window.bgcolor = C_CYAN

Window.loop do

end

dx03.png

1-4. タイトルを表示する;文字(dx04.rb)

ウィンドウの左上にタイトルを表示するには、Window.caption =を使います。

→DXRubyリファレンス:API INDEX;module Window
http://mirichi.github.io/dxruby-doc/api/Window.html

require 'dxruby'

Window.caption = "ブロック崩し"

Window.loop do

end

dx04.png

1-5. 画面に文字を出す(dx05.rb)

画面に文字を出すには、

font = Font.new(文字サイズ) で文字サイズ(とフォント名)を指定しておいて、

Window.draw_font(x位置, y位置, "文字列", フォント, {:color => 色})

で表示します。

→DXRubyリファレンス:チュートリアル 9. 文字の描画
http://mirichi.github.io/dxruby-doc/tutorial/basic.html

→DXRubyリファレンス:API INDEX;Font.new

http://mirichi.github.io/dxruby-doc/api/Font_new.html

require 'dxruby'

font = Font.new(32)

Window.loop do
  Window.draw_font(200, 100, "ブロック崩し", font, {:color => C_GREEN})
end


dx05.png

1-6. キー入力をとらえる(dx06.rb)

キー入力はInput.key_down?(キーコード定数)を使います。

キーコード定数については、「DXリファレンス:キーコード定数」
http://mirichi.github.io/dxruby-doc/api/constant_keycode.html

を見てください。

→DXRubyリファレンス:チュートリアル 8. キーボードの入力
http://mirichi.github.io/dxruby-doc/tutorial/basic.html

require 'dxruby'

font = Font.new(32)

Window.loop do
  Window.draw_font(200, 100, "ESCキーで終了します", font, {:color => C_GREEN})

  if Input.key_down?(K_ESCAPE)
    exit                 # exit でプログラムを終了する
  end
end

dx06.png

1-7. ウィンドウを切り替える(dx07.rb)

DXRuby 1.4.2 から、Window.loop do 〜 end が複数置けるようになったので、breakでループを抜けると次のWindow.loop do 〜 endが始まり、別のウィンドウを表示できます。

→Window.loopを複数置く - mirichiの日記 2014-11-24
https://mirichi.hatenadiary.org/entry/20141124/p1

require 'dxruby'

font = Font.new(32)

Window.loop do
  Window.draw_font(200, 100, "Nキーで次の画面へ", font, {:color => C_WHITE})

  if Input.key_down?(K_N)
    break                # breakで loopを抜ける
  end
end


Window.loop do
  Window.draw_font(200, 100, "ブロック崩し", font, {:color => C_GREEN})

  Window.draw_font(200, 150, "(ESCキーで終了)", font, {:color => C_RED})

  if Input.key_down?(K_ESCAPE)
    exit                 # exit でプログラムを終了する
  end
end

dx07a.png

dx07b.png

1-8. 画像の読み込み(dx08.rb)

画像ファイルの読み込みは、Image.load(画像ファイル)を使います。対応するファイル形式は、jpgpngbmpです。

読み込んだ画像はイメージになります。

→DXRubyリファレンス:チュートリアル 2. 画像の読み込みと描画
http://mirichi.github.io/dxruby-doc/tutorial/basic.html

→DXRubyリファレンス:API INDEX;Image.load
http://mirichi.github.io/dxruby-doc/api/Image_load.html

require 'dxruby'

image = Image.load('../image/apple.png')

Window.loop do
  Window.draw(100, 100, image)
end

dx08.png

1-9. 画像の背景色の透明化(dx09.rb)

イメージでset_color_key(色)を使うと、指定した色を透明にできます。

→DXRubyリファレンス:Image;set_color_key
http://mirichi.github.io/dxruby-doc/api/Image_23set_color_key.html

require 'dxruby'

image = Image.load('../image/apple.png')
image.set_color_key(C_WHITE)

Window.loop do
  Window.draw(100, 100, image)
end

dx09.png

1-10. 画像の拡大・縮小(dx10.rb)

イメージを拡大・縮小して表示したい時は、Window.draw_scale(x位置, y位置, イメージ, 横の拡大率, 縦の拡大率)を使います。

→DXRubyリファレンス:API INDEX;Image;Window.draw_scale
http://mirichi.github.io/dxruby-doc/api/Window_draw_scale.html

require 'dxruby'

image = Image.load('../image/apple.png')
image.set_color_key(C_WHITE)

Window.loop do
  Window.draw_scale(100, 100, image, 0.2, 0.2)
end

dx10.png

1-11. トウフを表示(イメージで表示)(dx11.rb)

画像は元の画像ファイルがなくても、作ることもできます。Image.new(横幅, 縦幅, 色) で、四角形のイメージを作ります。
(白い四角形の見た目で豆腐(トウフ)

表示するには、Window.draw(x位置, y位置, イメージ)を使います。位置の基準はイメージの左上になります。

→DXRubyリファレンス:API INDEX;class Image
http://mirichi.github.io/dxruby-doc/api/Image.html

→DXRubyリファレンス:API INDEX;Window.draw
http://mirichi.github.io/dxruby-doc/api/Window_draw.html

require 'dxruby'

img_tohu = Image.new(100, 100, C_WHITE)

Window.loop do
  Window.draw(200, 200, img_tohu)
end

dx11.png

1-12. トウフを表示(スプライトで表示)(dx12.rb)

DXRubyには画像を扱うのに、もう一つスプライトというクラスがあります。スプライトは、x位置y位置のデータを自分で持ち、描画メソッドも独自に持っており、衝突判定もできるのが特徴です。そのため、「ブロック崩し」ではスプライトを主に使います。

スプライトは、Sprite.new(x位置, y位置, イメージ)
で作ります。(イメージはあらかじめ作っておきます。)

表示させるのは、drawです。

→DXRubyリファレンス:Spriteを使うためのチュートリアル
http://mirichi.github.io/dxruby-doc/tutorial/sprite.html

→DXRubyリファレンス:API INDEX;class Sprite
http://mirichi.github.io/dxruby-doc/api/Sprite.html

require 'dxruby'

img_tohu = Image.new(100, 100, C_WHITE)

tohu = Sprite.new(200, 200, img_tohu)

Window.loop do
  tohu.draw
end

dx12.png

1-13. トウフの中に赤丸を描く(dx13.rb)

丸を描くには、イメージにcircle_fill(中心のx位置, 中心のy位置, 半径, 色)を使います。中心の位置の座標は、(ウィンドウの位置ではなく)そのイメージの左上が(0, 0)になります。

→DXRubyリファレンス:API INDEX;Image#circle_fill
http://mirichi.github.io/dxruby-doc/api/Image_23circle_fill.html

require 'dxruby'

img_tohu = Image.new(100, 100, C_WHITE).circle_fill(50, 50, 50, C_RED)

tohu = Sprite.new(200, 200, img_tohu)

Window.loop do
  tohu.draw
end

dx13.png

1-14. 赤丸だけにする(dx14.rb)

Image.new(横幅, 縦幅, 色)の指定を省略すると、透明色が指定されたことになり、見えなくなります。

→DXRuby1.4.1 リファレンス:Imageクラス;Image.new
http://dxruby.osdn.jp/DXRubyReference/200953184038328.htm

require 'dxruby'

img_ball = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)

ball = Sprite.new(200, 200, img_ball)

Window.loop do
  ball.draw
end

dx14.png

1-15. カーソルキーで移動(dx15.rb)

カーソルキーが押されると、左右方向はInput.x、上下方向はInput.y-1, 0, 1 に変化します。

これを利用することで、画面上を移動させることができます。

→ DXRuby 1.4.1 リファレンス:Inputモジュール
http://dxruby.osdn.jp/DXRubyReference/200953184011156.htm

require 'dxruby'

img_ball = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)

ball = Sprite.new(0, 0, img_ball)
ball.x = 200
ball.y = 200

Window.loop do
  ball.x = ball.x + Input.x
  ball.y = ball.y + Input.y

  ball.draw
end

dx15.png

1-16. カーソルキーで移動(別の書き方)(dx16.rb)

ball.x = ball.x + Input.x を別の書き方である、

ball.x += Input.x に書き換えてみます。同じ意味ですが、こちらの方が文字数が少なくて済むので、間違いが減らせるかも。

require 'dxruby'

img_ball = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)

ball = Sprite.new(0, 0, img_ball)
ball.x = 200
ball.y = 200

Window.loop do
  ball.x += Input.x
  ball.y += Input.y

  ball.draw
end

dx16.png

1-17. マウスに合わせて移動(dx17.rb)

マウスのx座標、y座標はそれぞれmouse_pos_xmouse_pos_yで取得できます。
(DXRuby 1.4.2 からは、mouse_xmouse_yという書き方もできます。)

→DXRubyリファレンス:API INDEX;module Input
http://mirichi.github.io/dxruby-doc/api/Input.html

→DXRubyリファレンス:API INDEX;Input.mouse_pos_x
http://mirichi.github.io/dxruby-doc/api/Input_23mouse_pos_x.html

require 'dxruby'

img_ball = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)

ball = Sprite.new(0, 0, img_ball)

Window.loop do
  ball.x = Input.mouse_pos_x
  ball.y = Input.mouse_pos_y

  ball.draw
end

dx17.png

1-18. 画像を切り替える(dx18.rb)

スプライトの画像イメージは、スプライト.image = で変更できます。

ここでは、x位置が300を超えるかどうかで、画像が変わるようにしています。

→DXRubyリファレンス:API INDEX;Sprite.image =
http://mirichi.github.io/dxruby-doc/api/Sprite_23image_3D.html

require 'dxruby'

img_ball = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)
img_tohu = Image.new(100, 100, C_WHITE)

ball  = Sprite.new(0, 0, img_ball)

Window.loop do
  ball.x = Input.mouse_pos_x
  ball.y = Input.mouse_pos_y

  if ball.x > 300
    ball.image = img_ball
  else
    ball.image = img_tohu
  end

  ball.draw
end

dx18a.png

dx18b.png

1-19. 2つの画像を表示する(dx19.rb)

ひとつの画像は位置が固定で、もうひとつはマウスに追従します。

require 'dxruby'

img_ball  = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)
img_tohu = Image.new(100, 100, C_WHITE)

ball  = Sprite.new(0, 0, img_ball)
tohu  = Sprite.new(100, 100, img_tohu)

Window.loop do
  ball.x = Input.mouse_pos_x
  ball.y = Input.mouse_pos_y

  ball.draw
  tohu.draw
end

dx19.png

1-20. 画像をまとめて表示する(dx20.rb)

複数のスプライトを配列にまとめると、ひとつの命令Sprite.drawで表示させることができます。

ここでは、スプライトballとスプライトtohuを配列imgsにまとめて、Sprite.draw(imgs)で一気に表示させています。

→DXRubyリファレンス:Spriteを使うためのチュートリアル
http://mirichi.github.io/dxruby-doc/tutorial/sprite.html

→DXRubyリファレンス:API INDEX;Sprite.draw
http://mirichi.github.io/dxruby-doc/api/Sprite_draw.html

require 'dxruby'

img_ball  = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)
img_tohu = Image.new(100, 100, C_WHITE)

ball  = Sprite.new(0, 0, img_ball)
tohu  = Sprite.new(100, 100, img_tohu)

imgs = [ball, tohu]

Window.loop do
  ball.x = Input.mouse_pos_x
  ball.y = Input.mouse_pos_y

  Sprite.draw(imgs)
end

dx20.png

1-21. 衝突判定1;===(dx21.rb)

スプライトは衝突したかを判定する===があります。
衝突しているしていないtruefalseで返します。

→DXRubyリファレンス:Spriteを使うためのチュートリアル;衝突判定
http://mirichi.github.io/dxruby-doc/tutorial/sprite.html

→DXRubyリファレンス:API INDEX;Sprite;===
http://mirichi.github.io/dxruby-doc/api/Sprite_23_3D_3D_3D.html

require 'dxruby'

img_ball  = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)
img_tohu = Image.new(100, 100, C_WHITE)

ball  = Sprite.new(0, 0, img_ball)
tohu  = Sprite.new(100, 100, img_tohu)

imgs = [ball, tohu]

font = Font.new(24)

Window.loop do
  ball.x = Input.mouse_pos_x
  ball.y = Input.mouse_pos_y

  if ball === tohu
    Window.draw_font(200, 300, "衝突!", font)
  end

  Sprite.draw(imgs)
end

dx21.png

1-22. 衝突判定2;check(dx22.rb)

スプライトの衝突判定===では、衝突の有無は分かりますが、衝突した相手は分かりません。そんな時は、checkを使います。checkは衝突している相手を配列で返します。

配列をcollとすると(collision:衝突)、0番目のcoll[0]に何か入っていれば衝突してるし、ならば衝突していないという、衝突判定にも使えます。

→DXRubyリファレンス:Spriteを使うためのチュートリアル;衝突したオブジェクトを取得する
http://mirichi.github.io/dxruby-doc/tutorial/sprite.html

→DXRubyリファレンス:API INDEX;Sprite;check
http://mirichi.github.io/dxruby-doc/api/Sprite_23check.html

require 'dxruby'

img_ball  = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)
img_tohu   = Image.new(100, 100, C_WHITE)
img_tohu_r = Image.new(100, 100, C_RED)


ball   = Sprite.new(  0,   0, img_ball)
tohu1  = Sprite.new(150, 150, img_tohu)
tohu2  = Sprite.new(300, 150, img_tohu)
tohu3  = Sprite.new(300, 300, img_tohu)

blocks = [tohu1, tohu2, tohu3]

font = Font.new(24)

Window.loop do
  ball.x = Input.mouse_pos_x
  ball.y = Input.mouse_pos_y

  coll = ball.check(blocks)
  if coll[0]
    coll[0].image = img_tohu_r
  end

  Window.draw_font(200, 10, "#{blocks.size}個中、#{coll.size}個 衝突!", font)

  Sprite.draw(blocks)
  ball.draw
end

dx22.png

1-23. 衝突判定3;check後に、vanish, clean(dx23.rb)

vanishは、checkで衝突していると判定されたスプライトを無効化します。無効化されたスプライトは描画されません。ただし、無効化されただけで、そのスプライトは削除された訳ではありません。

衝突したスプライトを削除したい時は、Sprite.clean(配列)を使います。これは、配列内のスプライトで無効化されているものを配列から削除します。まだ衝突していないスプライトの数を知りたい時などに使えます。

→DXRubyリファレンス:Spriteを使うためのチュートリアル;ライフサイクルを管理する
http://mirichi.github.io/dxruby-doc/tutorial/sprite.htmlSpriteの

→DXRubyリファレンス:API INDEX;Sprite;vanish
http://mirichi.github.io/dxruby-doc/api/Sprite_23vanish.html

→DXRubyリファレンス:API INDEX;Sprite.clean
http://mirichi.github.io/dxruby-doc/api/Sprite_clean.html

require 'dxruby'

img_ball  = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)
img_tohu   = Image.new(100, 100, C_WHITE)


ball   = Sprite.new(  0,   0, img_ball)
tohu1  = Sprite.new(150, 150, img_tohu)
tohu2  = Sprite.new(300, 150, img_tohu)
tohu3  = Sprite.new(300, 300, img_tohu)

blocks = [tohu1, tohu2, tohu3]

font = Font.new(24)

Window.loop do
  ball.x = Input.mouse_pos_x
  ball.y = Input.mouse_pos_y

  coll = ball.check(blocks)
  if coll[0]
    coll[0].vanish
  end

  Sprite.clean(blocks)

  Window.draw_font(200, 10, "#{blocks.size}個中、#{coll.size}個 衝突!", font)

  Sprite.draw(blocks)
  ball.draw
end

dx23a.png

dx23b.png

dx23c.png

これで、DXRubyの練習は終わりです。
次は、「ブロック崩し」を作っていきます。

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

Ruby用2Dゲームライブラリ DXRuby:使い方の初歩

概要

この記事は中高生向けプログラミング教室の教材として作ったものを一部改変したものです。
Rubyでプログラミングの初歩を学んだ次のステップとして、ゲームライブラリDXRubyの使い方を学んでいきます。

技術解説

使用ライブラリ

Windows向けRuby用2DゲームライブラリDXRubyを使用します。
バージョン1.4.2以上を想定しています。

1.4.2より前のバージョンとの主な相違点

  • Window.loopが複数置ける

  • マウス位置を取得するInput.mouse_pos_xInput.mouse_pos_yの新しい書き方として、Input.mouse_xInput.mouse_yが追加

→DXRuby 1.4.6:更新履歴
http://mirichi.github.io/dxruby-doc/CHANGELOG.html

DXRubyインストールの注意点

→ DXRuby 1.4.6 をWindows10で使う時の注意点とインストール方法 - noanoa07 - Qiita
https://qiita.com/noanoa07/items/0ce14c2404df38de94b7

参考サイト

困ったときは、このページの「チュートリアル」と「マニュアル」にだいたい書いてあります。

古いバージョンのリファレンスも役に立つときがあります。

このテキストのブログ記事での解説です。

ライセンス

ソースコード、本解説ともにパブリックドメイン

プログラム解説

手順

1. DXRubyの練習

DXRubyの基本の使い方を練習します。

使い方を学んだ後は、「ブロック崩し」を作っていくのを目標にしています。

1-1. DXRubyのウィンドウを出す(dx01.rb)

DXRubyの基本の形です。
大きさを指定しないと、横640、縦480のウィンドウが開きます(左上の座標が(0, 0))。

Window.loop do 〜 end の間に書いたコードは、1秒間に60回繰り返し実行されます。

→DXRubyリファレンス:チュートリアル 1. 基本の形
http://mirichi.github.io/dxruby-doc/tutorial/basic.html

dx01.rb
require 'dxruby'

Window.loop do

end

dx01.png

1-2. ウィンドウの大きさを変える(dx02.rb)

ウィンドウの大きさを変えるには、Window.width =Window.height =を使います。

プログラムの内、最初の1回だけ実行されて以後変える必要のないものは、Window.loop do 〜 end より前に書いておきます。

→DXRubyリファレンス:API INDEX;module Window
http://mirichi.github.io/dxruby-doc/api/Window.html

dx02.rb
require 'dxruby'

Window.width  = 400
Window.height = 300

Window.loop do

end

dx02.png

1-3. ウィンドウの背景の色を変える(dx03.rb)

ウィンドウの背景の色を変えるには、Window.bgcolor =を使います。

色の指定は、「DXRubyリファレンス:色配列と色定数について」
http://mirichi.github.io/dxruby-doc/api/constant_color.html

を見てください。

→DXRubyリファレンス:API INDEX;module Window
http://mirichi.github.io/dxruby-doc/api/Window.html

dx03.rb
require 'dxruby'

Window.bgcolor = C_CYAN

Window.loop do

end

dx03.png

1-4. タイトルを表示する;文字(dx04.rb)

ウィンドウの左上にタイトルを表示するには、Window.caption =を使います。

→DXRubyリファレンス:API INDEX;module Window
http://mirichi.github.io/dxruby-doc/api/Window.html

dx04.rb
require 'dxruby'

Window.caption = "ブロック崩し"

Window.loop do

end

dx04.png

1-5. 画面に文字を出す(dx05.rb)

画面に文字を出すには、

font = Font.new(文字サイズ) で文字サイズ(とフォント名)を指定しておいて、

Window.draw_font(x位置, y位置, "文字列", フォント, {:color => 色})

で表示します。

→DXRubyリファレンス:チュートリアル 9. 文字の描画
http://mirichi.github.io/dxruby-doc/tutorial/basic.html

→DXRubyリファレンス:API INDEX;Font.new

http://mirichi.github.io/dxruby-doc/api/Font_new.html

dx05.rb
require 'dxruby'

font = Font.new(32)

Window.loop do
  Window.draw_font(200, 100, "ブロック崩し", font, {:color => C_GREEN})
end


dx05.png

1-6. キー入力をとらえる(dx06.rb)

キー入力はInput.key_down?(キーコード定数)を使います。

キーコード定数については、「DXリファレンス:キーコード定数」
http://mirichi.github.io/dxruby-doc/api/constant_keycode.html

を見てください。

→DXRubyリファレンス:チュートリアル 8. キーボードの入力
http://mirichi.github.io/dxruby-doc/tutorial/basic.html

dx06.rb
require 'dxruby'

font = Font.new(32)

Window.loop do
  Window.draw_font(200, 100, "ESCキーで終了します", font, {:color => C_GREEN})

  if Input.key_down?(K_ESCAPE)
    exit                 # exit でプログラムを終了する
  end
end

dx06.png

1-7. ウィンドウを切り替える(dx07.rb)

DXRuby 1.4.2 から、Window.loop do 〜 end が複数置けるようになったので、breakでループを抜けると次のWindow.loop do 〜 endが始まり、別のウィンドウを表示できます。

→Window.loopを複数置く - mirichiの日記 2014-11-24
https://mirichi.hatenadiary.org/entry/20141124/p1

dx07.rb
require 'dxruby'

font = Font.new(32)

Window.loop do
  Window.draw_font(200, 100, "Nキーで次の画面へ", font, {:color => C_WHITE})

  if Input.key_down?(K_N)
    break                # breakで loopを抜ける
  end
end


Window.loop do
  Window.draw_font(200, 100, "ブロック崩し", font, {:color => C_GREEN})

  Window.draw_font(200, 150, "(ESCキーで終了)", font, {:color => C_RED})

  if Input.key_down?(K_ESCAPE)
    exit                 # exit でプログラムを終了する
  end
end

dx07a.png

dx07b.png

1-8. 画像の読み込み(dx08.rb)

画像ファイルの読み込みは、Image.load(画像ファイル)を使います。対応するファイル形式は、jpgpngbmpです。

読み込んだ画像はイメージになります。

→DXRubyリファレンス:チュートリアル 2. 画像の読み込みと描画
http://mirichi.github.io/dxruby-doc/tutorial/basic.html

→DXRubyリファレンス:API INDEX;Image.load
http://mirichi.github.io/dxruby-doc/api/Image_load.html

dx08.rb
require 'dxruby'

image = Image.load('../image/apple.png')

Window.loop do
  Window.draw(100, 100, image)
end

dx08.png

1-9. 画像の背景色の透明化(dx09.rb)

イメージでset_color_key(色)を使うと、指定した色を透明にできます。

→DXRubyリファレンス:Image;set_color_key
http://mirichi.github.io/dxruby-doc/api/Image_23set_color_key.html

dx09.rb
require 'dxruby'

image = Image.load('../image/apple.png')
image.set_color_key(C_WHITE)

Window.loop do
  Window.draw(100, 100, image)
end

dx09.png

1-10. 画像の拡大・縮小(dx10.rb)

イメージを拡大・縮小して表示したい時は、Window.draw_scale(x位置, y位置, イメージ, 横の拡大率, 縦の拡大率)を使います。

→DXRubyリファレンス:API INDEX;Image;Window.draw_scale
http://mirichi.github.io/dxruby-doc/api/Window_draw_scale.html

dx10.rb
require 'dxruby'

image = Image.load('../image/apple.png')
image.set_color_key(C_WHITE)

Window.loop do
  Window.draw_scale(100, 100, image, 0.2, 0.2)
end

dx10.png

1-11. トウフを表示(イメージで表示)(dx11.rb)

画像は元の画像ファイルがなくても、作ることもできます。Image.new(横幅, 縦幅, 色) で、四角形のイメージを作ります。
(白い四角形の見た目で豆腐(トウフ)

表示するには、Window.draw(x位置, y位置, イメージ)を使います。位置の基準はイメージの左上になります。

→DXRubyリファレンス:API INDEX;class Image
http://mirichi.github.io/dxruby-doc/api/Image.html

→DXRubyリファレンス:API INDEX;Window.draw
http://mirichi.github.io/dxruby-doc/api/Window_draw.html

dx11.rb
require 'dxruby'

img_tohu = Image.new(100, 100, C_WHITE)

Window.loop do
  Window.draw(200, 200, img_tohu)
end

dx11.png

1-12. トウフを表示(スプライトで表示)(dx12.rb)

DXRubyには画像を扱うのに、もう一つスプライトというクラスがあります。スプライトは、x位置y位置のデータを自分で持ち、描画メソッドも独自に持っており、衝突判定もできるのが特徴です。そのため、「ブロック崩し」ではスプライトを主に使います。

スプライトは、Sprite.new(x位置, y位置, イメージ)
で作ります。(イメージはあらかじめ作っておきます。)

表示させるのは、drawです。

→DXRubyリファレンス:Spriteを使うためのチュートリアル
http://mirichi.github.io/dxruby-doc/tutorial/sprite.html

→DXRubyリファレンス:API INDEX;class Sprite
http://mirichi.github.io/dxruby-doc/api/Sprite.html

dx12.rb
require 'dxruby'

img_tohu = Image.new(100, 100, C_WHITE)

tohu = Sprite.new(200, 200, img_tohu)

Window.loop do
  tohu.draw
end

dx12.png

1-13. トウフの中に赤丸を描く(dx13.rb)

丸を描くには、イメージにcircle_fill(中心のx位置, 中心のy位置, 半径, 色)を使います。中心の位置の座標は、(ウィンドウの位置ではなく)そのイメージの左上が(0, 0)になります。

→DXRubyリファレンス:API INDEX;Image#circle_fill
http://mirichi.github.io/dxruby-doc/api/Image_23circle_fill.html

dx13.rb
require 'dxruby'

img_tohu = Image.new(100, 100, C_WHITE).circle_fill(50, 50, 50, C_RED)

tohu = Sprite.new(200, 200, img_tohu)

Window.loop do
  tohu.draw
end

dx13.png

1-14. 赤丸だけにする(dx14.rb)

Image.new(横幅, 縦幅, 色)の指定を省略すると、透明色が指定されたことになり、見えなくなります。

→DXRuby1.4.1 リファレンス:Imageクラス;Image.new
http://dxruby.osdn.jp/DXRubyReference/200953184038328.htm

dx14.rb
require 'dxruby'

img_ball = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)

ball = Sprite.new(200, 200, img_ball)

Window.loop do
  ball.draw
end

dx14.png

1-15. カーソルキーで移動(dx15.rb)

カーソルキーが押されると、左右方向はInput.x、上下方向はInput.y-1, 0, 1 に変化します。

これを利用することで、画面上を移動させることができます。

→ DXRuby 1.4.1 リファレンス:Inputモジュール
http://dxruby.osdn.jp/DXRubyReference/200953184011156.htm

dx15.rb
require 'dxruby'

img_ball = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)

ball = Sprite.new(0, 0, img_ball)
ball.x = 200
ball.y = 200

Window.loop do
  ball.x = ball.x + Input.x
  ball.y = ball.y + Input.y

  ball.draw
end

dx15.png

1-16. カーソルキーで移動(別の書き方)(dx16.rb)

ball.x = ball.x + Input.x を別の書き方である、

ball.x += Input.x に書き換えてみます。同じ意味ですが、こちらの方が文字数が少なくて済むので、間違いが減らせるかも。

dx16.rb
require 'dxruby'

img_ball = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)

ball = Sprite.new(0, 0, img_ball)
ball.x = 200
ball.y = 200

Window.loop do
  ball.x += Input.x
  ball.y += Input.y

  ball.draw
end

dx16.png

1-17. マウスに合わせて移動(dx17.rb)

マウスのx座標、y座標はそれぞれmouse_pos_xmouse_pos_yで取得できます。
(DXRuby 1.4.2 からは、mouse_xmouse_yという書き方もできます。)

→DXRubyリファレンス:API INDEX;module Input
http://mirichi.github.io/dxruby-doc/api/Input.html

→DXRubyリファレンス:API INDEX;Input.mouse_pos_x
http://mirichi.github.io/dxruby-doc/api/Input_23mouse_pos_x.html

dx17.rb
require 'dxruby'

img_ball = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)

ball = Sprite.new(0, 0, img_ball)

Window.loop do
  ball.x = Input.mouse_pos_x
  ball.y = Input.mouse_pos_y

  ball.draw
end

dx17.png

1-18. 画像を切り替える(dx18.rb)

スプライトの画像イメージは、スプライト.image = で変更できます。

ここでは、x位置が300を超えるかどうかで、画像が変わるようにしています。

→DXRubyリファレンス:API INDEX;Sprite.image =
http://mirichi.github.io/dxruby-doc/api/Sprite_23image_3D.html

dx18.rb
require 'dxruby'

img_ball = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)
img_tohu = Image.new(100, 100, C_WHITE)

ball  = Sprite.new(0, 0, img_ball)

Window.loop do
  ball.x = Input.mouse_pos_x
  ball.y = Input.mouse_pos_y

  if ball.x > 300
    ball.image = img_ball
  else
    ball.image = img_tohu
  end

  ball.draw
end

dx18a.png

dx18b.png

1-19. 2つの画像を表示する(dx19.rb)

ひとつの画像は位置が固定で、もうひとつはマウスに追従します。

dx19.rb
require 'dxruby'

img_ball  = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)
img_tohu = Image.new(100, 100, C_WHITE)

ball  = Sprite.new(0, 0, img_ball)
tohu  = Sprite.new(100, 100, img_tohu)

Window.loop do
  ball.x = Input.mouse_pos_x
  ball.y = Input.mouse_pos_y

  ball.draw
  tohu.draw
end

dx19.png

1-20. 画像をまとめて表示する(dx20.rb)

複数のスプライトを配列にまとめると、ひとつの命令Sprite.drawで表示させることができます。

ここでは、スプライトballとスプライトtohuを配列imgsにまとめて、Sprite.draw(imgs)で一気に表示させています。

→DXRubyリファレンス:Spriteを使うためのチュートリアル
http://mirichi.github.io/dxruby-doc/tutorial/sprite.html

→DXRubyリファレンス:API INDEX;Sprite.draw
http://mirichi.github.io/dxruby-doc/api/Sprite_draw.html

dx20.rb
require 'dxruby'

img_ball  = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)
img_tohu = Image.new(100, 100, C_WHITE)

ball  = Sprite.new(0, 0, img_ball)
tohu  = Sprite.new(100, 100, img_tohu)

imgs = [ball, tohu]

Window.loop do
  ball.x = Input.mouse_pos_x
  ball.y = Input.mouse_pos_y

  Sprite.draw(imgs)
end

dx20.png

1-21. 衝突判定1;===(dx21.rb)

スプライトは衝突したかを判定する===があります。
衝突しているしていないtruefalseで返します。

→DXRubyリファレンス:Spriteを使うためのチュートリアル;衝突判定
http://mirichi.github.io/dxruby-doc/tutorial/sprite.html

→DXRubyリファレンス:API INDEX;Sprite;===
http://mirichi.github.io/dxruby-doc/api/Sprite_23_3D_3D_3D.html

dx21.rb
require 'dxruby'

img_ball  = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)
img_tohu = Image.new(100, 100, C_WHITE)

ball  = Sprite.new(0, 0, img_ball)
tohu  = Sprite.new(100, 100, img_tohu)

imgs = [ball, tohu]

font = Font.new(24)

Window.loop do
  ball.x = Input.mouse_pos_x
  ball.y = Input.mouse_pos_y

  if ball === tohu
    Window.draw_font(200, 300, "衝突!", font)
  end

  Sprite.draw(imgs)
end

dx21.png

1-22. 衝突判定2;check(dx22.rb)

スプライトの衝突判定===では、衝突の有無は分かりますが、衝突した相手は分かりません。そんな時は、checkを使います。checkは衝突している相手を配列で返します。

配列をcollとすると(collision:衝突)、0番目のcoll[0]に何か入っていれば衝突してるし、ならば衝突していないという、衝突判定にも使えます。

→DXRubyリファレンス:Spriteを使うためのチュートリアル;衝突したオブジェクトを取得する
http://mirichi.github.io/dxruby-doc/tutorial/sprite.html

→DXRubyリファレンス:API INDEX;Sprite;check
http://mirichi.github.io/dxruby-doc/api/Sprite_23check.html

dx22.rb
require 'dxruby'

img_ball  = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)
img_tohu   = Image.new(100, 100, C_WHITE)
img_tohu_r = Image.new(100, 100, C_RED)


ball   = Sprite.new(  0,   0, img_ball)
tohu1  = Sprite.new(150, 150, img_tohu)
tohu2  = Sprite.new(300, 150, img_tohu)
tohu3  = Sprite.new(300, 300, img_tohu)

blocks = [tohu1, tohu2, tohu3]

font = Font.new(24)

Window.loop do
  ball.x = Input.mouse_pos_x
  ball.y = Input.mouse_pos_y

  coll = ball.check(blocks)
  if coll[0]
    coll[0].image = img_tohu_r
  end

  Window.draw_font(200, 10, "#{blocks.size}個中、#{coll.size}個 衝突!", font)

  Sprite.draw(blocks)
  ball.draw
end

dx22.png

1-23. 衝突判定3;check後に、vanish, clean(dx23.rb)

vanishは、checkで衝突していると判定されたスプライトを無効化します。無効化されたスプライトは描画されません。ただし、無効化されただけで、そのスプライトは削除された訳ではありません。

衝突したスプライトを削除したい時は、Sprite.clean(配列)を使います。これは、配列内のスプライトで無効化されているものを配列から削除します。まだ衝突していないスプライトの数を知りたい時などに使えます。

→DXRubyリファレンス:Spriteを使うためのチュートリアル;ライフサイクルを管理する
http://mirichi.github.io/dxruby-doc/tutorial/sprite.htmlSpriteの

→DXRubyリファレンス:API INDEX;Sprite;vanish
http://mirichi.github.io/dxruby-doc/api/Sprite_23vanish.html

→DXRubyリファレンス:API INDEX;Sprite.clean
http://mirichi.github.io/dxruby-doc/api/Sprite_clean.html

dx23.rb
require 'dxruby'

img_ball  = Image.new(100, 100).circle_fill(50, 50, 50, C_RED)
img_tohu   = Image.new(100, 100, C_WHITE)


ball   = Sprite.new(  0,   0, img_ball)
tohu1  = Sprite.new(150, 150, img_tohu)
tohu2  = Sprite.new(300, 150, img_tohu)
tohu3  = Sprite.new(300, 300, img_tohu)

blocks = [tohu1, tohu2, tohu3]

font = Font.new(24)

Window.loop do
  ball.x = Input.mouse_pos_x
  ball.y = Input.mouse_pos_y

  coll = ball.check(blocks)
  if coll[0]
    coll[0].vanish
  end

  Sprite.clean(blocks)

  Window.draw_font(200, 10, "#{blocks.size}個中、#{coll.size}個 衝突!", font)

  Sprite.draw(blocks)
  ball.draw
end

dx23a.png

dx23b.png

dx23c.png

これで、DXRubyの練習は終わりです。
次は、「ブロック崩し」を作っていきます。

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

Net::HTTPメソッドを使ってBasic認証を突破しよう

こんにちは。

今回、Rubyでgemを使わずにNet::HTTPメソッドを使ってBasic認証を突破し、スクレイピングしました。あまり文献がなかったので、誰かの参考になればと思いこの記事を書いた次第です。

Rubyのバージョンは2.6.5です。

1行1行丁寧に解説していこうと思います。コードだけ知りたい!という方は完成形のコードを参考に実行してみてください。
完成形のコードはこちら↓

scraping.rb
require "net/http"

username, password = "ユーザー名", "パスワード"
uri = URI.parse("スクレイピングしたいページのURL")
https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = true
http_get = Net::HTTP::Get.new(uri.path)
http_get.basic_auth(username, password)
response = https.request(http_get)
response.body

それでは、それぞれ一行ずつ、何をしているのか確認していきます。

net/httpライブラリを読み込む

require “net/http”

net/httpとは、rubyにデフォルトで備わっているライブラリで汎用データ転送プロトコルHTTPを扱うライブラリです。

汎用データ転送プロトコルとは、要するにホームページを構成しているhtmlファイルや画像ファイルなどの「ホームページの部品」を自分のパソコンにデータとしてダウンロードする手順のことです。HTTPという共通の通信規約を定めることで、インターネットを利用する環境が異なっていても、同じ手順でホームページのデータをやり取りすることができます。

以下の記事を参考にしました。
https://cybersecurity-jp.com/security-measures/25772

ユーザー名・パスワードを変数に代入

username, password = “ユーザー名”, “パスワード”

URIを作成

uri = URI.parse(“スクレイピングしたいページのURL")

URIモジュールのparseメソッドを呼び出し、引数にURLを文字列として与えています。parseメソッドは、与えられたURIから該当するURI::Genericのサブクラスのインスタンスを生成して返します。それを変数uriに代入しています。

※URIとは、URL(web上の住所)とURN(we上での名前、シリアルナンバー)の総称です。

Net::HTTPクラスのインスタンスを生成

https = Net::HTTP.new(uri.host, uri.port)

Net::HTTPクラスのnewメソッドを呼び出し、第一引数にuri.hostを、第二引数にuri.portを代入しています。ここで、新しいNet::HTTPクラスのインスタンスを生成しています。

SSL接続を可能にする

https.use_ssl = true

新しく生成したNet::HTTPクラスのインスタンスに対して、use_sslをtrueにします。HTTPSを使う場合は、このコードが必要です。

GETリクエストを得る

http_get = Net::HTTP::Get.new(uri.path)

Net::HTTP::GETクラスのnewメソッドを呼び出し、引数にuri.pathを渡しています。Net::HTTP::GETクラスはHTTPのGETリクエストを表すクラスです。
uri.pathでは、uriのpathを文字列で返しています。pathとは、URLのドメイン名の下にあるものです。

Basic認証を突破したリクエスト送信

http_get.basic_auth(username, password)

http_getはNet::HTTP::GETクラスのインスタンスですが、このクラスはNet::HTTPHeaderクラスを継承しているため、Net::HTTPHeaderクラスのbasic_authメソッドを呼び出すことができます。第一引数にusername、第二引数にpasswordを渡します。
basic_authメソッドは、ヘッダをBASIC認証用にセットするメソッドです。

responseを受け取る

response = https.request(http_get)

responseを受け取ります。正常に接続できている場合は、200が返ってきます。このコードでは、Net::HTTPクラスのインスタンスに対してrequestメソッドを呼び出しており、引数にhttp_getを渡しています。(getリクエスト)

アクセスしたページのHTMLを取得

response.body

responseのクラスはNet::HTTPOKクラスですが、Net::HTTPResponseから継承しているbodyメソッドを呼び出すことができます。このメソッドは、レスポンスのbody、つまりアクセスしたページのhtmlを文字列として返します。

以上になります。間違えている点、補足等ございましたら、是非コメントして頂けると幸いです。

また、参考になったよ!と思った方はいいねをお願いします!

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

【RSpec】spec/rails_helper.rbを和訳&補足してみた

はじめに

rails_helperの設定をこんな風にしていますという記事はあるのですが、そのオプションによって何をしているのか今一つわからなかったので、自分用に和訳&補足してみました。
忘れた頃の自分やRSpec初心者のためになればと思います。正確性に関しては自信ないです!

RSpecのバージョンは3.9です。

(2/9追記)編集リクエストを元に修正しました。

導入

$ rails g rspec:install

コマンドを打つと、.rspec, spec/rails_helper.rb, spec/spec_helper.rbファイルが生成されます。
rails_helper.rbはデフォルトではこのようになっています。

spec/rails_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'

require File.expand_path('../config/environment', __dir__)

# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!

# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
# in _spec.rb will both be required and run as specs, causing the specs to be
# run twice. It is recommended that you do not name files matching this glob to
# end with _spec.rb. You can configure this pattern with the --pattern
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
#
# The following line is provided for convenience purposes. It has the downside
# of increasing the boot-up time by auto-requiring all files in the support
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove these lines.
begin
  ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
  puts e.to_s.strip
  exit 1
end
RSpec.configure do |config|
  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
  config.fixture_path = "#{::Rails.root}/spec/fixtures"

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = true

  # RSpec Rails can automatically mix in different behaviours to your tests
  # based on their file location, for example enabling you to call `get` and
  # `post` in specs under `spec/controllers`.
  #
  # You can disable this behaviour by removing the line below, and instead
  # explicitly tag your specs with their type, e.g.:
  #
  #     RSpec.describe UsersController, :type => :controller do
  #       # ...
  #     end
  #
  # The different available types are documented in the features, such as in
  # https://relishapp.com/rspec/rspec-rails/docs
  config.infer_spec_type_from_file_location!

  # Filter lines from Rails gems in backtraces.
  config.filter_rails_from_backtrace!
  # arbitrary gems may also be filtered via:
  # config.filter_gems_from_backtrace("gem name")
end

このコメントアウトされた部分が今回のポイントです。
thisが何を示しているのかだとか、ちょっとした用語を噛み砕けるといいなと思います。

自分なりに和訳&補足してみた

rails generate rspec:installを実行すると、このファイルはspec/ディレクトリにコピーされます。

require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)

本番環境のときにデータベースのTRUNCATE1を防ぎます。

abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'

追加でrequireする場合はこの下に追加してください。この時点までRailsは読み込まれていません!

spec/support/配下のファイルを読み込む設定

カスタムマッチャやマクロ2などを記述したrubyファイルを読み込みたい場合、spec/support/配下に置いてください。

spec/配下にある_spec.rbで終わるファイルはbundle exec rspecコマンドを実行すると自動的に走ります。つまり、これをspec/support/内に置くと、読み込み時とテスト時の二回実行されることになります。
そのため、spec/support/_spec.rbで終わるファイルを置くのはやめましょう。

この様式3はコマンドを打つときに--patternを使うか、~/.rspec, .rspec, .rspec-localで設定できます。

以下の行は手間を省くために用意されています。この行を有効にするとspec/support/以下の全てのファイルが自動的に読み込まれるため、起動に時間がかかるという難点もあります。
代わりの方法として、それぞれの_spec.rbファイルでrequireを使って必要なファイルだけを読み込む方法もあります。

Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

マイグレーションの設定

保留にされている4マイグレーションを確認し、テストを走らせる前にマイグレーションを適用します。

ActiveRecordを使わない場合、以下の行(beginからendまで)を削除できます。

begin
  ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
  puts e.to_s.strip
  exit 1
end

(補足)
自動的にマイグレーションを行い、schemaとマイグレーションファイルに相違がある場合、例外を発生させます。

ActiveRecordのfixtureを使用する設定

もしActiveRecordやActiveRecordのfixtureを使用しない場合はこの行は必要ありません。

  config.fixture_path = "#{::Rails.root}/spec/fixtures"

(補足)例えばFactoryBotを使用する場合が当てはまりますが、他の人の設定を見たところ、わざわざ削除している人は少ないようです。

exampleごとにトランザクションを行う設定

ActiveRecordを使わない、またはトランザクション内で複数のexampleを走らせたい場合は、この行を削除するかオプションをtrueからfalseにしてください。

  config.use_transactional_fixtures = true

(補足)
デフォルトのtrueの場合、exampleごとにトランザクションが行われる設定になっています。つまり、exampleが始まるときにはきれいなデータベースが用意され、終わると全てのデータを削除します。
Database Cleanerなどを使って手動で削除する場合や、特定のSpecでだけトランザクションのロールバックを無効にする場合はfalseにします。

exampleについては以下の説明を参考にしてください。

itはテストをexampleという単位にまとめる役割をします。
it do ... endの中のエクスペクテーション(期待値と実際の値の比較)がすべてパスすれば、そのexampleはパスしたことになります。
使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」

ファイルの場所に応じた機能を使用する設定

RSpec Railsでは、ファイルの場所に応じた動作を使用することができます。
例えば、spec/controllers配下のファイルでは、getpostを使うことができる仕様となっています。

以下の行を削除するとこの仕様が無効になります。
代わりに、

  RSpec.describe UsersController, type: :controller do
    # ...
  end

type: :controllerのように、明示的にタイプを記述することができます。

利用可能なタイプは、以下のサイトに記述しています。
https://relishapp.com/rspec/rspec-rails/docs

  config.infer_spec_type_from_file_location!

バックトレースをフィルタリングする設定

Railsで読み込まれたgemによるバックトレースをフィルタリングします。

  config.filter_rails_from_backtrace!

特定のgemをフィルタリングする場合、次の設定をしてください。

  config.filter_gems_from_backtrace("gem name")

(補足)
テスト失敗時のノイズを減らすための設定です。
テスト実行時に--backtraceを付けると、フィルタリングされていないバックトレースが表示されます。

(和訳はここまで)

ちなみに

FactoryBotやモジュールを使う場合は、以下のように読み込んでください。

RSpec.configure do |config|
  ...
  # config.filter_gems_from_backtrace("gem name")
  # この下に記述している人が多い印象です

  config.include FactoryBot::Syntax::Methods
  config.include LoginHelpers
end

おわりに

自分で翻訳したり調べたりすると、なんとなくRSpecの仕様への理解が深まった気がします。
また、公式ドキュメントはGoogle翻訳でも読みやすかったです。
間違っていた場合はご指摘をお願いします。


  1. テーブルに格納されているデータを全削除すること 

  2. ヘルパーメソッド 

  3. テスト実行時にどのファイルを実行するか pattern - Configuration - RSpec Core 

  4. db:migrateされていない状態 

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

今日はクライアントサイドでセッション管理してもいいのか!!(あまりよくない)

これはなに?

セッションをクライアントサイドだけで管理するのは難しいのでやめようね。という話をする機会が稀にあるのですがどういうときに困るんだっけ?またはどういう値はクライアントサイドに持っていいんだっけ?というのを毎回自分で考えるのが面倒なので書きました。
ここではクライアントサイドだけでセッション管理するのが難しい理由について説明します。そのあとに翻って一般的にセッションはどういう性質を持っているのか(あるいは持っているべきか)という話をします。

クライアントサイドだけでやるセッション管理の例

Railsにはセッションストレージとして、CookieStoreというものがあります。説明に関しては
Railsセキュリティガイド: 2.3 セッションストレージ から引用しますが

RailsのCookieStoreはクライアント側のcookieにセッションハッシュを保存します。サーバーはこのセッションハッシュをcookieから取得することで、セッションIDの必要性を解消します。こうすることで、アプリケーションのスピードは著しく向上しますが、このストレージオプションについては議論の余地があるため、セキュリティ上の意味やストレージでの制約について以下の点を十分考えておかなければなりません

例えば、ログインが必要な機能にアクセスするごとに「このユーザはログインしてるんだっけ?」というチェックのためにサーバ側のストレージ(DB/KVSなど)にアクセスが走ると、同時接続数が多いアプリケーションだとそれだけでDBのread負荷が上がる可能性があります。このような時に例えばCookieStoreを使うとリクエストに乗ってくるcookieを見るだけで済むのでサーバサイドの負荷を軽減することができる、という利点があります。
あとは、jwtの中にセッション情報を全部ぶち込んでいるケースとかも同様だと思ってもらって良さそうです。

Cookie Storeを使うと難しいポイント

CookieStoreを使うとパフォーマンス向上してよかったね〜〜って話で終わるかというとそうではなくて、クライアントサイドでcookieを管理しているため前提としてcookieの有効状態を制御することは(少なくともサーバサイドにストレージがある時にくらべて)難しいです。
上で引用した箇所の下にも

  • セッションcookieはひとりでに失効することはないため、悪用目的で使い回される可能性もあります。保存済みのタイムスタンプを利用して古いセッションcookieをアプリケーションで失効させるのもよい方法かもしれません。

という文章があります。

Cookie Storeに関して想定される攻撃例

具体的にどういう攻撃が考えられるか、またその対策については railsguides.jp のRailsセキュリティガイドの中から1つ、別のサイトから1つ紹介します。

再生攻撃

Railsセキュリティガイド: 2.5 CookieStoreセッションに対する再生攻撃 から引用します

再生攻撃のしくみは次のとおりです。
* ユーザーがクレジットを受け取る。総額はセッションに保存されているとする (これはあくまで説明のためのものであり、やってはいけません)。
* ユーザーがクレジットで何かを購入する。
* つかった分減ったクレジットがセッションに保存される。
* ここでユーザーの暗黒面が発動する。最初にブラウザに保存されていたcookieをコピーしてあったものを、現在のブラウザのcookieと差し替える。
* ユーザーのクレジット額が元に戻る。

とあり、これについての対策は

結論から言うと、 この種のデータはセッションではなくデータベースに保存するのが最善です。この場合であれば、クレジットをデータベースに保存し、logged_in_user_idをセッションに保存します。

上の説明にも (これはあくまで説明のためのものであり、やってはいけません)。 と書いてあるように、この例では自明ですが色々なタイミングで「ユーザは任意のタイミングまでセッションを巻き戻せる状態だけどこれセッションに持たせていいんだっけ?」みたいなことを考えていく必要があります。

セッションハイジャックされた時に追い出せないよ問題

また、セッション管理がクライアントサイドで完結しているとログアウト/パスワード変更などに対してセッションを無効化する処理ができないという問題があります。
Rails SessionにCookieStore使った時の問題点 から引用しますが(これの元ネタの記事はどうも消されてしまっているようです)

server-sideではstate管理しないので、当然remoteでセッションの無効化はできません。 つまりログアウトしてもsession cookieのtoken自体は無効化されません。

という話があり、特に困りそうなポイントとしてはその後に書いてある、

問題は、session cookieが無効化できないのに、それが永遠に有効であること。 FBとかY!Jとかでやってる「パスワード変更したら既存のsessionが無効になる」ってのはRailsは一切面倒見てくれないので、パスワード変えても漏洩したsession cookieは有効なままです。 なので、ひとたびsession cookieが漏れたら、完全にアウト。 なにやってもアカウント乗っ取られたまんま。永遠に。

こういうケースです。
で、これはどう対策すればいいの?という話ですが、この記事の中ではセッションにnonceをつければいいじゃないというアイデアが書いています。(これ自体はジャストアイデア的に書かれていて、その後にもっとシンプルで効果的な対策が書かれています)
OpenID Connectの名前にも触れていることからDBにnonceを保存して、セッション中にnonceがあったらDBと照合する、というような実装を想像しました。実はRailsセキュリティガイドの再生攻撃のあたりにもnonceを使えば防げるっちゃ防げるみたいな話は書いてます(この話は後ほど触れます)
そうすると、攻撃者が古いcookieを持ってきても「そのnonceはもうさっき見たので無理で〜す」みたいな感じで弾けるんですが、ここで :thinking: みたいな顔になるポイントがあります。

人類はどうしてcookie storeを使っていたのか

という話に立ち戻るとRailsセキュリティガイドの中に

サーバーはこのセッションハッシュをcookieから取得することで、セッションIDの必要性を解消します。こうすることで、アプリケーションのスピードは著しく向上しますが

ということでクライアントサイドにセッションを完結させるのはレスポンス速度向上のためなんですね(ここが違うとこの後の話もおかしなことになりますが...)
再生攻撃のあたりでnonceって単語が出てるところを見ると

この再生攻撃は、セッションにnonce (1回限りのランダムな値) を含めておくことで防ぐことができます。nonceが有効なのは1回限りであり、サーバーはnonceが有効かどうかを常に追跡し続ける必要があります。複数のアプリケーションサーバーで構成された合いの子アプリケーションの場合、状況はさらに複雑になります。nonceをデータベースに保存してしまうと、せっかくデータベースへのアクセスを避けるために設置したCookieStoreを使う意味がなくなってしまいます。

こういう話があり、個人的にはCookieStoreの利点が薄れる + 実装の複雑度が増すので、nonceを使うというジャッジをするケースは少ないかなと思います

では、パスワードリセットなどの後にセッションを無効化するには?

CookieStore単体では(そして、クライアントサイドに完結したセッションストレージでは)セッションハイジャック対策として行われるセッション無効化ができないということがわかりました。
では、どうすればいいのかというとクライアントサイドでセッションを管理することを諦めてしまうのが一番簡単ではないかと思います。
引用元のサイトでも、このように触れられています(パフォーマンスに関しては、どういうケースでどういうボトルネックがあって、セッションの一部だけをクライアントサイドに逃がすとどういう風に解消しそうか、という話はまた別途議論が待たれる)

もしくはCookieStoreの代わりにMemcacheStore使うようにしてもいいです。 sessionをserver-sideで管理するようにさえすれば、この問題はそもそも発生しないですし。 パフォーマンスに影響しますけど。

セッションってどうあるべき?

ここまで、セッションをクライアントサイドだけで管理すると落とし穴あるよ!そもそもセッションの無効化ができないのである程度複雑な機能が入ってくるとクライアントサイドだけでの管理は無理だよ!って話をしましたが、じゃあお前はセッションをどうしたいの?というと

巻き戻ってもいい値を除いてユーザの状態はサーバサイドで管理すべき
だと思います。
ここでいう巻き戻ってもいい(または巻き戻っても特に変化しない)値の例としては

  • ユーザID/名前
  • ログイン日時

などが考えられます。このあたりをクライアントサイドに持たせてうまくストレージへのアクセス方法を減らすことはできるかもしれません

結論

ということでサーバサイドにセッションのストレージ(DBなりKVSなり)は用意したほうが良いと思います

参考

jwtでセッションを管理するのはやめようね、という話: Stop using JWT for sessions, part 2: Why your solution doesn't work - joepie91's Ramblings

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

親子関係のあるモデルのテーブルの値を一度に保存する方法

自分用メモ

Gemfile.rb
gem 'nested_form_fields'

gemをインストール。

モデル関係

親:post
子:program
子:dealer
孫:program

model/post.rb
  has_many :programs, inverse_of: :post, dependent: :destroy
  has_one :dealer

  accepts_nested_attributes_for :programs, allow_destroy: true
  accepts_nested_attributes_for :dealer, allow_destroy: true

postから見て子供であるprogramとdealerのアソシエーションを書く。

accepts_nested_attributes_forを書くことによって、モデル同士が関連付けられている時に、ネストさせることで一度にまとめて保存できる。
!注意点!
モデルはアソシエーションで単数なのか複数なのかきちんと意識すること。
最初dealerの記述をする時に、accepts_nested_attributes_for :dealers, allow_destroy: true と書いてしまいうまくいかなかった。
has_oneなので「s」はいらない。

model/dealer.rb
  belongs_to :company
  belongs_to :post, optional: true

dealerに紐づく子供はいないのでこれだけ。
今回の話にcompanyは関係ないので割愛。

model/program.rb
  belongs_to :post, optional: true 
  has_many :products, inverse_of: :program, dependent: :destroy

  accepts_nested_attributes_for :products, allow_destroy: true

programから見て親のpostと子供のproductとのアソシエーション。
postと同じようにaccepts_nested_attributes_forを記載。

product.rb
  belongs_to :program, optional: true
  belongs_to :category

productに子供はいないのでこれだけ。
今回の話にcategoryは関係ないので割愛。

コントローラー

posts_controller.rb
def  new
    @post = Post.new
    dealers = @post.build_dealer
    programs = @post.programs.build
    products = programs.products.build
  end

  def  create
    @post = Post.new(post_params)
    @post.save
  end

private

    def post_params
      params.require(:post).permit(:facility_name, :address, :tell, :registration_date, :estimate_sheet, :delivery_date, :delivery_note, :postal_code, :kananame, :user_id, dealer_attributes:[:name, :kananame, :company_id, :post_id, :_destroy, :id], programs_attributes:[:software, :user, :post_id, :_destroy, :id,  products_attributes:[:thing, :category_id, :model_number, :program_id, :_destroy, :id]]).merge(user_id: current_user.id)
    end

大元の親であるposts_controllerのみにnew、createを定義するだけで大丈夫。
ポイントは以下。
・newアクションで子供、孫のbuildを作成する。
 今回で言えば、dealer、program、productのbuild作成。
・paramsのpermitで子供、孫のハッシュをかっこでネストさせる。
今回のdealerに子供はいないのでとじかっこの位置には注意。
 programには子供であるproductがいるので、更にネストさせている。
・createアクションでは、モデルオブジェクトの作成はPost.new(post_params)と大元の親だけで大丈夫。
変数@postに作成したモデルオブジェクトを入れ、@post.saveとすることで保存される。

 View

new.html.haml
= render 'partial/sidebar'
.main
  %span.main-child
    情報を登録してください
  .main-registration__date
    .main-registration__date-child
      登録日
    = form_for @post, url: posts_path do |f|
      =f.text_field :registration_date, placeholder: "登録日", type: "text", class:"registration__date-input-text"
      .main-facility
        .main-facility-child
          名前
        =f.text_field :facility_name, placeholder: "名前", type: "text", class:"facility-input-text"
      .main-kana
        .main-kana-child
          名前(カナ)
        =f.text_field :kananame, placeholder: "カナ", type: "text", class:"kana-input-text"

      .top
        %span.top-name
          ディーラーを登録してください
        .top-dealer
          .top-dealer-child
          = f.fields_for :dealer do |k|
            .top-dealer-child_box
              .top-dealer-child_box-company_text
                会社名
                .top-dealer-child_box-company
                  = k.collection_select :company_id, Company.all, :id, :name
              .top-dealer-child_box-name_text
                名前
                .top-dealer-child_box-name
                  = k.text_field :name, placeholder: "名前", type: "text", class:"dealer-input-text", id: "dealer_text"
              .top-dealer-child_box-kana_text
                名前(カナ)
                .top-dealer-child_box-kana
                  = k.text_field :kananame, placeholder: "カナ", type: "text", class:"dealer_kana-input-text", id: "dealer_kana_text"

      .best
        %span.best-name
          納品内容を登録してください
        .best-software
          .best-software-child
            ソフトウェア
          = f.fields_for :programs do |d|
            #best-software-child_box
              = d.text_field :software, placeholder: "name", type: "text", class:"program-input-text", id: "program_text"

            .products
              .products-text_box
                = d.fields_for :products do |s|
                  .products-text_box-category_text
                    カテゴリー
                    .products-text_box-category
                      = s.collection_select :category_id, Category.all, :id, :name
                  .products-text_box-product_text
                    ハードウェア
                    .products-text_box-product
                      = s.text_field :thing, placeholder: "products", type: "text", class:"products-input-text", id: "product_text"

            .footer
              = f.submit "送信", class:"products_button"

本当のコードは長すぎるので一部削除。
form_forを使って、まず親であるpostを設定。

form_forの書き方
= form_for モデル, url: createのpath do |f|
モデル部分にモデルオブジェクトをいれる、今回は@post
url部分にはpostのcreateアクションのpathをいれる(コマンドrails routesでわかる)

続いてpostの子供であるdealerを設定。
子供以下は全てfields_forを使う。

fields_forの書き方
= f.fields_for :モデル do |k|
・モデル部分にシンボルを使ってモデル名を記載。
 ここでも単数なのか複数なのか意識すること。
・fields_forの前のfはpostのform_forで使っている変数の|f|←コイツ
 必ず親の変数をつけること。
・hamlの場合必ず親のform_forやfields_forよりも中にネストさせること。
※programのビューの塊がdealerのfields_forよりネストされていない理由はprogramがdealerと同じ位置の子供だから。
もしprogramがdealerの子供であればネストさせないといけない。

fields_forさえ使いこなせれば、大抵の親子関係の値は一度に保存可能なはず。

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

Rubyで学ぶ「ゼロから作るDeep Learning」Docker環境構築編

Dockerの環境構築

やること

  • ローカルのワーキングディレクトリを作成
  • DockerでRubyの環境を作成
  • Dockerで作成したワーキングディレクトリをマウントして環境に入る
  • Dockerに必要なパッケージ等をインストール
    • gnuplot & numo-gnuplot
      • プロット用
    • numo-narray
      • 行列計算 & 基礎的な数学の関数(sin cos exp等)

Docker環境を作って、ワーキングディレクトリをマウントする所まで

実行コマンド

# ローカルにワーキングディレクトリを作成
local$ mkdir working_dir
# RubyのDockerイメージを取得
local$ docker pull ruby:latest
# ワーキングディレクトリをマウントしてDocker環境に入る
local$ docker run -v /(workingディレクトリをおいているパス)/working_dir/:/working_dir/ -it ruby /bin/bash

なぜワーキングディレクトリをマウントしておくか

gnuplotで出力したファイルを、Dockerファイルシステムの外で簡単に確認できる。ローカル環境にコピーするコマンドを叩かず、手元の環境(macOS)のプレビューアプリ等で出力したpngイメージを確認できるので便利。他にも、特に設定せずに、自分のお気に入りのエディタを使えるのもよい。

Dockerに必要なパッケージ等をインストール

# gunuplotのインストール
docker$ apt update -y && apt upgrade -y && apt install -y gnuplot
# 各種gemのインストール
docker$ gem install numo-narray numo-gnuplot

とりあえず動かしてみる

ローカルのワーキングディレクトリに先人の記事のファイルをそのまま作成

Python vs Ruby 『ゼロから作るDeep Learning』 1章 sin関数とcos関数のグラフ
https://qiita.com/niwasawa/items/6d9aba43f3cdba5ca725

# ワーキングディレクトリへ移動
docker$ cd working_dir
# 作ったファイルがあるかの確認
docker:working_dir$ ls
sin_cons.rb # ←ちゃんとローカルで作ったファイルがある
# 実行
docker:working_dir$ ruby sin_cos.rb
docker:working_dir$ ls
ruby_graph.png  sin_cons.rb # ← 出力されている

結果

中身の確認。問題なくできた。
ruby_graph.png

備考:なぜRuby?

鳥取県米子市出身で、松江にサテライトオフィスを持つ企業で働いている為。
松江といえばMatzさん、Rubyの聖地。仕事でもRubyをつかうしね。

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

敢えてRubyで学ぶ「ゼロから作るDeep Learning」Docker環境構築編

Dockerの環境構築

やること

  • ローカルのワーキングディレクトリを作成
  • DockerでRubyの環境を作成
  • Dockerで作成したワーキングディレクトリをマウントして環境に入る
  • Dockerに必要なパッケージ等をインストール
    • gnuplot & numo-gnuplot
      • プロット用
    • numo-narray
      • 行列計算 & 基礎的な数学の関数(sin cos exp等)

Docker環境を作って、ワーキングディレクトリをマウントする所まで

実行コマンド

# ローカルにワーキングディレクトリを作成
local$ mkdir working_dir
# RubyのDockerイメージを取得
local$ docker pull ruby:latest
# ワーキングディレクトリをマウントしてDocker環境に入る
local$ docker run -v /(workingディレクトリをおいているパス)/working_dir/:/working_dir/ -it ruby /bin/bash

なぜワーキングディレクトリをマウントしておくか

gnuplotで出力したファイルを、Dockerファイルシステムの外で簡単に確認できる。ローカル環境にコピーするコマンドを叩かず、手元の環境(macOS)のプレビューアプリ等で出力したpngイメージを確認できるので便利。他にも、特に設定せずに、自分のお気に入りのエディタを使えるのもよい。

Dockerに必要なパッケージ等をインストール

# gunuplotのインストール
docker$ apt update -y && apt upgrade -y && apt install -y gnuplot
# 各種gemのインストール
docker$ gem install numo-narray numo-gnuplot

とりあえず動かしてみる

ローカルのワーキングディレクトリに先人の記事のファイルをそのまま作成

Python vs Ruby 『ゼロから作るDeep Learning』 1章 sin関数とcos関数のグラフ
https://qiita.com/niwasawa/items/6d9aba43f3cdba5ca725

# ワーキングディレクトリへ移動
docker$ cd working_dir
# 作ったファイルがあるかの確認
docker:working_dir$ ls
sin_cons.rb # ←ちゃんとローカルで作ったファイルがある
# 実行
docker:working_dir$ ruby sin_cos.rb
docker:working_dir$ ls
ruby_graph.png  sin_cons.rb # ← 出力されている

結果

中身の確認。問題なくできた。
ruby_graph.png

備考:なぜRuby?

鳥取県米子市出身で、松江にサテライトオフィスを持つ企業で働いている為。
松江といえばMatzさん、Rubyの聖地。仕事でもRubyをつかうしね。

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

Xcode11で、Distribute時のRubyのエラー:ipatool JSON: (null)

ipatool JSON: (null)

XcodeでアドホックまたはエンタープライズでDistributeをしようとしたら、以下のようなエラーが出てディストリビューションアシスタントがエラーをはいた。

...
 2020-02-07 01:53:07 +0000  ipatool JSON: (null)
 2020-02-07 01:55:20 +0000 [MT] Canceled distribution assistant

解決方法

Rubyのバージョンが問題らしい
システムのRubyを使用するように変更

rvm use system

↑を実行したら、Xcodeを再起動すると治る。

参考: https://stackoverflow.com/questions/48698874/react-native-ios-signature-export-ipa-file-error-the-data-couldn-t-be-read-beca

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

外部キー作成の時の注意事項

テーマ「データベース 頭文字は大文字か小文字か」

この投稿はRailsのデータべース作成に関する記事です。
データベース周辺は間違えると後のアプリ開発に悪い影響を与えてしまいます。
このミスを参考にみなさんは出来る限りデータベース周辺では間違わないようにしてください!

今回の主犯

外部キーの頭文字を大文字にしてしまったため、seed.rbからデータを入力できなくなった。
実物はこちらです。Faculity_id←これ。。。本来はfaculity_idとなるべきはず

scheme.rb
 create_table "majors", force: :cascade do |t|
    t.index ["Faculity_id"], name: "index_majors_on_faculity_id"
  end

ミスの原因

1ヶ月ぶりにRailsを触り、フレームワークの使い方を忘れてしまったこと:sob:
(初心者は毎日触らなけれぼなりませんね。思い知りました?)

ターミナルで入力した間違いのコマンド

referencesの前を大文字にしてしまったため、外部キーも大文字になった。

terminal.
$ rails g migration AddFaculityToMajors Faculity:references

本来は$ rails g migration Add(頭文字大カラム名)To(頭文字大テーブル名) (小文字カラム名):references
というコマンドの構成です。(下記のコマンドが正しい)

terminal.
$ rails g migration AddFaculityToMajors faculity:references

seed.rbからデータを入力すると。。。

seed.rbからデータが送れない。外部キーが合わないため当然ちゃ当然ですね。

scheme.rb
 create_table "majors", force: :cascade do |t|
    t.index ["Faculity_id"], name: "index_majors_on_faculity_id"
  end

こちらは小文字なので、キーが探せずrollbackされてしまった。

seed.rb
Major.create!(id: 1, name: '経営', faculity_id: 1)

ここでもう1つ問題が。。。Faculity_id(主犯)が消えない:cry:

Faculity_idの外部キーを削除するために、削除コマンドを試したが、、、

terminal.
$ rails g migration RemoveFaculity_idFromMajor faculity:references                                                            
Running via Spring preloader in process 8806
      invoke  active_record
      create    db/migrate/20200207160003_remove_faculity_id_from_major.rb
$ rails db:migrate
== 20200207160003 RemoveFaculityIdFromMajor: migrating ========================
-- remove_reference(:majors, :faculity, {:foreign_key=>true})
   -> 0.0078s
== 20200207160003 RemoveFaculityIdFromMajor: migrated (0.0083s) ===============
scheme.rb
 create_table "majors", force: :cascade do |t|
    t.index ["Faculity_id"], name: "index_majors_on_faculity_id"
  end

依然として、Faculity_idがある。なぜーーーーー:sob:(ぴえーん)

もうこれはテーブル丸ごと消してしまおう!!!!!(テーブル削除に初挑戦)

削除コマンドはこちらを参考にしました。
[Rails]不要になったmodelの削除方法(https://bokuranotameno.com/post-9880/)

terminal.
$ rails g migration drop_table_(テーブル名複数形)

migrationファイルに削除するテーブル名を記述し$ rails db:migrateを実行します。

20200207073446_○○_majors.rb
def change
    drop_table :majors(テーブル名複数形)
end

一連のコマンドの流れ

terminal.
$ rails g migration drop_table_majors
Running via Spring preloader in process 9120
      invoke  active_record
      create    db/migrate/20200207160634_drop_table_majors.rb
$ rails db:migrate    
== 20200207160634 DropTableMajors: migrating ============================== 
== 20200207160634 DropTableMajors: migrating ==================================
-- drop_table(:majors)
   -> 0.0020s

これでようやくFaculity_idは削除されました!!!(テーブルごと消えてしまったけど(笑))

modelの関連ファイルも削除します。
・modelファイルの削除 app/models/major.rb
・testファイルの削除 test/models/major_test.rb
・fixturesファイルの削除 test/fixtures/majors.yml

新しいmodelを作成しよう!だがまた新たな問題が、、、

terminal.
$ rails g model Major name:string faculity:references
Running via Spring preloader in process 9606
      invoke  active_record
    conflict    db/migrate/20200207161950_create_majors.rb
Another migration is already named create_majors: /home/ec2-user/environment/review/db/migrate/20200207072743_create_majors.rb. Use --force to replace this migration or --skip to ignore conflicted file.

どうやらmigrateフォルダ内にある前回Major modelを作成したmigrationファイルが残っていることが原因

migrateフォルダからmigrationファイルを削除すると、herokuにアップした時などデータベースが上手く作られない可能性があるが、新しくmodelを作成するので大丈夫だと信じる:angel:

migrationファイル削除後、もう一度試す!!!

$ rails g model Major name:string faculity:references                                                               
Running via Spring preloader in process 10134
      invoke  active_record
      create    db/migrate/20200207162947_create_majors.rb
      create    app/models/major.rb
      invoke    test_unit
      create      test/models/major_test.rb
      create      test/fixtures/majors.yml
$ rails db:migrate
== 20200207162947 CreateMajors: migrating =====================================
-- create_table(:majors)
   -> 0.0034s
== 20200207162947 CreateMajors: migrated (0.0040s) ============================

無事できた!!!

seedファイルも大丈夫かどうか確かめる。

terminal.
$ rails db:seed

こちらも無事エラーが出ない!!!!!

(教訓)データベースを作成する時大文字小文字に注意

モデルの作成は大文字

terminal.
$ rails g model User(大文字)

カラムの作成は小文字

terminal.
$ rails g migration AddPostToUser user:references(小文字)

皆さんも、これらの項目に注意をして、楽しくアプリ開発してください!!!
これが参考にされば幸いです!

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

【Rails】mechanizeを使えばrailsスクレイピングが余裕な件

スクレイピングに初挑戦したとき、検索上位にあがるサイトはどれもnokogiriを使用するのですが、dockerだとうまく利用できず四苦八苦していました。

その中で見つけたのがmechanizeというgemです。

自分用メモのためにも今回はコードを書いときたいと思います。

前提

railsアプリは作成済み
gem mechanize インストール済み

Controller

controllerでは.getメソッドとsearchメソッドを使ってページ情報を取得します。

def home

  agent = Mechanize.new

  page = agent.get("https://nemlog.nem.social/")

  @elements= page.search('.visit-w')

end

あとはインスタンス変数@elementsを利用してviewに表示するだけです。

View

<%@elements.each do|element|%>
  <ul>
    <li>
      <%=link_to(element.inner_text,element[:href])%>
    </li>
  </ul>
<%end%>

.inner_textはテキスト部分だけ抜き出してくれます。
また、element[:href]は指定したhtml(この場合はclass=visit-w)の属性を抜き出します。

まとめ

結論、mechanizeは神ですね。
スクレイピングをお手軽に試してみたい人はぜひ使ってみてください。

おまけ

これを利用して簡単なnemlolgのスクレイピング サイトを作りました。
コードも確認できるので参考になればと思います。

github↓
https://github.com/soehina/searchnemlog

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

rubocopで複数行の引数の最後の値にカンマを付ける

複数行の配列や引数を書くとき

最後にカンマを付けない派

seeds.rb
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password',
  password_confirmation: 'password'
)

rubocop -aで自動整形すると、デフォルトでは上記のように整形されます。
ruby以外の言語だと、引数や配列の最後にはカンマを付けるとエラーになったりするので、一般的に使われている書き方だと思います。

最後にカンマを付ける派

seeds.rb
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password',
  password_confirmation: 'password',
)

分かりづらいですが、先程の例と比べると、最後の引数の後にカンマが付いているのが分かります。
rubyでは上記のように最後の引数にカンマを付けても問題なく動きます。
慣れていないと気持ち悪く感じるかもしれませんが、引数を追加したい時などに素早くコピペできるので便利だったりします。

最後にカンマを付けるためのrubocopの設定

Style/TrailingCommaIn~~ を設定する

引数/配列/ハッシュでそれぞれ設定するスタイルが異なります。

種類 スタイル名
引数 Style/TrailingCommaInArguments
配列 Style/TrailingCommaInHashLiteral
ハッシュ Style/TrailingCommaInArrayLiteral

それぞれEnforcedStyleForMultilineプロパティをcommaまたはconsistent_commaに設定すればOKです。

rubocop.yml
Style/TrailingCommaInArguments:
  EnforcedStyleForMultiline: comma

Style/TrailingCommaInArrayLiteral:
  EnforcedStyleForMultiline: comma

Style/TrailingCommaInHashLiteral:
  EnforcedStyleForMultiline: comma

これでrubocop -aを実行したときに引数/配列/ハッシュの値にカンマがつくようになります。commaconsistent_commaにしても同様の結果が得られます。

consistent_commacomma の違い

こちらのようなドキュメントを読んでも違いがわからなかったのでいくつか試してみたところ、複数行に引数を書きつつも、1行に複数の引数がある場合に違いが出る事がわかりました。

seeds.rb
# consistent_comma
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password', password_confirmation: 'password',
)

# comma
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password', password_confirmation: 'password'
)

分かりづらいですが、最後2つの引数を同じ行に書いています。
このようなときconsistent_commaだと最後の引数にカンマが付き、commaだと付きません。

ちょっとハマったところ

下記の様にメソッドの行から引数を書き始めると、どの設定にしても下記のように整形されてしまいますのでご注意ください。

seeds.rb
User.create!(name: 'foo bar',
             email: 'foo@baa.com',
             password: 'password',
             password_confirmation: 'password')

なお古いバージョンのrubocopでは引数/配列/ハッシュでのカンマ有無をすべてStyle/TrailingCommaで設定していたようですが、現在こちらは削除されているので使えません。

参考

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

【RuboCop】複数行の引数の最後の値にカンマを付けたい

複数行の配列や引数を書くとき

最後にカンマを付けない派

seeds.rb
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password',
  password_confirmation: 'password'
)

rubocop -aで自動整形すると、デフォルトでは上記のように整形されます。
ruby以外の言語だと、引数や配列の最後にはカンマを付けるとエラーになったりするので、一般的に使われている書き方だと思います。

最後にカンマを付ける派

seeds.rb
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password',
  password_confirmation: 'password',
)

分かりづらいですが、先程の例と比べると、最後の引数の後にカンマが付いているのが分かります。
rubyでは上記のように最後の引数にカンマを付けても問題なく動きます。
慣れていないと気持ち悪く感じるかもしれませんが、引数を追加したい時などに素早くコピペできるので便利だったりします。

最後にカンマを付けるためのrubocopの設定

Style/TrailingCommaIn~~ を設定する

引数/配列/ハッシュでそれぞれ設定するスタイルが異なります。

種類 スタイル名
引数 Style/TrailingCommaInArguments
配列 Style/TrailingCommaInHashLiteral
ハッシュ Style/TrailingCommaInArrayLiteral

それぞれEnforcedStyleForMultilineプロパティをcommaまたはconsistent_commaに設定すればOKです。

rubocop.yml
Style/TrailingCommaInArguments:
  EnforcedStyleForMultiline: comma

Style/TrailingCommaInArrayLiteral:
  EnforcedStyleForMultiline: comma

Style/TrailingCommaInHashLiteral:
  EnforcedStyleForMultiline: comma

これでrubocop -aを実行したときに引数/配列/ハッシュの値にカンマがつくようになります。commaconsistent_commaにしても同様の結果が得られます。

consistent_commacomma の違い

こちらのようなドキュメントを読んでも違いがわからなかったのでいくつか試してみたところ、複数行に引数を書きつつも、1行に複数の引数がある場合に違いが出る事がわかりました。

seeds.rb
# consistent_comma
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password', password_confirmation: 'password',
)

# comma
User.create!(
  name: 'foo bar',
  email: 'foo@baa.com',
  password: 'password', password_confirmation: 'password'
)

分かりづらいですが、最後2つの引数を同じ行に書いています。
このようなときconsistent_commaだと最後の引数にカンマが付き、commaだと付きません。

ちょっとハマったところ

下記の様にメソッドの行から引数を書き始めると、どの設定にしても下記のように整形されてしまいますのでご注意ください。

seeds.rb
User.create!(name: 'foo bar',
             email: 'foo@baa.com',
             password: 'password',
             password_confirmation: 'password')

なお古いバージョンのrubocopでは引数/配列/ハッシュでのカンマ有無をすべてStyle/TrailingCommaで設定していたようですが、現在こちらは削除されているので使えません。

参考

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