20200323のRubyに関する記事は12件です。

ActionMailerでメール認証を実装した

バージョン

  • rails: ver 6.0.1
  • ruby: 2.6.5
  • テスト: rspec

はじめに

  1. ActionMailerを使ってメール認証を実装しました。

やりたいこと

  1. メール認証のリンクをクリックするimage.png

  2. メール送信完了画面(&メールが届く)image.png

  3. メールのリンクをクリックするimage.png

  4. メール認証完了image.png

この一連の流れを実装していきます

実装

メールを設定する

設定の部分はこの記事を参考にさせていただきました。本記事ではActionMailerにどのようにメール認証機能を持たせたか書きます。

rails g mailer NotificationMailer

でメーラーを作成します
ここにメールの送り先や、メールの件名を指定します

app/mailers/authenticate_mailer.rb
class AuthenticateMailer < ApplicationMailer
  def send_authenticate_mail(user)
    @user = user
    # メールを誰に送るか、メールの件名を設定
    mail to: user.email, subject: '[dot] メール認証'
  end
end

メーラーの名前.html.hamlにメールの本文を記述します
ここではauthenticate_mail.html.haml

app/views/authenticate_mail..htmlhaml
%p #{@user.name_sei}%p 以下のリンクをクリックしてメール認証を完了させてください。
-# トークンを含んだメールURL
%p= link_to 'メール認証完了する', authenticate_completed_url(@user.auth_token)

トークンを作成するためのカラムを作る

参考: Railsでトークン認証のログインAPI実装
ユーザーにトークンを持たせるためにカラムを追加してください

rails g migration add_token_to_users token:token

rails db:migrate

ユーザーモデルにhas_secure_passwordを書くことによってユーザーにトークンを持たせることができる

user.rb
class User < ApplicationRecord
  ...
  has_secure_token :auth_token
  ...
end

メールに認証機能をつける

ここからがメールに認証機能を持たせるための処理になります

app/controllers/authentications_controller.rb
class User::AuthenticationsController < User::ApplicationController
  # ログインしていないユーザーのアクセスを許可するかどうかはauthenticate_user!を使用してください
  # authenticate_user!はdeviseに標準で搭載されているので定義する必要はありません
  skip_before_action :authenticate_user!, only: [:authenticate_completed]

  # メールを送信しました
  def authenticate
    user = current_user
    # メールを送信するための処理
    AuthenticateMailer.send_authenticate_mail(user).deliver
  end

  # メール認証を完了しました
  def authenticate_completed
    # 認証メールのリンクからトークンを取得してそのトークンを持つユーザーを探す
    user = User.find_by(auth_token: params[:auth_token])
    if user
      # トークンを再生成する、古いトークンを新しく書き換える
      user.regenerate_auth_token
      # is_confirmed_atに日付がいれて認証が完了していることを表す。済んでいなければnil
      user.is_confirmed_at = Time.current
      user.save!
    else
      redirect_to root_path
    end
  end
end

適宜viewを作成してください
authenticate.html.haml,authenticate_completed

AuthenticateMailer.メーラーで作成したメソッドの名前(user).deliver
ここではAuthenticateMailer.send_authenticate_mail(user).deliver
で画面を表示すると同時にサーバーからメールを送信しています。

最後に

どんな簡単な質問でも大歓迎です!
(15分考えてわからなければ質問してください!)
※上から下までコピペしてできるものではありません。私の作ったサービス独特なものがあるのでカスタマイズしていただく必要があります。

間違っている点や、抜けがある、もっといいやり方があるなどもおしゃってください!

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

Railsにて検索機能を実装 (全体一致や部分一致などのselectボックスを入れ、複数モデルからの検索を可能にする)

備忘録として、なるべく自分の理解している範囲で、考え方の順序なども記載していく。

railsを触って、数週間なので、初心者の人のために

慣れている人や頭のいい人だと、そんなのわかってるよと言われることまで書いていきます。

間違っていたら教えてください。

devise でログイン機能は追加しており

UserモデルとBookモデルを作っています。

検索フォームをログインしている場合、全てのページに表示させたいので

app/views/layouts/application.html.erb のviewに記載していく。

<% if user_signed_in? %>

  <%= form_tag(search_path, method: :get) do %>

  <%= select_tag 'range' ,options_for_select([['---選択してください---', ''], ['User', '1'], ['Book', '2']]) %>

  <%= select_tag 'search', options_for_select([["前方一致","forward_match"], ["後方一致","backward_match"], ["完全一致","perfect_match"], ["部分一致","partial_match"]]) %>
  <%= text_field_tag (:word) %>
  <%= submit_tag "検索" %>

  <% end %>

<% end %>

全体を<%= form_tag(search_path, method: :get) do %>と<% end %>でくくって

searchアクションにparamsである'range', 'search',(:word)を飛ばす

<%= select_tag 'range' ,options_for_select([['---選択してください---', ''], ['User', '1'], ['Book', '2']]) %>

これの意味は['User', '1']この[]の中のUserを選んだ場合(ドロップダウンリスト)

’1’を'range'というparamsに入れて、コントローラに飛ばすために書いた。

<%= select_tag 'search', options_for_select([["前方一致","forward_match"], ["後方一致","backward_match"], ["完全一致","perfect_match"], ["部分一致","partial_match"]]) %>

これも同様にドロップダウンリストで選べるようにしているのですが

"前方一致"を選んだら"forward_match"を'search'というparamsに入れてsearchコントローラに飛ばすために書いた。

この2つのコードはselectボックスを作るために

select_tag 'paramsを決めてここに書く', options_for_select

params名はなんでも良い。

<%= text_field_tag (:word) %>

これは検索フォームに入力したワードをwordというparamsに入れて上と同様にsearchコントローラに飛ばすために書いた。

つまり3つのparamsをsearchコントローラに飛ばすコードを書いた。

2020-03-23 18.24のイメージ.jpg

こんな感じのができる。

続いてsearchesコントローラを書いていく

 
class SearchesController < ApplicationController

  def search
    @range = params[:range]
    search = params[:search]
    word = params[:word]

    if @range == '1'

    @user = User.search(search,word)

    else
    @book = Book.search(search,word)
  
    end

   end

viewページから飛ばした3つのparamsをそれぞれインスタンス変数かローカル変数の中に入れるために
@range = params[:range]
search = params[:search]
 word = params[:word]

を書いた。

searchとwordをローカル変数にしたのはmodelのメソッド(今回だとUserモデルとBookモデル)で利用するため、インスタンス変数じゃなくていいから。

    if @range == '1'

    @user = User.search(search,word)

    else
    @book = Book.search(search,word)

1をユーザーが選んだ場合、@userをviewページで反映させ、2を選んだら@bookを反映させる条件分岐。

ここで.search(search,word)という searchメソッドを定義しなければいけない。

これはモデルで定義していく。

モデルについて

以下がモデルに検索のメソッドを定義する部分です。

ドロップダウンリストでUserを選んだ場合、

Userモデルにsearchメソッドを書いて、それをsearchコントローラーで呼べるようにする。

app/models/user.rb

