20200517のRubyに関する記事は30件です。

railsでアプリを作る際にdbを指定する方法

railsでアプリを作る際にdbを指定する方法

簡単なことなのですが、よくDB指定を忘れてアプリを作ってしまう私への戒めの備忘録です、、、。

データベースの指定

rails newをする際に、データベースを指定してアプリを作ります。
何も指定しないとデータベースのデフォルトはsqliteになるので、Mysqlなどを使いたい時に行います。

$ rails new アプリ名 --database=mysql

簡略化した書き方でこういうのもあります。

$ rails new アプリ名 -d mysql

こっちの方が簡単でいいですね、-dはデータベースの意味です。

簡単に備忘録として書かせていただきました。

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

Day16 変数が使える範囲・スコープについて

変数の受け渡しまとめ

ReviewApp
def post_review
  # 変数の定義
  post = {}
  puts "ジャンルを入力してください:"
  post[:genre] = gets.chomp
  puts "タイトルを入力してください:"
  post[:title] = gets.chomp
  puts "感想を入力してください:"
  post[:review] = gets.chomp
  line = "---------------------------"

  # レビューの描画
  puts "ジャンル : #{post[:genre]}\n#{line}"
  puts "タイトル : #{post[:title]}\n#{line}"
  puts "感想 :\n#{post[:review]}\n#{line}"

  # 配列オブジェクトに追加
  posts << post
end

↑これだとpost_reviewメソッド内にpostsが定義されていないのでNG。
原則メソッド内で定義した変数しか使えない。
これを使えるようにするために、引数というものを使う。

<引数を使う上でのルール>
1. 「メソッドを定義している部分」と「メソッドを呼び出す部分」両方に書く
2. 「仮引数」と「実引数」の名前は、一致している必要はない

引数を使ったメソッドの実行
def multi(number)         #メソッド定義では、仮引数として、numberとしている。
  puts number * number
end

puts "何か数字を入力してください"
value = gets.to_i

multi(value)             #実際にmultiメソッドを使うときは、さっきのnumberではなく、valueを置いている。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webアプリケーション(Rails)のコードレビューをする時に気にしている観点

コードをレビューする時にいつも似たようなところを指摘している気がしたので自分が気にしている観点をまとめてみました。
使用言語に依存しない観点もありますが、私は仕事では主にRailsを使っているので、RubyやRailsに特化した観点もあります。

とりあえず現時点で思いつくものをざっと書きましたが、今後も気にしている観点を思い出したら追記していきます。
開発もQiita記事も最初から完璧を目指さずに継続してデリバリー(CD)していくのがよいですね。

インデントやコーディングルール

インデントやコーディングルールに従っているかなど、コードの静的な部分のチェックは静的解析ツールを使いましょう。
人が目視で確認するのは時間がもったいないし、漏れやブレが発生します。
また細かいところにばかりに気が取られてしまい本来指摘すべきところを見逃してしまいます。

静的解析ツールはRubyではRubocopが有名です。
このような開発をサポートする機能の導入は初期開発では後回しにされがちですが、静的解析ツールは途中で入れるのがとても大変です。
後から追加しようとすると、ルールを緩和したり免除するコードがでてきてしまうので初期開発の段階で入れるようにしましょう。
個人的にはきつめに設定しておいて、必要に応じて緩和していくのが良いと思います。

静的コードチェックに縛られすぎないか

1つ前に静的コードチェックを使えと書いたばかりで早速矛盾するテーマなのですが、静的コードチェックの指摘を直すためにわかりづらい修正をするくらいなら静的コードチェックの方を緩和したり例外を設定した方が良いです。
よくあるのがメソッドの行数超過です。
メソッドが一定行を超えると分割しろと言われるのですが、処理が1つのまとまりであるとことが妥当な場合はわかりづらくなってしまうだけなので無理に分ける必要はないと思います。

HTTPのステータスコードは適切か

Railsだとステータスコードとメソッドやレスポンスの整合性があっていなくても問題なく動くことが多いので、ステータスコードの使い方が適切かを確認するようにしています。
細かい使い分けは人によって解釈が分かれるところだと思いますが、主要なステータスコードについて私の解釈を書きます。

2XX

リクエストを正常に処理した時のステータスコードです。

201 Created

POSTやPUTリクエストでリソースを生成した時に使います。
201を使う場合はレスポンスボディに生成したリソースを返却することがよくあります。
もし返却するものがないのであれば、204 No Contentを使った方が良いと思います。

204 No Content

DELETEリクエストでリソースを削除した時に使うことが多いですが、POSTやPUTなどでもレスポンスボディがない場合に使います。
これを使う場合はレスポンスボディに値を入れてはいけません。

200 OK

上記以外で正常なレスポンスを返す場合は基本的に200 OKを使えば良いと思います。

3XX系

別のページへリダイレクトさせたい時のステータスコードです。

301 Moved Permanently

恒久的に別のページにリダイレクトさせる場合は301を使います。
例えばWebページを移行した場合などに古いページのURLから新しいページへリダイレクトさせる時に使います。
また、SEO対策としても役立ちます。301を指定すると検索エンジンが新しいURLをインデックスしてくれます。

303 See Other

私も最近まで意識していなかったのですが、リソースを登録・更新させた後にそのリソースの詳細ページなどにリダイレクトさせる場合は303を使います。
下記のようなパターンです。
POST /resources でリソースを新規登録して、リソース情報を表示するページ(GET /resources/:id)へリダイレクト

302 Found

301とは異なり、一時的に別のページにリダイレクトさせる場合は302を使います。
Railsでredirect_toを使うとデフォルトで302になりますが、リダイレクトさせる目的を考えると302の用途は少なく、301 or 303に振り分ける方が適切なことが多いと思います。

4XX系

クライアント起因のエラーが発生した場合のステータスコードです。

404 Not Found

ページが見つからない場合に使われるステータスコードです。
画面に大きく「404」と表示されることも多いので一般の方でも知っている方が多いと思います。

人によっては考え方が分かれるところかもしれませんが、私はリソースが見つからないことがクライアントにとって異常なのか正常なのかを考えて404と200を使い分けるようにしています。
イメージしやすいようにいくつか具体例を挙げます。

  • GET /resources/:idでリソースが見つからない場合
    クライアントが存在しないidを指定しているため404を返却

  • TODOリストを取得するGET /todosでTODOが0件の場合
    TODOが0件ということは正常動作なので200を返却

  • お店検索で絞り込み条件を指定したら0件になった
    クライアントが指定した条件に合致するお店がなかったの404を返却
    SEO観点でも0件のお店検索結果ページを検索エンジンにインデックスさせたくないので404を指定することでインデックスさせない。

401 Unauthorized

認証が必要なアクションに対して有効な認証情報が渡されなかった場合に使用します。

403 Forbidden

認証はしているが利用権限が不足している場合に使用します。
例えば、あるリソースに対して読み取り専用のユーザーが更新を行おうとした場合などに使用します。

ただし、403を使う時に考慮した方が良い点が1つあります。
あるユーザーにとって非公開のリソースにアクセスした場合です。
この場合、システム的に考えるとアクセス権限がないので403を使いたくなりますが、403を返却するとリソースが存在していることがユーザーに伝わってしまいます。
こういう場合は存在自体を知らせない方が良いので、存在しないという意味を込めて404を返却するのが良いでしょう。

400 Bad Request

必須パラメーターが指定されていないなど不正なリクエストが来た場合に使用します。
401, 403, 404に当てはまらない場合には大体400を使うことが多いです。

410 Gone

期間限定のキャンペーンページで期間が終了してページを削除した後など、恒久的に削除するURLにアクセスされた時に使います。
蛇足ですが、最近はキャンペーン期間が終わっても放置されているページが多くて検索した時のノイズになってしまい邪魔ですね。
期限が過ぎたら削除するというタスクも積んでおき、きちんと実行してほしいものです。

5XX系

サーバー起因のエラーが発生した場合のステータスコードです。

500 Internal Server Error

サーバーで予期せぬエラーが発生した場合に使用します。
プログラムで明示的に500エラーを発生させることはあまりないと思うので、明示的に500エラーを返却している箇所があったら適切なエラーを返却するように修正した方が良いと思います。

503 Service Unavailable

定期メンテナンスなどにより、一時的に正常にアクセスできずメンテナンスページを表示する場合などに利用します。

クライアントへのエラーメッセージが適切か

エラーが発生した時にクライアントにエラーメッセージを返却することがあると思いますが、そのメッセージが適切かを確認します。
観点はそのエラーメッセージを受け取ってアクションを起こせるかです。
よくある悪い例は複数入力項目がある画面で1つ入力条件を満たしていない時に「入力項目が不正です」のような項目を特定しないメッセージを返すパターンです。
これだとどの項目が間違っているのか分からず、何をすれば良いか分からなくなります。
「xx項目の値は数値で入力してください」のように項目とエラー原因を伝えてあげましょう。

ただ、ユーザーに細かく伝えることがNGの場合もあります。
例えばID/パスワードを入力するログイン画面で存在するIDを指定してパスワードが間違っていた場合に「パスワードが間違っています」と返してしまうとIDが存在していることがユーザーに伝わってしまいます。
この実装をしてしまうと悪意のあるユーザーが適当にIDを入力して存在しているIDのリストを作ることができてしまいます。
ログイン画面はIDが存在しない場合もパスワードだけが違う場合も「ログイン情報が不正です」のように何が間違っているか特定できないメッセージを返すのが良いです。

apiのレスポンスは意味のある値を返す

最近のWebアプリケーションはフロントとバックエンドを別で作ることが多いと思います。
そのためバックエンドはAPIとして機能を提供することがよくあります。
APIを作る際、列挙型の項目を返却する時は値を見るだけで意味がわかるように返却すべきです。
例えば下記のようなステータスがあるとします。

enum status {
  todo: 1,
  in_progress: 2,
  done: 3
}

これをフロントに返却する場合は数値(1, 2, 3)ではなく名称('todo'など)を返却すべきです。
status=1のように数値を返却するとレスポンスを見た時に1ってなんだっけ?となっていしまいますが、status='todo'のように名称を返却するとパッと見ただけで意味を理解することができます。

配列の並び順が一意になるか

配列を返却する場合は配列の並び順が一意になるかをチェックします。

下記ではuser_idで絞り込んだreviews配列を返却していますが、orderを指定していないのでどのような並び順で返却されるのか不定です。
呼び出し元が並び順を気にしないことが確定しているのであれば良いですが、並び順が定まるようにorderを指定しましょう。

def my_reviews(user_id)
  Review.where(user_id: user_id)
end

下記ではcreated_atの降順を指定しています。これであれば新規作成したものから順番に取得できそうです。

def my_reviews(user_id)
  Review.where(user_id: user_id).order(created_at: :desc)
end

ただこれでもまだ問題があります。created_atが同じreviewがある場合に順番が定まりません。
並び順を確実に定めるには下記のようにorderに指定しているカラムで一意になるようになっているかを考えると良いです。
今回は一意にするために第2ソートにidの降順を指定しました。

def my_reviews(user_id)
  # 1. created_atの降順に並べる。ただしcreated_atはユニーク制約はないので同日がありえる
  # 2. 同日だった場合、idの降順に並べる。idはユニークなので同じ値はありえない
  # => 並び順が一意に定まる
  Review.where(user_id: user_id).order(created_at: :desc, id: :desc)
end

他人が読んでスムーズに理解できること

コードレビューをしている時に理解するのに時間がかかる実装や勘違いしてしまう実装を見かけることがあります。
このような実装は今後このコードを見る人全員が同じように感じてコードリーディングの工数を上げてしまい、バグを埋め込む確率も上げてしまうので可能な限りシンプルな実装にした方が良いです。
どうしてもシンプルにできない場合はコメントを入れるなどすると良いと思います。
Railsなどフレームワークを使っていると下記のようにフレームワークの枠から離れた使い方をしていると勘違いさせてしまうことがあります。
勘違いさせるコードは高確率で後々バグを生み出すのでなくしていきましょう。

class Review
  belongs_to :user
  enum status {
    open: 0,
    close: 1,
  }
end

class User
  # × デフォルトのアソシエーションなのに条件を指定して絞り込んでいる
  has_many :reviews, -> { open }

  # ○ 条件をつける場合はそれがわかる名前にしましょう
  has_many :open_reviews, -> { open }, class_name: 'Review'
end

class Hoge
  def fuga(user_id)
    user = User.find user_id
    # ここだけ見るとuserのreviewsを全部取得していると勘違いする可能性が高い
    user.reviews
  end
end

YAGNI

ご存知の方も多いと思いますが、YAGNIという言葉があります。
詳細はwikipediaを参照してください。
https://ja.wikipedia.org/wiki/YAGNI

上記に記載されている通り、まだ使わない機能を想像して実装しておいてもそのまま使える可能性はほとんどありません。
未来を想像して書いているコードを見かけたら要注意です。実装されたコードを削除してもらうのは心苦しいですが削除しておく方が良いことが多いです。
残しておくと後々それが邪魔になったり、リファクタリングの時に使っていないのに直さないといけなかったりと生産性が下がる可能性があります。

メソッドや定数のscopeは小さくする

外部から呼び出さないメソッドや定数はprivateにしておきましょう。
publicだとそのメソッドや定数に手を入れる時の影響範囲がソース全体になっていしまいますが、privateだと同Class内だけを気にすれば良いのでリファクタリングなどソース修正の影響範囲調査がかなり楽になります。

責務は適切か

各クラスはそれぞれ責務を持っています。その責務が守れているかをチェックします。

例えばアカウントをアクティベートするアクションについて考えてみます。

  • アクティベーションするにはtokenが必要
  • Accountのstatusとアクティベート日時を更新してtokenを削除する

アクティベーションする時に実行される処理はAccountの責務を持っているAccountモデルが知っているべきことなので、下記の○のような方針で実装されている方が良いです。

class AccountController
  # × アクティベーションの時にすべきことをcontrollerが把握している
  def activate
    account = Account.find_by(token: params[:token])
    account.status = :activated
    account.activated_at = Time.now
    account.token = nil
    account.save!
  end

  # ○ アクティベーションの時にすべきことをcontrollerは知らず、accountモデルに依頼している
  def activate
    account = Account.find_by(token: params[:token])
    account.activate!
  end
end

class Account
  def activate!
    status = :activated
    activated_at = Time.now
    token = nil
    save!
  end
end

includes, eager_load, preload

これらの違いが曖昧な方は、Railsを使っている方は必ず1回は見ていると思われる下記のQiita記事をご覧ください。
https://qiita.com/k0kubun/items/80c5a5494f53bb88dc58

