20190625のRailsに関する記事は23件です。

Railsチュートリアル完走後に、改めてリソースについてまとめてみた

背景

  • Railsチュートリアル完走後に、どのようにして機能を拡張していくか考えたときになんとなく「機能をリソース」として扱えれば、設計がしやすいのでは?というのが発端。また、リソースについてもしっかり理解できていなかったので完走したあとに再度情報整理したかった。

目的

  • 「Railsにおけるリソース」について、構成する要素の関係性についてまとめることで理解を深めてオペレーション時にもすぐに活かせる状態にしたい。

RESTの原則

  • RailsはRESTの原則に従って設計されている。
  • RESTとは、ざっくりいうとwebを設計するための考え方のひとつ
  • Railsでのリソースを理解するためには、土台であるRESTの原則を知る必要がある。

4つの原則

  1. ステートレスなクライアント/サーバプロトコル
  2. すべての情報(リソース)に適用できる「よく定義された操作」のセット
  3. リソースを一意に識別する「汎用的な構文」
  4. アプリケーションの情報と状態遷移の両方を扱うことができる「ハイパーメディアの使用」

引用元:Representational State Transfer - Wikipedia

1. ステートレスなクライアント/サーバプロトコル

RESTな考え方では、サーバーへのリクエストも、クライアントへのレスポンスも毎回状態を管理しない(ステートレス)HTTPを使う。

メリットとしては、複数のサーバーで複数のクライアントに対してレスポンスする際に効率がよい。

web設計する際に1つ目の原則に関しては、基本的にHTTPを使っていれば意識しなくて遵守される。

HTTP(HTTP/1.1)には、8つのメソッドが定義されており、2つ目の原則と関連してくる。

  • GET
  • POST
  • PUT
  • HEAD
  • DELETE
  • OPTIONS
  • TRACE
  • CONNECT

引用元:Hypertext Transfer Protocol - Wikipedia

2. すべての情報(リソース)に適用できる「よく定義された操作」のセット

RESTな考え方では、全てのリソースにHTTPメソッド(GET、POST、PUT、DELETEなど)を使ったアクセス可能な共通インターフェイスを持つ。

HTTPメソッドは次の役割を持っており、この役割に当てはめて設計を考える。

HTTPメソッド 役割
GET リソースの取得
POST 子リソースの作成、リソースへのデータ追加、その他の処理
PUT リソースの更新、リソースの作成
DELETE リソースの削除
HEAD リソースのヘッダ(メタデータ)の取得
OPTIONS リソースがサポートしているメソッドの取得
TRACE 自分宛にリクエストメッセージを返す(ループバック)試験
CONNECT プロキシ動作のトンネル接続への変更

引用:Webの成功理由は制約 8つしかないHTTPメソッド

3. リソースを一意に識別する「汎用的な構文」

RESTな考え方では、全てのリソースはURI(広い概念でURLとURNが含まれる)で表されるユニークなアドレス持つ

4. アプリケーションの情報と状態遷移の両方を扱うことができる「ハイパーメディアの使用」

今ではHTMLページに他のページへのリンクを貼ることは当たり前だが、そのこと。
これも、意識してなくても基本的に遵守される。

RESTfulとは

4つの原則に従って作成されたものを指す。

RailsでRESTfulなリソースを作成するために意識すること

Railsにおいて、RESTの原則の1と4に関しては、現在であれば当たり前なのであまり意識せずともよいが、2と3に関しては意識する必要がある。

2. すべての情報(リソース)に適用できる「よく定義された操作」のセット

Railsにおいて、2に関しては、具体的にRailsチュートリアル内のコラム 2.2で言及されており、
Railsのリソースは、下記に対応する共通インターフェイスを持っているといえる。

  • リレーショナルデータベースの作成/取得/更新/削除 (Create/Read/Update/Delete: CRUD) 操作
  • 4つの基本的なHTTPメソッド (POST/GET/PATCH/DELETE)

Railsは、RESTの原則を取り入れいち早く対応しているフレームワークでもあり、共通インターフェイスを持たせるために、HTTPメソッドごとにデータベース上のCRUD操作と対応付けを定義している。

Railsのルーティング機能は、RESTの考え方を取り入れる上で、重要。
ルーティングでHTTPメソッドとアクションを紐づけ、webでのCRUD操作を実現する。

下記は、photosリソースの例(CRUD操作の列は、HTTPメソッドとデータベースの繋がりが分かりやすいように追加)。

HTTP動詞 パス コントローラ#アクション 目的 CRUD操作
GET /photos photos#index すべての写真の一覧を表示 取得(R)
GET /photos/new photos#new 写真を1つ作成するためのHTMLフォームを返す 取得(R)
POST /photos photos#create 写真を1つ作成する 作成(C)
GET /photos/:id photos#show 特定の写真を表示する 取得(R)
GET /photos/:id/edit photos#edit 写真編集用のHTMLフォームを1つ返す 削除(D)
PATCH/PUT /photos/:id photos#update 特定の写真を更新する 更新(U)
DELETE /photos/:id photos#destroy 特定の写真を削除する 削除(D)

引用元:Rails のルーティング - Rails ガイドの2.2の表参照

HTTPメソッドとURL(パス)を組み合わせて、CRUD操作を実現していることがわかる。

Railsの良いところは、resources :photosを使えば、RESTの考え方に沿った共通インターフェイスができる点。

3. リソースを一意に識別する「汎用的な構文」

RESTの原則の3に関しては、Railsチュートリアルの7章の7.1.2 Usersリソースで少し触れられています。

RESTの原則に従う場合、リソースへの参照はリソース名とユニークなIDを使うのが普通です。

他にチュートリアルで良い例が見つけられなかったが下記が参考になった。

  • URLに動詞を含めず、複数形の名詞のみで構成する

REST APIとは? - API設計のポイント

リソースを2.2.2 MVCの挙動のMVCモデルの図と使って説明

これらを踏まえた上で、自分のリソースのイメージは、こんな感じです。

図でいうと、ユーザーから見たインターフェイスは/usersになる。
/usersのページのリクエスト(HTTPメソッド)を元にCRUD操作と結びつける(ルーターとコントローラの役割。モデルも密接に関わっている)。

CRUD操作をするためには、データモデルが必要になり、最終的にユーザーから見える/usersに提供するためにビューが必要になる。

これらが組み合わさり、ユーザーにCRUD操作を提供するオブジェクトがリソース。これがリソースの正体。

image.png

Railsチュートリアルで出てきたリソースを比較してみる。

一番やりかったのがこれ。
下記ページのP25をみたときに全体像が分かっていれば、少しリソースの設計がわかりやすくと思ったため。
Rest ful api設計入門

3章以降で出てくるリソースをRailsのCRUD操作に当てはめてみた(自分が分かりやすいように少し調整)。

1~3行目までは、すべてのリソースに共通する項目で、4行目は、途中で説明したphotosリソースを例として使っている。5行目以降がRailsチュートリアルで出てくるリソース。

すべてのアクションを使っていないリソースがあったり、用途に合わせてパスを調整しているリソースもあったりすることがわかる。

目的 一覧表示 作成フォーム 作成 個別表示 編集フォーム 個別更新 個別削除
HTTPリクエスト GET GET POST GET GET PATCH/PUT DELETE
対応アクション index new create show edit update destroy
photos /photos /photos/new /photos /photos/:id /photos/:id/edit /photos/:id /photos/:id
users /users /signup /signup /users/:id
/users/:id/following
/users/:id/followers
/users/:id/edit /users/:id /users/:id
sessions - /login /login - - - /logout
account_activations - - - - account_activations#edit - -
password_resets - /password_resets/new /password_resets - /password_resets/:id/edit /password_resets/:id -
microposts - - /microposts - - - /microposts/:id
relationships - - /relationships - - - /relationships/:id

まとめ

最後は、良くわからない表になってしまったがなんとなくでしか分からなかったリソースの全体像がわかったと思う。

なにより必要に応じて、分かりやすいように名前付きルートの変更(users/newをsignup)をしている点全てのアクションを使わなくてもリソースの考え方に則って実装することで、リソースとして扱うという実装方法の統一が図れるという点14章で出てくるフォローの実装はさらにRESTのリソースをより活用した実装(ルーティングをネストさせている)という点に気づけた。

