20200405のRailsに関する記事は21件です。

Dockerを使ってrailsのAPIモードの環境構築

はじめに

今回は、Dockerを使ってRailsのAPIモードの環境構築をしていく記事です。
コマンド、設定などを詳細に書いていきます。

記事の最後までの所要時間は30分以内です。意外と簡単でした。

APIモードとは

APIモードとはRails5から追加された機能で、APIのようなRailsアプリケーションを作れる機能だそうです。
MVCのうち、モデルとコントローラーのみが作成されます。
APIモードで作成されたアプリのURLにリクエストを送ると、json形式のデータがレスポンスとして返ってきます。

環境構築

では、環境構築をしていきます。

$ mkdir sample_app
$ cd sample_app
$ docker pull ruby:2.5.1
$ docker run --rm -v "$PWD":/usr/src/sample_app -w /usr/src/sample_app ruby:2.5.1 bundle init
$ docker build -t developer_name/sample_app .

ここで、Dockerfiledocker-compose.ymlGemfileGemfile.lockの4つのファイルが必要となるので、sample_app以下にそれぞれ作っていきます。

$ touch 各ファイル

Dockerfilesample_appはそれぞれのディレクトリ名に変更してください。

Dockerfile
# Debianがベースのrubyイメージを指定
FROM ruby:2.5.1

# 必要なものをインストール
RUN apt-get update -qq && apt-get -y install \
    build-essential \
    libpq-dev \
    nodejs \
    mysql-client

# rails用のディレクトリを作成
RUN mkdir /sample_app

# ローカルマシン(Mac)からコンテナの中にファイルをコピー
COPY Gemfile /API_sample
COPY Gemfile.lock /sample_app

# 作業ディレクトリを指定
WORKDIR /sample_app

# 上でコピーしたGemfileに従ってGemをインストール
RUN gem install bundler && bundle install

続いて、docker-compose.ymlです。
MYSQL_ROOT_PASSWORDは、後で作成するdatabase.ymlのパスワードに合わせます。

docker-compose.yml
version: '3'
services:
  web:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - db
    volumes:
      - .:/API_sample
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
  db:
    image: mysql:5.7
    volumes:
      - mysql_data:/var/lib/mysql/
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "3306:3306"
volumes:
  mysql_data:

Railsアプリの作成

新規アプリ作成時に、後ろに--apiをつけるとAPIモードでアプリが作成されます。

$ docker-compose run web rails new . --force --database=mysql --api
$ docker-compose run --rm web rails generate scaffold User name:string
$ docker-compose run --rm web rails db:create
$ docker-compose run --rm web rails db:migrate

scaffoldでUserを作ったら、ファイルを見てみましょう。
普通のrailsアプリで作られるassetsviewが無いことが確認できると思います。

ページを開く

それでは、アプリをlocalhostで開いてみましょう。
http://localhost:3000/users

真っ白なページに、[]とだけ表示されているはずです。
これは、データが空であるということを表しています。

では、curlコマンドでデータのリクエストを送ってみます。

curlはURLシンタックスを用いてファイルを送信または受信するコマンドラインツールである。(Wikipediaより)

下記の例を簡単に説明すると、指定したURLに対して、POSTメソッドを使用してターミナルからjsonファイルを送信しているということです。

$ curl -X POST -H "Content-Type: application/json" -d '{"name": "hoge"}' http://localhost:3000/users

そして、http://localhost:3000/users に接続すると
[{"id":1,"name":"hoge","created_at":"","updated_at":""}]というデータが表示されると思います。(日付はカットしました)

これで完了です!意外とあっさりできますよね。
他にも、GET、PUT、DELETEも使えるので色々と試してみてください。

感想

APIモードと聞いて最初はよくわからないな、怖いなと思っていました。
ですが、やってみると簡単ですし、今まで深く考えていなかったHTTPについて調べたり知ったりするきっかけになったので良かったです。

駆け出しエンジニアにとっても簡単なのでぜひやってみてはいかがでしょうか?

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

MacにてRailsの環境構築を試みる

はじめに

Ruby on Railsの環境構築について、最低限必要なソフトウェアとインストールのコマンドについてまとめてみました。
Rails環境構築の全体像が掴むための記事となっています。

環境構築に必要なソフトウェア用語について

railsの環境構築に必要なソフトウェアは下記に示すものになります。

・Xcode
・homebrew
・rbenv
・ruby
・gem
・rails
・bundler

それぞれの関係性は下図のようになっています。

スクリーンショット 2020-04-05 18.56.47.png

Xcode

Apple社が開発・リリースしている、アプリケーション開発に特化したソフトウェアです。

homebrew

MacOS X上でソフトウェアのインストールを単純化するためのパッケージ管理システムのことです。
わかりやすく言うと、ソフトウェアをインストールする際に、パッケージやライブラリの依存関係をうまいこと調整してインストールしてくれるシステムのことです。
ソフトウェアをインストールする前にこいつをインストールしておきましょう。

[homebrewとは](https://qiita.com/omega999/items/6f65217b81ad3fffe7e6}

rbenv

rubyのバージョンを切り替えるツール。
環境ごとやディレクトリごとにバージョンを切り替えることもできる。

ruby

プログラミング言語のひとつ。オブジェクト指向型のスクリプト言語。
ちなみにスクリプト言語とは、javaやC言語のようにコンパイルすることが不要で、簡単に実行できる特徴を持った言語のことをいいます。比較的簡単に実装できるため、比較的取り組みやすいプログラミング言語になります。

gem

gemはある機能がパッケージされたものになります。
パッケージとは、プログラムの部品であり、便利な機能をひとまとめにしたものになります。
例えばユーザーログインシステムを作成するための機能がまとまったgemがあり、インストールすることで簡単にログイン機能を実装することができるようになります。

rails

rubyのフレームワークです。Webシステムなどを開発するための機能がまとまっているgemです。
rubyでは主にこのフレームワークを用いて開発が進められています。

bundler

gemを管理するためのツールです。
gemをインストールする際に必要になります。

各ソフトウェアのインストール

Command Line Tools for Xcodeのインストール

まずはターミナルを開き、下記コマンドによりXCodeをインストールします。

$ xcode-select --install

homebrewのインストール

homebrewの公式サイトからインストールを行いましょう。
https://brew.sh/index_ja.html

下図のようなホーム画面に入るかと思います。
image.png

このページに書かれているインストールのコマンドをターミナルに打ち込んでインストールを行いましょう。

rbenvのインストール

次にrubyのバージョンを管理するrbenvをインストールしましょう。
ちなみに「rbenv」の読み方はだいたい「アールビーエンブ」と呼ばれているようです。
確かではありませんが、
"rb"→ruby
"env"→environment(環境)
の2つをつなげてrbenvとなっていると思われます。
rubyの環境を整えると言う意味合いで使われているのであろうと推測できます。

インストールのコマンドは以下になります。
rbenvとともに、ruby-buildというrbenvのプラグインも同時にインストールしておきましょう。

$ brew install rbenv ruby-build

正常にインストールされたかどうかは

rbenv -v

を打ち込み、バージョンが確認できればOKです。

PATHを通す

ここでPATHの設定をしておきます。
PATHを通すとは、あるファイルの実行を、ファイル名を打ち込むだけで実行できるようにする設定です。

ファイルをターミナルから開く際には、そのファイルが置かれている場所のフルパスを打ち込むことが必要です。

しかし毎回毎回フルパスを書くのは効率が悪いですね。

そこでPATHの設定をしておくことでファイル名だけで実行ができるようになると言う仕組みです。

このことを「PATHを通す」といいます。
※参考記事
https://qiita.com/sta/items/63e1048025d1830d12fd

・rbenvのPATHを通すコマンド

$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile

・ターミナルを起動したときに自動的にrbenvを使用できるようにする設定

$ echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.bash_profile

※参考記事
https://qiita.com/soarflat/items/d5015bec37f8a8254380

最後にsourceコマンドを打ち、ファイルを再読み込みしておきましょう。

source ~/.bash_profile

rubyのインストール

rbenvがインストールできたら早速rubyをインストールします。

先ほどインストールしたrbenvのコマンドを使い、バージョン管理しながらインストールを行っていきます。

rubyをインストールする前に、現在インストールできるrubyのバージョンを確認しておきます。

$ rbenv install -l

下図のようにインストール可能なバージョンが一覧で表示されます。(一部抜粋)

image.png
それではインストールしたいRubyのバージョンを指定して実行します。今回はバージョン2.6.5をインストールすることにします。

$ rbenv install 2.6.5

こちらもインストールできたかの確認をしておきましょう。

ruby -v

image.png
画像のように表示されればOKです。

gemのインストール

下記の2つのgemをインストールしていきます。
・bundler
・rails

$ gem install bundler

今回はrailsの5.2.0のバージョンをインストールすることにします。

$ gem install rails --version="~>5.2.0"

こちらも

$ rails -v

によりインストール完了の確認をしておきましょう

最後にデータベースのインストール

最後にデータベースのインストールをして環境構築は終了です!
今回はよく使われるmysqlというデータベースをインストールしておきます。
バージョンは5.7をインストールします。

$ brew install mysql@5.7

こちらもPATHを通す作業を行っておきます。

echo 'export PATH="/usr/local/opt/mysql@5.7/bin:$PATH"' >> ~/.bash_profile

その後下記コマンドにより、設定の再読み込みをします。

source ~/.bash_profile

ここまでできたらmysqlを起動するコマンドを打ち込み、起動させましょう。

$ brew services start mysql@5.7

下記のように表示されれば正しく起動しています。

Starting MySQL
SUCCESS!

環境構築終了!

簡素的ですが、これで一通りrailsを使っていく環境が整いました。

環境構築はコマンドを実行していくだけの簡単な作業に思えるかも知れませんが、様々なソフトウェアをインストールする際にエラーが発生しやすく初心者殺しと言われる1つの大きな壁です。

今回は最低限必要なソフロウェアをインストールするコマンドについて記しましたが、
おそらくインストールを行っている途中でエラーが発生してうまく行かないところが出てくるかも知れません。

その時はエラーの内容を調べてどうにか頑張ってみてください。

以上、簡単ですが環境構築の手助けになればと思います。

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

[Rails]画像選択時にプレビュー表示

本記事投稿のいきさつ

アプリの中で画像投稿機能を実装したとき、ただフォームを作成するだけでは画像を選択しても表示がされず何を選んだのか確認をすることが出来ません。
そこで、画像を選択した時点でプレビュー表示することができればいいなと思い、実際に機プレビュー機能を作成したため、記録として残したいと思います。
今回はユーザーのプロフィール画像の編集画面を想定します。
そのため、既に登録されている画像は最初からプレビューさせた状態で表示をさせます。

前提

  • 変種画面はprofile_edit.html.haml
  • 画像の保存先は Usersテーブル: imageカラム
  • file_field本体は隠し、画像が選択されていない時はiconを、 選択されている時はプレビュー画像をクリックすることで画像選択できるようにします。
  • jQueryを使用しますが、必要なGemのインストール等は既に出来ているとします。
  • ユーザー機能はDeviseを使用しています。
  • cssは今回の内容に含まれません。

フォーム作成

まず、今回はhamlでビューのフォームを作成します。

profile_edit.html.haml
= form_for current_user, url: {action: 'profile_update'} do |f|

    .form-group 
      .image_form
        .image_form__contents
          -# ラベルでfile_fieldとicon、プレビュー画像を紐付けます
          = f.label :image, class: 'image_label' do
            .prev-contents
              -# 既に登録されている画像があれば表示をさせます
              - if current_user.image.present?
                .prev-content
                  = image_tag current_user.image.url, alt: "preview", class: "prev-image"
              -# 既に登録されている画像がなければiconを表示させます
              - else
                = icon('fas', 'image', class: 'photo-icon')
            -# file_fieldはdisplay: none;で隠します
            = f.file_field :image, class: 'image_form__contents__field hidden_file'

FileReader

今回はFileReaderを使用します。
FileReaderとはHTML5世代の機能でユーザーのPC内にあるファイルやバッファ上の生データに対して、読み取りアクセスを行えるオブジェクトです。

jsファイルの編集

今回はimage_preview.jsを作成して、そこに記述していきます。

image_preview.js
$(document).on('turbolinks:load', function () {
  $(function () {
    // 画像をプレビュー表示させる.prev-contentを作成
    function buildHTML(image) {
      var html =
        `
        <div class="prev-content">
          <img src="${image}", alt="preview" class="prev-image">
        </div>
        `
      return html;
    }

    // 画像が選択された時に発火します
    $(document).on('change', '.hidden_file', function () {
      // .file_filedからデータを取得して変数fileに代入します
      var file = this.files[0];
      // FileReaderオブジェクトを作成します
      var reader = new FileReader();
      // DataURIScheme文字列を取得します
      reader.readAsDataURL(file);
      // 読み込みが完了したら処理が実行されます
      reader.onload = function () {
        // 読み込んだファイルの内容を取得して変数imageに代入します
        var image = this.result;
        // プレビュー画像がなければ処理を実行します
        if ($('.prev-content').length == 0) {
          // 読み込んだ画像ファイルをbuildHTMLに渡します
          var html = buildHTML(image)
          // 作成した.prev-contentをiconの代わりに表示させます
          $('.prev-contents').prepend(html);
          // 画像が表示されるのでiconを隠します
          $('.photo-icon').hide();
        } else {
          // もし既に画像がプレビューされていれば画像データのみを入れ替えます
          $('.prev-content .prev-image').attr({ src: image });
        }
      }
    });
  });
});

上記で出てくるDataURISchemeとは、
簡単にいうと、画像やらJavascriptやらそういったHTMLのコンテンツを文字列として定義出来るものです。
以上でで、画像が表示されます。

終わり

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

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

gem annotate の README を翻訳しました

概要

gem annotateREADME を翻訳しました。

Annotate (aka AnnotateModels)

下記のファイルの上部あるいは下部に現在の DB スキーマを要約したコメントを追加します。

  • ActiveRecord models
  • Fixture files
  • Tests and Specs
  • Object Daddy exemplars
  • Machinist blueprints
  • Fabrication fabricators
  • Thoughtbot's factory_bot factories, i.e. the (spec|test)/factories/<model>_factory.rb files
  • routes.rb file (for Rails projects)

スキーマコメントは下記のように記載されます。

# == Schema Info
#
# Table name: line_items
#
#  id                  :integer(11)    not null, primary key
#  quantity            :integer(11)    not null
#  product_id          :integer(11)    not null
#  unit_price          :float
#  order_id            :integer(11)
#

class LineItem < ActiveRecord::Base
  belongs_to :product
  . . .

また、SpatialAdapter , PostgisAdapter , PostGISAdapter のいずれかを使用したときは、geom タイプや srid タイプのような幾何学的なカラムにも注釈をつけます。

# == Schema Info
#
# Table name: trips
#
#  local           :geometry        point, 4326
#  path            :geometry        line_string, 4326

また、-r オプションを渡すと、rake routes の出力を routes.rb にコメントとして追加します。

3.Xにアップグレードするとモデルへの注釈が動作しない?

バージョン 2.7.X では、引数が何も渡されない場合は、gem annotate はデフォルトでモデルに注釈を追加していました。
デフォルトでは、gem annotate は routes とモデルが一緒に注釈を追加されることを許可していません。
#647 で変更が追加されました。
詳細はこちらでご覧ください。

この問題を修正する方法はいくつかあります。

  • CLI を使用している場合は、--models を使用してモデルフラグを明示的に渡してください。

あるいは

a) rails g annotate:install を実行し、デフォルト設定が models オプション 'true' になるように上書きします。