includesはpreloadとeager_loadを勝手に使い分けてくれる便利な存在です。
Railsを使い始めたことはincludesを使っておけばいいだろうくらいに思っていたのですが今の考えは逆です。
基本preloadかeager_loadを使うようにしています。
というのも実装する時にjoinすべきかどうかはきちんと把握して実装すべきと考えているからです。
実装によってjoinしたりしなかったりするincludesは制御が難しく予期せぬ挙動をしてしまう可能性があるので使わない方が良いです。

無駄なjoinはないか

ActiveRecordはとても便利ですが、RailsエンジニアからSQLを遠い存在にしているように感じます。
自分でSQLを書いていると書かないようなSQLも平気で発行してしまいます。
その中でも特に見かけるのは無駄にjoinしているパターンです。
下記に具体例を示します。
organizatoin_idを指定してその組織に所属するuserを取得するメソッドfind_by_organizationです。

class User
  has_many :user_organization

  def self.find_by_organization(organization_id)
    # × organizationsテーブルは結合する必要がない
    # select * from users inner join user_organizations on users.id = user_organizations.user_id inner join organizations on user_organizations.organization_id = organizations.id where organizations.id = #{organization_id}
    joins(user_organization: :organization).merge(Organization.where(id: organization_id))

    # ○ user_organizationsしか結合していない
    # select * from users inner join user_organizations on users.id = user_organizations.user_id where user_organizations.organization_id = #{organization_id}
    joins(:user_organization).merge(UserOrganization.where(organization_id: organization_id))
  end
end

class Organization
  has_many :user_organization
end

class UserOrganization
  belongs_to :user
  belongs_to :organization
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails Docker 環境構築

前提

railsでDockerの環境構築をした時の備忘録。
Docker for macインストール済み

本題

rails
|--Dockerfile
|--Gemfile
|--Gemfile.lock
|--docker-compose.yml
Dockerfile.
FROM ruby:2.4.5
RUN apt-get update -qq && apt-get install -y build-essential nodejs
RUN mkdir /app
WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle install
COPY . /app

DockerfileをbuildしDocker Image(コンテナ仮想環境の雛形が作成される)

Gemfile.
source 'https://rubygems.org'
gem 'rails', '5.0.0.1'

Gemfile(Installしたいgemを定義)→bundle installコマンド実行→gemがインストールされる→InstallしたgemがGemfile.lockに記載される

docker-compose.yml
version: '3'
services:
  web:    #Railsコンテナの定義
    build:  .  #Dockerfileを元にイメージを作成し使用すること
    command: bundle exec rails s -p 3000 -b '0.0.0.0'  #Rails起動のコマンド
    volumes:
      - .:/app
    ports:
      - 3000:3000  #<公開するポート番号>:<コンテナ内部の転送先ポート番号>
    depends_on:
      - db  #Railsが起動する前にMySQLサーバーが先に起動するように設定
    tty: true
    stdin_open: true
  db:    #MySQLサーバーコンテナの定義
    image: mysql:5.7
    volumes:
      - db-volume:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: password
volumes:
  db-volume:  #この定義によりPC上にdb-volumeという名前でボリューム(データ保持領域)が作成される

上記設定が終わったらターミナルにてRailsプロジェクトの作成

terminal.
rails$ docker-compose run web rails new . --force --database=mysql

docker-compose run webでwebサービスのコンテナで後ろのコマンドを実行するコマンド

--forceは、既存ファイルを上書きするオプション
--databace=mysqlはMySQLを使用する設定を入れるオプション

buildが完了したら、Railsで使用するデーターベースファイルを編集

config/database.yml
#省略

default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root
  password: password  #編集
  host: db  #編集

#省略

設定が完了したら下記のコマンドをターミナルに入力

terminal.
rails$ docker-compose up -d

上記は、現在のディレクトリにあるdocker-compose.ymlに基づいてコンテナ(仮想環境)を起動するコマンド

続いて、開発環境用のデータベースが作成されていないため、下記コマンドで作成

terminal.
rails$ docker-compose run web bundle exec rake db:create

bundle exec rakeの部分は、Rails環境にインストールされているrakeコマンドを実行している
rake db:createでRailsで使用するデータベースをMySQLサーバー上に作成してくれる

ブラウザでlocalhost:3000と入力しRailsの画面が出てきたら完了!

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

【Ruby on Rails】NoMethodError undefined method `devise_for'のエラー解消

NoMethodError undefined method `devise_for'...のエラーの対処方法

実装が完了したあとにブラウザを更新して内容を確かめようとしたときにこのようなエラーがでてしまいました...
スクリーンショット 2020-05-17 21.05.04.png
打ち間違えを確認しても問題がないのにどうしてエラーになったんだろう...と思った方はサーバーをもう一度立ち上げ直すことで解消されます!簡単なエラーです^_^

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

Rspec、TDD(1)

初めまして。4月から医療業界→Webエンジニアに転職しました。Ruby経験は2ヶ月程度です。

入社して1ヶ月半が経過し、細かい修正以外に、新しい機能の開発なども少しずつ行うようになってきており、少しずつ成長を感じています(そう言い聞かせています)。

新しい機能を開発する際に「テスト書いておいてね。」と言われテストコードを見てみると、「書き方が分からん・・・。」という状態になり、優先的にテストの勉強をしています。どの条件でテストをした方が良いのかはなんとなく分かる(気がしている)のですが、Rspec独特の記法への慣れも少なからず必要であると思います。

現在は以下の書籍や記事を参考に学習を進めています。
Everyday Rails - RSpecによるRailsテスト入門
使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」
テスト駆動開発

備忘録ですが、テストを勉強した過程を少しずつ記事として残して行こうと思います。

そもそも自分の書いたテストを信頼出来ないと始まらないですもんね・・・。

そもそもテストを書くメリットとは?

1.テスト全体にかかるコスト削減

  • 自動テストをコツコツテストを書いていくことで、既存の機能が想定通りに動いていることを確認しながら、新機能の開発ができる
  • リファクタリングをする際に元の機能を破壊していないか担保することができる

2.環境の変化に強くなる

  • ruby、rails、gemのアップデートなどのバージョン変更時に影響を調査することができる

3.テスト自体が使用のドキュメントとなる

  • 期待している挙動の詳細を表すものとなる為、他者が仕様を理解する手助けとなる
  • 仕様やインターフェイスについてに再考する機会となる

4.コードの粒度を保つ助けとなる

  • テストが書きにくいメソッドやアクションはそれだけ多くの役割を持っていることとなり、実装について考え直す機会となる

5.全体的な開発効率の向上

  • ずばりこれ。手動テストして、お祈りデプロイするよりもずっと安心、安全に開発が行える

Rspecのセットアップ

  • Railsにデフォルトで含まれていない為Gemfileに追記→bundle install
Gemfile
group :development, :test do
  gem 'rspec-rails', '~> 3.8.0'
end
  • テスト用DBの作成
config/database.yml
test:
  <<: *default
  database: db/test.sqlite3

bin/rails db:create:allを実行

Rspecの設定

  • bin/rails g rspec:installを実行 spec格納ディレクトリ、ヘルパーファイル、設定ファイルが作成される
.rspec
--format documentation

追記すると出力情報が読みやすくなる

  • Rspecテスト効率を上げる為にbinstub、springをインストールする

binstubについて
Springについて

gemfile
group :development do
  gem 'spring-commands-rspec'
end

$bundle exec spring binstub rspecを実行
これで$bin/rspecでテスト実行可能になる

最初に基本的な構文の確認

  • describe: テストのグループ化宣言、ネスト可能
  • it: テストをexampleという単位にまとめる
  • expect(A).to eq B: AがBであると期待するテスト
sample_spec.rb
Rspec.describe '計算' do
  it '100 + 100は200' do
   expect(100 + 100).to eq 200
  end
  it '100 * 100は10000' do
    expect(100 * 100).to eq 10000
  end
end
  • context: 条件別にグループ分け
  • before: exampleの実行前に必ず呼ばれる、テスト実行前のデータ準備やアセットなどを行うことが多い
  • beforeとitはスコープが異なる為、変数を使用する時は注意が必要

  • let(:A) { B }: BをAとして参照することができる

  • letは遅延評価の為、呼び出されるまでは使われない

  • let!: exampleの実行前に定義した値が作られる、遅延評価が原因でテストが落ちている時はこちらが良いかも

  • subject: テスト対象のオブジェクトをまとめることができる

  • その場合はis_expectedを使用する

  • shared_example、it_behaves_like: exampleがDRYでない時に再利用することができる

  • shared_context、include_context: contextの再利用が可能

とりあえず今回はここまでです。
基礎は理解したのでどんどん書いていこうと思います

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

【開発ログ⑮】外部リンクやPDFリンクをつけたい

前提について

はじめまして、
プログラミングスクールに通ういりふねと申します。この記事は、スクールの課題である個人アプリの開発の記録を書くことで、自身のアウトプットに利用しています。もし、読んでいただけた方がいましたら、フィードバックをしていただけたら嬉しいです。

開発するのは「有給休暇管理ツール」です。仕様は過去記事をどうぞ。

アプリはデプロイまで行いますが、サービスとして提供するものではありません。あくまでも自学習の一環ですので、ご理解下さい。では本題へどうぞ。

今回の実施内容

今回は厚生労働省のリンクや有給休暇の届出のPDFのリンクを付けたいと思います。おまけ機能的なものですね。実際には以下の手順で実施します。

  • 厚生労働省のリンクを実装
  • 新しいタブで開くを実装
  • google driveの共有設定
  • 有給休暇の届出のリンクを実装

厚生労働省のリンクを実装

スクリーンショット 2020-05-17 19.19.29.png

ページ左下の関連サイトのボタンに設置します。こちらは、link_toにURLを入力するだけなので、とっても簡単です。

branches/_side.html.haml
〜中略〜
    .links
      = link_to '#' do
        有給休暇届
      = link_to "https://www.mhlw.go.jp/shingi/2004/04/s0428-7b2a.html", class: "link" do
        関連サイト

新しいタブで開くを実装

実際にリンクをクリックすると、現在開いている有給休暇管理ツールのページから厚生労働省のページに遷移します。これでは、いちいち戻るボタンを押す必要があるので、新しいタブにリンク先が表示されるようにします。

branches/_side.html.haml
〜中略〜
    .links
      = link_to '#' do
        有給休暇届
      = link_to "https://www.mhlw.go.jp/shingi/2004/04/s0428-7b2a.html", target: :_blank, class: "link" do
        関連サイト

追加したのは、「target: :_blank」の部分です。これで、新しいタブで開くようになりました。
スクリーンショット 2020-05-17 19.28.07.png

google driveの共有設定

次にPDFの文書「年次有給休暇届」のリンクを実装します。有給休暇の申請を紙ベースで管理している会社の方が多いと思いますので、有給休暇管理ツールに見本があれば、すぐにプリントアウトして社員さんに渡してあげることができます。
今回は、google driveの共有設定を利用してリンクを取得します。
スクリーンショット 2020-05-17 19.32.12.png
まずは、自身のdriveにPDFファイルを保存します。保存はドラッグ&ドロップでOKです。次に共有したいファイルを右クリックして、「共有」を押します。出てきたメニューの下側にリンクを取得があるので、その中の「リンクを知っている全員に変更」をクリック、「リンクをコピー」をクリックします。
スクリーンショット 2020-05-17 19.39.17.png
参考にさせていただいた記事「Google ドライブのファイルを共有する」

有給休暇の届出のリンクを実装

最後に取得したリンクをrailsのlink_toに反映させます。

branches/_side.html.haml
〜中略〜
    .links
      = link_to "https://drive.google.com/file/d/1NXmC35ZbmCOAqu7oGyCOc2LqipHDGDJj/view?usp=sharing", target: :_blank, class: "link" do
        有給休暇届
      = link_to "https://www.mhlw.go.jp/shingi/2004/04/s0428-7b2a.html", target: :_blank, class: "link" do
        関連サイト

こちらの方法は、手軽にできますが、開いたリンクにアカウント名やメールアドレスが表示されるので、きちんとサービスとして実施する際には、専用のgoogleアカウントを用意するか、別の方法が良いかも知れません。

今日の振り返り

個人発表会が近くなってきました。有休の付与と消化を手入力で行うという意味ではだいぶ形になってきましたが、大切な付与計算などは省いているので、5月19日以降も継続してアプリケーションを作っていこうと思います。

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

Windows10 「rails s」を起動できない。localhost:3000にアクセスできない

初学者です。初歩的な質問だと思いますが、環境構築のところで躓いてしまったのでご教示頂けたら助かります。
Windows10環境下でRails serverを起動しようとトライしています。

コマンドプロンプトから

ruby -vで実行すると、
ruby 2.3.3p222 (2016-11-21 revision 56859) [i386-mingw32]

rails -vで実行すると
Rails 5.1.7

と返ってきています。アプリケーション名を「book」にする予定だったので、

rails new book

と実行し、C:\Users\81804の中に「book」というフォルダが生成されました。

cmdから「cd book」と実行し、bookディレクトリまで移動し、そこで「rails s」と実行したところ以下のようなエラーが表示されました。

Could not find gem 'turbolinks (~> 5) x86-mingw32' in any of the gem sources listed in your Gemfile.
Run bundle install to install missing gems.
ここで「bundle install」とコマンドプロンプトで実行してみました。するとしばらく文字列が綴られ、テキスト下部のほうを読んでみると、エラーを起こしていることがわかりました。エラーメッセージはこのような内容でした。

An error occurred while installing sqlite3 (1.4.2), and Bundler cannot continue.
Make sure that gem install sqlite3 -v '1.4.2' --source 'https://rubygems.org/'
succeeds before bundling.

In Gemfile:
sqlite3
このような内容でした。エラー文から
gem install sqlite3 -v '1.4.2' --source 'https://rubygems.org/
とコマンドプロンプトで実行すればいいのか?と解釈し、こちらを入力してみると

Gem files will remain installed in C:/RailsInstaller/Ruby2.3.3/lib/ruby/gems/2.3.0/gems/sqlite3-1.4.2 for inspection.
Results logged to C:/RailsInstaller/Ruby2.3.3/lib/ruby/gems/2.3.0/extensions/x86-mingw32/2.3.0/sqlite3-1.4.2/gem_make.out
と書かれ、何やら成功しているようにみえるのですが、いざ改めて「rails s」を実行してみると

Could not find gem 'turbolinks (~> 5) x86-mingw32' in any of the gem sources listed in your Gemfile.
Run bundle install to install missing gems.
また同じエラーが発生する、という状況に陥ってます。
関係ないかもしれませんが、ウイルスバスターは無効にしてあります。

