20190828のRubyに関する記事は15件です。

Railsでメール送信機能を実装する手順を結構丁寧に(development環境編)

次からもっとスムースにできるように、ここに執筆します。

前提

  • バージョンは Rails 5.2.3 です。
  • テンプレートエンジンはHamlを使っていますが、ERBでもSlimでもそんなに変わらないはずです。
$ rails -v
Rails 5.2.3

要件(仕様、やりたいこと)

  • 問い合わせフォームで必要事項を入力して送信すると、その内容をDBに保存するとともに、管理者にテキストメールで通知する。

letter_opener を導入する

  • letter_opener というgemを導入すると、開発中にテストで送信したメールが実際にアドレス先に飛んでいく代わりに、ブラウザのポップアップで確認できるようになります。
  • 導入することで、テスト用のメールが誤って実際に使われているアドレス先に送信されるのを防ぐことができます。
  • 実際にやってみればどうなるかわかると思いますので、とりあえず以下の手順で導入だけ済ませておきます。
Gemfile
group :development do
  gem 'letter_opener'
end
$ bundle install
config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener

実装

では実装していきます。

config/environments/development.rb を編集する

  • config.action_mailer.raise_delivery_errors = false をコメントアウトすることで、development環境からもメールを送信する設定に切り替えることができます。
  • config.action_mailer.delivery_method は先ほど追加した部分です。
  • config.action_mailer.default_url_options は、後述のメールテンプレート内でURLヘルパーを使用できるようにする設定です。
config/environments/development.rb
Rails.application.configure do

  #...(中略)...

  # コメントアウトすることでdevelopment環境からもメールを送信する設定になる
  # config.action_mailer.raise_delivery_errors = false

  # smtpで実際にメール送信させるのではなく、letter_openerというgemを用いてテストする
  # config.action_mailer.delivery_method = :smtp
  config.action_mailer.delivery_method = :letter_opener

  # メールテンプレートはviewと違ってURLヘルパーを使ってもドメインが取得できず、メール本文にURLを載せられないので、その対策をする
  config.action_mailer.default_url_options = {:host => 'localhost:3000'}
end

メーラークラスを作成する

  • rails generate コマンドが使用できます。
  • 今回はHTMLメールではなくテキストメールを実装したいため、app/views/notice_mailer/sendmail_contact.html.haml は削除します(HTMLテンプレートの方が優先して呼ばれるみたいなので、そちらを残しておくとエラーになったり意図しない内容のメールになってしまいます)。
$ rails g mailer Notice sendmail_contact
Running via Spring preloader in process 1180
      create  app/mailers/notice_mailer.rb
      invoke  haml
      create    app/views/notice_mailer
      create    app/views/layouts/mailer.text.haml
      create    app/views/layouts/mailer.html.haml
      create    app/views/notice_mailer/sendmail_contact.text.haml
      create    app/views/notice_mailer/sendmail_contact.html.haml ->削除する
  • メーラークラスは以下の内容にします。
app/mailers/notice_mailer.rb
class NoticeMailer < ApplicationMailer

  # メール送信元アドレスを設定する
  default from: "noreply@example.com"

  def sendmail_contact(contact)
    @contact = contact
    # メール送信先アドレスを設定する
    mail to: "admin@example.com",
         subject: "お問い合わせが届きました"  # メール件名
  end

end

メールテンプレートを作成する

  • ここに記述した内容がメール本文になります。
  • メーラークラスのメソッド内で使用したインスタンス変数は、ここで参照できます。
  • バックスラッシュを使うことで、改行できます。
  • 先述した通り、config/environments/development.rb に必要な設定をしていれば、URLヘルパーを使うこともできます。
app/views/notice_mailer/sendmail_contact.text.haml
お問い合わせが届きました。
\
= "会社名: #{@contact.company}"
= "お名前: #{@contact.name}"
= "メールアドレス: #{@contact.email}"
= "電話番号: #{@contact.telephone}"
= "お問い合わせ内容:"
= @contact.message

メーラークラスのメソッドを呼び出す

  • 追加しているのは、NoticeMailer.sendmail_contact(@contact).deliver の1行です。
app/controllers/contacts_controller.rb
class ContactsController < ApplicationController

  def new
    @contact = Contact.new
  end

  def create
    @contact = Contact.new(contact_params)
    if @contact.save
      NoticeMailer.sendmail_contact(@contact).deliver # ここで呼び出している
      redirect_to new_contact_url, notice: '問い合わせの送信に成功しました。'
    else
      render :new
    end
  end

  private

  def contact_params
    params.require(:contact).permit(:company, :name, :email, :telephone, :message)
  end

end

これで一通りの実装が完了しました。実際に動かしてみましょう!

(option)development環境から実際にメール送信する

  • letter_opener を使わず、実際にメールを送信してみる場合は、config/environments/development.rb を以下のように記述します。
  • 今回は、ドメインはGmailのものを設定します。
  • Gmailのアプリパスワードというのは、普段Googleのアカウントにログインするために使っているパスワードとは異なりますので、ご注意ください。
config/environments/development.rb
Rails.application.configure do

  #...(中略)...

  # コメントアウトすることでdevelop環境からもメールを送信する設定になる
  # config.action_mailer.raise_delivery_errors = false

  # smtpで実際にメール送信させる
  config.action_mailer.delivery_method = :smtp
  # config.action_mailer.delivery_method = :letter_opener

  # メールテンプレートはviewと違ってURLヘルパーを使ってもドメインが取得できず、メール本文にURLを載せられないので、その対策をする
  config.action_mailer.default_url_options = {:host => 'localhost:3000'}

  config.action_mailer.smtp_settings = {
      :enable_starttls_auto => true,
      :address => 'smtp.gmail.com',
      :port => 587,
      :domain => 'gmail.com',
      :authentication => :plain,
      # 本当に飛ばす場合(letter_openerを使わない場合)は、Gmailのメールアドレスが必要
      :user_name => 'aaaaa@gmail.com',
      # 本当に飛ばす場合(letter_openerを使わない場合)は、Gmailのアプリパスワードが必要
      :password => 'aaaabbbbccccdddd'
  }
end

(WIP)production環境から実際にメール送信する

  • 本番環境については別記事にまとめる予定です。まだ私も実装できていないので…。

参考

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

【配送手数料1000円なんて高すぎる!!】UberEATSで安く注文する〜全てはタピオカのために〜

UberEATS CHEAPER

(間違って記事削除してしまったので再掲してます...)

TL; DR

UberEatsでタピオカミルクティーを頼みたいのに配送料が高すぎる
配送料が安くなったタイミングを通知できるようにした

GitHub

背景

私はタピオカミルクティーが好きです。正しくは、タピオカ抹茶ティーが大好きです。
口が寂しい時にタピオカミルクティーは丁度良いです。
糖分は取れるし、腹持ちいいし。
オフィスから歩いてタピオカミルクティーを買えるお店はありません。
なのでいつも UberEATSを利用させて頂いております。

しかし、いつも思うのです。
頼みたい時ってだいたい昼下がりの時間なんですけど、結構な頻度で
配送料高っっっっっっっっっっっっっ!!!!!!!!!!

と。頼むにしてはギリギリを超える手数料。

なので、配送料が安くなった時に通知してくれるプログラム作りました。

概要

Rubyツールです。
アクセス負荷がかからないくらいで5分おきにデータを取得し、安くなったかどうかを判断しています。
通知方法はSlackMacの通知センターの2つに対応しています
(他のサービス、Windowsなどは反響があれば作ろうかなと思います。)

demo.gif

技術的内容

seleniumで操作しながら情報を取得しています。
UberEatsでは、まずブラウザにお届け先の住所のデータをCookieで持つ必要があります。
その上で、お店の配送手数料や配送時間、そもそも配送可能かどうかが分かるようになります。
そのため、seleniumを利用しました。
一度お届け先の郵便番号を入力し、住所候補が出てきます。
出てきた住所候補をクリックしてCookieに住所情報を保持します。
その状態でお店のページに遷移します。
そうすると、住所を元に配送手数料が表示されます。
(色々調べたら緯度・経度を元にしていました。詳しくは別記事で書こうと思います。)

html要素はcssセレクター、xpathで取得していますが、html構造が変わったらこの
ツールは使えなくなるかもしれません。ご了承くださいませ。

使い方

$ bundle exec ruby ubereats_cheaper.rb [通知方法] [郵便番号] [希望配送手数料] [UberEATSのお店のURL] [Slack webhook url オプション引数]
  • 引数1 通知方法

mac(デフォルト)・・・macの通知センターから通知を送ります。画面右上からぴょこっと出てきます。
slack・・・Slackの特定のチャンネルに通知を送ります。引数5にてslack webhook urlが必要になります。

  • 引数2 郵便番号

[0-9]{6}のフォーマットで入力してください。

  • 引数3 希望配送手数料

数字を入力してください。ここで入力した数字よりも配送手数料が低くなったら通知がきます。

  • 引数4 UberEATSのお店のURL

