20200223のRailsに関する記事は27件です。

deviseインストール後rails s でローカルサーバー起動しない

自分用忘備録
環境
ruby 2.6.3
rails 5.2.4

undefined method `config' for Devise:Module (NoMethodError)
config/initializers/devise.rb
Devise.setup do |config|
~~
Devise.config.sign_out_via = :get
Deviseを入れていて、二重にしてしまったのが、原因。
config.sign_out_via = :get
に修正。
無事開通。

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

RSpecでHTTPメソッドDELETEのテストをする時の注意点

概要

"expected #count to have changed by -1, but was changed by 0"のエラー文の解消話です。

前提

  • 具体的なコードはほとんど書いていないです。

テストが落ちたコード

    describe OrdersController do
      describe 'delete #destroy' do
        it "deletes the article" do
          order = create(:order)
          expect{
            delete :destroy, id: order
          }.to change(Order,:count).by(-1)
        end
      end
    end

ブルーな気持ち

DELETEについてのテストを書いていたのですが、"expected #count to have changed by -1, but was changed by 0"のエラー文が登場して原因がわからず頭を抱えていました。

解決方法

DELETEの処理がレコードの物理削除ではなく論理削除を行なっていたことが原因でした

そのため、レコードの総数ではなく論理削除処理が行われていることをテストするようにテスト方法を見直して解決できました。

まとめや自己反省

  • "expected #count to have changed by -1, but was changed by 0"のエラーが発生したら
    物理削除ではなく論理削除ではないか?と疑ってみてください。

  • destroyメソッドの中身を読み直すと物理削除ではなく論理削除を行なっていることが気づけただろうから、テスト対象のメソッドを見直してからテストを書くようにしよう。

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

Rails6 DBのupdate_atなどに格納された日にちをYYYY-MM-DDの形で取り出す

目的

  • DBのcreated_atやupdate_atなどのカラムに格納された日にちの値をYYYY-MM-DD HH:MM:SSの形で取り出す方法をまとめる

困りごと

  • 特定のidのupdate_atをそのまま使用しようとすると下記の様に出力されてしまう。

    Tue, 04 Feb 2020 09:01:35 UTC +00:00
    
  • YYYY-MM-DD HH:MM:SSの形で出力したい。

    2020-02-04 09:01:35
    

結論

  • 取得したupdate_atの値を.to_sで文字列に変換することでYYYY-MM-DD HH:MM:SSで得ることができる。

書き方の例

  • Postモデルのpostsテーブルのidが1のレコードのupdate_atカラムに格納されている日にちデータをYYYY-MM-DD HH:MM:SSの形で変数に格納する方法を記載する。
  • postsデーブルのidが1のレコードに格納されているデータはすでにDBに格納されているものとする。

    #DBのpostsテーブルのidが1のレコード情報を変数@postに格納
    @post = Post.find_by(id: 1)
    #変数last_update_dateに先に取得したidが1のレコードのupdated_atをto_sメソッドにて文字列にしたものを格納する。
    last_update_date = @post.updated_at.to_s
    puts last_update_date
    
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby on Rails】 Railsのgem"ancestry"によるタグ機能実装(多階層構造)

今回はタグ機能を実装するために、 Railsのgem"ancestry"を利用していきます。
これに関しては今回初めてなので、学習しながらメモしていきたいと思います

仕組みを理解する。

タグの階層とどうやって紐づけるか理解していきましょう!!

gem無しの場合
親コテゴリー - 中間カテゴリ-(親_子) - 子カテゴリー - 中間カテゴリー(子_孫) - 孫カテゴリー - 中間テービル(孫_商品) - 商品

gemがないと上記のような仕組みのため、非常にめんどくさいです。

gem'ancestry'の場合
商品 - 中間テーブル - カテゴリー

上記だけで完結します。だから非常に楽ですね
ではどうして、カテゴリーテーブル一つで3階層可能なのか?疑問になりますよね
一緒に学習しましょう!

タグの3階層の仕組み

DBを見るとわかりやすいので、見ていきましょう!
スクリーンショット 2020-02-23 3.58.31.png

3階層までタグが作成可能で、

一番上の親カテゴリは祖先がないので、
ancestryカラム: NULL
スクリーンショット 2020-02-23 4.01.30.png

ID:14番目のレコードですが、ancestry: 1になってます。
祖先はID:1のレコードという意味ですね。つまりレディースが親カテゴリということです。
スクリーンショット 2020-02-23 4.03.05.png

では、さらにトップスよりも下の階層(3階層目)はどうなのか?
スクリーンショット 2020-02-23 4.04.03.png
つまり、祖先はID:1番目の レディースになるということです。ancestry: 1/14となっています。
つまり、ID:1の子である,ID:14番目の子という表示

カテゴリータグの仕組み
1階層目: null
2階層目: 1階層目のID
3階層目: 1階層目のID/2階層目のID

3階層目のancestryカラムを取得すれば、2階層目、1階層目と辿れる仕組みです。3階層目までしか構造上できないようです(もしもできる場合は、コメントで教えていただければ幸いです。)4階層目を実装するには、別のテーブルが必要となります。

どうやって商品と紐づけるのか?

タグIDと商品IDを紐づける中間テーブルをおきます。

product_categoriesテーブル

Column Type Options
product_id references null: false
category_id references null: false

実際にDBを見て理解していきましょう!!
Image from Gyazo
この例だと
productのID:1番目とcategory_id:7番目が紐づいてます。

スクリーンショット 2020-02-23 4.30.25.png
categoryの7番目は、コスメ・香水・美容ですから、
商品は「コスメ・香水・美容」カテゴリに紐づいているとわかります。

このように、中間テーブルを利用して商品とカテゴリーを紐づけていきます。

モデル
商品 - 中間テーブル - カテゴリー

まとめ

カテゴリータグの仕組み
1階層目: null
2階層目: 1階層目のID
3階層目: 1階層目のID/2階層目のID
モデル
商品 - 中間テーブル - カテゴリー
表示する仕組み
1. htmlより、親カテゴリを並べる
2. 親カテゴリを選択されたら、コントローラーで子カテゴリを取得、JS(ajax)で追加表示
3. 子カテゴリが選択されたら、コントローラーで孫kてゴリを取得、JS(ajax)で追加表示

親 > 子 > 孫
親 > 子: 親.children > 孫: 子.children #ここは後述します。

実装をしていく。

それでは実装していきましょう!!

インストール

Gemfile
gem 'ancestry'

を追加して

ターミナル
 $ bundle instal

インストールをしたら、再起動させたいので、

ターミナル
 $ rails s

モデルの作成

商品モデルはすでに作成してる前提で、作成方法を記述しません。

カテゴリーモデルを作成します。

ターミナル
$ rails g model category

モデルを作成したら、migrateファイルを記述していきましょう!!

categoriesテーブル

Column Type Options
name string null: false, index: true
ancestry string index: true

Association

  • has_many :products
  • has_ancestry
migateファイル
class CreateCategories < ActiveRecord::Migration[5.2]
  def change
    create_table :categories do |t|
      t.string :name,     index: true, null: false
      t.string :ancestry, index: true
      t.timestamps
    end
  end
end
ターミナル
$ rake db:migrate

中間テーブル(product_categories)を作成

ターミナル
$ rails g model product_category

product_categoriesテーブル

Column Type Options
product_id references null: false
category_id references null: false

アソシエーション

  • belongs_to :product
  • belongs_to :category

上記のテーブルになるようにmigrateファイルを記述していきます。

migrateファイル
class CreateProductCategories < ActiveRecord::Migration[5.2]
  def change
    create_table :product_categories do |t|
      t.references :product, null:false
      t.references :category, null:false
      t.timestamps
    end
  end
end
ターミナル
rake db:migrate

アソシエーション

カテゴリーモデルは下記になります

category.rb
class Category < ApplicationRecord
  has_ancestry
  has_many :product_categories, dependent: :destroy
  has_many :products, through: :product_categories
end

中間テーブルは下記になります。

product.category.rb
class ProductCategory < ApplicationRecord
  belongs_to :product
  belongs_to :category
end

商品モデルは下記になります

product.rb
class Product < ApplicationRecord
  has_many :product_categories, dependent: :destroy
  has_many :categories, through: :product_categories
end

はい!これでモデルは完了ですね。
では次に進みましょう!!!

DB → モデル → コントローラー

seeds.rbでカテゴリを生成

今回はモデル別にseedファイルを作成するやり方をします
[通常方法](https://www.sejuku.net/blog/28395)

seeds.rb
require './db/seeds/category.rb'

カテゴリー用のseedファイルを作成しましょう
db > seedsのフォルダを作成 > category.rbを作成

seeds/category.rb
#親カテゴリ
lady = Category.create(name: "レディース")

#子カテゴリー
lady_1 = lady.children.create(name: "トップス")

#孫カテゴリー
lady_1.children.create([{name: "Tシャツ/カットソー(半袖/袖なし)"},{name: "Tシャツ/カットソー(七分/長袖)"},{name: "シャツ/ブラウス(半袖/袖なし)"},{name: "シャツ/ブラウス(七分/長袖)"},{name: "ポロシャツ"},{name: "キャミソール"},{name: "タンクトップ"},{name: "ホルターネック"},{name: "ニット/セーター"},{name: "チュニック"},{name: "カーディガン/ボレロ"},{name: "アンサンブル"},{name: "ベスト/ジレ"},{name: "パーカー"},{name: "トレーナー/スウェット"},{name: "ベアトップ/チューブトップ"},{name: "ジャージ"},{name: "その他"}])

では生成しましょう

ターミナル
$ rails db:seed

コントローラーの作成

DB → モデル → コントローラーの処理で進むので、コントローラーを作成します。

仕組み
1. htmlより、親カテゴリを並べる
2. 親カテゴリを選択されたら、コントローラーで子カテゴリを取得、JSで追加表示
3. 子カテゴリが選択されたら、コントローラーで孫kてゴリを取得、JSで追加表示

親 > 子 > 孫

この仕組みを動かすための独自メソッドを作成します。

products_controller.rb
class ProductsController < ApplicationController
  def get_category_children
    @children = Category.find(params[:parent_id]).children
  end

  def get_category_grandchildren
    @grandchildren = Category.find("#{params[:child_id]}").children
  end
end

と上記のアクションを作成します。

@children = Category.find(params[:parent_id]).children

上記の.childenって何?ってなると思いますが、
親カテゴリから子カテゴリを取得するためのメソッドです。

先ほど親>子>孫の順番で取得すると説明しましたが、.childrenを使うので下記のようになります。

親 > 子:親カテゴリ.chidren > 孫: 子カテゴリ.children

他にもいろんなメソッドが用意されているので、一度Githubを見ていただければと思います。
Github:ancestroy

Ajaxを導入する(JQuery)

route.rb

Ajaxに対応したルーティングにします。

route.rb
  resources :products do
    collection do # 新規用(new) usr:products/newのため
      get 'get_category_children', defaults: { format: 'json' }
      get 'get_category_grandchildren', defaults: { format: 'json' }
    end
    member do # 編集(edit用) usl: products/id/editのため
      get 'get_category_children', defaults: { format: 'json' }
      get 'get_category_grandchildren', defaults: { format: 'json' }
    end
  end 

Ajax対応のコントローラーにする。

先ほどコントローラーを作成していましたが、改良します

products_conroller.rb
  def get_category_children
    respond_to do |format| 
      format.html
      format.json do
        @children = Category.find(params[:parent_id]).children
      end
    end
  end
  def get_category_grandchildren
    respond_to do |format| 
      format.html
      format.json do
        @grandchildren = Category.find("#{params[:child_id]}").children
      end
    end
  end

これでAjax対応のコントローラーになりました。

jbuilder作成

コントローラーとAjax用jsとで情報の架け橋となるjbuilderを作成
子カテゴリを追加するために、idと名前をjsに持っていきたい。

children.jbuilder
json.array! @grandchildren do |child|
  json.id child.id
  json.name child.name
end
grandchildren.jbuilder
json.array! @grandchildren do |grandchild|
  json.id grandchild.id
  json.name grandchild.name
end

Ajax用のjs

Ajaxで要素を追加するjsを記述します。

category-ajax.js
$(document).on('turbolinks:load', function(){
  // カテゴリーの選択肢が入ったdiv
  var categoryBox = $('.form-details__form-box__category')
  // 親カテゴリー
  function appendOption(category) {
    var html = `<option value="${category.id}" data-category="${category.id}">${category.name}</option>`
  }

  // 子カテゴリー
  function appendChildBox(insertHTML) {
    var childSelectHtml = '';
    childSelectHtml = `<div class='form-select' id="child-category">
                        <select class= 'select-default' name="product[category_ids][]">
                            <option value>---</option>
                            ${insertHTML}
                          </select>
                          <i class='fa fa-angle-down icon-angle-down'></i>
                      </div>`
    categoryBox.append(childSelectHtml);
  }

  // カテゴリーボックスで親カテゴリが変わった場合
  categoryBox.on("change", "#parent-category", function(){
    var parentCategory = $("parent-category").value;
    if(parentCategory !== "") {
      $.ajax ({
        url: '/products/get_category_children',
        type: "GET",
        data: {
          parent_id: parentCategory
        },
        dataType: 'json'
      })
      .done(function(children){
        $('#child-category').remove();
        $('#grandchild-category').remove();
        var insertHTML = '';
        children.forEach(function(grandchild){
          insertHTML += appendOption(grandchild);
        });
        appendGrandchildrenBox(insertHTML);
      })
      .fail(function(){
        alert('カテゴリー取得に失敗しました');
      })
    } else {
      //親カテゴリーが初期値(---)の場合、子カテゴリー以下は非表示にする
      //親カテゴリが未選択の場合、子、孫カテゴリの選択欄は非表示にしたいので、そのように変更
      $('#child-category').remove(); 
      $('#grandchild-category').remove();
      $('#size').remove();
    }
  })
})

学習して作成中

参考になるサイト

Github:ancestroy
Railsでタグ機能をgemを使わずに実装した際のメモ
Railsのgem"ancestry"による多階層構造の実現

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

RailsをAWSでデプロイする

はじめに

手軽にRailsをAWSにデプロイする手順をまとめました。
  • 極力シンプルになるように、EC2インスタンスを一つ作成し、その中に自分のRailsアプリを含め、必要なものを全てインストールしてデプロイします。

  • データベースはMySQL、WebサーバーはNginXでの環境構築です。

  • AWSのサーバーでのファイルの書き込みはviというコマンドで行います。(vi ファイル名でそのファイルを編集、そのファイルがない場合は新たにファイルを作って書き込みできます。詳しい使い方は要検索!)

  • 注意点
    コードの中で、

 /var/www/rails/{アプリ名} 

などと書いている時がありますが、自分の環境に合わせて、

 /var/www/rails/sample_app

としてください

 /var/www/rails/{sample_app} 

ではないのでご注意。

第一章 サーバーの準備

1.VPCを作成

  • 名前は適当につける
  • IPv4CIDERブロックは10.0.0.0/16に設定
  • あとはデフォルトで

2.サブネットを作成

  • 名前は適当につける
  • VPCは先程作ったVPCを選択
  • IPv4CIDERブロックは10.0.0.0/24に設定

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

  • 名前は適当につける
  • 作成後は先程作ったVPCをアタッチ

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

  • 名前は適当につける
  • 作成後はルートの設定で0.0.0.0/0を追加し保存
  • サブネットの関連付けで先程作ったサブネットを選択
  • あとはデフォルトで

5.セキュリティグループを作成

  • 名前は適当につける
  • 作成後は
    タイプ:HTTP ソース:任意の場所(他の設定はデフォルト)
    と、
    タイプ:SSH ソース:任意の場所(他の設定はデフォルト)
    を追加し保存
  • あとはデフォルトで

6.EC2を作成

  • EC2インスタンスの作成

    • 無料枠からAmazonLinuxを選ぶ(AmazonLinux2を選ばないように注意、この後の環境構築で差が出てきてしまうので)
    • ネットワーク:先程作ったVPCを選択
    • サブネット:先程作ったサブネットを選択
    • 自動割り当てパブリックIP:有効化を選択
    • セキュリティーグループ:先程作ったセキュリティグループを選択
  • キーペアの登録とElasticIPの割当て

    • キーペアファイル(~.pem)はローカル(自分のパソコンのこと)の.ssh配下に移動
    • ElasticIPを作り先ほど作ったEC2インスタンスに割り当てる

第二章 サーバーにログインし環境構築

1.EC2にログイン

キーペアファイルのあるディレクトリに移動

cd .ssh

キーペアファイルに権限を付与

chmod 600 {自分のキーペアの名前}.pem

サーバー(EC2インスタンス)にログイン

 ssh -i {自分のキーペアのpemファイル} ec2-user@{自分のElasticIPアドレス}

ユーザーを作成

sudo adduser {新規ユーザー名}
(#新規ユーザー名の登録)
sudo passwd {パスワード}
(#新規ユーザー名のパスワード登録)
sudo visudo

ユーザーの権限の変更

1.rootに関する権限の記述箇所
root    ALL=(ALL)       ALL  を探す。
2.その下に、作成したユーザーに権限を追加する記述
{ユーザー名}   ALL=(ALL)       ALL  を追加する

ユーザーの切替

sudo su - {ユーザー名}

2.諸々必要なものをインストール

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

3.Rubyをインストール

以下Rubyをインストールするための下準備

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

以下Rubyのインストールです、インストールするRubyのバージョンは自分の環境に合わせてください

rbenv install -v 2.6.5
rbenv global 2.6.5
rbenv rehash

インストールできたか確認

ruby -v

4.node.jsをインストール

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
. ~/.nvm/nvm.sh
nvm install node

5.Yarnをインストール

curl -o- -L https://yarnpkg.com/install.sh | bash
export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH"

第三章 Rails、DB、Webサーバーの設定

1.Railsの設定

/var/www/rails/ の配下にRailsアプリを持ってくる、以下を一行一行実行していく

cd / (/ディレクトリに移動)
sudo chown {ユーザー名} var (varフォルダの所有者を{ユーザー名}にする)
cd var (varディレクトリに移動)
sudo mkdir www (wwwディレクトリの作成)
sudo chown {ユーザー名} www (wwwフォルダの所有者を{ユーザー名}にする)
cd www  (wwwディレクトリに移動)
sudo mkdir rails  (wwwディレクトリの作成)
sudo chown {ユーザー名} rails (railsフォルダの所有者を{ユーザー名}にする)
cd rails (railsディレクトリに移動)

GitHubからクローン

git clone {自分のRailsのアプリのリポジトリのclone用URL}

DBとの接続用の設定、以下をRailsアプリ内のconfig/database.ymlに追加

production:
  <<: *default
  database: {アプリ名}
  username: root #ここをrootに変更する
  password:      #ここを空欄にする

※ローカルのconfig/master.keyの一行を全てコピーし、
cloneしてきたアプリのconfig/master.keyに記載をする
(master.keyはgitignoreされておりローカルのmaster.keyの内容は
githubには反映されないため自分でコピペしてくる)

2.MySQLの設定

起動

sudo service mysqld start
ln -s /var/lib/mysql/mysql.sock /tmp/mysql.sock

DBの作成

rails db:create RAILS_ENV=production

マイグレーション

rails db:migrate RAILS_ENV=production

3.Unicornの設定

Gemfileに以下を追記

group :production, :staging do
  gem 'unicorn'
end

bundlerをインストール

gem install bundler

bundlerでunicornのgemをインストール

bundle install 

設定ファイルの追加

vi ~/var/www/rails/{アプリ名}/config/unicorn.conf.rb

以下を設定ファイルに記載

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

4.NginXの設定

インストール

sudo yum install nginx

Nginxの設定

ディレクトリを移動

cd  /etc/nginx/conf.d

/etc/nginx/conf.d下に設定ファイルの作成

vi {アプリ名}.conf

以下を記載

# log directory
error_log  /var/www/rails/{アプリ名}/log/nginx.error.log; #自分のアプリケーション名に変更
access_log /var/www/rails/{アプリ名}/log/nginx.access.log; #自分のアプリケーション名に変更
# max body size
client_max_body_size 2G;
upstream app_server {
  # for UNIX domain socket setups
  server unix:/var/www/rails/{アプリ名}/tmp/sockets/.unicorn.sock fail_timeout=0; #自分のアプリケーション名に変更
}
server {
  listen 80;
  server_name ~~~.~~~.~~~.~~~; #自分のElasticIP
  # nginx so increasing this is generally safe...
  keepalive_timeout 5;
  # path for static files
  root /var/www/rails/{アプリ名}/public; #自分のアプリケーション名に変更
  # page cache loading
  try_files $uri/index.html $uri.html $uri @app;
  location @app {
    # HTTP headers
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://app_server;
  }
  # Rails error pages
  error_page 500 502 503 504 /500.html;
  location = /500.html {
    root /var/www/rails/{アプリ名}/public; #自分のアプリケーション名に変更
  }
}

第四章 世界に公開

1.Railsのプリコンパイル

rails assets:precompile RAILS_ENV=production 

2.NginXを起動

sudo service nginx start

3.Unicornを起動

bundle exec unicorn_rails -c /var/www/rails/{アプリ名}/config/unicorn.conf.rb -D -E production

4.お疲れ様です

これでブラウザに自分のElasticIPを打ち込むと世界中のパソコンやスマホから自分のアプリにアクセスできます!

5.その他コマンド

unicornの起動確認

ps -ef | grep unicorn | grep -v grep

unicornの終了

kill {masterのPID}

以下のようなYarnのエラーが出ることがあります(Railsコマンドを実行した際にこのようなエラーが出る時があります)

========================================
  Your Yarn packages are out of date!
  Please run `yarn install --check-files` to update.
========================================

エラー文に従い以下を実行しましょう

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

本番環境にrake db:seedを叩き込む方法

開発環境、テスト環境、本番環境にseedデータを叩き込む

seedデータとは

seedデータとは、データベースに投入する初期データのこと

この初期データの投入は、db/seeds.rb に記載します。

db/seeds.rb
# 親階層
lady                         = Category.create(name: "レディース")
men                          = Category.create(name: "メンズ")
baby_kids                    = Category.create(name: "ベビー・キッズ")
interior_residence_accessory = Category.create(name: "インテリア・住まい・小物")
book_music_game              = Category.create(name: "本・音楽・ゲーム")
toy_hobby_goods              = Category.create(name: "おもちゃ・ホビー・グッズ")
cosme_perfume_beauty         = Category.create(name: "コスメ・香水・美容")
appliance_smartphone_camera  = Category.create(name: "家電・スマホ・カメラ")
sport_leisure                = Category.create(name: "スポーツ・レジャー")
handmade                     = Category.create(name: "ハンドメイド")
ticket                       = Category.create(name: "チケット")
car_motorcycle               = Category.create(name: "自動車・オートバイ")
others                       = Category.create(name: "その他")

# 子階層_レディース
lady_1  = lady.children.create(name: "トップス")
lady_2  = lady.children.create(name: "ジャケット/アウター")
lady_3  = lady.children.create(name: "パンツ")
lady_4  = lady.children.create(name: "スカート")
lady_5  = lady.children.create(name: "ワンピース")
lady_6  = lady.children.create(name: "靴")
lady_7  = lady.children.create(name: "ルームウェア/パジャマ")
lady_8  = lady.children.create(name: "レッグウェア")
lady_9  = lady.children.create(name: "帽子")
lady_10 = lady.children.create(name: "バッグ")
lady_11 = lady.children.create(name: "アクセサリー")
lady_12 = lady.children.create(name: "ヘアアクセサリー")
lady_13 = lady.children.create(name: "小物")
lady_14 = lady.children.create(name: "時計")
lady_15 = lady.children.create(name: "ウィッグ/エクステ")
lady_16 = lady.children.create(name: "浴衣/水着")
lady_17 = lady.children.create(name: "スーツ/フォーマル/ドレス")
lady_18 = lady.children.create(name: "マタニティ")
lady_19 = lady.children.create(name: "その他")

# 孫階層_レディース
lady_1.children.create([{name: "Tシャツ/カットソー(半袖/袖なし)"},{name: "Tシャツ/カットソー(七分/長袖)"},{name: "シャツ/ブラウス(半袖/袖なし)"},{name: "シャツ/ブラウス(七分/長袖)"},{name: "ポロシャツ"},{name: "キャミソール"},{name: "タンクトップ"},{name: "ホルターネック"},{name: "ニット/セーター"},{name: "チュニック"},{name: "カーディガン/ボレロ"},{name: "アンサンブル"},{name: "ベスト/ジレ"},{name: "パーカー"},{name: "トレーナー/スウェット"},{name: "ベアトップ/チューブトップ"},{name: "ジャージ"},{name: "その他"}])
lady_2.children.create([{name: "テーラードジャケット"},{name: "ノーカラージャケット"},{name: "Gジャン/デニムジャケット"},{name: "レザージャケット"},{name: "ダウンジャケット"},{name: "ライダースジャケット"},{name: "ミリタリージャケット"},{name: "ダウンベスト"},{name: "ジャンパー/ブルゾン"},{name: "ポンチョ"},{name: "ロングコート"},{name: "トレンチコート"},{name: "ダッフルコート"},{name: "ピーコート"},{name: "チェスターコート"},{name: "モッズコート"},{name: "スタジャン"},{name: "毛皮/ファーコート"},{name: "スプリングコート"},{name: "スカジャン"},{name: "その他"}])
lady_3.children.create([{name: "デニム/ジーンズ"},{name: "ショートパンツ"},{name: "カジュアルパンツ"},{name: "ハーフパンツ"},{name: "チノパン"},{name: "ワークパンツ/カーゴパンツ"},{name: "クロップドパンツ"},{name: "サロペット/オーバーオール"},{name: "オールインワン"},{name: "サルエルパンツ"},{name: "ガウチョパンツ"},{name: "その他"}])
lady_4.children.create([{name: "ミニスカート"},{name: "ひざ丈スカート"},{name: "ロングスカート"},{name: "キュロット"},{name: "その他"}])
lady_5.children.create([{name: "ミニワンピース"},{name: "ひざ丈ワンピース"},{name: "ロングワンピース"},{name: "その他"}])
lady_6.children.create([{name: "ハイヒール/パンプス"},{name: "ブーツ"},{name: "サンダル"},{name: "スニーカー"},{name: "ミュール"},{name: "モカシン"},{name: "ローファー/革靴"},{name: "フラットシューズ/バレエシューズ"},{name: "長靴/レインシューズ"},{name: "その他"}])
lady_7.children.create([{name: "パジャマ"},{name: "ルームウェア"}])
lady_8.children.create([{name: "ソックス"},{name: "スパッツ/レギンス"},{name: "ストッキング/タイツ"},{name: "レッグウォーマー"},{name: "その他"}])
lady_9.children.create([{name: "ニットキャップ/ビーニー"},{name: "ハット"},{name: "ハンチング/ベレー帽"},{name: "キャップ"},{name: "キャスケット"},{name: "麦わら帽子"},{name: "その他"}])
lady_10.children.create([{name: "ハンドバッグ"},{name: "トートバッグ"},{name: "エコバッグ"},{name: "リュック/バックパック"},{name: "ボストンバッグ"},{name: "スポーツバッグ"},{name: "ショルダーバッグ"},{name: "クラッチバッグ"},{name: "ポーチ/バニティ"},{name: "ボディバッグ/ウェストバッグ"},{name: "マザーズバッグ"},{name: "メッセンジャーバッグ"},{name: "ビジネスバッグ"},{name: "旅行用バッグ/キャリーバッグ"},{name: "ショップ袋"},{name: "和装用バッグ"},{name: "かごバッグ"},{name: "その他"}])
lady_11.children.create([{name: "ネックレス"},{name: "ブレスレット"},{name: "バングル/リストバンド"},{name: "リング"},{name: "ピアス(片耳用)"},{name: "ピアス(両耳用)"},{name: "イヤリング"},{name: "アンクレット"},{name: "ブローチ/コサージュ"},{name: "チャーム"},{name: "その他"}])
lady_12.children.create([{name: "ヘアゴム/シュシュ"},{name: "ヘアバンド/カチューシャ"},{name: "ヘアピン"},{name: "その他"}])
lady_13.children.create([{name: "長財布"},{name: "折り財布"},{name: "コインケース/小銭入れ"},{name: "名刺入れ/定期入れ"},{name: "キーケース"},{name: "キーホルダー"},{name: "手袋/アームカバー"},{name: "ハンカチ"},{name: "ベルト"},{name: "マフラー/ショール"},{name: "ストール/スヌード"},{name: "バンダナ/スカーフ"},{name: "ネックウォーマー"},{name: "サスペンダー"},{name: "サングラス/メガネ"},{name: "モバイルケース/カバー"},{name: "手帳"},{name: "イヤマフラー"},{name: "傘"},{name: "レインコート/ポンチョ"},{name: "ミラー"},{name: "タバコグッズ"},{name: "その他"}])
lady_14.children.create([{name: "腕時計(アナログ)"},{name: "腕時計(デジタル)"},{name: "ラバーベルト"},{name: "レザーベルト"},{name: "金属ベルト"},{name: "その他"}])
lady_15.children.create([{name: "前髪ウィッグ"},{name: "ロングストレート"},{name: "ロングカール"},{name: "ショートストレート"},{name: "ショートカール"},{name: "その他"}])
lady_16.children.create([{name: "浴衣"},{name: "着物"},{name: "振袖"},{name: "長襦袢/半襦袢"},{name: "水着セパレート"},{name: "水着ワンピース"},{name: "水着スポーツ用"},{name: "その他"}])
lady_17.children.create([{name: "スカートスーツ上下"},{name: "パンツスーツ上下"},{name: "ドレス"},{name: "パーティーバッグ"},{name: "シューズ"},{name: "ウェディング"},{name: "その他"}])
lady_18.children.create([{name: "トップス"},{name: "アウター"},{name: "インナー"},{name: "ワンピース"},{name: "パンツ/スパッツ"},{name: "スカート"},{name: "パジャマ"},{name: "授乳服"},{name: "その他"}])
lady_19.children.create([{name: "コスプレ"},{name: "下着"},{name: "その他"}])

このseedデータをデータベースに投入するには

$ rake db:seed

で開発環境には投入出来ます。

テスト環境では

$ rake db:seed RAILS_ENV=test

でおけ

問題は本番環境でのseedデータの投入でござんす

上記のイメージだと

$ rake db:seed RAILS_ENV=production

これで入りそうですが 本番環境のデータベースはそんな単純ではない

おおん? 何やっても入らんぞ?

と悩んだあげく

/var/www/アプリ名/current まで移動して実行すればデータ投入ができました。

/var/www/アプリ名 に設定して

cd current

この状態で

rake db:seed RAILS_ENV=production

これで 本番環境にseedデータを叩き込むことが出来ました。
なぜcurrentだと入るのか? ここは謎のまんまです。

さて なぜ seedデータなのか

多階層カテゴリを扱いたかったからです

結局はancestryというgemを使いたくて そのためにはseedでデータ投入するのが一番効率的だというお話です。
gem ancestryについてはこちら
https://qiita.com/Sotq_17/items/120256209993fb05ebac

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

Nginxで403 Forbiddenエラーの解決策(Rails+AWS+Nginx+Unicorn環境)

ハマったこと

Railsで作ったアプリをAWSにデプロイして、いざアクセスしようとしたら403 Forbiddenエラーになりました。(下図)
Screen Shot 2020-02-23 at 16.59.26.png

前提条件

Ruby: 2.6.3
Rails: 6.0.2
Nginx: 1.12.2
サーバーのOS: Amazon Linux 2

解決策(先に結論だけ)

chmod 701 /home/ec2-userで解決しました(ec2-userディレクトリのパーミッションを700→701に変更しました)

/home/ec2-user/直下にRailsアプリを配置しています

エラーに遭遇するまでの経緯

下記のページを参考にしながら、RailsアプリをAWSにデプロイする作業を進めていました。
https://qiita.com/Yuki_Nagaoka/items/975b7598806d6ae0c0b2

上記のページでは、Railsアプリの設置場所を/var/www/rails/配下にしているのですが、「アプリの設置場所は好きなところでいいだろう」と思って、自分の場合は~//home/ec2-user/と同義)に配置しました。

結果的に、この設置場所の違いが今回のエラーを発生させることになりました。

エラーに遭遇してから、解決するまでの経緯

そもそも、403 Forbiddenとは?

ページが存在するものの、ページを表示する権限がなくてアクセスが拒否されたことを示すHTTPステータスコードです。
(今回の場合は、NginxがRailsアプリがあるディレクトリへのアクセス権限を持っていなかったのが原因で403エラーが発生しました)

参考:https://ja.wikipedia.org/wiki/HTTP_403

ログを確認し、エラーの原因を調べる

Nginxのログファイルの場所は、Nginxの設定ファイルに記述されています。
Nginxの設定ファイルは、OSによっても異なりますが、Amazon Linuxの場合は/etc/nginx/nginx.conf, /etc/nginx/nginx.conf.default, /etc/nginx/conf.d/***.confにあります。

/etc/nginx/conf.d/***.confの中身を見てみると、ログの場所が記述されているはずです。

/etc/nginx/conf.d/***.conf
error_log  /home/ec2-user/***/log/nginx.error.log;
access_log /home/ec2-user/***/log/nginx.access.log;
.
.
.

エラーログ(/home/ec2-user/***/log/nginx.error.log)の中身を見てみると、以下のようにPermission deniedのエラーが発生していることが分かりました。

/home/ec2-user/***/log/nginx.error.log
2020/02/23 05:01:35 [crit] 26964#0: *47 stat() "/home/ec2-user/***/public/" failed (13: Permission denied), client: ***, server: ***, request: "GET / HTTP/1.1", host: "***"
2020/02/23 05:01:35 [crit] 26964#0: *47 connect() to unix:/home/ec2-user/***/tmp/sockets/.unicorn.sock failed (13: Permission denied) while connecting to upstream, client: ***, server: ***, request: "GET / HTTP/1.1", upstream: "http://unix:/home/ec2-user/***/tmp/sockets/.unicorn.sock:/", host: "***"
2020/02/23 05:01:35 [error] 26964#0: *47 open() "/home/ec2-user/***/public/500.html" failed (13: Permission denied), client: ***, server: ***, request: "GET / HTTP/1.1", upstream: "http://unix:/home/ec2-user/***/tmp/sockets/.unicorn.sock/", host: "***"

念の為、Unicornのログファイルも見ておいた方が良いです。サーバー上のページにブラウザからアクセスして、Nginxのログは出てくるがUnicornのログに何も表示されないのであれば、Nginxでエラーが起きていることが分かるからです。

もしUnicornのログに何か表示されれば、Railsアプリ内でエラーが起きているということになります。

パーミッションとは?

以下サイトを参考にしてください。
https://eng-entrance.com/linux-permission-basic

なぜパーミッションがdenyされるのか?

ユーザーのディレクトリ(/home/ec2-user/)のパーミッションは700なので、その他のユーザーが/home/ec2-user/配下のディレクトリにアクセスできないことが原因です。

ブラウザからサーバー上のページにアクセスしたときに、nginxの実行ファイルはnginxというユーザー名で、Railsアプリがあるディレクトリにアクセスしていくつかのファイル(例えば、tmp/sockets/.unicorn.sockやpublic/など)を読み込みます。

つまり、nginxという名前のユーザーが、それらのファイルへのアクセス権限を持っている必要があります。また、ファイルにアクセスするためには、ルートディレクトリからそのファイルがあるディレクトリまでのすべてのディレクトリでアクセス権限を持っていなければいけません。

ちなみに、アクセス権限はパーミッションでいうところの実行権限(x)です。

解決策

したがって、nginxユーザーがec2-userディレクトリ配下にあるRailsアプリにアクセスできるようにするために、ec2-userディレクトリの「その他のユーザー」に実行権限を付与する必要があります。

(つまり、下記を実行することで解決)
chmod 701 /home/ec2-user

参考にしたサイト

https://maitakeramen.hatenablog.com/entry/2019/08/23/225552

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

フォームの遷移先の指定方法

Railsでフォームの遷移先が思ったようにいかなく、つまずいたため備忘録も兼ねて書かせていただきます。

問題点

編集データをcarts#updateに届けく、以下のようにしてみたが

carts/edit.html.erb
<%= form_with(model: @cart, local: true ) do |f| %>
  <div>
    <%= f.label :quantity, "購入数" %>
    <%= f.number_field :quantity %>
  </div>
  <%= f.submit "更新", class: "btn" %>
<% end %>

この遷移先が、なぜかcart_product_pathになってしまう。
また、Did you mean? で言われている edit_product_pathも希望する遷移先とは違う。

スクリーンショット 2020-02-21 20.46.30.png

改善方法

form_withのオプションで、url: cart_pathと指定するとうまく遷移するようになりました。

      carts_add   POST   /carts/add(.:format)        carts#add
      carts       GET    /carts(.:format)            carts#index
      edit_cart   GET    /carts/:id/edit(.:format)   carts#edit
      cart        PATCH  /carts/:id(.:format)        carts#update (#遷移先にしたい)
                  PUT    /carts/:id(.:format)        carts#update
                  DELETE /carts/:id(.:format)        carts#destroy
carts/edit.html.erb
<%= form_with(model: @cart, url: cart_path, local: true ) do |f| %>
  <div>
    <%= f.label :quantity, "購入数" %>
    <%= f.number_field :quantity %>
  </div>
  <%= f.submit "更新", class: "btn" %>
<% end %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(メモ)【Rails勉強会 第9回】アソシエーンション Part3(2020/2/23)

※こちらの記事は,「人生逆転サロン共同開発参加者限定の勉強会」で使用したコードのメモ書きです。

人生逆転サロンの共同開発について知りたい方はこちらをご覧下さい。
http://yanbaru-spike.com/l/c/yE5eqgMh/NuSs3Jlb

【開催日】 2/23(日) 19:30〜20:30

前回,Deviseでユーザーのログイン機能を実装し,メッセージの一覧表示,投稿機能までを実装しました。

今回は,メッセージの削除・編集機能を付け,さらに「いいね!」機能を実装するところまで解説したいと思います。

2.4 メッセージの削除機能

app/views/messages/index.html.erb
<% @messages.each do |message| %>
  <div>
    <p>【メールアドレス】 <%= message.user.email %></p>
    <p>【内容】 <%= message.content %></p>
    <!-- ***** 以下を追加 ***** -->
    <p>
      <%= link_to "削除", message_path(message), method: :delete, data: { confirm: "削除しますか?" } %>
    </p>
    <!-- ***** 以上を追加 ***** -->
  </div>
  <hr>
<% end %>
  • 悪い例
app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  # ***** 以下を編集 *****
  def destroy
    @message = Message.find(params[:id])
    @message.destroy!
    redirect_to root_path
  end
  # ***** 以上を編集 *****
end

【問題】 なぜこの書き方ではいけないのか?

  • 修正後
app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  # ログイン必須とする
  before_action :authenticate_user!
  # ***** 以下を追加 *****
  # 自分のメッセージであるかどうかをチェックする
  before_action :correct_user, only: %i[edit update destroy]
  # ***** 以上を追加 *****

  def index
    @messages = Message.includes(:user)
  end

  def new
    @message = Message.new
  end

  def create
    current_user.messages.create!(message_params)
    redirect_to root_path
  end

  def edit
  end

  def update
  end

  # ***** 以下を編集 *****
  def destroy
    @message.destroy!
    redirect_to root_path
  end
  # ***** 以上を編集 *****

  private

  def message_params
    params.require(:message).permit(:content)
  end

  # ***** 以下を追加 *****
  def correct_user
    @message = current_user.messages.find_by(id: params[:id])
    redirect_to root_path if @message.nil?
  end
  # ***** 以上を追加 *****
end

2.5 メッセージの編集機能

app/views/messages/index.html.erb
<% @messages.each do |message| %>
  <div>
    <p>【メールアドレス】 <%= message.user.email %></p>
    <p>【内容】 <%= message.content %></p>
    <p>
      <%= link_to "削除", message_path(message), method: :delete, data: { confirm: "削除しますか?" } %>
    <!-- ***** 以下を追加 ***** -->
      <%= link_to "編集", edit_message_path(message) %>
    <!-- ***** 以上を追加 ***** -->
    </p>
  </div>
  <hr>
<% end %>
app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  # ログイン必須とする
  before_action :authenticate_user!
  before_action :correct_user, only: %i[edit update destroy]

  def index
    @messages = Message.includes(:user)
  end

  def new
    @message = Message.new
  end

  def create
    current_user.messages.create!(message_params)
    redirect_to root_path
  end

  def edit
  end

  # ***** 以下を編集 *****
  def update
    @message.update!(message_params)
    redirect_to root_path
  end
  # ***** 以上を編集 *****

  def destroy
    @message.destroy!
    redirect_to root_path
  end

  private

  def message_params
    params.require(:message).permit(:content)
  end

  def correct_user
    @message = current_user.messages.find_by(id: params[:id])
    redirect_to root_path if @message.nil?
  end
end
app/views/messages/edit.html.erb
<%= form_with model: @message, local: true do |form| %>
  <%= form.text_field :content, required: true %>
  <%= form.submit "送信" %>
<% end %>

3. いいね機能の実装(多対多)

この章では,Twitterなどでおなじみの「いいね!」機能(の簡易版)を実装してみましょう。

2章のusersテーブルとmessagesテーブルのように関連付けを行えば……と思いきや,実は面倒な事態が起きています。

例えば次のような場合を考えてみましょう。

  • usersテーブル
id email
1 test@example.com
2 hoge@example.com
3 fuga@example.com
  • messagesテーブル
    • 「いいね!」と直接関係のないuser_idのカラムは省略
id content 「いいね!」した人 補足:「いいね!」したuser_id
1 おはよう hogeさん, fugaさん [2, 3]
2 こんにちは testさん, fugaさん [1, 3]
3 こんばんは fugaさん 3
4 おやすみ hogeさん 2
  • 1つのメッセージを「いいね!」しているユーザーは複数

  • 1人のユーザーが「いいね!」しているメッセージも複数

「いいね!」については,usersテーブルとmessagesテーブルの関係は「1対多」ではなく「多対多」になっている!

配列を入れたり,ほとんど同じレコードを作成するのは避けたい。どうすればよいか?

3.1 多対多のアソシエーション

「多対多」の場合は中間テーブルを作成し,2つの「1対多」に切り分ける

  • usersテーブル
id email
1 test@example.com
2 hoge@example.com
3 fuga@example.com
  • messagesテーブル
id content 補足:「いいね!」した人 補足:「いいね!」したuser_id
1 おはよう hogeさん, fugaさん [2, 3]
2 こんにちは testさん, fugaさん [1, 3]
3 こんばんは fugaさん 3
4 おやすみ hogeさん 2
  • likesテーブル(中間テーブル
id user_id message_id 補足
1 2 1 hogeさんが「おはよう」に「いいね!」
2 3 1 fugaさんが「おはよう」に「いいね!」
3 1 2 testさんが「こんにちは」に「いいね!」
4 3 2 fugaさんが「こんにちは」に「いいね!」
5 3 3 fugaさんが「こんばんは」に「いいね!」
6 2 4 hogeさんが「おやすみ」に「いいね!」
  • usersテーブルとlikesテーブルとの関係は「1対多」

  • messagesテーブルとlikesテーブルとの関係も「1対多」

3.2 中間テーブルの実装とアソシエーション

ターミナル
rails g model Like user_id:integer message_id:integer

「いいね!」が重複しないように,つまり,[user_id, message_id]の組が同じであるものが複数作成できないようにバリデーションを入れてからマイグレーションを行う。

db/migrate/日時_create_likes.rb
class CreateLikes < ActiveRecord::Migration[6.0]
  def change
    create_table :likes do |t|
      # ***** 以下を修正 *****
      t.integer :user_id, null: false, index: true
      t.integer :message_id, null: false, index: true
      # ***** 以上を修正 *****

      t.timestamps
    end
    # ***** 以下を追加 *****
    add_index :likes, [:user_id, :message_id], unique: true
    # ***** 以上を追加 *****
  end
end
ターミナル
rails db:migrate
  • モデルにバリデーションと関連付けを入れておく
    • UserモデルとLikeモデルは「1対多」
    • MessageモデルとLikeモデルは「1対多」
app/models/like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :message
  validates :user_id, presence: true, uniqueness: { scope: :message_id }
  validates :message_id, presence: true
end
app/models/user.rb
class User < ApplicationRecord
  has_many :messages, dependent: :destroy
  # ***** 以下を追加 *****
  has_many :likes, dependent: :destroy
  # ***** 以上を追加 *****
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end
app/models/message.rb
class Message < ApplicationRecord
  belongs_to :user
  # ***** 以下を追加 *****
  has_many :likes, dependent: :destroy
  # ***** 以上を追加 *****
  validates :content, presence: true, length: { maximum: 140 }
end
  • 先ほどの表のような初期データを投入
db/seeds.rb
# 中身は全て入れ替えて下さい
user_list = [
  { email: "test@example.com", password: "password" },
  { email: "hoge@example.com", password: "password" },
  { email: "fuga@example.com", password: "password" }
]

message_list = [
  { user_id: 1, content: "おはよう" },
  { user_id: 2, content: "こんにちは" },
  { user_id: 1, content: "こんばんは" },
  { user_id: 1, content: "おやすみ" }
]

like_list = [
  { user_id: 2, message_id: 1 },
  { user_id: 3, message_id: 1 },
  { user_id: 1, message_id: 2 },
  { user_id: 3, message_id: 2 },
  { user_id: 3, message_id: 3 },
  { user_id: 2, message_id: 4 }
]

User.create!(user_list)
Message.create!(message_list)
Like.create!(like_list)
puts '初期データの投入に成功しました!'
ターミナル
rails db:migrate:reset db:seed
  • rails cで確認

次のようにすれば,「いいね!」の総数を表示できる

app/views/shared/_header.html.erb
<header>
  <% if user_signed_in? %>
    <%= link_to "投稿一覧", root_path %>
    <%= link_to "新規投稿", new_message_path %>
    <%= link_to 'アカウント編集', edit_user_registration_path %>
    <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
    <%= "【ログイン中のアドレス】#{current_user.email}" %>
    <!-- ***** 以下を追加 ***** -->
    <%= "【いいね数】 #{current_user.likes.count}" %>
    <!-- ***** 以上を追加 ***** -->
  <% else %>
    <%= link_to "新規登録", new_user_registration_path %>
    <%= link_to "ログイン", new_user_session_path %>
  <% end %>
</header>
<hr>

【問題】メッセージを「いいね!」しているユーザーを取得するには?

app/models/message.rb
class Message < ApplicationRecord
  belongs_to :user
  has_many :likes, dependent: :destroy
  # ***** 以下を追加 *****
  # message.liked_users で message を「いいね!」しているユーザー一覧が取得できるようになる
  has_many :liked_users, through: :likes, source: :user
  # ***** 以上を追加 *****
  validates :content, presence: true, length: { maximum: 140 }
end

【問題】自分が「いいね!」しているメッセージを取得するには?

app/models/user.rb
class User < ApplicationRecord
  has_many :messages, dependent: :destroy
  has_many :likes, dependent: :destroy
  # ***** 以下を追加 *****
  # user.liked_messages で user が「いいね!」しているメッセージ一覧が取得できるようになる
  has_many :liked_messages, through: :likes, source: :message
  # ***** 以上を追加 *****
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end

3.2 「いいね!」ボタンの実装

app/views/messages/index.html.erb
<% @messages.each do |message| %>
  <div>
    <p>【メールアドレス】 <%= message.user.email %></p>
    <p>【内容】 <%= message.content %></p>
    <p>
      <%= link_to "削除", message_path(message), method: :delete, data: { confirm: "削除しますか?" } %>
      <%= link_to "編集", edit_message_path(message) %>
      <!-- ***** 以下を追加 ***** -->
      <% if message.likes.exists?(user_id: current_user.id) %>
        <!-- いいね!済み --><% else %>
        <!-- いいね!していない --><% end %>
      <!-- ***** 以上を追加 ***** -->
    </p>
  </div>
  <hr>
<% end %>

N+1問題を解消したい場合は,例えば次のように修正する。

app/controllers/messages_controller.rb
  def index
    @messages = Message.includes(:user)
    # ***** 以下を追加 *****
    # ユーザーが「いいね!」した全てのメッセージidの配列
    @liked_message_ids = current_user.likes.pluck(:message_id)
    # ***** 以上を追加 *****
  end
app/views/messages/index.html.erb
<% @messages.each do |message| %>
  <div>
    <p>【メールアドレス】 <%= message.user.email %></p>
    <p>【内容】 <%= message.content %></p>
    <p>
      <%= link_to "削除", message_path(message), method: :delete, data: { confirm: "削除しますか?" } %>
      <%= link_to "編集", edit_message_path(message) %>
      <!-- ***** 以下を修正 ***** -->
      <% if @liked_message_ids.include?(message.id) %>
      <!-- ***** 以上を修正 ***** -->
        <!-- いいね!済み --><% else %>
        <!-- いいね!していない --><% end %>
    </p>
  </div>
  <hr>
<% end %>

次に,「☆」を押したら「いいね!」の状態(「★」に変更)できるようにしましょう。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  # ***** 以下を追加 *****
  resources :messages do
    resource :likes, only: [:create, :destroy]
  end
  # ***** 以上を追加 *****
  root to: "messages#index"
end
  • rails routes | grep messageで作成されたルーティングを確認
ターミナル
touch app/controllers/likes_controller.rb
app/controllers/likes_controller.rb
class LikesController < ApplicationController
  def create
    current_user.likes.create!(message_id: params[:message_id])
    redirect_to root_path
  end

  def destroy
    current_user.likes.find_by(message_id: params[:message_id]).destroy!
    redirect_to root_path
  end
end
app/views/messages/index.html.erb
<% @messages.each do |message| %>
  <div>
    <p>【メールアドレス】 <%= message.user.email %></p>
    <p>【内容】 <%= message.content %></p>
    <p>
      <%= link_to "削除", message_path(message), method: :delete, data: { confirm: "削除しますか?" } %>
      <%= link_to "編集", edit_message_path(message) %>
      <% if @liked_message_ids.include?(message.id) %>
        <!-- いいね!済み -->
        <!-- ***** 以下を修正 ***** -->
        <%= link_to '★', message_likes_path(message), method: :delete %>
        <!-- ***** 以上を修正 ***** -->
      <% else %>
        <!-- いいね!していない -->
        <!-- ***** 以下を修正 ***** -->
        <%= link_to '☆', message_likes_path(message), method: :post %>
        <!-- ***** 以上を修正 ***** -->
      <% end %>
    </p>
  </div>
  <hr>
<% end %>

自分のメッセージに「いいね!」を表示させたくない場合は,次のように修正

app/controllers/likes_controller.rb
class LikesController < ApplicationController
  # ***** 以下を追加 *****
  # 他人のメッセージであるかどうかをチェックし,自分のメッセージであれば「いいね!」ができないようにする
  before_action :check_others_message
  # ***** 以上を追加 *****

  def create
    current_user.likes.create!(message_id: params[:message_id])
    redirect_to root_path
  end

  def destroy
    Like.find_by(user_id: current_user.id, message_id: params[:message_id]).destroy!
    redirect_to root_path
  end

  # ***** 以下を追加 *****
  private

  def check_others_message
    if Message.find(params[:message_id]).user_id == current_user.id
      redirect_to root_path
    end
  end
  # ***** 以上を追加 *****
end
app/views/messages/index.html.erb
<% @messages.each do |message| %>
  <div>
    <p>【メールアドレス】 <%= message.user.email %></p>
    <p>【内容】 <%= message.content %></p>
    <p>
      <%= link_to "削除", message_path(message), method: :delete, data: { confirm: "削除しますか?" } %>
      <%= link_to "編集", edit_message_path(message) %>
      <!-- ***** 以下を修正 ***** -->
      <%= render 'like', message: message %>
      <!-- ***** 以上を追加 ***** -->
    </p>
  </div>
  <hr>
<% end %>
ターミナル
touch app/views/messages/_like.html.erb
app/views/messages/_like.html.erb
<% if message.user != current_user %>
  <% if @liked_message_ids.include?(message.id) %>
    <!-- いいね!済み -->
    <%= link_to '★', message_likes_path(message), method: :delete %>
  <% else %>
    <!-- いいね!していない -->
    <%= link_to '☆', message_likes_path(message), method: :post %>
  <% end %>
<% end %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Redmineでもletter_openerを使って調整したメールを確認してみよう。

これはなに?

  • Redmineでは、メール本文のヘッダやフッタのカスタマイズができます
  • このカスタマイズが問題ないかどうか、letter_openerというgemを使って確認する方法をご紹介します

どういう人向けの記事?

このgemは、Railsでは非常に利用されているgemです。
どちらかというとRubyやRails専門ではないけれど、Redmineを運用したり、必要に応じてカスタマイズしたい人向けになります。

  • developmentモード で利用が前提のgemのため、開発環境で検証する方法を理解している方向けになります
    • カスタマイズの際に、まずdevelopmentモードで機能を追加調整する方針をとっている方
    • その上で本番に適用する、という流れを実施できる方

どういうシーンに使うと便利?

  • Redmineでメール部分のカスタマイズをしている方向け
  • 管理画面からのヘッダ / フッタ調整だけでなく、内部のメール用のテンプレートをカスタマイズしたい場合
  • プラグインでメールをカスタマイズしたり通知を追加したりする場合

想定通りの文言が当てはまってメールが生成されているかを確認しやすくなります。

やらないこと

letter_openerの設定

公式のREADMEに記載がある通り進めればOKです。
RedmineもRailsアプリケーションなので、Gemfileとdevelopment.rbの設定のみで利用できるようになります。

まずはgemの追加

  • Redmineのソースをダウンロード(clone) します
  • Redmine直下のGemfileに、以下を追記します
Gemfile
gem "letter_opener", :group => :development

追加したら、この追加gemを取得するため、bundle install を実行します。すでにRedmineに必要なgemが入っており、あとからその環境に追加する場合でも、同じくbundle install (bundle) であれば、追加で入ります。

# オプション無しだとdevelopmentモードのgemを取得します
bundle install

設定ファイルの調整

Redmine公式のメール設定を確認

Redmineの公式サイトでの、メール送信設定は以下に記載があります。

基本は、config/configuration.yml.example を元に config/configuration.yml ファイルを作成し、設定をしていきます。

developmentモードでも実際にsmtpを使ってメール送信を試すことができますが、デフォルトでは送信はされず、ログに送信メッセージが出力されます。

letter_openerの設定を追加する

letter_openerのREADMEに沿って設定する場合

developmentモードではメール送信をせず、letter_openerで確認する場合は、config/environments/development.rb に追記します。

config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_deliveries = true

Redmineにも config/environments/development.rb があるので、上記の通りの追記で利用が可能です。

Redmineのconfig/configuration.ymlを使う場合

Redmine公式のメール設定にも記載がある通り、こちらでも送信設定を調整することができます。
delivery_methodをletter_openerに設定となります。

config/configuration.yml
development:
  email_delivery:
    delivery_method: :letter_opener
    perform_deliveries: true

メールを送って確認してみる

developmentモードでチケットを作成、終了してみた例です。

  • メール送信対象のものは、tmp/letter_opener/ 以下に書き出されます
  • 実際の送信内容と同じく、メールのテンプレートに変数が展開された形で書き出しになります

mail-sending.png

カスタマイズしたメールの確認

Redmineに慣れていない場合は、ユーザの方が直にメールに返信してしまう、ということもありがちです。

運用の初めのころは、そういったことも配慮して「このメールには返信しないでくださいね!」的なメッセージを追加していました。

Redmineの管理画面では、メール本文の上部と下部(ヘッダ / フッタ)に簡単にメッセージを追加できますので、試しに設定して、想定通りになっているか確認してみます。

Redmineの管理画面

redmine-setting.png

結果はこのような感じ。

mail-customize.png

簡単ですが、十分に確認ができますね!

letter_opener_webも使う場合

letter_openerは、送信メールをファイルに書き出してくれるgemです。

さて、letter_openerはファイルシステムに書き出すため、ローカル開発環境やターミナルでの確認はできますが、開発環境がサーバにある場合は、ちょっと確認がしづらいですね。

こちらもRails開発ではポピュラーですが、letter_opener_webというgemを追加すると、Railsアプリケーションに同居する形で、ブラウザを通してletter_openerで書き出したメールを確認することができます。

追加するものは?

Gemfileに以下を追加します。

# developmentモードでのみ利用します
group :development do
  gem 'letter_opener_web', '~> 1.0'
end
  • gem "letter_opener" 単体を先に追加していれば、置き換えをします
  • bundle installの際に、依存関係でletter_openerも入ります
  • 上記の通り、letter_openerの設定が必要です
    • delivery_method = :letter_opener

ルーティングを追加

letter_opener_webのREADMEに記載のあるとおり、ルーティングを追加します。

config/routes.rb
# 設定例
redmine $ svn diff config/routes.rb
Index: config/routes.rb
===================================================================
--- config/routes.rb    (revision 19511)
+++ config/routes.rb    (working copy)
@@ -374,4 +374,6 @@
       end
     end
   end
+
+  mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?
 end
  • mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development? を追加して再起動します

ブラウザから確認してみる

ふたたびメール送信対象の操作をしてみます。
こちらはチケット変更の例。

letter_opener_web.png

ブラウザでアクセスすると、新しいメールから降順に一覧が表示されます。
また、右側には最新のメールが表示されます。

キャプチャの左隅の、tmp/letter_opener/ 側の件数と一致します。
letter_opener_webの画面から、不要になったメールを削除することも可能です。

まとめ

以上、簡単な例でした。

一度通知が出てしまうと、相手のもとに届いてしまったメールは取り消せません。必要以上の情報が盛り込まれていないか?宛先は適切か?など、心配なことがありますね。
Redmineに限らず、慎重さが必要なメールに関しては、こういったツールでチェックができるというのは、とても助かりますね。

実運用の際の、なにかにお役に立てば幸いです。

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

【Rails】deviseでパスワードなしでユーザ編集を完了させる

deviseでつくった編集画面で、パスワードを入力せずに編集を完了させようとすると、
Current password can't be blankというエラーメッセージが発生して、更新できないと思います。

今回は、これをパスワードを入れずにユーザー情報を編集できるようにします。

この記事などを参考にして、
1. deviseのregistration_controllerを継承したコントローラを作成
2. ルーティングの変更
を行ったあとに、update_resourceメソッドをオーバーライドします。

registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController

  protected
  def update_resource(resource, params)
    resource.update_without_password(params)
  end
end

omniauthのログインを使う方は、パスワードはもとから設定しない(もしくは、乱数で設定する)場合も多いかと思いますので、そういった場合は以下のようにすればよいのではないでしょうか。

registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController

  protected
  def update_resource(resource, params)
    if current_user.uid.present? || current_user.provider.present?
      resource.update_without_password(params)
    else
      super
    end
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

sidekiqざっくりまとめ

Railsで非同期処理を実現するsidekiqについて調べてわかったことをまとめます。
sidekiq自体はRails以外でも使えるものらしいです。

何ができるか

非同期処理を実現する。
例えば画像アップロードの処理を非同期にすることで、ユーザーは画像アップロードが終わるまで待つことなく他のページに遷移できて、快適。

必要なもの

  • redisサーバー
  • gem: sidekiq

使い方

app/workers/◯◯_worker.rbにクラスとメソッドを作成する

worker
class HogeWorker
  include Sidekiq::Worker

  def perform(id)
    @event = Event.find(id)
    @event.calculate_rank!
  end
end

controller内で<クラス名>.<メソッド名>_asyncでキューに入れる

controller
def ranking
  HogeWorker.perform_async @event.id
end

トリガーを発火して実行する

コマンドラインでコマンドを実行したり、あらかじめ定義した条件に合致した時、キューに入った処理が実行されます。

参考

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

ずぶの素人がAWSでRailsアプリを作るメモ

仕事でRailsを使うので、この機会に自分でも何かしようと思い行動に移すことにしました。

やりたいことリスト:
- Railsでサンプルアプリケーションをつくる
- 自分流にアレンジする
- 仕事で使わないような技術に触れる

ひとまずやってみようの精神で、失敗しながら学びます。

というわけでまずは環境から。
いろいろとローカルに用意するのは挫折しそうだったので、手っ取り早くAWSのCloud9を使用します。
ありがたいことに必要なものはそろっている(素人目線)ので、
create_env.png

新しく環境を作ったら、さくっと始めていきます。
新規作成した環境には初めは何もないので、Railsアプリをつくるときは
rails new sample -d mysql
でディレクトリから作成します。
(個人的にMySQLのほうが好きなのでDBを軽率に変更しています、「-d ~」はなくてもOKです)

用意ができたのでさっそくRailsサーバーを立ち上げて(rails s)、まっさらなアプリを起動しよう…
としたのですが、エラーメッセージが。
仕事でも見たことないメッセージだったのでここでしばらくワタワタしました(素人感)。
error.png

まだしっかり理解してないですが、MySQLが起動してないのでDBに接続できないと怒られているようです。
ネットで調べると以下のような記事が。
MySqlのソケットエラーを解決する

ありがたく以下のコマンドを拝借して、実行します。
sudo /etc/init.d/mysqld restart
そうするとエラーの種類が変わりまして、”sample_development”なんてDBはねえよ、と言われました。
これは仕事場でも見たことがあったので、おとなしく
rake db:create
します。(rails db:createでもいいはず?)

今度こそうまく動いて、Railsの初期アプリの画面が表示されました。
Rails.png

DBをSQLiteからMySQLに変更するだけでこんなにわたつく滑り出しで、今後どういった艱難辛苦があるか想像できませんが、ひとまず進めるだけは進めたいと思います。

ネタがあったり、詰まっていた部分が解決したらまたメモします。

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

【Rails】SQL文を直接実行する

ActiveRecordを使わずに、自分で作成したSQL文を使ってDBに問い合わせをする方法です。

name = 'サンプル太郎'
email = 'tarou@example.com'
now = Time.zone.now
args = ['INSERT INTO users (name, email, created_at, updated_at) VALUES (?, ?, ?, ?)', name, email, now, now] #下準備
sql = ActiveRecord::Base.send(:sanitize_sql_array, args) #sql文を作成
ActiveRecord::Base.connection.execute(sql) #sql文を実行

注意点

  • 謎の戻り値が返ってくるので、SELECTはできません。
  • 実行速度やサーバーへの負荷をかなり抑えることができますが、バリデーションや外部キー制約が効かなくなるなど懸念点も多いので注意が必要です。
  • いわゆる「Railsのレール」からは外れた実装だと思うので、ActiveRecordではどうにもならない時にだけ使う感じです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

attr_accessorで挫折した君へ

はじめに

僕は恥ずかしながらattr_accessorをRuby歴7ヶ月にして理解しました。学びたての頃は、『classの下についてる変なやつ』としか思っておらず、何のために置かれてるのかわからないまま、とりあえず置いとけ、エイっ!!という感じで使っていました(ひどい)

ということで、attr_accessorで挫折した僕が、attr_accessorで挫折した人にattr_accessorを挫折しないように分かりやすく説明したいと思います。

ゴール

本記事では、以下のコードを完全に理解することをゴールとして説明します。
すでに理解できてるよという方はそっとブラウザバックしてください。

user.rb
class User
  attr_accessor :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end
end

大前提

はじめに、大前提を説明して目線を合わせます。

『基本的に、オブジェクトの値をクラス外から参照・書き込みすることはできない』

どういうこと?と思った方は試しに以下のコードを実行してみてください。

user.rb
class User
  def initialize(name, age)
    @name = name
    @age = age
  end
end


user = User.new("山田",18)
user.name
user.age

おそらく、
undefined method `name' for #<User:0x00007faf4c182dd0 @name="山田", @age=18> (NoMethodError)