どなたかご助言いただけないでしょうか。

使用PC環境は以下の通りです。
・Windows10 Home
・Intel Corei7-8550U CPU 1.80GHz 1.99GHz
・実装メモリ16.0GB
・64ビットオペレーティングシステム,×64ベースプロセッサ

https://teratail.com/questions/176355
こちらのご質問内容に非常に近しかったので早速トライしてみました。有効と思われる回答↓
「sqlite3の1.4.0と言うバージョンはrailsと相性が悪く、使えません。Gemfile内で以下のようにバージョンを指定してください」

gem 'sqlite3', '~> 1.3.6'

私には「gemfile」が何なのかわからなかったのですが、新たに生成したディレクトリ内に「Gemfile」という2KBくらいのファイルがあったので、これをVS Codeで開き、

gem 'sqlite3' と書かれていたところを
→gem 'sqlite3', '~> 1.3.6'
に書き換えて上書き保存してみました。

そして、もう一度コマンドプロンプトで、こちらのディレクトリまで移動し「Rails s」と実行してみましたが、やはり

Could not find gem 'turbolinks (~> 5) x86-mingw32' in any of the gem sources listed in your Gemfile.
Run bundle install to install missing gems.
というエラーが返ってきてしまいます...。

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

AtCoder Beginners SelectionでRuby学習【Some Sums】使えるメソッドを増やす

はじめに

Ruby学習の一環として「競技プログラミング(競プロ)」に挑戦します。
そのための学習の中で学んだことをアウトプットしていきます。
今回は「AtCoder Beginners Selection」の六問目(Some Sums)より。
https://atcoder.jp/contests/abs

問題

1以上N以下の整数のうち、
10進法でA以上B以下であるものの総和を求めなさい。

制約
1 ≤ N ≤ 10,000
1 ≤ A ≤ B ≤ 36
入力は全て整数である

入力は以下の形で与えられる。

N A B

# 例
20 2 5
出力例
# 上記例の場合
=> 84

20以下の整数のうち、各桁の和が2以上5以下なのは
2,3,4,5,11,12,13,14,20です。これらの合計である84を出力します。

解答①

まずは僕の解答から。

n, a, b = gets.split(" ").map(&:to_i)
r = 0
(1..n).each do |i|
  r += i if i.to_s.split("").map(&:to_i).inject(:+).between?(a, b)
end
print r

この解答をする中で学んだメソッドを以下にまとめていきます。

splitメソッド

入力を受け取る時によく使ってましたが、
今回のeach文の中での使用で、改めてStringクラスのメソッドだと認識出来ました。
解答では一度String型に変換した上でsplitメソッドを使い、またInteger型に戻すということをしています。

#例
print "1234".split("")
=> ["1", "2", "3", "4"]

between?メソッド

self.between?(min, max)
#selfがminからmaxの間(min,max含む)であった場合、trueを返す

#例
print 6.between?(1, 5) 
=> false

最小値と最大値を指定し、selfがその範囲内だった場合はtrueを返すこのメソッドを使って、
各桁の和がa以上b以下の範囲内かを判定しています。

解答②

他の方の解答を見てみます。

n, a, b = gets.split.map(&:to_i)
puts (1..n).select{|e|(a..b).include?(e.to_s.chars.map(&:to_i).inject(&:+))}.inject(&:+)

入力を受け取るところまでは、解答①とほぼ同じですね。その後の流れを見てみると、
① 整数の各桁の和がa以上b以下であるかとinclude?メソッドで判定する
② ①を通過した整数は、selectメソッドで新しい配列を生成し、その中に追加する
③ 条件に合う整数が出そろったところで、最後尾のinjectメソッドで整数の総和を求め、出力する

ここで使われているメソッドについて簡単にまとめます。

include?メソッド(Rangeクラス)

オブジェクトが範囲内に含まれている時、trueを返します。

#例
print (1..5).include?(4)
=> true

selectメソッド

条件に一致した要素を取得して、新しい配列として返します。

#例
print [1,2,3,4,5].select { |num| num > 3 }
=> [4, 5]

最後に

以上、AtCoder Beginners Selection【Some Sums】を解く中で学んだメソッドをご紹介しました。

もし間違いなどございましたら、ご指摘いただけると嬉しいです。

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

描いて理解する Action Cable

はじめに

コロナ騒ぎで時間があるので Rails の Action Cable を触ってみました。

基本的な使い方は Rails ガイドで調べましたが、いまいちピンとこなかったので理解するために図を描きました。特に用語についての章を読んでもそれぞれの用語の関係性がよく分からなかったので、自分なりに整理しています。

用語について

Connection, Consumer

ActionCable-Connection.png

WebSocket の基礎

  • Web 上でクライアント、サーバ間の双方向通信を実現するための通信規格
  • Upgrade: websocket ヘッダをつけた HTTP リクエストで開始
  • レスポンスとして HTTP ステータス 101(Switching Protocols) が返ってきたら TCP コネクションを使いまわして WebSocket として通信ができる
  • 送信できるデータはテキストまたはバイナリ
  • HTTP とは別のプロトコルなので Cookie などは送られない
  • 通信が確立したあとはクライアント、サーバどちらからでもメッセージを送ることが可能

Connection

  • WebSocket としてのコネクションを表すクラス
  • (おそらく)WebSocket として通信が確立した時点でインスタンスが生成される
  • 開始時のリクエストはふつうの HTTP なので Cookie も送られる
  • なので connection.rb では cookies が使える
  • 認証処理はこのクラス内に記述

Consumer

  • WebSocket コネクションのクライアント(ブラウザなど)
  • HTTP とは異なり、同じブラウザの異なるタブは完全に別のクライアントとして扱われる

Consumer を区別する方法

HTTP とは異なり毎回認証情報が送られたりはしないので、コネクション確立時に「このコネクションは誰のものか」をサーバー側で管理する必要がある。

そのためのしくみとして Action Cable では Connection クラスの identified_by を使う。

# app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = User.find_by(id: cookies.encrypted[:user_id])
    end

  end
end

Channel, Subscription

ActionCable-Subscription.png

Channel

  • 文字通りのチャンネル
  • Controller のようなもの

Subscription

  • チャネルの購読
  • ひとつのコネクション内でいくつでも作成可能
  • あるチャネルの購読をしている、という意味ではコンシューマをサブスクライバとも呼ぶ

以下のように consumer.subscriptions.create で新たな Subscription オブジェクトが作成され、consumer.subscriptions に追加される。

chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create("ChatChannel", {
  // 略
});

Stream

ActionCable-Stream.png

  • ブロードキャストを行う場合に使う
  • チャネルがサブスクリプションとストリームの紐付けを行う
  • あるストリームにブロードキャストした場合、そのストリームに紐づくサブスクリプションにメッセージが送られる

たとえば以下のようなチャネルクラスを定義しておくと ChatChannel をサブスクライブしたときに chat_001 というストリームに紐付けられる。

chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_001"
  end
end

この状態で以下のコードが実行されると、サブスクライバに {"text": "Hello, World!"} というJSON文字列が送られる。

broadcast
data = {text: "Hello, World!"}
ActionCable.server.broadcast("chat_001", data)

ストリームに対するブロードキャスト自体は、Rails アプリケーションのどこからでも実行可能。

複数のStream

ActionCable-Stream-2.png

前節の例では固定文字列だったので、どのサブスクリプションも同じストリームに紐付いていました。しかし、チャネルをサブスクライブするときサーバー側にパラメータを渡すことができるので、ユーザーごと、もしくは開いているページごとに異なるストリームに紐付けることが可能です。

まとめ

ActionCable-まとめ.png

ストリームとサブスクリプションの関係がよく分からなかったのでそこを中心に図示してみました。この図だとストリームが必須みたいに見えてしまうので、action パラメータによってサーバー側のメソッドを実行する、という観点でも整理して別途記事にしてみようと思います。

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

正規表現でバリデーション設定した項目を空欄のままでも通過できるようにしたい

はじめに

電話番号やメールアドレス等の入力値のフォーマットチェックに正規表現が使える。

指定した文字列、数列以外の値が入力されたらバリデーションチェックに引っ掛かる仕組みだ。

今回は任意入力とした項目を指定された正規表現、または空欄のままでもバリデーションが通過するようにしたい。

電話番号にバリデーションをかけてみよう

今回は電話番号を例にして、まずは電話番号にバリデーションをかけてみる
スクリーンショット 2020-05-17 17.37.14.png

validates :phone_number, format: { with: /\A\d{10,11}\z/ }

これはハイフンなしの10桁または11桁の半角数字で入力してくださいという意味の正規表現だ。

今回は電話番号は任意入力にしたいのでpresence: trueは書いてないし、このままでいいだろうと思ったが…。

問題発生

指定された正規表現でのバリデーションは突破し、次に入力欄を空白にして進んだらバリデーションに引っかかってしまった!
スクリーンショット 2020-05-17 17.40.01.png

原因

どうやら正規表現でバリデーションをかけるとその正規表現以外の入力をたとえ空欄(nil)でも受け付けなくなってしまうらしい。

解決法その1

メンターに聞いてみるとカスタムメソッドというのを用いて

「空か、あるいは10桁または11桁の半角数字」といったカスタムメソッドを作るという方法があると言われたが、カスタムメソッドについて調べると非常に面倒臭いし大変そうである。

↓カスタムメソッドの参考記事
https://qiita.com/h1kita/items/772b81a1cc066e67930e

なので自分で他の方法がないか考えて試行錯誤した結果、非常に簡単な解決法が見つかった。

解決法その2

以下のようにallow_blank: trueを追加するだけである。

validates :phone_number, format: { with: /\A\d{10,11}\z/ }, allow_blank: true

これは文字通り空欄のままでもOKという意味。

これで電話番号が未入力でもバリデーションが通るようになりました!

まとめ

入力値を正規表現、または空欄でバリデーションを通過したい場合はallow_blank: trueを追加すれば良い。

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

Rubyのselfってなんだ?

記事の対象

selfって何?
クラスメソッドって?
classやインスタンスはなんとなくわかる

この記事で解説すること

  • selfとは
  • オブジェクトとは
  • いろいろな場所でのself(クラス内・クラス外・インスタンスメソッド内・クラスメソッド内など)

selfの一般的な説明

selfはオブジェクトそのものである

と言われてもピンとこなかったんですよね。
ただ、腑に落ちた今は確かにそのとおりだなと思います。

オブジェクトとは

まずオブジェクトについて理解していきましょう。
Rubyは全てがオブジェクトです。

  • 文字列オブジェクト
  • 数値オブジェクト
  • 配列オブジェクト
  • ハッシュオブジェクトなど

現実世界で言えば人やモノ、動物などありとあらゆるものをオブジェクトとして扱います。

オブジェクトとインスタンスの違い

オブジェクトとインスタンスの違いは下記サイトでわかりやすく説明されていたので気になる方は読んでみてください。
一言で言うと
クラスから生成されたオブジェクトのことをインスタンスと呼ぶ
です。

クラス・インスタンス・オブジェクトの違いは神の気分になるとわかる

インスタンスの生成

クラス名.new

インスタンスの生成
class Sample
end

p Sample.new #=> #<Sample:0x00007fe79b835240>

インスタンスが生成できました。

selfについて

ここまではインスタンスやオブジェクトについてでしたが、ついにselfに片足を突っ込みます。
とりあえず適当にrbファイルを作って実行してみましょう。
もしくはターミナルでirbと打った後selfと打ってみましょう。
(Rubyをインストールしていないと使えません。ruby -vと打ってバージョン情報が出ないようならインストールされていないのでインストールしてください。)

sample.rb
  p self
  #=> main

mainというものが出てきました。
mainとはなんでしょうか?

一言で言うと
mainとはトップレベルに存在するインスタンスのことでObjectクラスのインスタンスです。
難しいですね。

しっかり解説していきます。まずはObjectクラスからです。

Objectクラス

Objectクラスとはクラスの実質的頂点(親)にいるクラスです。(実際にはもう一つ上のクラスが存在しますが、ここでは割愛します。)
クラスは階層構造になっていて文字列クラスなどの親クラスがObjectクラスになります。
(classメソッドで自身のクラス、superclassメソッドで親クラスを取得することができます。)

p "文字列".class #=> String
p String.superclass #=> Object

トップレベル

次にトップレベルですが、これはclass定義やmodule定義の外側のことを言います。
(moduleについては本記事では解説しません。制限が加わったclassのようなものと思っておいてください。)

トップレベル
# ここから
#
#
#
# ここまでがトップレベル
class Sample
  # クラス内
end
# ここから
#
#
#
# ここまでもトップレベル
module SampleModule
  # モジュール内
end
# ここから
#
#
#
# ここまでもトップレベル

そしてターミナルでirbと打った時にいるのもトップレベルです。
ターミナルでirbと打つと
irb(main):001:0>
となります。
ここがトップレベルであり、そしてirbの後にmainとあることが確認できると思います。

またトップレベルでselfのクラスを確認してみると

p self       #=> main
p self.class #=> Object

トップレベルのself(main)のクラスはObjectだということがわかります。

先ほどの発言に戻ると
mainとはトップレベルに存在するインスタンスのことでObjectクラスのインスタンスです。でした。
これは

トップレベルでのpメソッドの実行結果はp self #=> mainだったこと。
そしてp self.class #=> Objectだったこと
そしてselfとはオブジェクト自身(インスタンス自身)を指すこと。

からもわかります。

トップレベルでいきなりpメソッドなどを実行できるのはmainがObjectクラスのインスタンスだからなんですね!
(ObjectクラスにはKernelモジュールがインクルードされているので実行できるというのが正確な答えだがここでは割愛)

クラス内のself

次にクラス内のselfをみていきます。

クラス内のself
class Sample
  p self #=> Sample
end

selfはオブジェクト自身のことでした。そのためクラス内ではそのクラスオブジェクト自身を表しています。

インスタンスメソッド内のself

インスタンスメソッド内のselfはインスタンス自身(オブジェクト自身)を表しています。

インスタンスメソッド内のself
class Sample
  def hoge
    p self
  end
end
sample = Sample.new
sample.hoge #=> #<Sample:0x00007f8524034f70>

ちなみにRubyはメソッドを呼び出す時、暗黙的にselfに対してメソッドを呼び出しています。(勝手にselfに対してメソッドを呼び出す。)

どういうことかというと

class Person
  def greeting
    p self
    hello
  end

  def hello
    p "hello"
  end
end

person = Person.new
person.greeting #=> #<Person:0x00007f9937848a28>
                #=> "hello"

これと同じものが以下になります。