ここにはお店の個別ページのURLを入力してください。
例:https://www.ubereats.com/ja-JP/tokyo/food-delivery/%E3%82%B7-%E3%82%A2%E3%83%AC%E3%82%A4-%E6%B8%8B%E8%B0%B7246%E5%BA%97-the-alley-shibuya-246/C7X1V9lWQ5KIoPq7YYLc4A/

  • 引数5 Slackwebhook url

https://www.sejuku.net/blog/74471
こちらを参考に、webhook urlを取得してください。一応このwebhook urlは後々削除されるとのことなので、反響があれば後々新しい方に切り替えようと思います。

コマンド例

bundle exec ruby ubereats_cheaper.rb 'slack' 
1070062 1000 'https://www.ubereats.com/ja-JP/tokyo/food-delivery/%E3%82%B7-%E3%82%A2%E3%83%AC%E3%82%A4-%E6%B8%8B%E8%B0%B7246%E5%BA%97-the-alley-shibuya-246/C7X1V9lWQ5KIoPq7YYLc4A/' 'https://hooks.slack.com/services/hoge1/hoge2/hoge3'

なお、bundle exec ruby ubereats_cheaper.rbだけで実行すると、macで、自分が最近よくいる南青山へ、大好きな渋谷のジ アレイ(有名なタピオカミルクティーのお店)の配送手数料が1000円(1000円より安くなることが割と少ない)よりも安くなった時に通知が来るようになっています。

インストール

  • このリポジトリをcloneしてください