def self.search(search,word)
  if search == "forward_match"
   @user = User.where("name LIKE?","#{word}%")
  elsif search == "backward_match"
   @user = User.where("name LIKE?","%#{word}")
  elsif search == "perfect_match"
   @user = User.where("#{word}")
  elsif search == "partial_match"
   @user = User.where("name LIKE?","%#{word}%")
  else
   @user = User.all
end
end

この書き方はググって出てきたものを真似した。

注意点は自分で定義した(search,word)2つのparamsを書くこと

searchにはviewページに書いた"forward_match"や"backward_match"が入ってきている。

wordにはユーザーが検索フォームに入れたワードが入っている。

User.where("name LIKE?","#{word}%")

このコードはUserモデルから検索ワードにヒットしているかを確認するコードで

nameはUserテーブルのカラム名を記載する。

名前での検索だと思うのでnameにしてある

続いて、ユーザーBookを選んだ場合のメソッドをBookモデルに書いていく

def self.search(search, word)
    if search == "forward_match"
                    @book = Book.where("title LIKE?","#{word}%")
    elsif search == "backward_match"
                    @book = Book.where("title LIKE?","%#{word}")
    elsif search == "perfect_match"
                    @book = Book.where("#{word}")
    elsif search == "partial_match"
                    @book = Book.where("title LIKE?","%#{word}%")
    else
                    @book = Book.all
    end

end

Userモデルと同様の書き方にする

そして検索結果を表示するviewページを作成

今回はsearchesフォルダにsearch.index.html.erbを作成したので

routes.rbに

get 'search' => 'searches#search'

追記した。

続いて

search.index.html.erbには

<h2>Results index</h2>
    <!--books一覧 -->
    <table class="table table-hover table-inverse">
        <thead>
            <tr>
                <th></th>
                <th>Title</th>
                <th>Opinion</th>
                <th colspan="3"></th>
            </tr>
        </thead>

  <tbody>
    <% if @range == '2' %>
            <% @book.each do |book| %>
            <tr>
                <td>
                    <%= link_to(book.user) do %>
                    <%= attachment_image_tag(book.user, :profile_image, :fill, 50, 50, fallback: "no-image-mini.jpg") %>
                    <% end %>
                </td>
                <td><%= link_to book.title, book, class: "book_#{book.id}" %></td>
                <td><%= book.body %></td>
      </tr>
      <% end %>
  </tbody>
    <% else %>
      <thead>
        <tr>
          <th>image</th>
          <th>name</th>
          <th colspan="3"></th>
        </tr>
      </thead>

      <tbody>
        <% @user.each do |user| %>
        <tr>
          <td><%= attachment_image_tag(user, :profile_image, :fill, 50, 50, fallback: "no-image-mini.jpg") %></td>
          <td><%= user.name%></td>
          <td><%= link_to "Show", user, class: "user_#{user.id}" %></td>
        </tr>
        <% end %>
      </tbody>
         <% end %>
    </table>

終わりに
以上が検索機能の実装方法になります。
疑問、気になるところがございましたら、質問、コメントよろしくお願いします!!!

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

3/23 最終課題五日目

3/23
最終課題五日目

メモです

できている商品詳細ページをgithubに
出してコードレビューをする

コンフリクトが起こっていたので
修正した後にコードレビューをしてもらう

aタグはlink_toで書きましょう。  
と指摘を頂いたので
修正
修正後無事にLGTMともらう

その後マージしてマスターにプッシュする
そして今まで作ったとこに変なところが無いかや修正があるか探す

とりあえず簡単な修正をしてコミットし
マスターにプッシュしました

これで商品詳細ページ(マークアップ)が終わったので次に進む

次はサーバーサイドに進むが
みんなで話し合った結果
先に商品出品のサーバーサイドを進めることに決定
2人で進めることになりました
画像のところとその他で別れることに決定

Git hubでブランチを作った後さらに
ブランチを作ることで二人で進めれるようにする
(孫ブランチ?)
子ブランチの状態で新しいブランチを作る
そうするとマスターブランチの小ブランチを作るか
小ブランチの孫ブランチを作るかと選択が出るので
孫の方を選択
作る際のブランチの名前は小ブランチの名前と一緒もしくは似ているものは
エラーの原因になる

プルリクエスするときは気をつける
間違ってもマスターにプルリクエストを送ることはしないこと!!!

やること
* 商品モデルと商品の画像モデルを作成し、1つの投稿フォームで記事とそれに紐付く複数の画像を投稿できる
* 画像は送信前にプレビューを表示できる
* 上記要件を満たし、1度出品した商品の編集ができる
* 編集画面から、ひもづく画像の変更、削除、追加ができる
* 商品を削除する機能がある
* 商品を削除する際は、削除した商品に紐づく画像が同時に削除される

データベースを作らないとできないので
データベースを作る
Itemsテーブルを作る際外部キーが原因でエラー
Itemテーブルと関連づけされているテーブルを作っていく

調べたこと

Webスクレイピングとは、ウェブサイトのHTMLから必要なデータを取得する事を言い、
それを行うプログラムをスクレイパとも呼びます。

Ancestryとは
AncestryはRuby on RailsのActiveRecordモデルのレコードを
ツリー構造(階層)として編成することを可能にするGemです。
カテゴリーを作る際に必要になるgemです
https://qiita.com/Rubyist_SOTA/items/49383aa7f60c42141871
参考ページ

orderとは
取得した値に対して、条件を指定して並び替えることができる機能
並びの順番を変えることができる
降順にする場合は”DESC”、昇順は”ASC”
https://techacademy.jp/magazine/7727
参考ページ

カテゴリーにpathカラムがあったので
Pathカラムがわからなかったので調べた
https://kyabatalian.hatenablog.com/entry/2016/12/19/193430
参考ページ
path列に対してパターン比較すれば先祖を取得できます。
メソッドとかもあるので必要みたいです

マイグレーションをしてエラーが出たので
結局は原因はわからなかったけど
メモで
Gemfile
gem 'ancestry'

ターミナル
$ bundle install
$ rails g migration add_ancestry_to_category ancestry:string:index
$ rake db:migrate
ここでエラー
エラーの原因を探して解決方法を見る
テーブルがおかしなことになっているのでリセットする
$ rake db:migrate reset
またエラー
というのを繰り返した結果
途中でモデルに記述するとうまくいった
記述していなかったのが原因?
初歩ミスっぽいです

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

ActiveRecord で unscoped を呼ぶとその前のクエリが消える

ブログ記事からの転載です。

unscoped を使うことで default_scope を取り除くことができる

さて、皆さん大好き default_scope ですが、モデルで default_scope を定義すると次のように暗黙的にクエリが追加されます。

class User < ActiveRecord::Base
  default_scope { order(:updated_at) }
end

# 暗黙的に ORDER BY のクエリが追加れる
puts User.all.to_sql
# => SELECT "users".* FROM "users" ORDER BY "users"."updated_at" ASC
puts User.where(name: "Tom").to_sql
# => SELECT "users".* FROM "users" WHERE "users"."name" = 'Tom' ORDER BY "users"."updated_at" ASC