b) lib/tasks/auto_annotate_models.rake において models のキーと値を追加します。

    Annotate.set_defaults(
      ...
      'models'                      => 'true',
      ...

インストール

rubygems.org から 追加

group :development do
  gem 'annotate'
end

Github から 追加

group :development do
  gem 'annotate', git: 'https://github.com/ctran/annotate_models.git'
end

rubygems.org からインストール

gem install annotate

Github のチェックアウトからインストール

git clone https://github.com/ctran/annotate_models.git annotate_models
cd annotate_models
rake build
gem install pkg/annotate-*.gem

利用方法

もし Gemfile 経由でインストールをしているなら、bundle exec を下記コマンドにつけてください。

Rails での利用方法

すべてのモデル、テスト、フィクスチャ、ファクトリに注釈をつける。

cd /path/to/app
annotate

モデル、テスト、ファクトリのみに注釈をつける。

annotate --models --exclude fixtures

モデルのみに注釈をつける。

annotate --models

routes.rb に注釈をつける。

annotate --routes

モデル、テスト、フィクスチャ、ファクトリ、シリアライザの注釈を削除する。

annotate --delete

routes.rb の注釈を削除する。

annotate --routes --delete

db:migrate を実行するたびに自動的に注釈をつけるためには、
rails g annotate:install を実行するか、RakefileAnnotate.load_tasks を追加してください。

詳細は Rails における設定 をご覧ください。

Rails の外での利用方法

--routes オプションが意味をなさないことを除けば Rails 外の利用でも上述のすべてが適用されます。明示的に1つあるいは複数の --require オプションと --model-dir オプションを設定することで、annotate にプロジェクトの構造を知らせ、プロジェクトが起動し関連のあるコードを読み込むことを助ける必要があります。

設定

もし特定のモデルで注釈を常に飛ばしたいときは、モデルファイルの任意の場所に下記の文字列を追加してください。

# -*- SkipSchemaAnnotations

Rails における設定

設定ファイル(.rake ファイル形式)を生成して、デフォルトオプションを設定するためには下記のコマンドを実行してください。

rails g annotate:install

このファイルを編集することで、注釈がファイルの上下どちらに追加されるかや、どのタイプのファイルに注釈が記載されるかといったような、出力形式などを制御します。

生成される rake ファイルである lib/tasks/auto_annotate_models.rakeAnnotate.load_tasks も含みます。この rake ファイルはコマンドラインの機能と重複するいくつかの rake タスクを追加します。

rake annotate_models                          # Add schema information (as comments) to model and fixture files
rake annotate_routes                          # Adds the route map to routes.rb
rake remove_annotation                        # Remove schema information from model and fixture files

デフォルトでは、一旦設定ファイルを生成したあとは、rake db:migrate が実行されるたびに annotate が実行されます(development 環境のみ)。
この挙動を永続的に無効にしたいときは、.rake ファイルを編集し、下記のように変更してください。

    'skip_on_db_migrate'   => 'false',

上記の記述から下記の記述へ変更する。

    'skip_on_db_migrate'   => 'true',

1回だけ annotate なしで rake db:migrate を実行したいときは、.rake ファイルを編集する代わりにシンプルな環境変数をつけることでそれを実現できます。

ANNOTATE_SKIP_ON_DB_MIGRATE=1 rake db:migrate

オプション

  • --additional-file-patterns

コンマで区切った注釈を行う追加のファイルパスあるいは glob(例: /foo/bar/%model_name%/*.rb,/baz/%model_name%.rb)を記載する。

  • -d, --delete

すべてのモデルファイルあるいは routes.rb ファイルから注釈を削除する。

  • -p [before|top|after|bottom], --position

model/test/fixture/factory/route/serializer ファイルの上部(前)あるいは下部(後)に注釈を置く。

  • --pc, --position-in-class [before|top|after|bottom]

model ファイルの上部(前)あるいは下部(後)に注釈を置く。

  • --pf, --position-in-factory [before|top|after|bottom]

factory ファイルの上部(前)あるいは下部(後)に注釈を置く。

  • --px, --position-in-fixture [before|top|after|bottom]

fixture ファイルの上部(前)あるいは下部(後)に注釈を置く。

  • --pt, --position-in-test [before|top|after|bottom]

test ファイルの上部(前)あるいは下部(後)に注釈を置く。

  • --pr, --position-in-routes [before|top|after|bottom]

routes.rb ファイルの上部(前)あるいは下部(後)に注釈を置く。

  • --ps, --position-in-serializer [before|top|after|bottom]

serializer ファイルの上部(前)あるいは下部(後)に注釈を置く。

  • --w, --wrapper STR

パラメーターとして渡されたテキストで注釈をラップします。
--w が使用された場合は、同じテキストが始端と終端に使用されます。

  • --wo, --wrapper-open STR

注釈の始端をラップするテキスト。

  • --wc, --wrapper-close STR

注釈の終端をラップするテキスト。

  • -r, --routes

'rake routes' の出力で routes.rb に注釈を付ける。

  • --models

ActiveRecord モデルに注釈を付ける。

  • -a, --active-admin

active_admin モデルに注釈を付ける。

  • -v, --version

この gem の現在のバージョンを表示する。

  • -m, --show-migration

注釈にマイグレーションのバージョン番号を含める。

  • -k, --show-foreign-keys

注釈にテーブルの外部キー制約の一覧を載せる。

  • --ck, --complete-foreign-keys

注釈に完全な外部キーの名前を載せる。

  • -i, --show-indexes

注釈にテーブルのインデックスの一覧を載せる。

  • -s, --simple-indexes

注釈内のカラムの関連したインデックスを結合する。

  • --model-dir dir

app/models ではないディレクトリに保存されたモデルファイルに注釈を付ける。ディレクトリ名はカンマで区切る。

  • --root-dir dir

ルートディレクトリのプロジェクト内に保存されたファイルに注釈を付ける。ディレクトリ名はカンマで区切る。

  • --ignore-model-subdirects

モデルディレクトリのサブディレクトリを無視する。

  • --sort

作成順ではなくアルファベット順でカラムをソートする。

  • --classified-sort

アルファベット順にカラムをソートする。ただし、一番目には id 、その次が残りのカラム、タイムスタンプカラム、関連付けカラムの順となる。

  • -R, --require path

モデルを読み込む前に必要とする追加のファイル。このファイルは複数回使用される。

  • -e [tests,fixtures,factories,serializers], --exclude

tests,fixtures,factories,serializers ファイルに注釈を付けないようにする。

  • -f [bare|rdoc|yard|markdown], --format

プレインテキスト/RDoc/YARD/Markdown としてスキーマ情報を記述する。

  • --force

変更がない場合でも新しい注釈を強制的に記述する。

  • --frozen

注釈の変更を許可しない。ファイルに変更がある場合は、ゼロ以外の値で終了します。

  • --timestamp

注釈にタイムスタンプを含ませる。

  • --trace

ファイルに注釈を付けられない場合は、例外メッセージだけでなく、スタックトレース全体を表示する。

  • -I, --ignore-columns REGEX

与えられた正規表現に合致するカラムに注釈をつけないようにする(例: annotate -I '^(id|updated_at|created_at)')。

  • --ignore-routes REGEX

与えられた正規表現に合致する routes に注釈をつけないようにする(例: annotate -I '(mobile|resque|pghero)')。

  • --hide-limit-column-types VALUES

与えられたカラムタイプに limit 値を表示させない。カラムタイプはコンマで区切る(例: integer,boolean,text)。

  • --hide-default-column-types VALUES

与えられたカラムタイプに default 値を表示させない。カラムタイプはコンマで区切る(例: json,jsonb,hstore)。

  • --ignore-unknown-models

不正なモデルファイルに対して警告を表示させない。

  • --with-comment

モデルの注釈にデータベースコメントを含める。

オプション: additional_file_patterns

CLI: --additional-file-patterns

Ruby: :additional_file_patterns

注釈を行うために追加のパスを提供します。このパスは glob を含むことができます。絶対パスを使用することを推奨します。下記に例を記載します。

  • /app/lib/decorates/%MODEL_NAME%/*.rb
  • /app/lib/forms/%PLURALIZED_MODEL_NAME%/**/*.rb
  • /app/lib/forms/%TABLE_NAME%/*.rb

適切なモデルは %*% 構文を用いて推論され、一致するファイルに注釈が付けられます。これは既存のファイル名解決とともに動作します(annotate_models.rbresolve_filename メソッドの中で発見されるオプション)。

Rails の設定の中で使用するときは、下記を使用できます。

File.join(Rails.application.root,
'app/lib/forms/%PLURALIZED_MODEL_NAME%/***/**.rb')

ソート

デフォルトでは、カラムはデータベース順でソートされます(つまりマイグレーションが行われた順番)。

もしアルファベット順にソートしてマイグレーションを実行した順番とは関係なく注釈の結果を一致させたいときは、--sort オプションを使用してください。

マークダウン

生成されるフォーマットは実際には MultiMarkdown で、テーブルのための構文拡張機能を利用しています。もしこのフォーマットを使用したいときはパーサーとして kramdown を使用することを推奨しています。もしドキュメントを生成するために yard を使用している場合は、.yardopts ファイルに kramdown を追加することでプロバイダとして kramdown をマークダウンのフォーマットに指定してください。

--markup markdown
--markup-provider kramdown

Gemfile にも同様に kramdown を追加するようにしてください。

gem 'kramdown', groups => [:development], require => false

警告

自動作成されたコメントブロックの後にテキストを追加しないでください。annotate によって以前にコメントブロックが追加された可能性があるとき、annotate はモデル内の始端・終端のコメントブロックを削除することがあります。

annotate が行った変更を必ず確認するようにしてください。Git を使用している場合は、annotate を実行した後にプロジェクトのステータスを確認することができます。

$ git status

VCS(Git や Subversion など)を使っていない人は、特に注意して annotate を扱い、1つの VCS の利用を検討してください。

リンク集

ライセンス

Ruby と同じライセンスでリリースしています。サポートと保証はありません。

作者

AUTHORS.md をご覧ください。

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

gem annotate のドキュメントを翻訳しました

概要

gem annotateREADME を翻訳しました。

Annotate (aka AnnotateModels)

下記のファイルの上部あるいは下部に現在の DB スキーマを要約したコメントを追加します。

  • ActiveRecord models
  • Fixture files
  • Tests and Specs
  • Object Daddy exemplars
  • Machinist blueprints
  • Fabrication fabricators
  • Thoughtbot's factory_bot factories, i.e. the (spec|test)/factories/<model>_factory.rb files
  • routes.rb file (for Rails projects)

スキーマコメントは下記のように記載されます。

# == Schema Info
#
# Table name: line_items
#
#  id                  :integer(11)    not null, primary key
#  quantity            :integer(11)    not null
#  product_id          :integer(11)    not null
#  unit_price          :float
#  order_id            :integer(11)
#

class LineItem < ActiveRecord::Base
  belongs_to :product
  . . .

また、SpatialAdapter , PostgisAdapter , PostGISAdapter のいずれかを使用したときは、geom タイプや srid タイプのような幾何学的なカラムにも注釈をつけます。

# == Schema Info
#
# Table name: trips
#
#  local           :geometry        point, 4326
#  path            :geometry        line_string, 4326

また、-r オプションを渡すと、rake routes の出力を routes.rb にコメントとして追加します。

3.Xにアップグレードするとモデルへの注釈が動作しない?

バージョン 2.7.X では、引数が何も渡されない場合は、gem annotate はデフォルトでモデルに注釈を追加していました。
デフォルトでは、gem annotate は routes とモデルが一緒に注釈を追加されることを許可していません。
#647 で変更が追加されました。
詳細はこちらでご覧ください。

この問題を修正する方法はいくつかあります。

  • CLI を使用している場合は、--models を使用してモデルフラグを明示的に渡してください。

あるいは

a) rails g annotate:install を実行し、デフォルト設定が models オプション 'true' になるように上書きします。

b) lib/tasks/auto_annotate_models.rake において models のキーと値を追加します。

    Annotate.set_defaults(
      ...
      'models'                      => 'true',
      ...

インストール

rubygems.org から 追加

group :development do
  gem 'annotate'
end

Github から 追加

group :development do
  gem 'annotate', git: 'https://github.com/ctran/annotate_models.git'
end

rubygems.org からインストール

gem install annotate

Github のチェックアウトからインストール

git clone https://github.com/ctran/annotate_models.git annotate_models
cd annotate_models
rake build
gem install pkg/annotate-*.gem

利用方法

もし Gemfile 経由でインストールをしているなら、bundle exec を下記コマンドにつけてください。

Rails での利用方法

すべてのモデル、テスト、フィクスチャ、ファクトリに注釈をつける。

cd /path/to/app
annotate

モデル、テスト、ファクトリのみに注釈をつける。

annotate --models --exclude fixtures

モデルのみに注釈をつける。

annotate --models

routes.rb に注釈をつける。

annotate --routes

モデル、テスト、フィクスチャ、ファクトリ、シリアライザの注釈を削除する。

annotate --delete

routes.rb の注釈を削除する。

annotate --routes --delete

db:migrate を実行するたびに自動的に注釈をつけるためには、
rails g annotate:install を実行するか、RakefileAnnotate.load_tasks を追加してください。

詳細は Rails における設定 をご覧ください。

Rails の外での利用方法

--routes オプションが意味をなさないことを除けば Rails 外の利用でも上述のすべてが適用されます。明示的に1つあるいは複数の --require オプションと --model-dir オプションを設定することで、annotate にプロジェクトの構造を知らせ、プロジェクトが起動し関連のあるコードを読み込むことを助ける必要があります。

設定

もし特定のモデルで注釈を常に飛ばしたいときは、モデルファイルの任意の場所に下記の文字列を追加してください。

# -*- SkipSchemaAnnotations

Rails における設定

設定ファイル(.rake ファイル形式)を生成して、デフォルトオプションを設定するためには下記のコマンドを実行してください。

rails g annotate:install

このファイルを編集することで、注釈がファイルの上下どちらに追加されるかや、どのタイプのファイルに注釈が記載されるかといったような、出力形式などを制御します。

生成される rake ファイルである lib/tasks/auto_annotate_models.rakeAnnotate.load_tasks も含みます。この rake ファイルはコマンドラインの機能と重複するいくつかの rake タスクを追加します。

rake annotate_models                          # Add schema information (as comments) to model and fixture files
rake annotate_routes                          # Adds the route map to routes.rb
rake remove_annotation                        # Remove schema information from model and fixture files

デフォルトでは、一旦設定ファイルを生成したあとは、rake db:migrate が実行されるたびに annotate が実行されます(development 環境のみ)。
この挙動を永続的に無効にしたいときは、.rake ファイルを編集し、下記のように変更してください。

    'skip_on_db_migrate'   => 'false',

上記の記述から下記の記述へ変更する。

    'skip_on_db_migrate'   => 'true',

1回だけ annotate なしで rake db:migrate を実行したいときは、.rake ファイルを編集する代わりにシンプルな環境変数をつけることでそれを実現できます。

ANNOTATE_SKIP_ON_DB_MIGRATE=1 rake db:migrate

オプション

  • --additional-file-patterns

コンマで区切った注釈を行う追加のファイルパスあるいは glob(例: /foo/bar/%model_name%/*.rb,/baz/%model_name%.rb)を記載する。

  • -d, --delete

すべてのモデルファイルあるいは routes.rb ファイルから注釈を削除する。

  • -p [before|top|after|bottom], --position

model/test/fixture/factory/route/serializer ファイルの上部(前)あるいは下部(後)に注釈を置く。

  • --pc, --position-in-class [before|top|after|bottom]

model ファイルの上部(前)あるいは下部(後)に注釈を置く。

  • --pf, --position-in-factory [before|top|after|bottom]

factory ファイルの上部(前)あるいは下部(後)に注釈を置く。

  • --px, --position-in-fixture [before|top|after|bottom]

fixture ファイルの上部(前)あるいは下部(後)に注釈を置く。

  • --pt, --position-in-test [before|top|after|bottom]

test ファイルの上部(前)あるいは下部(後)に注釈を置く。

  • --pr, --position-in-routes [before|top|after|bottom]

routes.rb ファイルの上部(前)あるいは下部(後)に注釈を置く。

  • --ps, --position-in-serializer [before|top|after|bottom]

serializer ファイルの上部(前)あるいは下部(後)に注釈を置く。

  • --w, --wrapper STR

パラメーターとして渡されたテキストで注釈をラップします。
--w が使用された場合は、同じテキストが始端と終端に使用されます。

  • --wo, --wrapper-open STR

注釈の始端をラップするテキスト。

  • --wc, --wrapper-close STR

注釈の終端をラップするテキスト。

  • -r, --routes

'rake routes' の出力で routes.rb に注釈を付ける。

  • --models

ActiveRecord モデルに注釈を付ける。

  • -a, --active-admin

active_admin モデルに注釈を付ける。

  • -v, --version

この gem の現在のバージョンを表示する。

  • -m, --show-migration

注釈にマイグレーションのバージョン番号を含める。

  • -k, --show-foreign-keys

注釈にテーブルの外部キー制約の一覧を載せる。

  • --ck, --complete-foreign-keys

注釈に完全な外部キーの名前を載せる。

  • -i, --show-indexes

注釈にテーブルのインデックスの一覧を載せる。

  • -s, --simple-indexes

注釈内のカラムの関連したインデックスを結合する。

  • --model-dir dir

app/models ではないディレクトリに保存されたモデルファイルに注釈を付ける。ディレクトリ名はカンマで区切る。

  • --root-dir dir

ルートディレクトリのプロジェクト内に保存されたファイルに注釈を付ける。ディレクトリ名はカンマで区切る。

  • --ignore-model-subdirects

モデルディレクトリのサブディレクトリを無視する。

  • --sort

作成順ではなくアルファベット順でカラムをソートする。

  • --classified-sort

アルファベット順にカラムをソートする。ただし、一番目には id 、その次が残りのカラム、タイムスタンプカラム、関連付けカラムの順となる。

  • -R, --require path

モデルを読み込む前に必要とする追加のファイル。このファイルは複数回使用される。

  • -e [tests,fixtures,factories,serializers], --exclude

tests,fixtures,factories,serializers ファイルに注釈を付けないようにする。

  • -f [bare|rdoc|yard|markdown], --format

プレインテキスト/RDoc/YARD/Markdown としてスキーマ情報を記述する。

  • --force

変更がない場合でも新しい注釈を強制的に記述する。

  • --frozen

注釈の変更を許可しない。ファイルに変更がある場合は、ゼロ以外の値で終了します。

  • --timestamp

注釈にタイムスタンプを含ませる。

  • --trace

ファイルに注釈を付けられない場合は、例外メッセージだけでなく、スタックトレース全体を表示する。

  • -I, --ignore-columns REGEX

与えられた正規表現に合致するカラムに注釈をつけないようにする(例: annotate -I '^(id|updated_at|created_at)')。

  • --ignore-routes REGEX

与えられた正規表現に合致する routes に注釈をつけないようにする(例: annotate -I '(mobile|resque|pghero)')。

  • --hide-limit-column-types VALUES

与えられたカラムタイプに limit 値を表示させない。カラムタイプはコンマで区切る(例: integer,boolean,text)。

  • --hide-default-column-types VALUES

与えられたカラムタイプに default 値を表示させない。カラムタイプはコンマで区切る(例: json,jsonb,hstore)。

  • --ignore-unknown-models

不正なモデルファイルに対して警告を表示させない。

  • --with-comment

モデルの注釈にデータベースコメントを含める。

オプション: additional_file_patterns

CLI: --additional-file-patterns

Ruby: :additional_file_patterns

注釈を行うために追加のパスを提供します。このパスは glob を含むことができます。絶対パスを使用することを推奨します。下記に例を記載します。

  • /app/lib/decorates/%MODEL_NAME%/*.rb
  • /app/lib/forms/%PLURALIZED_MODEL_NAME%/**/*.rb
  • /app/lib/forms/%TABLE_NAME%/*.rb

適切なモデルは %*% 構文を用いて推論され、一致するファイルに注釈が付けられます。これは既存のファイル名解決とともに動作します(annotate_models.rbresolve_filename メソッドの中で発見されるオプション)。

Rails の設定の中で使用するときは、下記を使用できます。

File.join(Rails.application.root,
'app/lib/forms/%PLURALIZED_MODEL_NAME%/***/**.rb')

ソート

デフォルトでは、カラムはデータベース順でソートされます(つまりマイグレーションが行われた順番)。

もしアルファベット順にソートしてマイグレーションを実行した順番とは関係なく注釈の結果を一致させたいときは、--sort オプションを使用してください。

マークダウン

生成されるフォーマットは実際には MultiMarkdown で、テーブルのための構文拡張機能を利用しています。もしこのフォーマットを使用したいときはパーサーとして kramdown を使用することを推奨しています。もしドキュメントを生成するために yard を使用している場合は、.yardopts ファイルに kramdown を追加することでプロバイダとして kramdown をマークダウンのフォーマットに指定してください。

--markup markdown
--markup-provider kramdown

Gemfile にも同様に kramdown を追加するようにしてください。

gem 'kramdown', groups => [:development], require => false

警告

自動作成されたコメントブロックの後にテキストを追加しないでください。annotate によって以前にコメントブロックが追加された可能性があるとき、annotate はモデル内の始端・終端のコメントブロックを削除することがあります。

annotate が行った変更を必ず確認するようにしてください。Git を使用している場合は、annotate を実行した後にプロジェクトのステータスを確認することができます。

$ git status

VCS(Git や Subversion など)を使っていない人は、特に注意して annotate を扱い、1つの VCS の利用を検討してください。

リンク集

ライセンス

Ruby と同じライセンスでリリースしています。サポートと保証はありません。

作者

AUTHORS.md をご覧ください。

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

undefined method 'page' for # <Array:0x000........>が出たときの対処法

はじめに

gem kaminariについて、新しい発見があったので、記録として残す。

今回のエラー

controller.rb
インスタンス変数 = オブジェクト.page(:params[:page]).per(10)
index.html.haml
= paginate インスタンス変数

と定義したところ、エラーが出た。

エラー文

undefined method 'page' for # <Array:0x000........>

なぜ???

実は、、、

私の解釈では、、

controller.rb
インスタンス変数 = オブジェクト.page(:params[:page]).per(10)

のつもりだったのが、

controller.rb
インスタンス変数 = 配列.page(:params[:page]).per(10)

の間違いだった。

解決策

kaminariの公式ページを読めばわかるのだが、

通常.pageメソッドはActive Recordのオブジェクトにしか適用されないらしい。

今回はそうではなく、配列(Array)だったのでエラーが出ていたようだ。

そこでkaminariでは、配列にも.pageメソッドを使用できるやり方がある。

controller.rb
インスタンス変数 = Kaminari.paginate_array(配列).page(params[:page]).per(10)

これで解決した。

おわりに

あまり深く理解せず使用していたジェムがたくさんあるが、こういうエラーがきっかけで、
改めて公式レファレンスを読むことの重要性を感じた。

公式レファレンスを読み進めていくと、新しい発見もたくさんあるので非常に楽しいので、皆さんもいろんなgemの公式レファレンスを読んでみてほしい。

参考記事

公式レファレンス
https://github.com/kaminari/kaminari/blob/master/README.md

kaminariでundefined method `page' for #<Array:0x000xxxxxxと出た
https://haayaaa.hatenablog.com/entry/2019/03/11/215042

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

rails newするとCould not load command "rails/commands/server/server_command". Error: uninitialized constant URI::Generic.と表示される時の対処法

Vagrantを使って環境構築をしている際、エラーに遭遇したので記録を残しておきます。

構築環境

Ruby 2.5.0
Ruby on rails 5.1.7
Vagrant 2.2.7
Virtual Box 6.1.4
rbenv 1.1.2

発生したエラー

$ rails new
#省略
[WARNING] Could not load command "rails/commands/server/server_command". Error:
uninitialized constant URI::Generic.

解決した方法

Rubyのバージョンを2.5.7に上げるとこのエラーは解消されました。

#バージョン2.5.7のRubyをインストール
$ rbenv install 2.5.7
#省略

#使用する全体のrubyのバージョンを指定
$rbenv global 2.5.7

#Rubyのバージョンを確認
$ruby -v
ruby 2.5.7p206 (2019-10-01 revision 67816) [x86_64-linux]

$rails new
#うまくいきました

https://stackoverflow.com/questions/59961343/failing-to-start-up-default-rails-server
この質問を参考にしてバージョンを変えてみたんですが、よく読むと2.5.7でも同じ現象が起こったって書かれてますね...。原因が分かる方は教えていただきたいです。

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

アカウントの有効化〜Railsチュートリアル11章〜

いよいよRailsチュートリアル11章に入っていきます。11章の体感としてはProgateの内容でも少し触った内容であったので割とスムーズにすすめることができました。
それではまとめていきます。

アカウントの有効化

現時点でのApplicationは新規登録したユーザーははじめからすべての機能にアクセスできるようになっている。
ここではアカウントを有効化するステップを新規登録の途中に差し込むことで本当にそのメールアドレスの持ち主なのかどうかを確認できるようにする。
大まかな流れ
(1)有効化トークンやダイジェストを関連付けておいた状態で
(2)有効化トークンを含めたリンクをユーザーにメールで送信し
(3)ユーザーがそのリンクをクリックすると有効化できるようにする
というものである。

基本的な手順

1.ユーザーの初期状態は「有効化されていない」にしておく
2.ユーザー登録が行われたときに有効化トークンとそれに対応する有効化ダイジェストを生成する。
3.有効化ダイジェストはデータベースに保存しておき、有効化トークンはメールアドレスと一緒にユーザーに送信する有効化用メールのリンクに仕込んでおく
4.ユーザーがメールのリンクをクリックしたらApplicationはメールアドレスをキーにしてユーザーを探し、データベース内に保存しておいた有効化ダイジェストと比較することでトークンを認証する。
5.ユーザーが認証できたら、ユーザーのステータスを「有効化されていない」から「有効化済み」に変更する。

AccountActivationsリソース

Session機能を使ってアカウントの有効化という作業を「リソース」としてモデル化する。アカウントの有効化リソースはActive Recordのモデルとは関係ないので両者を関連付けることはしない。その代わりにこの作業に必要なデータ(有効化トークンや有効化ステータス)をUserモデルに追加する。

普段のリソースとは異なる点
有効化用のリンクにアクセスして有効化のステータスを変更する部分では、RESTのルールに従うとPATCHリクエストとUpdateアクションになるべきである。しかし有効化リンクはメールでユーザーに送られる。ユーザーがこのリンクをクリックすればそれはブラウザで普通にクリックしたときと同じであり、その場合ブラウザから発行されるのはGETリクエストになってしまう。このためユーザーからのGETリクエストを受けるためにEDITアクションに変更して使っていく。

アカウント有効化に使うリソースを追加する

Rails.application.routes.draw do
resources :account_activations, only: [:edit]
↑
#有効化のメールにedit_account_activation_url
(activation_token, ...)
を使用したいのでeditアクションへ名前付きルートが必要になる。
そこで上記のResourcesを追加する。

AccountActivationのデータモデル

有効化のメールには一意の有効化トークンが必要であるも送信メールとデータベースのそれぞれに同じ文字列をおいておく方法だと情報漏えいが起こった際多大な被害につながる。
そこでデータベースに仮想的な属性をつけてハッシュ化した文字列をデータベースに保存するようにする。具体的には仮想属性の有効化トークンにアクセスし、user.activation_tokenでユーザーを認識できるようにする。

$ rails generate migration add_activation_to_users \
> activation_digest:string activated:boolean activated_at:datetime
↑をデータベースにmigrateする。

アクティブトークンのコールバック

ユーザーが新しい登録を完了するためには必ずアカウントの有効化が必要になるので有効化トークンや有効化ダイジェストはユーザーオブジェクトが作成される前に作成しておく必要がある。
そこでbefore_createコールバックが必要となる。

before_create :create_activation_digest

上のコードはメソッド参照と呼ばれるもので、こうするとRailsはcreate_activation_digestというメソッドを探し、ユーザーを作成する前に実行するようになる。create_activation_digestメソッド自体はUserモデル内でしか使わないので、外部に公開する必要はない。
よってprivateキーワードを指定してこのメソッドを隠蔽する。

self.activation_token  = User.new_token
self.activation_digest = User.digest(activation_token)

このコードを9章で作成した永続Sessionのためのユーザー登録した時と比べる。
9章では記憶トークンとダイジェストはすでにデータベースにいるユーザーのために作成されるのに対し、before_createコールバックはユーザーが作成される前に呼び出されることなので更新される属性がまだない。
このコールバックがあることでUser.newで新しいユーザーが定義されるとactivation_token属性やactivation_digest属性が得られるようになる。

class User < ApplicationRecord
  attr_accessor :remember_token, :activation_token
  before_save   :downcase_email
  before_create :create_activation_digest
  validates :name,  presence: true, length: { maximum: 50 }
  .
  .
  .
  private

    # メールアドレスをすべて小文字にする
    def downcase_email
      self.email = email.downcase
    end

    # 有効化トークンとダイジェストを作成および代入する
    def create_activation_digest
      self.activation_token  = User.new_token
      self.activation_digest = User.digest(activation_token)
    end
end

アカウント有効化のメール送信

データのモデル化が終わったのでアカウント有効化メールの送信に必要なコードを追加する。このメソッドではActionMailerライブラリを使ってUserのメイラーを追加する。メイラーは、モデルやコントローラと同様にrails generateで生成できる。メイラーの構成はコントローラのアクションとよく似ている。テンプレートはビューと同じようなもの。このテンプレートの中に有効化トークンとメールアドレス (= 有効にするアカウントのアドレス) のリンクを含め、使っていく。

Userメイラーで
account_activationメソッドと、第12章で必要となるpassword_resetメソッドを生成する。
生成したメイラーごとにビューのテンプレートが2つずつ生成される。
1つはテキストメール用のテンプレート
1つはHTML用のテンプレートである。

生成されたApplicationメイラーにはデフォルトのformアドレスがある。

最初に生成されたテンプレートをカスタマイズして実際に有効化メールで使えるようにする。

app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "noreply@example.com"
  layout 'mailer'
end

次にユーザーを含むインスタンス変数を作成してビューで使えるようにし、user.emailにメール送信を行う。subjectキーはmailの件名に当たる。

class UserMailer < ApplicationMailer

  def account_activation(user)
    @user = user
    mail to: user.email, subject: "Account activation"
  end

edit_account_activation_url(@user.activation_token, ...)
ここで思い出してみる。

edit_user_url(user)
上のメソッドは、次の形式のURLを生成します。

http://www.example.com/users/1/edit

これに対応するアカウント有効化リンクのベースURLは次のようになります。

http://www.example.com/account_activations/q5lt38hQDc_959PVoo6b7A/edit

クエリパラメータを使って、このURLにメールアドレスもうまく組み込んでみましょう。クエリパラメータとは、URLの末尾で疑問符「?」に続けてキーと値のペアを記述したものです。

account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com

このとき、メールアドレスの「@」記号がURLでは「%40」となっている点に注目してください。これは「エスケープ」と呼ばれる手法で、通常URLでは扱えない文字を扱えるようにするために変換されています。Railsでクエリパラメータを設定するには、名前付きルートに対して次のようなハッシュを追加します。

edit_account_activation_url(@user.activation_token, email: @user.email)

送信メールのプレビュー

Railsでは、特殊なURLにアクセスするとメールのメッセージをその場でプレビューすることができます。

 development環境のメール設定
config/environments/development.rb
Rails.application.configure do
  .
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :test
  host = 'example.com' # 自分のクラウドIDEのリンクを貼る
  config.action_mailer.default_url_options = { host: host, protocol: 'https' }
  .
end
# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview

  # Preview this email at
  # http://localhost:3000/rails/mailers/user_mailer/account_activation
  def account_activation
    user = User.first
    user.activation_token = User.new_token
    UserMailer.account_activation(user)
  end

  # Preview this email at
  # http://localhost:3000/rails/mailers/user_mailer/password_reset
  def password_reset
    UserMailer.password_reset
  end
end

メールtestの実装

test/mailers/user_mailer_test.rb
require 'test_helper'

class UserMailerTest < ActionMailer::TestCase

  test "account_activation" do
    user = users(:michael)
    user.activation_token = User.new_token
    mail = UserMailer.account_activation(user)
    assert_equal "Account activation", mail.subject
    assert_equal [user.email], mail.to
    assert_equal ["noreply@example.com"], mail.from
    assert_match user.name,               mail.body.encoded
    assert_match user.activation_token,   mail.body.encoded
    assert_match CGI.escape(user.email),  mail.body.encoded
  end
end

アカウントの有効化

AccountActivationsコントローラのeditアクションを書いていく。

authenticated?メソッドの抽象化

app/models/user.rb

class User < ApplicationRecord
  .
  .
  .
  # トークンがダイジェストと一致したらtrueを返す
  def authenticated?(attribute, token)
    digest = send("#{attribute}_digest")
    return false if digest.nil?
    BCrypt::Password.new(digest).is_password?(token)
  end
  .
  .
  .
end
module SessionsHelper
  .
  .
  .
  # 現在ログイン中のユーザーを返す (いる場合)
  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      user = User.find_by(id: user_id)
      if user && user.authenticated?(:remember, cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end``

アカウントを有効化するeditアクション

app/controllers/account_activations_controller.rb
class AccountActivationsController < ApplicationController

  def edit
    user = User.find_by(email: params[:email])
    if user && !user.activated? && user.authenticated?(:activation, params[:id])
      user.update_attribute(:activated,    true)
      user.update_attribute(:activated_at, Time.zone.now)
      log_in user
      flash[:success] = "Account activated!"
      redirect_to user
    else
      flash[:danger] = "Invalid activation link"
      redirect_to root_url
    end
  end
end

本日はここまで

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

【解決方法】Mysql2::Error: Table 'users' already exists

Mysql2::Error: Table 'users' already existsエラーに遭遇

Mysql2::Error: Table 'users' already exists


どうやらusersテーブルが既に存在しているため、マイグレートが実行できない様子


解決方法として既に存在するusersテーブルを削除し、再びマイグレートすれば良さそうだ

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

$ rails db:

すると

Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 490
Server version: 5.6.47 Homebrew

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>


長々と文章が出てくるが、とりあえず次のコマンドを実行

mysql> SHOW TABLES;


mysqlのテーブルが表示される

+---------------------------+
| Tables_in_app_development |
+---------------------------+
| ar_internal_metadata      |
| posts                     |
| schema_migrations         |
| users                     |
+---------------------------+

ここからusersテーブルを消すために、次のコマンドを実行

mysql> drop table テーブル名;

今回はusersテーブルなので

mysql> drop table users;
Query OK, 0 rows affected (0.04 sec)

削除完了。 sequelProで中身を確認してところ、無事消えていた。

ターミナル上の操作はなかなか覚えられないが、こうした作業ができるのを知っておくのが大切かなと。

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

【rails】haml導入した際のメモ

haml-rails導入

Gemfile
gem 'haml-rails'
console
bundle install

erbファイルをhamlファイルに変換する

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

[Rails][data-vocabulary.org スキーマのサポートは終了します。]gem Gretelをschema.orgに対応させる方法。

data-vocabulary.org スキーマのサポートは終了します。

スクリーンショット 2020-03-10 11.47.24.png

2020年4月6日からGoogleで「data-vocabulary.org」を利用した構造化データがリッチリザルトとしてサポートされなくなるので、引き続きリッチリザルトを利用したい場合は「schema.org」を利用した構造化データに移行する必要があります。

パンくずリスト  |  Google 検索デベロッパー ガイド  |  Google Developers
https://developers.google.com/search/docs/data-types/breadcrumb?hl=ja

Gem Gretelとは

Gretel( https://rubygems.org/gems/gretel/versions/3.0.7 )は設定ファイルを書くことで、簡単にパンクズリストを出力することができるようになるGemです。
パンクズの出力のオプションでsemantic: trueをつけることによりリッチリザルトにも対応していました(data-vocabulary.org)。

しかし、このGemはメンテナンスが長いこと止まっており、Googleの「schema.org」を利用するにはパッチを当てる必要がります。
自分で検索した所同じような対応をしている人が散見されたので、僕の方法を示して行こうと思います。

※ 本来は別のGemなどに移行することをオススメしますが、昔から使っている人がお手軽に対応できる方法を本記事にしています。

モンキーパッチを当てる

Gretel Gemを使っているRailsプロジェクトで config/initializers/gretel.rb を作成して

ruby
# frozen_string_literal: true

module Gretel
  module ViewHelpers
    delegate :breadcrumbs_json_ld, to: :gretel_renderer
  end

  class Renderer
    # rubocop:disable Rails/OutputSafety
    def breadcrumbs_json_ld
      {
        "@context": 'http://schema.org/',
        "@type": 'BreadcrumbList',
        "itemListElement": links.map.with_index do |link, i|
          {
            '@type': 'ListItem',
            'position': i + 1,
            'item': {
              '@id': "#{root_url.chop}#{link}",
              'name': link.text
            }
          }
        end
      }.to_json.html_safe
    end
    # rubocop:enable Rails/OutputSafety
  end
end

Yamitake gist gretel.rb https://gist.github.com/yamitake/3659b9d87404ad975f8a881b05971e33

使い方

breadcrumbs_json_ldを宣言したので、viewファイル側で下記のように宣言することにより、schema.orgに対応したjsonLDが出力されます。

.breadcrumbs
  == breadcrumbs
  = tag.script(breadcrumbs_json_ld, type: 'application/ld+json')

おわりに

今までGretelで開発してきた人が暫定対応としては上記の方法でお手軽に対応できますが、gretelはメンテナンスが長いことされていないので別のGemを探すか自前でパンクズの実装をした方がいいと思います。

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

Rails × jquery 爆速でGoogleカレンダーつぽいアプリを作ってみよう(FullCalenderバージョン)

今日の目標

爆速でGoogleカレンダーっぽいアプリを作ってみる

Image from Gyazo

FULLCalender公式ページ

機能

  • 月別、週別、日別の表示が可能

  • イベントをドラッグして作成および編集が可能(今回は未実装)

  • Googleカレンダーとの同期もできる(今回は未実装)

今回は爆速バージョンということでイベントタイトル、説明、開始日、終了日を登録し、カレンダーに登録するところまでの実装とします。

環境

Ruby 2.5.1
Ruby on rails 5.2.4
Sqlite3 1.4.2

gemの記載

Gemfileに使うgemを追加

Gemfile
gem 'jquery-rails', '4.3.3'
gem 'fullcalendar-rails'
gem 'momentjs-rails'

bundle installを実行

application.js

assets/javascripts/application.js
//= require jquery
//= require moment
//= require fullcalendar

今回は単一機能のアプリであるので application.jsに直書きします。

assets/javascripts/application.js
$(function () {
    function eventCalendar() {
        return $('#calendar').fullCalendar({});
    };
    function clearCalendar() {
        $('#calendar').html('');
    };
});

一つ目の関数ではFullCalendarの設定を読み込み、二つ目の関数ではFullCalendarを削除します。
そして、RailsにはTurbolinksというツールを使ってページ遷移を高速にさせています。この機能がないと、Turbolinksを使っているときにカレンダーが複数回表示されることが起きてしまいます。

関数を追加したら、今度は呼び出すコードをfunction clearCalender()の後ろに記述します。

application.js
$(document).on('turbolinks:load', function () {
    eventCalendar();
});
$(document).on('turbolinks:before-cache', clearCalendar);

application.css

*= require fullcalendarを追加

assets/application.css
*= require_tree .
 *= require_self
 *= require fullcalendar
 */

これで、viewのerbファイルに<div id="calendar"></div>と書き込むだけでカレンダーが表示されます。(が、まだこれではイベント表示機能を付けた時に上手く表示されないときがあります。この後改修していきます。)
要ルーティングの設定。

scaffoldで爆速でEventモデル、コントローラ等作成する

イベントにはタイトル、説明、開始日、終了日が必要です。
今回はscaffoldを用いて爆速でモデル、コントローラを作成します。

*scaffoldを使うとCRUD機能を爆速で実装出来ます。(テストアプリ向け)

CRUD とは Create(作成) / Read(表示) / Update(更新) / Delete(削除) の略でアプリケーションを実現するために必要十分な機能群のことを指します。

そして、Rails の Scaffold を利用することによって、その CRUD を素早く実現できるので実際に試してみましょう。

$ rails g scaffold Event title:string description:text start_date:datetime end_date:datetime

マイグレーションファイルが以下のように作成されているか確認しましょう
Image from Gyazo

ルーティング

scaffoldでモデル、コントローラを作成した後はルーティングを設定しましょう。

config/routes.rb
Rails.application.routes.draw do
  resources :events

  root 'events#index'
end

JSONファイルの作成

Fullcalendarにイベントの情報を表示するには、JSONファイルを使ってあげます。

JSONを渡すために、Railsのjbuilderというものを使っていきます。
scaffoldによってjson.jbuilderファイルが自動で作られています。

Image from Gyazo

ここに以下の記述を追加します。

app/views/_event.json.jbuilder
json.array!(@events) do |event|
  json.extract! event, :id, :title, :description   
  json.start event.start_date   
  json.end event.end_date   
  json.url event_url(event, format: :html) 
end

application.js

ここまでの記述のまとめ

assets/javascripts/application.js
$(function () {
    function eventCalendar() {
        return $('#calendar').fullCalendar({});
    };
    function clearCalendar() {
        $('#calendar').html('');
    };
    $(document).on('turbolinks:load', function () {
    eventCalendar();
    });
    $(document).on('turbolinks:before-cache', clearCalendar);

    $('#calendar').fullCalendar({
    events: '/events.json'
    });
});

このままでも作動するのですが 若干turbo-linksの不具合が生じます。

if ($('#calendar').length) {
   function eventCalendar() {
   return $('#calendar').fullCalendar({
    });

という形で lengthを呼び出すことで、#calendarが存在していた場合はtrueの処理がされ、無い場合はnillを返すという記述をすることで Turbo-linksが正常に動作するようになります。

application.js

完成記述 オプションを付け加える場合には 基本的に以下の記述に追加する

$(function () {
  $(document).on('turbolinks:load', function () {
    if ($('#calendar').length) {
      function eventCalendar() {
        return $('#calendar').fullCalendar({});
      };
      function clearCalendar() {
        $('#calendar').html('');
      };
      $(document).on('turbolinks:load', function () {
        eventCalendar();
      });
      $(document).on('turbolinks:before-cache', clearCalendar);

      $('#calendar').fullCalendar({
        events: '/events.json'
      });
    }
  });
});

Turbo-linksの記述位置に注意です。

見本完成形

Image from Gyazo

ドラッグ機能、Googleカレンダーとの同期も含め、オプションは豊富なので 是非研究してみてください。

公式ドキュメントを読み込む力は必須なのでこちらも読み込みましょう。

公式ドキュメント

オプション等の記事はまた次の機会に

Keita Higaki

がっきーTwitter

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

Request SpecでDeviseのsing_in userを実施する

Request specでDeviseのsign_inを実施する

検索するとModuleを利用したものや、認証用モデル作成としてFactory-girlを利用したやや複雑かつやや古いものが多く、少し困ったため
Rails6環境での実施を記録します。

実施環境

  • Rails 6.0.2.2
  • Factory-bot-rails 5.1.1
  • Devise 4.7.1
  • RSpec 3.9

下記を実施して、devise用ユーザーとpostモデルを作成しておきます。

rails generate devise user
rails generate scaffold post body:string

migrateも忘れずに

rails db:migrate

spec実施時に、Deviseのhelperを呼び出せるように設定

spec/rails_helper.rb
RSpec.configure do |config|
  #色々書いてある下に下記行を追記
  config.include Devise::Test::IntegrationHelpers, type: :request
end

specからrails_helperを通してDeviseのsigin_in等のヘルパー機能を呼び出せるようになります。

posts_controllerの設定

app/controller/posts_controller.rb
class PostsController < ApplicationController
  自動生成されたbefore_actionがありますが、今回は下記のように修正
  before_action :authenticate_user!, expect: [:index]
#省略
end
app/config/routes.rb
Rails.application.routes.draw do
  devise_for :users #先頭にくるようにしてください
  resources :posts
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

認証処理なしで一度spec(自動生成されています)を実行

rspec spec/requests/posts_spec.rb
Failures:

  1) Posts GET /posts works! (now write some real specs)
     Failure/Error: expect(response).to have_http_status(200)
       expected the response to have status code 200 but it was 302
     # ./spec/requests/posts_spec.rb:7:in `block (3 levels) in <main>'

Finished in 0.19729 seconds (files took 3.08 seconds to load)
1 examples, 1 failure

Failed examples:

rspec ./spec/requests/posts_spec.rb:5 # Posts GET /posts works! (now write some real specs)

失敗の内容から、レスポンスステータス200を期待した結果302(ログイン画面へのredirect)が返却され失敗となっています。

factory_Bot未使用の簡易な実装

spec実施時に認証用モデルを都度作成することで、簡易に実現できます。

spec/requests/posts_spec.rb
require 'rails_helper'

RSpec.describe "Posts", type: :request do
  describe "GET /posts" do
    before do
      #認証ユーザーを作成します。
      #各変数の中身は何でも良いです。passwordとpassword_confirmationが一致することだけ確認してください。
      @user = User.create(email: 'test@test.com', password: "password", password_confirmation: "password")
    end

    it "works! (now write some real specs)" do
      #認証処理を行います
      sign_in @user
      get posts_path
      expect(response).to have_http_status(200)
    end
  end
end

非常に簡易ですが、これだけでDevise認証を必要とするspecの作成は可能です。

実行結果

Finished in 0.12959 seconds (files took 1.07 seconds to load)
1 example, 0 failures

factory_botを利用したDevise認証

fixturesのデファクトであるfactory_bot(旧factory_girl)を利用した実装です。

factory_bot利用できるように設定

spec配下にsupportディレクトリを作成し下記のファイルを作成

spec/support/factory_bot.rb
RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end

併せて、下記ファイルのコメントアウトを削除し有効にします。

spec/rails_helper.rb
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

テスト用modelを作成します。

spec/factories/users.rb
FactoryBot.define do
  factory :user do
   email { "test@test.com"}
   #下記の値は同じになるようにしてください
   password { "password" }
   password_confirmation { "password"}
  end
end

specでfactoryを利用して認証ユーザーを取り出すようにします。

spec/requests/posts_spec.rb
require 'rails_helper'

RSpec.describe "Posts", type: :request do
  describe "GET /posts" do
    before do
      #factory_botを利用して認証モデルを作成します。
      @user = create(:user)
    end

    it "works! (now write some real specs)" do
      #認証処理を行います
      sign_in @user
      get posts_path
      expect(response).to have_http_status(200)
    end
  end
end

実行結果

Finished in 0.11056 seconds (files took 1.28 seconds to load)
1 example, 0 failures

サンプルコード

各verのサンプルコードはgithubへ上げてありますのでご参考までに。
factory_bot未使用
factory_bot利用

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

【Docker】Railsサーバが起動しない場合の対処

はじめに

とあるDockerの教材を学習していた時に表題の通り
railsサーバが起動しなかったため備忘録として記載していますrails&Docker初心者のため間違った見解があればご指摘いただけると嬉しいです。

結論

忙しい人のために先にどうやって解決したかを記載します

▼server.pidファイルを削除

$ rm tmp/pids/server.pid

▼削除確認されるので「yes」

$remove tmp/pids/server.pid? #yes

▼コンテナを立ち上げる

$docker-compose up -d

サーバー起動で無事解決♪

問題

【Docker】を使ったrailsの環境構築を以下の流れで設定

$docker-compose run web rails new . --force --database=mysql
//省略//

$docker-compose build 
//dockerfileからイメージをビルド//

$docker-compose up -d
//現在のディレクトリにあるdocker-compose.ymlに基づいて
コンテナを起動する//

$docker-compose run web bundle exec rake db:create
//railsで使用するデータベースをMySQLサーバ上に作成//

$rails s
//立ち上がらない...//

状態

$docker-compose psでコンテナの起動状態を確認

$docker-compose ps
     Name                   Command              State           Ports       
-----------------------------------------------------------------------------
original_db_1    docker-entrypoint.sh mysqld     Up       3306/tcp, 33060/tcp
original_web_1   bundle exec rails s -p 300      Exit 1    

original_web_1のStateがExit1になっていました...

$docker logs original_web_1 でログを確認

$docker logs original_web_1
=> Booting Puma
=> Rails 5.0.7.2 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
A server is already running. Check /app/tmp/pids/server.pid.
Exiting

サーバはすでに立ち上がっていて
Check /app/tmp/pids/server.pid.を確認してくれーと言われています

server.pid とは

WEB開発サーバを起動するときに書き込まれ、停止すると削除されるファイルみたいです。
server.pid に pid が書かれていると開発用WEBサーバが起動中と判断されてしまいます。
今回はこのserver.pidが書き込まれている状態のため、railsサーバーが起動しなかったんですね〜

解決策

ということで

$ rm tmp/pids/server.pid

server.pidファイルを削除で無事解決しました♪

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

Railsアプリのテスト実行中に cookies.signed がエラー(undefined) になったときの対処法

こんにちは、ペーパーエンジニアのよしこです。

Ruby on Railsのテストで、Cookies周りのバグに対応しました。
RSpecテスト環境での日本語情報が少なかったので、私と同じ初心者向けに情報共有します。

バグの内容

ControllerやHelperなどで使われているcookies.signed[:foo]をテストで呼び出すと、
NoMethodError: undefined method `signed' for #<Rack::Test::CookieJar:0x0>とエラーになりました。

cokkies.permanentcookies.encrypted でも同様のエラーになります。

hoges_controller.rb
class hogesController < ApplicationController
  def hogehoge
    pp cookies.signed[:foo] = "bar"
  end
end

上のコントローラー(もしくはヘルパー)のRSpecテストを書きます。

hoges_request_spec.rb
RSpec.describe "Hoges", type: :request do
  it { expect(hogehoge).to eq "bar" }
end

実行結果は、エラーとなります。

#ターミナル
$ rspec

Failures:

  1) Hoges #hogehoge
     Failure/Error: pp cookies.signed[:foo] = "bar"

     NoMethodError:
       undefined method `signed' for #<Rack::Test::CookieJar:0x00007ffddefd5460>
     # ./app/helpers/hoges_controller.rb:3:in 

対処方法(結論)

Rack::Test::CookieJarの未定義メソッド (undefined method) を定義し直します。

./spec/support/cookies.rbというファイル(名前は任意)を作成し、下のコードで保存します。

cookies.rb
# RSpecでcookies.signedがエラーになる対処
class Rack::Test::CookieJar
  def signed
    self
  end

  def permanent
    self
  end

  def encrypted
    self
  end
end

これで、 テストはパスできるはずです。

この解決方法は、GitHubのRSpec Issuesのコメントに記載されていました。
【GitHub】Signed cookies not available in controller specs - rspec-rails 3.5.0, rails 5.0 #1658
こちらの最下部コメントがヒントになりました。

@wangthony wangthony commented on 10 Nov 2018

@ivko999 's solution worked for me - thanks!
Even cleaner, just put it in a spec/support file:

# spec/support/cookies.rb
class ActionDispatch::Cookies::CookieJar
 def encrypted; self; end
 def signed; self; end
 def permanent; self; end # I needed this, too
end

なぜ動かないのか

Railsのcookieクラスは、環境によって参照元が変わります。

# テスト環境以外
ActionDispatch::Cookies::CookieJar

# テスト環境
Rack::Test::CookieJar

テスト環境のRack::Test::CookieJarが、
signed permanent encrypted メソッドをサポートしていないので、
テスト実行時にエラーになってしまいます。

※ ActionController::TestCaseではサポートしているとのことなので、Controllerテストではサポートしているのかもしれません。
【GitHub】Signed cookies not available in controller tests #27145

参考にしたブログ記事
Railsのインテグレーションテストでcookies.signedを使いたい

以上となります。
少しでもお力になれれば幸いです。

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

ユーザーの更新・表示・削除機能を追加します〜Railsチュートリアル10章〜

この章ではユーザー機能を充実させてRESTアクションを完成させます。
この章で行うことは
プロフィールの更新、認可モデルの実装。ユーザー一覧の追加。ユーザーの削除機能を追加していきます。

ユーザーを更新する

ユーザー情報を編集するパターンは新規ユーザーの作成と似通っている。
ユーザーを編集するためのeditアクションを作成する。
patchリクエストに応答するUpdateアクションを作成する。
またユーザー情報を更新できるのはそのユーザー自身だけであるよう設定する。

まずeditに対応するアクションとビューを追加する。

アクション

def edit
    @user = User.find(params[:id])
  end

ビュー

<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Save changes", class: "btn btn-primary" %>
    <% end %>

    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="http://gravatar.com/emails" target="_blank">change</a>
    </div>
  </div>
</div>

WebブラウザはネイティブではPATCHリクエスト (RESTの慣習として要求されている) を送信できないので、RailsはPOSTリクエストと隠しinputフィールドを利用してPATCHリクエストを「偽造」しています
ユーザーのeditビューで使われているtarget="_blank"ですが、これを使うとリンク先を新しいタブ(またはウィンドウ)で開くようになるので、別の Webサイトへリンクするときなどに便利です。

target="_blank"で新しいページを開くと、フィッシングサイトのような、悪意のあるコンテンツを導入させられてしまう可能性があります。対処方法は、リンク用のaタグのrel(relationship)属性に、"noopener"と設定します。

編集の失敗

編集に失敗した場合について扱う。まずupdateアクションの作成から始める。update_attributesを使って送信されたparamsハッシュに基いてユーザーを更新します。無効な情報が送信された場合、更新の結果としてfalseが返され、elseに分岐して編集ページをレンダリングします。

  def create
    @user = User.new(user_params)
    if @user.save
      log_in @user
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new'
    end
  end

  def edit
    @user = User.find(params[:id])
  end

  def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      # 更新に成功した場合を扱う。
    else
      render 'edit'
    end
  end

編集失敗のtest

require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "unsuccessful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    patch user_path(@user), params: { user: { name:  "",
                                              email: "foo@invalid",
                                              password:              "foo",
                                              password_confirmation: "bar" } }

    assert_template 'users/edit'
  end