と怒られたはずです。

ちなみに、userの名前を書き換えるとどうなるでしょうか?

user.name = "佐藤"
=> undefined method `name=' for #<User:0x00007fee848daac0 @name="山田", @age=18> (NoMethodError)

やはり怒られました...

そうなんです。何も設定をしなければ、いつも当たり前にやっていた参照、書き換えは実行できないんです。

『え??いつもRailsでやったときは出来てたのに何で今回は出来ないの??』

と思ったかもしれません。分かります。僕も全く同じように考えていました。
その答えは本記事の中でじっくり説明しますので、もう少し読み進めて行きましょう。

力技で解決してみる

何も設定しなけば、オブジェクトの値の読み書きができないことが分かりました。
それなら、力技で解決するためにこんなコードを書いてみました。

user.rb
class User
  def initialize(name, age)
    @name = name
    @age = age
  end

  def name
    @name
  end

  def name=(name)
    @name = name
  end
end

1つ目が、nameの値を読むためのメソッド、
2つ目が、nameの値を書き込むためのメソッドです。

※ name=(name)という変な書き方にアレルギー反応が出た人がいるかもしれませんが、「値を更新するときはこう書くんだ」ぐらいで覚えてもらえれば大丈夫です。

これを書けば、user.nameとしたとき、nameメソッドが呼び出され、その中で@nameを参照しているため、正常に値を取り出すことができます。書き込みについても同様です。

それでは、以下を実行してみましょう。

user.rb
class User
  省略
end
user = User.new("山田",18)

p user.name
p user.name = "佐藤"

結果

"山田"
"佐藤"

今度はうまく行きました。
これで解決ですね、めでたしめでたし。

。。。、となってしまったら大変です。なぜなら、このやり方だと属性(name、age)が増えるたびに読み書き用のメソッドを作らなければいけません。

今回でいうならこんな感じ。

user.rb
class User
  def initialize(name, age)
    @name = name
    @age = age
  end

  def name
    @name
  end

  def name=(name)
    @name = name
  end

  def age
    @age
  end

  def age=(age)
    @age = age
  end
end

これは流石に面倒の極みです。
ということで、Rubyはこれを解決するためにアクセスメソッドというものを準備してくれています。ありがたや。

すごく便利なアクセスメソッド

では、先ほどの面倒なコードをアクセスメソッドを使ってシンプルにしていきましょう。

attr_readerメソッド

attr_readerメソッドは以下の2つのメソッドの代わりになります。

# attr_reader :name, :age は以下の2つと同じ意味

def name
  @name
end

def age
  @age
end

つまり、先ほどの長ったらしいコードは以下のようにシンプルになります。

user.rb
class User
  attr_reader :name, :age
  def initialize(name, age)
    @name = name
    @age = age
  end

  def name=(name)
    @name = name
  end


  def age=(age)
    @age = age
  end
end

メソッドを2つ省略できたので、かなりシンプルになりました。もう少しスッキリさせましょう。

attr_writerメソッド

情報を書き込みするメソッドを省略するために、attr_writerメソッドがあります。
これを使うと以下のメソッドを省くことができます。

# attr_writer :name, :age は以下の2つと同じ意味

def name=(name)
  @name = name
end


def age=(age)
  @age = age
end

これを使うと、かなりスッキリ。

user.rb
class User
  attr_reader :name, :age
  attr_writer :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end
end

最初のコードと比べたら、別人のようですね!

でも、まだ甘いです。実はもっとシンプルにできます。

attr_accessorメソッド

もう十分でしょ、と思うかもしれませんがRubyにはattr_readerメソッドとattr_writerメソッドを兼ね備えた万能のメソッドが存在します。

それが、attr_accessorメソッドです!

これを使えば、

attr_reader :name, :age
attr_writer :name, :age

が、

attr_accessor :name, :age

になります。最終形態は以下の通り。

user.rb
class User
  attr_accessor :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end
end

最終的にたったの8行におさまりました。アクセスメソッドの威力はハンパないですね!

つまり、これまで何となくクラスの頭に置いていたattr_accessorメソッドは、オブジェクトの値の読み書きを可能にしてくれる万能なメソッドだったというわけです。

じゃあ何でRailsでは何もしてないのにオブジェクトの値の読み書きができるのさ

普段、Railsで開発しているとき、特に何も意識しなくても、以下のようにオブジェクトの値を読み書きできますよね?

user.name
=> "佐藤"
user.name = "山田"
user.name
=> "山田"

これはなぜでしょうか?

答え

Railsにおいて、テーブルに紐づく全てのModelはActiveRecordを継承しているから

解説

rails generateで作成されたModelは、自動的にActiveRecordというクラスを継承するようにRailsが設定しています。

class User < ApplicationRecord
end

superclassで先祖を辿っていくと、

irb> User.superclass
=> ApplicationRecord
irb > ApplicationRecord.superclass
=> ActiveRecord::Base

確かにActiveRecordというクラスがありました。

このActiveRecordにおいて、usersテーブルの各カラムをアクセスメソッド(attr_accessor)として登録しているため、
僕たちが意識しなくても、オブジェクトの値の読み書きが行えているわけですね。

ActiveRecord様には足を向けて寝れません。笑

まとめ

以上を簡単にまとめるとこうなります。

  • 基本的にクラス外からオブジェクトの値を読み書きすることができない
  • 1つの属性ごとに読み書き専用のメソッドを作ればいつも通り読み書きできるようになる
  • しかし、毎回メソッドを書くのはめんどくさい
  • そこで、アクセスメソッドの出番!
  • attr_readerメソッドを使うと、「読み」用のメソッドを省略できる
  • attr_writerメソッドを使うと「書き込み」用のメソッドを省略できる
  • attr_accessorメソッドを使えば「読み」「書き」どちらも省略できる
  • Railsでオブジェクトの値を自由に読み書きできるのはActiveRecordを継承しているから

この記事でattr_accessorという不気味なメソッドへの理解が少しでも深まれば幸いです。
最後までお読みいただきありがとうございました!

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

docker-composeでRails6開発環境

docker-composeでRails5.2開発環境に続いて、Rails6の環境も作ってみます。5.2から移行してくるとyarnとwebpackerがなかなか最初はややこしく感じます…。最下部に理由を書いていますが、alpine版のイメージをベースにしています。

必要なファイルは2つです。

docker-compose.yml
version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./postgresql/data:/var/lib/postgresql/data
  web:
    build: .
    command: "rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - ./railsapp:/railsapp
    ports:
      - "3000:3000"
    depends_on:
      - db
FROM ruby:2.6-alpine

RUN apk update && apk add libxml2-dev curl-dev make gcc libc-dev g++ nodejs \
  postgresql-client postgresql-dev tzdata yarn \
  && gem install rails -v "~> 6" -N

RUN mkdir /railsapp
WORKDIR /railsapp
# 後で有効化します
# COPY ./railsapp /railsapp
# RUN bundle install && yarn install

まずは以下の手順で新しいプロジェクトを生成します。railsappフォルダ下にファイルが作られます。

docker-compose build
docker-compose run web rails new . --database=postgresql --skip-git

その後、上記Dockerfile内の最後の2行を有効化します。

COPY ./railsapp /railsapp
RUN bundle install && yarn install

データベースの接続設定も修正が必要です。

railsapp/config/database.yml
host: db
username: postgres

以下のコマンドを実行します。

docker-compose build
docker-compose up

あとは、いつもの要領でrailsの開発が開始できます!

docker-compose exec web rails db:create

Screen Shot 2020-02-23 at 13.45.51.png

docker-compose exec web rails g scaffold post title:string body:text published:boolean
docker-compose exec web rails db:migrate

ハマりどころ

ruby:2.6ruby:2.7のイメージを使ってみたところ、yarnのバージョンが適合せず動作しなかったため(下記エラー)、
ruby:2.6-alpineイメージを使用しています。軽量さが特徴のようですが、逆に言うと、
何か追加のgemを入れるたびにOS側のパッケージ追加が必要になるのが大変そうです…。

ArgumentError: Malformed version number string 0.32+git

初回のbundle installがかなり重たいのに、この手順だと2回それが走ることになり、待ち時間が長いです。
もし最初から使えるGemfileが手元にあれば、それをrailsappに置いた状態でスタートすると速いです(公式の手順などで案内している方法)。

参考URL

https://opiyotan.hatenablog.com/entry/rails-tips-docker-development

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

docker-composeでRails6開発環境

docker-composeでRails5.2開発環境に続いて、Rails6の環境も作ってみます。5.2から移行してくるとyarnとwebpackerがなかなか最初はややこしく感じます…。最下部に理由を書いていますが、alpine版のイメージをベースにしています。

必要なファイルは2つです。

docker-compose.yml
version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./postgresql/data:/var/lib/postgresql/data
  web:
    build: .
    command: "rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - ./railsapp:/railsapp
    ports:
      - "3000:3000"
    depends_on:
      - db
FROM ruby:2.6-alpine

RUN apk update && apk add libxml2-dev curl-dev make gcc libc-dev g++ nodejs \
  postgresql-client postgresql-dev tzdata yarn \
  && gem install rails -v "~> 6" -N

RUN mkdir /railsapp
WORKDIR /railsapp
# 後で有効化します
# COPY ./railsapp /railsapp
# RUN bundle install && yarn install

まずは以下の手順で新しいプロジェクトを生成します。railsappフォルダ下にファイルが作られます。

docker-compose build
docker-compose run web rails new . --database=postgresql --skip-git

その後、上記Dockerfile内の最後の2行を有効化します。

COPY ./railsapp /railsapp
RUN bundle install && yarn install

データベースの接続設定も修正が必要です。

railsapp/config/database.yml
host: db
username: postgres

以下のコマンドを実行します。

docker-compose build
docker-compose up

あとは、いつもの要領でrailsの開発が開始できます!

docker-compose exec web rails db:create
docker-compose exec web rails g scaffold post title:string body:text published:boolean
docker-compose exec web rails db:migrate

ハマりどころ

ruby:2.6ruby:2.7のイメージを使ってみたところ、yarnのバージョンが適合せず動作しなかったため(下記エラー)、
ruby:2.6-alpineイメージを使用しています。軽量さが特徴のようですが、逆に言うと、
何か追加のgemを入れるたびにOS側のパッケージ追加が必要になるのが大変そうです…。

ArgumentError: Malformed version number string 0.32+git

初回のbundle installがかなり重たいのに、この手順だと2回それが走ることになり、待ち時間が長いです。
もし最初から使えるGemfileが手元にあれば、それをrailsappに置いた状態でスタートすると速いです(公式の手順などで案内している方法)。

参考URL

https://opiyotan.hatenablog.com/entry/rails-tips-docker-development

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

Railsで間違えて作成したcontrollerやmodelを削除する方法

railsでプログラミングをするにあたって、間違えてcontrollerを作成してしまうことが僕はたまにあります。一文字間違えとか、あるあるです。

僕の場合はコントローラーの作成にこういったミスをするとどうしたらいいかわからなくなって、一からアプリケーションを作り直すということをプログラミングを学び始めの時などはやっていたりしました。

この方法を知って「なんて便利なんだ」と感動した覚えがあるので、備忘録もかねて残しておこうと思います。

controllerやmodelを間違えて作成した時の対処法

方法はシンプルかつ簡単です。例えばusers controllerを削除したいときは、。

rails destroy controller users

User modelを削除したいときも同じように

rails destroy model user

とすることで間違って作成したものを削除することができます。参考になれば幸いです。

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

【Rails】APIモードで使えるHTTPステータスコードのシンボルまとめ

はじめに

Railsで使えるHTTPステータスコードのシンボルの一覧です。
Rails APIモードで使えるのではないかと思います。

環境

OS: macOS Catalina 10.15.3
Ruby: 2.6.5
Rails: 6.0.2.1

きっかけ

JSONを返すAPIのコントローラーでは、以下のような一文をよく見かけます。

users_controller.rb
render json: user, status: ok # この:okのこと

これは、下記のようにも書けます。

users_controller.rb
render json: user, status: 200

この方が簡潔に書けるし、「はいはい、200OKだよね!」と分かるかと思います。

でも、これならどうでしょうか?

sample.rb
status: 429

シンボルで書くと、こうなります。

sample.rb
status: :too_many_requests

「リクエスト多すぎ!」ってことですね。
1時間あたりに〇〇回しかリクエスト投げられないAPIを使っていて、リクエストを投げすぎたときに返ってきます。

結論:200 or :ok どちらを使うべきか

  • チーム開発を経た経験から、開発者のスキルが全員同じは絶対にない
  • HTTPステータスコードより英語の方がわかりやすい

という点から、:okの方が全体に対するメリットが多いのではないかと思います。

とはいえ、「覚えられないのでリファレンス欲しいと思いましたので今回まとめました!

HTTPステータスコードのシンボル一覧

シンボル一覧.rb
100 :continue
101 :switching_protocols
102 :processing
103 :early_hints
200 :ok
201 :created
202 :accepted
203 :non_authoritative_information
204 :no_content
205 :reset_content
206 :partial_content
207 :multi_status
208 :already_reported
226 :im_used
300 :multiple_choices
301 :moved_permanently
302 :found
303 :see_other
304 :not_modified
305 :use_proxy
306 :unused
307 :temporary_redirect
308 :permanent_redirect
400 :bad_request
401 :unauthorized
402 :payment_required
403 :forbidden
404 :not_found
405 :method_not_allowed
406 :not_acceptable
407 :proxy_authentication_required
408 :request_timeout
409 :conflict
410 :gone
411 :length_required
412 :precondition_failed
413 :payload_too_large
414 :uri_too_long
415 :unsupported_media_type
416 :range_not_satisfiable
417 :expectation_failed
421 :misdirected_request
422 :unprocessable_entity
423 :locked
424 :failed_dependency
425 :too_early
426 :upgrade_required
428 :precondition_required
429 :too_many_requests
431 :request_header_fields_too_large
451 :unavailablefor_legal_reasons
500 :internal_server_error
501 :not_implemented
502 :bad_gateway
503 :service_unavailable
504 :gateway_timeout
505 :http_version_not_supported
506 :variant_also_negotiates
507 :insufficient_storage
508 :loop_detected
509 :bandwidth_limit_exceeded
510 :not_extended
511 :network_authentication_required

対応するヘルパーメソッド

ヘルパー一覧.rb
def invalid?;             status < 100 || status >= 600;        end

def informational?;       status >= 100 && status < 200;        end
def successful?;          status >= 200 && status < 300;        end
def redirection?;         status >= 300 && status < 400;        end
def client_error?;        status >= 400 && status < 500;        end
def server_error?;        status >= 500 && status < 600;        end

def ok?;                  status == 200;                        end
def created?;             status == 201;                        end
def accepted?;            status == 202;                        end
def no_content?;          status == 204;                        end
def moved_permanently?;   status == 301;                        end
def bad_request?;         status == 400;                        end
def unauthorized?;        status == 401;                        end
def forbidden?;           status == 403;                        end
def not_found?;           status == 404;                        end
def method_not_allowed?;  status == 405;                        end
def precondition_failed?; status == 412;                        end
def unprocessable?;       status == 422;                        end

def redirect?;            [301, 302, 303, 307, 308].include? status; end

おわりに

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

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

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

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

Rails プロジェクト作成手順

今までCloud9を使ってRails開発をちょこちょこしていました。
今回は、イチからやってみようということでCloud9を使わずにプロジェクト作成をすることにしました。

①Homebrewのインストール

Macユーザー限定ですが、まずはHomebrewのインストールを行います。
公式に書いてある通りにスクリプトを実行すれば、問題ないはずです。

バージョンが表示されれば、うまくインストールされています。

$ brew -v
Homebrew 2.2.6

②rbenvのインストール

Rubyのバージョン管理ができるようにrbenvのインストールを行います。

$ brew install rbenv ruby-build

次にパスを通します。
なぜかというと、MacデフォルトのRubyを使わずにrbenvでインストールしたRubyを使用するためです。

MacデフォルトのRubyは、/usr/binというところに入っているのですが、rbenvでインストールしたRubyは、/Users/ユーザー名/.rbenv/shims/に入ります。

そのため、ターミナルからRubyを実行するときに、rbenvの中のRubyでRubyを実行するように切り替えをする必要があります。

$ echo 'export PATH="~/.rbenv/shims:/usr/local/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile  //.bash_profileを読み込む

eval “$(rbenv init -)”.bash_profileファイルに書いて読み込みすることによって、Macにログインしたら、Rubyコマンドを実行するときはrbenv内のRubyを実行するように設定しておくができます。

➂Rubyのインストール

ここでようやくRubyのインストールを行います。
まずは、インストール可能なバージョンを確認します。

$ rbenv install --list

今回は、2.7.0をインストールします。

$ rbenv install 2.7.0
  // 環境全体の有効なバージョンを2.7.0にする
$ rbenv global 2.7.0
$ rbenv rehash
$ ruby -v

④Bundlerのインストール

次に、Bundlerのインストールをします。
Bundlerとは、gemの依存関係とバージョンを管理するためのツールです。
Bundlerを使うと、依存関係のあるgemを一括でインストールしてくれます。
一括でインストールしたgemはすべて、依存関係が解決された状態で、インストールされます。

$ gem install bundler
$ bundle -v

⑤Railsプロジェクトの作成

ここで、プロジェクトを作成します。
作成したプロジェクトの中で、bundle initを行います。

$ mkdir ~/testApp
$ cd ~/testApp
$ bundle init

成功すれば、Gemfileが作られているはずです。
Gemfileとは、インストールしたいgemを列挙するものです。
ただ、このままだと使えないので、Gemfileを編集する必要があります。

GemFile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "rails"  ←コメントアウトを外す

システムのgemはできるだけクリーンに保ち、gemはvendor/bundleに入れてbundle execで呼び出すのがいいそうなのでそうします。

$ bundle install --path=vendor/bundle
$ bundle exec rails -v
Rails 6.0.2.1

--path vendor/bundleオプションを付けることで、プロジェクトのvendor/bundle以下にgemが格納されます。
次回以降はオプションを付けなくてもvendor/bundle以下に格納されるはずです。

プロジェクト別にgemをインストールすると、プロジェクトごとのgemのバージョンの違いを気にすることがなくなります。

最後に、Railsプロジェクトを作成します。

$ bundle exec rails new . -B --skip-test

.を付けると現在のディレクトリに作成されます。

少しだけオプションの説明をすると、-BはRailsプロジェクト作成時にbundle installを行わないようにします。
また、--skip-testはデフォルトのminitestというテストを使わない時に付ける。他のテストフレームワークを利用したい時に使うといいです。
他のオプションはドキュメントを参考にするといいと思います。

プロジェクトにいる状態で、下記コマンドを実行して、http://localhost:3000/ へアクセスします。(サーバー起動)

$ rails server

Railsの画面が表示されれば、成功です。

スクリーンショット 2020-02-23 11.54.27.png

Cloud9はそこまで意識せずにプロジェクトが作れちゃうので、楽ですね!
ただ、環境構築で何が行われるのかを知るという意味ではやってみるのもいいかもしれません。
また、実際の開発現場でCloud9を使ってというのはあんまりないと思うので、できた方がいい気がします。

補足

Rails6からWebpackerがデフォルトでインストールされます。
なので、Webpackeryarnをインストールしないとサーバー起動時に失敗する場合があります。
その場合は、インストールしましょう。

// yarnを先にインストールしないとwebpackerをインストールできない
$ brew install yarn

$ rails webpacker:install

参照

Ruby初学者のRuby On Rails 環境構築【Mac】
Rails6 Webpackerでエラーが出た

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

[Rails] rbenvのコマンド、ググるのめんどくさい方へ(ターミナル上で完結する方法)

きっかけ

rbenvってそこまで使うものでもないので、
時間が経てばコマンドは忘れてしまいます。

ただインターネットでいちいち調べるのが個人的には面倒で(おい)、
全部ターミナル上で完結させたいと感じていました。

その方法を色々と調べましたので、同じような考えを持つ方のためにまとめておきます。

rbenvで使用できるコマンドを表示しよう

rbenvと打ち込むと、コマンドが色々と出てきます。

$ rbenv

rbenv 1.1.2
Usage: rbenv <command> [<args>]

Some useful rbenv commands are:
   commands    List all available rbenv commands
   local       Set or show the local application-specific Ruby version
   global      Set or show the global Ruby version
   shell       Set or show the shell-specific Ruby version
   install     Install a Ruby version using ruby-build
   uninstall   Uninstall a specific Ruby version
   rehash      Rehash rbenv shims (run this after installing executables)
   version     Show the current Ruby version and its origin
   versions    List installed Ruby versions
   which       Display the full path to an executable
   whence      List all Ruby versions that contain the given executable

Some useful rbenv commands are:以下がrbenvで用意されているコマンドです。

各コマンドで使用できるオプションを表示しよう

今回はrbenv installに焦点をあてます。
コマンドの後ろに--helpを打ち込むか、helpコマンドを使用してください。

$ rbenv install --help

または

$ rbenv help install

前者を実行すると

$ rbenv install --help

Usage: rbenv install [-f|-s] [-kpv] <version>
       rbenv install [-f|-s] [-kpv] <definition-file>
       rbenv install -l|--list
       rbenv install --version

  -l/--list          List all available versions
  -f/--force         Install even if the version appears to be installed already
  -s/--skip-existing Skip if the version appears to be installed already

  ruby-build options:

  -k/--keep          Keep source tree in $RBENV_BUILD_ROOT after installation
                     (defaults to $RBENV_ROOT/sources)
  -p/--patch         Apply a patch from stdin before building
  -v/--verbose       Verbose mode: print compilation status to stdout
  --version          Show version of ruby-build

For detailed information on installing Ruby versions with
ruby-build, including a list of environment variables for adjusting
compilation, see: https://github.com/rbenv/ruby-build#usage

とでます。

このUsageの部分がrbenv installで用意されているオプションです。
そしてその下に各コマンドの説明が書いてあります。

こうすることでターミナル上でコマンドおよびオプションを確認することができました。

(ご参考) よく使うコマンド/オプション一覧

インストール可能なRubyのバージョンを確認

$ rbenv install -l

(省略)
2.6.3
2.6.4
2.6.5
2.7.0-dev
(省略)

ファイルのパスを確認

$ rbenv which ファイル名

rbenv whichまで入力してtabを押すと、候補が出てきます

$ rbenv which
--help     bundler    gem        nokogiri   racc2y     rails      rdoc       ruby       thor
bundle     erb        irb        racc       rackup     rake       ri         sprockets  y2racc

実行結果

$ rbenv which ruby
=> /Users/owner/.rbenv/versions/2.6.3/bin/ruby

現在インストールされているRubyのバージョンを確認

$ rbenv versions

  system
  2.5.3
  2.6.0-dev
* 2.6.3 (set by /Users/owner/.rbenv/version)

現在使用しているRubyのバージョンを確認

$ rbenv version
2.6.3 (set by /Users/reiforu/.rbenv/version)

ローカルのRubyのバージョンを変更・確認

$ rbenv local
2.6.3
$ rbenv local 2.5.3
$ rbenv local
2.6.3

グローバルのRubyのバージョンを変更・確認

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

【Rails】portを使っていないのにAddress already in useエラーが出る場合の応急処置

はじめに

少し昔のrailsアプリ少しいじろうかと思ったのですが、見事にハマりました。
bundle updateだとかgem pristineだとかをしたのですが、結局rails serverができなかったので、奮闘記録と共にとりあえずのサーバー起動方法を書いておきます。

動作環境

  • Ruby 2.5.3
  • Rails 5.2.4.1
  • puma 3.12.2
  • ローカル環境

Address already in useエラー

$ rails s
=> Booting Puma
=> Rails 6.0.2.1 application starting in development 
=> Run `rails server --help` for more startup options
[1234] Puma starting in cluster mode...
[1234] * Version 4.3.1 (ruby 2.5.3-p105), codename: Mysterious Traveller
[1234] * Min threads: 5, max threads: 5
[1234] * Environment: development
[1234] * Process workers: 2
[1234] * Preloading application
[1234] * Listening on tcp://127.0.0.1:3000
[1234] * Listening on tcp://[::1]:3000
Exiting
Traceback (most recent call last):
        37: from bin/rails:3:in `<main>'
        36: from bin/rails:3:in `load'
        35: from /Users/k_end/workspace/personal/kiite_app/bin/spring:15:in `<top (required)>'
        34: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'
        33: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'
        32: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `<top (required)>'
        31: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `load'
        30: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/spring-2.1.0/bin/spring:49:in `<top (required)>'
        29: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/spring-2.1.0/lib/spring/client.rb:30:in `run'
        28: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/spring-2.1.0/lib/spring/client/command.rb:7:in `call'
        27: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `call'
        26: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `load'
        25: from /Users/k_end/workspace/personal/kiite_app/bin/rails:9:in `<top (required)>'
        24: from /Users/k_end/workspace/personal/kiite_app/bin/rails:9:in `require'
        23: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/commands.rb:18:in `<top (required)>'
        22: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/command.rb:46:in `invoke'
        21: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/command/base.rb:69:in `perform'
        20: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor.rb:392:in `dispatch'
        19: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/invocation.rb:127:in `invoke_command'
        18: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/command.rb:27:in `run'
        17: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/commands/server/server_command.rb:138:in `perform'
        16: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/commands/server/server_command.rb:138:in `tap'
        15: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/commands/server/server_command.rb:147:in `block in perform'
        14: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/commands/server/server_command.rb:39:in `start'
        13: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/rack-2.2.2/lib/rack/server.rb:327:in `start'
        12: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/rack/handler/puma.rb:73:in `run'
        11: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/puma/launcher.rb:172:in `run'
        10: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/puma/cluster.rb:413:in `run'
         9: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/puma/runner.rb:161:in `load_and_bind'
         8: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/puma/binder.rb:90:in `parse'
         7: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/puma/binder.rb:90:in `each'
         6: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/puma/binder.rb:106:in `block in parse'
         5: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/puma/binder.rb:222:in `add_tcp_listener'
         4: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/puma/binder.rb:222:in `each'
         3: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/puma/binder.rb:223:in `block in add_tcp_listener'
         2: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/puma/binder.rb:229:in `add_tcp_listener'
         1: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/puma/binder.rb:229:in `new'
/Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/puma/binder.rb:229:in `initialize': Address already in use - bind(2) for "127.0.0.1" port 3000 (Errno::EADDRINUSE)

サーバーを起動させたままコンソールを閉じてしまうというよくあるパターンだと思い、いつも通りの手順を踏む。

$ ps ax | grep rails
  834 s000  S+     0:00.00 grep --color=auto rails

834ps ax | grep railsによるプロセスなので、他に動いているプロセスはない。

$ ps aux | grep puma
k_end              882   0.0  0.0  4276968    712 s000  R+    7:50AM   0:00.00 grep --color=auto puma

こちらも同様。

$ lsof -wni tcp:3000

もちろん意味なし。
この辺りで、いやまさかそんなはずが……と思い始める。

ポートを使っているか確認してみた

teratailのRails sでポート3000番で立ち上がらないのコメントを参考にし、bashで確認したところ、

$ exec 3<>/dev/tcp/localhost/3000
bash: connect: Connection refused
bash: /dev/tcp/localhost/3000: Connection refused

ポート使っていないですね。
ということは、他のアプリなら起動できるのでは?と思い、やってみたところ……

$ rails s
=> Booting Puma
=> Rails 5.2.3 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 4.3.1 (ruby 2.6.5-p114), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3000
* Listening on tcp://[::1]:3000
Use Ctrl-C to stop

行けるんじゃん!!
やっぱり、ポートの問題ではなく、違うところでエラーがあるらしい。

ということは、これはpumaの問題かなあ。というか、とりあえずでアップデートしたせいかrailsのバージョンも6台になってる。

奮闘の記録

pumaをシングルモードにしてみた

Puma starting in cluster mode...とあったので、うまく動く方に合わせてsingle modeにしてみる。
参考:Pumaの使い方 まとめ

$ rails s
=> Booting Puma
=> Rails 6.0.2.1 application starting in development 
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.1 (ruby 2.5.3-p105), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3000
* Listening on tcp://[::1]:3000
Exiting
Traceback (most recent call last):
        37: from bin/rails:3:in `<main>'
        36: from bin/rails:3:in `load'
        35: from /Users/k_end/workspace/personal/kiite_app/bin/spring:15:in `<top (required)>'
        34: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'
         .
         .
         .
         2: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/puma/binder.rb:229:in `add_tcp_listener'
         1: from /Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/puma/binder.rb:229:in `new'