毎回 order(:updated_at) する必要がないので便利ですね。
でも『あ〜今日は default_scope のクエリ追加してほしくないな〜〜〜』って思うときがあると思うんですよ。
そういう時に unscoped を使うと default_scope のクエリを取り除く事ができます。

# unscoped を付けると default_scope はつかなくなる
puts User.unscoped.all.to_sql
# => SELECT "users".* FROM "users"
puts User.unscoped.where(name: "Tom").to_sql
# => SELECT "users".* FROM "users" WHERE "users"."name" = 'Tom'

これで default_scope をつかっていてもシュッと取り除く事ができて便利ですね!

default_scope 以外のクエリも取り除かれる

unscoped を使うことで default_scope を取り除く事ができるようになります。
しかし unscoped はめちゃくちゃ強くて『呼び出すよりも前のリレーション』も取り除いてしまします。

# unscoped よりも前に付けた where のクエリも消してしまう
puts User.where(name: "Tom").unscoped.to_sql
# => SELECT "users".* FROM "users"

unscopeddefault_scope を消したい場合は必ず『一番最初』に unscoped を呼び出しましょう。
逆に『レシーバのクエリを全部消したい』場合は unscoped を呼び出すと一括で消すことができるので便利です。

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

Herokuへのデプロイ手順|Rails + MySQL

はじめに

MySQLで作成したRailsアプリを、Herokuを使ってデプロイすることがあったので備忘録としてまとめました。同じ環境で初めてデプロイする方の参考になれば幸いです。

自分の環境

  • macOS 10.15.3
  • DB: MySQL
  • Rails 5.0.7

Railsアプリケーションの作成

・アプリ作成 [現在作成中]

・Gemfile修正

sqlite3が入っている場合は、以下のようにしてproduction環境下で反映されないようにしましょう。

Gemfile.rb
group :development, :test do
  gem 'sqlite3', '~> 1.4'
end

下記mysql2を追加。

Gemfile.rb
  gem 'mysql2'

・config/environments/production.rbの設定

Heroku上の本番環境でHTMLにCSSやJavaScriptが反映されるように、以下の記述を書き換えます。

ターミナル
# デフォルトのfalseをtrueにします。
config.assets.compile = true

作成したRailsアプリをGit管理

Railsプロジェクトに移動します。(ディレクトリへのパスは自身の物へ置き換えてください)

ターミナル
$ cd rails/MyApp

以下を実行すると、RailsアプリがGit管理されます。

ターミナル
# リポジトリを新規作成。
$ git init

# 変更があったすべてのファイルがaddされる。
$ git add .

# ファイルの変更や追加などを保存。
$ git commit -m "<ここにコミットメッセージをいれる>"

Herokuアカウントの登録

https://jp.heroku.com/ にて登録。

Heroku CLIのインストール

Heroku CLIをインストールすると、ターミナル上でHerokuのコマンド操作ができるようになります。
https://devcenter.heroku.com/articles/heroku-cli
上記サイト、もしくは下記コマンドでインストールできます。

ターミナル
brew tap heroku/brew && brew install heroku

以下のコマンドで、インストールができているか確認します。

ターミナル
$heroku -v
>>heroku/7.0.47 darwin-x64 node-v10.1.0

SSH公開鍵の作成、Herokuへ追加

・SSH公開鍵の作成

まずは公開鍵が作成されているか確認しましょう。

ターミナル
cat ~/.ssh/id_rsa.pub

作成されてない場合は以下のように表示されます。

ターミナル
No such file or directory

それでは公開鍵を作成していきましょう。(実行ディレクトリはどこでも大丈夫です)

ターミナル
$ ssh-keygen
Generating public/private rsa key pair

# 鍵の保存先を聞かれます。変更する必要はないので、そのままEnterキーで進みます。
Enter file in which to save the key (/Users/ユーザー名/.ssh/id_rsa):

# 鍵のパスワードを設定するか聞かれます。
# 設定しない場合はそのままEnterを、設定する場合はパスワードを入力します。
Enter passphrase (empty for no passphrase):

# パスワードの確認をされます。同じパスワードを入力してください。
# 設定していない場合は空欄のままEnterキーで進みます。
Enter same passphrase again:

これで公開鍵が作成されました。確認しましょう。

ターミナル
cat ~/.ssh/id_rsa.pub

#このように文字列が表示されるはずです。
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU
GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3
Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA
t3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/En
mZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbx
NrRFi9wrf+M7Q== schacon@mylaptop.local

・Herokuに公開鍵を追加

Herokuにログインします。

ターミナル
$ heroku login

以下のコマンドでSSH公開鍵がHerokuに追加されます。

ターミナル
$ heroku keys:add ~/.ssh/id_rsa.pub

追加されたか確認しておきましょう。

ターミナル
$ heroku keys
# SSHキーの一部が表示されます。
ssh-rsa AAAA...BBBB

作成したRailsアプリとHerokuの紐付け

Railsプロジェクトに移動します。(ディレクトリへのパスは自身の物へ置き換えてください)

ターミナル
$ cd rails/MyApp

Herokuへ新しいアプリケーションを作成しましょう。

ターミナル
$ heroku apps:create <好きなアプリ名>

以下のように表示されたらアプリ名が既に使われちゃってます。
重複は許されないので他のアプリ名へ変更しましょう。

ターミナル
Name myapp is already taken

HerokuにDBを追加

・MySQLを追加

以下のコマンドで、clearDBというMysqlを使うためのアドオンがigniteプランで追加されます。

ターミナル
$ heroku addons:create cleardb:ignite

以下のような表示が出たら、クレジットカードの登録が必要です。

ターミナル
▸    Please verify your account to install this add-on plan (please enter a credit card) For more
▸    information, see https://devcenter.heroku.com/categories/billing Verify now at
▸    https://heroku.com/verify

*アドオンを追加するためにクレジットの登録が必要ですが、iginteプランは無料で使えます。

詳しいプラン内容については以下を参照。
https://elements.heroku.com/addons/cleardb

・Herokuアプリに環境変数を設定

以下のコマンドで、環境変数に入る値を取得します。

ターミナル
$ heroku config
=== <アプリの名前> Config Vars
CLEARDB_DATABASE_URL: mysql://<ユーザー名>:<パスワード>@<ホスト名>/<データベース名>?reconnect=true

Herokuアプリの環境変数に、上記で取得した値をそれぞれ設定。

ターミナル
$ heroku config:add DB_NAME='<データベース名>'

$ heroku config:add DB_USERNAME='<ユーザー名>'

$ heroku config:add DB_PASSWORD='<パスワード>'

$ heroku config:add DB_HOSTNAME='<ホスト名>'

$ heroku config:add DB_PORT='3306'

# gemで「mysql2」を使用しているので、mysql://ではなく「mysql2://」とします。
$ heroku config:add DATABASE_URL='mysql2://<ユーザー名>:<パスワード>@<ホスト名>/<データベース名>?reconnect=true'

設定した値を確認しましょう。
以下のように表示されるはずです。

$ heroku config

