20200602のRubyに関する記事は27件です。

[Rails]ECサイトのカート機能

はじめに

Ruby on RailsでECサイトを作成しました。
その際、カート機能の実装でかなり時間を費やしたので、やり方を記載してみようと思います。

前提条件

カート機能を持たせるためには、DBにテーブルが必要になってきます。
他テーブルとの関連付けなどについては以下の記事にて記載しております。
[Rails] ECサイトのDB設計

カート機能の根幹の部分

ここでは、セッションを作成します。

セッションについては、以下の記事がわかりやすいです。
【Rails】Sessionの使い方について

要は、データを保持してくれる仕組みですね。
で、このカートのセッション作成は以下のようにコーディングしました。

# application_controller.rb
class ApplicationController < ActionController::Base

private
  #セッションの作成
  def current_cart
    # セッションから取得したcart_idを元にCartテーブルからCart情報を取得
    current_cart = Cart.find_by(id: session[:cart_id])
    # Cart情報が存在しない場合、@current_cartを作成
    current_cart = Cart.create unless current_cart
    # 取得したCart情報よりIDを取得し、セッションに設定
    session[:cart_id] = current_cart.id
    # Cart情報を返却
    current_cart
  end
end

ここで定義した"current_cart"を他のcontrollerでも使用していきます。

カートに商品を入れていく

ここからは先ほど作成したメソッドをcarts_controllerで使用していきます。

class CartsController < ApplicationController
  before_action :set_line_item, only: [:add_item, :destroy]
  before_action :set_user
  before_action :set_cart

  def show
    @line_items = @cart.line_items
  end

  def add_item
    @line_item = @cart.line_items.build(product_id: params[:product_id]) if @line_item.blank?
    @line_item.quantity += params[:quantity].to_i
    if @line_item.save
      redirect_to current_cart
    else
      redirect_to controller: "products", action: "show"
    end
  end

  def destroy
    @cart.destroy
    redirect_to current_cart
  end

  private
  def set_user
    @user = current_user
  end

  def set_line_item
    @line_item = current_cart.line_items.find_by(product_id: params[:product_id])
  end

  def set_cart
    @cart = current_cart
  end
end

add_itemアクションですが、商品詳細ページに"カートに入れる"ボタンを設置しており、POSTのHTTPメソッドでカートに商品の追加をしています。

@line_item = @cart.line_items.build(product_id: params[:product_id]) if @line_item.blank?

上記ですが、buildメソッドを使用しています。
buildはnewのエイリアスですが、慣例として関連付けされたモデルのオブジェクトを生成する際には、buildを使い、それ以外でnewを用いるという使い分けがされているらしいです。

今回はcartが親、line_itemが子の関係なので、line_itemモデルのオブジェクトを生成したいためにbuildを使用したということです。(間違ってたらごめんなさい)

基本的にここまででカートとして機能しているかと思います。

因みに

solidusというgemを使用すれば、秒でECサイトを作成できます。

参考記事↓
Spreeの後継ECシステム、Solidusのインストールメモ

試しに使ってみましたが、カスタマイズがだるそうです。
目的によっては使ってみるのもアリかもですね。

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

Rubyの『クラス』関係の問題を集めてみて解いてみた。

Rubyのクラスが分からない

チェリー本を読んでいざアウトプットしていこうと思ったものの、自分でプログラムを作るとなると中々解けずに難しい。。。

そんな僕と同じ悩みを抱えている方のために基礎問題を集めてみました。

基礎問題と言えど正直、曖昧な知識のままこの問題を解いていくと分からないことがあります。

出来れば答えを見ずに進めていくのが良いと思いますが、考えてもどうしても分からない場合は記事の下の方に回答を書いておきますので参考にして下さい。

そして、回答を読んでも分からないと言うことが僕自身ありましたので、分からなかった点まで記載していきます。

問題(3問)

①クラス変数とクラスメソッドについて

sample.rb
class Car
  def self.run
    @@count += 1
  end

  def count
    @@count
  end
end

car1 = Car.new
car1.run

car2 = Car.new

car2.run
car1.run

puts Car.count

こちらの記事の問題を参照しました。

●<問題>
上のプログラムは実行するとエラーが出ます。
ある箇所を修正して期待通りの出力ができる様にソースを変更してください。

(期待出力)
・インスタンスメソッドrunを呼ぶとクラスの共有カウンタが1ずつ増加する。
・クラスメソッドcountを呼ぶと現在のカウンタの値が返される。

3と表示されればオッケーです。

●<ヒント>
以下の記事が、この問題を解く上で非常に参考になりました。
https://qiita.com/mogulla3/items/cd4d6e188c34c6819709

②特異メソッド

この記事を引用させていただきました。

food.rb
class Food
  def eat
    puts "I like."
  end
end

natto = Food.new()
wasabi = Food.new()
karaage = Food.new()

natto.eat #=>I like.
wasabi.eat #=>I don't like.
karaage.eat #=>I love.

●<問題>
コメントアウト通りの出力になる様にコードを変えてください。

③Musicクラスを継承したRapクラスを定義する

こちらの記事を参照しました。

●<問題>
Rapクラスへ記述するコードを下記の出力結果から予測してください。

sample.rb
class Music
  def mc
    puts "This is #{@genre} of #{self.class.to_s}"
  end

  def initialize(genre)
    @genre = genre
  end
end

Rap.new("mc-battle").mc

(出力例)

This is mc-battle of Rap
Yo, mic check 1, 2.

ー※※※※※※※※※※※解答※※※※※※※※※※※ー

①クラス変数とクラスメソッドについて

クラス変数とクラスメソッドの使い方を理解していないと解けないですね。

●<クラス変数>

  • クラスとそのインスタンスがスコープになる
  • 定数と似ているがクラス変数は何度でも値を変更できる点で異なる
  • クラスメソッド、インスタンスメソッド、クラス定義式内でアクセス可能

以下の記事が参考で、この問題を解く上で非常に参考になりました。
https://qiita.com/mogulla3/items/cd4d6e188c34c6819709

まず、見るべき点は2、6行目です。

sample.rb
class Car
  def self.run #2行目
    @@count += 1
  end

  def count #6行目
    @@count
  end
end

car1 = Car.new
car1.run

car2 = Car.new

car2.run
car1.run

puts Car.count

気づくことはなかったでしょうか?

最後の行を見てください。
puts Car.count

これは、Carクラスcountメソッドをそび出しているみたいですが、おかしくありませんか?

クラスをそのまま呼び出す際は、クラスメソッド出なければいけません。

修正すると、

sample.rb
class Car
  def self.run #2行目
    @@count += 1
  end

  def self.count #6行目
    @@count
  end
end

#-中略-

puts Car.count #最後の行

そして、2行目もおかしいですね。

こちらはインスタンスメソッドにしなければいけません。

sample.rb
class Car
  def run #2行目/def self.runから変更
    @@count += 1
  end
end

car1 = Car.new
car1.run

なぜなら、classのそとではcar1とインスタンスを作成しているからそのインスタンスを呼び出すためのメソッドとして、インスタンスメソッドにしなければいけません。

しかしこれで終わりではありません。

クラス変数 @@count が定義されていません。

sample.rb
class Car
  @@count = 0 #クラス変数の定義

  def run #クラスメソッド
    @@count += 1 #@@countはクラス変数
  end

  def self.count #インスタンスメソッド
    @@count
  end
end

car1 = Car.new
car1.run

car2 = Car.new

car2.run
car1.run

puts Car.count

# => $ ruby sample.rb 
# => 3

これでオッケーです。

②特異メソッド

そもそも、特異メソッドとは?
1つのインスタンス固有のメソッドのことを指します。

特異メソッドは、def オブジェクト名.メソッド名で定義できます。
https://qiita.com/k-penguin-sato/items/d637dced7af32e4ec7c0

よって、

food.rb
class Food
  def eat
    puts "I like."
  end
end

natto = Food.new()
wasabi = Food.new()
karaage = Food.new()

#--追加--↓

def wasabi.eat
  puts "I don't like."
end

def karaage.eat
  puts "I love."
end

#--追加--↑

natto.eat
wasabi.eat
karaage.eat

同じクラスのインスタンスであるオブジェクトに、同じ名前の動作が異なるメソッドを定義します。

これが特異メソッドで、これはクラスに対してではなくクラスからできたインスタンスに対してのメソッドになります。

よってそれぞれのメソッドからの出力ができます。

(結果)

I like.
I don't like.
I love.

③Musicクラスを継承したRapクラスを定義する

sample.rb
class Music
  def mc
    puts "This is #{@genre} of #{self.class.to_s}"
  end

  def initialize(genre)
    @genre = genre
  end
end

Rap.new("mc-battle").mc

self.class.to_sRap.newの様に作られたインスタンス自体のクラス名を文字列に変換する。

では、Rapクラスと、を作っていきます。

sample.rb
#--中略--
class Rap < Music
end

出力文字を見てください。
Yo, mic check 1, 2.

Musicクラスでは定義されていない出力ですので、Rapクラスで出力していきます。

最後の行のRap.new("mc-battle").mcを見てみると、メソッドはmcが使われていますので、記載していきます。

sample.rb
#--中略--
class Rap < Music
  def mc
  end
end

Musicクラスを継承しているので必要な部分を記載すると

sample.rb
#--中略--
class Rap < Music
  def mc
    puts "Yo, mic check 1, 2."
  end
end

しかしこれで終わりではないです。

まだ、現時点ではMusicクラスの呼び出しができていません。

どうすればいいでしょうか?

「super」メソッドを実行します。

superメソッドは、スーパークラスの中でその呼び出されたメソッドと同じメソッド名を持つメソッドを探して実行します。

それでは、書き換えると

sample.rb
class Music
  def mc
    puts "This is #{@genre} of #{self.class.to_s}"
  end

  def initialize(genre)
    @genre = genre
  end
end

class Rap < Music
  def mc
    super #追加コード
    puts "Yo, mic check 1, 2."
  end
end

Rap.new("mc-battle").mc

完成です。

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

【Rails・MySQL】Mac環境構築

MacのRails・MySQL環境構築について

目次

  1. ログインシェルの設定
  2. Command Line Toolsのインストール
  3. Homebrewのインストール・設定
  4. Rubyのインストール・設定
  5. MySQLのインストール・設定
  6. Railsのインストール・設定

1.ログインシェルの設定

※ OSがCatalina以降の場合、zshを利用するため、設定の確認・変更を行います。

ターミナル

# zshをデフォルトに設定
$ chsh -s /bin/zsh

# ログインシェルを表示
$ echo $SHELL
# 以下のように表示されれば成功
/bin/zsh

echo $SHELL コマンドで、 /bin/zsh と表示されない場合は、
一度ターミナルを閉じて、再度開いた上でもう一度 echo $SHELL コマンドを打ってください。

2.Command Line Toolsのインストール

Command Line ToolsはWebアプリケーション開発に必要なソフトウェアをダウンロードするために必要な機能となります。(コマンドでプログラムを操作するもの)

Command Line Toolsのインストール

以下のコマンドを入力します。

ターミナル

$ xcode-select --install

コマンド入力後にモーダルが表示されるので以下の順に操作します。
①「インストール」をクリック
②「同意する」をクリック
③Command Line Toolsのダウンロードが始まるので、完了まで待機
④ダウンロードが完了したら、「完了」をクリック

以上でCommand Line Toolsのインストールは完了となります。

3.Homebrewのインストール・設定

3-1. Homebrewのインストール

Homebrewというソフトウェア管理ツールをインストールします。
以下のコマンドを入力します。

ターミナル