end

test内容
1 そのユーザーにとっての編集画面を開ける
2 user/editのビューが表示される
3 編集した内容(無効な情報)をpatchリクエストとして送る
4 user/editのビューが表示される

編集成功のtest

require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
  .
  .
  .
  test "successful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end
end

このままでは長さに対するバリデーションが有効となっているためREDとなってしまう。パスワードのバリデーションに対して例外処理を加える。

ユーザーmodelsへ

validates :password, presence: true, length: { minimum: 6 }, allow_nil: true

を追加する、これは新規ユーザー登録時には有効とならない処理である。

認可

認証 (authentication) はサイトのユーザーを識別することであり、認可 (authorization) はそのユーザーが実行可能な操作を管理すること。
editアクションとupdateアクションはセキュリティ上欠陥がある。それはどのユーザーでもあらゆるアクションにアクセスできるため、誰でも (ログインしていないユーザーでも) ユーザー情報を編集できてしまう。そこでユーザーにログインを要求し、かつ自分以外のユーザー情報を変更できないように制御する(こういったセキュリティ上の制御機構をセキュリティモデルと呼ぶ)。

まずログインしたユーザーが保護されたページへアクセスしようとした際にログインページへ転送する方法と許可されていないページに対しアクセスするログイン済みのユーザーにはルートURLにリダイレクトさせるようにする。