=== <アプリの名前> Config Vars
CLEARDB_DATABASE_URL: mysql://<ユーザー名>:<パスワード>@<ホスト名>/<データベース名>?reconnect=true
DATABASE_URL:         mysql2://<ユーザー名>:<パスワード>@<ホスト名>/<データベース名>?reconnect=true
DB_HOSTNAME:          <ホスト名>
DB_NAME:              <データベース名>
DB_PASSWORD:          <パスワード>
DB_PORT:              3306
DB_USERNAME:          <ユーザー名>

Herokuにデプロイ

以下コマンドでローカルリポジトリをHerokuへpushすると、自動でデプロイが進んでいきます。

ターミナル
$ git push heroku master

*master以外のブランチをpushする場合は以下を実行

ターミナル
$ git push heroku <ブランチ名>:master

最後に以下のコマンドを入力して、データベースのマイグレーションをします。

ターミナル
$ heroku rake db:migrate

以下のコマンドを実行すると、ブラウザでアプリケーションにアクセスできます。

ターミナル
$ heroku open

お疲れ様でした!

参考サイト・記事

mysqlを使ったRailsアプリをHerokuにデプロイする流れ
Deploying with Git
SSH 公開鍵の作成

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

ツイッター風Railsアプリをテストする(統合テスト編)

この記事の基本的な方針

今回は別記事、ツイッター風Railsアプリ最短復習(忙しい人の流し読みで開発シリーズ)で作ったアプリの統合テストをします。

手を動かしながら読みたいようでしたら、以下でこのツイッター風Railsアプリを手に入れてください。

Terminal
$ git clone https://github.com/annaPanda8170/cheaptweet.git
$ bundle install
$ bundle exec rake db:create
$ bundle exec rake db:migrate

基本解説はしません。手順のみ示します。

想定する読み手

既に一度Railsアプリをチュートリアルやスクール等で作ったことがある方を想定しております。
Mac使用で、パソコンの環境構築は完了していることが前提です。

具体的な手順

完成品GitHub

①準備

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

group :development do
#省略
  gem 'spring-commands-rspec'
end
#省略

※spring-commands-rspecは起動時間を速くするためのものでなくても問題はありません。

Terminal
$ bundle install
$ rails g rspec:install
$ rails g rspec:feature cheapTweet
$ rails g factory_bot:model user
$ rails g factory_bot:model tweet
$ bundle exec spring binstub rspec

control + c
$ rails s
.rspec
--format documentation

※これでRspecの出力が読みやすくなるそうです。

spec/rails_helper.rb
#省略
require 'capybara/rspec'
include Warden::Test::Helpers

ここで空っぽのまま一度起動してみます。

Terminal
$ bundle exec rspec
Output
CheapTweets
  add some scenarios (or delete) /Users/handaryouhei/Desktop/cheaptweet/spec/features/cheap_tweet_spec.rb (PENDING: Not yet implemented)

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) CheapTweets add some scenarios (or delete) /Users/handaryouhei/Desktop/cheaptweet/spec/features/cheap_tweet_spec.rb
     # Not yet implemented
     # ./spec/features/cheap_tweet_spec.rb:4


Finished in 0.0014 seconds (files took 2.21 seconds to load)
1 example, 0 failures, 1 pending
spec/factories/tweets.rb
FactoryBot.define do
  factory :tweet do
    text {"hello"}
    association :user
  end
end
spec/factories/users.rb
FactoryBot.define do
  factory :user do
    sequence(:nickname) { |n| "annaPanda#{n}" }
    sequence(:email) { |n| "a#{n}@a" }
    password {"111111"}
    password_confirmation {"111111"}
  end
end

②テスト構築

spec/features/cheap_tweet_spec.rb
require 'rails_helper'

RSpec.feature "CheapTweets", type: :feature do
  scenario "新規登録するとログアウトが表示されているTOP画面に遷移する" do
    visit root_path
    click_link "会員登録"
    fill_in "user_nickname", with: "annaPanda"
    fill_in "user_email", with: "a@a"
    fill_in "user_password", with: "111111"
    fill_in "user_password_confirmation", with: "111111"
    click_button "Sign up"
    expect(page).to have_content 'ログアウト'
  end
  scenario "ログインするとログアウトが表示されているTOP画面に遷移する" do
    user = FactoryBot.create(:user)
    visit root_path
    click_link "ログイン"
    fill_in "user_email", with: "a1@a"
    fill_in "user_password", with: "111111"
    click_button "Log in"
    expect(page).to have_content 'ログアウト'
  end
  scenario "非ログイン状態でTOP画面に遷移するとログアウトが表示されていない" do
    visit root_path
    expect(page).not_to have_content 'ログアウト'
  end
  scenario "投稿したらデータベースにtweetが一つ増える" do
    user = FactoryBot.create(:user)
    login_as(user, scope: :user)
    visit root_path
    expect{
      click_link "投稿"
      fill_in "tweet_text", with: "こんにちは"
      click_button "投稿"
    }.to  change( Tweet.all, :count ).by(1)
  end
  scenario "自分で投稿したtweetをshowして編集するとindexに反映される" do
    user = FactoryBot.create(:user)
    login_as(user, scope: :user)
    visit root_path
    click_link "投稿"
    fill_in "tweet_text", with: "こんにちは"
    click_button "投稿"
    click_link "こんにちは"
    click_link '編集'
    fill_in "tweet_text", with: "こんにちは!"
    click_button '編集'
    visit root_path
    expect(page).to have_content 'こんにちは!'
  end
  scenario "自分以外が投稿したtweetをshowすれば編集ボタンが表示されない" do
    tweet = FactoryBot.create(:tweet)
    visit root_path
    click_link "a"
    expect(page).not_to have_content '編集'
  end
  scenario "自分で投稿したtweetをshowして削除ボタンを押せばデータベースのtweetが一つ減る" do
    user = FactoryBot.create(:user)
    login_as(user, scope: :user)
    visit root_path
    click_link "投稿"
    fill_in "tweet_text", with: "こんばんは"
    click_button "投稿"
    click_link "こんばんは"
    expect{
      click_link "削除"
    }.to  change( Tweet.all, :count ).by(-1)
  end
  scenario "自分以外が投稿したtweetをshowすれば削除ボタンが表示されない" do
    tweet = FactoryBot.create(:tweet)
    visit root_path
    click_link "hello"
    expect(page).not_to have_content '削除'
  end
  scenario "ヘッダーの自分の名前をクリックすると自分の投稿一覧が表示される" do
    tweet = FactoryBot.create(:tweet)
    user = FactoryBot.create(:user)
    login_as(user, scope: :user)
    visit root_path
    click_link "投稿"
    fill_in "tweet_text", with: "おはよう"
    click_button "投稿"
    click_link "投稿"
    fill_in "tweet_text", with: "さようなら"
    click_button "投稿"
    within 'header' do
      click_link user.nickname
    end
    expect(page).to have_content 'おはよう'
    expect(page).to have_content 'さようなら'
    expect(page).not_to have_content 'hello'
  end
  scenario "TOP画面の投稿者をクリックするとそのユーザーの一覧が表示される" do
    tweet = FactoryBot.create(:tweet)
    user = FactoryBot.create(:user)
    login_as(user, scope: :user)
    visit root_path
    click_link "投稿"
    fill_in "tweet_text", with: "おはよう"
    click_button "投稿"
    click_link "投稿"
    fill_in "tweet_text", with: "さようなら"
    click_button "投稿"
    click_link tweet.user.nickname
    expect(page).not_to have_content 'おはよう'
    expect(page).not_to have_content 'さようなら'
    expect(page).to have_content 'hello'
  end
  scenario "コメントすると表示される" do
    tweet = FactoryBot.create(:tweet)
    user = FactoryBot.create(:user)
    login_as(user, scope: :user)
    visit root_path
    click_link "hello"
    fill_in "comment_text", with: "ハロー"
    click_button "コメント"
    expect(page).to have_content 'ハロー'
  end