参考

Representational State Transfer - Wikipedia

REST理論をわかりやすく。 - Qiita

ステートレスとは - Qiita

ステートフル ステートレスとはどういうことか - Sojiro’s Blog

yohei-y:weblog: ステートレスとは何か

Hypertext Transfer Protocol - Wikipedia

Representational State Transfer (REST)

RESTful Webサービスの概要

Webの成功理由は制約 8つしかないHTTPメソッド

Webのアーキテクチャスタイル(設計思想) REST

RESTとは - Blank?=False

RESTfulって・・・?調べたメモ | めもめも

「Webを支える技術」を読みました とRESTのまとめ - 大学生からの Web 開発

URLとURIは何が違うの? どちらが正しい呼び方? | 初代編集長ブログ―安田英久 | Web担当者Forum

RESTful-APIのURL設計を考えてみる - Qiita

Railsのルーティングを極める(前編)

Web API(REST API)のURL設計 | DevelopersIO

REST APIとは? - API設計のポイント

翻訳: WebAPI 設計のベストプラクティス - Qiita

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

form_forについて

form_forについて

構文の書き方

.haml
form_for(モデルオブジェクト , オプション) do |f|
  フォームコントロールの設置

form_forが生成するフォームコントロール

check_box :チェックボックス
color_field :色の入力欄
date_field :日付の入力欄
datetime_field :日時の入力欄(グローバルタイム)
datetime_local_field :日時の入力欄(ローカルタイム)
email_field :emailアドレスの入力欄
fields_for :form_forの中で別のモデルを指定したフォーム
file_field :画像や文章などのファイルを選択するフォーム
hidden_field :隠しフィールドの生成
label :ラベルの生成
month_field :月の入力欄
number_field :数値入力欄
password_field :パスワード入力欄
phone_field
telephone_field :電話番号入力欄
radio_button :ラジオボタンの生成
range_field :範囲選択バー
search_field :検索ボックスフォーム
text_area :文字列入力欄
text_field :文字列入力欄
time_field :時間入力欄
url_field :URL入力欄
week_field :週の入力欄
submit :送信ボタン

以上のようにフォームコントロールに設置するオプションと軽い説明を作成しました。

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

Rails x Docker環境にテストDBを構築する

Docker上でRailsアプリを開発するにあたり、テスト用のDBと開発用DBをそれぞれ構築する必要があったのでそのときの構築手順を記載。

前提

以下のようなDockerファイルでRailsアプリケーションを構築しているとする。

Dockerfile
FROM ruby:2.6
RUN apt-get update -qq && apt-get install -y nodejs
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install

COPY . /myapp

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]

RailsアプリケーションのDB設定を以下のようになっているとする。

database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch('DB_USERNAME', 'root') %>
  password: <%= ENV.fetch('DB_PASSWORD', 'pass') %>

development:
  <<: *default
  host: db
  database: myapp_development

test:
  <<: *default
  host: test-db
  database: myapp_test

production:
  <<: *default
  database: myapp_production

Dockerの設定方法

DBのコンテナ名はdatabase.ymlで設定したhostと一緒にする。

今回の場合、開発用DBのdockerコンテナは'db', テスト用DBのdockerコンテナは'test-db'とする。

MYSQL_USERとMYSQL_PASSWORDはdatabase.ymlで設定したユーザー名とパスワードと一致させる必要がある。

MYSQL_ROOT_PASSWORDはroot用パスワード。設定が必須。

MYSQL_DATABASEはdatabase.ymlで設定したdatabaseと一緒にする。

docker-compose.yml
version: '3'
services:
  web:
    build: .
    env_file: development.env
    ports:
      - '3001:3000'
    volumes:
      - .:/myapp
    depends_on:
      - db
      - test-db
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_USER: 'webapp'
      MYSQL_PASSWORD: 'test'
      MYSQL_DATABASE: myapp_development
    ports:
      - '3306:3306'

  test-db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_USER: 'webapp'
      MYSQL_PASSWORD: 'test'
      MYSQL_DATABASE: myapp_test
    ports:
      - '3307:3306' # ローカルPCから接続するために設定

docker-compose.ymlに直接記述してもいいけど、環境変数は外出ししておく。
今回は、webappというユーザーがtestというパスワードでDBに接続できるような設定にする。↓

development.env
DB_USERNAME=webapp
DB_PASSWORD=test

接続確認

webのコンテナ名がdocker-rails_web_1という名前で立ち上がっている前提。

$ docker exec -it $(docker ps -f name=docker-rails_web_1 -q) /bin/bash

[docker] # rails c -e test
irb(main):001:0> ENV['RAILS_ENV']
=> "test"

[docker] # rails c
irb(main):001:0> ENV['RAILS_ENV']
=> "development"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

progate 6/25

/login」で2つのルーティングが被っているように見えますが、「get」と「post」では異なるルーティングとして扱われるので問題ありません。(link_toメソッドではデフォルトでgetのルーティングを探し、form_tagメソッドがデフォルトでpostのルーティングを探します。)

  1. インスタンス変数に対するnameメソッドの意味 @user.name = params[:name]

次回 8回 8/15以降

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

【Ruby on Rails】ストロングパラメータって何なの?

【注:Rails初心者記事】
 記載に間違いなどがありましたら、指摘いただけると幸いです。

はじめに.この記事の目的

ストロングパラメータって何ぞや?
いろいろ調べた結果、頭の中が崩壊したので、一度記事としてまとめます。
これで、少しは理解が深まる・・・はず!!

1.ストロングパラメータって何?

ストロングパラメータは、Web上から受けたつけたパラメータが、本当に安全なデータかどうかを検証した上で、取得するための仕組みです。Rails4から実装されています。

2.ストロングパラメータがなぜ必要なのか?

この仕組みを使うことで、意図しない(安全では無い)データの登録・更新を防いでくれるます。

具体的にどうやって防ぐかと言うと、メソッドにあらかじめ登録・更新を許可するカラム名を指定(ホワイトリスト形式)しておきます。そうすると、万が一、未許可のカラムデータが送られてきても、データの登録前に未許可であることを検出し、登録対象として無視することができます。

不特定多数に公開するWebアプリケーションだからこそ、ストロングパラメータの仕組みは必要不可欠な訳ですね。

2−1.「マスアサインメント(Mass Assignment)機能と脆弱性」という問題

ストロングパラメータ機能が実装される以前(rails3)は、実際に意図しないデータの登録・更新が発生していたようです。実例では無いですが、どう言った問題なのか具体例をあげて整理してみました。

・ 問題発生の土壌

以下のとおり、マスアサインメント機能の脆弱性を孕んだWebアプリを作成したことを前提とします。

モデル

ユーザの管理を行うUserテーブル(モデル)
最後の「admin」カラムは管理者ユーザかどうかを識別するために作っています。
(admin=1(管理者))

user_id name email admin
1 はむ太郎 hamu@hamu.co.jp 1
ビュー

ユーザ情報の登録を求めるビュー
下図のとおり、Userモデルのname、emailのみの入力を意図して作成しています。
「admin」カラムは管理者ページからのみ操作したいので対象としていません。

new.html.erb
・・・中略・・・
<%= form_for @user do |f| %>
<div class="field">
  <%= f.text_field :name, placeholder: "ユーザ名を入力してね!" %>
</div>
<div class="field">
  <%= f.text_field :email, placeholder: "e-mailアドレスを入力してね!" %>
</div>
<div class="actions">
  <%= f.submit %>
</div>
<% end %>
・・・中略・・・
コントローラー

ユーザ情報の登録を行うコントローラ
マスアサインメント機能を使用し、Userモデル丸ごとデータ登録をさせるよう作成されています。

test_controller.rb
def create
  user = User.new(params[:user(モデル名)])
  user.save
def

・ 問題の発生契機と内容

このアプリのWeb公開後、該当のViewから下図のようなパラメータを受け取りました。最後のパラメータ"admin"は、開発者側が意図していない(安全で無い)データでした。
このパラメータは悪意あるユーザによるパラメータの改ざんによって発生したようです。

params.
 {"user"=>{"name"=>"はむ太郎","email"=>"hamu@hamu.co.jp","admin"=>1}}
                                                       ^^^^^^^^^^^^^^