ユーザーに対しログインを要求する

Usersコントローラの中でbeforeフィルターを使い、転送させる仕組みを作る。

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:edit, :update]

 # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end

デフォルトでは、beforeフィルターはコントローラ内のすべてのアクションに適用されるので、ここでは適切な:onlyオプション (ハッシュ) を渡すことで、:editと:updateアクションだけにこのフィルタが適用されるように制限をかけている。unlessは条件が偽の時に対応する処理が作動する。

editとupdateアクションの保護に対するtest

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
  .
  .
  .
  test "should redirect edit when not logged in" do
    get edit_user_path(@user)
    assert_not flash.empty?
    assert_redirected_to login_url
  end

  test "should redirect update when not logged in" do
    patch user_path(@user), params: { user: { name: @user.name,
                                              email: @user.email } }
    assert_not flash.empty?
    assert_redirected_to login_url
  end
end

正しいユーザーを要求する

ログインを要求するだけでは不十分であり、ユーザーが自分の情報だけを編集できるようにする必要がある。そこでUserコントローラのtestを補完するようにtestを追加する。

まずfixtureファイルに2人目のユーザーを追加する。
次に9章で定義したlog_in_asメソッドを使ってeditアクションとupdateアクションをtestする。

require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user       = users(:michael)
    @other_user = users(:archer)
  end
  .
  .
  .
  test "should redirect edit when logged in as wrong user" do
    log_in_as(@other_user)
    get edit_user_path(@user)
    assert flash.empty?
    assert_redirected_to root_url
  end

  test "should redirect update when logged in as wrong user" do
    log_in_as(@other_user)
    patch user_path(@user), params: { user: { name: @user.name,
                                              email: @user.email } }
    assert flash.empty?
    assert_redirected_to root_url
  end
