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

Sinatraで接続元IPを表示する

Sinatraで接続元IPを表示する。

get '/' do
"Your IP address is #{request.ip}"
end

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

[Rails]本日のエラー

今度作業する時に、あれ?過去にも同じエラーが起こったことあるぞ!!となった時用に記事として保管しておくのが目的です。
なので、個人的に使用する記事なので投稿内容は大雑把にまとめます。

記事の流れ
1.なんの作業中にエラーが起きたのか
2.エラーの内容
3.対処法
4.原因

以上の流れで書いていきます。

1.dbをリセットしようとした時

ターミナル.
rake db:migrate:reset

2.エラー内容

ActiveRecord::NoEnvironmentInSchemaError: 

Environment data not found in the schema. To resolve this issue, run: 

        bin/rails db:environment:set RAILS_ENV=development


Tasks: TOP => db:migrate:reset => db:drop => db:check_protected_environments
(See full trace by running task with --trace)

3.対処法

rake db:migrate:status

上記のコマンドでdbのステータスを確認します。
⬇︎実行内容

   up     20191223083031  Create articles
   up     20191223091431  Devise create users
   up     20191225043801  Add name to user
  down    20191225083536  Add frequency to habits

どうやら、一番下のマイグレーションファイルが怪しい。。。
確認したところ
habitsテーブルにfrequencyカラムを二回作成していました。。
なので、一番下のマイグレーションファイルを削除してみます。
※downの状態だったら、手動でファイルを削除しても問題ない
⬇︎実行結果

== 20191225234523 CreateHabits: migrating =====================================
-- create_table(:habits)
   -> 0.0302s
== 20191225234523 CreateHabits: migrated (0.0302s) ============================

== 20191229141445 AddImageToHabits: migrating =================================
-- add_column(:habits, :image, :string)
   -> 0.0195s
== 20191229141445 AddImageToHabits: migrated (0.0195s) ========================

== 20200101140707 AddDateAtToHabits: migrating ================================
-- add_column(:habits, :date_at, :date)
   -> 0.0231s
== 20200101140707 AddDateAtToHabits: migrated (0.0232s) =======================

== 20200103135302 CreateLikes: migrating ======================================
-- create_table(:likes)
   -> 0.0157s
== 20200103135302 CreateLikes: migrated (0.0158s) =============================

== 20200107020748 AddLikesCountToHabits: migrating ============================
-- add_column(:habits, :likes_count, :integer)
   -> 0.0174s
== 20200107020748 AddLikesCountToHabits: migrated (0.0174s) ===================

問題なくできました。

4.原因

1つのテーブルに同じカラム名のカラムを追加、作成するマイグレーションファイルが重複していたため、migrateコマンドが実行できなかったと思われる。
なぜ二つ追加、作成したかは謎。
参考記事
https://blog.freedom-man.com/no-environment-in-schema-error

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

文字列とシンボルと双方の違いについて

はじめに

学習メモです。
今回扱う対象は以下となります。

  • 文字列(リテラル)
  • シンボル(リテラル)

文字列から数値へ変換

最初の文字列が整数に変換できない場合は0を返します。

"100".to_i     #=> 100

"1.5".to_f     #=> 1.5

"7/2".to_r     #=> (7/2) Rationalオブジェクトに変換

"1+2i".to_c    #=> (1+2i) Complexオブジェクトに変換

"123ab45".to_i #=> 123 aの前までが変換対象

"123ab45".to_f #=> 123.0 aの前までが変換対象

"2.3".to_i     #=> 2 ピリオドの前までが変換対象

"3.5.6".to_f   #=> 3.5 2番目のピリオドの前までが変換対象

"a123".to_i    #=> 0

バックスラッシュ記法

指定する内容   生成される文字
\x xの文字そのもの
\n 改行
\r キャリッジリターン
\f 改ページ
\a ベル
\e エスケープ
\s 空白
\b バックスペース
\t タブ
\v 垂直タブ
\nnn 8進数表記 (nは0〜7)
\xnn 16進数表記 (nは0〜9、a〜f)
\cx または \C-x コントロール文字 (xはASCII文字)
\M-x メタx
\M-\C-x メタコントロールx
\unnnn Unicode文字 (nは0〜9、a〜f、A〜F)
\u{nnnn} Unicode文字列 (nは0〜9、a〜f、A〜F) nnnnは16進数で1〜6桁まで指定可能。スペースかタブ区切りで複数のUnicode文字を指定可能

ちなみに、表中に書かれている「キャリッジリターン」とは、文字コード体系において、カーソルを文頭へ戻すことを表す制御文字です。(引用元:Weblio)

8進数表記と16進数表記

# "@"の文字コードは8進数で100、16進数で40
p "\100" #=> @ 8進数表記
p "\x40" #=> @ 16進数表記

出力メソッドの種類と違い

メソッド 改行 出力内容の構築メソッド バックスラッシュ記法
p 引数ごとに改行 inspectメソッド そのまま出力
print 改行しない to_sメソッド 適用した結果を出力
puts 引数ごとに改行 to_sメソッド 適用した結果を出力

pのバックスラッシュ記法

p "\x61" #=> "a"
p "\x0a" #=> "\n"

バックスラッシュ記法をシングルクォートで囲んだ場合は、シングルクォートとバックスラッシュのエスケープのみ適用され、それ以外はそのまま出力されます。

ヒアドキュメント

改行コードを含む文字列を指定するためにヒアドキュメントを利用します。
ヒアドキュメントは、「<<」に続けて文字列の終端を示す任意の識別子を指定します。

ヒアドキュメントには、終端を示す識別子の前にはスペースなどの文字を記述してはいけないというルールがあります。なので、階層が深い場合でも、終端は行頭に指定します。

以下はヒアドキュメントの例です。
終端を示す識別子にSQLを使用しており、2行目と3行目が文字列になります。

query = <<SQL
  select *
     from animal_table;
SQL

query #=> " select *\n    from animal_table;\n"

階層が深い場合のヒアドキュメントでは、開始の識別子の頭にハイフンをつけます。

def foo
  <<-RESULT
    Ba
    nana
  RESULT
end

p foo #=> "    Ba\n    nana\n"

ヒアドキュメントにダブルクォートで囲んだ時と同じような式展開やバックスラッシュ記法の適用を無効とする場合は、識別子をシングルクォートで囲みます。終端の識別子は囲まないよう注意しましょう。

a = 1
s = <<'SAMPLE'
  #{a}
SAMPLE

p s #=> "  \#{a}\n"

識別子をダブルクォートで囲む事により、式展開が有効であることを明示的に示すことができます。

a = 1
s = <<"SAMPLE"
  #{a}
SAMPLE

p s #=> "  1\n"

パーセント記法

パーセント記法を用いる事で文字列を囲む記号をプログラマが指定できます。
文字列の中でダブルクォートを使う場合などに、エスケープしなくてもよいので便利(らしい)です。
文字列を囲む記号には、数値やアルファベット以外の文字が使用可能です。

# パーセント記法の例
a = %*cat* # 囲む記号に「*」を使用
p a        #=> "cat"

# エスケープなしでダブルクォートを文字列中に使用
a = %*"cat"*
p a        #=> "\"cat\""

# パーセント記号に[]なども使える
a = %[cat]
p a        #=> "cat"

文字列のシングルクォートの時と同じにするには「%q」を使います。
また、ダブルクォートの際は「%Q」を使います。

a = 1
%q!#{a + 2}! #=> "\#{a + 2}"
%Q?#{a + 2}? #=> "3"

以下パーセント記法を表にまとめました。

書式   生成される値
% ダブルクォート文字列
%Q ダブルクォート文字列 (%のみと同等)
%q シングルクォート文字列
%s シンボル
%W 要素がダブルクォート文字列となる配列、要素の区切りは空白文字
%w 要素がシングルクォート文字列となる配列、要素の区切りは空白文字
%x コマンド出力
%r 正規表現

異なる文字コード間での文字列操作

Ruby2.0以降デフォルトのスクリプトエンコーディングはUTF-8となり、明示的に文字コードを指定しない限りUTF-8となります。

x = "モンキー"
x.encoding           #=> #<Encoding:UTF-8>
y = x.encode("SJIS") # "モンキー"をWindows-31Jに変換
y.encoding           #=> #<Encoding:Windows-31J>
x + y                #=> Encoding::CompatibilityError

sprintfによるフォーマット指定

桁数を揃える場合にsprintfという組み込み関数を使います。
sprintfは、第一引数にフォーマット、第二引数以降にフォーマットしたい値を指定します。

# 進数の指定
sprintf("result: %#b", 16)        #=> "result: 0b10000"
sprintf("result: %#o", 16)        #=> "result: 020"
sprintf("result: %#x", 16)        #=> "result: 0x10"
sprintf("result: %#X", 16)        #=> "result: 0X10"

# 桁数の指定
sprintf("result: %02d", 1)        #=> "result: 01"
sprintf("result: %03d", 1)        #=> "result: 001"
sprintf("result: %05.2f", 1.1111) #=> "result: 01.11"

ここで重要なのはsprintf関数はStringクラスの%演算と同じ結果が得られるということです。

"result: %02d" % 1        #=> "result: 01"
"result: %03d" % 1        #=> "result: 001"
"result: %05.2f" % 1.1111 #=> "result: 01.11"

シンボル

# シンボルの記述例
foo1 = :"foo1"        #=> :foo1
foo2 = :"#{foo1}foo2" #=> :foo1foo2
foo3 = :'foo3'        #=> :foo3
foo4 = :foo4          #=> :foo4

パーセント記法を使ってシンボル生成することもできます。
この場合、「%s」とすることで実現できます。(以下例)

%s?foo1? #=> :foo1
%s{foo2} #=> :foo2

生成された値はSymbolクラスのインスタンスとなります。
文字列からシンボルを生成するには「to_sym」を使います。

v1 = "foo1"    #=> "foo1"
v2 = v1.to_sym #=> :foo1
v3 = v2.to_s   #=> "foo1" シンボルから文字列に変換

文字列とシンボルの違い

作成したシンボルは、文字の並びが同じであれば、同一のオブジェクトを参照しますが、文字列リテラルは文字の並びが同一でも、指定するごとに新たなStringオブジェクトが生成されます。
これを確かめるにはオブジェクトID(object_idメソッド)を使います。

文字列リテラルでは、毎回新しくオブジェクトを生成するのでオブジェクトIDが変わります。
一方、シンボルは同じオブジェクトを参照するのでオブジェクトIDは変化しません。
また、nilやtrueも、一つのオブジェクトを参照するので、シンボルと同様にオブジェクトIDは変化しません。

p "foo1".object_id #=> 70311790346380
p "foo1".object_id #=> 70311790401140

p :foo1.object_id  #=> 1537628
p :foo1.object_id  #=> 1537628
p :foo2.object_id  #=> 1537948

Ruby 2.3以降では、「#frozen_string_literal:true」というマジックコメントを記述した場合、文字列リテラルで生成される文字列は値が変更できないようにfreezeされ、同じ内容の文字列リテラルは同一の文字列オブジェクトを返すようになります。

「equal?」メソッドは2つのオブジェクトが同一かどうか(等しいかどうかではない)を論理値で返し、サブクラスではオーバーライドしない慣習となっている(そうです)。
等価演算子「==」は、2つのオブジェクトが等しいかどうかを判定します。
これは多くのクラスでオーバーライドされています。

「==」と同様の動作をするメソッドに「eql?」メソッドがあります。
「eql?」メソッドは値の比較だけでなく、型の比較も行うため、値が等しくても型が違えばfalseを返します。
つまり、「eql?」メソッドは「==」よりも厳密に値の比較を行うと考えても良いでしょう。

# オブジェクトの同値性・同一性判定
"foo1" == "foo1"     #=> true
"foo1".equal? "foo1" #=> false 互いにobject_idが異なるため

:foo1 == :foo1       #=> true
:foo1.equal? :foo1   #=> true

# eql?メソッド
"foo1".eql? "foo1"  #=> true
1.0 == 1            #=> true
(1.0).eql? 1        #=> false
(1.0).eql? 1.0      #=> true

破壊的メソッド

破壊的メソッド「!」は、自分自身の内容を変更するだけでなく、他の変数の参照先にも影響を与えるので気を付けなければなりません。

以下の例に出てくる「chop」メソッドは、最後の文字を取り除いた文字列を返すメソッドです。

# 破壊メソッドの例
a = "foo1"
b = a                # aとbは同じ文字列"foo"を参照
p a.chop             # "foo" aの参照先は変更されない
p b                  #=> "foo1"
p a.chop!            #=> "foo" aの参照先が変更される("foo"に上書きされる)
p b                  #=> "foo"

破壊的メソッドに「!」を付けるのは、同じメソッド名で破壊的でないメソッドもある場合に限った習慣(らしい)です。
「concat」メソッドのように破壊的であっても「!」が付いていないメソッドも存在します。

(まとめ) 文字列とシンボルの違い

変数に文字列を代入していくと、たとえ同じ値だとしてもそれぞれ異なるオブジェクトを示しています。
一方シンボルでは、変数に代入しても同じオブジェクトを示しています。

x = "foo"
y = "foo"
z = "foo"
# x, y, zはそれぞれ別のオブジェクト
p x.object_id #=> 70311790464220
p y.object_id #=> 70311790482240
p z.object_id #=> 70311790500280

x = :foo 
y = :foo 
z = :foo 
# x, y, zは同一のオブジェクト
p x.object_id #=> 1538908
p y.object_id #=> 1538908
p z.object_id #=> 1538908
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lambda+API GatewayのレスポンスをJSONとしてパース出来ない

はじめに

内閣府の祝日CSVをLambda上でJSON形式に加工して、API Gateway経由で使えるようにする」というプログラムを考えたのですが、API GatewayのレスポンスがJSONとして正しくパース出来ないことが分かりました。

Lambda上で動いているRubyプログラムは単体のスクリプトとして問題なく動作することを既に確認していましたが、原因が分かるまで少し時間がかかったので、その記録をまとめてみました。

使用した環境

  • Lambda(Ruby)
  • API Gateway