/Users/k_end/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-4.3.1/lib/puma/binder.rb:229:in `initialize': Address already in use - bind(2) for "127.0.0.1" port 3000 (Errno::EADDRINUSE)

single modeにはなったけど、やっぱりだめ。

Rails をダウングレードしてみた

gem 'rails', '~> 5.2.3'を指定し、Gemfile.lockを削除してbundle install

結果、変わらず。

とりあえずの応急処置

pumaコマンドで起動

pumaコマンドでconfig/puma.rbを指定して起動する。

$ bundle exec puma -t 5:5 -p 3000 -e development -C config/puma.rb
Puma starting in single mode...
* Version 3.12.2 (ruby 2.5.3-p105), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

pumaには問題はないのか?

WEBrickで起動

エラーが出ていたファイルが~/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/puma-3.12.2/lib/puma/binder.rbだったので、とりあえずpumaディレクトリごと削除。

$ rails s
=> Rails 5.2.4.1 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
[2020-02-23 10:06:46] INFO  WEBrick 1.4.2
[2020-02-23 10:06:46] INFO  ruby 2.5.3 (2018-10-18) [x86_64-darwin17]
[2020-02-23 10:06:46] INFO  WEBrick::HTTPServer#start: pid=21009 port=3000

WEBrickではちゃんとサーバーを起動できている。ということは、やっぱりpumaが原因だ。

ポート番号3000を指定して起動

$ rails s -p 3000
=> Booting Puma
=> Rails 5.2.4.1 application starting in development 
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.12.2 (ruby 2.5.3-p105), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop

問題なく起動できます。

以上の3通りの方法で起動することができました。一番普通と同じ動きをするのは、この方法かと思われます。

終わりに

この通り、とりあえずサーバーを起動する方法はありますが、いまだにrails sコマンドでは起動できていません。
rails srails s -port 3000ではポート番号を設定する部分が違うので、多分そこがぶっ壊れてるんだろうなと思っています。
解決策などございましたら、ぜひコメントをいただければ幸いです。

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

【rails】mysqlのデータベース、テーブル、全データを削除する方法(初心者向け)

データベースの中身を確認

$sudo service mysqld start
$mysqld -u root
mysql> show databases;
mysql> USE データベース名;
mysql> show tables;
mysql> describe テーブル名;
mysql> select * FROM データベース名.テーブル名;

データベースの削除

※確認なしに即削除されます。使用注意。

mysql> show databases;
mysql> DROP DATABASE データベース名;

テーブルの削除

※確認なしに即削除されます。使用注意。

mysql> USE データベース名;
mysql> show tables;
mysql> DROP TABLE データベース名.テーブル名;

全データ削除

mysql> USE データベース名;
mysql> show tables;
mysql> select * FROM データベース名.テーブル名;
mysql> DELETE FROM データベース名.テーブル名;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】APIモードで使えるHTTPステータスコードの対応シンボル一覧

はじめに

Rails APIモードを使用するときに使えるHTTPレスポンスコードの対応シンボルをまとめました。

users_controller.rb
render json: user status: :ok # この:okのこと

これは、

users_controller.rb
render json: user status: 200

とも書けますし、「はいはい、200OKでしょ!」と分かるかと思います。
でも、

users_controller.rb
render json: user status: 429

だと「何だっけこれ?」となりませんか?笑

429:too many requestで外部APIを使ったときに1時間あたりのリクエスト回数の制限に達したときに返されます。

チーム開発経験から、:ok:too many requestとしたほうが、
HTTPステータスコードを知らない人が読めるようにしたほうが開発者のレベルが揃っていないときに便利です。

環境

OS: macOS Catalina 10.15.3
Ruby: 2.6.5
Rails: 6.0.2.1

HTTPステータスコードの対応シンボル

※GitHubソースのコピペです:bow_tone1:

HTTP_STATUS_CODES = {
      100 => 'Continue',
      101 => 'Switching Protocols',
      102 => 'Processing',
      103 => 'Early Hints',
      200 => 'OK',
      201 => 'Created',
      202 => 'Accepted',
      203 => 'Non-Authoritative Information',
      204 => 'No Content',
      205 => 'Reset Content',
      206 => 'Partial Content',
      207 => 'Multi-Status',
      208 => 'Already Reported',
      226 => 'IM Used',
      300 => 'Multiple Choices',
      301 => 'Moved Permanently',
      302 => 'Found',
      303 => 'See Other',
      304 => 'Not Modified',
      305 => 'Use Proxy',
      306 => '(Unused)',
      307 => 'Temporary Redirect',
      308 => 'Permanent Redirect',
      400 => 'Bad Request',
      401 => 'Unauthorized',
      402 => 'Payment Required',
      403 => 'Forbidden',
      404 => 'Not Found',
      405 => 'Method Not Allowed',
      406 => 'Not Acceptable',
      407 => 'Proxy Authentication Required',
      408 => 'Request Timeout',
      409 => 'Conflict',
      410 => 'Gone',
      411 => 'Length Required',
      412 => 'Precondition Failed',
      413 => 'Payload Too Large',
      414 => 'URI Too Long',
      415 => 'Unsupported Media Type',
      416 => 'Range Not Satisfiable',
      417 => 'Expectation Failed',
      421 => 'Misdirected Request',
      422 => 'Unprocessable Entity',
      423 => 'Locked',
      424 => 'Failed Dependency',
      425 => 'Too Early',
      426 => 'Upgrade Required',
      428 => 'Precondition Required',
      429 => 'Too Many Requests',
      431 => 'Request Header Fields Too Large',
      451 => 'Unavailable for Legal Reasons',
      500 => 'Internal Server Error',
      501 => 'Not Implemented',
      502 => 'Bad Gateway',
      503 => 'Service Unavailable',
      504 => 'Gateway Timeout',
      505 => 'HTTP Version Not Supported',
      506 => 'Variant Also Negotiates',
      507 => 'Insufficient Storage',
      508 => 'Loop Detected',
      509 => 'Bandwidth Limit Exceeded',
      510 => 'Not Extended',
      511 => 'Network Authentication Required'
    }

ヘルパーメソッドもあります

invalid?;             status < 100 || status >= 600;

informational?;       status >= 100 && status < 200;
successful?;          status >= 200 && status < 300;
redirection?;         status >= 300 && status < 400;
client_error?;        status >= 400 && status < 500;
server_error?;        status >= 500 && status < 600;

ok?;                  status == 200;                
created?;             status == 201;                
accepted?;            status == 202;                
no_content?;          status == 204;                
moved_permanently?;   status == 301;                
bad_request?;         status == 400;                
unauthorized?;        status == 401;                
forbidden?;           status == 403;                
not_found?;           status == 404;                
method_not_allowed?;  status == 405;                
precondition_failed?; status == 412;                
unprocessable?;       status == 422;                

redirect?;            [301, 302, 303, 307, 308].include? status;

おわりに

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

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

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

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

【Rails】Railsでブランチを行き来して開発していてマイグレーションに変更がある場合の対応方法

はじめに

実務で開発をしているとブランチを行き来しなければならない場合はよくあると思います。
その場合、コミットしていないファイルについてはgit stashなどで対応できますが(参考:開発の流れの中で理解するGit + チートシート)、マイグレーションに変更がある場合はその対応も必要になるかと思います。
今回はそういった場合にどのように対応すれば良いか個人的な対応方法を書いていきます。

前提

  • masterブランチからトピックブランチにチェックアウトして開発を行っている
  • トピックブランチでマイグレーションを変更し、rails db:migrateを実行した
  • その後、別のトピックブランチで作業する必要があり、マイグレーションを戻したい

現在のマイグレーションの状態を確認する

まずは以下のコマンドで現在のマイグレーションの状態を確認しましょう。

rails db:migrate:status

database: hoge

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20200222000000  hoge
   up     20200222111111  hoge
   up     20200222222222  hoge
   up     20200222333333  hoge

Statusupであればマイグレーションが実行済み、downであれば未実行です。
一番下のup 20200222333333 hogeがトピックブランチで変更したマイグレーションだとします。
マイグレーションを戻すには、一番下のup 20200222333333 hogedown 20200222333333 hogeにする必要があります。

マイグレーションを戻す

マイグレーションを戻すには以下のコマンドを実行します。

rails:db:rollback

マイグレーションの状態を確認すると、意図した結果が得られているはずです。

rails db:migrate:status

database: hoge

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20200222000000  hoge
   up     20200222111111  hoge
   up     20200222222222  hoge
  down    20200222333333  hoge

また、rails:db:rollbackを実行するとscheme.rbが書き換わるので、その変更を破棄、またはgit stashしてから、別のトピックブランチに移動しましょう。

ちなみにSTEP=nでどこまでマイグレーションを戻すか指定できます。
以下の例では一番下から3つのマイグレーションがdownになります。(4つ前(n + 1前)までマイグレーションが実行された状態に戻せます。)

rails:db:rollback STEP=3

rails db:migrate:status

database: hoge

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20200222000000  hoge
  down    20200222111111  hoge
  down    20200222222222  hoge
  down    20200222333333  hoge

おわりに

もう一度最初のトピックブランチに戻った時にはrails db:migrateを実行して、マイグレーションを元に戻せばOKです。

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

Cloud9からGitHubのレポジトリへアップする手順(エラー解決方法含む)

  • Git BashなどでGitを動かしたことがある。
  • GitHubのレポジトリは作成済。

という方で、

  • 「初めてAWS Cloud9上で開発を始めたため、作成したソースを、既にあるGitHubのレポジトリへアップしたい」

という方向けに作成しました。

私自身はgit bashでGit/GitHubを操作したことはありましたが、初めてCloud9でgitを扱う際、色々とエラーが出て困ったので、Cloud9用の記事が欲しかったなと思って作成しました。

なお、GitHubのレポジトリは「公開」にしている前提です。
(非公開だとまた手順が異なるよう)

※Git/GitHub初学者の方は以下の動画がおすすめです。
https://www.udemy.com/share/101vBkAEMYcFxTTHgE/

はじめに

まずはcloud9のターミナルを用意。
念のため、gitが入っているか確認します。(Cloud9だと当然入っていますが)

$ git --version
git version 2.14.5

ローカルレポジトリ作成

ローカルレポジトリを作成したい任意のディレクトリに移動し

$ git init

を実行すると、「.git」ディレクトリ(ローカルレポジトリ)が生成されます。

ステージングエリアにadd

まずは、ステージングエリアにadd

$ git add [file]

※git addのオプションについては以下がわかりやすかったので参照下さい。
https://note.nkmk.me/git-add-u-a-period/

ローカルレポジトリにcommit

そして、ステージングエリアにあるファイルをローカルレポジトリにcommit

$ git commit -m "[任意のメッセージ]"

リモートレポジトリの登録とgit push

commitできたら、cloud9上で作成したgitのローカルレポジトリには登録完了です。
次に、GitHubのレポジトリ(リモートレポジトリ)にpush(アップロード)する必要があります。
まずはリモートレポジトリの登録から

$ git remote add origin git@github.com:[自身のGitHubのレポジトリ].git

その後、

$ git push -u origin master

ここで下記のようなエラーが出る方がいると思います。

Permission denied (publickey). 
fatal: Could not read from remote repository.
Please make sure you have the correct access rights and the repository exists. 

上記のエラーは、それぞれの行ごとに、以下のような意味です。

パブリックキーで権限が拒否された。
リモートリポジトリが読み取れない。
アクセス権持っている?それともリポジトリ自体存在している?

エラーを解決していきましょう。

先ほど、リモートレポジトリの登録は完了したかと思います。
そのため、先ほどのエラーメッセージの「リモートレポジトリ自体存在している?」は問題ないということになり、
「アクセス権持っている?」が問題になります。

では、アクセス権を得ましょう。

GitHubへアクセスには、公開鍵が必要です。
開発環境で公開鍵を作成し、GitHubへ公開鍵を登録することでアクセスできるようになります。

公開鍵の作成

まずは、cloud9内のディレクトリに、公開鍵を作成します。

$ ssh-keygen -t rsa -C "[リモートレポジトリを登録した自分のメールアドレス(・・・@gmail.comなど)]"

(-Cより後のコメント部分はなくても実行可能ですが、GitHubに登録しているEmailアドレスを指定するのが一般的のようです)

「ssh-keygen」はSSH(Secure SHell)の公開鍵と秘密鍵を作成するコマンドです。
ちなみにオプションの意味は

  • 「-t rsa」・・・作成する鍵の暗号化形式を「rsa」で指定
  • 「-C "コメント"」・・・コメントを指定

もっと詳しく知りたい方は以下記事がよいと思います。
https://www.atmarkit.co.jp/ait/articles/1908/02/news015.html

実行すると、以下のメッセージが出てきます。Enter~から始まる行が3回あり、各行にて入力を求められますが、すべて何も入力せずEnterを押して問題ありません。

Generating public/private rsa key pair.
Enter file in which to save the key (/home/ec2-user/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again:

上記でEnterを3回押すと、以下のメッセージが表示され、公開鍵が作成されます。(randomart imageの箇所は適当に書き換えています)

Your identification has been saved in /home/ec2-user/.ssh/id_rsa.
Your public key has been saved in /home/ec2-user/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:・・・・・・・・・・・・・・・・・・・・・・・・・・  ……………@gmail.com
The key's randomart image is:
+---[RSA 2048]----+
|....................       |
| ..........        |
|   ......= ......       |
|    *..... .....        |
|   ...... BS+       |
|    =...........o..      |
ho
host github
|   .................       |
|    ......o*=.....+       |
|    .E..........*......      |
+----[SHA256]-----+

その後、.sshディレクトリを確認すると、公開鍵が作成されているのがわかります。

$ ls ~/.ssh/
authorized_keys  id_rsa  id_rsa.pub  known_hosts

id_rsa.pubファイルの内容をコピー

下記コマンドでid_rsa.pubのファイル内容を表示し、中身をコピーします。
(lessコマンドで中身を見るのではなく、ファイルの中身をすべてコピーしてもOK)

$ less ~/.ssh/id_rsa.pub

ファイル内の以下のssh-rsaから始まる部分をコピーします。(メールアドレスまでコピーに含めても含めなくても特に変わりないのでどちらでも構いません)

ssh-rsa ・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
・・・・・・・・・・・・・・・・・・・・・・・・・・・ [メールアドレス]

公開鍵をGitHubに登録

今コピーした部分を、GitHubに登録するのですが、まずGitHubにブラウザからアクセスし、右上の自分のプロフィール画像から「settings」をクリック

image2.png

そして、左側のメニューから「SSH and GPG keys」を選択し、「New SSH key」をクリックします。

image3.png

すると、登録画面が出てきます。
登録画面の「key」部分のテキストエリアに先ほどコピーした「ssh-rsa」から始まる文言を貼り付けます。
ちなみに、タイトルは何でも構いません。

image4.png

貼り付けができたら「Add SSH key」をクリック。

ここで、下記コマンドを打てば接続に成功できると書いている記事が多いですが、エラーが出て接続に失敗する場合もあります。

$ ssh -T git@github.com

configファイルの作成

接続に失敗する場合は、.sshのディレクトリ内に「config」というファイルを作成します。
vimで、ファイルの作成と中身の記述を行います。

$ vim ~/.ssh/config

configファイルの中身に、下記の文言を貼り付けます。
なお、IdentityFileの後のパスは、自身の「id_rsa」ファイルを格納しているパスに変えてください。

Host github github.com
  HostName github.com  IdentityFile ~/.ssh/id_rsa
  User git

新規作成ファイルの権限設定

作成できたら、権限設定をします。
新規でファイルを作成したので、適切な権限を設定し、適切に実行できるようにします。

作成した当初は、configファイルに何も権限がない状態。

しかし今回は、「所有者読み取り権限があり、その他のユーザには権限がない」という状態にする必要があります。

そのため、権限は600か400にします。
(書き込み権限はあってもなくてもよいため)

所有者以外には権限を与えてはいけません。
600にするならば、以下を実行します。

$ chmod 600 ~/.ssh/config

ssh実行

そして、下記を実行。

$ ssh -T git@github.com

実行し、下記のような文字列が出力されたらOK!

Hi [username]! You've successfully authenticated, but GitHub does not provide shell access.

これで、git pushが可能になります。

最後に、git pushを行う

git pushで、ローカルレポジトリのファイル類をリモートレポジトリにアップロードできます。

$ git push origin master

これで完了です!

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

dockerでrailsの開発環境を構築する

概要

「Quickstart: Compose and Rails」
https://docs.docker.com/compose/rails/

「もう環境構築で悩まない!Dockerを使ってRails環境構築!」
https://www.youtube.com/watch?v=BZS8AHF3TTo

「DockerでのRuby on Rails環境構築を一つずつ詳解する」
https://qiita.com/daichi41/items/dfea6195cbb7b24f3419

この記事をもとに

  • ruby
  • rails
  • postgres

を設定していきます。途中記事内容だとerrorで

全体図

  • dockerfileにてwebサーバーとdbをそれぞれコンテナビルド
  • rails読み込む用のgemfile、gemfilerockを作成。これをもとに作業ディレクトリにrailsnewされる。
  • $ docker-compose run web rails new
  • docker-compose build
  • docker-compose upで起動
  • docker-compose run web rake db:createでdb作成

つまづきポイント

could not translate host name "db" to address: Name or service not

dbのpassがうまく渡せていなかった?

docker-compose.yml
db:
    image: postgres:9.5.18
    environment:
      POSTGRES_PASSWORD: password

にしてdatabase.ymlを下記に

config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password: password
  pool: 5

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test

railsコマンドの打ち方

docker-compose run web rails ~

でrailsコマンドを打てる。

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