end

click_linkclick_buttonで指定しているのはテキストです。
fill_inで指定しているのはidです。
have_contentで表示されているかを確認しています。

Terminal
$ bundle exec rspec
Output
CheapTweets
  新規登録するとログアウトが表示されているTOP画面に遷移する
  ログインするとログアウトが表示されているTOP画面に遷移する
  非ログイン状態でTOP画面に遷移するとログアウトが表示されていない
  投稿したらデータベースにtweetが一つ増える
  自分で投稿したtweetをshowして編集するとindexに反映される
  自分以外が投稿したtweetをshowすれば編集ボタンが表示されない
  自分で投稿したtweetをshowして削除ボタンを押せばデータベースのtweetが一つ減る
  自分以外が投稿したtweetをshowすれば削除ボタンが表示されない
  ヘッダーの自分の名前をクリックすると自分の投稿一覧が表示される
  TOP画面の投稿者をクリックするとそのユーザーの一覧が表示される
  コメントすると表示される

Finished in 0.87206 seconds (files took 2.18 seconds to load)
11 examples, 0 failures

まとめ

読むだけなら割と直感的にいけると思います。
この統合テストさえ済ませていれば、コントローラは不要なのでは?という考えもあるようです。
モデルのテストとこの統合テストを行うことがRailsアプリのテストのスタンダードになるかもしれませんね。

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

Kinx 実現技術 - Switch-Case

Switch-Case

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。作ったものの紹介だけではなく実現のために使った技術を紹介していくのも貢献。その道の人には当たり前でも、そうでない人にも興味をもって貰えるかもしれない。

前回のテーマは構文解析。今回のテーマは Switch-Case。

Switch-Case

なぜ Switch-Case を取り上げるかというと、やはり ジャンプテーブル の魅力があるから。基本多分岐なので、複数の条件に対して一発でジャンプできるのが期待値だろう。ただ、そのために結構複雑な処理をしている。

整数値・それ以外

まず、数値(整数)かそれ以外かで動作が異なる。整数値以外は基本 if-else にするしかないのだが、整数値は可能ならジャンプテーブルにしようと試みる。なので、まずは整数値かどうか確認し、どちらかに分岐させる。

整数値の場合

整数値の場合、ジャンプ・テーブル化を試みる。ただし、下限と上限の差がありすぎる場合、無駄なジャンプが増えすぎてしまうという問題がある。そこで、閾値を設定して複数のブロックに分割する。例えば以下のケースをパースした場合、

switch (a) {
case  10: break;
case  11: break;
case   1: break;
case   2: break;
case   3: break;
case   4: break;
case   5: break;
case   6: break;
case  51: break;
case  52: break;
case  53: break;
case  54: break;
case 100: break;
case 'aaa': break;
default: break;
}

1~100 までのジャンプテーブルを作ってしまうと 13 個だけが有効で、残りの 87 個は default にジャンプするだけの無駄が多いテーブルを作ってしまう。アドレス(ポインタ)サイズが 64bit の場合、100 個のジャンプを用意するだけで 800 バイト必要になる。800 バイトが多いかどうかは別にして、無駄なスペースが沢山あることに違いはない。

そこで、まず数値自体をソートして順に確認していき、閾値(デフォルトは 16)以上のインターバルがあった場合、別々のグループ(ブロック)に分けて、それぞれでジャンプテーブル化させるようにする。どのブロックに分岐するかは二分探索で選択する形でコード出力する(場合によっては線形探索)。

ちなみにブロック内の選択肢が少ない場合はあえてジャンプテーブル化はしない。線形探索や二分探索の方が効率が良い場合がある。なぜなら、ジャンプテーブルの場合、上限・下限値を越えないか比較し、評価値から下限値を減算してその上でジャンプさせることになるので都合3回は比較が入る。例えば比較値が 1 種類の場合、単に一発比較するだけの方が効率が良い。したがって、これにもブロック内の要素数を考慮してどの方式を使うかを決定する。

ちなみに、今後触れようと思うが VM コードの実行で Switch-Case を採用する場合のペナルティ(ダイレクト・スレッディングの有効性)は一般的には CPU パイプラインの投機実行ミスを指摘されるが、この比較回数もあるんじゃないかと思う。何せ 1 命令実行するために最低 3 回は比較と分岐が入る。明らかに上限・下限を超えないと判断できない限り、範囲外の条件判断は必要だしね。この辺は投機実行でカバーできているのかもしれないが。

上記の場合、1~11 のブロック、51~54 のブロック、100 だけのブロックに分かれる。評価値(a)の値によって二分探索(または線形探索)でどのグループを探索するかを決め、それぞれのグループの中で実際のジャンプ先を決定する。

それ以外

それ以外の場合、単に線形探索でジャンプ先を決定する。つまり if-else の連続で値をチェックする。

出力例

具体的な出力コードは以下のような感じ。break しかしてないので結局最後に jmp しているだけで、本気で最適化したらこのコード自体出力されないよなものだが現時点ではサーチのエッセンスは出力されている。

  .L460
     d04:   enter                   7, vars(1), args(1)
  .L462
     d06:   pushvl0                 $0(0)
     d07:   dup
     d08:   typeof                  is integer
     d09:   jz                      .L463(d1d)
     d0a:   dup
     d0b:   lti                     1
     d0c:   jnz                     .L464(d22)
     d0d:   dup
     d0e:   gti                     11
     d0f:   jnz                     .L464(d22)
     d10:   subi                    1
     d11:   jmptbl
     d12:   jmp                     .L482(d47)
     d13:   jmp                     .L482(d47)
     d14:   jmp                     .L482(d47)
     d15:   jmp                     .L482(d47)
     d16:   jmp                     .L482(d47)
     d17:   jmp                     .L482(d47)
     d18:   jmp                     .L463(d1d)
     d19:   jmp                     .L463(d1d)
     d1a:   jmp                     .L463(d1d)
     d1b:   jmp                     .L482(d47)
     d1c:   jmp                     .L482(d47)
  .L463
     d1d:   dup
     d1e:   pushs                   "aaa"
     d1f:   eqeq
     d20:   jnz                     .L482(d47)
     d21:   jmp                     .L482(d47)
  .L464
     d22:   dup
     d23:   lti                     51
     d24:   jnz                     .L465(d32)
     d25:   dup
     d26:   gti                     54
     d27:   jnz                     .L465(d32)
     d28:   dup
     d29:   eqeqi                   51
     d2a:   jnz                     .L482(d47)
     d2b:   dup
     d2c:   eqeqi                   52
     d2d:   jnz                     .L482(d47)
     d2e:   dup
     d2f:   eqeqi                   53
     d30:   jnz                     .L482(d47)
     d31:   jmp                     .L482(d47)
  .L465
     d32:   dup
     d33:   neqi                    100
     d34:   jnz                     .L463(d1d)
     d35:   jmp                     .L482(d47)
  .L466
    (省略)
  .L482
     d47:   ret                     null
     d48:   halt