class Person
  def greeting
    p self 
    self.hello
  end

  def hello
    p "hello"
  end
end

person = Person.new
person.greeting #=> #<Person:0x00007f9937848a28>
                #=> "hello"

どこが違うかお気づきでしょうか?
そうです。greetingメソッド内のhelloの呼び出しの前にself.がついています。
実はこのself.は付けても付けなくても同じ動きになるのです。
メソッドの呼び出しのたびにselfをつけるのは大変なのでこの仕様になっているのだと思います。

この時のgreetingメソッド内のselfはPersonクラスのインスタンス自身ですのでそのインスタンスに対してhelloメソッドが呼ばれているんですね。
(greetingメソッド内のself.helloトップレベルのperson.helloは同じ結果になる。)

クラスメソッドのself

クラスメソッド内のselfもオブジェクト自身を表しています。

クラスメソッド内のself
class Person
  def self.greeting
    p self
  end
end

Person.greeting #=> Person

Personが返されています。

もちろんこのメソッド定義部分のselfもオブジェクト自身を表しているのですがメソッドの定義場所にあるselfってちょっと不思議な感じですよね。

def self.greeting # ← このself
end

クラスメソッドとは特異メソッドの一つです。
では特異メソッドとは何か?

特異メソッド

特異メソッドはクラスとは関係なく個々のオブジェクト(インスタンス)に対してメソッドを定義します。

morning = "good morning"
def morning.greeting
  p self
end

morning.greeting #=> "good morning"

これは何が起こっているのでしょうか?
まずmorning = "good morning"でmorningという変数(オブジェクト)に"good morning"という文字列を代入しています。
その後morningオブジェクトに対してgreetingメソッドを定義しています。
greetingメソッド内ではpメソッドでselfを返しています。
この場合のselfは"good morning"という文字列オブジェクトなので
"good mornig"という文字列が出力されます。

この形が特異メソッドです。

クラスメソッド定義方法と同じですね。

クラスメソッド定義
class Person
  def self.greeting
  end
end

上のコードと見比べるとdef 〇〇.メソッド名のように定義方法が一緒ですね。
そしてこの時のselfはPersonだったので

クラスメソッド定義
class Person
  def Person.greeting
  end
end

このようにも書けます。

最後に

いろいろなところでselfを使ってみました。
selfがオブジェクト自信を表すということが理解できれば幸いです。

やはり自分でコードを動かして検証していくと理解が高まりますね。
何かお気づきのことがありましたらコメント欄で教えていただけると助かります。

参考

るびま
http://i.loveruby.net/ja/rhg/book/minimum.html

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

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

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

ファイル構成とコードを書いた時に、考えていたこと

  • card.rb(カードクラス)

    • 1枚ごとのカードデータの書き換えができないように(データが壊れないように)、attr_readerにを使った。
  • deck.rb(デッキクラス)

    • インスタンスを生成した段階で、デッキがシャッフルされているようにし、それをテストした。
    • each文を使って、デッキの生成をなるべく簡潔に書いた。
    • クラス変数を使い、カードをドローしたときに、今、山札の何枚目がドローされているかカウントするようにした。
  • player.rb(プレイヤークラス)

    • プレイヤーとディーラーで共通していて、同じデッキを使う為に、クラス変数にデッキのインスタンスを代入して、デッキを使用した。
  • judge.rb(ジャッジクラス)

    • 判定するためのクラス、わかりやすくするために、あえてプレイヤーと別でモジュールを使用した。
  • test.rb(動作確認テスト)

    • minitestは「プロを目指す人のためのRuby入門」で、書いて以来、使ったことがなかったので、理解を深める為に使ってみた。
  • main.rb(ブラックジャックを動かすメインファイル)

    • わかりやすいようにゲームの全体の流れのみを最初に書いた
    • そこから必要なクラスとメソッドを考えて、付け足していった。

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'
            10
        elsif @number == 'A'
            1
        else
            @number.to_i
        end
    end
end

deck.rb (デッキクラス)

deck.rb
require './card'

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 = ['❤︎', '♦︎', '♤', '♧']
        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
end

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

player.rb
require './deck'
require './judge'


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

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


    def show
        puts "#{self.name}の手札です"
        self.hands.each do |hand|
            puts "[ #{hand.mark} #{hand.number} ]"
        end
        puts "[ トータルスコア #{@total_score} ]"
    end

    # テスト用メソッド

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

    def total_score_calc
        self.hands.each do |hand|
            @total_score += hand.convert
        end
        #puts "トータル「#{@total_score}」" 
    end

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

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

judge.rb(ジャッジクラス)

judge.rb
module Judge
    # def game_judge(target)
    #     if self.total_score > target.total_score
    #         puts "あなたのトータルスコア[ #{self.total_score} ]"
    #         puts "ディーラーのトータルスコア[ #{target.total_score} ]"
    #         puts 'あなたの勝ちです'
    #     elsif self.total_score == target.total_score
    #         puts "あなたのトータルスコア[ #{self.total_score} ]"
    #         puts "ディーラーのトータルスコア[ #{target.total_score} ]"
    #         puts '引き分けです'
    #     else
    #         puts "あなたのトータルスコア[ #{self.total_score} ]"
    #         puts "ディーラーのトータルスコア[ #{target.total_score} ]"
    #         puts 'あなたの負けです'
    #     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?
        if self.total_score > 21
            true
        else
            false
        end
    end
end

test.rb(動作確認テスト)

test.rb
require 'minitest/autorun'
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
        # 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 1, @@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
end

main.rb(ブラックジャックを動かすメインファイル)

main.rb
require './player'

lose_message =<<~TEXT
    あなたの負けです
TEXT
win_message =<<~TEXT
    あなたの勝ちです
TEXT
game_draw_message =<<~TEXT
    引き分けです
TEXT
error_message = "無効な文字です。もう一度入力してください"


class Play
    attr_accessor :player, :dealer

    def initialize
        @player = Player.new
        @dealer = Player.new(name: "ディーラー")
    end

    def test
        @player.draw
        @player.draw
        @player.show
    end
end

while true # 再プレイのためのループ処理
    # 初ターンの処理
    player = Player.new
    dealer = Player.new(name: "ディーラー")
    puts "ようこそ、ブラックジャックへ"
    puts "カードを配ります"
    player.draw
    player.draw
    dealer.draw  
    dealer.show
    # ブラックジャックテスト
    # dealer.reset
    # dealer.in_blackjac 
    # dealer.show
    # player.reset
    # player.in_blackjack
    # player.show

    # プレイヤーがブラックジャックじゃないか確認の処理
    if player.blackjack?
            puts "ブラックジャック!"
            player.show
    else
        player.show
        puts h_or_s_message =<<~TEXT
        選択してください
        Push「h」, ヒット
        Push「s」, スタンド
        TEXT
    end

    # ヒット、スタンドの処理(プレイヤーのターン)
    while true
        # ブラックジャックの場合は強制的にスタンドさせる処理
        if player.blackjack?
            command = 's'
        else
            command = gets.chomp # ヒットかスタンドか選択
        end
        # ヒットの場合の処理
        if command == "h"
            while true
                player.draw
                if player.burst? # バーストしていないか確認
                    player.show
                    puts "バースト!!"
                    puts lose_message
                    break
                end
                # ドロー後のトータルスコアの確認
                player.show
                # 最大値(21)の場合は強制的にスタンドの処理
                if player.total_score == 21
                    puts 'トータルスコアが最大値に達しました'
                    command = 's'
                    break
                end
                # 最大値ではない場合は、再度ヒットするか処理
                puts "もう一度引きますか?"
                puts h_or_s_message
                while true
                    command = gets.chomp # 再度、ヒットかスタンドの選択
                    if command == 'h'
                        break
                    elsif command == 's'
                        break
                    else
                        puts error_message
                        redo
                    end
                end
                # ヒットかスタンドの処理
                if command == 'h' 
                    redo
                elsif command == 's'
                    break
                end
            end
            # バースト時の強制終了処理
            if player.burst?
                break
            end
        else
            # h,s以外が入力されたときの処理
            puts error_message
            redo
        end
        # スタンド時の処理(ディーラーのターン)
        if command == 's'
            # プレイヤーがブラックジャックだった場合のディーラーの処理 
            if player.blackjack?
                dealer.draw
                dealer.show
                if !dealer.blackjack?
                    puts win_message
                    break
                else
                    puts game_draw_message
                    break
                end
            end
            # プレイヤーに勝つまでディーラーはヒットし続ける処理
            while player.total_score > dealer.total_score
                dealer.draw
                dealer.show
                if dealer.burst?
                    puts 'バースト!'
                    puts win_message
                    break
                elsif dealer.total_score == 21 && player.total_score == 21
                    break
                end
            end
            if dealer.burst? # バーストしていた場合処理処理終了
                break
            elsif dealer.total_score == 21 && player.total_score == 21 # お互い最大値に達していないか確認
                puts game_draw_message
                break
            else
                puts lose_message
                break
            end
        end
    end
    # 再プレイの処理
    puts <<~TEXT
    もう一回遊びますか?
    選択してください
    Push「h」, はい
    Push「s」, いいえ
    TEXT
    while true
        command = gets.chomp # 再プレイするか選択
        if command == "h"
            command = nil
            break
        elsif command == "s"
            break
        else
            puts error_message
            redo
        end
    end
    # 再プレイの処理
    if command == nil
        redo
    else
        break
    end
end

学べたこと

「プロを目指す人のためのRuby入門」
「なぜこんなことをするんだろう」、
「いつこれは使うのだろう」、
と、思ってたことが、使うタイミングがよくわかった。

最初はminitestは使わず「p」を使ってテストしていたが、
使った方が作業が早くなるということを理解した。

minitestのメソッド名の最初の単語に「test」を入れないと、
テストしてくれないことをすっかり忘れてて、2日くらい悩んだけど笑

使ったことないものは積極的に使っていくと理解が深まる。

まだリファクタリングできるところは、たくさんあるんだけど、
作るの飽きそうになったので、一旦、Qiita書きました。

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

巨大なファイル(100万行とかでも)を1行1行処理するrubyスクリプトのテンプレートを作った

私が巨大ファイルを作るときにテンプレートをコピーして使います。

メリット

  • いちいちCSVやファイルの読み書きをするときにコマンドを調べなくていい
  • プログレスが出る
    • 大抵巨大ファイルを読む場合10分くらいかかるけど、進捗が見えなくて進んでるかわからないため表示

スクリプトテンプレート

以下のスクリプトはCSVを読む場合のスクリプトです

  • テキストファイルをいじる場合はこちら
  • 1行1行の逐次処理でなく全行見たあとに集計処理をとかをやりたい場合はこちら
require "csv"

$is_debug = true

def main(csv) #, output_file)
  # output_file_writer = CSV.open(output_file, "w")
  # output_cols = ["hoge"]
  # output_file_writer.puts(output_cols)

  FileHandler.csv_foreach(csv) do |row|
    # 処理
    p row

    # output_row_values = []
    # output_file_writer.puts(output_row_values)
  end

  # puts "#{output_file}を作成しました"
  # output_file_writer.close
end

module FileHandler
  class << self
    def csv_foreach(csv)
      log "#{Time.now}: read start #{csv}"
      all_line_count = line_count(csv)

      return_values = CSV.foreach(csv, headers: true).with_index(1).map do |row, row_no|
        log progress(row_no, all_line_count) if progress_timing?(all_line_count, row_no)
        yield(row)
      end

      log "#{Time.now}: read end #{csv}"

      return_values
    end

    def line_count(file)
      open(file){|f|
        while f.gets; end
        f.lineno
      }
    end

    private

    def log(message)
      puts(message) if $is_debug
    end

    def progress_timing?(all_line_count, line_no)
      return false if all_line_count < 100

      # NOTE 処理時間によって変更
      div_number = 100

      percent_unit = all_line_count / div_number
      line_no % percent_unit == 0
    end

    def progress(current_count, all_count)
      "#{Time.now}: #{CommonUtilities.percent(current_count, all_count)}% (#{CommonUtilities.number_with_commma(current_count)} / #{CommonUtilities.number_with_commma(all_count)})"
    end
  end
end

module CommonUtilities
  class << self
    def percent(num, all_count)
      (num.fdiv(all_count) * 100).round(2)
    end

    def number_with_commma(number)
      number.to_s.gsub(/(\d)(?=\d{3}+$)/, '\\1,')
    end
  end
end

main(ARGV[0])
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【開発ログ⑭】社員を削除したら有休データも削除したい

前提について

はじめまして、
プログラミングスクールに通ういりふねと申します。この記事は、スクールの課題である個人アプリの開発の記録を書くことで、自身のアウトプットに利用しています。もし、読んでいただけた方がいましたら、フィードバックをしていただけたら嬉しいです。

開発するのは「有給休暇管理ツール」です。仕様は過去記事をどうぞ。

アプリはデプロイまで行いますが、サービスとして提供するものではありません。あくまでも自学習の一環ですので、ご理解下さい。では本題へどうぞ。

今回の実施内容

前回までで有休登録の機能が実装できたので、今回は社員と支社の削除機能を実装します。具体的には以下の通りです。

  • 社員削除の問題点
  • 社員削除の制約の設定
  • 支社の削除画面の検討
  • ビューで確認

社員削除の問題点

今回のアプリケーションでは、4つのテーブルを用意しています。社員は、employeeテーブルに保存していますが、外部キーとして支社のbranch_idを持っています。社員にとっては親テーブルは支社になるわけです。さらに社員は、有休登録されたデータを複数持ちます。これは、holidayテーブルに保管され、holidayレコードは、外部キーとして社員のemployee_idを持ちます。

親子関係を表すと以下のとおりです。
親:支社のbranchテーブル
子:社員のemployeeテーブル
孫:有休のholidayテーブル

ここで、親や子(つまり支社や社員)を削除すると、その下のテーブルには外部キーに対応する親レコードがなくなってしまいエラーになってしまいます。
そこで、社員を削除したら、社員の持つ有休データもまとめて消せるように実装していきます。

社員削除の制約の設定

実装は非常に簡単で、モデルファイルにdependent制約を記述するだけです。
参考にさせていただいた記事「[Rails] has_manyおよびbelongs_toへのdependent: :destroyの設定について」@after4649

employee.rb
class Employee < ApplicationRecord
〜中略〜
  has_many :holidays, dependent: :destroy
〜中略〜
end

アソシエーションを記述しているhas_manyの後ろに文章を追記するだけで、コントローラーなどの設定は変更せずにそのまま使用することができました。
ただし、記事にもありますが、belongs_toの方につけると大変なことになるので、注意が必要です。それからこのdependent制約には:destroy以外にも記述方法があるようなので、ご自身にあったものを使用して下さい。

