20200601のRubyに関する記事は23件です。

[Rails] ECサイトのDB設計

はじめに

Ruby on Rails(-v 5.2)を使用してECサイトを作成しました。今回はその際のDB設計についてこんな感じでやったよ、っていうのを書いていきます。

参考にしたサイト

若手プログラマー必読!5分で理解できるER図の書き方5ステップ

上記サイトの『システムシナリオを確認する』を参考にしていました。

以下の通り。

ユースケース記述

  • ユースケース名:顧客が商品を注文する
  • 概要     :ECサイトで商品を注文する
  • アクター   :顧客
  • 事前条件   :ログインしている
  • トリガー   :カートに商品をいれ、カート画面で”注文手続き”ボタンを押す
  • 基本フロー
    • 注文画面で送付先、決済方法を入力する
    • 注文内容の確認”ボタンを押す
    • 注文内容を確認して、”注文確定”ボタンを押すと購入が完了する
  • 備考     :”注文内容の確認”を押す際、入力をしていないと”注文内容の確認”ボタンを押せないようにする

エンティティの洗い出し

  • 顧客
  • 商品
  • 注文
  • 決済
  • 送付先住所

マスタ系かトランザクション系か

  • マスタ系
    • 顧客
    • 商品
  • トランザクション系
    • 注文
    • 決済
    • 送付先住所

初期段階でここ位まで考えていましたが、作成に取りかかってからは適宜変更はしていました。
決済方法もクレジットカード(PAY.JPを使用)のみの決済にしています。

本題のDB設計

ECサイトでは必須機能であるカート機能のためのカートテーブル及び、注文の詳細テーブルをどうするべきか悩みましたが、以下のような感じで中間テーブルとして落とし込みました。

スクリーンショット 2020-06-01 23.28.25.png

カートテーブルに関しては、カートとしての役割でしかないのでカラムはPK以外ありません。
ユースケースでは記載していませんでしたが、ログインしていなくてもカート機能は使用したいので、user_id(FK)は持たせていません。

users table

Column Type Options
nickname string null: false
email string default: "", null: false
password string null: false
encrypted_password string default: "", null: false
reset_password_token string
reset_password_token string
admin boolean default: false

Association

  • has_many :comments, dependent: :destroy
  • has_one :card, dependent: :destroy
  • has_one :address, dependent: :destroy
  • has_many :orders, dependent: :nullify

orders table

Column Type Options
user references foreign_key: true
card references foreign_key: true
product references foreign_key: true
quantity integer null: false
status integer default: 0, null: false
postage integer default: 0, null: false
price integer null: false

Association

  • belongs_to :user
  • has_many :products, through: :order_details
  • has_many :order_details, dependent: :destroy
  • belongs_to :card
  • belongs_to :address

cards table

Column Type Options
customer_id string null: false
card_id string null: false
user_id string null: false

Association

  • belongs_to :user
  • has_one :order, dependent: :nullify

addresses table

Column Type Options
destination_family_name string null: false
destination_first_name string null: false
destination_family_name_kana string null: false
destination_family_name_kana string null: false
postcode integer null: false
prefecture_code string null: false
address_city string null: false
address_street string null: false
address_building string
phone_number bigint
user references foreign_key: true, null: false

Association

  • belongs_to :user
  • has_one :order, dependent: :nullify

brands table

Column Type Options
name string

Association

has_many :products, dependent: :destroy

sexes table

Column Type Options
name string

Association

has_many :products, dependent: :destroy

seasons table

Column Type Options
name string

Association

has_many :products, dependent: :destroy

smell_impressions table

Column Type Options
name string

Association

has_many :products, dependent: :destroy

smell_types table

Column Type Options
name string

Association

has_many :products, dependent: :destroy

user_scenes table

Column Type Options
name string

Association

has_many :products, dependent: :destroy

products table

Column Type Options
name string null: false
namelap string null: false
description text
image text
price integer
stock_quantity
brand references foreign_key: true
sex references foreign_key: true
season references foreign_key: true
smell_type references foreign_key: true
main_spice references foreign_key: true
smell_impression references foreign_key: true
use_scene references foreign_key: true

Association

  • belongs_to :brand
  • belongs_to :sex
  • belongs_to :season, optional: true
  • belongs_to :smell_type
  • belongs_to :main_spice
  • belongs_to :smell_impression
  • belongs_to :use_scene
  • has_many :comments, dependent: :destroy
  • has_many :order_details
  • has_many :orders, through: :order_details
  • has_many :line_items, dependent: :destroy

carts table

Column Type Options

Association

has_many :line_items, dependent: :destroy

comments table

Column Type Options
user references foreign_key: true
product references foreign_key: true
text text null: false
rate float

Association

belongs_to :product
belongs_to :user

order_details table

Column Type Options
product references foreign_key: true
order references foreign_key: true
quantity integer null: false

Association

  • belongs_to :order
  • belongs_to :product

line_items table

Column Type Options
product references foreign_key: true
cart references foreign_key: true
quantity integer default: 0, null: false

Association

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

Ruby の Range で ("b".."aa") が使えなかった件について

背景

事の発端

  1. Kinx では Ruby と同じように文字列の Range をサポートしたが、"z" の次は "aa" なので、単純に辞書順ではない <=> 演算子を定義してました。
  2. で、普通にソートすると、当然辞書順にはなりません。(文字列長いほうが大きいと判断されるため)
  3. で、そういえば Ruby は辞書順ですね、文字列のソート。なんか変だな

[注] Kinx って何?
最初の動機 ⇒ こちらをご覧ください
リポジトリ ⇒ こちらをご覧ください

確認してみた

  1. irb で "b" <=> "aa" やってみよう、

    irb(main):001:0> "b" <=> "aa"
    => 1
    irb(main):002:0>
    
  2. おゃ?!

  3. では Range では?試しに "a".."c" を表示すると。

    irb(main):002:0> a = "a".."c"
    => "a".."c"
    irb(main):003:0> a.each {|e| p e }
    "a"
    "b"
    "c"
    => "a".."c"
    
  4. ちゃんと列挙されます。では疑惑の "b".."aa" は?

    irb(main):004:0> a = "b".."aa"
    => "b".."aa"
    irb(main):005:0> a.each {|e| p e }
    => "b".."aa"
    
  5. なんと、"b".."aa" は使えないのか...

結論

私の気持ち

やっぱり "b".."aa"動いてほしい よね。"b" の次は "c" で、そのまま "z" まで進んだ後に "aa" で終了してほしいなーと思いましたが、他の方はどうなんでしょう。

実際に行ったこと

なので、最新のリポジトリ(0.8.1 には入ってません)では以下のように修正しましたよ。

  1. 文字列の <=> 演算子は辞書順に戻した。
  2. class Range<=> の部分は文字列だけ特別扱いにして "b".."aa" も使えるようにした。
    • "b", "c", "d", ..., "z", "aa" になる。

おわりに

ということで、ここ(=文字に対する Range の仕様)は Ruby と仕様が違います。何か問題があればお知らせください。

まあ、こんな特殊な条件ではあまり使わないので、誰も困ってなさそうですが。だから別にどっちでもいいと言えばどっちでもいいかもしれません。細かい話でした。

Ruby 的にはどうなんでしょうね。想定内なのか、想定外なのか。意図してなのか、そうでないのか。まあ定義が明確で、定義に従った動作なので、こういうもの=仕様、ってことで落ち着きそうですが。

ではまた、次回。

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

JekyllでパーマリンクにFront Matterのカスタム変数を使う方法

「Front Matterのカスタム変数をパーマリンクに使いたい!」ので、使えるようにしてみたという記事です。

背景

Jekyll で生成する記事のURLは、_config.yml の設定や Front Matter での直接の指定でカスタマイズできます。以下で軽く説明しますが、詳しくは、公式ガイドの Permalinks | Jekyll を参照ください。日本語版

_config.yml で記事のURLを設定する方法

_config.yml
permalink: /:categories/:title

_config.yml で、上記のような指定をすることで、全ポストのURLを一定のルールで決めることができます。しかし、事前に用意された Placeholder(置換用変数) しか使えません。
(組み込みフォーマットなどの説明はここでは割愛します)

Front Matter で記事のURLを設定する方法

Front Matter とは、ファイルの最初に3つのダッシュで挟まれたYAMLで、このブロックがあると Jekyllさんはファイルをいい感じに処理してくれます。記事のタイトルやカテゴリーなどを設定するのに使ったことがある方も多いでしょう。

about.md
---
permalink: /about/
---

この方法は柔軟に好きなパーマリンクを設定できますが、一定のルールで複数のページのパーマリンクを設定するのには向いていません。

本題 (_plugins を使ってパーマリンクをカスタムする方法)

しかし、「Front Matterのカスタム変数をパーマリンクに使いたい!」という場合にはどちらも向いていません。そこで、_plugins を使ってパーマリンクをカスタムする方法の登場です。
今回はこんな記事たち↓があるとします。デフォルトの変数の他に、author という変数を使っていますね。この著者猫さんの名前をパーマリンクに含めるのが目標です。

2020-06-01-test.md
---
layout: post
title: "テスト記事だにゃー"
date: 2020-06-01 11:14:26 +0900
categories: cat
author: toraneko
---
  1. まず、サイトのrootディレクトリ直下に _plugins というディレクトリを作ります。
  2. 適当な名前の.rbファイルを作ります。ここでは jekyll_change_permalinks.rb にしてみました。
  3. 中身を書きます。