end

@other_user(二人目のユーザー)を定義。
上段のtestは2人目のユーザーが1人目のユーザーのedit_user_pathに入った場合、フラッシュがでるか。ルートURLへリダイレクトするのかを確認している。
下段のtestは二人目のユーザーが一人目のユーザーの情報を変更する内容のpatchリクエストを送信した場合にフラッシュがでてルートURLへ値ダイレクトされるかを確認している。

下段のtestをパスするため、correct_userというメソッドを作成し、beforeフィルターからこのメソッドを呼び出せるようにする。

before_action :correct_user,   only: [:edit, :update]


    # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless @user == current_user
    end

フレンドリーフォワーディング

これまでは保護されたページにアクセスしようとすると問答無用で自分のプロフィールページに移動させられてしまう。別の言い方をすればログインしていないユーザーが編集ページにアクセス使用としていたならば、ユーザーがログインした後にはその編集ページにリダイレクトされるようにするのが望ましい動作である。

フレンドドリーフォワーディングのtest

require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
  .
  .
  .
  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    log_in_as(@user)
    assert_redirected_to edit_user_url(@user)
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end
end

編集URLへアクセス。ログインした時、編集ページにリダイレクトされているか?
失敗するテストが書けたので、ようやくフレンドリーフォワーディングを実装する準備ができた。ユーザーを希望のページに転送するには、リクエスト時点のページをどこかに保存しておき、その場所にリダイレクトさせる必要があります。この動作をstore_locationとredirect_back_orの2つのメソッドを使って実現する。これらのメソッドはSessionsヘルパーで定義しています