支社の削除画面の検討

社員の設定は、これで良いとして支社を削除する際に同じように社員とその有休のデータをまとめて削除してよいか悩みましたが、同じようにdependent制約をかけることにしました。
そのかわり、支店削除は簡単に行えないよいうにステップに分けるようにしました。
まず、削除ボタンがindexに表示されないように「編集/削除」のボタンを設定し、編集画面を表示させます。編集画面内に「支社削除」がありますが、大きめの文字で注意を促します。
スクリーンショット 2020-05-17 17.20.08.png
ここについては、私の技術がさらに向上したときに別の方法を検討しようと思います。今は、これが精一杯汗

ビューで確認

ちょっと回り道をしましたが、ビューで確認します。座布団運びの山田くんは、5日有休を付与されていますが、このたび解雇となります。スクリーンショット 2020-05-17 17.25.46.png

社員の削除は表の右端(これも簡単に押せないよう配慮しているつもり)にあります。クリックすると見事に削除されました。
スクリーンショット 2020-05-17 17.26.54.png
付与及び消化の履歴にも山田くんの履歴は残っていません。無事に成功しました。
スクリーンショット 2020-05-17 17.27.38.png
ちなみに可愛そうだったので、この後山田くんは再登録してあげることにしました。

今日の積み上げ

dependent制約は、実はカリキュラムで触れられていました。まだまだ知識が十分定着していないのかも知れません。今後もどんどんアウトプットをして学んだことを定着させたいです。

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

YouTube Data APIを使わずにチャンネルの最新ライブ配信IDを取得する方法

バックグランド