正しく動作しなかったコード

  • JSON.pretty_generateJSON.generateJSON.dumpなどでシリアライズした結果を返すと、API Gatewayを通して取得したレスポンスの前後に不要なダブルクォート(")が付いてしまい、JSONとしてパース出来ませんでした。
    • その後にさらに調べてみると、API Gatewayを通さずにLambda単体の状態でもレスポンスの前後に不要なダブルクォートが付いていたため、この現象がAPI Gatewayによるものではないと分かりました。
lambda_function.rb
require 'json'
require 'open-uri'

def lambda_handler(event:, context:)
    csv = OpenURI.open_uri('https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv') {|io|
        io.read
    }
    csv = csv.force_encoding("sjis")

    array = []
    csv.split(/\r\n|\r|\n/).each_with_index {|row, i|
        next if (i==0) # ヘッダ行は読み飛ばす
        buffs = row.split(",")
        hash = {"date": buffs[0], "name": buffs[1]}
        array.push(hash)
    }
    return JSON.pretty_generate(array)  # この行をJSON.generate()やJSON.dump()に変えても結果は変わらず...
end
出力結果の例
"[\n  {\n    \"date\": \"1955/1/1\",\n    \"name\": \"元日\"\n  },...,\n  {\n    \"date\": \"2020/11/23\",\n    \"name\": \"勤労感謝の日\"\n  }\n]"

正しく動作したコード

  • こちらのページに書かれているように、シリアライズせずにオブジェクトをそのまま返す形にコードを直したところ、正しいJSONをレスポンスとして受け取れるようになりました。
  • どうやら、 「Lambdaの関数の戻り値として文字列のJSONを指定すると、JSONとして二重にシリアライズされてしまう」 ことが今回の問題の原因のようです。
lambda_function.rb
require 'json'
require 'open-uri'

def lambda_handler(event:, context:)
    csv = OpenURI.open_uri('https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv') {|io|
        io.read
    }
    csv = csv.force_encoding("sjis")

    array = []
    csv.split(/\r\n|\r|\n/).each_with_index {|row, i|
        next if (i==0) # ヘッダ行は読み飛ばす
        buffs = row.split(",")
        hash = {"date": buffs[0], "name": buffs[1]}
        array.push(hash)
    }
    return array  # ArrayやHashなどのオブジェクトをそのまま返せばOK!
end
出力結果の例
[{"date":"1955/1/1","name":"元日"},...,{"date":"2020/11/23","name":"勤労感謝の日"}]

まとめ

  • ある程度調べたところでLambdaの仕様による現象だと分かっていたものの、中々解決策が見つからずに苦労しました。
  • 上記の仕様について、AWS公式ページのどこかに記載されているのかもしれませんが、大事な事なのでもう少し分かりやすく書いて欲しかったです。
    • もしかしたら私が気付かなかっただけで、どこかに明記されていたのかもしれません。
    • あるいは私がLambda初心者で知らなかっただけで、暗黙知だったのかもしれませんが...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者によるプログラミング学習ログ 206日目

100日チャレンジの206日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。

100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。

206日目は

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

【Rails】2つのテーブルを結合しつつ、欲しいカラムだけを抜き出してJSONで返すサンプルコード

はじめに

Railsで2つのテーブルを結合しつつ、欲しいカラムだけを抜き出してJSONにして返すサンプルコードです。

このコードで出来たJSONはネストされておらず、シンプルなので扱いやすいです。

環境

OS: macOS Catalina 10.15.1
Ruby: 2.6.5
Rails: 6.0.2.1

前提

  • UserモデルとPostモデルがある
  • User1に対してPostが多の関係
  • それぞれのテーブル名はusersとposts
  • 以下のように関連付け済み
user.rb
class User < ActiveRecord::Base
  has_many :posts
end
post.rb
class Post < ApplicationRecord
  belongs_to :user
end

欲しいデータ

各テーブルのカラムは以下のように多数あり、
「不要なデータは取得したくない!」
という状況を想定し、#欲しいを付けたカラムだけを抜き出したJSONが欲しいとします。

- usersテーブル
    - id
    - name #欲しい
    - email
    - ...

- postsテーブル
    - id #欲しい
    - name #欲しい
    - description #欲しい
    - user_id #欲しい
    - date
    - ...

これは以下コードで実現可能です。

結論

posts_controller.rb
  def posts_needed
    posts_needed = Post.joins(:user)
    .select("
      posts.id,
      posts.name,
      description, #ここはusersテーブルと名前が被らない
      user_id, #ここはusersテーブルと名前が被らない
      users.name AS user_name #別名を付けられる
      ")
    render json: posts_needed
  end

【ポイント】

  • Post.joins(:user)postsテーブルとusersテーブルを結合

  • .select("カラム1, カラム2, ...")で欲しいカラムを指定

  • 元のカラム名が被る場合posts.nameusers.nameのようにテーブル名.カラム名として指定しないと、両方取り出すのは不可

  • テーブル名.カラム名 AS 任意名で出力されるカラム名を指定可能

  • テーブル名は複数形

出力

出力(JSON)
[
    {
        "id": 1,
        "name": "投稿名1",
        "description": "投稿詳細1",
        "user_id": 2,
        "user_name": "Brutus"
    },
    {
        "id": 2,
        "name": "投稿名2",
        "description": "投稿詳細2",
        "user_id": 3,
        "user_name": "Omae Dattanoka"
    },
...
]

ネストされたものよりシンプルで扱いやすいJSONに仕上がりました:relaxed:

おわりに

最後まで読んで頂きありがとうございました:bow_tone1:

どなたかの参考になれば幸いです:relaxed:

参考にさせて頂いたサイト(いつもありがとうございます)

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

Dockerでalpineイメージ使用時に発生したstandard_init_linux.go:211: exec user process caused "no such file or directory" を解決

alpineイメージ使用時に発生するstandard_init_linux.go:211: exec user process caused "no such file or directory" を解決

Docker X Rails6 X MySQL8で環境構築する際に、初めてalpineイメージを使用するとエラーに出会いました。
alpineはLinuxディストリビューションの1つで、CentOSやUbuntuよりも軽量なためDockerイメージのサイズを小さくすることができます。

各種設定

Dockerfile
FROM ruby:2.7.0-alpine
ENV LANG C.UTF-8

RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock

RUN apk update -qq && apk add --no-cache yarn build-base tzdata libxml2-dev mariadb-dev libxslt-dev alpine-sdk mysql-dev nodejs vim g++
RUN bundle install
run yarn install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]
docker-copose.yml
~~省略
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
~~省略
entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

(他, Gemfile,Gemfile.lock)

エラー発生

$ docker-compose run web rails new .

を実行すると

standard_init_linux.go:211: exec user process caused "no such file or directory

というエラーが発生し、rails newは実行できませんでした。

原因

alpineのシェルはbashではなくashだった。

解決

docker-compose.ymlentrypoint.shを編集します。

docker-compose.yml
  web:
    build: .
    # bash → ashに変更
    command: ash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
entrypoint.sh
#!/bin/sh ←bashからshへ
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

編集後、再度

$ docker-compose run web rails new .

すれば実行できました。

参考

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

alpineイメージ使用時に発生するstandard_init_linux.go:211: exec user process caused "no such file or directory" を解決

alpineイメージ使用時に発生するstandard_init_linux.go:211: exec user process caused "no such file or directory" を解決

Docker X Rails6 X MySQL8で環境構築する際に、初めてalpineイメージを使用するとエラーに出会いました。
alpineはLinuxディストリビューションの1つで、CentOSやUbuntuよりも軽量なためDockerイメージのサイズを小さくすることができます。

各種設定

Dockerfile
FROM ruby:2.7.0-alpine
ENV LANG C.UTF-8

RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock

RUN apk update -qq && apk add --no-cache yarn build-base tzdata libxml2-dev mariadb-dev libxslt-dev alpine-sdk mysql-dev nodejs vim g++
RUN bundle install
run yarn install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]
docker-copose.yml
~~省略
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
~~省略
entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

(他, Gemfile,Gemfile.lock)

エラー発生

$ docker-compose run web rails new .

を実行すると

standard_init_linux.go:211: exec user process caused "no such file or directory

というエラーが発生し、rails newは実行できませんでした。

原因

alpineのシェルはbashではなくashだった。

解決

docker-compose.ymlentrypoint.shを編集します。

docker-compose.yml
  web:
    build: .
    # bash → ashに変更
    command: ash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
entrypoint.sh
#!/bin/sh ←bashからshへ
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

編集後、再度

$ docker-compose run web rails new .

すれば実行できました。

参考

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

Dockerでalpineイメージ使用時に発生するstandard_init_linux.go:211: exec user process caused "no such file or directory" を解決

alpineイメージ使用時に発生するstandard_init_linux.go:211: exec user process caused "no such file or directory" を解決

Docker X Rails6 X MySQL8で環境構築する際に、初めてalpineイメージを使用するとエラーに出会いました。
alpineはLinuxディストリビューションの1つで、CentOSやUbuntuよりも軽量なためDockerイメージのサイズを小さくすることができます。

各種設定

Dockerfile
FROM ruby:2.7.0-alpine
ENV LANG C.UTF-8

RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock

RUN apk update -qq && apk add --no-cache yarn build-base tzdata libxml2-dev mariadb-dev libxslt-dev alpine-sdk mysql-dev nodejs vim g++
RUN bundle install
run yarn install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]
docker-copose.yml
~~省略
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
~~省略
entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

(他, Gemfile,Gemfile.lock)

エラー発生

$ docker-compose run web rails new .

を実行すると

standard_init_linux.go:211: exec user process caused "no such file or directory

というエラーが発生し、rails newは実行できませんでした。

原因

alpineのシェルはbashではなくashだった。

解決

docker-compose.ymlentrypoint.shを編集します。

docker-compose.yml
  web:
    build: .
    # bash → ashに変更
    command: ash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
entrypoint.sh
#!/bin/sh ←bashからshへ
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

編集後、再度

$ docker-compose run web rails new .

すれば実行できました。

参考

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

herokuへデプロイ後、We're sorry, but something went wrong.

We're sorry, but something went wrong.

herokuへデプロイ後にブラウザで表示されるました。

image.png

原因

色々調べたところ

・データベースの変更が更新されていない。

・モデルにアクセスする処理を行っているのに、heroku上のデータベースをリセットを行っていない。

これは、デプロイ自体はできているみたい。
このパターンが多いみたいです。
僕もそうでした。

解決法

$heroku run rake db:migrate

↑これで、heroku上のデータベースを更新

いけました。。一安心です

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

AWSでのデプロイ手順①ネットワーク環境設定

初心者には難関である、AWSを使用したデプロイ手順を書いてみます

AWSはUIもよく変化するので、
現在のもの(2019/10)で書いています

※アカウントがない人はまず新規で申し込みしておいてください

今回内容
STEP1 ネットワーク環境設定
1.VPCの作成
2.サブネットの作成
3.インターネットゲートウェイの作成
4.ルートテーブルの作成
5.サブネットとの紐付け
6.セキュリティグループの作成

STEP2 EC2の設定
1.EC2にてインスタンスの作成
2.Elastic IPの割り当て
3.インスタンスにSSHでログイン

次回内容
STEP3以降
AWSでデプロイするまでの手順②サーバー(EC2インスタンス)環境設定

※ここでいうサーバーとはAWS EC2インスタンス (Amazon Linux) のこととする

STEP1 ネットワーク環境設定

1.VPCの作成

https://aws.amazon.com/
Amazon Web Servicesでサインインします

ヘッダーにあるリージョンを東京に設定し、
「VPC」を検索し「VPC」にいきます

スクリーンショット 2019-10-01 10.13.39.png

サイドメニューのVPCより[VPCの作成]を押下します
スクリーンショット 2019-09-29 20.26.18.png

作成画面になるので
今回は下のように設定し、[作成]を押下します

・ネームタグ       :testVPC
・IPv4 CIDRブロック:10.0.0.0/16
・IPv4 CIDRブロック:ブロックなし
・テナンシー       :デフォルト

スクリーンショット 2019-10-01 10.18.41.png

2.サブネットの作成

今回のサブネットは[RDS]を配置するためのサブネットを2つを作成する構成です。

サイドメニューのサブネットより[サブネットの作成]を押下します
スクリーンショット 2019-09-29 20.30.39.png

作成画面になるので
下のように設定し、[作成]を押下します

・ネームタグ:testSubnet
・VPC:先ほど作成したものを選択
・アベイラビリティゾーン:ap-northeast-1a
・CIDRブロック:10.0.0.0/24

同様にもう一つ作成します

・ネームタグ:testSubnet2
・VPC:先ほど作成したものを選択
・アベイラビリティゾーン:ap-northeast-1c
・CIDRブロック:10.0.1.0/24

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3335353531372f34633731633536382d346231352d653262622d636665612d6333373631643964363634642e706e67.png

3.インターネットゲートウェイの作成

サイドメニューのインターネットゲートウェイより
[インターネットゲートウェイの作成]を押下します

スクリーンショット 2019-09-29 20.34.02.png

作成画面になるので
下のように設定し、[作成]を押下します

名前タグ:testGateway

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3335353531372f30303636333333392d383839352d643333632d343637332d6362376530643365353366632e706e67.png

[アクション]を押下し、[VPCにアタッチ]を押下します
68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3335353531372f63333666633763662d313638622d626136392d306135652d3365326235653836303433642e706e67.png

下のように設定し、[アタッチ]を押下する

VPC:先ほど作成したものを選択

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3335353531372f66303032333238392d653838612d326264652d306337632d3661323536666230373264312e706e67.png

4.ルートテーブルの作成

サイドメニューのルートテーブルより[ルートテーブルの作成]を押下

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3335353531372f37616534643065332d323862302d613333372d346132632d6630623434383936643238352e706e67.png

作成画面になるので
下のように設定し、[作成]を押下します

名前タグ:testTable
VPC:先ほど作成したものを選択

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3335353531372f32623430326161332d633761652d666461622d616163622d6130326265323933383230652e706e67.png

作成したルートテーブルを選択した状態で、
下のタブ「ルート」>「ルートの編集」を押下、[ルートの追加]を押下

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3335353531372f35336331383337372d353137662d613265342d313139352d3263613730396130306661632e706e67.png

下のように設定し[ルールの保存]を押下します

送信先:0.0.0.0/0
ターゲット:Internet Gatewayを選択し、先ほど作成したゲートウェイIDを選択

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3335353531372f63626462313066362d656534352d616239312d613164612d6534363733636333313136662e706e67.png

5.サブネットとの紐付け

→左側の「サブネット」を選択します
→関連付けを変更するサブネットを選択します
→画面下のルートテーブルを選択します
→[ルートテーブルの関連付けの編集]を押下します

スクリーンショット 2019-10-01 10.35.17.png

→先ほど作成したルートテーブルIDを選択します
→[保存]を押下します

スクリーンショット 2019-10-01 10.39.06.png

6.セキュリティグループの作成

サイドメニューの
セキュリティ>セキュリティグループより>[セキュリティグループの作成]を押下します
スクリーンショット 2019-09-29 21.07.37.png

下のように設定し、[作成]を押下する

セキュリティグループ名:testSecurityGroup
説明:そのセキュリティグループの説明
VPC:先ほど作成したものを選択

スクリーンショット 2019-10-01 10.43.36.png

画面下のインバウンドのルールの[ルールの編集]を押下し、[ルールの追加]を押下、
下のように設定し、[ルールの保存]を押下する

タイプ:SSH
ソース:マイIP

スクリーンショット 2019-09-30 21.23.54.png

STEP2 EC2の設定

AWSでDBを利用したい場合、

・EC2にてインスタンスの作成
・RDSを利用する
※ ただRDSを使用すると料金が掛かるので、
使用しない場合はサーバーに直接データベースを作成してください。

この2種類の方法があります。
今回は
EC2にてインスタンスの作成(サーバーに直接データベースを作成)
をメインに行なっていきます

1.EC2にてインスタンスの作成

インスタンスは起動のままだと
課金請求されるので、使わない場合は
停止状態にすることをお勧めします

AWS マネジメントコンソールにて"EC2"を検索しアクセスする

スクリーンショット 2019-09-26 23.58.51.png

サイドメニューのインスタンスより[インスタンスの作成]を押下する

スクリーンショット 2019-09-27 0.02.03.png

今回はこちらをクリックします
スクリーンショット 2019-09-27 0.06.56.png

[次の手順: インスタンスの詳細の設定]をクリックする

スクリーンショット 2019-09-27 0.08.21.png

下を設定し,[次の手順: ストレージの追加]を押下する

ネットワーク:先ほど作成したVPCを選択
サブネット:先ほど作成したサブネットを選択
自動割り当てパブリック IP:有効
他はデフォルトのまま

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3335353531372f30353438646266632d333631362d396662392d386166662d6265383133353536653934632e706e67.png

デフォルトのままで,[次の手順: タグの追加]を押下する

タグを追加する

[タグの追加]を押下する
スクリーンショット 2019-09-30 16.46.54.png

キー:Name
値:testInstance

で[次の手順: セキュリティグループの設定]を押下する

セキュリティグループの設定する

セキュリティグループの割り当て:既存を
選択し、先ほど作成したセキュリティグループを選択し、
[確認と作成]を押下する
68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3335353531372f39616532356237392d396565342d363531342d396237392d6237333732613434373030322e706e67.png

一覧画面にて、
最後に[起動]を押下します

下ような表示になり、
新しいキーペアを作成を選択、
キーペア名を入力し、
[キーペアのダウンロード]を押下します
(一度作成されたファイルは再度ダウンロードができなくなるので注意)
スクリーンショット 2019-09-30 19.51.28.png

ダウンロードしたら[インスタンスの作成]を押下します

しばらくするとインスタンスが作成されます!!

2.Elastic IPの割り当て

サイドメニューの
ネットワーク&セキュリティ>
Elastic IP[新しいアドレスの割り当て]を押下する

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3335353531372f36323739383338622d633864352d643366312d383066342d6639613635663234633238612e706e67.png

[割り当て]を押下します
スクリーンショット 2019-09-30 17.06.31.png

[閉じる]で戻り

[アクション]>アドレスの関連付けを押下します
68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3335353531372f34643235323034332d663531632d653261362d396330362d3462626139363734326635662e706e67.png