この結果どうなるでしょうか?
「admin」と言うカラムは管理者ユーザかどうかを識別をしています。
そのため、この悪意あるユーザが管理者権限付きのユーザIDを獲得してしまったのです!
こんな事しちゃうユーザです。その後の更なるセキュリティ攻撃に発展しそうな予感大ですね。。。

こうした問題が「マスアサインメント機能と脆弱性の問題」でした。多分。
ただ、Rails4以降はストロングパラメータの使用が必須になっていて、上であげたような記述は出来ないようです。以下に実際の事象に関する記事がありましたので、参考までに。。。

【マスアサインメント機能の脆弱性問題に関する記事】
https://www.infoq.com/jp/news/2012/03/GitHub-Compromised/

3.ストロングパラメータの適用対象

3-1.適用対象について

ユーザがフォームから入力する情報がストロングパラメータの適用対象となります。
観点としては、以下2つを覚えておくと迷わないと思います。

・対象のデータ
 ユーザフォーム(View)から送られてきたデータ。
 コントローラ上の表記で言う”params”
 ※ただし、1カラムずつparams内のデータを指定する場合は不要
・対象の機能:対象のデータに対する登録・更新機能
 コントローラ上に定義された”create/update"などのメソッド

3-2.適用対象外について

対象となる機能・データ以外であれば、ストロングパラメータを介する必要はないようです。

・不要なデータ
 ユーザフォームから入力された情報ではない(=params以外)データ。
 例えば、current_user(devise利用時に使える変数)を登録する場合などです。ただし、敢えて、ストロングパラメータの対象とすることも可能です。

・不要な機能
 データの登録・更新が発生しない機能はもちろんですが、View/controllerを介さない機能も不要です。例えば、テストデータの一括登録、データ移行、バッチ処理などは不要。と言うより、機能自体使えないですね。きっと。

4.ストロングパラメータの書き方

前置きが長くなりましたが、ストロングパラメータの書き方について整理してみましょう。

4-1. 基本構文

ストロングパラメータは以下のように記述します。
メソッド名に命名規則は無いようですが[ モデル名_params ]とするのが一般的なようです。
また、実行結果として、許可されたカラムの値だけを抽出し、ハッシュ形式で呼び出し元に値を返してくれます。

user_controller.rb
private
def user_params
  params.require(:キー(モデル名)).permit(:カラム名1,:カラム名2,・・・).marge(カラム名: 入力データ)
end

4-2.requireメソッド

 requireメソッドを使用する事で、params内の特定のキーに紐付く値だけを抽出する事ができます。そのため、引数には取り出したい値のキーを指定する必要があります。

例)キー値userに対するデータを抽出したい場合は、以下のように設定します

  params.require(:user).permit(・・・略・・・)

・ キーの設定元

 View上でform_forメソッドを使用した場合のキー設定箇所を見てみましょう。下図の例ではform_forに続く”@user"がrequireメソッドで指定すべきキーです。
 なお、form_forメソッドは、モデルに基づくformを作成する際に使うヘルパーメソッドで"@user"が対象のモデル名をさしています。

new.html.erb
・・・中略・・・
<%= form_for @user do |f| %>
  <%= f.text_field :name, placeholder: "ユーザ名を入力してね!" %>
  <%= f.submit %>
<% end %>
・・・中略・・・

4-3.permitメソッド

 permitメソッドを使用する事で、許可された値のみを取得することができます。
 そのため、permitメソッドの引数には登録を許可する全てのカラム名を指定しておく必要があります。もし、許可されいないカラムがparams内に存在した場合、そのデータは取得されず無視されます。

例)Userモデルに存在するnameおよび、emailカラムのみ入力を受け付けたい場合
 (他のカラム(admin)は受け付けないたくない)

  params.require(:user).permit(:name,:email)

※ユーザの入力項目を増やした場合は、ストロングパラメータへの項目追加も忘れずに!
 もし、忘れたら・・・何と明示的にエラーとなりません!(エラーキャッチとかしてるとなるのかな?)
 必須項目だった場合、DB自体に保存されませんし、必須項目でない場合も、該当データのみDBに反映されない歯抜けの状態になっちゃいます。

4-4.mergeメソッド

mergeメソッドを使用することでハッシュ同士を結合することができます。
例えば、paramsに含まれない値をストロングメソッドに加えたい場合などに、ストロングパラメータの後に記述することができます。

  params.require(:user).permit(:name,:email).merge(user_id: current_user.id)

4-5.privateメソッド

 privateメソッド配下に記述したメソッドは、クラス外からのアクセスができません。
 基本的にストロングパラメータは、クラス外からのアクセスをさせないようにprivateメソッド配下に書くようです。

5.ストロングパラメータの呼び出し方

 折角定義したストロングパラメータのメソッドですが、呼び出して使わないと意味がありません。どのように呼び出すか、記述例を見てみましょう。
 なお、呼び出し方には大きく2パターンあります。(もっとあるかもしれませんが・・・)

5-1. 丸投げパターン

 このパターンが多いと思います。ストロングパラメータに全てお任せパターンです。

user_controller.rb
  def create
    User.create(user_params)
  end

5-2. 部分投げパターン

 params以外のデータを含む場合などに使うようです。
 ただ、このパターンでは、そもそもマスアサインメント機能を使っていません。そのため、ストロングパラメータ自体不要ですね。実際にストロングパラメータを使わなくても登録が可能です。
 なお、params以外のデータもストロングパラメータとして指定できるので、丸投げパターンで呼び出すことも可能です。(4-4.margeメソッド参照)

post_controller.rb
  def create
    Post.create(image: post_params[:image], text: post_params[:text],user_id: current_id)  
  end

まとめ

 ストロングパラメータは、マスアサインメント機能の脆弱性問題を回避するために作られた機能。
 そのため、対象はparamsを使用したデータの登録、更新のみ。
 さらに言うと、paramsを使っていたとしても、1カラムずつ定義する場合は、ストロングパラメータの利用が必須ではない!

これでストロングパラメータと少しは仲良くなれたかな。。。

参考

・Ruby on Rails5 アプリケーションプログラミング 山田祥寛 (参考書)
https://kirohi.com/strong_parameters_rails
https://diveintocode.jp/tips/strong_parameter

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

「Gemfile」は「gemfile」じゃだめだよ!

タイトル通り、Gemfileの「G」は大文字じゃないとデプロイの際、エラーになります。
僕は「gemfile」のファイル名のままデプロイして12時間ハマりました。
僕みたいにハマる方がこれ以上生まれないよう、念のため共有しておきます。

ふと新しいwebアプリケーションを作りたいと思い、以下の記事にしたがってrails newしました。
https://qiita.com/yuitnnn/items/b45bba658d86eabdbb26

ただ、以下の記事で説明されているように、gemを--path vendor/bundle配下で管理する必要は別にないかもしれないです。
https://qiita.com/jnchito/items/99b1dbea1767a5095d85

原因はわかりませんがrails newして新しいrailsアプリを作成した時になぜかファイル名が「Gemfile」ではなく「gemfile」(先頭の文字が小文字のg)になっていました。
多分、自分が何かおかしいことをやったんだと思います。

その状態でリモートリポジトリにpush、Herokuにデプロイして、https://×××××.herokuapp.comにアクセスするとエラーが発生しています。

heroku-error.png

heroku使っている人なら一度は見たことがあるであろうおなじみの画面ですよね!
そこで試しにheroku run rails cをしてみると