$ cd  # ホームディレクトリに移動
$ pwd  # ホームディレクトリにいるかどうか確認
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"  # コマンドを実行

※ 処理に時間がかかるので、ゆっくり待ちましょう。
途中でエンターキーの入力を求められるので入力をしましょう。

ターミナル

$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"  # コマンドを実行
...
...
...
Pres RETURN to continue or any other key to abort

また、途中でパスワードの入力を求められますので入力をしましよう。
(パスワードを入力しても文字は表示されませんが、間違いなく入力はされています)

ターミナル

Pres RETURN to continue or any other key to abort
...
...
...
Password:

ダウンロードが完了し、再びコマンドを入力できるようになれば成功です。

2-2. Homebrewがインストールされているか確認

以下のコマンドを入力します。

ターミナル

$ brew -v
Homebrew 2.2.12  # このバージョンはインストールのタイミングで異なります

3-3. Homebrewをアップデート

Homebrewを最新の状態にアップデートします。
以下のコマンドを入力します。

ターミナル

$ brew update

3-4. Homebrewの権限を変更

Homebrewの権限を変更します。
以下のコマンドを入力します。

ターミナル

$ sudo chown -R `whoami`:admin /usr/local/bin

4.Rubyのインストール・設定

4-1. rbenvとruby-buildをインストール

rbenvとruby-buildを、Homebrewを用いてインストールします。
以下のコマンドを入力します。

ターミナル

$ brew install rbenv ruby-build

4-2. rbenvをどこからも使用できるように設定

PCにおけるどこのディレクトリからも使用できるようにするため、
以下のコマンドを入力します。

ターミナル

$ echo 'eval "$(rbenv init -)"' >> ~/.zshrc

zshrcというのは、設定ファイルの名称です。
(CatalinaかMojave以前の方で、設定ファイルの場所が異なるのでご注意してください)

4-3. zshrcの変更を反映

設定ファイルであるzshrcを修正したので、以下のコマンドでzshrcを再読み込みし、変更を反映させます。
以下のコマンドを入力します。

ターミナル

$ source ~/.zshrc

4-4. readlineをinstall

ターミナルのirb上で日本語入力を可能にする設定を行うためにインストールします。
以下のコマンドを入力します。

ターミナル

$ brew install readline

こちらについてはコマンドを実行すると、すでにインストール済みと表示されることがあります。
(特に問題はありませんので、そのまま次に進みましょう。)

4-5. readlineをどこからも使用できるように設定

readlineをどこからでも使用できるようにするため、
以下のコマンドを入力します。

ターミナル

$ brew link readline --force

4-6. rbenvを利用してRubyをインストール

Webアプリケーション開発用のRubyをインストールします。(Rubyのversionは任意のversionを設定して下さい)
以下のコマンドを入力します。

ターミナル

$ RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline)"
$ rbenv install 2.7.1

※ 処理に時間がかかるので、ゆっくり待ちましょう。(約10分くらい)

4-7. 利用するRubyのバージョンを設定

インストールしたRuby 2.7.1を使用するために、
以下のコマンドを入力します。

ターミナル

$ rbenv global 2.7.1

これまで使用していた、デフォルトでPCに入っていたRubyから、先ほどインストールしたRubyを使用するように切り替えることができました。

4-8. rbenvを読み込んで変更を反映

Rubyのバージョンを切り替えたので、変更を反映させます。
以下のコマンドを入力します。

ターミナル

$ rbenv rehash

4-9. Rubyのバージョンを確認

バージョンを切り替えることができたか、確認をします。
以下のコマンドを入力します。

ターミナル

$ ruby -v

Rubyのバージョンが、先ほどインストールした2.7.1であることが表示されれば完了です。

5. MySQLのインストール・設定

5-1. MySQLのインストール

MySQLをインストールします。(MySQLのversionは任意のversionを設定して下さい)
以下のコマンドを入力します。

ターミナル

$ brew install mysql@5.7

※ 処理に時間がかかるので、ゆっくり待ちましょう。

5-2. MySQLの自動起動設定

MySQLは本来であればPC再起動のたびに起動し直す必要がありますが、それでは面倒であるため、自動に起動するように設定します。
以下のコマンドを入力します。

ターミナル