module SessionsHelper
  .
  .
  .
  # 記憶したURL (もしくはデフォルト値) にリダイレクト
  def redirect_back_or(default)
    redirect_to(session[:forwarding_url] || default)
    session.delete(:forwarding_url)
  end

  # アクセスしようとしたURLを覚えておく
  def store_location
    session[:forwarding_url] = request.original_url if request.get?
  end
end

転送先のURLを保存する仕組みは、ユーザーをログインさせたときと同じで、session変数を使います。requestオブジェクトも使っています (request.original_urlでリクエスト先が取得できます)。store_locationメソッドでは、 リクエストが送られたURLをsession変数の:forwarding_urlキーに格納しています。ただし、GETリクエストが送られたときだけ格納するようにしておきます。これによって、例えばログインしていないユーザーがフォームを使って送信した場合、転送先のURLを保存させないようにできる。

ログインユーザー用beforeフィルターにstore_locationを追加する。

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:edit, :update]
  before_action :correct_user,   only: [:edit, :update]
  def edit
  end
  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end

    # beforeアクション

    # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        store_location
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end

    # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless current_user?(@user)
    end
end

フォワーディング自体を実装するには、redirect_back_orメソッドを使います。リクエストされたURLが存在する場合はそこにリダイレクトし、ない場合は何らかのデフォルトのURLにリダイレクトします。デフォルトのURLは、Sessionコントローラのcreateアクションに追加し、サインイン成功後にリダイレクトします このコードは、値がnilでなければsession[:forwarding_url]を評価し、そうでなければデフォルトのURLを使っています。
またリストではsession.delete(:forwarding_url) という行を通して転送用のURLを削除している点にも注意してください。