Traceback (most recent call last):
    4: from /app/bin/rails:3:in `<main>'
    3: from /app/bin/rails:3:in `load'
    2: from /app/bin/spring:8:in `<top (required)>'
    1: from /usr/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'
/usr/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require': cannot load such file -- bundler (LoadError)

bundlerがないと怒られてます。

HerokuダッシュボードのSettingsを見てみると

mosaic.jpg

RailsアプリをデプロイしたのにFrameworkがNode.jsになっています。
HerokuのBuildpacksを見てみると、RailsでWebpackerを利用するために必要なNode.jsのbuildpackしか使われていないことが分かります。

スクリーンショット 2019-06-25 16.31.27.jpg

肝心なrubyのbuildpackがないため、rubyのbuildpackを追加してもう一度Herokuへデプロイしてみました。

remote: -----> Build succeeded!
remote:  !     This app may not specify any way to start a node process
remote:        https://devcenter.heroku.com/articles/nodejs-support#default-web-process-type
remote: 
remote: -----> App not compatible with buildpack: https://buildpack-registry.s3.amazonaws.com/buildpacks/heroku/ruby.tgz
remote:        More info: https://devcenter.heroku.com/articles/buildpacks#detection-failure
remote: 
remote:  !     Push failed
remote: Verifying deploy...
remote: 
remote: !   Push rejected to ×××××××××××××.
remote: 
To https://git.heroku.com/×××××××××××××.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'https://git.heroku.com/×××××××××××××.git'

なぜかデプロイできない!!!

ここからが長かった。。。
buildpackについて調べたり、bundlerについて調べたり色々したけど、
結局、原因は「Gemfile」のファイル名が「gemfile」と先頭の「g」がなぜか小文字になっていることが原因でした。

https://devcenter.heroku.com/articles/buildpacks#detection-failure
上記URL先に「アプリケーションのルートフォルダにGemfileがないといけない」と記載されていますが、まさか小文字の「gemfile」だと正しく読み込んでくれないとは気づかなかったです。ってかなんで小文字の「gemfile」になってたんだ...

どこかの記事に「エラーで長時間詰まるときは大体単純なミスが多い」と書いてあったのを読んだことがありますが、今回はまさにそれでした。

今回みたいになぜかファイル名が小文字で「gemfile」となっているようなことが他の方に起こることはほぼないと思いますが、一応投稿して共有させて頂きました。

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

【Rails】html.erbでの記述

画像

railsで扱う画像は [app-assets-images] がルートとなる

<%= image_tag 'icon/hogehoge.png' %>

テキストリンク

<%= link_to "リンク名","リンクパス" %>

<%= link_to "ホーム",root_path %>

画像リンク

<%= link_to image_tag('shop_images/hogehoge.png'),'/areas/hogehoge' %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】i18nを利用して、enumのf.selectオプションを日本語化する[Rails]

こんにちは、エンジニアとして就職を目指しています、タヌキです。
前回の記事では、haml, form_withを利用して、f.selectの入力フォームを作るために色々と試行錯誤した話、
さらに、enumを利用してデータを利用しやすくした話を書きました。

▼前回の記事はこちら
【初心者向け】form_with, haml, enumを使ってselectによるプルダウンリストを作った話[Rails]
https://qiita.com/tanutanu/items/1bb5f12ac8ae90e71352

その中で、f.selectの入力フォームは実装できたのですが、
最後に選択肢が英語になってしまうという課題が残りました。

そのため、今回はenum利用時に、f.selectの選択肢を日本語にするための方法をご紹介したいと思います。
合わせて、日本語化したデータをビューなど他の場所でも使うための方法もご紹介いたします。
どうぞよろしくお願いいたします。

今回参考にした記事

まず、今回参考にさせていただいた記事はこちらです。
参考というよりも、こちらの記事がとても良すぎて、ほとんどこのままの内容で実装できましたので、
本記事の内容も下記の記事とほぼ同じです。

ページ下方の、selectオプション以外への使用方法の項だけ、内容が異なります。
自分自身のまとめのために、やったことを記しているので、
selectオプションへの使用方法だけが知りたい!という方は下記の記事を参照された方が良いと思います。

▼参考にした記事はこちら
https://qiita.com/tomoharutsutsumi/items/272a10f4fefb555944f2

必要なファイル

enumを日本語化する上で必要だったファイルは下記の通りです。

  • (gem) enum_help, rails-i18n
  • model ←今回は restaurant.rb
  • ja.yml
  • application.rb

その他、enumを日本語化して記載したいビューファイルです。

gem ファイルのインストール

まずは、enumをI18n(国際化)対応させるgem
enum_help をインストールします。

rails-i18nの方はまだしっかり言語化できていないのですが、
i18nの機能が使いやすくなるそうです。

Gemfile
gem 'rails-i18n'
gem 'enum_help'

bundle lnstall します。

model に enumを記載する

次に、modelにenumを記載します。
今回は、レストラン情報を載せるrestaurantsテーブルの、昼の予算のカラム budget_d に対して、下記のようにenumを記載しました。

models/restaurant.rb
enum budget_d: {
    default: 0,
    till_1000: 1,
    till_2000: 2,
    till_3000: 3,
    till_4000: 4,
    till_5000: 5,
    over_5000: 6
  },  _prefix: true

最後の prefix: true は、同じ値をもつ複数のenumが存在するときにつけるものです。
今回は、夜の予算を定義する budget
n も同じアプリ内に存在していたので、 _prefix:true をつけました。

▼詳しくは、こちらをご覧ください。
https://qiita.com/emacs_hhkb/items/fce19f443e5770ad2e13

ja.ymlに翻訳情報を記載する

翻訳情報を記したファイル、ja.ymlを
config/locales/ 内に作成し、下記のように記します。

config/locales/ja.yml
ja:
  enums:
    restaurant:
      budget_d:
        default: "--"
        till_1000: "~¥999"
        till_2000: "¥1,000~¥1,999"
        till_3000: "¥2,000~¥2,999"
        till_4000: "¥3,000~¥3,999"
        till_5000: "¥4,000~¥4,999"
        over_5000: "¥5,000~"

上記は、enumのデータを翻訳したい時の記載方法ですので、その他の場所を翻訳したいときには、別の記載方法となります。

▼詳しくは、こちらの記事をご覧ください。
https://qiita.com/shi-ma-da/items/7e5c3d75c9a9f51abdd5

デフォルトの言語を日本語化する

application.rb の設定を変更して、デフォルトの言語を日本語にします。

config/application.rb
 #  前略

module SomeApp
  class Application < Rails::Application

    # 中略

    config.i18n.default_locale = :ja # デフォルトのlocaleを日本語(:ja)にする

 end
end

パスを通して、i18nのロケールファイルが読み込まれるようにする。

以下の記述も application.rb に追記して、locales フォルダ内のファイルが全て読み込まれるようにします。

config/application.rb
#  前略

module SomeApp
  class Application < Rails::Application

    # 中略

    config.i18n.default_locale = :ja
 config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # 追記

 end
end

これで、enumを日本語化するための設定は完了です!
設定だけでかなり長かったです・・・。

最終的なコード

そして、今回 f.select に使用したコードは下記のようになりました。

viewfile
= f.select :budget_d, Restaurant.budget_ds_i18n.keys.map{|k| [I18n.t("enums.restaurant.budget_d.#{k}"), k]}

まず、budget_d の中身を含む配列、budget_ds に対し、
keys メソッドを実施し、[["default": "--"], ["till_1000": "~¥999"], ...] などのキーだけ(["default", "till_1000"])を配列の形で取り出します。

そして、その値一つ一つに対して、mapを使い処理をしています。

処理の内容は、
I18n.t ... enumの内容を翻訳するメソッド
を使って、["翻訳した内容":"value"]の配列を作る処理です。

enums.restaurant.budget_d のように、
ja.ymlに書いたenumの翻訳情報の位置をきちんと記します。

結果

その結果、できたドロップダウンリストがこちら。

Image from Gyazo

生成されたコードがこちらです。

Image from Gyazo

valueがdefaultやtill_1000などになっていますが^^;これで正しくvalueはデータベースに保存されます。

その他の場所で、翻訳したデータを使う。

最後に、ビューのその他の場所で翻訳したファイルを使う方法をご紹介します。
基本的には、「カラム名_i18n」をつけた表記にすればokです。

view
%p= restaurant.budget_d_i18n #これで、昼の予算が日本語で表示される

enumの日本語化といい、f.selectの表記といい、なかなか時間のかかった実装でした。
それでは、ここまで読んでくださり、ありがとうございました。

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

Railsでのsqliteの基本操作

超基本操作

--コンソールの起動--

$ rails dbconsole

--テーブルの確認--

sqlite> .tables

--スキーマの確認--

sqlite> .schema

テーブルのカラムの追加・削除

rails g migrationでファイルを作成して、rails db:migration でDBに反映させる流れ

--カラムの追加--

rails g migration[Addカラム名Toテーブル名] [カラム名:型]
※Addの後のカラム名とテーブル名の表記に注意
※カラム名にしてはいけないワード[img]

$ rails g migration AddSurlToLists surl:string
$ rake db:migrate

---カラムの削除--

$ rails g migration RemoveAreaFromLists area:int
$ rake db:migrate
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ローカルでpostgreSQLに接続できなくなった時(備忘録)

エラー内容

could not connect to server: Connection refused Is the server running on host "localhost" (::1) and accepting TCP/IP connections on port 5432? could not connect to server: Connection refused Is the server running on host "localhost" (127.0.0.1) and accepting TCP/IP connections on port 5432?

$ brew uninstall postgresql
$ rm -fr /usr/local/var/postgres/
$ brew install postgresql

もちろん、ローカルのpostgreSQLデータ吹っ飛ぶので注意。

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

rspecの導入+カバレッジを出力する

概要

Ruby on Railsの開発で単体試験、カバレッジを測りたいという時のための手順
すでにRailsが起動してるよ!という状態からスタート

環境

Ruby on Rails自体はDockerの上で起動(Dockerじゃなくても同じように動くはず)
* Ruby : 2.6
* Rails : 5.2.3
* rspec-rails : 3.8.2
* simplecov : 0.16.1

やったこと

Gemfileの編集

Gemfileにrspec-railssimplecovを追加する
test`モードのときにのみインストールするように設定