$ mkdir ~/Library/LaunchAgents 
$ ln -sfv /usr/local/opt/mysql\@5.7/*.plist ~/Library/LaunchAgents
$ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql\@5.7.plist

5-3. mysqlコマンドをどこからでも実行できるように設定

rbenvやreadlineの時と同様に、どこからでもMySQLを操作するためのコマンドmysqlを実行できるように設定します。
以下のコマンドを入力します。

ターミナル

# mysqlのコマンドを実行できるようにする
$ echo 'export PATH="/usr/local/opt/mysql@5.7/bin:$PATH"' >> ~/.zshrc
$ source ~/.zshrc
# mysqlのコマンドが打てるか確認する
$ which mysql
# 以下のように表示されれば成功
/usr/local/opt/mysql@5.7/bin/mysql

6. Railsのインストール・設定

6-1. bundlerをインストール

Rubyの拡張機能(gem)を管理するためのbundler(バンドラー)をインストールします。
以下のコマンドを入力します。

ターミナル

$ gem install bundler

6-2. Railsをインストール

Railsをインストールします。(Railsのversionは任意のversionを設定して下さい)
以下のコマンドを入力します。

ターミナル

$ gem install rails --version='6.0.3'

※ 処理に時間がかかるので、ゆっくり待ちましょう。

6-3. rbenvを再読み込み

一通り開発に必要なものはインストールできたので、以下のコマンドでrbenvを読み込んで、変更を反映させましょう。
以下のコマンドを入力します。

ターミナル

$ rbenv rehash

6-4. Railsが導入できたか確認

以下のコマンドを実行して、Rails 6.0.3が表示されれば問題なくインストールが完了しています。

ターミナル

$ rails -v

以上で、Macでの【Rails・MySQL】の環境構築は完了です。

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

AtCoder Beginner Contest 169 A,B,Cまでをrubyで

4回目。前回C問題まで解けたので、今回もできるだろう、と舐めてかかったら、計算機工学の基礎のところでつまづく。

A問題

abc169a.rb
ab = gets.chomp.split(" ").map!{|item| item.to_i}
puts ab[0] * ab[1]

書くだけ。

B問題

abc169b.rb
n = gets.chomp.to_i
a = gets.chomp.split(" ").map!{|item| item.to_i}
num = 1
ans = 1
th = 10**18
a.sort!{|a,b| (-1)*(a <=> b)}
if a[n - 1] == 0
    puts 0
    exit
end
for num in 1..n do
    ans = ans * a[num-1]
    num = num + 1
    if ans > th
        puts "-1"
        exit
    end
end
puts ans

整数の掛け算なのだが、順に、
1. 0が一つでもあったら0
2. 10^18を超えたら即-1を返して終了
3. 要素全部の積を返す
で正解。cだと2のチェックを丁寧にやる必要があるが、rubyなら整数型で必要に応じて多倍長整数が使われるので気にしなくて良い。
2のチェックに早く引っかかるように逆順にソートして、末尾の要素が0かどうかを先にチェック、と書いたが、a.include?(0)か a.minで済む。
破壊的にソートする!をつけ忘れて通せず。

C問題

abc169c.rb
ab = gets.chomp.split(" ")
a=ab[0].to_i
b=((ab[1].to_s).delete(".")).to_i
ans = a*b/100
puts ans

bを100倍してto_sで整数にして計算、100で割れば良いや、とやったところで、2進表現で循環小数になるパターンを踏んでwa。
bをintegerとして扱い、小数点を除いた後にto_sが簡単。
これも通せず。
精進します。

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

スコープについてのメモ

def 〜 endの処理に対して、
処理外から渡したい数値が存在する場合、
発生する。

最初に渡したい変数は何かを宣言、
受け取る変数に、その渡したい変数を追記するだけ。

[スコープ発生例]

def show_review
    line = "---------------------------"
    puts "ジャンル : #{genre}\n#{line}\n"
    puts "タイトル : #{title}\n#{line}\n"
    puts "感想 :\n#{impression}\n#{line}\n"
  end

genre = "aaa"
title = "qqq"
impression = "ddd"

review.show_review

①「review.show_review」の中の「genre」「title」「impression」に
  値を渡す
②スコープでエラーが出る
③「review.show_review」に(genre,title,impression)を加える
④合わせて、渡される側も(genre,title,impression)を加える
⑤渡せる!!

<正しい書き方>

def show_review (genre,title,impression)←②受け取る側も追加
    line = "---------------------------"
    puts "ジャンル : #{genre}\n#{line}\n"
    puts "タイトル : #{title}\n#{line}\n"
    puts "感想 :\n#{impression}\n#{line}\n"
  end

genre = "aaa"
title = "qqq"
impression = "ddd" 
review.show_review(genre,title,impression) ←①渡したい項目を追加
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】gemのgoogle-cloud-visionでv1.0が出たので対応してみた

以前に書いた記事でコードを書き換える必要があったので対応してみました。

gem google-cloud-visionをv0.x系からv1.0.0にアップデートしたら要注意です。

基本的に

Migrating to google-cloud-vision 1.0
https://github.com/googleapis/google-cloud-ruby/blob/master/google-cloud-vision/MIGRATING.md

に書いてあるとおりなのですが#safe_search_detectionについてはこちらのほうがわかりやすかったです。

    require "google/cloud/vision"
    image_annotator = Google::Cloud::Vision::ImageAnnotator.new

    require "google/cloud/vision/v1"
    image_annotator = Google::Cloud::Vision::V1::ImageAnnotator::Client.new

に書き換えました。

Google Cloud Visionについては以前にも破壊的なバージョンアップがあったような...

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

Rails Tutorial 第6版 学習まとめ 第2章

概要

この記事は私の知識をより確実なものにするためにRailsチュートリアル解説記事を書くことで理解を深め
勉強の一環としています。稀にとんでもない内容や間違えた内容が書いてあるかもしれませんので
ご了承ください。
できればそれとなく教えてくれますと幸いです・・・

出典
Railsチュートリアル第6版

この章でやること

toy_appの作成を通してscaffoldの強力な機能を知る
RESTアーキテクチャとは何か大まかに把握する。

Toy_appの生成

①hello_appと同じようにRailsのバージョンを指定して生成する

$ cd ~/environment
$ rails _6.0.3_ new toy_app
$ cd toy_app/

②Gemfileも新しくなったのでToyアプリ用に書き換える

Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

gem 'rails',      '6.0.3'
gem 'puma',       '4.3.4'
gem 'sass-rails', '5.1.0'
gem 'webpacker',  '4.0.7'
gem 'turbolinks', '5.2.0'
gem 'jbuilder',   '2.9.1'
gem 'bootsnap',   '1.4.5', require: false

group :development, :test do
  gem 'sqlite3', '1.4.1'
  gem 'byebug',  '11.0.1', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  gem 'web-console',           '4.0.1'
  gem 'listen',                '3.1.5'
  gem 'spring',                '2.1.0'
  gem 'spring-watcher-listen', '2.0.1'
end

group :test do
  gem 'capybara',           '3.28.0'
  gem 'selenium-webdriver', '3.142.4'
  gem 'webdrivers',         '4.1.2'
end

group :production do
  gem 'pg', '1.1.4'
end

# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

③そして以前と同じく
bundle installを実行、

You have requested:
  listen = 3.1.5

The bundle currently has listen locked at 3.2.1.
Try running `bundle update listen`

If you are updating multiple gems in your Gemfile at once,
try passing them all to `bundle update`

このようなエラーが出たらGemfile.lockで引っかかっているのでGemfile.lockを一度削除するか
bundle updateを実行してから
bundle installする。

④最後にhello_appと同じくGitのバージョン管理下に置く。

$ git init
$ git add -A
$ git commit -m "Initialize repository"

ローカルのリポジトリはできたので、リモートのリポジトリをToyアプリ用に新規作成して
プッシュする。(リモートリポジトリ作成は第1章と同じ方法でGithubのサイト上で行う)
リポジトリは依然と同じくプライベートで。

$ git remote add origin https://github.com/<あなたのGitHubアカウント名>/toy_app.git
$ git push -u origin master

⑤とりあえずデプロイもしてみる。
今の状態だと中身がないのでとりあえずhello_appと同じように
hello,worldを出力するように追記しておく

application_controller.rb
class ApplicationController < ActionController::Base

  def hello
    render html: "hello, world!"
  end
end
routes.rb
Rails.application.routes.draw do
  root 'application#hello'
end

つづいてこの変更をコミット&Github/Herokuにプッシュ

$ git commit -am "Add hello"  
$ heroku create
$ git push && git push heroku master

Userモデルを作成する

ユーザーのモデル設計

ユーザーのデータモデル
Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#sec-modeling_demo_users

id,name,emailの属性がそれぞれテーブルのカラムに相当する。

マイクロポストのモデル設計

マイクロポストはTwitterのツイートと同じような140文字程度の短い投稿が前提となる。
投稿のid、投稿のテキスト内容を格納するtext型のcontent、誰の投稿かを記録するためのuser_idも追加すると

マイクロポストのデータモデル
Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#sec-modeling_demo_microposts

このようなデータモデルになる。

Usersリソース

さっそくUsersリソースからRailsをscaffoldジェネレータで生成する。

$ rails generate scaffold User name:string email:string

書き方はrails generate scaffold リソース名 カラム名:データ型 ~ となる。
id属性は必ず1つのモデルに1つ主キーとして付加されるので書く必要はない

scaffoldでUserモデルを作成したがこのままでは使えない。
ジェネレータで作成したデータベースはマイグレート(migrate)する必要がある。

データベースをマイグレートする↓

$ rails db:migrate

これでローカルWebサーバーを実行できるようになったので、Cloud9を使っている場合は
第1章と同じくdevelopment.rbを編集してCloud9への接続を許可しておく。

development.rb
Rails.application.configure do
  .
  .
  .
  config.hosts.clear
end

あとは別タブでターミナルを起動し、rails serverを実行。
これでローカルサーバーが立ち上がり、hello,world!が表示される。

ユーザーページを探検する

先ほどの一連の流れでUsersリソースをscaffoldで生成したことで自動でユーザー管理用のページが多数追加されている。
ページのURLと内容は以下の通り。

URL アクション 用途
/users index すべてのユーザーを一覧するページ
/users/1 show id=1のユーザーを表示するページ
/users/new new 新規ユーザーを作成するページ
/users/1/edit edit id=1のユーザーを編集するページ

Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#sec-a_user_tour

ユーザーページの探検はRailsチュートリアルを参照。

演習

  1. F12キーなどでブラウザのデベロッパーツールを起動し、該当の箇所を調べると、リロード時に内容が削除されている。
  2. emailを入力しないで登録しても登録できてしまう。これはまずいので3章から作るアプリではこのような問題の対策もしていく。
  3. 間違えたメールアドレスを入力されても登録できてしまう。2と同じでこれも対策することになる。
  4. Are you sure?と警告メッセージが表示されるようになっている。こういったユーザーの使いやすいように工夫された機能も実装していく。

MVCの挙動

/usersのindexページを開くと内部でどういった処理をしているのか、MVCで図解するとこのようになる。
image.png
Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#sec-mvc_in_action

ブラウザからのリクエスト→ルーターが該当するアクションに割り当て→必要なデータをUserモデルに問い合わせる
→データベースからモデルがデータを取り出す→モデルがデータをコントローラに渡す
→インスタンス変数に受け取ったデータを代入してビューに渡す→ビューはHTMLを生成し、コントローラに渡す
→コントローラがブラウザに受け取ったHTMLを返す
という流れ。

Toyアプリのとりあえずの形ができたのでルートURLをToyアプリのインデックスページに変更してみる。

routes.rb
Rails.application.routes.draw do
  resources :users
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  root "users#index"
end

今回も書き方は同じで "コントローラ名#アクション名"で記述する

ここでscaffoldで生成されたusers_controllerをのぞいてみる

users_controller.rb
class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  # GET /users
  # GET /users.json
  def index
    @users = User.all
  end

  # GET /users/1
  # GET /users/1.json
  def show
  end

  # GET /users/new
  def new
    @user = User.new
  end

  # GET /users/1/edit
  def edit
  end


このような記述がされている。
1行目のUsersController < ApplicationControllerという記述は
Rubyの文法で継承を意味する。ここでは
ApplicationControllerを継承したUserControllerクラスという意味になる。

以下の表はRESTに対応するアクションの一覧で、
URLが一部重複するものがあるがこれらはHTTPリクエストの種類が別なため別なルートとカウントされる。

HTTPリクエスト URL アクション 用途
GET /users index すべてのユーザーを一覧するページ
GET /users/1 show id=1のユーザーを表示するページ
GET /users/new new 新規ユーザーを作成するページ
POST /users create ユーザーを作成するアクション
GET /users/1/edit edit id=1のユーザーを編集するページ
PATCH /users/1 update id=1のユーザーを更新するアクション
DELETE /users/1 destroy id=1のユーザーを削除するアクション

Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#table-demo_RESTful_users

users_controllerのindexアクションをみてみると

  def index
    @users = User.all
  end

このような記述になっている。
これは@users変数にUserデータベースのすべてのデータを取り出して代入する処理を行っている。

Userモデル自体は何も記述がなく非常にシンプルだが
Userクラスが継承している"ActiveRecordクラス"がDB操作に関する非常に多くの機能を持っているため
今回の場合はUser.allでデータベースからUserのデータを取り出すことができた。

この@users変数はindexアクションに対応するindexビューで使われている。
該当の場所をみてみる。

index.html.erb


<% @users.each do |user| %>
      <tr>
        <td><%= user.name %></td>
        <td><%= user.email %></td>
        <td><%= link_to 'Show', user %></td>
        <td><%= link_to 'Edit', edit_user_path(user) %></td>
        <td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
<% end %>

この記述の<% @users.each do |user| %>の部分だが
この段階で簡潔に説明すると、@users変数に入っているデータ(レコード)を一つずつ引っ張り出して
userに代入し、endで囲まれた部分をそのレコードの数分繰り返す処理。
ユーザーが3人(A、B、C)といたら
Aのデータをuserに入れてhtmlを生成
Bのデータをuserに入れてhtmlを生成
Cのデータをuserに入れてhtmlを生成
という繰り返し処理を行っている。
現時点では理解しなくてもOK

演習

1.
①ブラウザからリクエストがルーターに送られる
②ルーターがリクエストに対応するコントローラのeditアクションに割り当てる。
③editアクションが必要とするデータをモデルにリクエストする。
④モデルがリクエストされたデータをデータベースから読み出し、コントローラに返す
⑤editアクションが対応するビューedit.html.erbを呼ぶ(この時ビューには読み出したデータを渡す。)
⑥ビューがHTMLを生成し、コントローラに返す。
⑦コントローラがブラウザに渡されたHTMLを返す。

2.
users_controllerのprivateメソッドに記述されている。

users_controller.rb
class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]






  private
    # Use callbacks to share common setup or constraints between actions.
    def set_user
      @user = User.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def user_params
      params.require(:user).permit(:name, :email)
    end
end

before_actionという記述でeditアクションを実行する前にset_userメソッドを実行するよう指定されている。
ser_userアクションでは@user変数にUserデータベースのリクエストされたIDのテーブルを読み出すという文が書いてある。

3.edit.html.erb

Usersリソースの欠点について

前回の演習でも挙げたがユーザー名やメールアドレスがめちゃくちゃでも登録できてしまう。

またログイン機構がないので誰でもユーザーを削除したり編集したりできてしまう。権限もくそもない。

そしてアプリの動作を確認するテストがほぼない。上記の内容が正しいか。アプリの機能が正しく動作するかなどの
テストが全くかかれていない。→不都合があっても見つからない

UIがダサい、レイアウトもめちゃくちゃこんなレイアウトのWebアプリはだれも使わない(断言)

理解が難しい。これに尽きる
Railsチュートリアルを1週読破した自分でさえも完全にコードを理解するのは難しい。
コードは基本簡潔にわかりやすく書かないとNG 

マイクロポストを作成する

MicropostsもUsersと同様scaffoldで生成してみる。

$ rails g scaffold Micropost content:text user_id:integer
Running via Spring preloader in process 3794
      invoke  active_record
      create    db/migrate/20200601141608_create_microposts.rb
      create    app/models/micropost.rb
      invoke    test_unit
      create      test/models/micropost_test.rb
      create      test/fixtures/microposts.yml
      invoke  resource_route
       route    resources :microposts
      invoke  scaffold_controller
      create    app/controllers/microposts_controller.rb
      invoke    erb
      create      app/views/microposts
      create      app/views/microposts/index.html.erb
      create      app/views/microposts/edit.html.erb
      create      app/views/microposts/show.html.erb
      create      app/views/microposts/new.html.erb
      create      app/views/microposts/_form.html.erb
      invoke    test_unit
      create      test/controllers/microposts_controller_test.rb
      create      test/system/microposts_test.rb
      invoke    helper
      create      app/helpers/microposts_helper.rb
      invoke      test_unit
      invoke    jbuilder
      create      app/views/microposts/index.json.jbuilder
      create      app/views/microposts/show.json.jbuilder
      create      app/views/microposts/_micropost.json.jbuilder
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/microposts.scss
      invoke  scss
   identical    app/assets/stylesheets/scaffolds.scss

Usersの生成時と同じようにまずはデータベースをマイグレートして使えるようにする。

$ rails db:migrate
== 20200601141608 CreateMicroposts: migrating =================================
-- create_table(:microposts)
   -> 0.0041s
== 20200601141608 CreateMicroposts: migrated (0.0048s) ========================

MicropostsもUsersの時と変わらず、ルーターが更新され、resourcesでRESTアーキテクチャに対応した
各ルーティングが割り当てられる。

HTTPリクエスト URL アクション 用途
GET /microposts index すべてのマイクロポストを表示するページ
GET /microposts/1 show id=1のマイクロポストを表示するページ
GET /microposts/new new マイクロポストを新規作成するページ
POST /microposts create マイクロポストを新規作成するアクション
GET /microposts/1/edit edit id=1のマイクロポストを編集するページ
PATCH /microposts/1 update id=1のマイクロポストを更新するアクション
DELETE /microposts/1 destroy id1のマイクロポストを削除する

Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#code-demo_microposts_resource

scaffoldで生成されるコードはクラス名が自分でつけた名前に代わるだけで他は変わらない。
つまり同じ構造である。

MicropostsもUsersと同じように/micropostsでインデックスページにアクセスでき、New micropostで投稿ができる。

演習

  1. Usersの時と同じく、中身が消える。
  2. これもUsersの時と同じで、現状対策されていないため空のユーザーで空の投稿ができてしまう。
  3. 文字数を制限するコードが生成されていないため、投稿できてしまう。(これも欠点の一つ)
  4. indexページからdestroyリンクを踏むことで削除できる。これも確認メッセージが表示されてから削除される。
マイクロポストをマイクロなポストに

前回の演習の3番目の項目で文字数制限がないため、長い文章でも投稿できてしまいマイクロでない問題が発覚した。
この問題を修正していく。

データベースの内容に制限(バリデーション)を加えることでこの問題を修正する。
具体的なコードは下のとおり

micropost.rb
class Micropost < ApplicationRecord
  validates :content, length:{maximum: 140}

end

データベースを扱うモデルにバリデーションを書くことでcontentを140字に制限する。
このコードは
contentのlengthのmaximumを140に制限するという、非常に単純なコード。
投稿の文字数の最大を140文字とも言い換えられる。

バリデーションを追加することでRailsがエラーを吐くようになった。
実際にMicropostを140文字以上で投稿してみた画面がこちら。
image.png

超優秀。

演習

  1. 上の画面のようにエラーを吐くようになり、indexページに戻っても投稿は追加されない。(データベースに登録されない)
  2. error_explanationクラスの中にulリストでエラーの内容が格納されている。エラーが増えればliタグのコンテンツがエラーとして追加される。
ユーザーとマイクロポストの関連付け

TwitterやQiitaなどでもそうだが
”一人のユーザーは複数の投稿を持っている”
具体的な言い方に換えると
ユーザIDが1の投稿は複数存在しうる。
こういった1対多のデータの関係性をRailsは超簡単に実装できる。
そのコードがこちら

user.rb
class User < ApplicationRecord
  has_many :microposts
end
micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  validates :content, length:{maximum: 140}
end

Userはmicropostを複数持っている。
micropostsは一つのユーザーに属する。
この関係性をこのコードで実装している。

このコードを書くことでMicropostsのuser_idカラムの値からユーザーを表示することができる。
実際にやってみよう。

まずはRailsの機能を使えるコンソールrails consoleを起動する。
rails console内ではデータベースのデータやコードを自由に試せるので非常に便利。
まずはrails console内でユーザーを作成し、そのユーザーのIDをもったMicropostも作成する。

user1 = User.first
   (0.4ms)  SELECT sqlite_version(*)
  User Load (0.1ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
 => #<User id: 1, name: "takemo", email: "take.webengineer@gmail.com", created_at: "2020-05-31 14:00:23", updated_at: "2020-05-31 14:00:23"> 
2.6.3 :002 > user1.microposts

user1にUserデータベースの一番最初に登録されているデータを代入
ローカルサーバーで以前ユーザーをお試しで作ったのでそのデータが代入されている。

ここで先ほどの関連付けが役立つシーンがこちら

> user1.microposts
  Micropost Load (0.1ms)  SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? LIMIT ?  [["user_id", 1], ["LIMIT", 11]]
 => #<ActiveRecord::Associations::CollectionProxy [#<Micropost id: 1, content: "taskemodsjfa jl", user_id: 1, created_at: "2020-06-01 14:24:23", updated_at: "2020-06-01 14:24:23">, #<Micropost id: 3, content: "Ruby(ルビー)は、まつもとゆきひろ(通称: Matz)により開発されたオブジェクト指向スクリプト...", user_id: 1, created_at: "2020-06-01 14:27:03", updated_at: "2020-06-01 14:27:03">, #<Micropost id: 5, content: "異なるデータモデル同士の関連付けは、Railsの強力な機能です。ここでは、1人のユーザーに対し複数の...", user_id: 1, created_at: "2020-06-01 14:41:51", updated_at: "2020-06-01 14:41:51">]> 
2.6.3 :003 > 

関連付けによってUserには複数のMicropostが結びついていることになったので、user1.micropostsとすると
user1に結びついたMicropostをすべて取り出すことができる。(ユーザーが何を投稿したか調べられる。)

逆方向の使い方を試してみる(投稿から誰が投降したのかを調べる。)

> micropost = user1.microposts.first
  Micropost Load (0.1ms)  SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."id" ASC LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
 => #<Micropost id: 1, content: "taskemodsjfa jl", user_id: 1, created_at: "2020-06-01 14:24:23", updated_at: "2020-06-01 14:24:23"> 

micropost変数にuser1(user_id=1)の最初のmicropostを代入した。
つまりmicropostからuser_idが1のユーザーを取り出せればいい。

> micropost.user
 => #<User id: 1, name: "takemo", email: "take.webengineer@gmail.com", created_at: "2020-05-31 14:00:23", updated_at: "2020-05-31 14:00:23"> 
2.6.3 :004 > 

micropost.user とするだけでmicropostが属するユーザーを取り出すことができた。
1対多の関連性をhas_manyとbelongs_toを使って実装するだけでこのような便利な使い方ができる。

演習

  1. @userのmicropostsの中でfirstのmicropostのcontentを表示すればいいのでそのままほかの行からコピペして書き換えるとこうなる。
show.html.erb
<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @user.name %>
</p>

<p>
  <strong>Email:</strong>
  <%= @user.email %>
</p>


<p> 
  <strong>Micropost_first</strong>
  <%= @user.microposts.first.content %>
</p>
<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>

↓結果
image.png

2.コードを追加したら実際に空投稿してみると
バリデーションに引っかかってエラーを吐いた。
image.png

3.FILL_INにはバリデーションを適用したいカラムを指定する。(nameとemail)

user.rb
class User < ApplicationRecord
  has_many :microposts
  validates :name, presence: true
  validates :email, presence: true
end

バリデーションを追加したのでこちらも空登録するとエラーを吐くようになった。
image.png

Railsの継承について

ここは何となくの理解でもとりあえずOK
Railsのモデルは User < ApplicationRecordというRuby特有の文法で継承されている。
モデルの継承の構造は図の通り
image.png

Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#fig-demo_model_inheritance

UserやMicropostといったRailsのモデルはすべてApplicationRecordクラスを継承している。
そしてApplicationRecordはActiveRecord::Baseという基本クラスを継承している。
このActiveRecordがデータベースを扱う機能を提供している。
User.firstのfirstメソッドなどもこのActiveRecordを継承していることにより使える。

コントローラーも似たような構造でできている。
image.png
Railsチュートリアル第6版より
https://railstutorial.jp/chapters/toy_app?version=6.0#fig-demo_controller_inheritance

各コントローラーはApplicationControllerを継承しておりApplicationControllerは
ActionController::Baseという基本クラスを継承していることにより
様々なコントローラーで等しくコントローラーの基本機能を使うことができる。
またApplicationControllerを継承しているのでApplicationControllerで定義したルールは
継承先の各コントローラーで適用され、定義したメソッドなども同じように使用できる。

演習

1.1行目

application_controller.rb
class ApplicationController < ActionController::Base
  def hello
    render html:"hello,world!"
  end
end

2.モデルが格納されているmodelsフォルダのapplication_record.rbの1行目

application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end

最後に

この章で作ったToyアプリをHerokuにデプロイする

$ git add -A
$ git commit -m "Finish toy app"
$ git push
$ git push heroku 

このままだとHeroku上でエラーが発生して本番環境でサイトが表示されない。
heroku logsでHeroku上のエラーログを追ってみる。
するとこのような行がある。

2020-06-01T15:52:35.330114+00:00 app[web.1]: [3a72d85c-e21f-41d2-92ce-40c241660d8f]
 ActionView::Template::Error (PG::UndefinedTable: ERROR:  relation "users" does not exist

PGとはPostgreSQLのことである。
意味はPGでusersテーブルが存在しないと怒られている。
本番環境のデータベースと開発環境のデータベースは違うため
マイグレーションを本番環境でも行わなければならない。
つまりマイグレーションをしていないから怒られている。

heroku run rails db:migrateで本番環境のデータベースをマイグレートする。
これでしっかり動作するはず。

演習

1.ここで先ほどの演習2.3.3.1をやっていると本番環境のデータベースにはマイクロポストが一つも登録されておらず
エラーを吐いてしまうため、演習で追加したコード部は削除して、コミット、プッシュしなおしておく。
image.png
うまくいけばこのようにユーザーを作成できる。


2.本番環境でも同じくマイクロポストを作成できることが確認できた。

image.png

3.もちろんちゃんと動く
image.png

←前の章へ

次の章へ→

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

プログラム基礎知識を勉強する際の考え方

自分はこんな感じでのイメージ学んだので、メモ書きです。

  1. 作りたいプログラムの仕様を考える
  2. プログラムをトリガーやイベントごとに分割して日本語で書き出す
  3. コードを書く&説明コメントをつけて理解出来ない部分を無くす
  4. 小さいコードのプログラムを沢山書く
  5. 少しづつ大きいプログラムを書いてみる

プログラムを書く際は、自分でコメント説明文を入れて
合っているかを確認してもらう!!
→自分で説明できるか?
 なぜ、そのコードを入れたのかを意識する

<仕様>
そのプログラムができる事を具体的に箇条書き


<プログラムでどんな処理をするか日本語で説明>
・登場する変数は?



実際にコードを書く!
※その際に、自分でコメントを入れて全て
 説明できるかを確認していく

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

【Error】編集後に画面遷移しない事象の解消法

概要

タスクの編集画面を実装し、確認のため編集を行い、ボタンを押下したのですが画面が変わらず。。。。:confounded:

結果としては、「redirect_to」していなかった:sweat_smile:ということでしたが、エラー解決するまでの経緯を備忘録として残します!

確認方法

【前提】
編集画面で登録ボタンを押した後は、ボタンは押下できない状況にあった。
画面遷移もされない。
①MySQLに編集データが保存されているかを確認

変更されている:ok_hand:

②エラー画面が出ていないのであれば、ターミナルにエラー情報が記入されているのではないかを確認

terminal
No template found for TasksController#update, rendering head :no_content
Completed 204 No Content in 139ms (ActiveRecord: 3.4ms)

あった:rolling_eyes:
No templateって記載されてた!!

③コントローラーを確認
→redirect_toが記載なし:scream:

修正箇所

修正前

tasks_controller.rb
def update
  @task.update(task_params)
  if @task.valid?
    @task.save
  else
    flash.now[:alert] = 'タスク名を入力してください'
    render :index
  end
end

修正後

tasks_controller.rb
def update
  @task.update(task_params)
  if @task.valid?
    @task.save
    redirect_to group_tasks_path(@group), notice: 'タスクが変更されました'
  else
    flash.now[:alert] = 'タスク名を入力してください'
    render :index
  end
end

皆さま、お気をつけください:bow_tone1:

参考

エラー204に関するURLです。
https://developer.mozilla.org/ja/docs/Web/HTTP/Status/204

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

form_with scope

form_withのscopeについて

こんなフォームがあったけどscopeってどんな意味だってなった

ruby.rb
<%= form_with scope: :session, url: sessions_path do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end %>

とりあえず検証ツールを見てみる
なるほどこんな働きなわけね。

<form action="/sessions" method="post" data-remote="true">
  <input type="text" name="session[name]">
</form>

つまりscopeオプションはどんなはたらき?

spopeオブジェクトに私た値がname値のプレフィックス(接頭語)になっている

name = "session[name]"

という形でパラメータが送信されている。ってこと

どうやって受け取る?

パラメータのプレフィックスにsessionがついただけなので難しく考えることはないです

controller.rb
def create
  #スコープを用いて送信された値をsession[:user_id]に代入
  session[:user_name] = session_params[:name]
end

こんな働きをしているってことですね。大したことはなかった。

本日はここまで。
form_withひとつとっても色々ありますね。

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

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

HerokuにBugsnagを導入

Heroku × Railsです。

アドオンを追加→APIキーを取得

ダッシュボードから操作する方法と、コマンドラインから操作する方法があります。

ダッシュボードから操作する場合

  • Herokuのダッシュボードにアクセス
  • Resourcesタブを開く
  • Add-onsの下の検索窓に'Bugsnag'と入力
  • Provisionをクリック
  • Settingsタブを開く
  • Reveal Config Varsをクリックし、BugsnagのAPIキーを確認する

コマンドラインから操作

Heroku CLIがインストールされている状態で以下のコマンドを叩く

$ heroku addons:create bugsnag
$ heroku config:get BUGSNAG_API_KEY
70d9b0852a968b1d0d0e329b5507f287 #APIキー

アプリケーション側の設定

Gemfile
gem 'bugsnag'
$ bundle install
$ rails generate bugsnag 70d9b0852a968b1d0d0e329b5507f287 # APIキー

config/initializers/bugsnag.rbが生成される。デフォルトではAPIキーがベタ書きされているので、環境変数にしまう。dotenvというgemを使って以下のように記述しました。

config/initializers/bugsnag.rb
Bugsnag.configure do |config|
  config.api_key = ENV['BUGSNAG_API_KEY'] #修正
end

production環境でのみ動くように修正

config/initializers/bugsnag.rb
Bugsnag.configure do |config|
  config.api_key = ENV['BUGSNAG_API_KEY']
  config.notify_release_stages = ['production'] #追加
end

その他

raise等を使って自分で発生させている例外を捕捉したい場合、Bugsnag.notify(exception)を使う。

begin
  raise 'Something went wrong!'
rescue => exception
  Bugsnag.notify(exception)
end

参考

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

初心者向けRailsでブログを作ってみるチュートリアルその1

これはなに?

初心者向けRailsでブログを作ってみるチュートリアルその0 の続きです。

今回はブログサービスを作ります。
では、ブログサービスの機能を分解して考えてみましょう
実際のブログサービスにはいくつか機能がありますが、今回はブログサービスと呼ぶために必要なミニマムの機能を開発しましょう。

機能一覧

訪問者用機能

  • 記事閲覧機能
  • コメント機能

管理機能

  • 管理者ログイン機能
  • 記事投稿機能

この中で、次のステップから最初に最低限ブログらしいものに必要な「記事閲覧機能」を作っていこうと思います。

サイトマップ

image.png

ワイヤーフレーム

今回は最初に作る記事閲覧機能に含まれる2画面についてのワイヤーフレームを draw.io で作ってみました。
今回は、全体の機能がコンパクトなためワイヤーフレームと次回以降でやるモデリングにとどめて細かい仕様設計などはしません(実際開発者が1-3名程度のスタートアップや新規事業だともっと大雑把なワイヤーフレームしかない状態で進めてもまあまあうまくいったりします)

記事一覧画面

image.png

記事詳細画面

image.png

完成版にはコメント機能をつけますが、最初の時点では省略します

チュートリアルその1まとめ

今回は、開発に移る前の設計の初期段階として何を作るのか、というところを大まかに定義しました。
次回はプロジェクトのセットアップを行います。

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

道のりは長い…( ´Д`)y━・~~ MySQLが起動しない事象への対応

Railsを起動するため、Gem、bundleの再設定を行いました。ようやくアプリケーションを起動することができたのですが、アプリケーションにアクセスするとエラー出力され、アプリが動きません。
非常に道のりが長いですが、1つ1つ対応を進めていきます。

エラー内容:

* Listening on tcp://localhost:3000
Use Ctrl-C to stop
Started GET "/" for ::1 at 2020-06-02 11:31:07 +0900

Mysql2::Error::ConnectionError (Can't connect to local MySQL server through socket '/tmp/mysql.sock' (38)):
(省略)

①そもそもサービスが起動していなかった。(mysql.server statusで確認)

②ログを確認すると、プロセスを起動する際にファイルのオープンに失敗している
(ichikawadaisukenoMacBook-Air.local.err )

200602 11:51:16 mysqld_safe mysqld from pid file /usr/local/var/mysql/ichikawadaisukenoMacBook-Air.local.pid ended

③そういえば、先日Mysqlが稼働しなかったため、homebrewコマンドでインストールしなおした経緯がある。/usr/local/Cellar配下をみると、v5.6系とv8.0が存在していた。
競合している可能性もあるので、先日追加したv8.0系はアンインストールした。

④さらにリンクも再設定すれば復旧するかもしれないと考え、以下を試した。

CMD> brew unlink mysql@5.6
Unlinking /usr/local/Cellar/mysql@5.6/5.6.42... 0 symlinks removed
CMD> brew link --force mysql56
Linking /usr/local/Cellar/mysql@5.6/5.6.42... 99 symlinks created

⑤この後、以下のファイルで直接5.6系のサービスを起動しようとするも
/usr/local/Cellar/mysql@5.6/5.6.42/support-files/mysql.server start

 ERROR! The server quit without updating PID file (/usr/local/var/mysql/ichikawadaisukenoMacBook-Air.local.pid).

⑥出来るだけ、mysqlの再インストールは選択したくなかったのですが、時間を消耗してしまうので、以下のコマンドからまず環境をクリーンにしました。

※ファイルが存在している場合はバックアップを取得する
brew uninstall mysql56
$ sudo rm -rf /usr/local/mysql
$ sudo rm -rf /Library/StartupItems/MYSQL
$ sudo rm -rf /Library/PreferencePanes/MySQL.prefPane
$ sudo rm -rf /Library/Receipts/mysql-.pkg
$ sudo rm -rf /usr/local/Cellar/mysql*
$ sudo rm -rf /usr/local/bin/mysql*
$ sudo rm -rf /usr/local/var/mysql*  ※ここにはファイルが存在したため、bk取得
$ sudo rm -rf /usr/local/etc/my.cnf
$ sudo rm -rf /usr/local/share/mysql*
$ sudo rm -rf /usr/local/opt/mysql*

⑦最後にインストールを実行し、希望のサービスが稼働していることを確認しました。

[\W staff@term]brew install mysql56
Updating Homebrew...
==> Downloading https://homebrew.bintray.com/bottles/mysql%405.6-5.6.47.catalina.bottle.tar.gz
Already downloaded: /Users/ichikawadaisuke/Library/Caches/Homebrew/downloads/c270819d76ed326059143e1b4c6c6c0ab672e4259c328c140ec71d917babc348--mysql@5.6-5.6.47.catalina.bottle.tar.gz
==> Pouring mysql@5.6-5.6.47.catalina.bottle.tar.gz
==> /usr/local/Cellar/mysql@5.6/5.6.47/bin/mysql_install_db --verbose --user=ichikawadaisuke --basedir=/usr/local/Ce
==> Caveats
A "/etc/my.cnf" from another install may interfere with a Homebrew-built
server starting up correctly.

MySQL is configured to only allow connections from localhost by default

To connect:
    mysql -uroot

mysql@5.6 is keg-only, which means it was not symlinked into /usr/local,
because this is an alternate version of another formula.

If you need to have mysql@5.6 first in your PATH run:
  echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.zshrc

For compilers to find mysql@5.6 you may need to set:
  export LDFLAGS="-L/usr/local/opt/mysql@5.6/lib"
  export CPPFLAGS="-I/usr/local/opt/mysql@5.6/include"


To restart mysql@5.6 after an upgrade:
  brew services restart mysql@5.6
Or, if you don't want/need a background service you can just run:
  /usr/local/opt/mysql@5.6/bin/mysql.server start
==> Summary
?  /usr/local/Cellar/mysql@5.6/5.6.47: 344 files, 155.2MB
[\W staff@term]/usr/local/opt/mysql@5.6/bin/mysql.server status
 SUCCESS! MySQL running (37799)

次はまたRailsが動かなくなりました…?

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

macOSをバージョンアップしたらrails s出来なくなった

問題

① macOS Catalina のバージョンを10.15.4にアップデートする

② 現在制作途中のアプリケーションのディレクトリにてrails sする

③ 以下のようなエラーが出る

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

解決

https://nodejs.org/ja/download/current/

上記のサイトにて、最新版のNode.jsをインストールするか

ターミナルで、homebrewを用いてインストールを行う

% brew install nodejs     

すると、無事rails sでローカルサーバーを起動できた。

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

Rspecでwebmockを使う

概要

外部APIなどにアクセスする機能をテストする際に、APIレスポンスのMockを定義できるgem。

Gemfileにwebmockを追加

group :test do
  gem 'webmock'
end
$ bundle install --path vendor/bundle

テスト対象のmodel

model/smaple.rb
require 'net/https'
require 'uri'

class Sample
  include ActiveModel::Model

  def get_request(api_url, params)
    uri = URI(api_url)
    uri.query = params
    request(uri)
  end
end

rspecを作る

前述のget_requestメソッドを実行すると、WebMockで定義したstubの内容が返る。

require 'rails_helper'
require 'webmock/rspec'

RSpec.describe Abyss, type: :model do
  describe 'facet', type: :facet do
    before do
      WebMock.enable!
      WebMock.stub_request(:any, "www.example.com").to_return(
        body: "This is a mock",
        status: 200,
        headers: { 'Content-Length' => 7 }
      )
    end
    subject { Sample.new }
    it 'debug' do
      res = subject.send(:get_request, 'http://www.example.com', '')
      puts res.body # This is a mock
    end
  end
end

参考

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

capistranoで構築した環境でassetsの中身を1から作り直したい

なぜそんなことをしたくなったのか

capistranoのassets:precompileが走っている時にEC2がメモリ不足に陥り、インスタンスを再起動させた結果、deployが中途半端なところで終わってしまいました。
もし中途半端なコンパイル結果をもとにWebサーバーが動いて実行時エラーなんて起きたときには目も当てられないので、なんとかまっさらな状態に戻そうとしてあれこれしました。

バージョン情報

rails (5.2.4.2)
capistrano (3.11.0)
sprockets (3.7.2)
sprockets-rails (3.2.1)

前提条件

current_pathはこのパスとする

  • /var/www/app_name/current

shared_pathはこのパスとする

  • /var/www/app_name/shared

先に結論

結論から言うと、やるべきことはこれです。

  1. /var/www/app_name/shared/public/assets の中身を消す
  2. /var/www/app_name/shared/tmp/cache/assets の中身を消す
  3. いつもどおりデプロイ

注意点があります。
capistranoにはdeploy:clobber_assetsのようなassetsをいかにも消しそうなコマンドが標準で用意されていますが、
これを使わずちゃんと手で消すということです。理由は後述します。

説明

capistranoのディレクトリ構成

まず、capistranoによってデプロイされたサーバーの/var/www/app_nameの中は、下記のような構造になっています。

├── current (releases内の最新のディレクトリにシンボリックリンクが貼られてる)
├── releases
│   ├── 20200601101105
│   ├── 20200601102714
│   └── 20200601105159
├── repo
├── revisions.log
└── shared

このcurrentの下、つまり、releases/20200601105159の下には、あなたのプロジェクトのリポジトリにあるような構成になっているでしょう。
そこにはapp/があり、config/があり、public/があるはずです。

このpublic/の下にはビルドされた静的ファイルが詰め込まれたassets/が入っている……ように見せかけて、
/var/www/app_name/current/public/assets/var/www/app_name/shared/assetsへのシンボリックリンクになっています。

そして/var/www/app_name/current/tmp/cacheは同様に/var/www/app_name/shared/tmp/cacheへのシンボリックリンクになっています。

assets:precompileのキャッシュの仕組み

Railsガイド アセットのキャッシュストア

デフォルトのSprocketsは、development環境とproduction環境でtmp/cache/assetsにアセットをキャッシュします。

こう書かれている通り、tmp/cache/assetsにコンパイル結果のキャッシュがあるので、これを消します。
capistranoだと/var/www/app_name/shared/tmp/cache/assetsがこれにあたるので消します。

Sprocketsのデプロイ結果のキャッシュの仕組み

assets:precompileのキャッシュとは別に、Sprocketsはデプロイ結果のファイルも取っておいてあります。
Sprocketsのコードにこんなのが書かれています。

module Sprockets
  module Exporters
    # Writes a an asset file to disk
    class FileExporter < Exporters::Base
      def skip?(logger)
        if ::File.exist?(target)
          logger.debug "Skipping #{ target }, already exists"
          true
        else
          logger.info "Writing #{ target }"
          false
        end
      end

      def call
        write(target) do |file|
          file.write(asset.source)
        end
      end
    end
  end
end

deploy:assets:precompileしているときに、標準出力でログがずらっと出てきているのを目にしていませんか? アレを実現しているのがおそらくは上のコードです。
logger.info "Writingのところを見てください。

こんな感じ:

      01 I, [2020-06-01T08:56:09.691790 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/develop/application-232387090aed00e6b038…
      01 I, [2020-06-01T08:56:09.692557 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/develop/application-232387090aed00e6b038…
      01 I, [2020-06-01T08:56:17.418572 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0002/application-d1bedc9937772b59211a…
      01 I, [2020-06-01T08:56:17.418856 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0002/application-d1bedc9937772b59211a…
      01 I, [2020-06-01T08:56:21.414096 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0003/application-5a60e0a26af1e40a9188…
      01 I, [2020-06-01T08:56:21.414368 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0003/application-5a60e0a26af1e40a9188…

このskip?の判定に引っかかった場合。
つまり、この例だと/var/www/app_name/releases/20200601085319/public/assets/develop/application-232387090aed00e6b038…のdigest込みのファイルが既に存在していた場合、
Sprocketsはファイルを書き出しません。

なので、capistranoだと/var/www/app_name/shared/assetsに同じファイルがあった場合、このskip?でスキップされてしまうということです。
消しておく必要がありますね。

おや? 便利なコマンドがありそうだが……?

capistranoはcap -Tで全てのコマンドを表示してくれます。
assets関係だとこんなコマンドが見つかります。

cap deploy:cleanup_assets          # Cleanup expired assets
cap deploy:clobber_assets          # Clobber assets

標準でdeploy:clobber_assetsというコマンドが用意されているではありませんか!
clobberとはぶん殴るという意味です。強制的に新しいassetsにしてくれそうだ! こりゃいいや! と思って使うと、実は思ったような挙動をしてくれません。

$ bundle exec cap review deploy:clobber_assets

00:00 deploy:clobber_assets
      01 bundle exec rake assets:clobber
      01 I, [2020-06-01T11:22:46.062204 #6756]  INFO -- : Removed /var/www/app_name/releases/20200601094959/public/assets
      01 Removed webpack output path directory /var/www/app_name/releases/20200601094959/public/packs

いかにもassetsをディレクトリごと消してくれていそうな雰囲気ですが、よく見るとRemoveしているのはただのシンボリックリンクで、肝心の中身は消えていません。マジか。
そして、そもそもコンパイル時のキャッシュであるtmp/cacheの方は消してくれていません。

このことについてはissueも上がっているようです。

なので、「結論」に書いた通り、手動でコマンドを実行して消すか、自分でコマンドを定義してやるのが良さそうです。

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

「多段階選抜」の問題をRubyで解いてみた

問題

第24回オフラインリアルタイムどう書くの問題

参考 (Clojure 版)

先に Clojure でも解いています。
https://qiita.com/kts_h/items/64bb74fdcef642198bc5

Ruby 版

すでにRubyによる解答が多数出されているのですが、面白くなって自分でも解いてみたので、貼っておきます。

solve の中の each_char ブロックを綺麗に書きたかったので、クラスにしてインスタンス変数の中身を次々と変えていくようにしてみました。

class MultiStageSelection
  def self.solve(cmds)
    new.solve(cmds)
  end

  def initialize
    @enum = 1.step
  end

  def drop_nth(n)
    enum = @enum
    @enum = Enumerator.new do |y|
      n -= 1
      loop do
        n.times { y << enum.next }
        enum.next
      end
    end
    self
  end

  def drop_before(test)
    enum = @enum
    @enum = Enumerator.new do |y|
      a, b = enum.next, enum.next
      loop do
        y << a unless send(test, b)
        a, b = b, enum.next
      end
    end
    self
  end

  def drop_after(test)
    enum = @enum
    @enum = Enumerator.new do |y|
      a, b = enum.next, enum.next
      y << a
      loop do
        y << b unless send(test, a)
        a, b = b, enum.next
      end
    end
    self
  end

  def drop_100
    enum = @enum
    @enum = Enumerator.new do |y|
      100.times { enum.next }
      loop { y << enum.next }
    end
    self
  end

  def solve(cmds)
    cmds.each_char do |cmd|
      case cmd
      when "s" then drop_before(:square?)
      when "S" then drop_after(:square?)
      when "c" then drop_before(:cube?)
      when "C" then drop_after(:cube?)
      when "h" then drop_100
      else drop_nth(cmd.to_i)
      end
    end
    puts @enum.take(10).join(",")
  end

  private

  def square?(n)
    Math.sqrt(n).truncate**2 == n
  end

  def cube?(n)
    Math.cbrt(n).truncate**3 == n
  end
end


MultiStageSelection.solve("ss6cc24S")
MultiStageSelection.solve("4scC3hh982Cc5s")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

いいね機能の追加について

非同期通信でのいいね機能

しばらくJavaに浮気をしていて久しぶりに個人アプリ(Ruby)に触れたら結構記憶が飛んでたので思い出すためにいいね機能を搭載してみました。
いろいろな記事を見ながら実装したので自分用に纏めるメモです。
userとarticleを紐付けてボタンを押すといいねができるようになるまで。

注意点...すでにアプリとしての形はある程度できている状態からの説明です

①モデル、テーブルの追加

rails g model favorite をしてマイグレーションファイルを修正

マイグレーションファイル
class CreateFavorites < ActiveRecord::Migration[5.2]
  def change
    create_table :favorites do |t|
      t.references :user, null: false, foreign_key: true
      t.references :article, null: false, foreign_key: true
      t.timestamps
    end
  end
end

その後rails db:migrate

モデルにアソシエーションを追記

user.rb
  has_many :favorites, dependent: :destroy
  has_many :favorite_articles, through: :favorites, source: :article
article.rb
  has_many :favorites, dependent: :destroy
  has_many :favorite_users, through: :favorites, source: :user
favorite.rb
  belongs_to :user
  belongs_to :article

  validates :user_id, presence: true
  validates :article_id, presence: true
  validates_uniqueness_of :article_id, scope: :user_id

②コントローラの追加(ついでにルーティング)

rails g controller favorites でfavoriteコントローラを追加する
そして以下のように追記

favorites_contoroller.rb
before_action :set_article, only: [:create, :destroy]

  def create
    @favorite = Favorite.create(user_id: current_user.id, article_id: @article.id)
  end

  def destroy
    @favorite = Favorite.find_by(user_id: current_user.id, article_id: @article.id)
    @favorite.destroy
  end

  private
  def set_article
    @article = Article.find(params[:article_id])
  end

ルーティングも編集

routes.rb
  resources :articles do
    resources :favorites , only: [:create, :destroy] #追記
  end

③ビューの編集

いいねをするボタンを部分テンプレートを用いて追加

view
.content
  = render "layouts/favorite", article: @article
_favorite.html.haml
.content__favorite{id: "like-#{@article.id}"}
  - if Favorite.find_by(user_id: current_user.id, article_id: article.id)
    .btn1
      = link_to article_favorite_path(article.id, current_user.id), method: :delete, class: "like-delete", remote: true do
        = icon('fa', 'star')
        お気に入り追加済み
        = article.favorites.length
  - else
    .btn2
      = link_to article_favorites_path(article.id), method: :post, class: "like-create", remote: true do
        = icon('fa', 'star')
        お気に入り
        = article.favorites.length

remote:trueをつけるとリンクを踏んだ時ajaxが起動する。

④ボタンを非同期通信にする

create.js.haml と destroy.js.hamlファイル作成し追記

views/favorites/create.js.haml
$("#like-#{@article.id}").html("#{j(render partial: 'layouts/favorite', locals: { article: @article })}");
views/favorites/destroy.js.haml
$("#like-#{@article.id}").html("#{j(render partial: 'layouts/favorite', locals: { article: @article })}");

ボタンにつけたidを参照してボタンが押されるとファイルを再読み込みするようにしてあるっぽい。

あとはボタンの見た目とかを綺麗に整えたら完成。

参考にした記事
https://qiita.com/yummy888/items/2b7708a498861e5ba733
https://qiita.com/nojinoji/items/2c66499848d882c31ffa

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

【Rails】Google Mapの表示方法

目標

ezgif.com-video-to-gif.gif

前提

下記実装済み。

Googleアカウントを作成済み。
Slim導入

Google Cloud Platformに登録

1.下記リンクにアクセス

Google Cloud Platform

2.「使ってみる」をクリック

スクリーンショット 2020-06-02 10.25.36.png

3.プロジェクト名(適当で良い)を入力し、「作成」をクリック

スクリーンショット 2020-06-02 10.36.24.png

4.「請求先アカウントの作成」をクリック

スクリーンショット 2020-06-02 10.38.49.png

5.利用規約にチェックを入れ、「続行」をクリック

スクリーンショット 2020-06-02 10.40.35.png

6.請求情報を入力し、「無料トライアルを開始」をクリック

スクリーンショット 2020-06-02 10.43.38.png

APIキーを取得

1.「APIの概要に移動」をクリック

スクリーンショット 2020-06-02 10.53.15.png

2.「APIとサービスを有効化」をクリック

スクリーンショット 2020-06-02 10.55.46.png

3.「Maps JavaScript API」をクリック

スクリーンショット 2020-06-02 10.58.27.png

4.「有効にする」をクリック

スクリーンショット 2020-06-02 10.58.37.png

5.「認証情報」をクリック

スクリーンショット 2020-06-02 11.01.05.png

6.「APIとサービスの認証情報」をクリック

スクリーンショット 2020-06-02 11.01.35.png

7.「認証情報を作成」をクリック

スクリーンショット 2020-06-02 11.01.43.png

8.「APIキー」をクリック

スクリーンショット 2020-06-02 11.01.50.png

9.一旦「閉じる」をクリック

スクリーンショット 2020-06-02 11.13.09.png

10. 「APIキーの名前」をクリック

スクリーンショット 2020-06-02 11.06.41.png

11.認証情報の設定をする

①アプリケーションの制限
なしを選択する。

②APIの制限
キーを制限を選択し、プルダウンメニューからMaps JavaScript APIを選択する。

Maps JavaScript APIが選択されている事を確認して、保存をクリック
スクリーンショット 2020-06-02 11.10.13.png

12.赤枠で囲われているマークをクリックし、APIキーをコピー

スクリーンショット 2020-06-02 11.18.56.png

実装

1.APIキーを環境変数化

①「gem 'dotenv-rails'」を導入

Gemfile
gem 'dotenv-rails'
ターミナル
& bundle

②アプリケーション直下に「.env」ファイルを作成
※アプリケーションのディレクトリに移動してから下記コマンドを実行

ターミナル
$ touch .env 

スクリーンショット 2020-06-02 11.32.54.png

.envファイルを編集

.env
GOOGLE_MAP_API = 'コピーしたAPIキー' # 追記

.gitignoreファイルを編集

.gitignore
/.env # 追記

2.ビューを編集

~html.slim
/ マップを表示
#map style='height: 500px; width: 500px;'

/ APIを読み込み
- google_api = "https://maps.googleapis.com/maps/api/js?key=#{ ENV['GOOGLE_MAP_API'] }&callback=initMap".html_safe
script{ async src=google_api }

javascript:

  let map;

  function initMap() {
    geocoder = new google.maps.Geocoder()

    // マップを作成
    map = new google.maps.Map(document.getElementById('map'), {
      // マップの中心に表示する場所の緯度経度を指定
      center: { lat: 40.7828, lng:-73.9653 },
      zoom: 12,
    });

    // マーカーを立てる場所の緯度経度を指定
    marker = new google.maps.Marker({
      position: { lat: 40.7828, lng:-73.9653 },
      map: map
    });
  }

スクリーンショット 2020-06-02 11.58.42.png

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

[Rails]初学者がform_withの流れを整理・理解するための記事

はじめに

Rails学習初期に、form_withの流れが理解できなかったのでまとめます。
Rails5.2.4です。

先にまとめ

「早くまとめろよ!」という方もおられるかと思いますので、先に流れをまとめます。
スクリーンショット 2020-06-02 1.08.39.png

簡単に解説

UserモデルとPostモデルがある状況です。

①newアクションで、ビューページ(new.html.erb)を表示
newアクションを呼び出すリクエストが出されたら、まずはpostの新規投稿(new)ページが表示される
※イメージ
73ee6ee22fb145fb7034907eef85317e.png

②newアクションで生成している@postインスタンスをビューに渡す

post_controller.rb
class PostsController < ApplicationController
~~
  def new
    @post = Post.new #ここで生成されている@postが、new.html.erbに渡される
  end
~~

③インスタンスのプロパティに値を入力してsubmit
f3253df790131a6ac795eeff52d4ee5d.gif

④createアクションを呼び出す
上記の画像のように、投稿するボタン(form_withのsubmit)をクリックすると、form_withがcreateアクションを呼び出してくれます。

※form_withヘルパーメソッドは、渡されたインスタンス(この場合は@post)のプロパティを見て、createに飛ばすかupdateに飛ばすかを判断してくれる便利な子です。

form_withが考えてること
- @post が空 → 初めて作るんだな!じゃあcreate!
- @post が空じゃない → 変更したいんだな!じゃあupdate!

⑤post_paramsメソッドを呼び出す

post_controller.rb
class PostsController < ApplicationController
~~
  def create
    #createアクションが呼び出され、Post.create(post_params)が実行される→ post_paramsが呼び出される
    Post.create(post_params) 
    redirect_to root_path
  end

  private
  def post_params 
    params.require(:post).permit(:title, :content).merge(user_id: current_user.id)
  end
~~

⑥paramsで受け取った値のうち、permitで受け取る値を指定(許可)

paramsで受け取る値の状態
[1] pry(#<PostsController>)> params
=> <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"oLGgLMTcqSPi7cbK9j1wFhH5rqiwXYO7GyRoZcZFZe6Y5VdbHrWCOyeo37kgW/bsl+eANQrz7p/lAzZMnAS8Gg==", 
"post"=>{"title"=>"今日学んだこと", "content"=>"form_withは賢い子!"}, 
"commit"=>"投稿する", "exept"=>:index, "controller"=>"posts", "action"=>"create"} permitted: false>

見にくいので本来ない位置で改行していますが、通常は1行で返ってきます。

"post"=>{"title"=>"今日学んだこと", "content"=>"form_withは賢い子!"},
post_paramsで扱う値です。

permitで受け取る値を許可しておかないと、意図しない値がテーブルに保存される可能性があるので、指定がほぼ必須です。

⑦post_paramsの値でPostをcreateする(成功すればテーブルに保存される)
post_paramsメソッドで返された値で、Post.createすることになるので、
実質下記と同じ状態です。

  def create
    Post.create(title: "今日学んだこと", content: "form_withは賢い子!", user_id: 1) 
    redirect_to root_path
  end

この流れを確認した上で、もう一度冒頭の画像を見ると理解が深まると思います!

おわりに

form_tag、form_forとの違いはこちらの記事にまとめています。
[Rails]hamlでのform_with/form_for/form_tagの書き方

間違い等ありましたらご指摘いただけると幸いです。

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

【Rails】rails newしてPostgreSQLでデータベース作成まで

はじめに

今年はWebにも少しづつ挑戦しようということで、参考文献の一番多そうなRailsをイジイジしています。取り敢えずローカルでの環境構築を終えてRails newして色々と試しているところです。
このあたりで一度頭の中を整理しつつテンプレ化しておきたいのと、間違いの指摘など頂けたら嬉しいなあと思って記事を書いています。
あとはRails newがうまくいかない人の参考になれば嬉しいです。

環境

terminal
Windows10
ruby 2.6.6
Rails 6.0.3.1
psql (PostgreSQL) 12.3

環境構築に関しては書きませんので必要に応じてお調べください。
データベースはPostgreSQLに変更します。
それとgemについては基本的にローカルに入れてます。どちらでもいいみたいなんですが、なんとなくそうしています。

アプリケーションのディレクトリ内にGemfileを作成する

ディレクトリは適当に作ってください。

terminal
C:\Users\user\sample_app> bundle init

Gemfileに追加するgemを記述する

作成したGemfileをエディタで開き、以下を追加する。

Gemfile
gem 'rails'//コメントアウト(#)を外すだけ
gem 'pg'//PostgreSQLに変更する場合

gemのバージョンを指定したほうがいいのか、よく分からなかったので一先ず指定しないでおく。

bundle installする

terminal
bundle install --path vendor/bundle

--path vendor/bundleはgemをローカルにインストールする場合に指定する。こうすることでvendor/bundle内にgemがインストールされるらしい。
--path vendor/bundleは最初の一回目だけ指定すれば、あとは省略できるみたい。

rails newする

terminal
bundle exec rails new . -d postgresql --skip-turbolinks --skip-test

bundle execはRailsをローカルにインストールした場合につける。以降全てのrailsコマンドにつけます。
-d postgresql --skip-turbolinks --skip-testはオプションでこれら以外にも色々あるみたいなので必要に応じてお調べてください。
いまいち分からないものが多いので取り敢えずみんな書いているものだけ書く。

SQL Shellでデータベースを作成する

SQLShell(psql)
create role APPLICATION_NAME with createdb login password 'PASSWORD';
select * from pg_user;

APPLICATION_NAMEにはユーザーネーム(アプリ名)、PASSWORDには任意のパスワードをいれます。
select * from pg_user;でロールが作成されているか確認します。

database.ymlを更新する

configフォルダ内のdatabase.ymlに以下を記述します。

database.yml


default: &default
  adapter: postgresql
  encoding: unicode
  # For details on connection pooling, see Rails configuration guide
  # https://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

  username: APPLICATION_NAME//追加(データベース作成時に決めたユーザーネーム)
  password: PASSWORD//追加(データベース作成時に決めたパスワード)
  host: localhost//追加


database.ymlを保存したらデータベースも更新します。

terminal
bundle exec rails db:migrate:reset

サーバーを起動する

terminal
bundle exec rails s

ブラウザでhttp://localhost:3000/
にアクセスして画像が表示されれば成功。

rails.png

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

複数の正規表現にmatchするかの判定

こんな正規表現のバリデーションメソッドがあった時、

  def customer_number_valid_format?
    return true if a.blank?
    return true if b.blank?
    binding.pry
   regex = /\A(\d{5})-?(\d{5})-?(\d{1})-?(\d{2})\z/, /\A(\d{4})-?(\d{3})-?(\d{3})\z/
  # p regex => [/\A(\d{5})-?(\d{5})-?(\d{1})-?(\d{2})\z/, /\A(\d{4})-?(\d{3})-?(\d{3})\z/]
    return true if regex.blank?
    unless customer_number.match?(regex)  ?
      self.errors.add(:customer_number, :invalid_and_confirm, target: 'お客様番号')
    end
  end
unless customer_number.match?(regex)

ここでcustomer_number12345-12345-1-121234-123-123の時にtrueを返して欲しいが上記コードだと

[27] pry(#<Order>)> customer_number.match?(regex)
TypeError: wrong argument type Array (expected Regexp)

となる。
match?はStringクラスのメソッドだからだね。
https://docs.ruby-lang.org/ja/latest/method/String/i/match=3f.html

解決

配列に入ってるどっちかの正規表現にあってるか判定するにはRegexp.unionというものを使うらしい。

customer_number.match?(Regexp.union(regex))
[25] pry(#<Order>)> Regexp.union(regex)
=> /(?-mix:\A(\d{5})-?(\d{5})-?(\d{1})-?(\d{2})\z)|(?-mix:\A(\d{4})-?(\d{3})-?(\d{3})\z)/
# 配列ではなくパイプで繋がれ他文字列になる

[26] pry(#<Order>)> low_voltage_customer_number.match?(Regexp.union(regex))
=> true

参考: 配列を渡して正規表現オブジェクトをつくる

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

Railsサーバが起動しない2。エラー「autodetect': Could not find a JavaScript runtime. 」

発生した背景

久々にアプリケーションを起動しようとしたところ、アプリが起動しない。
※こちらは「サーバが起動しないこと」の記事を連載しています。

以下のエラーが発生しました:

Traceback (most recent call last):
    61: from bin/rails:3:in `<main>'
    60: from bin/rails:3:in `load'
------- 一部割愛
     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)

対応方法

  • ①ローカルで「bundle install --path vendor」を実施してみる
Fetching libv8 3.16.14.19
Installing libv8 3.16.14.19 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

In file included from ../src/allocation.cc:33:
../src/utils.h:33:10: fatal error: 'climits' file not found
#include <climits>
         ^~~~~~~~~
1 error generated.
(省略)

上記のエラーが発生する。

  • ②エラー文言の指示に従い、以下のコマンドを実行する
CMD> gem install libv8 -v '3.16.14.19' --source 'https://rubygems.org/'

①と同じエラーメッセージが出力され、コマンドに失敗する。

  • ③現在、mac-osは「Catalina v10.15.4」を使用している。調べたところ下位互換性がないとの情報があったため、以下コマンドを実行し、互換性をとる。
CMD> brew install v8-315

brewコマンドを実行し、インストールしたので、ローカルアプリケーションの設定を変更します。

CMD>bundle config --local build.therubyracer --with-v8-dir=$(brew --prefix v8-315)

最後にbudleインストールをローカルで実行しました。
問題が解決し、Railsサーバが起動出来るようになりました。

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

エラー行が多い…(-_-)zzz Railsサーバが起動しない2。「autodetect': Could not find a JavaScript runtime. 」

発生した背景

久々にアプリケーションを起動しようとしたところ、アプリが起動しない。
※こちらは「サーバが起動しないこと」の記事を連載しています。

以下のエラーが発生しました:

Traceback (most recent call last):
    61: from bin/rails:3:in `<main>'
    60: from bin/rails:3:in `load'
------- 一部割愛
     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)

対応方法

  • ①ローカルで「bundle install --path vendor」を実施してみる
Fetching libv8 3.16.14.19
Installing libv8 3.16.14.19 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

In file included from ../src/allocation.cc:33:
../src/utils.h:33:10: fatal error: 'climits' file not found
#include <climits>
         ^~~~~~~~~
1 error generated.
(省略)

上記のエラーが発生する。

  • ②エラー文言の指示に従い、以下のコマンドを実行する
CMD> gem install libv8 -v '3.16.14.19' --source 'https://rubygems.org/'

①と同じエラーメッセージが出力され、コマンドに失敗する。

  • ③現在、mac-osは「Catalina v10.15.4」を使用している。調べたところ下位互換性がないとの情報があったため、以下コマンドを実行し、互換性をとる。
CMD> brew install v8-315

brewコマンドを実行し、インストールしたので、ローカルアプリケーションの設定を変更します。

CMD>bundle config --local build.therubyracer --with-v8-dir=$(brew --prefix v8-315)

最後にbudleインストールをローカルで実行しました。
問題が解決し、Railsサーバが起動出来るようになりました。

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

SinatraでHTTP Live Streaming

動画をダイレクトにサーバから視聴する方法は他にもストリーミング系の仕組みがあるらしいのですが、Sinatraがストリーミングにうまく対応できてないような感じなので、ガチガチの配信ではなく「なんちゃってストリーミング動画配信」をする為のメモです。Appleが考案していてiOSとも親和性が高い技術なので採用しました。

環境

  • Ubuntu 18.04.2 LTS (GNU/Linux 4.15.0-99-generic x86_64)
  • Sinatra 2.0.5 (bundlerで起動する前提です)
  • ffmpeg N-97994-g83fa39e

UbuntuとSinatraは既に環境に配置されているとします。
また、インストール作業用フォルダとffmpegのバイナリを格納するフォルダをhomeに作成します。

  • ffmpeg_sources
  • ffmpeg_build
  • bin

ffmpegのインストール

ffmpegをaptでインストールするとlibfdk-aacが含まれていませんので、自分でカスタマイズインストールしなければなりません。libfdk-aacはHTTP Live Streamingを行うために必要な動画を細切れに分割してプレイリストファイルを作るのに必要です。

ffmpegで利用するコーデック群のライブラリをインストールする為に事前に必要なツール群のインストール

console
sudo apt-get update -qq && sudo apt-get -y install \
  autoconf \
  build-essential \
  cmake \
  git-core \
  libass-dev \
  libfreetype6-dev \
  libgnutls28-dev \
  libsdl2-dev \
  libtool \
  libva-dev \
  libvdpau-dev \
  libvorbis-dev \
  libxcb1-dev \
  libxcb-shm0-dev \
  libxcb-xfixes0-dev \
  pkg-config \
  texinfo \
  wget \
  yasm \
  zlib1g-dev

ffmpegで利用するコーデック群のライブラリのインストール

console
sudo apt-get install nasm
sudo apt-get install libx264-dev
sudo apt-get install libx265-dev libnuma-dev
sudo apt-get install libvpx-dev
sudo apt-get install libfdk-aac-dev
sudo apt-get install libmp3lame-dev
sudo apt-get install libopus-dev

ffmpegのビルド

console
cd ~/ffmpeg_sources && \
  wget -O ffmpeg-snapshot.tar.bz2 https://ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2 && \
  tar xjvf ffmpeg-snapshot.tar.bz2 && \
  cd ffmpeg && \
  env PATH="$HOME/bin:$PATH" PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure \
    --prefix="$HOME/ffmpeg_build" \
    --pkg-config-flags="--static" \
    --extra-cflags="-I$HOME/ffmpeg_build/include" \
    --extra-ldflags="-L$HOME/ffmpeg_build/lib" \
    --extra-libs="-lpthread -lm" \
    --bindir="$HOME/bin" \
    --enable-gpl \
    --enable-libass \
    --enable-libfdk-aac \
    --enable-libfreetype \
    --enable-libmp3lame \
    --enable-libopus \
    --enable-libvorbis \
    --enable-libvpx \
    --enable-libx264 \
    --enable-libx265 \
    --enable-nonfree && \
  env PATH="$HOME/bin:$PATH" make && \
  make install

※gnutlsとlibaomはなんかエラーが出たので(今回は必要ないので)除外

テスト

今回は人気アニメ「ミンクの刃」をストリーミング配信で観てみましょう。

mp4形式に変換

console
sudo ffmpeg -i 'ミンクの刃 01.mkv' -vcodec copy 'ミンクの刃 01.mp4'

mp4形式からストリーミング配信用のHLS形式へ変換

console
sudo /home/ユーザ名/bin/ffmpeg -i 'ミンクの刃 01.mp4' -codec copy -vbsf h264_mp4toannexb -map 0 -f segment -segment_format mpegts -segment_time 5 -segment_list 'ミンクの刃 01'/playlist.m3u8 'ミンクの刃 01'/a%03d.ts

配信用サイトの作成

test.rb
require 'sinatra'
require 'uri'
set :environment, :production

get '/' do
  erb(:test);
end

get '/:file_name' do |file_name|
  sendFilePath = "disk/live/minknoyaiba/#{file_name}";
  if file_name.match(/.+m3u8$/) then
    send_file(sendFilePath, :filename => file_name, :type => "application/vnd.apple.mpegurl");
  else
    send_file(sendFilePath, :filename => file_name, :type => "video/mp2t");
  end
end

/source/onlineフォルダ内にtest.rbがあり、同フォルダ内にdiskフォルダが存在する環境です。minknoyaibaフォルダ内にはHLS用のプレイリスト(.m3u8)や分割動画ファイル(.ts)が入っていると思ってください。
先ほど「ミンクの刃 01」フォルダにHLSのファイル一式を変換したのに何故「minknoyaiba」フォルダに入れるのか?それはSinatraのsend_fileが日本語フォルダをうまく認識してくれないからです。
また外部から接続する為にset :environment, :productionしています。

test.erb
<!DOCTYPE html>
<html>
  <head>
    <title>HTTP Live Streaming Example</title>
  </head>
  <body>
    <video src="/playlist.m3u8" width="400" height="300" controls></video>
  </body>
</html>

配信用サイトの起動

console
bundle exec ruby -I /source/class -C/source/online test.rb

この時点で以下にアクセスするとサイトで動画が見れます。
http://テスト用サーバのIP:4567/
次にiOS側で動画を観てみましょう。

iOSアプリの作成

MinkTubeBase.h
// global require
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>

// local require

@interface MagiTubeBase : AVPlayerViewController {

}

AVKitとAVFoundationをプロジェクトに追加しておいてください。

MinkTubeBase.m
@implementation MagiTubeBase

-(void)viewDidLoad {
    [super viewDidLoad];

    __strong NSURL *url = [NSURL URLWithString:@"http://テスト用サーバのIP:4567/playlist.m3u8"];
    __strong AVPlayerItem *item = [AVPlayerItem playerItemWithURL:url];
    __strong AVPlayer *player = [AVPlayer playerWithPlayerItem:item];
    self.player = player;    
}

@end

おしまい

ミンクの刃面白いですよね!
最後に溶鉱炉の中に沈むミンクが最高にカッコイイ!?

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

AWS Cloud9でRVMのアップデートをする

Cloud9の環境でプリインストールされている「RVM」ですが、Rubyの最新の安定版をインストールする際にRVMをアップデートしないといけなかったのでメモします。

RVMとは

RVMRuby Version Managerの略です。
Rubyのバージョンを管理するのに使います。
例えばインストール、アンインストール、バージョンの切り替えなどを実行できます。

公式サイト
RVM: Ruby Version Manager - RVM Ruby Version Manager - Documentation

RVMのサイトからキーをインストールしておく

セキュリティを確保するため、RVMでは署名をチェックしています。キーをインストールしていない場合はしておきます。

RVM: Ruby Version Manager -
からコマンドをコピーします。トリプルクリックすると便利です。

Cloud9のコンソールで、sudo(root権限で実行)の後にコマンドを貼り付けて実行します。

これでキーをインストールできました。

RVMのバージョンを確認する

rvm -v

今1.29.8ですが、これを1.29.10にしたいんです!

RVMのバージョンを更新する

アップデート(アップグレード)はgetコマンドです。
rvm get <version>

1.29.10に更新したい場合は以下のようになります。
rvm get 1.29.10

ではバージョンを指定してgetコマンドを実行します。
スクリーンショット 2020-06-01 14.50.10.png

確認のため、RVMのバージョンを表示します。
rvm -v
スクリーンショット 2020-06-02 2.38.48.png
これでアップデートできました。

(おまけ)RVMのヘルプを表示する

コマンドを調べたいときに使います。

rvm help
または
rvm
どちらでも表示されます。

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

Ruby+LambdaでAWS SDKを使おうとしたら `sam local` がめちゃくちゃ遅くなったんだけど

起こった現象

LambdaをRuby実装で使っていました。AWS SAM CLIを利用して開発を行っていました。開発自体は何事もなくすすんでいたのですが、機能追加のためにAWS SDKを使うように変更したところ sam local がすごく遅くなったのでした。こんな簡単なサンプルアプリでここまで遅くなるのはおかしいでしょ…と調べてみました。

サンプルプロジェクトはこちら
※deployして試したあとはstack及びcloudwatchのロググループを忘れずに削除してください!

さっさと結論だけ

Lambdaのランタイムには、各環境に合わせてAWS SDKがインストールされているのでLambdaのデプロイパッケージには含めなくてもよいです。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtimes.html

特にRubyであれば Gemfileへの「gem 'aws-sdk', '~> 3'」のような記載は不要 です!
普段利用するGemをGemfileに書く癖がついているとすごく気持ち悪いですが、グローバルなgemにインストールされたgemはそのままrequireできますので問題ないです。

Gemfileに「gem 'aws-sdk', '~> 3'」を書いた場合(修正前)と、削除(修正後)して比較する

Gemfileに書くかどうかでpackageのサイズが大きく変わります(AWS SDKだけで23M近くある)ので、その差が大きくでる結果となりました。

ちなみに修正前だと sam local はかろうじて動きましたのでその比較だけ。デプロイ後はLambdaのメモリ及びタイムアウト時間をかなり伸ばさないとうまく動きませんでした。

修正前

Init Duration: 54544.65 ms とかなり時間がかかります。

$ sam local invoke HelloWorldFunction --event events/event.json --env-vars env.json
Invoking app.lambda_handler (ruby2.5)

Fetching lambci/lambda:ruby2.5 Docker container image......
Mounting /home/ubuntu/environment/test/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
START RequestId: 22ddc38d-745a-1d37-0ce9-324760f75ec4 Version: $LATEST
END RequestId: 22ddc38d-745a-1d37-0ce9-324760f75ec4
REPORT RequestId: 22ddc38d-745a-1d37-0ce9-324760f75ec4  Init Duration: 54544.65 ms      Duration: 801.49 ms     Billed Duration: 900 ms Memory Size: 128 MBMax Memory Used: 60 MB

{"statusCode":200,"body":"{\"message\":\"t2.micro\"}"}

修正後

Init Duration: 2266.99 ms と改善!

$ sam local invoke HelloWorldFunction --event events/event.json --env-vars env.json
Invoking app.lambda_handler (ruby2.5)

Fetching lambci/lambda:ruby2.5 Docker container image......
Mounting /home/ubuntu/environment/test/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
START RequestId: a81dffd9-915d-13db-3ed7-4fe0e3b63464 Version: $LATEST
END RequestId: a81dffd9-915d-13db-3ed7-4fe0e3b63464
REPORT RequestId: a81dffd9-915d-13db-3ed7-4fe0e3b63464  Init Duration: 2266.99 ms       Duration: 557.91 ms     Billed Duration: 600 ms Memory Size: 128 MBMax Memory Used: 58 MB

{"statusCode":200,"body":"{\"message\":\"t2.micro\"}"}

最後に

Lambdaを使うならサイズやパフォーマンスには注意を払わないと余計な費用が掛かってしまいますね。処理時間もパッケージサイズも軽く。Lambdaの分割や、AWS Lambda Layersなども使って対応していきたいと思います。

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