出力コードを見るとまだまだ改善点はあるものの、初版としては十分かなー、と。

Switch-Case の魅力はやはり ジャンプテーブル と言っても過言ではない。それが無ければ Switch-Case を使う意味は間違いなく半減するよね。それができることを期待して Switch-Case を選ぶということも多いですし。例えば、Yacc で出力された構文解析器なんかは Switch-Case の塊なので、全ての比較が if-else で行われていたら正直パフォーマンス的にやってられないレベルで遅くなってしまう。つまり、Switch-Case はジャンプテーブル化しないと使い物にならないので頑張った。

おわりに

ここまで読んでいただいてありがとうございます。最後はいつもの以下の定型フォーマットです。

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

headerとflashの間にできた謎の余白を消す(Devise, Bootstrap)

はじめに

先日投稿したこちら
Bootstrap + jQueryを使ってFlashなどの
<button>xボタン</button>をクリックした時にFlashを閉じる機能を実装しました。
ただ、ブラウザ場で確認すると、
Flashが消える前、後、双方で との間に余白がないようにぴったり合わしているにも関わらず
<header>flashの間に余白が発生していました。

この余白を無くすことができたので紹介します。

開発環境

  • bootstrap-sass (3.3.7)
  • devise (4.7.1)
  • jquery-rails (4.3.1)
  • rails (5.2.4.1)

前置き (layoutのデザイン)

header.html
<header class= "header-layout navbar navbar-fixed-top navbar-inverse">
  • Bootstrapのnavbar-fixed-topを使ってheaderを固定しております。
  • headerの下の<%= yield%>には画面いっぱいの背景画像を設置した表紙のようになっております。
  • headerの下にHome画面レイアウトの背景画像がぴったりになるように<body>padding: top;でしっかり合わせてあります。)

  • (余白ができていた時のパターン↓)

application.html.erb
<body>
  <%= render 'shared/header'%>
  <%= notice %> //ここ
  <%= alert %> //ここ
<body>

*おそらく、ユーザーがログイン後にflash[:notice]が呼び出された場合、flash[:alert]は、余白としてflash[:norice]の上に表示されていました。

if構文(余白消えない。)

まずこちらを参考にしてみた
https://whatsupguys.net/programming-school-dive-into-code-learning-47/]

app/views/shared/_flash_messages.html.erb
<% if notice %>
  //条件
<% elsif alert %>
 //条件
<% end %>

上記のように条件を組んでブラウザ場の検証を確認しても

ブラウザ場の検証(要素)
<header> </header>
  "       "   //謎の余白
<class= "alert">
  "ログインしました" //flash[:notice]
</>

flashは表示できているけれど<header>flashが呼ばれる<class= "alert">の間に" "と表示され、余白の原因になっています。
おそらく" "の部分は表示されなかったflash[alert]だと思います。
if構文を使って条件を書いても、、余分な余白は表示されたままでした。

解決方法

解決方法を調べてみました。
まず、deviseとBootstrapを組み合わせる祭、Bootstrapには、
deviseのkeyである[:notice],[:alert]に対応したクラスがないので、keyを置き換えることが最善です。