Gemfile
group :test do
  gem 'rspec-rails'
  gem 'simplecov'
end

rspecをインストールする

gemをダウンロードする(今回はdocker-compose使ってます)

docker-compose build

Docker使ってないとbundle installかな

bundle install

rspecをインストール(generate)する

rails generate rspec:install

こんなファイル構成が生まれてばOK

.spec
spec/
spec/spec_helper.rb
spec/rails_helper.rb

設定を書き換える

Railsのモード指定をする

spec_helper.rb
RSpec.configure do |config|
  ENV['RAILS_ENV'] = 'test'

  ()
end

カバレッジレポート作成を設定する

spec_helper.rb
require 'simplecov'

RSpec.configure do |config|
  (略)

  if ENV['CIRCLE_ARTIFACTS']
    dir = File.join(ENV['CIRCLE_ARTIFACTS'], 'coverage')
    SimpleCov.coverage_dir(dir)
  end

  SimpleCov.start
end

テストクラス作成

テスト対象のクラスを作る
本来はrails generate model item ・・・というようにrails generateしたときにテストクラス(〜_spec.rb)が作られる
けど、すでに存在するクラスに対しては作られないので主導で作成
以下のようなファイル構成で作る(例はitemというモデルがあった場合)

spec/models/item_spec.rb

ファイルの中身はこんな感じ

item_spec.rb
require 'rails_helper'

RSpec.describe Item, type: :model do
  # describeはテストの大きな枠で、クラスが持つ機能(メソッド)ごとに作るといいかも
  # 文字列のところは任意で決められ、表すものを書けばOK
  describe 'aaa test' do

    # itはテストケース単位
    # 文字列のところは任意で決められ、表すものを書けばOK
    it 'bbb test' do

      # テスト内容を記載
    end
  end
end

試験データを作成するときはfactory_bot_railsを使うと良い
(今回は説明しません)

テスト実行

テスト実行は1コマンド

rspec

# テストケースを指定する場合は引数でファイル名を渡す(対象ファイルが多いと時間がかかるため、開発中は限定したい)
rspec spec/models/item_spec.rb

結果がベローって出て、全部緑ならOK
最後にカバレッジが出るので、試験結果とカバレッジ結果を両方確認できる

Finished in 0.03117 seconds (files took 3.09 seconds to load)
1 example, 0 failures

Coverage report generated for RSpec to /sample_app/coverage. 187 / 194 LOC (96.39%) covered.

カバレッジ結果はcaverage/index.htmlに出力されるので、通過していない箇所をブラウザで確認できる

注意点

specファイルが書かれていないクラスはカバレッジ計測されません
(他のテストケースを実行したときに呼ばれれば計測される)
全テスト対象クラスを最初から用意しておくことを激しくお勧めします
テスト結果が正しくないまま「カバレッジが高いぞ!」ってはしゃぐことになります

応用編

手動で実行すると忘れるものです
なので、リポジトリにpushしたときにテストが実行されるよう自動化をお勧めします
(github → circleCIとかgitlabのスクリプトで十分効果が発揮できる)
テスト結果をslackなどのコミュニケーションツールに投げ込むと失敗に気づけると思います
考えたら色々きりがないので、色々な環境で試してみてください。

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

MacOS(Mojave)でrailsをinstallしようとしてコケた

概要

MacにはデフォルトでRubyが入っているので早速railsを入れようとsudo gem install railsしたらコケた。
概ねこの記事通りにやったら解決した。

Failed to build gem native extension.

実行すると以下のようなエラーが発生。

ERROR:  Error installing rails:
        ERROR: Failed to build gem native extension.

    current directory: /Library/Ruby/Gems/2.3.0/gems/nokogiri-1.10.3/ext/nokogiri
/System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/bin/ruby -r ./siteconf20190625-38062-z2jdpr.rb extconf.rb
mkmf.rb can't find header files for ruby at /System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/include/ruby.h

extconf failed, exit code 1

Gem files will remain installed in /Library/Ruby/Gems/2.3.0/gems/nokogiri-1.10.3 for inspection.
Results logged to /Library/Ruby/Gems/2.3.0/extensions/universal-darwin-18/2.3.0/nokogiri-1.10.3/gem_make.out

xcode-select --installで治るという情報もあったが、こちらは既にインストールしてあったので、解決策にはならなそう。
MacOSのMojaveにはmacOS SDK Headerが入っていないのがエラーの原因らしいので、インストールする。

$ sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target /

the package path specified was invalid

実行すると以下のようなエラーが発生。

installer: Error - the package path specified was invalid:
 '/Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg'.

仕方ないので直接ダウンロードする。
AppleDeveloperからCommand Line Tools (macOS 10.14)for Xcode 10.2.1を検索。ダウンロードファイルを開いてそのままインストールを進めると/Library/Developer/CommandLineTools/Packages/にインストールされる。
AppleIDで認証する必要があるため少し面倒。

まとめ

無事sudo gem install railsでrailsをインストールできた。
Mojave以前のソフトウェアアップデートでも同じような現象が起きたらしいので、以後のアップデートで同じ現象が起きた時用に。

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

Rails6 のちょい足しな新機能を試す41(MailDeliveryJob 編)

はじめに

Rails 6 に追加されそうな新機能を試す第41段。 今回は、 MailDeliveryJob 編です。
Rails 6 では、 ActionMailer::DeliveryJob を使うと DEPRECATION WARNING が表示されるようになります。
代わりに ActionMailer::MailDeliveryJob が用意されています。

Ruby 2.6.3, Rails 6.0.0.rc1 で確認しました。Rails 6.0.0.rc1 は gem install rails --prerelease でインストールできます。

$ rails --version
Rails 6.0.0.rc1
  1. parameterized mail に対応するために、 ActionMailer::Parameterized::DeliveryJob が導入される。
  2. ActionMailer::DeliveryJobActionMailer::Parameterized::DeliveryJob の2つがあるのはややこしいから、1つのクラス ActionMailer::MailDeliveryJob に統合しよう。
  3. クラスが変わってしまうから、 ActionMailer::DeliveryJobActionMailer::Parameterized::DeliveryJob を使った場合に DEPRECATION WARNING を表示しよう。

という流れだったみたいです。

今回の準備

今回は、 Rails6 のちょい足しな新機能を試す27(perform_deliveries 編) のソースに手を加えていくことにより動作確認します。

MyMailDeliveryJob を作る

メール送信のジョブを差し変えるため、 MyMailDeliveryJob クラスを作ります。
このとき派生元のクラスを DEPRECATION WARNING を出すために、意図的に ActionMailer::DeliveryJob にします。

app/jobs/my_mail_delivery_job.rb
class MyMailDeliveryJob < ActionMailer::DeliveryJob
  before_perform :logger_info

  def logger_info
    Rails.logger.info('BEFORE MyMailDeliveryJob perform')
  end
end

MyMailDeliveryJob を使う

MyMailDeliveryJob を使うように UserMailer を修正します。

