20190324のRubyに関する記事は18件です。

Ruby 配列展開 *Array

@koshi_life です。

*Array の記法が読めなかったので備忘です。

前提

  • ruby 2.6

読めなかったコード

hoge = {name: 'hoge', address: 'hoge@example.jp'}
a = [{name: 'a1', address: 'a1@example.jp'}]
b = [{name: 'b1', address: 'b1@example.jp'}, {name: 'b2', address: 'b2@example.jp'}]
c = [{name: 'c1', address: 'c1@example.jp'}, {name: 'c2', address: 'c2@example.jp'}, {name: 'c3', address: 'c3@example.jp'}]

addresses = [
  hoge[:address],
  *a.pluck(:address),
  *b.pluck(:address),
  *c.pluck(:address)
]

ちなみに pluckは

引数に指定したカラムの配列を返すメソッドです。このメソッドはRailsで使用できるメソッドなので、Rubyのみでは使用することができません。
mapとpluck より

> b.pluck(:address)
=> ["b1@example.jp", "b2@example.jp"]

pluck部分は上記リンク,consoleで試して読めたのでコードを簡略化します。

読めなかったのは *Array

hoge = {name:'hoge', address:'hoge@jp'}
a = ['a1@jp']
b = ['b1@jp', 'b2@jp']
c = ['c1@jp', 'c2@jp', 'c3@jp']

addresses = [
  hoge[:address],
  *a,
  *b,
  *c
]
=> ["hoge@jp", "a1@jp", "b1@jp", "b2@jp", "c1@jp", "c2@jp", "c3@jp"]

*をつけないと

addresses = [
  hoge[:address],
  a,
  b,
  c
]
=> ["hoge@jp", ["a1@jp"], ["b1@jp", "b2@jp"], ["c1@jp", "c2@jp", "c3@jp"]]

結論

*Array は配列の要素を展開してくれる便利な記法。

# ちなみに以下と同意
> [hoge[:address]] + a + b + c
=> ["hoge@jp", "a1@jp", "b1@jp", "b2@jp", "c1@jp", "c2@jp", "c3@jp"]

参考

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

Ruby on Railsを体系的に学ぶためのRAILS GUIDESの使い方

始めに

この記事は、Railsアプリケーションがリクエストを受け取って、レスポンスを返すまでにどんな処理を行うかについて触れています。
個人的に「一度は読んでおいて損はない」と感じたRAILSGUIDEのリンクを集めました。
ここで紹介するリンクは、はじめは読んでも意味がわからないと思いますが、目を通す意味があると思います。

取り急ぎ執筆したため、現時点では世界一投げやりなRails講座です。

今後少しずつ説明を書いていこうと思います。
こうした方がいいんじゃないかなど、意見がありましたら、ご教授願います。

注意書き

初心者の人がここに書いてあることを全て理解するのは不可能だと思います。
目を通して頭の片隅に入れておくだけでいいです。
単語も覚えようとしないでください。「ふーん、こんなのがあるんだ」程度でいいです。

対象

以下のような人が「ギリギリ理解できるかできないか」レベルの箇所を抜粋しています。

  • Railsの勉強始めたけど腑に落ちない人
  • コードの意味がわかってない人
  • コピペコーダー

対象じゃない人

  • Rails newしたことない人
  • MVCという言葉を聞いたことすらない人
  • 読むより実践派の人
  • Railsできる人

0. リクエストとレスポンス

Webのアプリケーションはリクエスト(お願い)を受け取ることではじまります。
アプリ内のボタンをおされたり、URLが入力されたり、google検索に引っかかったりすることで、アプリケーションにリクエスト(お願い)が送られます。

リクエストはパス(URL)とHTTPリクエストメソッド(GETやPOST)で構成されます。

Webアプリケーションは、このリクエスト(お願い) に対して、様々な処理をした後にレスポンス(返信) を返します。

HTTPリクエストについては、こちらの記事を見るといいと思います。
超絶初心者のためのサーバとクライアントの話

1. Routing

Railsアプリケーションはリクエストを受け取ると、Railsのルーターがリクエストを識別します。
ルーターのカスタマイズは、config/routes.rbで行います。

以下のリンクの2.4までは目を通すといいと思います。
Railsのルーティング | RAILS GUIDE

2. Controller

MVCモデルのCです。
ルーターに指名されたコントローラは、何かしらの処理を行い、最終的にViewを用いてHTMLを表示します。

この「何かしらの処理」を理解するために、リンクの以下の部分を目を通すといいと思います。

  • 1 ~ 4.1 コントローラの役割〜ハッシュと配列パラメータ
  • 4.3 ルーティングパラメータ
  • 4.5 Strong Parameter
  • 8 フィルタ

Railsのコントローラ | RAILS GUIDE

3. View

MVCモデルのVです。

前章で、コントローラは以下のように説明しました。

ルーターに指名されたコントローラは、何かしらの処理を行い、最終的にViewを用いてHTMLを表示します。

この章では、「Viewとは何か」と「コントローラはどうやってViewを用いるのか」について触れます。

3-1 Viewとは何か

リンクの以下の部分を目を通すといいと思います。

  • 1 ~ 3.1.1 Action Viewについて 〜 ERB
  • 3.2 ~ 4 パーシャル 〜 パーシャルレイアウト
  • 6.1 Action Viewのヘルパーメソッドの概要(余裕があれば)

Action View の概要 | RAILS GUIDE

もし余裕があればこちらも読んでみましょう。(優先順位は3-2の方が高いです。)
ユーザに入力させる、フォームについてです。

  • 1 ~ 2.2 基本的なフォームを作成する 〜 フォームとオブジェクトを結び付ける
  • 7 パラメータの命名ルールを理解する

Action View フォームヘルパー | RAILS GUIDE

3-2 コントローラはどうやってViewを用いるのか

リンクの以下の部分を目を通すといいと思います。

  • 1 ~ 2.2 概要: 部品を組み上げる 〜 レンダリングを使用する
  • 2.3 redirect_toを使用する
  • 3.4 パーシャルを使用する

レイアウトとレンダリング | RAILS GUIDE

4. モデル

MVCモデルのMです。

前章で、コントローラは以下のように説明しました。

ルーターに指名されたコントローラは、何かしらの処理を行い、最終的にViewを用いてHTMLを表示します。

この「何らかの処理」にデータベースが絡む場合、モデルが必要になります。
モデルは、基本的にはデータベース内のテーブルの数だけ存在します。モデルが複数存在するということは、テーブルのデータ同士が関連している可能性があります。この関連自体をアソシエーションといいます。

この章では、以下について触れます。

  • 4-1 Modelとは何か
  • 4-2 データベースの編集方法
  • 4-3 どうやってModelを通じてデータを取り出すか
  • 4-4 アソシエーションとは何か

4-1 Modelとは何か

リンクの以下の部分を目を通すといいと思います。

  • 1 ~ 3 Active Recordについて 〜 Active Recordのモデルを作成する
  • 5 ~ 8 CRUD: データの読み書き 〜 マイグレーション

Active Record の基礎 | RAILS GUIDE

4-2 データベースの編集方法

リンクの以下の部分を目を通すといいと思います。

  • 1 ~ 2 マイグレーションの概要 〜 マイグレーションを作成する
  • 3 (余裕があったら)
  • 4 ~ 8 CRUD: マイグレーションを実行する 〜 マイグレーションとシードデータ

Active Record マイグレーション | RAILS GUIDE

4-3 データベースの編集方法

リンクの以下の部分を目を通すといいと思います。

  • 1 ~ 3 データベースからオブジェクトを取り出す 〜 並び順

Active Record クエリインターフェイス | RAILS GUIDE

4-3 データベースの編集方法

リンクの以下の部分を目を通すといいと思います。

  • 1 ~ 2.3 関連付けを使用する理由 〜 has_many関連付け(余裕があったら、2章全体を読んでみましょう)

Active Record の関連付け (アソシエーション) | RAILS GUIDE

フィードバックについて

「こうした方がいいんじゃないか」
「この部分の説明はリンク先だけでは1mmも理解できない」
などの意見がありましたら、コメントにてご教授頂けると嬉しいです。

説明、オススメリンクを随時足していく予定です。

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

13日目(2):Ruby認定試験

恐ろしく長いのですが、自分用ですので

クラスの継承

# エラーにならないものを選べ
class Hoge < Object ; end
class Hoge << Object ; end
class Hoge < Kernel ; end
class Hoge << Kernel ; end

# A  1のみ

クラスの継承は<
なお、Kernelはモジュールなので不可

module Kernel

irb
Kernel.call
=>Module

全てのクラスから参照できるメソッドを定義しているモジュール。
Objectクラスはこのモジュールをインクルードしています。
Objectクラスのメソッドは実際にはこのモジュールで定義されています。

to_i(n)

to_iメソッドは、文字列をn進数の表現と見なして整数に変換。
文字列の先頭から10進数と見なせる部分を切り取って変換し、見なせる部分がなければ0を返す。

p "12abc".to_i =>123
p "abc12".to_i =>0
p "ab12c".to_i =>0
p "1abc2".to_i =>`1

予約語のエスケープ?

正規表現ではあるのだろう

s = "a;b:c;d:e;f"
p s.split (/:|;/)
=>["a", "b", "c", "d", "e", "f"]

配列と範囲オブジェクト

いつも、ごちゃごちゃになる。

a=[1,2,3,4]

a[1,3]    => [2,3,4]
a[1..3]   => [2,3,4]
a[1...3]  => [2,3]
a[1....3] => error
a[2..-1]  => [3,4]

## `範囲オブジェクトは文字列にも使える`

a="abcdefghijk"
a[1,3] = "x"
p a => "axefghijk"
a[1..3] = "x"
p a => "axefghijk"
a[1...3] = "x"
p a => "axdefghijl"
```

&と&&、|と||

a = [1,2,3,4]
b = [1,3,5,7]

p a & b =>[1, 3]
p a && b =>[1, 3, 5, 7]
p a | b =>[1, 2, 3, 4, 5, 7]
p a || b =>[1, 2, 3, 4]
1 作用
& 日本語の”且つ”と同じ。
&& 左辺が真なら、右辺を評価して、右辺を返す
パイプ1本 日本語の”又は”と同じ
パイプ2本 左辺が真なら、右辺を評価せずに、左辺を返す

※パイプ=『|』。エスケープできなかった

可変長引数