すべてのユーザーを表示する

indexアクションを追加していく。ここではすべてのユーザーの一覧表示を行っていく。
データベースにサンプルデータを追加する方法
ユーザー出力のページネーション用リンクの追加をする

ユーザーの一覧ページを実装するためにまずはセキュリティモデルについて考える。ユーザーのshowページは今後もサイトを訪れた全てのユーザーから見えるようにするがindexページはログインしたユーザーにしか見せないようにし、未登録のユーザーがデフォルトで表示できるページを制限する。

indexページを不正なアクセスから守るためにindexアクションが正しくリダイレクトするか検証するtestを書いてみる。

require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user       = users(:michael)
    @other_user = users(:archer)
  end

  test "should redirect index when not logged in" do
    get users_path
    assert_redirected_to login_url
  end
  .
  .
  .
end

次にbeforeフィルターのlogged_in_userにindexアクションを追加してこのアクションを保護する。

そしてすべてのユーザーを表示するために、全ユーザーが格納された変数を作成し、順々に表示するindexビューを実装します。

<% provide(:title, 'All users') %>
<h1>All users</h1>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 50 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

そしてユーザー一覧ページのリンクをheaderに更新して動かせるようにする。

サンプルユーザーの追加

今のままでは一人しかユーザー一覧がないため、ここからはサンプルのユーザーを追加する。
サンプルのユーザーを作成するにはGemfileにFakergemを追加する。

データベース上にサンプルユーザーを生成するRailsタスク

db/seeds.rb
User.create!(name:  "Example User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar")

99.times do |n|
  name  = Faker::Name.name
  email = "example-#{n+1}@railstutorial.org"
  password = "password"
  User.create!(name:  name,
               email: email,
               password:              password,
               password_confirmation: password)
end

Example Userという名前とメールアドレスを持つ1人のユーザと、それらしい名前とメールアドレスを持つ99人のユーザーを作成します。

ページネーション

これで、最初のユーザーにも仲間ができたが、今度は逆に1つのページに大量のユーザーが表示されてしまっている。これを解決するのがページネーション (pagination) というもので、この場合は、例えば1つのページに一度に30人だけユーザーを表示するというものです。
これを使うためには、Gemfileにwill_paginate gem とbootstrap-will_paginate gemを両方含め、Bootstrapのページネーションスタイルを使ってwill_paginateを構成する必要があります。

gem 'will_paginate',           '3.1.6'
gem 'bootstrap-will_paginate', '1.0.0'

indexアクションにあるUser.allを、ページネーションを理解できるオブジェクトに置き換える必要もあります。まずは、ビューに特殊なwill_paginateメソッドを追加します。

app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 50 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

<%= will_paginate %>

indexアクションでUsersをページネートする。

  def index
    @users = User.paginate(page: params[:page])
  end

ユーザー一覧のtest

今回のテストでは、ログイン、indexページにアクセス、最初のページにユーザーがいることを確認、ページネーションのリンクがあることを確認、といった順でテストしていく。
まずFixtureで30人のユーザーを追加する。

次にページネーションを含めたUserIndexのtestを追加

test/integration/users_index_test.rb
require 'test_helper'

class UsersIndexTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "index including pagination" do
    log_in_as(@user)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination'
    User.paginate(page: 1).each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
    end
  end
end

ユーザーの削除

ここではユーザーを削除するためのリンクを追加する。また削除に必要なdestroyアクションも実装する。しかしその前に、削除を実行できる権限をもつ管理(Admin)ユーザーのクラスを作成する。
まず特権を持つ管理ユーザーを識別するために論理値をとるadmin属性をUserモデルに追加する。こうすると自動的にadmin?メソッドも使えるようになるのでこれを使って管理ユーザーの状態をtestする。

destroyアクション

Userリソースの最後の仕上げとしてdestroyアクションへのリンクを追加する。まずユーザーindexページの各ユーザーに削除用のリンクを追加し続いて管理ユーザーへのアクセスを制限する。これにより現在のユーザーが管理者のときに限り[delete]リンクが表示されるようになる。

ユーザー削除用リンクの実装

app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
  <% if current_user.admin? && !current_user?(user) %>
    | <%= link_to "delete", user, method: :delete,
                                  data: { confirm: "You sure?" } %>
  <% end %>
</li>

この削除リンクが動作するためには、destroyアクションを追加する必要があります。このアクションでは、該当するユーザーを見つけてActive Recordのdestroyメソッドを使って削除し、最後にユーザーindexに移動します。ユーザーを削除するためにはログインしていなくてはならないので、で:destroyアクションもlogged_in_userフィルターに追加しています。

destroyアクションでは、findメソッドとdestroyメソッドを1行で書くために2つのメソッドを連結 (chain) している。

結果として、管理者だけがユーザーを削除できるようになります (より具体的には、削除リンクが見えているユーザーのみ削除できる)。しかし、実はまだ大きなセキュリティホールがあります。ある程度の腕前を持つ攻撃者なら、コマンドラインでDELETEリクエストを直接発行するという方法でサイトの全ユーザーを削除してしまうことができるでしょう。サイトを正しく防衛するには、destroyアクションにもアクセス制御を行う必要があります。これを実装してようやく、管理者だけがユーザーを削除できるようにします。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
  before_action :correct_user,   only: [:edit, :update]
  before_action :admin_user,     only: :destroy
  .
  .
  .
  private
    .
    .
    .
    # 管理者かどうか確認
    def admin_user
      redirect_to(root_url) unless current_user.admin?
    end
end

ユーザー削除のtest

Usersコントローラをテストするために、アクション単位でアクセス制御をテストします。削除をテストするために、DELETEリクエストを発行してdestroyアクションを直接動作させます。このとき2つのケースをチェックします。

・ログインしていないユーザーであれば、ログイン画面にリダイレクトされること。
・ログイン済みではあっても管理者でなければ、ホーム画面にリダイレクトされること。

管理者権限の制御をアクションレベルでテストする green
test/controllers/users_controller_test.rb

require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user       = users(:michael)
    @other_user = users(:archer)
  end
  .
  .
  .
  test "should redirect destroy when not logged in" do
    assert_no_difference 'User.count' do
      delete user_path(@user)
    end
    assert_redirected_to login_url
  end

  test "should redirect destroy when logged in as a non-admin" do
    log_in_as(@other_user)
    assert_no_difference 'User.count' do
      delete user_path(@user)
    end
    assert_redirected_to root_url
  end
end

assert_no_differenceメソッドを使って、ユーザー数が変化しないことを確認している点に注目してください。

このテストでは、管理者ではないユーザーの振る舞いについて検証していますが、管理者ユーザーの振る舞いと一緒に確認できるとよさそうです。そこで、管理者であればユーザー一覧画面に削除リンクが表示される仕様を利用して今回のテストを追加していくことにします。

assert_difference 'User.count', -1 do
  delete user_path(@other_user)
end

assert_differenceメソッドを使ってユーザーが作成されたことを確認しましたが、今回は同じメソッドを使ってユーザーが削除されたことを確認しています。具体的には、DELETEリクエストを適切なURLに向けて発行し、User.countを使ってユーザー数が
1
減ったかどうかを確認しています。

したがって、管理者や一般ユーザーのテスト、そしてページネーションや削除リンクのテストをすべてまとめると、次のようになります。

削除リンクとユーザー削除に対する統合テスト green

test/integration/users_index_test.rb
require 'test_helper'

class UsersIndexTest < ActionDispatch::IntegrationTest

  def setup
    @admin     = users(:michael)
    @non_admin = users(:archer)
  end

  test "index as admin including pagination and delete links" do
    log_in_as(@admin)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination'
    first_page_of_users = User.paginate(page: 1)
    first_page_of_users.each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
      unless user == @admin
        assert_select 'a[href=?]', user_path(user), text: 'delete'
      end
    end
    assert_difference 'User.count', -1 do
      delete user_path(@non_admin)
    end
  end

  test "index as non-admin" do
    log_in_as(@non_admin)
    get users_path
    assert_select 'a', text: 'delete', count: 0
  end
end

となります。
本日はここまで。

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

Javascriptとrails(未完成)

JavaScriptとは

こちらで紹介しています。JavaScriptについて(素人が浅い知識で書かせていただきました)

Ajax

サーバーとの通信でデータを受け取り、ページを書き換える時に、
処理をするために別のcontrollerに飛んで、処理後に再度今のページの中身を読み込んで戻ってくるのは処理が多くて遅くなる

Ajaxを使えば今のページのままで、自分の命令が非同期でバックグラウンドで行割れる(今のページの再読み込みをせずに処理できる)。Ajaxの処理は全てJavaScriptが行わせている。

Ajaxの実行の仕方

linkにAjaxを仕込む

そのリンクが押されれば、今のページを表示したまま、別の処理を実行する(非同期処理)

サーバサイドでその処理が完了すれば処理後の画面になるよう表示する。非同期処理後の画面表示をする処理は2種類あるため次の項目で示す

注意:Ajaxでは現在のページを表示したまま、リンク先の処理非同期で実行するため、リンク先の処理(アクション内)にはriedirectを記述しない。

非同期処理後の画面表示

非同期処理後の画面表示をする処理は2種類ある。以下で削除ボタンにAjaxを仕込んだときの例で説明する

1

サーバーサイドからの反応で、Ajaxの成功、失敗を判断して、Ajax処理が成功すれば、rails-ujsによってAjaxを仕込んだリンクにAjax::successという値がつく

Ajax::successになっていれば、そのリンクに関連した要素を非表示にする。この処理をJsファイルに記述。

2(SJRというやり方)

サーバーサイドで処理を完了すれば、サーバーサイド側からJsの処理を命令するようにする。実際は、命令内容はjs.erbで書かれrubyでjsの処理を実行できるようにしてある。これはサーバーサイド川ではrubyでしか命令を行えなえずjsファイルでは命令実行ができないから、

注意:サーバーサイド側ではDOMを判断できない。しかし削除されたインスタンスのidを把握することはできるため、それを使って画面変更(削除)を行う。

実際のやり方
削除したい要素に、そのインスタンスのidがわかるようにHTMLのid属性を追加する

js.erbで任意のHTMLのid属性を持つ要素を削除するようにする。

2のメリットはサーバー側でデータを使って、Jsの内容を作成できること。デメリットはサーバーサイドからのjs命令(js.erb)をviewディレクトリの下で作成するため、共通化や見直すのが大変