jekyll_change_permalinks.rb
module Jekyll
  class PermalinkRewriter < Generator # Generatorクラスを継承してあげます
    safe true
    priority :low # プラグインがロードされる順位を決めてあげます

    def generate(site) # ジェネレータはこのメソッドを使います
      site.posts.each do |item|
        @author = item.data['author'] # Front Matter の変数はこれで取得できます
        unless @author.nil? # nilじゃないかな?チェック
          item.data['permalink'] = "/:categories/#{@author}/:title/"
        end
      end
    end
  end
end

/cat/toraneko/test/ で、先程の記事が見られるようになりました!

まとめ

「Front Matterのカスタム変数をパーマリンクに使いたい!」というときはこんな方法があるよ!という紹介でした(´∀`*)

それぞれに長所と短所があるので、目的にぴったりな方法を見つけると良さそうです?

比較

方法 ルールがある カスタム変数を使える
_config.ymlを使う方法
Front Matterで直指定
_pluginsを使う方法

参考

Your first plugin | Jekyll • Simple, blog-aware, static sites
Generators | Jekyll • Simple, blog-aware, static sites
Permalinks | Jekyll • Simple, blog-aware, static sites
What are the steps to getting this 'custom' permalink scheme in Jekyll?
Search · class PermalinkRewriter < Generator

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

Railsチュートリアル1日目

  • Railsチュートリアルを1.3.1まで進める
  • 1.3.2で、$ rails serverを実行する
  • Could not find gem 'puma (= 4.3.4)' in any of the gem sources listed in your Gemfile.と表示される
  • Could not find gem 'puma (= 4.3.4)' in any of the gem sources listed in your Gemfile. でググる
    • この記事にたどり着く(この記録の取り方もこの記事を参考にしている)
  • $ bundle update を実行する
  • あらためて$ rails server を実行する
  • できた!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RubyとPython間のプロセス間通信を行う(POSIX メッセージキュー)

概要

rubyとpythonのdaemon間でデータの受け渡しをredis(Pub/Sub)1を使って考えていたら、プロセス間通信(POSIX IPC)使ったほうが手軽だよと天の声が聞こえたので試してみました。

なお、今回はraspberry pi上で動かすので、電源問題のためにもプロセス数は可能な限り減らす作戦で検討しています。

POSIX メッセージキューとは

POSIXのシステムコール/ライブラリ関数を通じて利用できる、プロセス間通信の一つ。非同期型通信プロトコルで、誤解を恐れずにいえばRabbitMQなどのメッセージングミドルウェアの超簡易版のようなもの。POSIX準拠なので簡素なAPIでお手軽に利用できます。

詳しくは以下をご参照ください。

https://linuxjm.osdn.jp/html/LDP_man-pages/man7/mq_overview.7.html

システム全体

Ruby側の実装(送信側)

どっちが送信/受信でもよいのですが、今回はRubyを送信側としてみました。
利用ライブラリはposix-mqueueを利用させていただきました。
めっちゃシンプル。

require 'posix/mqueue'
require 'json'

def main
  m = POSIX::Mqueue.new("/whatever", msgsize: 8192, maxmsg: 10)
  10.times do |i|
    m.send({ messsage: "hello python world #{i}" }.to_json)
    sleep 1
  end
  m.send ({ message: 'end'}.to_json)
ensure
  m.unlink if m
end

if __FILE__ == $PROGRAM_NAME
  main
end

Python側の実装(受信側)

こちらも恐ろしくシンプルです。
利用ライブラリはposix_ipc

# -*- coding: utf-8 -*-
import time
import posix_ipc
import json

def main () :
    mq = posix_ipc.MessageQueue("/whatever", posix_ipc.O_CREAT)
    while True:
        data = mq.receive()
        j = json.loads(data[0]) # ->  (message, priority)のタプルで戻ってくる
        print j
        if j.get('message') == "end":
            break

if __name__ == "__main__" : 
    main()

結果

想定通りPython側で出力されました!

$ python main.py 
{u'messsage': u'hello python world 0'}
{u'messsage': u'hello python world 1'}
{u'messsage': u'hello python world 2'}
{u'messsage': u'hello python world 3'}
{u'messsage': u'hello python world 4'}
{u'messsage': u'hello python world 5'}
{u'messsage': u'hello python world 6'}
{u'messsage': u'hello python world 7'}
{u'messsage': u'hello python world 8'}
{u'messsage': u'hello python world 9'}
{u'message': u'end'}

簡単なソースですが一応githubにも上げておきました。
https://github.com/y-amadatsu/posix-mq-with-raspberry-pi-os

余談

ちなみにPOSIX メッセージを使うとメッセージQueueが /dev/mqueue 配下に表示されます。マウントして lsrm で操作することも可能です。このあたりはすごくUNIX!って感じ:relaxed:

cat /dev/mqueue/whatever 
QSIZE:350        NOTIFY:0     SIGNO:0     NOTIFY_PID:0     

  1. redis5からPub/Sub機能よりもより信頼性の高いStreamsという機能が増えています。 

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

メモ

偶数か奇数か

puts "数字入れて"
n = gets.to_i

if n.even?
 puts "偶数です"
else n.odd?
  puts "奇数です"
end  

to_i to_s

  • to_iメソッドが文字→数値
  • to_sメソッドが数値→文字

getsは文字列で保存する。
数値に変換するため→gets.to_i

each_with_index

fruits=[“りんご”, “メロン”, “イチゴ”]

fruits.each_with_index do |fruit, i|
puts “#{i+1}番目の要素は#{fruit}です。”
end
【結果】
1番目の要素はりんごです。
2番目の要素はメロンです。
3番目の要素はイチゴです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

マイグレーション up, downメソッド

マイグレーション up, downメソッド

マイグレーションファイルで定義されるup, downメソッドについて考えていきます

テーブルの定義が書いてあるこんなマイグレーションファイルがあるとする

テーブルを定義

class CreateTasks < ActiveRecord::Migration[6.0]
  def change
    create_table :tasks do |t|
      t.string :name
      t.text :description

      t.timestamps
    end
  end
end

このテーブルの :name属性に
30文字以内という文字制限をつけたい場合にどのようなマイグレーションファイルを作るか、というのが今日のテーマ

普通に考えればこれでいい。

limit制約を付ける

class ChangeTasksNameLimit30 < ActiveRecord::Migration[6.0]
  def change
    change_column :tasks, :name, :string, limit: 30
  end
end

テーブルには望み通りの制限を課すことができる。

ところが後日、やっぱりlimit: 30はなくそうという話になった。
そこでこんなコマンドを叩いてバージョンを一つ戻そうとすると...

マイグレーションをロールバック

rails db:rollback
# => not automatically reversible エラーが発生

つまり定義されたマイグレーションファイルを自動的に戻すことができないよ
って怒られてしまう。

何が起こったの??

Railsはchange_columnのバージョンを戻す処理をバージョンを上げる際のコードから自動生成できないため、こう言ったエラーが起こってします。
つまりRailsが「このマイグレーションファイルを消した後の元の姿がわからん!!」
って言ってエラーを吐き出します。

どうするか?

upメソッドとdownメソッドを使う

ruby.rb
class ChangeTasksNameLimit30 < ActiveRecord::Migration[6.0]
  def up
    change_column :tasks, :name, :string, limit: 30
  end

  def down
    change_column :tasks, :name, :string
  end
end

upメソッドは rails db:migrateのときの処理
downメソッドは rails db:rollbackのときの処理

つまりバージョンを上げるときはupメソッドで処理をしてください。
下げるときはdownメソッドで処理をしてください。と伝えることで、

rails db:rollbackをした時にアプリケーションがどんな処理をするべきかを
伝えることができるんですね。こうすることでnot automatically reversible エラーの発生も防ぐことができますし、コードをみた人がどんな処理をしたいかもわかりやすいということですね。

めでたしめでたし。

本日はここまでです。

一人前のエンジニアになるまであと88日

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

Ruby インスタンス変数・ローカル変数 違い

インスタンスメソッド

クラスの中でメソッドを定義すると、そのメソッドはインスタンスメソッドとなります。
インスタンスメソッドは、そのクラスのインスタンスに対して呼び出すことができるメソッドです。
(例)

class User
  def emotion
     "Happy!"
  end
end

user = User.new
user.emotion

結果

"Happy!"

インスタンス変数

クラス内では、インスタンス変数(同じインスタンス内で共有される変数)を使うことができます。
変数名は@から始まります。
(例)

class User
  def initialize(emotion)
    @emotion = emotion
  end

  def happy
    "I am #{@emotion}."
  end
end
user = User.new('Happy')
user.happy

結果

"I am Happy."

ローカル変数

メソッドやブロック内で作成される変数のことをいいます。
アルファベットの小文字、アンダースコアで始めます。
ローカル変数は参照する前に必ず=で値を代入して作成する必要があります。

(例)

class User
  def initialize(emotion)
    @emotion = emotion
  end

  def happy
    shuffled_emotion = @emotion.chars.shuffle.join
    "I am #{shuffled_emotion}."
  end
end
user = User.new('Happy')
user.happy

この例の場合、ローカル変数はshuffled_emotionになります。

結果

"I am ayppH."

参考にした文献

プロを目指す人のためのRuby入門

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

CGIプログラムをローカルで動かすときの設定等(自分用)

はじめに

CGIプログラム完全初心者の自分用メモです.
こういう記事を書くのは初めてなので,お見苦しい点あるかと思いますがお許しください.
アドバイスやご指摘がありましたら,コメントくださると幸いです.

モチベーション

Rubyで記述されたCGIプログラムを,ローカルサーバ環境にて実行テストしたい.

サーバの決定

サーバには,ApacheのHTTPサーバを使う.

AN-HTTPDを使う方法(参考:http://www.aikis.or.jp/~s-suzuki/cgilabo/localserver/
もあるみたいだけど,入手先のURLが死んでる.
一応アーカイブからダウンロードできるよってことらしい
(参考:https://www.nishishi.com/blog/2019/02/an_httpd_waybac.html )けど,
試したけどなんかうまくいかなかった.

環境

  • Windows 10
  • Perl 5.28.1
  • Ruby 2.7.1-1
  • Apache 2.4.43

ActivePerlのインストール

今回のモチベには関係ないけどCGIプログラムにはPerlが定番っぽい(そりゃそうか)ので入れた.

ActivePerlのサイト(https://www.activestate.com/ )から最新版(5.28.1)をダウンロード

Sign In (ログイン必要/GitHubアカウントでログイン可)
-> Featured Projects & Languages
-> ActivePerl 5.28 の枠中の Windows
-> Windows10 の .msi をクリックして ActivePerl-5.28.1.0000-MSWin32-x64-b462fde1.msi
をダウンロードしてインストール

☆インストール中
Setup TypeはTypical
Setup Optionsでは,
「Add Perl to the PATH environment variable」(パス通す)と
「Create Perl file extension association」(plファイル関連付け)にはチェックを入れる.
(参考:https://www.hiskip.com/pg-notes/how-to-install/develop-kit/active-perl526.html

インストール後,C:の下にPerl64ってディレクトリが置かれる(C:\Perl64).

Rubyのインストール

RubyInstaller(https://rubyinstaller.org/downloads/ )から
最新版(2.7.1-1)をダウンロード

rubyinstaller-2.7.1-1-x64.exeをダウンロードしてインストール

☆インストール中
Perlの時と同じ理由で,
「Add Ruby exexutables to your PATH」と
「Associate .rb and .rbw files with this Ruby installation」にはチェックを入れる.

インストール後,C:の下にRuby27-x64ってディレクトリが置かれる(C:\Ruby27-x64).

ApacheのHTTPサーバのインストール

ApacheのHTTPサーバのダウンロードサイト(http://httpd.apache.org/download.cgi )から
最新版(2.4.43)をダウンロード

Apache HTTP Server Server 2.4.43 (httpd): 2.4.43 is the latest available version
-> Files for Microsoft Windows
-> Apache Lounge (ダウンロード先の指定,何でもいいと思う)
-> httpd-2.4.43-win64-VS16.zip
をダウンロード,展開

展開したら,httpd-2.4.43-win64-VS16のディレクトリの下にApache24っていうディレクトリが
できるのでC:の下に置く(C:/Apache24).

Apacheの httpd.conf の設定

Apache24/conf/http.conf は,Apache(httpd)の設定ファイルである.
これを編集する.
以下,編集点

#CGIの実行場所の設定
ScriptAlias /cgi-bin/ "C:/Apache24/cgi-bin/"

#CGIの実行を許可
<Directory "C:/Apache24/cgi-bin">
AllowOverride All
Options Indexes FollowSymLinks ExecCGI
Require all granted
</Directory>

#拡張子.cgi、.pl .rbが使えるようする
AddHandler cgi-script .cgi .pl .rb

#拡張子.cgi、.pl .rbが使えるようするver2
AddType application/x-httpd-cgi .cgi
AddType application/x-httpd-cgi .pl
AddType application/x-httpd-cgi .rb

(参考1:https://phpjavascriptroom.com/?t=php&p=cgi#google_vignette
(参考2:https://qiita.com/hirotoyoshidome/items/6d103e04dd07e90519d2
上2つともスペシャルサンクス

AddHandlerとAddTypeの違いとかについてはこちら(https://senooken.jp/post/2019/06/01/

Apacheの起動

Apache24/bin/httpd.exeを実行すると,ApacheのHTTPサーバを起動できる.

私はhttpd.exeを実行できなくて,(http://t12488mac.blogspot.com/2011/06/windowsapache.html )を参照した.
Apache24\binのディレクトリでhttpd.exe -tを叩いたら
httpd.confにダメな記述があって実行できないよ~」って教えてくれた.
直したら直った(あたりまえ)

CGIプログラムの置き場・記述

CGIプログラムは
Apache24/cgi-binの下に置く.

  • rubyのCGIプログラムには1行目に以下の記述
    #!/Ruby27-x64/bin/ruby
    または
    #!C:/Ruby27-x64/bin/ruby

  • perlのCGIプログラムには1行目に以下の記述
    #!/perl64/bin/perl
    または
    #!C:/Ruby27-x64/bin/ruby

ブラウザからアクセス

以下の3つを満たす状態にたどり着けたら,いよいよブラウザで表示できる.

  • Apacheのhttpd.confを適切に編集済の状態
  • ApacheのHTTPサーバを起動(httpd.exeを実行)した状態
  • Apache24/cgi-bin下に何らかのCGIプログラム(**.cgi)を置いた状態

ブラウザのURLバーに
http://127.0.0.1/cgi-bin/**.cgi
または
http://localhost/cgi-bin/**.cgi
と入力すると,自分が書いたページが,ブラウザ上に表示される.

これを以て,ローカルサーバ環境での動作完了とする.

ディレクトリ配置

本記事で紹介したものに関連するディレクトリ配置を以下に示す.

C:/
├ Perl64
│ ├ bin
│ │ ├ Perl.exe
│ : :

├ Ruby27-x64
│ ├ bin
│ │ ├Ruby.exe
│ : :

├ Apache24
│ ├ bin
│ │ ├ httpd.exe
│ │ :
│ │
│ ├ conf
│ │ ├ httpd.conf
│ │ :
│ │
│ ├cgi-bin
│ : └**.cgi (自作CGIファイル)
│ :
::

おわりに

手探りながらも,Rubyで記述されたCGIプログラムを,ローカルサーバ環境でテストできた.
疲れた.

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

【Rails5.2+Rspec/TDD】Rspecを導入して簡単にテストする手順(テスト駆動開発)

スクリーンショット 2020-05-31 1.36.03.png

環境

ruby 2.6.4

Rails 5.2.4.1

rbenv 1.1.2

mysql2 0.5.3

やりたいこと

TDD(テスト駆動開発)をするためにRspecを使っていきます

Rspecを使う手順を簡単に説明している記事がそこまで多くないので自分用にメモしておきます

今回は簡単な導入方法を残しておきます

元記事:【Rails5.2/TDD】Rspecを超簡単に導入する簡単手順(テスト駆動開発)

カンタン導入手順

ステップ1. 新規アプリ作成

$ rails new sample_project --api -T

ステップ2. TDD用にGem追加

Gemfile
group :development, :test do
  gem 'rspec-rails', '~> 3.5'
end

group :test do
  gem 'factory_bot_rails', '~> 4.0'
  gem 'shoulda-matchers', '~> 3.1'
  gem 'faker'
  gem 'database_cleaner'
end

ステップ3. gemインストール

$ bundle install

ステップ4. Rspecファイル生成

$ rails generate rspec:install

ステップ5. factoriesディレクトリ作成

$ mkdir spec/factories

ステップ6. rails_helper.rb修正

spec/rails_helper.rb
require 'database_cleaner'

# 追加
Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

# 追加
RSpec.configure do |config|
# 追加
  config.include FactoryBot::Syntax::Methods
# 追加
  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
    DatabaseCleaner.strategy = :transaction
  end

# 追加
  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run
    end

end

今回は導入のみですが実際のテストで下の記事がとても参考になります
スクリーンショット 2020-06-01 18.46.00.png

参考文献

参考にした記事です
スクリーンショット 2020-06-01 18.47.54.png

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

【Rails5】[ Turbolinks ] ページ遷移やブラウザバックでJSが動かないときの対処法

実装した機能

開発環境

ruby > 2.6.5
rails > 5.2.4.2

実装したJS

$(document).ready(function(){
   $("#menu").on("click", function() {
      $(this).next().slideToggle();
   });
});

jqueryを使って、なんの変哲もない開閉式ハンバーガーメニューをつけました。
JSのコード自体の良し悪しはおいておいて、動作としては問題ないはずです。

状況

初期ロード時には問題なく動作する。
ページ遷移、ブラウザバックのときに挙動がおかしい。
クリックで発火はしているが、開閉をループしたり、不安定。
リロードすると通常動作する。

考察と対策

動いてはいるので、おそらく読み込みのタイミングが間違っている?
ready onload ajaxStop など一通り試してみてもダメ。

グーグル先生に相談したら、こんな記述を発見

$(document).on('turbolinks:load', function () {
...
});

turbolinks:load なにこれ見たことない…
どうやらRails独自の記述らしいです。

Turbolinksの扱い

こちらの記事を参考にさせていただきました。
turbolinksチートシート

Turbolinksとは?

  • Ajaxによるページ遷移の高速化のためのライブラリ(Gem)
  • ユーザ側から見て、通常のページ遷移と同じように表示される/動作する
  • Rails4からデフォルトでインストールされている

つまり、この機能が今回のJSに影響してしまっているようです。

turbolinksをどう扱うか

Gemなので、消してしまえば解消はできますが解決にはならないので
どう扱うべきかリサーチしてみました。

主にこのような扱いがあります。

  1. <a>タグごとにturbolinksを無効にする
  2. turbolinks自体を無効化(削除)する
  3. JSの読み込み時にturbolinksを適応しない、タイミングを変える

1.<a>タグごとにturbolinksを無効にする

リンク自体に{"turbolinks" => false}を指定すると
そのリンクはturbolinksが無効になります。

<%= link_to "HOGE", root_path, data: {"turbolinks" => false} %>
<%# => <a data-turbolinks="false" href="/">HOGE</a> %>

これを記述すれば、間違いなくturbolinksを外すことができます。
特定のスクリプトのみ制御する場合は良さそうですが、さすがに全部に記述するのは厳しそうですね…

2.turbolinks自体を無効化(削除)する

Gemを削除

この一行を削除

Gemfile
#gem 'turbolinks', '~> 5'
ターミナル
$ bundle update

application.jsを編集

app/assets/javascripts/application.js
//= require turbolinks #この行を削除

application.html.erbを編集

'data-turbolinks-track': 'reload' を削除します。

app/views/layouts/application.html.erb
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

これで無効化されました。
JSなどを多用するサイトでなければ無効化してしまうのが確実かもしれません。

3.JSの読み込みにturbolinksを適応しない

ready onload などと同じように、
この記述でturbolinksを適応せずにロードできます。

$(document).on('turbolinks:load', function () {
...
});

他にも、turbolinksを適応するタイミングも変更ができます。
詳しく知りたい方はこちらを参照ください。
その他のライフサイクルイベントをとる

スクリプトごとに微調整が効くので、今回はこれが最適解だと思います。

まとめ

果たして、Turbolinksは優れた機能なのか、おせっかい機能なのか…
今の所どちらとも言えません笑
デフォルトでインストールされているということは、きっとあったほうが良いのだろうと思いますが…
もっと効果的な使用法をご存じの方はぜひコメントを下さい!

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

Ruby+Seleniumでブラウザの自動化ツール作成

大学の履修登録の抽選科目で誰かが外した時に、先着でその授業を取るためにひたすらポチポチする作業を自動化するツールを作りました。
大学ごとに履修登録の仕方は異なると思うので、大枠だけ記しておきます。
使用したのはRuby、SeleniumWebDriver、ブラウザはGoogleChromeです。

Seleniumとは

Webブラウザ上でWebアプリケーションを自動で操作(テスト)するツール。

Selenium WebDriverとは

旧来のSelenium RCの問題点を解決し、ブラウザを操作する仕組み。
Selenium RCをSelenium1、Selenium WebDriverをSelenium2とも呼ぶ。

環境

$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.14.6
BuildVersion: 18G103

$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin18]

Selenium WebDriverのインストール

$ gem install selenium-webdriver

ターミナルでSelenium WebDriverのgemを追加。

$ gem list

でselenium-webdriverと表示されればOK!

ChromeDriverのインストール

今回はブラウザでGoogleChromeを使用するので、下記リンクからChromeDriverをインストールします。

Chrome Driver

サンプルコード

エディタでコードを書いていきます。大学によってページ遷移など異なると思うので、簡潔に。
今回はRubyを使用するため、selenium_test.rbなどと名付けておきます。

selenium_test.rb
require 'selenium-webdriver'

driver = Selenium::WebDriver.for :chrome
driver.manage.timeouts.implicit_wait = 60
# 指定したドライバの要素が見つかるまでの待ち時間を指定

driver.navigate.to "URL"
# URLを指定してアクセス

driver.find_element(:xpath, '該当のxpathを記載').click
# xpathでボタンを指定して押す

driver.find_element(:name '指定したいname属性').send_keys('入力したいワード')
# name属性の入力フォームを指定して文字を入力する。

# 後はif文やloop文などを使用して、各々の大学の履修登録に合わせて作成してください

driver.quit
# ドライバを閉じる

以下コマンドで実行。

$ ruby selenium_test.rb

おかしな点があればご指摘お願いします!!

参考

Seleniumチートシート

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

posts_controller.rb

class PostsController < ApplicationController
def index
@posts = Post.all.order(created_at: :desc)
end

def show
@post = Post.find_by(id: params[:id])
end

def new
@post = Post.new
end

def create
@post = Post.new(content: params[:content])
if @post.save
# 変数flash[:notice]に、指定されたメッセージを代入してください
flash[:notice]="投稿を作成しました"
redirect_to("/posts/index")
else
render("posts/new")
end
end

def edit
@post = Post.find_by(id: params[:id])
end

def update
@post = Post.find_by(id: params[:id])
@post.content = params[:content]
if @post.save
flash[:notice] = "投稿を編集しました"
redirect_to("/posts/index")
else
render("posts/edit")
end
end

def destroy
@post = Post.find_by(id: params[:id])
@post.destroy
# 変数flash[:notice]に、指定されたメッセージを代入してください
flash[:notice] = "投稿を削除しました"
redirect_to("/posts/index")
end

end

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

ポートフォリオ用にRubyで作ったブラックジャックを自分でアップデートしてみた

初めに作ったブラックジャック↓↓
Rubyでブラックジャック作ってみた(minitest使ってみた)

アップデートの内容

  • 100万円稼いだら勝ち、所持金0円になったら負けというルールを追加。
  • play.rb(ゲーム起動クラス)
    • ディーラーターンを同じ文で2箇所書いていたため、ディーラーターンメソッドを作り、よりコードを読みやすくした。
    • カードを引いたときのドキドキ感を増す為に、waitメソッドを作り、ディーラーがドローする前にEnterを押さないと進めないようにした。
    • 指定した金額をベットできるようにした。
  • card.rb(カードクラス)

    • マークがわかりやすくなるように「スペード」「ハート」の、様にカタカナ表記を追加。
  • deck.rb(デッキクラス)

    • プレイクラスを作った為、プレイヤーが毎回リセットされなくなった為、デッキをリセットできるように、リセットメソッドを作成した。
  • player.rb(プレイヤークラス)

    • デッキクラスと同様プレイヤーが毎回初期化されなくなった為、テスト用に使っていたリセットメソッドをゲーム中にも使うようにした。
    • 「A」はバーストした際に「1」になるようにスコア計算メソッドに機能を追加。
    • ベット金額の計算機能を追加。
  • judge.rb(ジャッジクラス)

    • 勝敗が決まったときのベットの計算機能を追加。
    • 所持金でゲームクリアかゲームオーバーかを判定する機能を追加。
  • test.rb(動作確認テスト)

    • ベットの計算機能のテストを追加。
  • main.rb(ブラックジャックを動かすメインファイルだったが、プレイクラスを作った為、削除)

    ルール

  • 所持金が100万以上で勝利。0円になったら敗北

  • 「A」は「1」は都合良い数字に変換。というよりトータルスコアを変換。

  • ヒットかスタンドのみ

  • トータルスコアが高い方が勝ち

  • ブラックジャック勝ちでベット金額3倍もらえる。通常2倍。

play.rb(プレイクラス)

play.rb
require './card'
require './deck'
require './judge'
require './money'
require './player'

class Play
    def initialize
        @player = Player.new
        @dealer = Player.new(name: 'ディーラー')
        play
    end

    def message(title, choice1="ヒット", choice2="スタンド") # win or lose or draw or error
        case title
        when :win
            puts 'あなたの勝ちです'
        when :lose
            puts 'あなたの負けです'
        when :draw
            puts '引き分けです'
        when :error
            puts '無効です。もう一度入力してください'
        when :choice
            puts "選択してください\s\sPush「 1 」→ #{choice1} 、Push「 2 」→ #{choice2}" 
        end
    end

    def dealer_turn
        # スタンド時の処理(ディーラーのターン)
        puts 'ディーラーのターンです'
        wait
        # プレイヤーがブラックジャックだった場合のディーラーの処理 
        if @player.blackjack?
            @dealer.draw
            @dealer.show
            if !@dealer.blackjack?
                message :win
                @player.money.stock = @player.bet_judge(@@bet, :blackjack)
                @player.money.show
                return
            else
                message :draw
                @player.money.stock = @player.bet_judge(@@bet, :draw)
                @player.money.show
                return
            end
        end
        # プレイヤーに勝つまでディーラーはヒットし続ける処理
        while true
            @dealer.draw
            @dealer.show
            wait
            if @dealer.burst?
                puts 'バースト!'
                message :win
                @player.money.stock = @player.bet_judge(@@bet, :win)
                @player.money.show
                break
            elsif @dealer.total_score == 21 && @player.total_score == 21
                message :draw
                @player.money.stock = @player.bet_judge(@@bet, :draw)
                @player.money.show
                break
            elsif @player.total_score < @dealer.total_score
                message :lose
                @player.money.stock = @player.bet_judge(@@bet, :lose)
                @player.money.show
                break
            end
        end
    end

    def bet_turn
        @player.money.show
        puts "いくらベットしますか?"
        while true
            bet = gets.to_i
            if bet < 1
                message :error
                redo
            elsif bet > @player.money.stock.to_i
                puts "賭け金が手持ち金額を上回っています。もう一度入力してください。"
                redo
            else
                puts <<~TEXT
                #{bet} 円ベットしました。
                #{'-' * 41}
                TEXT
                return bet
            end
        end
    end

    def play
        while true # 再プレイのためのループ処理
            # 手札リセット
            @player.reset
            @dealer.reset
            # 初ターンの処理
            puts "ようこそ、ブラックジャックへ"
            puts "ルール:手持ち資金が100万円を超えたら勝ちです。0円になったら負けです。"
            puts "カードを配ります"
            @player.draw
            @player.draw
            @dealer.draw  
            @dealer.show
            @@bet = bet_turn
            @player.bet_calc(@@bet)
            @player.money.show
            # プレイヤーがブラックジャックじゃないか確認の処理
            if @player.blackjack?
                    puts "ブラックジャック!"
                    @player.show
            else
                @player.show
                message :choice
            end
            # ヒット、スタンドの処理(プレイヤーのターン)
            while true
                # ブラックジャックの場合は強制的にスタンドさせる処理
                if @player.blackjack?
                    command = 2
                else
                    command = gets.to_i # ヒットかスタンドか選択
                end
                # ヒットの場合の処理
                if command == 1
                    while true
                        @player.draw
                        if @player.burst? # バーストしていないか確認
                            @player.show
                            puts "バースト!!"
                            message :lose
                            @player.money.stock = @player.bet_judge(@@bet, :lose)
                            @player.money.show
                            break
                        end
                        # ドロー後のトータルスコアの確認
                        @player.show
                        # 最大値(21)の場合は強制的にスタンドの処理
                        if @player.total_score == 21
                            puts 'トータルスコアが最大値に達しました'
                            command = 2
                            break
                        end
                        # 最大値ではない場合は、再度ヒットするか処理
                        puts "もう一度引きますか?"
                        message :choice
                        while true
                            command = gets.to_i # 再度、ヒットかスタンドの選択
                            if command == 1
                                break
                            elsif command == 2
                                break
                            else
                                message :error
                                redo
                            end
                        end
                        # ヒットかスタンドの処理
                        if command == 1 
                            redo
                        elsif command == 2
                            break
                        end
                    end
                    # バースト時の強制終了処理
                    if @player.burst?
                        break
                    end
                elsif command == 2
                    dealer_turn
                    command = nil
                    break
                else
                    # h,s以外が入力されたときの処理
                    message :error
                    redo
                end

                if command == 2
                    dealer_turn
                    break
                end

            end
            # ゲームオーバーか判定
            if @player.gameover?
                puts "所持金が0円になりました。"
                puts "ゲームオーバーです。"
                break
            elsif @player.win?
                puts "所持金が100万円を超えました。"
                puts "あなたの勝ちです!!!!!!おめでとう!!!!!!!!!!!!"
                break
            end
            # 再プレイの処理
            puts "もう一回遊びますか?"
            message :choice, "遊ぶ", "遊ばない"
            while true
                command = gets.to_i # 再プレイするか選択
                if command == 1
                    command = nil
                    break
                elsif command == 2
                    break
                else
                    message :error
                    redo
                end
            end
            # 再プレイの処理

            command == nil ? redo : break
        end
    end

    def gameover

    end

    def wait(words='Enterキーを押してください')
        puts words
        c = gets
    end
end

Play.new

card.rb(カードクラス)

card.rb
class Card 
    attr_reader :mark, :number
    def initialize(mark, number)
        @mark   = mark.freeze
        @number = number.freeze
    end

    def convert
        if @number == 'J' || @number == 'Q' || @number == 'K' || @number == 'A'
            10
        else
            @number.to_i
        end
    end
end

deck.rb(デッキクラス)

deck.rb
class Deck
    attr_accessor :cards
    @@draw_count = 0

    def initialize
        @cards = []
        create
        @cards = @cards.shuffle
    end

    def create
        nums = ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K']
        mark = ["❤ ハート\s\s\s\s", "♦︎ ダイヤ\s\s\s\s", "♤ スペード\s\s", "♧ クローバー"]
        mark.each do |m|
            nums.each do |n|
                @cards << Card.new(m, n)
            end
        end
    end

    def draw
        cards[@@draw_count]
    end

    def draw_count
        @@draw_count += 1
    end

    # テスト用のカード参照メソッド
    def show
        52.times do |n|
            puts "[ #{cards[n].mark} #{cards[n].number} ]"
        end
    end

    def reset
        @cards = @cards.shuffle
        @@draw_count = 0
    end
end

player.rb(プレイヤークラス)

player.rb
class Player
    include Judge
    attr_accessor :hands, :total_score, :name, :money
    @@deck = Deck.new
    def initialize(name: "プレイヤー")
        @hands = []
        @total_score = 0
        @name = name
        @money = Money.new
    end

    def draw
        self.hands << @@deck.draw
        self.total_score = self.total_score_calc
        @@deck.draw_count
    end


    def show
        hands = []
        self.hands.each do |hand|
            hands << "#{hand.mark} #{hand.number}"
        end
        puts "#{self.name}の手札です"
        puts hands
        puts  <<~TEXT
        #{puts "[ トータル  #{@total_score} ]"}
        -----------------------------------------
        TEXT
    end

    def a_count
        a = 0
        self.hands.each do |card|
            if card.number == 'A'
                a += 1
            end
        end
        a
    end

    def reset
        self.hands = []
        self.total_score = 0
        @@deck.reset
    end

    def total_score_calc
        @total_score = 0 # 複数回計算した場合に、加算しないように、毎回トータルスコアをリセットする
        self.hands.each do |hand|   # @numberに「A」があり、トータルスコアが22以上ある場合、「A」の数だけ、トータルスコアから-9する。「A」の数をカウントするメソッドを作る。
            @total_score += hand.convert
        end
        if self.burst? && self.in_a?
            @total_score -= (self.a_count * 9)                # 「A」の数だけ、トータルスコアから-9する。「A」の数をカウントするメソッドを作る。
        end
        @total_score
    end

    def bet_calc(bet)
        self.money.stock  = self.money.stock.to_i - bet 
    end

    # テスト用メソッド

    def test
        puts "テストーーーーーー"
    end



    def reset_score
        self.total_score = 0
    end

    def in_blackjack
        self.hands << Card.new("♦︎", "J")
        self.hands << Card.new("♦︎", "A")
    end
end

# player = Player.new
# enemy  = Player.new
# player.draw
# player.draw
# enemy.draw
# enemy.draw
# player.hands
# player.total_score_calc
# enemy.total_score_calc
# p player.blackjack?
# p enemy.blackjack?

judge.rb(ジャッジモジュール)

judge.rb
module Judge

    def bet_judge(bet, judge)
        stock = self.money.stock
        case judge
        when :win
            stock += bet * 2
        when :lose
            stock += 0
        when :blackjack
            stock += bet * 3
        when :draw
            stock += bet
        end
    end

    def blackjack?
        if self.hands[1].number == "A" || self.hands[0].number == "A" 
            if  self.hands[0].number == "J" || self.hands[0].number == "Q"|| self.hands[0].number == "K" || \
                self.hands[1].number == "J" || self.hands[1].number == "Q"|| self.hands[1].number == "K"
                self.total_score = "ブラックジャック"
                true
            else
                false
            end
        else
            false
        end
    end

    def burst?
        self.total_score > 21 ? true : false
    end

    def in_a?
        self.hands.map do |card|
            if card.number == 'A'
                return true
            end
        end
        false
    end

    def gameover?
        self.money.stock <= 0 ? true : false
    end

    def win?
        self.money.stock > 1000000 ? true : false
    end

end

test.rb(minitest)

test.rb
require 'minitest/autorun'
require './card'
require './deck'
require './judge'
require './money'
require './player'
class BlackJackTest < Minitest::Test
    # テストカードのマークは「 ♦︎ 」で統一
    @@test_J      = Card.new('♦︎', 'J')
    @@test_Q      = Card.new('♦︎', 'Q')
    @@test_K      = Card.new('♦︎', 'K')
    @@test_A      = Card.new('♦︎', 'A')
    @@test_5      = Card.new('♦︎', 5)
    @@test_10     = Card.new('♦︎', 10)
    @@test_player = Player.new

    def test_blackjack?
        blackjack_player = Player.new
        blackjack_player.hands << @@test_A
        blackjack_player.hands << @@test_J
        assert blackjack_player.blackjack?

        blackjack_player.reset
        blackjack_player.hands << @@test_J
        blackjack_player.hands << @@test_A
        assert blackjack_player.blackjack?

        blackjack_player.reset
        blackjack_player.hands << @@test_Q
        blackjack_player.hands << @@test_A
        assert blackjack_player.blackjack?

        blackjack_player.reset
        blackjack_player.hands << @@test_A
        blackjack_player.hands << @@test_Q
        assert blackjack_player.blackjack?

        blackjack_player.reset
        blackjack_player.hands << @@test_A
        blackjack_player.hands << @@test_K
        assert blackjack_player.blackjack?

        blackjack_player.reset
        blackjack_player.hands << @@test_K
        blackjack_player.hands << @@test_A
        assert blackjack_player.blackjack?

        # false パターン
        # blackjack_player.reset
        # blackjack_player.hands << @@test_A
        # blackjack_player.hands << @@test_10
        # assert blackjack_player.blackjack?

        # blackjack_player.reset
        # blackjack_player.hands << @@test_5
        # blackjack_player.hands << @@test_5
        # assert blackjack_player.blackjack?

        # blackjack_player.reset
        # blackjack_player.hands << @@test_J
        # blackjack_player.hands << @@test_Q
        # assert blackjack_player.blackjack?


    end

    def test_burst?
        burst_player = Player.new
        burst_player.hands << @@test_J
        burst_player.hands << @@test_J
        burst_player.hands << @@test_5
        burst_player.total_score_calc
        burst_player.total_score
        assert burst_player.burst?
        # false
        # burst_player.reset
        # burst_player.hands << @@test_10
        # burst_player.total_score
        # assert burst_player.burst?
    end

    def test_total_score_calc
        total_score_calc_player = Player.new
        total_score_calc_player.hands << @@test_10
        total_score_calc_player.hands << @@test_5
        total_score_calc_player.total_score_calc
        assert_equal 15, total_score_calc_player.total_score
        total_score_calc_player.reset
        total_score_calc_player.hands << @@test_A
        total_score_calc_player.hands << @@test_10
        total_score_calc_player.hands << @@test_5
        total_score_calc_player.total_score_calc
        assert_equal 16, total_score_calc_player.total_score
        total_score_calc_player.reset
        total_score_calc_player.hands << @@test_A
        total_score_calc_player.hands << @@test_J
        assert_equal 20, total_score_calc_player.total_score_calc
        total_score_calc_player.hands << @@test_5
        assert_equal 16, total_score_calc_player.total_score_calc
        total_score_calc_player.hands << @@test_A
        assert_equal 17, total_score_calc_player.total_score_calc
        # false
        #assert_equal 10, total_score_calc_player.total_score
    end


    def test_convert
        assert_equal 10, @@test_J.convert
        assert_equal 10, @@test_Q.convert
        assert_equal 10, @@test_K.convert
        assert_equal 10, @@test_A.convert
        assert_equal 5, @@test_5.convert
    end

    def test_draw_count
        deck = Deck.new
        assert_equal 1, @@test_player.draw
        assert_equal 2, @@test_player.draw
        assert_equal 3, @@test_player.draw
    end


    def test_a_count
        a_player = Player.new
        a_player.hands << @@test_A
        a_player.hands << @@test_A
        a_player.hands << @@test_A
        assert_equal 3, a_player.a_count
    end

    def test_bet_calc
        #assert_equal 19000, @@test_player.bet_calc(1000)

    end

    def test_bet_judge
        p @@test_player.money.stock -= 250 # ベット
        assert_equal 20250, @@test_player.bet_judge(250,  :win)
        assert_equal 19750, @@test_player.bet_judge(250,  :lose)
        assert_equal 20500, @@test_player.bet_judge(250,  :blackjack)
        assert_equal 20000, @@test_player.bet_judge(250,  :draw)
    end

end

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

さくらのVPSにRubyをインストール

環境等は以下の記事をご参照ください。
さくらのVPSにSSH接続+最低限のセキュリティ対策

Gitをインストール

まずはgit cloneを使うためにgitをインストールします。

$ sudo yum -y install git
$ git --version

rbenvをインストール

Rubyのバージョンを管理するツール、rbenvをインストールします。

$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build #'rbenv install'コマンドの実行に必要なプラグイン

依存パッケージをインストール

rubyをインストールするための関連するパッケージです。これがないと、rbenv installの際にコケるらしいです。

$ yum -y install bzip2 gcc openssl-devel readline-devel zlib-devel

rbenvの読み込み設定

rbenvコマンドを使える用にするために、シェル起動時読み込むbash_profileを編集します。

$ vi ~/.bash_profile
~/.bash_profile
# 省略
PATH=$PATH:$HOME/bin

export PATH
export PATH="$HOME/.rbenv/bin:$PATH" # 追記
eval "$(rbenv init -)" # 追記
$ source ~/.bash_profile
$ rbenv -v

Rubyをインストール

rbenvの設定が終わったので、いよいよRubyのインストールです。

$ rbenv install 2.4.1
$ rbenv global 2.4.1
$ rbenv rehash
$ ruby -v

bundlerをインストール

gemの管理をいい感じにするツールbundlerをインストールしましょう。

$ gem install bundle

node.js、yarnをインストール

Rails6を動かすためにはyarnが必須です。

$ yum install -y nodejs npm --enablerepo=epel
$ npm install -g yarn

参考

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

Railsサーバが起動しない1。エラー「Ignoring nokogiri-1.10.x because its extensions are not built.」

発生した背景

久々に自分で作成したアプリケーションを起動しようとすると、以下のエラーが出力されて、アプリが起動されません。対応した手順を記録します。
(バージョンが合わないなどの問題もありましたが、こちらは別記事にしようと思います。)
非常に長いエラーのため、慎重に対応していく

再現手順/実行した内容

アプリのrootpathで、rails -s」コマンド実行した際に、出力されたエラーです。

エラー1:

[staff@term]rails s
Ignoring nokogiri-1.10.1 because its extensions are not built. Try: gem pristine nokogiri --version 1.10.1
Ignoring bcrypt-3.1.12 because its extensions are not built. Try: gem pristine bcrypt --version 3.1.12
Ignoring bindex-0.7.0 because its extensions are not built. Try: gem pristine bindex --version 0.7.0
Ignoring bindex-0.5.0 because its extensions are not built. Try: gem pristine bindex --version 0.5.0
Ignoring binding_of_caller-0.8.0 because its extensions are not built. Try: gem pristine binding_of_caller --version 0.8.0
Ignoring bootsnap-1.4.3 because its extensions are not built. Try: gem pristine bootsnap --version 1.4.3
Ignoring bootsnap-1.4.1 because its extensions are not built. Try: gem pristine bootsnap --version 1.4.1
Ignoring bootsnap-1.4.0 because its extensions are not built. Try: gem pristine bootsnap --version 1.4.0
Ignoring bootsnap-1.3.2 because its extensions are not built. Try: gem pristine bootsnap --version 1.3.2
------- 一部割愛

エラー2:

Traceback (most recent call last):
    61: from bin/rails:3:in `<main>'
    60: from bin/rails:3:in `load'
    59: from /Users/ichikawadaisuke/projects/krown/bin/spring:15:in `<top (required)>'
    58: from /Users/ichikawadaisuke/.rbenv/versions/2.5.1/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:70:in `require'
    57: from /Users/ichikawadaisuke/.rbenv/versions/2.5.1/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:70:in `require'
    56: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/spring-2.0.2/lib/spring/binstub.rb:31:in `<top (required)>'
    55: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/spring-2.0.2/lib/spring/binstub.rb:31:in `load'
    54: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/spring-2.0.2/bin/spring:49:in `<top (required)>'
    53: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/spring-2.0.2/lib/spring/client.rb:30:in `run'
    52: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/spring-2.0.2/lib/spring/client/command.rb:7:in `call'
    51: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/spring-2.0.2/lib/spring/client/rails.rb:28:in `call'
    50: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/spring-2.0.2/lib/spring/client/rails.rb:28:in `load'
    49: from /Users/ichikawadaisuke/projects/krown/bin/rails:9:in `<top (required)>'
    48: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:291:in `require'
    47: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:257:in `load_dependency'
    46: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:291:in `block in require'
    45: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
    44: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
    43: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
    42: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
    41: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
    40: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/railties-5.2.3/lib/rails/commands.rb:18:in `<main>'
    39: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/railties-5.2.3/lib/rails/command.rb:46:in `invoke'
    38: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/railties-5.2.3/lib/rails/command/base.rb:65:in `perform'
    37: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/thor-0.20.3/lib/thor.rb:387:in `dispatch'
    36: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/thor-0.20.3/lib/thor/invocation.rb:126:in `invoke_command'
    35: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/thor-0.20.3/lib/thor/command.rb:27:in `run'
    34: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/railties-5.2.3/lib/rails/commands/server/server_command.rb:142:in `perform'
    33: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/railties-5.2.3/lib/rails/commands/server/server_command.rb:142:in `tap'
    32: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/railties-5.2.3/lib/rails/commands/server/server_command.rb:145:in `block in perform'
    31: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:291:in `require'
    30: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:257:in `load_dependency'
    29: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:291:in `block in require'
    28: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
    27: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
    26: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
    25: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
    24: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
    23: from /Users/ichikawadaisuke/projects/krown/config/application.rb:7:in `<main>'
    22: from /Users/ichikawadaisuke/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-2.0.1/lib/bundler.rb:114:in `require'
    21: from /Users/ichikawadaisuke/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-2.0.1/lib/bundler/runtime.rb:65:in `require'
    20: from /Users/ichikawadaisuke/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-2.0.1/lib/bundler/runtime.rb:65:in `each'
    19: from /Users/ichikawadaisuke/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-2.0.1/lib/bundler/runtime.rb:76:in `block in require'
    18: from /Users/ichikawadaisuke/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-2.0.1/lib/bundler/runtime.rb:76:in `each'
    17: from /Users/ichikawadaisuke/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-2.0.1/lib/bundler/runtime.rb:81:in `block (2 levels) in require'
    16: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
    15: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
    14: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
    13: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
    12: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
    11: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/uglifier-4.1.20/lib/uglifier.rb:5:in `<main>'
    10: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:291:in `require'
     9: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:257:in `load_dependency'
     8: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb:291:in `block in require'
     7: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
     6: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
     5: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
     4: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
     3: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
     2: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/execjs-2.7.0/lib/execjs.rb:4:in `<main>'
     1: from /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/execjs-2.7.0/lib/execjs.rb:5:in `<module:ExecJS>'
/Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/execjs-2.7.0/lib/execjs/runtimes.rb:58:in `autodetect': Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes. (ExecJS::RuntimeUnavailable)

対応方法

エラーの出力量が非常に多いため、環境を再構築する方が早いかもしれないと考えたが、時間を決めて対応してみることに。

  • ①記述に従い、「gem pristine nokogiri --version 1.10.1」を実行してみる。
    • bundle execを付加して実行してみる。
    • bundle install --path vendorの実行
    • 結果 -> 同じエラーが出力される。
  • ②背景から一度「bundle update」を実行してみる。
    • 結果 -> エラー2だけが出力されるようになった

エラー2の対応方法については別記事にて掲載する。

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

ハッシュについて詳しく!

ハッシュで使用頻度の高いメソッド

keysメソッド

ハッシュのキーを配列として返すメソッド

keyメソッド
cunrrencies = { japan: 'yen', us: 'doller', india: 'rupee' }
currencies.key #=>[:japan, :us, :india]  

valuesメソッド

ハッシュの値を配列として返すメソッド

valuesメソッド
currencies = { japan: 'yen', us: 'doller', india: 'rupee' }
currencies.values #=> ["yen", "doller, "rupee"]

has_key?メソッド

ハッシュの中に指定されたキーが存在するかどうか確認するメソッド
※エイリアスメソッドとして、key?, include?, member? があります。

has_key?
currencies = { japan: 'yen', us: 'doller', india: 'rupee' }
currencies.has_key?(:japan) #=> true
currencies.has_key?(:italy) #=> false

**でハッシュを展開

**をハッシュの前に付けるとハッシュリテラル内でほかのハッシュのキーと値を展開できる

**
h = { us: 'doller', india: 'rupee' }
{ japan: 'yen', **h } #=> {:japan=>"yen", :us=>"doller", :india=>"rupee"}
#mergeメソッドを使っても同じ効果が得られる
{ japan: 'yen' }.merge(h) #=> {:japan=>"yen", :us=>"doller", :india=>"rupee"}

配列とハッシュ

to_aメソッド

ハッシュをto_aメソッドを使って配列に変換するメソッド

to_a
currencies = { japan: 'yen', us: 'doller', india: 'rupee' }
currencies.to_a #=> [[:japan, "yen"], [:us, "doller"], [:india, "rupee"]]

to_hメソッド

配列をハッシュに変換することができるメソッド

to_h
array = [[:japan, "yen"], [:us, "doller"], [:india, "rupee"]]
array.to_h #=> {:japan=>"yen", :us=>"dollar", :india=>"rupee"}
#ハッシュとして解析不能な配列に対してto_hメソッドを呼ぶとエラーになる
array = [1, 2, 3, 4]
array.to_h #=> TypeError: wrong element type Integer at 0 (expected array)
#キーが重複したりすると思いがけないエラーになる
array = [[:japan, "yen"], [:japan, "円"]]
array.to_h #=> {:japan=>"円"}

Hash[]に対して渡すことで配列に変換することもできる

Hash[]
array = [[:japan, "yen"], [:us, "doller"], [:india, "rupee"]]
Hash[array] #=> {:japan=>"yen", :us=>"dollar", :india=>"rupee"}

ハッシュの初期値

h = Hash.new('hello')
a = h[:foo] #=> "hello"
b = h[:bar] #=> "hello"

#変数aと変数bは同一オブジェクト
a.equal?(b) #=> true

#変数aに破壊的な変更をすると、変数bの値も一緒に変わってしまう
a.upcase!
a #=> "HELLO"
b #=> "HELLO"
ブロックを使う
h = Hash.new{ 'hello' }
a = h[:foo] #=> "hello"
b = h[:bar] #=> "hello"

#変数aと変数bは異なるオブジェクト
a.equal?(b) #=> false

#変数aに破壊的な変更を適用しても、変数bの値は変わらない
a.upcase!
a #=> "HELLO"
b #=> "hello"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】マイグレーションコマンドまとめ

開発環境

・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina

マイグレーションコマンドの書き方

$ rails g migration ChangeColumnToBooks は、
$ rails generation migration change_column_to_books と書くのと同じ。

つまり、generationgと略す事ができ、
AddBodyToBooksの様に、単語の先頭を大文字にする事で「 _ 」を書く手間が省ける。

基本コマンド

1.モデルとテーブルを作成

$ rails g model モデル名 カラム名:型名

ターミナル
$ rails g model Book title:string
migrate/~_create_books.rb
class CreateBooks < ActiveRecord::Migration[5.2]
  def change
    create_table :books do |t|
      t.string :title

      t.timestamps
    end
  end
end

2.モデルとテーブルを削除

rails d model モデル名

ターミナル
$ rails d model Book

3.マイグレーションを実行

ターミナル
$ rails db:migrate

4.マイグレーションの内容を戻す

①1ステップ前に戻る場合

ターミナル
$ rails db:rollback

①複数ステップ前に戻る場合

ターミナル
$ rails db:rollback STEP=5 # 数字は自由に変更可能

4.マイグレーションのステータス確認

ターミナル
rails db:migrate:status

テーブル関係

1.テーブルのみを削除

$ rails g migration Dropテーブル名

ターミナル
$ rails g migration DropBooks
migrate/~_drop_books.rb
class DropBooks < ActiveRecord::Migration[5.2]
  def change
    drop_table :books # 追記
  end
end

2.テーブル名を変更

$ rails g migration Rename変更前のテーブル名To変更後のテーブル名

ターミナル
$ rails g migration RenameBooksToArticles
migrate/~_rename_books_to_articles.rb
class RenameBooksToArticles < ActiveRecord::Migration[5.2]
  def change
    rename_table :books, :articles # 追記
  end
end

カラム関係

1.カラムを追加

①単体

$ rails g migration Addカラム名Toテーブル名 カラム名:型名

ターミナル
$ rails g migration AddBodyToBooks body:text
migrate/~_add_body_to_books.rb
class AddBodyToBooks < ActiveRecord::Migration[5.2]
  def change
    add_column :books, :body, :text
  end
end

②複数

$ rails g migration AddColumnsToテーブル名 カラム名:型名 カラム名:型名 カラム名:型名

ターミナル
$ rails g migration AddColumnsToBooks body:text introduction:text price:integer
migrate/~_add_columns_to_books.rb
class AddColumnsToBooks < ActiveRecord::Migration[5.2]
  def change
    add_column :books, :body, :text
    add_column :books, :introduction, :text
    add_column :books, :price, :integer
  end
end

2.カラムを削除

①単体

$ rails g migration Removeカラム名Fromテーブル名 カラム名:型名

ターミナル
$ rails g migration RemoveTitleFromBooks title:string
migrate/~_remove_title_from_books.rb
class RemoveTitleFromBooks < ActiveRecord::Migration[5.2]
  def change
    remove_column :books, :title, :string
  end
end

②複数

$ rails g migration RemoveColumnsFromテーブル名 カラム名:型名 カラム名:型名 カラム名:型名

ターミナル
$ rails g migration RemoveColumnsFromBooks body:text introduction:text price:integer
migrate/~_remove_columns_from_books.rb
class RemoveColumnsFromBooks < ActiveRecord::Migration[5.2]
  def change
    remove_column :books, :body, :text
    remove_column :books, :introduction, :text
    remove_column :books, :price, :integer
  end
end

3.カラムのデータ型を変更

$ rails g migration ChangeDataカラム名Toテーブル名 カラム名:型名

ターミナル
$ rails g migration ChangeDataTitleToBooks
migrate/~_change_data_title_to_books.rb
class ChangeDataTitleToBooks < ActiveRecord::Migration[5.2]
  def change
    change_column :books, :title, :text # 追記
  end
end

4.カラムのオプションを追加

$ rails g migration ChangeOptionカラム名Toテーブル名 カラム名:型名

ターミナル
$ rails g migration ChangeOptionTitleToBooks
migrate/~_change_option_title_to_books.rb
class ChangeOptionTitleToBook < ActiveRecord::Migration[5.2]
  def change
    change_column :books, :title, :string, null: false # 追記
  end
end

5.データ型一覧

型名 役割
string 短い文字列
text 長い文字列
integer 整数
float 浮動少数
decimal 精度の高い少数
datetime 日時
timestamp タイムスタンプ
time 時間
date 日付
binary バイナリ文字列
boolean 真偽値

6.よく使うオプション

オプション名 役割
default 初期値を設定
null 空欄の真偽
limit 長さを制限
unique 一意制約を付与
unique インデックスを付与
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【リファクタリング】ルーティングの書き方

概要

ルーティングをスッキリ書く方法です:relaxed:

背景
ルーティングの指定をする際、7つのアクションのうち6つを使用する場合、onlyで書いたらダラダラと長くなってしまって、個人的に自分ダサっ:weary:っとなったので備忘録として残します:bow_tone1:

Railsの7つのアクション

まずは、Railsの7つのアクションについておさらい:writing_hand:

Action 役割
index 一覧を表示する
new 追加する
create 追加内容を登録す
edit 編集する
update 編集内容を更新する
destroy 削除する
show 個別内容を表示する

書き方

:warning:ここでは「tweets」というリソースにルーティングを行います

7つのアクション全てを実装する場合

routes.rb
  resources :tweets

使用するアクションを限定する場合

:warning:ここでは「index」「new」「create」を使用することとします

routes.rb
  resources :tweets, only: [:index, :new, :create]

不要なアクションを削除する場合

:warning:ここでは「show」以外のアクションを使用することとします

routes.rb
  resources :tweets, except: [:show]

まとめ

ついつい、onlyで記入しがちですが、exceptを使用することでコードがスッキリし読みやすくなります:point_up:
私もスッキリ:sparkles:を心掛けてまいります:laughing:

参考

https://web-camp.io/magazine/archives/16815

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

Railsチュートリアルで遭遇したテスト関連のバグ

Railsチュートリアルを勉強していて原因不明のバグに遭遇したので対処方法を記録しておきます。

環境

ホストマシン:macOS Catalina 10.15.4
関係してそうなGemのバージョン:

  gem 'rails',                   '6.0.3'
  gem 'spring',                  '2.1.0'
  gem 'rails-controller-testing', '1.0.4'
  gem 'minitest',                 '5.11.3'

エラー

発生条件

詳細は不明ですが、いろいろなタイミングで発生しました。rails generateコマンドでControllerを作った後などに頻発。

エラー内容

テストがGreenになるはずの状況でrails test コマンドでエラーが出る。しかもエラーの原因箇所を特定不能。
2000行以上にわたる大量のログ(おそらくGemのパスを示している)が吐かれたのちに以下のエラーメッセージ。

[NOTE]
You may have encountered a bug in the Ruby interpreter or extension libraries.
Bug reports are welcome.
For details: https://www.ruby-lang.org/bugreport.html

[IMPORTANT]
Don't forget to include the Crash Report log file under
DiagnosticReports directory in bug reports.

Traceback (most recent call last):
        5: from -e:1:in `<main>'
        4: from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
        3: from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
        2: from /Library/Ruby/Gems/2.6.0/gems/minitest-5.11.3/lib/minitest.rb:63:in `block in autorun'
        1: from /Library/Ruby/Gems/2.6.0/gems/minitest-5.11.3/lib/minitest.rb:140:in `run'
/Library/Ruby/Gems/2.6.0/gems/activesupport-6.0.3/lib/active_support/testing/parallelization.rb:124:in `shutdown': Queue not empty, but all workers have finished. This probably means that a worker crashed and 7 tests were missed. (RuntimeError)

冒頭の[NOTE]にあるようにライブラリ関連のバグなのでしょうか。

対処方法

ターミナルに以下のコマンドを打ち込んでspringを再起動する

rm bin/spring
pkill -9 -f spring

これは推測になりますが追加したファイルの内容が正確に読み込まれていないのが原因かもしれません。このようなバグがあるとTDDの効率は著しく下がってので辛いです。本来はGreenなテストがコード以外の部分が原因でRedになってしまうという厄介なバグでした。

追記

Railsチュートリアル内にも対処方法の記述がありました。記載箇所は第3章。

bin/spring stop    # テストが原因不明で動かなくなったら、このコマンドを実行してみる

とにかくspringとやらを一回止めることが大事らしい。springが一体何者なのか...誰か教えてください?

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

Rubyでチンチロゲームを作る  第4回 ゲーム進行の処理の作成

1. はじめに

 いよいよゲーム進行にかかわる処理を書いていきます。あらかじめ各メソッドを実装しているので、なにもない状態から書き始めるよりも楽にできるはずです。それではやってみましょう。

2. main.rbの作成

 各ファイルと同じフォルダに main.rbを作成します。まずは以下のように流れを書いてみましょう。

# チンチロゲーム

# プレイヤーの作成

# ----決着が着くまでループ
#  賭け金の設定(自分)
#  賭け金の設定(相手)
#  サイコロを振る
#  役の決定
#  勝敗判定
#  賭け金の移動
# 最終的な勝敗判定
# ----決着が着くまでループ

 ちょっとずつ付け足していきます。 transfer_money.rbplayer_class.rbに変更しています。

require './player_class'  # transfer_money.rbから変更
require './dice_roll'
# チンチロゲーム

# プレイヤーの作成
player_A = Player.new(money:1000,name:'カイジ')
player_B = Player.new(money:1000,name:'班長')
puts '---チンチロゲーム---'

# ----決着が着くまでループ
loop do
#  賭け金の設定(自分)
  puts '掛け金を入力してください'
  bet_money = gets.chomp
  player_A.bet_money = bet_money.to_i
#  賭け金の設定(相手)
  bet_money_B = rand(1..4) * 200
  player_B.bet_money = player_B.money < bet_money_B ? player_B.money : bet_money_B

    puts <<~TEXT
    名前: #{player_A.name} 
     所持金:#{player_A.money} ペリカ
     賭け金:#{player_A.bet_money} ペリカ
    -
    名前: #{player_B.name} 
     所持金:#{player_B.money} ペリカ
     賭け金:#{player_B.bet_money} ペリカ

    press enter
    TEXT
    teisi = gets
#  サイコロを振る/役の決定
    eye_on_the_dices1 = [rand(1..6),rand(1..6),rand(1..6)]
    eye_on_the_dices2 = [rand(1..6),rand(1..6),rand(1..6)]
    player_A.hand = roll_dice(eye_on_the_dices1)
    player_B.hand = roll_dice(eye_on_the_dices2)

    # 出た目の確認・役の決定
    puts <<~TEXT
    名前: #{player_A.name} 
    出目  #{eye_on_the_dices1}
     役: #{player_A.hand}
    -
    名前: #{player_B.name}
    出目  #{eye_on_the_dices2} 
     役: #{player_B.hand}

    press enter
    TEXT
    teisi = gets
#  勝敗判定
   win_or_lose = player_A.check_win_lose(player_B)
#  賭け金の移動
   move_money = player_A.transfer_money(player_B,win_or_lose)

   player_A.money += move_money
   player_B.money -= move_money
    puts <<~TEXT
    名前: #{player_A.name} 
     所持金:#{player_A.money} ペリカ
    -
    名前: #{player_B.name} 
     所持金:#{player_B.money} ペリカ
    --------------------------
    TEXT
# 最終的な勝敗判定
    if player_A.money <= 0
        puts '所持金ゼロ。負けました…'  
        break
    elsif player_B.money <= 0
        puts '勝ちました!'
        break
    end
# ----決着が着くまでループ
end

いちおうこれで遊べるようになりました。しかし、まだ改善が必要ですね。

3.賭け金の例外処理

 上記のコードでは賭け金の入力に制限がありません。この場合、所持金以上に賭け金を入力することもできますし、負の数や小数点、文字列も受け付けてしまいます。
 改善しましょう。

#  賭け金の設定(自分)
    while true
        puts '掛け金を入力してください (終了するにはexitを入力)'
        bet_money = gets.chomp

        if bet_money == "exit"
            puts '終了'
            exit
        end
        if bet_money =~ /^[0-9]+$/ 
            if bet_money.to_i > player_A.money
                puts '所持金より多い金額は賭けられません'
            elsif bet_money.to_i == 0
                puts '0は無効です'
            else
                player_A.bet_money = bet_money.to_i
                break
            end
        else
            puts '正の整数で入力してください'
        end
    end

変更点は三つです。

  • exit と入力するとゲームを終了できるように変更
  • =~ /^[0-9]+$/ で文字列や小数点がないことを確認
  • 所持金より多い場合または0の場合はエラーが発生

これで問題なく動くはずです。

4. ゲームができた!

 Rubyだけでゲームがつくれてしまった。ちょっと嬉しい。嬉しかったので勉強会で発表した。広島cluster というイベントで、広島県のITちっくなものならなんでも発表できる。

https://hmcn.connpass.com/event/175209/

 資料はこちら。
 https://www.slideshare.net/Kkondo2/ruby-234646600?ref=https://hmcn.connpass.com/event/175209/presentation/

5. 改善点がたくさんある!

 無事にコードが書けましたが、まだまだ改善するところがあります。エンジニアの方々にレビューを依頼したらたくさんのフィードバックをいただきました。ひとまずgithubのissueに入れておいて、どんどん対応することにしました。

 やっていくぞ!
https://github.com/kyokucho1989/ruby-game/issues

 あと、こちらのソースコードは自由に使ってください。cloneして改造してもOKです。
https://github.com/kyokucho1989/ruby-game

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

【Rails5.2】RESTful APIをRailsで簡単に実装する手順(APIモード)

環境

ruby 2.6.4

Rails 5.2.4.1

rbenv 1.1.2

mysql2 0.5.3

やりたいこと

APIモードで簡単なtodoアプリを実装します

RESTfulなAPIをRailsで実装する手順を書いていきます

わかりやすさを追求して書いていきます

元記事:【Rails5.2】RESTful APIをRuby on Railsで実装する簡単手順(APIモード)

カンタン実装手順

ステップ1. APIモードで新規アプリを作成

$ rails new sample_app --api -T

ステップ2. TDD(テスト駆動開発)用にGem追加

Gemfile
group :development, :test do
  gem 'rspec-rails', '~> 3.5'
end

group :test do
  gem 'factory_bot_rails', '~> 4.0'
  gem 'shoulda-matchers', '~> 3.1'
  gem 'faker'
  gem 'database_cleaner'
end

ステップ3. gemインストール

$ bundle install

ステップ4. Rspecファイル生成

$ rails generate rspec:install

ステップ5. factories作成

$ mkdir spec/factories

ステップ6. rails_helper.rb修正

spec/rails_helper.rb
require 'database_cleaner'

# 追加
Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

# 追加
RSpec.configure do |config|
# 追加
  config.include FactoryBot::Syntax::Methods
# 追加
  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
    DatabaseCleaner.strategy = :transaction
  end

# 追加
  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run
    end
end

今回は導入部分だけですがapiモードで開発する際に参考になればと思います

参考文献

今回の記事の続きはこちらです
スクリーンショット 2020-06-01 1.44.45.png

参考にした記事です
スクリーンショット 2020-06-01 1.46.35.png

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

Ruby で嵌る AtCoder ABC 169 C 浮動小数点

はじめに

「AtCoderは、世界最高峰の競技プログラミングサイトです。
リアルタイムのオンラインコンテストで競い合うことや、
3,000以上の過去問にいつでもチャレンジすることができます。」

を利用して、プログラミングの勉強をしています。
AtCoder さん、ありがとうございます。

今回のお題

AtCoder Beginner Contets C - Multiplication 3
Difficulty: 536

今回のテーマ、浮動小数点による誤差

浮動小数点数 -WikiPedia

Ruby

WA1

wa1.rb
a, b = gets.split.map(&:to_f)
puts (a * b).floor

切り捨てなのでfloorを使用するもWA

WA2

wa2.rb
a, b = gets.split.map(&:to_f)
puts ((a.to_i * (b * 100)) / 100).floor

100倍してから100で割る作戦、失敗。

WA3

wa3.rb
a, b = gets.split.map(&:to_f)
puts ((a.to_i * (b * 100)) / 100).to_i

神頼みでfloorto_iにしてみる。

WA4

wa4.rb
a, b = gets.chomp.split
puts ((a.to_i * (b.to_f * 100).to_i) / 100)

標準入力からの受け取りを文字列にする作戦。

AC

ac.rb
a, b = gets.chomp.split
puts ((a.to_i * (b.gsub!('.', '').to_i).to_i) / 100)

to_f100倍するのを諦め、小数点.の文字を削除する作戦。
$\huge{成功}$

何が良くて、何が拙かったよく分かっていないのですが、とにかく疲れました。

まとめ

  • ABC 169 C を解いた
  • Ruby に詳しくなっていない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む