インスタンスを先ほど作成したものを選択し、[関連付け]を押下する
スクリーンショット 2019-09-30 17.09.45.png

※停止していたインスタンスにElastic IPを関連付けたままにしていると小額の料金が発生してしまうので、
インスタンスを停止している場合は解放することをお勧めします

Elastic IPをアタッチ(関連付け)しているインスタンスが起動している状態であれば、料金は発生しません。しかし、インスタンスが「停止」している状態や、Elastic IPを使っていない場合、料金が発生します。そのため、使わないElastic IPは削除しなければいけません。

Elastic IPがまだインスタンスにアタッチしている場合は、Elastic IPから該当のIPを選択し、「アクション」→「アドレスの関連付けの解除」をクリックします。確認画面が表示されますので、間違いなければ、「アドレスの関連付けの解除」をクリックし、EIPをデタッチします。

その後、再度該当のIPを選択し、「アクション」→「アドレスの解放」をクリックし、EIPを削除します。これで、料金が発生しなくなります。

3.インスタンスにSSHでログイン

各種インストール

python

$ brew install python

pip(pythonのパッケージ管理システム)

$ easy_install pip

awscli(awsをPCのコンソール上から扱うためのもの)

$ pip install awscli

できない場合はこちら
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-macos.html

ec2-userでインスタンスにログインする

AWSでは、
EC2インスタンスにログインできるユーザーとして、
デフォルトでec2-userという名のユーザーが用意されています
こちらではまずec2-userでログインします

ターミナルで以下を入れていきます
(※testKey.pemは、先ほどSTEP2でダウンロードしたキーです)