こちらを参考に
(https://hachy.github.io/2019/10/15/flashes-with-devise-and-bootstrap.html)

app/helpers/users_helper.rb
module UsersHelper
#Bootstrapに対応できるdevise flash[notice][alert]を変更する
  def bootstrap_alert(key)
    case key
      when "alert"
       "warning"
      when "notice"
        "success"
      when "error"
       "danger"
    end
  end
end

:alert:warning,:notice:successに変更する為にヘルパーメソッドを作成しています。(devise userモデルを使っているのでそのヘルパーを利用しています。)

Flashの変更

変更前↓ (deviseのデフォルト)

application.html.erb
<body>
  <%= render 'shared/header'%>
  <%= notice %> //ここ
  <%= alert %>  //ここ
<body>

変更後↓

app/views/shared/_flash_messages.html.erb
<% flash.each do |key, value| %>
  <div class="alert alert-<%= bootstrap_alert(key)%> close-flash">
    <%= value %>
    <button type="button" class="close close-button" data-dismiss="alert">
      &times;
    </button>
  </div>
<% end %>
  • deviseのデフォルトflashを変更し、Bootstrapに組み合わせれるように作ったヘルパーメソッドbootstrap_alert(key)を使ってdevise flash[:key]を変更。

これでFlash[:key]がしっかりBotstrapにも適用でき、余分な" "が呼び出されることなく、特定のflash[:key]だけが呼び出され余白も消えました。

参考にしたサイト

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

Devise <%= notice %> <%= alert %>による余白をなくす方法

はじめに

先日投稿したこちら
Bootstrap + jQueryを使ってFlashなどの
<button>xボタン</button>をクリックした時にFlashを閉じる機能を実装しました。
ただ、ブラウザ場で確認すると、
Flashが消える前、後、双方で
<header><%= yield%>の間に余白が発生していました。

この余白を無くす方法を紹介します。

開発環境

  • bootstrap-sass (3.3.7)
  • devise (4.7.1)
  • jquery-rails (4.3.1)
  • rails (5.2.4.1)

前置き (layoutのデザイン)

私の場合<header>

header.html
<header class= "header-layout navbar navbar-fixed-top navbar-inverse">
  • Bootstrapのnavbar-fixed-topを使ってheaderを固定しております。
  • headerの下の<%= yield%>には画面いっぱいの背景画像を設置した表紙のようになっております。

  • (余白ができていた時のパターン↓)

application.html.erb
<body>
  <%= render 'shared/header'%>
  <%= notice %> //ここ
  <%= alert %> //ここ
<body>

ユーザーがログイン後に、flash[:notice]が呼び出された場合、呼び出されなかったflash[:alert]は、余白としてflash[:norice]の上に表示されていました。

if構文(余白消えない。)

まずこちらを参考にしてみた
https://whatsupguys.net/programming-school-dive-into-code-learning-47/]

app/views/shared/_flash_messages.html.erb
<% if notice %>
  //条件
<% elsif alert %>
 //条件
<% end %>

上記のように条件を組んでブラウザ場の検証を確認しても

ブラウザ場の検証(要素)
<header> </header>
  "       "   //謎の余白
<class= "alert">
  "ログインしました" //flash[:notice]
</>

flashは表示できているけれど<header>とflashが呼ばれる<class= "alert">の間に" "と表示され、余白の原因になっております。
おそらく" "の部分は表示されなかったflash[alert]だと思います。
if構文を使って条件を書いても、、余分な余白は表示されたままでした。

解決方法

解決方法を調べてみました。
まず、deviseとBootstrapを組み合わせる祭、Bootstrapには、
deviseのkeyである[:notice],[:alert]に対応したクラスがないので、keyを置き換えることが最善です。

こちらを参考に
(https://hachy.github.io/2019/10/15/flashes-with-devise-and-bootstrap.html)

app/helpers/users_helper.rb
module UsersHelper
#Bootstrapに対応できるdevise flash[notice][alert]を変更する
  def bootstrap_alert(key)
    case key
      when "alert"
       "warning"
      when "notice"
        "success"
      when "error"
       "danger"
    end
  end
end

:alert:warning,:notice:successに変更する為にヘルパーメソッドを作成しています。(devise userモデルを使っているのでそのヘルパーを利用しています。)

Flashの変更

変更前↓ (deviseのデフォルト)

application.html.erb
<body>
  <%= render 'shared/header'%>
  <%= notice %> //
  <%= alert %>  //
<body>

変更後↓

app/views/shared/_flash_messages.html.erb
<% flash.each do |key, value| %>
  <div class="alert alert-<%= bootstrap_alert(key)%> close-flash">
    <%= value %>
    <button type="button" class="close close-button" data-dismiss="alert">
      &times;
    </button>
  </div>
<% end %>
  • deviseのデフォルトflashを変更し、Bootstrapに組み合わせれるように作ったヘルパーメソッドbootstrap_alert(key)を使ってdevise flash[:key]を変更。

これでFlash[:key]がしっかりBotstrapにも適用でき、余分な" "が呼び出されることなく、特定のflash[:key]だけが呼び出され余白も消えました。

参考にしたサイト

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

Devise <%= notice %> <%= alert %>による謎の余白をなくす方法

はじめに

先日投稿したこちら
Bootstrap + jQueryを使ってFlashなどの
<button>xボタン</button>をクリックした時にFlashを閉じる機能を実装しました。
ただ、ブラウザ場で確認すると、
Flashが消える前、後、双方で
<header><%= yield%>の間に余白が発生していました。

この余白を無くす方法を紹介します。

開発環境

  • bootstrap-sass (3.3.7)
  • devise (4.7.1)
  • jquery-rails (4.3.1)
  • rails (5.2.4.1)

前置き (layoutのデザイン)

私の場合<header>

header.html
<header class= "header-layout navbar navbar-fixed-top navbar-inverse">
  • Bootstrapのnavbar-fixed-topを使ってheaderを固定しております。
  • headerの下の<%= yield%>には画面いっぱいの背景画像を設置した表紙のようになっております。

  • (余白ができていた時のパターン↓)

application.html.erb
<body>
  <%= render 'shared/header'%>
  <%= notice %> //ここ
  <%= alert %> //ここ
<body>

ユーザーがログイン後に、flash[:notice]が呼び出された場合、呼び出されなかったflash[:alert]は、余白としてflash[:norice]の上に表示されていました。

if構文(余白消えない。)

まずこちらを参考にしてみた
https://whatsupguys.net/programming-school-dive-into-code-learning-47/]

app/views/shared/_flash_messages.html.erb
<% if notice %>
  //条件
<% elsif alert %>
 //条件
<% end %>

上記のように条件を組んでブラウザ場の検証を確認しても

ブラウザ場の検証(要素)
<header> </header>
  "       "   //謎の余白
<class= "alert">
  "ログインしました" //flash[:notice]
</>

flashは表示できているけれど<header>とflashが呼ばれる<class= "alert">の間に" "と表示され、余白の原因になっております。
おそらく" "の部分は表示されなかったflash[alert]だと思います。
if構文を使って条件を書いても、、余分な余白は表示されたままでした。

解決方法

解決方法を調べてみました。
まず、deviseとBootstrapを組み合わせる祭、Bootstrapには、
deviseのkeyである[:notice],[:alert]に対応したクラスがないので、keyを置き換えることが最善です。

こちらを参考に
(https://hachy.github.io/2019/10/15/flashes-with-devise-and-bootstrap.html)

app/helpers/users_helper.rb
module UsersHelper
#Bootstrapに対応できるdevise flash[notice][alert]を変更する
  def bootstrap_alert(key)
    case key
      when "alert"
       "warning"
      when "notice"
        "success"
      when "error"
       "danger"
    end
  end
end

:alert:warning,:notice:successに変更する為にヘルパーメソッドを作成しています。(devise userモデルを使っているのでそのヘルパーを利用しています。)

Flashの変更

変更前↓ (deviseのデフォルト)

application.html.erb
<body>
  <%= render 'shared/header'%>
  <%= notice %> //
  <%= alert %>  //
<body>

変更後↓

app/views/shared/_flash_messages.html.erb
<% flash.each do |key, value| %>
  <div class="alert alert-<%= bootstrap_alert(key)%> close-flash">
    <%= value %>
    <button type="button" class="close close-button" data-dismiss="alert">
      &times;
    </button>
  </div>
<% end %>
  • deviseのデフォルトflashを変更し、Bootstrapに組み合わせれるように作ったヘルパーメソッドbootstrap_alert(key)を使ってdevise flash[:key]を変更。

これでFlash[:key]がしっかりBotstrapにも適用でき、余分な" "が呼び出されることなく、特定のflash[:key]だけが呼び出され余白も消えました。

参考にしたサイト

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

重要なgemのうち、開発が止まってしまったgemがいくつあるか集計してみた

スクリーンショット 2020-03-23 16.00.28.png

core-jsがメンテされていない理由を読みました。この記事によると、core-jsというJavaScript界隈の重要な(しかし一般の開発者にはほとんど知られていない)ライブラリのメンテが止まってしまったとのこと。

JavaScript界隈の特殊な事情があるにせよ、Rubyにも、開発が止まりそうなgemはたくさんある気がします。そこで、重要なgemのうち、開発が止まりそうなものがいくつあるのかを調べてみました。

先に結論

重要なgemのうち、開発が既に止まっているものは0件でした。「利用者数に対して開発者が極端に少ない」という意味で止まる可能性が比較的高いものは minitest gemでした。

補足:minitest gemについては、主なコミッタの人数が少ないだけです。今も活発にコミットされています。今すぐ何かがあるわけではまったくありません。

やったこと

重要なgemのうち、開発が止まっている、もしくは止まる可能性があるものをリストアップしてみました。

「重要なgem」の定義

本記事では、Rails gemから依存関係をたどったときに、二次のつながりまでに含まれるgemの最新バージョンから依存されているgemを「重要なgem」とします。

もっと言うと、下記のgemから依存されているgemであれば、それは重要なgemです。

  • 一次のつながり
    • sprockets-rails
    • railties
    • activesupport
    • activestorage
    • activerecord
    • activemodel
    • activejob
    • actionview
    • actiontext
    • actionpack
    • actionmailer
    • actionmailbox
    • actioncable
  • 二次のつながり
    • websocket-driver
    • mail
    • rails-dom-testing
    • rack-test
    • globalid
    • marcel
    • concurrent-ruby
    • method_source

探索の開始点としてRailsを選んだ理由は、簡易的な集計の結果、もっとも一般に利用されているgemがRailsだったからです。

上記の条件を満たすgemであっても、開発が活発なgemからのスピンオフgemであったり、止まっているというよりは安定しているgemについては除外しています。

集計に使ったデータの出典

当たりを付けるための簡易的な集計には、Libraries.io Open Dataを利用しました。このデータの最終更新は2020年1月12日なので、さらに最新データの裏取りとしてGitHubのデータを利用しています。

集計結果

最終更新に着目した集計

最終更新が半年以上前のgemのうち、本当に開発が止まりそうなgemは0件でした。

最終更新から時間が経っているgemはいくつかあるものの、いずれも、活発なgemからのスピンオフであったり、ドキュメントの更新くらいしか必要なかったりという状況です。

最終候補に残ったgemは下記の通りです。

+------------------+----------------------+------------------------+-------------------------------+----------------------------------+-----------------------------------------------------+
| Project Name     | Dependency Name      | Repository Stars Count | Repository Contributors Count | Repository Last pushed Timestamp | left(b.`Repository URL`, 100)                       |
+------------------+----------------------+------------------------+-------------------------------+----------------------------------+-----------------------------------------------------+
| actioncable      | websocket-driver     | 171                    | 13                            | 2019-09-10 12:55:43 UTC          | https://github.com/faye/websocket-driver-ruby       |
| actionmailbox    | mail                 | 2976                   | 184                           | 2018-10-13 20:15:01 UTC          | https://github.com/mikel/mail                       |
| actionmailer     | mail                 | 2976                   | 184                           | 2018-10-13 20:15:01 UTC          | https://github.com/mikel/mail                       |
| actionmailer     | rails-dom-testing    | 75                     | 15                            | 2018-07-04 12:29:01 UTC          | https://github.com/rails/rails-dom-testing          |
| actionpack       | rack-test            | 770                    | 57                            | 2018-07-22 10:43:43 UTC          | https://github.com/rack-test/rack-test              |
| actionpack       | rails-dom-testing    | 75                     | 15                            | 2018-07-04 12:29:01 UTC          | https://github.com/rails/rails-dom-testing          |
| actionview       | rails-dom-testing    | 75                     | 15                            | 2018-07-04 12:29:01 UTC          | https://github.com/rails/rails-dom-testing          |
| activejob        | globalid             | 650                    | 43                            | 2019-01-11 13:58:34 UTC          | https://github.com/rails/globalid                   |
| activestorage    | marcel               | 56                     | 6                             | 2018-09-15 03:06:56 UTC          | https://github.com/basecamp/marcel                  |
| activesupport    | concurrent-ruby      | 4493                   | 103                           | 2019-03-11 09:52:28 UTC          | https://github.com/ruby-concurrency/concurrent-ruby |
| concurrent-ruby  | ref                  | 60                     | 6                             | 2017-11-18 10:41:28 UTC          | https://github.com/ruby-concurrency/ref             |
| mail             | mini_mime            | 38                     | 12                            | 2019-07-08 09:42:28 UTC          | https://github.com/discourse/mini_mime              |
| marcel           | mimemagic            | 176                    | 16                            | 2018-12-20 08:49:51 UTC          | https://github.com/minad/mimemagic                  |
| railties         | rake                 | 1627                   | 164                           | 2019-09-09 07:15:18 UTC          | https://github.com/ruby/rake                        |
| railties         | method_source        | 244                    | 16                            | 2019-02-05 15:25:47 UTC          | https://github.com/banister/method_source           |
| websocket-driver | websocket-extensions | 10                     | 4                             | 2017-11-11 01:27:15 UTC          | https://github.com/faye/websocket-extensions-ruby   |
+------------------+----------------------+------------------------+-------------------------------+----------------------------------+-----------------------------------------------------+

開発者数に着目した集計

開発者数が一桁のgemのうち、本当に開発が止まりそうなgemは0件でした。

こちらも基本的には0件です。minitestのみ、開発者が2人しかいなくてピンチに見えますが、GitHubのリポジトリを見る限りプルリクを自分で書き直してコミットしているだけのようです。プルリク作成者まで含めた貢献者は数多くいます。

ただ一方で、この2人のうち主なコミッターは実質1人のようなので、この人物に何かあると、minitestの開発は止まってしまうかもしれません。(何もないことを祈ります)

最終候補に残ったgemは下記の通りです。

+------------------+----------------------+------------------------+-------------------------------+----------------------------------+---------------------------------------------------+
| Project Name     | Dependency Name      | Repository Stars Count | Repository Contributors Count | Repository Last pushed Timestamp | left(b.`Repository URL`, 100)                     |
+------------------+----------------------+------------------------+-------------------------------+----------------------------------+---------------------------------------------------+
| actionview       | erubi                | 238                    | 9                             | 2019-09-25 16:09:05 UTC          | https://github.com/jeremyevans/erubi              |
| activestorage    | marcel               | 56                     | 6                             | 2018-09-15 03:06:56 UTC          | https://github.com/basecamp/marcel                |
| activesupport    | minitest             | 2703                   | 2                             | 2020-01-12 00:16:05 UTC          | https://github.com/seattlerb/minitest             |
| concurrent-ruby  | ref                  | 60                     | 6                             | 2017-11-18 10:41:28 UTC          | https://github.com/ruby-concurrency/ref           |
| websocket-driver | websocket-extensions | 10                     | 4                             | 2017-11-11 01:27:15 UTC          | https://github.com/faye/websocket-extensions-ruby |
+------------------+----------------------+------------------------+-------------------------------+----------------------------------+---------------------------------------------------+

結論

重要なgemのうち、既に開発が止まったものは0件、開発が止まりそうなgemはminitest gemという結果でした。

補足:minitest gemについては、主なコミッタの人数が少ないだけです。今も活発にコミットされています。今すぐ何かがあるわけではまったくありません。

感想

Railsから二次のつながりまでに含まれるgemが思ったよりもだいぶ少なかったのが印象的でした。過去のバージョンからの変遷を見る限り、Railsを始めとしたたくさんのgemは、開発が進むにつれ出来るだけ依存関係を減らしていこうと努力しているようです。ライブラリとしては非常に健全な姿勢であり、今後もこの傾向が続くと良いなと思います。

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

PHP サイト でも Capistrano (Ruby Gem)

環境

  • Ruby 2.6.5

設定手順

Capistrano 設定ファイルの用意

bundle add capistrano
bundle exec cap install

これで必要なファイルが作成されます。

あとは deploy.rb, config/deploy/* を変更して設定完了です。

ファイルを置けば使えるPHPサイトなら、認証設定程度を記述すればOKです。

設定ファイル例

staging.rb/production.rb
# server configuration
role :web, %w{ec2-user@123.123.123.123}
# file location
set :deploy_to, "/var/www/xxx"
deploy.rb
# application name
set :application, "xxxxxx"
# repository
set :repo_url, "sample@sample.git.jp:/sample.git"

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

# revision to be kept
set :keep_releases, 2

デプロイ手順

# config/deploy/staging.rb を使ったデプロイ
bundle exec cap staging deploy
# config/deploy/production.rb を使ったデプロイ
bundle exec cap production deploy

ロールバック手順

cap deploy:rollback

このほかにも cap deploy:rollback:code などがある。

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