app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
  self.delivery_job = MyMailDeliveryJob # この行を追加
  ...
end

ユーザーを登録する

ブラウザから User を登録してメールを送信します。

development.log に DEPRECATION WARNING が表示されます。また、 MyMailDeliveryJob を設定したにも関わらず、 ActionMailer::Parameterized::DeliveryJob が動作していることにも注意してください。

log/development.log
...
[ActiveJob] [ActionMailer::Parameterized::DeliveryJob] [...] DEPRECATION WARNING: Sending mail with DeliveryJob and Parameterized::DeliveryJob is deprecated and will be removed in Rails 6.1. Please use MailDeliveryJob instead. (called from instance_exec at /usr/local/bundle/gems/activesupport-6.0.0.rc1/lib/active_support/callbacks.rb:429)
...

MyMailDeliveryJob クラスの親クラスを変更する

MyMailDeliveryJob の親クラスを ActionMailer::MailDeliveryJob に変更します。

app/job/my_mail_delivery_job.rb
class MyMailDeliveryJob < ActionMailer::MailDeliveryJob
  ...
end

再度ユーザーを登録する

ブラウザから User を登録してメールを送信します。

今度は、 DEPRECATION WARNING も消えて、 MyMailDeliveryJob が動作していることがわかります。

log/development.log
...
[ActiveJob] [MyMailDeliveryJob] [...] Performing MyMailDeliveryJob ...
[ActiveJob] [MyMailDeliveryJob] [...] BEFORE MyMailDeliveryJob perform
...

結論

ActionMailer::DeliveryJobActionMailer::Parameterized::DeliveryJob を使っている場合や独自のメール送信ジョブのクラスを作っている場合は、 ActionMailer::MailDeliveryJob から派生させたメール送信ジョブのクラスに変更するのが良いでしょう。

試したソース

試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try041_mail_delivery_job

参考情報

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

マイグレーションについて

前職で、「マイグレーションについて調べてみて。」と言われたけど、結局何を調べて答えたのか忘れてメモもしてなくて。改めて調べてみました。

 ↑キーワード検索から、いろんな人の記事を漁るのも良い方法かもしれません。

Migration とは何者か

【現時点での理解】
  複数人で開発していて、データベースが勝手に書き換えられるような環境での開発で、
 全員がおんなじ状態のDBを使えるようにする仕組み。

 ソースコードに対するバージョンコントローラの Git 、
 データベースに対するバージョンコントローラの Migration

 みたいなものでしょうか。
 ※筆者は主に PHP/Laravel を書きます。

 teratailでの質問

PHP/Laravel の場合

 Laravelドキュメント:マイグレーション
 bz0さんの記事

Ruby on Rails の場合

 sntkazuさんの記事
 wacker8818さんの記事

Python/Django の場合

 okoppe8さんの記事

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

Rails production環境で Mysql2::Error: Disk full が出たときの対処

エラー内容

ある日、production環境で以下のようなDisk fullエラーが出ました。