補足

ajaxの処理はrails-ujsによって行われており、ajaxの成功失敗の判断もこいつが行っている

参考文献は現場で使える Ruby on Rails 5速習実践ガイド

補足

coffeeScriptとは

これはJsの代替言語で、JSで表現したいことを簡単にかける。またアセットパイプラインによって最終的にjsに変換されのでrailsでも使用できる。しかし最近はcoffeeScriptのメリットが少なくなってきたため、使用しないケースもある

turbolinks

こちらで紹介しています。JavaScriptについて(素人が浅い知識で書かせていただきました)
またturbolinksの具体的な機能はgetを用いたページ移行を高速化すること。具体的にいうと、ページ移行が起きた時に共通部分なる部分(cssやjs)は前のページの内容を残したまま移行するようにする。どのページでも同じcssやjsファイルを読み込んでいるため、読み込みを無くし引き継ぎをすることで高速化を実現している

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

Javascriptとrails

JavaScriptとは

こちらで紹介しています。JavaScriptについて(素人が浅い知識で書かせていただきました)

アセットパイプライン

こちらで紹介しています。railsのアセットパイプラインと本番環境のデプロイについて

Ajax

サーバーとの通信でデータを受け取り、ページを書き換える時に、
処理をするために別のcontrollerに飛んで、処理後に再度今のページの中身を読み込んで戻ってくるのは処理が多くて遅くなる

Ajaxを使えば今のページのままで、自分の命令が非同期でバックグラウンドで行割れる(今のページの再読み込みをせずに処理できる)。Ajaxの処理は全てJavaScriptが行わせている。

Ajaxの実行の仕方

linkにAjaxを仕込む

そのリンクが押されれば、今のページを表示したまま、別の処理を実行する(非同期処理)

サーバサイドでその処理が完了すれば処理後の画面になるよう表示する。非同期処理後の画面表示をする処理は2種類あるため次の項目で示す

注意:Ajaxでは現在のページを表示したまま、リンク先の処理非同期で実行するため、リンク先の処理(アクション内)にはriedirectを記述しない。

非同期処理後の画面表示

非同期処理後の画面表示をする処理は2種類ある。以下で削除ボタンにAjaxを仕込んだときの例で説明する

1

サーバーサイドからの反応で、Ajaxの成功、失敗を判断して、Ajax処理が成功すれば、rails-ujsによってAjaxを仕込んだリンクにAjax::successという値がつく

Ajax::successになっていれば、そのリンクに関連した要素を非表示にする。この処理をJsファイルに記述。

2(SJRというやり方)

サーバーサイドで処理を完了すれば、サーバーサイド側からJsの処理を命令するようにする。実際は、命令内容はjs.erbで書かれrubyでjsの処理を実行できるようにしてある。これはサーバーサイド川ではrubyでしか命令を行えなえずjsファイルでは命令実行ができないから、

注意:サーバーサイド側ではDOMを判断できない。しかし削除されたインスタンスのidを把握することはできるため、それを使って画面変更(削除)を行う。

実際のやり方
削除したい要素に、そのインスタンスのidがわかるようにHTMLのid属性を追加する

js.erbで任意のHTMLのid属性を持つ要素を削除するようにする。

2のメリットはサーバー側でデータを使って、Jsの内容を作成できること。デメリットはサーバーサイドからのjs命令(js.erb)をviewディレクトリの下で作成するため、共通化や見直すのが大変

補足

ajaxの処理はrails-ujsによって行われており、ajaxの成功失敗の判断もこいつが行っている

参考文献は現場で使える Ruby on Rails 5速習実践ガイド

補足

coffeeScriptとは

これはJsの代替言語で、JSで表現したいことを簡単にかける。またアセットパイプラインによって最終的にjsに変換されのでrailsでも使用できる。しかし最近はcoffeeScriptのメリットが少なくなってきたため、使用しないケースもある

turbolinks

こちらで紹介しています。JavaScriptについて(素人が浅い知識で書かせていただきました)
またturbolinksの具体的な機能はgetを用いたページ移行を高速化すること。具体的にいうと、ページ移行が起きた時に共通部分なる部分(cssやjs)は前のページの内容を残したまま移行するようにする。どのページでも同じcssやjsファイルを読み込んでいるため、読み込みを無くし引き継ぎをすることで高速化を実現している

yarn

これはJsのパッケージマネージャー。Jsのパッケージをどんなバージョンでインストールするかを管理。インストール内容に一貫性を持たせたりできる。railsでいうgemみたいなもの。また同じJsのパッケージマネージャーのnpmとも互換性があるため、便利。

参考文献
yarnとは
高速で、信頼性が高く、そして安全な依存関係の管理

webpacker

これはJsのビルドツール。ビルドツールとは実行環境で動作できるように組み立てるツールのこと。アセットパイプライン作成にはSprocketsが採用されていたが、webpackerを使用することで、より高度な処理をできるようにする。

参考文献は
【Rails入門】Webpackerではじめるフロントエンド開発!Rails5.1対応

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

[rails,payjp]カード登録の際に登録画面をリロードしないとトークンを作成できない時の解決例

1.原因

今回の筆者の問題はturbolinksが効いていたからでした。

2.なぜ起こった?

理由は、turbolinksの機能が影響範囲ページの遷移を非同期で行う(影響範囲の全てのviewページが合わさって1ページとして扱う)ためでした。
他画面を経由し複数で情報を持った状態でクレジットカードの登録をしようとすると、悪意のある情報を持っている危険性があり安全性を保てないと見なされてしまいpayjpからの返答を受けることができなくなっていました。

3.解決方法

クレジットカード登録画面のみturbolinks(非同期通信)を切ってページ遷移するように記述することで筆者は解決しました。

具体的にはapp/views/layouts/apprication.html.haml(全体のbodyの記述部分)app/views/cards/index(ページ遷移元)を下記のように修正しました

app/views/layouts/apprication.html.haml
〜前略(header部分の記述は省略します)〜

%body
    - if content_for?(:body_attributes)
      = yield(:body_attributes)
    = render 'layouts/notification'
    = yield
app/views/cards/index
〜前略〜
= link_to "/cards/new", class: 'method-of-payment__body__button__icon',"data-turbolinks": false do
#app/views/cards/newに遷移する記述の部分に "data-turbolinks": false をつけました
〜後略〜

4.参考記事

https://gist.github.com/saboyutaka/8727377
https://qiita.com/Cheekyfunkymonkey/items/216bf7426493e6213927
https://qiita.com/keitah/items/05f02efe4e11ab2473e8

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

CircleCI で並列実行した RSpec のカバレッジを Coveralls に送る

Coveralls に以下のドキュメントがあるのだけど、書いてある通りにやってもうまくいかなかったので、なんとかうまくいった方法を書いておきます。

やりたいこと

RSpec を並列で実行し、すべてのインスタンスの終了を待って、別のジョブで各インスタンスのカバレッジの結果をマージして Coveralls に送信したい。

  • ドキュメントには Coveralls.wear_merged! を呼んで最後に coveralls:push を実行しろとあるけれど、coveralls:push (Coveralls.push!) で使われている SimpleCov::ResultMerger.merged_result同じ環境で実行された 複数のテストのカバレッジ結果をマージするものであり、違う環境で実行された結果をマージできるわけではない。
  • Webhookを使う方法もあるけれど、テストを実行するジョブと結果を送信するジョブが違うので、結果を送信するときに payload[build_num] を指定することができない。

やり方

https://github.com/lemurheavy/coveralls-ruby

Gemfile
group :test do
  gem 'coveralls', '~> 0.8.23', require: false
end

RSpec

Coveralls.wear! は Coveralls にカバレッジを送信するけれど、Coveralls.wear_merged! は送信しない(SimpleCov を実行してるだけ)。

spec/rails_helper.rb
if ENV['COVERALLS_REPO_TOKEN']
  require 'coveralls'
  Coveralls.wear_merged!('rails')
end

# [...]

カバレッジ結果をマージ・送信する Rake Task をつくる

SimpleCov の README にある "Merging test runs under different execution environments" の通りにやればいいのだけど、Coveralls の gem が依存している SimpleCov のバージョンが古くて、SimpleCov.collate が存在しない。よって自分でつくる必要がある。

lib/tasks/coverage.rake
namespace :coverage do
  desc 'Collate all result sets and push to Coveralls'
  task push: %i[environment collate] do
    require 'coveralls'
    Coveralls.push!
  end

  desc 'Collate all result sets generated by the different test runners'
  task collate: :environment do
    require 'simplecov'

    # coverage/.resultset-*.json を coverage/.resultset.json にまとめる
    # see: https://github.com/colszowka/simplecov/blob/v0.18.5/lib/simplecov.rb#L80

    results = Dir['coverage/.resultset-*.json'].flat_map do |filename|
      (JSON.parse(File.read(filename)) || {}).map do |command_name, coverage|
        SimpleCov::Result.from_hash(command_name => coverage)
      end
    end

    SimpleCov::ResultMerger.merge_results(*results).tap do |result|
      SimpleCov::ResultMerger.store_result(result)
    end
  end
end

CircleCI

.circleci/config.yml
version: 2.1

executors:

  app:
    docker:
      - image: circleci/ruby:2.6.5-node-browsers
        environment:
          BUNDLE_PATH: vendor/bundle
          RAILS_ENV: test

jobs:

  test:
    executor: app
    parallelism: 2
    steps:
      - checkout

      # [...]

      - persist_to_workspace:
          root: .
          paths:
            - .

      - run:
          name: RSpec in parallel
          command: bin/rspec $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)

      # coverage/.resultset.json にカバレッジ結果が生成されているので、
      # 名前がぶつからないように別の名前を付けたファイルを persist_to_workspace する
      - run:
          name: Prepare to collate coverage results
          command: cp coverage/.resultset.json "coverage/.resultset-${CIRCLE_NODE_INDEX}.json"

      - persist_to_workspace:
          root: .
          paths:
            - coverage/.resultset-*.json

  report:
    executor: app
    steps:
      - attach_workspace:
          at: .

      # こんな感じになっているはず
      # ./coverage
      #   .resultset-0.json
      #   .resultset-1.json

      - run: bin/rake coverage:push

workflows:
  version: 2

  test-report:
    jobs:
      - test
      - report:
          requires:
            - test

できた

#!/bin/bash -eo pipefail
bin/rake coverage:push
Running via Spring preloader in process 81
[Coveralls] Submitting to https://coveralls.io/api/v1
[Coveralls] Job #132.1
[Coveralls] https://coveralls.io/jobs/XXXXXXXX
Coverage is at 100.0%.
Coverage report sent to Coveralls.
CircleCI received exit code 0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】deviseを使用した簡単なログイン機能まとめ

はじめに

学習中の備忘録です。

概要

新規アプリ作成の際にdeviseのコマンドなど忘れるのでまとめ。

  • 導入
  • devise設定ファイル作成
  • モデル作成
  • ビューファイル作成
  • deviseによって設定されるPrefixの一部

前提

rails 5.2.3

導入

gemファイルに追記

Gemfile
gem 'devise'

インストール

ターミナル
$ bundle install

サーバー再起動

ターミナル
$ rails s

deviseの設定ファイルを作成

ターミナル
$ rails g devise:install

新規作成されるファイル

  • config/initializers/devise.rb
  • config/locales/devise.en.yml

モデル作成

ターミナル
$ rails g devise user

新規作成されるファイル

  • app/models/user.rb
  • db/migrate/20XXXXXXXXXXXX_devise_create_users.rb
  • test/fixtures/users.yml
  • test/models/user_test.rb

また、config/routes.rbに以下の様な記述が自動的に追記されます。

【例】config/routes.rb
Rails.application.routes.draw do
  devise_for :users
#以下略

devise_for :usersの記述により、ログイン・新規登録で必要なルーティングが生成されます。

作成されたmigrationファイルを実行

ターミナル
$ rails db:migrate

ビューファイル作成

ターミナル
$ rails g devise:views

新規作成されるファイル

  • app/views/devise以下のディレクトリにあるビューファイル各種

deviseによって設定されるPrefixの一部

リクエスト Prefix パス
devise/sessions#new new_user_session /users/sign_in
devise/registrations#new new_user_registration /users/sign_up
devise/sessions#destroy destroy_user_session /users/sign_out

あとは好きな場所に上記のリンクをはれば完成です。

まとめ

ユーザー情報の編集などは必要に応じて追記するかもです。

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