最近の個人アプリ開発中に出会った問題です。
YouTubeのライブ配信が日々活発している今、チャンネルIDを持ちながらそのチャンネルの最新ライブ配信の情報を取得したい場合がよくあります。しかし、YouTube Data APIを使って最新ライブ配信情報を取得しようとすると以下二つの問題があります。

  1. チャンネル情報丸ごと取得するチャンネル(channels)リソースからチャンネル最新動画取得しようとしてもライブ配信情報(ライブ配信中または公開予定の情報)が含まれない。(現在2020年5月時点)
  2. チャンネルIDを持ってsearchリソースを使うと、どうやら書き込み操作と同様に扱われるので他の情報取得操作リスト(list)の何十倍のクォータコスト(https://developers.google.com/youtube/v3/getting-started?hl=ja) が消費されます。

ライブ配信情報の有無を頻繁に取得・更新しないと意味ないので、searchリソースを使うと一日使えるクォータコストの割り当てはすぐなくなります。一方、ライブ配信IDであるvideoIdを取得する手段があれば、videoIdを指定して動画(videos)リソースから低いクォータコストでライブ配信の情報をアクセスできます。
そのため、YouTube Data APIを使わずにチャンネルの最新ライブ配信IDであるvideoIdを取得することが必要とされています。

方法(Ruby)

あまり使われていないかもしれませんが、チャンネルにライブ配信情報があれば下記のurlを使ってその最新のライブ配信playerをページに埋め込むことができます。

url = "https://www.youtube.com/embed/live_stream?channel=<チャンネルID>"

最新のライブ配信を表示してくれれば話がしやすくなります。次のやることがそのページを裸にして必要な情報videoIdを取り出すことです。

content = Net::HTTP.get_response(URI.parse(url)).entity
unless content.match(/watch\?.+/) == nil
  match = content.match(/watch\?.+/)[0]
  videoId = match.sub("watch?v=","").sub("\">","")
end

YouTubeのページやurlには基本的にwatch?v=<videoId>のような形に書かれているので、取得したcontentからmatchsubメソッド使えばvideoIdを簡単に入手できます。(そこにちゃんと整備したYouTubeさんに感謝)
videoIdを入手した以上、YouTube Data APIの動画(videos)リソースを使えば配信状態やら配信時間やらの取得はもう何ても来い!

参考(PHPでのやり方)

https://stackoverflow.com/questions/58040154/how-to-get-live-video-id-from-from-youtube-channel-html

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

AtCoder Beginners SelectionでRuby学習【Coins】短いコードで解答する

はじめに

Ruby学習の一環として「競技プログラミング(競プロ)」に挑戦します。
そのための学習の中で学んだことをアウトプットしていきます。
今回は「AtCoder Beginners Selection」の五問目(Coins)より。
https://atcoder.jp/contests/abs

問題

500円がA枚、100円がB枚、50円がC枚持っています。
これらの硬貨の中から何枚かを選び、合計金額をX円にする方法は何通りあるか。
同じ種類の硬貨同士は区別出来ません。

制約
0 ≤ A,B,C ≤ 50
A + B + C ≥ 1
50 ≤ X ≤ 20,000
A,B,Cは整数である。
Xは50の倍数である。

入力は以下の形で与えられる。

A
B
C
X

# 例
2
2
2
100
出力例
# 上記例の場合
=> 2

条件を満たす選び方は以下の2通り。
500円を0枚、100円を1枚、50円を0枚
500円を0枚、100円を0枚、50円を2枚

解答

まずは僕が最初に書いたコードです。

a = gets.to_i
b = gets.to_i
c = gets.to_i
x = gets.to_i

count = 0

a_array = []
b_array = []
c_array = []

for i in 0..a do
    a_array.push(500 * i)
end

for i in 0..b do
    b_array.push(100 * i)
end 

for i in 0..c do
    c_array.push(50 * i)
end

all_array = a_array.product(b_array, c_array)

all_array.each do |one_array|
    if one_array.inject(:+) == x
        count += 1
    end
end

print count

① 硬貨の種類ごとに、0枚からn枚まで、選べる枚数に応じた金額を持つ配列を生成する(for~do~end)。
※100円を3枚持っているなら、[0,2,4,6]の配列が生成されるように。
② 生成された3つの配列から1つずつの要素を取り出し、新たに配列を生成。これを全パターン用意(productメソッド)
③ ②で用意した配列1つ1つに対して、その要素の合計がX(を50で割ったもの)と等しくなるパターンの数を記録する (count)。

上記のような流れで解答しました。
この解答をする中で学んだメソッドを以下にまとめていきます。

for文

for 変数 in オブジェクト do
  実行する処理
end

オブジェクトに繰り返す範囲を指定します。
解答では、0から持っている硬貨の枚数まで、0枚、1枚、2枚と順に取り出し、変数に入れるイメージです。
その上でブロック内に指定する処理を繰り返す、といった具合です。
解答では、直前に生成した配列に、枚数に応じた金額を順に入れていっています。

pushメソッド

配列オブジェクト.push(要素, )

pushメソッドは、配列の末尾に指定した要素を追加します。

productメソッド

#例
[1,2].product([3,4],[5,6])
=> [[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]]

複数の配列が持つ全要素の組み合わせを配列にして返します。

eachメソッド

配列オブジェクト.each do |変数|
  実行する処理
end

配列から要素をひとつずつ取り出し、変数に代入した上で、
要素の数だけ指定した処理を実行します。

injectメソッド

配列に対して、要素の合計を算出して返します。
今回はシンボル(:+)を用いた記法を使っています。

#例
array = [1, 2, 3]
result = array.inject(:+)
print result
=> 6

改めて調べていて知りましたが、sumメソッドを使うともっと簡単に書けるようです。

#例
array = [1, 2, 3]
result = array.sum
print result
=> 6

短いコードで解答する

ここからは今回の解答を短くしていきます。

縦に並んだ複数の整数入力を受け取る

A, B, C, x = 4.times.map{gets.to_i}

timesメソッド

指定の回数処理を繰り返す場合に使います。

#例
3.times { puts "Hello, World!" } 
=>Hello, World!
=>Hello, World!
=>Hello, World!

配列を生成せず、入力された整数を直接判定に使う

解答では配列を生成して、全てのパターンの金額を追加する、ということを行っていましたが、
これによって解答がえらく長いことになってしまっていました。
入力した整数をそのままeach文を使って判定に使用することでコードの長さを抑えられました。

(0..A).each{|a|
  (0..B).each{|b|
    (0..C).each{|c|
      x == (500*a + 100*b + 50*c) ? count +=1 : count += 0
    }
  }
}

完成

A, B, C, x = 4.times.map{gets.to_i}
count = 0
(0..A).each{|a|
  (0..B).each{|b|
    (0..C).each{|c|
      x == (500*a + 100*b + 50*c) ? count +=1 : count += 0
    }
  }
}
print count

空白を入れて32行あったコードが9行になりました。

最後に

以上、AtCoder Beginners SelectionでRuby学習【Coins】から学んだメソッド・記法をご紹介しました。
今回のように短いコードで解答する練習は常にやっていきたいです。
他の方の解答を見てみると1行で解答をされている方もいました。
そこまでいくには道のりは長いですが、その分やりがいもありますね!

もし間違いなどございましたら、ご指摘いただけると嬉しいです。

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

【開発ログ⑬】formに0は入れさせない!!

前提について

はじめまして、
プログラミングスクールに通ういりふねと申します。この記事は、スクールの課題である個人アプリの開発の記録を書くことで、自身のアウトプットに利用しています。もし、読んでいただけた方がいましたら、フィードバックをしていただけたら嬉しいです。

開発するのは「有給休暇管理ツール」です。仕様は過去記事をどうぞ。

アプリはデプロイまで行いますが、サービスとして提供するものではありません。あくまでも自学習の一環ですので、ご理解下さい。では本題へどうぞ。

今回のアプリのフォームについて

記事にはしていませんが、有休の付与や消化を登録するフォームとそれらを表示するログを作成しました。以下は写真です。

こちらは、フォームです。たい平師匠がよくわからない理由で休もうとしていますが、今回は見逃します。
スクリーンショット 2020-05-15 10.53.14.png

次に有休の付与や消化の履歴を確認するビューです。社員の消化理由を確認したいとか、有休登録を行ったけど間違って登録していないか確認したいなど、入力する人にとって必要であろうと思い設置しています。有休登録の削除もできます。
スクリーンショット 2020-05-17 16.05.12.png

この消化の履歴は、右下に「付与か」「消化か」「付与と消化」が表示されるようにメソッドを設定していますが、ここの条件が今回のテーマに関連します。以下はメソッド部分です。

holiday.rb
class Holiday < ApplicationRecord
〜中略〜
  def add_or_delete
    if add_day.nil?
      "#{delete_day}日消化"
    elsif delete_day.nil?
      "#{add_day}日付与"
    else
      "#{add_day}日付与と#{delete_day}日消化"
    end
  end
end

add_day(付与日数)やdelete_day(消化日数)がnil(空)かどうかを条件に設定していますが、そこで問題になるのが、「0」という存在です。

0もデータでnilとは違う

例えば、有休登録時に付与を0、消化を1と登録すると、履歴には「0日付与と1日消化」と表示されてしまって、非常に不格好になりました。これを避けるためにモデルファイルに定義しているメソッドの条件を「nilまたは0ではない」と変更してもよいのですが、この後、実装する機能にいちいちこの条件を設定するのは面倒。ということでフォーム入力時点で「0」が登録されないようにしたいのです。

今回の実施内容

毎度のことながら前置きが長くなりましたが、ここから真の本題です。付与日や消化日のカラムには、nilか1以上の数字しか入らないようにいくつか改良します。具体的には以下のことを行いました。全部必要だったかどうかは、わかりません。

  • バリデーション「exclusion」
  • フォームの入力制限
  • リセットボタンの設置

バリデーション

0を保存させないと思って真っ先に検索したのが、バリデーションでした。調べてみると「exclusion」という、特定の文字を保存させないバリデーションがありました。

参考にさせていただいた記事【Rails】完全解説!Railsのバリデーションの使い方をマスターしよう!

記事を元に付与日と消化日に「0」という文字が入らないようにバリデーションをかけてみました。

holiday.rb
class Holiday < ApplicationRecord
  validates :reason, presence: true
  validates :add_day, exclusion: { in: [0]}
  validates :delete_day, exclusion: { in: [0]}
〜中略〜
end

0と入れて試してみましたが、どうやら保存されていないようです。念のため、10日付与なども行いましたが、こちらはちゃんと保存できました。

フォームの入力制限

しかし、保存されずにindexに戻ってきてしまうため、利用者の方にとっては、なぜ保存されなかったのかが分かりにくくなってしまいます。例えば、付与日を2、消化日を0と打ったが、送信前に間違えに気づき、付与日を0、消化日を2に修正して送信ボタンを押したのに保存されていないなどです。
そこで、フォームそのものにも1以下の数字は入れられないように制限をかけていきます。

holidays/new.html.haml
〜中略〜
            .field
              .field-label
                = f.label "消化日数"
              .field-input
                = f.number_field :delete_day, max: "50", min: "1"
            .field
              .field-label
                = f.label "付与日数"
              .field-input
                = f.number_field :add_day, max: "50", min: "1"
〜以下省略〜

number_fieldに最大50、最小1を追記しました。これによりフォームの上下のボタンでは1~50までしか選択できないようになりました。万が一、手入力で、0を入れて送信しようとしたときには、このフォームの制限によりエラー文が出てくるようになります。
スクリーンショット 2020-05-15 14.11.21.png

リセットボタンの設置

これで、0を保存されず、かつフォーム上でも0が送信できないように設定できましたが、念のためフォームのリセットボタンも設置します。わざわざ手入力で0を入れる方は少ないと思ったので、何かの入力ミスで0になってしまったけれど、フォームについている上下ボタンでは、変更できない!!という事態を防ぐためです。

holidays/new.html.haml
〜中略〜
          .btns
            = f.button "リセット", type: :reset, class: 'btn'
            = f.button "登録", type: :submit, class: 'btn'

最初、「f.reset...」とsubmitと同じ要領で記入していたのですが、うまく行かなかったので、再び調べて、下の記事で解決しました。
参考にさせていただいた記事「Railsでリセットボタンの実装方法」

ビューの確認

最終的なビューのスクリーンショットを取り忘れていたので、CSSが付与された後の状態になりますが、以下のように完成しました。
スクリーンショット 2020-05-17 16.46.51.png

今日の積み上げ

今まで、派手な機能にばかり目を向けていたので、こういった機能も大切であると学びました。あからさまなエラーが出ない分、開発者として細やかな気配りが求められるのではないかと思います。
一方で、こういったバリデーションは、必要であれば、どんどん付けられるのかも知れません。必要十分な量にとどめておかないと作業量が増えすぎるとも感じました。

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

容量がいっぱいなときに使うduコマンドが使いにくいから、rubyでラッピングしてみた

結論

duコマンドをrubyでラッピングしたスクリプトを作りました。

expand.rbという名前を作って、下のスクリプトを貼って、以下のように実行します。

# ruby expand.rb (調べたいパス)
$ ruby expand_du.rb /var/lib/
    7GB /var/lib/
    3GB /var/lib/jenkins
    2GB /var/lib/docker
    1GB /var/lib/mysql
    ...
    4KB /var/lib/mysql-files
du_-k_--max-depth_1_var_lib__20200516231438.logに保存しました

スクリプト(github)

# 使い方
# ruby expand_du.rb パス
# 例:ruby expand_du.rb ~
#
# 効果
# - パスに対してduコマンドを実行
# - 実行結果を標準出力とファイルに吐き出す
#
# 引数
# - パス: duコマンドを行う対象パス

require 'rbconfig'

def main(target_path)
  return puts "ArgumentError: パスを引数に入れてください" if target_path.nil?

  max_depth_option_str = if os == :macosx
    "-d"
  else
    "--max-depth"
  end

  exec_command = "du -k #{max_depth_option_str} 1 #{target_path}"
  du_result_str = `#{exec_command}`

  return if du_result_str.empty?

  output_disksizes(du_result_str, exec_command)
end

def output_disksizes(du_result_str, exec_command)
  disk_usages = du_result_str
                  .split("\n")
                  .map{|du_result| DiskUsage.new(du_result)}
                  .sort{|x, y| x.size <=> y.size}.reverse

  output_filename = "#{exec_command.gsub(/( |\/){1,}/, "_")}_#{Time.new.strftime("%Y%m%d%H%M%S")}.log"
  output_file = File.open(output_filename, "w")

  disk_usages.each do |disk_usage|
    puts disk_usage.to_s
    output_file.puts(disk_usage.to_s)
  end

  output_file.close
  puts "#{output_filename}に保存しました"
end

class DiskUsage
  attr_reader :size, :path
  def initialize(du_result_line)
    du_result_params = du_result_line.split(" ").map(&:strip)
    @size = du_result_params[0].to_i
    @humanreadable_size, @humanreadable_unit = calc_humanreadable_size
    @path = du_result_params[1]
  end

  def to_s
    # NOTE とりあえず5桁を指定。必要になったら増やす
    "#{sprintf("%5d" % @humanreadable_size)}#{@humanreadable_unit} #{@path}"
  end

  def humanreadable_size_with_unit
    "#{@humanreadable_size}#{@humanreadable_unit}"
  end

  private

  def calc_humanreadable_size
    return [@size, "KB"] if mb_size < 1
    return [mb_size, "MB"] if gb_size < 1
    return [gb_size, "GB"] if tb_size < 1
    [tb_size, "TB"]
  end

  def kb_size
    @size
  end

  def mb_size
    kb_size.fdiv(1024).round(1)
  end

  def gb_size
    mb_size.fdiv(1024).round(1)
  end

  def tb_size
    gb_size.fdiv(1024).round(1)
  end
end

def os
  case RbConfig::CONFIG['host_os']
  when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
    :windows
  when /darwin|mac os/
    :macosx
  when /linux/
    :linux
  when /solaris|bsd/
    :unix
  else
    :unknown
  end
end

経緯

レンタルサーバやMacの容量がいっぱいになったときに、どのフォルダが原因なのか知りたくなります。

そこでMacならストレージ使用状況を見たり、レンタルサーバでもdfコマンドとかで全体のどれくらいを使っているかはわかります。しかし全体がわかるだけでどのフォルダが原因かわかりません。

そこでターミナルを使える人が使えるのはduコマンドです(lsコマンドも候補にあがるんですが、lsコマンドは直下のファイルの大きさはわかるんですが、フォルダがどれだけ大きいかはわかりません)。

しかしこのduコマンドなんですが、UNIXコマンドであるせいか、ちょっといまいちなんですよね。

ソートがいまいち

読みやすくするのに -h オプションをつけて、sortコマンドを使ってサイズを降順で出すんですが、そうすると5KBが4GBよりも上に来るんですよね。数字だけ見ているので仕方ないんですが。

記録するのに2回打つのが面倒くさい

duを使うと標準出力に結果を出してくれるけれど、出したあと結局結果を記録したくなります。最初からファイル出力すればいいんですが、ファイル出力してからcatするのが面倒なんですよね。工夫してコマンド打てばファイルにも標準出力にも出してくれるんですが、duコマンド必要なときって大抵急いでいるので、楽にやりたかったんです。

スクリプトの特徴

↑の問題を解決したスクリプトが最初に載せたスクリプトです。

特徴は次のようになります。

  • 単位を考えた上でソートしてくれる
  • 標準出力にもファイルにも出してくれる(&どのフォルダを調べたかわかるファイル名で出力してくれる)

完全に自分用に作ったんですが、同じことに困っている方がいらっしゃったら使ってみてください。

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

Ruby on rails を初心者向けに解説①

はじめに

今回はRuby on railsの解説記事です。

初心者向けに、わかりやすく解説していきます。

頑張っていきましょう。

今回は、記事を書く上でたかし君に協力してもらいます。

Ruby on rails とは

Ruby on rails とは、Webフレームワークの一種です。

最初に、そもそもフレームワークとは一体何なのかについて解説します

フレームワークとは

フレームとは、簡単に言えば骨組みのことです。

フレームワークの意味をwikipedia先生に聞いてみましょう。

プログラミングにおいて、一般的な機能をもつ共通コードをユーザーが選択的に上書きしたり特化させたりすることで、ある特定の機能をもたせようとする抽象概念のことである。

相変わらず堅い言葉なので、噛み砕いて解説します。

フレームワークとは、様々な機能を提供してくれる骨組みのことであると考えて問題ないと思います。

今度は、フレームワークの具体例をみて考えていきましょう。

Bootstrap

何かWebサイトを作成しようとしたときに、コツコツHTMLとCSSを書いていくのは大変ですよね。

当然、何とか楽をしたいと考えます。どうすれば楽にできるでしょうか。

そうです、誰かが作ってくれたものをパクれば楽ができるのです。

しかし、Webサイトをそのままパクれば怒られてしまうし、作れるバリエーションも限られてしまいます。

ここでたかし君は考えました。

たかし君「そういえば、Webサイトに使われる部品って限られてるよな。ヘッダー部分とかボタン部分とかを個別に楽にかける枠組みがあれば楽に書けるんじゃね?」

こうして生まれたのがBootstrapです。

BootstrapはCSSのフレームワークで、Webサイトの各々の部品ごとの枠組みを提供してくれています。

Webフレームワークとは

ここまでの説明でなんとなく分かりましたね。

Webフレームワークとは、Webアプリを作るための枠組みのことです。

もう一度たかし君に聞いてみましょう。

たかし君「Webアプリ作るのめっちゃだるいな。でも、Webアプリで使う機能って以外に限られてるよな。ユーザーが打ち込んだURLに応じてデータベースを操作したり、HTMLファイルを作成して送ったり、ログイン機能が必要だったりするだけじゃん。この枠組みを誰かが作ってくれて、それをパクれば楽に書けるんじゃね?」

このようにして生まれたのが、Webフレームワークだったり、Ruby on railsだったりするわけです。

それでは、実際にRuby on railsでアプリを作ってみましょう。

骨組みの作成 (rails new)

最初にローカルのパソコンにRubyとRuby on railsをインストールしてください。

環境構築については、色々な人が記事を書いているので割愛します。

環境構築はうまく行かないことがほとんどですが、できるまで粘り強く調べ続けてください。応援しています。

最初に、railsアプリを作りたいディレクトリに移動して、以下のコマンドを打ちましょう。

rails new qiita_project

newの後ろは何でも好きな名前にしてください。

以下のようファイルが作成されます。

image.png

これで、Webアプリを作る際の枠組みを作成することができました。

サーバーの起動(rails server)

作成したqiita_projectに移動してください。

cd qiita_project

これで作成したqiita_projectに移動することができました。

次はサーバーを起動してみましょう。次のコマンドです。

rails server

次に、chromeなどのブラウザのURL欄に以下のURLを入力してください。

http://localhost:3000/

image.png

ここまでで、サーバーを起動することができました。

コントローラーの作成 (rails generate controller コントローラー名)

以下のコードでコントローラーを作成することができます。

コントローラーが何を行うかは後に解説します。

rails generate controller home

上のコードを実行すると、app>>controllers配下にhome_controller.rbという名前のファイルが作成されます。

image.png

home_controllerの中身を見てみましょう。

home_controller.rb
class HomeController < ApplicationController

end

このようにして、コントローラーを作成することができました。

それでは、コントローラーの解説行います。

コントローラーとは

コントローラーを理解するためには、まず以下の図を理解する必要があります。railが動作する仕組みについてです。

image.png

順番に見ていきましょう。

①ユーザーがサーバーにリクエストを送る

image.png

まず、左のユーザーがサーバーにリクエストを送ります。右の様々な動作は、全てサーバー側の話です。

リクエスト送るとは、サーバーに何かを要求することです。例えば、URLを入力すると、「このURLに対応するファイルを送ってこい!」と要求(getリクエスト)することになりますし、IDとパスワードを入れてログインしようとすれば、「送ったデータを使って何かしらの処理をしろ!」と要求(postリクエスト)することになります。

②ルーティングによりどのコントローラーを使うのかを決定

image.png

ルーティングにより、ユーザーから受け取ったURLを処理します。

例えば、ユーザーからhome/topというURLを受け取ったとします。

そうすると、ルーティングはhome#topというルーティングに変換します。

これは、「homeコントローラーのtopアクションを利用します」という意味です。アクションというのは、コントローラーごとに設定されている「何かを行うコード」だと考えてください。

今回は、簡単のためにユーザーからは「このURLに対応するファイルを送ってこい!」と要求(getリクエスト)されたと考えます。

ユーザーから"home/top"というURLが送られてきて、それをhomeコントローラーのtopアクションを実行するという命令にルーティングしましょう。

configディレクトリ配下のroutes.rbファイルに以下のように追記してください。

routes.rb
Rails.application.routes.draw do
  get "home/top" => "home#top"
end

コントローラーによりviewファイルを探索

image.png

home/topというURLがユーザーから送られてきて、ルーティングによりhome#topに変換されたとします。

この場合、homeコントローラーのtopアクションを行うことになります。

以下のように、homeコントローラーにtopアクションを追加しましょう。

home_controller.rb
class HomeController < ApplicationController
    def top
    end
end

このようにコードを書くと、homeコントローラーはhome/top.html.erbファイルを探して、それをユーザーに返してくれます。

viewファイルの作成

app >> views 配下のhomeディレクトリにtop.html.erbファイルを追加してください。

image.png

top.html.erbファイルを以下のように編集してください。

top.html.erb
<h1>Hello World</h1>

このようにして、viewファイルを作成することができました。

実際にレスポンスを見てみましょう。

ユーザーがURLを打ち込んだ場合

再び、以下のコードでサーバーを起動しましょう。

rails server

webブラウザで以下のURLを打ち込んでください。

http://localhost:3000/home/top

そうすると、以下の画面が送られてきます。

image.png

何が起きているのかを、もう一度ざっくり解説します。

http://localhost:3000/home/topというURLが送られると、それがルーティングにより`home#top`という形に変換されます。

それにより、homeコントローラーのtopアクションが行われます。

そうすると、app >> viewsディレクトリ配下のhomeディレクトリの中のtop.html.erbファイルがrailsにより発見され、それがユーザーに送られます。

終わりに

長くなってきたので、今回はここまでになります。

また暇なときに次回の記事を書きますので、読んで頂けると嬉しいです。

お疲れさまでした。

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

rails server起動時に「Could not find a JavaScript runtime.」とエラーが出る場合の対処法について

windowsでは出来ていたサーバーの立ち上げが買ったばかりのMacでは出来ず・・・。
色々調べた結果なんとか出来たのでまとめました。

エラー内容 

Progateの「Ruby on Railsの環境構築をしてみよう!(macOS)」の「5. ローカルでRailsサーバを立てる」を進め、rails sコマンドを実行したところ以下のエラーが発生。

% rails s
  (略)
Could not find a JavaScript runtime. 
See https://github.com/rails/execjs 
for a list of available runtimes. 
(ExecJS::RuntimeUnavailable)
 

ブラウザでlocalhost:3000と入力してもエラーで表示されず。
どうやらJavaScript runtimeとやらが存在しないためエラーが出ている模様。runtimeが分からず検索すると以下の記事を発見。

https://wa3.i-3-i.info/word13464.html

記事によると、

ランタイム(英:runtime)とは

プログラムとかを動かすとき(実行時)のこと。

あるいは

プログラムとかを動かすときに必要な部品のこと

とのこと。つまり、
Ruby自体にはJavaScriptを実行する機能が存在せず、実行するためにはruntimeというものが必要
ということらしい。

解決まで

とりあえずネットでCould not find a JavaScript runtime.と検索したところRuby on Railsチュートリアル の記事を発見。

https://railstutorial.jp/chapters/beginning?version=4.0#sec-rails_server

記事によると、

(JavaScriptランタイムがインストールされていないというエラーが表示された場合は、GitHubのexecjsページにあるインストール可能なランタイムを確認してください。Node.jsが特にお勧めです)。

とのこと。早速エラー文中にあるGitHubのサイトへアクセス。

https://github.com/rails/execjs

Node.jsをクリックし、公式サイトからダウンロード後インストール。
その後もう一度コマンドでrails sを実行するとサーバーが立った。

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

ドリル(自作)

経緯

 某プログラミングスクールのドリルはわかってなかったことだらけで勉強になるのですが,「問題は」出すけど,「解釈,解答の背景」は自分で調べろ的なスタンスなので公開記事で記述形式でまとめていきます。

 ちなみに前回の記事のようにスクールの問題は限定記事にまとめて復習しています。ちなみに下のを解く場合,自分が示した答えは最適解とは限りませんが,参考程度にして自分で調べていただければと。

railsにおけるコールバックってなんなの?












A

・オブジェクトはrailsのアクションで生成されたり削除されたりする。この過程をオブジェクトライフサイクルという。このサイクルのなかでオブジェクトが変化する前後で行われる処理がコールバック。
・乱暴に噛み砕くとはデータベースから抽出する時,登録する時に行われる処理。身近なものだとバリデーションとかもそう。データベースに登録する前になにか処理を行うなら,before_createとか,モデルにメソッド同様に記述。

シンボルってなんなの?












A

見た目は文字列だけど処理では数値として扱われるよ。シンボルに対応する数値は1対1だからたくさん定義をしてもメモリを圧迫しないよ

 selfってなんなの?










A

・モデルを通過するオブジェクトそのものだよ
・モデルの中はインスタンスとか定義してないから@postをコントローラーで定義しても認識してくれない。だからselfがその代わりになってくれる。p self.nameってやったらTaroが出力される感じ。selfはわざわざ定義する必要なし。

CSRFという攻撃の意味とrails上での対策












A

クッキーを不正に利用して他人のアカウントで不正操作(投稿削除)を行うこと。(噛み砕き)
対策ー protect_from_forgery with: :exception
これによってフォームからの投稿ごとに不正に利用されていないかトークンを発行して照合している。

countの使い方(引数の有無でどう変わるか)











A

レシーバの要素数を返します。
引数を指定しない場合は、レシーバの要素数を返します。
引数を一つ指定した場合は、レシーバの要素のうち引数に一致するものの個数をカウント
ブロックを指定した場合は、ブロックを評価して真になった要素の個数をカウント

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

Rubyの基礎~わかりにくいところ復習~

オブジェクト

オブジェクトとは、Rubyで扱うデータのことを指します。
文字列オブジェクトでは文字を扱います。
文字をダブルクォーテションまたはシングルクォーテーションで囲むことによって文字列になります。つまりそれを文字列オブジェクトと言います。
文字列オブジェクトの他にも、時刻オブジェクトや、日付オブジェクト、配列オブジェクト、数値オブジェクトなどもあります。

オブジェクト名   扱うもの      
文字列オブジェクト 文字        
配列オブジェクト 複数のデータ
数値オブジェクト 数値      
時刻オブジェクト 時間      
日付オブジェクト   日付     

メソッド

メソッドとは、プログラミングにおける何らかの処理をする命令群のことです。
例えば、ターミナルに文字列オブジェクトを出力したい時にはputsメソッドを使います。

オブジェクトを別のオブジェクトに形を変えるメソッドもあります。
例えばlengthメソッドです。このメソッドは文字列メソッドに使えるメソッドで文字列メソッドを自身の文字数の値を数値メソッドに変換します。
to_sメソッドは数値メソッドを文字列メソッドに変換してくれます。

返り値

返り値とは、オブジェクトや、メソッドが処理されたあとの最終的な値のことです。
Rubyのオブジェクト自体、メソッドを利用した式には必ず返り値があります。

つまりlengthメソッドを使ったときは、文字列の文字数の値の数値を返り値として返します。
to_sメソッドは数値オブジェクトを、文字列オブジェクトに変換して、それを返り値として返します。
to_iメソッドは文字列オブジェクトを数値オブジェクトに変換してくれます。

  • オブジェクト単体の返り値はそのオブジェクト自体を返します。
  • 計算式はその式の答えが返り値として返ります。
  • メソッドを利用したときはその処理の実行結果が返り値として返ります。
    • しかしputsメソッドは返り値としてnilを返します。空という意味です。出力と返り値は別です。
  • 代入式は格納した値(代入した値)自体が返り値です。

getsメソッドはユーザーが入力した値の文字列オブジェクトを返り値として返します。
getsメソッドは、末尾に改行がついて返り値として返してしまいます。バックスラッシュ記法の\nがついた状態です。
それを取り除くにはchomp メソッドを使います。このメソッドを使うと新たに\nを取り除いた状態の文字列オブジェクトを返してくれます。

比較演算子

== は左右の式や値が等しい時にtrueを返り値として返します。等しくないときはfalseを返します。
それとは逆に
!= では左右の値が等しくない時にtrueを返し、等しい時にfalseを返します。

変数

変数とはオブジェクトの入れ物のようなもので、その入れ物に名前がついています。
変数にオブジェクトを格納することができるのです。
変数は最代入が可能で値を更新することができます。
それに対して、定数は最代入が原則として禁止で、かつ全て大文字で書くのが慣習です。
つまり、固定した値を格納したい時に定数を使います。

ハッシュ

ハッシュは変数の一つで、複数のデータを持つことができるオブジェクトです。
値をキーで管理しています。このペアで保存する形式のことをキーバリューストアと言います。
ハッシュオブジェクトのキーには文字列オブジェクトとシンボルオブジェクトが使えます。
実行速度がシンボルオブジェクトの方が速いので、シンボルオブジェクトが推奨されます。
シンボルオブジェクトは名前を識別するためのラベルのようなものでほぼほぼ文字列オブジェクトと同義で使えます。

index.rb
hash = {}         ##空のハッシュを生成

hash[:name] = "taro"  ##値の代入
hash[:age] = 20     ##値の代入

puts hash[:name]     ##値の取得

自分で定義するメソッド

putsのような処理を自分で定義してメソッド化することができます。
同じコードを何度も書かなくて済みますし、コードの可読性も上がります。また。修正や管理もしやすくなります。

定義は  defの隣に任意のメソッド名を書いてendまでの間に実行した処理を記述します。

index.rb
def my_name
 puts "私の名前は太郎です。"
end

注意しなければならないのはメソッドの定義部分はそのメソッドが呼びだれるまで読み込まれません。
ですのでメソッド名が呼び出される前は定義部分はスルーされます。

index.rb
def my_name         ##次にここが読まれる。それまではスルー。
 puts "私の名前は太郎です。"
end

my_name                   ##メソッドの呼び出し 最初にここが読まれる

while文

同じ処理を繰り返すことができる文法です。

index.rb
while 条件式 do
#処理
end

条件式の部分がtrueであるかぎり処理を何回も繰り返します。

つまり

index.rb
while true do
#処理
end

だと無限ループでプログラムを終了させなくできます。

処理の中にexitメソッドを使えば特定の時に無限ループを止めることができます。

配列

配列とは配列オブジェクトのことです。ハッシュがキーでオブジェクトを管理していたのに対して、配列は順番で管理します。配列にはハッシュも入れることができます。
順番の管理は0から始まるので、1つ目に入れた要素は0で管理されます。
配列オブジェクトに要素を追加するときは<<メソッドを使います。
配列の数を数えたいときはlengthメソッドを使います。

変数のスコープ

変数にはその変数が使える範囲というものが決まっています。
通常はメソッド内では。そのメソッド内で定義された変数しか使用することができません。
メソッド外で定義された変数を使うには引数を使います。

引数

メソッドを呼び出す部分に書く引数を本引数と言います。
また、メソッドを定義している部分に書く引数を仮引数と言います。
本引数と呼び出したい変数は同じ名前である必要がありますが、本引数と仮引数は同じでなくて構いません。
仮引数で設定した名前でメソッド内で変数を使えます。

index.rb
def age_sister(age)

puts "my sister is #{age} years old."
return age     ##putsメソッドは返り値としてnilを返してしまうのでreturn文で変数の値を返り値として返している

end

sister = 25    ##age_sisterメソッドのスコープ外に変数sisterが定義されている。
age_sister_and_me(sister)    ##age_sisterメソッドを呼び出す部分で本引数に変数sisterを取っている。

return文はそこで返り値が確定するのでそこで処理が終了する。
定義されたメソッドの返り値は最後に記述された式の値が返ります。

index.rb
def animal_name(name)
 puts "この動物は#{name}です。"
 return "僕は#{name}!"
end

puts animal_name("犬")

上記のような場合、animal_nameメソッドでreturn文が使われているので返り値として"僕は犬!"が返ります。
putsメソッドで 僕は犬! が出力されています。
メソッドの呼び出し側で定義された("犬”)はメソッド内で変数として使うことができます。今回で言うと変数nameとして使えるようになっています。

引数は関数を呼び出す際にどんなことを出力させるか決めるために利用される。
メソッドは引数を渡すと何かを返してくれる。
メソッドが引数を受け取るときその名前は何でも良い。
メソッドの最後で、メソッドの呼び出し元に値を返すときはreturnを使います。

eachメソッド

eachメソッドとは繰り返しのためのメソッドです。
配列オブジェクトにeachメソッドを使うと、その配列の要素の数だけ繰り返し処理が行われます。

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

【開発ログ⑫】有休未消化のアラートを実装

前提について

はじめまして、
プログラミングスクールに通ういりふねと申します。この記事は、スクールの課題である個人アプリの開発の記録を書くことで、自身のアウトプットに利用しています。もし、読んでいただけた方がいましたら、フィードバックをしていただけたら嬉しいです。

開発するのは「有給休暇管理ツール」です。仕様は過去記事をどうぞ。

アプリはデプロイまで行いますが、サービスとして提供するものではありません。あくまでも自学習の一環ですので、ご理解下さい。では本題へどうぞ。

今回実施する内容

前回までで、有休に関する簡単な計算までが終了しましたので、今回は有給休暇の未消化アラートを実装したいと思っています。以下の手順で実施します。今回は軽め。

  • 未消化アラートとは?
  • 未消化アラートの実装
  • ビューに表示

未消化アラートとは?

働き方改革法案により、2019年4月1日より有給休暇を消化させることが、義務化されました。条件は有休を10日以上持つ社員に対して、毎年5日以上は時期を指定して計画的に消化させるというものです。毎年の期間については、就業規則により変わると考えられますが、今回は「直近付与日から次回付与日までの間」に5日未満しか消化していない社員については、有給休暇管理表のアラートカラムに「未消化」を表示させることにします。

未消化アラートの実装

今回も、モデルファイルに未消化アラートを表示させるメソッドを記述しておきます。

employee.rb
class Employee < ApplicationRecord
〜中略〜
  def delete_days_alert
    a = total_delete_day
    b = total_add_day

    if b >= 10 && a < 5
      "未消化"
    else
      "-"
    end
  end
end

IF文の前のメソッド、total_delete_dayとtotal_add_dayは、前回の記事で定義済みです。これは、直近の付与日から現在までの日数を計算するメソッドになります。

そして、IF文の条件は、b(付与日数の合計)が10以上、かつ、a(消化日数の合計)が5未満のとき、ということになります。有休の付与日数が10日未満の方やすでに今年5日以上消化している方は対象外となります。

ビューに表示

branches/index.html.haml
.main
  =render 'branches/mainheader'
〜中略〜
        %th{id: "short", class: "bgc"}
          5日消化
〜中略〜
        - @employees.each do |employee|
          %tr
〜中略〜
            %th{id: "short", class: "bgc"}
              = employee.delete_days_alert
〜中略〜

結果は、以下のとおりです。
スクリーンショット 2020-05-17 13.28.48.png
未消化アラートは、目立ったほうが良いと思ったので、5日消化カラムには、class名bgcを追加し、黄色の背景色を設定しました。

今日の積み上げ

本来であれば、「未消化」と表示されている方だけに黄色を表示させたかったのですが、私の能力では調べても実装できませんでした。一応、hamlでclassを定義する際にIF文で条件を定義し、条件に合う時、classを追加するという方法があるようです。まだまだ未熟であると感じましたが、できる限りで頑張りたいと思います。

参考にさせていただいた記事
「【Haml】条件(if)に応じてClassを変更させる方法」@nishina555
「Hamlで条件に合わせてclassを追加する方法」@day-1
「Hamlで条件に合う時だけ特定のクラスを加えたい」@aprikip

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

RubyのテストRspecでコードカバレッジを理解する

テストケースを書いていると、どこまでカバレッジをカバーするって話がでてきます。

コードカバレッジってそういえばどんなのだっけ?って思ったので、自分の備忘録としてまとめようと思います。

コードカバレッジとは?

ソフトウェアテストにおいて、テスト対象となるプログラムコード(内部ロジック)全
体の中で、テストが行われた部分が占める割合(網羅率)です。
参照

代表的なカバレッジ

C0:命令網羅

それぞれの命令文が少なくとも1回は実行

C1:分岐網羅

それぞれの判定条件に置ける真偽が少なとも1回は実行

C2:条件網羅

それぞれの条件文に置ける真偽が少なくとも1回は実行

MCC:複合条件網羅

それぞれの条件に置ける真偽の組み合わせが全て実行

実際Rspecでカバレッジを意識してテストを書く

テストをするRubyコード

sample.rb
module CodeCoverage
  class << self
  # main
  # @params [interger] x
  # @params [interger] y
  # @return [String] xとyの値によってことなる文字列
  #
  # 引数は、intergerのみを想定
  def main(x, y)
    result = ''
    if x >= y || x % 2 == 0
      result << '処理1'
    else
      result << '処理2'
    end
    if x*y > 10
      result << '処理3'
    else
      result << '処理4'
    end
    return result
  end
  end
end

テストコード

rspecで書いてます。

RSpec.describe CodeCoverage do
  shared_examples '処理1と処理3通る' do
    it '処理1と処理3の文言が返却される' do
      expect(subject).to eq '処理1処理3'
    end
  end
  shared_examples '処理1と処理4通る' do
    it '処理1と処理4の文言が返却される' do
      expect(subject).to eq '処理1処理4'
    end
  end
  shared_examples '処理2と処理3通る' do
    it '処理2と処理3の文言が返却される' do
      expect(subject).to eq '処理2処理3'
    end
  end
  shared_examples '処理2と処理4通る' do
    it '処理2と処理4の文言が返却される' do
      expect(subject).to eq '処理2処理4'
    end
  end

  describe 'main' do
    subject { described_class.send(:main, x, y) }
      # C0:命令網羅
    # 命令が少なくても1回だけ実行するように書く
      context 'C0の場合' do
        context '処理1と処理3を通る' do
          let(:x) { 11 }
          let(:y) { 1 }
          it_behaves_like '処理1と処理3通る'
        end
        context '処理2と処理4を通る' do
          let(:x) { 1 }
          let(:y) { 9 }
          it_behaves_like '処理2と処理4通る'
        end
      end
      # C1:分岐網羅
      # それぞれの判定条件に置ける真偽が少なとも1回は実行
      context 'C1の場合' do
        context '処理1と処理3を通る' do
          let(:x) { 11 }
          let(:y) { 1 }
          it_behaves_like '処理1と処理3通る'
        end
        context '処理1と処理4を通る' do
          let(:x) { 3 }
          let(:y) { 2 }
          it_behaves_like '処理1と処理4通る'
        end
        context '処理2と処理3を通る' do
          let(:x) { 3 }
          let(:y) { 5 }
          it_behaves_like '処理2と処理3通る'
        end
        context '処理2と処理4を通る' do
          let(:x) { 1 }
          let(:y) { 4 }
          it_behaves_like '処理2と処理4通る'
        end
      end

      # C2:条件網羅
      # それぞれの条件文に置ける真偽が少なくとも1回は実行
      context 'C2の場合' do
        context 'x>=yかつx*y>10' do
          context '処理1と処理3を通る' do
            let(:x) { 11 }
            let(:y) { 1 }
            it_behaves_like '処理1と処理3通る'
          end
        end
        context 'x>=yかつx*y<10' do
          context '処理1と処理4を通る' do
            let(:x) { 3 }
            let(:y) { 2 }
            it_behaves_like '処理1と処理4通る'
          end
        end
        context 'x<yかつxが偶数かつx*y>10' do
          context '処理1と処理3を通る' do
            let(:x) { 4 }
       let(:y) { 5 }
            it_behaves_like '処理1と処理3通る'
          end
        end
        context 'x<yかつxが偶数かつx*y<10' do
          context '処理1と処理4を通る' do
            let(:x) { 2 }
            let(:y) { 3 }
            it_behaves_like '処理1と処理4通る'
          end
        end
        context 'x<yかつxが奇数かつx*y>10' do
          context '処理2と処理3を通る' do
            let(:x) { 3 }
            let(:y) { 5 }
            it_behaves_like '処理2と処理3通る'
          end
        end
        context 'x<yかつxが奇数かつx*y<10' do
          context '処理2と処理4を通る' do
            let(:x) { 1 }
            let(:y) { 4 }
            it_behaves_like '処理2と処理4通る'
          end
        end
      end
    end
  end
end

こうやって書くとパッとわかりやすいですね。
でも実務だと、C1までしか書かないです。

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

【開発ログ⑪】有休付与日から現在までの消化日数の合計を計算〜残日数まで

前提について

はじめまして、
プログラミングスクールに通ういりふねと申します。この記事は、スクールの課題である個人アプリの開発の記録を書くことで、自身のアウトプットに利用しています。もし、読んでいただけた方がいましたら、フィードバックをしていただけたら嬉しいです。

開発するのは「有給休暇管理ツール」です。仕様は過去記事をどうぞ。

アプリはデプロイまで行いますが、サービスとして提供するものではありません。あくまでも自学習の一環ですので、ご理解下さい。では本題へどうぞ。

今回の実施内容

前回までで、従業員の入社日をもとに付与日(入社日から6ヶ月後の有休が付与される日)と勤続年数まで計算できました。今日は、消化日数の合計を計算させます。具体的には以下の手順で考えます。

  • 消化日数の合計とは?
  • 今回扱う消化日数の合計は?
  • 合計する対象範囲を検索
  • 検索結果を合計させる
  • 合計した結果をビューに表示
  • ローカルブラウザで確認できればOK

消化日数の合計とは?

まず毎年もらえる有給休暇は、付与されてから2年間有効となります。消化する場合は、古いものから順に消化されるのもポイントです。

例えば、2018年4月1日に入社した方が、有休のうち3日間を2回に分けて消化した場合、以下のような流れになります。

日付 有休の増減 残日数
2018/4/1 入社時は増減なし 0日
2018/10/1 有休10日付与 10日
2019/1/1 有休3日消化★ 7日
2019/10/1 有休11日付与 18日
2020/1/1 有休3日消化★ 15日
2020/5/17 有休の残日数を確認 15日
2020/10/1 有休4日は未使用のまま消滅、有休12日付与 23日

つまり、現在2020/5/17日時点の有休消化日の合計は、以下の2つをレコードをテーブルから検索し、合計すればよいということになります。

  • 直近付与日から現在までの合計(2019/10/01~2020/5/17)
  • 2つ前の付与日から直近付与日までの合計(2018/10/1~2019/10/1)

ただし、この計算式で行く場合は、別途2020/10/01に未消化のまま消滅する有休も計算させる必要があります。

今回扱う消化日数の合計は?

今回、最終的にアプリケーションでで実装したい日数計算は、以下の通りになります。

① 過去2年間の付与日数の合計
② 直近付与日から現在までの消化日数合計
③ 2つ前の付与日から直近付与日までの消化日数合計
④ ①から②と③を引いた残日数

長々書きましたが、私自身の備忘録として書かせていただきました。よって、今後考え方が変わるかも知れません。あくまで記事作成時点のいりふねの脳内の出来事とお考え下さい。
で、今回実装したのは、「②直近付与日から現在までの消化日数合計」なので、これの紹介を以下より行います。

合計する対象範囲を検索

前回までは、社員のレコードのみで表を作成していましたが、今回から有給休暇のレコードも計算に入ってきます。社員はemployeeテーブル、有給休暇はholidayテーブルにそれぞれ格納されているので、コントローラーから編集します。

employees_controller.rb
class EmployeesController < ApplicationController
  def index
    @employees = Employee.where(branch_id: params[:branch_id]).includes(:holidays)
  end
〜中略〜
end

後半にincludesメソッドでemployee_idをもつholidayのレコードも一緒に取得しています。includesメソッドはカリキュラムで「N+1問題」を学んだときに登場しました。その際は、1対多関係のテーブルのうち、外部キーを持つ多のレコードを取得する際に、主キーを持つレコードを先読みするために使用すると学びました。今回は主キーをもつレコードに紐付く外部キーのレコードを取得しているので、順番が逆になっていますが、無事使えました。

参考にした記事「Railsで1対多のテーブルデータを取得し表示させる」@bitarx

次にモデルファイルで、「直近付与日から現在まで」検索を行わせるメソッドを定義します。

models/employee.rb
class Employee < ApplicationRecord
〜中略〜
  def range_to_add_or_delete
    grant_day = hire_date >> 6
    month = grant_day.month
    day = grant_day.mday
    year = Date.today.year
    grant_date_this_year = Time.local(year, month, day)

    if grant_date_this_year > Date.today
      last_year = year - 1
      grant_date_this_year = Time.local(last_year, month, day)
    else
      grant_date_this_year
    end

    holidays.where(created_at: grant_date_this_year..Date.tomorrow)
end

まず、冒頭のgrant_dayは、法定付与日(入社日から6ヵ月後)を計算しています。これに「.month」や「.mday」のメソッドを使用して、月と日の数字を取り出して変数に格納しています。年については、「Date.today」で今日の日付を用意し、それに「.year」メソッドを実行することで年の数字を取り出しています。最後に「Time.local」で3つの数字を合体させ、一旦直近付与日(grant_date_this_year)を完成させます。

参考にさせていただいた記事「[Ruby入門] 14. 日付と時刻を扱う(全パターン網羅)」@prgseek

続けて、IF文の箇所ですが、条件に応じて年を加工して、最終的な直近付与日を確定させています。条件は、先程作った直近付与日(grant_date_this_year)が現在の日付より大きいかというものです。これを行う理由は、法定付与日が12月の方などは、直近法定付与日の結果が2020年12月1日と未来の日付になってしまうからです。そこで、一旦完成させた直近付与日が、現在の日付よりも大きくなっている場合は、年からマイナス1を実行して、「last_year」という新しい変数で、直近付与日を作り直しています。else以降は、必要かどうかが、現段階で判断できません。すみません。

スクリーンショット 2020-05-14 11.38.31.png

途中結果のビューですが、消化日数のカラムに各社員の直近付与日が表示されました。4月入社の昇太師匠も未来の日付にならずに済んでいます。良かった〜!!

コードの解説に戻ります。
最後の1文で完成した直近付与日から現在までの範囲のcreated_atを検索し、必要なレコードを検索させます。

参考にさせていただいた記事「ActiveRecordで日付・時刻の範囲検索をシンプルに書く方法」@hachi8833

検索結果を合計させる

指定の範囲で検索できたので、これを元に合計を出していきます。モデルファイルに合計を計算させるメソッドを別で書きます。
なお、今回は開発途中ということで付与日の合計日数も同じ方式で計算することにしました。前述した通り付与日の合計の算出方法は別にありますが、付与日の合計には他に実装しなければならないメソッドがあるので、今回はビュー確認用で一旦同じにしておきます。

models/employee.rb
class Employee < ApplicationRecord
〜中略〜
  def total_delete_day
    total_delete_day = range_to_add_or_delete.sum(:delete_day)
  end
  def total_add_day
    total_add_day = range_to_add_or_delete.sum(:add_day)
  end
  def calculate_remaining_days
    a = total_delete_day
    b = total_add_day
    b - a
  end
end

「.sum」メソッドの引数は、合計を出したいカラム名になります。
参考にさせていただいた記事「【Rails】カラムの合計値を求める!」@tomokichi_ruby

ついでにcalculate_remaining_daysを定義し、残日数を計算させています。本来であれば、付与日の合計と消化日の合計を引いた結果が、マイナスであればエラーが出るように条件分岐させるべきですが、同じくビュー確認用でシンプルに引き算だけさせます。

合計した結果をビューに表示

branches/index.html.haml
.main
  =render 'branches/mainheader'
  .main__body
    社員データの編集は「名前」をクリック、削除は右端です。
    %table 
      %tr
~中略~
        %th{id: "short"}
          付与日数
        %th{id: "short"}
          消化日数
        %th{id: "short"}
          残日数
        - @employees.each do |employee|
          %tr
〜中略〜
            %th{id: "short"}
              = employee.total_add_day
            %th{id: "short"}
              = employee.total_delete_day
            %th{id: "short"}
              = employee.calculate_remaining_days
〜中略〜

先程、定義したメソッドを「index.html.haml」に反映させました。ビューの結果は以下のとおりです。無事に計算させることができました。
スクリーンショット 2020-05-14 11.51.20.png

今日の積み上げ

だいぶ、Qiitaの記事投稿に慣れてきました。内容の割に文章が多いと感じますが、自分が調べたことや躓いたこと、同じ初学者の助けになればと思えば、解説がバカ丁寧になるものなのかな?と思うようにしています。とはいえ、参考にさせていただいた記事のように簡潔に書いて初学者の参考になる記事も多いので、簡潔さを意識したいです。以上。

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

【超かんたん】Rubyの環境構築(mac)

環境構築のやり方

全て、progateのサイトにわかりやすく載っています。
初めての環境構築でしたが、20分ほどでできました。
【macの人】
https://prog-8.com/docs/ruby-env

【windowsの人】
https://prog-8.com/docs/ruby-env-win

補足

記事の最後のほうに、

rbenv global 2.6.5

というコマンドを入力することで、今回インストールしたバージョンのRubyを使用するように設定を変更し、

ruby -v

とコマンド入力をすることで2.6.5 と表示されていればOKと書いてあります。
しかし、rbenv globalコマンド実行後にターミナルの再起動を行ってからruby -vとコマンド入力しないと、
2.6.5と表示されない(古いバージョンが表示される)ことがあるので、そこだけ注意しましょう。

また、記事ではRubyのバージョン2.6.5をインストールするようなコマンドが書かれています(2020/5/17現在)が、
他のバージョンをインストールしたいときには、バージョンの部分だけ置き換えてコマンド入力すればOKです。

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

【超かんたん】Rubyの環境構築

環境構築のやり方

全て、progateのサイトにわかりやすく載っています。
初めての環境構築でしたが、20分ほどでできました。
【macの人】
https://prog-8.com/docs/ruby-env

【windowsの人】
https://prog-8.com/docs/ruby-env-win

補足

記事の最後のほうに、

rbenv global 2.6.5

というコマンドを入力することで、今回インストールしたバージョンのRubyを使用するように設定を変更し、

ruby -v

とコマンド入力をすることで2.6.5 と表示されていればOKと書いてあります。
しかし、rbenv globalコマンド実行後にターミナルの再起動を行ってからruby -vとコマンド入力しないと、
2.6.5と表示されない(古いバージョンが表示される)ことがあるので、そこだけ注意しましょう。

また、記事ではRubyのバージョン2.6.5をインストールするようなコマンドが書かれています(2020/5/17現在)が、
他のバージョンをインストールしたいときには、バージョンの部分だけ置き換えてコマンド入力すればOKです。

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

【Rails基礎】画面ロード時のエラーについて(NameError, LoadError)

2回目の投稿になります。よろしくお願いします。
今回は個人アプリを作る中で起きた画面ロード時に起きたエラーについての記事を書きたいと思います。

環境

Ruby: 2.5.1
RubyOnRais: 5.0.7

エラー内容

エラー内容は以下の二つです。

① LoadError in EventsController#choise_artist
Unable to autoload constant Set_list, expected ~ FesLive-app/app/models/set_list.rb to define it.

② Name Error in EventsController#choise_artist
uninitialized constant EventsController:Setlist

解決方法

①の解決方法

まずひとつ目のエラーですが、autoloadできないと言われています。
まず、Railsのautoloadとは「命名規則に則ったファイルを自動でrequireしてくれる機能」のことです。
つまり、下の記述の @set_lists = Set_list.all は命名規則に従っていないということになります。
実はrails の命名規則ではクラス名に対してアンダーバー(_)を使うことはできません。
そのため、Set_listクラスをautoloadできませんよ〜と言われているのでした。

class EventsController < ApplicationController

  def choise_artist
    @set_lists = Set_list.all
    @event = Event.find(params[:id])
  end

end

なので、「@set_lists = Setlist.all」とすれば①のエラーに関しては突破できました。

【参考】エラー画面
LoadError in EventsController#choise_artistのエラー.png

②の解決方法

クラス名の表記の仕方を変えautoloadはできるようになりましたが、今度はNameErrorのエラーがでました。
uninitialized ⇒ 初期化されていない ⇒ クラスが使える状態が整っていないということなので、
クラス名の指定の仕方に問題がありそうだということが分かります。
実際にSetlistモデルを見に行くと、クラス名が「SetList」とLが大文字になっていました。
なので、クラス名がまちがってますよ〜と言われているのでした。

class SetList < ApplicationRecord
  belongs_to :event
end
この「@set_lists = SetList.all」とすればこのエラーは突破できました。

【参考】エラー画面
uninitialized constant EventsController--Setlist.png

以上、ここまで読んでくださりありがとうございました。
分かりにくい箇所やアドバイス等ありましたら、コメントくださると幸いです。では!

【参考サイト】
https://qiita.com/hirokisoccer/items/4ba62a56b18eb834a8ee
https://wa3.i-3-i.info/word16120.html

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

ログインしていない時は遷移先のアクションを制約する方法

背景

今回は、ログインしていないユーザーが指定したページ以外遷移できないように制約をかける方法を紹介していきます。

やりたいこと

ログインしていないユーザーは、index,showページのみ遷移できて、newページやeditページに遷移しようとすると強制的にindexページに遷移されるように処理します。

使い方

コントローラー内で繰り返し使用されるコードは、private以下でメソッド化します。
以下の処理を行うことで、ユーザーがログインしていない状態でindex,showページ以外に遷移しようとすると、強制的にindexページに遷移されるようになります。

controller.rb
class PracticeController < ApplicationController
  before_action :move_to_index, except: [:index, :show]

  ~省略~

  private

  def  move_to_index
    redirect_to action: :index unless user_signed_in?
  end


以上

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