$ mv Downloads/testKey.pem .ssh/
(#作成した公開鍵をsshフォルダに移動)

$ cd .ssh/
(#ディレクトリをsshに移動)

$ chmod 600 testKey.pem
(#公開鍵に600番で定義されたアクセス権を付与する)

$ ssh -i testKey.pem ec2-user@(@以降のURLは、作成したEC2インスタンスと紐付けたElastic IPを使用してください。)
(#公開鍵を利用してec2-userとしてログイン)

例:
($ ssh -i testKey.pem ec2-user@13.112.140.56)

yes/noを聞かれるので

yesでEnterを押下
スクリーンショット 2019-09-30 21.49.54.png

無事にログインできました!!!


ssh port 22 Operation timed out
エラーになる場合はこちら
https://qiita.com/yokoto/items/338bd80262d9eefb152e

https://qiita.com/minicoopers0716/items/cac50f29ef79a03f1d8d

ユーザー作成(EC2上での操作)

上記方法でインスタンスにログインしている状態で

[ec2-user|~]$ sudo adduser testuser
(#新規ユーザー名の登録)
[ec2-user|~]$ sudo passwd testuser
(#新規ユーザー名のパスワード登録)


パスワードを登録します


vimでユーザーに権限を追加する記述する

$ sudo visudo

vimモードになります

## Allows people in group wheel to run all commands
# %wheel        ALL=(ALL)       ALL

## Same thing without a password
# %wheel  ALL=(ALL)       NOPASSWD: ALL

## Allows members of the users group to mount and unmount the
## cdrom as root
# %users  ALL=/sbin/mount /mnt/cdrom, /sbin/umount /mnt/cdrom

## Allows members of the users group to shutdown this system
# %users  localhost=/sbin/shutdown -h now

検索モードにし下の
"wheel"を探します
(キーボードで"/wheel"を入力し押下、"N"を押下すると次にいけます)

## Same thing without a password
# %wheel  ALL=(ALL)       NOPASSWD: ALL

キーボードの「i」を押下し、編集モードで

# %wheel ALL=(ALL) NOPASSWD: ALL
のコメントアウトを外します

## Same thing without a password
%wheel  ALL=(ALL)       NOPASSWD: ALL

さらに

その下に、作成したユーザーに権限を追加する記述
testuser   ALL=(ALL)       ALL  を追加する
## Allow root to run any commands anywhere
root    ALL=(ALL)       ALL
testuser   ALL=(ALL)       ALL

キーボードの「esc」を押下します
「:wq」を入力しEnterで保存します

こちらでユーザーの切り替えを行ってください。

[ec2-user|~]$ sudo su - testuser
(#ユーザー名の切り替え)
[testuser@ ~]

無事に[ec2-user|がtestuser(作成したユーザー名)と切り替わればOKです。

exit

を二回いれて
で一度ログアウトする

追加ユーザ用キーペアを作成

こちらはローカルでの作業です

$ cd .ssh
[.ssh]$ ssh-keygen -t rsa
(#公開鍵を作成)
-----------------------------
Enter file in which to save the key ():
(#ここでファイルの名前を記述して、エンターを押す)
test_key_rsa 

Enter passphrase (empty for no passphrase): 
(#何もせずそのままエンター)
Enter same passphrase again: 
(#何もせずそのままエンター)
-----------------------------

[.ssh]$ ls
#「test_key_rsa」と「test_key_rsa.pub」が生成されたことを確認
[.ssh]$ vi config
(#VIMを起動し、設定ファイルを編集する)
-----------------------------

キーボードの「i」を押下し、編集モードで

# 以下を追記
Host test_key_rsa
  Hostname 前出のElastic IP (#自分の設定に合わせて)
  Port 22
  User testuser (#先ほどのユーザー名)
  IdentityFile ~/.ssh/test_key_rsa (#秘密鍵の設定)
* ()部分は削除する。
-----------------------------
キーボードの「esc」を押下します
「:wq」で保存します

[.ssh]$ cat test_key_rsa.pub (#鍵の中身をターミナル上に出力)

★ ssh-rsa~~~~localまでをコピーしておく

サーバー側作業

続いてサーバーでの作業です
ec2-userでログインします

$ cd .ssh
$ ssh -i testKey.pem ec2-user@(@以降のURLは、作成したEC2インスタンスと紐付けたElastic IPを使用してください。)
[ec2-user|~]$ sudo su - testuser
[testuser@ ~]$ mkdir .ssh
[testuser@ ~]$ chmod 700 .ssh
[testuser@ ~]$ cd .ssh
[testuser@ |.ssh~]$ vi authorized_keys

(vimがオープンするので、「i」を押し、
先ほど ★ で、コピーしたssh-rsaをペーストする)
キーボードの「esc」を押下します
「:wq」で保存します

[testuser@ |.ssh~]$ chmod 600 authorized_keys

$ exit
もう一度
$ exit
ログアウト
[~]$ ssh test_key_rsa

ログインできれば、無事ユーザー設定は終了です。

なお、時間が経つとローカルからログインできなくなることがあるので、その場合は、
セキュリティグループ>インバウンド>編集で
SSHのソースで
マイIPを選択し[保存]すると繋がるようになります

続きはこちら
AWSでデプロイするまでの手順②サーバー(EC2インスタンス)環境設定

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

#Ruby + #AWS S3 / publish presigned url ( temporary access URL ) / example codes

Code

# https://www.rubydoc.info/github/aws/aws-sdk-ruby/Aws%2FS3%2FObject:presigned_url

require 'aws-sdk-s3'

credentials = Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'])
s3_resource = Aws::S3::Resource::new(region: ENV['AWS_REGION'], credentials: credentials) # e.g ap-northeast-1

# If no bucket then create
# s3_bucket.create

s3_bucket = s3_resource.bucket(ENV['AWS_BUCKET'])
# <Aws::S3::Bucket:0x00007fd7145b34f0 @arn=nil, @client=#<Aws::S3::Client>, @data=nil, @name="yumainaura", @waiter_block_warned=false>

object = s3_bucket.object("path/to/file_#{Time.now.to_i}.txt")
# #<Aws::S3::Object:0x00007fd7139fb8b0
#  @bucket_name="yumainaura",
#  @client=#<Aws::S3::Client>,
#  @data=nil,
#  @key="path/to/file_1578302095.txt",
#  @waiter_block_warned=false>

object.put(body: 'ABC')

presigned_url = object.presigned_url(:get, expires_in: 10)
# => "https://yumainaura.s3.ap-northeast-1.amazonaws.com/path/to/file_1578302095.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA4INFFTNLCQ6PIZOP%2F20200106%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=20200106T091516Z&X-Amz-Expires=10&X-Amz-SignedHeaders=host&X-Amz-Signature=f413cb0d564b69422f58b988cd2cbe39f4ca6b52e7a5e2975d1e22094706d3a0"

p presigned_url

Browser

access to text file

image

10 seconds later

image

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2921

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

#Ruby + #AWS S3 / パブリックに一時アクセス可能な「resigned URL」を発行するコード例

Ref

【AWS S3】S3 Presigned URLの仕組みを調べてみた - Qiita
https://qiita.com/tmiki/items/87697d3d3d5330c6fc08

Code

# https://www.rubydoc.info/github/aws/aws-sdk-ruby/Aws%2FS3%2FObject:presigned_url

require 'aws-sdk-s3'

credentials = Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'])
s3_resource = Aws::S3::Resource::new(region: ENV['AWS_REGION'], credentials: credentials) # e.g ap-northeast-1

# If no bucket then create
# s3_bucket.create

s3_bucket = s3_resource.bucket(ENV['AWS_BUCKET'])
# <Aws::S3::Bucket:0x00007fd7145b34f0 @arn=nil, @client=#<Aws::S3::Client>, @data=nil, @name="yumainaura", @waiter_block_warned=false>

object = s3_bucket.object("path/to/file_#{Time.now.to_i}.txt")
# #<Aws::S3::Object:0x00007fd7139fb8b0
#  @bucket_name="yumainaura",
#  @client=#<Aws::S3::Client>,
#  @data=nil,
#  @key="path/to/file_1578302095.txt",
#  @waiter_block_warned=false>

object.put(body: 'ABC')

presigned_url = object.presigned_url(:get, expires_in: 10)
# => "https://yumainaura.s3.ap-northeast-1.amazonaws.com/path/to/file_1578302095.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA4INFFTNLCQ6PIZOP%2F20200106%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=20200106T091516Z&X-Amz-Expires=10&X-Amz-SignedHeaders=host&X-Amz-Signature=f413cb0d564b69422f58b988cd2cbe39f4ca6b52e7a5e2975d1e22094706d3a0"

p presigned_url

Browser

access to text file

image

10 seconds later

image

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2922

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

railsでdeviseを使う手順

ユーザー登録の際に使える、deviseという便利なツール。
よく使うことが多いので、簡単な手順をまとめました。

gemを入れる

まずはgemを入れます

gem 'devise'
$ bundle install

deviseをインストールする

次にアプリケーション内にdeviseを読み込みます

$ rails g devise:install

deviseを使って、モデルを作成

今回はユーザーモデルを作ってみます

$ rails g model devise user

必要な追加カラム等があれば、マイグレーションファイルを編集します

t.string :email,              null: false, default: ""
t.string :encrypted_password, null: false, default: ""
#以下のように追加
t.string :name,              null: false, default: ""

その後、application_controllerを編集し、カラム追加を許可してもらいます

before_action :configure_permitted_parameters, if: :devise_controller?

def configure_permitted_parameters
  devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
end
#[:name]のところが、追加したいカラム名。複数指定の場合はカンマ区切りで記入

必要なビューをダウンロード

新規登録ページやログインページなどのビューをダウンロードします

$ rails g devise:views

特殊な処理が必要な場合はdevise用のコントローラーを生成する

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

rails db:migrateでエラー(StandardError: An error has occurred, this and all later migrations canceled:)

$rails db:migrate、または、$rake db:migrateを行った後の謎のエラー。

== 20200107095832 CreateMicroposts: migrating =================================
-- create_table(:microposts)
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:

SQLite3::SQLException: table "microposts" already exists: CREATE TABLE "microposts" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "content" text, "user_id" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL)
・
・以下つづく
・

原因

『この前に行ったコマンドのmigrationで何らかの手違いがあると思います。おそらく、migration処理の途中からエラーが起きていて、テーブルは生成されているが、テーブルを生成したmigrationは未実行まま。といった状態なのかな?と思います。』

解決策

$ rake db:migrate:reset

↑これで、データベースのリセットを行った後。

$ rake db:migrate

↑もう一度!

直った!

これでオッケーかと❗️

このコマンドで、マイグレーションを順に実行し、データベースに変更を加えていくんですね。

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

CapistranoをMacOS X MojaveのRoRにインストールする

Capistrano

前提条件

  • Capistrano 3.11.2
  • slackistrano >= 4.0.1
  • P

    セットアップ

  • Capistranoのインストール

Terminal
$ bundle exec cap install STAGES=staging,production
bundler: command not found: cap
Install missing gem executables with `bundle install`

あら。まあそりゃそうです笑

command not found: capとなった場合

  • この場合、capistranoのGEMがインストールされていない為、GEMインストールを行う

  • Gemfileを開き、編集

Terminal
# Gemfileの編集
$ vi Gemfile
group :staging, :production do
  # Use Capistrano for deployment
  gem 'capistrano',                 '3.11.2'
  gem 'capistrano-rails',           '1.4.0'
  gem 'capistrano-rbenv',           '2.1.4 '
  gem 'capistrano-passenger',       '0.2.0'
  # 今回はSlack通知も行うため、便宜上載せています
  gem 'slackistrano', '>=4.0.1'
end
  • bundle install
Terminal
$ bundle _1.17.2_ install --path vendor/bundle
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
・・・
Fetching capistrano 3.11.2
Installing capistrano 3.11.2
Fetching capistrano-bundler 1.6.0
Installing capistrano-bundler 1.6.0
Fetching capistrano-passenger 0.2.0
Installing capistrano-passenger 0.2.0
Fetching capistrano-rails 1.4.0
Installing capistrano-rails 1.4.0
Fetching capistrano-rbenv 2.1.4
Installing capistrano-rbenv 2.1.4
・・・
Fetching slackistrano 4.0.1
Installing slackistrano 4.0.1
・・・
Bundle complete! 38 Gemfile dependencies, 148 gems now installed.
Bundled gems are installed into `./vendor/bundle`
Post-install message from capistrano-passenger:
==== Release notes for capistrano-passenger ====
passenger once had only one way to restart: `touch tmp/restart.txt`
Beginning with passenger v4.0.33, a new way was introduced: `passenger-config restart-app`

The new way to restart was not initially practical for everyone,
since for versions of passenger prior to v5.0.10,
it required your deployment user to have sudo access for some server configurations.

capistrano-passenger gives you the flexibility to choose your restart approach, or to rely on reasonable defaults.

If you want to restart using `touch tmp/restart.txt`, add this to your config/deploy.rb:

    set :passenger_restart_with_touch, true

If you want to restart using `passenger-config restart-app`, add this to your config/deploy.rb:

    set :passenger_restart_with_touch, false # Note that `nil` is NOT the same as `false` here

If you don't set `:passenger_restart_with_touch`, capistrano-passenger will check what version of passenger you are running
and use `passenger-config restart-app` if it is available in that version.

If you are running passenger in standalone mode, it is possible for you to put passenger in your
Gemfile and rely on capistrano-bundler to install it with the rest of your bundle.
If you are installing passenger during your deployment AND you want to restart using `passenger-config restart-app`,
you need to set `:passenger_in_gemfile` to `true` in your `config/deploy.rb`.
================================================
  • CapistranoとCapifyアプリケーションをインストールする
Terminal
$ bundle exec cap install STAGES=staging,production
mkdir -p config/deploy
create config/deploy.rb
create config/deploy/staging.rb
create config/deploy/production.rb
mkdir -p lib/capistrano/tasks
create Capfile
Capified

階層構造は以下となる

Tree-map
[rails app root]
├─ Capfile
├─ config
│ ├─ deploy
│ │ ├─production.rb
│ │ └─staging.rb
│ └─deploy.rb
└─ lib
  └─capistrano
    └─tasks
  └─tasks

capistranoをインストールしたので、capistrano/setup と capistrano/deploy の準備は一旦はこれでできました

capistrano/deploy タスク

  • デプロイを自動化する、capistranoのタスクです

Deployタスクの一覧

実行順 タスク名 意味
1 deploy Deploy実行
2 deploy:starting Deploy実行開始、サーバー待機中
2.1 deploy:check -
2.1.1 #{scm}:check -
2.1.2 deploy:check:directories -
2.1.3 deploy:check:linked_dirs -
2.1.4 deploy:check:make_linked_dirs -
2.1.5 deploy:check:linked_files -
2.2 deploy:set_previous_revision -
3 deploy:started Deploy開始中
4 deploy:updating Deploy実行中
4.1 #{scm}:create_release -
4.2 deploy:symlink:shared -
4.2.1 deploy:symlink:linked_files -
4.2.2 deploy:symlink:linked_dirs -
5 deploy:updated 更新完了
6 deploy:publishing デプロイ処理中
6.1 deploy:symlink:release -
7 deploy:published デプロイ処理終了
8 deploy:finishing デプロイ終了中、サーバークリーンアップ中
8.1 deploy:cleanup -
9 deploy:finished 処理終了
9.1 deploy:log_revision -
実行順 タスク名 意味
1 deploy:rollback 前回リリースバージョンにロールバック
2 deploy:starting Deploy実行開始、サーバー待機中
3 deploy:started Deploy開始中
4 deploy:reverting 前回リリースバージョンにrevert完了
5 deploy:reverted 前回リリースバージョンにrevert
6 deploy:publishing デプロイ処理中
7 deploy:published デプロイ処理終了
8 deploy:finishing_rollback ロールバック完了
9 deploy:finished 処理終了
実行順 タスク名 意味
1 install Capistranoインストール
(凡例:cap install STAGES=staging,production)

プラグインによるフローの追加

capistrano/rails/assets

  • Assetsのプリコンパイルを行う
インストール
Capfile
require capistrano/rails/assets
実行順 タスク名 意味
5 deploy:updated 更新完了
5.1 deploy:compile_assets 追加される
5.1.1 deploy:assets:precompile 追加される
5.1.2 deploy:assets:backup_manifest 追加される
5.2 deploy:normalize_assets 追加される

capistrano/rails/migrations

  • DBMigrationを行う
インストール
Capfile
require "capistrano/rails/migrations"
実行順 タスク名 意味
5 deploy:updated 更新完了
5.1 deploy:migrate 追加される

capistrano/npm

  • npm installを行う
インストール
Capfile
require "capistrano/rails/migrations"
実行順 タスク名 意味
4 deploy:updating Deploy実行中
4.1 #{scm}:create_release -
4.2 deploy:symlink:shared -
4.2.1 deploy:symlink:linked_files -
4.2.2 deploy:symlink:linked_dirs -
5 npm:install 追加される

前提条件

  • ssh-agentを使用

セットアップ手順

  • Capfile を編集する
Capfile
# Load DSL and set up stages
require "capistrano/setup"

# Include default deployment tasks
require "capistrano/deploy"

# Load the SCM plugin appropriate to your project:
#
# require "capistrano/scm/hg"
# install_plugin Capistrano::SCM::Hg
# or
# require "capistrano/scm/svn"
# install_plugin Capistrano::SCM::Svn
# or
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git

# Include tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
#   https://github.com/capistrano/rvm
#   https://github.com/capistrano/rbenv
#   https://github.com/capistrano/chruby
#   https://github.com/capistrano/bundler
#   https://github.com/capistrano/rails
#   https://github.com/capistrano/passenger
#
# require "capistrano/rvm"
# require "capistrano/rbenv"
# require "capistrano/chruby"
# require "capistrano/bundler"
# require "capistrano/rails/assets"
# require "capistrano/rails/migrations"
# require "capistrano/passenger"
# if you used rbenv
require "capistrano/rbenv"

require 'capistrano/rails'

require 'capistrano/bundler'

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
  • deploy.rb を編集する
config/deploy.rb
# config valid for current version and patch releases of Capistrano
lock "~> 3.11.2"

set :application, "%app name%"
set :repo_url, "git@github.com:****/*****.git"

# Default branch is :master
# ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp

# Default deploy_to directory is /var/www/my_app_name
# set :deploy_to, "/var/www/my_app_name"
set :deploy_to, "/var/www/#{fetch(:application)}"

# Default value for :format is :airbrussh.
# set :format, :airbrussh

# You can configure the Airbrussh format using :format_options.
# These are the defaults.
# set :format_options, command_output: true, log_file: "log/capistrano.log", color: :auto, truncate: :auto

# Default value for :pty is false
# set :pty, true
set :pty, true      # sudoを使用する場合はtrue

# Default value for :linked_files is []
# append :linked_files, "config/database.yml"

# Default value for linked_dirs is []
# append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"

# Default value for default_env is {}
# capistrano用bundleするのに必要
# set :default_env, { path: "/opt/ruby/bin:$PATH" }
set :default_env, { path: "/usr/local/rbenv/shims:/usr/local/rbenv/bin:$PATH" }

# Default value for local_user is ENV['USER']
# set :local_user, -> { `git config user.name`.chomp }

# Default value for keep_releases is 5
# set :keep_releases, 5
set :keep_releases, 5

# Uncomment the following to require manually verifying the host key before first deploy.
# set :ssh_options, verify_host_key: :secure


# Configure ur Slack's Incoming Webhook
# if you want to be disable deployment notifications to a specific stage by setting the :slackistrano configuration variable to false instead of actual settings.
# When you desable slackistrano, comment out the description below.
set :slackistrano, false
# set :slackistrano, {
#  klass:   Slackistrano::CustomMessaging,
#  channel: '#bot-#{fetch(:application)}_deploy',
#  webhook: 'https://hooks.slack.com/services/*/*/*'
#}

# Configure ssh agent
set :ssh_options, {
  keys: %w(~/.ssh/id_rsa),
  forward_agent: true,
  auth_methods: %w(publickey)
}

# Configure 
# デプロイ開始通知タイミングの変更
# デフォルトだと、gitのチェック等のstarting処理後に通知が行くが、それよりタイミングを早めたいため
# 'deploy:starting'の前にすることで、rollbackなのか通常デプロイなのかタスク自体で判別できなくなるため、`deploying`にセットされた値のboolean値でどちらなのか判断している。
# 参考:https://github.com/capistrano/capistrano/blob/v3.11.2/lib/capistrano/tasks/framework.rake#L57
before 'deploy:starting', 'slack:deploy:updating'

# デプロイ失敗時にエラーを変数に保存しておくための処理
# https://github.com/capistrano/capistrano/blob/v3.11.2/lib/capistrano/dsl/task_enhancements.rb#L51
# ここの処理に一行追加しているだけ
module Capistrano
  module TaskEnhancements
    def exit_deploy_because_of_exception(ex)
      warn t(:deploy_failed, ex: ex.message)
      set :deploy_failed_exception, ex # 追加した1行
      invoke "deploy:failed"
      exit(false)
    end
  end
end
  1. 各環境のデプロイ用ファイルの編集
config/deploy/stating.rb
# server-based syntax
# ======================
# Defines a single server with a list of roles and multiple properties.
# You can define all roles on a single server, or split them:

# server "example.com", user: "deploy", roles: %w{app db web}, my_property: :my_value
# server "example.com", user: "deploy", roles: %w{app web}, other_property: :other_value
# server "db.example.com", user: "deploy", roles: %w{db}



# role-based syntax
# ==================

# Defines a role with one or multiple servers. The primary server in each
# group is considered to be the first unless any hosts have the primary
# property set. Specify the username and a domain or IP for the server.
# Don't use `:all`, it's a meta role.

# role :app, %w{deploy@example.com}, my_property: :my_value
# role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value
# role :db,  %w{deploy@example.com}



# Configuration
# =============
# You can set any configuration variable like in config/deploy.rb
# These variables are then only loaded and set in this stage.
# For available Capistrano configuration variables see the documentation page.
# http://capistranorb.com/documentation/getting-started/configuration/
# Feel free to add new variables to customise your setup.



# Custom SSH Options
# ==================
# You may pass any option but keep in mind that net/ssh understands a
# limited set of options, consult the Net::SSH documentation.
# http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start
#
# Global options
# --------------
#  set :ssh_options, {
#    keys: %w(/home/rlisowski/.ssh/id_rsa),
#    forward_agent: false,
#    auth_methods: %w(password)
#  }
#
# The server-based syntax can be used to override options:
# ------------------------------------
# server "example.com",
#   user: "user_name",
#   roles: %w{web app},
#   ssh_options: {
#     user: "user_name", # overrides user setting above
#     keys: %w(/home/user_name/.ssh/id_rsa),
#     forward_agent: false,
#     auth_methods: %w(publickey password)
#     # password: "please use keys"
#   }

set :stage, :staging
set :branch, ENV['GIT_BRANCH'] || `git rev-parse --abbrev-ref HEAD`.strip
set :rails_env, "staging"

# for *****
server "%hostname%", user: `whoami`.strip, roles: %w{web app}
config/deploy/production.rb
# server-based syntax
# ======================
# Defines a single server with a list of roles and multiple properties.
# You can define all roles on a single server, or split them:

# server "example.com", user: "deploy", roles: %w{app db web}, my_property: :my_value
# server "example.com", user: "deploy", roles: %w{app web}, other_property: :other_value
# server "db.example.com", user: "deploy", roles: %w{db}



# role-based syntax
# ==================

# Defines a role with one or multiple servers. The primary server in each
# group is considered to be the first unless any hosts have the primary
# property set. Specify the username and a domain or IP for the server.
# Don't use `:all`, it's a meta role.

# role :app, %w{deploy@example.com}, my_property: :my_value
# role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value
# role :db,  %w{deploy@example.com}



# Configuration
# =============
# You can set any configuration variable like in config/deploy.rb
# These variables are then only loaded and set in this stage.
# For available Capistrano configuration variables see the documentation page.
# http://capistranorb.com/documentation/getting-started/configuration/
# Feel free to add new variables to customise your setup.



# Custom SSH Options
# ==================
# You may pass any option but keep in mind that net/ssh understands a
# limited set of options, consult the Net::SSH documentation.
# http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start
#
# Global options
# --------------
#  set :ssh_options, {
#    keys: %w(/home/rlisowski/.ssh/id_rsa),
#    forward_agent: false,
#    auth_methods: %w(password)
#  }
#
# The server-based syntax can be used to override options:
# ------------------------------------
# server "example.com",
#   user: "user_name",
#   roles: %w{web app},
#   ssh_options: {
#     user: "user_name", # overrides user setting above
#     keys: %w(/home/user_name/.ssh/id_rsa),
#     forward_agent: false,
#     auth_methods: %w(publickey password)
#     # password: "please use keys"
#   }

set :stage, :production
set :branch, "master"
set :rails_env, "production"
  • Slackistranoの設定
lib/slackistrano/custom_messaging.rb
if defined?(Slackistrano::Messaging)
   module Slackistrano
     class CustomMessaging < Messaging::Base

      # Suppress starting message.
      def payload_for_updating

        attachments = []
        # 通常デプロイなのかロールバックなのか場合分け
        title = fetch(:deploying, false) ? 'Deploy start' : 'Rollback start'
        color = fetch(:deploying, false) ? 'good' : '#439FE0'

        # メイン
        attachments << base_attachments(title: title, color: color, fallback: super[:text])

        # gitの差分が取れた場合は差分を、取れなかった場合はgitの直近のlogを表示する
        diff_commits_str = `cap #{fetch(:rails_env)} deploy:pending`
        unless diff_commits_str.delete("\n").length.zero?
          diff_commits = convert_diff_commits(diff_commits_str.split("\n\ncommit "))
          attachments << {
            color:  color,
            title:  "git 差分",
            fields: diff_commits.map{|commit|
              {
                value: commit
              }
            }
          }
        else
          # git log
          attachments << {
            color:  color,
            title:  "git log",
            fields: git_last_commits_fields
          }
        end

        return {attachments: attachments}
      end

      # デプロイ完了
      def payload_for_updated
        attachments = []
        attachments << base_attachments(title: 'Deploy accomplished', color: '#439FE0', fallback: super[:text])

        return {attachments: attachments}
      end

      # ロールバック開始
      def payload_for_reverting
        # ロールバックの開始はデプロイ開始側のタスクで場合分けしている
      end

      # ロールバック完了
      def payload_for_reverted
        attachments = []
        attachments << base_attachments(title: 'Rollback accomplished', color: '#439FE0', fallback: super[:text])

        return {attachments: attachments}
      end

      # デプロイ失敗
      def payload_for_failed
        attachments = []
        attachments << base_attachments(title: 'Deploy failed', color: 'danger', fallback: super[:text])

        # エラー内容を取得
        exception = fetch(:deploy_failed_exception)
        error_message = exception.inspect + "\n" + exception.backtrace.join("\n")

        # エラー内容
        attachments << {
          color: 'danger',
          title: "エラー内容",
          fields: [{
            value: error_message
          }]
        }

        return {attachments: attachments}
      end

      private
        def git_last_commits_fields
          git_last_commit_messages = `git log -5 --date=iso --pretty=format:'%H_|_%an_|_%s_|_%cd' HEAD`.force_encoding('UTF-8').split("\n").map do |message|
            data = message.split('_|_')
            commit_name, pr_number = data[2].scan(/^[\s]*(.*)\s\(\#([^\)]+)\)$/)[0]
            commit_name = data[2] if commit_name.nil?
            {
              hash:        data[0],
              user_name:   data[1],
              commit_name: commit_name,
              pr_number:   pr_number,
              commit_date: data[3]
            }
          end
          git_last_commit_messages.map do |message|
            if message[:pr_number].nil?
              {
                value: "*#{message[:commit_name]}*>\nAuthor: #{message[:user_name]}\nDate:   #{message[:commit_date]}"
              }
            else
              # git@github.com:%organization%/%repo name%.git to github.com/%organization%/%repo name%
              {
                value: "*#{message[:commit_name]}* (<https://#{fetch(:repo_url).split("git@").second.split("/").first.sub(/:/, "/")}/#{fetch(:application)}/pull/|##{message[:pr_number]}>)\nAuthor: #{message[:user_name]}\nDate:   #{message[:commit_date]}"
              }
            end
          end
        end

        def base_attachments(title:, color:, fallback: nil)
          {
            color: color,
            title: "#{application} #{title}",
            fields: [
              {
                title: 'Environment',
                value: stage,
                short: true
              },
              {
                title: 'Branch',
                value: branch,
                short: true
              },
              {
                title: 'Deployer',
                value: deployer,
                short: true
              },
              {
                title: 'Time',
                value: elapsed_time,
                short: true
              }
            ],
            fallback: fallback
          }
        end

        def convert_diff_commits(diff_commits)
          diff_commits.map do |diff_commit|
            lines = diff_commit.split("\n")

            # commit_hash = lines[0]
            author      = lines[1].match(/^Author:\s([^\s]+)/)[1] rescue nil
            date        = Time.parse(lines[2].match(/^Date:\s*(.+)/)[1]) rescue nil
            commit_name, pr_number = lines[4].scan(/^[\s]*(.*)\s\(\#([^\)]+)\)$/)[0] rescue []
            commit_name = lines[4] if commit_name.nil?

            messages = []
            if pr_number.blank?
              messages << "*#{commit_name}*"
            else
              # git@github.com:%organization%/%repo name%.git to github.com/%organization%/%repo name%
              messages << "*#{commit_name}* (<https://#{fetch(:repo_url).split("git@").second.split("/").first.sub(/:/, "/")}/#{fetch(:application)}/pull/#{pr_number}|##{pr_number}>)"
            end
            messages << "Author: <@#{author}>"
            messages << "Date: #{date}"

            messages.join("\n")
          end
        end

        def deployer
          `who -m | awk '{print $1}'`
        end
    end
  end
end

変更の構成は以下の通り

Terminal
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

  modified:   Gemfile
  modified:   Gemfile.lock
  modified:   config/environments/development.rb
  modified:   config/environments/production.rb

Untracked files:
  (use "git add <file>..." to include in what will be committed)

  Capfile
  config/deploy.rb
  config/deploy/
  lib/slackistrano/

ADD

Terminal
$ git add Gemfile*
$ git add config/environments
$ git add Capfile
$ git add config/deploy*
$ git add lib/slackistrano
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

  new file:   Capfile
  modified:   Gemfile
  modified:   Gemfile.lock
  new file:   config/deploy.rb
  new file:   config/deploy/production.rb
  new file:   config/deploy/staging.rb
  modified:   config/environments/development.rb
  modified:   config/environments/production.rb
  new file:   lib/slackistrano/custom_messaging.rb

コミット&push

Terminal
$ git commit -m "capistranoたたき台"
[master 26dbe1b] capistranoたたき台
 9 files changed, 567 insertions(+), 3 deletions(-)
 create mode 100644 Capfile
 create mode 100644 config/deploy.rb
 create mode 100644 config/deploy/production.rb
 create mode 100644 config/deploy/staging.rb
 create mode 100644 lib/slackistrano/custom_messaging.rb

$ git push --set-upstream origin master
Enumerating objects: 23, done.
Counting objects: 100% (23/23), done.
Delta compression using up to 8 threads
Compressing objects: 100% (15/15), done.
Writing objects: 100% (16/16), 9.37 KiB | 3.12 MiB/s, done.
Total 16 (delta 6), reused 0 (delta 0)
remote: Resolving deltas: 100% (6/6), completed with 5 local objects.
To https://github.com/%organization%/%repos name%.git
   578d396..26dbe1b  master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

Slackistrano

  • Capistranoの自動デプロイをChatOpsに組み込みたいので、Incoming Webhookに連携します

セットアップ手順

  • Capfile を編集する
Capfile
# Require the slackistrano need the deploy.rb
require 'slackistrano/capistrano'

NameError: uninitialized constant Slackistrano::CustomMessaging が発生した場合

  • この場合、 Capfile にrequireでGemがロードされていないのが原因の為、Capfileを編集する

  • Capfileを開き、編集(上記セットアップ手順を忘れていることが考えられる)

デバッグ実行

  • 設定が一通り終わったら、次はデバッグ実行でチェックしてみます

  • --dry-run オプションで実行せずにデプロイをシミュレーションします

  • trace オプションでデプロイ実行中の例外スロー(スタックトレース?)をします

Terminal
$ bundle exec cap staging deploy --dry-run --trace
** Invoke staging (first_time)
** Execute staging
** Invoke load:defaults (first_time)
** Execute load:defaults
[Deprecation Notice] `set :scm, :git` is deprecated.
To ensure your project is compatible with future versions of Capistrano,
remove the :scm setting and instead add these lines to your Capfile after
`require "capistrano/deploy"`:
    require "capistrano/scm/git"
    install_plugin Capistrano::SCM::Git
** Invoke rbenv:validate (first_time)
** Execute rbenv:validate
** Invoke rbenv:map_bins (first_time)
** Execute rbenv:map_bins
** Invoke bundler:map_bins (first_time)
** Execute bundler:map_bins
** Invoke deploy:set_rails_env (first_time)
** Execute deploy:set_rails_env
** Invoke deploy:set_linked_dirs (first_time)
** Execute deploy:set_linked_dirs
** Invoke deploy:set_rails_env 
** Invoke deploy (first_time)
** Invoke deploy:git_confirm (first_time)
** Execute deploy:git_confirm
00:00 deploy:git_confirm
      [git:confirm] Skip git confirm
      [git:confirm] Skip git confirm
** Execute deploy
** Invoke deploy:starting (first_time)
** Invoke slack:deploy:starting (first_time)
** Execute slack:deploy:starting
** Invoke slack:deploy:updating (first_time)
** Execute slack:deploy:updating
** Invoke deploy:upload (first_time)
** Execute deploy:upload
00:00 deploy:upload
      01 mkdir -p /var/www/capistrano_sample_app_v1/shared/config
      02 config/database.yml /var/www/capistrano_sample_app_v1/shared/config/database.yml
** Execute deploy:starting
** Invoke deploy:check (first_time)
** Invoke git:check (first_time)
** Invoke git:wrapper (first_time)
** Execute git:wrapper
00:00 git:wrapper
      01 mkdir -p /tmp
      02 #<StringIO:0x00000000025f9838> /tmp/git-ssh-capistrano_sample_app_v1-staging-webmaster-patche.sh
      03 chmod 700 /tmp/git-ssh-capistrano_sample_app_v1-staging-webmaster-patche.sh
      04 #<StringIO:0x0000000002617720> /tmp/git-ssh-capistrano_sample_app_v1-staging-webmaster-patche.sh
** Execute git:check
00:00 git:check
      01 git ls-remote git@github.com:webmaster-patche/capistrano_sample_app_v1.git HEAD
** Execute deploy:check
** Invoke deploy:check:directories (first_time)
** Execute deploy:check:directories
00:00 deploy:check:directories
      01 mkdir -p /var/www/capistrano_sample_app_v1/shared /var/www/capistrano_sample_app_v1/releases
** Invoke deploy:check:linked_dirs (first_time)
** Execute deploy:check:linked_dirs
00:00 deploy:check:linked_dirs
      01 mkdir -p /var/www/capistrano_sample_app_v1/shared/bin /var/www/capistrano_sample_app_v1/shared/log /var/www/capistrano_sample_app_v1/shared/tmp/backup /var/www/capistrano_sample_app_v1/shared/tmp/pids /var/www/capistrano_sample_app_v1/shared/tmp/cache /var/www/capistrano_sample_app_v1/shared/t…
** Invoke deploy:check:make_linked_dirs (first_time)
** Execute deploy:check:make_linked_dirs
00:00 deploy:check:make_linked_dirs
      01 mkdir -p /var/www/capistrano_sample_app_v1/shared/config
** Invoke deploy:check:linked_files (first_time)
** Execute deploy:check:linked_files
** Invoke deploy:set_previous_revision (first_time)
** Execute deploy:set_previous_revision
** Invoke deploy:started (first_time)
** Execute deploy:started
** Invoke deploy:updating (first_time)
** Invoke deploy:new_release_path (first_time)
** Execute deploy:new_release_path
** Invoke git:create_release (first_time)
** Invoke git:update (first_time)
** Invoke git:clone (first_time)
** Invoke git:wrapper 
** Execute git:clone
00:00 git:clone
      The repository mirror is at /var/www/capistrano_sample_app_v1/repo
      The repository mirror is at /var/www/capistrano_sample_app_v1/repo
** Execute git:update
00:00 git:update
      01 git remote set-url origin git@github.com:webmaster-patche/capistrano_sample_app_v1.git
      02 git remote update --prune
** Execute git:create_release
00:00 git:create_release
      01 mkdir -p /var/www/capistrano_sample_app_v1/releases/20200108015441
      02 git archive master | /usr/bin/env tar -x -f - -C /var/www/capistrano_sample_app_v1/releases/20200108015441
** Invoke slack:deploy:updating 
** Execute deploy:updating
** Invoke deploy:set_current_revision (first_time)
** Invoke git:set_current_revision (first_time)
** Execute git:set_current_revision
** Execute deploy:set_current_revision
00:00 deploy:set_current_revision
      01 echo "" > REVISION
** Invoke deploy:symlink:shared (first_time)
** Execute deploy:symlink:shared
** Invoke deploy:symlink:linked_files (first_time)
** Execute deploy:symlink:linked_files
00:00 deploy:symlink:linked_files
      01 mkdir -p /var/www/capistrano_sample_app_v1/releases/20200108015441/config
** Invoke deploy:symlink:linked_dirs (first_time)
** Execute deploy:symlink:linked_dirs
00:00 deploy:symlink:linked_dirs
      01 mkdir -p /var/www/capistrano_sample_app_v1/releases/20200108015441 /var/www/capistrano_sample_app_v1/releases/20200108015441/tmp /var/www/capistrano_sample_app_v1/releases/20200108015441/vendor /var/www/capistrano_sample_app_v1/releases/20200108015441/public
** Invoke deploy:updated (first_time)
** Invoke bundler:install (first_time)
** Execute bundler:install
00:00 bundler:install
      The Gemfile's dependencies are satisfied, skipping installation
      The Gemfile's dependencies are satisfied, skipping installation
** Execute deploy:updated
** Invoke deploy:compile_assets (first_time)
** Invoke deploy:set_rails_env 
** Execute deploy:compile_assets
** Invoke deploy:assets:precompile (first_time)
** Execute deploy:assets:precompile
** Invoke deploy:assets:backup_manifest (first_time)
** Execute deploy:assets:backup_manifest
** Invoke deploy:cleanup_assets (first_time)
** Invoke deploy:set_rails_env 
** Execute deploy:cleanup_assets
** Invoke deploy:normalize_assets (first_time)
** Invoke deploy:set_rails_env 
** Execute deploy:normalize_assets
** Invoke deploy:migrate (first_time)
** Invoke deploy:set_rails_env 
** Execute deploy:migrate
** Invoke deploy:publishing (first_time)
** Execute deploy:publishing
** Invoke deploy:symlink:release (first_time)
** Execute deploy:symlink:release
00:00 deploy:symlink:release
      01 ln -s /var/www/capistrano_sample_app_v1/releases/20200108015441 /var/www/capistrano_sample_app_v1/releases/current
      02 mv /var/www/capistrano_sample_app_v1/releases/current /var/www/capistrano_sample_app_v1
** Invoke deploy:published (first_time)
** Execute deploy:published
** Invoke deploy:finishing (first_time)
** Execute deploy:finishing
** Invoke deploy:cleanup (first_time)
** Execute deploy:cleanup
** Invoke slack:deploy:updated (first_time)
** Execute slack:deploy:updated
** Invoke deploy:finished (first_time)
** Execute deploy:finished
** Invoke deploy:log_revision (first_time)
** Execute deploy:log_revision
00:00 deploy:log_revision
      01 echo "Branch master (at ) deployed as release 20200108015441 by webmaster-patche" >> /var/www/capistrano_sample_app_v1/revisions.log
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】ブックマーク(お気に入り)機能

備忘録です。

掲示板におけるブックマーク機能追加し、ブックマーク一覧ページを作成。
※初学者です。ご指摘あればお願いします!

環境

Rails 5.2.3
mysql 5.7.28
gem font-awesome-sass
gem Sorcery
→installしていない場合、後述のcurrent_userメソッドが使用できません。

ER図

中間テーブル(Bookmark)使用。
中間テーブル...userとboardのidだけを保存するテーブル。
bookmark_ER図.png

User:Board = 1:多
User:Bookmark = 1:多
Board:Bookmark = 1:多

あくまでブックマーク機能実装の記事ですので、UserとBoardモデルはすでに作成されているものとして進行しますのでご注意ください。

実装

それでは実装していきます。

Bookmarkモデル作成

ターミナル
$ rails g model Bookmark user:references board:references

上記コマンドでbookmark.rb(モデルファイル)とxxxxxxxx_create_bookmarks.rb(マイグレーションファイル)が作成されます。

referencesを指定することで外部キー制約がついてインデックスも貼られるので便利です。

bookmark.rb
belongs_to :user
belongs_to :board
validates :user_id, uniqueness: { scope: :board_id } # 追加

uniqueness: trueでモデルレベルで一意性(ユニーク)を保ちます。
Railsガイドバリデーション

xxxxxxx_create_bookmarks.rb
def change
  create_table :bookmarks do |t|
    t.references :user, foreign_key: true, null: false
    t.references :board, foreign_key: true, null: false

    t.timestamps
    t.index [:user_id, :board_id], unique: true # 追加
  end
end

t.index [:user_id, :board_id], unique: trueと書き足してDBレベルで一意性(ユニーク)を保ちます。

モデルとDB両方に一意性を保つことでより安全性が高まります。
ここでいう一意性とは1人のユーザーが同じ投稿を複数回bookmarkできないようにすることです。

ターミナル
$ rails db:migrate

上記コマンド実行で中間テーブル完成!

UserモデルとBoardモデルのアソシエーション

user.rb
class User < ApplicationRecord
  has_many :boards, dependent: :destroy
  has_many :bookmarks, dependent: :destroy
  has_many :bookmark_boards, through: :bookmarks, source: :board
end

dependent: :destroyでユーザーが削除されたとき、そのユーザーに関連する情報を削除するよう紐付けます。

注目すべきは

has_many :bookmark_boards, through: :bookmarks, source: :board

です。
has many throughオプションでユーザーがブックマークした投稿を直接アソシエーションで取得できます。
後ほどboard_controller.rb実装時に使用しますので、覚えておいてください。

sourceでは参照するモデルを指定しています。

board.rb
class Board < ApplicationRecord
  belongs_to :user
  has_many :bookmarks, dependent: :destroy

# boardのお気に入り判定 → vies側で呼び出し
  def bookmark_by?(user)
    bookmarks.where(user_id: user.id).exists?
  end
end

こちらで注目するのは

def bookmark_by?(user)
  bookmarks.where(user_id: user.id).exists?
end

このインスタンスメソッドです。
ここではbookmark_by?(user)というアクションを明記してcurrent_userに特定の投稿がブックマークされているかされていないかの判定をするメソッドを記述しています。

後々記述しますが引数の(user)にはcurrent_userが引き渡されています。

インスタンスメソッド...インスタンスに使用するメソッド。

ルーティング設定

routes.rb
resources :boards, shallow: true do
  resource :bookmarks, only: %i[create destroy]
  get :bookmarks, on: :collection
end

ブックマーク一覧ページをboards/bookmarksにルーティンングを設定したいのでcollectionオプションを使用します。
ネスト(入れ子)すると外部キーのboard_idを取得するのに容易になります。
また単数形のメソッドであるresourceとしたのはbookmarkのidが必要ないため。

shallowオプションについてはこちら
resources を nest するときは shallow を使うと幸せになれる

Bookmarkコントローラー作成

ターミナル
$ rails g controller bookmarks create destroy

上記コマンドでbookmarks_controller.rbが作成されます。

bookmarks_controller.rb
class BookmarksController < ApplicationController
  def create
    bookmark = current_user.bookmarks.build(board_id: params[:board_id])
    bookmark.save!
    redirect_to boards_path, success: t('.flash.bookmark')
  end

  def destroy
    current_user.bookmarks.find_by(board_id: params[:board_id]).destroy!
    redirect_to boards_path, success: t('.flash.not_bookmark')
  end

ここの記述でリクエストが送られた際のブックマーク機能を管理しています。

create
bookmark = current_user.bookmarks.build(board_id: params[:board_id])

ここでcurrent_userのBookmarkインスタンスを生成(buildはnewと同じ意味で、アソシエーションしながらインスタンスを作成するときに形式的に使用される模様)

create
bookmark.save!
redirect_to boards_path, success: t('.flash.bookmark')

登録して、リダイレクト先を指定。
success以降はフラッシュメッセージ関連です。

destroy
current_user.bookmarks.find_by(board_id: params[:board_id]).destroy!
redirect_to boards_path, success: t('.flash.not_bookmark')

ここではブックマークしているユーザー情報を取得、ブックマークを解除して、リダイレクト先を指定。

destroysaveではなくdestroy!save!にしているのは条件分岐使わない場合は失敗したときに例外を発生させて処理を終了させるのがベターと見聞きしたのでこの記述にしています。

boards_controller実装

boards_controller.rb
def index
    @boards = Board.all.includes(:user).recent
end

.
.
.

def bookmarks
    @boards = current_user.bookmark_boards.includes(:user).recent
end

今回関係あるのはこの2つのアクションだけなので他は割愛します。
上から見ていきます。

def index
    @boards = Board.all.includes(:user).recent
end

indexアクションでは@boardsインスタンス変数にboardsテーブルから全てのデータを取得しています。

includesはn+1問題解消のため。
Rails「N+1問題」超分かりやすい解説・解決方法【includesを使おう】

recentはScopeを用いて可読性を保つために記述しているもので、掲示板を降順で表示するためのオプションをモデルで設定しています。
今回は関係ないので削除しても問題ありません。

def bookmarks
    @boards = current_user.bookmark_boards.includes(:user).recent
end

bookmarksアクションでは@boardsインスタンス変数にログインしているユーザー(current_user)がブックマークしている全てのデータを取得しています。

current_user.bookmark_boardsこういう書き方でユーザーがブックマークしてる投稿を取得できるのは先ほどhas many throughでリレーションを組んだことによる効果です。

ビュー実装

views/boards/index.html.erb
<div class="row d-flex">
  <%= render @boards %>
</div>

パーシャルを読み込んで

views/boards/_board.html.erb
<% if current_user.own_board?(board) %>
  <div style='display: inline; float: right;'>
    <%= link_to icon('fas', 'pen'), edit_board_path(board), id: :"button-edit-#{board.id}" %>
    <%= link_to icon('fas', 'trash-alt'), board_path(board), id: :"button-delete-#{board.id}",method: :delete, data: { confirm: '削除してよろしいですか?' } %>
  </div>
<% else %>
  <div style='display: inline; float: right;'>
     <%= render 'boards/bookmark_area', board: board %>
  </div>
<% end %>

<% if current_user.own_board?(board) %>で投稿をログインユーザーか他ユーザーかを条件分岐させてます。
own_board?(board)は自身で作成したインスタンスメソッドなので、このままコピペしても動きません。

own_board?メソッド中身はこちら↓
[Rails]ログインidと投稿者が一致した時のみ編集・削除アイコンを表示させる条件式をモデルに書き出す

views/boards/_bookmark_area.html.erb
<% if board.bookmark_by?(current_user) %> 
  <%= render 'boards/bookmark', board: board %>
<% else %>
  <%= render 'boards/unbookmark', board: board %>
<% end %>

bookmark_by?は先ほどboard.rbで定義したブックマーク判定をするインスタンスメソッドです。引数はログインユーザーであるcurrent_userのインスタンスを引き渡しています。

views/boards/_bookmark.html.erb
<%= link_to icon('fas', 'star'), board_bookmarks_path(board.id), method: :delete, id: :"js-bookmark-button-for-board-#{board.id}" %>
views/boards/_unbookmark.html.erb
<%= link_to icon('far', 'star'), board_bookmarks_path(board.id), method: :post, id: :"js-bookmark-button-for-board-#{board.id}" %>

参考記事

【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】

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

【Rails】Bookmark機能

備忘録です。

掲示板におけるブックマーク機能追加し、ブックマーク一覧ページを作成。
※初心者です。ご指摘あればお願いします!

環境

Rails 5.2.3
mysql 5.7.28
gem font-awesome-sass
gem Sorcery
→installしていない場合、後述のcurrent_userメソッドが使用できません。

ER図

中間テーブル(Bookmark)使用。
中間テーブル...userとboardのidだけを保存するテーブル。
bookmark_ER図.png

User:Board = 1:多
User:Bookmark = 1:多
Board:Bookmark = 1:多

あくまでブックマーク機能実装の記事ですので、UserとBoardモデルはすでに作成されているものとして進行しますのでご注意ください。

実装

それでは実装していきます。

Bookmarkモデル作成

ターミナル
$ rails g model Bookmark user:references board:references

上記コマンドでbookmark.rb(モデルファイル)とxxxxxxxx_create_bookmarks.rb(マイグレーションファイル)が作成されます。

referencesを指定することで外部キー制約がついてインデックスも貼られるので便利です。

bookmark.rb
belongs_to :user
belongs_to :board
validates :user_id, uniqueness: { scope: :board_id } # 追加

uniqueness: trueでモデルレベルで一意性(ユニーク)を保ちます。
Railsガイドバリデーション

xxxxxxx_create_bookmarks.rb
def change
  create_table :bookmarks do |t|
    t.references :user, foreign_key: true, null: false
    t.references :board, foreign_key: true, null: false

    t.timestamps
    t.index [:user_id, :board_id], unique: true # 追加
  end
end

t.index [:user_id, :board_id], unique: trueと書き足してDBレベルで一意性(ユニーク)を保ちます。

モデルとDB両方に一意性を保つことでより安全性が高まります。
ここでいう一意性とは1人のユーザーが同じ投稿を複数回bookmarkできないようにすることです。

ターミナル
$ rails db:migrate

上記コマンド実行で中間テーブル完成!

UserモデルとBoardモデルのアソシエーション

user.rb
class User < ApplicationRecord
  has_many :boards, dependent: :destroy
  has_many :bookmarks, dependent: :destroy
  has_many :bookmark_boards, through: :bookmarks, source: :board
end

dependent: :destroyでユーザーが削除されたとき、そのユーザーに関連する情報を削除するよう紐付けます。

注目すべきは

has_many :bookmark_boards, through: :bookmarks, source: :board

です。
has many throughオプションでユーザーがブックマークした投稿を直接アソシエーションで取得できます。
後ほどboard_controller.rb実装時に使用しますので、覚えておいてください。

sourceでは参照するモデルを指定しています。

board.rb
class Board < ApplicationRecord
  belongs_to :user
  has_many :bookmarks, dependent: :destroy

# boardのお気に入り判定 → vies側で呼び出し
  def bookmark_by?(user)
    bookmarks.where(user_id: user.id).exists?
  end
end

こちらで注目するのは

def bookmark_by?(user)
  bookmarks.where(user_id: user.id).exists?
end

このインスタンスメソッドです。
ここではbookmark_by?(user)というアクションを明記してcurrent_userに特定の投稿がブックマークされているかされていないかの判定をするメソッドを記述しています。

後々記述しますが引数の(user)にはcurrent_userが引き渡されています。

インスタンスメソッド...インスタンスに使用するメソッド。

ルーティング設定

routes.rb
resources :boards, shallow: true do
  resource :bookmarks, only: %i[create destroy]
  get :bookmarks, on: :collection
end

ブックマーク一覧ページをboards/bookmarksにルーティンングを設定したいのでcollectionオプションを使用します。
ネスト(入れ子)すると外部キーのboard_idを取得するのに容易になります。
また単数形のメソッドであるresourceとしたのはbookmarkのidが必要ないため。

shallowオプションについてはこちら
resources を nest するときは shallow を使うと幸せになれる

Bookmarkコントローラー作成

ターミナル
$ rails g controller bookmarks create destroy

上記コマンドでbookmarks_controller.rbが作成されます。

bookmarks_controller.rb
class BookmarksController < ApplicationController
  def create
    bookmark = current_user.bookmarks.build(board_id: params[:board_id])
    bookmark.save!
    redirect_to boards_path, success: t('.flash.bookmark')
  end

  def destroy
    current_user.bookmarks.find_by(board_id: params[:board_id]).destroy!
    redirect_to boards_path, success: t('.flash.not_bookmark')
  end

ここの記述でリクエストが送られた際のブックマーク機能を管理しています。

create
bookmark = current_user.bookmarks.build(board_id: params[:board_id])

ここでcurrent_userのBookmarkインスタンスを生成(buildはnewと同じ意味で、アソシエーションしながらインスタンスを作成するときに形式的に使用される模様)

create
bookmark.save!
redirect_to boards_path, success: t('.flash.bookmark')

登録して、リダイレクト先を指定。
success以降はフラッシュメッセージ関連です。

destroy
current_user.bookmarks.find_by(board_id: params[:board_id]).destroy!
redirect_to boards_path, success: t('.flash.not_bookmark')

ここではブックマークしているユーザー情報を取得、ブックマークを解除して、リダイレクト先を指定。

destroysaveではなくdestroy!save!にしているのは条件分岐使わない場合は失敗したときに例外を発生させて処理を終了させるのがベターと見聞きしたのでこの記述にしています。

boards_controller実装

boards_controller.rb
def index
    @boards = Board.all.includes(:user).recent
end

.
.
.

def bookmarks
    @boards = current_user.bookmark_boards.includes(:user).recent
end

今回関係あるのはこの2つのアクションだけなので他は割愛します。
上から見ていきます。

def index
    @boards = Board.all.includes(:user).recent
end

indexアクションでは@boardsインスタンス変数にboardsテーブルから全てのデータを取得しています。

includesはn+1問題解消のため。
Rails「N+1問題」超分かりやすい解説・解決方法【includesを使おう】

recentはScopeを用いて可読性を保つために記述しているもので、掲示板を降順で表示するためのオプションをモデルで設定しています。
今回は関係ないので削除しても問題ありません。

def bookmarks
    @boards = current_user.bookmark_boards.includes(:user).recent
end

bookmarksアクションでは@boardsインスタンス変数にログインしているユーザー(current_user)がブックマークしている全てのデータを取得しています。

current_user.bookmark_boardsこういう書き方でユーザーがブックマークしてる投稿を取得できるのは先ほどhas many throughでリレーションを組んだことによる効果です。

ビュー実装

views/boards/index.html.erb
<div class="row d-flex">
  <%= render @boards %>
</div>

パーシャルを読み込んで

views/boards/_board.html.erb
<% if current_user.own_board?(board) %>
  <div style='display: inline; float: right;'>
    <%= link_to icon('fas', 'pen'), edit_board_path(board), id: :"button-edit-#{board.id}" %>
    <%= link_to icon('fas', 'trash-alt'), board_path(board), id: :"button-delete-#{board.id}",method: :delete, data: { confirm: '削除してよろしいですか?' } %>
  </div>
<% else %>
  <div style='display: inline; float: right;'>
     <%= render 'boards/bookmark_area', board: board %>
  </div>
<% end %>

<% if current_user.own_board?(board) %>で投稿をログインユーザーか他ユーザーかを条件分岐させてます。
own_board?(board)は自身で作成したインスタンスメソッドなので、このままコピペしても動きません。
<% current_user == board.board.id %>で上記判断ができると思います。

views/boards/_bookmark_area.html.erb
<% if board.bookmark_by?(current_user) %> 
  <%= render 'boards/bookmark', board: board %>
<% else %>
  <%= render 'boards/unbookmark', board: board %>
<% end %>

bookmark_by?は先ほどboard.rbで定義したブックマーク判定をするインスタンスメソッドです。引数はログインユーザーであるcurrent_userのインスタンスを引き渡しています。

views/boards/_bookmark.html.erb
<%= link_to icon('fas', 'star'), board_bookmarks_path(board.id), method: :delete, id: :"js-bookmark-button-for-board-#{board.id}" %>
views/boards/_unbookmark.html.erb
<%= link_to icon('far', 'star'), board_bookmarks_path(board.id), method: :post, id: :"js-bookmark-button-for-board-#{board.id}" %>

参考記事

【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】

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

Pay.jpとRailsで決済機能実装してみた。

投稿経緯

payjpの実装記事そのものはたくさんあるのですが、hamlで書かれてるのがほとんど。
正直僕はまだslimhamlのような短縮系での書き方がわからず手間取ったので、今回通常盤での記載で投稿しようと思いました。

実装

APIキー取得

スクリーンショット 2020-01-07 15.35.24.png

1. Payjpにログイン

https://pay.jp/login
※アカウントがなければ↑からも作成可能。

2. ログイン後APIをクリック

クリック後が写真の画面になります。
大事な情報になるので、基本的には第三者に見せないようにしましょう。

gemの導入

1.Gemfileに記載する。

Gemfile
# 決済
gem 'payjp'
2.インストール
ターミナル
bundle install

コントローラー

1.決済情報アクションを作成
pays.controller.rb
 def index
 end

 def pay
    Payjp.api_key = 'sk_test_c6a7dfaf173ad9733cd904e9'
    Payjp::Charge.create(
      #amountは値段を記載
      amount: 3500, 
      card: params['payjp-token'],
      currency: 'jpy'
    )
  end

ActionController::InvalidAuthenticityTokenが出た場合

protect_from_forgery except: :payを一番上に記載してください。
有効ではないトークンみたいな感じで弾いてしまうみたいなので、
例外としてpayアクションを指定して通すようにします。

pays.controller.rb
 protect_from_forgery except: :pay

 def index
 end

 def pay
    Payjp.api_key = 'sk_test_c6a7dfaf173ad9733cd904e9'
    Payjp::Charge.create(
      #amountは値段を記載
      amount: 3500, 
      card: params['payjp-token'],
      currency: 'jpy'
    )
  end

View

1.Payjoが用意しているUIを使用する

data-keyの箇所は先ほどのPayjpのAPIに記載されいるものを入れる。
※今回は簡単な導入方法になりますので、セキュリティー面では直かきはよくないみたいなので、本番でやる場合は違う場所書いて参照するようにお願いします。

index.html.erb
<h1>決済画面</h1>
<form action="/pay" method="post">
  <script src="https://checkout.pay.jp/" class="payjp-button" data-key="テスト公開鍵"></script>
</form>

2.トークン

トークンを作成するために必要なものになりますので、indexの一番下に書きます。

index.html.erb
<script>
  $(document).on('turbolinks:load', function() {
      var form = $("#charge-form");
      Payjp.setPublicKey('pk_test_0383a1b8f91e8a6e3ea0e2a9');

      $("#charge-form").on("click", "#submit-button", function(e) {
        e.preventDefault();
        form.find("input[type=submit]").prop("disabled", true);
        var card = {
            number: parseInt($("#payment_card_no").val()),
            cvc: parseInt($("#payment_card_security_code").val()),
            exp_month: parseInt($("#payment_card_expire_mm").val()),
            exp_year: parseInt($("#payment_card_expire_yy").val())
        };
        Payjp.createToken(card, function(s, response) {
          if (response.error) {
            alert("error")
            form.find('button').prop('disabled', false);
          }
          else {
            $(".number").removeAttr("name");
            $(".cvc").removeAttr("name");
            $(".exp_month").removeAttr("name");
            $(".exp_year").removeAttr("name");

            var token = response.id;
            $("#charge-form").append($('<input type="hidden" name="payjp_token" class="payjp-token" />').val(token));
            $("#charge-form").get(0).submit();
          }
        });
      });
    });
</script>

ルーティング

今回はわかりやすく/payだけのURLで作成します。
変更する場合はindex.html.erbのformのaction="~"で場所を買えてください

config/routes.rb
post '/pay', to: 'pays#pay'

完成

index.html.erbにカードで支払うボタンが出てるのそれをクリック
スクリーンショット 2020-01-07 16.11.03.png
すると入力フォームが出現しますので、記入してカードで支払うボタンをクリック

すると元のページへリダイレクトするので、次はpayjpに移動
スクリーンショット 2020-01-07 16.13.23.png
サイドバーの売上をクリックすると
先ほどの金額で支払済みと確認できましたね。

まとめ

今回はかなり簡略的に書きましたので、これをもとに各々に合わせた形にアレンジしていっていただけたらと思いまいす。

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

haml-railsでerbファイルをhamlに変換する方法

個人アプリ開発において新しく立ち上げる度に何度も調べてしまうので備忘録として残す。

結論

このgemをGemfileに記述

Gemfile
gem "haml-rails"

bundle installを実行

ターミナル
$ bundle install #bundle でも可

下記コマンドを実行し、erbファイルをhamlに変換。

ターミナル
$ rails haml:erb2haml

元のerbファイルを削除するかどうかを聞かれる。不要な場合はyを選択。

ターミナル
Would you like to delete the original .erb files? (This is not recommended unless you are under version control.) (y/n)
y
Deleting original .erb files.
--------------------------------------------------------------------------------
Task complete!
No .erb files found. Task will now exit.

hamlで開発を行う事の利点とは

hamlはdivタグ自体を書く必要がなく、また各タグを閉じる必要もない為、コード量の削減、簡略化ができる。rubyも使用可。
インデントによる管理である事から、誰が書いても似たような構造になり、可読性が高い。
逆にインデントが少しでもずれるとすぐエラーを吐くため慣れるまでが大変。

rails newを叩く際にデフォルトで生成されるerbファイルはhtmlとほとんど同じ記述で書けて且つ、rubyも実行できる。
ただhamlと違いタグ1つ1つに対しての閉じタグが必要になる為、コードが冗長になりやすい。
学習し始めのうちは視覚的に理解しやすいので使うのも有り。

まとめ

爆速で開発を行いたいのであれば時間が掛かっても少しずつhamlに慣れていくのが良いであろう。
似たような書き方でslimというのもあるが、基本的な書き方はhamlと変わらず、2020年1月現在ではhamlの方が若干使用比率が高いようなので、現場に合わせ必要に応じて学ぶと良いのではないだろうか。

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

配列の中にネストしたハッシュがある場合の取り出し方

課題で配列の中にネストされたハッシュを取り出すという問題が出題され時間内に課題を解けなかったので健忘禄として。

test.rb
user_data = [
  {
    user: {
        profile: {
            name: 'George'
        }
    }
  },
  {
    user: {
        profile: {
          name: 'Alice'
        }
    }
  },
  {
    user: {
        profile: {
            name: 'Taro'
        }
    }
  }
]

この配列の中にあるユーザーのnameの値を取り出して一覧表示をするという内容で

George
Alice
Taro

このように出力させるには

test.rb
puts 変数名[:key1][:key2][:key3]

のように配列ないのキーに個別にアクセスしていかなければならないようで、
課題のように配列の中にuserが3つありその構造が同じであればeach文で上記の処理を個別に行ってあげれば良い。

test.rb
user_data.each do |user|
  puts user[:user][:profile][:name]
end

ネストされたハッシュが配列の中にあるデータをみるのが初めてで検索の仕方も悪かったのもあり時間内に解決方法を見つけられませんでした。
勉強不足ですね。

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

Mix-inはやめてくれ。「関数的モジュール」だ!

TL;DR

  • Mix-inは危険な機能だ
  • Mix-inは必要なシーンにだけ使おう
  • 関数だけを提供するだけのモジュールを作るのが、弊害が少なくシンプル

Mix-in は弊害が多い

Mix-inはRubyの基本的な機能であり、入門書でも序盤で紹介されます。

それゆえか単に処理をクラス間で共有するためだけにMix-inを使おうとしているのをしばしば見かけます。

実際には、Mix-inにはうかつに使うと後々痛い目に会う機能であり、大抵のケースではMix-inを使うのは適切ではありません

落とし穴1: インスタンス変数やメソッドが上書きされる

includeがやっているのは、「単にMix-inのメソッドをクラスにコピーする」ことだけです1

なので同じ名前のメソッドをクラス側で定義すると、Mix-inのメソッドを警告なしに上書きしてしまいます。同様にMix-inのインスタンス変数を、クラス側で参照したり変更したりできてしまいます。

module HttpRequestable
  def get(uri)
    request(:get, uri)
  end

  private

  def request(http_method, uri)
    # HTTPリクエストする処理
  end
end

class StoreAPI
  include HttpRequestable

  ...

  # 商品の入荷をリクエストする機能を追加しよう!
  # (実はHttpRequestableのメソッドを上書きしてしまっている)
  def request(product_id, count)
  end
end

落とし穴2: メソッドの関係が複雑になる

クラスからMix-inのメソッドを呼び出せるだけでなく、Mix-inからクラスのメソッドを呼び出したり、Mix-inから別のMix-inのメソッドを呼び出すことができるため、

クラスのメソッド => Mix-in Aのメソッド => Mix-in Bのメソッド => クラスのメソッド => ...

のような複雑な参照関係が生じてしまうことがあります。

呼び出しが適切に管理されていればTemplate Method パターンと呼べるのですが、

おっ、親クラスの〇〇っていうメソッドを呼べば動かせるぞ。よし!!

と、深く考えずに呼んでしまうことが、稀によくあります。

落とし穴3: メソッドの定義場所が分かりにくい

最近はIDEが良くなったので問題になりにくいのですが、includeするモジュールが増えてくると、メソッドの定義箇所がわかりにくくなりがちです。

RailsのActionMailer::Baseには12個のモジュールがincludeされているのですが、この中からrelative_url_rootの定義箇所をIDEなしで見つけるのは一苦労です。

落とし穴4: 確信が持てない

最大の問題は、上記のような落とし穴を踏んでいないか確信を持てないことです。Rubyは動的言語なので、静的なチェックが難しく、

これは private メソッドだから、変更しても安全だな!!

と思っていたら、実はMix-inで誤って呼び出していないとも限らないのです。

そこで「関数的モジュール」

(以下の「関数的モジュール」は私の造語です。もっと適切な用語があるのかもしれませんが、調べ方が悪いのか見つからなかったので、ここでは「関数的モジュール」と呼びます)

関数的モジュールとは、特異メソッドだけを提供するモジュールのことです。

module HttpRequestable
  class << self
    def get(uri)
      request(:get, uri)
    end

    private

    def request(http_method, uri)
      # HTTPリクエストする処理
    end
  end
end

使うときは、includeせずメソッドを直接呼び出します。

class StoreApi
  # メソッド名が被っても大丈夫!!
  def request 
    HttpRequestable.get(uri)
  end
end

関数的モジュールでは、Mix-inのような問題は起きません。

  • インスタンス変数を使わない(使えない2
  • メソッドがコピーされることが無い
  • クラス側のメソッドを呼び出すことがない(できない)
  • どのモジュールのメソッドを呼んでいるかは明らか

それでもMix-in を使うケース

そうはいっても、Mix-inを定義するのが便利なケースもあります。

Mix-inを定義する際にはTemplate Method パターンとして正しく定義することを心がけましょう。

具体的には・・・

  • Mix-inがクラスのどのメソッドを呼ぶか明らかにする
  • クラスからはMix-inのどのメソッドを呼んでよいのか明らかにする
  • インスタンス変数を避ける
  • privateメソッドを避ける(RubyのprivateはC++やJavaのprivateと異なることに気をつける)

  1. 掘り下げるともっと複雑でしょうが、基本的な理解としては、「メソッドをコピーするだけ」。 

  2. インスタンス変数を使うこともできるが、Mix-inとは振る舞いが異なるので、Mix-inでのような問題は起きません。 

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

無料で特定アカウント群の最新ツイートを自動リツイートする方法

手記として残しておく。
このプログラムの特徴は、

  • 無料で
  • 複数のアカウントのツイートを
  • 1時間ごとに
  • 自動リツイート

してくれること。
なんで作ろうと思ったかというと、Twitterのパスワードを組織内で使いまわして欲しくなかった && リツイートが面倒だったから。
ちなみに、Cronとか分からなかったので、HerokuのAuto Scheduleを使った。
GitHub レポジトリ→ https://github.com/tsuchiura-1st-hs-ec/Ichikosai-Auto-Retweeting

使ったもの

  • Ruby
    • Twitter gem
  • Heroku
  • Twitter Developer Account

この中でも、Twitter Developer Accountは、審査があるので早く通しておいた方がいい。

Consumer API keys & API secret & Access token & access token secretを取ってくる

Twitter Developer Accountで取ってくる。

twitter gemをインストール

% gem i twitter

でTwitter gemをインストール。

rubyファイルを作る

% touch retweet.rb

で作って、

retweet.rb
require 'twitter'

# Config
@client = Twitter::REST::Client.new do |config|
  config.consumer_key        = ENV["CONSUMER_KEY"]
  config.consumer_secret     = ENV["CONSUMER_SECRET"]
  config.access_token        = ENV["CONSUMER_TOKEN"]
  config.access_token_secret = ENV["CONSUMER_TOKEN_SECRET"]
end

# PRODUCTION ENVIRONMENT -----------------------------------
@client.list_timeline("ichikosai", "list").each do |tweet|
    if tweet.user.screen_name != "ichikosai" && !tweet.retweeted? && !tweet.full_text.include?("匿名質問") # Avoidance of a specified word
        @client.retweet(tweet.id)
    end
end

Herokuで環境変数に上のAPI Keysなどの4つを設定する。
これで @ichikosai のリスト「list」のタイムラインに流れてくるツイートをキャッチして、リツイートしてくれる。
(匿名質問を除いているのは、マシュマロの広告を避けるため。)

ここで

% ruby retweet.rb

としてあげると、リツイートされると思う。(ローカルに環境変数を設定してね)

定期的にこのコードを実行したい

Heroku Auto Scheduleを使う。
Herokuのアドオンで探して、一時間毎にコードを実行するように設定する。(無料で使えるのが最大で一時間毎。それ以上の短いスパンは有料)
以上。

まとめ

ぜひ文化祭に来てね!
文化祭公式Twitterアカウント→ https://twitter.com/ichikosai

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

Deviseを使ってController側からログアウトさせる

1. 経緯

userテーブルにflgを持たせて、0以外の人はログインさせたくないなぁ・・・
ということで調べてみたところ以下の内容でできました:raised_hands:

2. 結果

appllication_controller.rb
  before_action :sign_out_user,  if: :user_signed_in?
  protected
    # ログイン後のパス
    def after_sign_in_path_for(resource)
      root_path
    end
    # flgが0以外はログアウトさせる
    def sign_out_user
      sign_out_and_redirect(current_user) if current_user.flg == 0
    end

3. 詳しい内容

4. ネットの記事

ネットの記事見てると以下の内容とかもあったんですが、サインアウトはHTTPリクエストからしか
受け付けないみたいで以下の書き方をしてもダメでした・・・。

redirect_to :controller=>"user/sessions", :action=>"destroy"

4. 最後に

いいね!もらえると励みになるのでよろしくお願いします!:v:

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

Rubyの公式リファレンスで調べたいとき

Rubyには公式リファレンス( https://docs.ruby-lang.org/ ) がありますが検索ができません。

おまけにググってみると、非公式のものばかりヒットします。

Screenshot from 2020-01-07 10-43-18.png

解決策

siteオプションをつけて検索します。

attr_writer site:docs.ruby-lang.org で検索してみると…

Screenshot from 2020-01-07 10-51-10.png

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

条件付きバリデーション(特定の状況においてバリデーションを発生させる方法)

実現したいこと

PostモデルのFormにてGenreモデルのnameはuniquenessかつnameは空でもバリデーションに引っかからないようにしたい。そして空のデータは表示させたくない。

整理すると、
1. カラムはuniqueness、しかしカラムが空であった場合でもデータ送信ができる。
2. 空の状態を保存したものは出現させない。

課題
カラムが空であった場合データ送信ができる=空の場合uniquenessのバリデーションには引っかからないようにする。

解決策
条件付きバリデーションを作成する

前提

新規投稿を作るformに置いてPostモデルとGenreモデルの2つのデータ送信を同時に行いたい。

Formの一部(ここでPostモデルのFormでGenreモデルのデータも同時に送信)

<%= fields_for @genre, :genres do |form| %>
        <%= form.label :新しくジャンルを作る %>
        <%= form.text_field :name, class:"form-control" %>
      <% end %>

Form全体

<div class="container">
  <%= form_with(model: [:mypage, post], local: true) do |form| %>
    <% if post.errors.any? %>
      <div id="error_explanation">
        <h2><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h2>

        <ul>
          <% post.errors.full_messages.each do |message| %>
            <li><%= message %></li>
          <% end %>
        </ul>
      </div>
    <% end %>
    <div class="form-group">
      <%= form.text_field :title, class:"form-control", placeholder:"タイトル" %>
    </div >
    <div class="form-group">
      <%= form.text_area :body, class:"form-control", placeholder:"自分の辞書をマークダウン記法で記入してみよう" %>
    </div>

    <div class="field">
      <%= form.label :status %>
      <%= form.select :status, Post.statuses.keys,class:"form-control" %>
    </div>

    <div class="field">
      <%= form.label :posted_at %>
      <%= form.datetime_select :posted_at %>
    </div>

    <div class="form-group">
      <%= form.text_area :reason, class:"form-control", placeholder:"メモした経緯を書いてみよう" %>
    </div>

    <div class="field">
      <%= form.label :rank %>
      <%= form.select :rank, Post.ranks.keys, class:"form-control" %>
    </div>

    <div class="form-group">
      <%= form.label :summary %>
      <%= form.text_field :summary, class:"form-control" %>
    </div>

    <div class="form-group">
      <%= form.label :URL %>
      <%= form.text_field :url, class:"form-control" %>
    </div>

    <%= form.label :genre, 'ジャンル' %>
      <%= form.collection_check_boxes(:genre_ids, current_user.genres.where.not(name:""), :id, :name) do |genre| %>
        <%= genre.label do %>
          <%= genre.check_box %>
          <%= genre.text %>
        <% end %>
      <% end %><br>
      <%= fields_for @genre, :genres do |form| %>
        <%= form.label :新しくジャンルを作る %>
        <%= form.text_field :name, class:"form-control" %>
      <% end %>

    <div class="actions">
      <%= form.submit class:"btn btn-info" %>
    </div>
  <% end %>
</div>


Genreモデルでの条件付きバリデーションの作成
これによってから以外でuniquenessが発動するようになった

validates :name, uniqueness: true, unless: :name_check?
def name_check?
    return true if self.name == ""
  end

空は表示させない
where.not

<%= form.collection_check_boxes(:genre_ids, current_user.genres.where.not(name:""), :id, :name) do |genre| %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ruby: 個人でよく使うtrue falseを返す正規表現の書き方

※備忘録

半角小文字の英文字かどうかを判定

/\A[a-z]+\z/.match?("kaye")
=> true

半角大文字の英文字かどうかを判定

/\A[A-Z]+\z/.match?("KAYE")
=> true

半角数字かどうかを判定

/\A[0-9]+\z/.match?("21")
=> true

半角英文字と半角数字かどうかを判定

/\A[a-zA-Z0-9]+\z/.match?("kayeN21")
=> true

半角英文字と半角数字かどうかを判定

/\A[a-zA-Z0-9]+\z/.match?("kayeN21")
=> true

半角英文字と半角数字と特定の記号のみを許容する判定

/\A[a-z0-9[.][_]]+\z/.match?("kaye.nr")
=> true

適宜暇なときに追記していきます。
なお、こんな書き方の方がいいよとかがあればご教示いただけると嬉しいです。

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

Rubyをインストールする(macOS)

rbenvを使ってRubyをインストールします(2020/1/7実施)

環境

macOS Catalina 10.15.2

Homebrewのインストール

こちらにアクセス

記載されているコマンドを実行します

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

完了確認

brew -v

バージョンが表示されて完了です

Homebrew 2.2.2

rbenvのインストール

こちらの手順通りコマンドを実行します

brew install rbenv

初期設定を行います

rbenv init

以下のように表示されました

# Load rbenv automatically by appending
# the following to ~/.bash_profile:

eval "$(rbenv init -)"

.bash_profileを開きます。なければ新規作成になります

vim ~/.bash_profile

.bash_profileに以下を追記しましょう

eval "$(rbenv init -)"

.bash_profileの変更を反映します

source ~/.bash_profile

正しく設定できたか確認

curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash

このような表示ならOKです

Checking for `rbenv' in PATH: /usr/local/bin/rbenv
Checking for rbenv shims in PATH: OK
Checking `rbenv install' support: /usr/local/bin/rbenv-install (ruby-build 20191225)
Counting installed Ruby versions: none
  There aren't any Ruby versions installed under `/Users/username/.rbenv/versions'.
  You can install Ruby versions like so: rbenv install 2.2.4
Checking RubyGems settings: OK
Auditing installed plugins: OK

インストールできるRubyのバージョンを確認します

rbenv install -l

現時点での最新(2.7.0)をインストールします

rbenv install 2.7.0

インストールしたバージョンを有効化します

rbenv rehash

バージョンを確認します

rbenv versions

インストールされたバージョンが表示されます。「*」がついているものが設定されています

* system (set by /Users/username/.rbenv/version)
  2.7.0

バージョンを切り替えます

rbenv global 2.7.0

バージョンを確認します

ruby -v
ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-darwin19]

これで完了です

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

バックエンドエンジニアのロードマップ in2020

記事投稿の背景

rubyを独学で勉強している最中、とても参考になるエンジニアの学習ロードマップを発見しました(有名なものらしい)。知識整理のためアウトプットさせていただきます!
元ネタは下記リンクです。
https://github.com/kamranahmedse/developer-roadmap

フロントエンド、バックエンド問わず必要になる分野

roadmap1.png

  • Git
  • Linuxコマンドなどの基本的なターミナル操作
  • データ構造、アルゴリズム
  • GitHub
  • Semantic Versioning (バージョン番号のつけ方のルールのこと)
  • SSH
  • HTTP/HTTPS、API

等々。やはりGitHubを使ったバージョン管理は必須スキル、ということですね。そのほかだとデータ構造とアルゴリズム(スタック、キューとか)の勉強は必要になるよう。

バックエンドエンジニアの学習ロードマップ

roadmap2.png

  • まずは言語の選択
    • スクリプト言語なのかコンパイラ言語なのか、またその中のどの言語を学習するかを最初に決定。基本的にはどの言語を選択しても学習の流れに大きな違いはない。
  • コマンドベースで簡単なアプリケーション作成
    • 選択した言語の実行環境を整え、HelloWorldの表示程度の、簡単なプログラムを組んで慣れていく。
  • パッケージマネージャーについて学習
    • rubyであればgem。javascriptならNode.jsのように、言語毎にパッケージマネージャーが存在。その基本的な使用方法を学習する。
  • その言語におけるベストプラクティスは何かを調べる
    • 例えばPHPであればPHP-FIGとPSRsをnode.jsとともに使用するケースが多い。このように、学習している言語の一般的な使用方法を整理しておく。
  • いくつかアプリを作って公開する
    • 簡単なアプリケーションを作成、Githubなどで公開してみる。
  • テストの仕方(単体、結合)を学習
    • その言語における単体テスト、結合テストの実施方法を調べる。
  • テスト(単体、結合)を計画し、実施
    • 実際に、作成したアプリケーションに対して単体テスト、結合テストを実施する。
  • RDBについて学習する
    • RDB(リレーショナルデータベース)について学習。RDBを使用したアプリケーションを作成する。
  • 作成したアプリを運用してみる
    • 作成したアプリケーションを一般公開してみる。例えば、CRUD機能(ユーザー登録や、削除、ブログ等の投稿機能など)を持ったアプリケーションのようなもの。
  • フレームワークを学習する
    • その言語における、よく使われるフレームワークを学習。
  • これまでに作成したアプリを、フレームワークを用いて再度開発
    • 一度作成したアプリケーションを、フレームワークに沿って再度作成してみる
  • NoSQLを学習
    • NoSQLについても学習し、RDBとの違いを整理してみる。
  • キャッシュ(メモリキャッシュ、ブラウザキャッシュ両方)を意識した実装を学習
    • ワンランク上の実装を身に着けるため、キャッシュを意識した実装方法を学習。
  • REST APIを使用してアプリを作成
    • プログラムの中にAPI連携を含むようなアプリケーションを作成

さらに学習するなら

さらにweb関連の知識をつけるために、実装を進める中で疑問に思った点は都度調べていくことがよい。例えば下記のようなことは調べると吉。

  • 認証 ( Authentication )と承認 ( Authorization )の違いを学習
  • Message brokerについて学習
  • サーチエンジンについて学習
  • Dockerの使い方を学習
  • Webサーバーについて学習
  • Webソケットの使い方を学習
  • GraphQLを学習
  • GraphDBを調べる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

serverが起動せず"Usage: rails new APP_PATH [options]..."となってしまう場合の解決法[Ruby on Rails]

経緯

私がRailsの環境を構築する際「Rails s」でサーバーが起動しなかったが解決に至ったためここに記載したいと思う。
投稿に至った経緯は以下の通り。

  • Web上で私と同じように悩んでいる方が多く、またほとんどの記事において解決に至っていなかったため。

  • 該当する記事がそうないように感じたため。

  • 同じ状況に陥っている同志の助けになりたいと考えたため。

陥った状況

  1. 前提として使用OSは Windows10
  2. コマンドプロンプトでrails sを実行すると「Usage: rails new APP_PATH [options] ...(以下省略)」が表示されてサーバが起動できていない。
  3. Ruby、SQLite、Ruby on Railsはインストールされている。
  4. rails new プロジェクト名を実行済み ( 新しいrailsプロジェクトを生成するコマンド )
  5. cd プロジェクト名を実行済み ( プロジェクトのrootに移動するコマンド )

詳細な状況

  • 本来はrails sを実行した際、下記のようになればよい。
C:¥Users¥leone> rails s
=> Booting Puma
=> Rails 5.2.4.1 application starting in development
=> Run `rails server -h` for more startup options
*** SIGUSR2 not implemented, signal based restart unavailable!
*** SIGUSR1 not implemented, signal based restart unavailable!
*** SIGHUP not implemented, signal based logs reopening unavailable!
Puma starting in single mode...
* Version 3.12.2 (ruby 2.6.4-p104), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop
  • しかし問題の状態では下記のようになってしまう。
C:¥Users¥leone> rails s
Usage:
  rails new APP_PATH [options]

Options:
      [--skip-namespace], [--no-skip-namespace]            # Skip namespace (affects only isolated applications)
  -r, [--ruby=PATH]                                        # Path to the Ruby binary of your choice
                                                           # Default: C:/Ruby26-x64/bin/ruby.exe
  -m, [--template=TEMPLATE]                                # Path to some application template (can be a filesystem path or URL)
  -d, [--database=DATABASE]                                # Preconfigure for selected database (options: mysql/postgresql/sqlite3/oracle/frontbase/ibm_db/sqlserver/jdbcmysql/jdbcsqlite3/jdbcpostgresql/jdbc)
                                                           # Default: sqlite3
      [--skip-yarn], [--no-skip-yarn]                      # Don't use Yarn for managing JavaScript dependencies
      [--skip-gemfile], [--no-skip-gemfile]                # Don't create a Gemfile
  -G, [--skip-git], [--no-skip-git]                        # Skip .gitignore file
      [--skip-keeps], [--no-skip-keeps]                    # Skip source control .keep files
  -M, [--skip-action-mailer], [--no-skip-action-mailer]    # Skip Action Mailer files
  -O, [--skip-active-record], [--no-skip-active-record]    # Skip Active Record files
      [--skip-active-storage], [--no-skip-active-storage]  # Skip Active Storage files
  -P, [--skip-puma], [--no-skip-puma]                      # Skip Puma related files
  -C, [--skip-action-cable], [--no-skip-action-cable]      # Skip Action Cable files
  -S, [--skip-sprockets], [--no-skip-sprockets]            # Skip Sprockets files
      [--skip-spring], [--no-skip-spring]                  # Don't install Spring application preloader
      [--skip-listen], [--no-skip-listen]                  # Don't generate configuration that depends on the listen gem
      [--skip-coffee], [--no-skip-coffee]                  # Don't use CoffeeScript
  -J, [--skip-javascript], [--no-skip-javascript]          # Skip JavaScript files
      [--skip-turbolinks], [--no-skip-turbolinks]          # Skip turbolinks gem
  -T, [--skip-test], [--no-skip-test]                      # Skip test files
      [--skip-system-test], [--no-skip-system-test]        # Skip system test files
      [--skip-bootsnap], [--no-skip-bootsnap]              # Skip bootsnap gem
      [--dev], [--no-dev]                                  # Setup the application with Gemfile pointing to your Rails checkout
      [--edge], [--no-edge]                                # Setup the application with Gemfile pointing to Rails repository
      [--rc=RC]                                            # Path to file containing extra configuration options for rails command
      [--no-rc], [--no-no-rc]                              # Skip loading of extra configuration options from .railsrc file
      [--api], [--no-api]                                  # Preconfigure smaller stack for API only apps
  -B, [--skip-bundle], [--no-skip-bundle]                  # Don't run bundle install
      [--webpack=WEBPACK]                                  # Preconfigure for app-like JavaScript with Webpack (options: react/vue/angular/elm/stimulus)

Runtime options:
  -f, [--force]                    # Overwrite files that already exist
  -p, [--pretend], [--no-pretend]  # Run but do not make any changes
  -q, [--quiet], [--no-quiet]      # Suppress status output
  -s, [--skip], [--no-skip]        # Skip files that already exist

Rails options:
  -h, [--help], [--no-help]        # Show this help message and quit
  -v, [--version], [--no-version]  # Show Rails version number and quit

Description:
    The 'rails new' command creates a new Rails application with a default
    directory structure and configuration at the path you specify.

    You can specify extra command-line arguments to be used every time
    'rails new' runs in the .railsrc configuration file in your home directory.

    Note that the arguments specified in the .railsrc file don't affect the
    defaults values shown above in this help message.

Example:
    rails new ~/Code/Ruby/weblog

    This generates a skeletal Rails installation in ~/Code/Ruby/weblog.

原因

"Usage: rails new APP_PATH [options]"が表れるということは考えられることはアプリケーションがないことが原因と考えられますが、前提としてrails new プロジェクト名cd プロジェクト名は実行されているはずです。

そこから察するに、実行はされたが何らかが原因で生成されたプロジェクトが認識されてないのではないかと考えました。ではなぜ認識されないのか考えたところ、予想としてrails new プロジェクト名で生成されるファイルが足りないのではないかと考えたため、私はプロジェクトとして生成されたディレクトリ内を確認したのです。すると予想は的中し明らかにファイルが足りなかったのでした。

下記は本来、プロジェクト生成時に生成されるはずのファイル、またはその処理

C:¥Users¥leone> rails new アプリケーション名
      create
      create  README.md
      create  Rakefile
      create  .ruby-version
      create  config.ru
      create  .gitignore
      create  Gemfile
         run  git init from "."
Initialized empty Git repository in C:/Users/leone/アプリケーション名/.git/
      create  package.json
      create  app
      create  app/assets/config/manifest.js
      create  app/assets/javascripts/application.js
      create  app/assets/javascripts/cable.js
      create  app/assets/stylesheets/application.css
      create  app/channels/application_cable/channel.rb
      create  app/channels/application_cable/connection.rb
      create  app/controllers/application_controller.rb
      create  app/helpers/application_helper.rb
      create  app/jobs/application_job.rb
      create  app/mailers/application_mailer.rb
      create  app/models/application_record.rb
      create  app/views/layouts/application.html.erb
      create  app/views/layouts/mailer.html.erb
      create  app/views/layouts/mailer.text.erb
      create  app/assets/images/.keep
      create  app/assets/javascripts/channels
      create  app/assets/javascripts/channels/.keep
      create  app/controllers/concerns/.keep
      create  app/models/concerns/.keep
      create  bin
      create  bin/bundle
      create  bin/rails
      create  bin/rake
      create  bin/setup
      create  bin/update
      create  bin/yarn
      create  config
      create  config/routes.rb
      create  config/application.rb
      create  config/environment.rb
      create  config/cable.yml
      create  config/puma.rb
      create  config/storage.yml
      create  config/environments
      create  config/environments/development.rb
      create  config/environments/production.rb
      create  config/environments/test.rb
      create  config/initializers
      create  config/initializers/application_controller_renderer.rb
      create  config/initializers/assets.rb
      create  config/initializers/backtrace_silencers.rb
      create  config/initializers/content_security_policy.rb
      create  config/initializers/cookies_serializer.rb
      create  config/initializers/cors.rb
      create  config/initializers/filter_parameter_logging.rb
      create  config/initializers/inflections.rb
      create  config/initializers/mime_types.rb
      create  config/initializers/new_framework_defaults_5_2.rb
      create  config/initializers/wrap_parameters.rb
      create  config/locales
      create  config/locales/en.yml
      create  config/master.key
      append  .gitignore
      create  config/boot.rb
      create  config/database.yml
      create  db
      create  db/seeds.rb
      create  lib
      create  lib/tasks
      create  lib/tasks/.keep
      create  lib/assets
      create  lib/assets/.keep
      create  log
      create  log/.keep
      create  public
      create  public/404.html
      create  public/422.html
      create  public/500.html
      create  public/apple-touch-icon-precomposed.png
      create  public/apple-touch-icon.png
      create  public/favicon.ico
      create  public/robots.txt
      create  tmp
      create  tmp/.keep
      create  tmp/pids
      create  tmp/pids/.keep
      create  tmp/cache
      create  tmp/cache/assets
      create  vendor
      create  vendor/.keep
      create  test/fixtures
      create  test/fixtures/.keep
      create  test/fixtures/files
      create  test/fixtures/files/.keep
      create  test/controllers
      create  test/controllers/.keep
      create  test/mailers
      create  test/mailers/.keep
      create  test/models
      create  test/models/.keep
      create  test/helpers
      create  test/helpers/.keep
      create  test/integration
      create  test/integration/.keep
      create  test/test_helper.rb
      create  test/system
      create  test/system/.keep
      create  test/application_system_test_case.rb
      create  storage
      create  storage/.keep
      create  tmp/storage
      create  tmp/storage/.keep
      remove  config/initializers/cors.rb
      remove  config/initializers/new_framework_defaults_5_2.rb
         run  bundle install
Fetching gem metadata from https://rubygems.org/.............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies....
Using rake 13.0.1
Using concurrent-ruby 1.1.5
Using i18n 1.7.0
Using minitest 5.13.0
Using thread_safe 0.3.6
Using tzinfo 1.2.6
Using activesupport 5.2.4.1
Using builder 3.2.4
Using erubi 1.9.0
Using mini_portile2 2.4.0
Using nokogiri 1.10.7 (x64-mingw32)
Using rails-dom-testing 2.0.3
Using crass 1.0.5
Using loofah 2.4.0
Using rails-html-sanitizer 1.3.0
Using actionview 5.2.4.1
Using rack 2.0.8
Using rack-test 1.1.0
Using actionpack 5.2.4.1
Using nio4r 2.5.2
Using websocket-extensions 0.1.4
Using websocket-driver 0.7.1
Using actioncable 5.2.4.1
Using globalid 0.4.2
Using activejob 5.2.4.1
Using mini_mime 1.0.2
Using mail 2.7.1
Using actionmailer 5.2.4.1
Using activemodel 5.2.4.1
Using arel 9.0.0
Using activerecord 5.2.4.1
Using mimemagic 0.3.3
Using marcel 0.3.3
Using activestorage 5.2.4.1
Using public_suffix 4.0.3
Using addressable 2.7.0
Using io-like 0.3.0
Using archive-zip 0.12.0
Using bindex 0.8.1
Using msgpack 1.3.1 (x64-mingw32)
Using bootsnap 1.4.5
Using bundler 1.17.2
Using byebug 11.0.1
Using regexp_parser 1.6.0
Using xpath 3.2.0
Using capybara 3.30.0
Using childprocess 3.0.0
Using chromedriver-helper 2.1.1
Using coffee-script-source 1.12.2
Using execjs 2.7.0
Using coffee-script 2.4.1
Using method_source 0.9.2
Using thor 1.0.1
Using railties 5.2.4.1
Using coffee-rails 4.2.2
Using duktape 2.3.0.0
Using ffi 1.11.3 (x64-mingw32)
Using jbuilder 2.9.1
Using puma 3.12.2
Using sprockets 3.7.2
Using sprockets-rails 3.2.1
Using rails 5.2.4.1
Using rb-fsevent 0.10.3
Using rb-inotify 0.10.1
Using rubyzip 2.0.0
Using sass-listen 4.0.0
Using sass 3.7.4
Using tilt 2.0.10
Using sass-rails 5.1.0
Using selenium-webdriver 3.142.7
Using sqlite3 1.4.2
Using turbolinks-source 5.2.0
Using turbolinks 5.2.1
Using tzinfo-data 1.2019.3
Using uglifier 4.2.0
Using web-console 3.7.0
Bundle complete! 16 Gemfile dependencies, 76 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

下記は陥った状況

C:¥Users¥leone> rails new アプリケーション名
      create
      create  README.md
      create  Rakefile
      create  .ruby-version
      create  config.ru
      create  .gitignore
      create  Gemfile
  //ここらで終わっていた。

ではなぜこのようなことが起こるのか。
それはgitがインストールされていない、または上手くインストールされていないためです。
既にしている場合は、gitbashしかgitコマンド使用の許可がされていない。つまりはpathが通っていないことが原因ということになります。

解決方法

Gitをインストールする

既にインストールしてある場合は再インストール、またはpathを設定し直す。
pathを設定し直すのが早くC:\Program Files\Git\cmdをpathに追加。
再インストールする際は、インストール前にアンインストールを行う。

アンインストール手順
デスクトップ > スタート > 設定 > ホーム > アプリ > Git version〇〇を選択 > アンインストール

Gitインストール手順
Gitのインストール方法は検索して該当したサイトを参考にインストールして構いませんが、下記に記す部分は注意してほしい。
https://proengineer.internous.co.jp/content/columnfeature/6893(こちらのサイトを参考にするとよい)

Gitインストール時の注意点
6 (3).png

上記の画面に進んだら画像のように真ん中を選んでください。これはbash以外のシェルでもgitコマンドを使えるようにするためです。もし上を選んでインストールしてしまったら環境変数からpathを追加することをおすすめしますが、pathの概念がわからなければ再インストールしても問題はありません。

最後に

私はこのような方法で解決に至ったのですが、皆さんはどうだったでしょうか。当投稿でお力添え出来たら幸いです。
解決出来た、または役に立ったという方がいればグッドボタン:thumbsup:を押して頂けると励みになります。

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