def sum (##AAA##)
  total = 0
  a.each {|i| total += i }
  return total
end
puts sum(1, 2, 3, 4, 5)
=>15

delete_if = reject! 破壊的メソッド

ブロックの戻り値が真になった要素を削除。
なお、rejectは非破壊的

a= [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a.delete_if{|v| v % 2 == 0 }
=>[1, 3, 5, 7, 9]
p a => [1, 3, 5, 7, 9]

enum.each_with_index {|item, idx| block }

繰り返しごとにブロック引数itemには各要素が入り、idxには0,1,2と番号が入る

a = ["apple", "orange", "grape", "pine"]
a.each_with_index{ |item, i| print i, ":", item, "\n" }
=> 
0:apple
1:orange
2:grape
3:pine

array.each_index {|index| block }

『ほぼ』each_with_indexと同じ
繰り返しごとにブロック引数には各要素のインデックス(位置)の整数が入ります。戻り値はレシーバ自身

hash.each_pair {|key, val| block }

eachと同じ

hash.each_key {|key| block }

繰り返しごとにブロック引数keyにはキーが入る。

hash.each_value {|val| block }

繰り返しごとにブロック引数valにはキーの値が入る。

each_何たらが他にも結構あるので、見ておこう

shift とunshift 破壊的メソッド

shiftメソッドは、配列の最初の要素を削除し、その要素を返します。
レシーバ自身を変更するメソッドです。配列が空のときはnilを返します。

s = ["one","two","three"]

s.shift         => "one" ※先頭のoneを削除
s.shift         => "two" ※先頭のtwoを削除
s.unshift       => ["three"] ※引数が無いので、何もしてない
s.push "four"   => ["three", "four"]
p s             =>["three", "four"]

compactとuniq

a = [:a,:a,:b,:c]
a[5] = :e
a.concat([:a,:b,:c])
a.compact
a.uniq
p a
=> [:a, :a, :b, :c, nil, :e, :a, :b, :c]

concat, compact, uniqのうち、破壊的メソッドはconcatのみなので、
結果、元の配列aに影響を及ぼすのはconcatのみ

concat 破壊的 = push = <<

配列の末尾に引数を結合。感嘆符が無い破壊的メソッド

conpact 非破壊的

配列からnilを取り除いた、新しい配列を作成
感嘆符がついたconpact!が破壊的。

uniq 非破壊的

配列から重複した要素を取り除いて、新しい配列を作成
感嘆符が付くと破壊的に。

arr = [1, 2, 5, 5, 1, 3, 1, 2, 4, 3]
p arr.uniq
[1, 2, 5, 3, 4]

map = collect

a = [1, 2, 3, 4, 5, 6]

a.collect {|v| v * 2}
=> [2, 4, 6, 8, 10, 12]
a.inject {|v| v * 2}
=> 32
a.each {|v| v * 2}
=> [1, 2, 3, 4, 5, 6]
a.map {|v| v * 2}
=>[2, 4, 6, 8, 10, 12]
a.select {|v| v * 2}
=> [1, 2, 3, 4, 5, 6]
a.execute {|v| v * 2}
=> error  undefined method

どうしたらinjectが32になるか、リファレンス見ても理解できない

%w記法

配列を作る

sarray = %w(Apple Orange Grape)
sarray.each {|v| print v, " "}
#
Apple Orange Grape

zip

実行結果になるように、##AAA##に記述するコードを選んで下さい

a = ["a", "b", "c"]
b = [1, 2, 3]
##AAA##

#実行結果
["a", 1]
["b", 2]
["c", 3]

#選択肢
a.zip(b).each{|x| p x }
a.zip(b){|x| p x }
[a, b].zip{|x, y| p [x, y] }
[a, b].transpose.each{|x, y| p [x, y] }

#答え 1,2,4

zip

行と列を入れ替える。足りない要素はnilを返す

transpose

zipと同様、行と列を入れ替える。要素が足りない場合、例外IndexErrorを発生。

EOB

以下のコードは正しく動きません。修正案を。

s = <<EOB
Hello, Ruby World.
Hi, Ruby World.
Goodbye, Ruby World.
  EOB

#解答
5行目のEOBの前の空白を削除する。

encoding

puts "hello".encoding.name
UTF-8
=> nil

chop 非破壊的

文字列の末尾から1文字を取り除いた新しい文字列を返す
!が付くと、破壊的

chomp 非破壊的

文字列の末尾の改行文字を取り除いた新しい文字列を返す
!が付くと、破壊的

キャレット^

名をキャレット、否定をする

puts "0123456789-".delete("^13-56-")
=> 13456-

13456-に該当『しない』ものを削除している

正規表現

p "abc def 123 ghi 456".scan(/\d+/).length
=> 2

\dは10進数なので、10進数から始まる部分を配列で返し(scan)、その要素の数lengthを返してる

scan(string)

指定した正規表現のパターンとマッチする部分を文字列からすべて取り出し、配列にして返す。

p "HogeHOGEhoge"[/[A-Z][^A-Z]+/]
=> "Hoge"

大文字と小文字のアルファベットで構成される文字列を返している

invert(hash)

hashのキーと値を入れ替える

h = {1 => "Hoge", 2=> "Piyo", 3=>"fuga"}
p h.invert
#
{“Hoge”=>1, “Piyo”=>2,“fuga”=>3 }

update と sortメソッド

a = {"Foo" => "Hoge", "Bar" => "Piyo", "Baz" => "Fuga"}
b = {"Foo" => "hoge", "Bar" => "piyo", "Baz" => "fuga"}
p a.update(b).sort{ |a, b| a[1] <=> b[1] }

[["Baz", "fuga"], ["Foo", "hoge"], ["Bar", "piyo"]]

update

わかりづらい

sort

配列の要素をソートした新しい配列を返します。
「要素1 <=> 要素2」の結果が-1なら要素1が先、0なら同じ、1なら要素2が先となります。

ルート

以下のコードは、ファイル test . txt を読み、文字を逆順に書き込む処理です。
「##AAA##」に入る適切な記述を選びなさい。

open("test.txt","##AAA##") do |f|
  data = f.read.chomp
  data.reverse!
  f.rewind
  f.write data
end
#選択肢
a  w  r  a+  r+  w+

組み込み関数openの第2引数には、ファイルのオープン モードを指定します。
r、w、a に + を付けると、読み書き 両用でオープンします。
w+ を指定すると元ファイルの内 容を空にします。
a+ を指定すると、追記モードとなり、 元のファイルを書き換えることができません。

chr と ord は対義語的な

puts 65.chr   => "A"
puts "a".ord  => 97

コードポイントの話。
n.chrは,nで登録されている文字を返す
string.ordはstringのコードポイントを返す
メソッドの意味は覚えてるけど、もう『65を見たらA』と覚えるしか。

securerandom Module

安全な乱数発生器のためのインターフェースを提供するモジュール

require 'securerandom'

SecureRandom.urlsafe_base64

urlsafe_base64

ランダムで URL-safe な base64 文字列を生成して返します。

どこをみても、『url-safeな』『url-safeな』と書いてあるけど、
『url-safeな』とは。

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

Rubyのzipメソッドをイメージで理解する【3分で読めます】

ついつい存在自体を忘れてしまうzipメソッド。
イメージで覚えてすぐに思い出せるようにしよう。

メソッドの詳細

使い方は下記リンクを参考にしてください。
Array#zip(公式リファレンス)

考え方

とりあえずジッパーをイメージする
ジッパーの画像

上の画像を逆さまにしたものをイメージしてください。

解説

まずは実行結果のサンブル。

sample.rb
a1 = [1,2,3]
a2 = [4,5]
a3 = [6,7,8,9]

a1.zip(a2,a3)

# > [[1, 4, 6], [2, 5, 7], [3, nil, 8]]

oh,ムズカシイネ

1.配列をジッパーの歯(zip teeth)と見立てる

配列を
縦に並べて
zip teeth


普通のArray

a1 = [1,2,3]

縦版Array

a1


1
,
2
,
3


この調子で全部縦にすると.....

a1 = [1,2,3]
a2 = [4,5]
a3 = [6,7,8,9]

↓↓↓↓↓↓↓↓↓↓

a1 a2 a3
    
  
1  4  6
,  ,  ,
2  5  7
,     ,
3     8
   ,
      9
      

案外見やすいですね。

2.上から下にジッパーを閉める

[
[1,4,6]
[2,5,7]
[3,nil,8]
]

zip teathが噛み合いました。
もう外せそうにありません。

むすびのことば

命名大事だと思えました。

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

数値オブジェクトについて

数値オブジェクトについて

  • [目次]
    • [基本の使い方]
    • [数値クラス]
    • [数値メソッド]
      • [四則演算]
      • [分数]
      • [浮動小数点数]
    • [クラスの変更]
    • [クラス・メソッドの調べ方]
    • [まとめ]

基本の使い方

普通に数字を記載してもらえれば大丈夫です。

example

入力

puts 3

puts 1.2

出力

3
1.2

数値クラス

Numeric_class.png


数値オブジェクトの大元となるクラスがNumericです。

そこから整数の値を扱うInteger、浮動小数点数を扱うFloatに分かれます。

IntegerはさらにFixnumBignumに分かれます。

Bignumの方が大きな値を扱う事ができます。ただし、FixnumからBignumへのクラスの変換は自動で行われるので、クラスとして存在していることを知っておくだけで十分です。

*Ruby 2.4 以降は Integer に一本化されました。

数値メソッド

数値オブジェクトに関わる代表的なメソッドについて説明します。

+や-などの四則演算もメソッドの一つです。

四則演算

shisoku.png


足し算と引き算は数学と同様に+-を使用します。

掛け算は*、割り算は/を使用します。

割り算の余りを表示したい場合は%を使います。

べき乗を行うには**を使用します。

example

入力

puts 4+3 #足し算

puts 4-3 #引き算

puts 4*3 #掛け算

puts 4/3 #割り算

puts 4%3 #4/3の余り

puts 4**3 #4の3乗

出力

7
1
12
1
1
64

割り算で少数点以下も表示したい場合

例えば、計算式の中で4の表記を4.0といった表記にして、小数点以下を表示しておけばOKです。

example

入力

puts 4/3 #割り算

puts 4.0/3

出力

1
1.3333333333333333

分数

四則演算の中で分数にして表記することも可能です。

その場合Rationalを使用します。

使い方は

Rational(分子,分母)

です。

example

入力

puts Rational(4,5)

puts Rational(4,5)+Rational(4,5)

出力

4/5
8/5

Rationalにはもっと簡単な書き方もあります。

example

入力

puts 4/5r

puts 4/5r+4/5r

出力

4/5
8/5

浮動小数点数

syosu.png

少数を扱うメソッドとして
- 四捨五入
- 小数点以下切り捨て
- 小数点以下切り上げ
があります。

四捨五入を行いたい時はroundを使用します。

example

入力

puts 1.5.round

puts 1.4.round

出力

2
1

小数点以下の切り捨てを行いたい時はfloorを使用します。

example

入力

puts 1.5.floor

puts 1.4.floor

出力

1
1

小数点以下の切り上げを行いたい時はceilを使用します。

example

入力

puts 1.5.ceil

puts 1.4.ceil

出力

2
2

クラスの変更

change.png

クラスの変更を行ってくれるメソッドについてです。

例えば4.04に変更したいとします。

これは少数(Float)整数(Integer)に変更するということです。

ですのでこの場合Integerに変更するという意味で、to_iというメソッドになります。

Numeric_class.png

example

入力

puts 1.5.to_i

puts 1.4.to_i

出力

1
1

この場合、小数点以下は切り捨てになります。

逆に、整数(Integer)少数(Float)に変更する場合。

Floatに変更するので、to_fを使用します。

example

入力

puts 1.to_f

puts 20.to_f

出力

1.0
20.0

クラス・メソッドの調べ方

クラスを調べる場合はclassを使用します。

example

入力

puts 2.class

puts 2.0.class

出力

Integer
Float

メソッドを調べる場合はmethodsを使用します。

example

入力

puts 2.methods

出力

-@
**
<=>
upto
<<
<=
>=
==
chr
===
>>
[]
%
&
inspect
+
ord
-
/
*
size
succ
<
>
to_int

以下略

まとめ

matome.png

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

gemのあらゆるコマンドがAugumentErrorで実行できなかった件

この時のPC環境 ※要確認

OS:Windows10
Ruby:2.5.3p105 (2018-10-18 revision 65156) [x64-mingw32]
RubyGems:2.3.0

Ruby on Railsのバージョンを確かめようとコマンド"rails -v"を実行したところ、以下のようなエラー表示が出ました。

C:\Ruby25-x64\rubygems-3.0.3>rails -v
Traceback (most recent call last):
        2: from C:/Ruby25-x64/bin/rails:23:in `<main>'
        1: from C:/Ruby25-x64/lib/ruby/site_ruby/2.5.0/rubygems.rb:302:in `activate_bin_path'
C:/Ruby25-x64/lib/ruby/site_ruby/2.5.0/rubygems.rb:283:in `find_spec_for_exe': can't find gem railties (>= 0.a) with executable rails (Gem::GemNotFoundException)

エラー表示"ArgumentError"

Ruby on Railsをインストールしてみようと、コマンド"gem install rails"を実行したところ、以下のようなエラーが返ってきました。

ERROR:  While executing gem ... (ArgumentError)
    wrong number of arguments (given 1, expected 0)

ArgumentError・・・?引数がおかしい?

とりあえず、上のエラー表示についてググってみると、以下の記事を見つけました。
https://teratail.com/questions/156929
自分と同じようなエラーかなーと思いましたが、結局解決できていない模様。

さらにググってみると、以下の記事を発見。
https://github.com/rubygems/rubygems/issues/2224

この記事から、RubyとRubyGemsのバージョンによっては上手くいかないことがあると考え、RubyGemsをアップデートしてみることに。

コマンドプロンプトからRubyGemsをアップデートできない

gem update --systemを実行すると、以下のような表示が出ました。

C:\>gem update --system
Updating rubygems-update
ERROR:  While executing gem ... (ArgumentError)
    wrong number of arguments (given 1, expected 0)

またArgumentError・・・

どうにか別の方法でRubyGemsをアップデートできないものかとググりまくっていると、次の記事を発見。
http://d.hatena.ne.jp/c_mutoh/20100329/1269877259

この記事にはZipファイルを使ってRubyGemsをアップデートする方法が書かれていました。

Zipファイルを使ってRubyGemsをアップデートする方法

※先の記事に書かれている手順通りに実行しましたが、念のため書いておきます。

この時、私がインストールしたRubyGemsのバージョンは3.0.3です。(この記事を書いている時点において最新)

まず、RubyGemsのサイトのダウンロードページからZIPを選択して、Zipファイルをダウンロードします。

C:\Ruby25-x64のディレクトリ(Rubyがインストールされているディレクトリ)にダウンロードしたZipファイルを解凍します。

解凍できたら、cdコマンドで解凍したディレクトリに移動します。(私の場合"cd C:\Ruby25-x64\rubygems-3.0.3"コマンドを実行)

ここで、setup.rbを以下のように実行します。

C:\Ruby25-x64\rubygems-3.0.3>ruby setup.rb

これでRubyGemsのバージョン3.0.3がインストールできたはず・・・

バージョンを確認すると、

C:\Ruby25-x64\rubygems-3.0.3>gem -v
3.0.3

バージョンが2.3.0から3.0.3に更新されました!

Ruby on Railsをインストール

RubyGemsのバージョンアップが完了したところで、Ruby on Railsをインストールしてみます。
以下のコマンドを実行します。

C:\Ruby25-x64\rubygems-3.0.3>gem install rails
Fetching rails-5.2.2.1.gem
Successfully installed railties-5.2.2.1
Successfully installed rails-5.2.2.1
Parsing documentation for railties-5.2.2.1
Installing ri documentation for railties-5.2.2.1
Parsing documentation for rails-5.2.2.1
Installing ri documentation for rails-5.2.2.1
Done installing documentation for railties, rails after 0 seconds
2 gems installed

今度はエラーが返ってきませんでした。

Ruby on Railsが無事インストールされたか確認してみます。

C:\Ruby25-x64\rubygems-3.0.3>rails -v
Rails 5.2.2.1

インストールできたみたいです!

まとめ

Ruby on Railsをインストールしたいが、できない

RubyGemsのバージョンを変更する必要がある(?)

コマンドプロンプトでRubyGemsのバージョンを更新できない

Zipファイルを使ってRubyGemsのバージョンを更新

Ruby on Railsインストール成功

という話の流れでした。

個人的な感想

Qiita初投稿の記事になります。

私はRuby初心者ですが、プログラミングする以前に環境構築の時点でつまづくのは結構辛いですよね・・・
私はこの問題で半日以上費やしてしまい、同じような思いを他の方が味わうのは何とも酷だなと思い、思い切って投稿しました。

参考になれば幸いです。

あぁ・・・おとなしくMac買って使った方が良いんだろうな・・・

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

gemのあらゆるコマンドがAugument Errorで実行できなかった件

この時のPC環境

OS:Windows10
Ruby:2.5.3p105 (2018-10-18 revision 65156) [x64-mingw32]
RubyGems:2.3.0

Ruby on Railsのバージョンを確かめようとコマンド"rails -v"を実行したところ、以下のような表示が出ました。

C:\Ruby25-x64\rubygems-3.0.3>rails -v
Traceback (most recent call last):
        2: from C:/Ruby25-x64/bin/rails:23:in `<main>'
        1: from C:/Ruby25-x64/lib/ruby/site_ruby/2.5.0/rubygems.rb:302:in `activate_bin_path'
C:/Ruby25-x64/lib/ruby/site_ruby/2.5.0/rubygems.rb:283:in `find_spec_for_exe': can't find gem railties (>= 0.a) with executable rails (Gem::GemNotFoundException)

エラー表示"ArgumentError"

Ruby on Railsをインストールしてみようと、コマンド"gem install rails"を実行したところ、以下のようなエラーが返ってきました。

ERROR:  While executing gem ... (ArgumentError)
    wrong number of arguments (given 1, expected 0)

ArgumentError・・・?引数がおかしい?

とりあえず、上のエラー表示についてググってみると、以下の記事を見つけました。
https://teratail.com/questions/156929
自分と同じようなエラーかなーと思いましたが、結局解決できていない模様。

さらにググってみると、以下の記事を発見。
https://github.com/rubygems/rubygems/issues/2224

この記事から、RubyとRubyGemsのバージョンによっては上手くいかないことがあると考え、RubyGemsをアップデートしてみることに。

コマンドプロンプトからRubyGemsをアップデートできない

gem update --systemを実行すると、以下のような表示が出ました。

C:\>gem update --system
Updating rubygems-update
ERROR:  While executing gem ... (ArgumentError)
    wrong number of arguments (given 1, expected 0)

またArgumentError・・・

どうにか別の方法でRubyGemsをアップデートできないものかとググりまくっていると、次の記事を発見。
http://d.hatena.ne.jp/c_mutoh/20100329/1269877259

この記事にはZipファイルを使ってRubyGemsをアップデートする方法が書かれていました。

Zipファイルを使ってRubyGemsをアップデートする方法

※先の記事に書かれている手順通りに実行しましたが、念のため書いておきます。

この時、私がインストールしたRubyGemsのバージョンは3.0.3です。(この記事を書いている時点において最新)

まず、RubyGemsのサイトのダウンロードページからZIPを選択して、Zipファイルをダウンロードします。

C:\Ruby25-x64のディレクトリ(Rubyがインストールされているディレクトリ)にダウンロードしたZipファイルを解凍します。

解凍できたら、cdコマンドで解凍したディレクトリに移動します。(私の場合"cd C:\Ruby25-x64\rubygems-3.0.3"コマンドを実行)

ここで、setup.rbを以下のように実行します。

C:\Ruby25-x64\rubygems-3.0.3>ruby setup.rb

これでRubyGemsのバージョン3.0.3がインストールできたはず・・・

バージョンを確認すると、

C:\Ruby25-x64\rubygems-3.0.3>gem -v
3.0.3

バージョンが2.3.0から3.0.3に更新されました!

Ruby on Railsをインストール

RubyGemsのバージョンアップが完了したところで、Ruby on Railsをインストールしてみます。
以下のコマンドを実行します。

C:\Ruby25-x64\rubygems-3.0.3>gem install rails
Fetching rails-5.2.2.1.gem
Successfully installed railties-5.2.2.1
Successfully installed rails-5.2.2.1
Parsing documentation for railties-5.2.2.1
Installing ri documentation for railties-5.2.2.1
Parsing documentation for rails-5.2.2.1
Installing ri documentation for rails-5.2.2.1
Done installing documentation for railties, rails after 0 seconds
2 gems installed

今度はエラーが返ってきませんでした。

Ruby on Railsが無事インストールされたか確認してみます。

C:\Ruby25-x64\rubygems-3.0.3>rails -v
Rails 5.2.2.1

インストールできたみたいです!

まとめ

Ruby on Railsをインストールしたいが、できない

RubyGemsのバージョンを変更する必要がある(?)

コマンドプロンプトでRubyGemsのバージョンを更新できない

Zipファイルを使ってRubyGemsのバージョンを更新

Ruby on Railsインストール成功

という話の流れでした。

個人的な感想

Qiita初投稿の記事になります。

私はRuby初心者ですが、プログラミングする以前に環境構築の時点でつまづくのは結構辛いですよね・・・
私はこの問題で半日以上費やしてしまい、同じような思いを他の方が味わうのは何とも酷だなと思い、思い切って投稿しました。

参考になれば幸いです。

あぁ・・・おとなしくMac買って使った方が良いんだろうな・・・

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

複数バージョンのRubyを切り替えられるようにする

rbenvというツールを使う。brewはインストール済み前提。
https://github.com/rbenv/rbenv

rbenvの導入

Macのターミナルで以下のコマンドを実行。

brew install rbenv
rbenv init
rbenv install -l # rubyのバージョン一覧を確認する。
rbenv install 2.6.2 # 2019/3/24時点の最新版をインストール
rbenv install 2.4.5 # 動作確認用に適当なバージョンをインストール
rbenv global 2.6.2 # 2.6.2を使うように設定

~/.bashrcに以下を記述。

export PATH=~/.rbenv/shims:$PATH

動作確認

Macのターミナルで以下のコマンドを実行。実際に2.6.2が選択されていることがわかる。

$ rbenv versions
  system
  2.4.5
* 2.6.2 (set by /Users/XXX/.rbenv/version)
$ ruby -v
ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-darwin18]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

fluentdのbuffer周りで注意すべき点

topic

  • buffering parameters
  • flushing parameters
  • retries parameters

公式ドキュメントを参照していますが、私の解釈が誤っている場合もあるため間違っていた場合はご指摘ください。

主にbufferに関するパラメータは上記の三種類となります。
retryに関しては下記のページでも紹介しているので特にここでは記載しません。
参考:
https://qiita.com/smith_30/items/1a8df503613f7e7e2904
https://qiita.com/tatsu-yam/items/bd7006e483f3b3c64309

また、buffer/chunk/flush/queue等の動作に関してもある程度上記で説明してくださっているので参考にしてみると良いと思います。(ちょっと古いけど)
bufferファイルの作成単位に関しては、tag/timekey等のmeta情報の知識も必要ですが、ここでは書きません。(気が向いたら追記します。)

buffering parameters

ここでは、主にbufferのファイルサイズや分割に関する設定を説明します。
bufferのサイズは、主に転送(書き込み)等のflunetdから外部へ送信される際のデータの出力単位だと思ってもらって良いと思います。

ここの設定により、送信先のデータ受信の最大サイズにあったチューニングや、ディスク書き込みの際のブロックサイズに合わせて設定を行うことができます。
まれに処理レコード数の制限が存在する出力先が存在した場合などは送信単位を50レコードまでと設定する必要があったり、、

(ただ厳密にここの設定が守られているわけではなく、一定周期でwatchしており設定値を超えたら次のファイルを作成するなどの動作のため設定値を超える場合もある。)

chunk_limit_size

ここでは、作成されるbufferファイルの最大サイズを設定可能。
この設定値か、chunk_limit_sizeの設定でファイルが分割される。

入力データの大きさによりここで設定したファイルサイズを超える入力があった場合エラーが頻出しますので、その際はここのパラメータを上げるか、入力元を絞る必要があります。
厳密にはflushの設定も関係するがここでは割愛します。

chunk_limit_records

一つのbufferファイル内の最大レコード数を設定可能。
ここで設定したレコード数を超えると新しくbufferファイルが作成される。

total_limit_size

bufferプラグイン内で溜め込める合計bufferサイズ。
outputプラグインで何らかの原因で出力が出来ない状態で、flunetd側のbufferで溜め込めるbufferのサイズとなる。
このサイズは、flunetd全体に適用されるのではなく、bufferプラグインごとに設定されるためディスクサイズやメモリサイズに考慮して設定する必要がある。

例えば、ディスクサイズが128GBなのにbuffer_fileプラグインを2つデフォルトで利用していると最大で128GB貯まるため、ディスクフルとなる可能性がある。

chunk_full_threadhold

bufferファイルをflushする際のファイルサイズに関連する項目。
上記では、chunk_limit_sizeを超えるとbufferファイルが新しく作成されると、書いたが厳密にはchunk_limit_size × chunk_full_threadhold のサイズを超えるとflushされ、新しいbufferファイルが作成される。

flushing parameters

ここではfluentdのflushの際の動作に関するパラメータを設定する。
簡単にflushの動作を説明すると
bufferの状態としては、staged/enqueuedの状態が存在し、stagedの状態からenqueuedの状態へ移行する処理がflushである。(ここちょっと自信ない)

flush_at_shutdonw

fluentdを終了する際に保持しているbufferファイルをすべてflushする設定。
buffer_memoryを利用している場合、この設定を行わないとメモリ内のbufferが損失するため、設定を行うことをおすすめします。

また、buffer_fileを使用している場合はbufferの損失は起きませんが扱うデータ量が多い(または大量のbufferファイルが作成される環境)場合は次回起動時にbufferの読み込み処理に時間がかかるため、これを避けたい場合は設定を有効にすることをおすすめします。

flush_interval

ここはflushの判定ロジックを設定可能

  • lazy

timekey毎にflush/writeを行う。特にリアルタイム処理が不要であったりする場合はデフォルトでこの設定なので変えなくても良いと思う。

  • interval

後述するflush_intervalの設定値ごとに一定間隔でflushを行う。

  • immediate

bufferが作成されたらすぐにflushを行う。
データ入力が極端に大きい場合は、この設定で良いと思うが小さなデータが細々と入力される環境だとプロセスのCPU使用率が上がるためこの設定は避けること。
処理データのサイズを小さくしたい場合はこの設定が有用。

flush_interval

前述のflush_modeで*intervalを指定した際に設定可能

flush_thread_count

enqueueされたデータをoutputプラグインにより処理するthread数を設定する。
基本的にこの設定を上げると、outputプラグインの処理速度が上がるがCPU使用率が上がるため気をつけること。
公式ドキュメントではボトルネックがディスクにある場合は、この設定で書き込みを平行に行うことである程度ディスク書き込み遅延による性能劣化を避けることが可能だと記載されている。

flush_thread_interval

enqueuedされたデータが存在しない場合の、データ入力監視の感覚(ざっくり)
この数値を小さくすると、enqueueされたデータが新規にできた際のoutputまでの処理のタイムラグが小さくなるが、入力データが存在しない場合のCPU使用率が上がるため基本的には0.1以上に設定することをおすすめする。
0.01に設定したときは入力データが存在しないのに常時CPU30%ほど使用していた。
0.1に設定すると5%以下に収まった。。

flush_thread_burst_interval

flush_thread_intervalと異なり、すでにenqueueされているデータが存在する場合のoutputでの処理感覚(ざっくり)
この数値を小さくするとoutputプラグインでの処理間隔が短くなり処理速度が上がるが、CPU使用率が上がる。

まとめ

flunetdのbuffer周りの設定は下手すると処理速度に直結するため大容量のデータを扱う際は慎重に設定することをおすすめします。
あとは、出力先への負荷の削減等。

参考:https://docs.fluentd.org/v1.0/articles/buffer-section#buffering-parameters

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

13日目(1):Deviseによるログイン機能付きサイトの作成

12日目:12日目:PostgreSQLを用いたログイン機能付きサイトの続き

環境

  • ホストOS: Windows10 Home
  • 仮想環境OS: Ubuntu Bento/Bionic
  • Ruby:2.51
  • Rails: 5.2.2 -主使用gem : devise(参照)
  • DB: PostgreSQL

前回やったこと

  1. nodejsとpostgresqlのインストール
  2. rails new self_univ3 -d postgresql とbundle install (devise)
  3. PostgreSQLのパス設定、DB作成
  4. rails g devise:install
  5. modelsにdeviseを追加。rails g devise Student

今回

  1. controllersとviewsを以前の大学データの方から流用
  2. migrationファイル作成
  3. rooting変更

実作業

controllersフォルダとviewsフォルダのコピー

# cp -r コピーしたいフォルダの場所 ペースト先

migrationファイル作成

rails g migration AddNameToStudents name:string gender:integer age:integer opinion:text
# 実行
 create    db/migrate/20190324043018_add_name_to_students.rb

DBに反映

rails db:migrate

rooting変更

app/confing/routes.rb
# 追加
resources :students
root to: 'students#index'

viewsの変更

app/views/student.html.erb
# 今回不要なExamResultNewのリンク削除
# ログアウトリンクの作成
<% @students.each do |student| %>
      <tr>
        <td><%= student.try(:name) %></td>
        <td><%= student.email %></td>
        <td><%= student.try(:gender) %></td>
        <td><%= student.try(:age) %></td>
        <td><%= student.try(:opinion) %></td>
        <td><%= link_to 'Show', student %></td>
        <td><%= link_to 'Edit', edit_student_path(student) %></td>
        <td><%= link_to 'Destroy', student, method: :delete, data: { confirm: 'Are you sure?' } %></td>
        <%= link_to 'Log Out', destroy_student_session_path, method: :delete %>
      </tr>
    <% end %>

コントローラ変更

app/controllers/student_controller.rb
class StudentsController < ApplicationController
  before_action :authenticate_student!

devise-logout.JPG

次からは、このページに、以前の大学データを組み合わせる

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

rails s 時のエラー (Gem::GemNotFoundException)

Railsアプリ作成中に、rails sでサーバー起動しようとしたら、Gem::GemNotFoundException エラー。
とくになにかしたわけではないが、突如発生。
stack overflowで似たようなエラー事例があったので、参考にし、解決。
参考:stack overflow
rails s 時のエラー。

Traceback (most recent call last):
   4: from /home/ec2-user/.rvm/gems/ruby-2.6.0/bin/ruby_executable_hooks:24:in `<main>'
   3: from /home/ec2-user/.rvm/gems/ruby-2.6.0/bin/ruby_executable_hooks:24:in `eval'
   2: from /home/ec2-user/.rvm/gems/ruby-2.6.0/bin/rails:23:in `<main>'
   1: from /home/ec2-user/.rvm/rubies/ruby-2.6.0/lib/ruby/2.6.0/rubygems.rb:302:in `activate_bin_path'
/home/ec2-user/.rvm/rubies/ruby-2.6.0/lib/ruby/2.6.0/rubygems.rb:283:in `find_spec_for_exe': can't find gem railties (>= 0.a) with executable rails (Gem::GemNotFoundException)

以下で解決。

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

VScodeでRails開発をしていたらerbファイルでEmmet(エメット)が使えなかったので使えるようにした

VScodeRails開発してるんだけど、erbファイルEmmet使えない!という人や
そもそもHTMLファイルでもEmemt 使えないよ! という人は以下を試してみてください。

VScodeの設定

まずVSCodeの左下の歯車マークをクリック
次に、settingsをクリック

検索窓で、

"Trigger Expansion On Tab" と検索
Emmet:Trigger Expansion on tab の左のチェックマークを入れる

これでHtmlファイルではEmmetが使えるようになったはず。

セッティングファイルの記述

さらに続けて、検索窓で

edit in settings.json”と検索
少し小さい文字の edit in settings.jsonをクリック

ファイルが開くので以下を追記

settings.json
{
    "workbench.iconTheme": "vscode-icons",
    "window.zoomLevel": 0,
    "emmet.triggerExpansionOnTab": true,

    # ここから記述
    "emmet.includeLanguages": {
        "erb": "html"
    }
}

これで試しに erbファイル
h1 と打ってからTabキーを叩いてください。

それでうまくいかなければ,

拡張機能のインストール

VScode画面上の左上側にあるアイコンの一番下の四角い拡張機能をクリック
Rails と検索してRailsをインストール

コレです⬇︎
https://marketplace.visualstudio.com/items?itemName=bung87.rails


上記全て終えたら、晴れてEmmetが使用できるはずです。

※EmmetはVScodeじゃなくても使えます。

たくさんコードを書いてみんなで爆速になりましょう

ごあいさつ

実はこの記事がQiitaでの初投稿になります。
1人でも多くの人のお役に立てたらと願っていますが、至らない所あるかもしれません。
その際は遠慮なく教えていただけると幸いです。

参考

https://qiita.com/gorohash/items/bb5c42e0054c83fd4a2d

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

第1回クソコードレビュー大会解説!

はじめに

この記事は僕参加しているグループ内でプログラミングの知識の底上げのために行った大会の解説のための記事です。
良ければみなさんもやって見てください!
https://github.com/takehanKosuke/bad_cord_test_of_rails

そしてやった後はぜひ感想やなどのフォードバックを何かしらで教えていただけると嬉しいです!

指摘して欲しかったジャンル

1. とりあえず直さないといけない
2. きれいなコーディング(レベル1)※フォーマット周り
3. きれいなコーディング(レベル2)※gemのメソッドとか
====ここより下は独学でプログラミングしてたら多分かなり難しいと思っている====
4. きれいなコーディング(レベル3)※処理回数を減らす的なやつ
5. セキュリティー対策※XSS

問題項目一覧(全20問)

  • 余計な文字列が出現している
  • 正常に式展開がされていない
  • 記事の削除ができない
  • userのarticlesのアソシエーションにdependent: :destroyをつけていない
  • バリデーションを掛けていない
  • save/update/destroyでエラーハンドリングをしていない
  • userのenumであるroleカラムにdefault値をつけていない
  • 他の人になりすましてデータを作成更新削除ができてしまう
  • seeds.rbのcreateに「!」をつけてない
  • インデントが揃っていない
  • シングルクォーテーションとダブルクォーテーションが混在
  • new/editでの共通部分を部分テンプレート化していない
  • article_pathの引数が「article.id」になっている
  • enumの検索メソッドを使用していない
  • authenticate_user!を使用していない
  • n+1問題が発生している
  • ビジネスロジックをcontrollerに書いている
  • デメテルの法則を使用していない
  • Articleにindexを貼っていない
  • XSS(クロスサイトスクリプティング)攻撃からの脆弱性がある

1、とりあえず直さないといけない

  • 余計な文字列が出現している
  • 正常に式展開がされていない
  • 記事の削除ができない
  • userのarticlesのアソシエーションにdependent: :destroyをつけていない
  • バリデーションを掛けていない
  • save/update/destroyでエラーハンドリングをしていない
  • statusにデフォルト値をつけていない
  • 他の人になりすましてデータを作成更新削除ができてしまう
  • seeds.rbのcreateに「!」をつけてない

余計な文字列が出現している

views/articles/index.html.erb
<!--修正前-->
</tr>
<%= @articles.each do |article| %>
  <tr>

<!--修正後-->
</tr>
<% @articles.each do |article| %>
  <tr>

スクリーンショット 2019-03-31 13.43.52.png
これはもう解説不要な気がしますが、ここに”=”はいらないです。
気をつけましょうw

正常に式展開がされていない

view/articles/index.html.erb
<!--修正前-->
<td><%= '#{article.pv}pv' %></td>

<!--修正後-->
<td><%= "#{article.pv}pv" %></td>

スクリーンショット 2019-04-02 1.28.27.png

rubyでは式展開を行うときにには シングルクォーテーション ではなく ダブルクォーテーション を使う必要があります。

じゃあそんなこと言うならシングルクォーテーション使わなければいいんじゃね?って話になりそうですが、、、

下の記事にもあるようにシングルクォーテーションの方がわずかながら処理スピードが速くなるそうなので、処理速度をめちゃめちゃ意識した場合はその辺のことも気をつけていければいいかなと思います。
https://qiita.com/Kenji_TAJIMA/items/78555053a36c214be350

記事の削除ができない

view/articles/index.html.erb
<!--修正前-->
<td><%= link_to '削除', article_path(article.id), data: { confirm: "#{article.title}を削除しますか?" } %></td>

<!--修正後-->
<td><%= link_to '削除', article_path(article.id), method: :delete, data: { confirm: "#{article.title}を削除しますか?" } %></td>

railsのpath は何も指定しない場合getメソッドを呼んでしまうためdeleteメソッドを使う場合はしっかりとmethod: :deleteというようにメソッド名を指定しないといけません
今回の場合method名を指定しない場合はarticleのshowアクションに移動します

userのarticlesのアソシエーションにdependent: :destroyをつけていない

models/user.rb
#修正前
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :articles

  validates :name, presence: true

  enum role: { normal: 1, admin: 2 }
end

#修正後
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :articles, dependent: :destroy  # 追記

  validates :name, presence: true

  enum role: { normal: 1, admin: 2 }
end

もしこのdependent: :destroyをつけなかった場合アドミンユーザーでユーザーの削除を行うと
スクリーンショット 2019-03-31 13.19.40.png
こんな感じのエラーが返されると思います。
これはどういうことかというと、、、
記事の一覧を表示させるときにユーザーの名前を出しているのですが、その記事に紐づくユーザーが削除されてしまっているため参照するユーザー名がないよ〜
というようなエラーです。

これを解決するためには、ユーザーを削除したときにそれと一緒にユーザーの作った記事も一緒に削除するというのが一般的ですので、
今回はuserのarticlesのアソシエーションにdependent: :destroyをつけてuserが削除された時にそのユーザーに紐付く記事も一緒に削除しています

バリデーションを掛けていない

models/article.rb
#修正前
class Article < ApplicationRecord
  belongs_to :user
end

#修正後
class Article < ApplicationRecord
  belongs_to :user

  validates :title, presence: true
  validates :body, presence: true
end

記事を作成する際タイトルや内容が空だと不自然ですよね?
バリデーションを掛けないとタイトルや内容が空のまま記事が作成できてしまいます。
今回はデータがあることを強要するvalidatesを使ってバリデーションをかけています。

バリデーションについての詳細は↓の記事がかなりよかったので気になるひとは是非読んでみてください
https://qiita.com/shunhikita/items/772b81a1cc066e67930e

save/updateでエラーハンドリングしていない

controllers/articles_controller.rb
#修正前
  def create
    @article = Article.new(article_params)
    @article.save
    redirect_to root_path
  end

  def update
    @article = Article.find(params[:id])
    @article.update(article_params)
    redirect_to root_path
  end

#修正後
def create
   @article = Article.new(article_params)
   if @article.save
     redirect_to @article, flash: { success: 'articleが作成されました' }
   else
     render :new
   end
end

def update
   if @article.update(article_params)
     redirect_to @article, flash: { success: 'articleが更新されました' }
   else
     render :edit
   end
end

そもそもエラーハンドリングって?

エラーハンドリングとは、プログラムの処理中に処理が妨げられる事象が発生した際、その処理をエラーとして対処する処理のことである。 例外処理とも呼ばれる。
(出典:weblio)

今回の場合でエラーハンドリングをちゃんとしないと、、、
スクリーンショット 2019-03-17 20.57.06.png
スクリーンショット 2019-03-17 20.57.34.png
このように新規作成ボタンを押してもボタンが無効になるだけでデータが作成されないです

なので、save,updateなどの処理を挟む時は必ずif文を使ってエラーハンドリングをして
データが作成されない時のフォローをしてあげましょう。

userのenumであるroleカラムにdefault値をつけていない

db/migrate/20190220135142_devise_create_users.rb
#修正前
 # カスタムカラム
t.string :name, null: false
t.integer :role, null: false

#修正後
 # カスタムカラム
t.string :name, null: false
t.integer :role, null: false, default: 1

もしroleカラムにデフォルト値をつけなかった場合ユーザーを新規に作成するときに以下のようなエラーが返されると思います。
スクリーンショット 2019-03-31 13.16.41.png

これはどういうことかというと、、、「roleカラムが指定されてないんだけど〜」っていうエラーですね
今回ユーザーのroleカラムはユーザーの新規登録時に操作させません
(というか、新規登録時にユーザー自身アドミンユーザーかノーマルユーザーかを決められたらアドミンユーザーの意味がw)
ですが、ユーザーのroleカラムは全てのユーザーが持っていなければならないのです。
そのようなときは最初からroleにデフォルト値を設定してフォローするのがベターだと思われます。

またenumは基本的にデフォルト値をつけるのが慣習となっているので(多分w)
そう言った意味でもデフォルト値をつけるべきでしょう

他の人になりすましてデータを作成更新削除ができてしまう

controller/articles_controller.rb
#修正前
#一部省略

  def new
    @article = Articles.new
  end

  def create
    @article = Article.new(article_params)
    @article.save
    redirect_to root_path
  end

  def edit
    @article = Article.find(params[:id])
  end

  def update
    @article = Article.find(params[:id])
    @article.update(article_params)
    redirect_to root_path
  end

  def destroy
    @article = Article.find(params[:id])
    @article.destroy
    redirect_to root_path
  end

#一部省略

def article_params
  params.require(:article).permit(
    :title,
    :body,
    :user_id
  )
end


#修正後
before_action :article_author, only: %i[edit update destroy]

#一部省略

def new
  @article = current_user.articles.new
end

def create
  @article = current_user.articles.new(article_params)
  if @article.save
    redirect_to @article, flash: { success: 'articleが作成されました' }
  else
    render :new
  end
end

#一部省略
private
def article_author
  @article = current_user.articles.find(params[:id])
end

def article_params
  params.require(:article).permit(
    :title,
    :body,
  )
end

どういうことか実際にやってみましょう!
スクリーンショット 2019-03-31 13.32.52.png
新規作成の画面を開き、開発者ツールを開き上記の写真のuserIDを指定しているところを見つけましょう。
そこのIDを今ログインしているユーザー以外のIDを指定して新規作成をおします。
すると以下のようにログインしているユーザー以外で記事を作成できてしまいます
スクリーンショット 2019-03-31 13.33.12.png
この現象を避けるため、今回はuserとarticleとの紐付けをhidden_fieldで現在のユーザーのidを送るのではなく、

@article = current_user.articles.new

というような記述の仕方でユーザーと記事の紐付けを行いました。

seeds.rbのcreateに「!」をつけてない

seeds.rb
#修正前
puts "ユーザー作成中"
User.create(
  [
    {
      name: 'kosuke',
      email: 'test1@test.com',
      password: '111111',
      role: 2,

#修正後
puts "ユーザー作成中"
User.create!( #「!」をつけた
  [
    {
      name: 'kosuke',
      email: 'test1@test.com',
      password: '111111',
      role: 2,

seedファイルのcreateには「!」をつけないとエラーを吐いてくれません
つまり、seedが失敗してもそれに気づくことが難しくなってしまうのでseedファイルには必ず「!」をつけましょう

本当にエラー吐かないの?!って思う人はrake db:seedを「!」をつけたバージョンとつけなかったバージョンの2回叩いてみるとわかると思います

2、きれいなコーディング(レベル1)

  • インデントが揃っていない
  • シングルクォーテーションとダブルクォーテーションが混在
  • new/editでの共通部分を部分テンプレート化していない
  • article_pathの引数が「article.id」になっている

インデントが揃っていない

views/articles/index.html.erb
<!--修正前-->
<table>
  <tr>
    <th>タイトル</th>
      <th>内容</th>
      <th>pv数</th>
    <th>ライター</th>
    <th></th>
    <th></th>
  </tr>
  <% @articles.each do |article| %>
    <tr>
        <td><%= link_to '#{article.title}', article_path(article.id) %></td>
        <td><%= article.body.html_safe %></td>
        <td><%= "#{article.pv}pv" %></td>
        <td><%= "#{article.user.name}" %></td>
        <td><%= link_to "編集", edit_article_path(article.id) %></td>
      <% if article.user == current_user %>
        <td><%= link_to '削除', article_path(article.id), method: :delete, data: { confirm: "#{article.title}を削除しますか?" } %></td>
      <% end %>
      </tr>
    <% end %>
</table>

<!--修正後-->
<table>
  <tr>
    <th>タイトル</th>
    <th>内容</th>
    <th>pv数</th>
    <th>ライター</th>
    <th></th>
    <th></th>
  </tr>
  <% @articles.each do |article| %>
    <tr>
      <td><%= link_to '#{article.title}', article_path(article.id) %></td>
      <td><%= article.body.html_safe %></td>
      <td><%= "#{article.pv}pv" %></td>
      <td><%= "#{article.user.name}" %></td>
      <td><%= link_to "編集", edit_article_path(article.id) %></td>
      <% if article.user == current_user %>
        <td><%= link_to '削除', article_path(article.id), method: :delete, data: { confirm: "#{article.title}を削除しますか?" } %></td>
      <% end %>
    </tr>
  <% end %>
</table>

初めてインデントとかを気にし始めた時はかなり大変さを感じるかもしれないですが、それに慣れてしまうと、むしろインデントが揃ってないと違和感しか感じないですw

ちなみに各エディタにはインデントとかを揃えてくれるプラグインが存在するはずなので是非調べて見てください。
僕自身は「atom」を使っているのですが、「atom」では「atom-beautify」と言うプラグインがあり、それを使うと
control + option + B
で自動で整形してくれます。

シングルクォーテーションとダブルクォーテーションが混在

views/articles/index.html.erb
<!--21行目-->
<!--修正前-->
<td><%= link_to "編集", edit_article_path(article.id) %></td>
<% if article.user == current_user %>
  <td><%= link_to '削除', article_path(article.id), method: :delete, data: { confirm: "#{article.title}を削除しますか?" } %></td>

<!--修正後-->
<td><%= link_to '編集', edit_article_path(article.id) %></td>
<% if article.user == current_user %>
  <td><%= link_to '削除', article_path(article.id), method: :delete, data: { confirm: "#{article.title}を削除しますか?" } %></td>

プログラミングをする際はできるだけ書き方を統一しましょう。
今回はシングルクォーテーションに揃えていますが、プログラム全体でダブルクォーテーションに揃えているのであれば、ダブルクォーテーションで揃えるのが適切だと思います。

new/editの共通部分が部分テンプレート化していない

views/articles/new.html.erb
<!--修正前-->
<h1>新規作成</h1>
<% if @article.errors.any? %>
  <ul>
    <% @article.errors.full_messages.each do |message| %>
      <li><%= message %></li>
    <% end %>
  </ul>
<% end %>

<%= form_with model: @article, local: true do |f| %>
  <%= f.hidden_field :user_id, value: current_user.id %>
  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>
  <div>
    <%= f.label :body %>
    <%= f.text_area :body %>
  </div>
  <%= f.submit '作成' %>
<% end %>

<!--修正後-->
<%= render 'form', article: @article %>
views/articles/edit.html.erb
<!--修正前-->
<h1>編集</h1>
<% if @article.errors.any? %>
  <ul>
    <% @article.errors.full_messages.each do |message| %>
      <li><%= message %></li>
    <% end %>
  </ul>
<% end %>

<%= form_with model: @article, local: true do |f| %>
  <%= f.hidden_field :user_id, value: current_user.id %>
  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>
  <div>
    <%= f.label :body %>
    <%= f.text_area :body %>
  </div>
  <%= f.submit "更新" %>
<% end %>

<!--修正後-->
<%= render 'form', article: @article %>

※新規作成

views/articles/_form.html.erb
<h1><%= current_page?(new_article_path)? "新規作成" : "編集" %></h1>
<%= render 'layouts/errors', object: @article %>

<%= form_with model: article, local: true do |f| %>
  <%= f.hidden_field :user_id, value: current_user.id %>
  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>
  <div>
    <%= f.label :body %>
    <%= f.text_area :body %>
  </div>
  <%= f.submit "#{ current_page?(new_article_path)? "新規作成" : "更新" }" %>
<% end %>


※新規作成

views/layouts/_errors.html.erb
<% if object.errors.any? %>
  <ul>
    <% object.errors.full_messages.each do |message| %>
      <li><%= message %></li>
    <% end %>
  </ul>
<% end %>

新規登録と編集画面のフォームは全く一緒なので部分テンプレートとして切り出し、
エラーメッセージは他のところでも使う可能性があるので、さらに別のファイルに切り出して実装を行いました

今回は、「新規作成/編集」のところもcurrent_page?メソッド(現在のページのパスを返すrailsのメソッド)を使って変化させていますが、多少やりすぎ感は否めないかなとも思ってますw

article_pathの引数が「article.id」になっている

views/articles/index.html.erb
<!--修正前-->
<td><%= link_to article.title, article_path(article.id) %></td>


<!--修正後-->
<td><%= link_to article.title, article_path(article)%></td>

idを引数として渡す場合はオブジェクトそのものを渡してあげることもできる。
かなり細かいところではあるが、意外と重宝することがある

3、きれいなコーディング(レベル2)

  • enumの検索メソッドを使用していない
  • authenticate_user!を使用していない

enumの検索メソッドを使用していない

views/users/show.html.erb
#修正前
<% if current_user.role == "admin" %>

#修正後
<% if current_user.admin? %>

enumを使うことでcurrent_user.admin?というように記述することができよりシンプルな記述をすることができます。

参考文献
https://qiita.com/shizuma/items/d133b18f8093df1e9b70

authenticate_user!を使用していない

application_controller.rb
#修正前
def user_sign_in?
  if user_loged_in?
    redirect_to :user_session_path
  end
end

#修正後
before_action :authenticate_user!

gemのdeviseを使うことで様々なメソッドを使うことができるようになります

メソッド 用途
before_action :authenticate_user! コントローラーに設定して、ログイン済ユーザーのみにアクセスを許可する
user_signed_in? ユーザーがサインイン済かどうかを判定する
current_user サインインしているユーザーを取得する
user_session ユーザーのセッション情報にアクセスする

詳細は↓の記事から、、、
https://qiita.com/tobita0000/items/866de191635e6d74e392#user_session

4、きれいなコーディング(レベル3)

  • n+1問題が発生している
  • ビジネスロジックをcontrollerに書いている
  • デメテルの法則を使用していない
  • Articleにindexを貼っていない

n+1問題が発生している

controllers/articles_controller.rb
#修正前
  def index
    @articles = Article.all
  end

#修正後
  def index
    @articles = Article.all.includes(:user)
  end

そもそもn+1問題って?

めちゃめちゃ簡単に言うと無駄なSQL(データベースからデータを取ってくる命令)が発行されてしまっている現象です。
どれくらい違うかと言うと、、、

修正前
スクリーンショット 2019-03-30 13.10.03.png

修正後
スクリーンショット 2019-03-30 13.11.14.png

たった10個の初期データを表示するだけにも関わらず
ここまで発行量に差があります。

じゃあ、なんでこなんでこんなに違いがあるのかと言うと、、、
調べましょう!!!説明がめんどくさかったとかそう言うのじゃないんだからね!

まあでもとりあえず参考文献だけは置いておきます
https://qiita.com/TsubasaTakagi/items/8c3f4317ad917924b860

デメテルの法則を使用していない

models/article.rb
#追加
delegate :email,
         :name,
         to: :user,
         prefix: true
views/articles/_article_list.html.erb
<!--修正前-->
<tr>
  <td><%= link_to article.title, article_path(article) %></td>
  <td><%= article.body.html_safe %></td>
  <td><%= "#{article.pv}pv" %></td>
  <td><%= article.user.name %></td>

<!--修正後-->
<tr>
  <td><%= link_to article.title, article_path(article) %></td>
  <td><%= article.body.html_safe %></td>
  <td><%= "#{article.pv}pv" %></td>
  <td><%= article.user_name %></td>

デメテルの法則とは、

あるオブジェクトAは別のオブジェクトBのサービスを要求してもよい(メソッドを呼び出してもよい)が、オブジェクトAがオブジェクトBを「経由して」さらに別のオブジェクトCのサービスを要求してはならない。(wikipediaより引用)

まあよくわかんないと思うのでめちゃめちゃ簡単にいうと、、、
「.」を2つ以上繋げんなよ!って感じですな(ただこの解釈で固まりすぎると行きすぎると死ぬことあるのでその辺は注意、、、)

じゃあ、今回みたいに article.user.name みたいに書かなきゃいけない時はどうすればいいの?ってことが起きると思います。

models/article.rb
  def user_name
    user.name
  end

その時は上のようにarticleのモデルにこんな感じのメソッドを書いてあげれば
article.user_name みたいな感じで「.」を一つで呼ぶことができます。(※正確にいうとArticleモデルのメソッドとしてuser名を引っ張ってこれる)
なので今回の実装ではrailsのActiveSupportのコンポーネントのdelegateを使って実装を行いました。

↓参考文献
https://railsguides.jp/active_support_core_extensions.html#%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E5%A7%94%E8%AD%B2

何を言ってるのかよくわからん!っていう人は、
「.」を2つ以上繋げるのはあんまり良くないんだな〜みたいな認識でオッケーかと思います。

Articleにindexを貼っていない

db/migrate/20190220133546_init.rb
#修正前
create_table :articles do |t|
  t.string :title, null: false
  t.text :body, null: false
  t.integer :pv, null: false, default: 0

  t.integer :user_id, null: false
  t.timestamps
end

#修正後
create_table :articles do |t|
  t.string :title, null: false
  t.text :body, null: false
  t.integer :pv, null: false, default: 0

  t.integer :user_id, null: false
  t.timestamps
end
add_index :articles, [:user_id] #追記

indexを張るってどういうこと?
プログラミングでindexというといろんな意味がありますが、、、
今回のindexの意味は検索を早くする仕組みのことです

なんでindexを張ると検索が早くなるのかは下の記事を見ていただくとして、、、
indexは基本的には外部キーに貼っておくといいとされているらしいので、よくわからん!!!って人もとりあえず外部キーには貼っておきましょう

参考文献
〜簡単に分かりたい人向け〜
https://wa3.i-3-i.info/word11906.html
〜割とちゃんと分かりたい人向け〜
https://www.atmarkit.co.jp/ait/articles/1703/01/news199.html

ビジネスロジックをモデルにかいていない

controller/articles_controller.rb
#修正前
def show
  @article = Article.find(params[:id])
  @article.pv += 1
  @article.save
end

#修正後
def show
  @article = Article.find(params[:id])
  Article.increment_pv(@article)
end
models/article.rb
def self.increment_pv(article)  
  article.pv += 1   
  article.save  
end

そもそもビジネスロジックってなんやねん!

ビジネスロジック(英: business logic)は、データベースとユーザーインタフェース間の情報のやりとりを制御する手順といったようなものを指す(技術的でない)用語である。(wikipediaより引用)

ってことだそうです、、、いや、どういうことやねんwww

まあちょっとよくわからないので
逆にcontrollerの役割について確認したいと思います。
スクリーンショット 2019-03-31 12.24.19.png
上の図はMVCフレームワークの大まかな流れなのですが
ズバリコントローラーの役割とは、、、

送られてきたリクエストを適切なアクションに振り分けること!

です。
そして、データベースとのやりとりはmodelが行うことになっています。

そこで改めて該当のコードを見てみると、、、

def show
  @article = Article.find(params[:id])
  @article.pv += 1
  @article.save
end

というようになっています。

ここで行なっている処理は
1. 該当のarticleを持ってくる
2. 持ってきたarticleのPV数を1を足す

まず、1の「該当のarticleを持ってくる」というところですが、
先ほどの話からするとこの作業は本来modelが担当するべきところですが、@article = Article.find(params[:id])
となっているので、まるでコントローラがDBにアクセスているように見えますが、このrailsのfindというメソッドは実はmodels/user.rbで継承されているActiveRecordのメソッドを呼び出しているのです!
なのでちゃんとMVC通りmodelでデータベースとやりとりを行なっていたんですね!(この辺は話すと長くなるのでこれくらいで割愛w)

そして、2の「持ってきたarticleのPV数を1を足す」という処理ですが、この作業も本来はmodelが担当するべきところですが修正前のコードではcontrollerでpv数を1足しています(ちなみにsaveもActiveRecordのメソッドです)
コントローラは処理を振り分けることなので、pv数に1を足すという処理をここで行なってしまうとMVCの考え方に反してしまいます。
なので今回はこれらの処理はメソッドとして切り出してmodelファイルに記述して、それを呼びだすという実装にしています。

ってまあ、ぶっちゃけよくわからん!ってなる人も多いと思います汗
なのでそんな人は、とりあえず、、、

よくわかんないけど、
普段使ってる「find」とか「save」とかはmodelファイルで定義しているメソッドをcontrollerで呼び出して使ってるだけなんだなぁ〜

ってことだけ頭の片隅にでも置いておいてもらえるといいかなと思います

ビジネスロジックをさっくり知りたい
https://wa3.i-3-i.info/word13666.html

もっと知りたい
〜MVCモデル〜
https://qiita.com/tshinsay/items/5b1724baf32b8b5113c2

〜ActiveRecord〜
https://railsguides.jp/active_record_basics.html

5、セキュリティー系

XSS(クロスサイトスクリプティング)攻撃からの脆弱性がある

views/articles/_article_list.html.erb
<!--修正前-->
<% articles.each do |article| %>
  <tr>
    <td><%= link_to article.title, article_path(article) %></td>
    <td><%= article.body.html_safe %></td><!--⬅︎修正するところ-->
    <td><%= "#{article.pv}pv" %></td>
    <td><%= article.user_name %></td>

<!--修正後-->
<% articles.each do |article| %>
  <tr>
    <td><%= link_to article.title, article_path(article) %></td>
    <td><%= simple_format(article.body) %></td> <!--⬅︎修正した-->
    <td><%= "#{article.pv}pv" %></td>
    <td><%= article.user_name %></td>

クロスサイトスクリプティングってなんぞ?
こればっかりは実際にやって見たほうが早いのでやって見ましょう!

<script>location.href="https://qiita.com/"</script>

これをbodyに書いて記事を作成してみましょう
すると、、、
おそらく記事の一覧画面に言った時にqiitaのトップページに勝手にリダイレクトされると思います。
今回はこれがqiitaのトップページであるため、問題はありませんが例えばこれの飛ばされる先が悪質なサイトである可能性は十分にあるわけです。。。
ではこれらはどのようにすれば防ぐことができるのでしょうか?

キーワードとしては サニタイズ です

サニタイズとは簡単にいうと特別な文字を他の文字に置き換えちゃおうぜって感じのことです。
railsでは基本的にrubyタグ(<%= %>こんな感じのやつ)を使うことでサニタイズをしてくれるのですが .html_safe を使うとサニタイズしてくれなくなってしまいます。
(safeって書いてあるのに全然安全じゃないってのがツッコミどころ満載な感じですが、あまりにツッコミどころ多いので逆に覚えやすいw)

でも、だからと言って<td><%= article.body %></td> と書いてしまうと、改行がうまくされませんそのため今回はsimple_formatというrailsのヘルパーを用いて実装を行っています。

参考文献
〜サニタイズについて〜
https://wa3.i-3-i.info/word16265.html
〜simple_formatについて〜
http://railsdoc.com/references/simple_format

お疲れ様です!!!

みなさん、問題の回答から解説の読了までお疲れ様でした!!!
今回の問題楽しんでいただけましたでしょうか?また何か新しい発見や学びはありましたでしょうか?

もし少しでも発見や学びがあればとても嬉しいなと思います!

というか今回の問題個人的にはなかなか難しいと思っていて、自分で作っていながらこれ俺が出されても1発で満点取れます!みたいなことは口が裂けても言えないなぁ〜って思っていますので、全然点数が取れなかった人もあまり気にせずにまたゆっくり一緒に勉強していければなと思います!

最後に、、、意見や質問、感想などあったら教えていただけると幸いです!

   

参考文献まとめ

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

クラスメソッドとインスタンスメソッド

クラスメソッドとインスタンスメソッド

前回の続きで今回はこちらのテーマで書いていきたいと思います。
クラスとインスタンスについてわからない場合は
こちらを参考にしてみてください。

クラスメソッド

クラスメソッドは定義したクラス自身が使えるものであり、クラスで共通の情報を持った処理に使います。

では、共通の情報とは何か?
前回と引き続き学校のクラスを例にとって考えてみましょう。
クラスには生徒がいて、生徒の総数がありますね、これはクラスがクラス内共通の値です。他にも、机の数や、受ける授業などもクラス内共通だと思います。

クラスメソッドの定義

それでは上記に書いた生徒の総数をコード内に定義していきます。

school.rb
class Student

   def self.students_count
   puts "生徒の総数は30人です。"
   end

end

student = Student.new

これで定義できました。クラスメソッドを定義するときはメソッド名の前にselfをつけます。
この後、メソッド内に処理を記述することにより実行する事ができます。

インスタンスメソッド

インスタンスメソッドはインスタンスが使用するものであり、個別の情報を使った処理に使います。
学校を例にとるのであれば、生徒一人一人に対する情報に対した処理という事です。
例えば生徒の身長という個々の情報を例にとると、
その身長を「測る」や「書く」、「見る」のような処理が浮かぶと思います。

インスタンスメソッドの定義

それでは身長という情報を「見る」というメソッドを定義していきたいと思います。

school.rb
class Student

   def self.students_count
   puts "生徒の総数は30人です。"
   end

   def show_student_height
     puts "私の身長は170cmです。"
   end

end

student = Student.new

これでインスタンスメソッドの定義ができました。
インスタンスメソッドの場合はクラスメソッドのようにメソッド名の前にselfなどをつける必要はありません。

ちなみに上に書いた「puts」は実行した際に表示をするというメソッドです。
上のshow_student_heightを実行すれば、puts以降の「私の身長は170cmです。」が出力されます。

最後に

今回はクラスメソッドとインスタンスメソッドについて説明していきました。
クラスメソッドはクラス内共通の処理
インスタンスメソッドは個別の情報に対する処理
ということを覚えておきましょう。
次回はクラス変数、インスタンス変数について説明していきたいと思います。

最後まで読んでいただきありがとうございました。

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

初学者がRailsで作ったポートフォリオをAWSへデプロイするまでの記録

エンジニアへ転職するためにこちらの記事を参考にして
ポートフォリオをAWSへデプロイすることが出来たので、それまでにやったことを出会ったエラーを紹介しつつ、振り返っていきたいと思います。

ポートフォリオをAWSにデプロイしてみたい、
AWSへデプロイするまで何を準備すれば良いのかわからない
という方のお役に立てれば幸いです。

ちなみにこの記事はEC2へログインするところから始まります。

EC2へログインする

EC2インスタンスを作成した時に、キーペアをダウンロードしてるはずなので
それを使ってログインしていきます。

あ、ちなみに今回デプロイしたアプリ名はlovekitchenなるものなので
各々アプリ名の部分は変更してくださいね。

$ mv Downloads/lovekitchen.pem .ssh/
$ cd .ssh/
$ chmod 400 lovekitchen.pem
$ ssh -i lovekitchen.pem ec2-user@パブリックIP

yesと入力すれば変なアスキーアートが表示されてログイン完了です。

管理ユーザー作成

sudo adduser tatsuya
sudo passwd tatsuya
(# パスワードの登録)
sudo vi visudo
(# 設定を編集)

ここからはVIMを使っての編集になります。
まずiを押してINSERTモードにして下さい。

## Allow root to run any commands anywhere
root ALL=(ALL)    ALL
(この下に)
chiroru ALL=(ALL)   ALL

そしてこんな感じで編集したら、 escキーを押して、:wqで上書き保存します。
完了したら

[ec2-user] sudo su - tatsuya

これでユーザー名が変わってると思います。
以降このユーザーで作業を行なっていきます。

そして一旦ログアウトして、次はローカル環境で作業します。

[tatsuya] exit
[ec2-user] exit
[.ssh] ssh-keygen -t rsa
(公開鍵の名前を入力してエンター)
(空のままエンターを2回)
[.ssh] vi config
(INSERT)
______________________________________
Host lovekitchen_key_rsa
  Hostname パブリックIP
  Port 22
  User tatsuya
  IdentityFile ~/.ssh/lovekitchen_key_rsa
______________________________________
[.ssh] cat アプリ名_key_rsa.pub
(ssh-rsaからlocalまでをコピー)

ここから再びサーバー側の作業となります。

[.ssh] ssh -i lovekitchen.pem ec2-user@パブリックIP
[ec2-user] sudo su - tatsuya
[tatsuya] mkdir .ssh
[tatsuya] chmod 700 .ssh
[tatsuya] cd .ssh
[tatsuya] vi authorized_keys
__________________________
さっきコピーした鍵をペースト
__________________________
[tatsuya] exit
[ec2-user] exit
[.ssh] ssh lovekitchen_key_rsa

これでログインできればユーザーの設定は完了!
最後にec2-userではログイン出来ないようにします。

[tatsuya] sudo vi /etc/ssh/sshd_config
_______________________________
DenyUsers ec2-user
(ec2-userではログイン出来ない)
_______________________________
[tatsuya] sudo service sshd reload
(設定を反映)
[tatsuya] exit
[.ssh] ssh -i アプリ名.pem ec2-user@パブリックIP

ec2-user@パブリックIP: Permission denied (publickey).
と表示されればOK!

これで諸々の準備は完了したので
これからはいよいよプラグインをインストールしたり
アプリをGithubからcloneしたりしていきます。

環境構築編

unicornのインストール

ここはローカル環境での作業となります。

Gemfile
group :production do
    gem 'unicorn'
end
$ bundle install

次は unicorn の設定ファイルを作成し、編集して行きます。

config/unicorn.conf.rb
$worker  = 2
$timeout = 30
$app_dir = "/var/www/projects/アプリ名"
$listen  = File.expand_path 'tmp/sockets/.unicorn.sock', $app_dir
$pid     = File.expand_path 'tmp/pids/unicorn.pid', $app_dir
$std_log = File.expand_path 'log/unicorn.log', $app_dir
# set config
worker_processes  $worker
working_directory $app_dir
stderr_path $std_log
stdout_path $std_log
timeout $timeout
listen  $listen
pid $pid
# loading booster
preload_app true
# before starting processes
before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      Process.kill "QUIT", File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end
# after finishing processes
after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

database.ymlを編集

config/database.yml
production:
  <<: *default
  database: lovekitchen_production
  username: root
  password:

リポジトリへ反映させて行きます。

$ git add .
$ git commit
$ git push origin master

Nginxのインストール

[tatsuya] sudo yum update
(サーバーをアップデート)
[tatsuya] sudo yum install -y nginx
(Nginxをインストール)
[tatsuya] sudo /etc/init.d/nginx start
(起動確認)
[tatsuya] sudo chkconfig nginx on
(自動起動を設定)

作業用ディレクトリの作成

[tatsuya] cd /
[tatsuya| /] sudo chown tatsuya var
[tatsuya| /] cd var
[tatsuya| var] sudo mkdir www
[tatsuya| var] sudo chown tatsuya www
[tatsuya| var] cd www
[tatsuya| www] sudo mkdir projects
[tatsuya| www] sudo chown tatsuya projects

プラグインをインストール

*この中に後々問題となるプラグインが含まれてます(泣)

[tatsuya] sudo yum install git make gcc-c++ patch openssl-devel libyaml-devel libffi-devel libicu-devel libxml2 libxslt libxml2-devel libxslt-devel zlib-devel readline-devel mysql mysql-server mysql-devel ImageMagick ImageMagick-devel epel-release

Node.js 6xをインストール

[tatsuya] curl -sL https://rpm.nodesource.com/setup_6.x | sudo bash -

yarnをインストール

[tatsuya] sudo npm install yarn -g

rbenvをインストール

[tatsuya] git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
[tatsuya] echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
[tatsuya] echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
[tatsuya] source ~/.bash_profile

ruby-buildをインストール

[tatsuya] git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
[tatsuya] rbenv rehash

rubyをインストール

[tatsuya] rbenv install -v 2.5.3
[tatsuya] rbenv global 2.5.3
[tatsuya] rbenv rehash
[tatsuya] ruby -v

ここも特に問題なかったです。
rubyがちゃんとインストールされた時はホッとしました。
とにかくコマンド一つ実行するだけでドキドキでしたよもう。

Githubと連携

ここからGithubと連携して、そして自分のアプリをさっき作ったディレクトリにクローンしていきます。

[tatsuya| ~] vi .gitconfig
-----------------------------------
[user]
  name (gitに登録した名前)
  email (gitに登録したメールアドレス)

[color]
  ui = true

[url "github:"]
  InsteadOf = https://github.com/
  InsteadOf = git@github.com:
-----------------------------------
[tatsuya| ~] chmod 700 .ssh
[tatsuya| ~] cd .ssh
[tatsuya| .ssh] ssh-keygen -t rsa
[tatsuya| .ssh] vi config
-----------------------------------
Host github
  Hostname github.com
  User git
  IdentityFile ~/.ssh/aws_git_rsa
-----------------------------------
[tatsuya| .ssh] chmod 600 config
[tatsuya| .ssh] cat aws_git_rsa.pub

このコピーした公開鍵をGithubへアップして行きます。
まずGithubの右上にあるアカウントメニューから Settingsへ進んで下さい。
スクリーンショット 2019-03-30 22.21.14.png
次はこちらの SSH and GPG Keys へ進みます。
スクリーンショット 2019-03-30 22.22.05.png

[Title] aws_git_rsa
[Key] さっきコピーしたaws_git_rsa.pub

これで Add SSH Key で完了!
ちゃんと接続出来ているか確認しましょう。

[tatsuya| .ssh] ssh -T github
Hi tatsuya! You've successfully authenticated, but GitHub does not provide shell access.

こんな感じでメッセージが返ってきます。

そして、いよいよクローンです!

[tatsuya| .ssh] cd /var/www/projects
[tatsuya| projects] git clone git@github.com:machamp0714/love_kitchen.git

ここで初めてエラーが表示されました。
本当はこう入力しないとダメみたいです。

[tatsuya| projects] git clone github:machamp0714/love_kitchen.git
[tatsuya| projects] ls
love_kitchen

これでクローンも無事完了しました!

Gemをインストール

お次は、 bundler をインストールします。
この時、Gemfile.lockに記載されている bundler のバージョンと合わせました。

bundlerをインストール
[tatsuya| projects] gem install bundler -v 1.7.1
Gemfileを作成
[tatsuya| projects] bundle init
Gemfileを編集
[tatsuya| projects] vi Gemfile
-------------------------------
*コメントアウトを外して、versionを記載
gem "rails", '5.2.2'

-------------------------------
[tatsuya| projects] bundle install --path vendor/bundle --jobs=4
[tatsuya| projects] bundle exec rails -v
# Rails 5.2.2

次はクローンしたディレクトリへ移動してgemをインストールして行きます。

[tatsuya| projects] cd love_kitchen
[tatsuya| love_kitchen] bundle install --path vendor/bundle

するとここで次のエラーが出現。。。

/home/tatsuya/.rbenv/versions/2.5.3/bin/ruby -r ./siteconf20190321-31332-65ucq1.rb extconf.rb
checking for gcc... yes
checking for Magick-config... yes
checking for outdated ImageMagick version (<= 6.8.9)... *** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers. Check the mkmf.log file for more details. You may
need configuration options.

Provided configuration options:
--with-opt-dir
--without-opt-dir
--with-opt-include
--without-opt-include=${opt-dir}/include
--with-opt-lib
--without-opt-lib=${opt-dir}/lib
--with-make-prog
--without-make-prog
--srcdir=.
--curdir
--ruby=/home/tatsuya/.rbenv/versions/2.5.3/bin/$(RUBY_BASE_NAME)

To see why this extension failed to compile, please check the mkmf.log which can be found here:

/var/www/projects/love_kitchen/vendor/bundle/ruby/2.5.0/extensions/x86_64-linux/2.5.0-static/rmagick-3.0.0/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in /var/www/projects/love_kitchen/vendor/bundle/ruby/2.5.0/gems/rmagick-3.0.0 for inspection.
Results logged to /var/www/projects/love_kitchen/vendor/bundle/ruby/2.5.0/extensions/x86_64-linux/2.5.0-static/rmagick-3.0.0/gem_make.out

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

In Gemfile:
rmagick

エラー文を見る限りどうも rmagick をインストールすることが出来ないようです。
ローカル環境でインストールした際も同じようなエラーが出たので、これもversion関連が
原因だろうなと思い、この時はすぐに解決できると思っていました。

ですが、ここで問題発生。
そもそも、AmazonLinuxのyum標準リポジトリにあるImageMagickのversionは一番新しいやつが6.7.8と古く、
ここで欲しいのはversionが6.8.9以上の物なので、ImageMagickのversionを新しいものにするというシンプルな
解決策を実行することが出来ません。

色々調べた結果、yum先生とremi先生のお力を借りればImageMagick6系の最新版をインストール出来るみたいなので
こちらを試してみました。ちなみに、 rmagickはImageMagickのversionを7系にしてしまうと使えないので注意してください。

remiリポジトリを追加

[tatsuya] rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm

CentOS-Baseリポジトリを追加

[tatsuya] vi /etc/yum.repos.d/CentOS-Base.repo
--------------------------------------------------------------------------
[base]
name=CentOS-6 - Base
mirrorlist=http://mirrorlist.centos.org/?release=6&arch=x86_64&repo=os
gpgcheck=1
enabled=0
gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-CentOS-6
--------------------------------------------------------------------------

古いパッケージを削除

[tatsuya] yum remove -y libtiff
[tatsuya] yum remove -y ImageMagick ImageMagick-devel

ImageMagick6系の最新版をインストール

[tatsuya] yum install -y libtiff --enablerepo=remi,epel,base --disablerepo=amzn-main
[tatsuya] yum install -y ImageMagick6 ImageMagick6-devel ImageMagick6-libs --enablerepo=remi,epel,base
[tatsuya] yum install -y libwebp libwebp-devel --enablerepo=epel --disablerepo=amzn-main
[tatsuya] yum install -y ImageMagick6 ImageMagick6-devel ImageMagick6-libs --enablerepo=remi,epel,base

これで欲しいversionのImageMagickがインストール出来たので,もう一度 bundle install を実行します。

[tatsuya| love_kitchen] bundle install --path vendor/bundle

これでインストール完了です。
僕の環境の場合、このエラーだけしか生じませんでした。

nginxの設定

次は、nginxの設定ファイルを作成し、編集して行きます。

[tatsuya| love_kitchen] sudo vi /etc/nginx/conf.d/アプリ名.conf
----------------------------------------------------------------------
upstream unicorn_server {
    server unix:/var/www/projects/love_kitchen/tmp/sockets/.unicorn.sock
    fail_timeout=0;
}
server {
    listen 80;
    client_max_body_size 4G;
    server_name IPアドレス;
    keepalive_timeout 5;
    # Location of our static files
    root /var/www/projects/love_kitchen/public;
    location ~ ^/assets/ {
        root /var/www/projects/love_kitchen/public;
    }
    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        if (!-f $request_filename) {
            proxy_pass http://unicorn_server;
            break;
        }
    }
    error_page 500 502 503 504 /500.html;
    location = /500.html {
        root /var/www/projects/love_kitchen/public;
    }
}
アプリ名や、IPアドレスはご自身の環境に合わせてください。
----------------------------------------------------------------------
nginxを再起動
[tatsuya| love_kitchen] sudo service nginx restart
権限を設定
[tatsuya| love_kitchen] sudo chmod -R 775 /var/lib/nginx
mysqlを起動
[tatsuya| love_kitchen] sudo service mysqld start
マイグレーションを実行
[tatsuya| love_kitchen] bundle exec rails db:migration RAILS_ENV=production

Missing encryption key to decrypt file with. Ask your team for your master key and write it to /xxx/my-app/config/master.key or put it in the ENV['RAILS_MASTER_KEY'].
Exiting

マイグレーションを実行しようとしたら、こんなエラーが出ました。
どうもconfig/master.keyがないので怒られてるみたいです。

そういえば master.key は、 .gitignore に記載されていてリポジトリには反映されないのを思い出しました。
というわけで、ローカル環境で master.key の中身をコピーして、本番環境の方にも master.key ファイルを作成してあげます。

[tatsuya| love_kitchen] vi config/master.key
----------------------------------
自分のアプリのconfig/master.keyをコピペ
----------------------------------
[tatsuya| love_kitchen] bundle exec rails db:migration RAILS_ENV=production

これで全ての準備が完了しました。
あとはrailsアプリを起動するだけです。

railsアプリを起動する

railsアプリをプリコンパイル
[tatsuya| love_kitchen] bundle exec assets:precompile RAILS_ENV=production
nginxを再起動
[tatsuya| love_kitchen] sudo service nginx restart
unicornを起動
[tatsuya| love_kitchen] bundle exec unicorn_rails -c /var/www/projects/love_kitchen/config/unicorn.conf.rb -D -E production

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

unicornを起動するコマンドを入力するとこんなエラーが出ました。
mysql.sock の場所がおかしいのでしょうか。

mysql.sockの場所を調べる
[tatsuya| love_kitchen] mysql_config --socket

/var/lib/mysql/mysql.sock

/var/lib/mysql.sock にあることが判明したので、
database.ymlを編集します。

[tatsuya| love_kitchen] vi config/database.yml
------------------------------------------------
production:
  <<: *default
  database: love_kitchen_production
  username: root
  password:
  socket: /var/lib/mysql/mysql.sock
------------------------------------------------
再びunicornを起動
[tatsuya| love_kitchen] bundle exec unicorn_rails -c /var/www/projects/love_kitchen/config/unicorn.conf.rb -D -E production
bundler: failed to load command: unicorn_rails

お次はこんなエラーが。。。。。。。。。
unicorn_rails の他に、 unicorn コマンドもあるみたいなので、そちらを試してみることに。

[tatsuya| love_kitchen] bundle exec unicorn -c /var/www/projects/love_kitchen/config/unicorn.conf.rb -D -E production
[tatsuya| love_kitchen] ps -ef | grep unicorn | grep -v grep
プロセスが3行表示される

これでようやっと行けたかな?と思い、ブラウザに直接IPアドレスを入力!

http://IPアドレス

遂にRailsアプリが起動しました!!!!!

ここまで来るのに丸一日かかってしまいましたが、挑戦してホント良かったです。
今は取り敢えずデプロイしました、みたいな感じなのでこれからちゃんと勉強して行きたいですね。

参考にした記事

世界一丁寧なAWS解説。EC2を利用して、RailsアプリをAWSにあげるまで

ホントに素晴らしい記事ありがとうございます。

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

Rails開発をVScodeしていたらerbファイルでEmmet(エメット)が使えなかったので使えるようにした

VScodeRails開発してるんだけど、erbファイルEmmet使えない!という人や
そもそもHTMLファイルでもEmemt 使えないよ! という人は以下を試してみてください。

VScodeの設定

まずVSCodeの左下の歯車マークをクリック
次に、settingsをクリック

検索窓で、

"Trigger Expansion On Tab" と検索
Emmet:Trigger Expansion on tab の左のチェックマークを入れる

これでHtmlファイルではEmmetが使えるようになったはず。

セッティングファイルの記述

さらに続けて、検索窓で

edit in settings.json”と検索
少し小さい文字の edit in settings.jsonをクリック

ファイルが開くので以下を追記

settings.json
{
    "workbench.iconTheme": "vscode-icons",
    "window.zoomLevel": 0,
    "emmet.triggerExpansionOnTab": true,

    # ここから記述
    "emmet.includeLanguages": {
        "erb": "html"
    }
}

これで試しに erbファイル
h1 と打ってからTabキーを叩いてください。

それでうまくいかなければ,

拡張機能のインストール

VScode画面上の左上側にあるアイコンの一番下の四角い拡張機能をクリック
Rails と検索してRailsをインストール

コレです⬇︎
https://marketplace.visualstudio.com/items?itemName=bung87.rails


上記全て終えたら、晴れてEmmetが使用できるはずです。

※EmmetはVScodeじゃなくても使えます。

たくさんコードを書いてみんなで爆速になりましょう

ごあいさつ

実はこの記事がQiitaでの初投稿になります。
1人でも多くの人のお役に立てたらと願っていますが、至らない所あるかもしれません。
その際は遠慮なく教えていただけると幸いです。

参考

https://qiita.com/gorohash/items/bb5c42e0054c83fd4a2d

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

Ruby 2.5.xでdeep-coverを使うときは、エッジケースのバグがあるので注意

Ruby 2.5.x + deep-coverでエッジケースのバグを見つけました。
原因はおそらくRubyのバグです。
Ruby 2.6.0では再現しません。

詳細はこちら
https://github.com/deep-cover/deep-cover/issues/47

deep-coverを有効にすると、以下のコードの挙動が変化します。

class NgClass
  def self.my_class_method(
      a0: 0, a1: 0, a2: 0, a3: 0, a4: 0, a5: 0, a6: 0, a7: 0, a8: 0, a9: 0,
      b0: 0, b1: 0, b2: 0, b3: 0, b4: 0, b5: 0, b6: 0, b7: 0, b8: 0, b9: 0,
      c0: 0, c1: 0, c2: 0, c3: 0, c4: 0, c5: 0, c6: 0, c7: 0, c8: 0, c9: 0,
      d0: 0, d1: 0,
      x: 0
    )
    x
  end
end

deep-coverなしでも以下のコードはRuby 2.5.xで仕様どおりに動きません

deep-coverが書き換えたコード

($_cov ||= {})[0]||=Array.new(38,0);$_cov[0][37]+=1;_temp=nil;((_temp=(class NgClassRewritten
  $_cov[0][36]+=1;((_temp=(def (self).my_class_method(
    a0: ($_cov[0][0]+=1;0), a1: ($_cov[0][1]+=1;0), a2: ($_cov[0][2]+=1;0), a3: ($_cov[0][3]+=1;0), a4: ($_cov[0][4]+=1;0), a5: ($_cov[0][5]+=1;0), a6: ($_cov[0][6]+=1;0), a7: ($_cov[0][7]+=1;0), a8: ($_cov[0][8]+=1;0), a9: ($_cov[0][9]+=1;0),
        b0: ($_cov[0][10]+=1;0), b1: ($_cov[0][11]+=1;0), b2: ($_cov[0][12]+=1;0), b3: ($_cov[0][13]+=1;0), b4: ($_cov[0][14]+=1;0), b5: ($_cov[0][15]+=1;0), b6: ($_cov[0][16]+=1;0), b7: ($_cov[0][17]+=1;0), b8: ($_cov[0][18]+=1;0), b9: ($_cov[0][19]+=1;0),
        c0: ($_cov[0][20]+=1;0), c1: ($_cov[0][21]+=1;0), c2: ($_cov[0][22]+=1;0), c3: ($_cov[0][23]+=1;0), c4: ($_cov[0][24]+=1;0), c5: ($_cov[0][25]+=1;0), c6: ($_cov[0][26]+=1;0), c7: ($_cov[0][27]+=1;0), c8: ($_cov[0][28]+=1;0), c9: ($_cov[0][29]+=1;0),
        d0: ($_cov[0][30]+=1;0), d1: ($_cov[0][31]+=1;0),
        x: ($_cov[0][32]+=1;0)
    )
    $_cov[0][34]+=1;_temp=nil;x
  end);$_cov[0][33]+=1;_temp=_temp))
end);$_cov[0][35]+=1;_temp=_temp))

Rubyの修正差分

誰か、元となるrubyの修正差分やバグ報告を知っていたら教えてください。

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

railsで複合主キーをやめるmigration

概要

railsのシステムでは複合主キーは認められてません。全てテーブルがidという自動採番の主キーを持つのが決まりです。

それはわかっていたのですが、多対多の中間テーブル、例えば、モデルで言えば下記ようなものです。

class User < ApplicationRecord
  has_many :user_skills
  has_many :skills, inverse_of: :users, through: :user_skills
end

class Skill < ApplicationRecord
  has_many :user_skills
  has_many :users, inverse_of: :skills, through: :user_skills
end

# これが私のいうところの中間テーブルです。
class UserSkill < ApplicationRecord
  belongs_to :skill
  belongs_to :user
end

これだけは自分の経験上納得がいかないというか、先入観も大きかったと思いますが、下記の理由で複合主キーを使っていました。

  • 必要のないIDの分の無駄な容量が増える
  • 頻繁にすげ替えが行われるとIDが枯渇するんじゃないか?

ただ、それで運用してみていくつかの問題がわかっています。

  • UserSkillをレシーバーにして削除ができない。
  • UserSkillをレシーバーにして更新ができない。
  • UserSkillをeager_loadできない。

他にもあるかもしれませんが、今の所私が把握してるのは上記です。

問題点に関して言えば容量はだいぶ低価格になってきてるし、アクセス速度の早いステレージもだいぶ低価格になってきてます。IDの枯渇に関してはbigintであれば一般的なシステムでは多分問題にならないので気にしなくてもいいかも。

こういうGemもあるのですが、いつまでメンテナンスされるかわからないし、メンテナンスが止まったからといって自分でどうにかならないくらい深いところに手を入れてるGEMだと思いました。

そこでRailsの恩恵を受けた方がいいと思うテーブルに関しては複合主キーをやめてみようと思い、そのmigarationを書いてみました。

class UserSkillToSinglePKey < ActiveRecord::Migration[5.2]
  def change
    remove_foreign_key :user_skill, :skill
    remove_foreign_key :user_skill, :user
    reversible do |change|
      change.up do
        execute 'ALTER TABLE user_skill DROP PRIMARY KEY'
      end

      change.down do
        execute 'ALTER TABLE user_skill ADD PRIMARY KEY (skill_id, user_id)'
      end
    end
    add_column :user_skill, :id, :primary_key, unsigned: true, first: true
    add_foreign_key :user_skill, :skill
    add_foreign_key :user_skill, :user
  end
end

ポイントとしては生SQLのところでreversibleを使ってる点、foreign_keyを外さないと主キーをDROPできないことと、add_column :user_skill, :id, :primary_keyとすると、勝手にAUTOINCREMENTになるところくらいですかね。

もし、運用しているサービスにやるのであれば、テーブルロックがかかるのでレコード数によっては問題になるかもしれません。また、一度外部制約を外すので不整合が起きる可能性があるとは思いますのでその点のも考慮に入れてください。

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