$ git clone git@github.com:nochiraaa/ubereats_cheaper.git

  • $ bundle install --path vendor/bundleを実行

  • Chromeドライバをインストール。以下のURLより取得できます。
    https://sites.google.com/a/chromium.org/chromedriver/downloads
    もしくはHomebrewを使っている方は下記でもインストールができます。
    $ brew install chromedriver
    Homebrewが入っていない方はこちらから
    http://brew.sh/index_ja.html
    Chromeドライバのバージョンは最新のものであれば問題なく動くと思います。
    動作確認はChromeDriver 76.0.3809.68 (420c9498db8ce8fcd190a954d51297672c1515d5-refs/branch-heads/3809@{#864})で行っています。

今後の展開

シンプルな内容で作りましたが、下記の改善も検討中です!

  • 配送時間にも対応する
  • オススメのお店をレコメンド
  • 他に安いお店をレコメンド
  • 他に早く届くお店をレコメンド
  • 他のプラットフォームにも対応する
    • LINE@
    • Twitter
  • API的に動くようにする
  • firefox, IE, Edge, Safariにも対応する
  • アメリカでも使えるようにする
  • seleniumを使わず処理する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【配送手数料1000円なんて高すぎる!!】UberEATSで安く注文する〜全てはタピオカのために〜[再掲]

UberEATS CHEAPER

(間違って記事削除してしまったので再掲してます...)

TL; DR

UberEatsでタピオカミルクティーを頼みたいのに配送料が高すぎる
配送料が安くなったタイミングを通知できるようにした

GitHub

背景

私はタピオカミルクティーが好きです。正しくは、タピオカ抹茶ティーが大好きです。
口が寂しい時にタピオカミルクティーは丁度良いです。
糖分は取れるし、腹持ちいいし。
オフィスから歩いてタピオカミルクティーを買えるお店はありません。
なのでいつも UberEATSを利用させて頂いております。

しかし、いつも思うのです。
頼みたい時ってだいたい昼下がりの時間なんですけど、結構な頻度で
配送料高っっっっっっっっっっっっっ!!!!!!!!!!

と。頼むにしてはギリギリを超える手数料。

なので、配送料が安くなった時に通知してくれるプログラム作りました。

概要

Rubyツールです。
アクセス負荷がかからないくらいで5分おきにデータを取得し、安くなったかどうかを判断しています。
通知方法はSlackMacの通知センターの2つに対応しています
(他のサービス、Windowsなどは反響があれば作ろうかなと思います。)

demo.gif

技術的内容

seleniumで操作しながら情報を取得しています。
UberEatsでは、まずブラウザにお届け先の住所のデータをCookieで持つ必要があります。
その上で、お店の配送手数料や配送時間、そもそも配送可能かどうかが分かるようになります。
そのため、seleniumを利用しました。
一度お届け先の郵便番号を入力し、住所候補が出てきます。
出てきた住所候補をクリックしてCookieに住所情報を保持します。
その状態でお店のページに遷移します。
そうすると、住所を元に配送手数料が表示されます。
(色々調べたら緯度・経度を元にしていました。詳しくは別記事で書こうと思います。)

html要素はcssセレクター、xpathで取得していますが、html構造が変わったらこの
ツールは使えなくなるかもしれません。ご了承くださいませ。

使い方

$ bundle exec ruby ubereats_cheaper.rb [通知方法] [郵便番号] [希望配送手数料] [UberEATSのお店のURL] [Slack webhook url オプション引数]
  • 引数1 通知方法

mac(デフォルト)・・・macの通知センターから通知を送ります。画面右上からぴょこっと出てきます。
slack・・・Slackの特定のチャンネルに通知を送ります。引数5にてslack webhook urlが必要になります。

  • 引数2 郵便番号

[0-9]{6}のフォーマットで入力してください。

  • 引数3 希望配送手数料

数字を入力してください。ここで入力した数字よりも配送手数料が低くなったら通知がきます。

  • 引数4 UberEATSのお店のURL

ここにはお店の個別ページのURLを入力してください。
例:https://www.ubereats.com/ja-JP/tokyo/food-delivery/%E3%82%B7-%E3%82%A2%E3%83%AC%E3%82%A4-%E6%B8%8B%E8%B0%B7246%E5%BA%97-the-alley-shibuya-246/C7X1V9lWQ5KIoPq7YYLc4A/

  • 引数5 Slackwebhook url

https://www.sejuku.net/blog/74471
こちらを参考に、webhook urlを取得してください。一応このwebhook urlは後々削除されるとのことなので、反響があれば後々新しい方に切り替えようと思います。

コマンド例

bundle exec ruby ubereats_cheaper.rb 'slack' 
1070062 1000 'https://www.ubereats.com/ja-JP/tokyo/food-delivery/%E3%82%B7-%E3%82%A2%E3%83%AC%E3%82%A4-%E6%B8%8B%E8%B0%B7246%E5%BA%97-the-alley-shibuya-246/C7X1V9lWQ5KIoPq7YYLc4A/' 'https://hooks.slack.com/services/hoge1/hoge2/hoge3'

なお、bundle exec ruby ubereats_cheaper.rbだけで実行すると、macで、自分が最近よくいる南青山へ、大好きな渋谷のジ アレイ(有名なタピオカミルクティーのお店)の配送手数料が1000円(1000円より安くなることが割と少ない)よりも安くなった時に通知が来るようになっています。

インストール

  • このリポジトリをcloneしてください

$ git clone git@github.com:nochiraaa/ubereats_cheaper.git

  • $ bundle install --path vendor/bundleを実行

  • Chromeドライバをインストール。以下のURLより取得できます。
    https://sites.google.com/a/chromium.org/chromedriver/downloads
    もしくはHomebrewを使っている方は下記でもインストールができます。
    $ brew install chromedriver
    Homebrewが入っていない方はこちらから
    http://brew.sh/index_ja.html
    Chromeドライバのバージョンは最新のものであれば問題なく動くと思います。
    動作確認はChromeDriver 76.0.3809.68 (420c9498db8ce8fcd190a954d51297672c1515d5-refs/branch-heads/3809@{#864})で行っています。

今後の展開

シンプルな内容で作りましたが、下記の改善も検討中です!

  • 配送時間にも対応する
  • オススメのお店をレコメンド
  • 他に安いお店をレコメンド
  • 他に早く届くお店をレコメンド
  • 他のプラットフォームにも対応する
    • LINE@
    • Twitter
  • API的に動くようにする
  • firefox, IE, Edge, Safariにも対応する
  • アメリカでも使えるようにする
  • seleniumを使わず処理する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails & Nuxt.jsのアプリケーションにGraphQLを導入する

前提

Rails & Nuxt.jsのDocker環境をalpineイメージで構築する

こちらのポストの環境をもとに進めるので、Dockerのサービス名等は適宜読み替えていただくようにお願いします。

ディレクトリ構成

後述のコマンドでは、Railsは backend、Nuxtは frontend がDockerのサービス名になっています。

.
├── backend <- Ruby on Rails
│   ├── Dockerfile
│   ├── Gemfile
│   ├── Gemfile.lock
│   (中略)
│   
├── frontend <- Nuxt.js
│   ├── Dockerfile
│   ├── README.md
│   ├── nuxt.config.js
│   ├── package-lock.json
│   ├── package.json
│  (中略)
│
├── docker-compose.yml
└── .env

ライブラリ追加

Rails

./backend/Gemfile を修正し、 bundle install します。

## (中略) ##

gem 'graphql' #added

group :development do
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'

  gem 'graphiql-rails' #added
end

## (中略) ##
$ docker-compose exec backend bundle install

Nuxt

こちらのライブラリをインストールします。

本記事では graphql-tag は使いません。

$ docker-compose exec frontend yarn add @nuxtjs/apollo

実装

Rails

generator で雛形を作成します。

$ docker-compose exec backend rails g graphql:install

graphiql-railsの Readme にしたがって、Railsのconfigファイルを修正します。

./backend/config/routes.rb

GraphiQLエンジンをマウントし、ブラウザからアクセスできるようにします。

Rails.application.routes.draw do
  post "/graphql", to: "graphql#execute" #generatorでinsertされる
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html

  #added
  if Rails.env.development?
    mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: '/graphql'
  end
end

./backend/config/application.rb

APIモードの場合に必要な修正です。

## (中略) ##

- # require "sprockets/railtie"
+ require "sprockets/railtie"

## (中略) ##

GraphiQLで動作確認

dockerコンテナを再起動後、ブラウザで http://localhost:3000/graphiql にアクセスし、GraphiQLを開きます。

./backend/app/graphql/types/query_type.rb のサンプルを利用して、下記のようにqueryの結果が返ってくればOKです。

$ docker-compose restart backend

graphiql.png

Nuxt

Nuxtアプリのルートに、下記のディレクトリ、ファイルを追加、編集します。

今回はmutationは使いませんが、あわせて作成しておきます。

.
└── frontend 
    ├── nuxt.config.js
    │
    ├── pages
    │   └── index.vue
    │
    └── apollo
        ├── client-configs
        │   └── default.js
        └── gqls
            ├── mutations
            └── queries
                └── getTestField.gql

./frontend/nuxt.config.js

Nuxtでapolloクライアントを使用するための設定を追加します。

default.js を読み込まず、nuxt.config.jsに直書きしてもOKです。

export default {

  /* (中略) */

  modules: [
    '@nuxtjs/apollo', //added
  ],

  /* (中略) */

  apollo: {
    clientConfigs: {
      default: '~/apollo/client-configs/default.js'
    }
  }
}

./frontend/apollo/client-configs/default.js

apolloには様々なオプションありますがが、今回はqueryの実行が確認できればよいので最低限です。
uriのホスト名は、DockerのRailsアプリのサービス名 backend になります。

import { HttpLink } from 'apollo-link-http'

export default () => {
  const httpLink = new HttpLink({ uri: 'http://backend:3000/graphql' })
  return {
    link: httpLink
  }
}

./frontend/apollo/gqls/queries/testField.gql

GraphiQLで実行したものです。

query {
  testField
}

./frontend/pages/index.vue

queryを実行した結果を表示します。

<template>
  <p>{{ testField }}</p>
</template>

<script>
import testField from '~/apollo/gqls/queries/testField';

export default {
  data() {
    return {
      testField: {}
    }
  },
  apollo: {
    testField: {
      query: testField
    }
  }
}
</script>

nuxt.png

お疲れさまでした。

(簡略化シすぎた感)

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

Railsチュートリアルメモ 第3章

個人的なメモ。

Railsチュートリアルでは2章ではScaffoldを使ってあらましを、
3章から本格的にアプリ制作に入る。
3章では「静的なページの制作」、「自動テストのあらまし」の学習をする

メモでは気になった部分、忘れそうな部分を記述。

3章 ほぼ静的なページの作成

3.2 静的ページ

3.2.1 静的なページの生成

コントローラ生成(rails generate)時にアクションは複数指定できる

$ rails generate controller StaticPages home help

home helpのようにcontroller名のあとに複数指定可能

Rubyの命名規則の特徴

クラス名にはキャメルケース(例:StaticPages)が使われる
ファイル名にはスネークケース(例:static_pages)が使われる
※あくまで慣習だが、同様に習うべき

Railsコマンドの短縮

完全なコマンド 短縮形
$ rails server $ rails s
$ rails console $ rails c
$ rails generate $ rails g
$ rails test $ rails t
$ bundle install $ bundle

Railsでの手戻し

コントローラ, モデル
$ rails destroy controller HogeHoges

generate に対しての destroy

※ destroy時、/config/routes.rb内にルーティングが残っているかもしれない。手作業で削除

DB

DBも逐一マイグレーションが必要

$ rails db:migrate

だが、1手手戻しもできる

$ rails db:rollback

またdbのマイグレーションは逐一バージョンが付与されているので、最初に戻す場合、以下

$ rails db:migrate VERSION=0

3.2.2 静的なページの調整

viewsの場所

app/views/コントローラ名/アクション名.html.erb
erb (Embedded Ruby)
htmlにrubyを埋め込んである。

3.3 テストから始める

テスト駆動開発 (test-driven development; TDD)
RailsチュートリアルではガチガチのTDDでは進みが悪いので、必要に応じてTDD

チュートリアル内で行うテストは3つ

  • コントローラテスト (3章〜)
  • モデルテスト (6章〜)
  • 統合テスト (7章〜)

結局テストはいつ行えばよいのか

3.3.1

コントローラテスト

$ rails generate controller hogehoge の時点でテストが既に作成されている。
/test/controllers/

3.3.2

テスト駆動のサイクル

  1. 失敗するテストを最初に書く
  2. アプリケーションのコードを書いて成功させる (パスさせる)
  3. 必要ならリファクタリングする

それぞれステータスから

  1. RED
  2. GREEN
  3. REFACTOR

とも。
このサイクルを繰り返す。

以下テストを元に、$ rails testを確認しながら、通るようにしていく。
テストを書き、通るように開発。簡単なTDD。

require 'test_helper'

class StaticPagesControllerTest < ActionDispatch::IntegrationTest

  # 省略

  test "should get about" do
    get static_pages_about_url
    assert_response :success
  end
end

リファクタリング

「1匹いたら30匹いると思え」
金言。BUGでなくとも腐敗臭漂うだけでリファクタリングもの。
ネズミもGもBUGもスパゲッティコードも。早いうちからのリファクタリングが大事。

3.4 少しだけ動的なページ

HTML内でheadのtitleタグを動的に表示し、TDDでつなぐ。

3.4.2

リファクタリング。
2,3回出てくる文字列を変数化
(インスタンス変数、文字列の式展開について、触れられている)

3.4.3

DRY (Donot Repeat Yourself)
Rubyの原則。
共通部分をRailsの機能でまとめる。
/app/views/layouts/application.html.erb
上記に大枠のレイアウトを記述。
その他アクションのerb内の内容は、application.html.erb内に
<%= yield %>
と記述し、挿入する。

3.5 最後に

コミットとデプロイ
masterブランチに変更をmerge
herokuにデプロイ

$ git checkout master
$ git merge 作業ブランチ名

# プッシュとデプロイ
$ git push # github側 push
$ rails t # herokuデプロイ前テスト
$ git push heroku # heroku側 push
$ heroku open # 確認

デプロイ前に rails test を実行。癖をつけるようにする。

3.6 高度なセットアップ

3.6.1 minitest reporters

minitest-reporters (gem) でRailsのテストを見やすくする。
RED、GREENで色がつくようになる。

3.6.2 Guardによるテストの自動化

Guardというgemでファイルを監視、自動テスト

$ bundle exec guard init # 初期化
$ bundle exec guard # 監視スタート

guardのプロンプトが開く。
フルスキャンはそのままEnter。
終了は <C-d> でデタッチ。

GuardはSpringサーバ(Railsの機能)を使うが、バグが有りプロセスが残ることが有るので、重いときには余計なものをkillする。

Linux killコマンド

プロセスのkill

$ ps aux | grep spring

パイプでgrepに渡し、springのみ抽出
pid(数字5桁)を指定してkill

$ kill -15 12345 #pidはあくまで例

ここのoptionで指定するシグナルは15がterminationで一緒か?
どうやらCPUごとに一部変わるそうで、x86なら-15SIGTERM(termination)で終了できるらしい。
我が家のintelmacさんはx86かと思うので-15

Springなら
1. 一括終了を最初に試す、
2. ダメならpkill

$ spring stop
$ pkill -15 -f プロセス名 # ダメなら

所感

テスト駆動で書きながら、コントローラからビューに至るまでの流れをトレースできたように思う。
簡単なテストだが、まだ苦に思わない程度で済んでいる。
演習で2,3度同じような作業が繰り返されるので、少しずつ馴染めそう。

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

Railsチュートリアル(第4版)メモ 第3章

Railsチュートリアル(第4版)の個人メモ
気になった部分、忘れそうな部分を記述。

  • Ruby 2.6.1
  • Rails 5.1.6

2章ではScaffoldを使ってあらましを、
3章から本格的にアプリ制作に入る。
3章では「静的なページの制作」、「自動テストのあらまし」の学習をする

3章 ほぼ静的なページの作成

3.2 静的ページ

3.2.1 静的なページの生成

コントローラ生成(rails generate)時にアクションは複数指定できる

$ rails generate controller StaticPages home help

home helpのようにcontroller名のあとに複数指定可能

Rubyの命名規則の特徴

クラス名にはキャメルケース(例:StaticPages)が使われる
ファイル名にはスネークケース(例:static_pages)が使われる
※あくまで慣習だが、同様に習うべき

Railsコマンドの短縮

完全なコマンド 短縮形
$ rails server $ rails s
$ rails console $ rails c
$ rails generate $ rails g
$ rails test $ rails t
$ bundle install $ bundle

Railsでの手戻し

コントローラ, モデル
$ rails destroy controller HogeHoges

generate に対しての destroy

※ destroy時、/config/routes.rb内にルーティングが残っているかもしれない。手作業で削除

DB

DBも逐一マイグレーションが必要

$ rails db:migrate

だが、1手手戻しもできる

$ rails db:rollback

またdbのマイグレーションは逐一バージョンが付与されているので、最初に戻す場合、以下

$ rails db:migrate VERSION=0

3.2.2 静的なページの調整

viewsの場所

app/views/コントローラ名/アクション名.html.erb
erb (Embedded Ruby)
htmlにrubyを埋め込んである。

3.3 テストから始める

テスト駆動開発 (test-driven development; TDD)
RailsチュートリアルではガチガチのTDDでは進みが悪いので、必要に応じてTDD

チュートリアル内で行うテストは3つ

  • コントローラテスト (3章〜)
  • モデルテスト (6章〜)
  • 統合テスト (7章〜)

結局テストはいつ行えばよいのか

3.3.1

コントローラテスト

$ rails generate controller hogehoge の時点でテストが既に作成されている。
/test/controllers/

3.3.2

テスト駆動のサイクル

  1. 失敗するテストを最初に書く
  2. アプリケーションのコードを書いて成功させる (パスさせる)
  3. 必要ならリファクタリングする

それぞれステータスから

  1. RED
  2. GREEN
  3. REFACTOR

とも。
このサイクルを繰り返す。

以下テストを元に、$ rails testを確認しながら、通るようにしていく。
テストを書き、通るように開発。簡単なTDD。

require 'test_helper'

class StaticPagesControllerTest < ActionDispatch::IntegrationTest

  # 省略

  test "should get about" do
    get static_pages_about_url
    assert_response :success
  end
end

リファクタリング

「1匹いたら30匹いると思え」
金言。BUGでなくとも腐敗臭漂うだけでリファクタリングもの。
ネズミもGもBUGもスパゲッティコードも。早いうちからのリファクタリングが大事。

3.4 少しだけ動的なページ

HTML内でheadのtitleタグを動的に表示し、TDDでつなぐ。

3.4.2

リファクタリング。
2,3回出てくる文字列を変数化
(インスタンス変数、文字列の式展開について、触れられている)

3.4.3

DRY (Donot Repeat Yourself)
Rubyの原則。
共通部分をRailsの機能でまとめる。
/app/views/layouts/application.html.erb
上記に大枠のレイアウトを記述。
その他アクションのerb内の内容は、application.html.erb内に
<%= yield %>
と記述し、挿入する。

3.5 最後に

コミットとデプロイ
masterブランチに変更をmerge
herokuにデプロイ

$ git checkout master
$ git merge 作業ブランチ名

# プッシュとデプロイ
$ git push # github側 push
$ rails t # herokuデプロイ前テスト
$ git push heroku # heroku側 push
$ heroku open # 確認

デプロイ前に rails test を実行。癖をつけるようにする。

3.6 高度なセットアップ

3.6.1 minitest reporters

minitest-reporters (gem) でRailsのテストを見やすくする。
RED、GREENで色がつくようになる。

3.6.2 Guardによるテストの自動化

Guardというgemでファイルを監視、自動テスト

$ bundle exec guard init # 初期化
$ bundle exec guard # 監視スタート

guardのプロンプトが開く。
フルスキャンはそのままEnter。
終了は <C-d> でデタッチ。

GuardはSpringサーバ(Railsの機能)を使うが、バグが有りプロセスが残ることが有るので、重いときには余計なものをkillする。

Linux killコマンド

プロセスのkill

$ ps aux | grep spring

パイプでgrepに渡し、springのみ抽出
pid(数字5桁)を指定してkill

$ kill -15 12345 #pidはあくまで例

ここのoptionで指定するシグナルは15がterminationで一緒か?
どうやらCPUごとに一部変わるそうで、x86なら-15SIGTERM(termination)で終了できるらしい。
我が家のintelmacさんはx86かと思うので-15

Springなら
1. 一括終了を最初に試す、
2. ダメならpkill

$ spring stop
$ pkill -15 -f プロセス名 # ダメなら

所感

テスト駆動で書きながら、コントローラからビューに至るまでの流れをトレースできたように思う。
簡単なテストだが、まだ苦に思わない程度で済んでいる。
演習で2,3度同じような作業が繰り返されるので、少しずつ馴染めそう。

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

rails specで特定のファイルを指定してテストする

ググると

rails spec spec/models/user_spec.rb

のようなのが出てきますが、実際は全部テスト実行されます。どこかのバージョンで変わったみたい。

うちのRails 5.2環境ではこれでいけた。

rails spec SPEC=spec/models/user_spec.rb
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UFO演算子をメソッドとして定義する

概要

最近、Rubyについてもっと理解を深めたくて、Ruby技術者認定試験合格教本を読んでいます。
以下のようなUFO演算子(<=>)をメソッドとして定義している部分が分からず、まとめてみました。
(宇宙船演算子ともいうらしいですが、UFO演算子と呼ばせていただきます)

  def <=> other
    return self.id <=> other.id
  end

ざっくり結論

sortメソッド等に利用する為に、<=> を定義している記述だった。

@scivola さんより以下ご指摘いただき、「再」を削除しました。

Object には <=> は定義されていないので,Employee クラスで 再定義 しているわけではないんですよね。

ですので、単純に「定義」している記述という表現が正しいです!

どうしてこの結論に至ったか、以下経緯をまとめました。

経緯

下記のような記述が登場。
パッとみて、上記のメソッド部分が全然分からなかったので調べました。

class Employee
  attr_reader :id
  attr_accessor :name

  def initialize id, name
    @id = id
    @name = name
  end

  def to_s
    return "#{@id}: #{@name}"
  end

  def <=> other
    return self.id <=> other.id
  end

end

employees = []
employees << Employee.new("3","Tanaka")
employees << Employee.new("1","Suzuki")
employees << Employee.new("2","Sato")

employees.sort!
employees.each do |employee| puts employee end

実行結果

1: Suzuki
2: Sato
3: Tanaka

分からなかったこと

employees.sort! の所で呼ばれてるメソッドっぽいけど、
どうやって呼ばれているの? 役割は?

調べてみたこと

<=> ってなんだっけ?

UFO演算子と呼ばれる比較演算子で、これは比較の結果を数値で返します。左辺の値が右辺の値より大きければ、1、等しければ0、左辺の値が小さければ-1を返します。ソート処理などで順番の決定などに使用します。

100 <=> 10 #=> 1
100 <=> 100 #=> 0
10 <=> 100 #=> -1

Ruby技術者認定試験合格教本より

ふむふむ。

sortメソッドについても改めて調べてみる

sortメソッドは、配列の要素をソートした新しい配列を返します。要素の順序の比較には<=>演算子が使われ、「要素1 <=> 要素2」の結果が-1なら要素1が先、0なら同じ、1なら要素2が先となります。
https://ref.xaio.jp/ruby/classes/array/sort

ほうほう。sort って、<=> を使っているか!
じゃあ、そのタイミングで呼ばれているっぽいな・・・

挙動確認してみた

ダメ元で、UFO演算子メソッド部分をコメントアウトして、employees.sort!してみる。

    23: employees = []
    24: employees << Employee.new("3","Tanaka")
    25: employees << Employee.new("1","Suzuki")
    26: employees << Employee.new("2","Sato")
    27: binding.pry
 => 28: employees.sort!
    29: employees.each do |employee| puts employee end

[1] pry(main)> employees
=> [#<Employee:0x00007ff24eb8d9c0 @id="3", @name="Tanaka">,
 #<Employee:0x00007ff24eb8d948 @id="1", @name="Suzuki">,
 #<Employee:0x00007ff24eb8d8d0 @id="2", @name="Sato">]
[2] pry(main)> employees.sort!
ArgumentError: comparison of Employee with Employee failed
from (pry):3:in `sort!'

やっぱり出来ない!
比較する要素が複数あるからかな。
コメントアウトを外して、実行してみると上手くいく↓

    22: employees = []
    23: employees << Employee.new("3","Tanaka")
    24: employees << Employee.new("1","Suzuki")
    25: employees << Employee.new("2","Sato")
    26: binding.pry
 => 27: employees.sort!
    28: employees.each do |employee| puts employee end

[1] pry(main)> employees.sort!
=> [#<Employee:0x00007fdb013d13d0 @id="1", @name="Suzuki">,
 #<Employee:0x00007fdb013d1358 @id="2", @name="Sato">,
 #<Employee:0x00007fdb013d1448 @id="3", @name="Tanaka">]

次に、UFO演算子のメソッド部分でbinding.pryして、
self.idother.id の値をチェック、どんな値が返されるかを考えてみる。

    16: def <=> other
    17:   binding.pry
 => 18:   return self.id <=> other.id
    19: end

[1] pry(#<Employee>)> self.id
=> "3"
[2] pry(#<Employee>)> other.id
=> "1"

3 <=> 1 だから、1が返る。
結果、 1 => 3 => 2 の順番になるかな。

exit してみると止まるので、次のself.idother.id をチェック。

    16: def <=> other
    17:   binding.pry
 => 18:   return self.id <=> other.id
    19: end

[1] pry(#<Employee>)> self.id
=> "3"
[2] pry(#<Employee>)> other.id
=> "2"

3 <=> 2 だから、1が返る。
結果、 1 => 2 => 3 の順番かな。

exit して、self.idother.id をチェック。

    16: def <=> other
    17:   binding.pry
 => 18:   return self.id <=> other.id
    19: end

[1] pry(#<Employee>)> self.id
=> "1"
[2] pry(#<Employee>)> other.id
=> "2"

1 <=> 2 だから、-1が返る。
結果変わらず、 1 => 2 => 3 の順番かな。

ここでexitしたら、最後まで実行された!
id 同士だけを比較しているので、エラーにならなかった!!

まとめ

やはり、sort! のタイミングで、UFO演算子のメソッドが呼ばれていた。

UFO演算子のメソッド内で、<=> の定義を、id同士を比較するものに定義していた為、
エラーにならずsortメソッドが最後まで実行された!

sortメソッドは、1が返ると右辺と左辺の順序が入れ替わり、
-1が返ると順序が入れ替わらないので、1=>2=>3 の順に並び替わる。

最後に

分かってしまえばそんなに難しくないですが、最初にdef <=> otherを見た時は、正直混乱しました。
「演算子を再定義することが出来る」という概念がなかったので、勉強になりました!
sort の挙動についてもよく分かってよかった。

Qiita 初投稿です。見辛い&レベル低いかもですが、お許しください・・
「違うよ!」「ここの説明の方が分かりやすいよ!」などのアドバイスありましたら、
コメントいただけると喜びます。

補足

@scivola さんより、 <=> を定義せず idの値を sort_by を使って比較する方法も教えていただきました!
コメント欄にて詳しく解説してくださいましたので、チェックしてみてください!
@scivola さん、ありがとうございます!!

参考にさせていただいたサイト様

https://www.uosansatox.biz/entry/2018/01/25/112048
https://codeday.me/jp/qa/20190301/344992.html
https://www.buildinsider.net/language/rubytips/0018

最後まで読んでくださり、ありがとうございました!

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

他言語経験者がRailsの案件にジョインしたときに、何を足掛かりにすべきか

はじめに

 この記事はOmotesando.rb #50で、参加者から質問を募集して、LTを行うという企画の中で、未経験者・初学者からのRailsプロジェクトに参画する際の質問が多かったことから、「実際に案件に参画するときにどのようなキャッチアップをしていけばよいか」という観点で話してみようと思い立ち、作成しました。

対象読者・想定プロジェクト

 Ruby/Railsのプロジェクトに参画したことのない、Ruby/Railsプロジェクト未経験なWeb技術者を対象としています。また、Ruby/Railsの知識については、Railsチュートリアルは一通り目を通しているという前提で書いています。また、想定しているプロジェクトとしては、既に案件はスタートしており、gitリポジトリの整備、環境等の整備が一通り終わっており、人の受け入れ態勢が整っているプロジェクトであることを想定しています。
 最初の関門である、環境構築の部分から書こうと思ったのですが、ここはプロジェクトによって様々なケースがあり、ボリュームが大きくなりそうだったので、今回の記事の中ではスキップして、環境構築は完了したという前提で話を勧めます。

全体を把握する

 プロジェクトに初めて入った時に、最初にやるべきことは全体の把握です。というのも、現実のRailsプロジェクトはRailsチュートリアルのようにシンプルではありません。むしろ基本に忠実に作られていることの方が稀です。いくつかのRuby/Railsプロジェクトに参画した経験があれば、どの程度しっかり作られているのかの勘が利くのですが、Rails未経験者・初学者の場合、そのあたりの感覚がよく分からないため、まず関わるプロジェクトがどの程度基本を押さえて作られているのかを把握しておく必要があります。

 全体を把握するのに、まず以下を見ておくとよいでしょう。

  • Gemfile / Gemfile.lock
  • routes.rb
  • app以下、lib以下
  • config/initializers以下
  • spec、もしくはtest以下

Gemfile / Gemfile.lock

 Railsでは多数のGem(ライブラリ)が使われていて、中規模ぐらいのRailsプロジェクトになってくると、依存するGemも含めて100以上のGemが使われていることも珍しくはありません。Railsチュートリアルでは、極力Gemを使わない構成になっているので、あまり触れることはありませんが、現実のプロジェクトではGemが大量に導入されています。
 ライブラリによってはRailsの挙動を大きく変更するものがあったり、プロジェクトのルールとしてGem特有の記法を強制するものもあるので、まずどのようなGemが使われているのかを把握しておく必要があります。

 Gemfileの中のgemを調べるときはrubygems.orgから、調べたいGem名を入力して、詳細画面からgithubやHomepage等のサイトに飛んで、README.md等のドキュメントを参照します。初学者のうちは分からないGemが多いと思いますので、調べるのも大変でしょうが、経験が蓄積されてくると全体の作りやプロジェクトのルールなども見えてくるので、ざっくりと概要をつかんでおくとよいと思います。

 以下は私がプロジェクトにジョインしたときにあったら注意すべきGemと対応例の一例です。

gem 対応例
devise 認証処理がある。ユーザ名・パスワードの管理のテーブルにUsers等が使われていると考えられるので、app/models以下からdeviseの文字列を検索する
omniauth-xxxx 外部認証連携が存在する。config/以下に認証連携用の設定が書き込まれていると考えておく。
acts_as_paranoid フラグを立ててデータを消したことにする論理削除と呼ばれる概念がある。対象のモデルは強制的にdeleted_at is not nullのクエリが付加されるため、app/models以下からacts_as_paranoidの文字列を検索し、対象となるモデルを把握しておく。
paranoia 同上
ruby-grape ActionControllerではないAPIエンドポイントを持っている。app/以下からGrape::APIを検索して、場所を把握しておく。また、エンドポイントはroutes.rbに書かれていることが多いため、合わせて参照しておく。
sidekiq 実際のURLリクエストとは別タスクとして実行される非同期処理がある。perform / perform_laterなどの文字列を検索し、非同期処理の内容自体やトリガー部分を確認しておく。
resque 同上
sequel DB検索にActiveRecordを使っていない可能性がある。プロジェクトのルールを確認する。
composite_primary_keys データベースの主キーがidではない可能性がある。app/models以下でprimary_keyで検索し、各テーブルの主キーを確認する。
sassc-rails CSSの代わりにassets/stylesheetsにSASSが使用されている可能性がある。
haml-rails ERBの代わりにhamlが採用されている。hamlの記法を用いてViewを書く必要がある。
slim-rails ERBの代わりにslimが採用されている。slimの記法を用いてViewを書く必要がある。
simple_form Form Objectと呼ばれる概念が導入されている可能性がある。

 一つ一つ詳細に見ている時間がないという場合には、そのGemがどういう用途で導入されているかぐらいは確認しておきましょう。
 The ruby toolboxというサイトで、Gem名で検索するとGemのカテゴリが分かるので、大まかに使用用途を推測することができます。

routes.rb

 RailsではRESTful URLでリソースベースのアクセスになるようにURL設計をしていくのが一般的ですが、現実のプロジェクトがしがらみなくRailsの推奨される構成でURL設計されているかというと、そういうわけではありません。例えば他のプロジェクトから移行するようなリプレース案件では、以前のURLと整合性がとれるようにURLをマッピングしなおさなければいけませんし、Railsを良く知らない設計者の趣味ですべてのURLをPOSTで記述しなければならないというトンデモルールが強制されている可能性すらあります。routes.rbを読んで、まずRails一般的なURL設計になっているかどうかを理解しておく必要があります。

 Rails GuidesのRailsルーティングを熟読し、URL定義のパターンを理解しておきましょう。普通にget/postなどのメソッドで個別に定義する表現、matchメソッドでパターンマッチさせる表現、resourcesを使った表現や、複数のresourcesメソッドをネスト構造にしたnested resourcesやnamespaceと組み合わせた表現など、色々あります。

 routes.rbを読む前にrake routesを叩いて、エンドポイントをざっくりと俯瞰しておきましょう。

app以下、lib以下

app以下

 app以下にはassets, controllers, models, views, helpers, mailersがRailsの標準のディレクトリとして作成されています。これらのディレクトリの中身については、後で詳しく確認するとして、その他にディレクトリが存在しないかを確認しましょう。例えばservicesというディレクトリが存在していたら、そのプロジェクトではServiceクラスという独自のレイヤが存在する可能性がありますし、formsというディレクトリが存在していたら、そのプロジェクトではForm Objectという独自のレイヤが存在する可能性があります。

 いずれもmodelクラスやcontrollerクラスが混沌としないように、プロジェクトの誰かが導入したものですので、これらの独自のレイヤをどのようなケースで使うべきなのかについては、導入した人物がまだ在籍しているのであれば直接質問する。そうでなければ、既存のコードから、どのようなメソッドが定義されていて、どのようなケースで、どこから呼ばれるのか、については把握しておく必要があります。

 ServiceクラスもForm Objectも賛否両論あるものですが、プロジェクトの中できちんとルールが統一されているのであれば、導入自体はあまり問題ではありません。問題となるケースとしては責務が曖昧である、もしくは使用用途を逸脱した神クラスになっている場合や、導入が中途半端であるものは使っていて、あるものは使っていないなどの混在が存在する場合です。そのような可能性も含めて、プロジェクトとして使うべきなのか・使わないべきなのかの意思統一をしておく必要があります。

参考文献
Railsで重要なパターンpart1:Sercice Object(翻訳)
Rails: Form Objectと#to_modelを使ってバリデーションをモデルから分離する(翻訳)

lib以下

 lib以下は古くからGemにするまでもないようなプロジェクトの内部だけで完結するプライベートなファイルの置き場として利用されています。よく見る例としてはomniauthの独自strategyのファイルの置き場や、隠れオレオレライブラリ、オープンクラスで既存の振る舞いを書き換えたモジュールなどが配置されていることがあります。プロジェクトのファイルはapp以下に並ぶので、見落としがちなのですがlib以下にファイルがある場合は、そのファイルがどこでrequireされていて、何をしているのかは簡単に把握しておくとよいでしょう。コードが分からなくても、git log / git blameや、チケットから目的が理解できる場合もあります。

config/initializers以下

 config/initializers以下はRailsが起動するプロセスの中で呼び出されます。起動プロセスの詳細についてはRailsガイドのRailsの初期化プロセスに詳しく載っているので、そちらを参照してください。ここもlib以下と同様に、app以外でプロジェクトのファイルが配置される可能性があるため見落としがちなのですが、Gemの初期化処理や、lib以下に置いたファイルのrequire処理など、アプリケーションの重要な手掛かりとなるコードが置かれていることがあります。

spec(もしくはtest以下)

 参画しているプロジェクトにテストコードが十分に書かれていれば、キャッチアップを早める足がかりとなります。テストコードがどの程度書かれているかについては、rake statsで、本体コードとテストコードのコード行数の比率を見てみましょう。あくまで個人的な目安ですが、本体コードとテストコードが同程度書かれていれば、正常系程度の最低限のテストコードが用意されていると見込める。本体コードの2~3倍程度テストコードがあれば、十分な分量のテストコードが用意されていると判断しても良いでしょう。
 注意点としては過去にはテストコードが用意されていたが、メンテナンスする文化がなくなり、現在はテストコードはFailするものばかりで役に立っていないようなケースもあるため、まずはテストコードを手元で実行して、全部Passするかの確認はしておきましょう。大抵のRailsプロジェクトはbundle exec rake specbundle exec rspec spec/**/*_spec.rbで動くようになっていますが、特殊な初期化が必要なために起動は別のコマンドで行っていたり、巨大なプロジェクトではRSpecが多すぎるため、分割して実行しているような場合もあります。プロジェクトでどのようにテストコードを実行するかの手順は確認しておきましょう。

app以下を詳細に確認していく

 全体を軽く見通した後は、app以下の個別のファイルについて目を通していきます。
 個別のファイルについて詳細に目を通すのは実作業に取り掛かる段にして、先に確認しておくのは以下のようなことです。

app/controllers以下

  • application_controller.rbに定義されている共通のコールバック
  • application_controller.rbにMix-inされている各種モジュールの意味
  • app/controllers/concernsディレクトリ以下にあるモジュール群
  • controllers以下の各コントローラに定義されているコールバック
  • controllers以下の各コントローラにMix-inされているモジュール

app/models以下

  • application_record.rbに定義されている共通のコールバック
  • application_record.rbにMix-inされている各種モジュールの意味
  • app/models/concernsディレクトリ以下にあるモジュール群
  • models以下の各モデルに定義されているコールバック
  • models以下の各モデルにMix-inされているモジュール
  • models以下の各のhas_one / has_many / belongs_toの対応関係
  • 各種バリデーションの充足度

application_controller.rbやapplication_record.rbに定義されているコールバックはコントローラ・モデルに影響するため、個別のモデルだけを見ていても分からないような振る舞いをすることがあります。またRubyではMix-inと呼ばれる方法でinclude / extend / prepend等でモジュールが差し込まれることがあり、これがapplication_controller.rbやapplication_record.rbに入っていると、すべてのクラスで影響を受けるようになっています。

モジュールはGemによって利用可能なものだったり、concerns以下のディレクトリに配置されたものを呼び出したりするものが一般的なので、concernsディレクトリを見ておけば振る舞いを変えるようなモジュールを先に見つけておくことができるでしょう。

application_record.rbについては、Rails5以降で導入された概念なので、もしかするとActiveRecord::Base.include AwesomeModuleのような形でモジュールがincludeされているケースもあります。このような記述をしているときは、大抵イレギュラーな処理が挟み込まれているので、includeされているモジュールが何者なのかは把握しておけると良いです。

コールバックについてはRailsチュートリアルにもあるので、細かい説明は不要かと思いますが、より詳細に知りたい場合はRailsガイドのActiveRecordコールバックに目を通しておきましょう。

バリデーションについては、プロジェクトによっては全く書かれていなかったり、とても厳密に記述していたりと記述がまちまちなので、参画するプロジェクトがどの程度しっかりと書かれているのかを見ておきましょう。ベースとなる設計書や、バリデーションのガイドラインがプロジェクトとして用意されているのであれば、当然それに従うのが良いと思います。

app/views以下

 views以下については、コントローラとメソッドの対応が、ディレクトリとファイル名の関係になっていることが多いので、コントローラから追っていけば対応関係は大体把握できるので、特に留意点もないのですが、layoutsファイルにapplication.html.erb以外のファイルがある場合は、controller側からlayoutメソッドを呼び出している箇所を検索しておくとよいです。views以下で使われているテンプレートエンジンはプロジェクトによってerbだったり、slimだったり、hamlだったり、jbuilderだったり、rablだったりしますが、記述の仕方が違うだけで、最終的にはhtmlやjsonファイルに変換されるという点では本質的には同じことなので、記述方法は公式ドキュメントで学びましょう。

app/helpers以下

 ActionView内でレシーバなしで呼び出されているメソッドがあれば、Gemのメソッド、もしくはapp/helpers以下に定義されている可能性が高いです。application_helper.rbにまとめて書かれていて、他のモジュールは空という場合もあります。Railsではconfig.action_controller.include_all_helpersを明示的にfalseにしない限り、どこのhelperに記述しても結果は同じということもあり、どのhelperに書くかはプロジェクト次第なところがあります。まずはhelpers以下のファイルをざっと読んでみて、どういう指針で定義されているかは見ておきましょう。

app/assets以下、もしくはapp/javascript以下

 まだ多くのRailsプロジェクトはapp/assets以下で管理されていると思いますが、最近はWebpackerなどを導入するプロジェクトもあり、app/javascript以下にJavascriptが配置されていることもあります。ビルド時に何か問題が発生したときに、sprocketsなのかwebpackなのかは知っておく必要があります。

まとめ

 以上、Railsのプロジェクトに参画しておくときに見ておくとよいものを、簡単にまとめてみました。
 ここまで一通り読んでおくと、エンドポイントから機能をたどる時は、routes.rb → app/controllers/ → app/models → app/viewsと辿って、該当の機能を追うことができますし、新しい機能を追加するときも、周りの記述方針と合わせることができます。Railsに慣れないうちは、残念ながら目の前にあるコードが全てだと思うので、良し悪しを判断することは難しいと思いますが、まずは全体的に一貫性のある、統一感のあるコードが書けるようになることから始めましょう。

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

Rails | Devise: DoubleRenderError

Deviseをいじいじしたあと、サインアップを試みるとこんなエラーが...

AbstractController::DoubleRenderError in Users::SessionsController#create
Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".

解決方法

Devise: コントローラのオーバライドでredirect_toを使わない

  • 原因となった箇所
def after_sign_up_path_for
  redirect_to users_path
end
  • 修正
def after_sign_up_path_for
  users_path
end

理由

redirect_to users_path

# deviseの仕様上、上記の記述は以下と同義となってしまうから

redirect_to(redirect_to(users_path))

助けられた記事

Rails, Devise: DoubleRenderError in FailureApp

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

Ruby on RailsのアプリをGAEでデプロイする方法

1.まずはじめに

ポートフォリオを作成する上で少しオリジナル性を出したくてherokuではなくGCPを利用しようとしたのがきっかけになります。
その中で一番時間を取らなさそうなGAEでアプリをデプロイすることにしましたのでその際にやったことを記述しております。

2.準備したもの

2-1.rubyのバージョン

サーバーのrubyにバージョンとアプリのバージョンを揃えました。
こちらでバージョンアップについて書いてます

2-2.GitHubのレポジトリ

GitHubのレポジトリをクローンしますので必須になります。

2-3.app.yaml

ルートディレクトリーにapp.yamlファイルを作成しておきます。

app.yaml
entrypoint: bundle exec rackup --port $PORT
env: flex
runtime: ruby

3.GCP操作方法

3-1.GAE内での操作

GCPでApp Engineをクリックします。
GCPでGAEの選択

クリックすると下記の画面になるかと思います。
その中の赤枠でカッコってあるものをクリックします。(Cloud Shellが起動されます)

Cloud Shellの起動

3-2.GitHubのクローン

Cloud Shellが起動したらまず初めに自身のレポジトリをクローンします。

$ git clone https://github.com/Nash-BETA/test.git
Cloning into 'test'...
remote: Enumerating objects: 8394, done.
remote: Counting objects: 100% (8394/8394), done.
remote: Compressing objects: 100% (6611/6611), done.
remote: Total 8394 (delta 1502), reused 7935 (delta 1047), pack-reused 0
Receiving objects: 100% (8394/8394), 34.13 MiB | 9.60 MiB/s, done.
Resolving deltas: 100% (1502/1502), done.

3-4.GAEのgemのインストールおよびDBの作成

クローンが完了したらディレクトリを移動してアプリケーションコードを表示させます。

$ cd test
$ cat app.yaml

次にbundle installを行います。

$ bundle install

DBの作成をします。

$ rake db:migrate

3-5.サーバーの選択

GCPコマンドでアプリの作成およびサーバーの選択

$ gcloud app create
You are creating an app for project [First project].
WARNING: Creating an App Engine application for a project is irreversible and the region
cannot be changed. More information about regions is at
<https://cloud.google.com/appengine/docs/locations>.
Please choose the region where you want your App Engine application
located:
 [1] asia-east2    (supports standard and flexible)
 [2] asia-northeast1 (supports standard and flexible)
 [3] asia-northeast2 (supports standard and flexible)
 [4] asia-south1   (supports standard and flexible)
 [5] australia-southeast1 (supports standard and flexible)
 [6] europe-west   (supports standard and flexible)
 [7] europe-west2  (supports standard and flexible)
 [8] europe-west3  (supports standard and flexible)
 [9] europe-west6  (supports standard and flexible)
 [10] northamerica-northeast1 (supports standard and flexible)
 [11] southamerica-east1 (supports standard and flexible)
 [12] us-central    (supports standard and flexible)
 [13] us-east1      (supports standard and flexible)
 [14] us-east4      (supports standard and flexible)
 [15] us-west2      (supports standard and flexible)
 [16] cancel
Please enter your numeric choice:

14を選択。(特に理由がないのですがus-east4を選択しました)

3-5.app.yamlファイルにシークレットコードの追加

$ bundle exec rails secret
[シークレットキーが表示されます]
$ vim app.yaml
 entrypoint: bundle exec rackup --port $PORT
 env: flex
 runtime: ruby

vim上でoをタイピングして挿入モードにして、下記の様に追加する。

app.yaml
 entrypoint: bundle exec rackup --port $PORT
 env: flex
 runtime: ruby
 env_variables:
  SECRET_KEY_BASE: [シークレットキー]

escキーを押して:wqを入力すれば、変更内容の保存完了

3-6.デプロイして完了

$ gcloud app deploy
Updating service [default] (this may take several minutes)...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://Firstproject.appspot.com]

これで完了

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

Rails 画像アップロード機能の実装方法 メモ

初めに

railsで画像をアップロードする機能を実装するには、carrierwaveというgemを使う方法があります。
今回、画像を投稿する機能について学習したので、自分なりにまとめておこうと思います。

使用するgem

・Carrierwave

画像をアップロードするために必要な機能を追加するためのgem

※Carrierwaveの導入には「ImageMagick」がインストールされている必要があるので、
実装前に以下のコマンドを入力して置くといいかもです。

$ brew install imagemagick

・Minimagick

画像に対して、画像同士を合成したり、リサイズしたりと編集することができるようになるためのgem

実装

では実装に入っていきます。
1.まずは上記の2つのgemを扱えるようにするためGemfileに以下のように追記します。

Gemfile
gem 'carrierwave'
gem 'mini_magick' #画像に対して処理を行う場合

2.gemをGemfileに追記したのでターミナルで以下のコマンドを実行します。

$ bundle install

3. 次にcarrierwaveを利用するためのアップローダーを作成します。carrierwaveを導入したことで以下のコマンドが利用できるようになったので、ターミナルで実行しましょう。

$ rails g uploader image ※imageの箇所は任意の名前でOKです。例:pictureなど

するとapp/uploadersディレクトリ以下にimage_uploader.rbが作成されます。
このファイルに別途記述を行うことで、アップロードの仕方を設定できます。
※設定方法は下記5.以降を参照

4.アップローダーが作成できたので、画像アップロード機能を実装したいモデルに対して編集を行っていきます。

モデル名.rb
# app/models/モデル名.rb
  mount_uploader :image, ImageUploader

上記を追記することで、アップローダーを任意のモデルに対して
マウントすることができました。
※マウント
取り付ける、搭載するなどの意味

↑↑↑画像をアップロードする機能のみの場合はここまで↑↑↑

5.アップロードする画像に対して別途処理を行いたい場合は
以下の設定を行います。
3.で行ったrails g uploader imageで作成された
image_uploader.rbを確認し、
include CarrierWave::MiniMagickがコメントアウトされているので、これを以下のように解除しましょう。
コメントアウトを解除することでgem 'mini_magick'を使用することができるようになります。

app/uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base
  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  include CarrierWave::MiniMagick

これでmini_magickの機能が使用できるようになったので
例えば、image_uploader.rbの任意の箇所に以下のコードを追記すると、縦横比を維持したまま、width, heightを800pxにリサイズしアップロードすることができるようになります。

app/uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base
  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  include CarrierWave::MiniMagick
  # ~省略~
  process resize_to_fit: [800, 800]
end

おわりに

以上がgemのCarrierwaveとmini_magickを使用した画像のアップロード機能の実装になります。
画像の投稿はwebアプリケーションにおいて基本的な機能ですので抑えて置きたいと思います。

使用したgemのGithub

・Carrierwave
https://github.com/carrierwaveuploader/carrierwave
・mini_magick
https://github.com/minimagick/minimagick

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

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

はじめに

(多分)Rails 6 に追加された新機能を試す第71段。 今回は、 implicit_order_column 編です。
Rails 6 では、 firstlast で使われるソートのカラムを implicit_order_column で指定できるようになっています。
これは、id を UUID にしていた場合に、 firstlast が予測できる結果となるようにするためのようです。
(Rails 6.0.0 がリリースされましたが、確認当時は、 Rails 6.0.0.rc2 が最新でした。悪しからず :bow:)

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

$ rails --version
Rails 6.0.0.rc2

今回は、 User モデルを作成して rails console を使って確認します。

プロジェクトを作る

rails new rails_sandbox --database postgresql
cd rails_sandbox

User モデルを作る

bin/rails g model User name

id を UUID にする

id を UUID にするために、マイグレーションのファイルを変更します。

db/migrate/20190803221758_create_users.rb
class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto') #この行を追加
    create_table :users, id: :uuid do |t| # id: :uuid オプションを追加
      t.string :name

      t.timestamps
    end
  end
end

seed データを作成する

seed データを作成します。 name の昇順にデータを登録します。

db/seeds.rb
User.create(name: 'Andy')
User.create(name: 'Bob')
User.create(name: 'Cindy')

マイグレーションを実行し seed データを登録する

bin/rails db:create db:migrate db:seed

rails console で確認する

rails console で確認します。

User.first を実行してみます。

irb(main):001:0> User.first
  User Load (0.3ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
=> #<User id: "5da612ca-f055-4678-9c87-927b0a1a28d2", name: "Cindy", created_at: "2019-08-03 22:24:51", updated_at: "2019-08-03 22:24:51">

結果が Cindy になってしまいました。 (結果は必ず Cindy になるとは限りません。)
SQL を確認すると id でソートされていることもわかります。

User.last を実行してみます。

irb(main):002:0> User.last
  User Load (0.5ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1  [["LIMIT", 1]]
=> #<User id: "fe31d9a5-8de0-4d35-8a6f-183902f2884b", name: "Bob", created_at: "2019-08-03 22:24:51", updated_at: "2019-08-03 22:24:51">

結果が Bob になりました。
SQLを確認すると id の降順にソートした最初のレコードが検索されているのがわかります。

implicit_order_column を使う

User モデルを変更して implicit_order_column で :name を指定してみます。

app/models/user.rb
class User < ApplicationRecord
  self.implicit_order_column = :name
end

rails console で確認する

修正を反映させるため reload! します。

irb(main):003:0> reload!
Reloading...
=> true

User.first の結果は、 Andy になります。 name の昇順でソートされていることがわかります。

irb(main):004:0> User.first
  User Load (0.2ms)  SELECT "users".* FROM "users" ORDER BY "users"."name" ASC LIMIT $1  [["LIMIT", 1]]
=> #<User id: "cc7ff0b1-4dc3-47f4-a5cd-259a804c9690", name: "Andy", created_at: "2019-08-03 22:24:51", updated_at: "2019-08-03 22:24:51">

User.last の結果は、 Cindy になります。 name の降順でソートされていることがわかります。

irb(main):005:0> User.last
  User Load (0.6ms)  SELECT "users".* FROM "users" ORDER BY "users"."name" DESC LIMIT $1  [["LIMIT", 1]]
=> #<User id: "5da612ca-f055-4678-9c87-927b0a1a28d2", name: "Cindy", created_at: "2019-08-03 22:24:51", updated_at: "2019-08-03 22:24:51">

複数カラムの指定はできない

app/models/user.rb
  self.implicit_order_column = %i[name created_at]

とした場合は、 ActiveRecord::StatementInvalid エラーになりました。
まあ、そりゃそうですよね。 複数形 ( implicit_order_columns ) じゃないですもんね。

irb(main):011:0> User.first
Traceback (most recent call last):
        2: from (irb):11
        1: from (irb):11:in `rescue in irb_binding'
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR:  column users.[:name, :created_at] does not exist)
LINE 1: SELECT "users".* FROM "users" ORDER BY "users"."[:name, :cre...
                                               ^

試したソース

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

参考情報

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

bundle install時にnokogiriエラー(nokogiriのバージョン1.10.4)

Railsで新しいプロジェクトを作ろうと、
bundle install —path vendor/bundle
を実行した際に、nokogiriのインストールでエラーが発生しました。

環境

$ brew --version
Homebrew 2.1.10
Homebrew/homebrew-core (git revision ea340; last commit 2019-08-20)
$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin18]
$ gem --version
3.0.3

エラーメッセージ

エラーメッセージの中から原因と対処法が記された部分を抜粋します。

IMPORTANT NOTICE:

Building Nokogiri with a packaged version of libxslt-1.1.33
with the following patches applied:
    - 0001-Fix-security-framework-bypass.patch

Team Nokogiri will keep on doing their best to provide security
updates in a timely manner, but if this is a concern for you and want
to use the system library instead; abort this installation process and
reinstall nokogiri as follows:

    gem install nokogiri -- --use-system-libraries
        [--with-xml2-config=/path/to/xml2-config]
        [--with-xslt-config=/path/to/xslt-config]

If you are using Bundler, tell it to use the option:

    bundle config build.nokogiri --use-system-libraries
    bundle install

対処法

Nokogiriのインストールに必要なlibxml2ライブラリというものがNokogiriの中に同梱されているのですが、これをsystem内にあるlibmxl2を利用するように設定するとうまくいくとのことです。
今回はbundleを使ってインストールするので、下記のコマンドでうまくいきました。

bundle config build.nokogiri --use-system-libraries
bundle install —path vendor/bundle

まとめ

どうやら古より伝わるエラーらしく、様々な対処法がネットで報告されています。
再発率の高い厄介なエラーですね…

今回はエラーメッセージに対処法がそのまま載っていました。ググることも大事ですが、その前にエラーメッセージや公式リファレンスに目を通すことが大事ですね。

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

大学2年生のオリジナルWebアプリケーション開発7日目

今日の流れ

  1. Eventモデル
  2. Event一覧の表示
  3. dateの短縮日付表示

改めて僕の開発しているアプリをご紹介しますと、出欠管理アプリを開発しています。
そのきっかけはサークルの出欠管理が非効率だったので、せっかくだから自分で作ってみようというところから始まりました。

管理する出欠は何かしらのイベント(Event)なので、Eventモデルを構築することを考えました。

1. Eventモデル

Eventモデルには何が必要なのか考えた結果、次のようなカラムとそのデータ型を選択しました。

カラム名 データ型
user_id references
event_name string
date datetime
memo text

カラム以外は基本的にRailsチュートリアルのmicropostをeventに置き換えただけとなっています。

次にイベントモデルのバリデーションを考えました。
user_id, event_name, dateには存在の、event_nameとmemoには文字数制限のバリデーションを施しました。

そして、userとeventを関連付けるためにbelong_to/has_manyを利用しました。

models/event.rb
belongs_to :user
models/user.rb
has_many :events, dependent: :destroy

2. Event一覧の表示

次にユーザーのページにそのユーザーの企画したEvent(フォームから作成予定)を一覧表示させました。
この部分はRailsチュートリアル第13章の13.2にあたります。
micropostの部分をeventに変更させるだけでしたが、後述するdateの表示で手間取りました。

3. dateの短縮日付表示

今日最も時間がかかったのはこの部分です。Railsチュートリアルにはdatetimeや日付表示に関して記述はなかったので自分で模索しながら進めました。

Event一覧中のdateの表示は短縮日付(正確な呼び名は知りません)、yyyy/mm/dd(曜日)という形で表示したかったのです。

調べているとすぐにstrftimeというメソッドを用いればよいことがわかりました。
しかし、以下の多くの要因で混乱してなかなかうまく表示されませんでした。具体的には、yyyy/mm/dd(曜日)としたいのに、2019-08-026 17:52:54 +0900みたいな感じで表示されていました。
要因:

  • strftimeで作られるオブジェクトはStringクラスであること
  • ブロック付きメソッドを用いていた
  • 時間を表すクラスには、Timeクラス, Dateクラス, DateTimeクラスなど複数存在すること

一つ目の要因

まず、一つ目の要因への策として、一回dateカラムのデータ型をstringに変更して解決はできたのですが(その方法が気になる方はこちら)、後のフォームのことを考えてやはりdatetimeでいくことにしました。
最終的な結論は、viewで表示される2019-08-026 17:52:54 +0900をview側でyyyy/mm/dd(曜日)表示にしてしまうということです。これに気づいたことが大きかったです。

これらのサイトを参考にさせていただきました。
https://www.javadrive.jp/ruby/date_class/index5.html
https://techracho.bpsinc.jp/hachi8833/2016_10_06/25960

二つ目の要因

db/seeds.rb
40.times do |n|
end

の "n" が混乱させてきたのですが、まあこれはdb/seeds.rbの中でRailsチュートリアルと同様にしていれば平気でした。
とりあえず、rails cを多用して実験しまくりました!

三つ目の要因

こちらのサイトなどを拝見したところ、どうも時間を扱うクラスは奥が深くここではすべてを理解することはやめました。
試行錯誤していると、dateのデータ型はdatetimeですが、timeとかでもうまくRailsがやっていてくれている感じはしていました。
まあ、面倒くさいという理由からすべての時間は

to_datetime

でdatetimeに無理やり直しました。

最終的に

最終的には次のようにした結果、うまくいきました。

db/seeds.rb
users = User.order(:created_at).take(2)
40.times do |n|
  event_name = "練習#{n}"
  date = n.days.ago.to_datetime # datetimeに変更
  memo = "楽しみましょう"
  users.each { |user| user.events.create!(event_name: event_name,
                                          date:       date, #seed.rbでstrftimeを使うとstringとなるが、viewで(date)time?に変更されてしまう(2019-08-026 17:52:54 +0900の表示になる)
                                          memo:       memo) }
end
views/events/_event.html.erb
<% d = event.date %> <%# この時点ではdatetimeとして受け取っている %>
<% wd = ["日", "月", "火", "水", "木", "金", "土"] %>
<span class="date">日時:<%= d.strftime("%Y/%m/%d(#{wd[d.wday]})") %></span> <%# datetimeのdをここでstrftimeを用いてstringに変更している(yyyy/mm/dd(曜日)の表示になる) %>

終わりに

今日から本格的にオリジナルの部分を作り始めました。まぁRailsチュートリアルの第13章を参考にしているのですが。とにかく大変で時間がかかるのですが、その大変さを乗り越えるために必要なものとして今回気づいたのは

  • ググる力
  • rails c

が大事だということ。
ググる力はもちろんですが、Railsチュートリアルでなんとなく使っていたrails cがこんなに大事だとは思いませんでした。コンソールでの実験がいかに強力かわかりました。
明日はフォームの作成をしようと思います。個人的にはまだモデルや、モデルのカラムが足りないかなと思っていて、今後はEventにパスワード?やShareLinkモデルやAnswerモデルなんかが必要になるのかと思っていますので、モデルを考える時間がさらに必要だと考えています。

とりあえず今日はこの辺で。

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