Mysql2::Error: Disk full (/tmp/#sql_253_1.MAI); waiting for someone to free some space... (errno: 28 "No space left on device"):

ログがたまりすぎてディスク容量がいっぱいエラーなので、以下のコマンド実行で解決。

解決法

bundle exec rails log:clear

ログローテション設定しないと。。

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

simplecovでデフォルトのタブを消す方法

結論

spec/rails_helper.rbにgroups.clearを記述して1つづつ再定義しよう

環境

Ruby 2.3.7
Rails 4.2.5
simplecov 0.16.1
rspec-rails 3.4.2

やりたい事

simplecovのデフォルトグルーピングを削除したい。
(うちはMailers、Jobsとか殆ど使ってないんで無くしたい。)

これを

image.png

こうしたい

image.png

やり方

以下のファイルを編集しよう

spec/rails_helper.rb

  SimpleCov.start :rails do
    groups.clear # 一旦全定義をclear
    # グルーピングしたい定義を1つづつ再定義
    add_group 'Controllers', 'app/controllers'
    add_group 'Models', 'app/models'
    add_group 'Helpers', 'app/helpers'
    add_group 'Libraries', 'lib/'
    # 集計除外したい場合はfilterにぶち込もう
    add_filter %w[app/services app/jobs]
  end

デフォルトのタブは以下で定義されている。(これは編集しない)

.bundle/ruby/2.3.0/gems/simplecov-0.16.1/lib/simplecov/profiles/rails.rb

# frozen_string_literal: true
[SimpleCov.profiles.define "rails" do
  load_profile "test_frameworks"

  add_filter %r{^/config/}
  add_filter %r{^/db/}

  add_group "Controllers", "app/controllers"
  add_group "Channels", "app/channels" if defined?(ActionCable)
  add_group "Models", "app/models"
  add_group "Mailers", "app/mailers"
  add_group "Helpers", "app/helpers"
  add_group "Jobs", %w[app/jobs app/workers]
  add_group "Libraries", "lib/"

  track_files "{app,lib}/**/*.rb"
end](url)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

マイクロサービスでは環境によって変わる変数は環境変数を直接書いた方が良いと思ったという話

About

タイトルだけを見ると何を当たり前の事をと思われると思いますが
Ruby on Railsではdevelopmentやproductionでの変数を切り替える方法がいくつかあります。

プログラム内で分岐する

def switch_var
  case Rails.env
  when 'production' then 'var of production'
  when 'development' then 'var of development'
  else 'default var'
  end
end

こんなコードを書いている人もいると思います。
私も昔書いていましたが、何度か変数の変更が入ると変更すべきコードを探すのが億劫になります。

dotenv-rails

https://github.com/bkeepers/dotenv
.envファイルを読み込む事で各環境での変数を定義します。
ただ、.envはgitへのコミット非推奨な割に
dotenvとrailsが密になってしまいstaging環境やproduction環境を構築する際に窮屈さを感じました。
ただ、ローカル環境においてはdocker-composeと.envを組み合わせるという方法はアリだと思います。

config設定用のgemを利用する

他にも色々ありますが、yamlでconfigを管理する様なgemがあります。
それなりに便利なので一時期使っていましたが、
結局gemに依存するとメンテナンスコストもかかってしまうので
特にマイクロサービスレベルでの環境変数程度で有ればgemは不要だと私は思いました。

環境変数をアプリケーションに渡す

test, development環境

Docker前提で作っているのでdocker-composeで渡します。
CI上でもdocker-compose.ymlを専用に作っておいてそちらを利用しています。

version: '3'
services:
  web:
    environment:
      - RAILS_ENV
      - RACK_ENV
      - AWS_BUCKET_NAME=xxxx
      - AWS_ACCESS_KEY_ID=xxxx
      - AWS_SECRET_ACCESS_KEY=xxxx

何も書かないとhostの環境変数が使われます。

production環境

マイクロサービスをTerraformで定義しています。
ECSのタスク定義で渡します。

ecs.tf
resource "aws_ecs_task_definition" "task" {
  container_definitions = data.template_file.service_container_definition.rendered
}

data "template_file" "service_container_definition" {
  template = file("./templates/container_definitions.json.tpl")
}
container_definitions.json.tpl
[
  {
    "environment": [
      {
        "name": "RAILS_ENV",
        "value": "production"
      },
      {
        "name": "RACK_ENV",
        "value": "production"
      },
      {
        "name": "AWS_BUCKET_NAME",
        "value": "xxxx"
      },
      {
        "name": "AWS_ACCESS_KEY_ID",
        "value": "xxxx"
      },
      {
        "name": "AWS_SECRET_ACCESS_KEY",
        "value": "xxxx"
      }
    ]
  }
]

実際にはアプリケーションケーションの規模によって
最適なツールが有るのかも知れませんが
マイクロサービスにおいてはこういう形で渡した方が見通しも良くなっていると思いました。

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

なぜかActiveRecord::RecordNotUniqueで怒られる

状況

rails初心者です
railsでdeviseを使ってログイン機能を作った際に新規登録のUIを変更し実行した際に
ActiveRecord::RecordNotUniqueで怒られた。

原因

主な原因はテーブルのindexにありそうでした。

schema.rb
#~~省略
t.index ["username"], name: "index_users_on_username", unique: true
t.index ["nil"], :name "index_users_on_name", unique: true

このようにnilに対してのインデックスがなぜに出来たのかもどのような効果があるのかもはっきりとは分かっていませんが用意されていました。

解決策

直前に'username'というカラムを追加していました。
そこで一旦

$ rails db:rollback

テーブルを1つ前の状態に戻し

$ rails db:migrate

migrateし直した結果見事にnilに対してのindexは削除されていました!

終わりに

nilに対してのindexをremove_indexで消す方法もありなのかなと思いましたがあんまりやり方が分からず今回のやり方でうまくいったのでよしとしました。
どうしてnilに対してのindexが作られていたのかあんまり分かっていないのでどなたか詳しい方教えていただけたら嬉しいです。

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

ローカルでrails sが止まらない!

ユースケース

railsのゾンビプロセスを発生させる手法

  • tmux, terminalを閉じてしまった場合
  • railsをバックグラウンドで実行する方法

railsのバックグラウンド実行

$ rails server -d
  • process idを消す
$ rm ./tmp/pids/server.pid

サーバーを止める方法

process id(以下pid)を消す必要があります。
消し方としては以下の3つの方法があります。

  • ポートからpidを探す
  • tmpのserver.pidからpidを探す
  • pumaのprocessからpidを探す

ポートからpidを探す

$ lsof -i:3000
COMMAND   PID  USER   FD   TYPE            DEVICE SIZE/OFF NODE NAME
ruby    31584 _user_name_   10u  IPv4 0xac1c1f275d70c41      0t0  TCP localhost:hbci (LISTEN)
ruby    31584 _user_name_   11u  IPv6 0xac1c1f26fce7e19      0t0  TCP localhost:hbci (LISTEN)
$ kill -kill 31584

tmpのserver.pidからpidを探す

$ ls tmp/pids/server.pid
tmp/pids/server.pid
$ cat tmp/pids/server.pid
31584
$ kill -kill 31584

pumaのprocessからpidを探す

$ ps aux | grep puma
_user_name_            32578   0.0  0.0 xxx xxx s000  S+   xx:xxAM   0:00.00 grep puma
_user_name_            31584   0.0  0.8 xxx xxx s005  S+   xx:xxAM   0:02.52 puma 3.12.1 (tcp://localhost:3000) [project_name]
$ kill -kill 31584

参考文献

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

スクレイピング

用途

ウェブサイト上のHTMLからデータを抜き出す処理

使用例

例えばこのようなHTMLのサイトがあったとして

<ul>
    <li>TEST1</li>
    <li>TEST2</li>
    <li>TEST3</li>
  </ul>

TEST1
TEST2
TEST3
の値を取り出す事ができる

必要なGem

Mechanize

Mechanizeクラスが使えるようになる

Gemfileの最後の行に以下のコードを記述する

Gemfile.
gem 'mechanize'

Gemfileに記述されたgemをインストールする

$ bundle install

Mechanizeクラスのインスタンスを生成

スクレイピングするにはまず、Mechanizeクラスのインスタンスを生成する

例.
agent = Mechanize.new
#Mechanizeクラスのインスタンスを生成して、agentへ代入

webサイトのHTML情報を取得する

getメソッド

getメソッドはMechanizeクラスのインスタンスメソッド
get(スクレイピングしたいウェブサイトのURL)

例.
agent = Mechanize.new
page  = get("https://qiita.com/")
#QiitaのHTMLを取得

HTMLの文字列ではなく、ウェブサイトのHTMLの情報を持ったMechanize::Pageオブジェクトを取得
※オブジェクト
関連する変数(値)とメソッド(動作)をまとめて、そのまとまりに名前を付けたもの

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

6/24 progate rails メモ

progate 次回6-10からスタート

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

【Rails】gretelを使ってパンくずリストを作成

gretelの使い方

某ECサイトのコピーを作成する際に、パンくずリストの実装をしました。
いくつかgemがありましたが、コントローラを使用せずに設定ファイルとビューに記述するだけで実装できるgretelを選択しました。
あと名前が可愛い(←

パンくずリストとは

下記の画像をご覧下さい。

スクリーンショット 2019-06-24 23.47.13.png

よくサイトで見るやつですね!
今回はこれを実装していきます。

① gretelの導入

GitHub
https://github.com/lassebunk/gretel

公式
https://www.rubydoc.info/gems/gretel

② gemのinstall

gem "gretel"
$ bundle install

③ ファイルの設定

設定ファイルを下記コマンドで作成

$ rails generate gretel:install

すると下記のファイルが生成されます。

config/breadcrumbs.rb
crumb :root do
  link "Home", root_path
end

# crumb :projects do
#   link "Projects", projects_path
# end

# crumb :project do |project|
#   link project.name, project_path(project)
#   parent :projects
# end

# crumb :project_issues do |project|
#   link "Issues", project_issues_path(project)
#   parent :project, project
# end

# crumb :issue do |issue|
#   link issue.title, issue_path(issue)
#   parent :project_issues, issue.project
# end

# If you want to split your breadcrumbs configuration over multiple files, you
# can create a folder named `config/breadcrumbs` and put your configuration
# files there. All *.rb files (e.g. `frontend.rb` or `products.rb`) in that
# folder are loaded and reloaded automatically when you change them, just like
# this file (`config/breadcrumbs.rb`).


まずはマイページをパンくずリストに記載したいので、下記のように追記します。

config/breadcrumbs.rb
# ルート
crumb :root do
  link "トップページ", root_path
end

# マイページ
crumb :mypage do
  link "マイページ", mypage_users_path
end

・ :mypage ← 設定ファイルを呼び出します。
・ "マイページ" ← パンくずリストに表示される名称です。
・ mypage_users_path ← 呼び出し元のパスを指定します。

④ ビューの設定

mypage.html.haml
-# config/breadcrumbs.rbに定義したmypageを呼び出し
- breadcrumb :mypage
-# 下記を記述した箇所にパンくずリストが表示される。
= breadcrumbs pretext: "You are here:",separator: " &rsaquo; "

・ pretext ← パンくずリストの先頭に挿入する文章を記述。いらない場合は設定しなくて大丈夫です。
・ separator ← パンくずの区切り文字を指定。「&rsaquo」は出力されると「›」になります。

パンくずリストの表示は、一般的に全ページに表示できるlayouts/application.html.hamlに記載することが多いかと思いますが、今回は一部のページでのみ表示させたいのと、改修を容易にする為、下記のように部分テンプレート化しています。

layouts/_breadcrumbs.html.haml
.breadcrumbs
  = breadcrumbs pretext: "",separator: " &rsaquo; ", class: "breadcrumbs-list"



実際のリスト呼び出しと表示結果はこのようになっています。

mypage.html.haml
- breadcrumb :mypage
= render "layouts/breadcrumbs"

スクリーンショット 2019-06-25 0.49.29.png

⑤ 親の設定

config/breadcrumb.rbのcrumbとendの間にparentを設定することで親を設定することができます。
マイページを親に設定してプロフィールをリストに表示させましょう。

config/breadcrumbs.rb
# ルート
crumb :root do
  link "トップページ", root_path
end

# マイページ
crumb :mypage do
  link "マイページ", mypage_users_path
end

# プロフィール
crumb :profile do
  link "プロフィール", edit_user_path
  parent :mypage
end

すると以下のように表示されます。

スクリーンショット 2019-06-25 0.58.32.png


今回参考にさせていただいた記事
https://qiita.com/you8/items/d2d37a745060b79c112f
https://qiita.com/namitop/items/5bcb3b90e63af758a9b0
http://vdeep.net/rubyonrails-gretel
https://doruby.jp/users/kisuzuki/entries/gretel%E3%81%A7%E3%83%91%E3%83%B3%E3%81%8F%E3%81%9A%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BD%9C%E6%88%90

以上がgretelを使用したパンくずリストの実装となります。

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

自作アプリのUserモデルにテストコード(RSpec)を書いてみた

はじめに

Railsでよく見るバリデーションのテストをRSpecで書きました。まだまだ勉強中なので、問題点があればご指摘ください。

テスト内容

今回は、name, user_id, email を持つUserモデルを作成しました。

マイグレーションファイル
class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :name
      t.string :user_id
      t.string :email

      t.timestamps
    end
  end
end

Userモデルのテストでは、主にバリデーションのテストを行いました。

app/models/user.rb
class User < ApplicationRecord
  before_save { email.downcase! }
  validates :name, presence: true,
                   length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true,
                    length: { maximum: 255 },
                    uniqueness: { case_sensitive: false },
                    format: { with: VALID_EMAIL_REGEX }
end

バリデーションの設定に関しては、Railsチュートリアルを参考にしています。バリデーションの設定や意味などは、そちらを参考に。

環境設定

Gemfile に以下のように追記します。

group :development, :test do
  # 省略
  gem 'factory_bot_rails'
  gem 'rspec-rails'
  # 省略
end

そうすると、Userモデル作成と同時にRSpecのファイルが生成されます。

$ rails g model User ...

Running via Spring preloader in process 19654
      invoke  active_record
      create    db/migrate/20190617xxxxxx_create_userss.rb
      create    app/models/user.rb
      invoke    rspec
      create      spec/models/user_spec.rb

なので、テストは以下に書き込めばいいと。

spec/models/user_spec.rb

テスト作成

以下が、Userモデルに対するすテスト(RSpec)

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  before do
    @user = User.create(
      name:    "Yamada Taro",
      user_id: "taro",
      email:   "taro@example.com",
    )
  end

  # name、user_id、emailがあれば有効な状態であること
  it "is valid with a name, user_id, email" do
    user = User.new(
      name:    "Sato Taro",
      user_id: "sato",
      email:   "sato@example.com",
    )
    expect(user).to be_valid
  end

  # nameがなければ無効な状態であること(nameは必須項目)
  it "is invalid without a name" do
    user = User.new(name: nil)
    user.valid?
    expect(user.errors[:name]).to include("can't be blank")
  end

  # メールアドレスがなければ無効な状態であること(emailは必須項目)
  it "is invalid without an email address" do
    user = User.new(email: nil)
    user.valid?
    expect(user.errors[:email]).to include("can't be blank")
  end

  # 重複したメールアドレスなら無効な状態であること
  it "is invalid with a duplicate email address" do
    user = User.new(email: "taro@example.com")
    user.valid?
    expect(user.errors[:email]).to include("has already been taken")
  end

  # メールアドレスが小文字化されていること
  it "email addresses should be saved as lower-case" do
    mixed_case_email = "TANAKA@example.com"
    @user.email = mixed_case_email
    @user.save
    expect(@user[:email]).to eq("tanaka@example.com")
  end

  # メールアドレスの一意性の検証
  it "email is unique" do
    user = User.create(name: "Test", user_id: "test", email: "taro@example.com")
    expect(user).to_not be_valid
    expect(user.errors[:email]).to include("has already been taken")
  end

  # メールアドレスのフォーマットの検証
  describe "mail" do
    context "Correct format" do
      it "is OK" do
        @user.email = 'user@example.com'
        expect(@user).to be_valid
      end
    end
    context "Incorrect format" do
      it "is NG" do
        @user.email = 'user@example,com'
        expect(@user).to_not be_valid
        expect(@user.errors[:email]).to include("is invalid")
      end
    end
  end

  # nameの長さの検証
  describe "name length" do
    context "length 50 txt" do
      it "is OK" do
        @user.name = 'a' * 50
        expect(@user).to be_valid
      end
    end
    context "length 51 txt" do
      it "is NG" do
        @user.name = 'a' * 51
        expect(@user).to_not be_valid
        expect(@user.errors[:name]).to include("is too long (maximum is 50 characters)")
      end
    end
  end
end

1ブロックづつ説明します

重複のチェックの際に使用するので、事前にインスタンスを作成しておきます。

spec/models/user_spec.rb
before do
  @user = User.create(
    name:    "Yamada Taro",
    user_id: "taro",
    email:   "taro@example.com",
  )
end

name、user_id、emailがあれば有効な状態であること

spec/models/user_spec.rb
  # name、user_id、emailがあれば有効な状態であること
  it "is valid with a name, user_id, email" do
    user = User.new(
      name:    "Sato Taro",
      user_id: "sato",
      email:   "sato@example.com",
    )
    expect(user).to be_valid
  end

expect(user).to be_valid は、user が妥当(valid)なことを期待するという意味です。

nameがなければ無効な状態であること(nameは必須項目)

spec/models/user_spec.rb
it "is invalid without a name" do
  user = User.new(name: nil)
  user.valid?
  expect(user.errors[:name]).to include("can't be blank")
end

メールアドレスがなければ無効な状態であること(emailは必須項目)

spec/models/user_spec.rb
it "is invalid without an email address" do
  user = User.new(email: nil)
  user.valid?
  expect(user.errors[:email]).to include("can't be blank")
end

valid? が false なら、errors.messagesにエラー内容が格納されます。今回は、それが、"can't be blank"であることを期待しています。

重複したメールアドレスなら無効な状態であること

spec/models/user_spec.rb
it "is invalid with a duplicate email address" do
  user = User.new(email: "taro@example.com")
  user.valid?
  expect(user.errors[:email]).to include("has already been taken")
end

valid? が false なら、errors.messagesにエラー内容が格納されます。今回は、それが、"has already been taken"であることを期待しています。

メールアドレスが小文字化されていること

spec/models/user_spec.rb
it "email addresses should be saved as lower-case" do
  mixed_case_email = "TANAKA@example.com"
  @user.email = mixed_case_email
  @user.save
  expect(@user[:email]).to eq("tanaka@example.com")
end

@user[:email]の値 = "tanaka@example.com"TRUE であることを期待しています。

メールアドレスの一意性の検証

spec/models/user_spec.rb
it "email is unique" do
  user = User.create(name: "Test", user_id: "test", email: "taro@example.com")
  expect(user).to_not be_valid
  expect(user.errors[:email]).to include("has already been taken")
end

taro@example.comはすでに保存されたアドレスなので、バリデーションは通らないことと、エラーメッセージは"has already been taken"であることを期待しています。

メールアドレスのフォーマットの検証

  • ,comのカンマがあるのは、無効である
  • .comであれば、有効である(すくなくとも無効ではない)

ことを

Userモデルのバリデーション部分に記載しているので、テストがパスしないことを期待しています。

spec/models/user_spec.rb
describe "mail" do
  context "Correct format" do
    it "is OK" do
      @user.email = 'user@example.com'
      expect(@user).to be_valid
    end
  end
  context "Incorrect format" do
    it "is NG" do
      @user.email = 'user@example,com'
      expect(@user).to_not be_valid
      expect(@user.errors[:email]).to include("is invalid")
    end
  end
end

nameの長さの検証

nameの中身の文字数が

  • 50文字までは有効
  • 51文字以上は無効

であることをUserモデルのバリデーション部分に記載しているので、テストがパスしないことを期待しています。

spec/models/user_spec.rb
describe "name length" do
  context "length 50 txt" do
    it "is OK" do
      @user.name = 'a' * 50
      expect(@user).to be_valid
    end
  end
  context "length 51 txt" do
    it "is NG" do
      @user.name = 'a' * 51
      expect(@user).to_not be_valid
      expect(@user.errors[:name]).to include("is too long (maximum is 50 characters)")
    end
  end
end

テスト実行結果

bundle exec rspec spec で実行できます。

$ bundle exec rspec spec

StaticPagesController
  GET #top
    returns http success
  GET #about
    returns http success

UsersController
  GET #new
    returns http success

User
  is valid with a name, user_id, email
  is invalid without a name
  is invalid without an email address
  is invalid with a duplicate email address
  email addresses should be saved as lower-case
  email is unique
  mail
    Correct format
      is OK
    Incorrect format
      is NG
  name length
    length 50 txt
      is OK
    length 51 txt
      is NG

Finished in 0.34401 seconds (files took 1.19 seconds to load)
13 examples, 0 failures

全て通りました。

まとめ

テストとは基本的に、期待する値と、実際に生成される値を比較検証なんだと体感しました。

テストの妥当性を検証するために、期待する値を変更したりして、テストをパスしないことを確かめることも重要だとわかりました。

it ... end 中にあるブロックは、1つのテストをまとめる役割をしています。これによって、テストがパスしなかった場合、どこでコケたか、原因究明がわかりやすくなることがわかりました。書いていて。

参考

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